Skip to content

Commit

Permalink
initial commit after sprint
Browse files Browse the repository at this point in the history
  • Loading branch information
m-romberg committed May 5, 2023
0 parents commit b181d0d
Show file tree
Hide file tree
Showing 7 changed files with 438 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/venv
.env
125 changes: 125 additions & 0 deletions app.py
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 added errors.py
Empty file.
174 changes: 174 additions & 0 deletions models.py
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
45 changes: 45 additions & 0 deletions requirements.txt
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
Loading

0 comments on commit b181d0d

Please sign in to comment.