Compare commits

...

10 Commits

Author SHA1 Message Date
8de5f162eb feat:Add notification when AN is set unchecked
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-11-22 11:57:13 +01:00
dc3859d589 test: fix search test 2024-11-22 11:56:11 +01:00
b4f52c7876 fix: Save last checked correctly 2024-11-22 07:15:50 +01:00
885622e581 feat: allow not searching for location 2024-11-21 23:07:27 +01:00
a42a3fa177 feat: allow search for sex 2024-11-21 22:51:15 +01:00
27541c6fb6 feat: add choice with all 2024-11-21 22:50:04 +01:00
14547ad621 fix: don't exchange set for new one 2024-11-21 22:49:00 +01:00
8d2d80c30e feat: Add intersex option for animals, fix bug 2024-11-21 20:37:38 +01:00
e6f5a42d15 feat: Add intersex option for animals
Intersex rats are rare but well documented.
2024-11-21 20:35:09 +01:00
052e42f76a feat: Use text choices for sex 2024-11-21 20:29:52 +01:00
7 changed files with 116 additions and 32 deletions

View File

@ -1,7 +1,7 @@
from django import forms
from .models import AdoptionNotice, Animal, Image, ReportAdoptionNotice, ReportComment, ModerationAction, User, Species, \
Comment
Comment, SexChoicesWithAll
from django_registration.forms import RegistrationForm
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit, Layout, Fieldset, HTML, Row, Column, Field, Hidden
@ -66,7 +66,8 @@ class AdoptionNoticeForm(forms.ModelForm):
class AdoptionNoticeFormWithDateWidget(AdoptionNoticeForm):
class Meta:
model = AdoptionNotice
fields = ['name', "group_only", "further_information", "description", "searching_since", "location_string", "organization"]
fields = ['name', "group_only", "further_information", "description", "searching_since", "location_string",
"organization"]
widgets = {
'searching_since': DateInput(),
}
@ -185,5 +186,7 @@ def _get_distances():
class AdoptionNoticeSearchForm(forms.Form):
location = forms.CharField(max_length=20, label=_("Stadt"))
location = forms.CharField(max_length=20, label=_("Stadt"), required=False)
max_distance = forms.ChoiceField(choices=_get_distances, label=_("Max. Distanz"))
sex = forms.ChoiceField(choices=SexChoicesWithAll, label=_("Geschlecht"), required=False,
initial=SexChoicesWithAll.ALL)

View File

