diff --git a/rc_hip/rc_hip/settings.py b/rc_hip/rc_hip/settings.py
index 78d64cf..a18ca3a 100644
--- a/rc_hip/rc_hip/settings.py
+++ b/rc_hip/rc_hip/settings.py
@@ -53,10 +53,13 @@ INSTALLED_APPS = [
"crispy_forms",
"crispy_bootstrap5",
"import_export",
+ "ckeditor",
+ "ckeditor_uploader",
]
CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"
CRISPY_TEMPLATE_PACK = "bootstrap5"
+CKEDITOR_UPLOAD_PATH = "uploads/"
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
@@ -166,7 +169,7 @@ AUTHENTICATION_BACKENDS = [
"repapp.backends.OneTimeLoginBackend",
]
-LOGIN_REDIRECT_URL = "/guest/profile/"
+LOGIN_REDIRECT_URL = "/guest/"
OIDC_RP_CLIENT_ID = "repapp"
OIDC_RP_CLIENT_SECRET = os.getenv("DJANGO_OIDC_RP_CLIENT_SECRET", None)
diff --git a/rc_hip/rc_hip/urls.py b/rc_hip/rc_hip/urls.py
index 9cd3412..7f57906 100644
--- a/rc_hip/rc_hip/urls.py
+++ b/rc_hip/rc_hip/urls.py
@@ -23,6 +23,8 @@ from django.conf.urls.static import static
urlpatterns = [
path("", include("repapp.urls")),
path('oidc/', include('mozilla_django_oidc.urls')),
- path('accounts/', include('django.contrib.auth.urls')),
+ # user management for guests not needed, use one-time-login only
+ # path('accounts/', include('django.contrib.auth.urls')),
+ path('ckeditor/', include('ckeditor_uploader.urls')),
path('admin/', admin.site.urls),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
diff --git a/rc_hip/repapp/forms.py b/rc_hip/repapp/forms.py
index cc8650a..ec58ed3 100644
--- a/rc_hip/repapp/forms.py
+++ b/rc_hip/repapp/forms.py
@@ -3,7 +3,8 @@ Django forms for RepApp.
"""
from django import forms
from django.core.exceptions import ValidationError
-from crispy_forms.layout import Field
+from ckeditor.widgets import CKEditorWidget
+from .models import Question, Device
EMPTY_VALUES = (None, '')
@@ -16,7 +17,7 @@ class HoneypotWidget(forms.TextInput):
"""
is_hidden = True
- def __init__(self, attrs=None, html_comment=False, *args, **kwargs):
+ def __init__(self, *args, attrs=None, html_comment=False, **kwargs):
self.html_comment = html_comment
super(HoneypotWidget, self).__init__(attrs, *args, **kwargs)
if 'class' not in self.attrs:
@@ -113,3 +114,18 @@ class RegisterGuest(forms.Form):
max_length=200
)
accept_agb = HoneypotField(label="")
+
+
+class Mail(forms.Form):
+ """
+ Form for rejecting a device.
+ """
+ message = forms.CharField(widget=CKEditorWidget(), label="")
+
+
+class UpdateDeviceForm(forms.ModelForm):
+ class Meta:
+ model = Question
+ fields = ['question']
+ question = forms.CharField(widget=CKEditorWidget(
+ attrs={'style': 'width:100%;'}), label="Frage")
diff --git a/rc_hip/repapp/mail_interface.py b/rc_hip/repapp/mail_interface.py
index a5a22ec..dbf8492 100644
--- a/rc_hip/repapp/mail_interface.py
+++ b/rc_hip/repapp/mail_interface.py
@@ -48,6 +48,7 @@ def process_message(message: MailMessage, guest: Guest):
if question:
if question.device.guest.mail == message.from_:
question.answer = content
+ question.answered = True
question.save()
# TODO: save attachments and add to message
logger.info(
diff --git a/rc_hip/repapp/migrations/0005_device_status.py b/rc_hip/repapp/migrations/0005_device_status.py
new file mode 100644
index 0000000..d6c2964
--- /dev/null
+++ b/rc_hip/repapp/migrations/0005_device_status.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.2 on 2023-05-29 10:39
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('repapp', '0004_alter_onetimelogin_secret'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='device',
+ name='status',
+ field=models.IntegerField(default=0, verbose_name='Status'),
+ ),
+ ]
diff --git a/rc_hip/repapp/migrations/0006_alter_question_answer.py b/rc_hip/repapp/migrations/0006_alter_question_answer.py
new file mode 100644
index 0000000..1f9d1e4
--- /dev/null
+++ b/rc_hip/repapp/migrations/0006_alter_question_answer.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.2 on 2023-05-29 12:45
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('repapp', '0005_device_status'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='question',
+ name='answer',
+ field=models.TextField(null=True, verbose_name='Antwort'),
+ ),
+ ]
diff --git a/rc_hip/repapp/migrations/0007_question_open.py b/rc_hip/repapp/migrations/0007_question_open.py
new file mode 100644
index 0000000..c747947
--- /dev/null
+++ b/rc_hip/repapp/migrations/0007_question_open.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.2 on 2023-05-29 12:55
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('repapp', '0006_alter_question_answer'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='question',
+ name='open',
+ field=models.BooleanField(default=True, verbose_name='offen'),
+ ),
+ ]
diff --git a/rc_hip/repapp/migrations/0008_question_sent.py b/rc_hip/repapp/migrations/0008_question_sent.py
new file mode 100644
index 0000000..f49e80a
--- /dev/null
+++ b/rc_hip/repapp/migrations/0008_question_sent.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.2 on 2023-05-29 14:08
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('repapp', '0007_question_open'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='question',
+ name='sent',
+ field=models.BooleanField(default=False, verbose_name='gesendet'),
+ ),
+ ]
diff --git a/rc_hip/repapp/migrations/0009_question_answered.py b/rc_hip/repapp/migrations/0009_question_answered.py
new file mode 100644
index 0000000..b840470
--- /dev/null
+++ b/rc_hip/repapp/migrations/0009_question_answered.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.2 on 2023-05-29 14:45
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('repapp', '0008_question_sent'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='question',
+ name='answered',
+ field=models.BooleanField(default=False, verbose_name='beantwortet'),
+ ),
+ ]
diff --git a/rc_hip/repapp/models.py b/rc_hip/repapp/models.py
index b78fce5..327bd13 100644
--- a/rc_hip/repapp/models.py
+++ b/rc_hip/repapp/models.py
@@ -113,6 +113,7 @@ class Device(models.Model):
type_plate_picture = models.FileField(
upload_to=device_directory_path, null=True, verbose_name=_("Foto vom Typenschild"))
confirmed = models.BooleanField(verbose_name=_("Bestätigung gesendet?"))
+ status = models.IntegerField(verbose_name=_("Status"), default=0)
guest = models.ForeignKey(
Guest, on_delete=models.CASCADE, null=True, verbose_name=_("Gast"))
cafe = models.ForeignKey(
@@ -123,7 +124,7 @@ class Device(models.Model):
verbose_name_plural = _('Geräte')
def __str__(self):
- return f'Gerät {self.device}'
+ return f'Gerät {self.device} - {self.manufacturer}'
class Appointment(models.Model):
@@ -153,7 +154,7 @@ class Question(models.Model):
A Question is a request for information form a Organisator or a Reparateur for a Device.
"""
question = models.TextField(verbose_name=_("Frage"))
- answer = models.TextField(verbose_name=_("Antwort"))
+ answer = models.TextField(verbose_name=_("Antwort"), null=True)
date = models.DateField(verbose_name=_(
"Erstellungsdatum"), default=django.utils.timezone.now)
organisator = models.ForeignKey(
@@ -162,6 +163,10 @@ class Question(models.Model):
Reparateur, on_delete=models.CASCADE, null=True, verbose_name=_("Reparateur"))
device = models.ForeignKey(
Device, on_delete=models.CASCADE, verbose_name=_("Gerät"))
+ open = models.BooleanField(verbose_name=_("offen"), default=True)
+ sent = models.BooleanField(verbose_name=_("gesendet"), default=False)
+ answered = models.BooleanField(
+ verbose_name=_("beantwortet"), default=False)
class Meta:
verbose_name = _('Frage')
diff --git a/rc_hip/repapp/templates/repapp/base.html b/rc_hip/repapp/templates/repapp/base.html
index 91436ab..a291bdb 100644
--- a/rc_hip/repapp/templates/repapp/base.html
+++ b/rc_hip/repapp/templates/repapp/base.html
@@ -15,6 +15,11 @@
{% endblock title %}
+
{% block head %}
{% endblock head %}
@@ -39,10 +44,6 @@
Abmelden
- {% else %}
-
- Gast Login
-
{% endif %}
Mitglieder
diff --git a/rc_hip/repapp/templates/repapp/mail/mail_new_guest_text.html b/rc_hip/repapp/templates/repapp/mail/mail_new_guest_text.html
deleted file mode 100644
index 9e7beb6..0000000
--- a/rc_hip/repapp/templates/repapp/mail/mail_new_guest_text.html
+++ /dev/null
@@ -1,6 +0,0 @@
-Guten Tag,
-wir haben ein Benutzerkonto für Sie erstellt. Sie können sich im Web-Broswer unter {{ link }} um ihre Daten einzusehen und zu ändern.
-Ihr Anmeldedaten sind:
-Benutzername: {{ username }}
-Passwort: {{ password }}
-Herzliche Grüße sendet dir das Organisationsteam vom Repair-Café Hilpoltstein!
diff --git a/rc_hip/repapp/templates/repapp/mail/mail_one_time_login_text.html b/rc_hip/repapp/templates/repapp/mail/mail_one_time_login_text.html
deleted file mode 100644
index 4c28274..0000000
--- a/rc_hip/repapp/templates/repapp/mail/mail_one_time_login_text.html
+++ /dev/null
@@ -1,3 +0,0 @@
-Guten Tag,
-wir haben für Sie einen Einmal-Ammeldelink erstellt. Wenn sie {{ link }} im Web-Broswer öffnen können sie auf ihre Informationen zugreifen.
-Herzliche Grüße sendet dir das Organisationsteam vom Repair-Café Hilpoltstein!
diff --git a/rc_hip/repapp/templates/repapp/mail/mail_question_device_html.html b/rc_hip/repapp/templates/repapp/mail/mail_question_device_html.html
new file mode 100644
index 0000000..0a14009
--- /dev/null
+++ b/rc_hip/repapp/templates/repapp/mail/mail_question_device_html.html
@@ -0,0 +1,4 @@
+Guten Tag,
+Wir benötigen mehr Informationen zu ihrem Gerät {{ device.device }} - {{ device.manufacturer }}.
+HIER FRAGE EINFÜGEN
+Herzliche Grüße sendet dir das Organisationsteam vom Repair-Café Hilpoltstein!
diff --git a/rc_hip/repapp/templates/repapp/mail/mail_question_device_subject.html b/rc_hip/repapp/templates/repapp/mail/mail_question_device_subject.html
new file mode 100644
index 0000000..5516a71
--- /dev/null
+++ b/rc_hip/repapp/templates/repapp/mail/mail_question_device_subject.html
@@ -0,0 +1 @@
+Repair-Café Hilpoltstein - Q#{{ question.pk }}: Rückfrage zu {{ device.device }}
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
deleted file mode 100644
index 775aa7e..0000000
--- a/rc_hip/repapp/templates/repapp/mail/mail_register_device_text.html
+++ /dev/null
@@ -1,8 +0,0 @@
-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 }}.
-Sie können die Daten zu ihrer Geräteanmeldung hier einsehen.
-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!
diff --git a/rc_hip/repapp/templates/repapp/mail/mail_reject_device_html.html b/rc_hip/repapp/templates/repapp/mail/mail_reject_device_html.html
new file mode 100644
index 0000000..c79ef97
--- /dev/null
+++ b/rc_hip/repapp/templates/repapp/mail/mail_reject_device_html.html
@@ -0,0 +1,6 @@
+Guten Tag,
+Leider können wir das Gerät {{ device.device }} - {{ device.manufacturer }} nicht annehmen.
+
+ In unserem Repair-Café gibt es leider keinen passenden Reparateur mit den notwenidgen Fähigkeiten um das Gerät zu reparieren.
+
+Herzliche Grüße sendet dir das Organisationsteam vom Repair-Café Hilpoltstein!
diff --git a/rc_hip/repapp/templates/repapp/mail/mail_reject_device_subject.html b/rc_hip/repapp/templates/repapp/mail/mail_reject_device_subject.html
new file mode 100644
index 0000000..3610b6f
--- /dev/null
+++ b/rc_hip/repapp/templates/repapp/mail/mail_reject_device_subject.html
@@ -0,0 +1 @@
+Repair-Café Hilpoltstein - Geräteablehnung {{ device.device }}
diff --git a/rc_hip/repapp/templates/repapp/orga/cafe.html b/rc_hip/repapp/templates/repapp/orga/cafe.html
index 0db2d30..b0c8c9d 100644
--- a/rc_hip/repapp/templates/repapp/orga/cafe.html
+++ b/rc_hip/repapp/templates/repapp/orga/cafe.html
@@ -8,7 +8,7 @@
{% block content %}
Neues Repair-Cafés anlegen
- Zurück
+ Zurück
diff --git a/rc_hip/repapp/templates/repapp/orga/device_question.html b/rc_hip/repapp/templates/repapp/orga/device_question.html
new file mode 100644
index 0000000..77c0c30
--- /dev/null
+++ b/rc_hip/repapp/templates/repapp/orga/device_question.html
@@ -0,0 +1,23 @@
+{% extends "repapp/base.html" %}
+{% load static %}
+{% load crispy_forms_tags %}
+{% block title %}
+ - Rückfrage zum Gerät
+{% endblock title %}
+{% block page_title %}
+ Rückfrage zum Gerät
+{% endblock page_title %}
+{% block content %}
+
+ Zurück
+
+
+ Rückfrage zum Gerät
+
+
+
+{% endblock content %}
diff --git a/rc_hip/repapp/templates/repapp/orga/edit_question.html b/rc_hip/repapp/templates/repapp/orga/edit_question.html
new file mode 100644
index 0000000..2573a4e
--- /dev/null
+++ b/rc_hip/repapp/templates/repapp/orga/edit_question.html
@@ -0,0 +1,28 @@
+{% extends "repapp/base.html" %}
+{% load static %}
+{% load crispy_forms_tags %}
+{% block title %}
+ - Rückfrage zum Gerät
+{% endblock title %}
+{% block page_title %}
+ Rückfrage zum Gerät
+{% endblock page_title %}
+{% block content %}
+
+ Zurück
+
+
+ Rückfrage zum Gerät
+ {{ question.device.device }} - {{ question.device.manufacturer }} vom {{ question.date|date }}
+ {% if show_guest %}Gast: {{ question.device.guest.name }} ({{ question.device.guest.mail }})
{% endif %}
+ {% if question.device.cafe %}Repair-Café: {{ question.device.cafe.event_date|date }}
{% endif %}
+ {% if question.organisator %}Organisator: {{ question.organisator.name }}
{% endif %}
+ {% if question.reparateur %}Reparateur: {{ question.reparateur.name }}
{% endif %}
+
+
+
+{% endblock content %}
diff --git a/rc_hip/repapp/templates/repapp/orga/guest.html b/rc_hip/repapp/templates/repapp/orga/guest.html
index a587a6b..c4f06c9 100644
--- a/rc_hip/repapp/templates/repapp/orga/guest.html
+++ b/rc_hip/repapp/templates/repapp/orga/guest.html
@@ -7,8 +7,7 @@
{% endblock page_title %}
{% block content %}
- Neuen Gast anlegen
- Zurück
+ Zurück
@@ -19,7 +18,6 @@
Wohnort
eMail Adresse
Bearbeiten
- Löschen
diff --git a/rc_hip/repapp/templates/repapp/orga/main.html b/rc_hip/repapp/templates/repapp/orga/main.html
index 788764c..6ec43ce 100644
--- a/rc_hip/repapp/templates/repapp/orga/main.html
+++ b/rc_hip/repapp/templates/repapp/orga/main.html
@@ -17,10 +17,119 @@
// TODO: Button nächstes Repair-Café
Geräte
- // TODO: Liste mit neuen Geräten
+
+
+
+ Art des Geräts
+ Hersteller & Modell/Typ
+ Name des Gastes
+ eMail des Gastes
+
+
+
+
+ {% for device in devices %}
+
+ {{ device.device }}
+ {{ device.manufacturer }}
+ {{ device.guest.name }}
+ {{ device.guest.mail }}
+
+ überpüfen
+
+
+ {% empty %}
+
+ Es gibt keine neuen Geräte.
+
+ {% endfor %}
+
+
Rückfragen
- // TODO: Liste mit Antworten auf Rückfragen
+
+
+
+ Geräts
+ Hersteller
+ Gast
+ Organisator
+ Reparateur
+ gesendet
+ beantwortet
+
+
+
+
+
+ {% for question in questions_not_sent %}
+
+ {{ question.device.device }}
+ {{ question.device.manufacturer }}
+ {{ question.device.guest.name }}
+ {{ question.organisator.mail }}
+ {{ question.reparateur.mail }}
+
+ {% if question.sent %}
+ Ja
+ {% else %}
+ Nein
+ {% endif %}
+
+
+ {% if question.answered %}
+ Ja
+ {% else %}
+ Nein
+ {% endif %}
+
+
+ ansehen
+
+
+ {% if not question.sent %}
+ senden
+ {% endif %}
+
+
+ {% endfor %}
+ {% for question in questions_open_and_answered %}
+
+ {{ question.device.device }}
+ {{ question.device.manufacturer }}
+ {{ question.device.guest.name }}
+ {{ question.organisator.mail }}
+ {{ question.reparateur.mail }}
+
+ {% if question.sent %}
+ Ja
+ {% else %}
+ Nein
+ {% endif %}
+
+
+ {% if question.answered %}
+ Ja
+ {% else %}
+ Nein
+ {% endif %}
+
+
+ ansehen
+
+
+ {% if not question.sent %}
+ senden
+ {% endif %}
+
+
+ {% endfor %}
+
+
Nachrichten
// TODO: Liste mit neuen Nachrichten
diff --git a/rc_hip/repapp/templates/repapp/orga/question.html b/rc_hip/repapp/templates/repapp/orga/question.html
new file mode 100644
index 0000000..e81534f
--- /dev/null
+++ b/rc_hip/repapp/templates/repapp/orga/question.html
@@ -0,0 +1,41 @@
+{% extends "repapp/base.html" %}
+{% load static %}
+{% load crispy_forms_tags %}
+{% block title %}
+ - Rückfrage zum Gerät
+{% endblock title %}
+{% block page_title %}
+ Rückfrage zum Gerät
+{% endblock page_title %}
+{% block content %}
+
+ Zurück
+
+
+ Rückfrage zum Gerät
+ {{ question.device.device }} - {{ question.device.manufacturer }} vom {{ question.date|date }}
+ {% if show_guest %}Gast: {{ question.device.guest.name }} ({{ question.device.guest.mail }})
{% endif %}
+ {% if question.device.cafe %}Repair-Café: {{ question.device.cafe.event_date|date }}
{% endif %}
+ {% if question.organisator %}Organisator: {{ question.organisator.name }}
{% endif %}
+ {% if question.reparateur %}Reparateur: {{ question.reparateur.name }}
{% endif %}
+
+ Offen:
+ {% if question.open %}
+ Ja
+ {% else %}
+ Nein
+ {% endif %}
+
+
+ Frage
+ {{ question.question|safe }}
+
+ Antwort
+ {% if question.answer %}
+
+
{{ question.answer|safe }}
+
+ {% else %}
+ Der Gast hat noch nicht geantwortet.
+ {% endif %}
+{% endblock content %}
diff --git a/rc_hip/repapp/templates/repapp/orga/review_device.html b/rc_hip/repapp/templates/repapp/orga/review_device.html
new file mode 100644
index 0000000..52d7db6
--- /dev/null
+++ b/rc_hip/repapp/templates/repapp/orga/review_device.html
@@ -0,0 +1,72 @@
+{% extends "repapp/base.html" %}
+{% load crispy_forms_tags %}
+{% block title %}
+ - Gerät überprüfen
+{% endblock title %}
+{% block page_title %}
+ Gerät überprüfen
+{% endblock page_title %}
+{% block content %}
+
+ Zurück
+ Annehmen
+ Ablehnen
+ Rückfrage
+
+
+ Gerät
+
+
+
+ Art des Geräts
+ {{ device.device }}
+
+
+ Hersteller & Modell/Typ
+ {{ device.manufacturer }}
+
+
+ Fehlerbeschreibung
+ {{ device.error }}
+
+
+ Folgetermin
+
+ {% if device.follow_up %}
+ Ja
+ {% else %}
+ Nein
+ {% endif %}
+
+
+
+ Foto vom Gerät
+
+ {% if device.device_picture %}
+
+ {% else %}
+ kein Bild
+ {% endif %}
+
+
+
+ Foto vom Typenschild
+
+ {% if device.device_picture %}
+
+ {% else %}
+ kein Bild
+ {% endif %}
+
+
+
+
+{% endblock content %}
diff --git a/rc_hip/repapp/templates/repapp/orga/review_device_reject.html b/rc_hip/repapp/templates/repapp/orga/review_device_reject.html
new file mode 100644
index 0000000..8a18fa4
--- /dev/null
+++ b/rc_hip/repapp/templates/repapp/orga/review_device_reject.html
@@ -0,0 +1,23 @@
+{% extends "repapp/base.html" %}
+{% load static %}
+{% load crispy_forms_tags %}
+{% block title %}
+ - Gerät ablehnen
+{% endblock title %}
+{% block page_title %}
+ Gerät ablehnen
+{% endblock page_title %}
+{% block content %}
+
+ Zurück
+
+
+ Gerät ablehnen
+
+
+
+{% endblock content %}
diff --git a/rc_hip/repapp/urls.py b/rc_hip/repapp/urls.py
index ea75c77..e87c4d9 100644
--- a/rc_hip/repapp/urls.py
+++ b/rc_hip/repapp/urls.py
@@ -25,8 +25,20 @@ urlpatterns = [
path("onetimelogin//",
views.one_time_login, name="one_time_login"),
path("member/", views.member, name="member"),
+ path("logout/", views.user_logout, name="logout"),
path("orga/select/", views.select_role, name="select_role"),
path("orga/", views.orga, name="orga"),
+ path("orga/review//", views.review_device, name="review_device"),
+ path("orga/review//accept/",
+ views.review_device_accept, name="review_device_accept"),
+ path("orga/review//reject/",
+ views.RejectDeviceFormView.as_view(), name="review_device_reject"),
+ path("member/device//question/",
+ views.QuestionDeviceFormView.as_view(), name="device_question"),
+ path("member/device//question/edit",
+ views.QuestionUpdateView.as_view(), name="edit_device_question"),
+ path("member/question//",
+ views.question, name="view_device_question"),
path("repa/", views.repa, name="repa"),
path("cafe/", views.CafeView.as_view(), name="cafe"),
path("cafe/create", views.CafeCreateView.as_view(), name="create_cafe"),
@@ -38,5 +50,4 @@ urlpatterns = [
path("cron", views.cron, name="cron"),
path("bootstrap", views.bootstrap, name="bootstrap"),
path("process_mails", views.process_mails, name="process_mails"),
-
]
diff --git a/rc_hip/repapp/views.py b/rc_hip/repapp/views.py
index 692dcf4..0cb3686 100644
--- a/rc_hip/repapp/views.py
+++ b/rc_hip/repapp/views.py
@@ -8,6 +8,7 @@ import time
import os
import logging
from hashlib import sha256
+from bs4 import BeautifulSoup
from django.template.loader import render_to_string
from django.urls import reverse
from django.core.mail import send_mail
@@ -19,10 +20,10 @@ from django.http import HttpResponseRedirect, HttpResponse
from django.core.exceptions import PermissionDenied
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
-from django.contrib.auth import authenticate, login
+from django.contrib.auth import authenticate, login, logout
from django.utils.timezone import now
-from .forms import RegisterDevice, RegisterGuest
-from .models import (Cafe, Device, Guest, OneTimeLogin,
+from .forms import RegisterDevice, RegisterGuest, Mail, UpdateDeviceForm
+from .models import (Cafe, Device, Guest, OneTimeLogin, Question,
CustomUser, Organisator, Reparateur)
from . import mail_interface
@@ -38,12 +39,11 @@ def send_one_time_login_mail(secret, mail, request):
f'/onetimelogin/{secret}/')
subject = render_to_string(
'repapp/mail/mail_one_time_login_subject.html').replace('\n', '')
- text = render_to_string('repapp/mail/mail_one_time_login_text.html', {
- 'link': url,
- })
html = render_to_string('repapp/mail/mail_one_time_login_html.html', {
'link': url,
})
+ soup = BeautifulSoup(html)
+ text = soup.get_text('\n')
send_ok = send_mail(
subject=subject,
@@ -90,18 +90,14 @@ def send_confirmation_mails(device, guest, cafe, request):
subject = render_to_string('repapp/mail/mail_register_device_subject.html', {
'cafe': cafe,
}).replace('\n', '')
- text = render_to_string('repapp/mail/mail_register_device_text.html', {
- 'guest': guest,
- 'device': device,
- 'cafe': cafe,
- 'login_url': login_url,
- })
html = render_to_string('repapp/mail/mail_register_device_html.html', {
'guest': guest,
'device': device,
'cafe': cafe,
'login_url': login_url,
})
+ soup = BeautifulSoup(html)
+ text = soup.get_text('\n')
mail_count = send_mail(
subject=subject,
@@ -127,16 +123,13 @@ def send_guest_account_mail(guest, password, request):
url = request.build_absolute_uri('/guest/profile/')
subject = render_to_string(
'repapp/mail/mail_new_guest_subject.html').replace('\n', '')
- text = render_to_string('repapp/mail/mail_new_guest_text.html', {
- 'link': url,
- 'username': guest.user.email,
- 'password': password,
- })
html = render_to_string('repapp/mail/mail_new_guest_html.html', {
'link': url,
'username': guest.user.email,
'password': password,
})
+ soup = BeautifulSoup(html)
+ text = soup.get_text('\n')
send_ok = send_mail(
subject=subject,
@@ -152,6 +145,62 @@ def send_guest_account_mail(guest, password, request):
'Sie haben ihre Benutzerdaten per eMail erhalten.')
+def send_device_reject_mail(device, reply, request):
+ """
+ Send a mail to reject the given device.
+ """
+ subject = render_to_string(
+ 'repapp/mail/mail_reject_device_subject.html', {
+ 'device': device
+ }).replace('\n', '')
+
+ soup = BeautifulSoup(reply)
+ text = soup.get_text('\n')
+
+ send_ok = send_mail(
+ subject=subject,
+ message=text,
+ from_email=os.getenv("DJANGO_SENDER_ADDRESS", ""),
+ recipient_list=[device.guest.mail],
+ fail_silently=True,
+ html_message=reply
+ )
+
+ if send_ok > 0:
+ messages.add_message(request, messages.INFO,
+ f'Das Gerät {device.device} wurde abgelehnt.')
+
+
+def send_device_question_mail(question, request):
+ """
+ Send a mail to ask more details about an device.
+ """
+ subject = render_to_string(
+ 'repapp/mail/mail_question_device_subject.html', {
+ 'device': question.device,
+ 'question': question,
+ }).replace('\n', '')
+
+ soup = BeautifulSoup(question.question)
+ text = soup.get_text('\n')
+
+ send_ok = send_mail(
+ subject=subject,
+ message=text,
+ from_email=os.getenv("DJANGO_SENDER_ADDRESS", ""),
+ recipient_list=[question.device.guest.mail],
+ fail_silently=True,
+ html_message=question.question
+ )
+
+ if send_ok > 0:
+ question.sent = True
+ question.save()
+
+ messages.add_message(request, messages.INFO,
+ f'Die Rückfrage zum Gerät {question.device.device} wurde gesendet.')
+
+
def is_member(user):
"""
Test is a user is a Repair-Café member.
@@ -325,7 +374,8 @@ class RegisterGuestFormView(generic.edit.FormView):
guest.user = user
guest.save()
- send_guest_account_mail(guest, password, self.request)
+ # Simplify flow for guests, hide account and use only one-time-login
+ # send_guest_account_mail(guest, password, self.request)
send_confirmation_mails(device, guest, cafe, self.request)
@@ -450,9 +500,231 @@ def orga(request):
logger.warning('The user %s is no organisator!', str(request.user))
raise PermissionDenied('Not organisator!')
+ devices = Device.objects.filter(status=0).order_by('-date')
+ questions_not_sent = Question.objects.filter(sent=False).order_by('-date')
+ questions_open_and_answered = Question.objects.filter(
+ open=True, answered=True).order_by('-date')
+
return render(
request,
- "repapp/orga/main.html"
+ "repapp/orga/main.html",
+ {
+ 'devices': devices,
+ 'questions_not_sent': questions_not_sent,
+ 'questions_open_and_answered': questions_open_and_answered,
+ }
+ )
+
+
+@login_required(login_url=reverse_lazy('member'))
+def review_device(request, device):
+ """
+ Review new device registration.
+ """
+ # TODO: test
+ if not is_organisator(request.user):
+ logger.warning('The user %s is no organisator!', str(request.user))
+ raise PermissionDenied('Not organisator!')
+
+ device = get_object_or_404(Device, pk=device)
+
+ return render(
+ request,
+ "repapp/orga/review_device.html",
+ {
+ 'device': device
+ }
+ )
+
+
+@login_required(login_url=reverse_lazy('member'))
+def review_device_accept(request, device):
+ """
+ Review new device registration.
+ """
+ # TODO: test
+ if not is_organisator(request.user):
+ logger.warning('The user %s is no organisator!', str(request.user))
+ raise PermissionDenied('Not organisator!')
+
+ device = get_object_or_404(Device, pk=device)
+ device.status = 3
+ device.save()
+
+ return HttpResponseRedirect(reverse_lazy('orga'))
+
+
+class RejectDeviceFormView(LoginRequiredMixin, UserPassesTestMixin, generic.edit.FormView):
+ """
+ View for rejecting a device.
+ """
+ template_name = "repapp/orga/review_device_reject.html"
+ form_class = Mail
+ login_url = reverse_lazy('member')
+
+ def test_func(self):
+ return is_organisator(self.request.user)
+
+ def form_valid(self, form):
+ device = self.kwargs['device']
+ device = get_object_or_404(Device, pk=device)
+
+ message = form.cleaned_data['message']
+
+ send_device_reject_mail(device, message, self.request)
+
+ device.status = -1
+ device.save()
+
+ return HttpResponseRedirect(
+ reverse_lazy('orga')
+ )
+
+ def get_initial(self, **kwargs):
+ device = self.kwargs['device']
+ device = get_object_or_404(Device, pk=device)
+
+ text = render_to_string('repapp/mail/mail_reject_device_html.html', {
+ 'device': device,
+ })
+
+ initial = super(RejectDeviceFormView, self).get_initial()
+ initial['message'] = text
+
+ return initial
+
+ def get_context_data(self, **kwargs):
+ device = self.kwargs['device']
+ device = get_object_or_404(Device, pk=device)
+
+ context = super(RejectDeviceFormView, self).get_context_data(
+ **kwargs
+ )
+ context["device"] = device
+
+ return context
+
+
+class QuestionDeviceFormView(LoginRequiredMixin, UserPassesTestMixin, generic.edit.FormView):
+ """
+ View for rejecting a device.
+ """
+ template_name = "repapp/orga/device_question.html"
+ form_class = Mail
+ login_url = reverse_lazy('member')
+
+ def test_func(self):
+ return is_organisator(self.request.user)
+
+ def form_valid(self, form):
+ device = self.kwargs['device']
+ device = get_object_or_404(Device, pk=device)
+
+ question = None
+ message = form.cleaned_data['message']
+ mail = self.request.user.email
+ organisator = Organisator.objects.filter(mail=mail).first()
+ if organisator:
+ question = Question(
+ question=message,
+ organisator=organisator,
+ device=device
+ )
+ else:
+ raise PermissionDenied('Not valid member found!')
+ question.save()
+
+ send_device_question_mail(question, self.request)
+
+ device.status = 1
+ device.save()
+
+ return HttpResponseRedirect(
+ reverse_lazy('orga')
+ )
+
+ def get_initial(self, **kwargs):
+ device = self.kwargs['device']
+ device = get_object_or_404(Device, pk=device)
+
+ text = render_to_string('repapp/mail/mail_question_device_html.html', {
+ 'device': device,
+ })
+
+ initial = super(QuestionDeviceFormView, self).get_initial()
+ initial['message'] = text
+
+ return initial
+
+ def get_context_data(self, **kwargs):
+ device = self.kwargs['device']
+ device = get_object_or_404(Device, pk=device)
+
+ context = super(QuestionDeviceFormView, self).get_context_data(
+ **kwargs
+ )
+ context["device"] = device
+
+ return context
+
+
+class QuestionUpdateView(LoginRequiredMixin, UserPassesTestMixin, generic.edit.UpdateView):
+ """
+ Organisator view for edit a Repair-Café.
+ """
+ # TODO: create test
+ login_url = reverse_lazy('member')
+ template_name = "repapp/orga/edit_question.html"
+ model = Question
+ form_class = UpdateDeviceForm
+ success_url = reverse_lazy('orga')
+
+ def test_func(self):
+ return is_organisator(self.request.user)
+
+ def form_valid(self, form):
+ response = super(QuestionUpdateView, self).form_valid(form)
+
+ question = self.kwargs['pk']
+ question = get_object_or_404(Question, pk=question)
+ send_device_question_mail(question, self.request)
+
+ return response
+
+ def get_context_data(self, **kwargs):
+ question = self.kwargs['pk']
+ question = get_object_or_404(Question, pk=question)
+
+ context = super(QuestionUpdateView, self).get_context_data(
+ **kwargs
+ )
+ context["question"] = question
+ context["show_guest"] = True
+
+ return context
+
+
+@login_required(login_url=reverse_lazy('member'))
+def question(request, question):
+ """
+ Review new device registration.
+ """
+ # TODO: test
+ if not is_member(request.user):
+ raise PermissionDenied('Not member!')
+
+ question = get_object_or_404(Question, pk=question)
+ show_guest = False
+ if is_organisator(request.user):
+ show_guest = True
+
+ return render(
+ request,
+ "repapp/orga/question.html",
+ {
+ 'question': question,
+ 'show_guest': show_guest
+ }
)
@@ -633,3 +905,11 @@ class GuestUpdateView(LoginRequiredMixin, UserPassesTestMixin, generic.edit.Upda
def test_func(self):
return is_organisator(self.request.user)
+
+
+def user_logout(request):
+ """
+ Log current user out.
+ """
+ logout(request)
+ return HttpResponseRedirect(reverse_lazy('index'))
diff --git a/requirements_dev.txt b/requirements_dev.txt
index fce3d99..553b43c 100644
--- a/requirements_dev.txt
+++ b/requirements_dev.txt
@@ -12,3 +12,5 @@ djlint==1.23.3
django-import-export==3.2.0
selenium==4.9.0
imap-tools==1.0.0
+django-ckeditor==6.5.1
+beautifulsoup4==4.12.2
diff --git a/requirements_prod.txt b/requirements_prod.txt
index 0eb9641..6bde7d7 100644
--- a/requirements_prod.txt
+++ b/requirements_prod.txt
@@ -6,3 +6,5 @@ crispy-bootstrap5==0.7
mozilla-django-oidc==3.0.0
django-import-export==3.2.0
imap-tools==1.0.0
+django-ckeditor==6.5.1
+beautifulsoup4==4.12.2