design, rework flow, extend device

This commit is contained in:
Tom Irgang 2023-04-21 18:24:31 +02:00
parent 673be80284
commit 9c69dba56d
17 changed files with 712 additions and 303 deletions

View file

@ -63,6 +63,7 @@ Attribute:
- Datum: Datum der Veranstaltung
- Ort: Beschreibung wo das Repair-Café stattfindet
- Adresse: Adresse an der das Repair-Café stattfindet
Annahmen:
@ -102,7 +103,6 @@ Attribute:
- Telefon: Festnetz oder Mobilnummer des Gastes
- Wohnort: Wohnort des Gastes
- Mail: eMail-Adresse des Gastes für Benachrichtigungen
- Bestätigt: Ein boolesches Flag das anzeigt ob der Gast seine eMail-Adresse bestätigt hat.
Technische Attribute:
@ -115,15 +115,18 @@ Ein Gerät ist ein defekter Gegenstand der im Rahmen eines Repair-Cafés reparie
Attribute:
- Gerät: Bezeichnung des Geräts
- Hersteller: Hersteller des Geräts
- Fehler: Beschreibung des Defekts
- Folgetermin: Boolesches Flag das anzeigt ob es sich um einen Folgetermin handelt.
- Bestätigt: Ein boolesches Flag das anzeigt ob der Gast die Greäteanmeldung bestätigt hat.
- Foto vom Gerät
- Foto vom Typenschild
- Bestätigung gesendet?: Ein boolesches Flag das anzeigt ob eine eMail als Anmeldebestätigung gesendet wurde.
Technische Attribute:
- Identifier: SHA256 Hash aus Gerät + Gast + Timestamp
- Geheimnis: SHA256 Hash aus Gerät + Gast + Timestamp + Zufallszahl
- Gast: Referenz zum Gast der das Gerät besitzt
- Cafe: Referenz zum Repair-Café für das diese Gerät angemeldet ist
### Termin
@ -192,17 +195,19 @@ URL: cafe/int:cafe/
Name: register_device
Diese Seite zeigt das Formular zum anmelden der Geräte.
Das Formular hat die Felder `eMail-Adresse`, `Gerätebezeichnung`, `Fehlerbeschreibung`, eine Kontrollkästchen `Folgetermin` und einen Knopf `Absenden` zum senden des Formulars.
Das Formular hat die Felder `eMail-Adresse`, `Art des Geräts`, `Hersteller & Modell/Typ`, `Fehlerbeschreibung`, `Foto vom Gerät`, `Foto vom Typenschild`, ein Kontrollkästchen `Folgetermin`, ein Kontrollkästchen `Informationen zur Reparaturabwicklung`, ein Kontrollkästchen `Datenschutz` und einen Knopf `Absenden` zum senden des Formulars.
Das Repair-Café zu dem die Anmeldung gehört ist über eine ID in der URL der Seite festgelegt.
Die eMail-Benachrichtigung and die Organisatoren und die Bestätigung an den Gast wird beim absenden des Formulars gesendet, falls die eMail Adresse zu einem bekannten Gast gehört.
#### Sicherheit
Die Seite ist ohne Zugangsbeschränkung oder Anmeldung erreichbar.
### Gast anmelden (S3)
URL: cafe/int:cafe/device/str:deviceentifier/str:mail/
URL: cafe/int:cafe/device/str:device_identifier/mail/str:mail/
Name: register_guest
@ -211,37 +216,20 @@ Das Formular hat die Felder `Name`, `Telefon`, `Wohnort` und einen Knopf `Absend
Die eMail-Adresse und das Gerät zu dem die Gast-Kontaktdaten gehören ist über IDs in der URL der Seite festgelegt.
#### Sicherheit
Die Seite ist ohne Zugangsbeschränkung oder Anmeldung erreichbar.
### Bestätigung gesendet (S4)
URL: cafe/int:cafe/device/str:deviceentifier/guest/str:guestentifier/
Name: register_device_final
Die Seite Bestätigung gesendet zeigt einen Hinweis dass eine eMail gesendet wurde und die Anmeldung des Gerätes durch einen Klick auf einen Link in der eMail bestätigt werden muss.
Die eMail-Bestätigung wird während des Aufrufs dieser Seite gesendet.
Das Repair-Café, der Gast und das Gerät sind über IDs in der URL der Seite festgelegt.
Die eMail-Benachrichtigung and die Organisatoren und die Bestätigung an den Gast wird beim absenden des Formulars gesendet.
#### Sicherheit
Die Seite ist ohne Zugangsbeschränkung oder Anmeldung erreichbar.
### Anmeldung bestätigt (S7)
URL: confirm/str:deviceentifier/code/str:device_secret/
URL: cafe/int:cafe/device/str:device_identifier/confirm/
Name: register_device_confirm
Name: register_device_final
Die Seite Anmeldung bestätigt zeigt einen Hinweis dass die Anmeldung des Gerätes erfolgreich abgeschlossen wurde.
Die eMail-Benachrichtigung and die Organisatoren wird während des Aufrufs dieser Seite gesendet.
Die Geräte ID ist in der URL der Seite festgelegt.
#### Sicherheit
@ -250,11 +238,11 @@ Die Seite ist ohne Zugangsbeschränkung oder Anmeldung erreichbar.
### Geräte Detailseite (S5)
URL: device/str:deviceentifier/
URL: device/str:device_identifier/
Name: view_device
Die Geräte Detailseite zeigt die Informationen `Gerätebezeichnung`, `Fehlerbeschreibung` und `Folgetermin` an.
Die Geräte Detailseite zeigt die Informationen `Art des Geräts`, `Hersteller & Modell/Typ`, `Fehlerbeschreibung`, `Foto vom Gerät`, `Foto vom Typenschild` und `Folgetermin` an.
Das Gerät ist über IDs in der URL der Seite festgelegt.
@ -283,16 +271,16 @@ Die Seite enthält persönliche Daten und ist nur nach Anmeldung erreichbar.
Als neuer Gast der ein defekten Geräte anmelden möchte,
- gehe ich auf die Seite (S1) der Repair-Café Termine. Dort klicke ich auf `Gerät anmelden` bei dem richtigen Repair-Café Termin. Dieser Klick bringt mich auf die nächste Seite.
- Die nächste Seite (S2) enthält ein Formular mit den Feldern `eMail-Adresse`, `Gerätebezeichnung` und `Fehlerbeschreibung` und eine Kontrollkästchen `Folgetermin`. Nachdem ich die Felder ausgefüllt habe klicke auf auf `Absenden`. Dieser Klick bringt mich auf die nächste Seite.
- Die nächste Seite (S2) enthält ein Formular mit den Feldern `eMail-Adresse`, `Art des Geräts`, `Hersteller & Modell/Typ`, `Fehlerbeschreibung`, `Foto vom Gerät`, `Foto vom Typenschild`, ein Kontrollkästchen `Folgetermin`, ein Kontrollkästchen `Informationen zur Reparaturabwicklung` une ein Kontrollkästchen `Datenschutz`. Nachdem ich die Felder ausgefüllt habe klicke auf auf `Absenden`. Dieser Klick bringt mich auf die nächste Seite.
- Die nächste Seite (S3) enthält ein Formular mit den Feldern `Name`, `Telefon` und `Wohnort`. Nachdem ich die Felder ausgefüllt habe klicke auf auf `Absenden`. Dieser Klick bringt mich auf die nächste Seite.
- Die nächste Seite (S4) weißt mich darauf hin dass eine eMail gesendet wurde und ich die Anmeldung über einen Klick in der eMail bestätigen muss.
- In meinem eMail Posteingang finde ich eine Bestätigung der Geräteanmeldung die ebenfalls einen Link enthält um die Anmeldung zu bestätigen. Ein Klick auf diesen Link bringt mich auf die Seite Anmeldung bestätigt (S7).
- Die nächste Seite (S5) bestätigt mir die Anmeldung meines Gerätes.
- In meinem eMail Posteingang finde ich ebenfalls eine Bestätigung der Geräteanmeldung.
#### Variante: Bekannter Gast (existierende eMail Adresse)
Als bekannter Gast der ein defekten Geräte anmelden möchte,
- gehe ich auf die Seite (S1) der Repair-Café Termine. Dort klicke ich auf `Gerät anmelden` bei dem richtigen Repair-Café Termin. Dieser Klick bringt mich auf die nächste Seite.
- Die nächste Seite (S2) enthält ein Formular mit den Feldern `eMail-Adresse`, `Gerätebezeichnung` und `Fehlerbeschreibung` und eine Kontrollkästchen `Folgetermin`. Nachdem ich die Felder ausgefüllt habe klicke auf auf `Absenden`. Dieser Klick bringt mich auf die nächste Seite.
- Die nächste Seite (S4) weißt mich darauf hin dass eine eMail gesendet wurde und ich die Anmeldung über einen Klick in der eMail bestätigen muss.
- In meinem eMail Posteingang finde ich eine Bestätigung der Geräteanmeldung die ebenfalls einen Link enthält um die Anmeldung zu bestätigen. Ein Klick auf diesen Link bringt mich auf die Seite Anmeldung bestätigt (S7).
- Die nächste Seite (S2) enthält ein Formular mit den Feldern `eMail-Adresse`, `Art des Geräts`, `Hersteller & Modell/Typ`, `Fehlerbeschreibung`, `Foto vom Gerät`, `Foto vom Typenschild`, ein Kontrollkästchen `Folgetermin`, ein Kontrollkästchen `Informationen zur Reparaturabwicklung` une ein Kontrollkästchen `Datenschutz`. Nachdem ich die Felder ausgefüllt habe klicke auf auf `Absenden`. Dieser Klick bringt mich auf die nächste Seite.
- Die nächste Seite (S5) bestätigt mir die Anmeldung meines Gerätes.
- In meinem eMail Posteingang finde ich ebenfalls eine Bestätigung der Geräteanmeldung.

View file

@ -10,4 +10,7 @@ server {
location /static {
root /opt/app/rc_hip;
}
location /media {
root /opt/app/rc_hip;
}
}

View file

@ -17,6 +17,7 @@ Including another URLconf
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("", include("repapp.urls")),
path('admin/', admin.site.urls),

View file

@ -8,9 +8,12 @@ class RegisterDevice(forms.Form):
" zur Anfrage verwendet.",
max_length=200)
device = forms.CharField(
label="Gerätebezeichnung",
help_text="Bitte geben Sie den Hersteller und die"
" Gerätebezeichnung an.",
label="Art des Geräts",
help_text="z.B. Küchenmaschine, Smartphone, Stehlampe, Drucker, ...",
max_length=200
)
manufacturer = forms.CharField(
label="Hersteller & Modell/Typ",
max_length=200
)
error = forms.CharField(
@ -18,12 +21,33 @@ class RegisterDevice(forms.Form):
help_text="Beschreiben Sie hier das Problem ihres Gerätes.",
widget=forms.Textarea
)
device_picture = forms.FileField(
label="Foto vom Gerät ", required=False
)
type_plate_picture = forms.FileField(
label="Foto vom Typenschild ", required=False
)
follow_up = forms.BooleanField(
label="Folgetermin",
help_text="Klicken Sie dieses Kästchen an wenn sie mit diesem"
" Gerät bereits bei einem Repair-Café waren.",
help_text="Kreuzen Sie diese Kästchen an wenn sie mit diesem Gerät bereits bei einem Repair-Café Termin waren.",
required=False
)
confirm_repair = forms.BooleanField(
label="Informationen zur Reparaturabwicklung",
help_text="Ich bin mit den <a href="
"\"https://www.repaircafe-hilpoltstein.de/fileadmin/repaircafe-hilpoltstein/images/"
"repaircafe-hilpoltstein.de/docs/Infos_Reparatur-Abwicklung_v2_2023-02.pdf\">"
"Allgemeinen Bedingungen zur Reparaturabwicklung</a> einverstanden.",
required=True
)
confirm_data = forms.BooleanField(
label="Datenschutz",
help_text=" Ich bin mit der Erhebung, Verarbeitung und Speicherung meiner Daten für "
"die Bearbeitung meiner Anfrage gemäß der "
"<a href=\"https://www.repaircafe-hilpoltstein.de/impressum#c1375\">"
"Datenschutzerklärung</a> einverstanden.",
required=True
)
class RegisterGuest(forms.Form):

View file

@ -0,0 +1,49 @@
# Generated by Django 4.2 on 2023-04-21 15:21
from django.db import migrations, models
import repapp.models
class Migration(migrations.Migration):
dependencies = [
('repapp', '0011_rename_cafe_id_appointment_cafe_and_more'),
]
operations = [
migrations.RemoveField(
model_name='device',
name='secret',
),
migrations.RemoveField(
model_name='guest',
name='confirmed',
),
migrations.AddField(
model_name='cafe',
name='address',
field=models.CharField(default='', max_length=200, verbose_name='Ort'),
preserve_default=False,
),
migrations.AddField(
model_name='device',
name='device_picture',
field=models.FileField(null=True, upload_to=repapp.models.device_directory_path, verbose_name='Foto vom Gerät'),
),
migrations.AddField(
model_name='device',
name='manufacturer',
field=models.CharField(default='', max_length=200, verbose_name='Hersteller'),
preserve_default=False,
),
migrations.AddField(
model_name='device',
name='type_plate_picture',
field=models.FileField(null=True, upload_to=repapp.models.device_directory_path, verbose_name='Foto vom Typenschild'),
),
migrations.AlterField(
model_name='device',
name='confirmed',
field=models.BooleanField(verbose_name='Bestätigung gesendet?'),
),
]

View file

@ -11,6 +11,8 @@ class Cafe(models.Model):
"""
location = models.CharField(
max_length=200, verbose_name=_("Ort"))
address = models.CharField(
max_length=200, verbose_name=_("Adresse"))
event_date = models.DateField(verbose_name=_("Datum"))
class Meta:
@ -60,7 +62,6 @@ 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')
@ -70,21 +71,29 @@ class Guest(models.Model):
return f'Gast {self.name} (eMail: {self.mail})'
def device_directory_path(instance, filename):
return f'device_{instance.identifier}/{filename}'
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"))
device = models.CharField(max_length=200, verbose_name=_("Art des Geräts"))
manufacturer = models.CharField(
max_length=200, verbose_name=_("Hersteller & Modell/Typ"))
error = models.TextField(verbose_name=_("Fehlerbeschreibung"))
follow_up = models.BooleanField(verbose_name=_("Folgetermin"))
confirmed = models.BooleanField(verbose_name=_("Bestätigt"), default=False)
device_picture = models.FileField(
upload_to=device_directory_path, null=True, verbose_name=_("Foto vom Gerät"))
type_plate_picture = models.FileField(
upload_to=device_directory_path, null=True, verbose_name=_("Foto vom Typenschild"))
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é"))
confirmed = models.BooleanField(verbose_name=_("Bestätigung gesendet?"))
class Meta:
verbose_name = _('Gerät')
@ -116,25 +125,6 @@ class Appointment(models.Model):
return f'Termin {self.cafe.event_date} {self.time} für Gerät {self.device.device}'
class Candidate(models.Model):
"""
A Candidate is a match of a broken Device and a Repair-Café without a fixed timeslot
or pre-assigned Reparateur.
"""
confirmed = models.BooleanField(verbose_name=_("bestätigt"))
cafe = models.ForeignKey(
Cafe, on_delete=models.CASCADE, verbose_name=_("Repair-Café"))
device = models.ForeignKey(
Device, on_delete=models.CASCADE, null=True, verbose_name=_("Gerät"))
class Meta:
verbose_name = _('Kandidat')
verbose_name_plural = _('Kandidaten')
def __str__(self):
return f'Kandidat {self.cafe.event_date} für Gerät {self.device.device}'
class Question(models.Model):
"""
A Question is a request for information form a Organisator or a Reparateur for a Device.
@ -155,3 +145,22 @@ class Question(models.Model):
def __str__(self):
return f'Frage vom {self.date} zum Gerät {self.device.device}'
class Candidate(models.Model):
"""
A Candidate is a match of a broken Device and a Repair-Café without a fixed timeslot
or pre-assigned Reparateur.
"""
confirmed = models.BooleanField(verbose_name=_("bestätigt"))
cafe = models.ForeignKey(
Cafe, on_delete=models.CASCADE, verbose_name=_("Repair-Café"))
device = models.ForeignKey(
Device, on_delete=models.CASCADE, null=True, verbose_name=_("Gerät"))
class Meta:
verbose_name = _('Kandidat')
verbose_name_plural = _('Kandidaten')
def __str__(self):
return f'Kandidat {self.cafe.event_date} für Gerät {self.device.device}'

View file

@ -2,6 +2,10 @@
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="description"
content="{% block description %} {% endblock description %}" />
<meta name="keywords"
content="{% block keywords %} {% endblock keywords %}" />
<meta name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<link rel="stylesheet"
@ -15,16 +19,15 @@
{% block title %}
{% endblock title %}
</title>
{% block head %}
{% endblock head %}
</head>
<body>
<div class="container">
<div class="row justify-content-center">
<div class="col-8">
{% if messages %}
{% for message in messages %}
<div class="alert {% if message.tags %}alert-{{ message.tags }}{% else %}alert-primary{% endif %}"
role="alert">{{ message }}</div>
{% endfor %}
{% for message in messages %}<div class="alert alert-primary" role="alert">{{ message }}</div>{% endfor %}
{% endif %}
{% block content %}
{% endblock content %}

View file

@ -5,14 +5,56 @@
{% block content %}
<h1 class="mt-2">Gerät: {{ device.device }}</h1>
<hr class="mt-0 mb-4">
<p>Fehler:</p>
<p>{{ device.error }}</p>
<p>
Folgetermin:
<table class="table">
<tbody>
<tr>
<th scope="row">Art des Geräts</th>
<td>{{ device.device }}</td>
</tr>
<tr>
<th scope="row">Hersteller & Modell/Typ</th>
<td>{{ device.manufacturer }}</td>
</tr>
<tr>
<th scope="row">Fehlerbeschreibung</th>
<td>{{ device.error }}</td>
</tr>
<tr>
<th scope="row">Folgetermin</th>
<td>
{% if device.follow_up %}
Ja
{% else %}
Nein
{% endif %}
</p>
</td>
</tr>
<tr>
<th scope="row">Foto vom Gerät</th>
<td>
{% if device.device_picture %}
<img alt="Foto vom Gerät"
height="100%"
width="50%"
src="/media/{{ device.device_picture }}" />
{% else %}
kein Bild
{% endif %}
</td>
</tr>
<tr>
<th scope="row">Foto vom Typenschild</th>
<td>
{% if device.device_picture %}
<img alt="Foto vom Typenschild"
height="100%"
width="50%"
src="/media/{{ device.type_plate_picture }}" />
{% else %}
kein Bild
{% endif %}
</td>
</tr>
</tbody>
</table>
{% endblock content %}

View file

@ -1,16 +1,321 @@
{% extends "repapp/base.html" %}
{% block title %}
- Repair-Cafés
{% endblock title %}
{% block head %}
<style>
@font-face {
font-family:'Source Sans Pro';
font-style:normal;
font-weight:300;
src:url('https://www.repaircafe-hilpoltstein.de/fileadmin/repaircafe-hilpoltstein/default/templates/fonts/source-sans-pro-v11-latin-300.eot');
/* IE9 Compat Modes */
src:local('Source Sans Pro Light'), local('SourceSansPro-Light'),
url('https://www.repaircafe-hilpoltstein.de/fileadmin/repaircafe-hilpoltstein/default/templates/fonts/source-sans-pro-v11-latin-300.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('https://www.repaircafe-hilpoltstein.de/fileadmin/repaircafe-hilpoltstein/default/templates/fonts/source-sans-pro-v11-latin-300.woff2') format('woff2'), /* Super Modern Browsers */
url('https://www.repaircafe-hilpoltstein.de/fileadmin/repaircafe-hilpoltstein/default/templates/fonts/source-sans-pro-v11-latin-300.woff') format('woff'), /* Modern Browsers */
url('https://www.repaircafe-hilpoltstein.de/fileadmin/repaircafe-hilpoltstein/default/templates/fonts/source-sans-pro-v11-latin-300.ttf') format('truetype'), /* Safari, Android, iOS */
url('https://www.repaircafe-hilpoltstein.de/fileadmin/repaircafe-hilpoltstein/default/templates/fonts/source-sans-pro-v11-latin-300.svg#SourceSansPro') format('svg');/* Legacy iOS */;
}
@font-face {
font-family:'Source Sans Pro';
font-style:normal;
font-weight:400;
src:url('https://www.repaircafe-hilpoltstein.de/fileadmin/repaircafe-hilpoltstein/default/templates/fonts/source-sans-pro-v11-latin-regular.eot');
/* IE9 Compat Modes */
src:local('Source Sans Pro Regular'), local('SourceSansPro-Regular'),
url('https://www.repaircafe-hilpoltstein.de/fileadmin/repaircafe-hilpoltstein/default/templates/fonts/source-sans-pro-v11-latin-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('https://www.repaircafe-hilpoltstein.de/fileadmin/repaircafe-hilpoltstein/default/templates/fonts/source-sans-pro-v11-latin-regular.woff2') format('woff2'), /* Super Modern Browsers */
url('https://www.repaircafe-hilpoltstein.de/fileadmin/repaircafe-hilpoltstein/default/templates/fonts/source-sans-pro-v11-latin-regular.woff') format('woff'), /* Modern Browsers */
url('https://www.repaircafe-hilpoltstein.de/fileadmin/repaircafe-hilpoltstein/default/templates/fonts/source-sans-pro-v11-latin-regular.ttf') format('truetype'), /* Safari, Android, iOS */
url('https://www.repaircafe-hilpoltstein.de/fileadmin/repaircafe-hilpoltstein/default/templates/fonts/source-sans-pro-v11-latin-regular.svg#SourceSansPro') format('svg');/* Legacy iOS */;
}
@font-face {
font-family:'Source Sans Pro';
font-style:normal;
font-weight:700;
src:url('https://www.repaircafe-hilpoltstein.de/fileadmin/repaircafe-hilpoltstein/default/templates/fonts/source-sans-pro-v11-latin-700.eot');
/* IE9 Compat Modes */
src:local('Source Sans Pro Bold'), local('SourceSansPro-Bold'),
url('https://www.repaircafe-hilpoltstein.de/fileadmin/repaircafe-hilpoltstein/default/templates/fonts/source-sans-pro-v11-latin-700.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('https://www.repaircafe-hilpoltstein.de/fileadmin/repaircafe-hilpoltstein/default/templates/fonts/source-sans-pro-v11-latin-700.woff2') format('woff2'), /* Super Modern Browsers */
url('https://www.repaircafe-hilpoltstein.de/fileadmin/repaircafe-hilpoltstein/default/templates/fonts/source-sans-pro-v11-latin-700.woff') format('woff'), /* Modern Browsers */
url('https://www.repaircafe-hilpoltstein.de/fileadmin/repaircafe-hilpoltstein/default/templates/fonts/source-sans-pro-v11-latin-700.ttf') format('truetype'), /* Safari, Android, iOS */
url('https://www.repaircafe-hilpoltstein.de/fileadmin/repaircafe-hilpoltstein/default/templates/fonts/source-sans-pro-v11-latin-700.svg#SourceSansPro') format('svg');/* Legacy iOS */;
}
@media all{
* {
box-sizing: border-box;
}
{% block title %}- Repair-Cafés{% endblock title %}
.frame > :last-child {
margin-bottom: 0;
}
.frame {
position: relative;
padding-top: 2rem;
padding-bottom: 2rem;
}
}
@media (min-width: 576px){
.frame {
padding-top: 2.5rem;
padding-bottom: 2.5rem;
}
}
@media (min-width: 768px){
.frame {
padding-top: 3rem;
padding-bottom: 3rem;
}
}
@media (min-width: 992px){
.frame {
padding-top: 3.5rem;
padding-bottom: 3.5rem;
}
}
@media (min-width: 1200px){
.frame {
padding-top: 3.5rem;
padding-bottom: 3.5rem;
}
}
@media all{
.frame-space-before-none {
margin-top: 0rem;
}
.frame-space-after-none {
margin-bottom: 0rem;
}
}
@media screen and (min-width: 1200px){
.frame {
padding-top: 2.5rem;
padding-bottom: 2.5rem;
}
}
@media all{
.col-md-8 {
position: relative;
width: 100%;
padding-right: 20px;
padding-left: 20px;
}
}
@media (min-width: 768px){
.col-md-8 {
flex: 0 0 66.6666666667%;
max-width: 66.6666666667%;
}
}
@media screen and (min-width: 992px){
.col-md-8 {
padding-right: 40px;
}
}
@media all{
.row {
display: flex;
flex-wrap: wrap;
margin-right: -20px;
margin-left: -20px;
}
.container {
width: 100%;
padding-right: 20px;
padding-left: 20px;
margin-right: auto;
margin-left: auto;
}
}
@media (min-width: 576px){
.container {
max-width: 540px;
}
}
@media (min-width: 768px){
.container {
max-width: 720px;
}
}
@media (min-width: 992px){
.container {
max-width: 960px;
}
}
@media (min-width: 1200px){
.container {
max-width: 1140px;
}
}
@media all{
.section {
overflow: hidden;
}
}
@media screen and (min-width: 1200px){
.section {
padding-top: 30px;
}
}
@media all{
body {
margin: 0;
font-family: "Source Sans Pro", sans-serif;
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #2b2b2b;
text-align: left;
background-color: #fff;
}
html {
font-family: sans-serif;
line-height: 1.15;
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
.news-list-view {
padding-right: 20px;
padding-top: 10px;
}
*,:before,:after {
box-sizing: border-box;
}
:selection {
background-color: darkred;
}
.article {
padding: 30px 0;
border-bottom: 2px solid #cccccc;
}
.article:last-child {
border-bottom: 2px solid transparent;
}
.article.event {
padding: 0 0 30px 0;
}
.dtdate {
font-size: 21px;
color: #FFFFFF;
background: darkred;
padding: 0 5px;
display: inline-block;
}
.article.event .ort {
margin-top: 5px;
font-size: 19px;
}
.teaser-text {
position: relative;
padding-bottom: 30px;
width: 100%;
}
.article.event .teaser-text {
padding-top: 20px;
padding-bottom: 10px;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
strong {
font-weight: bolder;
}
a {
color: darkred;
text-decoration: none;
background-color: transparent;
}
a {
color: darkblue;
border-bottom: 1px solid;
font-style: normal;
}
a:hover {
color: #FFFFFF;
text-decoration: underline;
background: darkred;
}
a:hover {
background: darkblue;
color: #ffffff!important;
}
}
</style>
{% endblock head %}
{% block content %}
<h1 class="mt-2">Repair-Cafés</h1>
<hr class="mt-0 mb-4">
<ul class="list-group">
{% for cafe in object_list %}
<li class="list-group-item">
<a href="{% url 'register_device' cafe.id %}">
<button type="button" class="btn btn-primary">Repair-Café am {{ cafe.event_date|date }}, {{ cafe.location }}</button>
</a>
<div class="news-list-view eventhome snipcss-aSNzE" id="news-container-82">
<div class="rc-date">
<div class="article event rc-date "
itemscope="itemscope"
itemtype="http://schema.org/Article">
<div class="dtdate">
<time itemprop="datePublished" datetime="13.05.2023">
{{ cafe.event_date|date:"l" }}, {{ cafe.event_date|date }}, 14.00 - 17.00 Uhr
</time>
</div>
<div class="ort">
{{ cafe.location }}
<br>
{{ cafe.address }}
</div>
<div class="teaser-text">
<div itemprop="description">
<p>
<strong>
Nur per Voranmeldung.
<a href="{% url 'register_device' cafe.id %}">Zum Anmeldeformular</a>
</strong>
</p>
</div>
</div>
</div>
</div>
</div>
</li>
{% empty %}
<li class="list-group-item">Es gibt keine geplanten Repair-Cafés.</li>

View file

@ -1,20 +1,22 @@
<!DOCTYPE html>
<html lang="de">
<body>
<p>Hallo {{ guest.name }},</p>
<p>Guten Tag,</p>
<p>
Ihre Anmeldung von {{ device.device }} für das Repair-Café am {{ cafe.event_date|date }}
ist bei uns angekommen.
dies ist eine automatische Nachricht, die du erhältst, nachdem du unser Anmeldeformular abgesendet hast und deine Angaben in unserer Datenbank gespeichert wurden.
</p>
<p>
Bitte bestätigen sie ihre Anmeldung in dem Sie
<a href="https://repapp.rc-hip.de/confirm/{{ device.identifier }}/code/{{ device.secret }}">hier</a>
klicken.
Vielen Dank für die Anmeldung des Geräts {{ device.device }} - {{ device.manufacturer }} zur Reparatur im Repair-Café am {{ cafe.event_date|date }}.
</p>
<p>
Viele Grüße,
<br>
Ihr Repair-Café
Die Organisation unserer Repair-Cafés beginnt i.a. einige Tage vor dem jeweiligen Termin. Dann erstellen wir anhand der angemeldeten Geräte und der Spezialgebiete der anwesenden Reparateure einen Zeitplan.
</p>
<p>
Sobald dieser Zeitplan fertig gestellt ist, erhältst du von einem Mitglied unseres Organisationsteams eine Mail. Diese beinhaltet im Normalfall den Reparaturtermin inkl. der Uhrzeit, für die die Reparatur vorgesehen ist. In Ausnahmefällen (z.B. wenn kein Spezialist für die Gerätegruppe anwesend sein wird oder überbucht ist) ist es aber auch möglich, dass wir deinen gewünschten Termin absagen und dir einen Alternativtermin vorschlagen müssen.
</p>
<p>
Falls du in der Zwischenzeit Rückfragen hast oder deinen Wunschtermin doch nicht wahrnehmen kannst, antworte bitte einfach auf diese Nachricht.
</p>
<p>Herzliche Grüße sendet dir das Organisationsteam vom Repair-Café Hilpoltstein!</p>
</body>
</html>

View file

@ -1 +1 @@
Anmeldung von {{ device.device }} für das Repair-Café am {{ cafe.event_date|date }}
Repair-Café Hilpoltstein am {{ cafe.event_date|date }} - Geräteanmeldung

View file

@ -1,5 +1,7 @@
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é
Guten Tag,
dies ist eine automatische Nachricht, die du erhältst, nachdem du unser Anmeldeformular abgesendet hast und deine Angaben in unserer Datenbank gespeichert wurden.
Vielen Dank für die Anmeldung des Geräts {{ device.device }} - {{ device.manufacturer }} zur Reparatur im Repair-Café am {{ cafe.event_date|date }}.
Die Organisation unserer Repair-Cafés beginnt i.a. einige Tage vor dem jeweiligen Termin. Dann erstellen wir anhand der angemeldeten Geräte und der Spezialgebiete der anwesenden Reparateure einen Zeitplan.
Sobald dieser Zeitplan fertig gestellt ist, erhältst du von einem Mitglied unseres Organisationsteams eine Mail. Diese beinhaltet im Normalfall den Reparaturtermin inkl. der Uhrzeit, für die die Reparatur vorgesehen ist. In Ausnahmefällen (z.B. wenn kein Spezialist für die Gerätegruppe anwesend sein wird oder überbucht ist) ist es aber auch möglich, dass wir deinen gewünschten Termin absagen und dir einen Alternativtermin vorschlagen müssen.
Falls du in der Zwischenzeit Rückfragen hast oder deinen Wunschtermin doch nicht wahrnehmen kannst, antworte bitte einfach auf diese Nachricht.
Herzliche Grüße sendet dir das Organisationsteam vom Repair-Café Hilpoltstein!

View file

@ -1,13 +1,14 @@
{% extends "repapp/base.html" %}
{% load crispy_forms_tags %}
{% block title %}- Gerät anmelden{% endblock title %}
{% block title %}
- Gerät anmelden
{% endblock title %}
{% block content %}
<h1 class="mt-2">Gerät anmelden</h1>
<hr class="mt-0 mb-4">
<form action="{% url 'register_device' cafe.id %}" method="post">
<form action="{% url 'register_device' cafe.id %}"
method="post"
enctype="multipart/form-data">
{% csrf_token %}
{{ form|crispy }}
<button type="submit" class="btn btn-primary">Absenden</button>

View file

@ -1,12 +0,0 @@
{% extends "repapp/base.html" %}
{% block title %}
{% endblock title %}
{% block content %}
<h1 class="mt-2">Anmeldung abgeschlossen</h1>
<hr class="mt-0 mb-4">
<p>Ihr Gerät ist erfolgreich angemeldet!</p>
<p>
Wir versuchen für sie einen Termin bei einem geeigneten Reparateur zu finden.
Wenn wir einen Termin gefunden haben werden Sie per eMail benachrichtigt.
</p>
{% endblock content %}

View file

@ -1,12 +1,16 @@
{% extends "repapp/base.html" %}
{% block title %}{% endblock title %}
{% block title %}
{% endblock title %}
{% block content %}
<h1 class="mt-2">Bestätigung ihrer Daten</h1>
<h1 class="mt-2">Anmeldung erfolgreich!</h1>
<hr class="mt-0 mb-4">
<p>
Wir haben ihnen eine eMail mit ihren Daten gesendet. Bitte bestätigen
Sie ihre Anmeldung durch Klick auf den Link in der eMail.
Vielen Dank für die Anmeldung des Geräts {{ device.device }} - {{ device.manufacturer }} zur Reparatur im Repair-Café am {{ cafe.event_date|date }}.
</p>
<p>
Die Organisation unserer Repair-Cafés beginnt i.a. einige Tage vor dem jeweiligen Termin. Dann erstellen wir anhand der angemeldeten Geräte und der Spezialgebiete der anwesenden Reparateure einen Zeitplan.
</p>
<p>
Sobald dieser Zeitplan fertig gestellt ist, erhältst du von einem Mitglied unseres Organisationsteams eine Mail. Diese beinhaltet im Normalfall den Reparaturtermin inkl. der Uhrzeit, für die die Reparatur vorgesehen ist. In Ausnahmefällen (z.B. wenn kein Spezialist für die Gerätegruppe anwesend sein wird oder überbucht ist) ist es aber auch möglich, dass wir deinen gewünschten Termin absagen und dir einen Alternativtermin vorschlagen müssen.
</p>
{% endblock content %}

View file

@ -13,14 +13,12 @@ urlpatterns = [
name="register_device"
),
path(
"cafe/<int:cafe>/device/<str:deviceentifier>/<str:mail>/",
"cafe/<int:cafe>/device/<str:device_identifier>/mail/<str:mail>/",
views.RegisterGuestFormView.as_view(),
name="register_guest"
),
path("cafe/<int:cafe>/device/<str:deviceentifier>/guest/<str:guestentifier>/",
path("cafe/<int:cafe>/device/<str:device_identifier>/confirm/",
views.register_device_final, name="register_device_final"),
path("confirm/<str:deviceentifier>/code/<str:device_secret>/",
views.register_device_confirm, name="register_device_confirm"),
path("device/<str:deviceentifier>/",
path("device/<str:device_identifier>/",
views.device_view, name="view_device"),
]

View file

@ -3,12 +3,11 @@ 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, Http404
from django.http import HttpResponseRedirect
from django.contrib import messages
from django.core.mail import send_mail
from django.template.loader import render_to_string
@ -28,119 +27,24 @@ class IndexView(generic.ListView):
return Cafe.objects.filter(event_date__gte=datetime.date.today())
class RegisterDeviceFormView(generic.edit.FormView):
template_name = "repapp/register_device.html"
form_class = RegisterDevice
def send_confirmation_mails(device, guest, cafe, request):
organizers = []
for organizer in Organisator.objects.all():
organizers.append(organizer.mail)
def form_valid(self, form):
cafe = self.kwargs['cafe']
cafe = get_object_or_404(Cafe, pk=cafe)
text = render_to_string('repapp/mail/notice_new_device.html', {
'guest': device.guest,
'device': device,
'cafe': device.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,
send_mail(
subject=f"Neues Gerät { device.device }",
message=text,
from_email=os.getenv("DJANGO_SENDER_ADDRESS", ""),
recipient_list=organizers,
fail_silently=True
)
device.save()
guest = Guest.objects.filter(mail=mail).first()
if guest:
device.guest = guest
device.save()
return HttpResponseRedirect(
reverse_lazy('register_device_final', kwargs={
'cafe': cafe.pk,
'deviceentifier': device.identifier,
'guestentifier': guest.identifier})
)
else:
return HttpResponseRedirect(
reverse_lazy('register_guest', kwargs={
'cafe': cafe.pk,
'deviceentifier': device.identifier,
'mail': mail})
)
def get_context_data(self, **kwargs):
cafe = self.kwargs['cafe']
cafe = get_object_or_404(Cafe, pk=cafe)
context = super(RegisterDeviceFormView, self).get_context_data(
**kwargs
)
context["cafe"] = cafe
return context
class RegisterGuestFormView(generic.edit.FormView):
template_name = "repapp/register_guest.html"
form_class = RegisterGuest
def form_valid(self, form):
cafe = self.kwargs['cafe']
cafe = get_object_or_404(Cafe, pk=cafe)
deviceentifier = self.kwargs['deviceentifier']
device = get_object_or_404(Device, identifier=deviceentifier)
name = form.cleaned_data['name']
residence = form.cleaned_data['residence']
identifier = sha256(
f'{name}{residence}{datetime.datetime.now()}'.encode('utf-8')
).hexdigest()
guest = Guest(
identifier=identifier,
name=name,
phone=form.cleaned_data['phone'],
residence=residence,
mail=self.kwargs['mail'],
)
guest.save()
device.guest = guest
device.save()
return HttpResponseRedirect(
reverse_lazy('register_device_final', kwargs={
'cafe': cafe.pk,
'deviceentifier': device.identifier,
'guestentifier': guest.identifier})
)
def get_context_data(self, **kwargs):
cafe = self.kwargs['cafe']
cafe = get_object_or_404(Cafe, pk=cafe)
deviceentifier = self.kwargs['deviceentifier']
device = get_object_or_404(Device, identifier=deviceentifier)
context = super(RegisterGuestFormView, self).get_context_data(
**kwargs
)
context["cafe"] = cafe
context["device"] = device
context["mail"] = self.kwargs['mail']
return context
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,
@ -168,58 +72,144 @@ def register_device_final(request, cafe, deviceentifier, guestentifier):
)
if ok > 0:
messages.add_message(request, messages.INFO,
'Die Bestätigungs-eMail wurde erfolgreich gesendet.')
device.confirmed = True
device.save()
else:
messages.add_message(request, messages.ERROR,
'Fehler beim senden der Bestätigungs-eMail!')
class RegisterDeviceFormView(generic.edit.FormView):
template_name = "repapp/register_device.html"
form_class = RegisterDevice
def form_valid(self, form):
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()
device = Device(
identifier=identifier,
device=device,
manufacturer=form.cleaned_data['manufacturer'],
error=form.cleaned_data['error'],
follow_up=form.cleaned_data['follow_up'],
device_picture=self.request.FILES.get("device_picture", None),
type_plate_picture=self.request.FILES.get(
"type_plate_picture", None),
cafe=cafe,
confirmed=False,
)
device.save()
guest = Guest.objects.filter(mail=mail).first()
print(f'Register: Dev: {device}, Guest: {guest}')
if guest:
print('Guest already known')
device.guest = guest
device.save()
send_confirmation_mails(device, guest, cafe, self.request)
print('Mails sent')
resp = HttpResponseRedirect(
reverse_lazy('register_device_final', kwargs={
'cafe': cafe.pk,
'device_identifier': device.identifier})
)
print(resp)
return resp
else:
return HttpResponseRedirect(
reverse_lazy('register_guest', kwargs={
'cafe': cafe.pk,
'device_identifier': device.identifier,
'mail': mail})
)
def get_context_data(self, **kwargs):
cafe = self.kwargs['cafe']
cafe = get_object_or_404(Cafe, pk=cafe)
context = super(RegisterDeviceFormView, self).get_context_data(
**kwargs
)
context["cafe"] = cafe
return context
class RegisterGuestFormView(generic.edit.FormView):
template_name = "repapp/register_guest.html"
form_class = RegisterGuest
def form_valid(self, form):
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)
name = form.cleaned_data['name']
residence = form.cleaned_data['residence']
identifier = sha256(
f'{name}{residence}{datetime.datetime.now()}'.encode('utf-8')
).hexdigest()
guest = Guest(
identifier=identifier,
name=name,
phone=form.cleaned_data['phone'],
residence=residence,
mail=self.kwargs['mail'],
)
guest.save()
device.guest = guest
device.save()
send_confirmation_mails(device, guest, cafe, self.request)
return HttpResponseRedirect(
reverse_lazy('register_device_final', kwargs={
'cafe': cafe.pk,
'device_identifier': device.identifier})
)
def get_context_data(self, **kwargs):
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)
context = super(RegisterGuestFormView, self).get_context_data(
**kwargs
)
context["cafe"] = cafe
context["device"] = device
context["mail"] = self.kwargs['mail']
return context
def register_device_final(request, cafe, device_identifier):
cafe = get_object_or_404(Cafe, pk=cafe)
device = get_object_or_404(Device, identifier=device_identifier)
return render(
request,
"repapp/register_device_final.html",
{},
{"device": device, 'cafe': cafe},
)
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)
def device_view(request, device_identifier):
device = get_object_or_404(Device, identifier=device_identifier)
return render(
request,