diff --git a/backend/api/grants/tests/test_update_grant.py b/backend/api/grants/tests/test_update_grant.py index d94ac4b76c..5d4703d02c 100644 --- a/backend/api/grants/tests/test_update_grant.py +++ b/backend/api/grants/tests/test_update_grant.py @@ -104,7 +104,7 @@ def test_update_grant(graphql_client, user, conference_factory, grant_factory): needAccommodation=True, why="why not", notes="🧸", - travellingFrom="London", + travellingFrom="GB", website="https://marcotte.house", twitterHandle="@marcottebear", githubHandle="marcottebear", diff --git a/backend/countries/filters.py b/backend/countries/filters.py new file mode 100644 index 0000000000..19593215ff --- /dev/null +++ b/backend/countries/filters.py @@ -0,0 +1,53 @@ +from django.contrib.admin import FieldListFilter + +from countries import countries + + +class CountryFilter(FieldListFilter): + """ + Custom filter for Country fields in Django Admin. + + This filter will display only the countries that are actually used in the + objects of the model. + Each country is displayed with its name and emoji. + """ + + def __init__(self, field, request, params, model, model_admin, field_path): + self.lookup_kwarg = "%s__exact" % field_path + self.lookup_val = request.GET.get(self.lookup_kwarg) + self.field_generic = "%s__" % field_path + super().__init__(field, request, params, model, model_admin, field_path) + + def expected_parameters(self): + """ + Return the expected parameters for the filter. + + This is used by Django admin to build the correct query string. + """ + return [self.lookup_kwarg] + + def choices(self, changelist): + """ + Generate the choices for the filter. + + This method retrieves the countries actually used in the model instances + and yields the choices for the filter. + """ + # Retrieve the distinct country codes used in the model + used_countries = set( + changelist.model.objects.values_list(self.field_path, flat=True) + ) + + # Map these codes to their corresponding names and emojis + country_choices = [ + (country.code, f"{country.name} {country.emoji}") + for country in countries + if country.code in used_countries + ] + + for code, name in country_choices: + yield { + "selected": self.lookup_val == str(code), + "query_string": changelist.get_query_string({self.lookup_kwarg: code}), + "display": name, + } diff --git a/backend/grants/admin.py b/backend/grants/admin.py index 57a284ec82..813e41f499 100644 --- a/backend/grants/admin.py +++ b/backend/grants/admin.py @@ -3,7 +3,7 @@ from datetime import timedelta from itertools import groupby from typing import Dict, List, Optional - +from countries.filters import CountryFilter from django import forms from django.contrib import admin, messages from django.db.models.query import QuerySet @@ -26,6 +26,7 @@ from .models import Grant, GrantRecap + EXPORT_GRANTS_FIELDS = ( "name", "full_name", @@ -39,7 +40,6 @@ "why", "notes", "travelling_from", - "traveling_from", "conference__code", "created", ) @@ -352,7 +352,6 @@ class Meta: "why", "notes", "travelling_from", - "traveling_from", "country_type", "applicant_message", "applicant_reply_sent_at", @@ -389,7 +388,7 @@ class GrantAdmin(ExportMixin, admin.ModelAdmin): "occupation", "grant_type", "interested_in_volunteering", - "traveling_from", + ("travelling_from", CountryFilter), ) search_fields = ( "email", @@ -454,7 +453,6 @@ class GrantAdmin(ExportMixin, admin.ModelAdmin): "need_visa", "need_accommodation", "travelling_from", - "traveling_from", "why", "python_usage", "been_to_other_events", @@ -483,8 +481,8 @@ def user_display_name(self, obj): description="C", ) def country(self, obj): - if obj.traveling_from: - country = countries.get(code=obj.traveling_from) + if obj.travelling_from: + country = countries.get(code=obj.travelling_from) if country: return country.emoji @@ -575,10 +573,10 @@ def get_queryset(self, request): return self.qs def changelist_view(self, request, extra_context=None): - qs = self.get_queryset(request).order_by("traveling_from") + qs = self.get_queryset(request).order_by("travelling_from") results = [] - for country_code, group in groupby(list(qs), key=lambda k: k.traveling_from): + for country_code, group in groupby(list(qs), key=lambda k: k.travelling_from): country = countries.get(code=country_code) if not country: continue diff --git a/backend/grants/migrations/0013_remove_grant_travelling_from.py b/backend/grants/migrations/0013_remove_grant_travelling_from.py new file mode 100644 index 0000000000..2299879c25 --- /dev/null +++ b/backend/grants/migrations/0013_remove_grant_travelling_from.py @@ -0,0 +1,16 @@ +# Generated by Django 4.2.7 on 2023-12-28 17:31 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("grants", "0012_grant_community_contribution_grant_github_handle_and_more"), + ] + + operations = [ + migrations.RemoveField( + model_name="grant", + name="travelling_from", + ), + ] diff --git a/backend/grants/migrations/0014_rename_grant_traveling_from_grant_travelling_from.py b/backend/grants/migrations/0014_rename_grant_traveling_from_grant_travelling_from.py new file mode 100644 index 0000000000..47bbaa29eb --- /dev/null +++ b/backend/grants/migrations/0014_rename_grant_traveling_from_grant_travelling_from.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.7 on 2023-12-28 18:09 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("grants", "0013_remove_grant_travelling_from"), + ] + + operations = [ + migrations.RenameField( + model_name='grant', + old_name='traveling_from', + new_name='travelling_from', + ), + ] diff --git a/backend/grants/models.py b/backend/grants/models.py index cf8f42a094..f42f3436c3 100644 --- a/backend/grants/models.py +++ b/backend/grants/models.py @@ -90,16 +90,13 @@ class ApprovedType(models.TextChoices): grant_type = models.CharField( _("grant type"), choices=GrantType.choices, max_length=10 ) - traveling_from = models.CharField( - _("Traveling from"), + travelling_from = models.CharField( + _("Travelling from"), max_length=100, blank=True, null=True, choices=[(country.code, country.name) for country in countries], ) - travelling_from = models.CharField( - _("Travelling from"), max_length=200 - ) # OLD FIELD needs_funds_for_travel = models.BooleanField(_("Needs funds for travel")) need_visa = models.BooleanField(_("Need visa/invitation letter?"), default=False) need_accommodation = models.BooleanField(_("Need accommodation"), default=False) @@ -207,8 +204,8 @@ def __str__(self): return f"{self.full_name}" def save(self, *args, **kwargs): - if self.traveling_from: - country = countries.get(code=self.traveling_from) + if self.travelling_from: + country = countries.get(code=self.travelling_from) assert country if country.code == "IT": self.country_type = Grant.CountryType.italy diff --git a/backend/grants/tests/factories.py b/backend/grants/tests/factories.py index e380a0dc0d..42bec0fda5 100644 --- a/backend/grants/tests/factories.py +++ b/backend/grants/tests/factories.py @@ -6,6 +6,7 @@ from grants.models import Grant from helpers.constants import GENDERS from users.tests.factories import UserFactory +from countries import countries @register @@ -31,7 +32,7 @@ class Meta: needs_funds_for_travel = factory.Faker("boolean") why = factory.Faker("text") notes = factory.Faker("text") - travelling_from = factory.Faker("country") + travelling_from = factory.fuzzy.FuzzyChoice([country.code for country in countries]) website = factory.Faker("url") twitter_handle = "@handle" github_handle = factory.Faker("user_name") diff --git a/frontend/src/locale/index.ts b/frontend/src/locale/index.ts index 3a4d4ccc6e..c5265afb4f 100644 --- a/frontend/src/locale/index.ts +++ b/frontend/src/locale/index.ts @@ -668,7 +668,7 @@ We look forward to reading about you and hope to see you at PyCon Italia 2024! "grants.form.fields.grantType.values.speaker": "Speaker", "grants.form.fields.travellingFrom": "Where are you travelling from?", "grants.form.fields.travellingFrom.description": - "Please indicate the city and country you will be traveling from to attend the conference.", + "Please indicate the city and country you will be travelling from to attend the conference.", "grants.form.fields.needsFundsForTravel": "Do you need financial aid for travelling to PyCon Italia?", "grants.form.fields.needsFundsForTravel.description":