diff --git a/fiesta/apps/events/models/participant.py b/fiesta/apps/events/models/participant.py index 4f524230..407db3ce 100644 --- a/fiesta/apps/events/models/participant.py +++ b/fiesta/apps/events/models/participant.py @@ -16,7 +16,7 @@ class Participant(BaseModel): user = models.ForeignKey( to="accounts.User", - related_name="participants", + related_name="event_participants", on_delete=models.SET_NULL, null=True, db_index=True, @@ -26,7 +26,7 @@ class Participant(BaseModel): event = models.ForeignKey( to="events.Event", on_delete=models.SET_NULL, - related_name="participants", + related_name="event_participants", null=True, db_index=True, verbose_name=_("event"), diff --git a/fiesta/apps/events/templates/events/event_detail.html b/fiesta/apps/events/templates/events/event_detail.html index cd346af4..1266ab1f 100644 --- a/fiesta/apps/events/templates/events/event_detail.html +++ b/fiesta/apps/events/templates/events/event_detail.html @@ -99,7 +99,7 @@

Organizers

- {% for role_value, role_display in organizer_roles %}
- -
- {% trans "Add organizer" %} -
{% endif %} +
+ {% trans "Add organizer" %} +
@@ -142,7 +141,11 @@

Organizers

Event

{% translate "Show participants" %} - {% translate "Update event" %} + {% translate "Update event" %} +
+ {% csrf_token %} + +
@@ -191,12 +194,16 @@

{{ event.title }}

{% if request.membership.is_international %} -
- {% csrf_token %} - -
- + + {%translate "Register"%} + +
{% endif %} + @@ -249,10 +256,14 @@

{{ event.title }}

{% if request.membership.is_international %} -
- {% csrf_token %} - -
+ + {%translate "Register"%} + +
{% endif %} diff --git a/fiesta/apps/events/templates/events/index.html b/fiesta/apps/events/templates/events/index.html index 9f89a2e3..9b47cfd3 100644 --- a/fiesta/apps/events/templates/events/index.html +++ b/fiesta/apps/events/templates/events/index.html @@ -7,10 +7,10 @@

Your events

diff --git a/fiesta/apps/events/templates/events/parts/kick_participant_button.html b/fiesta/apps/events/templates/events/parts/kick_participant_button.html new file mode 100644 index 00000000..ee05ae5d --- /dev/null +++ b/fiesta/apps/events/templates/events/parts/kick_participant_button.html @@ -0,0 +1,9 @@ +{% load i18n %} + + +{% translate "Kick" %} + \ No newline at end of file diff --git a/fiesta/apps/events/templatetags/event_tags.py b/fiesta/apps/events/templatetags/event_tags.py index cfb9992f..770fd3d2 100644 --- a/fiesta/apps/events/templatetags/event_tags.py +++ b/fiesta/apps/events/templatetags/event_tags.py @@ -18,6 +18,7 @@ def show_participants(context, event: Event): return event.participants.filter(state='confirmed') return None + @register.simple_tag(takes_context=True) def get_price_variants(context, event: Event): request: HttpRequest = context["request"] @@ -29,11 +30,11 @@ def get_price_variants(context, event: Event): @register.simple_tag(takes_context=True) def get_event_fullness(context, event: Event): - if event.participants.all().count() <= event.capacity: + if event.event_participants.all().count() <= event.capacity: return 0 - elif event.participants.all().count() < event.capacity & event.participants.all().count() > event.capacity/2: + elif event.event_participants.all().count() < event.capacity & event.participants.all().count() > event.capacity/2: return 1 - elif event.participants.all().count() >= event.capacity: + elif event.event_participants.all().count() >= event.capacity: return 2 @register.simple_tag(takes_context=True) @@ -54,9 +55,10 @@ def is_participant(context, event: Event) -> bool: @register.simple_tag(takes_context=True) def can_edit(context, event: Event) -> bool: request: HttpRequest = context["request"] - return is_moc(context, event) or event.author == request.membership.user or request.membership.user.has_perm("events.change_event") + return is_moc(context, event) or event.author == request.membership.user or request.membership.is_privileged or request.membership.user.has_perm('events.change_event') @register.simple_tag(takes_context=True) def can_see_participants(context, event: Event) -> bool: request: HttpRequest = context["request"] - return is_moc(context, event) or event.author == request.membership.user or request.membership.user.has_perm("events.change_event") \ No newline at end of file + return is_moc(context, event) or event.author == request.membership.user or request.membership.is_privileged or request.membership.user.has_perm('events.change_event') + diff --git a/fiesta/apps/events/urls.py b/fiesta/apps/events/urls.py index f5d7db03..a570acae 100644 --- a/fiesta/apps/events/urls.py +++ b/fiesta/apps/events/urls.py @@ -3,7 +3,7 @@ from django.urls import path from .views import EventsIndexView -from .views.event import AddEventView, EventDetailView, ParticipantsView, UpdateEventView, EventParticipantRegister +from .views.event import AddEventView, EventDetailView, ParticipantsView, UpdateEventView, EventParticipantRegister, DeleteEventView, EventParticipantKick from .views.organizer import AddOrganizerView, UpdateOrganizerRole, DeleteOrganizerView from .views.price import AddPriceView, UpdatePriceView, DeletePriceView from .views.place import PlaceView, AddPlaceView, UpdatePlaceView, DeletePlaceView @@ -15,8 +15,9 @@ urlpatterns = [ path('', EventsIndexView.as_view(), name="index"), path('add-event', AddEventView.as_view(), name="add-event"), - path('event-update/', UpdateEventView.as_view(), name="event-update"), + path('event-detail/update/', UpdateEventView.as_view(), name="event-update"), path('event-detail/', EventDetailView.as_view(), name="event-detail"), + path('event-detail/delete/', DeleteEventView.as_view(), name="event-delete"), path("event-detail//participants", ParticipantsView.as_view(), name="participants"), # event-detail// path("event-detail//organizers/", UpdateOrganizerRole.as_view(), name="role-change"), # event-detail// path("event-detail//organizers/delete/", DeleteOrganizerView.as_view(), name="organizer-delete"), # event-detail// @@ -30,4 +31,5 @@ path("place/update/", UpdatePlaceView.as_view(), name="place-update"), path("place/delete/", DeletePlaceView.as_view(), name="place-delete"), path("event-detail//register", EventParticipantRegister.as_view(), name="event-register"), + path("event-detail//kick/", EventParticipantKick.as_view(), name="event-kick") ] diff --git a/fiesta/apps/events/views/event.py b/fiesta/apps/events/views/event.py index bfa9d776..77477da3 100644 --- a/fiesta/apps/events/views/event.py +++ b/fiesta/apps/events/views/event.py @@ -14,14 +14,14 @@ import django_filters from django.contrib.postgres.search import SearchVector from django.forms import TextInput -from django.views.generic import CreateView, DetailView, UpdateView, View +from django.views.generic import CreateView, DetailView, UpdateView, DeleteView, View import django_tables2 as tables from django.utils.translation import gettext_lazy as _ from django.urls import reverse from django_filters import CharFilter, ChoiceFilter -from ..models import Participant, Organizer +from ..models import Participant, Organizer, PriceVariant from ..models.event import Event from apps.utils.views import AjaxViewMixin from apps.fiestaforms.views.htmx import HtmxFormViewMixin @@ -38,6 +38,7 @@ from ...utils.breadcrumbs import with_breadcrumb, with_plugin_home_breadcrumb, with_object_breadcrumb from allauth.account.utils import get_next_redirect_url from django.contrib.auth import REDIRECT_FIELD_NAME +import datetime @@ -88,7 +89,7 @@ class UpdateEventView( form_class = AddEventForm template_name = "fiestaforms/pages/card_page_for_ajax_form.html" - ajax_template_name = "events/templates/parts/update_event_form.html" + ajax_template_name = "events/parts/update_event_form.html" success_message = _("Event updated") @@ -99,6 +100,7 @@ def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpRespo def get_initial(self): return dict( section=self.request.in_space_of_section, + author=self.event.author ) def get_object(self, queryset=None): @@ -117,7 +119,20 @@ def get_context_data(self, **kwargs): def get_success_url(self): return reverse("events:event-detail", args=[self.object.id]) +class DeleteEventView( + EnsurePrivilegedUserViewMixin, + EnsureInSectionSpaceViewMixin, + DeleteView, + HtmxFormViewMixin, + AjaxViewMixin, + SuccessMessageMixin): + + def get_object(self, queryset=None) -> Event: + return get_object_or_404(Event, pk=self.kwargs.get("pk")) + def get_success_url(self): + return reverse("events:index") + @with_plugin_home_breadcrumb @with_breadcrumb(_("Detail")) class EventDetailView( @@ -163,7 +178,14 @@ def filter_search(self, queryset, name, value): return queryset.annotate(search=SearchVector("user__last_name", "user__first_name", "state")).filter( search=value ) + +class EventKickButtonColumn(tables.TemplateColumn): + def __init__(self, event, *args, **kwargs): + self.event = event + super().__init__(*args, **kwargs) + def render(self, record, *args, **kwargs): + return super().render(record, extra_context={'event': self.event}, *args, **kwargs) class EventParticipantsTable(tables.Table): created = tables.DateTimeColumn( @@ -182,9 +204,9 @@ class EventParticipantsTable(tables.Table): accessor="event.title", attrs={"th": {"class": "text-center"}}, ) - price__name = tables.Column( + price__amount = tables.Column( verbose_name=_("Price"), - accessor="price.name", + accessor="price.amount", attrs={"th": {"class": "text-center"}}, ) state = tables.Column( @@ -193,6 +215,11 @@ class EventParticipantsTable(tables.Table): order_by="state", attrs={"th": {"class": "text-center"}}, ) + kick_button = EventKickButtonColumn( + event=None, + template_name="events/parts/kick_participant_button.html", + attrs={"th": {"class": "text-center"}}, + ) class Meta: model = Participant @@ -202,8 +229,9 @@ class Meta: sequence = ( "user__full_name", "event__title", - "price__name", - "state" + "price__amount", + "state", + "kick_button" ) empty_text = _("There are no participants") @@ -213,6 +241,11 @@ def render_created(self, value): def render_price__name(self, value): return str(value) + + def __init__(self, *args, **kwargs): + event = kwargs.pop('event', None) + super().__init__(*args, **kwargs) + self.columns['kick_button'].event = event @with_plugin_home_breadcrumb @@ -232,7 +265,17 @@ class ParticipantsView( def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: self.event = get_object_or_404(Event, pk=self.kwargs.get("pk")) return super().dispatch(request, *args, **kwargs) - + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context ['event'] = self.event + return context + + def get_table_kwargs(self): + kwargs = super().get_table_kwargs() + kwargs['event'] = self.event + return kwargs + def get_queryset(self): return self.request.in_space_of_section.events.get(id=self.event.pk).participants.filter( state__in=( @@ -245,14 +288,49 @@ def get_queryset(self): class EventParticipantRegister( EnsureInSectionSpaceViewMixin, - CreateView): - - model = Participant + View): - def form_valid(self, form: BaseModelForm) -> HttpResponse: + model = Participant + + def dispatch(self, request, *args, **kwargs): + self.event = get_object_or_404(Event, pk=self.kwargs.get("pk")) + return super().dispatch(request, *args, **kwargs) + + def choose_price(self): + if PriceVariant.objects.filter(event=self.event, type=EventPriceVariantType.FREE).exists(): + return PriceVariant.objects.filter(event=self.event, type=EventPriceVariantType.FREE).get() + elif self.request.user.is_esn_card_holder & PriceVariant.objects.filter(event=self.event, type=EventPriceVariantType.WITH_ESN_CARD).exists(): + return PriceVariant.objects.filter(event=self.event, type=EventPriceVariantType.WITH_ESN_CARD).get() + elif PriceVariant.objects.filter(event=self.event, type=EventPriceVariantType.STANDARD).exists(): + return PriceVariant.objects.filter(event=self.event, type=EventPriceVariantType.STANDARD).get() + return None + + def post(self, request, *args, **kwargs): + if Participant.objects.filter(user=self.request.user, event=self.event).exists(): + return HttpResponse("You are already registered for this event.") + if self.choose_price() is None: + return HttpResponse("You can't register for this event yet.") Participant.objects.create( + created=datetime.datetime.now(), user=self.request.user, - + event=self.event, + price=self.choose_price() ) - return super().form_valid(form) + return HttpResponse("Sucsessfully registered for this event.") + +class EventParticipantKick( + EnsurePrivilegedUserViewMixin, + EnsureInSectionSpaceViewMixin, + DeleteView): + + model = Participant + + def get_object(self, queryset= None): + return get_object_or_404(Participant, pk=self.kwargs.get("pkp")) + + def get_success_url(self) -> str: + return reverse("events:participants", args=[self.kwargs.get("pk")]) + + + \ No newline at end of file diff --git a/fiesta/apps/events/views/index.py b/fiesta/apps/events/views/index.py index 150df070..813701cb 100644 --- a/fiesta/apps/events/views/index.py +++ b/fiesta/apps/events/views/index.py @@ -14,10 +14,10 @@ class EventsIndexView(EnsureInSectionSpaceViewMixin, ListView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - # Get the user's confirmed participants - participations = self.request.user.participants.filter(state=Participant.State.CONFIRMED) + participants = Participant.objects.filter(user=self.request.membership.user) - # Add the confirmed participants to the context - context['participations'] = participations + events = [participant.event for participant in participants if participant.event] + + context['users_events'] = events return context \ No newline at end of file