@ -0,0 +1,18 @@
# Generated by Django 5.1.1 on 2024-11-21 19:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('fellchensammlung', '0023_user_email_notifications'),
]
operations = [
migrations.AlterField(
model_name='animal',
name='sex',
field=models.CharField(choices=[('M_N', 'neutered male'), ('M', 'male'), ('F_N', 'neutered female'), ('F', 'female'), ('I', 'intersex')], max_length=20),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 5.1.1 on 2024-11-21 19:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('fellchensammlung', '0024_alter_animal_sex'),
]
operations = [
migrations.AlterField(
model_name='animal',
name='sex',
field=models.CharField(choices=[('F', 'Weiblich'), ('M', 'Männlich'), ('M_N', 'Männlich, kastriert'), ('F_N', 'Weiblich Kastriert'), ('I', 'Intersex')], max_length=20),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 5.1.1 on 2024-11-21 21:49
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('fellchensammlung', '0025_alter_animal_sex'),
]
operations = [
migrations.AlterField(
model_name='animal',
name='sex',
field=models.CharField(choices=[('F', 'Weiblich'), ('M', 'Männlich'), ('M_N', 'Männlich, kastriert'), ('F_N', 'Weiblich, kastriert'), ('I', 'Intergeschlechtlich')], max_length=20),
),
]

View File

@ -240,15 +240,18 @@ class AdoptionNotice(models.Model):
def sexes(self):
sexes = set()
for animal in self.animals:
sexes.update(animal.sex)
sexes.add(animal.sex)
return sexes
def sex_code(self):
# Treat Intersex as mixed in order to increase their visibility
if len(self.sexes) > 1:
return "mixed"
elif self.sexes.pop() == Animal.MALE:
sex = self.sexes.pop()
if sex == SexChoices.MALE:
return "male"
elif self.sexes.pop() == Animal.FEMALE:
elif sex == SexChoices.FEMALE:
return "female"
else:
return "mixed"
@ -348,18 +351,26 @@ class AdoptionNotice(models.Model):
def set_closed(self):
self.last_checked = timezone.now()
self.adoptionnoticestatus.set_closed()
self.save()
def set_active(self):
self.last_checked = timezone.now()
if not hasattr(self, 'adoptionnoticestatus'):
AdoptionNoticeStatus.create_other(self)
self.adoptionnoticestatus.set_active()
self.save()
def set_unchecked(self):
self.last_checked = timezone.now()
if not hasattr(self, 'adoptionnoticestatus'):
AdoptionNoticeStatus.create_other(self)
self.adoptionnoticestatus.set_unchecked()
self.save()
for subscription in self.get_subscriptions():
notification_title = _("Vermittlung deaktiviert:") + f" {self}"
text = _("Die folgende Vermittlung wurde deaktiviert: ") + f"{self.name}, {self.get_absolute_url()}"
BaseNotification.objects.create(user=subscription.owner, text=text, title=notification_title)
class AdoptionNoticeStatus(models.Model):
@ -457,24 +468,33 @@ class AdoptionNoticeStatus(models.Model):
self.save()
class Animal(models.Model):
MALE_NEUTERED = "M_N"
MALE = "M"
FEMALE_NEUTERED = "F_N"
FEMALE = "F"
SEX_CHOICES = {
MALE_NEUTERED: "neutered male",
MALE: "male",
FEMALE_NEUTERED: "neutered female",
FEMALE: "female",
}
class SexChoices(models.TextChoices):
FEMALE = "F", _("Weiblich")
MALE = "M", _("Männlich")
MALE_NEUTERED = "M_N", _("Männlich, kastriert")
FEMALE_NEUTERED = "F_N", _("Weiblich, kastriert")
INTER = "I", _("Intergeschlechtlich")
class SexChoicesWithAll(models.TextChoices):
FEMALE = "F", _("Weiblich")
MALE = "M", _("Männlich")
MALE_NEUTERED = "M_N", _("Männlich, kastriert")
FEMALE_NEUTERED = "F_N", _("Weiblich Kastriert")
INTER = "I", _("Intergeschlechtlich")
ALL = "A", _("Alle")
class Animal(models.Model):
date_of_birth = models.DateField(verbose_name=_('Geburtsdatum'))
name = models.CharField(max_length=200)
description = models.TextField(null=True, blank=True, verbose_name=_('Beschreibung'))
species = models.ForeignKey(Species, on_delete=models.PROTECT)
photos = models.ManyToManyField(Image, blank=True)
sex = models.CharField(max_length=20, choices=SEX_CHOICES, )
sex = models.CharField(
max_length=20,
choices=SexChoices.choices,
)
adoption_notice = models.ForeignKey(AdoptionNotice, on_delete=models.CASCADE)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
updated_at = models.DateTimeField(auto_now=True)

View File

@ -16,7 +16,7 @@ from notfellchen import settings
from fellchensammlung import logger
from .models import AdoptionNotice, Text, Animal, Rule, Image, Report, ModerationAction, \
User, Location, AdoptionNoticeStatus, Subscriptions, CommentNotification, BaseNotification, RescueOrganization, \
Species, Log, Timestamp, TrustLevel
Species, Log, Timestamp, TrustLevel, SexChoicesWithAll
from .forms import AdoptionNoticeForm, AdoptionNoticeFormWithDateWidget, ImageForm, ReportAdoptionNoticeForm, \
CommentForm, ReportCommentForm, AnimalForm, \
AdoptionNoticeSearchForm, AnimalFormWithDateWidget, AdoptionNoticeFormWithDateWidgetAutoAnimal
@ -169,27 +169,34 @@ def animal_detail(request, animal_id):
def search(request):
place_not_found = None
latest_adoption_list = AdoptionNotice.objects.order_by("-created_at")
active_adoptions = [adoption for adoption in latest_adoption_list if adoption.is_active]
if request.method == 'POST':
latest_adoption_list = AdoptionNotice.objects.order_by("-created_at")
active_adoptions = [adoption for adoption in latest_adoption_list if adoption.is_active]
sex = request.POST.get("sex")
if sex != SexChoicesWithAll.ALL:
active_adoptions = [adoption for adoption in active_adoptions if sex in adoption.sexes]
search_form = AdoptionNoticeSearchForm(request.POST)
max_distance = int(request.POST.get('max_distance'))
if max_distance == "":
max_distance = None
geo_api = GeoAPI()
search_position = geo_api.get_coordinates_from_query(request.POST['location'])
if search_position is None:
place_not_found = True
search_form.is_valid()
if search_form.cleaned_data["location"] == "":
adoption_notices_in_distance = active_adoptions
place_not_found = False
else:
adoption_notices_in_distance = [a for a in active_adoptions if a.in_distance(search_position, max_distance)]
max_distance = int(request.POST.get('max_distance'))
if max_distance == "":
max_distance = None
geo_api = GeoAPI()
search_position = geo_api.get_coordinates_from_query(request.POST['location'])
if search_position is None:
place_not_found = True
adoption_notices_in_distance = active_adoptions
else:
adoption_notices_in_distance = [a for a in active_adoptions if a.in_distance(search_position, max_distance)]
context = {"adoption_notices": adoption_notices_in_distance, "search_form": search_form,
"place_not_found": place_not_found}
else:
latest_adoption_list = AdoptionNotice.objects.order_by("-created_at")
active_adoptions = [adoption for adoption in latest_adoption_list if adoption.is_active]
search_form = AdoptionNoticeSearchForm()
context = {"adoption_notices": active_adoptions, "search_form": search_form}
return render(request, 'fellchensammlung/search.html', context=context)

View File

@ -120,7 +120,7 @@ class SearchTest(TestCase):
self.assertNotContains(response, "TestAdoption2")
def test_plz_search(self):
response = self.client.post(reverse('search'), {"max_distance": 100, "location": "Berlin"})
response = self.client.post(reverse('search'), {"max_distance": 100, "location": "Berlin", "sex": "A"})
self.assertEqual(response.status_code, 200)
self.assertContains(response, "TestAdoption1")
self.assertNotContains(response, "TestAdoption3")