more fixes, AI
This commit is contained in:
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user