feat: Add general field-based status and migrate data

This commit is contained in:
2025-08-31 00:28:44 +02:00
parent 1c7d943a21
commit 70f077e393
9 changed files with 236 additions and 12 deletions

View File

@@ -173,7 +173,6 @@ admin.site.register(Image)
admin.site.register(ModerationAction)
admin.site.register(Language)
admin.site.register(Announcement)
admin.site.register(AdoptionNoticeStatus)
admin.site.register(Subscriptions)
admin.site.register(Log)
admin.site.register(Timestamp)

View File

@@ -5,7 +5,7 @@ from fellchensammlung.api.serializers import LocationSerializer, AdoptionNoticeG
from rest_framework.views import APIView
from rest_framework.response import Response
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 rest_framework import status, serializers
from rest_framework.permissions import IsAuthenticated
@@ -374,7 +374,7 @@ class LocationApiView(APIView):
class AdoptionNoticeGeoJSONView(ListAPIView):
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
renderer_classes = [GeoJSONRenderer]

View File

@@ -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,
),
]

View File

@@ -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),
]

View File

@@ -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 AbstractUser
from django.core.exceptions import ValidationError
from sphinx.ext.inheritance_diagram import module_sig_re
from .tools import misc, geo
from notfellchen.settings import MEDIA_URL, base_url
from .tools.geo import LocationProxy, Position
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
@@ -395,6 +396,8 @@ class AdoptionNotice(models.Model):
location_string = models.CharField(max_length=200, verbose_name=_("Ortsangabe"))
location = models.ForeignKey(Location, blank=True, null=True, on_delete=models.SET_NULL, )
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
def animals(self):

View File

@@ -10,7 +10,7 @@ from django.template.loader import render_to_string
from django.core import mail
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
from fellchensammlung.tools.misc import is_404

View File

@@ -1,4 +1,4 @@
from fellchensammlung.models import User, AdoptionNotice, AdoptionNoticeStatus
from fellchensammlung.models import User, AdoptionNotice, AdoptionNoticeStatusChoices
def gather_metrics_data():
@@ -9,14 +9,14 @@ def gather_metrics_data():
"""Adoption notices"""
num_adoption_notices = AdoptionNotice.objects.count()
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_closed = AdoptionNotice.objects.filter(
adoptionnoticestatus__major_status=AdoptionNoticeStatus.CLOSED).count()
adoptionnoticestatus=AdoptionNoticeStatusChoices.all_choices()) # TODO fix
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(
adoptionnoticestatus__major_status=AdoptionNoticeStatus.AWAITING_ACTION).count()
adoptionnoticestatus=AdoptionNoticeStatusChoices.all_choices()) # TODO fix
adoption_notices_without_location = AdoptionNotice.objects.filter(location__isnull=True).count()

View File

@@ -55,3 +55,74 @@ ndm = {NotificationTypeChoices.NEW_USER: NotificationDisplayMapping(
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 ""

View File

@@ -21,7 +21,7 @@ from notfellchen import settings
from fellchensammlung import logger
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, \
ImportantLocation, SpeciesSpecificURL, NotificationTypeChoices, SocialMediaPost
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 rest_framework.authtoken.models import Token
from .tools.model_helpers import AdoptionNoticeStatusChoices
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):
"""View function for home page of site."""
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]
language_code = translation.get_language()
lang = Language.objects.get(languagecode=language_code)