Compare commits

...

10 commits

Author SHA1 Message Date
Jannis Portmann
1246c86691 Fix user issue 2023-04-05 23:39:26 +02:00
Jannis Portmann
e727904323 Set titles in forms 2023-04-05 23:31:57 +02:00
Jannis Portmann
af1a55138c Add option for title 2023-04-05 23:29:42 +02:00
Jannis Portmann
15f5a4d281 User registration 2023-04-05 23:29:30 +02:00
Jannis Portmann
300e535b5c Update readme 2023-04-05 18:22:34 +02:00
Jannis Portmann
35ada3edff Update packages 2023-04-05 18:20:15 +02:00
Jannis Portmann
a53a6f9344 Load settings from .env 2023-04-05 18:19:21 +02:00
Jannis Portmann
35ec98f830 Round distance 2023-04-05 18:19:06 +02:00
Jannis Portmann
4e01a6ff90 Reset migrations and add user filed 2023-04-05 18:18:55 +02:00
Jannis Portmann
3691061f71 Calculate distance between PLZ 2023-04-05 15:34:00 +02:00
14 changed files with 236 additions and 87 deletions

View file

@ -7,7 +7,7 @@ A platform where people can trade their plants. You can post what you have and s
## Tech stack
- [Django](https://djangoproject.com/)
- [MariaDB](https://www.postgresql.org/)
- [Postgres](https://www.postgresql.org/)
## Admin dashboard
Find it under `/admin`
@ -31,4 +31,8 @@ Searching with filters such as:
| Category | `Category` | dropdown |
## Development
To get started with development, see [DEVELOPMENT.md](DEVELOPMENT.md)
To get started with development, see [DEVELOPMENT.md](DEVELOPMENT.md)
## Open Source Data
For calculating distances between zip codes, the `PLZ_Verzeichnis` is used.
Source: https://opendata.swiss/de/dataset/plz_verzeichnis

View file

@ -1,9 +1,19 @@
from django import forms
from django.contrib.auth.forms import UserCreationForm
from friendly_captcha.fields import FrcCaptchaField
from .models import User, Offer
from .models import Offer, PflaenzliUser
class CreateOfferForm(forms.ModelForm):
class Meta:
model = Offer
fields = ['title', 'description', 'zipcode', 'image']
class RegistrationForm(UserCreationForm):
class Meta(UserCreationForm.Meta):
model = PflaenzliUser
fields = UserCreationForm.Meta.fields + ('zipcode',)
captcha = FrcCaptchaField()

View file

@ -1,8 +1,11 @@
# Generated by Django 4.1.7 on 2023-02-19 12:38
# Generated by Django 4.1.7 on 2023-04-05 21:38
from django.conf import settings
import django.contrib.auth.models
import django.contrib.auth.validators
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
@ -10,10 +13,148 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("auth", "0012_alter_user_first_name_max_length"),
]
operations = [
migrations.CreateModel(
name="PflaenzliUser",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("password", models.CharField(max_length=128, verbose_name="password")),
(
"last_login",
models.DateTimeField(
blank=True, null=True, verbose_name="last login"
),
),
(
"is_superuser",
models.BooleanField(
default=False,
help_text="Designates that this user has all permissions without explicitly assigning them.",
verbose_name="superuser status",
),
),
(
"username",
models.CharField(
error_messages={
"unique": "A user with that username already exists."
},
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
max_length=150,
unique=True,
validators=[
django.contrib.auth.validators.UnicodeUsernameValidator()
],
verbose_name="username",
),
),
(
"first_name",
models.CharField(
blank=True, max_length=150, verbose_name="first name"
),
),
(
"last_name",
models.CharField(
blank=True, max_length=150, verbose_name="last name"
),
),
(
"email",
models.EmailField(
blank=True, max_length=254, verbose_name="email address"
),
),
(
"is_staff",
models.BooleanField(
default=False,
help_text="Designates whether the user can log into this admin site.",
verbose_name="staff status",
),
),
(
"is_active",
models.BooleanField(
default=True,
help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
verbose_name="active",
),
),
(
"date_joined",
models.DateTimeField(
default=django.utils.timezone.now, verbose_name="date joined"
),
),
("zipcode", models.PositiveIntegerField(blank=True)),
(
"groups",
models.ManyToManyField(
blank=True,
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
related_name="user_set",
related_query_name="user",
to="auth.group",
verbose_name="groups",
),
),
(
"user_permissions",
models.ManyToManyField(
blank=True,
help_text="Specific permissions for this user.",
related_name="user_set",
related_query_name="user",
to="auth.permission",
verbose_name="user permissions",
),
),
],
options={
"verbose_name": "user",
"verbose_name_plural": "users",
"abstract": False,
},
managers=[
("objects", django.contrib.auth.models.UserManager()),
],
),
migrations.CreateModel(
name="Wish",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("created", models.DateTimeField(default=django.utils.timezone.now)),
("title", models.CharField(max_length=200)),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
],
),
migrations.CreateModel(
name="Offer",
fields=[
@ -26,9 +167,11 @@ class Migration(migrations.Migration):
verbose_name="ID",
),
),
("created", models.DateTimeField(default=django.utils.timezone.now)),
("title", models.CharField(max_length=50)),
("description", models.TextField(max_length=5000)),
("zipcode", models.IntegerField(blank=True, default=0)),
("image", models.ImageField(upload_to="uploads/")),
(
"user",
models.ForeignKey(

View file

@ -1,45 +0,0 @@
# Generated by Django 4.1.7 on 2023-02-19 22:03
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("pflaenzli", "0001_initial"),
]
operations = [
migrations.AddField(
model_name="offer",
name="created",
field=models.DateTimeField(default=django.utils.timezone.now),
),
migrations.CreateModel(
name="Wish",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("created", models.DateTimeField(default=django.utils.timezone.now)),
("title", models.CharField(max_length=200)),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
],
),
]

View file

@ -1,19 +0,0 @@
# Generated by Django 4.1.7 on 2023-02-19 22:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("pflaenzli", "0002_offer_created_wish"),
]
operations = [
migrations.AddField(
model_name="offer",
name="image",
field=models.ImageField(default="placeholder.png", upload_to="uplaods"),
preserve_default=False,
),
]

