Overhaul PLZ
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Jannis Portmann 2023-10-15 00:29:21 +02:00
parent cb346b59c7
commit b9fa20e4d6
8 changed files with 109 additions and 31 deletions

View file

@ -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']

View file

@ -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!'))

View file

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

View file

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

View file

@ -1,5 +1,6 @@
{% extends 'base.html' %}
{% load i18n %}
{% load plz %}
{% block title %}Offer: {{ offer.title }}{% endblock %}
{% block meta %}
<meta name="description"
@ -30,7 +31,7 @@
{% endif %}
</p>
<p class="mr-3">
<i class="fas fa-map-marker-alt"></i> {{ offer.zipcode }}
{% with plz=offer|get_plz %}<i class="fas fa-map-marker-alt"></i> {{ plz.plz }} {{ plz.name }}{% endwith %}
</p>
{% if dist %}
<p class="pr-3">
@ -90,7 +91,7 @@
</div>
<form method="post" action="{% url 'offer_trade' %}">
{% csrf_token %}
<input type="hidden" name="offer" value="{{ offer.id }}"/>
<input type="hidden" name="offer" value="{{ offer.id }}" />
<button class="btn btn-pfl mb-3" data-umami-event="Trade offer">{% trans "Offer trade" %}</button>
</form>
{% endif %}

View file

@ -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

View file

@ -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

View file

@ -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: