From 308415f133490573ea271c3f9014aa215f3975ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20H=C3=B8rl=C3=BCck=20Berg?= <36937807+henrikhorluck@users.noreply.github.com> Date: Sun, 1 Sep 2024 14:04:33 +0200 Subject: [PATCH] Remove attendance through UI in OW4 --- apps/events/api/tests/attendance_tests.py | 144 ++++- apps/events/forms.py | 74 --- apps/events/models/Attendance.py | 4 + apps/events/templatetags/__init__.py | 0 apps/events/templatetags/calendar_filters.py | 10 - apps/events/templatetags/payment_tag.py | 8 - apps/events/templatetags/shuffle.py | 12 - apps/events/tests/api_tests.py | 97 --- apps/events/tests/utils.py | 20 +- apps/events/tests/view_tests.py | 603 +----------------- apps/events/urls.py | 31 +- apps/events/utils.py | 134 +--- apps/events/views.py | 290 +-------- apps/notifications/utils.py | 9 +- assets/events/archive/filters.js | 82 --- assets/events/archive/index.js | 6 - assets/events/archive/less/event_archive.less | 103 --- assets/events/details/events.js | 91 --- assets/events/details/index.js | 4 - assets/events/details/less/event_details.less | 276 -------- assets/frontpage/events/components/Event.jsx | 33 - .../events/components/EventFilter.jsx | 35 - .../events/components/EventImage.jsx | 32 - assets/frontpage/events/components/Events.jsx | 40 -- .../events/components/EventsHeading.jsx | 33 - .../events/components/SmallEvent.jsx | 21 - .../events/containers/EventsContainer.jsx | 195 ------ assets/frontpage/events/utils/index.js | 64 -- assets/frontpage/index.jsx | 11 - assets/frontpage/initFrontpage.js | 113 ---- assets/frontpage/less/events.less | 217 ------- assets/frontpage/less/frontpage.less | 1 - esbuild.mjs | 2 - onlineweb4/urls.py | 21 - pyproject.toml | 2 +- templates/events/dashboard/details.html | 14 + templates/events/details.html | 477 -------------- templates/events/extras.html | 20 - templates/events/index.html | 103 --- templates/events/payment_tag.html | 17 - templates/events/search.html | 78 --- templates/frontpage.html | 20 +- uv.lock | 2 +- 43 files changed, 204 insertions(+), 3345 deletions(-) delete mode 100644 apps/events/forms.py delete mode 100644 apps/events/templatetags/__init__.py delete mode 100644 apps/events/templatetags/calendar_filters.py delete mode 100644 apps/events/templatetags/payment_tag.py delete mode 100644 apps/events/templatetags/shuffle.py delete mode 100644 apps/events/tests/api_tests.py delete mode 100644 assets/events/archive/filters.js delete mode 100644 assets/events/archive/index.js delete mode 100644 assets/events/archive/less/event_archive.less delete mode 100644 assets/events/details/events.js delete mode 100644 assets/events/details/index.js delete mode 100644 assets/events/details/less/event_details.less delete mode 100644 assets/frontpage/events/components/Event.jsx delete mode 100644 assets/frontpage/events/components/EventFilter.jsx delete mode 100644 assets/frontpage/events/components/EventImage.jsx delete mode 100644 assets/frontpage/events/components/Events.jsx delete mode 100644 assets/frontpage/events/components/EventsHeading.jsx delete mode 100644 assets/frontpage/events/components/SmallEvent.jsx delete mode 100644 assets/frontpage/events/containers/EventsContainer.jsx delete mode 100644 assets/frontpage/events/utils/index.js delete mode 100755 assets/frontpage/initFrontpage.js delete mode 100755 assets/frontpage/less/events.less delete mode 100644 templates/events/details.html delete mode 100644 templates/events/extras.html delete mode 100644 templates/events/index.html delete mode 100644 templates/events/payment_tag.html delete mode 100644 templates/events/search.html diff --git a/apps/events/api/tests/attendance_tests.py b/apps/events/api/tests/attendance_tests.py index 53e6bc5e1..dc0299e37 100644 --- a/apps/events/api/tests/attendance_tests.py +++ b/apps/events/api/tests/attendance_tests.py @@ -1,9 +1,17 @@ +from datetime import UTC, datetime, timedelta +from time import sleep + +from django.conf import settings from django.contrib.auth.models import Group +from django.core import mail from django.utils import timezone from django_dynamic_fixture import G +from freezegun import freeze_time from rest_framework import status -from rest_framework.test import APITestCase +from rest_framework.test import APITestCase, APITransactionTestCase +from apps.events.models import Event +from apps.events.models.Attendance import AttendanceEvent, Attendee from apps.events.tests.utils import ( attend_user_to_event, generate_event, @@ -11,12 +19,18 @@ generate_user, pay_for_event, ) +from apps.marks.models import MarkRuleSet +from apps.notifications.constants import PermissionType +from apps.notifications.models import Permission +from apps.payment.models import PaymentDelay from onlineweb4.fields.turnstile import mock_validate_turnstile from onlineweb4.testing import GetUrlMixin from ...models import Extras, StatusCode from .utils import generate_attendee +User = settings.AUTH_USER_MODEL + class AttendanceEventTestCase(GetUrlMixin, APITestCase): basename = "events_attendance_events" @@ -100,7 +114,7 @@ def test_guest_user_can_register_for_event_with_guest_attendance(self, _): @mock_validate_turnstile() def test_signup_settings_override_defaults(self, _): - test_cases: list[tuple[dict[str, bool]]] = [ + test_cases: list[tuple[dict[str, bool], dict[str, bool], dict[str, bool]]] = [ ( { "show_as_attending_event": True, @@ -450,3 +464,129 @@ def test_extras_only_works_for_authenticated_users(self): response = self.client.get(self.get_extras_url(self.event.id)) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + +# This needs to be a TransactionTestCase since notifications are sent using on_commit hooks +# see https://docs.djangoproject.com/en/5.1/topics/testing/tools/#django.test.TransactionTestCase +class EventsUnattendWaitList(GetUrlMixin, APITransactionTestCase): + basename = "events_attendance_events" + + def setUp(self): + G(Permission, permission_type=PermissionType.WAIT_LIST_BUMP, force_email=True) + self.event = G(Event, event_start=timezone.now() + timedelta(days=1)) + G( + AttendanceEvent, + event=self.event, + unattend_deadline=timezone.now() + timedelta(days=1), + max_capacity=2, + waitlist=True, + ) + self.user = generate_user("test") + self.client.force_login(self.user) + self.other_user = generate_user("other") + self.url = self.get_action_url("unregister", self.event.id) + self.rule_Set = G( + MarkRuleSet, + duration=timedelta(days=14), + valid_from_date=datetime(year=2016, month=1, day=1, tzinfo=UTC), + ) + + def test_unattend_notifies_waitlist_when_attending(self): + G(Attendee, event=self.event.attendance_event) + attend_user_to_event(self.event, self.user) + G(Attendee, event=self.event.attendance_event, n=3) + + r = self.client.delete(self.url) + + self.assertEqual(r.status_code, status.HTTP_204_NO_CONTENT) + self.assertEqual(len(mail.outbox), 1) + self.assertIn("Du har fått plass på", mail.outbox[0].subject) + + def test_unattend_does_not_notify_waitlist_when_on_waitlist(self): + G(Attendee, event=self.event.attendance_event, n=2) + attend_user_to_event(self.event, self.user) + G(Attendee, event=self.event.attendance_event) + + r = self.client.delete(self.url) + + self.assertEqual(r.status_code, status.HTTP_204_NO_CONTENT) + sleep(2) + self.assertEqual(len(mail.outbox), 0) + + @freeze_time("2017-01-01 12:00") + def test_payment_type_instant_uses_extended(self): + generate_payment(self.event, payment_type=1) + G(Attendee, event=self.event.attendance_event) + attend_user_to_event(self.event, self.user) + attend_user_to_event(self.event, self.other_user) + G(Attendee, event=self.event.attendance_event) + payment_delay_time = timedelta(days=2) + + r = self.client.delete(self.url) + + self.assertEqual(r.status_code, status.HTTP_204_NO_CONTENT) + sleep(2) + self.assertEqual(len(mail.outbox), 1) + self.assertIn("Du har fått plass på", mail.outbox[0].subject) + payment_delay = PaymentDelay.objects.get(user=self.other_user) + self.assertEqual(payment_delay.valid_to, timezone.now() + payment_delay_time) + + def test_payment_delay_is_not_created_if_deadline_over_48_hours(self): + generate_payment( + self.event, payment_type=2, deadline=timezone.now() + timedelta(days=3) + ) + G(Attendee, event=self.event.attendance_event) + attend_user_to_event(self.event, self.user) + attend_user_to_event(self.event, self.other_user) + G(Attendee, event=self.event.attendance_event) + + r = self.client.delete(self.url) + + self.assertEqual(r.status_code, status.HTTP_204_NO_CONTENT) + sleep(2) + self.assertEqual(len(mail.outbox), 1) + self.assertIn("Du har fått plass på", mail.outbox[0].subject) + payment_delay = PaymentDelay.objects.filter(user=self.other_user) + self.assertFalse(payment_delay.exists()) + + @freeze_time("2017-01-01 12:00") + def test_payment_delay_is_created_if_deadline_under_48_hours(self): + generate_payment( + self.event, payment_type=2, deadline=timezone.now() + timedelta(hours=47) + ) + G(Attendee, event=self.event.attendance_event) + attend_user_to_event(self.event, self.user) + other_user_attendee = attend_user_to_event(self.event, self.other_user) + G(Attendee, event=self.event.attendance_event) + payment_delay_time = timedelta(days=2) + + r = self.client.delete(self.url) + + self.assertIn( + other_user_attendee, self.event.attendance_event.attending_attendees_qs + ) + self.assertEqual(r.status_code, status.HTTP_204_NO_CONTENT) + sleep(2) + self.assertEqual(len(mail.outbox), 1) + self.assertIn("Du har fått plass på", mail.outbox[0].subject) + payment_delay = PaymentDelay.objects.get(user=self.other_user) + self.assertEqual(payment_delay.valid_to, timezone.now() + payment_delay_time) + + @freeze_time("2017-01-01 12:00") + def test_payment_type_delay_uses_payment_delay(self): + delay_days = 4 + payment_delay_time = timedelta(days=delay_days) + generate_payment(self.event, payment_type=3, delay=payment_delay_time) + G(Attendee, event=self.event.attendance_event) + attend_user_to_event(self.event, self.user) + attend_user_to_event(self.event, self.other_user) + G(Attendee, event=self.event.attendance_event) + + r = self.client.delete(self.url) + + self.assertEqual(r.status_code, status.HTTP_204_NO_CONTENT) + sleep(2) + self.assertEqual(len(mail.outbox), 1) + self.assertIn("Du har fått plass på", mail.outbox[0].subject) + payment_delay = PaymentDelay.objects.get(user=self.other_user) + self.assertEqual(payment_delay.valid_to, timezone.now() + payment_delay_time) diff --git a/apps/events/forms.py b/apps/events/forms.py deleted file mode 100644 index 6546f237c..000000000 --- a/apps/events/forms.py +++ /dev/null @@ -1,74 +0,0 @@ -from django import forms -from django.conf import settings -from django.utils.translation import gettext as _ -from turnstile.fields import TurnstileField - -from apps.authentication.models import OnlineUser as User -from apps.marks.models import MarkRuleSet - - -class CaptchaForm(forms.Form): - phone_number = forms.CharField( - label=_("Telefonnummer er påkrevd for å være påmeldt et arrangement."), - error_messages={"required": _("Telefonnummer er påkrevd!")}, - ) - note = forms.CharField( - label=_( - "Som gjest ønsker vi at du oppgir din tilhørighet til Online og annen tilleggsinformasjon som f.eks. " - "hvem du ønsker å sitte med." - ), - error_messages={"required": _("Du må fylle inn et notat!")}, - max_length=100, - ) - mark_rules = forms.BooleanField( - label=_( - 'Jeg godtar prikkreglene' - ), - error_messages={"required": _("Du må godta prikkereglene!")}, - ) - captcha = TurnstileField( - error_messages={ - "error_turnstile": ("Du klarte ikke captchaen! Er du en bot?"), - "invalid_turnstile": ("Du klarte ikke captchaen! Er du en bot?"), - "required": ("Vennligst vis at du ikke er en bot."), - } - ) - - def __init__(self, *args, **kwargs): - self.user: User = kwargs.pop("user", None) - super().__init__(*args, **kwargs) - - # Removing mark rules field if user has already accepted the rules - if self.user and self.user.is_authenticated: - if self.user.mark_rules_accepted: - del self.fields["mark_rules"] - - if self.user.phone_number: - del self.fields["phone_number"] - - if self.user.is_member: - del self.fields["note"] - - if not settings.OW4_SETTINGS.get("events", {}).get("ENABLE_CAPTCHA", True): - del self.fields["captcha"] - - def clean(self): - super().clean() - cleaned_data = self.cleaned_data - - if "mark_rules" in self.fields: - if "mark_rules" in cleaned_data: - mark_rules = cleaned_data["mark_rules"] - - if mark_rules: - MarkRuleSet.accept_mark_rules(self.user) - - if "phone_number" in self.fields: - if "phone_number" in cleaned_data: - phone_number = cleaned_data["phone_number"] - - if phone_number: - self.user.phone_number = phone_number - self.user.save() - - return cleaned_data diff --git a/apps/events/models/Attendance.py b/apps/events/models/Attendance.py index de360e625..a05833664 100644 --- a/apps/events/models/Attendance.py +++ b/apps/events/models/Attendance.py @@ -730,6 +730,10 @@ def unattend(self, admin_user): if not self.has_paid: self._clean_payment_delays() + # if self.has_paid: + # if self.payment_relations.first() + # we need to issue a refund + # TODO: Not delete attendee unless payments have been refunded? self.delete() diff --git a/apps/events/templatetags/__init__.py b/apps/events/templatetags/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/apps/events/templatetags/calendar_filters.py b/apps/events/templatetags/calendar_filters.py deleted file mode 100644 index 43f89645e..000000000 --- a/apps/events/templatetags/calendar_filters.py +++ /dev/null @@ -1,10 +0,0 @@ -import re - -from django import template - -register = template.Library() - - -@register.filter -def unhttps(url): - return re.sub(r"^https", "http", url) diff --git a/apps/events/templatetags/payment_tag.py b/apps/events/templatetags/payment_tag.py deleted file mode 100644 index f0450994f..000000000 --- a/apps/events/templatetags/payment_tag.py +++ /dev/null @@ -1,8 +0,0 @@ -from django import template - -register = template.Library() - - -@register.inclusion_tag("events/payment_tag.html") -def display_payments(payment, payment_delay): - return {"payment": payment, "payment_delay": payment_delay} diff --git a/apps/events/templatetags/shuffle.py b/apps/events/templatetags/shuffle.py deleted file mode 100644 index 45d2b57f0..000000000 --- a/apps/events/templatetags/shuffle.py +++ /dev/null @@ -1,12 +0,0 @@ -import random - -from django import template - -register = template.Library() - - -@register.filter -def shuffle(arg): - li = list(arg) - random.shuffle(li) - return li diff --git a/apps/events/tests/api_tests.py b/apps/events/tests/api_tests.py deleted file mode 100644 index 02754061d..000000000 --- a/apps/events/tests/api_tests.py +++ /dev/null @@ -1,97 +0,0 @@ -from django.contrib.auth.models import Group -from django.urls import reverse -from django.utils import timezone -from django_dynamic_fixture import G -from rest_framework import status -from rest_framework.test import APITestCase - -from apps.authentication.models import OnlineUser -from apps.companyprofile.models import Company -from apps.events.models import CompanyEvent, GroupRestriction - -from .utils import attend_user_to_event, generate_event, generate_user - - -def generate_attendee(event, username, rfid): - user = G(OnlineUser, username=username, rfid=rfid) - return attend_user_to_event(event, user) - - -class EventsAPITestCase(APITestCase): - def setUp(self): - self.committee = G(Group, name="Bedkom") - self.user = generate_user(username="_user") - self.client.force_authenticate(user=self.user) - - self.url = reverse("events-list") - self.id_url = lambda _id: self.url + str(_id) + "/" - self.event = generate_event(organizer=self.committee) - self.event.attendance_event.registration_start = timezone.now() - self.event.attendance_event.registration_end = ( - timezone.now() + timezone.timedelta(days=2) - ) - self.event.attendance_event.max_capacity = 20 - self.event.attendance_event.save() - self.attendee1 = generate_attendee(self.event, "test1", "1231") - self.attendee2 = generate_attendee(self.event, "test2", "4321") - self.attendees = [self.attendee1, self.attendee2] - - def test_events_list_empty(self): - with self.assertNumQueries(7): - response = self.client.get(self.url) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - def test_events_detail(self): - with self.assertNumQueries(6): - response = self.client.get(self.id_url(self.event.id)) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - def test_events_list_exists(self): - response = self.client.get(self.url) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - events_list = response.json().get("results") - event_titles_list = [event.get("title") for event in events_list] - - self.assertIn(self.event.title, event_titles_list) - - def test_filter_companies_in_event_list(self): - onlinecorp: Company = G(Company, name="onlinecorp") - bedpres_with_onlinecorp = generate_event(organizer=self.committee) - G(CompanyEvent, company=onlinecorp, event=bedpres_with_onlinecorp) - evilcorp: Company = G(Company, name="evilcorp") - bedpres_with_evilcorp = generate_event(organizer=self.committee) - G(CompanyEvent, company=evilcorp, event=bedpres_with_evilcorp) - - response = self.client.get(f"{self.url}?companies={onlinecorp.id}") - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - events_list = response.json().get("results") - event_titles_list = [event.get("id") for event in events_list] - - self.assertIn(bedpres_with_onlinecorp.id, event_titles_list) - self.assertNotIn(bedpres_with_evilcorp.id, event_titles_list) - - def test_event_with_group_restriction(self): - with self.assertNumQueries(6): - response = self.client.get(self.id_url(self.event.id)) - self.assertEqual(response.status_code, status.HTTP_200_OK) - - restricted_to_group: Group = G(Group) - G(GroupRestriction, event=self.event, groups=[restricted_to_group]) - - with self.assertNumQueries(1): - response = self.client.get(self.id_url(self.event.id)) - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - - attendee = attend_user_to_event(self.event, self.user) - - self.assertIn(attendee, self.event.attendance_event.attendees.all()) - - with self.assertNumQueries(6): - response = self.client.get(self.id_url(self.event.id)) - self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/apps/events/tests/utils.py b/apps/events/tests/utils.py index b5cc395be..9fde47cd3 100644 --- a/apps/events/tests/utils.py +++ b/apps/events/tests/utils.py @@ -7,14 +7,14 @@ from apps.authentication.models import GroupMember, OnlineGroup, OnlineUser from apps.companyprofile.models import Company from apps.events.models.Attendance import CompanyEvent -from apps.payment.models import Payment, PaymentDelay, PaymentPrice, PaymentRelation +from apps.payment.models import Payment, PaymentPrice, PaymentRelation from ..constants import EventType from ..models import AttendanceEvent, Attendee, Event def generate_event( - event_type=EventType.BEDPRES, organizer: Group = None, attendance=True + event_type=EventType.BEDPRES, organizer: Group | None = None, attendance=True ) -> Event: if organizer is None: organizer = create_committee_group() @@ -25,7 +25,7 @@ def generate_event( def generate_company_event( - event_type=EventType.BEDPRES, organizer: Group = None, attendance=True + event_type=EventType.BEDPRES, organizer: Group | None = None, attendance=True ) -> Event: onlinecorp: Company = G(Company, name="onlinecorp") bedpres_with_onlinecorp = generate_event( @@ -66,10 +66,6 @@ def pay_for_event(event: Event, user: OnlineUser, *args, **kwargs) -> PaymentRel ) -def add_payment_delay(payment: Payment, user: OnlineUser) -> PaymentDelay: - return G(PaymentDelay, payment=payment, user=user) - - def generate_user(username: str, *args, **kwargs) -> OnlineUser: user = G( OnlineUser, @@ -87,16 +83,6 @@ def generate_attendee(event: Event, username: str) -> Attendee: return attend_user_to_event(event, generate_user(username)) -def add_to_committee(user: OnlineUser, group: Group = None) -> OnlineUser: - committee_group = create_committee_group() - add_to_group(committee_group, user) - - if group: - add_to_group(group, user) - - return user - - def create_committee_group(group: Group = None): if not group: group: Group = G(Group) diff --git a/apps/events/tests/view_tests.py b/apps/events/tests/view_tests.py index 4811308e0..d284493ab 100644 --- a/apps/events/tests/view_tests.py +++ b/apps/events/tests/view_tests.py @@ -1,33 +1,20 @@ -from datetime import UTC, datetime, timedelta -from unittest.mock import patch - from django.contrib.auth.models import Group from django.core import mail -from django.test import TestCase, TransactionTestCase +from django.test import TestCase from django.urls import reverse -from django.utils import timezone from django_dynamic_fixture import G -from freezegun import freeze_time from rest_framework import status -from apps.authentication.models import Membership, OnlineGroup +from apps.authentication.models import OnlineGroup from apps.marks.models import MarkRuleSet -from apps.notifications.constants import PermissionType -from apps.notifications.models import Permission -from apps.payment.models import PaymentDelay, PaymentPrice from ..constants import EventType -from ..models import AttendanceEvent, Event, Extras, GroupRestriction, StatusCode +from ..models import Event from .utils import ( - add_payment_delay, add_to_group, - attend_user_to_event, create_committee_group, - generate_attendee, generate_event, - generate_payment, generate_user, - pay_for_event, ) @@ -54,560 +41,6 @@ def assertInMessages(self, message_text, response): self.assertIn(message_text, messages) -class EventsDetailRestricted(EventsTestMixin, TestCase): - def test_ok(self): - response = self.client.get(self.event_url) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - def test_404(self): - event = generate_event() - url = reverse("events_details", args=(event.id + 10, event.slug)) - - response = self.client.get(url) - - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - - def test_group_restricted_access(self): - add_to_group(self.admin_group, self.user) - G(GroupRestriction, event=self.event, groups=[self.admin_group]) - - response = self.client.get(self.event_url) - messages = list(response.context["messages"]) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(len(messages), 0) - - def test_group_restricted_no_access(self): - access_group = create_committee_group() - G(GroupRestriction, event=self.event, groups=[access_group]) - - response = self.client.get(self.event_url) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertInMessages("Du har ikke tilgang til dette arrangementet.", response) - - def test_group_hidden_no_access(self): - self.event = G(Event, visible=False) - self.event_url = reverse( - "events_details", args=(self.event.id, self.event.slug) - ) - - response = self.client.get(self.event_url) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertInMessages("Du har ikke tilgang til dette arrangementet.", response) - - -class EventsDetailPayment(EventsTestMixin, TestCase): - def test_payment_logged_out(self): - payment = generate_payment(self.event) - - self.client.logout() - response = self.client.get(self.event_url) - context = response.context - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(context["payment"], payment) - self.assertEqual(context["user_paid"], False) - self.assertEqual(context["payment_delay"], None) - self.assertEqual(context["payment_relation_id"], None) - - def test_payment_not_attended(self): - payment = generate_payment(self.event) - - response = self.client.get(self.event_url) - context = response.context - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(context["payment"], payment) - self.assertEqual(context["user_attending"], False) - self.assertEqual(context["user_paid"], False) - self.assertEqual(context["payment_delay"], None) - self.assertEqual(context["payment_relation_id"], None) - - def test_payment_attended(self): - payment = generate_payment(self.event) - attend_user_to_event(self.event, self.user) - - response = self.client.get(self.event_url) - context = response.context - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(context["payment"], payment) - self.assertEqual(context["user_attending"], True) - self.assertEqual(context["user_paid"], False) - self.assertEqual(context["payment_delay"], None) - self.assertEqual(context["payment_relation_id"], None) - - def test_payment_paid(self): - payment = generate_payment(self.event) - attend_user_to_event(self.event, self.user) - payment_relation = pay_for_event(self.event, self.user) - - response = self.client.get(self.event_url) - context = response.context - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(context["payment"], payment) - self.assertEqual(context["user_attending"], True) - self.assertEqual(context["user_paid"], True) - self.assertEqual(context["payment_delay"], None) - self.assertEqual(context["payment_relation_id"], payment_relation.id) - - def test_payment_attended_with_delay(self): - payment = generate_payment(self.event) - payment_delay = add_payment_delay(payment, self.user) - attend_user_to_event(self.event, self.user) - - response = self.client.get(self.event_url) - context = response.context - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(context["payment"], payment) - self.assertEqual(context["user_attending"], True) - self.assertEqual(context["user_paid"], False) - self.assertEqual(context["payment_delay"], payment_delay) - self.assertEqual(context["payment_relation_id"], None) - - -class EventsDetailExtras(EventsTestMixin, TestCase): - def extras_post(self, event_url, extras_id): - return self.client.post( - event_url, - {"action": "extras", "extras_id": extras_id}, - HTTP_X_REQUESTED_WITH="XMLHttpRequest", - ) - - def test_extras_on_non_attendance_event(self): - event = G(Event) - extras = G(Extras) - event_url = reverse("events_details", args=(event.id, event.slug)) - - response = self.extras_post(event_url, extras.id) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual( - response.json()["message"], "Dette er ikke et påmeldingsarrangement." - ) - - def test_extras_on_not_attended_event(self): - extras = G(Extras) - - response = self.extras_post(self.event_url, extras.id) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual( - response.json()["message"], "Du er ikke påmeldt dette arrangementet." - ) - - def test_invalid_extras(self): - attend_user_to_event(self.event, self.user) - - response = self.extras_post(self.event_url, 1000) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.json()["message"], "Ugyldig valg") - - def test_extras_success(self): - extras = G(Extras) - event = G(Event) - G(AttendanceEvent, event=event, extras=[extras]) - attend_user_to_event(event, self.user) - event_url = reverse("events_details", args=(event.id, event.slug)) - - response = self.extras_post(event_url, extras.id) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.json()["message"], "Lagret ditt valg") - - -class EventsAttend(EventsTestMixin, TestCase): - def test_attend_404(self): - url = reverse("attend_event", args=(1000,)) - - response = self.client.post(url) - - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - - def test_attend_not_attendance_event(self): - event = G(Event) - url = reverse("attend_event", args=(event.id,)) - - response = self.client.post(url, follow=True) - - self.assertRedirects(response, event.get_absolute_url()) - self.assertInMessages("Dette er ikke et påmeldingsarrangement.", response) - - def test_attend_get(self): - url = reverse("attend_event", args=(self.event.id,)) - - response = self.client.get(url, follow=True) - - self.assertRedirects(response, self.event.get_absolute_url()) - self.assertInMessages("Vennligst fyll ut skjemaet.", response) - - def test_attend_missing_note(self): - url = reverse("attend_event", args=(self.event.id,)) - - response = self.client.post(url, self.dummy_form, follow=True) - - self.assertRedirects(response, self.event.get_absolute_url()) - self.assertInMessages("Du må fylle inn et notat!", response) - - def test_attend_not_accepted_rules(self): - url = reverse("attend_event", args=(self.event.id,)) - G( - Membership, - username=self.user.ntnu_username, - expiration_date=timezone.now() + timedelta(days=1), - ) - - response = self.client.post(url, self.dummy_form, follow=True) - - self.assertRedirects(response, self.event.get_absolute_url()) - self.assertInMessages("Du må godta prikkereglene!", response) - - def test_attend_invalid_captcha(self): - event = G(Event) - G( - AttendanceEvent, - event=event, - registration_start=timezone.now() - timedelta(days=1), - registration_end=timezone.now() + timedelta(days=1), - ) - url = reverse("attend_event", args=(event.id,)) - - G( - Membership, - username=self.user.ntnu_username, - expiration_date=timezone.now() + timedelta(days=1), - ) - MarkRuleSet.accept_mark_rules(self.user) - - response = self.client.post( - url, {"cf-turnstile-response": "WHATEVER"}, follow=True - ) - - self.assertRedirects(response, event.get_absolute_url()) - self.assertInMessages("Du klarte ikke captchaen! Er du en bot?", response) - - @patch("turnstile.fields.TurnstileField.validate") - def test_attend_before_registration_start(self, mocked_submit): - mocked_submit.return_value = None # no throw - event = G(Event) - G( - AttendanceEvent, - event=event, - registration_start=timezone.now() + timedelta(days=1), - registration_end=timezone.now() + timedelta(days=2), - ) - url = reverse("attend_event", args=(event.id,)) - - G( - Membership, - username=self.user.ntnu_username, - expiration_date=timezone.now() + timedelta(days=1), - ) - MarkRuleSet.accept_mark_rules(self.user) - - response = self.client.post(url, self.dummy_form, follow=True) - - self.assertRedirects(response, event.get_absolute_url()) - self.assertInMessages("Påmeldingen har ikke åpnet enda.", response) - - @patch("turnstile.fields.TurnstileField.validate") - def test_attend_successfully(self, mocked_submit): - mocked_submit.return_value = None # no throw - event = G(Event) - G( - AttendanceEvent, - event=event, - registration_start=timezone.now() - timedelta(days=1), - registration_end=timezone.now() + timedelta(days=1), - ) - url = reverse("attend_event", args=(event.id,)) - - G( - Membership, - username=self.user.ntnu_username, - expiration_date=timezone.now() + timedelta(days=1), - ) - MarkRuleSet.accept_mark_rules(self.user) - - response = self.client.post(url, self.dummy_form, follow=True) - - self.assertRedirects(response, event.get_absolute_url()) - self.assertInMessages("Du er nå meldt på arrangementet.", response) - - @patch("turnstile.fields.TurnstileField.validate") - def test_attend_twice(self, mocked_submit): - mocked_submit.return_value = None # no throw - event = G(Event) - G( - AttendanceEvent, - event=event, - registration_start=timezone.now() - timedelta(days=1), - registration_end=timezone.now() + timedelta(days=1), - ) - url = reverse("attend_event", args=(event.id,)) - - G( - Membership, - username=self.user.ntnu_username, - expiration_date=timezone.now() + timedelta(days=1), - ) - MarkRuleSet.accept_mark_rules(self.user) - - self.client.post(url, self.dummy_form, follow=True) - response = self.client.post(url, self.dummy_form, follow=True) - - self.assertRedirects(response, event.get_absolute_url()) - self.assertInMessages(StatusCode.ALREADY_SIGNED_UP.message, response) - - @patch("turnstile.fields.TurnstileField.validate") - def test_attend_with_payment_creates_paymentdelay(self, mocked_submit): - mocked_submit.return_value = None # no throw - event = G(Event) - G( - AttendanceEvent, - event=event, - registration_start=timezone.now() - timedelta(days=1), - registration_end=timezone.now() + timedelta(days=1), - ) - self.event_payment = generate_payment( - event, payment_type=3, delay=timedelta(days=2) - ) - G(PaymentPrice, price=200, payment=self.event_payment) - url = reverse("attend_event", args=(event.id,)) - - G( - Membership, - username=self.user.ntnu_username, - expiration_date=timezone.now() + timedelta(days=1), - ) - MarkRuleSet.accept_mark_rules(self.user) - - self.client.post(url, self.dummy_form, follow=True) - - self.assertTrue(PaymentDelay.objects.filter(user=self.user).exists()) - - -class EventsUnattend(EventsTestMixin, TestCase): - def test_unattend_not_attended(self): - url = reverse("unattend_event", args=(self.event.id,)) - - response = self.client.post(url, follow=True) - - self.assertRedirects(response, self.event.get_absolute_url()) - self.assertInMessages("Du er ikke påmeldt dette arrangementet.", response) - - def test_unattend_not_attendance_event(self): - event = G(Event) - url = reverse("unattend_event", args=(event.id,)) - - response = self.client.post(url, follow=True) - - self.assertRedirects(response, event.get_absolute_url()) - self.assertInMessages("Dette er ikke et påmeldingsarrangement.", response) - - def test_unattend_deadline_yesterday(self): - event = G(Event) - G( - AttendanceEvent, - event=event, - unattend_deadline=timezone.now() - timedelta(days=1), - ) - attend_user_to_event(event, self.user) - url = reverse("unattend_event", args=(event.id,)) - - response = self.client.post(url, follow=True) - - self.assertRedirects(response, event.get_absolute_url()) - self.assertInMessages( - "Avmeldingsfristen for dette arrangementet har utløpt.", response - ) - - def test_unattend_event_started(self): - event = G(Event, event_start=timezone.now() - timedelta(days=1)) - G( - AttendanceEvent, - event=event, - unattend_deadline=timezone.now() + timedelta(days=1), - ) - attend_user_to_event(event, self.user) - url = reverse("unattend_event", args=(event.id,)) - - response = self.client.post(url, follow=True) - - self.assertRedirects(response, event.get_absolute_url()) - self.assertInMessages("Dette arrangementet har allerede startet.", response) - - def test_unattend_successfully(self): - event = G(Event, event_start=timezone.now() + timedelta(days=1)) - G( - AttendanceEvent, - event=event, - unattend_deadline=timezone.now() + timedelta(days=1), - ) - attend_user_to_event(event, self.user) - url = reverse("unattend_event", args=(event.id,)) - - response = self.client.post(url, follow=True, HTTP_HOST="example.com") - - self.assertRedirects(response, event.get_absolute_url()) - self.assertInMessages("Du ble meldt av arrangementet.", response) - - def test_unattend_payment_not_refunded(self): - event = G(Event, event_start=timezone.now() + timedelta(days=1)) - G( - AttendanceEvent, - event=event, - unattend_deadline=timezone.now() + timedelta(days=1), - ) - attend_user_to_event(event, self.user) - generate_payment(event) - pay_for_event(event, self.user) - url = reverse("unattend_event", args=(event.id,)) - - response = self.client.post(url, follow=True, HTTP_HOST="example.com") - - self.assertRedirects(response, event.get_absolute_url()) - self.assertInMessages( - "Du har betalt for arrangementet og må refundere før du kan melde deg av", - response, - ) - - def test_unattend_payment_removes_payment_delays(self): - event = G(Event, event_start=timezone.now() + timedelta(days=1)) - G( - AttendanceEvent, - event=event, - unattend_deadline=timezone.now() + timedelta(days=1), - ) - attend_user_to_event(event, self.user) - payment = generate_payment(event) - pay_for_event(event, self.user, refunded=True) - payment_delay = add_payment_delay(payment, self.user) - url = reverse("unattend_event", args=(event.id,)) - - response = self.client.post(url, follow=True, HTTP_HOST="example.com") - - self.assertRedirects(response, event.get_absolute_url()) - self.assertInMessages("Du ble meldt av arrangementet.", response) - self.assertEqual(PaymentDelay.objects.filter(id=payment_delay.id).count(), 0) - - -class EventsUnattendWaitlist(TransactionTestCase): - def setUp(self): - G(Permission, permission_type=PermissionType.WAIT_LIST_BUMP, force_email=True) - self.event = G(Event, event_start=timezone.now() + timedelta(days=1)) - G( - AttendanceEvent, - event=self.event, - unattend_deadline=timezone.now() + timedelta(days=1), - max_capacity=2, - waitlist=True, - ) - self.user = generate_user("test") - self.client.force_login(self.user) - self.other_user = generate_user("other") - self.url = reverse("unattend_event", args=(self.event.id,)) - self.rule_Set = G( - MarkRuleSet, - duration=timedelta(days=14), - valid_from_date=datetime(year=2016, month=1, day=1, tzinfo=UTC), - ) - - def test_unattend_notifies_waitlist_when_attending(self): - generate_attendee(self.event, "user1") - attend_user_to_event(self.event, self.user) - generate_attendee(self.event, "user2") - generate_attendee(self.event, "user3") - - self.client.post(self.url, follow=True, HTTP_HOST="example.com") - - self.assertEqual(len(mail.outbox), 1) - self.assertIn("Du har fått plass på", mail.outbox[0].subject) - - def test_unattend_does_not_notify_waitlist_when_on_waitlist(self): - generate_attendee(self.event, "user1") - generate_attendee(self.event, "user2") - attend_user_to_event(self.event, self.user) - generate_attendee(self.event, "user3") - - self.client.post(self.url, follow=True, HTTP_HOST="example.com") - - self.assertEqual(len(mail.outbox), 0) - - @freeze_time("2017-01-01 12:00") - def test_payment_type_instant_uses_extended(self): - generate_payment(self.event, payment_type=1) - generate_attendee(self.event, "user1") - attend_user_to_event(self.event, self.user) - attend_user_to_event(self.event, self.other_user) - generate_attendee(self.event, "user3") - payment_delay_time = timedelta(days=2) - - self.client.post(self.url, follow=True, HTTP_HOST="example.com") - - self.assertEqual(len(mail.outbox), 1) - self.assertIn("Du har fått plass på", mail.outbox[0].subject) - payment_delay = PaymentDelay.objects.get(user=self.other_user) - self.assertEqual(payment_delay.valid_to, timezone.now() + payment_delay_time) - - def test_payment_delay_is_not_created_if_deadline_over_48_hours(self): - generate_payment( - self.event, payment_type=2, deadline=timezone.now() + timedelta(days=3) - ) - generate_attendee(self.event, "user1") - attend_user_to_event(self.event, self.user) - attend_user_to_event(self.event, self.other_user) - generate_attendee(self.event, "user3") - - self.client.post(self.url, follow=True, HTTP_HOST="example.com") - - self.assertEqual(len(mail.outbox), 1) - self.assertIn("Du har fått plass på", mail.outbox[0].subject) - payment_delay = PaymentDelay.objects.filter(user=self.other_user) - self.assertFalse(payment_delay.exists()) - - @freeze_time("2017-01-01 12:00") - def test_payment_delay_is_created_if_deadline_under_48_hours(self): - generate_payment( - self.event, payment_type=2, deadline=timezone.now() + timedelta(hours=47) - ) - generate_attendee(self.event, "user1") - attend_user_to_event(self.event, self.user) - attend_user_to_event(self.event, self.other_user) - generate_attendee(self.event, "user3") - payment_delay_time = timedelta(days=2) - - self.client.post(self.url, follow=True, HTTP_HOST="example.com") - - self.assertEqual(len(mail.outbox), 1) - self.assertIn("Du har fått plass på", mail.outbox[0].subject) - payment_delay = PaymentDelay.objects.get(user=self.other_user) - self.assertEqual(payment_delay.valid_to, timezone.now() + payment_delay_time) - - @freeze_time("2017-01-01 12:00") - def test_payment_type_delay_uses_payment_delay(self): - delay_days = 4 - payment_delay_time = timedelta(days=delay_days) - generate_payment(self.event, payment_type=3, delay=payment_delay_time) - generate_attendee(self.event, "user1") - attend_user_to_event(self.event, self.user) - attend_user_to_event(self.event, self.other_user) - generate_attendee(self.event, "user3") - - self.client.post(self.url, follow=True, HTTP_HOST="example.com") - - self.assertEqual(len(mail.outbox), 1) - self.assertIn("Du har fått plass på", mail.outbox[0].subject) - payment_delay = PaymentDelay.objects.get(user=self.other_user) - self.assertEqual(payment_delay.valid_to, timezone.now() + payment_delay_time) - - class EventMailParticipates(EventsTestMixin, TestCase): def setUp(self): super().setUp() @@ -713,36 +146,6 @@ def test_post_as_arrkom_invalid_to_email(self): self.assertEqual(len(mail.outbox), 0) -class EventsArchive(TestCase): - def test_events_index_empty(self): - url = reverse("events_index") - - response = self.client.get(url) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - def test_events_index_exists(self): - generate_event() - - url = reverse("events_index") - - response = self.client.get(url) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - -class EventsSearch(TestCase): - def test_search_events(self): - query = "" - - _url_pre_get_param = reverse("search_events") - url = _url_pre_get_param + f"?query={query}" - - response = self.client.get(url) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - class EventsCalendar(TestCase): def test_events_ics_all(self): url = reverse("events_ics") diff --git a/apps/events/urls.py b/apps/events/urls.py index de8a14875..0a1547cab 100644 --- a/apps/events/urls.py +++ b/apps/events/urls.py @@ -1,11 +1,15 @@ +from django.shortcuts import redirect from django.urls import re_path -from apps.api.utils import SharedAPIRootRouter from apps.events import views from apps.events.feeds import OpenedSignupsFeed urlpatterns = [ - re_path(r"^$", views.index, name="events_index"), + re_path( + r"^$", + lambda r: redirect("https://online.ntnu.no/events", permanent=True), + name="events_index", + ), re_path( r"^(?P\d+)/attendees/pdf$", views.generate_pdf, @@ -16,21 +20,18 @@ views.generate_json, name="event_attendees_json", ), - re_path(r"^(?P\d+)/attend/$", views.attend_event, name="attend_event"), - re_path( - r"^(?P\d+)/unattend/$", views.unattend_event, name="unattend_event" - ), - re_path( - r"^(?P\d+)/show_attending/$", - views.toggle_show_as_attending, - name="toggle_show_as_attending", - ), re_path( r"^(?P\d+)/(?P[a-zA-Z0-9_-]+)/$", - views.details, + lambda _r, event_id, event_slug: redirect( + f"https://online.ntnu.no/events/${event_id}", permanent=True + ), name="events_details", ), - re_path(r"^search/.*$", views.search_events, name="search_events"), + re_path( + r"^search/.*$", + lambda _r: redirect("https://online.ntnu.no/events", permanent=True), + name="search_events", + ), re_path( r"^mail-participants/(?P\d+)$", views.mail_participants, @@ -46,7 +47,3 @@ ), re_path("^signup-feed/$", OpenedSignupsFeed()), ] - -# API v1 -router = SharedAPIRootRouter() -router.register("events", views.EventViewSet, basename="events") diff --git a/apps/events/utils.py b/apps/events/utils.py index a095519c9..d295935b4 100644 --- a/apps/events/utils.py +++ b/apps/events/utils.py @@ -12,10 +12,10 @@ from apps.authentication.models import OnlineGroup from apps.authentication.models import OnlineUser as User -from apps.events.models import Attendee, Event, Extras +from apps.events.models import Event from apps.notifications.constants import PermissionType from apps.notifications.utils import send_message_to_users -from apps.payment.models import PaymentDelay, PaymentRelation, PaymentTypes +from apps.payment.models import PaymentTypes from utils.email import AutoChunkedEmailMessage, handle_mail_error @@ -169,136 +169,6 @@ def add_event(self, event): self.cal.add_component(cal_event) -def handle_attendance_event_detail(event, user, context): - attendance_event = event.attendance_event - - user_anonymous = True - user_attending = False - attendee = False - place_on_wait_list = 0 - will_be_on_wait_list = False - can_unattend = True - rules = [] - user_status = False - show_as_attending = False - user_setting_show_as_attending = False - - if attendance_event.rule_bundles: - for rule_bundle in attendance_event.rule_bundles.all(): - rules.append(rule_bundle.rule_strings) - - if user.is_authenticated: - user_anonymous = False - if attendance_event.is_attendee(user): - user_attending = True - attendee = Attendee.objects.get(event=attendance_event, user=user) - show_as_attending = attendee.show_as_attending_event - - will_be_on_wait_list = attendance_event.will_i_be_on_wait_list - - can_unattend = timezone.now() < attendance_event.registration_end - - user_status = event.attendance_event.is_eligible_for_signup(user) - - # Check if this user is on the waitlist - place_on_wait_list = attendance_event.what_place_is_user_on_wait_list(user) - - # Get the default setting for visible as attending event from users privacy setting - user_setting_show_as_attending = user.get_visible_as_attending_events() - - context.update( - { - "now": timezone.now(), - "attendance_event": attendance_event, - "user_anonymous": user_anonymous, - "attendee": attendee, - "user_attending": user_attending, - "will_be_on_wait_list": will_be_on_wait_list, - "can_unattend": can_unattend, - "rules": rules, - "user_status": user_status, - "place_on_wait_list": int(place_on_wait_list), - "user_setting_show_as_attending": user_setting_show_as_attending, - "show_as_attending": show_as_attending, - # 'position_in_wait_list': position_in_wait_list, - } - ) - return context - - -def handle_event_payment(event, user, payment, context): - user_paid = False - payment_delay = None - payment_relation_id = None - - context.update( - { - "payment": payment, - "user_paid": user_paid, - "payment_delay": payment_delay, - "payment_relation_id": payment_relation_id, - } - ) - - if not user.is_authenticated: # Return early if user not logged in, can't filter payment relations against no one - return context - - payment_relations = PaymentRelation.objects.filter( - payment=payment, user=user, refunded=False - ) - for payment_relation in payment_relations: - user_paid = True - payment_relation_id = payment_relation.id - if not user_paid and context["user_attending"]: - attendee = Attendee.objects.get(event=event.attendance_event, user=user) - if attendee: - user_paid = attendee.paid - - if not user_paid: - payment_delays = PaymentDelay.objects.filter(user=user, payment=payment) - if payment_delays: - payment_delay = payment_delays[0] - - context.update( - { - "user_paid": user_paid, - "payment_delay": payment_delay, - "payment_relation_id": payment_relation_id, - } - ) - - return context - - -def handle_event_ajax(event, user, action, extras_id): - if action == "extras": - return handle_event_extras(event, user, extras_id) - else: - raise NotImplementedError - - -def handle_event_extras(event, user, extras_id): - resp = {"message": "Feil!"} - - if not event.is_attendance_event(): - return {"message": "Dette er ikke et påmeldingsarrangement."} - - attendance_event = event.attendance_event - - if not attendance_event.is_attendee(user): - return {"message": "Du er ikke påmeldt dette arrangementet."} - - attendee = Attendee.objects.get(event=attendance_event, user=user) - try: - attendee.extras = attendance_event.extras.get(id=extras_id) - except Extras.DoesNotExist: - return {"message": "Ugyldig valg"} - - attendee.save() - resp["message"] = "Lagret ditt valg" - return resp - - def handle_attend_event_payment(event: Event, user: User): attendance_event = event.attendance_event payment = attendance_event.payment() diff --git a/apps/events/views.py b/apps/events/views.py index 7acfa967b..a88cf8b88 100644 --- a/apps/events/views.py +++ b/apps/events/views.py @@ -2,254 +2,19 @@ from django.contrib import messages from django.contrib.auth.decorators import login_required -from django.contrib.contenttypes.models import ContentType -from django.core.signing import Signer -from django.http import HttpResponse, JsonResponse +from django.http import HttpResponse from django.shortcuts import get_object_or_404, redirect, render -from django.urls import reverse -from django.utils import timezone from django.utils.translation import gettext as _ -from rest_framework import mixins, viewsets -from rest_framework.permissions import AllowAny -from watson import search as watson -from apps.events.filters import EventFilter -from apps.events.forms import CaptchaForm -from apps.events.models import AttendanceEvent, Attendee, Event +from apps.events.models import Event from apps.events.pdf_generator import EventPDF -from apps.events.serializers import EventSerializer from apps.events.utils import ( - handle_attend_event_payment, - handle_attendance_event_detail, - handle_event_ajax, - handle_event_payment, handle_mail_participants, ) -from apps.payment.models import Payment, PaymentDelay, PaymentRelation from .utils import EventCalendar -def index(request): - context = {} - if request.user and request.user.is_authenticated: - signer = Signer() - context["signer_value"] = signer.sign(request.user.username) - context["personal_ics_path"] = request.build_absolute_uri( - reverse("events_personal_ics", args=(context["signer_value"],)) - ) - return render(request, "events/index.html", context) - - -def details(request, event_id, event_slug): - event = get_object_or_404(Event, pk=event_id) - - # Restricts access to the event if it is group restricted - if not event.can_display(request.user): - messages.error(request, "Du har ikke tilgang til dette arrangementet.") - return index(request) - - if request.method == "POST": - if "action" in request.POST and "extras_id" in request.POST: - return JsonResponse( - handle_event_ajax( - event, - request.user, - request.POST["action"], - request.POST["extras_id"], - ) - ) - - form = CaptchaForm(user=request.user) - context = { - "captcha_form": form, - "event": event, - "ics_path": request.build_absolute_uri(reverse("event_ics", args=(event.id,))), - } - - if event.is_attendance_event(): - try: - payment = Payment.objects.get( - content_type=ContentType.objects.get_for_model(AttendanceEvent), - object_id=event_id, - ) - except Payment.DoesNotExist: - payment = None - - context = handle_attendance_event_detail(event, request.user, context) - if payment: - request.session["payment_id"] = payment.id - context = handle_event_payment(event, request.user, payment, context) - - return render(request, "events/details.html", context) - - -def get_attendee(attendee_id): - return get_object_or_404(Attendee, pk=attendee_id) - - -@login_required -def attend_event(request, event_id): - event = get_object_or_404(Event, pk=event_id) - - if not event.is_attendance_event(): - messages.error(request, _("Dette er ikke et påmeldingsarrangement.")) - return redirect(event) - - if not request.POST: - messages.error(request, _("Vennligst fyll ut skjemaet.")) - return redirect(event) - - form = CaptchaForm(request.POST, user=request.user) - - if not form.is_valid(): - for errors in form.errors.values(): - for error in errors: - messages.error(request, error) - - return redirect(event) - - # Check if the user is eligible to attend this event. - # If not, an error message will be present in the returned dict - attendance_event = event.attendance_event - - response = event.attendance_event.is_eligible_for_signup(request.user) - - if response.status: - attendee = Attendee(event=attendance_event, user=request.user) - if "note" in form.cleaned_data: - attendee.note = form.cleaned_data["note"] - attendee.show_as_attending_event = ( - request.user.get_visible_as_attending_events() - ) - attendee.save() - messages.success(request, _("Du er nå meldt på arrangementet.")) - - if attendance_event.payment(): - handle_attend_event_payment(event, request.user) - - return redirect(event) - else: - messages.error(request, response.message) - return redirect(event) - - -@login_required -def unattend_event(request, event_id): - event = get_object_or_404(Event, pk=event_id) - - if not event.is_attendance_event(): - messages.error(request, _("Dette er ikke et påmeldingsarrangement.")) - return redirect(event) - - attendance_event = event.attendance_event - - # Check if user is attending - if len(Attendee.objects.filter(event=attendance_event, user=request.user)) == 0: - messages.error(request, _("Du er ikke påmeldt dette arrangementet.")) - return redirect(event) - - # Check if the deadline for unattending has passed - if ( - attendance_event.unattend_deadline < timezone.now() - and not attendance_event.is_on_waitlist(request.user) - ): - messages.error( - request, _("Avmeldingsfristen for dette arrangementet har utløpt.") - ) - return redirect(event) - - if attendance_event.event.event_start < timezone.now(): - messages.error(request, _("Dette arrangementet har allerede startet.")) - return redirect(event) - - try: - payment = Payment.objects.get( - content_type=ContentType.objects.get_for_model(AttendanceEvent), - object_id=event_id, - ) - except Payment.DoesNotExist: - payment = None - - # Delete payment delays connected to the user and event - if payment: - payments = PaymentRelation.objects.filter( - payment=payment, user=request.user, refunded=False - ) - - # Return if someone is trying to unatend without refunding - if payments: - messages.error( - request, - _( - "Du har betalt for arrangementet og må refundere før du kan melde deg av" - ), - ) - return redirect(event) - - delays = PaymentDelay.objects.filter(payment=payment, user=request.user) - for delay in delays: - delay.delete() - - Attendee.objects.get(event=attendance_event, user=request.user).delete() - - messages.success(request, _("Du ble meldt av arrangementet.")) - return redirect(event) - - -def search_events(request): - query = request.GET.get("query") - filters = { - "future": request.GET.get("future"), - "myevents": request.GET.get("myevents"), - } - events = _search_indexed(request, query, filters) - - return render(request, "events/search.html", {"events": events}) - - -def _search_indexed(request, query, filters): - results = [] - kwargs = {} - order_by = "event_start" - - if filters["future"] == "true": - kwargs["event_start__gte"] = timezone.now() - else: - # Reverse order when showing all events - order_by = "-" + order_by - - if filters["myevents"] == "true": - kwargs["attendance_event__attendees__user"] = request.user - - events = ( - Event.objects.filter(**kwargs) - .order_by(order_by) - .prefetch_related( - "attendance_event", - "attendance_event__attendees", - "attendance_event__reserved_seats", - "attendance_event__reserved_seats__reservees", - ) - ) - - # Filters events that are restricted - display_events = set() - - for event in events: - if event.can_display(request.user): - display_events.add(event.pk) - - events = events.filter(pk__in=display_events) - - if query: - for result in watson.search(query, models=(events,)): - results.append(result.object) - return results[:10] - - return events - - @login_required() def generate_pdf(request, event_id): event = get_object_or_404(Event, pk=event_id) @@ -393,54 +158,3 @@ def mail_participants(request, event_id): "event": event, }, ) - - -@login_required -def toggle_show_as_attending(request, event_id): - event = get_object_or_404(Event, pk=event_id) - - if not event.is_attendance_event(): - messages.error(request, _("Dette er ikke et påmeldingsarrangement.")) - return redirect(event) - - attendance_event = event.attendance_event - attendee = Attendee.objects.get(event=attendance_event, user=request.user) - - if attendee.show_as_attending_event: - attendee.show_as_attending_event = False - messages.success( - request, _("Du er ikke lenger synlig som påmeldt dette arrangementet.") - ) - else: - attendee.show_as_attending_event = True - messages.success(request, _("Du er nå synlig som påmeldt dette arrangementet.")) - - attendee.save() - return redirect(event) - - -class EventViewSet( - viewsets.GenericViewSet, mixins.RetrieveModelMixin, mixins.ListModelMixin -): - serializer_class = EventSerializer - permission_classes = (AllowAny,) - filterset_class = EventFilter - filterset_fields = ("event_start", "event_end", "id") - ordering_fields = ( - "event_start", - "event_end", - "id", - "has_passed", - "closest", - ) - ordering = ("has_passed", "closest", "id") - - def get_queryset(self): - user = self.request.user - return Event.by_nearest_active_event.get_queryset_for_user(user).select_related( - "image", - "organizer", - "group_restriction", - "attendance_event", - "attendance_event__reserved_seats", - ) diff --git a/apps/notifications/utils.py b/apps/notifications/utils.py index c33b5f6f7..290150d30 100644 --- a/apps/notifications/utils.py +++ b/apps/notifications/utils.py @@ -1,4 +1,5 @@ from collections.abc import Iterable +from functools import partial from django.conf import settings from django.core.mail import send_mail @@ -62,15 +63,15 @@ def send_message_to_users( if has_push_permission: on_commit( - lambda notification=notification: dispatch_push_notification_task( - notification_id=notification.id + partial( + dispatch_push_notification_task, notification_id=notification.id ) ) if has_email_permission: on_commit( - lambda notification=notification: dispatch_email_notification_task( - notification_id=notification.id + partial( + dispatch_email_notification_task, notification_id=notification.id ) ) diff --git a/assets/events/archive/filters.js b/assets/events/archive/filters.js deleted file mode 100644 index 8c490c4f6..000000000 --- a/assets/events/archive/filters.js +++ /dev/null @@ -1,82 +0,0 @@ -import Urls from 'common/utils/django_reverse'; -// import $ from 'jquery'; - -class Filters { - constructor() { - this.query = ''; - this.future = true; - this.myevents = false; - } - - static showResult(result) { - $('#events').empty(); - $('#events').append(result); - } - - static scrollToClosestEvent() { - const menuOffset = $('nav.subnavbar').position().top + $('nav.subnavbar').outerHeight(true); - // Scroll to closest event - const articles = $('#events article'); - const today = new Date(); - articles.each((i, event) => { - const eventDate = new Date($(event).data('date')); - if (eventDate < today) { - // Scroll animation - $('html, body').animate({ scrollTop: $(event).offset().top - menuOffset }, 250); - // Break each loop - return false; - } - return true; - }); - } - - bindEventListeners() { - // Events - this.toggleFuture = this.toggleFuture.bind(this); - this.toggleMyEvents = this.toggleMyEvents.bind(this); - this.updateQuery = this.updateQuery.bind(this); - - // Elements - const searchInput = document.getElementById('search'); - const futureCheckbox = document.getElementById('future'); - const myeventsCheckbox = document.getElementById('myevents'); - - // Binding event listeners - searchInput.addEventListener('keyup', this.updateQuery); - futureCheckbox.addEventListener('click', this.toggleFuture); - if (myeventsCheckbox !== null) { - myeventsCheckbox.addEventListener('click', this.toggleMyEvents); - } - } - - updateQuery(e) { - this.query = e.target.value; - this.search(); - } - - toggleFuture() { - this.future = !this.future; - this.search(); - } - - toggleMyEvents() { - this.myevents = !this.myevents; - this.search(); - } - - apiUrl() { - const filters = `?query=${this.query}&future=${this.future}&myevents=${this.myevents}`; - return Urls.search_events() + filters; - } - - search() { - $.get(this.apiUrl(), (result) => { - Filters.showResult(result); - if (!this.future) { - Filters.scrollToClosestEvent(); - } - }); - } -} - -export default Filters; diff --git a/assets/events/archive/index.js b/assets/events/archive/index.js deleted file mode 100644 index cfecf4ac3..000000000 --- a/assets/events/archive/index.js +++ /dev/null @@ -1,6 +0,0 @@ -import Filters from './filters'; -import './less/event_archive.less'; - -const filters = new Filters(); -filters.bindEventListeners(); -filters.search(); diff --git a/assets/events/archive/less/event_archive.less b/assets/events/archive/less/event_archive.less deleted file mode 100644 index 84ba26aac..000000000 --- a/assets/events/archive/less/event_archive.less +++ /dev/null @@ -1,103 +0,0 @@ -@import "assets/common/less/colors.less"; - -#filter { - background: @baby-blue-medium; - padding: 10px; - color: #fff; - margin-bottom: 20px; - float: right; - - input[type="text"] { - width: 100%; - } -} - -#events { - #filter .input-group { - margin-bottom: 10px; - float: left; - - input { - font-size: 12px; - border-radius: 15px; - } - } - - .ingress { - margin-bottom: 10px; - line-height: 1.625em; - } - - article { - margin-bottom: 40px; - padding-bottom: 30px; - - /*border-bottom: 1px dotted #ccc;*/ - - /*position: relative;*/ - - button { - width: 100%; - margin: 2px 0 0 0; - border-radius: 0 0 0 0; - } - - img { - width: 100%; - } - - .content { - /*position: absolute; - top: 0; - left: 260px;*/ - } - - h1 { - font-size: 20px; - font-weight: 400; - color: #ee7810; - margin-top: 0; - } - - span { - margin: 0 8px 0 0; - font-size: 18px; - text-transform: uppercase; - font-weight: 400; - color: #ee7810; - float: right; - } - - .meta { - padding: 5px 0 0 0; - width: 100%; - border-top: 1px dotted #ccc; - font-size: 11px; - font-weight: normal; - - /*text-align: left;*/ - - p { - color: #aaa; - } - } - - @media (max-width: 989px) { - h1 { - font-size: 18px; - } - - span { - float: left; - font-size: 14px; - margin-bottom: 10px; - } - } - - @media (max-width: 767px) { - h1 { - margin-top: 15px; - } - } - } -} diff --git a/assets/events/details/events.js b/assets/events/details/events.js deleted file mode 100644 index 85f97102f..000000000 --- a/assets/events/details/events.js +++ /dev/null @@ -1,91 +0,0 @@ -// import $ from 'jquery'; -import Cookies from 'js-cookie'; -import { makeApiRequest, showFlashMessage } from 'common/utils/'; - -/* - The event module provides dynamic functions to event objects - such as saving user selection on event extras -*/ - -// Global variables defined in details template -const allExtras = window.all_extras; -const selectedExtra = window.selected_extra; - -const sendChoice = (id) => { - const url = window.location.href.toString(); - const data = { - extras_id: id, - action: 'extras', - }; - const success = (jsonData) => { - // var line = $('#' + attendee_id > i) - showFlashMessage(jsonData.message, 'alert-success'); - - const chosenText = 'Valgt ekstra: '; - const options = $('.extras-choice option'); - for (let i = options.length - 1; i >= 0; i -= 1) { - if (options[i].text.indexOf(chosenText) >= 0) { - options[i].text = allExtras[i]; - break; - } - } - - const selected = $('.extras-choice option:selected'); - selected.text(chosenText + selected.text()); - }; - const error = (xhr, txt, errorMessage) => { - const message = - 'Det skjedde en feil! Refresh siden og prøv igjen, eller kontakt de ansvarlige hvis det fortsatt ikke går. '; - showFlashMessage(message + errorMessage, 'alert-danger'); - }; - - // Make an AJAX request - makeApiRequest({ type: 'POST', url, data, success, error }); -}; - -const init = () => { - $('.extras-choice').on('change', function changeExtraChoice() { - const id = $(this).val(); - const text = $(this).text(); - sendChoice(id, text); - }); - - if (allExtras.length > 0 && selectedExtra === 'None') { - const message = - 'Vennligst velg et alternativ for extra bestilling. (Over avmeldingsknappen)'; - showFlashMessage(message, 'alert-warning'); - } else if ( - allExtras.length > 0 && - selectedExtra !== '' && - $.inArray(selectedExtra, allExtras) === -1 - ) { - const message = 'Ditt valg til ekstra bestilling er ikke lenger gyldig! Velg et nytt.'; - showFlashMessage(message, 'alert-warning'); - } -}; - -const refundPayment = async (relationId) => { - const refundUrl = `/api/v1/payment/relations/${relationId}/`; - const response = await fetch(refundUrl, { - method: 'DELETE', - credentials: 'include', - headers: { 'X-CSRFToken': Cookies.get('csrftoken') }, - }); - const responseData = await response.json(); - if (responseData.message) { - const status = response.ok ? 'alert-success' : 'alert-warning'; - showFlashMessage(responseData.message, status); - } -}; - -window.addEventListener('load', () => { - const refundButton = document.getElementById('refund-payment-button'); - if (refundButton) { - const relationId = refundButton.dataset.paymentrelationid; - refundButton.addEventListener('click', async () => { - await refundPayment(relationId); - }); - } -}); - -export default init; diff --git a/assets/events/details/index.js b/assets/events/details/index.js deleted file mode 100644 index e6e8463d6..000000000 --- a/assets/events/details/index.js +++ /dev/null @@ -1,4 +0,0 @@ -import Events from './events'; -import './less/event_details.less'; - -Events(); diff --git a/assets/events/details/less/event_details.less b/assets/events/details/less/event_details.less deleted file mode 100644 index 5050e429f..000000000 --- a/assets/events/details/less/event_details.less +++ /dev/null @@ -1,276 +0,0 @@ -@import "assets/common/less/colors.less"; - -section#event-details { - .event-details-single-image { - position: relative; - } - - .event-details-image-credits { - position: absolute; - bottom: 0; - padding: 3px 7px; - background-color: rgba(0, 0, 0, 0.57); - color: white; - margin: 0; - } - - .row-space { - margin-bottom: 30px; - - @media screen and (max-width: 767px) { - margin-bottom: 0; - } - } - - .event-hero { - color: #fff; - - .event-hero-duration { - background-color: @arrkom-color; - font-weight: 300; - font-size: 20px; - text-align: center; - padding: 20px; - - @media screen and (max-width: 767px) { - padding: 0; - margin-bottom: 0; - } - - .start-day { - width: 100%; - display: block; - font-size: 28px; - color: @arrkom-color; - font-weight: 600; - margin-bottom: 10px; - - .start-day-number { - padding: 7px; - background: white; - } - } - - .start-month { - width: 100%; - display: block; - font-weight: 300; - font-size: 20px; - text-transform: uppercase; - - .start-month-text { - padding: 9px; - } - } - - .start-year { - width: 100%; - font-weight: 300; - font-size: 14px; - } - } - } - - .event-hero-meta { - color: #fff; - background-color: #49a0cc; - padding: 20px; - - @media screen and (max-width: 767px) { - padding-top: 20px; - padding-bottom: 0; - } - - text-align: center; - - .event-hero-meta-info { - padding-bottom: 50px; - - @media screen and (max-width: 767px) { - padding-bottom: 20px; - } - - .event-hero-title { - font-size: 14px; - font-weight: 600; - - /*color:#FFE5BD;*/ - - color: #ffd2a5; - - /*color:#FF5A42;*/ - - /*color:#AEE50B;*/ - - /*color:#B0E59F;*/ - - /*color:#a1d40a;*/ - - text-transform: uppercase; - } - - .event-hero-large-text { - font-size: 28px; - font-weight: 300; - - .break { - width: 100%; - display: block; - margin-bottom: -15px; - } - - .date-small { - font-size: 13px; - } - } - - .event-hero-small-text { - font-weight: 300; - font-size: 20px; - margin-bottom: 9px; - } - } - } - - .event-status { - color: white; - background: #ee974f; - padding: 20px; - font-weight: 300; - text-align: center; - - .btn { - /*border-color: rgba(0, 0, 0, 0);*/ - border-radius: 2px; - } - - .event-status-region { - margin-bottom: 20px; - } - - .status-text { - font-size: 18px; - } - - .status-rules { - .status-rules-heading { - font-size: 12px; - font-weight: 600; - text-transform: uppercase; - } - - span { - margin-right: 5px; - } - - li { - list-style-type: none; - } - } - - .extras { - margin: 0 0 11px 0; - - .extras-text { - font-size: 1.1em; - font-weight: 400; - } - - .extras-choice { - width: auto; - margin-left: auto; - margin-right: auto; - border-radius: 2px; - border: none; - } - } - - p { - color: white; - } - } - - @media (min-width: 992px) { - .event-status-container { - float: right; - } - - .event-status-companies-container { - float: right; - clear: right; - } - } - - .event-status-extra { - padding: 10px 20px 0 20px; - text-align: center; - - /*background:#FF8874;*/ - - background: #ffbe8b; - color: white; - - p { - color: white; - } - } - - .event-status-companies { - .title { - font-size: 22px; - font-weight: 300; - margin-bottom: 20px; - padding-bottom: 10px; - border-bottom: 1px solid #ebebeb; - } - - .heading { - .title { - float: left; - font-weight: 300; - line-height: 65px; - font-size: 20px; - margin: 0; - border: none; - } - - .image { - float: right; - } - } - } - - .event-access { - padding: 10px; - margin-bottom: 20px; - background: #d6eaff; - text-align: center; - - .access-title { - color: @heading; - font-size: 15px; - font-weight: 300; - text-align: center; - } - - .label-info { - line-height: 21px; - } - } - - .payment-price-tag { - margin-bottom: 10px; - } - - .payment-button { - margin-bottom: 10px; - } - - .event-image { - width: 100%; - } - - .event-social-list-anon { - text-align: center; - } -} diff --git a/assets/frontpage/events/components/Event.jsx b/assets/frontpage/events/components/Event.jsx deleted file mode 100644 index 5e8592dce..000000000 --- a/assets/frontpage/events/components/Event.jsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import EventImage from '../components/EventImage'; - -const Event = ({ eventUrl, images, ingress, startDate, title }) => ( -
-
- -
-

{ ingress }

-
-
- -
-); - -Event.propTypes = { - eventUrl: PropTypes.string.isRequired, - images: EventImage.propTypes.images, - ingress: PropTypes.string.isRequired, - startDate: PropTypes.instanceOf(Date).isRequired, - title: PropTypes.string.isRequired, -}; - -export default Event; diff --git a/assets/frontpage/events/components/EventFilter.jsx b/assets/frontpage/events/components/EventFilter.jsx deleted file mode 100644 index 9ba65bcef..000000000 --- a/assets/frontpage/events/components/EventFilter.jsx +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { ButtonGroup, Button } from 'react-bootstrap'; -import classNames from 'classnames'; - -const EventFilter = ({ eventTypes, setEventVisibility }) => ( - - { eventTypes.map((eventType, index) => { - const filterButtonClass = classNames('event-filter-button', { - 'hidden-event-button': !eventType.display, - }); - return (); - }) - } - -); - - -EventFilter.propTypes = { - eventTypes: PropTypes.arrayOf(PropTypes.shape({ - display: PropTypes.bool.isRequired, - id: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - })).isRequired, - setEventVisibility: PropTypes.func.isRequired, -}; - - -export default EventFilter; diff --git a/assets/frontpage/events/components/EventImage.jsx b/assets/frontpage/events/components/EventImage.jsx deleted file mode 100644 index 86db78580..000000000 --- a/assets/frontpage/events/components/EventImage.jsx +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { Carousel } from 'react-bootstrap'; -import ImagePropTypes from 'common/proptypes/ImagePropTypes'; - -const EventImage = ({ date, eventUrl, images }) => ( -
- - { images.map((image, index) => ( - - - - - - - - - - )) - } - - { new Intl.DateTimeFormat("nb-NO", { month: "long", day: "2-digit" }).format(date) } -
-); - -EventImage.propTypes = { - date: PropTypes.instanceOf(Date).isRequired, - eventUrl: PropTypes.string.isRequired, - images: PropTypes.arrayOf(ImagePropTypes), -}; - -export default EventImage; diff --git a/assets/frontpage/events/components/Events.jsx b/assets/frontpage/events/components/Events.jsx deleted file mode 100644 index 1f8dcdebf..000000000 --- a/assets/frontpage/events/components/Events.jsx +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import Event from './Event'; -import SmallEvent from './SmallEvent'; -import EventsHeading from './EventsHeading'; - -const Events = ({ mainEvents, smallEvents, setEventVisibility, eventTypes }) => ( -
- -
- { - mainEvents.length !== 0 - ? mainEvents.map((event, index) => - , - ) - :
- Ingen arrangementer funnet, eller de er blokkert av en adblock. -
- } -
-
-
    - { - smallEvents.map((event, index) => ( - - )) - } -
-
-
-); - -Events.propTypes = { - eventTypes: EventsHeading.propTypes.eventTypes, - mainEvents: PropTypes.arrayOf(PropTypes.shape(Event.propTypes)), - setEventVisibility: EventsHeading.propTypes.setEventVisibility, - smallEvents: PropTypes.arrayOf(PropTypes.shape(Event.propTypes)), -}; - -export default Events; diff --git a/assets/frontpage/events/components/EventsHeading.jsx b/assets/frontpage/events/components/EventsHeading.jsx deleted file mode 100644 index 160a7b2fe..000000000 --- a/assets/frontpage/events/components/EventsHeading.jsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -import { Glyphicon } from 'react-bootstrap'; -import Urls from 'common/utils/django_reverse'; -import EventFilter from './EventFilter'; - -const EventsHeading = ({ eventTypes, setEventVisibility }) => ( -
-
-
-
-

Arrangementer

-
-
- -
-
-
-
-
- -
-
-
-); - -EventsHeading.propTypes = EventFilter.propTypes; - - -export default EventsHeading; diff --git a/assets/frontpage/events/components/SmallEvent.jsx b/assets/frontpage/events/components/SmallEvent.jsx deleted file mode 100644 index 5878725c4..000000000 --- a/assets/frontpage/events/components/SmallEvent.jsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -const SmallEvent = ({ eventUrl, startDate, title }) => ( -
  • - - {new Intl.DateTimeFormat("nb-NO", { month: "2-digit", day: "2-digit" }).format(startDate)} - - - {title} - -
  • -); - -SmallEvent.propTypes = { - eventUrl: PropTypes.string.isRequired, - startDate: PropTypes.instanceOf(Date).isRequired, - title: PropTypes.string.isRequired, -}; - -export default SmallEvent; diff --git a/assets/frontpage/events/containers/EventsContainer.jsx b/assets/frontpage/events/containers/EventsContainer.jsx deleted file mode 100644 index cca51998c..000000000 --- a/assets/frontpage/events/containers/EventsContainer.jsx +++ /dev/null @@ -1,195 +0,0 @@ -import React, { Component } from 'react'; -import Events from '../components/Events'; -import { setEventsForEventTypeId, toggleEventTypeDisplay } from '../utils'; - -const mergeEventImages = (eventImage, companyEvent) => { - const eventImages = []; - // Event images - if (eventImage) { - eventImages.push(eventImage); - } - // Company images - const companyImages = companyEvent.map(company => ( - company.company.image - )); - return [...eventImages, ...companyImages]; -}; - -// 3 days in milliseconds -const THREE_DAYS = 3 * 24 * 60 * 60 * 1000 -const DAYS_BACK_DELTA = new Date() - THREE_DAYS; -const DAYS_FORWARD_DELTA = new Date() + THREE_DAYS; - -const getRegistrationFiltered = ({ attendance_event: attendance, event_start: startDate }) => { - const registration_start = attendance !== null ? new Date(attendance.registration_start) : null; - if ( - attendance - && DAYS_BACK_DELTA <= registration_start - && registration_start < DAYS_FORWARD_DELTA - ) { - return registration_start; - } - return startDate; -}; - -const apiEventsToEvents = event => ({ - eventUrl: `/events/${event.id}/${event.slug}`, - ingress: event.ingress_short, - startDate: new Date(event.event_start), - endDate: new Date(event.event_end), - registrationFiltered: getRegistrationFiltered(event), - title: event.title, - images: mergeEventImages(event.image, event.company_event), -}); - -const sortEvents = (a, b) => { - // checks if the event is starting today or is ongoing - const now = new Date(); - if (a.startDate <= now && now < a.endDate) { - if (b.startDate <= now && now < b.endDate) { - return a.endDate < b.endDate ? -1 : 1; - } - return -1; - } - return a.registrationFiltered < b.registrationFiltered ? -1 : 1; -}; - -/* -Reduces array to object and adds some generic fields - -Example: -{ - 1: { - id: '1', - name: 'Test', - display: true, - ... - }, - ... -} -*/ -const initialEventTypes = eventTypes => ( - eventTypes.reduce((accumulator, eventType) => ( - Object.assign(accumulator, { - [eventType.id]: { - id: eventType.id, - name: eventType.name, - display: true, - events: [], - loaded: false, - }, - }) - ), {}) -); - -class EventsContainer extends Component { - constructor(props) { - super(props); - this.API_URL = `/api/v1/events/?event_end__gte=${new Date().toISOString().slice(0, "YYYY-MM-DD".length)}`; - const eventTypes = [ - { id: '1', name: 'Sosialt' }, - { id: '2', name: 'Bedriftspresentasjon' }, - { id: '3', name: 'Kurs' }, - { id: 'other', name: 'Annet' }, - ]; - this.state = { - events: [], - dirty: false, - eventTypes: initialEventTypes(eventTypes), - }; - this.setEventVisibility = this.setEventVisibility.bind(this); - this.getVisibleEvents = this.getVisibleEvents.bind(this); - - this.fetchEvents(); - // Loop over event types - Object.keys(this.state.eventTypes).forEach((eventTypeId) => { - const eventType = this.state.eventTypes[eventTypeId]; - this.fetchEventsByType(eventType.id); - }); - } - - getVisibleEvents() { - const { eventTypes } = this.state; - const allEventTypesLoaded = Object.keys(eventTypes).every(eventTypeId => ( - eventTypes[eventTypeId].loaded - )); - if (!allEventTypesLoaded) { - // Show initially loaded events instead - return this.state.events; - } - // Reduce all event type events to one array - const visibleEvents = Object.keys(eventTypes).reduce((events, eventTypeId) => { - const eventType = eventTypes[eventTypeId]; - if (eventType.display) { - return [...events, ...eventType.events]; - } - return events; - }, []); - - visibleEvents.sort(sortEvents); - return visibleEvents; - } - - setEventVisibility(e) { - this.setState({ - eventTypes: toggleEventTypeDisplay(this.state, e.eventType), - }); - } - - getEventTypes() { - const { eventTypes } = this.state; - // Turn object into array - return Object.keys(eventTypes).map(eventTypeId => ( - eventTypes[eventTypeId] - )); - } - - fetchEvents() { - const apiUrl = `${this.API_URL}&format=json`; - fetch(apiUrl, { credentials: 'same-origin' }) - .then(response => response.json()) - .then((json) => { - this.setState({ - events: json.results.map(apiEventsToEvents), - }); - }); - } - - fetchEventsByType(eventType) { - let apiUrl = `${this.API_URL}&format=json&event_type=`; - if (eventType === 'other') { - apiUrl += '4,5,6,7,8'; - } else { - apiUrl += eventType; - } - fetch(apiUrl, { credentials: 'same-origin' }) - .then(response => - response.json(), - ).then((json) => { - const events = json.results.map(apiEventsToEvents); - this.setState({ - eventTypes: setEventsForEventTypeId(this.state, eventType, events), - }); - }); - } - - mainEvents() { - return this.getVisibleEvents().slice(0, 2); - } - - smallEvents() { - return this.getVisibleEvents().slice(2, 10); - } - - render() { - return ( - - ); - } -} - -export default EventsContainer; diff --git a/assets/frontpage/events/utils/index.js b/assets/frontpage/events/utils/index.js deleted file mode 100644 index c516ff3a7..000000000 --- a/assets/frontpage/events/utils/index.js +++ /dev/null @@ -1,64 +0,0 @@ -export const toggleEventTypeDisplay = (state, eventType) => { - const newState = Object.assign({}, state); - const selected = []; - let eventCount = 0; - - Object.values(newState.eventTypes).forEach((type) => { - eventCount += 1; - if (type.display) { - selected.push(type.id); - } - }); - - if (selected.length === 1 && eventType.id === selected[0]) { - // If only one event is selected and is being deselected, - // then show all and set dirty to false - Object.assign(state, { dirty: false }); - - Object.values(newState.eventTypes).forEach((type) => { - Object.assign(type, { - display: true, - }); - }); - } else if (selected.length === eventCount - 1 && selected.indexOf(eventType.id) === -1) { - // If only one event is unselected and is being selected, - // then show all and set dirty to true - Object.assign(state, { dirty: true }); - - Object.values(newState.eventTypes).forEach((type) => { - Object.assign(type, { - display: true, - }); - }); - } else if (selected.length === eventCount && !state.dirty) { - // If all events are selected and dirty is false, - // then deselect the all other than the selected and set dirty to true - Object.assign(state, { dirty: true }); - - Object.values(newState.eventTypes) - .filter(type => type.id !== eventType.id) - .forEach((type) => { - Object.assign(type, { - display: false, - }); - }); - } else { - // If none of the edge cases above, then toggle as usual - Object.assign(newState.eventTypes, { - [eventType.id]: Object.assign({}, eventType, { - display: !eventType.display, - }), - }); - } - - return Object.assign({}, state.eventTypes, newState.eventTypes); -}; - -export const setEventsForEventTypeId = (state, eventTypeId, events) => ( - Object.assign({}, state.eventTypes, { - [eventTypeId]: Object.assign({}, state.eventTypes[eventTypeId], { - events, - loaded: true, - }), - }) -); diff --git a/assets/frontpage/index.jsx b/assets/frontpage/index.jsx index 84167bf96..594c89322 100644 --- a/assets/frontpage/index.jsx +++ b/assets/frontpage/index.jsx @@ -1,8 +1,6 @@ import React from 'react'; import ReactDom from 'react-dom'; import ArticlesContainer from './articles/containers/ArticlesContainer'; -import EventsContainer from './events/containers/EventsContainer'; -import './initFrontpage'; import './less/frontpage.less'; const renderArticles = (Articles) => { @@ -13,12 +11,3 @@ const renderArticles = (Articles) => { }; renderArticles(ArticlesContainer); - -const renderEvents = (Events) => { - ReactDom.render( - , - document.getElementById('event-items'), - ); -}; - -renderEvents(EventsContainer); diff --git a/assets/frontpage/initFrontpage.js b/assets/frontpage/initFrontpage.js deleted file mode 100755 index 4dadfe706..000000000 --- a/assets/frontpage/initFrontpage.js +++ /dev/null @@ -1,113 +0,0 @@ -// // import $ from 'jquery'; - -const TOP_OFFSET_ADJUST = 65; - - -/* FUNCTIONS ------------------------------------------------------------------------- */ -const cleanHash = () => $(location).attr('hash').replace(/^#!/, ''); - -const jump = (section) => { - if (typeof section !== 'undefined') { - const elementAnchor = $(`#${section}`).offset(); - if (elementAnchor) { - $('html, body').animate({ scrollTop: elementAnchor.top - TOP_OFFSET_ADJUST }, 250, () => { - window.location.hash = `#!${section}`; - }); - } - } -}; - -// On scroll, loop the navs and swap active (if it needs to) -const scrollspy = () => { - const current = $(window).scrollTop(); - - for (let i = 0; i < $('.subnavbar li a').length; i += 1) { - const section = `#${$($('.subnavbar li a')[i]).data('section')}`; - const diff = current - ($(section).offset().top + TOP_OFFSET_ADJUST); - - if (diff > -20) { - $('.subnavbar li.active').removeClass('active'); - $(`a[href='/${section}']`).parent().addClass('active'); - } - } -}; - -/* EVENT LISTENERS ------------------------------------------------------------------------- */ -// Enable tabbing in about section -$('#about-tabs').on('click', 'a', function tabClick(e) { - e.preventDefault(); - $(this).tab('show'); - - /* - Set anchor from tab href that matches tab pane id. Reason we need to do this is because - we want the anchor on the format !about/foo, but that is not a valid HTML5 id. - Instead we have about-foo, and replace the dash with a slash and prefix with a exclamation - point to follow the front page standard. - */ - const anchorUrl = this.href.split('#'); - if (anchorUrl.length > 1) { - window.location.hash = `!${anchorUrl[1].replace(/-/g, '/')}`; - } - - // Swap header - let title = $(this).html(); - if ($(this).data('prefixom') === undefined || $(this).data('prefixom') === 'false') { - title = `Om ${title}`; - } - - $('#about-heading').html(title.toUpperCase()); - - // Swap content - $('html, body').animate({ scrollTop: $('#about').offset().top - TOP_OFFSET_ADJUST }, 250); -}); - -// Clicking the links in the topnav -$('.subnavbar').on('click', 'a', function subnavbarClick(e) { - e.preventDefault(); - jump($(this).data('section')); -}); - -/* TODO: heavy shit? Find a reliable way to setnavs instead of doing it fucking all the time. ------------------------------------------------------------------------- */ -$(window).scroll(scrollspy); - -$(window).resize(() => { - if (cleanHash()) { - const anchor = $(location).attr('hash').replace(/^#!/, '').replace('/', '-'); - const elementAnchor = $(anchor).offset(); - if (elementAnchor) { - $(window).scrollTop(elementAnchor.top - TOP_OFFSET_ADJUST); - } - } -}); - - -/* On load highlight the current menu-item if an anchor is represented ------------------------------------------------------------------------- */ -scrollspy(); - - -/* Reload fix - reposition after reload ------------------------------------------------------------------------- */ -if (cleanHash().length > 0) { - const currentCleanHash = cleanHash(); - setTimeout(() => { - if (currentCleanHash.indexOf('/') > -1) { - const subHash = currentCleanHash.split('/'); - if (subHash.length > 1) { - $(`a[href$="#${subHash[0]}-${subHash[1]}"]`).trigger('click'); - } - } else { - jump(currentCleanHash.replace(/#/, '')); - } - }, 500); -} - -/* Menu retract on action */ -$('.top-menu-link a').on('click touchend', () => { - if ($('.navbar-toggle').is(':visible')) { - $('.navbar-toggle').trigger('click'); - } -}); diff --git a/assets/frontpage/less/events.less b/assets/frontpage/less/events.less deleted file mode 100755 index 4f04db5d4..000000000 --- a/assets/frontpage/less/events.less +++ /dev/null @@ -1,217 +0,0 @@ -section#events { - #eventimage { - margin-bottom: 15px; - } - - .hero { - .hero-time { - font-size: 18px; - } - - .hero-title p { - color: #666; - font-weight: 400; - font-size: 18px; - margin-bottom: 10px; - word-wrap: break-word; - } - - .hero-ingress p { - font-weight: 400; - } - - .hero-date { - background: @baby-blue; - display: block; - color: #fff; - padding: 2px; - text-align: center; - font-weight: 300; - font-size: 18px; - } - } - - /* Old stuff for refrence below */ - - .event-title { - font-size: 17px; - margin-bottom: 5px; - color: #666; - margin-top: -7px; - } - - .event-ingress { - font-size: 12px; - color: #777; - font-weight: 300; - } - - .event-calendar-box { - padding: 3px 3px 3px 3px; - text-align: center; - font-weight: 400; - font-size: 19px; - width: 6%; - } - - .event-arrkom { - .event-calendar-box(); - - background: @arrkom-color; - color: @arrkom-color; - } - - .event-fagkom { - .event-calendar-box(); - - background: @fagkom-color; - color: @fagkom-color; - } - - .event-bedkom { - .event-calendar-box(); - - background: @bedkom-color; - color: @bedkom-color; - } - - /* Gets overridden below if needed */ - [class*='event-type-'] { - .event-arrkom; - } - - .event-type-1 { - .event-arrkom; - } - - .event-type-2 { - .event-bedkom; - } - - .event-type-3 { - .event-fagkom; - } - - .event-calendar-date { - display: block; - background: #fff; - padding: 3px; - } - - .event-calendar-month { - color: #fff; - font-size: 13px; - font-weight: 400; - text-transform: uppercase; - } - - .event-filters { - padding-bottom: 20px; - float: right; - margin-top: -10px; - } - - .event-filter-button { - background-color: #027bbb; - color: #fff; - padding: 4px 8px; - font-weight: 600; - border-color: #0f88c7; - border-radius: 0; - } - - .event-filter-button:hover { - background: #2291cc; - border-color: #319fda; - } - - .event-kurs { - background-color: @fagkom-color; - color: #fff; - } - - .event-sosialt { - background-color: @arrkom-color; - color: #fff; - } - - .event-bedriftspresentasjon { - background-color: @baby-blue; - color: #fff; - } - - .event-annet { - background-color: @heading; - color: #fff; - } - - .hidden-event-button { - background-color: #fff; - color: #000; - } - - .hidden-event-button:hover { - background-color: #f2f2f2; - } - - ul.event-list { - list-style: none; - margin-top: 8px; - padding: 0 15px; - - li { - padding: 10px 0; - border-bottom: 1px solid #e0e0e0; - font-size: 14px; - - @media (min-width: 992px) { - width: 455px; - - &:nth-child(odd) { - float: left; - } - - &:nth-child(even) { - float: right; - } - - // Remove border-bottom from last row - &:last-child, &:nth-child(odd):nth-last-child(2) { - border: none; - } - } - - @media (min-width: 1200px) { - width: 555px; - } - - span { - float: right; - color: #ccc; - color: #428bca; - } - - a { - color: #666; - } - } - } - - @media (max-width: 991px) { - .hero-date { - margin-bottom: 10px; - } - - ul.event-list:nth-child(1) { - margin-bottom: 0; - - li:last-child { - border-bottom: 1px solid #e0e0e0; - } - } - - ul.event-list:nth-child(2) { - margin-top: 0; - } - } -} diff --git a/assets/frontpage/less/frontpage.less b/assets/frontpage/less/frontpage.less index 01bc4bc8b..b8222ea47 100644 --- a/assets/frontpage/less/frontpage.less +++ b/assets/frontpage/less/frontpage.less @@ -4,4 +4,3 @@ @import 'articles.less'; @import 'business.less'; @import 'company.less'; -@import 'events.less'; diff --git a/esbuild.mjs b/esbuild.mjs index 8dfc4740f..0ad93dcaa 100644 --- a/esbuild.mjs +++ b/esbuild.mjs @@ -18,8 +18,6 @@ let entryPoints = [ { out: 'dashboardHobbies', in: 'assets/dashboard/hobbies/index.jsx' }, { out: 'dashboardPosters', in: 'assets/dashboard/posters/index.js' }, { out: 'dashboardWebshop', in: 'assets/dashboard/webshop/index.js' }, - { out: 'eventsArchive', in: 'assets/events/archive/index.js' }, - { out: 'eventsDetails', in: 'assets/events/details/index.js' }, { out: 'eventsMail', in: 'assets/events/mail/index.js' }, { out: 'feedback', in: 'assets/feedback/index.js' }, { out: 'frontpage', in: 'assets/frontpage/index.jsx' }, diff --git a/onlineweb4/urls.py b/onlineweb4/urls.py index 6e84a0a79..1be9fc61e 100644 --- a/onlineweb4/urls.py +++ b/onlineweb4/urls.py @@ -32,27 +32,6 @@ def redirect_to_new_wiki(request, path): re_path(r"^$", HomePageView.as_view(), name="home"), # Django-js-reverse used to get django urls to react re_path(r"^jsreverse/$", urls_js, name="js_reverse"), - # nav-bar menu urls - re_path( - r"^#events$", - HomePageView.as_view(template_name="frontpage.html"), - name="events-link", - ), - re_path( - r"^#articles$", - HomePageView.as_view(template_name="frontpage.html"), - name="articles-link", - ), - re_path( - r"^#about$", - HomePageView.as_view(template_name="frontpage.html"), - name="about-link", - ), - re_path( - r"^#business$", - HomePageView.as_view(template_name="frontpage.html"), - name="business-link", - ), # Online Notifier Owner Verification (checked yearly or so by Google) re_path( r"^google79c0b331a83a53de\.html$", diff --git a/pyproject.toml b/pyproject.toml index 29c3a3218..9434b2c34 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,10 +58,10 @@ dev-dependencies = [ "pytest-cov~=5.0", "pytest-django~=4.8", "pytest-xdist~=3.6", - "freezegun~=1.5", "coverage~=7.6", "ruff~=0.6", "pre-commit~=3.8", + "freezegun>=1.5.1", ] [tool.ruff] diff --git a/templates/events/dashboard/details.html b/templates/events/dashboard/details.html index a2bbfda4f..e98713334 100644 --- a/templates/events/dashboard/details.html +++ b/templates/events/dashboard/details.html @@ -93,6 +93,20 @@

    {{ event }}

    {% include "events/dashboard/details/details.html" %}
    + +
    diff --git a/templates/events/details.html b/templates/events/details.html deleted file mode 100644 index 6be9fce42..000000000 --- a/templates/events/details.html +++ /dev/null @@ -1,477 +0,0 @@ -{% extends "base.html" %} -{% load markdown_deux_tags %} -{% load crispy_forms_tags %} -{% load calendar_filters %} -{% load payment_tag %} -{% load render_bundle from webpack_loader %} - -{% block title %} -{{ event.title }} - Online -{% endblock title %} - -{% block styles %} - {{ block.super }} - {% render_bundle 'eventsDetails' 'css' %} -{% endblock %} - -{% block js %} - {{ block.super }} - - {% render_bundle 'eventsDetails' 'js' attrs='async type="module"' %} -{% endblock %} - -{% block content %} -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    Fra
    -
    - {{ event.event_start|date:"H.i" }} - {{ event.event_start|date:"d. M Y" }} -
    -
    -
    -
    Til
    -
    - {{ event.event_end|date:"H.i" }} - {{ event.event_end|date:"d. M Y" }} -
    -
    -
    -
    Sted
    -
    {{ event.location }}
    -
    -
    -
    - {% if attendance_event %} -
    -
    -
    -
    Påmeldte
    -
    - {{ attendance_event.number_of_seats_taken }}/{{ attendance_event.max_capacity }} -
    -
    -
    - {% if attendance_event.waitlist_enabled %} -
    Antall på venteliste
    -
    {{ attendance_event.number_on_waitlist }}
    - {% else %} -
    Venteliste
    -
    Utilgjengelig
    - {% endif %} -
    -
    - {% if attendance_event.registration_start > now %} -
    Påmeldingsstart
    -
    -
    - {{ attendance_event.registration_start|date:"H.i" }} - {{ attendance_event.registration_start|date:"d. M Y" }} -
    -
    - {% else %} -
    Påmeldingsslutt
    -
    -
    - {{ attendance_event.registration_end|date:"H.i" }} - {{ attendance_event.registration_end|date:"d. M Y" }} -
    -
    - {% endif %} -
    -
    -
    - {% endif %} -
    -
    -
    -
    -
    -
    -
    - {% if attendance_event %} - {% if user_anonymous %} -

    Du må være logget inn for å se din status.

    - {% else %} - {% if user_attending %} - {% if place_on_wait_list %} -

    Du er nummer {{ place_on_wait_list }} på ventelisten.

    - {% elif payment_relation_id %} -

    Du er meldt på dette arrangementet, og du har betalt.

    - {% else %} -

    Du er meldt på dette arrangementet.

    - {% endif %} - {% endif %} - {% if user_status.status or user_attending %} - - {% endif %} - {% if user_attending %} - {% if attendance_event.extras.all %} - {% if attendance_event.registration_end > now or attendance_event.unattend_deadline > now %} - {% include 'events/extras.html' %} - {% else %} -

    Valgt extra: {{ attendee.extras }}

    - {% endif %} - {% endif %} - {% if user_paid %} - {% if attendance_event.unattend_deadline > now %} - {% if event.event_start > now %} - {% if payment_relation_id %} - - - {% else %} -

    Du har betalt og må kontakte ansvarlig komité for å melde deg av

    - {% endif %} - {% endif %} - {% endif %} - {% elif payment and not place_on_wait_list and not user_paid %} - {% display_payments payment payment_delay %} - - {% endif %} - {% if not user_paid %} - {% if attendance_event.unattend_deadline > now or place_on_wait_list %} - {% if event.event_start > now %} - - - {% endif %} - {% endif %} - {% endif %} - {% else %} - {% if user_status.status %} - {% if will_be_on_wait_list %} -

    Du kan sette deg på venteliste.

    - - {% elif payment and payment.payment_type == 1 %} -

    Påmelding til arangementet krever betaling.

    - {% display_payments payment payment_delay %} - {% else %} -

    Du kan melde deg på dette arrangementet.

    - - {% endif %} - {% elif user_status.offset %} -

    Du kan melde deg på {{ user_status.offset }}.

    - {% if not user_status.status and user_status.message %} -

    {{ user_status.message }}

    - {% endif %} - {% else %} -

    {{ user_status.message }}

    - {% if user_status.status_code == 403 %} -

    - - {% endif %} - {% endif %} - - {% if payment and payment.price %} -

    Pris: {{ payment.price.price }},-

    - {% endif %} - - {% endif %} - {% endif %} - {% else %} -

    Dette er ikke et påmeldingsarrangement.

    - {% endif %} - - {% if attendance_event %} - {% if user_status and user_status.status_code and user_status.status_code == 501 %} -

    Påmeldingen åpner {{ attendance_event.registration_start|date:"H.i d. M Y" }}

    - {% endif %} - -

    Avmeldingsfristen - {% if attendance_event.unattend_deadline > now %} - er - {% else %} - var - {% endif %} - {{ attendance_event.unattend_deadline|date:"H.i d. M Y" }} -

    - {% endif %} -
    - - {% if attendance_event %} -
    -
    -
    -

    - Dette arrangementet - {% if attendance_event.registration_end > now %} - er - {% else %} - var - {% endif %} - åpent for -

    -
    - - {% if attendance_event.guest_attendance %} - Alle - {% elif rules %} - {% for rule_bundle in rules %} - {% for rule in rule_bundle %} - {{ rule }} - {% endfor %} - {% endfor %} - {% else %} - Alle medlemmer - {% endif %} -
    -
    -
    -
    - {% endif %} -
    -
    -
    -
    -
    {{ event.ingress|striptags|markdown }}
    - {{ event.description|striptags|markdown }} -
    -
    -
    - {% if event.companies.all %} -
    -
    -
    Medarrangører
    - {% for company in event.companies.all %} - - {% endfor %} -
    -
    - {% else %} -
    - {{ event.image.description }} - {% if event.image.photographer %} -

    Foto: {{ event.image.photographer }}

    - {% endif %} -
    - {% endif %} -
    -
    - - - - - - - {% if user_status.status or user_attending %} - - {% endif %} - - {% if payment_relation_id %} - - {% endif %} -
    -
    - -{% endblock content %} diff --git a/templates/events/extras.html b/templates/events/extras.html deleted file mode 100644 index 34463e7ed..000000000 --- a/templates/events/extras.html +++ /dev/null @@ -1,20 +0,0 @@ -{% if attendance_event.extras.all %} - -{% endif %} diff --git a/templates/events/index.html b/templates/events/index.html deleted file mode 100644 index d91be091f..000000000 --- a/templates/events/index.html +++ /dev/null @@ -1,103 +0,0 @@ -{% extends "base.html" %} - -{% load calendar_filters %} -{% load render_bundle from webpack_loader %} - -{% block title %} -Arrangementsarkiv - Online -{% endblock title %} - -{% block styles %} - {{ block.super }} - {% render_bundle 'eventsArchive' 'css' %} -{% endblock %} - -{% block submenu %} - -{% endblock %} - -{% block content %} - -
    -
    - -
    -
    - -
    -
    - -
    -
    -
    - -
    -
    - - -
    - -
    - {% if user.is_authenticated %} -
    - -
    - {% endif %} - -
    - -
    - -
    - - -
    -
    -{%endblock content %} - -{% block js %} - {{ block.super }} - {% render_bundle 'eventsArchive' 'js' attrs='async type="module"' %} -{% endblock %} diff --git a/templates/events/payment_tag.html b/templates/events/payment_tag.html deleted file mode 100644 index 038fdff05..000000000 --- a/templates/events/payment_tag.html +++ /dev/null @@ -1,17 +0,0 @@ - - Gå til betalingssiden - -
    - -{% if payment_delay %} -

    Betalingsfrist: {{ payment_delay.valid_to }}

    -
    -{% elif payment.deadline %} -

    Betalingsfrist: {{ payment.deadline }}

    -
    -{% endif %} diff --git a/templates/events/search.html b/templates/events/search.html deleted file mode 100644 index a2add93e8..000000000 --- a/templates/events/search.html +++ /dev/null @@ -1,78 +0,0 @@ -{% load markdown_deux_tags %} -{% load shuffle %} - -{% if events %} - {% for event in events %} -
    - -
    -
    - {% if event.companies.all %} - {% with event.companies.all|shuffle|first as random_relation %} - - - - - - - {% endwith %} - {% else %} - - - - - - - {% endif %} - {% if event.is_attendance_event %} - - - - {% endif %} -
    -
    - -
    -
    - -
    - {{ event.event_start|date:"d.m.Y"}} -
    -
    - -
    {{ event.ingress|striptags|markdown }}
    - -
    - {% if event.is_attendance_event %} - {% if event.attendance_event.number_of_seats_taken == event.attendance_event.max_capacity %} -
    -

    Venteliste: {{ event.attendance_event.number_on_waitlist }}

    -
    - {% else %} -
    -

    Påmeldte: {{ event.attendance_event.number_of_seats_taken }} / {{ event.attendance_event.max_capacity }}

    -
    - {% endif %} -
    -

    Sted: {{ event.location }}

    -
    -
    -

    Påmelding åpner {{ event.attendance_event.registration_start|date:"d.m.y H.i" }}

    -
    - {% else %} -
    -

    Sted: {{ event.location }}

    -
    - {% endif %} -
    -
    - -
    - {% endfor %} -{% else %} -

    Ingen arrangementer funnet, eller de er blokkert av en adblock.

    -{% endif %} diff --git a/templates/frontpage.html b/templates/frontpage.html index 2cc02b84a..695a53970 100755 --- a/templates/frontpage.html +++ b/templates/frontpage.html @@ -48,16 +48,16 @@ @@ -66,9 +66,15 @@ {% block content %}
    -
    -
    -
    + +

    Arrangementer er flyttet til OWF!

    + Gå til nye OnlineWeb-en
    diff --git a/uv.lock b/uv.lock index 4a0481af5..a311aaef9 100644 --- a/uv.lock +++ b/uv.lock @@ -1192,7 +1192,7 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ { name = "coverage", specifier = "~=7.6" }, - { name = "freezegun", specifier = "~=1.5" }, + { name = "freezegun", specifier = ">=1.5.1" }, { name = "pre-commit", specifier = "~=3.8" }, { name = "pytest", specifier = "~=8.3" }, { name = "pytest-cov", specifier = "~=5.0" },