Files
AdventureLog/backend/server/main/admin_utils.py
Sean Morley db9f4a66f6 Add PDF generation for collections and enhance admin functionality
- Implemented PDF generation for collections, allowing users to create printable itineraries.
- Added support for Noto fonts to ensure proper rendering of text and emojis in PDFs.
- Enhanced admin interfaces for achievements and adventures, improving usability and data management.
- Introduced new fields and methods in models and serializers to support geolocation features and improve data handling.
- Updated migrations to transition from latitude/longitude fields to a unified PointField for geographic data.
- Added tests to validate PDF generation functionality and ensure reliability.
2026-06-03 17:39:24 -04:00

92 lines
2.4 KiB
Python

"""Shared Django admin helpers for AdventureLog."""
from __future__ import annotations
from django.contrib import admin
from django.contrib.gis import admin as gis_admin
from django.utils.html import format_html
from adventures.utils.geo import point_to_lat_lon
from main.utils import build_media_url
TIMESTAMP_READONLY = ('created_at', 'updated_at')
UUID_READONLY = ('id',)
def coordinate_display(point, precision: int = 5) -> str:
lat, lon = point_to_lat_lon(point)
if lat is None or lon is None:
return ''
return f'{lat:.{precision}f}, {lon:.{precision}f}'
def admin_image_preview(image_field, width: int = 100, height: int = 100):
if not image_field:
return ''
image_url = build_media_url(image_field.name)
if not image_url:
return ''
return format_html(
'<img src="{}" width="{}" height="{}" style="object-fit:cover;border-radius:4px;" />',
image_url,
width,
height,
)
class TimestampedAdminMixin:
readonly_fields = TIMESTAMP_READONLY
class AdventureLogGeoModelAdmin(gis_admin.GISModelAdmin):
"""GISModelAdmin with OSM map widgets for PointFields."""
gis_widget_kwargs = {
'attrs': {
'default_lon': 0,
'default_lat': 30,
'default_zoom': 2,
},
}
class CoordinatesDisplayMixin:
coordinates_field = 'coordinates'
@admin.display(description='Coordinates')
def display_coordinates(self, obj):
return coordinate_display(getattr(obj, self.coordinates_field, None))
@admin.display(description='Lat')
def display_latitude(self, obj):
lat, _ = point_to_lat_lon(getattr(obj, self.coordinates_field, None))
return lat
@admin.display(description='Lon')
def display_longitude(self, obj):
_, lon = point_to_lat_lon(getattr(obj, self.coordinates_field, None))
return lon
class UserOwnedAdminMixin:
list_filter = ('user',)
autocomplete_fields = ('user',)
search_fields = ('name',)
def generic_object_admin_link(obj, attr: str = 'item') -> str:
linked = getattr(obj, attr, None)
if not linked:
return ''
try:
from django.urls import reverse
ct = obj.content_type
admin_url = reverse(
f'admin:{ct.app_label}_{ct.model}_change',
args=[obj.object_id],
)
return format_html('<a href="{}">{}</a>', admin_url, str(linked))
except Exception:
return str(linked)