mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 07:31:07 -05:00
ok
This commit is contained in:
@@ -1,26 +1,31 @@
|
|||||||
from django import template
|
from django import template
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.db.models import Model
|
||||||
|
from typing import Optional, Dict, Any, List, Union
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
def get_object_name(value, model_path):
|
def get_object_name(value: Optional[int], model_path: str) -> Optional[str]:
|
||||||
"""Get object name from ID and model path."""
|
"""Get object name from ID and model path."""
|
||||||
if not value:
|
if not value or not model_path or '.' not in model_path:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
app_label, model = model_path.split('.')
|
app_label, model = model_path.split('.')
|
||||||
try:
|
try:
|
||||||
content_type = ContentType.objects.get(app_label=app_label.lower(), model=model.lower())
|
content_type = ContentType.objects.get(app_label=app_label.lower(), model=model.lower())
|
||||||
model_class = content_type.model_class()
|
model_class = content_type.model_class()
|
||||||
obj = model_class.objects.get(id=value)
|
if not model_class:
|
||||||
return str(obj)
|
return None
|
||||||
|
|
||||||
|
obj = model_class.objects.filter(id=value).first()
|
||||||
|
return str(obj) if obj else None
|
||||||
except Exception:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
def get_category_display(value):
|
def get_category_display(value: Optional[str]) -> Optional[str]:
|
||||||
"""Get display value for ride category."""
|
"""Get display value for ride category."""
|
||||||
if not value:
|
if not value:
|
||||||
return None
|
return None
|
||||||
@@ -33,24 +38,25 @@ def get_category_display(value):
|
|||||||
'TR': 'Transport',
|
'TR': 'Transport',
|
||||||
'OT': 'Other'
|
'OT': 'Other'
|
||||||
}
|
}
|
||||||
return categories.get(value, value)
|
return categories.get(value)
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
def get_park_area_name(value, park_id):
|
def get_park_area_name(value: Optional[int], park_id: Optional[int]) -> Optional[str]:
|
||||||
"""Get park area name from ID and park ID."""
|
"""Get park area name from ID and park ID."""
|
||||||
if not value or not park_id:
|
if not value or not park_id:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from parks.models import ParkArea
|
from parks.models import ParkArea
|
||||||
area = ParkArea.objects.get(id=value, park_id=park_id)
|
area = ParkArea.objects.filter(id=value, park_id=park_id).first()
|
||||||
return str(area)
|
return str(area) if area else None
|
||||||
except Exception:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
def get_item(dictionary, key):
|
def get_item(dictionary: Optional[Dict[str, Any]], key: Optional[Union[str, int]]) -> List[Any]:
|
||||||
"""Get item from dictionary by key."""
|
"""Get item from dictionary by key."""
|
||||||
if not dictionary or not key:
|
if not dictionary or not isinstance(dictionary, dict) or not key:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
return dictionary.get(str(key), [])
|
return dictionary.get(str(key), [])
|
||||||
|
|||||||
@@ -4,9 +4,12 @@ from django.http import HttpResponse, JsonResponse, HttpRequest
|
|||||||
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.db.models import Q
|
from django.db.models import Q, QuerySet
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from typing import Optional, Any, cast
|
from typing import Optional, Any, Dict, List, Tuple, Union, cast
|
||||||
|
from django.db import models
|
||||||
|
from django.core.serializers.json import DjangoJSONEncoder
|
||||||
|
import json
|
||||||
from accounts.models import User
|
from accounts.models import User
|
||||||
|
|
||||||
from .models import EditSubmission, PhotoSubmission
|
from .models import EditSubmission, PhotoSubmission
|
||||||
@@ -14,6 +17,7 @@ from parks.models import Park, ParkArea
|
|||||||
from designers.models import Designer
|
from designers.models import Designer
|
||||||
from companies.models import Manufacturer
|
from companies.models import Manufacturer
|
||||||
from rides.models import RideModel
|
from rides.models import RideModel
|
||||||
|
from location.models import Location
|
||||||
|
|
||||||
MODERATOR_ROLES = ['MODERATOR', 'ADMIN', 'SUPERUSER']
|
MODERATOR_ROLES = ['MODERATOR', 'ADMIN', 'SUPERUSER']
|
||||||
|
|
||||||
@@ -23,16 +27,53 @@ class ModeratorRequiredMixin(UserPassesTestMixin):
|
|||||||
def test_func(self) -> bool:
|
def test_func(self) -> bool:
|
||||||
"""Check if user has moderator permissions."""
|
"""Check if user has moderator permissions."""
|
||||||
user = cast(User, self.request.user)
|
user = cast(User, self.request.user)
|
||||||
return (
|
return user.is_authenticated and (user.role in MODERATOR_ROLES or user.is_superuser)
|
||||||
user.is_authenticated and
|
|
||||||
(getattr(user, 'role', None) in MODERATOR_ROLES or user.is_superuser)
|
|
||||||
)
|
|
||||||
|
|
||||||
def handle_no_permission(self) -> HttpResponse:
|
def handle_no_permission(self) -> HttpResponse:
|
||||||
if not self.request.user.is_authenticated:
|
if not self.request.user.is_authenticated:
|
||||||
return super().handle_no_permission()
|
return super().handle_no_permission()
|
||||||
raise PermissionDenied("You do not have moderator permissions.")
|
raise PermissionDenied("You do not have moderator permissions.")
|
||||||
|
|
||||||
|
def get_filtered_queryset(request: HttpRequest, status: str, submission_type: str) -> QuerySet:
|
||||||
|
"""Get filtered queryset based on request parameters."""
|
||||||
|
if submission_type == 'photo':
|
||||||
|
return PhotoSubmission.objects.filter(status=status).order_by('-created_at')
|
||||||
|
|
||||||
|
queryset = EditSubmission.objects.filter(status=status).order_by('-created_at')
|
||||||
|
|
||||||
|
if type_filter := request.GET.get('type'):
|
||||||
|
queryset = queryset.filter(submission_type=type_filter)
|
||||||
|
|
||||||
|
if content_type := request.GET.get('content_type'):
|
||||||
|
queryset = queryset.filter(content_type__model=content_type)
|
||||||
|
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
def get_context_data(request: HttpRequest, queryset: QuerySet) -> Dict[str, Any]:
|
||||||
|
"""Get common context data for views."""
|
||||||
|
park_areas_by_park: Dict[int, List[Tuple[int, str]]] = {}
|
||||||
|
|
||||||
|
if isinstance(queryset.first(), EditSubmission):
|
||||||
|
for submission in queryset:
|
||||||
|
if (submission.content_type.model == 'park' and
|
||||||
|
isinstance(submission.changes, dict) and
|
||||||
|
'park' in submission.changes):
|
||||||
|
park_id = submission.changes['park']
|
||||||
|
if park_id not in park_areas_by_park:
|
||||||
|
areas = ParkArea.objects.filter(park_id=park_id)
|
||||||
|
park_areas_by_park[park_id] = [(area.pk, str(area)) for area in areas]
|
||||||
|
|
||||||
|
return {
|
||||||
|
'submissions': queryset,
|
||||||
|
'user': request.user,
|
||||||
|
'parks': [(park.pk, str(park)) for park in Park.objects.all()],
|
||||||
|
'designers': [(designer.pk, str(designer)) for designer in Designer.objects.all()],
|
||||||
|
'manufacturers': [(manufacturer.pk, str(manufacturer)) for manufacturer in Manufacturer.objects.all()],
|
||||||
|
'ride_models': [(model.pk, str(model)) for model in RideModel.objects.all()],
|
||||||
|
'owners': [(user.pk, str(user)) for user in User.objects.filter(role__in=['OWNER', 'ADMIN', 'SUPERUSER'])],
|
||||||
|
'park_areas_by_park': park_areas_by_park
|
||||||
|
}
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def search_parks(request: HttpRequest) -> HttpResponse:
|
def search_parks(request: HttpRequest) -> HttpResponse:
|
||||||
"""HTMX endpoint for searching parks in moderation dashboard"""
|
"""HTMX endpoint for searching parks in moderation dashboard"""
|
||||||
@@ -43,19 +84,16 @@ def search_parks(request: HttpRequest) -> HttpResponse:
|
|||||||
query = request.GET.get('q', '').strip()
|
query = request.GET.get('q', '').strip()
|
||||||
submission_id = request.GET.get('submission_id')
|
submission_id = request.GET.get('submission_id')
|
||||||
|
|
||||||
# If no query, show first 10 parks
|
parks = Park.objects.all().order_by('name')
|
||||||
if not query:
|
if query:
|
||||||
parks = Park.objects.all().order_by('name')[:10]
|
parks = parks.filter(name__icontains=query)
|
||||||
else:
|
parks = parks[:10]
|
||||||
parks = Park.objects.filter(name__icontains=query).order_by('name')[:10]
|
|
||||||
|
|
||||||
context = {
|
return render(request, 'moderation/partials/park_search_results.html', {
|
||||||
'parks': parks,
|
'parks': parks,
|
||||||
'search_term': query,
|
'search_term': query,
|
||||||
'submission_id': submission_id
|
'submission_id': submission_id
|
||||||
}
|
})
|
||||||
|
|
||||||
return render(request, 'moderation/partials/park_search_results.html', context)
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def search_manufacturers(request: HttpRequest) -> HttpResponse:
|
def search_manufacturers(request: HttpRequest) -> HttpResponse:
|
||||||
@@ -67,19 +105,16 @@ def search_manufacturers(request: HttpRequest) -> HttpResponse:
|
|||||||
query = request.GET.get('q', '').strip()
|
query = request.GET.get('q', '').strip()
|
||||||
submission_id = request.GET.get('submission_id')
|
submission_id = request.GET.get('submission_id')
|
||||||
|
|
||||||
# If no query, show first 10 manufacturers
|
manufacturers = Manufacturer.objects.all().order_by('name')
|
||||||
if not query:
|
if query:
|
||||||
manufacturers = Manufacturer.objects.all().order_by('name')[:10]
|
manufacturers = manufacturers.filter(name__icontains=query)
|
||||||
else:
|
manufacturers = manufacturers[:10]
|
||||||
manufacturers = Manufacturer.objects.filter(name__icontains=query).order_by('name')[:10]
|
|
||||||
|
|
||||||
context = {
|
return render(request, 'moderation/partials/manufacturer_search_results.html', {
|
||||||
'manufacturers': manufacturers,
|
'manufacturers': manufacturers,
|
||||||
'search_term': query,
|
'search_term': query,
|
||||||
'submission_id': submission_id
|
'submission_id': submission_id
|
||||||
}
|
})
|
||||||
|
|
||||||
return render(request, 'moderation/partials/manufacturer_search_results.html', context)
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def search_designers(request: HttpRequest) -> HttpResponse:
|
def search_designers(request: HttpRequest) -> HttpResponse:
|
||||||
@@ -91,19 +126,16 @@ def search_designers(request: HttpRequest) -> HttpResponse:
|
|||||||
query = request.GET.get('q', '').strip()
|
query = request.GET.get('q', '').strip()
|
||||||
submission_id = request.GET.get('submission_id')
|
submission_id = request.GET.get('submission_id')
|
||||||
|
|
||||||
# If no query, show first 10 designers
|
designers = Designer.objects.all().order_by('name')
|
||||||
if not query:
|
if query:
|
||||||
designers = Designer.objects.all().order_by('name')[:10]
|
designers = designers.filter(name__icontains=query)
|
||||||
else:
|
designers = designers[:10]
|
||||||
designers = Designer.objects.filter(name__icontains=query).order_by('name')[:10]
|
|
||||||
|
|
||||||
context = {
|
return render(request, 'moderation/partials/designer_search_results.html', {
|
||||||
'designers': designers,
|
'designers': designers,
|
||||||
'search_term': query,
|
'search_term': query,
|
||||||
'submission_id': submission_id
|
'submission_id': submission_id
|
||||||
}
|
})
|
||||||
|
|
||||||
return render(request, 'moderation/partials/designer_search_results.html', context)
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def search_ride_models(request: HttpRequest) -> HttpResponse:
|
def search_ride_models(request: HttpRequest) -> HttpResponse:
|
||||||
@@ -117,50 +149,32 @@ def search_ride_models(request: HttpRequest) -> HttpResponse:
|
|||||||
manufacturer_id = request.GET.get('manufacturer')
|
manufacturer_id = request.GET.get('manufacturer')
|
||||||
|
|
||||||
queryset = RideModel.objects.all()
|
queryset = RideModel.objects.all()
|
||||||
|
|
||||||
if manufacturer_id:
|
if manufacturer_id:
|
||||||
queryset = queryset.filter(manufacturer_id=manufacturer_id)
|
queryset = queryset.filter(manufacturer_id=manufacturer_id)
|
||||||
|
if query:
|
||||||
|
queryset = queryset.filter(name__icontains=query)
|
||||||
|
queryset = queryset.order_by('name')[:10]
|
||||||
|
|
||||||
# If no query, show first 10 models
|
return render(request, 'moderation/partials/ride_model_search_results.html', {
|
||||||
if not query:
|
'ride_models': queryset,
|
||||||
ride_models = queryset.order_by('name')[:10]
|
|
||||||
else:
|
|
||||||
ride_models = queryset.filter(name__icontains=query).order_by('name')[:10]
|
|
||||||
|
|
||||||
context = {
|
|
||||||
'ride_models': ride_models,
|
|
||||||
'search_term': query,
|
'search_term': query,
|
||||||
'submission_id': submission_id
|
'submission_id': submission_id
|
||||||
}
|
})
|
||||||
|
|
||||||
return render(request, 'moderation/partials/ride_model_search_results.html', context)
|
|
||||||
|
|
||||||
class DashboardView(LoginRequiredMixin, ModeratorRequiredMixin, ListView):
|
class DashboardView(LoginRequiredMixin, ModeratorRequiredMixin, ListView):
|
||||||
template_name = 'moderation/dashboard.html'
|
template_name = 'moderation/dashboard.html'
|
||||||
context_object_name = 'submissions'
|
context_object_name = 'submissions'
|
||||||
paginate_by = 10
|
paginate_by = 10
|
||||||
|
|
||||||
def get_template_names(self):
|
def get_template_names(self) -> List[str]:
|
||||||
if self.request.headers.get('HX-Request'):
|
if self.request.headers.get('HX-Request'):
|
||||||
return ['moderation/partials/dashboard_content.html']
|
return ['moderation/partials/dashboard_content.html']
|
||||||
return [self.template_name]
|
return [self.template_name]
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self) -> QuerySet:
|
||||||
status = self.request.GET.get('status', 'PENDING')
|
status = self.request.GET.get('status', 'PENDING')
|
||||||
submission_type = self.request.GET.get('submission_type', '')
|
submission_type = self.request.GET.get('submission_type', '')
|
||||||
|
return get_filtered_queryset(self.request, status, submission_type)
|
||||||
if submission_type == 'photo':
|
|
||||||
queryset = PhotoSubmission.objects.filter(status=status).order_by('-created_at')
|
|
||||||
else:
|
|
||||||
queryset = EditSubmission.objects.filter(status=status).order_by('-created_at')
|
|
||||||
|
|
||||||
if type_filter := self.request.GET.get('type'):
|
|
||||||
queryset = queryset.filter(submission_type=type_filter)
|
|
||||||
|
|
||||||
if content_type := self.request.GET.get('content_type'):
|
|
||||||
queryset = queryset.filter(content_type__model=content_type)
|
|
||||||
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def submission_list(request: HttpRequest) -> HttpResponse:
|
def submission_list(request: HttpRequest) -> HttpResponse:
|
||||||
@@ -172,42 +186,25 @@ def submission_list(request: HttpRequest) -> HttpResponse:
|
|||||||
status = request.GET.get('status', 'PENDING')
|
status = request.GET.get('status', 'PENDING')
|
||||||
submission_type = request.GET.get('submission_type', '')
|
submission_type = request.GET.get('submission_type', '')
|
||||||
|
|
||||||
if submission_type == 'photo':
|
queryset = get_filtered_queryset(request, status, submission_type)
|
||||||
queryset = PhotoSubmission.objects.filter(status=status).order_by('-created_at')
|
|
||||||
else:
|
|
||||||
queryset = EditSubmission.objects.filter(status=status).order_by('-created_at')
|
|
||||||
|
|
||||||
if type_filter := request.GET.get('type'):
|
# Process location data for park submissions
|
||||||
queryset = queryset.filter(submission_type=type_filter)
|
|
||||||
|
|
||||||
if content_type := request.GET.get('content_type'):
|
|
||||||
queryset = queryset.filter(content_type__model=content_type)
|
|
||||||
|
|
||||||
# Get park areas for each park submission
|
|
||||||
park_areas_by_park = {}
|
|
||||||
for submission in queryset:
|
for submission in queryset:
|
||||||
if submission.content_type.model == 'park' and submission.changes.get('park'):
|
if (submission.content_type.model == 'park' and
|
||||||
park_id = submission.changes['park']
|
isinstance(submission.changes, dict)):
|
||||||
if park_id not in park_areas_by_park:
|
# Extract location fields into a location object
|
||||||
park_areas_by_park[park_id] = [(a.id, str(a)) for a in ParkArea.objects.filter(park_id=park_id)]
|
location_fields = ['latitude', 'longitude', 'street_address', 'city', 'state', 'postal_code', 'country']
|
||||||
|
location_data = {field: submission.changes.get(field) for field in location_fields}
|
||||||
|
# Add location data back as a single object
|
||||||
|
submission.changes['location'] = location_data
|
||||||
|
|
||||||
context = {
|
context = get_context_data(request, queryset)
|
||||||
'submissions': queryset,
|
|
||||||
'user': request.user,
|
|
||||||
'parks': [(p.id, str(p)) for p in Park.objects.all()],
|
|
||||||
'designers': [(d.id, str(d)) for d in Designer.objects.all()],
|
|
||||||
'manufacturers': [(m.id, str(m)) for m in Manufacturer.objects.all()],
|
|
||||||
'ride_models': [(m.id, str(m)) for m in RideModel.objects.all()],
|
|
||||||
'owners': [(u.id, str(u)) for u in User.objects.filter(role__in=['OWNER', 'ADMIN', 'SUPERUSER'])],
|
|
||||||
'park_areas_by_park': park_areas_by_park
|
|
||||||
}
|
|
||||||
|
|
||||||
# If it's an HTMX request, return just the content
|
template_name = ('moderation/partials/dashboard_content.html'
|
||||||
if request.headers.get('HX-Request'):
|
if request.headers.get('HX-Request')
|
||||||
return render(request, 'moderation/partials/dashboard_content.html', context)
|
else 'moderation/dashboard.html')
|
||||||
|
|
||||||
# For direct URL access, return the full dashboard template
|
return render(request, template_name, context)
|
||||||
return render(request, 'moderation/dashboard.html', context)
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def edit_submission(request: HttpRequest, submission_id: int) -> HttpResponse:
|
def edit_submission(request: HttpRequest, submission_id: int) -> HttpResponse:
|
||||||
@@ -218,26 +215,47 @@ def edit_submission(request: HttpRequest, submission_id: int) -> HttpResponse:
|
|||||||
|
|
||||||
submission = get_object_or_404(EditSubmission, id=submission_id)
|
submission = get_object_or_404(EditSubmission, id=submission_id)
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method != 'POST':
|
||||||
# Handle the edit submission
|
return HttpResponse("Invalid request method", status=405)
|
||||||
|
|
||||||
notes = request.POST.get('notes')
|
notes = request.POST.get('notes')
|
||||||
if not notes:
|
if not notes:
|
||||||
return HttpResponse("Notes are required when editing a submission", status=400)
|
return HttpResponse("Notes are required when editing a submission", status=400)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Update the moderator_changes with the edited values
|
edited_changes = dict(submission.changes) if submission.changes else {}
|
||||||
edited_changes = submission.changes.copy()
|
|
||||||
for field in submission.changes.keys():
|
# Update stats if present
|
||||||
if field == 'stats':
|
if 'stats' in edited_changes:
|
||||||
edited_stats = {}
|
edited_stats = {}
|
||||||
for key in submission.changes['stats'].keys():
|
for key in edited_changes['stats']:
|
||||||
if new_value := request.POST.get(f'stats.{key}'):
|
if new_value := request.POST.get(f'stats.{key}'):
|
||||||
edited_stats[key] = new_value
|
edited_stats[key] = new_value
|
||||||
edited_changes['stats'] = edited_stats
|
edited_changes['stats'] = edited_stats
|
||||||
else:
|
|
||||||
|
# Update location fields if present
|
||||||
|
if submission.content_type.model == 'park':
|
||||||
|
location_fields = ['latitude', 'longitude', 'street_address', 'city', 'state', 'postal_code', 'country']
|
||||||
|
location_data = {}
|
||||||
|
for field in location_fields:
|
||||||
if new_value := request.POST.get(field):
|
if new_value := request.POST.get(field):
|
||||||
# Handle special field types
|
if field in ['latitude', 'longitude']:
|
||||||
if field in ['latitude', 'longitude', 'size_acres']:
|
try:
|
||||||
|
location_data[field] = float(new_value)
|
||||||
|
except ValueError:
|
||||||
|
return HttpResponse(f"Invalid value for {field}", status=400)
|
||||||
|
else:
|
||||||
|
location_data[field] = new_value
|
||||||
|
if location_data:
|
||||||
|
edited_changes.update(location_data)
|
||||||
|
|
||||||
|
# Update other fields
|
||||||
|
for field in edited_changes:
|
||||||
|
if field == 'stats' or field in ['latitude', 'longitude', 'street_address', 'city', 'state', 'postal_code', 'country']:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if new_value := request.POST.get(field):
|
||||||
|
if field in ['size_acres']:
|
||||||
try:
|
try:
|
||||||
edited_changes[field] = float(new_value)
|
edited_changes[field] = float(new_value)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
@@ -245,64 +263,48 @@ def edit_submission(request: HttpRequest, submission_id: int) -> HttpResponse:
|
|||||||
else:
|
else:
|
||||||
edited_changes[field] = new_value
|
edited_changes[field] = new_value
|
||||||
|
|
||||||
submission.moderator_changes = edited_changes
|
# Convert to JSON-serializable format
|
||||||
|
json_changes = json.loads(json.dumps(edited_changes, cls=DjangoJSONEncoder))
|
||||||
|
submission.moderator_changes = json_changes
|
||||||
submission.notes = notes
|
submission.notes = notes
|
||||||
submission.save()
|
submission.save()
|
||||||
|
|
||||||
# Return the updated submission
|
# Process location data for display
|
||||||
context = {
|
if submission.content_type.model == 'park':
|
||||||
'submission': submission,
|
location_fields = ['latitude', 'longitude', 'street_address', 'city', 'state', 'postal_code', 'country']
|
||||||
'user': request.user,
|
location_data = {field: json_changes.get(field) for field in location_fields}
|
||||||
'parks': [(p.id, str(p)) for p in Park.objects.all()],
|
# Add location data back as a single object
|
||||||
'designers': [(d.id, str(d)) for d in Designer.objects.all()],
|
json_changes['location'] = location_data
|
||||||
'manufacturers': [(m.id, str(m)) for m in Manufacturer.objects.all()],
|
submission.changes = json_changes
|
||||||
'ride_models': [(m.id, str(m)) for m in RideModel.objects.all()],
|
|
||||||
'owners': [(u.id, str(u)) for u in User.objects.filter(role__in=['OWNER', 'ADMIN', 'SUPERUSER'])],
|
context = get_context_data(request, EditSubmission.objects.filter(id=submission_id))
|
||||||
'park_areas': [(a.id, str(a)) for a in ParkArea.objects.filter(park_id=edited_changes.get('park'))] if edited_changes.get('park') else []
|
|
||||||
}
|
|
||||||
return render(request, 'moderation/partials/submission_list.html', context)
|
return render(request, 'moderation/partials/submission_list.html', context)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return HttpResponse(str(e), status=400)
|
return HttpResponse(str(e), status=400)
|
||||||
|
|
||||||
return HttpResponse("Invalid request method", status=405)
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def approve_submission(request: HttpRequest, submission_id: int) -> HttpResponse:
|
def approve_submission(request: HttpRequest, submission_id: int) -> HttpResponse:
|
||||||
"""HTMX endpoint for approving a submission"""
|
"""HTMX endpoint for approving a submission"""
|
||||||
user = cast(User, request.user)
|
user = cast(User, request.user)
|
||||||
submission = get_object_or_404(EditSubmission, id=submission_id)
|
submission = get_object_or_404(EditSubmission, id=submission_id)
|
||||||
|
|
||||||
if submission.status == 'ESCALATED' and not (user.role in ['ADMIN', 'SUPERUSER'] or user.is_superuser):
|
if not ((submission.status != 'ESCALATED' and user.role in MODERATOR_ROLES) or
|
||||||
return HttpResponse("Only admins can handle escalated submissions", status=403)
|
user.role in ['ADMIN', 'SUPERUSER'] or user.is_superuser):
|
||||||
if not (user.role in MODERATOR_ROLES or user.is_superuser):
|
return HttpResponse("Insufficient permissions", status=403)
|
||||||
return HttpResponse(status=403)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
submission.approve(user)
|
submission.approve(user)
|
||||||
_update_submission_notes(submission, request.POST.get('notes'))
|
_update_submission_notes(submission, request.POST.get('notes'))
|
||||||
|
|
||||||
# Get updated queryset with filters
|
|
||||||
status = request.GET.get('status', 'PENDING')
|
status = request.GET.get('status', 'PENDING')
|
||||||
submission_type = request.GET.get('submission_type', '')
|
submission_type = request.GET.get('submission_type', '')
|
||||||
|
queryset = get_filtered_queryset(request, status, submission_type)
|
||||||
|
|
||||||
if submission_type == 'photo':
|
return render(request, 'moderation/partials/dashboard_content.html', {
|
||||||
queryset = PhotoSubmission.objects.filter(status=status).order_by('-created_at')
|
|
||||||
else:
|
|
||||||
queryset = EditSubmission.objects.filter(status=status).order_by('-created_at')
|
|
||||||
|
|
||||||
if type_filter := request.GET.get('type'):
|
|
||||||
queryset = queryset.filter(submission_type=type_filter)
|
|
||||||
|
|
||||||
if content_type := request.GET.get('content_type'):
|
|
||||||
queryset = queryset.filter(content_type__model=content_type)
|
|
||||||
|
|
||||||
context = {
|
|
||||||
'submissions': queryset,
|
'submissions': queryset,
|
||||||
'user': request.user,
|
'user': request.user,
|
||||||
}
|
})
|
||||||
|
|
||||||
return render(request, 'moderation/partials/dashboard_content.html', context)
|
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
return HttpResponse(str(e), status=400)
|
return HttpResponse(str(e), status=400)
|
||||||
|
|
||||||
@@ -312,42 +314,20 @@ def reject_submission(request: HttpRequest, submission_id: int) -> HttpResponse:
|
|||||||
user = cast(User, request.user)
|
user = cast(User, request.user)
|
||||||
submission = get_object_or_404(EditSubmission, id=submission_id)
|
submission = get_object_or_404(EditSubmission, id=submission_id)
|
||||||
|
|
||||||
if submission.status == 'ESCALATED' and not (user.role in ['ADMIN', 'SUPERUSER'] or user.is_superuser):
|
if not ((submission.status != 'ESCALATED' and user.role in MODERATOR_ROLES) or
|
||||||
return HttpResponse("Only admins can handle escalated submissions", status=403)
|
user.role in ['ADMIN', 'SUPERUSER'] or user.is_superuser):
|
||||||
if not (user.role in MODERATOR_ROLES or user.is_superuser):
|
return HttpResponse("Insufficient permissions", status=403)
|
||||||
return HttpResponse(status=403)
|
|
||||||
|
|
||||||
submission.reject(user)
|
submission.reject(user)
|
||||||
_update_submission_notes(submission, request.POST.get('notes'))
|
_update_submission_notes(submission, request.POST.get('notes'))
|
||||||
|
|
||||||
# Get updated queryset with filters
|
|
||||||
status = request.GET.get('status', 'PENDING')
|
status = request.GET.get('status', 'PENDING')
|
||||||
submission_type = request.GET.get('submission_type', '')
|
submission_type = request.GET.get('submission_type', '')
|
||||||
|
queryset = get_filtered_queryset(request, status, submission_type)
|
||||||
|
context = get_context_data(request, queryset)
|
||||||
|
|
||||||
if submission_type == 'photo':
|
|
||||||
queryset = PhotoSubmission.objects.filter(status=status).order_by('-created_at')
|
|
||||||
else:
|
|
||||||
queryset = EditSubmission.objects.filter(status=status).order_by('-created_at')
|
|
||||||
|
|
||||||
if type_filter := request.GET.get('type'):
|
|
||||||
queryset = queryset.filter(submission_type=type_filter)
|
|
||||||
|
|
||||||
if content_type := request.GET.get('content_type'):
|
|
||||||
queryset = queryset.filter(content_type__model=content_type)
|
|
||||||
|
|
||||||
context = {
|
|
||||||
'submission': submission,
|
|
||||||
'user': request.user,
|
|
||||||
'parks': [(p.id, str(p)) for p in Park.objects.all()],
|
|
||||||
'designers': [(d.id, str(d)) for d in Designer.objects.all()],
|
|
||||||
'manufacturers': [(m.id, str(m)) for m in Manufacturer.objects.all()],
|
|
||||||
'ride_models': [(m.id, str(m)) for m in RideModel.objects.all()],
|
|
||||||
'owners': [(u.id, str(u)) for u in User.objects.filter(role__in=['OWNER', 'ADMIN', 'SUPERUSER'])],
|
|
||||||
'park_areas': [(a.id, str(a)) for a in ParkArea.objects.filter(park_id=submission.changes.get('park'))] if submission.changes.get('park') else []
|
|
||||||
}
|
|
||||||
return render(request, 'moderation/partials/submission_list.html', context)
|
return render(request, 'moderation/partials/submission_list.html', context)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def escalate_submission(request: HttpRequest, submission_id: int) -> HttpResponse:
|
def escalate_submission(request: HttpRequest, submission_id: int) -> HttpResponse:
|
||||||
"""HTMX endpoint for escalating a submission"""
|
"""HTMX endpoint for escalating a submission"""
|
||||||
@@ -362,28 +342,14 @@ def escalate_submission(request: HttpRequest, submission_id: int) -> HttpRespons
|
|||||||
submission.escalate(user)
|
submission.escalate(user)
|
||||||
_update_submission_notes(submission, request.POST.get("notes"))
|
_update_submission_notes(submission, request.POST.get("notes"))
|
||||||
|
|
||||||
# Get updated queryset with filters
|
|
||||||
status = request.GET.get("status", "PENDING")
|
status = request.GET.get("status", "PENDING")
|
||||||
submission_type = request.GET.get("submission_type", "")
|
submission_type = request.GET.get("submission_type", "")
|
||||||
|
queryset = get_filtered_queryset(request, status, submission_type)
|
||||||
|
|
||||||
if submission_type == "photo":
|
return render(request, "moderation/partials/dashboard_content.html", {
|
||||||
queryset = PhotoSubmission.objects.filter(status=status).order_by("-created_at")
|
|
||||||
else:
|
|
||||||
queryset = EditSubmission.objects.filter(status=status).order_by("-created_at")
|
|
||||||
|
|
||||||
if type_filter := request.GET.get("type"):
|
|
||||||
queryset = queryset.filter(submission_type=type_filter)
|
|
||||||
|
|
||||||
if content_type := request.GET.get("content_type"):
|
|
||||||
queryset = queryset.filter(content_type__model=content_type)
|
|
||||||
|
|
||||||
context = {
|
|
||||||
"submissions": queryset,
|
"submissions": queryset,
|
||||||
"user": request.user,
|
"user": request.user,
|
||||||
}
|
})
|
||||||
|
|
||||||
return render(request, "moderation/partials/dashboard_content.html", context)
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def approve_photo(request: HttpRequest, submission_id: int) -> HttpResponse:
|
def approve_photo(request: HttpRequest, submission_id: int) -> HttpResponse:
|
||||||
@@ -393,18 +359,13 @@ def approve_photo(request: HttpRequest, submission_id: int) -> HttpResponse:
|
|||||||
return HttpResponse(status=403)
|
return HttpResponse(status=403)
|
||||||
|
|
||||||
submission = get_object_or_404(PhotoSubmission, id=submission_id)
|
submission = get_object_or_404(PhotoSubmission, id=submission_id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
submission.approve(user, request.POST.get("notes", ""))
|
submission.approve(user, request.POST.get("notes", ""))
|
||||||
return render(
|
return render(request, "moderation/partials/photo_submission.html",
|
||||||
request,
|
{"submission": submission})
|
||||||
"moderation/partials/photo_submission.html",
|
|
||||||
{"submission": submission},
|
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return HttpResponse(str(e), status=400)
|
return HttpResponse(str(e), status=400)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def reject_photo(request: HttpRequest, submission_id: int) -> HttpResponse:
|
def reject_photo(request: HttpRequest, submission_id: int) -> HttpResponse:
|
||||||
"""HTMX endpoint for rejecting a photo submission"""
|
"""HTMX endpoint for rejecting a photo submission"""
|
||||||
@@ -415,10 +376,8 @@ def reject_photo(request: HttpRequest, submission_id: int) -> HttpResponse:
|
|||||||
submission = get_object_or_404(PhotoSubmission, id=submission_id)
|
submission = get_object_or_404(PhotoSubmission, id=submission_id)
|
||||||
submission.reject(user, request.POST.get("notes", ""))
|
submission.reject(user, request.POST.get("notes", ""))
|
||||||
|
|
||||||
return render(
|
return render(request, "moderation/partials/photo_submission.html",
|
||||||
request, "moderation/partials/photo_submission.html", {"submission": submission}
|
{"submission": submission})
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _update_submission_notes(submission: EditSubmission, notes: Optional[str]) -> None:
|
def _update_submission_notes(submission: EditSubmission, notes: Optional[str]) -> None:
|
||||||
"""Update submission notes if provided."""
|
"""Update submission notes if provided."""
|
||||||
|
|||||||
@@ -1,23 +1,48 @@
|
|||||||
{% load moderation_tags %}
|
{% load moderation_tags %}
|
||||||
|
|
||||||
<div class="p-4 bg-gray-100 border rounded-lg dark:bg-gray-900 border-gray-200/50 dark:border-gray-700/50">
|
<div class="p-4 bg-white border rounded-lg dark:bg-gray-800 border-gray-200/50 dark:border-gray-700/50">
|
||||||
<h3 class="mb-4 text-lg font-semibold">Location</h3>
|
<h3 class="mb-4 text-lg font-semibold text-gray-900 dark:text-gray-300">Location</h3>
|
||||||
|
|
||||||
|
<!-- Map Container -->
|
||||||
<div class="h-[300px] w-full rounded-lg border border-gray-300 dark:border-gray-600"
|
<div class="h-[300px] w-full rounded-lg border border-gray-300 dark:border-gray-600"
|
||||||
id="viewMap-{{ submission.id }}"
|
id="viewMap-{{ submission.id }}"
|
||||||
x-init="setTimeout(() => {
|
x-init="setTimeout(() => {
|
||||||
const map = L.map('viewMap-{{ submission.id }}').setView([{{ location.latitude }}, {{ location.longitude }}], 13);
|
const map = L.map('viewMap-{{ submission.id }}');
|
||||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||||
attribution: '© OpenStreetMap contributors'
|
attribution: '© OpenStreetMap contributors'
|
||||||
}).addTo(map);
|
}).addTo(map);
|
||||||
L.marker([{{ location.latitude }}, {{ location.longitude }}]).addTo(map);
|
|
||||||
|
{% if submission.changes.latitude and submission.changes.longitude %}
|
||||||
|
const lat = {{ submission.changes.latitude }};
|
||||||
|
const lng = {{ submission.changes.longitude }};
|
||||||
|
map.setView([lat, lng], 13);
|
||||||
|
L.marker([lat, lng]).addTo(map);
|
||||||
|
{% else %}
|
||||||
|
map.setView([0, 0], 2);
|
||||||
|
{% endif %}
|
||||||
}, 100)"></div>
|
}, 100)"></div>
|
||||||
<div class="mt-4 space-y-2 text-sm text-gray-600 dark:text-gray-400">
|
|
||||||
{% if location.street_address %}<div>{{ location.street_address }}</div>{% endif %}
|
<!-- Address Display -->
|
||||||
<div>
|
<div class="mt-4 space-y-1">
|
||||||
{% if location.city %}{{ location.city }}{% endif %}
|
{% if submission.changes.street_address %}
|
||||||
{% if location.state %}, {{ location.state }}{% endif %}
|
<div class="flex items-center text-gray-600 dark:text-gray-400">
|
||||||
{% if location.postal_code %} {{ location.postal_code }}{% endif %}
|
<i class="w-5 mr-2 fas fa-map-marker-alt"></i>
|
||||||
|
{{ submission.changes.street_address }}
|
||||||
</div>
|
</div>
|
||||||
{% if location.country %}<div>{{ location.country }}</div>{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="flex items-center text-gray-600 dark:text-gray-400">
|
||||||
|
<i class="w-5 mr-2 fas fa-city"></i>
|
||||||
|
{% if submission.changes.city %}{{ submission.changes.city }}{% endif %}
|
||||||
|
{% if submission.changes.state %}, {{ submission.changes.state }}{% endif %}
|
||||||
|
{% if submission.changes.postal_code %} {{ submission.changes.postal_code }}{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if submission.changes.country %}
|
||||||
|
<div class="flex items-center text-gray-600 dark:text-gray-400">
|
||||||
|
<i class="w-5 mr-2 fas fa-globe"></i>
|
||||||
|
{{ submission.changes.country }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -19,15 +19,18 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<div class="p-4 bg-white border rounded-lg dark:bg-gray-800 border-gray-200/50 dark:border-gray-700/50">
|
||||||
|
<h3 class="mb-4 text-lg font-semibold text-gray-900 dark:text-gray-300">Location</h3>
|
||||||
|
|
||||||
<div class="location-widget" id="locationWidget-{{ submission.id }}">
|
<div class="location-widget" id="locationWidget-{{ submission.id }}">
|
||||||
{# Search Form #}
|
{# Search Form #}
|
||||||
<div class="relative mb-4">
|
<div class="relative mb-4">
|
||||||
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-gray-300">
|
<label class="block mb-2 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
Search Location
|
Search Location
|
||||||
</label>
|
</label>
|
||||||
<input type="text"
|
<input type="text"
|
||||||
id="locationSearch-{{ submission.id }}"
|
id="locationSearch-{{ submission.id }}"
|
||||||
class="relative w-full px-4 py-2 border border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
class="relative w-full px-4 py-2 text-gray-900 bg-white border rounded-lg dark:text-gray-300 dark:bg-gray-700 dark:border-gray-600 form-input"
|
||||||
placeholder="Search for a location..."
|
placeholder="Search for a location..."
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
style="z-index: 10;">
|
style="z-index: 10;">
|
||||||
@@ -39,67 +42,69 @@
|
|||||||
|
|
||||||
{# Map Container #}
|
{# Map Container #}
|
||||||
<div class="relative mb-4" style="z-index: 1;">
|
<div class="relative mb-4" style="z-index: 1;">
|
||||||
<div id="locationMap-{{ submission.id }}" class="h-[300px] w-full rounded-lg border border-gray-300 dark:border-gray-600"></div>
|
<div id="locationMap-{{ submission.id }}"
|
||||||
|
class="h-[300px] w-full rounded-lg border border-gray-300 dark:border-gray-600"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Location Form Fields #}
|
{# Location Form Fields #}
|
||||||
<div class="relative grid grid-cols-1 gap-4 md:grid-cols-2" style="z-index: 10;">
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
<div>
|
<div>
|
||||||
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-gray-300">
|
<label class="block mb-2 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
Street Address
|
Street Address
|
||||||
</label>
|
</label>
|
||||||
<input type="text"
|
<input type="text"
|
||||||
name="street_address"
|
name="street_address"
|
||||||
id="streetAddress-{{ submission.id }}"
|
id="streetAddress-{{ submission.id }}"
|
||||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
class="w-full px-4 py-2 text-gray-900 bg-white border rounded-lg dark:text-gray-300 dark:bg-gray-700 dark:border-gray-600 form-input"
|
||||||
value="{{ form.street_address }}">
|
value="{{ submission.changes.street_address }}">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-gray-300">
|
<label class="block mb-2 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
City
|
City
|
||||||
</label>
|
</label>
|
||||||
<input type="text"
|
<input type="text"
|
||||||
name="city"
|
name="city"
|
||||||
id="city-{{ submission.id }}"
|
id="city-{{ submission.id }}"
|
||||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
class="w-full px-4 py-2 text-gray-900 bg-white border rounded-lg dark:text-gray-300 dark:bg-gray-700 dark:border-gray-600 form-input"
|
||||||
value="{{ form.city }}">
|
value="{{ submission.changes.city }}">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-gray-300">
|
<label class="block mb-2 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
State/Region
|
State/Region
|
||||||
</label>
|
</label>
|
||||||
<input type="text"
|
<input type="text"
|
||||||
name="state"
|
name="state"
|
||||||
id="state-{{ submission.id }}"
|
id="state-{{ submission.id }}"
|
||||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
class="w-full px-4 py-2 text-gray-900 bg-white border rounded-lg dark:text-gray-300 dark:bg-gray-700 dark:border-gray-600 form-input"
|
||||||
value="{{ form.state }}">
|
value="{{ submission.changes.state }}">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-gray-300">
|
<label class="block mb-2 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
Country
|
Country
|
||||||
</label>
|
</label>
|
||||||
<input type="text"
|
<input type="text"
|
||||||
name="country"
|
name="country"
|
||||||
id="country-{{ submission.id }}"
|
id="country-{{ submission.id }}"
|
||||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
class="w-full px-4 py-2 text-gray-900 bg-white border rounded-lg dark:text-gray-300 dark:bg-gray-700 dark:border-gray-600 form-input"
|
||||||
value="{{ form.country }}">
|
value="{{ submission.changes.country }}">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-gray-300">
|
<label class="block mb-2 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
Postal Code
|
Postal Code
|
||||||
</label>
|
</label>
|
||||||
<input type="text"
|
<input type="text"
|
||||||
name="postal_code"
|
name="postal_code"
|
||||||
id="postalCode-{{ submission.id }}"
|
id="postalCode-{{ submission.id }}"
|
||||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
class="w-full px-4 py-2 text-gray-900 bg-white border rounded-lg dark:text-gray-300 dark:bg-gray-700 dark:border-gray-600 form-input"
|
||||||
value="{{ form.postal_code }}">
|
value="{{ submission.changes.postal_code }}">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Hidden Coordinate Fields #}
|
{# Hidden Coordinate Fields #}
|
||||||
<div class="hidden">
|
<div class="hidden">
|
||||||
<input type="hidden" name="latitude" id="latitude-{{ submission.id }}" value="{{ form.latitude }}">
|
<input type="hidden" name="latitude" id="latitude-{{ submission.id }}" value="{{ submission.changes.latitude }}">
|
||||||
<input type="hidden" name="longitude" id="longitude-{{ submission.id }}" value="{{ form.longitude }}">
|
<input type="hidden" name="longitude" id="longitude-{{ submission.id }}" value="{{ submission.changes.longitude }}">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -111,7 +116,37 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
const searchResults = document.getElementById('searchResults-{{ submission.id }}');
|
const searchResults = document.getElementById('searchResults-{{ submission.id }}');
|
||||||
let searchTimeout;
|
let searchTimeout;
|
||||||
|
|
||||||
|
// Initialize form fields with existing values
|
||||||
|
const fields = {
|
||||||
|
city: '{{ submission.changes.city|default:"" }}',
|
||||||
|
state: '{{ submission.changes.state|default:"" }}',
|
||||||
|
country: '{{ submission.changes.country|default:"" }}',
|
||||||
|
postal_code: '{{ submission.changes.postal_code|default:"" }}',
|
||||||
|
street_address: '{{ submission.changes.street_address|default:"" }}',
|
||||||
|
latitude: '{{ submission.changes.latitude|default:"" }}',
|
||||||
|
longitude: '{{ submission.changes.longitude|default:"" }}'
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.entries(fields).forEach(([field, value]) => {
|
||||||
|
const element = document.getElementById(`${field}-{{ submission.id }}`);
|
||||||
|
if (element) {
|
||||||
|
element.value = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set initial search input value if location exists
|
||||||
|
if (fields.street_address || fields.city) {
|
||||||
|
const parts = [
|
||||||
|
fields.street_address,
|
||||||
|
fields.city,
|
||||||
|
fields.state,
|
||||||
|
fields.country
|
||||||
|
].filter(Boolean);
|
||||||
|
searchInput.value = parts.join(', ');
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeCoordinate(value, maxDigits, decimalPlaces) {
|
function normalizeCoordinate(value, maxDigits, decimalPlaces) {
|
||||||
|
if (!value) return null;
|
||||||
try {
|
try {
|
||||||
const rounded = Number(value).toFixed(decimalPlaces);
|
const rounded = Number(value).toFixed(decimalPlaces);
|
||||||
const strValue = rounded.replace('.', '').replace('-', '');
|
const strValue = rounded.replace('.', '').replace('-', '');
|
||||||
@@ -167,22 +202,27 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create new map
|
// Create new map
|
||||||
maps[submissionId] = L.map(mapId).setView([0, 0], 2);
|
maps[submissionId] = L.map(mapId);
|
||||||
|
|
||||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||||
attribution: '© OpenStreetMap contributors'
|
attribution: '© OpenStreetMap contributors'
|
||||||
}).addTo(maps[submissionId]);
|
}).addTo(maps[submissionId]);
|
||||||
|
|
||||||
// Initialize with existing coordinates if available
|
// Initialize with existing coordinates if available
|
||||||
const initialLat = document.getElementById(`latitude-${submissionId}`).value;
|
const initialLat = fields.latitude;
|
||||||
const initialLng = document.getElementById(`longitude-${submissionId}`).value;
|
const initialLng = fields.longitude;
|
||||||
|
|
||||||
if (initialLat && initialLng) {
|
if (initialLat && initialLng) {
|
||||||
try {
|
try {
|
||||||
const normalized = validateCoordinates(initialLat, initialLng);
|
const normalized = validateCoordinates(initialLat, initialLng);
|
||||||
|
maps[submissionId].setView([normalized.lat, normalized.lng], 13);
|
||||||
addMarker(normalized.lat, normalized.lng);
|
addMarker(normalized.lat, normalized.lng);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Invalid initial coordinates:', error);
|
console.error('Invalid initial coordinates:', error);
|
||||||
|
maps[submissionId].setView([0, 0], 2);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
maps[submissionId].setView([0, 0], 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle map clicks
|
// Handle map clicks
|
||||||
@@ -236,6 +276,15 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
address.state || address.region || '';
|
address.state || address.region || '';
|
||||||
document.getElementById(`country-${submissionId}`).value = address.country || '';
|
document.getElementById(`country-${submissionId}`).value = address.country || '';
|
||||||
document.getElementById(`postalCode-${submissionId}`).value = address.postcode || '';
|
document.getElementById(`postalCode-${submissionId}`).value = address.postcode || '';
|
||||||
|
|
||||||
|
// Update search input
|
||||||
|
const locationString = [
|
||||||
|
document.getElementById(`streetAddress-${submissionId}`).value,
|
||||||
|
document.getElementById(`city-${submissionId}`).value,
|
||||||
|
document.getElementById(`state-${submissionId}`).value,
|
||||||
|
document.getElementById(`country-${submissionId}`).value
|
||||||
|
].filter(Boolean).join(', ');
|
||||||
|
searchInput.value = locationString;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Location update failed:', error);
|
console.error('Location update failed:', error);
|
||||||
alert(error.message || 'Failed to update location. Please try again.');
|
alert(error.message || 'Failed to update location. Please try again.');
|
||||||
@@ -355,6 +404,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
const mapContainer = document.getElementById(`locationMap-{{ submission.id }}`);
|
const mapContainer = document.getElementById(`locationMap-{{ submission.id }}`);
|
||||||
if (mapContainer) {
|
if (mapContainer) {
|
||||||
observer.observe(mapContainer.parentElement.parentElement, { attributes: true });
|
observer.observe(mapContainer.parentElement.parentElement, { attributes: true });
|
||||||
|
|
||||||
|
// Also initialize immediately if the container is already visible
|
||||||
|
if (window.getComputedStyle(mapContainer).display !== 'none') {
|
||||||
|
initMap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -103,15 +103,15 @@
|
|||||||
<!-- View Mode -->
|
<!-- View Mode -->
|
||||||
<div x-show="!isEditing">
|
<div x-show="!isEditing">
|
||||||
<!-- Location Map (View Mode) -->
|
<!-- Location Map (View Mode) -->
|
||||||
{% if submission.content_type.model == 'park' and submission.changes.latitude and submission.changes.longitude %}
|
{% if submission.content_type.model == 'park' %}
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
{% include "moderation/partials/location_map.html" with location=submission.changes %}
|
{% include "moderation/partials/location_map.html" with submission=submission %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="grid grid-cols-1 gap-3 md:grid-cols-2">
|
<div class="grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||||
{% for field, value in submission.changes.items %}
|
{% for field, value in submission.changes.items %}
|
||||||
{% if field != 'model_name' and field != 'latitude' and field != 'longitude' and field != 'street_address' and field != 'city' and field != 'state' and field != 'postal_code' and field != 'country' %}
|
{% if field != 'model_name' and field != 'latitude' and field != 'longitude' and field != 'street_address' and field != 'city' and field != 'state' and field != 'postal_code' and field != 'country' and field != 'location' %}
|
||||||
<div class="p-4 bg-gray-100 border rounded-lg dark:bg-gray-900 border-gray-200/50 dark:border-gray-700/50">
|
<div class="p-4 bg-gray-100 border rounded-lg dark:bg-gray-900 border-gray-200/50 dark:border-gray-700/50">
|
||||||
<div class="text-sm font-medium text-gray-900 dark:text-gray-300">
|
<div class="text-sm font-medium text-gray-900 dark:text-gray-300">
|
||||||
{{ field|title }}:
|
{{ field|title }}:
|
||||||
@@ -176,8 +176,16 @@
|
|||||||
hx-post="{% url 'moderation:edit_submission' submission.id %}"
|
hx-post="{% url 'moderation:edit_submission' submission.id %}"
|
||||||
hx-target="#submission-{{ submission.id }}"
|
hx-target="#submission-{{ submission.id }}"
|
||||||
class="grid grid-cols-1 gap-3 md:grid-cols-2">
|
class="grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||||
|
|
||||||
|
<!-- Location Widget for Parks -->
|
||||||
|
{% if submission.content_type.model == 'park' %}
|
||||||
|
<div class="col-span-2">
|
||||||
|
{% include "moderation/partials/location_widget.html" with submission=submission %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% for field, value in submission.changes.items %}
|
{% for field, value in submission.changes.items %}
|
||||||
{% if field != 'model_name' and field != 'stats' and field != 'latitude' and field != 'longitude' and field != 'street_address' and field != 'city' and field != 'state' and field != 'postal_code' and field != 'country' %}
|
{% if field != 'model_name' and field != 'stats' and field != 'latitude' and field != 'longitude' and field != 'street_address' and field != 'city' and field != 'state' and field != 'postal_code' and field != 'country' and field != 'location' %}
|
||||||
<div class="p-4 bg-gray-100 border rounded-lg dark:bg-gray-900 border-gray-200/50 dark:border-gray-700/50"
|
<div class="p-4 bg-gray-100 border rounded-lg dark:bg-gray-900 border-gray-200/50 dark:border-gray-700/50"
|
||||||
{% if field == 'post_closing_status' %}x-show="status === 'CLOSING'"{% endif %}
|
{% if field == 'post_closing_status' %}x-show="status === 'CLOSING'"{% endif %}
|
||||||
{% if field == 'closing_date' %}x-show="['CLOSING', 'SBNO', 'CLOSED_PERM', 'DEMOLISHED', 'RELOCATED'].includes(status)"{% endif %}>
|
{% if field == 'closing_date' %}x-show="['CLOSING', 'SBNO', 'CLOSED_PERM', 'DEMOLISHED', 'RELOCATED'].includes(status)"{% endif %}>
|
||||||
@@ -362,13 +370,6 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<!-- Location Widget for Parks -->
|
|
||||||
{% if submission.content_type.model == 'park' %}
|
|
||||||
<div class="col-span-2">
|
|
||||||
{% include "moderation/partials/location_widget.html" with form=submission.changes %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<!-- Coaster Fields -->
|
<!-- Coaster Fields -->
|
||||||
<div x-show="showCoasterFields"
|
<div x-show="showCoasterFields"
|
||||||
x-cloak
|
x-cloak
|
||||||
|
|||||||
Reference in New Issue
Block a user