feat: Add general field-based status and migrate data
This commit is contained in:
@@ -173,7 +173,6 @@ admin.site.register(Image)
|
|||||||
admin.site.register(ModerationAction)
|
admin.site.register(ModerationAction)
|
||||||
admin.site.register(Language)
|
admin.site.register(Language)
|
||||||
admin.site.register(Announcement)
|
admin.site.register(Announcement)
|
||||||
admin.site.register(AdoptionNoticeStatus)
|
|
||||||
admin.site.register(Subscriptions)
|
admin.site.register(Subscriptions)
|
||||||
admin.site.register(Log)
|
admin.site.register(Log)
|
||||||
admin.site.register(Timestamp)
|
admin.site.register(Timestamp)
|
||||||
|
@@ -5,7 +5,7 @@ from fellchensammlung.api.serializers import LocationSerializer, AdoptionNoticeG
|
|||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from fellchensammlung.models import AdoptionNotice, Animal, Log, TrustLevel, Location, AdoptionNoticeStatus
|
from fellchensammlung.models import Log, TrustLevel, Location, AdoptionNoticeStatusChoices
|
||||||
from fellchensammlung.tasks import post_adoption_notice_save, post_rescue_org_save
|
from fellchensammlung.tasks import post_adoption_notice_save, post_rescue_org_save
|
||||||
from rest_framework import status, serializers
|
from rest_framework import status, serializers
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
@@ -374,7 +374,7 @@ class LocationApiView(APIView):
|
|||||||
|
|
||||||
class AdoptionNoticeGeoJSONView(ListAPIView):
|
class AdoptionNoticeGeoJSONView(ListAPIView):
|
||||||
queryset = AdoptionNotice.objects.select_related('location').filter(location__isnull=False).filter(
|
queryset = AdoptionNotice.objects.select_related('location').filter(location__isnull=False).filter(
|
||||||
adoptionnoticestatus__major_status=AdoptionNoticeStatus.ACTIVE)
|
adoption_notice_status__in=AdoptionNoticeStatusChoices.Active.choices)
|
||||||
serializer_class = AdoptionNoticeGeoJSONSerializer
|
serializer_class = AdoptionNoticeGeoJSONSerializer
|
||||||
renderer_classes = [GeoJSONRenderer]
|
renderer_classes = [GeoJSONRenderer]
|
||||||
|
|
||||||
|
@@ -0,0 +1,87 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-08-30 21:49
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('fellchensammlung', '0059_rescueorganization_twenty_id_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='adoptionnotice',
|
||||||
|
options={'permissions': [('create_active_adoption_notice', 'Can create an active adoption notice')], 'verbose_name': 'Vermittlung', 'verbose_name_plural': 'Vermittlungen'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='adoptionnoticestatus',
|
||||||
|
options={'verbose_name': 'Vermittlungsstatus', 'verbose_name_plural': 'Vermittlungsstati'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='animal',
|
||||||
|
options={'verbose_name': 'Tier', 'verbose_name_plural': 'Tiere'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='announcement',
|
||||||
|
options={'verbose_name': 'Banner', 'verbose_name_plural': 'Banner'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='comment',
|
||||||
|
options={'verbose_name': 'Kommentar', 'verbose_name_plural': 'Kommentare'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='image',
|
||||||
|
options={'verbose_name': 'Bild', 'verbose_name_plural': 'Bilder'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='importantlocation',
|
||||||
|
options={'verbose_name': 'Wichtiger Standort', 'verbose_name_plural': 'Wichtige Standorte'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='location',
|
||||||
|
options={'verbose_name': 'Standort', 'verbose_name_plural': 'Standorte'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='moderationaction',
|
||||||
|
options={'verbose_name': 'Moderationsaktion', 'verbose_name_plural': 'Moderationsaktionen'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='notification',
|
||||||
|
options={'verbose_name': 'Benachrichtigung', 'verbose_name_plural': 'Benachrichtigungen'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='report',
|
||||||
|
options={'verbose_name': 'Meldung', 'verbose_name_plural': 'Meldungen'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='rescueorganization',
|
||||||
|
options={'ordering': ['name'], 'verbose_name': 'Tierschutzorganisation', 'verbose_name_plural': 'Tierschutzorganisationen'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='rule',
|
||||||
|
options={'verbose_name': 'Regel', 'verbose_name_plural': 'Regeln'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='searchsubscription',
|
||||||
|
options={'verbose_name': 'Abonnierte Suche', 'verbose_name_plural': 'Abonnierte Suchen'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='speciesspecificurl',
|
||||||
|
options={'verbose_name': 'Tierartspezifische URL', 'verbose_name_plural': 'Tierartspezifische URLs'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='subscriptions',
|
||||||
|
options={'verbose_name': 'Abonnement', 'verbose_name_plural': 'Abonnements'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='timestamp',
|
||||||
|
options={'verbose_name': 'Zeitstempel', 'verbose_name_plural': 'Zeitstempel'},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='adoptionnotice',
|
||||||
|
name='adoption_notice_status',
|
||||||
|
field=models.TextField(choices=[('active_searching', 'Searching'), ('active_interested', 'Interested'), ('awaiting_action_waiting_for_review', 'Waiting for review'), ('awaiting_action_needs_additional_info', 'Needs additional info'), ('closed_successful_with_notfellchen', 'Successful (with Notfellchen)'), ('closed_successful_without_notfellchen', 'Successful (without Notfellchen)'), ('closed_animal_died', 'Animal died'), ('closed_for_other_adoption_notice', 'Closed for other adoption notice'), ('closed_not_open_for_adoption_anymore', 'Not open for adoption anymore'), ('closed_other', 'Other (closed)'), ('disabled_against_the_rules', 'Against the rules'), ('disabled_unchecked', 'Unchecked'), ('disabled_other', 'Other (disabled)')], default='disabled_other', max_length=64, verbose_name='Status'),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
@@ -0,0 +1,63 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-08-30 21:51
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def map_status(adoption_notice_status):
|
||||||
|
minor = adoption_notice_status.minor_status
|
||||||
|
|
||||||
|
if minor == "searching":
|
||||||
|
return "active_searching"
|
||||||
|
if minor == "interested":
|
||||||
|
return "active_interested"
|
||||||
|
|
||||||
|
if minor == "waiting_for_review":
|
||||||
|
return "awaiting_action_waiting_for_review"
|
||||||
|
if minor == "needs_additional_info":
|
||||||
|
return "awaiting_action_needs_additional_info"
|
||||||
|
|
||||||
|
if minor == "successful_with_notfellchen":
|
||||||
|
return "closed_successful_with_notfellchen"
|
||||||
|
if minor == "successful_without_notfellchen":
|
||||||
|
return "closed_successful_without_notfellchen"
|
||||||
|
if minor == "animal_died":
|
||||||
|
return "closed_animal_died"
|
||||||
|
if minor == "closed_for_other_adoption_notice":
|
||||||
|
return "closed_for_other_adoption_notice"
|
||||||
|
if minor == "not_open_for_adoption_anymore":
|
||||||
|
return "closed_not_open_for_adoption_anymore"
|
||||||
|
if minor == "other":
|
||||||
|
return "closed_other"
|
||||||
|
|
||||||
|
if minor == "against_the_rules":
|
||||||
|
return "disabled_against_the_rules"
|
||||||
|
if minor == "unchecked":
|
||||||
|
return "disabled_unchecked"
|
||||||
|
if minor in ["missing_information", "technical_error"]:
|
||||||
|
return "disabled_other"
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_status(apps, schema_editor):
|
||||||
|
# We can't import the model directly as it may be a newer
|
||||||
|
# version than this migration expects. We use the historical version.
|
||||||
|
AdoptionNoticeStatus = apps.get_model("fellchensammlung", "AdoptionNoticeStatus")
|
||||||
|
AdoptionNotice = apps.get_model("fellchensammlung", "AdoptionNotice")
|
||||||
|
for ans in AdoptionNoticeStatus.objects.all():
|
||||||
|
adoption_notice = AdoptionNotice.objects.get(id=ans.adoption_notice.id)
|
||||||
|
new_status = map_status(ans)
|
||||||
|
logging.debug(f"{ans.minor_status} -> {new_status}")
|
||||||
|
adoption_notice.adoption_notice_status = map_status(ans)
|
||||||
|
adoption_notice.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('fellchensammlung', '0060_alter_adoptionnotice_options_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(migrate_status),
|
||||||
|
]
|
@@ -12,12 +12,13 @@ from django.db.models.signals import post_save
|
|||||||
from django.contrib.auth.models import Group
|
from django.contrib.auth.models import Group
|
||||||
from django.contrib.auth.models import AbstractUser
|
from django.contrib.auth.models import AbstractUser
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
from sphinx.ext.inheritance_diagram import module_sig_re
|
||||||
|
|
||||||
from .tools import misc, geo
|
from .tools import misc, geo
|
||||||
from notfellchen.settings import MEDIA_URL, base_url
|
from notfellchen.settings import MEDIA_URL, base_url
|
||||||
from .tools.geo import LocationProxy, Position
|
from .tools.geo import LocationProxy, Position
|
||||||
from .tools.misc import age_as_hr_string, time_since_as_hr_string
|
from .tools.misc import age_as_hr_string, time_since_as_hr_string
|
||||||
from .tools.model_helpers import NotificationTypeChoices
|
from .tools.model_helpers import NotificationTypeChoices, AdoptionNoticeStatusChoices
|
||||||
from .tools.model_helpers import ndm as NotificationDisplayMapping
|
from .tools.model_helpers import ndm as NotificationDisplayMapping
|
||||||
|
|
||||||
|
|
||||||
@@ -395,6 +396,8 @@ class AdoptionNotice(models.Model):
|
|||||||
location_string = models.CharField(max_length=200, verbose_name=_("Ortsangabe"))
|
location_string = models.CharField(max_length=200, verbose_name=_("Ortsangabe"))
|
||||||
location = models.ForeignKey(Location, blank=True, null=True, on_delete=models.SET_NULL, )
|
location = models.ForeignKey(Location, blank=True, null=True, on_delete=models.SET_NULL, )
|
||||||
owner = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=_('Creator'))
|
owner = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=_('Creator'))
|
||||||
|
adoption_notice_status = models.TextField(max_length=64, verbose_name=_('Status'),
|
||||||
|
choices=AdoptionNoticeStatusChoices.all_choices())
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def animals(self):
|
def animals(self):
|
||||||
|
@@ -10,7 +10,7 @@ from django.template.loader import render_to_string
|
|||||||
from django.core import mail
|
from django.core import mail
|
||||||
from django.utils.html import strip_tags
|
from django.utils.html import strip_tags
|
||||||
|
|
||||||
from fellchensammlung.models import AdoptionNotice, Location, RescueOrganization, AdoptionNoticeStatus, Log, \
|
from fellchensammlung.models import AdoptionNotice, Location, RescueOrganization, Log, \
|
||||||
Notification, NotificationTypeChoices
|
Notification, NotificationTypeChoices
|
||||||
from fellchensammlung.tools.misc import is_404
|
from fellchensammlung.tools.misc import is_404
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
from fellchensammlung.models import User, AdoptionNotice, AdoptionNoticeStatus
|
from fellchensammlung.models import User, AdoptionNotice, AdoptionNoticeStatusChoices
|
||||||
|
|
||||||
|
|
||||||
def gather_metrics_data():
|
def gather_metrics_data():
|
||||||
@@ -9,14 +9,14 @@ def gather_metrics_data():
|
|||||||
"""Adoption notices"""
|
"""Adoption notices"""
|
||||||
num_adoption_notices = AdoptionNotice.objects.count()
|
num_adoption_notices = AdoptionNotice.objects.count()
|
||||||
adoption_notices_active = AdoptionNotice.objects.filter(
|
adoption_notices_active = AdoptionNotice.objects.filter(
|
||||||
adoptionnoticestatus__major_status=AdoptionNoticeStatus.ACTIVE)
|
adoptionnoticestatus=AdoptionNoticeStatusChoices.all_choices()) # TODO fix
|
||||||
num_adoption_notices_active = adoption_notices_active.count()
|
num_adoption_notices_active = adoption_notices_active.count()
|
||||||
num_adoption_notices_closed = AdoptionNotice.objects.filter(
|
num_adoption_notices_closed = AdoptionNotice.objects.filter(
|
||||||
adoptionnoticestatus__major_status=AdoptionNoticeStatus.CLOSED).count()
|
adoptionnoticestatus=AdoptionNoticeStatusChoices.all_choices()) # TODO fix
|
||||||
num_adoption_notices_disabled = AdoptionNotice.objects.filter(
|
num_adoption_notices_disabled = AdoptionNotice.objects.filter(
|
||||||
adoptionnoticestatus__major_status=AdoptionNoticeStatus.DISABLED).count()
|
adoptionnoticestatus=AdoptionNoticeStatusChoices.all_choices()) # TODO fix
|
||||||
num_adoption_notices_awaiting_action = AdoptionNotice.objects.filter(
|
num_adoption_notices_awaiting_action = AdoptionNotice.objects.filter(
|
||||||
adoptionnoticestatus__major_status=AdoptionNoticeStatus.AWAITING_ACTION).count()
|
adoptionnoticestatus=AdoptionNoticeStatusChoices.all_choices()) # TODO fix
|
||||||
|
|
||||||
adoption_notices_without_location = AdoptionNotice.objects.filter(location__isnull=True).count()
|
adoption_notices_without_location = AdoptionNotice.objects.filter(location__isnull=True).count()
|
||||||
|
|
||||||
|
@@ -55,3 +55,74 @@ ndm = {NotificationTypeChoices.NEW_USER: NotificationDisplayMapping(
|
|||||||
web_partial='fellchensammlung/partials/notifications/body-an-for-search.html'
|
web_partial='fellchensammlung/partials/notifications/body-an-for-search.html'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class DescriptiveTextChoices(models.TextChoices):
|
||||||
|
class Descriptions:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_description(cls, value):
|
||||||
|
return cls.Descriptions.__getattribute__(value, "")
|
||||||
|
|
||||||
|
|
||||||
|
class AdoptionNoticeStatusChoices:
|
||||||
|
class Active(DescriptiveTextChoices):
|
||||||
|
SEARCHING = "active_searching", _("Searching")
|
||||||
|
INTERESTED = "active_interested", _("Interested")
|
||||||
|
|
||||||
|
class Descriptions:
|
||||||
|
SEARCHING = ""
|
||||||
|
INTERESTED = _("Jemand hat bereits Interesse an den Tieren.")
|
||||||
|
|
||||||
|
class AwaitingAction(DescriptiveTextChoices):
|
||||||
|
WAITING_FOR_REVIEW = "awaiting_action_waiting_for_review", _("Waiting for review")
|
||||||
|
NEEDS_ADDITIONAL_INFO = "awaiting_action_needs_additional_info", _("Needs additional info")
|
||||||
|
|
||||||
|
class Descriptions:
|
||||||
|
WAITING_FOR_REVIEW = _("Deaktiviert bis Moderator*innen die Vermittlung prüfen können.")
|
||||||
|
NEEDS_ADDITIONAL_INFO = _("Deaktiviert bis Informationen nachgetragen werden.")
|
||||||
|
|
||||||
|
class Closed(DescriptiveTextChoices):
|
||||||
|
SUCCESSFUL_WITH_NOTFELLCHEN = "closed_successful_with_notfellchen", _("Successful (with Notfellchen)")
|
||||||
|
SUCCESSFUL_WITHOUT_NOTFELLCHEN = "closed_successful_without_notfellchen", _("Successful (without Notfellchen)")
|
||||||
|
ANIMAL_DIED = "closed_animal_died", _("Animal died")
|
||||||
|
FOR_OTHER_ADOPTION_NOTICE = "closed_for_other_adoption_notice", _("Closed for other adoption notice")
|
||||||
|
NOT_OPEN_ANYMORE = "closed_not_open_for_adoption_anymore", _("Not open for adoption anymore")
|
||||||
|
OTHER = "closed_other", _("Other (closed)")
|
||||||
|
|
||||||
|
class Descriptions:
|
||||||
|
SUCCESSFUL_WITH_NOTFELLCHEN = _("Vermittlung erfolgreich abgeschlossen.")
|
||||||
|
SUCCESSFUL_WITHOUT_NOTFELLCHEN = _("Vermittlung erfolgreich abgeschlossen.")
|
||||||
|
ANIMAL_DIED = _("Die zu vermittelnden Tiere sind über die Regenbrücke gegangen.")
|
||||||
|
FOR_OTHER_ADOPTION_NOTICE = _("Vermittlung wurde zugunsten einer anderen geschlossen.")
|
||||||
|
NOT_OPEN_ANYMORE = _("Tier(e) stehen nicht mehr zur Vermittlung bereit.")
|
||||||
|
OTHER = _("Vermittlung geschlossen.")
|
||||||
|
|
||||||
|
class Disabled(DescriptiveTextChoices):
|
||||||
|
AGAINST_RULES = "disabled_against_the_rules", _("Against the rules")
|
||||||
|
UNCHECKED = "disabled_unchecked", _("Unchecked")
|
||||||
|
OTHER = "disabled_other", _("Other (disabled)")
|
||||||
|
|
||||||
|
class Descriptions:
|
||||||
|
AGAINST_RULES = _("Vermittlung deaktiviert da sie gegen die Regeln verstößt.")
|
||||||
|
UNCHECKED = _("Vermittlung deaktiviert bis sie vom Team auf Aktualität geprüft wurde.")
|
||||||
|
OTHER = _("Vermittlung deaktiviert.")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def all_choices(cls):
|
||||||
|
"""Return all subgroup choices as a single list for use in models."""
|
||||||
|
return (
|
||||||
|
cls.Active.choices
|
||||||
|
+ cls.AwaitingAction.choices
|
||||||
|
+ cls.Closed.choices
|
||||||
|
+ cls.Disabled.choices
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_description(cls, value):
|
||||||
|
"""Get description regardless of which subgroup the value belongs to."""
|
||||||
|
for subgroup in (cls.Active, cls.AwaitingAction, cls.Closed, cls.Disabled):
|
||||||
|
if value in subgroup.values:
|
||||||
|
return subgroup.get_description(value)
|
||||||
|
return ""
|
||||||
|
@@ -21,7 +21,7 @@ from notfellchen import settings
|
|||||||
|
|
||||||
from fellchensammlung import logger
|
from fellchensammlung import logger
|
||||||
from .models import AdoptionNotice, Text, Animal, Rule, Image, Report, ModerationAction, \
|
from .models import AdoptionNotice, Text, Animal, Rule, Image, Report, ModerationAction, \
|
||||||
User, Location, AdoptionNoticeStatus, Subscriptions, Notification, RescueOrganization, \
|
User, Location, Subscriptions, Notification, RescueOrganization, \
|
||||||
Species, Log, Timestamp, TrustLevel, SexChoicesWithAll, SearchSubscription, \
|
Species, Log, Timestamp, TrustLevel, SexChoicesWithAll, SearchSubscription, \
|
||||||
ImportantLocation, SpeciesSpecificURL, NotificationTypeChoices, SocialMediaPost
|
ImportantLocation, SpeciesSpecificURL, NotificationTypeChoices, SocialMediaPost
|
||||||
from .forms import AdoptionNoticeForm, ImageForm, ReportAdoptionNoticeForm, \
|
from .forms import AdoptionNoticeForm, ImageForm, ReportAdoptionNoticeForm, \
|
||||||
@@ -36,6 +36,7 @@ from .tools.admin import clean_locations, get_unchecked_adoption_notices, deacti
|
|||||||
from .tasks import post_adoption_notice_save
|
from .tasks import post_adoption_notice_save
|
||||||
from rest_framework.authtoken.models import Token
|
from rest_framework.authtoken.models import Token
|
||||||
|
|
||||||
|
from .tools.model_helpers import AdoptionNoticeStatusChoices
|
||||||
from .tools.search import AdoptionNoticeSearch, RescueOrgSearch
|
from .tools.search import AdoptionNoticeSearch, RescueOrgSearch
|
||||||
|
|
||||||
|
|
||||||
@@ -59,7 +60,7 @@ def fail_if_user_not_owner_or_trust_level(user, django_object, trust_level=Trust
|
|||||||
def index(request):
|
def index(request):
|
||||||
"""View function for home page of site."""
|
"""View function for home page of site."""
|
||||||
latest_adoption_list = AdoptionNotice.objects.filter(
|
latest_adoption_list = AdoptionNotice.objects.filter(
|
||||||
adoptionnoticestatus__major_status=AdoptionNoticeStatus.ACTIVE).order_by("-created_at")
|
adoption_notice_status__in=AdoptionNoticeStatusChoices.Active.choices).order_by("-created_at")
|
||||||
active_adoptions = [adoption for adoption in latest_adoption_list if adoption.is_active]
|
active_adoptions = [adoption for adoption in latest_adoption_list if adoption.is_active]
|
||||||
language_code = translation.get_language()
|
language_code = translation.get_language()
|
||||||
lang = Language.objects.get(languagecode=language_code)
|
lang = Language.objects.get(languagecode=language_code)
|
||||||
|
Reference in New Issue
Block a user