This commit is contained in:
parent
cb346b59c7
commit
b9fa20e4d6
|
@ -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']
|
||||
|
|
|
@ -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!'))
|
||||
|
|
31
pflaenzli/pflaenzli/migrations/0006_plz.py
Normal file
31
pflaenzli/pflaenzli/migrations/0006_plz.py
Normal 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)),
|
||||
],
|
||||
),
|
||||
]
|
|
@ -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')),
|
||||
|
|
|
@ -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 %}
|
||||
|
|
14
pflaenzli/pflaenzli/templatetags/plz.py
Normal file
14
pflaenzli/pflaenzli/templatetags/plz.py
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in a new issue