more fixes, AI

This commit is contained in:
2026-03-31 22:10:30 +02:00
parent de1fa76e40
commit 2beb7aa75a
14 changed files with 121 additions and 67 deletions

View File

@@ -159,13 +159,18 @@ class EventParticipation(models.Model):
return self.user.username
def save(self, *args, **kwargs):
prev = EventParticipation.objects.filter(event=self.event, user=self.user)
if len(prev) == 0:
# For new objects, just save directly
if self.pk is None:
super().save(*args, **kwargs)
else:
prev = prev[0]
return
# For existing objects, only save if values changed
try:
prev = EventParticipation.objects.get(pk=self.pk)
if prev.status != self.status or prev.comment != self.comment:
super().save(*args, **kwargs)
except EventParticipation.DoesNotExist:
super().save(*args, **kwargs)
@staticmethod
def hasUserSetParticipationForAllEvents(user):

View File

@@ -1,21 +1,48 @@
from django.contrib.auth.models import User
from rest_framework import serializers
from .models import Event, EventParticipation
class ParticipationSerializer(serializers.ModelSerializer):
event = serializers.PrimaryKeyRelatedField(queryset=Event.objects.all())
user = serializers.CharField(source="get_username", read_only=True)
status = serializers.CharField(required=False)
class ParticipationSerializer(serializers.Serializer):
"""Serializer for EventParticipation that handles username lookup."""
class Meta:
model = EventParticipation
fields = ("event", "user", "status", "comment")
event = serializers.PrimaryKeyRelatedField(queryset=Event.objects.all())
user = serializers.CharField()
status = serializers.CharField(required=False, default="-")
comment = serializers.CharField(required=False, allow_blank=True, default="")
def to_representation(self, instance):
"""Serialize an EventParticipation instance."""
return {
"event": instance.event.pk,
"user": instance.user.username,
"status": instance.status,
"comment": instance.comment,
}
def validate_user(self, value):
"""Look up user by username (case-insensitive)."""
try:
return User.objects.get(username__iexact=value)
except User.DoesNotExist:
raise serializers.ValidationError(f"User '{value}' does not exist")
def create(self, validated_data):
# Remove the get_username source field as it's read-only
validated_data.pop("get_username", None)
return super().create(validated_data)
"""Create or update EventParticipation based on event and user."""
event = validated_data.get("event")
user = validated_data.get("user")
status = validated_data.get("status", "-")
comment = validated_data.get("comment", "")
# Use update_or_create to handle both new and existing participations
participation, created = EventParticipation.objects.update_or_create(
event=event,
user=user,
defaults={"status": status, "comment": comment},
)
return participation
def update(self, instance, validated_data):
instance.status = validated_data.get("status", instance.status)

View File

@@ -14,26 +14,26 @@
{% addtoblock "css" strip %}<link
rel="stylesheet"
href="{{STATIC_URL}}/css/datepicker.css"
href="{% static 'css/datepicker.css' %}"
type="text/css"
media="screen"
/>
{% endaddtoblock %} {% addtoblock "css" strip %}<link
rel="stylesheet"
href="{{STATIC_URL}}/css/timepicker.css"
href="{% static 'css/timepicker.css' %}"
type="text/css"
media="screen"
/>
{% endaddtoblock %} {% addtoblock "css" strip %}<link
rel="stylesheet"
href="{{STATIC_URL}}/css/jquery-ui-1.8.21.custom.css"
href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.13.2/themes/smoothness/jquery-ui.css"
type="text/css"
media="screen"
/>
{% endaddtoblock %} {% addtoblock "js" %}
<script src="{{STATIC_URL}}/js/bootstrap-timepicker.js"></script>
<script src="{{STATIC_URL}}/js/bootstrap-datepicker.js"></script>
<script src="{{STATIC_URL}}/js/bootstrap-datepicker.de.js"></script>
<script src="{% static 'js/bootstrap-timepicker.js' %}"></script>
<script src="{% static 'js/bootstrap-datepicker.js' %}"></script>
<script src="{% static 'js/bootstrap-datepicker.de.js' %}"></script>
<script>

