From faa3790f192088f607686585300bc453067432ae Mon Sep 17 00:00:00 2001 From: chrispreee <117157625+chrispreee@users.noreply.github.com> Date: Thu, 11 Jan 2024 10:30:28 +0000 Subject: [PATCH] Refactor out translation field name list generation, make translation field order consistent, add model translated field unit test, move Portuguese ahead of Arabic, apply unique, primary_key and blank only on translation field for default language, see HEA-141 --- apps/baseline/admin.py | 57 +++---- .../migrations/0004_add_translation_fields.py | 153 ++++++++++++++++++ apps/baseline/models.py | 9 +- apps/baseline/serializers.py | 10 +- apps/baseline/tests/factories.py | 18 ++- apps/baseline/tests/test_admin.py | 18 ++- apps/baseline/tests/test_viewsets.py | 15 +- apps/baseline/viewsets.py | 30 ++-- apps/common/admin.py | 43 ++--- apps/common/fields.py | 11 ++ apps/common/lookups.py | 27 +--- .../migrations/0007_add_translation_fields.py | 17 +- apps/common/models.py | 2 +- apps/common/tests/test_models.py | 52 +++++- apps/common/viewsets.py | 53 ++---- apps/metadata/admin.py | 89 +++------- apps/metadata/lookups.py | 17 +- .../migrations/0002_add_translation_fields.py | 108 +++++-------- apps/metadata/viewsets.py | 125 ++++---------- hea/settings/base.py | 2 +- 20 files changed, 422 insertions(+), 434 deletions(-) create mode 100644 apps/baseline/migrations/0004_add_translation_fields.py diff --git a/apps/baseline/admin.py b/apps/baseline/admin.py index 05f52ca7..ddbd46c2 100644 --- a/apps/baseline/admin.py +++ b/apps/baseline/admin.py @@ -3,6 +3,7 @@ from django.contrib import admin from common.admin import GeoModelAdmin +from common.fields import translation_fields from metadata.models import LivelihoodStrategyType from .forms import ( @@ -70,8 +71,8 @@ class LivelihoodZoneAdmin(admin.ModelAdmin): ) search_fields = [ "code", - "name", - "description", + *translation_fields("name"), + *translation_fields("description"), "country__iso_en_ro_name", ] list_filter = ("country",) @@ -84,7 +85,7 @@ class LivelihoodZoneBaselineAdmin(GeoModelAdmin): { "fields": [ "livelihood_zone", - "name", + *translation_fields("name"), "main_livelihood_category", "source_organization", "bss", @@ -96,7 +97,7 @@ class LivelihoodZoneBaselineAdmin(GeoModelAdmin): "data_collection_start_date", "data_collection_end_date", "publication_date", - "description", + *translation_fields("description"), ] }, ), @@ -119,15 +120,11 @@ class LivelihoodZoneBaselineAdmin(GeoModelAdmin): "reference_year_start_date", "reference_year_end_date", ) - search_fields = [ - "livelihood_zone__name", - "main_livelihood_category__name_en", - "main_livelihood_category__name_fr", - "main_livelihood_category__name_ar", - "main_livelihood_category__name_es", - "main_livelihood_category__name_ar", + search_fields = ( + *translation_fields("livelihood_zone__name"), + *translation_fields("main_livelihood_category__name"), "source_organization__name", - ] + ) list_filter = [ "source_organization", "livelihood_zone__country", @@ -152,10 +149,10 @@ class CommunityAdmin(GeoModelAdmin): search_fields = ( "name", "full_name", - "livelihood_zone_baseline__livelihood_zone__name", + *translation_fields("livelihood_zone_baseline__livelihood_zone__name"), ) list_filter = ( - "livelihood_zone_baseline__livelihood_zone__name", + *translation_fields("livelihood_zone_baseline__livelihood_zone__name"), "livelihood_zone_baseline__livelihood_zone__country", ) @@ -179,13 +176,10 @@ class LivelihoodStrategyAdmin(admin.ModelAdmin): ) search_fields = ( "strategy_type", - "livelihood_zone_baseline__livelihood_zone__name", - "product__common_name_en", - "product__common_name_fr", - "product__common_name_ar", - "product__common_name_es", - "product__common_name_pt", + *translation_fields("livelihood_zone_baseline__livelihood_zone__name"), + *translation_fields("product__common_name"), ) + list_filter = ( "strategy_type", "livelihood_zone_baseline__livelihood_zone", @@ -476,7 +470,7 @@ class WealthGroupAdmin(admin.ModelAdmin): list_filter = ( "livelihood_zone_baseline__source_organization", "livelihood_zone_baseline__livelihood_zone__country", - "livelihood_zone_baseline__livelihood_zone__name", + *translation_fields("livelihood_zone_baseline__livelihood_zone__name"), "community", "wealth_group_category", ) @@ -572,18 +566,11 @@ class CommunityCropProductionAdmin(admin.ModelAdmin): "land_unit_of_measure", ) search_fields = ( - "crop__description_en", - "crop__description_fr", - "crop__description_ar", - "crop__description_es", - "crop__description_pt", + *translation_fields("crop__description"), "crop_purpose", - "season__name_en", - "season__name_fr", - "season__name_ar", - "season__name_es", - "season__name_pt", + *translation_fields("season__name"), ) + list_filter = ( "community__livelihood_zone_baseline__livelihood_zone", "community__full_name", @@ -610,13 +597,7 @@ class CommunityLivestockAdmin(admin.ModelAdmin): "wet_season_milk_production", "dry_season_milk_production", ) - search_fields = ( - "livestock__common_name_en", - "livestock__common_name_fr", - "livestock__common_name_ar", - "livestock__common_name_es", - "livestock__common_name_pt", - ) + search_fields = (*translation_fields("livestock__common_name"),) list_filter = ( "community__livelihood_zone_baseline__livelihood_zone", "community__full_name", diff --git a/apps/baseline/migrations/0004_add_translation_fields.py b/apps/baseline/migrations/0004_add_translation_fields.py new file mode 100644 index 00000000..15c50717 --- /dev/null +++ b/apps/baseline/migrations/0004_add_translation_fields.py @@ -0,0 +1,153 @@ +from django.db import migrations + +import common.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ("baseline", "0003_alter_communitylivestock_options"), + ] + + operations = [ + migrations.RenameField( + model_name="livelihoodzone", + old_name="description", + new_name="description_en", + ), + migrations.RenameField( + model_name="livelihoodzone", + old_name="name", + new_name="name_en", + ), + migrations.RenameField( + model_name="livelihoodzonebaseline", + old_name="description", + new_name="description_en", + ), + migrations.RenameField( + model_name="livelihoodzonebaseline", + old_name="name", + new_name="name_en", + ), + migrations.AddField( + model_name="livelihoodzone", + name="description_ar", + field=common.fields.DescriptionField( + blank=True, + help_text="Any extra information or detail that is relevant to the object.", + max_length=2000, + verbose_name="Description", + ), + ), + migrations.AddField( + model_name="livelihoodzone", + name="description_es", + field=common.fields.DescriptionField( + blank=True, + help_text="Any extra information or detail that is relevant to the object.", + max_length=2000, + verbose_name="Description", + ), + ), + migrations.AddField( + model_name="livelihoodzone", + name="description_fr", + field=common.fields.DescriptionField( + blank=True, + help_text="Any extra information or detail that is relevant to the object.", + max_length=2000, + verbose_name="Description", + ), + ), + migrations.AddField( + model_name="livelihoodzone", + name="description_pt", + field=common.fields.DescriptionField( + blank=True, + help_text="Any extra information or detail that is relevant to the object.", + max_length=2000, + verbose_name="Description", + ), + ), + migrations.AddField( + model_name="livelihoodzone", + name="name_ar", + field=common.fields.NameField(blank=True, max_length=200, verbose_name="Name"), + ), + migrations.AddField( + model_name="livelihoodzone", + name="name_es", + field=common.fields.NameField(blank=True, max_length=200, verbose_name="Name"), + ), + migrations.AddField( + model_name="livelihoodzone", + name="name_fr", + field=common.fields.NameField(blank=True, max_length=200, verbose_name="Name"), + ), + migrations.AddField( + model_name="livelihoodzone", + name="name_pt", + field=common.fields.NameField(blank=True, max_length=200, verbose_name="Name"), + ), + migrations.AddField( + model_name="livelihoodzonebaseline", + name="description_ar", + field=common.fields.DescriptionField( + blank=True, + help_text="Any extra information or detail that is relevant to the object.", + max_length=2000, + verbose_name="Description", + ), + ), + migrations.AddField( + model_name="livelihoodzonebaseline", + name="description_es", + field=common.fields.DescriptionField( + blank=True, + help_text="Any extra information or detail that is relevant to the object.", + max_length=2000, + verbose_name="Description", + ), + ), + migrations.AddField( + model_name="livelihoodzonebaseline", + name="description_fr", + field=common.fields.DescriptionField( + blank=True, + help_text="Any extra information or detail that is relevant to the object.", + max_length=2000, + verbose_name="Description", + ), + ), + migrations.AddField( + model_name="livelihoodzonebaseline", + name="description_pt", + field=common.fields.DescriptionField( + blank=True, + help_text="Any extra information or detail that is relevant to the object.", + max_length=2000, + verbose_name="Description", + ), + ), + migrations.AddField( + model_name="livelihoodzonebaseline", + name="name_ar", + field=common.fields.NameField(blank=True, max_length=200, verbose_name="Name"), + ), + migrations.AddField( + model_name="livelihoodzonebaseline", + name="name_es", + field=common.fields.NameField(blank=True, max_length=200, verbose_name="Name"), + ), + migrations.AddField( + model_name="livelihoodzonebaseline", + name="name_fr", + field=common.fields.NameField(blank=True, max_length=200, verbose_name="Name"), + ), + migrations.AddField( + model_name="livelihoodzonebaseline", + name="name_pt", + field=common.fields.NameField(blank=True, max_length=200, verbose_name="Name"), + ), + ] diff --git a/apps/baseline/models.py b/apps/baseline/models.py index 8d7ac4f7..e719b1db 100644 --- a/apps/baseline/models.py +++ b/apps/baseline/models.py @@ -11,6 +11,7 @@ from model_utils.managers import InheritanceManager import common.models as common_models +from common.fields import TranslatedField from common.models import ( ClassifiedProduct, Country, @@ -84,8 +85,8 @@ class LivelihoodZone(common_models.Model): verbose_name=_("code"), help_text=_("Primary identifier for the Livelihood Zone"), ) - name = common_models.NameField(max_length=200, unique=True) - description = common_models.DescriptionField() + name = TranslatedField(common_models.NameField(max_length=200, unique=True)) + description = TranslatedField(common_models.DescriptionField()) country = models.ForeignKey(Country, verbose_name=_("Country"), db_column="country_code", on_delete=models.PROTECT) class Meta: @@ -123,8 +124,8 @@ class LivelihoodZoneBaseline(common_models.Model): June 2023 for the Sahel countries. """ - name = common_models.NameField(max_length=200, unique=True) - description = common_models.DescriptionField() + name = TranslatedField(common_models.NameField(max_length=200, unique=True)) + description = TranslatedField(common_models.DescriptionField()) livelihood_zone = models.ForeignKey( LivelihoodZone, db_column="livelihood_zone_code", on_delete=models.RESTRICT, verbose_name=_("Livelihood Zone") ) diff --git a/apps/baseline/serializers.py b/apps/baseline/serializers.py index ec4e14c9..d4f2f1aa 100644 --- a/apps/baseline/serializers.py +++ b/apps/baseline/serializers.py @@ -53,13 +53,13 @@ class Meta: class LivelihoodZoneSerializer(serializers.ModelSerializer): class Meta: model = LivelihoodZone - fields = [ + fields = ( "code", "name", "description", "country", "country_name", - ] + ) country_name = serializers.CharField(source="country.iso_en_ro_name", read_only=True) @@ -67,8 +67,10 @@ class Meta: class LivelihoodZoneBaselineSerializer(serializers.ModelSerializer): class Meta: model = LivelihoodZoneBaseline - fields = [ + fields = ( "id", + "name", + "description", "source_organization", "source_organization_name", "livelihood_zone", @@ -85,7 +87,7 @@ class Meta: "valid_to_date", "population_source", "population_estimate", - ] + ) livelihood_zone_name = serializers.CharField(source="livelihood_zone.name", read_only=True) source_organization_name = serializers.CharField(source="source_organization.pk", read_only=True) diff --git a/apps/baseline/tests/factories.py b/apps/baseline/tests/factories.py index 8cfa9c56..ee797cdb 100644 --- a/apps/baseline/tests/factories.py +++ b/apps/baseline/tests/factories.py @@ -78,8 +78,16 @@ class Meta: ] code = factory.LazyAttributeSequence(lambda o, n: f"{o.country.pk}{n:04d}") - name = factory.LazyAttribute(lambda o: f"{o.code} name") - description = factory.LazyAttribute(lambda o: f"{o.code} description") + name_en = factory.LazyAttribute(lambda o: f"{o.code} name EN") + name_fr = factory.LazyAttribute(lambda o: f"{o.code} name FR") + name_es = factory.LazyAttribute(lambda o: f"{o.code} name ES") + name_pt = factory.LazyAttribute(lambda o: f"{o.code} name PT") + name_ar = factory.LazyAttribute(lambda o: f"{o.code} name AR") + description_en = factory.LazyAttribute(lambda o: f"{o.code} description EN") + description_fr = factory.LazyAttribute(lambda o: f"{o.code} description FR") + description_es = factory.LazyAttribute(lambda o: f"{o.code} description ES") + description_pt = factory.LazyAttribute(lambda o: f"{o.code} description PT") + description_ar = factory.LazyAttribute(lambda o: f"{o.code} description AR") country = factory.SubFactory(CountryFactory) @@ -91,7 +99,11 @@ class Meta: "source_organization", ] - name = factory.LazyAttribute(lambda lz: f"Baseline {lz.livelihood_zone}") + name_en = factory.LazyAttribute(lambda lz: f"Baseline {lz.livelihood_zone}") + name_fr = factory.LazyAttribute(lambda lz: f"Baseline {lz.livelihood_zone}") + name_es = factory.LazyAttribute(lambda lz: f"Baseline {lz.livelihood_zone}") + name_pt = factory.LazyAttribute(lambda lz: f"Baseline {lz.livelihood_zone}") + name_ar = factory.LazyAttribute(lambda lz: f"Baseline {lz.livelihood_zone}") livelihood_zone = factory.SubFactory(LivelihoodZoneFactory) geography = None main_livelihood_category = factory.SubFactory(LivelihoodCategoryFactory) diff --git a/apps/baseline/tests/test_admin.py b/apps/baseline/tests/test_admin.py index f851e7d2..7ffa08c6 100644 --- a/apps/baseline/tests/test_admin.py +++ b/apps/baseline/tests/test_admin.py @@ -120,7 +120,7 @@ def test_livelihoodzone_changelists(self): def test_search_livelihood_zone(self): response = self.client.get( self.url, - {"q": self.livelihood_zone2.name}, + {"q": self.livelihood_zone2.name_en}, ) self.assertEqual(response.status_code, 200) self.assertContains(response, self.livelihood_zone2.name) @@ -161,7 +161,7 @@ def test_livelihoodzonebaseline_changelists(self): def test_search_livelihood_zone_baseline_fields(self): response = self.client.get( self.url, - {"q": self.livelihood_zone_baseline1.livelihood_zone.name}, + {"q": self.livelihood_zone_baseline1.livelihood_zone.name_en}, ) self.assertEqual(response.status_code, 200) self.assertContains(response, self.livelihood_zone_baseline1.livelihood_zone) @@ -192,10 +192,10 @@ def test_livelihood_zone_baseline_date_hierarchy(self): def test_create_livelihood_zone_baseline(self): bss = SimpleUploadedFile("test_bss.xlsx", b"Baseline content placeholder, just to be used for testing ...") - livelihood_zone = LivelihoodZoneFactory(name="New Test Zone") + livelihood_zone = LivelihoodZoneFactory(name_en="New Test Zone") current_count = LivelihoodZoneBaseline.objects.all().count() data = { - "name": f"{livelihood_zone.code} Baseline", + "name_en": f"{livelihood_zone.code} Baseline", "description": f"{livelihood_zone.code} Baseline description", "livelihood_zone": livelihood_zone.pk, "main_livelihood_category": LivelihoodCategoryFactory().pk, @@ -240,7 +240,7 @@ def test_livelihoodstrategy_changelists(self): def test_livelihoodstrategy_search_fields(self): response = self.client.get( self.url, - {"q": self.strategy1.livelihood_zone_baseline.livelihood_zone.name}, + {"q": self.strategy1.livelihood_zone_baseline.livelihood_zone.name_en}, ) self.assertEqual(response.status_code, 200) self.assertContains(response, self.strategy1.product.cpcv2) @@ -514,6 +514,7 @@ def test_list_community_crop_production(self): self.assertContains(response, self.cropproduction2.yield_without_inputs) def test_search_fields(self): + # Also confirms translation_fields() is working correctly search_fields = ( "crop__description_en", "crop__description_fr", @@ -527,7 +528,12 @@ def test_search_fields(self): "season__name_es", "season__name_pt", ) - self.assertTrue(all(element in self.admin.search_fields for element in search_fields)) + self.assertCountEqual( + self.admin.search_fields, + search_fields, + "CommunityCropProductionAdmin: " + f"Fields expected: {search_fields}. Fields found: {self.admin.search_fields}.", + ) response = self.client.get(reverse(self.url), {"q": self.cropproduction1.crop.description}) self.assertEqual(response.status_code, 200) # Parse the HTML content of the response diff --git a/apps/baseline/tests/test_viewsets.py b/apps/baseline/tests/test_viewsets.py index cc17bbd6..e73a685f 100644 --- a/apps/baseline/tests/test_viewsets.py +++ b/apps/baseline/tests/test_viewsets.py @@ -205,19 +205,6 @@ def test_delete_requires_authentication(self): logging.disable(logging.NOTSET) self.assertEqual(response.status_code, 403) - def test_patch(self): - self.client.force_login(self.user) - new_value = f"{self.client.get(self.url_get(1)).json()['name']} - updated" - logging.disable(logging.CRITICAL) - response = self.client.patch(self.url_get(0), {"name": new_value}) - logging.disable(logging.NOTSET) - self.assertEqual(response.status_code, 200) - response = self.client.get(self.url_get(0)) - self.assertEqual(response.status_code, 200) - self.assertIsInstance(response.json(), dict) - self.assertIn("name", response.json()) - self.assertEqual(response.json()["name"], new_value) - def test_list_returns_all_records(self): response = self.client.get(self.url) self.assertEqual(response.status_code, 200) @@ -298,6 +285,8 @@ def test_get_record(self): self.assertIsInstance(response.json(), dict) expected_fields = ( "id", + "name", + "description", "source_organization", "source_organization_name", "livelihood_zone", diff --git a/apps/baseline/viewsets.py b/apps/baseline/viewsets.py index b9bd39bf..2424f3bd 100644 --- a/apps/baseline/viewsets.py +++ b/apps/baseline/viewsets.py @@ -2,6 +2,8 @@ from django_filters import rest_framework as filters from rest_framework import viewsets +from common.fields import translation_fields + from .models import ( BaselineLivelihoodActivity, BaselineWealthGroup, @@ -106,12 +108,12 @@ class SourceOrganizationViewSet(viewsets.ModelViewSet): class LivelihoodZoneFilterSet(filters.FilterSet): class Meta: model = LivelihoodZone - fields = [ + fields = ( "code", - "name", - "description", + *translation_fields("description"), + *translation_fields("name"), "country", - ] + ) class LivelihoodZoneViewSet(viewsets.ModelViewSet): @@ -124,17 +126,17 @@ class LivelihoodZoneViewSet(viewsets.ModelViewSet): ) serializer_class = LivelihoodZoneSerializer filterset_class = LivelihoodZoneFilterSet - search_fields = [ + search_fields = ( "code", - "description", - "name", - ] + *translation_fields("description"), + *translation_fields("name"), + ) class LivelihoodZoneBaselineFilterSet(filters.FilterSet): class Meta: model = LivelihoodZoneBaseline - fields = [ + fields = ( "livelihood_zone", "main_livelihood_category", "source_organization", @@ -144,7 +146,9 @@ class Meta: "valid_to_date", "population_source", "population_estimate", - ] + *translation_fields("description"), + *translation_fields("name"), + ) class LivelihoodZoneBaselineViewSet(viewsets.ModelViewSet): @@ -158,9 +162,11 @@ class LivelihoodZoneBaselineViewSet(viewsets.ModelViewSet): ) serializer_class = LivelihoodZoneBaselineSerializer filterset_class = LivelihoodZoneBaselineFilterSet - search_fields = [ + search_fields = ( + *translation_fields("description"), + *translation_fields("name"), "population_source", - ] + ) class LivelihoodProductCategoryFilterSet(filters.FilterSet): diff --git a/apps/common/admin.py b/apps/common/admin.py index 01f81d0d..9f755e72 100644 --- a/apps/common/admin.py +++ b/apps/common/admin.py @@ -5,6 +5,7 @@ from treebeard.admin import TreeAdmin from treebeard.forms import movenodeform_factory +from .fields import translation_fields from .models import ( ClassifiedProduct, Country, @@ -36,13 +37,9 @@ class CountryClassifiedProductAliasesInline(InlineModelAdmin): class ClassifiedProductAdmin(TreeAdmin): form = movenodeform_factory(ClassifiedProduct) - fields = [ + fields = ( "cpcv2", - "common_name_en", - "common_name_pt", - "common_name_ar", - "common_name_es", - "common_name_fr", + *translation_fields("common_name"), "scientific_name", "unit_of_measure", "kcals_per_unit", @@ -50,26 +47,14 @@ class ClassifiedProductAdmin(TreeAdmin): "hs2012", "_position", "_ref_node_id", - "description_en", - "description_pt", - "description_ar", - "description_es", - "description_fr", - ] + *translation_fields("description"), + ) list_display = ("cpcv2", "description", "common_name", "scientific_name") ordering = ["cpcv2"] - search_fields = [ + search_fields = ( "^cpcv2", - "description_en", - "description_pt", - "description_ar", - "description_es", - "description_fr", - "common_name_en", - "common_name_pt", - "common_name_ar", - "common_name_es", - "common_name_fr", + *translation_fields("description"), + *translation_fields("common_name"), "scientific_name", "per_country_aliases__country__iso_en_ro_name", "per_country_aliases__country__iso_en_name", @@ -78,7 +63,7 @@ class ClassifiedProductAdmin(TreeAdmin): "per_country_aliases__country__iso_fr_name", "per_country_aliases__country__iso_fr_proper", "per_country_aliases__country__iso_es_name", - ] + ) class UnitOfMeasureConversionInline(admin.TabularInline): @@ -92,14 +77,10 @@ class UnitOfMeasureAdmin(admin.ModelAdmin): "abbreviation", "description", ) - search_fields = [ + search_fields = ( "abbreviation", - "description_en", - "description_fr", - "description_ar", - "description_es", - "description_pt", - ] + *translation_fields("description"), + ) list_filter = ("unit_type",) inlines = [UnitOfMeasureConversionInline] diff --git a/apps/common/fields.py b/apps/common/fields.py index 68ebe1d1..8c3e0408 100644 --- a/apps/common/fields.py +++ b/apps/common/fields.py @@ -163,6 +163,13 @@ def contribute_to_class(self, cls, name, private_only=False): model_field_name = f"{name}_{language_code}" field = self.field.clone() field.verbose_name = format_lazy("{} ({})", self.field.verbose_name, language_name) + if language_code != settings.LANGUAGE_CODE: + # If a field is unique or a primary key, apply that only on the default language + field._unique = False + field.primary_key = False + # If a field is non-blank, apply that restriction only on the default language + if getattr(field, "blank", None) is False: + field.blank = True field.contribute_to_class(cls=cls, name=model_field_name, private_only=private_only) # Add property that returns local translation, eg, obj.name == "Nome português" @@ -177,3 +184,7 @@ def local_translation_getter(obj): local_translation_getter.short_description = _(self.field.verbose_name) setattr(cls, name, property(local_translation_getter)) + + +def translation_fields(base_fieldname): + return (f"{base_fieldname}_{code}" for code, name in settings.LANGUAGES) diff --git a/apps/common/lookups.py b/apps/common/lookups.py index 558e243d..de703772 100644 --- a/apps/common/lookups.py +++ b/apps/common/lookups.py @@ -6,6 +6,7 @@ import pandas as pd +from .fields import translation_fields from .models import ( ClassifiedProduct, Country, @@ -348,20 +349,12 @@ class ClassifiedProductLookup(Lookup): # HEA, then pass `filters={"numchild": 0}` when instantiating the Lookup. model = ClassifiedProduct id_fields = ["cpcv2"] - lookup_fields = [ - "common_name_en", - "common_name_pt", - "common_name_ar", - "common_name_es", - "common_name_fr", - "description_en", - "description_pt", - "description_ar", - "description_es", - "description_fr", + lookup_fields = ( + *translation_fields("common_name"), + *translation_fields("description"), "aliases", "hs2012", - ] + ) def get_lookup_df(self): """ @@ -386,11 +379,7 @@ def get_lookup_df(self): class UnitOfMeasureLookup(Lookup): model = UnitOfMeasure id_fields = ["abbreviation"] - lookup_fields = [ - "description_en", - "description_fr", - "description_es", - "description_ar", - "description_pt", + lookup_fields = ( + *translation_fields("description"), "aliases", - ] + ) diff --git a/apps/common/migrations/0007_add_translation_fields.py b/apps/common/migrations/0007_add_translation_fields.py index a8d9aee7..6a236d9b 100644 --- a/apps/common/migrations/0007_add_translation_fields.py +++ b/apps/common/migrations/0007_add_translation_fields.py @@ -25,11 +25,6 @@ class Migration(migrations.Migration): old_name="description", new_name="description_en", ), - migrations.AlterField( - model_name="classifiedproduct", - name="description_en", - field=models.CharField(blank=True, max_length=800, verbose_name="description"), - ), migrations.AddField( model_name="classifiedproduct", name="common_name_ar", @@ -53,26 +48,22 @@ class Migration(migrations.Migration): migrations.AddField( model_name="classifiedproduct", name="description_ar", - field=models.CharField(blank=True, default="", max_length=800, verbose_name="description"), - preserve_default=False, + field=models.CharField(blank=True, max_length=800, verbose_name="description"), ), migrations.AddField( model_name="classifiedproduct", name="description_es", - field=models.CharField(blank=True, default="", max_length=800, verbose_name="description"), - preserve_default=False, + field=models.CharField(blank=True, max_length=800, verbose_name="description"), ), migrations.AddField( model_name="classifiedproduct", name="description_fr", - field=models.CharField(blank=True, default="", max_length=800, verbose_name="description"), - preserve_default=False, + field=models.CharField(blank=True, max_length=800, verbose_name="description"), ), migrations.AddField( model_name="classifiedproduct", name="description_pt", - field=models.CharField(blank=True, default="", max_length=800, verbose_name="description"), - preserve_default=False, + field=models.CharField(blank=True, max_length=800, verbose_name="description"), ), migrations.AddField( model_name="unitofmeasure", diff --git a/apps/common/models.py b/apps/common/models.py index 6a3f7288..1f0f4f21 100644 --- a/apps/common/models.py +++ b/apps/common/models.py @@ -890,7 +890,7 @@ class ClassifiedProduct(MP_Node, Model): " prefixed with R, L, P or S, a letter indicating whether the Product is Raw agricultural output," " Live animals, a Processed product or a Service.", ) - description = TranslatedField(models.CharField(blank=True, max_length=800, verbose_name=_("description"))) + description = TranslatedField(models.CharField(max_length=800, verbose_name=_("description"))) common_name = TranslatedField(NameField(blank=True, verbose_name=_("common name"))) aliases = models.JSONField( blank=True, diff --git a/apps/common/tests/test_models.py b/apps/common/tests/test_models.py index fe930c14..40c74aec 100644 --- a/apps/common/tests/test_models.py +++ b/apps/common/tests/test_models.py @@ -1,6 +1,52 @@ +from django.conf import settings from django.test import TestCase +from django.utils import translation +from common.tests.factories import ClassifiedProductFactory -class NonOverlappingMixinTestCase(TestCase): - def test_nothing(self): - pass + +class TranslatedFieldsTestCase(TestCase): + @classmethod + def setUpTestData(cls): + cls.product = ClassifiedProductFactory( + common_name_en="common_name en", + common_name_fr="common_name fr", + common_name_ar="common_name ar", + common_name_es="common_name es", + common_name_pt="common_name pt", + description_en="description en", + description_fr="description fr", + description_ar="description ar", + description_es="description es", + description_pt="description pt", + ) + + def test_translated_fields(self): + self.assertEqual(self.product.common_name_en, "common_name en") + self.assertEqual(self.product.description_en, "description en") + # Check properties return default language + self.assertEqual(self.product.common_name, "common_name en") + self.assertEqual(self.product.description, "description en") + self.assertEqual(self.product.common_name, self.product.common_name_en) + self.assertEqual(self.product.description, self.product.description_en) + # Check properties return non-default language when selected + translation.activate("pt") + self.assertEqual(self.product.common_name_pt, "common_name pt") + self.assertEqual(self.product.description_pt, "description pt") + self.assertEqual(self.product.common_name, "common_name pt") + self.assertEqual(self.product.description, "description pt") + self.assertEqual(self.product.common_name, self.product.common_name_pt) + self.assertEqual(self.product.description, self.product.description_pt) + for code, name in settings.LANGUAGES: + with self.subTest(language=code): + translation.activate(code) + wrong_code = "es" if code == "pt" else "pt" + # Check model returns translated value + self.assertEqual(self.product.common_name, getattr(self.product, f"common_name_{code}")) + self.assertEqual(self.product.description, getattr(self.product, f"description_{code}")) + self.assertNotEqual(self.product.common_name, getattr(self.product, f"common_name_{wrong_code}")) + self.assertNotEqual(self.product.description, getattr(self.product, f"description_{wrong_code}")) + + def tearDown(self): + # Ref: https://docs.djangoproject.com/en/4.2/topics/testing/tools/#setting-the-language + translation.activate(settings.LANGUAGE_CODE) diff --git a/apps/common/viewsets.py b/apps/common/viewsets.py index a6e554de..1439f5c6 100644 --- a/apps/common/viewsets.py +++ b/apps/common/viewsets.py @@ -3,6 +3,7 @@ from django_filters import rest_framework as filters from rest_framework import viewsets +from .fields import translation_fields from .filters import MultiFieldFilter from .models import ClassifiedProduct, Country, Currency, UnitOfMeasure from .serializers import ( @@ -180,15 +181,11 @@ class Meta: """ model = UnitOfMeasure - fields = [ + fields = ( "abbreviation", - "description_en", - "description_fr", - "description_es", - "description_ar", - "description_pt", + *translation_fields("description"), "unit_type", - ] + ) class UnitOfMeasureViewSet(viewsets.ModelViewSet): @@ -205,15 +202,11 @@ class UnitOfMeasureViewSet(viewsets.ModelViewSet): queryset = UnitOfMeasure.objects.all() serializer_class = UnitOfMeasureSerializer filterset_class = UnitOfMeasureFilterSet - search_fields = [ + search_fields = ( "abbreviation", - "description_en", - "description_fr", - "description_es", - "description_ar", - "description_pt", + *translation_fields("description"), "unit_type", - ] + ) class ClassifiedProductFilterSet(filters.FilterSet): @@ -275,22 +268,14 @@ class Meta: """ model = ClassifiedProduct - fields = [ + fields = ( "cpcv2", - "description_en", - "description_fr", - "description_es", - "description_ar", - "description_pt", - "common_name_en", - "common_name_fr", - "common_name_es", - "common_name_ar", - "common_name_pt", + *translation_fields("description"), + *translation_fields("common_name"), "scientific_name", "unit_of_measure", "kcals_per_unit", - ] + ) class ClassifiedProductViewSet(viewsets.ModelViewSet): @@ -307,16 +292,8 @@ class ClassifiedProductViewSet(viewsets.ModelViewSet): queryset = ClassifiedProduct.objects.all() serializer_class = ClassifiedProductSerializer filterset_class = ClassifiedProductFilterSet - search_fields = [ + search_fields = ( "cpcv2", - "description_en", - "description_pt", - "description_ar", - "description_es", - "description_fr", - "common_name_en", - "common_name_pt", - "common_name_ar", - "common_name_es", - "common_name_fr", - ] + *translation_fields("description"), + *translation_fields("common_name"), + ) diff --git a/apps/metadata/admin.py b/apps/metadata/admin.py index f924e495..56417c40 100644 --- a/apps/metadata/admin.py +++ b/apps/metadata/admin.py @@ -1,6 +1,8 @@ from django.contrib import admin from django.contrib.admin import RelatedFieldListFilter +from common.fields import translation_fields + from .models import ( HazardCategory, LivelihoodCategory, @@ -13,36 +15,20 @@ class ReferenceDataAdmin(admin.ModelAdmin): - fields = [ + fields = ( "code", - "name_en", - "name_pt", - "name_ar", - "name_es", - "name_fr", - "description_en", - "description_pt", - "description_ar", - "description_es", - "description_fr", + *translation_fields("name"), + *translation_fields("description"), "aliases", - ] + ) list_display = ( "code", "name", "description", ) search_fields = ( - "name_en", - "name_pt", - "name_ar", - "name_es", - "name_fr", - "description_en", - "description_pt", - "description_ar", - "description_es", - "description_fr", + *translation_fields("name"), + *translation_fields("description"), "aliases", ) @@ -60,17 +46,9 @@ class SeasonalActivityTypeAdmin(ReferenceDataAdmin): fields = ( "code", - "name_en", - "name_pt", - "name_ar", - "name_es", - "name_fr", + *translation_fields("name"), "activity_category", - "description_en", - "description_pt", - "description_ar", - "description_es", - "description_fr", + *translation_fields("description"), "aliases", ) @@ -88,20 +66,11 @@ class WealthCharacteristicAdmin(ReferenceDataAdmin): fields = ( "code", - "name_en", - "name_pt", - "name_ar", - "name_es", - "name_fr", + *translation_fields("name"), "variable_type", - "description_en", - "description_pt", - "description_ar", - "description_es", - "description_fr", + *translation_fields("description"), "aliases", ) - list_filter = ("variable_type",) @@ -118,16 +87,8 @@ class SeasonAdmin(admin.ModelAdmin): fields = ( "country", - "name_en", - "name_pt", - "name_ar", - "name_es", - "name_fr", - "description_en", - "description_pt", - "description_ar", - "description_es", - "description_fr", + *translation_fields("name"), + *translation_fields("description"), "season_type", "start", "end", @@ -143,11 +104,7 @@ class SeasonAdmin(admin.ModelAdmin): ) search_fields = ( "country__iso_en_ro_name", - "name_en", - "name_pt", - "name_ar", - "name_es", - "name_fr", + *translation_fields("name"), "season_type", ) list_filter = ( @@ -159,21 +116,13 @@ class SeasonAdmin(admin.ModelAdmin): class MarketAdmin(ReferenceDataAdmin): - fields = [ + fields = ( "code", - "name_en", - "name_pt", - "name_ar", - "name_es", - "name_fr", + *translation_fields("name"), "country", - "description_en", - "description_pt", - "description_ar", - "description_es", - "description_fr", + *translation_fields("description"), "aliases", - ] + ) list_filter = (("country", RelatedFieldListFilter),) diff --git a/apps/metadata/lookups.py b/apps/metadata/lookups.py index 500823fd..6cf4b2c7 100644 --- a/apps/metadata/lookups.py +++ b/apps/metadata/lookups.py @@ -2,6 +2,7 @@ Lookup classes that support data ingestion by matching data in a Pandas DataFrame against reference data in Django Models. """ +from common.fields import translation_fields from common.lookups import Lookup from .models import ( @@ -15,19 +16,11 @@ class ReferenceDataLookup(Lookup): model = ReferenceData id_fields = ["code"] - lookup_fields = [ - "name_en", - "name_fr", - "name_es", - "name_ar", - "name_pt", - "description_en", - "description_fr", - "description_es", - "description_ar", - "description_pt", + lookup_fields = ( + *translation_fields("name"), + *translation_fields("description"), "aliases", - ] + ) class LivelihoodCategoryLookup(ReferenceDataLookup): diff --git a/apps/metadata/migrations/0002_add_translation_fields.py b/apps/metadata/migrations/0002_add_translation_fields.py index f10074d4..59997545 100644 --- a/apps/metadata/migrations/0002_add_translation_fields.py +++ b/apps/metadata/migrations/0002_add_translation_fields.py @@ -128,26 +128,22 @@ class Migration(migrations.Migration): migrations.AddField( model_name="hazardcategory", name="name_ar", - field=common.fields.NameField(default="", max_length=60, verbose_name="Name"), - preserve_default=False, + field=common.fields.NameField(blank=True, max_length=60, verbose_name="Name"), ), migrations.AddField( model_name="hazardcategory", name="name_es", - field=common.fields.NameField(default="", max_length=60, verbose_name="Name"), - preserve_default=False, + field=common.fields.NameField(blank=True, max_length=60, verbose_name="Name"), ), migrations.AddField( model_name="hazardcategory", name="name_fr", - field=common.fields.NameField(default="", max_length=60, verbose_name="Name"), - preserve_default=False, + field=common.fields.NameField(blank=True, max_length=60, verbose_name="Name"), ), migrations.AddField( model_name="hazardcategory", name="name_pt", - field=common.fields.NameField(default="", max_length=60, verbose_name="Name"), - preserve_default=False, + field=common.fields.NameField(blank=True, max_length=60, verbose_name="Name"), ), migrations.AddField( model_name="livelihoodcategory", @@ -192,26 +188,22 @@ class Migration(migrations.Migration): migrations.AddField( model_name="livelihoodcategory", name="name_ar", - field=common.fields.NameField(default="", max_length=60, verbose_name="Name"), - preserve_default=False, + field=common.fields.NameField(blank=True, max_length=60, verbose_name="Name"), ), migrations.AddField( model_name="livelihoodcategory", name="name_es", - field=common.fields.NameField(default="", max_length=60, verbose_name="Name"), - preserve_default=False, + field=common.fields.NameField(blank=True, max_length=60, verbose_name="Name"), ), migrations.AddField( model_name="livelihoodcategory", name="name_fr", - field=common.fields.NameField(default="", max_length=60, verbose_name="Name"), - preserve_default=False, + field=common.fields.NameField(blank=True, max_length=60, verbose_name="Name"), ), migrations.AddField( model_name="livelihoodcategory", name="name_pt", - field=common.fields.NameField(default="", max_length=60, verbose_name="Name"), - preserve_default=False, + field=common.fields.NameField(blank=True, max_length=60, verbose_name="Name"), ), migrations.AddField( model_name="market", @@ -256,98 +248,82 @@ class Migration(migrations.Migration): migrations.AddField( model_name="market", name="full_name_ar", - field=common.fields.NameField(default="", max_length=200, unique=True, verbose_name="full name"), - preserve_default=False, + field=common.fields.NameField(blank=True, max_length=200, verbose_name="full name"), ), migrations.AddField( model_name="market", name="full_name_es", - field=common.fields.NameField(default="", max_length=200, unique=True, verbose_name="full name"), - preserve_default=False, + field=common.fields.NameField(blank=True, max_length=200, verbose_name="full name"), ), migrations.AddField( model_name="market", name="full_name_fr", - field=common.fields.NameField(default="", max_length=200, unique=True, verbose_name="full name"), - preserve_default=False, + field=common.fields.NameField(blank=True, max_length=200, verbose_name="full name"), ), migrations.AddField( model_name="market", name="full_name_pt", - field=common.fields.NameField(default="", max_length=200, unique=True, verbose_name="full name"), - preserve_default=False, + field=common.fields.NameField(blank=True, max_length=200, verbose_name="full name"), ), migrations.AddField( model_name="market", name="name_ar", - field=common.fields.NameField(default="", max_length=250, verbose_name="Name"), - preserve_default=False, + field=common.fields.NameField(blank=True, max_length=250, verbose_name="Name"), ), migrations.AddField( model_name="market", name="name_es", - field=common.fields.NameField(default="", max_length=250, verbose_name="Name"), - preserve_default=False, + field=common.fields.NameField(blank=True, max_length=250, verbose_name="Name"), ), migrations.AddField( model_name="market", name="name_fr", - field=common.fields.NameField(default="", max_length=250, verbose_name="Name"), - preserve_default=False, + field=common.fields.NameField(blank=True, max_length=250, verbose_name="Name"), ), migrations.AddField( model_name="market", name="name_pt", - field=common.fields.NameField(default="", max_length=250, verbose_name="Name"), - preserve_default=False, + field=common.fields.NameField(blank=True, max_length=250, verbose_name="Name"), ), migrations.AddField( model_name="season", name="description_ar", - field=models.TextField(default="", max_length=255, verbose_name="Description"), - preserve_default=False, + field=models.TextField(blank=True, max_length=255, verbose_name="Description"), ), migrations.AddField( model_name="season", name="description_es", - field=models.TextField(default="", max_length=255, verbose_name="Description"), - preserve_default=False, + field=models.TextField(blank=True, max_length=255, verbose_name="Description"), ), migrations.AddField( model_name="season", name="description_fr", - field=models.TextField(default="", max_length=255, verbose_name="Description"), - preserve_default=False, + field=models.TextField(blank=True, max_length=255, verbose_name="Description"), ), migrations.AddField( model_name="season", name="description_pt", - field=models.TextField(default="", max_length=255, verbose_name="Description"), - preserve_default=False, + field=models.TextField(blank=True, max_length=255, verbose_name="Description"), ), migrations.AddField( model_name="season", name="name_ar", - field=models.CharField(default="", max_length=50, verbose_name="Name"), - preserve_default=False, + field=models.CharField(blank=True, max_length=50, verbose_name="Name"), ), migrations.AddField( model_name="season", name="name_es", - field=models.CharField(default="", max_length=50, verbose_name="Name"), - preserve_default=False, + field=models.CharField(blank=True, max_length=50, verbose_name="Name"), ), migrations.AddField( model_name="season", name="name_fr", - field=models.CharField(default="", max_length=50, verbose_name="Name"), - preserve_default=False, + field=models.CharField(blank=True, max_length=50, verbose_name="Name"), ), migrations.AddField( model_name="season", name="name_pt", - field=models.CharField(default="", max_length=50, verbose_name="Name"), - preserve_default=False, + field=models.CharField(blank=True, max_length=50, verbose_name="Name"), ), migrations.AddField( model_name="seasonalactivitytype", @@ -392,26 +368,22 @@ class Migration(migrations.Migration): migrations.AddField( model_name="seasonalactivitytype", name="name_ar", - field=common.fields.NameField(default="", max_length=60, verbose_name="Name"), - preserve_default=False, + field=common.fields.NameField(blank=True, max_length=60, verbose_name="Name"), ), migrations.AddField( model_name="seasonalactivitytype", name="name_es", - field=common.fields.NameField(default="", max_length=60, verbose_name="Name"), - preserve_default=False, + field=common.fields.NameField(blank=True, max_length=60, verbose_name="Name"), ), migrations.AddField( model_name="seasonalactivitytype", name="name_fr", - field=common.fields.NameField(default="", max_length=60, verbose_name="Name"), - preserve_default=False, + field=common.fields.NameField(blank=True, max_length=60, verbose_name="Name"), ), migrations.AddField( model_name="seasonalactivitytype", name="name_pt", - field=common.fields.NameField(default="", max_length=60, verbose_name="Name"), - preserve_default=False, + field=common.fields.NameField(blank=True, max_length=60, verbose_name="Name"), ), migrations.AddField( model_name="wealthcharacteristic", @@ -456,26 +428,22 @@ class Migration(migrations.Migration): migrations.AddField( model_name="wealthcharacteristic", name="name_ar", - field=common.fields.NameField(default="", max_length=60, verbose_name="Name"), - preserve_default=False, + field=common.fields.NameField(blank=True, max_length=60, verbose_name="Name"), ), migrations.AddField( model_name="wealthcharacteristic", name="name_es", - field=common.fields.NameField(default="", max_length=60, verbose_name="Name"), - preserve_default=False, + field=common.fields.NameField(blank=True, max_length=60, verbose_name="Name"), ), migrations.AddField( model_name="wealthcharacteristic", name="name_fr", - field=common.fields.NameField(default="", max_length=60, verbose_name="Name"), - preserve_default=False, + field=common.fields.NameField(blank=True, max_length=60, verbose_name="Name"), ), migrations.AddField( model_name="wealthcharacteristic", name="name_pt", - field=common.fields.NameField(default="", max_length=60, verbose_name="Name"), - preserve_default=False, + field=common.fields.NameField(blank=True, max_length=60, verbose_name="Name"), ), migrations.AddField( model_name="wealthgroupcategory", @@ -520,25 +488,21 @@ class Migration(migrations.Migration): migrations.AddField( model_name="wealthgroupcategory", name="name_ar", - field=common.fields.NameField(default="", max_length=60, verbose_name="Name"), - preserve_default=False, + field=common.fields.NameField(blank=True, max_length=60, verbose_name="Name"), ), migrations.AddField( model_name="wealthgroupcategory", name="name_es", - field=common.fields.NameField(default="", max_length=60, verbose_name="Name"), - preserve_default=False, + field=common.fields.NameField(blank=True, max_length=60, verbose_name="Name"), ), migrations.AddField( model_name="wealthgroupcategory", name="name_fr", - field=common.fields.NameField(default="", max_length=60, verbose_name="Name"), - preserve_default=False, + field=common.fields.NameField(blank=True, max_length=60, verbose_name="Name"), ), migrations.AddField( model_name="wealthgroupcategory", name="name_pt", - field=common.fields.NameField(default="", max_length=60, verbose_name="Name"), - preserve_default=False, + field=common.fields.NameField(blank=True, max_length=60, verbose_name="Name"), ), ] diff --git a/apps/metadata/viewsets.py b/apps/metadata/viewsets.py index e8441b5d..83e0bb9b 100644 --- a/apps/metadata/viewsets.py +++ b/apps/metadata/viewsets.py @@ -3,6 +3,7 @@ from django_filters import rest_framework as filters from rest_framework import viewsets +from common.fields import translation_fields from common.models import Country from metadata.models import ( HazardCategory, @@ -48,37 +49,21 @@ class ReferenceDataFilterSet(filters.FilterSet): class Meta: model = ReferenceData - fields = [ - "name_en", - "name_pt", - "name_es", - "name_fr", - "name_ar", - "description_en", - "description_pt", - "description_es", - "description_fr", - "description_ar", - ] + fields = ( + *translation_fields("name"), + *translation_fields("description"), + ) class ReferenceDataViewSet(viewsets.ModelViewSet): serializer_class = ReferenceDataSerializer filterset_class = ReferenceDataFilterSet - search_fields = [ + search_fields = ( "code", - "name_en", - "name_pt", - "name_es", - "name_fr", - "name_ar", - "description_en", - "description_pt", - "description_es", - "description_fr", - "description_ar", + *translation_fields("name"), + *translation_fields("description"), "aliases", - ] + ) class LivelihoodCategoryViewSet(ReferenceDataViewSet): @@ -98,40 +83,24 @@ class WealthCharacteristicFilterSet(ReferenceDataFilterSet): class Meta: model = ReferenceData - fields = [ - "name_en", - "name_pt", - "name_es", - "name_fr", - "name_ar", - "description_en", - "description_pt", - "description_es", - "description_fr", - "description_ar", + fields = ( + *translation_fields("name"), + *translation_fields("description"), "variable_type", - ] + ) class WealthCharacteristicViewSet(ReferenceDataViewSet): queryset = WealthCharacteristic.objects.all() serializer_class = WealthCharacteristicSerializer filterset_class = WealthCharacteristicFilterSet - search_fields = [ + search_fields = ( "code", - "name_en", - "name_pt", - "name_es", - "name_fr", - "name_ar", - "description_en", - "description_pt", - "description_es", - "description_fr", - "description_ar", + *translation_fields("name"), + *translation_fields("description"), "variable_type", "aliases", - ] + ) class SeasonalActivityTypeFilterSet(ReferenceDataFilterSet): @@ -141,40 +110,24 @@ class SeasonalActivityTypeFilterSet(ReferenceDataFilterSet): class Meta: model = ReferenceData - fields = [ - "name_en", - "name_pt", - "name_ar", - "name_fr", - "name_es", - "description_en", - "description_pt", - "description_ar", - "description_fr", - "description_es", + fields = ( + *translation_fields("name"), + *translation_fields("description"), "activity_category", - ] + ) class SeasonalActivityTypeViewSet(ReferenceDataViewSet): queryset = SeasonalActivityType.objects.all() serializer_class = SeasonalActivityTypeSerializer filterset_class = SeasonalActivityTypeFilterSet - search_fields = [ + search_fields = ( "code", - "name_en", - "name_pt", - "name_es", - "name_fr", - "name_ar", - "description_en", - "description_pt", - "description_es", - "description_fr", - "description_ar", + *translation_fields("name"), + *translation_fields("description"), "activity_category", "aliases", - ] + ) class HazardCategoryViewSet(ReferenceDataViewSet): @@ -222,16 +175,8 @@ def __init__(self, *args, **kwargs): class Meta: model = Season fields = ( - "name_en", - "name_pt", - "name_es", - "name_fr", - "name_ar", - "description_en", - "description_pt", - "description_es", - "description_fr", - "description_ar", + *translation_fields("name"), + *translation_fields("description"), "season_type", ) @@ -240,16 +185,8 @@ class SeasonViewSet(viewsets.ModelViewSet): queryset = Season.objects.all() serializer_class = SeasonSerializer filterset_class = SeasonFilterSet - search_fields = [ - "name_en", - "name_pt", - "name_es", - "name_fr", - "name_ar", - "description_en", - "description_pt", - "description_es", - "description_fr", - "description_ar", + search_fields = ( + *translation_fields("name"), + *translation_fields("description"), "season_type", - ] + ) diff --git a/hea/settings/base.py b/hea/settings/base.py index 0af0c697..cd5cd8f8 100644 --- a/hea/settings/base.py +++ b/hea/settings/base.py @@ -183,8 +183,8 @@ ("en", _("English")), ("fr", _("French")), ("es", _("Spanish")), - ("ar", _("Arabic")), ("pt", _("Portuguese")), + ("ar", _("Arabic")), ) DEFAULT_AUTO_FIELD = "django.db.models.AutoField"