feat: Add important locations to search around
This commit is contained in:
parent
f387930dee
commit
bb14a346cb
@ -7,7 +7,7 @@ from django.urls import reverse
|
|||||||
from django.utils.http import urlencode
|
from django.utils.http import urlencode
|
||||||
|
|
||||||
from .models import User, Language, Text, ReportComment, ReportAdoptionNotice, Log, Timestamp, SearchSubscription, \
|
from .models import User, Language, Text, ReportComment, ReportAdoptionNotice, Log, Timestamp, SearchSubscription, \
|
||||||
SpeciesSpecificURL
|
SpeciesSpecificURL, ImportantLocation
|
||||||
|
|
||||||
from .models import Animal, Species, RescueOrganization, AdoptionNotice, Location, Rule, Image, ModerationAction, \
|
from .models import Animal, Species, RescueOrganization, AdoptionNotice, Location, Rule, Image, ModerationAction, \
|
||||||
Comment, Report, Announcement, AdoptionNoticeStatus, User, Subscriptions, BaseNotification
|
Comment, Report, Announcement, AdoptionNoticeStatus, User, Subscriptions, BaseNotification
|
||||||
@ -94,12 +94,14 @@ class ReportAdoptionNoticeAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
reported_content_link.short_description = "Reported Content"
|
reported_content_link.short_description = "Reported Content"
|
||||||
|
|
||||||
|
|
||||||
class SpeciesSpecificURLInline(admin.StackedInline):
|
class SpeciesSpecificURLInline(admin.StackedInline):
|
||||||
model = SpeciesSpecificURL
|
model = SpeciesSpecificURL
|
||||||
|
|
||||||
|
|
||||||
@admin.register(RescueOrganization)
|
@admin.register(RescueOrganization)
|
||||||
class RescueOrganizationAdmin(admin.ModelAdmin):
|
class RescueOrganizationAdmin(admin.ModelAdmin):
|
||||||
search_fields = ("name","description", "internal_comment", "location_string")
|
search_fields = ("name", "description", "internal_comment", "location_string")
|
||||||
list_display = ("name", "trusted", "allows_using_materials", "website")
|
list_display = ("name", "trusted", "allows_using_materials", "website")
|
||||||
list_filter = ("allows_using_materials", "trusted",)
|
list_filter = ("allows_using_materials", "trusted",)
|
||||||
|
|
||||||
@ -122,14 +124,26 @@ class CommentAdmin(admin.ModelAdmin):
|
|||||||
class BaseNotificationAdmin(admin.ModelAdmin):
|
class BaseNotificationAdmin(admin.ModelAdmin):
|
||||||
list_filter = ("user", "read")
|
list_filter = ("user", "read")
|
||||||
|
|
||||||
|
|
||||||
@admin.register(SearchSubscription)
|
@admin.register(SearchSubscription)
|
||||||
class SearchSubscriptionAdmin(admin.ModelAdmin):
|
class SearchSubscriptionAdmin(admin.ModelAdmin):
|
||||||
list_filter = ("owner",)
|
list_filter = ("owner",)
|
||||||
|
|
||||||
|
|
||||||
|
class ImportantLocationInline(admin.StackedInline):
|
||||||
|
model = ImportantLocation
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Location)
|
||||||
|
class LocationAdmin(admin.ModelAdmin):
|
||||||
|
search_fields = ("name__icontains", "city__icontains")
|
||||||
|
inlines = [
|
||||||
|
ImportantLocationInline,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Animal)
|
admin.site.register(Animal)
|
||||||
admin.site.register(Species)
|
admin.site.register(Species)
|
||||||
admin.site.register(Location)
|
|
||||||
admin.site.register(Rule)
|
admin.site.register(Rule)
|
||||||
admin.site.register(Image)
|
admin.site.register(Image)
|
||||||
admin.site.register(ModerationAction)
|
admin.site.register(ModerationAction)
|
||||||
|
23
src/fellchensammlung/migrations/0045_importantlocation.py
Normal file
23
src/fellchensammlung/migrations/0045_importantlocation.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 5.1.4 on 2025-04-27 11:31
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('fellchensammlung', '0044_alter_location_place_id'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ImportantLocation',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('slug', models.SlugField(unique=True)),
|
||||||
|
('name', models.CharField(max_length=200)),
|
||||||
|
('location', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='fellchensammlung.location')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 5.1.4 on 2025-04-27 11:51
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('fellchensammlung', '0045_importantlocation'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='importantlocation',
|
||||||
|
name='location',
|
||||||
|
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='fellchensammlung.location'),
|
||||||
|
),
|
||||||
|
]
|
@ -3,6 +3,7 @@ from random import choices
|
|||||||
from tabnanny import verbose
|
from tabnanny import verbose
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.template.defaultfilters import slugify
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
@ -99,6 +100,12 @@ class Location(models.Model):
|
|||||||
instance.save()
|
instance.save()
|
||||||
|
|
||||||
|
|
||||||
|
class ImportantLocation(models.Model):
|
||||||
|
location = models.OneToOneField(Location, on_delete=models.CASCADE)
|
||||||
|
slug = models.SlugField(unique=True)
|
||||||
|
name = models.CharField(max_length=200)
|
||||||
|
|
||||||
|
|
||||||
class ExternalSourceChoices(models.TextChoices):
|
class ExternalSourceChoices(models.TextChoices):
|
||||||
OSM = "OSM", _("Open Street Map")
|
OSM = "OSM", _("Open Street Map")
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ from ..forms import AdoptionNoticeSearchForm
|
|||||||
from ..models import SearchSubscription, AdoptionNotice, AdoptionNoticeNotification, SexChoicesWithAll, Location
|
from ..models import SearchSubscription, AdoptionNotice, AdoptionNoticeNotification, SexChoicesWithAll, Location
|
||||||
|
|
||||||
|
|
||||||
def notify_search_subscribers(adoption_notice: AdoptionNotice, only_if_active : bool = True):
|
def notify_search_subscribers(adoption_notice: AdoptionNotice, only_if_active: bool = True):
|
||||||
"""
|
"""
|
||||||
This functions checks for all search subscriptions if the new adoption notice fits the search.
|
This functions checks for all search subscriptions if the new adoption notice fits the search.
|
||||||
If the new adoption notice fits the search subscription, it sends a notification to the user that created the search.
|
If the new adoption notice fits the search subscription, it sends a notification to the user that created the search.
|
||||||
@ -36,7 +36,7 @@ class Search:
|
|||||||
self.sex = None
|
self.sex = None
|
||||||
self.area_search = None
|
self.area_search = None
|
||||||
self.max_distance = None
|
self.max_distance = None
|
||||||
self.location = None # Can either be Location (DjangoModel) or LocationProxy
|
self.location = None # Can either be Location (DjangoModel) or LocationProxy
|
||||||
self.place_not_found = False # Indicates that a location was given but could not be geocoded
|
self.place_not_found = False # Indicates that a location was given but could not be geocoded
|
||||||
self.search_form = None
|
self.search_form = None
|
||||||
# Either place_id or location string must be set for area search
|
# Either place_id or location string must be set for area search
|
||||||
@ -47,7 +47,6 @@ class Search:
|
|||||||
elif search_subscription:
|
elif search_subscription:
|
||||||
self.search_from_search_subscription(search_subscription)
|
self.search_from_search_subscription(search_subscription)
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Search: {self.sex=}, {self.location=}, {self.area_search=}, {self.max_distance=}"
|
return f"Search: {self.sex=}, {self.location=}, {self.area_search=}, {self.max_distance=}"
|
||||||
|
|
||||||
@ -93,7 +92,6 @@ class Search:
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def get_adoption_notices(self):
|
def get_adoption_notices(self):
|
||||||
adoptions = AdoptionNotice.objects.order_by("-created_at")
|
adoptions = AdoptionNotice.objects.order_by("-created_at")
|
||||||
# Filter for active adoption notices
|
# Filter for active adoption notices
|
||||||
@ -118,13 +116,21 @@ class Search:
|
|||||||
else:
|
else:
|
||||||
self.search_form = AdoptionNoticeSearchForm()
|
self.search_form = AdoptionNoticeSearchForm()
|
||||||
|
|
||||||
|
def search_from_predefined_i_location(self, i_location, max_distance=100):
|
||||||
|
self.sex = SexChoicesWithAll.ALL
|
||||||
|
self.location = i_location.location
|
||||||
|
self.area_search = True
|
||||||
|
self.search_form = AdoptionNoticeSearchForm(initial={"location_string": self.location.name,
|
||||||
|
"max_distance": max_distance,
|
||||||
|
"sex": SexChoicesWithAll.ALL})
|
||||||
|
self.max_distance = max_distance
|
||||||
|
|
||||||
def search_from_search_subscription(self, search_subscription: SearchSubscription):
|
def search_from_search_subscription(self, search_subscription: SearchSubscription):
|
||||||
self.sex = search_subscription.sex
|
self.sex = search_subscription.sex
|
||||||
self.location = search_subscription.location
|
self.location = search_subscription.location
|
||||||
self.area_search = True
|
self.area_search = True
|
||||||
self.max_distance = search_subscription.max_distance
|
self.max_distance = search_subscription.max_distance
|
||||||
|
|
||||||
|
|
||||||
def subscribe(self, user):
|
def subscribe(self, user):
|
||||||
logging.info(f"{user} subscribed to search")
|
logging.info(f"{user} subscribed to search")
|
||||||
if isinstance(self.location, LocationProxy):
|
if isinstance(self.location, LocationProxy):
|
||||||
|
@ -45,6 +45,7 @@ urlpatterns = [
|
|||||||
|
|
||||||
# ex: /search/
|
# ex: /search/
|
||||||
path("suchen/", views.search, name="search"),
|
path("suchen/", views.search, name="search"),
|
||||||
|
path("suchen/<slug:important_location_slug>", views.search_important_locations, name="search-by-location"),
|
||||||
# ex: /map/
|
# ex: /map/
|
||||||
path("map/", views.map, name="map"),
|
path("map/", views.map, name="map"),
|
||||||
# ex: /vermitteln/
|
# ex: /vermitteln/
|
||||||
|
@ -18,7 +18,8 @@ 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, CommentNotification, BaseNotification, RescueOrganization, \
|
User, Location, AdoptionNoticeStatus, Subscriptions, CommentNotification, BaseNotification, RescueOrganization, \
|
||||||
Species, Log, Timestamp, TrustLevel, SexChoicesWithAll, SearchSubscription, AdoptionNoticeNotification
|
Species, Log, Timestamp, TrustLevel, SexChoicesWithAll, SearchSubscription, AdoptionNoticeNotification, \
|
||||||
|
ImportantLocation
|
||||||
from .forms import AdoptionNoticeForm, AdoptionNoticeFormWithDateWidget, ImageForm, ReportAdoptionNoticeForm, \
|
from .forms import AdoptionNoticeForm, AdoptionNoticeFormWithDateWidget, ImageForm, ReportAdoptionNoticeForm, \
|
||||||
CommentForm, ReportCommentForm, AnimalForm, \
|
CommentForm, ReportCommentForm, AnimalForm, \
|
||||||
AdoptionNoticeSearchForm, AnimalFormWithDateWidget, AdoptionNoticeFormWithDateWidgetAutoAnimal
|
AdoptionNoticeSearchForm, AnimalFormWithDateWidget, AdoptionNoticeFormWithDateWidgetAutoAnimal
|
||||||
@ -200,6 +201,26 @@ def animal_detail(request, animal_id):
|
|||||||
return render(request, 'fellchensammlung/details/detail_animal.html', context=context)
|
return render(request, 'fellchensammlung/details/detail_animal.html', context=context)
|
||||||
|
|
||||||
|
|
||||||
|
def search_important_locations(request, important_location_slug):
|
||||||
|
i_location = ImportantLocation.objects.get(slug=important_location_slug)
|
||||||
|
search = Search()
|
||||||
|
search.search_from_predefined_i_location(i_location)
|
||||||
|
context = {"adoption_notices": search.get_adoption_notices(),
|
||||||
|
"search_form": search.search_form,
|
||||||
|
"place_not_found": search.place_not_found,
|
||||||
|
"subscribed_search": None,
|
||||||
|
"searched": False,
|
||||||
|
"adoption_notices_map": AdoptionNotice.get_active_ANs(),
|
||||||
|
"map_center": search.position,
|
||||||
|
"search_center": search.position,
|
||||||
|
"map_pins": [search],
|
||||||
|
"location": search.location,
|
||||||
|
"search_radius": search.max_distance,
|
||||||
|
"zoom_level": zoom_level_for_radius(search.max_distance),
|
||||||
|
"geocoding_api_url": settings.GEOCODING_API_URL, }
|
||||||
|
return render(request, 'fellchensammlung/search.html', context=context)
|
||||||
|
|
||||||
|
|
||||||
def search(request):
|
def search(request):
|
||||||
# A user just visiting the search site did not search, only upon completing the search form a user has really
|
# A user just visiting the search site did not search, only upon completing the search form a user has really
|
||||||
# searched. This will toggle the "subscribe" button
|
# searched. This will toggle the "subscribe" button
|
||||||
|
Loading…
x
Reference in New Issue
Block a user