-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit b181d0d
Showing
7 changed files
with
438 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
/venv | ||
.env |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
import os | ||
|
||
from flask import Flask, request, redirect, jsonify, flash | ||
from werkzeug.utils import secure_filename | ||
from flask_debugtoolbar import DebugToolbarExtension | ||
import PIL.Image | ||
from werkzeug.exceptions import NotFound, BadRequest | ||
from flask_cors import CORS, cross_origin | ||
|
||
from models import db, connect_db, Image | ||
from s3 import S3 | ||
|
||
# if using api key | ||
from dotenv import load_dotenv | ||
load_dotenv() | ||
AWS_BUCKET_URL = "https://sarahgraup-pixly.s3.us-west-1.amazonaws.com" | ||
|
||
app = Flask(__name__) | ||
cors = CORS(app) | ||
|
||
app.config['SECRET_KEY'] = "secret" | ||
|
||
app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get( | ||
"DATABASE_URL", 'postgresql:///pixly') # must link to db | ||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False | ||
app.config['SQLALCHEMY_ECHO'] = True # always true to see sql in terminal | ||
app.config['CORS_HEADERS'] = 'Content-Type' | ||
|
||
connect_db(app) | ||
|
||
debug = DebugToolbarExtension(app) | ||
|
||
#TODO: add examples to doctsrings | ||
|
||
@app.get("/images") | ||
def get_images(): | ||
"""get request to access aws images and returns images, | ||
optional search term | ||
returns array of images with url: {images: [{img_data, url},...]} | ||
""" | ||
|
||
search = request.args.get('search_term') | ||
if not search: | ||
images = Image.get_images_optional_search() | ||
else: | ||
images = Image.get_images_optional_search(search_term=search) | ||
|
||
images_with_urls = [] | ||
for image in images: | ||
print(f"image path {image.path}") | ||
url = S3.get_preassigned_url(image.path) | ||
print(f"url {url}") | ||
serialize = image.serialize() | ||
|
||
images_with_urls.append({"image_data":serialize, "url":url}) | ||
return jsonify(images=images_with_urls) | ||
|
||
#TODO: have errors return something with actual error message | ||
@app.post("/images/upload") | ||
def handle_image_upload(): | ||
""" | ||
Adds new photo to db and aws | ||
returns images with url {images: [{img_data, url}]} | ||
""" | ||
print("inside /upload") | ||
print(f"request= {request.files}") | ||
|
||
if 'file' not in request.files: | ||
error_msg = "No 'file' found" | ||
return jsonify(error=error_msg) | ||
|
||
file = request.files['file'] | ||
print(f"handle_image_upload file= {file}") | ||
caption = request.form['caption'] | ||
|
||
if file.filename == '': | ||
error_msg = "No 'filename' found" | ||
return jsonify(error=error_msg) | ||
|
||
if file: | ||
file_name = secure_filename(file.filename) | ||
image = None | ||
try: | ||
image = Image.add_image_data(file=file, path=file_name, caption=caption) | ||
except( NotFound, BadRequest): | ||
error_msg = "Could not upload." | ||
return jsonify(error=error_msg) | ||
|
||
# print(image) | ||
try: | ||
S3.upload_file(file_name=file, save_as_name=file_name) | ||
except( NotFound, BadRequest): | ||
error_msg = "Could not upload to aws." | ||
return jsonify(error=error_msg) | ||
|
||
url = S3.get_preassigned_url(image.path) | ||
serialize = image.serialize() | ||
|
||
#returns database entry record | ||
return jsonify(images=[{"image_data": serialize, "url":url}]) | ||
|
||
@app.get("/images/<int:id>") | ||
def get_image_by_id(id): | ||
""" | ||
get request to access aws image by id and returns image w url | ||
output: {images: [{img_data, url}]} | ||
""" | ||
print("inside get image by id") | ||
try: | ||
image = Image.get_image_data(id=id) | ||
except( NotFound, BadRequest): | ||
error_msg = "Could not find image." | ||
return jsonify(error=error_msg) | ||
|
||
url = S3.get_preassigned_url(image.path) | ||
serialize = image.serialize() | ||
|
||
return jsonify(images=[{"image_data":serialize, "url":url}]) | ||
|
||
# # add to db | ||
# # use id from db as filename | ||
# # add to aws | ||
# file_name = secure_filename(file.filename) | ||
# save_as_name = f"{id}.jpeg" | ||
# S3.upload_file(file_name=file_name, save_as_name=save_as_name) |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
from flask_sqlalchemy import SQLAlchemy | ||
from datetime import datetime | ||
import PIL.Image | ||
from PIL.ExifTags import TAGS | ||
|
||
db = SQLAlchemy() | ||
|
||
IMG_EXIF_TAGS = ["GPSLatitude", | ||
"GPSLongitude", | ||
"DateTimeOriginal", | ||
"Make", | ||
"Model"] | ||
|
||
|
||
def connect_db(app): | ||
"""Connect to database.""" | ||
|
||
app.app_context().push() | ||
db.app = app | ||
db.init_app(app) | ||
|
||
|
||
class Image (db.Model): | ||
"""Creates an image instance""" | ||
|
||
__tablename__ = "images" | ||
|
||
id = db.Column( | ||
db.Integer, | ||
primary_key=True, | ||
autoincrement=True | ||
) | ||
|
||
date_time_uploaded = db.Column( | ||
db.DateTime, | ||
) | ||
date_time_created = db.Column( | ||
db.String, | ||
) | ||
|
||
gps_latitude = db.Column( | ||
db.String, | ||
) | ||
|
||
gps_longitude = db.Column( | ||
db.String, | ||
) | ||
|
||
make = db.Column( | ||
db.String, | ||
) | ||
|
||
model = db.Column( | ||
db.String, | ||
) | ||
|
||
path = db.Column( | ||
db.String, | ||
nullable=False | ||
) | ||
|
||
caption = db.Column( | ||
db.String, | ||
nullable=False | ||
) | ||
|
||
def serialize(self): | ||
"""Serialize instance""" | ||
return { | ||
"id": self.id, | ||
"date_time_uploaded": self.date_time_uploaded, | ||
"date_time_created": self.date_time_created, | ||
"gps_latitude": self.gps_latitude, | ||
"gps_longitude": self.gps_longitude, | ||
"make": self.make, | ||
"model": self.model, | ||
"path": self.path, | ||
"caption": self.caption, | ||
|
||
} | ||
|
||
@classmethod | ||
def add_image_data( | ||
cls, | ||
path, | ||
file, | ||
caption): | ||
""" | ||
uploads image properties to db | ||
Input: | ||
- path: aws key path | ||
- file: filestorage obj, .jpeg | ||
- caption: caption for img | ||
Calls cls.get_img_exif_data | ||
""" | ||
# change to add or save image data | ||
|
||
print(f"add_img_data file {file}") | ||
# grab exif data from image | ||
exif_data = cls.get_img_exif_data(file=file) | ||
# exif variables of interes for db | ||
gps_latitude = gps_longitude = date_time_created = make = model = None | ||
if "GPSLatitude" in exif_data: | ||
gps_latitude = exif_data["GPSLatitude"] | ||
if "GPSLongitude" in exif_data: | ||
gps_longitude = exif_data["GPSLongitude"] | ||
if "DateTimeOriginal" in exif_data: | ||
date_time_created = exif_data["DateTimeOriginal"] | ||
if "Make" in exif_data: | ||
make = exif_data["Make"] | ||
if "Model" in exif_data: | ||
model = exif_data["Model"] | ||
|
||
date_time_uploaded = datetime.utcnow() | ||
print(f"utc now {date_time_uploaded}") | ||
|
||
image = Image(path=path, | ||
caption=caption, | ||
date_time_uploaded=date_time_uploaded, | ||
date_time_created=date_time_created, | ||
gps_latitude=gps_latitude, | ||
gps_longitude=gps_longitude, | ||
make=make, | ||
model=model) | ||
db.session.add(image) | ||
db.session.commit() | ||
return image | ||
|
||
@classmethod | ||
def get_image_data(cls, id): # fetch | ||
"""downloads image properties from db or returns 404""" | ||
print("download_image ran") | ||
|
||
image = cls.query.get_or_404(id) | ||
return image | ||
|
||
@classmethod | ||
def get_images_optional_search(cls, search_term=None): | ||
""" | ||
downloads images, where caption includes matching search_term, from db | ||
""" | ||
# print("inside get_images_by_search") | ||
if not search_term: | ||
# print("inside NOT search") | ||
images = cls.query.all() | ||
return images | ||
else: | ||
# BUG: sql injections? | ||
# flicker for exif data | ||
print("inside searchterm") | ||
images = (cls.query | ||
.filter(cls.caption.ilike(f"%{search_term}%")) | ||
.order_by(cls.date_time_uploaded) | ||
.all()) | ||
# print(images) | ||
return images | ||
|
||
@classmethod | ||
def get_img_exif_data(cls, file): | ||
"""Get exif data from img""" | ||
# open image | ||
img = PIL.Image.open(file) | ||
exif_data = img._getexif() | ||
# print(f"exif_data={exif_data}") | ||
# print(f"IMGGGGGGG {img.tell()}") | ||
|
||
# img.close() | ||
|
||
img_tag_exif_data = {} | ||
for key_tag in exif_data: | ||
tag = TAGS[key_tag] | ||
if tag in IMG_EXIF_TAGS: | ||
img_tag_exif_data[tag] = exif_data[key_tag] | ||
return img_tag_exif_data |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
appnope==0.1.3 | ||
asttokens==2.2.1 | ||
backcall==0.2.0 | ||
blinker==1.6.2 | ||
boto3==1.26.125 | ||
botocore==1.29.125 | ||
click==8.1.3 | ||
decorator==5.1.1 | ||
executing==1.2.0 | ||
Flask==2.2.3 | ||
Flask-Cors==3.0.10 | ||
Flask-DebugToolbar==0.13.1 | ||
Flask-SQLAlchemy==3.0.3 | ||
Flask-WTF==1.1.1 | ||
greenlet==2.0.2 | ||
importlib-metadata==6.6.0 | ||
ipython==8.11.0 | ||
itsdangerous==2.1.2 | ||
jedi==0.18.2 | ||
Jinja2==3.1.2 | ||
jmespath==1.0.1 | ||
MarkupSafe==2.1.2 | ||
matplotlib-inline==0.1.6 | ||
parso==0.8.3 | ||
pexpect==4.8.0 | ||
pickleshare==0.7.5 | ||
Pillow==9.5.0 | ||
prompt-toolkit==3.0.38 | ||
psycopg2-binary==2.9.6 | ||
ptyprocess==0.7.0 | ||
pure-eval==0.2.2 | ||
Pygments==2.14.0 | ||
python-dateutil==2.8.2 | ||
python-dotenv==1.0.0 | ||
s3transfer==0.6.0 | ||
six==1.16.0 | ||
SQLAlchemy==2.0.12 | ||
stack-data==0.6.2 | ||
traitlets==5.9.0 | ||
typing_extensions==4.5.0 | ||
urllib3==1.26.15 | ||
wcwidth==0.2.6 | ||
Werkzeug==2.2.3 | ||
WTForms==3.0.1 | ||
zipp==3.15.0 |
Oops, something went wrong.