View file

@ -1,13 +1,17 @@
from django.db import models
from django.dispatch import receiver
from django.contrib.auth.models import User
from django.contrib.auth.models import AbstractUser
from django.utils import timezone
from django.core.files.storage import default_storage
class PflaenzliUser(AbstractUser):
zipcode = models.PositiveIntegerField(blank=True)
class Offer(models.Model):
created = models.DateTimeField(default=timezone.now)
user = models.ForeignKey(User, on_delete=models.CASCADE)
user = models.ForeignKey(PflaenzliUser, on_delete=models.CASCADE)
title = models.CharField(max_length=50)
description = models.TextField(max_length=5000)
zipcode = models.IntegerField(blank=True, default=0)
@ -16,7 +20,7 @@ class Offer(models.Model):
class Wish(models.Model):
created = models.DateTimeField(default=timezone.now)
user = models.ForeignKey(User, on_delete=models.CASCADE)
user = models.ForeignKey(PflaenzliUser, on_delete=models.CASCADE)
title = models.CharField(max_length=200)

View file

@ -13,7 +13,9 @@
</p>
<hr />
<p>To offer your plants, please register first.</p>
<a href="#" class="btn btn-pfl btn-lg mb-3" type="button">Register</a>
<a href="{% url 'register_user' %}"
class="btn btn-pfl btn-lg mb-3"
type="button">Register</a>
</div>
</div>
{% endblock %}

View file

@ -3,6 +3,7 @@
{% block title %}{{ title }}{% endblock %}
{% block meta %}<meta name="description" content="{{ page_description }}">{% endblock %}
{% block content %}
<h1>{{ title }}</h1>
<form method="post" enctype="multipart/form-data" class="mb-5">
{% csrf_token %}
{{ form|crispy }}

View file

@ -16,5 +16,6 @@ urlpatterns = [
path("accounts/<int:user_id>", views.user_detail, name="user_detail"),
path('accounts/login/', auth_views.LoginView.as_view(template_name='registration/login.html')),
path('accounts/profile/', auth_views.LoginView.as_view(template_name='user/detail.html'), name='user_profile'),
path('accounts/register/', views.register_user, name='register_user'),
path('accounts/', include('django.contrib.auth.urls')),
]

