{{ image.created_at }}
diff --git a/gallery/utils/contrast.py b/gallery/utils/contrast.py
index fb3115c..2872914 100644
--- a/gallery/utils/contrast.py
+++ b/gallery/utils/contrast.py
@@ -16,7 +16,10 @@ def contrast(background, light, dark, threshold=0.179):
# Calculate contrast
uicolors = [red / 255, green / 255, blue / 255]
- cont = [col / 12.92 if col <= 0.03928 else ((col + 0.055) / 1.055) ** 2.4 for col in uicolors]
+ cont = [
+ col / 12.92 if col <= 0.03928 else ((col + 0.055) / 1.055) ** 2.4
+ for col in uicolors
+ ]
lightness = (0.2126 * cont[0]) + (0.7152 * cont[1]) + (0.0722 * cont[2])
return light if lightness > threshold else dark
diff --git a/gallery/utils/generate_image.py b/gallery/utils/generate_image.py
index e14acfb..7a172cc 100644
--- a/gallery/utils/generate_image.py
+++ b/gallery/utils/generate_image.py
@@ -8,8 +8,8 @@ from PIL import Image, ImageOps
from werkzeug.utils import secure_filename
-CACHE_PATH = platformdirs.user_config_dir('onlylegs') + '/cache'
-UPLOAD_PATH = platformdirs.user_config_dir('onlylegs') + '/uploads'
+CACHE_PATH = os.path.join(platformdirs.user_config_dir("onlylegs"), "cache")
+UPLOAD_PATH = os.path.join(platformdirs.user_config_dir("onlylegs"), "uploads")
def generate_thumbnail(file_name, resolution, ext=None):
@@ -25,34 +25,34 @@ def generate_thumbnail(file_name, resolution, ext=None):
os.makedirs(CACHE_PATH)
# no sussy business
- file_name, file_ext = secure_filename(file_name).rsplit('.')
+ file_name, file_ext = secure_filename(file_name).rsplit(".")
if not ext:
- ext = file_ext.strip('.')
+ ext = file_ext.strip(".")
# PIL doesnt like jpg so we convert it to jpeg
if ext.lower() == "jpg":
ext = "jpeg"
# Set resolution based on preset resolutions
- if resolution in ['prev', 'preview']:
+ if resolution in ["prev", "preview"]:
res_x, res_y = (1920, 1080)
- elif resolution in ['thumb', 'thumbnail']:
- res_x, res_y = (350, 350)
- elif resolution in ['icon', 'favicon']:
+ elif resolution in ["thumb", "thumbnail"]:
+ res_x, res_y = (400, 400)
+ elif resolution in ["icon", "favicon"]:
res_x, res_y = (10, 10)
else:
return None
# If image has been already generated, return it from the cache
- if os.path.exists(os.path.join(CACHE_PATH, f'{file_name}_{res_x}x{res_y}.{ext}')):
- return os.path.join(CACHE_PATH, f'{file_name}_{res_x}x{res_y}.{ext}')
+ if os.path.exists(os.path.join(CACHE_PATH, f"{file_name}_{res_x}x{res_y}.{ext}")):
+ return os.path.join(CACHE_PATH, f"{file_name}_{res_x}x{res_y}.{ext}")
# Check if image exists in the uploads directory
- if not os.path.exists(os.path.join(UPLOAD_PATH, f'{file_name}.{file_ext}')):
+ if not os.path.exists(os.path.join(UPLOAD_PATH, f"{file_name}.{file_ext}")):
return None
# Open image and rotate it based on EXIF data and get ICC profile so colors are correct
- image = Image.open(os.path.join(UPLOAD_PATH, f'{file_name}.{file_ext}'))
+ image = Image.open(os.path.join(UPLOAD_PATH, f"{file_name}.{file_ext}"))
image_icc = image.info.get("icc_profile")
img_x, img_y = image.size
@@ -62,16 +62,20 @@ def generate_thumbnail(file_name, resolution, ext=None):
# Save image to cache directory
try:
- image.save(os.path.join(CACHE_PATH, f'{file_name}_{res_x}x{res_y}.{ext}'),
- icc_profile=image_icc)
+ image.save(
+ os.path.join(CACHE_PATH, f"{file_name}_{res_x}x{res_y}.{ext}"),
+ icc_profile=image_icc,
+ )
except OSError:
# This usually happens when saving a JPEG with an ICC profile,
# so we convert to RGB and try again
- image = image.convert('RGB')
- image.save(os.path.join(CACHE_PATH, f'{file_name}_{res_x}x{res_y}.{ext}'),
- icc_profile=image_icc)
+ image = image.convert("RGB")
+ image.save(
+ os.path.join(CACHE_PATH, f"{file_name}_{res_x}x{res_y}.{ext}"),
+ icc_profile=image_icc,
+ )
# No need to keep the image in memory, learned the hard way
image.close()
- return os.path.join(CACHE_PATH, f'{file_name}_{res_x}x{res_y}.{ext}')
+ return os.path.join(CACHE_PATH, f"{file_name}_{res_x}x{res_y}.{ext}")
diff --git a/gallery/utils/metadata/__init__.py b/gallery/utils/metadata/__init__.py
index 801f4d2..18157c7 100644
--- a/gallery/utils/metadata/__init__.py
+++ b/gallery/utils/metadata/__init__.py
@@ -16,6 +16,7 @@ class Metadata:
"""
Metadata parser
"""
+
def __init__(self, file_path):
"""
Initialize the metadata parser
@@ -32,17 +33,17 @@ class Metadata:
if tag in tags:
img_exif[value] = tags[tag]
- img_exif['FileName'] = os.path.basename(file_path)
- img_exif['FileSize'] = os.path.getsize(file_path)
- img_exif['FileFormat'] = img_exif['FileName'].split('.')[-1]
- img_exif['FileWidth'], img_exif['FileHeight'] = file.size
+ img_exif["FileName"] = os.path.basename(file_path)
+ img_exif["FileSize"] = os.path.getsize(file_path)
+ img_exif["FileFormat"] = img_exif["FileName"].split(".")[-1]
+ img_exif["FileWidth"], img_exif["FileHeight"] = file.size
file.close()
except TypeError:
- img_exif['FileName'] = os.path.basename(file_path)
- img_exif['FileSize'] = os.path.getsize(file_path)
- img_exif['FileFormat'] = img_exif['FileName'].split('.')[-1]
- img_exif['FileWidth'], img_exif['FileHeight'] = file.size
+ img_exif["FileName"] = os.path.basename(file_path)
+ img_exif["FileSize"] = os.path.getsize(file_path)
+ img_exif["FileFormat"] = img_exif["FileName"].split(".")[-1]
+ img_exif["FileWidth"], img_exif["FileHeight"] = file.size
self.encoded = img_exif
@@ -60,39 +61,42 @@ class Metadata:
Formats the data into a dictionary
"""
exif = {
- 'Photographer': {},
- 'Camera': {},
- 'Software': {},
- 'File': {},
+ "Photographer": {},
+ "Camera": {},
+ "Software": {},
+ "File": {},
}
# Thanks chatGPT xP
+ # the helper function works, so not sure why it triggers pylint
for key, value in encoded_exif.items():
for mapping_name, mapping_val in EXIF_MAPPING:
if key in mapping_val:
if len(mapping_val[key]) == 2:
- # the helper function works, so not sure why it triggers pylint
exif[mapping_name][mapping_val[key][0]] = {
- 'raw': value,
- 'formatted': (
- getattr(helpers, mapping_val[key][1]) # pylint: disable=E0602
- (value)
- ),
+ "raw": value,
+ "formatted": (
+ getattr(
+ helpers, mapping_val[key][1] # pylint: disable=E0602
+ )(
+ value
+ )
+ ),
}
else:
exif[mapping_name][mapping_val[key][0]] = {
- 'raw': value,
+ "raw": value,
}
continue
# Remove empty keys
- if not exif['Photographer']:
- del exif['Photographer']
- if not exif['Camera']:
- del exif['Camera']
- if not exif['Software']:
- del exif['Software']
- if not exif['File']:
- del exif['File']
+ if not exif["Photographer"]:
+ del exif["Photographer"]
+ if not exif["Camera"]:
+ del exif["Camera"]
+ if not exif["Software"]:
+ del exif["Software"]
+ if not exif["File"]:
+ del exif["File"]
return exif
diff --git a/gallery/utils/metadata/helpers.py b/gallery/utils/metadata/helpers.py
index e7b8cd3..0f2b349 100644
--- a/gallery/utils/metadata/helpers.py
+++ b/gallery/utils/metadata/helpers.py
@@ -21,28 +21,28 @@ def date_format(value):
"""
Formats the date into a standard format
"""
- return str(datetime.strptime(value, '%Y:%m:%d %H:%M:%S'))
+ return str(datetime.strptime(value, "%Y:%m:%d %H:%M:%S"))
def fnumber(value):
"""
Formats the f-number into a standard format
"""
- return 'ƒ/' + str(value)
+ return "ƒ/" + str(value)
def iso(value):
"""
Formats the ISO into a standard format
"""
- return 'ISO ' + str(value)
+ return "ISO " + str(value)
def shutter(value):
"""
Formats the shutter speed into a standard format
"""
- return str(value) + 's'
+ return str(value) + "s"
def focal_length(value):
@@ -50,27 +50,23 @@ def focal_length(value):
Formats the focal length into a standard format
"""
try:
- return str(value[0] / value[1]) + 'mm'
+ return str(value[0] / value[1]) + "mm"
except TypeError:
- return str(value) + 'mm'
+ return str(value) + "mm"
def exposure(value):
"""
Formats the exposure value into a standard format
"""
- return str(value) + 'EV'
+ return str(value) + "EV"
def color_space(value):
"""
Maps the value of the color space to a human readable format
"""
- value_map = {
- 0: 'Reserved',
- 1: 'sRGB',
- 65535: 'Uncalibrated'
- }
+ value_map = {0: "Reserved", 1: "sRGB", 65535: "Uncalibrated"}
try:
return value_map[int(value)]
except KeyError:
@@ -82,28 +78,28 @@ def flash(value):
Maps the value of the flash to a human readable format
"""
value_map = {
- 0: 'Flash did not fire',
- 1: 'Flash fired',
- 5: 'Strobe return light not detected',
- 7: 'Strobe return light detected',
- 9: 'Flash fired, compulsory flash mode',
- 13: 'Flash fired, compulsory flash mode, return light not detected',
- 15: 'Flash fired, compulsory flash mode, return light detected',
- 16: 'Flash did not fire, compulsory flash mode',
- 24: 'Flash did not fire, auto mode',
- 25: 'Flash fired, auto mode',
- 29: 'Flash fired, auto mode, return light not detected',
- 31: 'Flash fired, auto mode, return light detected',
- 32: 'No flash function',
- 65: 'Flash fired, red-eye reduction mode',
- 69: 'Flash fired, red-eye reduction mode, return light not detected',
- 71: 'Flash fired, red-eye reduction mode, return light detected',
- 73: 'Flash fired, compulsory flash mode, red-eye reduction mode',
- 77: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected',
- 79: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light detected',
- 89: 'Flash fired, auto mode, red-eye reduction mode',
- 93: 'Flash fired, auto mode, return light not detected, red-eye reduction mode',
- 95: 'Flash fired, auto mode, return light detected, red-eye reduction mode'
+ 0: "Flash did not fire",
+ 1: "Flash fired",
+ 5: "Strobe return light not detected",
+ 7: "Strobe return light detected",
+ 9: "Flash fired, compulsory flash mode",
+ 13: "Flash fired, compulsory flash mode, return light not detected",
+ 15: "Flash fired, compulsory flash mode, return light detected",
+ 16: "Flash did not fire, compulsory flash mode",
+ 24: "Flash did not fire, auto mode",
+ 25: "Flash fired, auto mode",
+ 29: "Flash fired, auto mode, return light not detected",
+ 31: "Flash fired, auto mode, return light detected",
+ 32: "No flash function",
+ 65: "Flash fired, red-eye reduction mode",
+ 69: "Flash fired, red-eye reduction mode, return light not detected",
+ 71: "Flash fired, red-eye reduction mode, return light detected",
+ 73: "Flash fired, compulsory flash mode, red-eye reduction mode",
+ 77: "Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected",
+ 79: "Flash fired, compulsory flash mode, red-eye reduction mode, return light detected",
+ 89: "Flash fired, auto mode, red-eye reduction mode",
+ 93: "Flash fired, auto mode, return light not detected, red-eye reduction mode",
+ 95: "Flash fired, auto mode, return light detected, red-eye reduction mode",
}
try:
return value_map[int(value)]
@@ -116,15 +112,15 @@ def exposure_program(value):
Maps the value of the exposure program to a human readable format
"""
value_map = {
- 0: 'Not defined',
- 1: 'Manual',
- 2: 'Normal program',
- 3: 'Aperture priority',
- 4: 'Shutter priority',
- 5: 'Creative program',
- 6: 'Action program',
- 7: 'Portrait mode',
- 8: 'Landscape mode'
+ 0: "Not defined",
+ 1: "Manual",
+ 2: "Normal program",
+ 3: "Aperture priority",
+ 4: "Shutter priority",
+ 5: "Creative program",
+ 6: "Action program",
+ 7: "Portrait mode",
+ 8: "Landscape mode",
}
try:
return value_map[int(value)]
@@ -137,14 +133,14 @@ def metering_mode(value):
Maps the value of the metering mode to a human readable format
"""
value_map = {
- 0: 'Unknown',
- 1: 'Average',
- 2: 'Center-Weighted Average',
- 3: 'Spot',
- 4: 'Multi-Spot',
- 5: 'Pattern',
- 6: 'Partial',
- 255: 'Other'
+ 0: "Unknown",
+ 1: "Average",
+ 2: "Center-Weighted Average",
+ 3: "Spot",
+ 4: "Multi-Spot",
+ 5: "Pattern",
+ 6: "Partial",
+ 255: "Other",
}
try:
return value_map[int(value)]
@@ -156,11 +152,7 @@ def resolution_unit(value):
"""
Maps the value of the resolution unit to a human readable format
"""
- value_map = {
- 1: 'No absolute unit of measurement',
- 2: 'Inch',
- 3: 'Centimeter'
- }
+ value_map = {1: "No absolute unit of measurement", 2: "Inch", 3: "Centimeter"}
try:
return value_map[int(value)]
except KeyError:
@@ -172,27 +164,27 @@ def light_source(value):
Maps the value of the light source to a human readable format
"""
value_map = {
- 0: 'Unknown',
- 1: 'Daylight',
- 2: 'Fluorescent',
- 3: 'Tungsten (incandescent light)',
- 4: 'Flash',
- 9: 'Fine weather',
- 10: 'Cloudy weather',
- 11: 'Shade',
- 12: 'Daylight fluorescent (D 5700 - 7100K)',
- 13: 'Day white fluorescent (N 4600 - 5400K)',
- 14: 'Cool white fluorescent (W 3900 - 4500K)',
- 15: 'White fluorescent (WW 3200 - 3700K)',
- 17: 'Standard light A',
- 18: 'Standard light B',
- 19: 'Standard light C',
- 20: 'D55',
- 21: 'D65',
- 22: 'D75',
- 23: 'D50',
- 24: 'ISO studio tungsten',
- 255: 'Other light source',
+ 0: "Unknown",
+ 1: "Daylight",
+ 2: "Fluorescent",
+ 3: "Tungsten (incandescent light)",
+ 4: "Flash",
+ 9: "Fine weather",
+ 10: "Cloudy weather",
+ 11: "Shade",
+ 12: "Daylight fluorescent (D 5700 - 7100K)",
+ 13: "Day white fluorescent (N 4600 - 5400K)",
+ 14: "Cool white fluorescent (W 3900 - 4500K)",
+ 15: "White fluorescent (WW 3200 - 3700K)",
+ 17: "Standard light A",
+ 18: "Standard light B",
+ 19: "Standard light C",
+ 20: "D55",
+ 21: "D65",
+ 22: "D75",
+ 23: "D50",
+ 24: "ISO studio tungsten",
+ 255: "Other light source",
}
try:
return value_map[int(value)]
@@ -205,10 +197,10 @@ def scene_capture_type(value):
Maps the value of the scene capture type to a human readable format
"""
value_map = {
- 0: 'Standard',
- 1: 'Landscape',
- 2: 'Portrait',
- 3: 'Night scene',
+ 0: "Standard",
+ 1: "Landscape",
+ 2: "Portrait",
+ 3: "Night scene",
}
try:
return value_map[int(value)]
@@ -221,8 +213,8 @@ def white_balance(value):
Maps the value of the white balance to a human readable format
"""
value_map = {
- 0: 'Auto white balance',
- 1: 'Manual white balance',
+ 0: "Auto white balance",
+ 1: "Manual white balance",
}
try:
return value_map[int(value)]
@@ -235,9 +227,9 @@ def exposure_mode(value):
Maps the value of the exposure mode to a human readable format
"""
value_map = {
- 0: 'Auto exposure',
- 1: 'Manual exposure',
- 2: 'Auto bracket',
+ 0: "Auto exposure",
+ 1: "Manual exposure",
+ 2: "Auto bracket",
}
try:
return value_map[int(value)]
@@ -250,22 +242,14 @@ def sensitivity_type(value):
Maps the value of the sensitivity type to a human readable format
"""
value_map = {
- 0:
- 'Unknown',
- 1:
- 'Standard Output Sensitivity',
- 2:
- 'Recommended Exposure Index',
- 3:
- 'ISO Speed',
- 4:
- 'Standard Output Sensitivity and Recommended Exposure Index',
- 5:
- 'Standard Output Sensitivity and ISO Speed',
- 6:
- 'Recommended Exposure Index and ISO Speed',
- 7:
- 'Standard Output Sensitivity, Recommended Exposure Index and ISO Speed',
+ 0: "Unknown",
+ 1: "Standard Output Sensitivity",
+ 2: "Recommended Exposure Index",
+ 3: "ISO Speed",
+ 4: "Standard Output Sensitivity and Recommended Exposure Index",
+ 5: "Standard Output Sensitivity and ISO Speed",
+ 6: "Recommended Exposure Index and ISO Speed",
+ 7: "Standard Output Sensitivity, Recommended Exposure Index and ISO Speed",
}
try:
return value_map[int(value)]
@@ -278,7 +262,7 @@ def lens_specification(value):
Maps the value of the lens specification to a human readable format
"""
try:
- return str(value[0] / value[1]) + 'mm - ' + str(value[2] / value[3]) + 'mm'
+ return str(value[0] / value[1]) + "mm - " + str(value[2] / value[3]) + "mm"
except TypeError:
return None
@@ -288,55 +272,55 @@ def compression_type(value):
Maps the value of the compression type to a human readable format
"""
value_map = {
- 1: 'Uncompressed',
- 2: 'CCITT 1D',
- 3: 'T4/Group 3 Fax',
- 4: 'T6/Group 4 Fax',
- 5: 'LZW',
- 6: 'JPEG (old-style)',
- 7: 'JPEG',
- 8: 'Adobe Deflate',
- 9: 'JBIG B&W',
- 10: 'JBIG Color',
- 99: 'JPEG',
- 262: 'Kodak 262',
- 32766: 'Next',
- 32767: 'Sony ARW Compressed',
- 32769: 'Packed RAW',
- 32770: 'Samsung SRW Compressed',
- 32771: 'CCIRLEW',
- 32772: 'Samsung SRW Compressed 2',
- 32773: 'PackBits',
- 32809: 'Thunderscan',
- 32867: 'Kodak KDC Compressed',
- 32895: 'IT8CTPAD',
- 32896: 'IT8LW',
- 32897: 'IT8MP',
- 32898: 'IT8BL',
- 32908: 'PixarFilm',
- 32909: 'PixarLog',
- 32946: 'Deflate',
- 32947: 'DCS',
- 33003: 'Aperio JPEG 2000 YCbCr',
- 33005: 'Aperio JPEG 2000 RGB',
- 34661: 'JBIG',
- 34676: 'SGILog',
- 34677: 'SGILog24',
- 34712: 'JPEG 2000',
- 34713: 'Nikon NEF Compressed',
- 34715: 'JBIG2 TIFF FX',
- 34718: '(MDI) Binary Level Codec',
- 34719: '(MDI) Progressive Transform Codec',
- 34720: '(MDI) Vector',
- 34887: 'ESRI Lerc',
- 34892: 'Lossy JPEG',
- 34925: 'LZMA2',
- 34926: 'Zstd',
- 34927: 'WebP',
- 34933: 'PNG',
- 34934: 'JPEG XR',
- 65000: 'Kodak DCR Compressed',
- 65535: 'Pentax PEF Compressed',
+ 1: "Uncompressed",
+ 2: "CCITT 1D",
+ 3: "T4/Group 3 Fax",
+ 4: "T6/Group 4 Fax",
+ 5: "LZW",
+ 6: "JPEG (old-style)",
+ 7: "JPEG",
+ 8: "Adobe Deflate",
+ 9: "JBIG B&W",
+ 10: "JBIG Color",
+ 99: "JPEG",
+ 262: "Kodak 262",
+ 32766: "Next",
+ 32767: "Sony ARW Compressed",
+ 32769: "Packed RAW",
+ 32770: "Samsung SRW Compressed",
+ 32771: "CCIRLEW",
+ 32772: "Samsung SRW Compressed 2",
+ 32773: "PackBits",
+ 32809: "Thunderscan",
+ 32867: "Kodak KDC Compressed",
+ 32895: "IT8CTPAD",
+ 32896: "IT8LW",
+ 32897: "IT8MP",
+ 32898: "IT8BL",
+ 32908: "PixarFilm",
+ 32909: "PixarLog",
+ 32946: "Deflate",
+ 32947: "DCS",
+ 33003: "Aperio JPEG 2000 YCbCr",
+ 33005: "Aperio JPEG 2000 RGB",
+ 34661: "JBIG",
+ 34676: "SGILog",
+ 34677: "SGILog24",
+ 34712: "JPEG 2000",
+ 34713: "Nikon NEF Compressed",
+ 34715: "JBIG2 TIFF FX",
+ 34718: "(MDI) Binary Level Codec",
+ 34719: "(MDI) Progressive Transform Codec",
+ 34720: "(MDI) Vector",
+ 34887: "ESRI Lerc",
+ 34892: "Lossy JPEG",
+ 34925: "LZMA2",
+ 34926: "Zstd",
+ 34927: "WebP",
+ 34933: "PNG",
+ 34934: "JPEG XR",
+ 65000: "Kodak DCR Compressed",
+ 65535: "Pentax PEF Compressed",
}
try:
return value_map[int(value)]
@@ -349,15 +333,15 @@ def orientation(value):
Maps the value of the orientation to a human readable format
"""
value_map = {
- 0: 'Undefined',
- 1: 'Horizontal (normal)',
- 2: 'Mirror horizontal',
- 3: 'Rotate 180',
- 4: 'Mirror vertical',
- 5: 'Mirror horizontal and rotate 270 CW',
- 6: 'Rotate 90 CW',
- 7: 'Mirror horizontal and rotate 90 CW',
- 8: 'Rotate 270 CW',
+ 0: "Undefined",
+ 1: "Horizontal (normal)",
+ 2: "Mirror horizontal",
+ 3: "Rotate 180",
+ 4: "Mirror vertical",
+ 5: "Mirror horizontal and rotate 270 CW",
+ 6: "Rotate 90 CW",
+ 7: "Mirror horizontal and rotate 90 CW",
+ 8: "Rotate 270 CW",
}
try:
return value_map[int(value)]
@@ -370,16 +354,16 @@ def components_configuration(value):
Maps the value of the components configuration to a human readable format
"""
value_map = {
- 0: '',
- 1: 'Y',
- 2: 'Cb',
- 3: 'Cr',
- 4: 'R',
- 5: 'G',
- 6: 'B',
+ 0: "",
+ 1: "Y",
+ 2: "Cb",
+ 3: "Cr",
+ 4: "R",
+ 5: "G",
+ 6: "B",
}
try:
- return ''.join([value_map[int(x)] for x in value])
+ return "".join([value_map[int(x)] for x in value])
except KeyError:
return None
@@ -388,18 +372,18 @@ def rating(value):
"""
Maps the value of the rating to a human readable format
"""
- return str(value) + ' stars'
+ return str(value) + " stars"
def rating_percent(value):
"""
Maps the value of the rating to a human readable format
"""
- return str(value) + '%'
+ return str(value) + "%"
def pixel_dimension(value):
"""
Maps the value of the pixel dimension to a human readable format
"""
- return str(value) + 'px'
+ return str(value) + "px"
diff --git a/gallery/utils/metadata/mapping.py b/gallery/utils/metadata/mapping.py
index 804374d..1e3ca43 100644
--- a/gallery/utils/metadata/mapping.py
+++ b/gallery/utils/metadata/mapping.py
@@ -4,66 +4,66 @@ Mapping for metadata
"""
PHOTOGRAHER_MAPPING = {
- 'Artist': ['Artist'],
- 'UserComment': ['Comment'],
- 'ImageDescription': ['Description'],
- 'Copyright': ['Copyright'],
+ "Artist": ["Artist"],
+ "UserComment": ["Comment"],
+ "ImageDescription": ["Description"],
+ "Copyright": ["Copyright"],
}
CAMERA_MAPPING = {
- 'Model': ['Model'],
- 'Make': ['Make'],
- 'BodySerialNumber': ['Camera Type'],
- 'LensMake': ['Lens Make'],
- 'LenseModel': ['Lens Model'],
- 'LensSpecification': ['Lens Specification', 'lens_specification'],
- 'ComponentsConfiguration': ['Components Configuration', 'components_configuration'],
- 'DateTime': ['Date Processed', 'date_format'],
- 'DateTimeDigitized': ['Time Digitized', 'date_format'],
- 'OffsetTime': ['Time Offset'],
- 'OffsetTimeOriginal': ['Time Offset - Original'],
- 'OffsetTimeDigitized': ['Time Offset - Digitized'],
- 'DateTimeOriginal': ['Date Original', 'date_format'],
- 'FNumber': ['F-Stop', 'fnumber'],
- 'FocalLength': ['Focal Length', 'focal_length'],
- 'FocalLengthIn35mmFilm': ['Focal Length (35mm format)', 'focal_length'],
- 'MaxApertureValue': ['Max Aperture', 'fnumber'],
- 'ApertureValue': ['Aperture', 'fnumber'],
- 'ShutterSpeedValue': ['Shutter Speed', 'shutter'],
- 'ISOSpeedRatings': ['ISO Speed Ratings', 'iso'],
- 'ISOSpeed': ['ISO Speed', 'iso'],
- 'SensitivityType': ['Sensitivity Type', 'sensitivity_type'],
- 'ExposureBiasValue': ['Exposure Bias', 'exposure'],
- 'ExposureTime': ['Exposure Time', 'shutter'],
- 'ExposureMode': ['Exposure Mode', 'exposure_mode'],
- 'ExposureProgram': ['Exposure Program', 'exposure_program'],
- 'WhiteBalance': ['White Balance', 'white_balance'],
- 'Flash': ['Flash', 'flash'],
- 'MeteringMode': ['Metering Mode', 'metering_mode'],
- 'LightSource': ['Light Source', 'light_source'],
- 'SceneCaptureType': ['Scene Capture Type', 'scene_capture_type'],
+ "Model": ["Model"],
+ "Make": ["Make"],
+ "BodySerialNumber": ["Camera Type"],
+ "LensMake": ["Lens Make"],
+ "LenseModel": ["Lens Model"],
+ "LensSpecification": ["Lens Specification", "lens_specification"],
+ "ComponentsConfiguration": ["Components Configuration", "components_configuration"],
+ "DateTime": ["Date Processed", "date_format"],
+ "DateTimeDigitized": ["Time Digitized", "date_format"],
+ "OffsetTime": ["Time Offset"],
+ "OffsetTimeOriginal": ["Time Offset - Original"],
+ "OffsetTimeDigitized": ["Time Offset - Digitized"],
+ "DateTimeOriginal": ["Date Original", "date_format"],
+ "FNumber": ["F-Stop", "fnumber"],
+ "FocalLength": ["Focal Length", "focal_length"],
+ "FocalLengthIn35mmFilm": ["Focal Length (35mm format)", "focal_length"],
+ "MaxApertureValue": ["Max Aperture", "fnumber"],
+ "ApertureValue": ["Aperture", "fnumber"],
+ "ShutterSpeedValue": ["Shutter Speed", "shutter"],
+ "ISOSpeedRatings": ["ISO Speed Ratings", "iso"],
+ "ISOSpeed": ["ISO Speed", "iso"],
+ "SensitivityType": ["Sensitivity Type", "sensitivity_type"],
+ "ExposureBiasValue": ["Exposure Bias", "exposure"],
+ "ExposureTime": ["Exposure Time", "shutter"],
+ "ExposureMode": ["Exposure Mode", "exposure_mode"],
+ "ExposureProgram": ["Exposure Program", "exposure_program"],
+ "WhiteBalance": ["White Balance", "white_balance"],
+ "Flash": ["Flash", "flash"],
+ "MeteringMode": ["Metering Mode", "metering_mode"],
+ "LightSource": ["Light Source", "light_source"],
+ "SceneCaptureType": ["Scene Capture Type", "scene_capture_type"],
}
SOFTWARE_MAPPING = {
- 'Software': ['Software'],
- 'ColorSpace': ['Colour Space', 'color_space'],
- 'Compression': ['Compression', 'compression_type'],
+ "Software": ["Software"],
+ "ColorSpace": ["Colour Space", "color_space"],
+ "Compression": ["Compression", "compression_type"],
}
FILE_MAPPING = {
- 'FileName': ['Name'],
- 'FileSize': ['Size', 'human_size'],
- 'FileFormat': ['Format'],
- 'FileWidth': ['Width', 'pixel_dimension'],
- 'FileHeight': ['Height', 'pixel_dimension'],
- 'Orientation': ['Orientation', 'orientation'],
- 'XResolution': ['X-resolution'],
- 'YResolution': ['Y-resolution'],
- 'ResolutionUnit': ['Resolution Units', 'resolution_unit'],
- 'Rating': ['Rating', 'rating'],
- 'RatingPercent': ['Rating Percent', 'rating_percent'],
+ "FileName": ["Name"],
+ "FileSize": ["Size", "human_size"],
+ "FileFormat": ["Format"],
+ "FileWidth": ["Width", "pixel_dimension"],
+ "FileHeight": ["Height", "pixel_dimension"],
+ "Orientation": ["Orientation", "orientation"],
+ "XResolution": ["X-resolution"],
+ "YResolution": ["Y-resolution"],
+ "ResolutionUnit": ["Resolution Units", "resolution_unit"],
+ "Rating": ["Rating", "rating"],
+ "RatingPercent": ["Rating Percent", "rating_percent"],
}
EXIF_MAPPING = [
- ('Photographer', PHOTOGRAHER_MAPPING),
- ('Camera', CAMERA_MAPPING),
- ('Software', SOFTWARE_MAPPING),
- ('File', FILE_MAPPING)
+ ("Photographer", PHOTOGRAHER_MAPPING),
+ ("Camera", CAMERA_MAPPING),
+ ("Software", SOFTWARE_MAPPING),
+ ("File", FILE_MAPPING),
]
diff --git a/gallery/views/group.py b/gallery/views/group.py
new file mode 100644
index 0000000..39b6325
--- /dev/null
+++ b/gallery/views/group.py
@@ -0,0 +1,157 @@
+"""
+Onlylegs - Image Groups
+Why groups? Because I don't like calling these albums
+sounds more limiting that it actually is in this gallery
+"""
+from flask import Blueprint, abort, render_template, url_for
+
+from sqlalchemy.orm import sessionmaker
+from gallery import db
+from gallery.utils import contrast
+
+
+blueprint = Blueprint("group", __name__, url_prefix="/group")
+db_session = sessionmaker(bind=db.engine)
+db_session = db_session()
+
+
+@blueprint.route("/", methods=["GET"])
+def groups():
+ """
+ Group overview, shows all image groups
+ """
+ groups = db_session.query(db.Groups).all()
+
+ # For each group, get the 3 most recent images
+ for group in groups:
+ group.author_username = (
+ db_session.query(db.Users.username)
+ .filter(db.Users.id == group.author_id)
+ .first()[0]
+ )
+
+ # Get the 3 most recent images
+ images = (
+ db_session.query(db.GroupJunction.post_id)
+ .filter(db.GroupJunction.group_id == group.id)
+ .order_by(db.GroupJunction.date_added.desc())
+ .limit(3)
+ )
+
+ # For each image, get the image data and add it to the group item
+ group.images = []
+ for image in images:
+ group.images.append(
+ db_session.query(
+ db.Posts.filename, db.Posts.alt, db.Posts.colours, db.Posts.id
+ )
+ .filter(db.Posts.id == image[0])
+ .first()
+ )
+
+ return render_template("list.html", groups=groups)
+
+
+@blueprint.route("/
")
+def group(group_id):
+ """
+ Group view, shows all images in a group
+ """
+ # Get the group, if it doesn't exist, 404
+ group = db_session.query(db.Groups).filter(db.Groups.id == group_id).first()
+
+ if group is None:
+ abort(404, "Group not found! D:")
+
+ # Get the group's author username
+ group.author_username = (
+ db_session.query(db.Users.username)
+ .filter(db.Users.id == group.author_id)
+ .first()[0]
+ )
+
+ # Get all images in the group from the junction table
+ junction = (
+ db_session.query(db.GroupJunction.post_id)
+ .filter(db.GroupJunction.group_id == group_id)
+ .order_by(db.GroupJunction.date_added.desc())
+ .all()
+ )
+
+ # Get the image data for each image in the group
+ images = []
+ for image in junction:
+ images.append(
+ db_session.query(db.Posts).filter(db.Posts.id == image[0]).first()
+ )
+
+ # Check contrast for the first image in the group for the banner
+ text_colour = "rgb(var(--fg-black))"
+ if images:
+ text_colour = contrast.contrast(
+ images[0].colours[0], "rgb(var(--fg-black))", "rgb(var(--fg-white))"
+ )
+
+ return render_template(
+ "group.html", group=group, images=images, text_colour=text_colour
+ )
+
+
+@blueprint.route("//")
+def group_post(group_id, image_id):
+ """
+ Image view, shows the image and its metadata from a specific group
+ """
+ # Get the image, if it doesn't exist, 404
+ image = db_session.query(db.Posts).filter(db.Posts.id == image_id).first()
+ if image is None:
+ abort(404, "Image not found")
+
+ # Get the image's author username
+ image.author_username = (
+ db_session.query(db.Users.username)
+ .filter(db.Users.id == image.author_id)
+ .first()[0]
+ )
+
+ # Get all groups the image is in
+ groups = (
+ db_session.query(db.GroupJunction.group_id)
+ .filter(db.GroupJunction.post_id == image_id)
+ .all()
+ )
+
+ # Get the group data for each group the image is in
+ image.groups = []
+ for group in groups:
+ image.groups.append(
+ db_session.query(db.Groups.id, db.Groups.name)
+ .filter(db.Groups.id == group[0])
+ .first()
+ )
+
+ # Get the next and previous images in the group
+ next_url = (
+ db_session.query(db.GroupJunction.post_id)
+ .filter(db.GroupJunction.group_id == group_id)
+ .filter(db.GroupJunction.post_id > image_id)
+ .order_by(db.GroupJunction.date_added.asc())
+ .first()
+ )
+ prev_url = (
+ db_session.query(db.GroupJunction.post_id)
+ .filter(db.GroupJunction.group_id == group_id)
+ .filter(db.GroupJunction.post_id < image_id)
+ .order_by(db.GroupJunction.date_added.desc())
+ .first()
+ )
+
+ # If there is a next or previous image, get the URL for it
+ if next_url:
+ next_url = url_for("group.group_post", group_id=group_id, image_id=next_url[0])
+ if prev_url:
+ prev_url = url_for("group.group_post", group_id=group_id, image_id=prev_url[0])
+
+ return render_template(
+ "image.html", image=image, next_url=next_url, prev_url=prev_url
+ )
diff --git a/gallery/views/groups.py b/gallery/views/groups.py
deleted file mode 100644
index ac9cd5e..0000000
--- a/gallery/views/groups.py
+++ /dev/null
@@ -1,139 +0,0 @@
-"""
-Onlylegs - Image Groups
-Why groups? Because I don't like calling these albums
-sounds more limiting that it actually is in this gallery
-"""
-from flask import Blueprint, abort, render_template, url_for
-
-from sqlalchemy.orm import sessionmaker
-from gallery import db
-from gallery.utils import contrast
-
-
-blueprint = Blueprint('group', __name__, url_prefix='/group')
-db_session = sessionmaker(bind=db.engine)
-db_session = db_session()
-
-
-@blueprint.route('/', methods=['GET'])
-def groups():
- """
- Group overview, shows all image groups
- """
- groups = db_session.query(db.Groups).all()
-
- # For each group, get the 3 most recent images
- for group in groups:
- group.author_username = (db_session.query(db.Users.username)
- .filter(db.Users.id == group.author_id)
- .first()[0])
-
- # Get the 3 most recent images
- images = (db_session.query(db.GroupJunction.post_id)
- .filter(db.GroupJunction.group_id == group.id)
- .order_by(db.GroupJunction.date_added.desc())
- .limit(3))
-
- # For each image, get the image data and add it to the group item
- group.images = []
- for image in images:
- group.images.append(db_session.query(db.Posts.filename, db.Posts.alt,
- db.Posts.colours, db.Posts.id)
- .filter(db.Posts.id == image[0])
- .first())
-
- return render_template('groups/list.html', groups=groups)
-
-
-@blueprint.route('/')
-def group(group_id):
- """
- Group view, shows all images in a group
- """
- # Get the group, if it doesn't exist, 404
- group = (db_session.query(db.Groups)
- .filter(db.Groups.id == group_id)
- .first())
-
- if group is None:
- abort(404, 'Group not found! D:')
-
- # Get the group's author username
- group.author_username = (db_session.query(db.Users.username)
- .filter(db.Users.id == group.author_id)
- .first()[0])
-
- # Get all images in the group from the junction table
- junction = (db_session.query(db.GroupJunction.post_id)
- .filter(db.GroupJunction.group_id == group_id)
- .order_by(db.GroupJunction.date_added.desc())
- .all())
-
- # Get the image data for each image in the group
- images = []
- for image in junction:
- images.append(db_session.query(db.Posts)
- .filter(db.Posts.id == image[0])
- .first())
-
- # Check contrast for the first image in the group for the banner
- text_colour = 'rgb(var(--fg-black))'
- if images:
- text_colour = contrast.contrast(images[0].colours[0],
- 'rgb(var(--fg-black))',
- 'rgb(var(--fg-white))')
-
- return render_template('groups/group.html',
- group=group,
- images=images,
- text_colour=text_colour)
-
-
-@blueprint.route('//')
-def group_post(group_id, image_id):
- """
- Image view, shows the image and its metadata from a specific group
- """
- # Get the image, if it doesn't exist, 404
- image = (db_session.query(db.Posts)
- .filter(db.Posts.id == image_id)
- .first())
- if image is None:
- abort(404, 'Image not found')
-
- # Get the image's author username
- image.author_username = (db_session.query(db.Users.username)
- .filter(db.Users.id == image.author_id)
- .first()[0])
-
- # Get all groups the image is in
- groups = (db_session.query(db.GroupJunction.group_id)
- .filter(db.GroupJunction.post_id == image_id)
- .all())
-
- # Get the group data for each group the image is in
- image.groups = []
- for group in groups:
- image.groups.append(db_session.query(db.Groups.id, db.Groups.name)
- .filter(db.Groups.id == group[0])
- .first())
-
- # Get the next and previous images in the group
- next_url = (db_session.query(db.GroupJunction.post_id)
- .filter(db.GroupJunction.group_id == group_id)
- .filter(db.GroupJunction.post_id > image_id)
- .order_by(db.GroupJunction.date_added.asc())
- .first())
- prev_url = (db_session.query(db.GroupJunction.post_id)
- .filter(db.GroupJunction.group_id == group_id)
- .filter(db.GroupJunction.post_id < image_id)
- .order_by(db.GroupJunction.date_added.desc())
- .first())
-
- # If there is a next or previous image, get the URL for it
- if next_url:
- next_url = url_for('group.group_post', group_id=group_id, image_id=next_url[0])
- if prev_url:
- prev_url = url_for('group.group_post', group_id=group_id, image_id=prev_url[0])
-
- return render_template('image.html', image=image, next_url=next_url, prev_url=prev_url)
diff --git a/gallery/views/image.py b/gallery/views/image.py
new file mode 100644
index 0000000..33303ec
--- /dev/null
+++ b/gallery/views/image.py
@@ -0,0 +1,94 @@
+"""
+Onlylegs - Image View
+"""
+from math import ceil
+
+from flask import Blueprint, abort, render_template, url_for, current_app
+
+from sqlalchemy.orm import sessionmaker
+from gallery import db
+
+
+blueprint = Blueprint("image", __name__, url_prefix="/image")
+db_session = sessionmaker(bind=db.engine)
+db_session = db_session()
+
+
+@blueprint.route("/")
+def image(image_id):
+ """
+ Image view, shows the image and its metadata
+ """
+ # Get the image, if it doesn't exist, 404
+ image = db_session.query(db.Posts).filter(db.Posts.id == image_id).first()
+ if not image:
+ abort(404, "Image not found :<")
+
+ # Get the image's author username
+ image.author_username = (
+ db_session.query(db.Users.username)
+ .filter(db.Users.id == image.author_id)
+ .first()[0]
+ )
+
+ # Get the image's groups
+ groups = (
+ db_session.query(db.GroupJunction.group_id)
+ .filter(db.GroupJunction.post_id == image_id)
+ .all()
+ )
+
+ # For each group, get the group data and add it to the image item
+ image.groups = []
+ for group in groups:
+ image.groups.append(
+ db_session.query(db.Groups.id, db.Groups.name)
+ .filter(db.Groups.id == group[0])
+ .first()
+ )
+
+ # Get the next and previous images
+ # Check if there is a group ID set
+ next_url = (
+ db_session.query(db.Posts.id)
+ .filter(db.Posts.id > image_id)
+ .order_by(db.Posts.id.asc())
+ .first()
+ )
+ prev_url = (
+ db_session.query(db.Posts.id)
+ .filter(db.Posts.id < image_id)
+ .order_by(db.Posts.id.desc())
+ .first()
+ )
+
+ # If there is a next or previous image, get the url
+ if next_url:
+ next_url = url_for("image.image", image_id=next_url[0])
+ if prev_url:
+ prev_url = url_for("image.image", image_id=prev_url[0])
+
+ # Yoink all the images in the database
+ total_images = db_session.query(db.Posts.id).order_by(db.Posts.id.desc()).all()
+ limit = current_app.config["UPLOAD_CONF"]["max-load"]
+
+ # If the number of items is less than the limit, no point of calculating the page
+ if len(total_images) <= limit:
+ return_page = None
+ else:
+ # How many pages should there be
+ for i in range(ceil(len(total_images) / limit)):
+ # Slice the list of IDs into chunks of the limit
+ for j in total_images[i * limit : (i + 1) * limit]:
+ # Is our image in this chunk?
+ if image_id in j:
+ return_page = i + 1
+ break
+
+ return render_template(
+ "image.html",
+ image=image,
+ next_url=next_url,
+ prev_url=prev_url,
+ return_page=return_page,
+ )
diff --git a/gallery/views/index.py b/gallery/views/index.py
new file mode 100644
index 0000000..9093311
--- /dev/null
+++ b/gallery/views/index.py
@@ -0,0 +1,59 @@
+"""
+Onlylegs Gallery - Index view
+"""
+from math import ceil
+
+from flask import Blueprint, render_template, request, current_app
+from werkzeug.exceptions import abort
+
+from sqlalchemy.orm import sessionmaker
+from gallery import db
+
+
+blueprint = Blueprint("gallery", __name__)
+db_session = sessionmaker(bind=db.engine)
+db_session = db_session()
+
+
+@blueprint.route("/")
+def index():
+ """
+ Home page of the website, shows the feed of the latest images
+ """
+ # meme
+ if request.args.get("coffee") == "please":
+ abort(418)
+
+ # pagination, defaults to page 1 if no page is specified
+ page = request.args.get("page", default=1, type=int)
+ limit = current_app.config["UPLOAD_CONF"]["max-load"]
+
+ # get the total number of images in the database
+ # calculate the total number of pages, and make sure the page number is valid
+ total_images = db_session.query(db.Posts.id).count()
+ pages = ceil(max(total_images, limit) / limit)
+ if page > pages:
+ abort(
+ 404,
+ "You have reached the far and beyond, "
+ + "but you will not find your answers here.",
+ )
+
+ # get the images for the current page
+ images = (
+ db_session.query(
+ db.Posts.filename,
+ db.Posts.alt,
+ db.Posts.colours,
+ db.Posts.created_at,
+ db.Posts.id,
+ )
+ .order_by(db.Posts.id.desc())
+ .offset((page - 1) * limit)
+ .limit(limit)
+ .all()
+ )
+
+ return render_template(
+ "index.html", images=images, total_images=total_images, pages=pages, page=page
+ )
diff --git a/gallery/views/profile.py b/gallery/views/profile.py
new file mode 100644
index 0000000..0022e7e
--- /dev/null
+++ b/gallery/views/profile.py
@@ -0,0 +1,39 @@
+"""
+Onlylegs Gallery - Profile view
+"""
+from flask import Blueprint, render_template, request
+from werkzeug.exceptions import abort
+from flask_login import current_user
+
+from sqlalchemy.orm import sessionmaker
+from gallery import db
+
+
+blueprint = Blueprint("profile", __name__, url_prefix="/profile")
+db_session = sessionmaker(bind=db.engine)
+db_session = db_session()
+
+
+@blueprint.route("/profile")
+def profile():
+ """
+ Profile overview, shows all profiles on the onlylegs gallery
+ """
+ user_id = request.args.get("id", default=None, type=int)
+
+ # If there is no userID set, check if the user is logged in and display their profile
+ if not user_id:
+ if current_user.is_authenticated:
+ user_id = current_user.id
+ else:
+ abort(404, "You must be logged in to view your own profile!")
+
+ # Get the user's data
+ user = db_session.query(db.Users).filter(db.Users.id == user_id).first()
+
+ if not user:
+ abort(404, "User not found :c")
+
+ images = db_session.query(db.Posts).filter(db.Posts.author_id == user_id).all()
+
+ return render_template("profile.html", user=user, images=images)
diff --git a/gallery/views/routing.py b/gallery/views/routing.py
deleted file mode 100644
index a398e54..0000000
--- a/gallery/views/routing.py
+++ /dev/null
@@ -1,102 +0,0 @@
-"""
-Onlylegs Gallery - Routing
-"""
-from flask import Blueprint, render_template, url_for, request
-from werkzeug.exceptions import abort
-from flask_login import current_user
-
-from sqlalchemy.orm import sessionmaker
-from gallery import db
-
-
-blueprint = Blueprint('gallery', __name__)
-db_session = sessionmaker(bind=db.engine)
-db_session = db_session()
-
-
-@blueprint.route('/')
-def index():
- """
- Home page of the website, shows the feed of the latest images
- """
- images = db_session.query(db.Posts.filename,
- db.Posts.alt,
- db.Posts.colours,
- db.Posts.created_at,
- db.Posts.id).order_by(db.Posts.id.desc()).all()
-
- if request.args.get('coffee') == 'please':
- abort(418)
-
- return render_template('index.html', images=images)
-
-
-@blueprint.route('/image/')
-def image(image_id):
- """
- Image view, shows the image and its metadata
- """
- # Get the image, if it doesn't exist, 404
- image = db_session.query(db.Posts).filter(db.Posts.id == image_id).first()
- if not image:
- abort(404, 'Image not found :<')
-
- # Get the image's author username
- image.author_username = (db_session.query(db.Users.username)
- .filter(db.Users.id == image.author_id)
- .first()[0])
-
- # Get the image's groups
- groups = (db_session.query(db.GroupJunction.group_id)
- .filter(db.GroupJunction.post_id == image_id)
- .all())
-
- # For each group, get the group data and add it to the image item
- image.groups = []
- for group in groups:
- image.groups.append(db_session.query(db.Groups.id, db.Groups.name)
- .filter(db.Groups.id == group[0])
- .first())
-
- # Get the next and previous images
- next_url = (db_session.query(db.Posts.id)
- .filter(db.Posts.id > image_id)
- .order_by(db.Posts.id.asc())
- .first())
- prev_url = (db_session.query(db.Posts.id)
- .filter(db.Posts.id < image_id)
- .order_by(db.Posts.id.desc())
- .first())
-
- # If there is a next or previous image, get the url
- if next_url:
- next_url = url_for('gallery.image', image_id=next_url[0])
- if prev_url:
- prev_url = url_for('gallery.image', image_id=prev_url[0])
-
- return render_template('image.html', image=image, next_url=next_url, prev_url=prev_url)
-
-
-@blueprint.route('/profile')
-def profile():
- """
- Profile overview, shows all profiles on the onlylegs gallery
- """
- user_id = request.args.get('id', default=None, type=int)
-
- # If there is no userID set, check if the user is logged in and display their profile
- if not user_id:
- if current_user.is_authenticated:
- user_id = current_user.id
- else:
- abort(404, 'You must be logged in to view your own profile!')
-
- # Get the user's data
- user = db_session.query(db.Users).filter(db.Users.id == user_id).first()
-
- if not user:
- abort(404, 'User not found :c')
-
- images = db_session.query(db.Posts).filter(db.Posts.author_id == user_id).all()
-
- return render_template('profile.html', user=user, images=images)
diff --git a/gallery/views/settings.py b/gallery/views/settings.py
index 94926e9..97a48d5 100644
--- a/gallery/views/settings.py
+++ b/gallery/views/settings.py
@@ -4,40 +4,40 @@ OnlyLegs - Settings page
from flask import Blueprint, render_template
from flask_login import login_required
-blueprint = Blueprint('settings', __name__, url_prefix='/settings')
+blueprint = Blueprint("settings", __name__, url_prefix="/settings")
-@blueprint.route('/')
+@blueprint.route("/")
@login_required
def general():
"""
General settings page
"""
- return render_template('settings/general.html')
+ return render_template("settings/general.html")
-@blueprint.route('/server')
+@blueprint.route("/server")
@login_required
def server():
"""
Server settings page
"""
- return render_template('settings/server.html')
+ return render_template("settings/server.html")
-@blueprint.route('/account')
+@blueprint.route("/account")
@login_required
def account():
"""
Account settings page
"""
- return render_template('settings/account.html')
+ return render_template("settings/account.html")
-@blueprint.route('/logs')
+@blueprint.route("/logs")
@login_required
def logs():
"""
Logs settings page
"""
- return render_template('settings/logs.html')
+ return render_template("settings/logs.html")
diff --git a/poetry.lock b/poetry.lock
index 26dbdbd..40ba6db 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -166,6 +166,17 @@ files = [
[package.dependencies]
Pillow = "*"
+[[package]]
+name = "cssmin"
+version = "0.2.0"
+description = "A Python port of the YUI CSS compression algorithm."
+category = "main"
+optional = false
+python-versions = "*"
+files = [
+ {file = "cssmin-0.2.0.tar.gz", hash = "sha256:e012f0cc8401efcf2620332339011564738ae32be8c84b2e43ce8beaec1067b6"},
+]
+
[[package]]
name = "dill"
version = "0.3.6"
@@ -431,6 +442,17 @@ MarkupSafe = ">=2.0"
[package.extras]
i18n = ["Babel (>=2.7)"]
+[[package]]
+name = "jsmin"
+version = "3.0.1"
+description = "JavaScript minifier."
+category = "main"
+optional = false
+python-versions = "*"
+files = [
+ {file = "jsmin-3.0.1.tar.gz", hash = "sha256:c0959a121ef94542e807a674142606f7e90214a2b3d1eb17300244bbb5cc2bfc"},
+]
+
[[package]]
name = "lazy-object-proxy"
version = "1.9.0"
@@ -1020,4 +1042,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more
[metadata]
lock-version = "2.0"
python-versions = "^3.8"
-content-hash = "431b58579dc3ebde52c8f3905c851556a465d5a82796a8a26718d69cb4915959"
+content-hash = "58c3430743ce1cfd8e5b89db371a0d454a478cbe79ced08c645b4628980ca9f1"
diff --git a/pyproject.toml b/pyproject.toml
index 4620353..1988cbb 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "onlylegs"
-version = "23.04.05"
+version = "23.04.08"
description = "Gallery built for fast and simple image management"
authors = ["Fluffy-Bean "]
license = "MIT"
@@ -22,6 +22,8 @@ colorthief = "^0.2.1"
Pillow = "^9.4.0"
platformdirs = "^3.0.0"
pylint = "^2.16.3"
+jsmin = "^3.0.1"
+cssmin = "^0.2.0"
[build-system]
requires = ["poetry-core"]
diff --git a/run.py b/run.py
index 89de085..27cdc1d 100644
--- a/run.py
+++ b/run.py
@@ -5,7 +5,8 @@ from setup.args import PORT, ADDRESS, WORKERS, DEBUG
from setup.configuration import Configuration
-print("""
+print(
+ """
:::::::: :::: ::: ::: ::: ::: ::: ::::::::: ::::::::: ::::::::
:+: :+: :+:+: :+: :+: :+: :+: :+: :+: :+: :+: :+: :+:
+:+ +:+ :+:+:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+
@@ -14,8 +15,9 @@ print("""
#+# #+# #+# #+#+# #+# #+# #+# #+# #+# #+# #+# #+#
######## ### #### ########## ### ########## ######### ######### ########
- Created by Fluffy Bean - Version 23.04.05
-""")
+ Created by Fluffy Bean - Version 23.04.08
+"""
+)
# Run pre-startup checks and load configuration
@@ -24,6 +26,7 @@ Configuration()
if DEBUG:
from gallery import create_app
+
create_app().run(host=ADDRESS, port=PORT, debug=True, threaded=True)
else:
from setup.runner import OnlyLegs # pylint: disable=C0412
@@ -33,8 +36,8 @@ else:
sys.argv = [sys.argv[0]]
options = {
- 'bind': f'{ADDRESS}:{PORT}',
- 'workers': WORKERS,
+ "bind": f"{ADDRESS}:{PORT}",
+ "workers": WORKERS,
}
OnlyLegs(options).run()
diff --git a/setup/args.py b/setup/args.py
index bfc299f..a443338 100644
--- a/setup/args.py
+++ b/setup/args.py
@@ -13,11 +13,17 @@ Startup arguments for the OnlyLegs gallery
import argparse
-parser = argparse.ArgumentParser(description='Run the OnlyLegs gallery')
-parser.add_argument('-p', '--port', type=int, default=5000, help='Port to run on')
-parser.add_argument('-a', '--address', type=str, default='127.0.0.0', help='Address to run on')
-parser.add_argument('-w', '--workers', type=int, default=4, help='Number of workers to run')
-parser.add_argument('-d', '--debug', action='store_true', help='Run as Flask app in debug mode')
+parser = argparse.ArgumentParser(description="Run the OnlyLegs gallery")
+parser.add_argument("-p", "--port", type=int, default=5000, help="Port to run on")
+parser.add_argument(
+ "-a", "--address", type=str, default="127.0.0.0", help="Address to run on"
+)
+parser.add_argument(
+ "-w", "--workers", type=int, default=4, help="Number of workers to run"
+)
+parser.add_argument(
+ "-d", "--debug", action="store_true", help="Run as Flask app in debug mode"
+)
args = parser.parse_args()
diff --git a/setup/configuration.py b/setup/configuration.py
index 8968379..d01ddf9 100644
--- a/setup/configuration.py
+++ b/setup/configuration.py
@@ -9,13 +9,14 @@ import platformdirs
import yaml
-USER_DIR = platformdirs.user_config_dir('onlylegs')
+USER_DIR = platformdirs.user_config_dir("onlylegs")
class Configuration:
"""
Setup the application on first run
"""
+
def __init__(self):
"""
Main setup function
@@ -27,11 +28,11 @@ class Configuration:
self.make_dir()
# Check if the .env file exists
- if not os.path.exists(os.path.join(USER_DIR, '.env')):
+ if not os.path.exists(os.path.join(USER_DIR, ".env")):
self.make_env()
# Check if the conf.yml file exists
- if not os.path.exists(os.path.join(USER_DIR, 'conf.yml')):
+ if not os.path.exists(os.path.join(USER_DIR, "conf.yml")):
self.make_yaml()
# Load the config files
@@ -43,8 +44,8 @@ class Configuration:
Create the user directory
"""
os.makedirs(USER_DIR)
- os.makedirs(os.path.join(USER_DIR, 'instance'))
- os.makedirs(os.path.join(USER_DIR, 'uploads'))
+ os.makedirs(os.path.join(USER_DIR, "instance"))
+ os.makedirs(os.path.join(USER_DIR, "uploads"))
print("Created user directory at:", USER_DIR)
@@ -54,21 +55,23 @@ class Configuration:
Create the .env file with default values
"""
env_conf = {
- 'FLASK_SECRET': os.urandom(32).hex(),
+ "FLASK_SECRET": os.urandom(32).hex(),
}
- with open(os.path.join(USER_DIR, '.env'), encoding='utf-8', mode='w+') as file:
+ with open(os.path.join(USER_DIR, ".env"), encoding="utf-8", mode="w+") as file:
for key, value in env_conf.items():
file.write(f"{key}={value}\n")
- print("""
+ print(
+ """
####################################################
# A NEW KEY WAS GENERATED FOR YOU! PLEASE NOTE #
# DOWN THE FLASK_SECRET KEY LOCATED IN YOUR #
# .config/onlylegs/.env FOLDER! LOOSING THIS KEY #
# WILL RESULT IN YOU BEING UNABLE TO LOG IN! #
####################################################
- """)
+ """
+ )
@staticmethod
def make_yaml():
@@ -76,8 +79,8 @@ class Configuration:
Create the YAML config file with default values
"""
is_correct = False
- email_regex = re.compile(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b')
- username_regex = re.compile(r'\b[A-Za-z0-9._%+-]+\b')
+ email_regex = re.compile(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b")
+ username_regex = re.compile(r"\b[A-Za-z0-9._%+-]+\b")
print("\nNo config file found, please enter the following information:")
while not is_correct:
@@ -99,46 +102,52 @@ class Configuration:
continue
# Check if user is happy with the values
- if input("Is this correct? (y/n): ").lower() == 'y':
+ if input("Is this correct? (y/n): ").lower() == "y":
is_correct = True
yaml_conf = {
- 'admin': {
- 'name': name,
- 'username': username,
- 'email': email,
+ "admin": {
+ "name": name,
+ "username": username,
+ "email": email,
},
- 'upload': {
- 'allowed-extensions': {
- 'jpg': 'jpeg',
- 'jpeg': 'jpeg',
- 'png': 'png',
- 'webp': 'webp',
+ "upload": {
+ "allowed-extensions": {
+ "jpg": "jpeg",
+ "jpeg": "jpeg",
+ "png": "png",
+ "webp": "webp",
},
- 'max-size': 69,
- 'rename': 'GWA_{{username}}_{{time}}',
+ "max-size": 69,
+ "max-load": 50,
+ "rename": "GWA_{{username}}_{{time}}",
+ },
+ "website": {
+ "name": "OnlyLegs",
+ "motto": "A gallery built for fast and simple image management!",
+ "language": "en",
},
- 'website': {
- 'name': 'OnlyLegs',
- 'motto': 'A gallery built for fast and simple image management!',
- 'language': 'en',
- }
}
- with open(os.path.join(USER_DIR, 'conf.yml'), encoding='utf-8', mode='w+') as file:
+ with open(
+ os.path.join(USER_DIR, "conf.yml"), encoding="utf-8", mode="w+"
+ ) as file:
yaml.dump(yaml_conf, file, default_flow_style=False)
- print("Generated config file, you can change these values in the settings of the app")
+ print(
+ "Generated config file, you can change these values in the settings of the app"
+ )
@staticmethod
def logging_config():
"""
Set the logging config
"""
- logging.getLogger('werkzeug').disabled = True
+ logging.getLogger("werkzeug").disabled = True
logging.basicConfig(
- filename=os.path.join(USER_DIR, 'only.log'),
+ filename=os.path.join(USER_DIR, "only.log"),
level=logging.INFO,
- datefmt='%Y-%m-%d %H:%M:%S',
- format='%(asctime)s %(levelname)s %(name)s %(threadName)s : %(message)s',
- encoding='utf-8')
+ datefmt="%Y-%m-%d %H:%M:%S",
+ format="%(asctime)s %(levelname)s %(name)s %(threadName)s : %(message)s",
+ encoding="utf-8",
+ )
diff --git a/setup/runner.py b/setup/runner.py
index 20496d2..8a4f052 100644
--- a/setup/runner.py
+++ b/setup/runner.py
@@ -9,6 +9,7 @@ class OnlyLegs(Application):
"""
Gunicorn application
"""
+
def __init__(self, options={}): # pylint: disable=W0102, W0231
self.usage = None
self.callable = None
@@ -27,7 +28,7 @@ class OnlyLegs(Application):
@staticmethod
def prog(): # pylint: disable=C0116, E0202
- return 'OnlyLegs'
+ return "OnlyLegs"
def load(self):
- return util.import_app('gallery:create_app()')
+ return util.import_app("gallery:create_app()")