-
Notifications
You must be signed in to change notification settings - Fork 79
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Restructure utils.py
to its own package
#915
Merged
Merged
Changes from 16 commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
7bc69c5
Move utils to its own package
michplunkett aa629af
Move constants to own file
michplunkett ba67866
Update references
michplunkett 28f9a1c
Update models.py
michplunkett f67e9c9
pre-check
michplunkett 03d035b
Update constants.py
michplunkett 53c9e5c
References
michplunkett abc28a8
Fixed pre-checkn
michplunkett 65a910e
Use relative paths
michplunkett f4577bc
Update Makefile
michplunkett 1c0aaec
Fix @patch string
michplunkett eb1d0df
Update forms.py
michplunkett c6d0471
Update test_utils.py
michplunkett e59db30
Use absolute path to avoid error
michplunkett 56fe1ef
Use absolute imports
michplunkett 756c2b5
Update 59e9993c169c_change_faces_to_thumbnails.py
michplunkett 7a72b0a
util -> utils
michplunkett 42e3af7
More util -> utils
michplunkett fedf57f
Comment and pathway things
michplunkett a291e91
Update general.py
michplunkett 6a90c0f
Update 59e9993c169c_change_faces_to_thumbnails.py
michplunkett b1ee101
Revert "Update 59e9993c169c_change_faces_to_thumbnails.py"
michplunkett befcfff
Update .gitignore
michplunkett 7824d7f
Update 59e9993c169c_change_faces_to_thumbnails.py
michplunkett 1ec0659
Merge branch 'develop' into util_restructure
michplunkett c7666fc
Merge branch 'develop' into util_restructure
abandoned-prototype File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
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
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
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
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
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
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
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 |
---|---|---|
|
@@ -12,11 +12,11 @@ | |
from werkzeug.security import check_password_hash, generate_password_hash | ||
|
||
from . import login_manager | ||
from .util.constants import ENCODING_UTF_8 | ||
from .validators import state_validator, url_validator | ||
|
||
|
||
db = SQLAlchemy() | ||
model_encoding = "utf-8" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could remove this now that the constant isn't a circular reference. |
||
jwt = JsonWebToken("HS512") | ||
|
||
BaseModel = db.Model # type: DefaultMeta | ||
|
@@ -552,7 +552,7 @@ def verify_password(self, password): | |
|
||
def generate_confirmation_token(self, expiration=3600): | ||
payload = {"confirm": self.id} | ||
return self._jwt_encode(payload, expiration).decode(model_encoding) | ||
return self._jwt_encode(payload, expiration).decode(ENCODING_UTF_8) | ||
|
||
def confirm(self, token): | ||
try: | ||
|
@@ -572,7 +572,7 @@ def confirm(self, token): | |
|
||
def generate_reset_token(self, expiration=3600): | ||
payload = {"reset": self.id} | ||
return self._jwt_encode(payload, expiration).decode(model_encoding) | ||
return self._jwt_encode(payload, expiration).decode(ENCODING_UTF_8) | ||
|
||
def reset_password(self, token, new_password): | ||
try: | ||
|
@@ -588,7 +588,7 @@ def reset_password(self, token, new_password): | |
|
||
def generate_email_change_token(self, new_email, expiration=3600): | ||
payload = {"change_email": self.id, "new_email": new_email} | ||
return self._jwt_encode(payload, expiration).decode(model_encoding) | ||
return self._jwt_encode(payload, expiration).decode(ENCODING_UTF_8) | ||
|
||
def change_email(self, token): | ||
try: | ||
|
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,154 @@ | ||
import datetime | ||
import hashlib | ||
import imghdr as imghdr | ||
import os | ||
import sys | ||
from io import BytesIO | ||
from traceback import format_exc | ||
from urllib.request import urlopen | ||
|
||
import boto3 | ||
import botocore | ||
from botocore.exceptions import ClientError | ||
from flask import current_app | ||
from flask_login import current_user | ||
from PIL import Image as Pimage | ||
from PIL.PngImagePlugin import PngImageFile | ||
|
||
from ..models import Image, db | ||
|
||
|
||
def compute_hash(data_to_hash): | ||
return hashlib.sha256(data_to_hash).hexdigest() | ||
|
||
|
||
def crop_image(image, crop_data=None, department_id=None): | ||
"""Crops an image to given dimensions and shrinks it to fit within a configured | ||
bounding box if the cropped image is still too big. | ||
""" | ||
# Cropped officer face image size | ||
THUMBNAIL_SIZE = 1000, 1000 | ||
|
||
if "http" in image.filepath: | ||
with urlopen(image.filepath) as response: | ||
image_buf = BytesIO(response.read()) | ||
else: | ||
image_buf = open(os.path.abspath(current_app.root_path) + image.filepath, "rb") | ||
|
||
pimage = Pimage.open(image_buf) | ||
|
||
if ( | ||
not crop_data | ||
and pimage.size[0] < THUMBNAIL_SIZE[0] | ||
and pimage.size[1] < THUMBNAIL_SIZE[1] | ||
): | ||
return image | ||
|
||
# Crops image to face and resizes to bounding box if still too big | ||
if crop_data: | ||
pimage = pimage.crop(crop_data) | ||
if pimage.size[0] > THUMBNAIL_SIZE[0] or pimage.size[1] > THUMBNAIL_SIZE[1]: | ||
pimage.thumbnail(THUMBNAIL_SIZE) | ||
|
||
# JPEG doesn't support alpha channel, convert to RGB | ||
if pimage.mode in ("RGBA", "P"): | ||
pimage = pimage.convert("RGB") | ||
|
||
# Save preview image as JPEG to save bandwidth for mobile users | ||
cropped_image_buf = BytesIO() | ||
pimage.save(cropped_image_buf, "jpeg", quality=95, optimize=True, progressive=True) | ||
|
||
return upload_image_to_s3_and_store_in_db( | ||
cropped_image_buf, current_user.get_id(), department_id | ||
) | ||
|
||
|
||
def find_date_taken(pimage): | ||
if isinstance(pimage, PngImageFile): | ||
return None | ||
|
||
exif = hasattr(pimage, "_getexif") and pimage._getexif() | ||
if exif: | ||
# 36867 in the exif tags holds the date and the original image was taken | ||
# https://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html | ||
if 36867 in exif: | ||
return exif[36867] | ||
else: | ||
return None | ||
|
||
|
||
def upload_obj_to_s3(file_obj, dest_filename): | ||
s3_client = boto3.client("s3") | ||
|
||
# Folder to store files in on S3 is first two chars of dest_filename | ||
s3_folder = dest_filename[0:2] | ||
s3_filename = dest_filename[2:] | ||
file_ending = imghdr.what(None, h=file_obj.read()) | ||
file_obj.seek(0) | ||
s3_content_type = "image/%s" % file_ending | ||
s3_path = "{}/{}".format(s3_folder, s3_filename) | ||
s3_client.upload_fileobj( | ||
file_obj, | ||
current_app.config["S3_BUCKET_NAME"], | ||
s3_path, | ||
ExtraArgs={"ContentType": s3_content_type, "ACL": "public-read"}, | ||
) | ||
|
||
config = s3_client._client_config | ||
config.signature_version = botocore.UNSIGNED | ||
url = boto3.resource("s3", config=config).meta.client.generate_presigned_url( | ||
"get_object", | ||
Params={"Bucket": current_app.config["S3_BUCKET_NAME"], "Key": s3_path}, | ||
) | ||
|
||
return url | ||
|
||
|
||
def upload_image_to_s3_and_store_in_db(image_buf, user_id, department_id=None): | ||
""" | ||
Just a quick explaination of the order of operations here... | ||
we have to scrub the image before we do anything else like hash it | ||
but we also have to get the date for the image before we scrub it. | ||
""" | ||
image_buf.seek(0) | ||
image_type = imghdr.what(image_buf) | ||
if image_type not in current_app.config["ALLOWED_EXTENSIONS"]: | ||
raise ValueError("Attempted to pass invalid data type: {}".format(image_type)) | ||
image_buf.seek(0) | ||
pimage = Pimage.open(image_buf) | ||
date_taken = find_date_taken(pimage) | ||
if date_taken: | ||
date_taken = datetime.datetime.strptime(date_taken, "%Y:%m:%d %H:%M:%S") | ||
pimage.getexif().clear() | ||
scrubbed_image_buf = BytesIO() | ||
pimage.save(scrubbed_image_buf, image_type) | ||
pimage.close() | ||
scrubbed_image_buf.seek(0) | ||
image_data = scrubbed_image_buf.read() | ||
hash_img = compute_hash(image_data) | ||
existing_image = Image.query.filter_by(hash_img=hash_img).first() | ||
if existing_image: | ||
return existing_image | ||
try: | ||
new_filename = "{}.{}".format(hash_img, image_type) | ||
scrubbed_image_buf.seek(0) | ||
url = upload_obj_to_s3(scrubbed_image_buf, new_filename) | ||
new_image = Image( | ||
filepath=url, | ||
hash_img=hash_img, | ||
date_image_inserted=datetime.datetime.now(), | ||
department_id=department_id, | ||
date_image_taken=date_taken, | ||
user_id=user_id, | ||
) | ||
db.session.add(new_image) | ||
db.session.commit() | ||
return new_image | ||
except ClientError: | ||
exception_type, value, full_tback = sys.exc_info() | ||
current_app.logger.error( | ||
"Error uploading to S3: {}".format( | ||
" ".join([str(exception_type), str(value), format_exc()]) | ||
) | ||
) | ||
return None |
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,14 @@ | ||
import os | ||
|
||
|
||
# Encoding constants | ||
ENCODING_UTF_8 = "utf-8" | ||
|
||
# HTTP Method constants | ||
# TODO: Remove these constants and use HTTPMethod in http package when we | ||
# migrate to version 3.11 | ||
HTTP_METHOD_GET = "GET" | ||
HTTP_METHOD_POST = "POST" | ||
|
||
# Ensure the file is read/write by the creator only | ||
SAVED_UMASK = os.umask(0o077) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a missing
PHONY
.