View file

@ -0,0 +1,16 @@
from geopy.distance import distance
import os
from pandas import read_pickle
path = os.path.dirname(os.path.abspath(__file__))
df = read_pickle(os.path.join(path, 'plz.pkl'))
def calculate_distance(zip_1, zip_2):
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)
return None if dist > 400 else dist

Binary file not shown.

View file

@ -1,12 +1,13 @@
from django.shortcuts import render, get_object_or_404, redirect
from django.utils import timezone
from django.contrib import messages
from django.contrib.auth import login
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.http import HttpResponseForbidden
from .forms import CreateOfferForm
from .models import Offer, Wish
from .forms import CreateOfferForm, RegistrationForm
from .models import PflaenzliUser, Offer, Wish
from .mail import send_offer_email
from .upload import generate_unique_filename
@ -35,7 +36,7 @@ def create_offer(request):
else:
form = CreateOfferForm()
return render(request, "basic_form.html", {"form": form, "button_label": "Create"})
return render(request, "basic_form.html", {"form": form, "button_label": "Create", "title": "Create Offer"})
@ login_required
@ -74,7 +75,7 @@ def offer_edit(request, offer_id):
else:
form = CreateOfferForm(instance=offer)
return render(request, "basic_form.html", {"form": form, "button_label": "Update"})
return render(request, "basic_form.html", {"form": form, "button_label": "Update", "title": "Edit Offer"})
@login_required
@ -93,8 +94,21 @@ def offer_trade(request, offer_id):
def user_detail(request, user_id):
user = get_object_or_404(User, id=user_id)
user = get_object_or_404(PflaenzliUser, id=user_id)
offers = Offer.objects.filter(user=user_id)
wishes = Wish.objects.filter(user=user_id)
return render(request, "user/public.html", {"user": user, "offers": offers, "wishes": wishes})
def register_user(request):
if request.method == "POST":
form = RegistrationForm(request.POST)
if form.is_valid():
user = form.save()
login(request, user)
return redirect("index")
else:
form = RegistrationForm()
return render(request, "basic_form.html", {"form": form, "button_label": "Register", "title": "Registeration"})

View file

@ -12,6 +12,12 @@ https://docs.djangoproject.com/en/4.1/ref/settings/
import os
from pathlib import Path
from dotenv import load_dotenv
# Parse .env
load_dotenv()
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
@ -42,6 +48,7 @@ INSTALLED_APPS = [
"fontawesomefree",
"crispy_forms",
"crispy_bootstrap5",
"friendly_captcha",
]
MIDDLEWARE = [
@ -116,6 +123,7 @@ AUTH_PASSWORD_VALIDATORS = [
},
]
AUTH_USER_MODEL = 'pflaenzli.PflaenzliUser'
LOGOUT_REDIRECT_URL = "/"
# Internationalization
@ -145,11 +153,19 @@ DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
CRISPY_TEMPLATE_PACK = 'bootstrap5'
if DEBUG:
# Mailhog configuration
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = '0.0.0.0'
EMAIL_PORT = 1025
EMAIL_HOST_USER = ''
EMAIL_HOST_PASSWORD = ''
EMAIL_USE_TLS = False
# Email Settings
# SMTP Server
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = os.getenv('SMTP_HOST')
EMAIL_PORT = os.getenv('SMTP_PORT')
EMAIL_HOST_USER = os.getenv('SMTP_USER')
EMAIL_HOST_PASSWORD = os.getenv('SMTP_PASSWORD')
EMAIL_USE_SSL = True
# Friendly Captcha setting
FRC_CAPTCHA_SECRET = os.getenv('FRC_SECRET')
FRC_CAPTCHA_SITE_KEY = os.getenv('FRC_SITEKEY')
FRC_CAPTCHA_VERIFICATION_URL = 'https://api.friendlycaptcha.com/api/v1/siteverify'

View file

@ -3,7 +3,9 @@ Django==4.1.7
django-bootstrap5==22.2
django-crispy-forms==2.0
django-jquery==3.1.0
geopy==2.3.0
gunicorn==20.1.0
fontawesomefree==6.3.0
Pillow==9.4.0
psycopg2-binary==2.9.5
python-dotenv==1.0.0