diff --git a/events/admin.py b/events/admin.py index 87b197a..daff27b 100644 --- a/events/admin.py +++ b/events/admin.py @@ -1,7 +1,7 @@ from django.contrib import admin from django.db.models import Q -from events.enums import EventStatus +from events.enums.status import EventStatus from events.filters.category import ActiveCategoryFilter from events.models.banner import Banner from events.models.category import Category diff --git a/events/apps.py b/events/apps.py index 564388a..5dce844 100644 --- a/events/apps.py +++ b/events/apps.py @@ -6,5 +6,9 @@ class EventsConfig(AppConfig): name = "events" def ready(self): # noqa: PLR6301 - import events.signals # type: ignore # noqa: F401 + import events.signals.banner # type: ignore # noqa: F401 + import events.signals.category + import events.signals.events + import events.signals.signup + import events.signals.triggers import events.validators # type: ignore # noqa: F401 diff --git a/events/enums.py b/events/enums/status.py similarity index 100% rename from events/enums.py rename to events/enums/status.py diff --git a/events/management/commands/fake_events.py b/events/management/commands/fake_events.py index 40c7ec1..9b7dcc8 100644 --- a/events/management/commands/fake_events.py +++ b/events/management/commands/fake_events.py @@ -7,7 +7,7 @@ from faker import Faker from PIL import Image -from events.enums import EventStatus +from events.enums.status import EventStatus from events.models.banner import Banner from events.models.category import Category from events.models.event import Event diff --git a/events/managers/banners.py b/events/managers/banners.py index 13ff71a..1e4b0b8 100644 --- a/events/managers/banners.py +++ b/events/managers/banners.py @@ -2,7 +2,7 @@ from django.db import models from django.db.models import QuerySet -from events.enums import EventStatus +from events.enums.status import EventStatus class BannerQuerySet(QuerySet): diff --git a/events/models/event.py b/events/models/event.py index 3877827..bbf9107 100644 --- a/events/models/event.py +++ b/events/models/event.py @@ -1,7 +1,7 @@ from django.contrib.auth.models import User from django.db import models -from events.enums import EventStatus +from events.enums.status import EventStatus from events.validators import ( validate_event_attributes, validate_total_participants, diff --git a/events/models/signup.py b/events/models/signup.py index d430af4..c8db799 100644 --- a/events/models/signup.py +++ b/events/models/signup.py @@ -1,7 +1,7 @@ from django.contrib.auth.models import User from django.db import models -from events.enums import EventStatus +from events.enums.status import EventStatus from events.models.event import Event from events.validators import ( validate_event_attributes, diff --git a/events/serializers.py b/events/serializers.py index 41d0ec4..f2175ae 100644 --- a/events/serializers.py +++ b/events/serializers.py @@ -2,7 +2,7 @@ from django.core.exceptions import ValidationError as DjangoValidationError from rest_framework import serializers -from events.enums import EventStatus +from events.enums.status import EventStatus from events.models.event import Event from events.models.signup import EventSignup diff --git a/events/signals.py b/events/signals.py deleted file mode 100644 index e6270e8..0000000 --- a/events/signals.py +++ /dev/null @@ -1,146 +0,0 @@ -from django.core.exceptions import PermissionDenied -from django.db.models.signals import post_delete, post_save, pre_delete, pre_save -from django.dispatch import receiver - -from events.enums import EventStatus -from events.models.banner import Banner -from events.models.category import Category -from events.models.event import Event -from events.models.location import Location -from events.models.schedule import Schedule -from events.models.signup import EventSignup -from preferences.enums import EmailTemplateType -from preferences.models import EmailTemplate -from root.tasks import send_email_task - - -@receiver(pre_delete, sender=Event) -def restrict_event_deletion(instance, **kwargs): - """Restrict the deletion of an event if the user is not the creator or a superuser.""" - request = kwargs.get("request") - if request: - user = request.user - if not (user == instance.created_by or user.is_superuser): - raise PermissionDenied("You do not have permission to delete this event.") - - -@receiver(pre_save, sender=Event) -def validate_event(instance, **kwargs): - """Validate the event instance before saving it to the database.""" - instance.full_clean() - - -@receiver(post_save, sender=EventSignup) -def send_event_registration_email(instance, created, **kwargs): - """Send an email to the user after successfully signing up for an event.""" - if created: - event_signup_template = EmailTemplate.get_email_template_by_type( - EmailTemplateType.EVENT_SIGNUP - ) - - if not event_signup_template: - raise ValueError("Event signup email template not found") - - email_template = { - "html": event_signup_template.body_html, - "plaintext": event_signup_template.body_plaintext, - } - email_title = event_signup_template.subject - - context = { - "user": instance.user.username, - "event": { - "title": instance.event.title, - "start_date": instance.event.schedule.start_date, - "end_date": instance.event.schedule.end_date, - "start_time": instance.event.schedule.start_time, - "end_time": instance.event.schedule.end_time, - "address": instance.event.location.address, - "map_link": instance.event.location.google_map_link, - }, - "current_year": instance.signup_date.year, - } - send_email_task.delay(email_template, instance.user.email, email_title, context) - - -@receiver(pre_save, sender=EventSignup) -def validate_eventSignup(instance, **kwargs): - """Validate the eventsignup instance before saving it to the database.""" - instance.full_clean() - - -@receiver(pre_save, sender=Location) -def validate_location(instance, **kwargs): - """Validate the location instance before saving it to the database.""" - instance.full_clean() - - -@receiver(pre_save, sender=Schedule) -def validate_schedule(instance, **kwargs): - """Validate the schedule instance before saving it to the database.""" - instance.full_clean() - - -@receiver(post_delete, sender=Banner) -@receiver(post_delete, sender=Location) -@receiver(post_delete, sender=Schedule) -def check_verified_event_requirements(instance, **kwargs): - event = instance.event - if event and event.status in [EventStatus.ACTIVE]: - has_banner = Banner.objects.filter(event=event).exists() - has_location = Location.objects.filter(event=event).exists() - has_schedule = Schedule.objects.filter(event=event).exists() - - if not all([has_banner, has_location, has_schedule]): - event.status = EventStatus.DRAFT - event.save() - - -@receiver(pre_delete, sender=Location) -@receiver(pre_delete, sender=Schedule) -def restrict_deletion(instance, **kwargs): - """Restrict the deletion of a related model if the event is verified.""" - if instance.event.status in [EventStatus.ACTIVE.name, EventStatus.COMPLETED.name]: - raise PermissionDenied( - "You cannot delete this object because its associated with active event." - ) - - -@receiver(pre_delete, sender=Banner) -def restrict_banner_deletion(instance, **kwargs): - """Restrict the deletion of a banner if it is the last one for a active/completed event.""" - event = instance.event - if event.status in [EventStatus.ACTIVE.name, EventStatus.COMPLETED.name]: - remaining_banners = Banner.objects.filter(event=event).exclude(id=instance.id).count() - if remaining_banners == 0: - raise PermissionDenied( - "You cannot delete this banner because it is the last one " - "associated with a active event." - ) - - -@receiver(pre_save, sender=Banner) -def validate_banner(instance, **kwargs): - """ - Validate the banner instance before saving it to the database. - Restrict the addition of a banner for a completed/cancelled event. - """ - event = instance.event - if event.status in [EventStatus.COMPLETED, EventStatus.CANCELLED]: - raise PermissionDenied("You cannot add a banner for a cancelled/completed event.") - instance.full_clean() - - -@receiver(pre_save, sender=Category) -def restrict_category_update(instance, **kwargs): - """ - Restrict the update of a category if it is associated with a active/completed event. - """ - if ( - instance.pk - and instance.event_set.filter( - status__in=[EventStatus.ACTIVE.name, EventStatus.COMPLETED.name] - ).exists() - ): - raise PermissionDenied("You cannot update a category linked to an active/completed event.") - instance.full_clean() diff --git a/events/signals/__init__.py b/events/signals/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/events/signals/banner.py b/events/signals/banner.py new file mode 100644 index 0000000..f61354b --- /dev/null +++ b/events/signals/banner.py @@ -0,0 +1,32 @@ +from django.core.exceptions import PermissionDenied +from django.db.models.signals import pre_delete, pre_save +from django.dispatch import receiver + +from events.enums.status import EventStatus +from events.models.banner import Banner + + +@receiver(pre_delete, sender=Banner) +def restrict_banner_deletion(instance, **kwargs): + """ + Restrict the deletion of a banner if it is for a active/completed event. + """ + + if instance.event.status not in [ + EventStatus.DRAFT, + ]: + raise PermissionDenied("You can only delete the banner of a draft event.") + + +@receiver(pre_save, sender=Banner) +def validate_banner(instance, **kwargs): + """ + Validate the banner instance before saving it to the database. + Restrict the addition of a banner for a active/completed/cancelled event. + """ + + if instance.event.status not in [ + EventStatus.DRAFT, + ]: + raise PermissionDenied("You can only add banners to draft events.") + instance.full_clean() diff --git a/events/signals/category.py b/events/signals/category.py new file mode 100644 index 0000000..c3633af --- /dev/null +++ b/events/signals/category.py @@ -0,0 +1,16 @@ +from django.core.exceptions import PermissionDenied +from django.db.models.signals import pre_save +from django.dispatch import receiver + +from events.enums.status import EventStatus +from events.models.category import Category + + +@receiver(pre_save, sender=Category) +def restrict_category_update(instance, **kwargs): + """ + Restrict the update of a category if it is associated with a active/completed event. + """ + if instance.pk and instance.event_set.exclude(status__in=[EventStatus.DRAFT]).exists(): + raise PermissionDenied("You can only update a category linked to a draft event.") + instance.full_clean() diff --git a/events/signals/events.py b/events/signals/events.py new file mode 100644 index 0000000..ff5c1bb --- /dev/null +++ b/events/signals/events.py @@ -0,0 +1,27 @@ +from django.core.exceptions import PermissionDenied +from django.db.models.signals import pre_delete, pre_save +from django.dispatch import receiver + +from events.models.event import Event + + +@receiver(pre_delete, sender=Event) +def restrict_event_deletion(instance, **kwargs): + """ + Restrict the deletion of an event if the user is not the creator or a superuser. + """ + + request = kwargs.get("request") + if request: + user = request.user + if not (user == instance.created_by or user.is_superuser): + raise PermissionDenied("You do not have permission to delete this event.") + + +@receiver(pre_save, sender=Event) +def validate_event(instance, **kwargs): + """ + Validate the event instance before saving it to the database. + """ + + instance.full_clean() diff --git a/events/signals/signup.py b/events/signals/signup.py new file mode 100644 index 0000000..b764c27 --- /dev/null +++ b/events/signals/signup.py @@ -0,0 +1,52 @@ +from django.db.models.signals import post_save, pre_save +from django.dispatch import receiver + +from events.models.signup import EventSignup +from preferences.enums import EmailTemplateType +from preferences.models import EmailTemplate +from root.tasks import send_email_task + + +@receiver(post_save, sender=EventSignup) +def send_event_registration_email(instance, created, **kwargs): + """ + Send an email to the user after successfully signing up for an event. + """ + + if created: + event_signup_template = EmailTemplate.get_email_template_by_type( + EmailTemplateType.EVENT_SIGNUP + ) + + if not event_signup_template: + raise ValueError("Event signup email template not found") + + email_template = { + "html": event_signup_template.body_html, + "plaintext": event_signup_template.body_plaintext, + } + email_title = event_signup_template.subject + + context = { + "user": instance.user.username, + "event": { + "title": instance.event.title, + "start_date": instance.event.schedule.start_date, + "end_date": instance.event.schedule.end_date, + "start_time": instance.event.schedule.start_time, + "end_time": instance.event.schedule.end_time, + "address": instance.event.location.address, + "map_link": instance.event.location.google_map_link, + }, + "current_year": instance.signup_date.year, + } + send_email_task.delay(email_template, instance.user.email, email_title, context) + + +@receiver(pre_save, sender=EventSignup) +def validate_eventSignup(instance, **kwargs): + """ + Validate the eventsignup instance before saving it to the database. + """ + + instance.full_clean() diff --git a/events/signals/triggers.py b/events/signals/triggers.py new file mode 100644 index 0000000..5c71300 --- /dev/null +++ b/events/signals/triggers.py @@ -0,0 +1,52 @@ +from django.core.exceptions import PermissionDenied +from django.db.models.signals import post_delete, pre_delete, pre_save +from django.dispatch import receiver + +from events.enums.status import EventStatus +from events.models.banner import Banner +from events.models.location import Location +from events.models.schedule import Schedule + + +@receiver(pre_save, sender=Schedule) +@receiver(pre_save, sender=Location) +def validate_location(instance, **kwargs): + """ + Validate the instance before saving it to the database. + Dont allow changes to a related model if the event is verified. + """ + + if instance.event.status not in [EventStatus.DRAFT]: + raise PermissionDenied("You can only add objects to draft events.") + instance.full_clean() + + +@receiver(pre_delete, sender=Schedule) +@receiver(pre_delete, sender=Location) +def restrict_deletion(instance, **kwargs): + """ + Restrict the deletion of a related model if the event is verified. + """ + + if instance.event.status not in [EventStatus.DRAFT]: + raise PermissionDenied("You can only delete objects of a draft event.") + + +@receiver(post_delete, sender=Banner) +@receiver(post_delete, sender=Location) +@receiver(post_delete, sender=Schedule) +def check_verified_event_requirements(instance, **kwargs): + """ + Check if the event requirements are met after deleting a related model. + If the requirements are not met, change the event status to draft. + """ + + event = instance.event + if event and event.status in [EventStatus.ACTIVE]: + has_banner = Banner.objects.filter(event=event).exists() + has_location = Location.objects.filter(event=event).exists() + has_schedule = Schedule.objects.filter(event=event).exists() + + if not all([has_banner, has_location, has_schedule]): + event.status = EventStatus.DRAFT + event.save() diff --git a/events/tests.py b/events/tests.py deleted file mode 100644 index a39b155..0000000 --- a/events/tests.py +++ /dev/null @@ -1 +0,0 @@ -# Create your tests here. diff --git a/events/validators.py b/events/validators.py index 55d60be..2512523 100644 --- a/events/validators.py +++ b/events/validators.py @@ -5,7 +5,7 @@ from django.utils import timezone from django.utils.timezone import make_aware -from events.enums import EventStatus +from events.enums.status import EventStatus def validate_event_dates_and_time(instance): diff --git a/events/views.py b/events/views.py index 75cec46..719c63b 100644 --- a/events/views.py +++ b/events/views.py @@ -3,10 +3,10 @@ from rest_framework.permissions import AllowAny, IsAuthenticated from rest_framework.response import Response +from events.enums.status import EventStatus from events.models.event import Event from events.models.signup import EventSignup -from .enums import EventStatus from .serializers import EventSerializer, EventSignupSerializer