feat: Add important locations to search around
This commit is contained in:
		@@ -7,7 +7,7 @@ from django.urls import reverse
 | 
			
		||||
from django.utils.http import urlencode
 | 
			
		||||
 | 
			
		||||
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, \
 | 
			
		||||
    Comment, Report, Announcement, AdoptionNoticeStatus, User, Subscriptions, BaseNotification
 | 
			
		||||
@@ -94,12 +94,14 @@ class ReportAdoptionNoticeAdmin(admin.ModelAdmin):
 | 
			
		||||
 | 
			
		||||
    reported_content_link.short_description = "Reported Content"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SpeciesSpecificURLInline(admin.StackedInline):
 | 
			
		||||
    model = SpeciesSpecificURL
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@admin.register(RescueOrganization)
 | 
			
		||||
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_filter = ("allows_using_materials", "trusted",)
 | 
			
		||||
 | 
			
		||||
@@ -122,14 +124,26 @@ class CommentAdmin(admin.ModelAdmin):
 | 
			
		||||
class BaseNotificationAdmin(admin.ModelAdmin):
 | 
			
		||||
    list_filter = ("user", "read")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@admin.register(SearchSubscription)
 | 
			
		||||
class SearchSubscriptionAdmin(admin.ModelAdmin):
 | 
			
		||||
    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(Species)
 | 
			
		||||
admin.site.register(Location)
 | 
			
		||||
admin.site.register(Rule)
 | 
			
		||||
admin.site.register(Image)
 | 
			
		||||
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 django.db import models
 | 
			
		||||
from django.template.defaultfilters import slugify
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
@@ -99,6 +100,12 @@ class Location(models.Model):
 | 
			
		||||
        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):
 | 
			
		||||
    OSM = "OSM", _("Open Street Map")
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@ from ..forms import AdoptionNoticeSearchForm
 | 
			
		||||
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.
 | 
			
		||||
    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.area_search = 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.search_form = None
 | 
			
		||||
        # Either place_id or location string must be set for area search
 | 
			
		||||
@@ -47,7 +47,6 @@ class Search:
 | 
			
		||||
        elif search_subscription:
 | 
			
		||||
            self.search_from_search_subscription(search_subscription)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return f"Search: {self.sex=}, {self.location=}, {self.area_search=}, {self.max_distance=}"
 | 
			
		||||
 | 
			
		||||
@@ -93,7 +92,6 @@ class Search:
 | 
			
		||||
                return False
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def get_adoption_notices(self):
 | 
			
		||||
        adoptions = AdoptionNotice.objects.order_by("-created_at")
 | 
			
		||||
        # Filter for active adoption notices
 | 
			
		||||
@@ -118,13 +116,21 @@ class Search:
 | 
			
		||||
        else:
 | 
			
		||||
            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):
 | 
			
		||||
        self.sex = search_subscription.sex
 | 
			
		||||
        self.location = search_subscription.location
 | 
			
		||||
        self.area_search = True
 | 
			
		||||
        self.max_distance = search_subscription.max_distance
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def subscribe(self, user):
 | 
			
		||||
        logging.info(f"{user} subscribed to search")
 | 
			
		||||
        if isinstance(self.location, LocationProxy):
 | 
			
		||||
 
 | 
			
		||||
@@ -45,6 +45,7 @@ urlpatterns = [
 | 
			
		||||
 | 
			
		||||
    # ex: /search/
 | 
			
		||||
    path("suchen/", views.search, name="search"),
 | 
			
		||||
    path("suchen/<slug:important_location_slug>", views.search_important_locations, name="search-by-location"),
 | 
			
		||||
    # ex: /map/
 | 
			
		||||
    path("map/", views.map, name="map"),
 | 
			
		||||
    # ex: /vermitteln/
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,8 @@ 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, SexChoicesWithAll, SearchSubscription, AdoptionNoticeNotification
 | 
			
		||||
    Species, Log, Timestamp, TrustLevel, SexChoicesWithAll, SearchSubscription, AdoptionNoticeNotification, \
 | 
			
		||||
    ImportantLocation
 | 
			
		||||
from .forms import AdoptionNoticeForm, AdoptionNoticeFormWithDateWidget, ImageForm, ReportAdoptionNoticeForm, \
 | 
			
		||||
    CommentForm, ReportCommentForm, AnimalForm, \
 | 
			
		||||
    AdoptionNoticeSearchForm, AnimalFormWithDateWidget, AdoptionNoticeFormWithDateWidgetAutoAnimal
 | 
			
		||||
@@ -200,6 +201,26 @@ def animal_detail(request, animal_id):
 | 
			
		||||
    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):
 | 
			
		||||
    # 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
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user