View File

@@ -3,7 +3,7 @@ Coordinates or textual adresses: {{route.origin}} {{route.destination}} Event
object: {{route.event}} {% endcomment %} {% load sekizai_tags static %}
{% if route %} {% addtoblock "css" strip %}<link
rel="stylesheet"
href="{{STATIC_URL}}/css/concert_route.css"
href="{% static 'css/concert_route.css' %}"
type="text/css"
media="screen"
/>{% endaddtoblock %} {% addtoblock "js" strip %}
@@ -74,23 +74,21 @@ object: {{route.event}} {% endcomment %} {% load sekizai_tags static %}
google.maps.event.addDomListener(controlUI, 'click', function()
{
{% if not route.event.map_location %}
geocoder = new google.maps.Geocoder();
geocoder.region = "de";
geocoder.geocode( {"address": "{{ route.event.location }}" }, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
map.setMapTypeId( google.maps.MapTypeId.HYBRID );
map.setZoom( 15 );
map.setCenter( results[0].geometry.location );
var geocoder = new google.maps.Geocoder();
geocoder.geocode( {"address": "{{ route.event.location }}", "region": "de" }, function(results, status) {
if (status === google.maps.GeocoderStatus.OK) {
map.setMapTypeId(google.maps.MapTypeId.HYBRID);
map.setZoom(15);
map.setCenter(results[0].geometry.location);
} else {
console.error("Geocoding failed: " + status);
}
});
{% else %}
var loc = new google.maps.LatLng( {{ route.event.map_location }} );
map.setMapTypeId( google.maps.MapTypeId.HYBRID );
map.setZoom( 20 );
map.setCenter( loc );
var loc = new google.maps.LatLng({{ route.event.map_location }});
map.setMapTypeId(google.maps.MapTypeId.HYBRID);
map.setZoom(20);
map.setCenter(loc);
{% endif %}
});
}
@@ -116,11 +114,11 @@ object: {{route.event}} {% endcomment %} {% load sekizai_tags static %}
var request = {
origin: "{{route.origin}}",
destination: "{{route.destination}}",
travelMode: google.maps.DirectionsTravelMode.DRIVING
travelMode: google.maps.TravelMode.DRIVING
}
directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK) {
if (status === google.maps.DirectionsStatus.OK) {
directionsDisplay.setDirections(response);
var leg = response.routes[0].legs[0];

View File

@@ -7,6 +7,7 @@ app_name = "eventplanner"
urlpatterns = [
path("", views.eventplanning, name="eventplanning"),
path("grid/", views.events_grid, name="events_grid"),
path("grid/add", views.EventCreate.as_view(), name="event_create_from_grid"),
path("delete/<int:pk>/", views.deleteEvent, name="delete_event"),
# Event detail/update views - support both URL patterns
path("<int:pk>/", views.EventUpdate.as_view(), name="event_detail"),

View File

@@ -46,14 +46,29 @@ def event_api(request, username=None, eventId=None):
EventParticipation.isMember(request.user)
or EventParticipation.isAdmin(request.user)
):
return Response(status=status.HTTP_403_FORBIDDEN)
if user_obj != request.user:
return Response(
{"error": "Permission denied - not a member"},
status=status.HTTP_403_FORBIDDEN,
)
# Allow users to update their own participation
# Admins can update anyone's participation
if user_obj.pk != request.user.pk:
if not EventParticipation.isAdmin(request.user):
return Response(status=status.HTTP_403_FORBIDDEN)
return Response(
{"error": "Permission denied - can only update own status"},
status=status.HTTP_403_FORBIDDEN,
)
serializer.save()
return Response(serializer.data)
instances = serializer.save()
# Re-serialize the saved instances to return proper data
response_serializer = ParticipationSerializer(instances, many=True)
return Response(response_serializer.data)
else:
import logging
logger = logging.getLogger(__name__)
logger.error(f"API validation errors: {serializer.errors}")
logger.error(f"Request data: {request.data}")
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)