diff --git a/pflaenzli/pflaenzli/admin.py b/pflaenzli/pflaenzli/admin.py index 1de3f16..f0ebb3f 100644 --- a/pflaenzli/pflaenzli/admin.py +++ b/pflaenzli/pflaenzli/admin.py @@ -1,5 +1,5 @@ from django.contrib import admin -from .models import Offer, Wish, PflaenzliUser +from .models import Offer, Wish, PflaenzliUser, Plz @admin.register(Offer) @@ -15,3 +15,8 @@ class WishAdmin(admin.ModelAdmin): @admin.register(PflaenzliUser) class UserAdmin(admin.ModelAdmin): list_display = ['id', 'username', 'email', 'zipcode', 'language_code', 'date_joined'] + + +@admin.register(Plz) +class PlzAdmin(admin.ModelAdmin): + list_display = ['id', 'name', 'plz', 'lat', 'lon'] diff --git a/pflaenzli/pflaenzli/management/commands/getplzindex.py b/pflaenzli/pflaenzli/management/commands/getplzindex.py index 0c0b99b..b3f000e 100644 --- a/pflaenzli/pflaenzli/management/commands/getplzindex.py +++ b/pflaenzli/pflaenzli/management/commands/getplzindex.py @@ -5,36 +5,52 @@ from urllib import request import pandas as pd from django.core.management.base import BaseCommand from pflaenzli_django.settings import BASE_DIR +from pflaenzli.models import Plz class Command(BaseCommand): help = 'Get the zip code index from post and compile it to a dataframe pickle' - def handle(self, *args, **options): - self.parse_data(self.download_geojson(api='v2', data='v2')) - self.stdout.write(self.style.SUCCESS('Done!')) + def add_arguments(self, parser): + parser.add_argument("--force", action="store_true", required=False) - def download_geojson(self, api, data): + def handle(self, *args, **options): + self.parse_data(*self.download_geojson(api='v2', data='v2', force=options["force"]), force=options["force"]) + + def download_geojson(self, api, data, force=False): file = f'plz_verzeichnis_{data}.json' - if os.path.exists(file): - self.stdout.write('File already downloaded. Skipping...\n') + if os.path.exists(file) and not force: + self.stdout.write('File already downloaded.') + self.stdout.write(self.style.SUCCESS( + 'Skipping...\n')) + exists = True else: self.stdout.write('Downloading geojson...') url = f'https://swisspost.opendatasoft.com/api/{api}/catalog/datasets/plz_verzeichnis_{data}/exports/geojson' request.urlretrieve(url, file) self.stdout.write(self.style.SUCCESS('Done!\n')) + exists = False - return file + return file, exists + + def parse_data(self, file, exists, force=False): + if exists and not force: + self.stdout.write(self.style.WARNING( + 'Nothing was done, if you want to redownload the PLZ index, use the --force option.\n')) + return - def parse_data(self, file): self.stdout.write('Opening file...') # Load the GeoJSON data for the zip codes with open(file, encoding='UTF-8') as f: full_data = json.load(f) self.stdout.write(self.style.SUCCESS('Done!\n')) - self.stdout.write('Parsing file...') - zip_dict = {} + self.stdout.write('Deleting existing data...') + Plz.objects.all().delete() + self.stdout.write(self.style.SUCCESS('Done!\n')) + + self.stdout.write('Parsing file and add new data...') + for plz_entry in full_data['features']: plz_entry = plz_entry['properties'] try: @@ -51,14 +67,12 @@ class Command(BaseCommand): if plz is None or lat is None or lon is None: continue - zip_dict[int(plz)] = [lat, lon] - self.stdout.write(self.style.SUCCESS('Done!\n')) + try: + name = plz_entry['ortbez27'] + except (KeyError, AttributeError, TypeError): + name = None - df = pd.DataFrame.from_dict(zip_dict, orient='index') - df.columns = ['lat', 'lon'] + plz, _ = Plz.objects.get_or_create(plz=int(plz), lat=lat, lon=lon, name=name) - self.stdout.write('Saving pickle...') - destination = os.path.join(BASE_DIR, 'pflaenzli', 'utils', 'plz.pkl') - df.to_pickle(destination) - - self.stdout.write(self.style.SUCCESS(f'Wrote pickle to {destination}\n')) + self.stdout.write(self.style.SUCCESS('Wrote PLZ data to the databse successfully\n')) + self.stdout.write(self.style.SUCCESS('Done!')) diff --git a/pflaenzli/pflaenzli/migrations/0006_plz.py b/pflaenzli/pflaenzli/migrations/0006_plz.py new file mode 100644 index 0000000..efbe8d2 --- /dev/null +++ b/pflaenzli/pflaenzli/migrations/0006_plz.py @@ -0,0 +1,31 @@ +# Generated by Django 4.2.5 on 2023-09-13 15:09 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("pflaenzli", "0005_pflaenzliuser_language_code_alter_offer_category"), + ] + + operations = [ + migrations.CreateModel( + name="Plz", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("plz", models.IntegerField(verbose_name="PLZ")), + ("lat", models.DecimalField(decimal_places=6, max_digits=8)), + ("lon", models.DecimalField(decimal_places=6, max_digits=8)), + ("name", models.CharField(max_length=27)), + ], + ), + ] diff --git a/pflaenzli/pflaenzli/models.py b/pflaenzli/pflaenzli/models.py index b1e2b48..aa50a60 100644 --- a/pflaenzli/pflaenzli/models.py +++ b/pflaenzli/pflaenzli/models.py @@ -14,6 +14,13 @@ class PflaenzliUser(AbstractUser): language_code = models.CharField(max_length=2, default='de') +class Plz(models.Model): + plz = models.IntegerField(verbose_name='PLZ') + lat = models.DecimalField(max_digits=8, decimal_places=6) + lon = models.DecimalField(max_digits=8, decimal_places=6) + name = models.CharField(max_length=27) + + class Offer(models.Model): CATEGORIES = [ ('PLNT', _('Plant')), diff --git a/pflaenzli/pflaenzli/templates/base.html b/pflaenzli/pflaenzli/templates/base.html index 286d262..1e1f6a3 100644 --- a/pflaenzli/pflaenzli/templates/base.html +++ b/pflaenzli/pflaenzli/templates/base.html @@ -140,7 +140,7 @@
-
+
{% if request.user.is_authenticated %}
{% csrf_token %} - +
{% endif %} diff --git a/pflaenzli/pflaenzli/templatetags/plz.py b/pflaenzli/pflaenzli/templatetags/plz.py new file mode 100644 index 0000000..f971ab1 --- /dev/null +++ b/pflaenzli/pflaenzli/templatetags/plz.py @@ -0,0 +1,14 @@ +from django import template +from pflaenzli.models import Plz + +register = template.Library() + + +@register.filter +def get_plz(offer): + try: + return Plz.objects.get(plz=offer.zipcode) + except Plz.MultipleObjectsReturned: + return Plz.objects.filter(plz=offer.zipcode).first() + except Plz.DoesNotExist: + return None diff --git a/pflaenzli/pflaenzli/utils/distance.py b/pflaenzli/pflaenzli/utils/distance.py index 403f00e..19c3866 100644 --- a/pflaenzli/pflaenzli/utils/distance.py +++ b/pflaenzli/pflaenzli/utils/distance.py @@ -2,28 +2,34 @@ from geopy.distance import distance import os from pandas import read_pickle from django.db.models import F, Func +from pflaenzli.models import Plz path = os.path.dirname(os.path.abspath(__file__)) df = read_pickle(os.path.join(path, 'plz.pkl')) -def calculate_distance(zip_1, zip_2): - if zip_1 == zip_2: +def calculate_distance(plz_1: Plz, plz_2: Plz): + if plz_1 == plz_2: return 0 - zip_1_coords = tuple(df[df.index == zip_1].values) - zip_2_coords = tuple(df[df.index == zip_2].values) - - dist = round(distance((zip_1_coords), (zip_2_coords)).kilometers) + dist = round(distance((plz_1.lat, plz_1.lon), (plz_2.lat, plz_2.lon)).kilometers) return None if dist > 400 else dist def filter_by_distance(qs, filter_zipcode, max_dist): filtered_offers = [] + + try: + filter_plz = Plz.objects.get(plz=filter_zipcode) + except Plz.DoesNotExist: + return filtered_offers + for offer in qs: - d = calculate_distance(int(offer.zipcode), int(filter_zipcode)) + offer_plz = Plz.objects.get(plz=offer.zipcode) + + d = calculate_distance(offer_plz, filter_plz) if d is not None and d <= max_dist: filtered_offers.append(offer) return filtered_offers diff --git a/pflaenzli/pflaenzli/views.py b/pflaenzli/pflaenzli/views.py index cb0ce9e..beb492c 100644 --- a/pflaenzli/pflaenzli/views.py +++ b/pflaenzli/pflaenzli/views.py @@ -12,7 +12,7 @@ from django.views.decorators.http import require_POST from .forms import CreateOfferForm, FilterForm, RegistrationForm, WishForm from .mail import send_offer_email -from .models import Offer, PflaenzliUser, Wish +from .models import Offer, PflaenzliUser, Wish, Plz from .upload import generate_unique_filename from .utils.compress_image import compress_image from .utils.distance import calculate_distance, filter_by_distance @@ -59,7 +59,7 @@ def offer_detail(request, offer_id): if offer.zipcode == request.user.zipcode: dist = 0 else: - dist = calculate_distance(offer.zipcode, request.user.zipcode) + dist = calculate_distance(Plz.objects.get(plz=offer.zipcode), Plz.objects.get(plz=request.user.zipcode)) else: dist = None else: