Compare commits
No commits in common. "main" and "v1.0.1" have entirely different histories.
30
app.py
30
app.py
@ -331,7 +331,6 @@ def admin():
|
|||||||
photos=photos,
|
photos=photos,
|
||||||
accent_color=config["appearance"]["accent_color"],
|
accent_color=config["appearance"]["accent_color"],
|
||||||
config=config,
|
config=config,
|
||||||
nonce=g.csp_nonce,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@app.route("/admin/logout")
|
@app.route("/admin/logout")
|
||||||
@ -462,22 +461,17 @@ def admin_upload():
|
|||||||
file_path = os.path.join(app.config["UPLOAD_FOLDER"], filename)
|
file_path = os.path.join(app.config["UPLOAD_FOLDER"], filename)
|
||||||
file.save(file_path)
|
file.save(file_path)
|
||||||
|
|
||||||
# Extract EXIF data with error handling
|
# Extract EXIF data
|
||||||
exif = {}
|
exif = None
|
||||||
exifraw = None
|
exifraw = None
|
||||||
width = height = 0
|
|
||||||
try:
|
|
||||||
with Image.open(file_path) as img:
|
with Image.open(file_path) as img:
|
||||||
|
exifraw = img.info["exif"]
|
||||||
width, height = img.size
|
width, height = img.size
|
||||||
if hasattr(img, '_getexif') and img._getexif() is not None:
|
|
||||||
exifraw = img.info.get("exif")
|
|
||||||
exif = {
|
exif = {
|
||||||
ExifTags.TAGS[k]: v
|
ExifTags.TAGS[k]: v
|
||||||
for k, v in img._getexif().items()
|
for k, v in img._getexif().items()
|
||||||
if k in ExifTags.TAGS
|
if k in ExifTags.TAGS
|
||||||
}
|
}
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"Error reading EXIF data for {filename}: {str(e)}")
|
|
||||||
|
|
||||||
# Generate a unique key for the image
|
# Generate a unique key for the image
|
||||||
unique_key = hashlib.sha256(
|
unique_key = hashlib.sha256(
|
||||||
@ -495,25 +489,25 @@ def admin_upload():
|
|||||||
# Generate thumbnails
|
# Generate thumbnails
|
||||||
generate_thumbnails(filename)
|
generate_thumbnails(filename)
|
||||||
|
|
||||||
# Handle exposure time with error handling
|
# Get image dimensions
|
||||||
try:
|
with Image.open(file_path) as img:
|
||||||
exposure_time = exif.get("ExposureTime", 0)
|
width, height = img.size
|
||||||
|
|
||||||
|
exposure_time = exif["ExposureTime"]
|
||||||
if isinstance(exposure_time, tuple):
|
if isinstance(exposure_time, tuple):
|
||||||
exposure_fraction = f"{exposure_time[0]}/{exposure_time[1]}"
|
exposure_fraction = f"{exposure_time[0]}/{exposure_time[1]}"
|
||||||
else:
|
else:
|
||||||
exposure_fraction = f"1/{int(1/float(exposure_time))}" if exposure_time else "0"
|
exposure_fraction = f"1/{int(1/float(exposure_time))}"
|
||||||
except (TypeError, ZeroDivisionError):
|
|
||||||
exposure_fraction = "0"
|
|
||||||
|
|
||||||
# Create database entry with safe defaults
|
# Create database entry
|
||||||
db_session = DBSession()
|
db_session = DBSession()
|
||||||
new_photo = Photo(
|
new_photo = Photo(
|
||||||
input_filename=filename,
|
input_filename=filename,
|
||||||
thumbnail_filename=f"{os.path.splitext(filename)[0]}/256_{filename}",
|
thumbnail_filename=f"{os.path.splitext(filename)[0]}/256_{filename}",
|
||||||
focal_length=str(
|
focal_length=str(
|
||||||
exif.get("FocalLengthIn35mmFilm", exif.get("FocalLength", "0"))
|
exif.get("FocalLengthIn35mmFilm", exif.get("FocalLength", ""))
|
||||||
),
|
),
|
||||||
aperture=str(exif.get("FNumber", "0")),
|
aperture=str(exif.get("FNumber", "")),
|
||||||
shutter_speed=exposure_fraction,
|
shutter_speed=exposure_fraction,
|
||||||
date_taken=datetime.strptime(
|
date_taken=datetime.strptime(
|
||||||
str(exif.get("DateTime", "1970:01:01 00:00:00")), "%Y:%m:%d %H:%M:%S"
|
str(exif.get("DateTime", "1970:01:01 00:00:00")), "%Y:%m:%d %H:%M:%S"
|
||||||
|
@ -203,8 +203,8 @@
|
|||||||
<td class="editable" data-field="iso">{{ photo.iso }}</td>
|
<td class="editable" data-field="iso">{{ photo.iso }}</td>
|
||||||
<td>{{ photo.width }}x{{ photo.height }}</td>
|
<td>{{ photo.width }}x{{ photo.height }}</td>
|
||||||
<td>
|
<td>
|
||||||
<button class="save-btn">Save</button>
|
<button onclick="saveChanges(this)">Save</button>
|
||||||
<button class="delete-btn">Delete</button>
|
<button onclick="deletePhoto(this)" class="delete-btn">Delete</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@ -241,11 +241,8 @@
|
|||||||
<input type="text" id="about.location" name="about.location" value="{{ config.about.location }}">
|
<input type="text" id="about.location" name="about.location" value="{{ config.about.location }}">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="about.profile_image">Profile Image:</label>
|
<label for="about.profile_image">Profile Image Path:</label>
|
||||||
<div style="display: flex; align-items: center; gap: 1rem;">
|
<input type="text" id="about.profile_image" name="about.profile_image" value="{{ config.about.profile_image }}">
|
||||||
<img id="profile-preview" src="/static/profile.jpeg" alt="Profile" style="width: 100px; height: 100px; object-fit: cover; border-radius: 50%;">
|
|
||||||
<input type="file" class="profile-image-upload" accept="image/jpeg,image/png" style="flex: 1;">
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="about.bio">Bio (Markdown):</label>
|
<label for="about.bio">Bio (Markdown):</label>
|
||||||
@ -261,7 +258,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script nonce="{{ nonce }}">
|
<script nonce="{{ g.csp_nonce }}">
|
||||||
function makeEditable(element) {
|
function makeEditable(element) {
|
||||||
const value = element.textContent;
|
const value = element.textContent;
|
||||||
const input = document.createElement('input');
|
const input = document.createElement('input');
|
||||||
@ -284,8 +281,7 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function saveChanges(event) {
|
function saveChanges(button) {
|
||||||
const button = event.target;
|
|
||||||
const row = button.closest('tr');
|
const row = button.closest('tr');
|
||||||
const photoId = row.dataset.id;
|
const photoId = row.dataset.id;
|
||||||
const updatedData = {};
|
const updatedData = {};
|
||||||
@ -315,9 +311,8 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function deletePhoto(event) {
|
function deletePhoto(button) {
|
||||||
if (confirm('Are you sure you want to delete this photo?')) {
|
if (confirm('Are you sure you want to delete this photo?')) {
|
||||||
const button = event.target;
|
|
||||||
const row = button.closest('tr');
|
const row = button.closest('tr');
|
||||||
const photoId = row.dataset.id;
|
const photoId = row.dataset.id;
|
||||||
|
|
||||||
@ -340,45 +335,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.querySelectorAll('.delete-btn').forEach(button => {
|
|
||||||
button.addEventListener('click', (event) => deletePhoto(event));
|
|
||||||
});
|
|
||||||
|
|
||||||
document.querySelectorAll('.save-btn').forEach(button => {
|
|
||||||
button.addEventListener('click', (event) => saveChanges(event));
|
|
||||||
});
|
|
||||||
|
|
||||||
document.querySelectorAll('.profile-image-upload').forEach(input => {
|
|
||||||
input.addEventListener('change', async (e) => {
|
|
||||||
const file = e.target.files[0];
|
|
||||||
if (!file) return;
|
|
||||||
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('profile_image', file);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('/admin/upload_profile', {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await response.json();
|
|
||||||
if (result.success) {
|
|
||||||
document.getElementById('profile-preview').src = '/static/profile.jpeg?' + new Date().getTime();
|
|
||||||
} else {
|
|
||||||
alert('Error uploading profile image: ' + result.error);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
alert('Error uploading profile image: ' + error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('configForm').addEventListener('submit', async (e) => {
|
document.getElementById('configForm').addEventListener('submit', async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const formData = {};
|
const formData = {};
|
||||||
const inputs = e.target.querySelectorAll('input:not([type="file"]), textarea');
|
const inputs = e.target.querySelectorAll('input, textarea');
|
||||||
|
|
||||||
inputs.forEach(input => {
|
inputs.forEach(input => {
|
||||||
formData[input.name] = input.value;
|
formData[input.name] = input.value;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user