def get_context_data(self, **kwargs) -> dict: lecture_year = datetime_to_lectureyear(timezone.now()) years = {x: {} for x in reversed(range(1990, lecture_year + 1))} for year in years: years[year] = { "documents": { "policy": None, "report": None, "financial": None }, "general_meetings": [], } for document in AnnualDocument.objects.filter(subcategory="policy"): years[document.year]["documents"]["policy"] = document for document in AnnualDocument.objects.filter(subcategory="report"): years[document.year]["documents"]["report"] = document for document in AnnualDocument.objects.filter(subcategory="financial"): years[document.year]["documents"]["financial"] = document for obj in GeneralMeeting.objects.all(): meeting_year = datetime_to_lectureyear(obj.datetime) years[meeting_year]["general_meetings"].append(obj) context = super().get_context_data(**kwargs) context.update({ "association_documents": AssociationDocument.objects.order_by( f"name_{get_language()}").all(), "years": list(years.items()), }) return context
def lookups(self, request, model_admin): current_year = datetime_to_lectureyear(timezone.now()) first_year = datetime_to_lectureyear( models.MemberGroupMembership.objects.earliest("since").since ) return [ (year, "{}-{}".format(year, year + 1)) for year in range(first_year, current_year + 1) ]
def lookups(self, request, model_admin): objects_end = models.Event.objects.aggregate(Max("end")) objects_start = models.Event.objects.aggregate(Min("start")) if objects_end["end__max"] and objects_start["start__min"]: year_end = datetime_to_lectureyear(objects_end["end__max"]) year_start = datetime_to_lectureyear(objects_start["start__min"]) return [ (year, "{}-{}".format(year, year + 1)) for year in range(year_end, year_start - 1, -1) ] return []
def gen_stats_year() -> Dict[str, Dict[str, int]]: """ Generate list with 6 entries, where each entry represents the total amount of Thalia members in a year. The sixth element contains all the multi-year students. """ stats_year = {} current_year = datetime_to_lectureyear(date.today()) for i in range(5): new = {} for key, _ in Membership.MEMBERSHIP_TYPES: new[key] = (Membership.objects.filter( user__profile__starting_year=current_year - i).filter(since__lte=date.today()).filter( Q(until__isnull=True) | Q(until__gt=date.today())).filter(type=key).count()) stats_year[str(current_year - i)] = new # Add multi year members new = {} for key, _ in Membership.MEMBERSHIP_TYPES: new[key] = (Membership.objects.filter( user__profile__starting_year__lt=current_year - 4).filter(since__lte=date.today()).filter( Q(until__isnull=True) | Q(until__gt=date.today())).filter(type=key).count()) stats_year[str(gettext("Older"))] = new return stats_year
def test_create_payment_for_entry(self): self.e1.username = "******" self.e1.save() self.e2.username = "******" self.e2.save() p1 = services._create_payment_for_entry(self.e1) # 'year' payment p2 = services._create_payment_for_entry(self.e2) # 'study' payment lecture_year = datetime_to_lectureyear(timezone.now()) Membership.objects.create( type=Membership.MEMBER, since=timezone.datetime(year=lecture_year, month=9, day=1), until=timezone.datetime(year=lecture_year + 1, month=9, day=1), user=self.e3.member, ) Membership.objects.create( type=Membership.MEMBER, since=timezone.datetime(year=lecture_year, month=9, day=1), until=timezone.datetime(year=lecture_year + 1, month=9, day=1), user=self.e4.member, ) Membership.objects.create( type=Membership.MEMBER, since=timezone.now() - timedelta(days=31), until=timezone.now() - timedelta(days=2), user=self.e5.member, ) # upgrade 'study' p3 = services._create_payment_for_entry(self.e3) # upgrade 'year' p4 = services._create_payment_for_entry(self.e4) # upgrade 'study' after membership end p5 = services._create_payment_for_entry(self.e5) self.e1.membership_type = Membership.BENEFACTOR self.e1.contribution = 10 p6 = services._create_payment_for_entry(self.e1) # contribu payment self.assertEqual(p1.amount, settings.MEMBERSHIP_PRICES["year"]) self.assertEqual(p1.processed, False) self.assertEqual(p2.amount, settings.MEMBERSHIP_PRICES["study"]) self.assertEqual(p2.processed, False) self.assertEqual( p3.amount, settings.MEMBERSHIP_PRICES["study"] - settings.MEMBERSHIP_PRICES["year"], ) self.assertEqual(p3.processed, False) self.assertEqual(p4.amount, settings.MEMBERSHIP_PRICES["year"]) self.assertEqual(p4.processed, False) self.assertEqual( p5.amount, settings.MEMBERSHIP_PRICES["study"] - settings.MEMBERSHIP_PRICES["year"], ) self.assertEqual(p5.processed, False) self.assertEqual(p6.amount, 10) self.assertEqual(p6.processed, False)
def get_automatic_mailinglists(): """Return mailing list names that should be generated automatically.""" lectureyear = datetime_to_lectureyear(timezone.now()) list_names = [ "leden", "members", "begunstigers", "benefactors", "ereleden", "honorary", "mentors", "activemembers", "commissievoorzitters", "committeechairs", "optin", "oldboards", "oudbesturen", ] if Board.objects.exists(): for year in range( Board.objects.earliest("since").since.year, lectureyear): board = Board.objects.get(since__year=year) if board is not None: years = str(board.since.year)[-2:] + str(board.until.year)[-2:] list_names += [f"bestuur{years}", f"board{years}"] return list_names
def __init__(self, *args, **kwargs): """Initialize form and set the year choices.""" super().__init__(*args, **kwargs) self.initial["year"] = datetime_to_lectureyear(timezone.now()) self.fields["year"] = forms.ChoiceField( label="Academic year", choices=association_year_choices())
def _create_membership_from_entry(entry: Entry, member: Member = None ) -> Union[Membership, None]: """ Create or update Membership model based on Entry model information :param entry: Entry model :type entry: Entry :return: The created or updated membership :rtype: Membership """ lecture_year = datetime_to_lectureyear(timezone.now()) since = calculate_membership_since() until = None if timezone.now().month == 8: lecture_year += 1 if entry.length == Entry.MEMBERSHIP_YEAR: # If entry is Renewal set since to current membership until + 1 day # Unless there is no current membership try: member = entry.renewal.member membership = member.current_membership if membership is not None: if membership.until is None: raise ValueError("This member already has a never ending " "membership") since = membership.until except Renewal.DoesNotExist: pass until = timezone.datetime(year=lecture_year + 1, month=9, day=1).date() elif entry.length == Entry.MEMBERSHIP_STUDY: try: renewal = entry.renewal member = renewal.member membership = member.latest_membership # Having a latest membership which has an until date implies that # this membership last(s/ed) till the end of the lecture year # This means it's possible to renew the 'year' membership # to a 'study' membership thus the until date should now be None # and no new membership is needed. # The rules for this behaviour are taken from the HR if membership is not None: if membership.until is None: raise ValueError("This member already has a never ending " "membership") if entry.created_at.date() < membership.until: membership.until = None membership.save() return membership except Renewal.DoesNotExist: pass else: return None return Membership.objects.create(user=member, since=since, until=until, type=entry.membership_type)
def setup(self, request, *args, **kwargs) -> None: super().setup(request, *args, **kwargs) current_lectureyear = datetime_to_lectureyear(date.today()) self.year_range = list( reversed(range(current_lectureyear - 5, current_lectureyear + 1)) ) self.keywords = request.GET.get("keywords", "").split() or None self.query_filter = kwargs.get("filter", None)
def association_year_choices(): current_year = datetime_to_lectureyear(timezone.now()) choices = [] for year in range(1990, current_year + 2): choices.append((year, "{}-{}".format(year, year + 1))) choices.reverse() return choices
def association_year_choices(): """Return the academic years Thalia existed.""" current_year = datetime_to_lectureyear(timezone.now()) choices = [] for year in range(1990, current_year + 2): choices.append((year, "{}-{}".format(year, year + 1))) choices.reverse() return choices
def test_gen_stats_different_years(self): current_year = datetime_to_lectureyear(date.today()) # postgres does not define random access directly on unsorted querysets members = [member for member in Member.objects.all()] # one first year student m = members[0] m.profile.starting_year = current_year m.profile.save() # one second year student m = members[1] m.profile.starting_year = current_year - 1 m.profile.save() # no third year students # one fourth year student m = members[2] m.profile.starting_year = current_year - 3 m.profile.save() # no fifth year students # one >5 year student m = members[3] m.profile.starting_year = current_year - 5 m.profile.save() # 4 active members result = gen_stats_year() self.assertEqual(4, self.sum_members(result)) self.assertEqual(4, self.sum_members(result, Membership.MEMBER)) # one first year student self.assertEqual(1, result["2019"][Membership.MEMBER]) # one second year student self.assertEqual(1, result["2018"][Membership.MEMBER]) # no third year students self.assertEqual(0, result["2017"][Membership.MEMBER]) # one fourth year student self.assertEqual(1, result["2016"][Membership.MEMBER]) # no fifth year students self.assertEqual(0, result["2015"][Membership.MEMBER]) # one >5 year student self.assertEqual(1, result["Older"][Membership.MEMBER])
def generate_category_statistics(): """ Generate statistics about events, number of events per category :return: Dict with key, value resp. being category, event count. """ year = datetime_to_lectureyear(timezone.now()) data = {} for i in range(5): year_start = date(year=year - i, month=9, day=1) year_end = date(year=year - i + 1, month=9, day=1) data[str(year - i)] = { str(display): Event.objects.filter( category=key, start__gte=year_start, end__lte=year_end ).count() for key, display in Event.EVENT_CATEGORIES } return data
class AddSummaryForm(ModelForm): """ Custom form to add summaries, orders courses by name and formats the year as lecture years """ course = ModelChoiceField( queryset=Course.objects.order_by("name"), empty_label=None, ) this_year = datetime_to_lectureyear(timezone.now()) years = reversed([(x, "{} - {}".format(x, x + 1)) for x in range(this_year - 20, this_year + 1)]) year = TypedChoiceField(choices=years, coerce=int, empty_value=this_year) class Meta: model = Summary fields = ("name", "year", "language", "file", "course", "author")
def handle(self, *args, **options): """ Handle the command being executed :param options: the passed-in options """ opts = [ "all", "board", "committee", "event", "partner", "pizza", "user", "vacancy", "document", "newsletter", "course", "registration", "payment", "photoalbum", ] if all([not options[opt] for opt in opts]): self.stdout.write("Use ./manage.py help createfixtures to find out" " how to call this command") if options["all"]: self.stdout.write("all argument given, overwriting" " all other inputs") options = { "user": 20, "board": 3, "committee": 3, "society": 3, "event": 20, "partner": 6, "vacancy": 4, "pizza": 5, "newsletter": 2, "document": 8, "course": 10, "registration": 20, "payment": 5, "photoalbum": 5, } # Users need to be generated before boards and committees if options["user"]: for __ in range(options["user"]): self.create_user() if options["board"]: lecture_year = datetime_to_lectureyear(date.today()) for i in range(options["board"]): self.create_board(lecture_year - i) # Member groups need to be generated before events if options["committee"]: for __ in range(options["committee"]): self.create_member_group(Committee) if options["society"]: for __ in range(options["society"]): self.create_member_group(Society) if options["event"]: for __ in range(options["event"]): self.create_event() # Partners need to be generated before vacancies if options["partner"]: for __ in range(options["partner"]): self.create_partner() # Make one of the partners the main partner try: Partner.objects.get(is_main_partner=True) except Partner.DoesNotExist: main_partner = random.choice(Partner.objects.all()) main_partner.is_active = True main_partner.is_main_partner = True main_partner.save() if options["vacancy"]: categories = VacancyCategory.objects.all() if not categories: self.stdout.write("No vacancy categories found. " "Creating 5 categories.") for __ in range(5): self.create_vacancy_category() categories = VacancyCategory.objects.all() partners = Partner.objects.all() for __ in range(options["vacancy"]): self.create_vacancy(partners, categories) if options["pizza"]: for __ in range(options["pizza"]): self.create_pizza() if options["newsletter"]: for __ in range(options["newsletter"]): self.create_newsletter() if options["document"]: for __ in range(options["document"]): self.create_document() # Courses need to be created before exams and summaries if options["course"]: # Create course categories if needed if len(Category.objects.all()) < 5: for _ in range(5): category = Category() category.name_nl = _generate_title() category.name_en = category.name_nl category.save() for _ in range(options["course"]): self.create_course() # Registrations need to be created before payments if options["registration"]: for _ in range(options["registration"]): self.create_event_registration() if options["payment"]: for _ in range(options["payment"]): self.create_payment() if options["photoalbum"]: for _ in range(options["photoalbum"]): self.create_photo_album()
def year_choices(): """Get the lecture years""" current = datetime_to_lectureyear(timezone.now()) return [(year, "{}-{}".format(year, year + 1)) for year in range(current + 1, 1989, -1)]
def current_year(): """Get the current lecture year""" return datetime_to_lectureyear(timezone.now())
def test_gen_stats_active(self): """ Testing if active and non-active objects are counted correctly """ current_year = datetime_to_lectureyear(date.today()) # Set start date to current year - 1: for m in Member.objects.all(): m.profile.starting_year = current_year - 1 m.profile.save() result = gen_stats_year() self.assertEqual(10, self.sum_members(result)) self.assertEqual(10, self.sum_members(result, Membership.MEMBER)) result = {k: v for k, v in gen_stats_member_type().items()} self.assertEqual(10, self.sum_member_types(result)) # Change one membership to benefactor should decrease amount of members m = Membership.objects.all()[0] m.type = Membership.BENEFACTOR m.save() result = gen_stats_year() self.assertEqual(10, self.sum_members(result)) self.assertEqual(9, self.sum_members(result, Membership.MEMBER)) self.assertEqual(1, self.sum_members(result, Membership.BENEFACTOR)) result = {k: v for k, v in gen_stats_member_type().items()} self.assertEqual(10, self.sum_member_types(result)) self.assertEqual(9, result[Membership.MEMBERSHIP_TYPES[0][1]]) self.assertEqual(1, result[Membership.MEMBERSHIP_TYPES[1][1]]) # Same for honorary members m = Membership.objects.all()[1] m.type = Membership.HONORARY m.save() result = gen_stats_year() self.assertEqual(10, self.sum_members(result)) self.assertEqual(8, self.sum_members(result, Membership.MEMBER)) self.assertEqual(1, self.sum_members(result, Membership.BENEFACTOR)) self.assertEqual(1, self.sum_members(result, Membership.HONORARY)) result = {k: v for k, v in gen_stats_member_type().items()} self.assertEqual(10, self.sum_member_types(result)) self.assertEqual(8, result[Membership.MEMBERSHIP_TYPES[0][1]]) self.assertEqual(1, result[Membership.MEMBERSHIP_TYPES[1][1]]) self.assertEqual(1, result[Membership.MEMBERSHIP_TYPES[2][1]]) # Terminate one membership by setting end date to current_year -1, # should decrease total amount and total members m = Membership.objects.all()[2] m.until = timezone.now() - timedelta(days=365) m.save() result = gen_stats_year() self.assertEqual(9, self.sum_members(result)) self.assertEqual(7, self.sum_members(result, Membership.MEMBER)) self.assertEqual(1, self.sum_members(result, Membership.BENEFACTOR)) self.assertEqual(1, self.sum_members(result, Membership.HONORARY)) result = {k: v for k, v in gen_stats_member_type().items()} self.assertEqual(9, self.sum_member_types(result)) self.assertEqual(7, result[Membership.MEMBERSHIP_TYPES[0][1]]) self.assertEqual(1, result[Membership.MEMBERSHIP_TYPES[1][1]]) self.assertEqual(1, result[Membership.MEMBERSHIP_TYPES[2][1]])
def get_automatic_lists(): """Return list of mailing lists that should be generated automatically.""" current_committee_chairs = ( MemberGroupMembership.active_objects.filter(group__board=None) .filter(group__society=None) .filter(chair=True) .select_related("member") ) committee_chairs = _get_members_email_addresses( [x.member for x in current_committee_chairs] ) + ["*****@*****.**"] current_society_chairs = ( MemberGroupMembership.active_objects.filter(group__board=None) .filter(group__committee=None) .filter(chair=True) .select_related("member") ) society_chair_emails = _get_members_email_addresses( [x.member for x in current_society_chairs] ) + ["*****@*****.**"] active_committee_memberships = ( MemberGroupMembership.active_objects.filter(group__board=None) .filter(group__society=None) .select_related("member") ) active_members = _get_members_email_addresses( [x.member for x in active_committee_memberships] ) lectureyear = datetime_to_lectureyear(timezone.now()) # Change to next lecture year after December if 0 < timezone.now().month < 9: lectureyear += 1 active_mentorships = Mentorship.objects.filter(year=lectureyear).prefetch_related( "member" ) mentors = _get_members_email_addresses([x.member for x in active_mentorships]) lists = [ { "name": "members", "aliases": ["leden"], "description": "Automatic moderated mailinglist that can be used " "to send mail to all members", "addresses": _get_members_email_addresses( Member.all_with_membership("member") ), "moderated": True, }, { "name": "benefactors", "aliases": ["begunstigers"], "description": "Automatic moderated mailinglist that can be used " "to send mail to all benefactors", "addresses": _get_members_email_addresses( Member.all_with_membership(Membership.BENEFACTOR) ), "moderated": True, }, { "name": "honorary", "aliases": ["ereleden"], "description": "Automatic moderated mailinglist that can be used " "to send mail to all honorary members", "addresses": _get_members_email_addresses( Member.all_with_membership("honorary") ), "moderated": True, }, { "name": "mentors", "description": "Automatic moderated mailinglist that can be used " "to send mail to all orientation mentors. These " "members should have a mentorship with the current " "calendar year.", "addresses": mentors, "moderated": True, }, { "name": "activemembers", "description": "Automatic moderated mailinglist that can be used " "to send mail to all active members. These are all " "users that are currently a member of a committee.", "addresses": active_members, "moderated": True, }, { "name": "committeechairs", "aliases": ["commissievoorzitters"], "description": "Automatic mailinglist that can be used to send " "mail to all committee chairs", "addresses": committee_chairs, "moderated": False, }, { "name": "societychairs", "aliases": ["gezelschapvoorzitters"], "description": "Automatic mailinglist that can be used to send " "mail to all society chairs", "addresses": society_chair_emails, "moderated": False, }, { "name": "optin", "description": "Automatic mailinglist that can be used to send " "mail to all members that have opted-in to receive " "these (mostly recruitment) emails.", "addresses": _get_members_email_addresses( Member.current_members.filter(profile__receive_optin=True) ), "moderated": True, }, { "name": "committees", "description": "Automatic moderated mailinglist that is a " "collection of all committee lists", "addresses": [ f"{c.contact_mailinglist.name}@{settings.GSUITE_DOMAIN}" for c in Committee.objects.exclude( contact_mailinglist=None ).select_related("contact_mailinglist") ], "moderated": True, }, { "name": "societies", "description": "Automatic moderated mailinglist that is a " "collection of all society lists", "addresses": [ f"{c.contact_mailinglist.name}@{settings.GSUITE_DOMAIN}" for c in Society.objects.exclude( contact_mailinglist=None ).select_related("contact_mailinglist") ], "moderated": True, }, ] for language in settings.LANGUAGES: lists.append( { "name": f"newsletter-{language[0]}", "description": "Automatic moderated mailinglist that can be used " f"to send newsletters in {language[1]}", "addresses": _get_members_email_addresses( Member.current_members.all().filter( profile__receive_newsletter=True, profile__language=language[0] ) ), "moderated": True, } ) all_previous_board_members = [] for board in Board.objects.filter(since__year__lte=lectureyear).order_by( "since__year" ): board_members = [ board.member for board in MemberGroupMembership.objects.filter( group=board ).prefetch_related("member") ] all_previous_board_members += board_members years = str(board.since.year)[-2:] + str(board.until.year)[-2:] lists.append( { "name": f"board{years}", "aliases": [f"bestuur{years}"], "description": "Automatic mailinglist to send email to all board " f"members of {board.since.year}-{board.until.year}", "addresses": _get_members_email_addresses(board_members), "moderated": False, } ) lists.append( { "name": "oldboards", "aliases": ["oudbesturen"], "description": "Automatic mailinglist to send " "email to all previous board members", "moderated": True, "addresses": _get_members_email_addresses(all_previous_board_members), } ) return lists
def year(self): return datetime_to_lectureyear(self.exam_date)
def test_create_membership_from_entry(self): self.e1.username = "******" self.e1.save() self.e2.username = "******" self.e2.save() with freeze_time("2017-01-12"): lecture_year = datetime_to_lectureyear(timezone.now()) self.assertEqual(lecture_year, 2016) m1 = services._create_member_from_registration(self.e1) m2 = services._create_member_from_registration(self.e2) # Registration to new 'year' membership starting today membership1 = services._create_membership_from_entry(self.e1, m1) self.assertEqual(membership1.since, timezone.now().date()) self.assertEqual( membership1.until, timezone.datetime(year=2017, month=9, day=1).date() ) self.assertEqual(membership1.user, m1) self.assertEqual(membership1.type, self.e1.membership_type) # Registration to new 'study' membership starting today membership2 = services._create_membership_from_entry(self.e2, m2) self.assertEqual(membership2.since, timezone.now().date()) self.assertEqual(membership2.until, None) self.assertEqual(membership2.user, m2) self.assertEqual(membership2.type, self.e2.membership_type) membership2.delete() with freeze_time("2017-08-12"): # Check if since is new lecture year in august membership2 = services._create_membership_from_entry(self.e2, m2) self.assertEqual( membership2.since, timezone.datetime(year=2017, month=9, day=1).date() ) self.assertEqual(membership2.until, None) self.assertEqual(membership2.user, m2) self.assertEqual(membership2.type, self.e2.membership_type) with freeze_time("2017-01-12"): # Renewal to new 'study' membership starting today self.e3.length = Entry.MEMBERSHIP_STUDY membership3 = services._create_membership_from_entry(self.e3) self.assertEqual(membership3.since, timezone.now().date()) self.assertEqual(membership3.until, None) self.assertEqual(membership3.user, self.e3.member) self.assertEqual(membership3.type, self.e3.membership_type) membership3.delete() # Renewal to new 'year' membership starting today self.e3.length = Entry.MEMBERSHIP_YEAR membership3 = services._create_membership_from_entry(self.e3) self.assertEqual(membership3.since, timezone.now().date()) self.assertEqual( membership3.until, timezone.datetime(month=9, day=1, year=2017).date() ) self.assertEqual(membership3.user, self.e3.member) self.assertEqual(membership3.type, self.e3.membership_type) membership3.delete() self.e3.length = Entry.MEMBERSHIP_YEAR existing_membership = Membership.objects.create( type=Membership.MEMBER, since=timezone.datetime(year=2016, month=9, day=1).date(), until=timezone.datetime(year=2017, month=1, day=31).date(), user=self.e3.member, ) # Renewal to new 'year' membership starting 1 day after # end of the previous membership self.e3.length = Entry.MEMBERSHIP_YEAR membership3 = services._create_membership_from_entry(self.e3) self.assertEqual(membership3.since, existing_membership.until) self.assertEqual( membership3.until, timezone.datetime(year=2017, month=9, day=1).date() ) self.assertEqual(membership3.user, self.e3.member) self.assertEqual(membership3.type, self.e3.membership_type) membership3.delete() self.e3.length = Entry.MEMBERSHIP_STUDY # Renewal (aka upgrade) existing membership to 'study' membership # It doesn't work when the entry was made after the renewal # was due, so this is a new membership membership3 = services._create_membership_from_entry(self.e3) self.assertEqual(membership3.since, timezone.now().date()) self.assertEqual(membership3.until, None) self.assertEqual(membership3.user, self.e3.member) self.assertEqual(membership3.type, self.e3.membership_type) membership3.delete() # But it does work when the entry was created before the renewal # was actually due. This modifies the existing membership self.e3.created_at = timezone.make_aware(timezone.datetime(2017, 1, 30)) self.e3.save() membership3 = services._create_membership_from_entry(self.e3) self.assertEqual(membership3.since, existing_membership.since) self.assertEqual(membership3.until, None) self.assertEqual(membership3.user, self.e3.member) self.assertEqual(membership3.type, self.e3.membership_type) # Fail 'study' renewal of existing 'study' membership existing_membership.until = None existing_membership.save() with self.assertRaises(ValueError): services._create_membership_from_entry(self.e3) # Fail 'year' renewal of existing 'study' membership self.e3.length = Entry.MEMBERSHIP_YEAR existing_membership.until = None existing_membership.save() with self.assertRaises(ValueError): services._create_membership_from_entry(self.e3)
def dispatch(self, request, *args, **kwargs) -> HttpResponse: lecture_year = datetime_to_lectureyear(datetime.date.today()) self.current_board = Board.objects.filter(since__year=lecture_year, until__year=lecture_year + 1).first() return super().dispatch(request, *args, **kwargs)