diff --git a/src/economy/factories.py b/src/economy/factories.py index e67d0481a..c57ccfb24 100644 --- a/src/economy/factories.py +++ b/src/economy/factories.py @@ -6,10 +6,10 @@ import faker from django.contrib.auth.models import User from django.utils import timezone -from utils.slugs import unique_slugify from camps.models import Camp from teams.models import Team +from utils.slugs import unique_slugify from .models import Bank from .models import BankAccount @@ -32,6 +32,7 @@ fake = faker.Faker() + class BankFactory(factory.django.DjangoModelFactory): class Meta: model = Bank @@ -530,6 +531,7 @@ class Meta: address = factory.Faker("address", locale="dk_DK") notes = factory.Faker("text") + class ExpenseFactory(factory.django.DjangoModelFactory): """Factory for creating expense data.""" diff --git a/src/economy/migrations/0044_posreport_bank_responsible_end_and_more.py b/src/economy/migrations/0044_posreport_bank_responsible_end_and_more.py index d8d7dfd18..911a20192 100644 --- a/src/economy/migrations/0044_posreport_bank_responsible_end_and_more.py +++ b/src/economy/migrations/0044_posreport_bank_responsible_end_and_more.py @@ -1,36 +1,65 @@ # Generated by Django 4.2.21 on 2025-06-09 18:13 +from __future__ import annotations -from django.conf import settings -from django.db import migrations, models import django.db.models.deletion +from django.conf import settings +from django.db import migrations +from django.db import models class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('economy', '0043_coinifysettlement'), + ("economy", "0043_coinifysettlement"), ] operations = [ migrations.AddField( - model_name='posreport', - name='bank_responsible_end', - field=models.ForeignKey(blank=True, help_text='The banker responsible for the day end count of this PosReport', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='pos_report_banker_end', to=settings.AUTH_USER_MODEL), + model_name="posreport", + name="bank_responsible_end", + field=models.ForeignKey( + blank=True, + help_text="The banker responsible for the day end count of this PosReport", + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="pos_report_banker_end", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AddField( - model_name='posreport', - name='bank_responsible_start', - field=models.ForeignKey(blank=True, help_text='The banker responsible for the day start count of this PosReport', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='pos_report_banker_start', to=settings.AUTH_USER_MODEL), + model_name="posreport", + name="bank_responsible_start", + field=models.ForeignKey( + blank=True, + help_text="The banker responsible for the day start count of this PosReport", + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="pos_report_banker_start", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AddField( - model_name='posreport', - name='pos_responsible_end', - field=models.ForeignKey(blank=True, help_text='The pos responsible for the day end count of this PosReport', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='pos_report_pos_end', to=settings.AUTH_USER_MODEL), + model_name="posreport", + name="pos_responsible_end", + field=models.ForeignKey( + blank=True, + help_text="The pos responsible for the day end count of this PosReport", + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="pos_report_pos_end", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AddField( - model_name='posreport', - name='pos_responsible_start', - field=models.ForeignKey(blank=True, help_text='The pos responsible for the day start count of this PosReport', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='pos_report_pos_start', to=settings.AUTH_USER_MODEL), + model_name="posreport", + name="pos_responsible_start", + field=models.ForeignKey( + blank=True, + help_text="The pos responsible for the day start count of this PosReport", + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="pos_report_pos_start", + to=settings.AUTH_USER_MODEL, + ), ), ] diff --git a/src/economy/migrations/0045_populate_bank_and_pos_responsible.py b/src/economy/migrations/0045_populate_bank_and_pos_responsible.py index a7723d04f..97ad14bd9 100644 --- a/src/economy/migrations/0045_populate_bank_and_pos_responsible.py +++ b/src/economy/migrations/0045_populate_bank_and_pos_responsible.py @@ -1,4 +1,5 @@ # Generated by Django 4.2.21 on 2025-06-09 18:13 +from __future__ import annotations from django.db import migrations @@ -14,9 +15,8 @@ def populate_pos_people(apps, schema_editor) -> None: class Migration(migrations.Migration): - dependencies = [ - ('economy', '0044_posreport_bank_responsible_end_and_more'), + ("economy", "0044_posreport_bank_responsible_end_and_more"), ] operations = [ diff --git a/src/economy/migrations/0046_remove_posreport_bank_responsible_and_more.py b/src/economy/migrations/0046_remove_posreport_bank_responsible_and_more.py index 898a47e6d..a17b3058b 100644 --- a/src/economy/migrations/0046_remove_posreport_bank_responsible_and_more.py +++ b/src/economy/migrations/0046_remove_posreport_bank_responsible_and_more.py @@ -1,21 +1,21 @@ # Generated by Django 4.2.21 on 2025-06-09 18:59 +from __future__ import annotations from django.db import migrations class Migration(migrations.Migration): - dependencies = [ - ('economy', '0045_populate_bank_and_pos_responsible'), + ("economy", "0045_populate_bank_and_pos_responsible"), ] operations = [ migrations.RemoveField( - model_name='posreport', - name='bank_responsible', + model_name="posreport", + name="bank_responsible", ), migrations.RemoveField( - model_name='posreport', - name='pos_responsible', + model_name="posreport", + name="pos_responsible", ), ] diff --git a/src/economy/views.py b/src/economy/views.py index 0fce098d3..da67d290a 100644 --- a/src/economy/views.py +++ b/src/economy/views.py @@ -6,9 +6,8 @@ from django.conf import settings from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin -from django.db.models import OuterRef -from django.db.models import Q from django.db.models import Prefetch +from django.db.models import Q from django.db.models import Sum from django.http import HttpResponse from django.http import HttpResponseRedirect @@ -67,8 +66,8 @@ def get_context_data(self, **kwargs): ).count() reimbursement_total = 0 for reimbursement in Reimbursement.objects.filter( - reimbursement_user=self.request.user, - camp=self.camp, + reimbursement_user=self.request.user, + camp=self.camp, ): reimbursement_total += reimbursement.amount context["reimbursement_total"] = reimbursement_total @@ -158,18 +157,14 @@ class ChainListView(CampViewMixin, RaisePermissionRequiredMixin, ListView): permission_required = "camps.expense_create_permission" def get_queryset(self): - queryset = (Chain.objects - .filter(credebtors__expenses__camp=self.camp) - .order_by('name')) + queryset = Chain.objects.filter(credebtors__expenses__camp=self.camp).order_by("name") return queryset def get_context_data(self, **kwargs): """Add chains with expenses in past years""" context = super().get_context_data(**kwargs) - context['past_year_chains'] = (Chain.objects - .filter(~Q(credebtors__expenses__camp=self.camp)) - .order_by('name')) + context["past_year_chains"] = Chain.objects.filter(~Q(credebtors__expenses__camp=self.camp)).order_by("name") return context @@ -219,23 +214,27 @@ class CredebtorListView( permission_required = "camps.expense_create_permission" def get_queryset(self): - expenses=Expense.objects.filter(camp=self.camp) - revenues=Revenue.objects.filter(camp=self.camp) - return (Credebtor.objects.filter( - chain=self.chain - ).prefetch_related( - Prefetch( - 'expenses', - queryset=expenses, - to_attr="current_expenses", + expenses = Expense.objects.filter(camp=self.camp) + revenues = Revenue.objects.filter(camp=self.camp) + return ( + Credebtor.objects.filter( + chain=self.chain, ) - ).prefetch_related( - Prefetch( - 'revenues', - queryset=revenues, - to_attr="current_revenues", + .prefetch_related( + Prefetch( + "expenses", + queryset=expenses, + to_attr="current_expenses", + ), ) - )) + .prefetch_related( + Prefetch( + "revenues", + queryset=revenues, + to_attr="current_revenues", + ), + ) + ) def get_context_data(self, **kwargs): """Add chain to context.""" @@ -422,9 +421,9 @@ class ReimbursementCreateView(CampViewMixin, ExpensePermissionMixin, CreateView) def get(self, request, *args, **kwargs): """Check if this user has any approved and un-reimbursed expenses.""" if not request.user.expenses.filter( - reimbursement__isnull=True, - approved=True, - paid_by_bornhack=False, + reimbursement__isnull=True, + approved=True, + paid_by_bornhack=False, ): messages.error( request, diff --git a/src/program/tests.py b/src/program/tests.py new file mode 100644 index 000000000..fd8d3d111 --- /dev/null +++ b/src/program/tests.py @@ -0,0 +1,25 @@ +from django.urls import reverse + +from utils.tests import BornhackTestBase +from program.models import Event + +class TestFeedbackCreateView(BornhackTestBase): + """Test FeedbackCreateView""" + + @classmethod + def setUpTestData(cls) -> None: + """Test setup.""" + super().setUpTestData() + cls.bootstrap.create_camp_proposals(cls.camp, cls.bootstrap.event_types) + + def test_create_feedback_requires_login(self) -> None: + """Test creating feedback for an event requires user to be signed in""" + event = Event.objects.all().first() + kwargs = {"camp_slug": self.camp.slug, "event_slug": event.slug} + url = reverse("program:event_feedback_create", kwargs=kwargs) + expected = reverse("program:event_detail", kwargs=kwargs) + + response = self.client.get(url) + + self.assertRedirects(response, expected) + diff --git a/src/program/views.py b/src/program/views.py index 7c749ad67..c0a1b827e 100644 --- a/src/program/views.py +++ b/src/program/views.py @@ -1362,6 +1362,21 @@ class FeedbackCreateView(LoginRequiredMixin, EventViewMixin, CreateView): def setup(self, *args, **kwargs) -> None: super().setup(*args, **kwargs) + if not self.request.user.is_authenticated: + messages.error( + self.request, + "You must be logged in to provide Event Feedback", + ) + raise RedirectException( + reverse( + "program:event_detail", + kwargs={ + "camp_slug": self.camp.slug, + "event_slug": self.event.slug, + }, + ), + ) + if models.EventFeedback.objects.filter( event=self.event, user=self.request.user, diff --git a/src/utils/bootstrap/base.py b/src/utils/bootstrap/base.py index 18ccb33da..679cd659e 100644 --- a/src/utils/bootstrap/base.py +++ b/src/utils/bootstrap/base.py @@ -2350,6 +2350,7 @@ def bootstrap_tests(self) -> None: ticket_types, ) self.create_prize_ticket(camp, ticket_types) + self.create_camp_tracks(camp) teams[year] = self.create_camp_teams(camp) self.create_camp_team_memberships(camp, teams[year], self.users)