diff --git a/docs/workflows.md b/docs/workflows.md index d6589c5..9b64206 100644 --- a/docs/workflows.md +++ b/docs/workflows.md @@ -21,7 +21,9 @@ Als bekannter Gast der ein defekten Geräte anmelden möchte, - 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. -## Organisator: Neues Gerät - akzeptieren für ausgewähltes Repair-Café +## Organisator: Neues Gerät + +### Variante: Akzeptieren für ausgewähltes Repair-Café Als Organisator habe ich eine eMail bekommen dass ein neues Gerät angemeldet wurde. Ich möchte dieses Gerät nun ansehen un die Eingangsüberprüfung durchführen. - Ich öffne die RepApp im Browser @@ -31,7 +33,7 @@ Als Organisator habe ich eine eMail bekommen dass ein neues Gerät angemeldet wu - Ich klicke auf den Knopf "Details" für das neue Gerät, und mein Browser öffnet eine Seite in der ich die Details zu dem Gerät sehe. Auf der Seite sind Knöpfe: `Annehmen für gewähltes Repair-Café`, `Annehmen für anderes Repair-Café`, `Ablehnen` und `Rückfrage`. Alle notwendigen Informationen sind vorhanden und ich entscheide mich das Gerät für das ausgewählte Repair-Café zu akzeptieren. Der Klick auf `Annehmen für gewähltes Repair-Café` bringt mich zurück zum Hauptmenü. - In der Liste der neuen Geräte ist das gerade bearbeitete Gerät nicht mehr sichtbar. -## Organisator: Neues Gerät - akzeptieren für späteres Repair-Café +### Variante: Akzeptieren für späteres Repair-Café Als Organisator habe ich eine eMail bekommen dass ein neues Gerät angemeldet wurde. Ich möchte dieses Gerät nun ansehen un die Eingangsüberprüfung durchführen. - Ich öffne die RepApp im Browser @@ -41,7 +43,7 @@ Als Organisator habe ich eine eMail bekommen dass ein neues Gerät angemeldet wu - Ich klicke auf den Knopf "Details" für das neue Gerät, und mein Browser öffnet eine Seite in der ich die Details zu dem Gerät sehe. Auf der Seite sind Knöpfe: `Annehmen für gewähltes Repair-Café`, `Annehmen für anderes Repair-Café`, `Ablehnen` und `Rückfrage`. Alle notwendigen Informationen sind vorhanden, aber da ich weiß dass im nächsten Repair-Café kein geeigneter Reparateur verfügbar ist entscheide ich mich das Gerät für ein späteres Repair-Café zu akzeptieren. Der Klick auf `Annehmen für anderes Repair-Café` bringt mich zurück zum Hauptmenü. - In der Liste der neuen Geräte ist das gerade bearbeitete Gerät nicht mehr sichtbar. -## Organisator: Neues Gerät - ablehnen +### Variante: Ablehnen Als Organisator habe ich eine eMail bekommen dass ein neues Gerät angemeldet wurde. Ich möchte dieses Gerät nun ansehen un die Eingangsüberprüfung durchführen. - Ich öffne die RepApp im Browser @@ -52,7 +54,7 @@ Als Organisator habe ich eine eMail bekommen dass ein neues Gerät angemeldet wu - Auf der neuen Seite sehe ich ein Formular mit einem Textbereich der die Überschrift "Antwort an den Gast" trägt. Im Formular ist bereits ein Standard-Text zur Ablehnung eines Geräts. Ich passe den Text an um dem Gast mitzuteilen dass wir leider keinen geeigneten Reparateur haben und klicke auf den Knopf `Absenden`. Der Klick bringt mich zurück zum Hauptmenü. - In der Liste der neuen Geräte ist das gerade bearbeitete Gerät nicht mehr sichtbar. -## Organisator: Neues Gerät - Rückfrage +### Variante: Rückfrage Als Organisator habe ich eine eMail bekommen dass ein neues Gerät angemeldet wurde. Ich möchte dieses Gerät nun ansehen un die Eingangsüberprüfung durchführen. - Ich öffne die RepApp im Browser 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/static/repapp/css/repapp.css b/rc_hip/repapp/static/repapp/css/repapp.css new file mode 100644 index 0000000..5a150b1 --- /dev/null +++ b/rc_hip/repapp/static/repapp/css/repapp.css @@ -0,0 +1,3 @@ +.django-ckeditor-widget { + display: block !important +} \ No newline at end of file diff --git a/rc_hip/repapp/templates/repapp/base.html b/rc_hip/repapp/templates/repapp/base.html index ac5b2a3..570ca00 100644 --- a/rc_hip/repapp/templates/repapp/base.html +++ b/rc_hip/repapp/templates/repapp/base.html @@ -15,6 +15,7 @@ {% endblock title %} + {% block head %} {% endblock head %} @@ -39,10 +40,6 @@ - {% else %} - {% endif %}