diff --git a/.gitignore b/.gitignore
index f2d17b8..306b0ca 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,6 @@
+.env
+
rc_hip/static
rc_hip/static/**
diff --git a/cspell.json b/cspell.json
index a821b93..f3042a7 100644
--- a/cspell.json
+++ b/cspell.json
@@ -6,8 +6,8 @@
"words": [
"Reparateur",
"Reparateure",
- "reparateur_id",
- "organisator_id",
+ "reparateur",
+ "organisator",
"Reparateurs",
"repapp"
],
diff --git a/env.example b/env.example
new file mode 100644
index 0000000..92c7c23
--- /dev/null
+++ b/env.example
@@ -0,0 +1,7 @@
+
+export DJANGO_EMAIL_HOST=""
+export DJANGO_EMAIL_PORT="25"
+export DJANGO_EMAIL_HOST_USER=""
+export DJANGO_EMAIL_HOST_PASSWORD=""
+export DJANGO_EMAIL_USE_TLS="true"
+export DJANGO_SENDER_ADDRESS=""
diff --git a/rc_hip/rc_hip/settings.py b/rc_hip/rc_hip/settings.py
index 4bc1163..c695e11 100644
--- a/rc_hip/rc_hip/settings.py
+++ b/rc_hip/rc_hip/settings.py
@@ -143,3 +143,9 @@ CSRF_TRUSTED_ORIGINS = [
'https://repapp.rc-hip.de',
'http://127.0.0.1:8020'
]
+
+EMAIL_HOST = os.getenv("DJANGO_EMAIL_HOST", '')
+EMAIL_PORT = (int)(os.getenv("DJANGO_EMAIL_PORT", '25'))
+EMAIL_HOST_USER = os.getenv("DJANGO_EMAIL_HOST_USER", None)
+EMAIL_HOST_PASSWORD = os.getenv("DJANGO_EMAIL_HOST_PASSWORD", None)
+EMAIL_USE_TLS = os.getenv("DJANGO_EMAIL_USE_TLS", 'true') in ('true', '1', 't')
diff --git a/rc_hip/repapp/migrations/0011_rename_cafe_id_appointment_cafe_and_more.py b/rc_hip/repapp/migrations/0011_rename_cafe_id_appointment_cafe_and_more.py
new file mode 100644
index 0000000..2ecef41
--- /dev/null
+++ b/rc_hip/repapp/migrations/0011_rename_cafe_id_appointment_cafe_and_more.py
@@ -0,0 +1,80 @@
+# Generated by Django 4.2 on 2023-04-20 18:43
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('repapp', '0010_candidate'),
+ ]
+
+ operations = [
+ migrations.RenameField(
+ model_name='appointment',
+ old_name='cafe_id',
+ new_name='cafe',
+ ),
+ migrations.RenameField(
+ model_name='appointment',
+ old_name='device_id',
+ new_name='device',
+ ),
+ migrations.RenameField(
+ model_name='appointment',
+ old_name='reparateur_id',
+ new_name='reparateur',
+ ),
+ migrations.RenameField(
+ model_name='candidate',
+ old_name='cafe_id',
+ new_name='cafe',
+ ),
+ migrations.RenameField(
+ model_name='candidate',
+ old_name='device_id',
+ new_name='device',
+ ),
+ migrations.RenameField(
+ model_name='device',
+ old_name='guest_id',
+ new_name='guest',
+ ),
+ migrations.RenameField(
+ model_name='question',
+ old_name='device_id',
+ new_name='device',
+ ),
+ migrations.RenameField(
+ model_name='question',
+ old_name='organisator_id',
+ new_name='organisator',
+ ),
+ migrations.RenameField(
+ model_name='question',
+ old_name='reparateur_id',
+ new_name='reparateur',
+ ),
+ migrations.AddField(
+ model_name='device',
+ name='cafe',
+ field=models.ForeignKey(default=2, on_delete=django.db.models.deletion.CASCADE, to='repapp.cafe', verbose_name='Repair-Café'),
+ preserve_default=False,
+ ),
+ migrations.AddField(
+ model_name='device',
+ name='confirmed',
+ field=models.BooleanField(default=False, verbose_name='Bestätigt'),
+ ),
+ migrations.AddField(
+ model_name='device',
+ name='secret',
+ field=models.CharField(default='TEST12345', max_length=200, verbose_name='Bestätigungscode'),
+ ),
+ migrations.AddField(
+ model_name='guest',
+ name='confirmed',
+ field=models.BooleanField(default=False, verbose_name='Bestätigt'),
+ ),
+ ]
diff --git a/rc_hip/repapp/models.py b/rc_hip/repapp/models.py
index 5775a44..35ef4dc 100644
--- a/rc_hip/repapp/models.py
+++ b/rc_hip/repapp/models.py
@@ -60,6 +60,7 @@ class Guest(models.Model):
phone = models.CharField(max_length=200, verbose_name=_("Telefonnummer"))
residence = models.CharField(max_length=200, verbose_name=_("Wohnort"))
mail = models.CharField(max_length=200, verbose_name=_("eMail"))
+ confirmed = models.BooleanField(verbose_name=_("Bestätigt"), default=False)
class Meta:
verbose_name = _('Gast')
@@ -74,11 +75,16 @@ class Device(models.Model):
A Device is a broken device owned by a guest which shall be repaired during a Repair-Café.
"""
identifier = models.CharField(max_length=200, verbose_name=_("ID"))
+ secret = models.CharField(
+ max_length=200, verbose_name=_("Bestätigungscode"), default="TEST12345")
device = models.CharField(max_length=200, verbose_name=_("Gerät"))
error = models.TextField(verbose_name=_("Fehler"))
follow_up = models.BooleanField(verbose_name=_("Folgetermin"))
- guest_id = models.ForeignKey(
+ confirmed = models.BooleanField(verbose_name=_("Bestätigt"), default=False)
+ guest = models.ForeignKey(
Guest, on_delete=models.CASCADE, null=True, verbose_name=_("Gast"))
+ cafe = models.ForeignKey(
+ Cafe, on_delete=models.CASCADE, null=False, verbose_name=_("Repair-Café"))
class Meta:
verbose_name = _('Gerät')
@@ -95,11 +101,11 @@ class Appointment(models.Model):
"""
time = models.TimeField(verbose_name=_("Zeit"))
confirmed = models.BooleanField(verbose_name=_("bestätigt"))
- cafe_id = models.ForeignKey(
+ cafe = models.ForeignKey(
Cafe, on_delete=models.CASCADE, verbose_name=_("Repair-Café"))
- reparateur_id = models.ForeignKey(
+ reparateur = models.ForeignKey(
Reparateur, on_delete=models.CASCADE, verbose_name=_("Reparateur"))
- device_id = models.ForeignKey(
+ device = models.ForeignKey(
Device, on_delete=models.CASCADE, null=True, verbose_name=_("Gerät"))
class Meta:
@@ -107,7 +113,7 @@ class Appointment(models.Model):
verbose_name_plural = _('Termine')
def __str__(self):
- return f'Termin {self.cafe_id.event_date} {self.time} für Gerät {self.device_id.device}'
+ return f'Termin {self.cafe.event_date} {self.time} für Gerät {self.device.device}'
class Candidate(models.Model):
@@ -116,9 +122,9 @@ class Candidate(models.Model):
or pre-assigned Reparateur.
"""
confirmed = models.BooleanField(verbose_name=_("bestätigt"))
- cafe_id = models.ForeignKey(
+ cafe = models.ForeignKey(
Cafe, on_delete=models.CASCADE, verbose_name=_("Repair-Café"))
- device_id = models.ForeignKey(
+ device = models.ForeignKey(
Device, on_delete=models.CASCADE, null=True, verbose_name=_("Gerät"))
class Meta:
@@ -126,7 +132,7 @@ class Candidate(models.Model):
verbose_name_plural = _('Kandidaten')
def __str__(self):
- return f'Kandidat {self.cafe_id.event_date} für Gerät {self.device_id.device}'
+ return f'Kandidat {self.cafe.event_date} für Gerät {self.device.device}'
class Question(models.Model):
@@ -136,11 +142,11 @@ class Question(models.Model):
question = models.TextField(verbose_name=_("Frage"))
answer = models.TextField(verbose_name=_("Antwort"))
date = models.DateField(verbose_name=_("Erstellungsdatum"))
- organisator_id = models.ForeignKey(
+ organisator = models.ForeignKey(
Organisator, on_delete=models.CASCADE, null=True, verbose_name=_("Organisator"))
- reparateur_id = models.ForeignKey(
+ reparateur = models.ForeignKey(
Reparateur, on_delete=models.CASCADE, null=True, verbose_name=_("Reparateur"))
- device_id = models.ForeignKey(
+ device = models.ForeignKey(
Device, on_delete=models.CASCADE, verbose_name=_("Gerät"))
class Meta:
@@ -148,4 +154,4 @@ class Question(models.Model):
verbose_name_plural = _('Fragen')
def __str__(self):
- return f'Frage vom {self.date} zum Gerät {self.device_id.device}'
+ return f'Frage vom {self.date} zum Gerät {self.device.device}'
diff --git a/rc_hip/repapp/templates/repapp/base.html b/rc_hip/repapp/templates/repapp/base.html
index 7d18e85..115c1d2 100644
--- a/rc_hip/repapp/templates/repapp/base.html
+++ b/rc_hip/repapp/templates/repapp/base.html
@@ -2,27 +2,33 @@
-
-
-
- RepApp{% block title %}{% endblock title %}
+
+
+
+ RepApp
+ {% block title %}
+ {% endblock title %}
+
-
{% block content %} {% endblock %}
+
+ {% if messages %}
+ {% for message in messages %}
+
{{ message }}
+ {% endfor %}
+ {% endif %}
+ {% block content %}
+ {% endblock content %}
+
diff --git a/rc_hip/repapp/templates/repapp/device_view.html b/rc_hip/repapp/templates/repapp/device_view.html
new file mode 100644
index 0000000..e5c67f7
--- /dev/null
+++ b/rc_hip/repapp/templates/repapp/device_view.html
@@ -0,0 +1,18 @@
+{% extends "repapp/base.html" %}
+{% block title %}
+ - {{ device.device }}
+{% endblock title %}
+{% block content %}
+ Gerät: {{ device.device }}
+
+ Fehler:
+ {{ device.error }}
+
+ Folgetermin:
+ {% if device.follow_up %}
+ Ja
+ {% else %}
+ Nein
+ {% endif %}
+
+{% endblock content %}
diff --git a/rc_hip/repapp/templates/repapp/mail/mail_register_device_html.html b/rc_hip/repapp/templates/repapp/mail/mail_register_device_html.html
new file mode 100644
index 0000000..69e5b75
--- /dev/null
+++ b/rc_hip/repapp/templates/repapp/mail/mail_register_device_html.html
@@ -0,0 +1,20 @@
+
+
+
+ Hallo {{ guest.name }},
+
+ Ihre Anmeldung von {{ device.device }} für das Repair-Café am {{ cafe.event_date|date }}
+ ist bei uns angekommen.
+
+
+ Bitte bestätigen sie ihre Anmeldung in dem Sie
+ hier
+ klicken.
+
+
+ Viele Grüße,
+
+ Ihr Repair-Café
+
+
+
diff --git a/rc_hip/repapp/templates/repapp/mail/mail_register_device_subject.html b/rc_hip/repapp/templates/repapp/mail/mail_register_device_subject.html
new file mode 100644
index 0000000..7268866
--- /dev/null
+++ b/rc_hip/repapp/templates/repapp/mail/mail_register_device_subject.html
@@ -0,0 +1 @@
+Anmeldung von {{ device.device }} für das Repair-Café am {{ cafe.event_date|date }}
diff --git a/rc_hip/repapp/templates/repapp/mail/mail_register_device_text.html b/rc_hip/repapp/templates/repapp/mail/mail_register_device_text.html
new file mode 100644
index 0000000..053c2f9
--- /dev/null
+++ b/rc_hip/repapp/templates/repapp/mail/mail_register_device_text.html
@@ -0,0 +1,5 @@
+Hallo {{ guest.name }},
+Ihre Anmeldung von {{ device.device }} für das Repair-Café am {{ cafe.event_date|date }} ist bei uns angekommen.
+Bitte bestätigen sie ihre Anmeldung in dem Sie https://repapp.rc-hip.de/confirm/{{ device.identifier }}/code/{{ device.secret }} in ihrem Web-Browser aufrufen.
+Viele Grüße,
+Ihr Repair-Café
diff --git a/rc_hip/repapp/templates/repapp/mail/notice_new_device.html b/rc_hip/repapp/templates/repapp/mail/notice_new_device.html
new file mode 100644
index 0000000..a0dadfb
--- /dev/null
+++ b/rc_hip/repapp/templates/repapp/mail/notice_new_device.html
@@ -0,0 +1,3 @@
+Ein neues Gerät wurde für das Repair-Café am {{ cafe.event_date|date }} angemeldet!
+Gerät: {{ device.device }}
+Gast: {{ guest.name }} ({{ guest.mail }})
diff --git a/rc_hip/repapp/templates/repapp/register_device_confirm.html b/rc_hip/repapp/templates/repapp/register_device_confirm.html
new file mode 100644
index 0000000..e181974
--- /dev/null
+++ b/rc_hip/repapp/templates/repapp/register_device_confirm.html
@@ -0,0 +1,12 @@
+{% extends "repapp/base.html" %}
+{% block title %}
+{% endblock title %}
+{% block content %}
+ Anmeldung abgeschlossen
+
+ Ihr Gerät ist erfolgreich angemeldet!
+
+ Wir versuchen für sie einen Termin bei einem geeigneten Reparateur zu finden.
+ Wenn wir einen Termin gefunden haben werden Sie per eMail benachrichtigt.
+
+{% endblock content %}
diff --git a/rc_hip/repapp/urls.py b/rc_hip/repapp/urls.py
index 391fe0e..3b7f444 100644
--- a/rc_hip/repapp/urls.py
+++ b/rc_hip/repapp/urls.py
@@ -8,15 +8,19 @@ from . import views
urlpatterns = [
path("", views.IndexView.as_view(), name="index"),
path(
- "cafe//",
+ "cafe//",
views.RegisterDeviceFormView.as_view(),
name="register_device"
),
path(
- "cafe//device//",
+ "cafe//device//",
views.RegisterGuestFormView.as_view(),
name="register_guest"
),
- path("cafe//device//guest/",
+ path("cafe//device//guest/",
views.register_device_final, name="register_device_final"),
+ path("confirm//code/",
+ views.register_device_confirm, name="register_device_final"),
+ path("device/",
+ views.device_view, name="view_device"),
]
diff --git a/rc_hip/repapp/views.py b/rc_hip/repapp/views.py
index c6271c8..23d944f 100644
--- a/rc_hip/repapp/views.py
+++ b/rc_hip/repapp/views.py
@@ -2,12 +2,17 @@
Views of RepApp.
"""
import datetime
+import os
+import random
from hashlib import sha256
from django.views import generic
from django.urls import reverse_lazy
from django.shortcuts import get_object_or_404, render
-from django.http import HttpResponseRedirect
-from .models import Cafe, Device, Guest
+from django.http import HttpResponseRedirect, Http404
+from django.contrib import messages
+from django.core.mail import send_mail
+from django.template.loader import render_to_string
+from .models import Cafe, Device, Guest, Organisator
from .forms import RegisterDevice, RegisterGuest
@@ -18,6 +23,8 @@ class IndexView(generic.ListView):
template_name = "repapp/index.html"
def get_queryset(self):
+ messages.add_message(self.request, messages.INFO,
+ 'Dies ist eine Test. Leider können sie sich hier noch nicht anmelden.')
return Cafe.objects.filter(event_date__gte=datetime.date.today())
@@ -26,44 +33,50 @@ class RegisterDeviceFormView(generic.edit.FormView):
form_class = RegisterDevice
def form_valid(self, form):
- cafe_id = self.kwargs['cafe_id']
- cafe = get_object_or_404(Cafe, pk=cafe_id)
+ cafe = self.kwargs['cafe']
+ cafe = get_object_or_404(Cafe, pk=cafe)
mail = form.cleaned_data['mail']
device = form.cleaned_data['device']
identifier = sha256(
f'{device}{mail}{datetime.datetime.now()}'.encode('utf-8')
).hexdigest()
+ secret = sha256(
+ f'{device}{mail}{datetime.datetime.now()}{random.randint(0, 999999)}'.encode(
+ 'utf-8')
+ ).hexdigest()
device = Device(
identifier=identifier,
+ secret=secret,
device=device,
error=form.cleaned_data['error'],
follow_up=form.cleaned_data['follow_up'],
+ cafe=cafe,
)
device.save()
guest = Guest.objects.filter(mail=mail).first()
if guest:
- device.guest_id = guest
+ device.guest = guest
device.save()
return HttpResponseRedirect(
reverse_lazy('register_device_final', kwargs={
- 'cafe_id': cafe.pk,
- 'device_identifier': device.identifier,
- 'guest_identifier': guest.identifier})
+ 'cafe': cafe.pk,
+ 'deviceentifier': device.identifier,
+ 'guestentifier': guest.identifier})
)
else:
return HttpResponseRedirect(
reverse_lazy('register_guest', kwargs={
- 'cafe_id': cafe.pk,
- 'device_identifier': device.identifier,
+ 'cafe': cafe.pk,
+ 'deviceentifier': device.identifier,
'mail': mail})
)
def get_context_data(self, **kwargs):
- cafe_id = self.kwargs['cafe_id']
- cafe = get_object_or_404(Cafe, pk=cafe_id)
+ cafe = self.kwargs['cafe']
+ cafe = get_object_or_404(Cafe, pk=cafe)
context = super(RegisterDeviceFormView, self).get_context_data(
**kwargs
@@ -77,11 +90,11 @@ class RegisterGuestFormView(generic.edit.FormView):
form_class = RegisterGuest
def form_valid(self, form):
- cafe_id = self.kwargs['cafe_id']
- cafe = get_object_or_404(Cafe, pk=cafe_id)
+ cafe = self.kwargs['cafe']
+ cafe = get_object_or_404(Cafe, pk=cafe)
- device_identifier = self.kwargs['device_identifier']
- device = get_object_or_404(Device, identifier=device_identifier)
+ deviceentifier = self.kwargs['deviceentifier']
+ device = get_object_or_404(Device, identifier=deviceentifier)
name = form.cleaned_data['name']
residence = form.cleaned_data['residence']
@@ -97,22 +110,22 @@ class RegisterGuestFormView(generic.edit.FormView):
)
guest.save()
- device.guest_id = guest
+ device.guest = guest
device.save()
return HttpResponseRedirect(
reverse_lazy('register_device_final', kwargs={
- 'cafe_id': cafe.pk,
- 'device_identifier': device.identifier,
- 'guest_identifier': guest.identifier})
+ 'cafe': cafe.pk,
+ 'deviceentifier': device.identifier,
+ 'guestentifier': guest.identifier})
)
def get_context_data(self, **kwargs):
- cafe_id = self.kwargs['cafe_id']
- cafe = get_object_or_404(Cafe, pk=cafe_id)
+ cafe = self.kwargs['cafe']
+ cafe = get_object_or_404(Cafe, pk=cafe)
- device_identifier = self.kwargs['device_identifier']
- device = get_object_or_404(Device, identifier=device_identifier)
+ deviceentifier = self.kwargs['deviceentifier']
+ device = get_object_or_404(Device, identifier=deviceentifier)
context = super(RegisterGuestFormView, self).get_context_data(
**kwargs
@@ -124,15 +137,92 @@ class RegisterGuestFormView(generic.edit.FormView):
return context
-def register_device_final(request, cafe_id, device_identifier, guest_identifier):
- cafe = get_object_or_404(Cafe, pk=cafe_id)
- device = get_object_or_404(Device, identifier=device_identifier)
- guest = get_object_or_404(Guest, identifier=guest_identifier)
- print(cafe)
- print(device)
- print(guest)
+def register_device_final(request, cafe, deviceentifier, guestentifier):
+ cafe = get_object_or_404(Cafe, pk=cafe)
+ device = get_object_or_404(Device, identifier=deviceentifier)
+ guest = get_object_or_404(Guest, identifier=guestentifier)
+
+ subject = render_to_string('repapp/mail/mail_register_device_subject.html', {
+ 'guest': guest,
+ 'device': device,
+ 'cafe': cafe,
+ }).replace('\n', '')
+ text = render_to_string('repapp/mail/mail_register_device_text.html', {
+ 'guest': guest,
+ 'device': device,
+ 'cafe': cafe,
+ })
+ html = render_to_string('repapp/mail/mail_register_device_html.html', {
+ 'guest': guest,
+ 'device': device,
+ 'cafe': cafe,
+ })
+
+ ok = send_mail(
+ subject=subject,
+ message=text,
+ from_email=os.getenv("DJANGO_SENDER_ADDRESS", ""),
+ recipient_list=[f"{guest.mail}"],
+ fail_silently=True,
+ html_message=html
+ )
+
+ if ok > 0:
+ messages.add_message(request, messages.INFO,
+ 'Die Bestätigungs-eMail wurde erfolgreich gesendet.')
+ else:
+ messages.add_message(request, messages.ERROR,
+ 'Fehler beim senden der Bestätigungs-eMail!')
+
return render(
request,
"repapp/register_device_final.html",
{},
)
+
+
+def register_device_confirm(request, deviceentifier, device_secret):
+ device = get_object_or_404(Device, identifier=deviceentifier)
+ if not device.guest:
+ raise Http404('Unknown guest!')
+
+ device.confirmed = True
+ device.save()
+
+ if not device.guest.confirmed:
+ device.guest.confirmed = True
+ device.guest.save()
+
+ text = render_to_string('repapp/mail/notice_new_device.html', {
+ 'guest': device.guest,
+ 'device': device,
+ 'cafe': device.cafe,
+ })
+
+ organizers = []
+ for organizer in Organisator.objects.all():
+ organizers.append(organizer.mail)
+
+ send_mail(
+ subject=f"Neues Gerät { device.device }",
+ message=text,
+ from_email=os.getenv("DJANGO_SENDER_ADDRESS", ""),
+ recipient_list=organizers,
+ fail_silently=True
+ )
+
+ return render(
+ request,
+ "repapp/register_device_confirm.html",
+ {"device": device, "guest": device.guest, "cafe": device.cafe},
+ )
+
+
+def device_view(request, deviceentifier):
+ device = get_object_or_404(Device, identifier=deviceentifier)
+
+ return render(
+ request,
+ "repapp/device_view.html",
+ {"device": device},
+ )