This repository has been archived on 2026-03-30. You can view files and clone it, but cannot push or open issues or pull requests.
RepApp/rc_hip/repapp/views.py

465 lines
13 KiB
Python
Raw Normal View History

2023-04-18 19:13:30 +00:00
"""
Views of RepApp.
"""
import datetime
2023-04-23 11:56:52 +00:00
import random
import string
2023-04-24 16:02:43 +00:00
import time
import os
2023-04-19 19:28:59 +00:00
from hashlib import sha256
from django.template.loader import render_to_string
from django.urls import reverse
from django.core.mail import send_mail
from django.contrib import messages
2023-04-18 18:04:32 +00:00
from django.views import generic
2023-04-19 19:28:59 +00:00
from django.urls import reverse_lazy
from django.shortcuts import get_object_or_404, render
2023-05-03 06:26:24 +00:00
from django.http import HttpResponseRedirect, HttpResponse
2023-04-23 11:56:52 +00:00
from django.core.exceptions import PermissionDenied
from django.contrib.auth.decorators import login_required
2023-04-24 16:02:43 +00:00
from django.contrib.auth import authenticate, login
from django.utils.timezone import now
2023-04-19 19:28:59 +00:00
from .forms import RegisterDevice, RegisterGuest
2023-05-03 06:26:24 +00:00
from .models import (Cafe, Device, Guest, OneTimeLogin,
CustomUser, Organisator, Reparateur)
from . import mail_interface
def send_one_time_login_mail(secret, mail, request):
2023-04-28 09:54:13 +00:00
"""
Send a mail with a one time login link.
"""
url = request.build_absolute_uri(
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,
})
ok = send_mail(
subject=subject,
message=text,
from_email=os.getenv("DJANGO_SENDER_ADDRESS", ""),
recipient_list=[mail],
fail_silently=True,
html_message=html
)
if ok > 0:
messages.add_message(request, messages.INFO,
'Sie haben einen neuen Login per eMail erhalten.')
def send_confirmation_mails(device, guest, cafe, request):
2023-04-28 09:54:13 +00:00
"""
Send confirmation mails for device registration.
"""
organizers = []
for organizer in Organisator.objects.all():
organizers.append(organizer.mail)
text = render_to_string('repapp/mail/notice_new_device.html', {
'guest': device.guest,
'device': device,
'cafe': device.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
)
path = reverse('view_device', kwargs={
'device_identifier': device.identifier})
url = request.build_absolute_uri(path)
secret = create_one_time_login(guest.user, url)
login_url = request.build_absolute_uri(f'/onetimelogin/{secret}/')
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,
})
mail_count = 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 mail_count > 0:
device.confirmed = True
device.save()
else:
messages.add_message(request, messages.ERROR,
'Fehler beim senden der Bestätigungs-eMail!')
def send_guest_account_mail(guest, password, request):
2023-04-28 09:54:13 +00:00
"""
Send guest user account created mail.
"""
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,
})
ok = send_mail(
subject=subject,
message=text,
from_email=os.getenv("DJANGO_SENDER_ADDRESS", ""),
recipient_list=[f"{guest.user.email}"],
fail_silently=True,
html_message=html
)
if ok > 0:
messages.add_message(request, messages.INFO,
'Sie haben ihre Benutzerdaten per eMail erhalten.')
def is_member(user):
2023-04-28 09:54:13 +00:00
"""
Test is a user is a Repair-Café member.
"""
organisator = Organisator.objects.filter(mail=user.email).first()
reparateur = Reparateur.objects.filter(mail=user.email).first()
return organisator or reparateur
def create_one_time_login(user, url) -> str:
"""
2023-04-28 09:54:13 +00:00
create_one_time_login creates a one time login object for guest user logins.
"""
secret = sha256(
f'{user.email}{url}{datetime.datetime.now()}{random.randint(0,9999999)}'.encode(
'utf-8')
).hexdigest()
2023-04-25 19:04:13 +00:00
secret_hash = sha256(secret.encode('utf-8')).hexdigest()
otl = OneTimeLogin(
2023-04-25 19:04:13 +00:00
secret=secret_hash,
user=user,
url=url,
)
otl.save()
return secret
2023-04-18 18:04:32 +00:00
class IndexView(generic.ListView):
2023-04-18 19:13:30 +00:00
"""
The IndexView lists all future Repair-Cafés.
"""
2023-04-18 18:04:32 +00:00
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.')
2023-04-18 19:13:30 +00:00
return Cafe.objects.filter(event_date__gte=datetime.date.today())
2023-04-19 19:28:59 +00:00
class RegisterDeviceFormView(generic.edit.FormView):
2023-04-24 16:02:43 +00:00
"""
RegisterDeviceFormView shows the form for registering new devices.
"""
2023-04-19 19:28:59 +00:00
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)
2023-04-19 19:28:59 +00:00
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,
2023-04-21 16:24:31 +00:00
manufacturer=form.cleaned_data['manufacturer'],
2023-04-19 19:28:59 +00:00
error=form.cleaned_data['error'],
follow_up=form.cleaned_data['follow_up'],
2023-04-21 16:24:31 +00:00
device_picture=self.request.FILES.get("device_picture", None),
type_plate_picture=self.request.FILES.get(
"type_plate_picture", None),
cafe=cafe,
2023-04-21 16:24:31 +00:00
confirmed=False,
2023-04-19 19:28:59 +00:00
)
device.save()
guest = Guest.objects.filter(mail=mail).first()
2023-05-03 06:26:24 +00:00
user = CustomUser.objects.filter(email=mail).first()
2023-04-19 19:28:59 +00:00
2023-05-03 06:26:24 +00:00
if guest and user:
device.guest = guest
2023-04-19 19:28:59 +00:00
device.save()
2023-04-21 16:24:31 +00:00
2023-05-03 06:26:24 +00:00
guest.user = user
guest.save()
2023-04-21 16:24:31 +00:00
send_confirmation_mails(device, guest, cafe, self.request)
resp = HttpResponseRedirect(
2023-04-19 19:28:59 +00:00
reverse_lazy('register_device_final', kwargs={
'cafe': cafe.pk,
2023-04-21 16:24:31 +00:00
'device_identifier': device.identifier})
2023-04-19 19:28:59 +00:00
)
2023-04-21 16:24:31 +00:00
return resp
2023-04-19 19:28:59 +00:00
else:
return HttpResponseRedirect(
reverse_lazy('register_guest', kwargs={
'cafe': cafe.pk,
2023-04-21 16:24:31 +00:00
'device_identifier': device.identifier,
2023-04-19 19:28:59 +00:00
'mail': mail})
)
def get_context_data(self, **kwargs):
cafe = self.kwargs['cafe']
cafe = get_object_or_404(Cafe, pk=cafe)
2023-04-19 19:28:59 +00:00
context = super(RegisterDeviceFormView, self).get_context_data(
**kwargs
)
context["cafe"] = cafe
return context
class RegisterGuestFormView(generic.edit.FormView):
2023-04-24 19:03:56 +00:00
"""
View for registering a new guest.
"""
2023-04-19 19:28:59 +00:00
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)
2023-04-19 19:28:59 +00:00
2023-04-21 16:24:31 +00:00
device_identifier = self.kwargs['device_identifier']
device = get_object_or_404(Device, identifier=device_identifier)
2023-04-19 19:28:59 +00:00
name = form.cleaned_data['name']
2023-04-23 11:56:52 +00:00
mail = self.kwargs['mail']
2023-04-19 19:28:59 +00:00
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,
2023-04-23 11:56:52 +00:00
mail=mail,
2023-04-19 19:28:59 +00:00
)
guest.save()
device.guest = guest
2023-04-19 19:28:59 +00:00
device.save()
2023-04-23 11:56:52 +00:00
if not CustomUser.objects.filter(email=mail).exists():
password = ''.join(random.choice(string.ascii_letters)
for i in range(10))
2023-04-25 18:47:53 +00:00
username = name
i = 1
while CustomUser.objects.filter(username=username).first():
username = f'{name}{i}'
i = i + 1
2023-04-23 11:56:52 +00:00
user = CustomUser.objects.create_user(
2023-04-25 18:47:53 +00:00
username=username,
2023-04-23 11:56:52 +00:00
email=mail,
2023-04-24 19:03:56 +00:00
)
user.set_password(password)
2023-04-23 11:56:52 +00:00
user.save()
2023-04-25 07:14:26 +00:00
guest.user = user
guest.save()
2023-04-24 16:02:43 +00:00
send_guest_account_mail(guest, password, self.request)
2023-04-23 11:56:52 +00:00
2023-04-21 16:24:31 +00:00
send_confirmation_mails(device, guest, cafe, self.request)
2023-04-19 19:28:59 +00:00
return HttpResponseRedirect(
reverse_lazy('register_device_final', kwargs={
'cafe': cafe.pk,
2023-04-21 16:24:31 +00:00
'device_identifier': device.identifier})
2023-04-19 19:28:59 +00:00
)
def get_context_data(self, **kwargs):
cafe = self.kwargs['cafe']
cafe = get_object_or_404(Cafe, pk=cafe)
2023-04-19 19:28:59 +00:00
2023-04-21 16:24:31 +00:00
device_identifier = self.kwargs['device_identifier']
device = get_object_or_404(Device, identifier=device_identifier)
2023-04-19 19:28:59 +00:00
2023-04-25 07:14:26 +00:00
mail = self.kwargs['mail']
guest = Guest.objects.filter(mail=mail).first()
if guest:
return HttpResponseRedirect(
reverse_lazy('register_device_final', kwargs={
'cafe': cafe.pk,
'device_identifier': device.identifier})
)
2023-04-19 19:28:59 +00:00
context = super(RegisterGuestFormView, self).get_context_data(
**kwargs
2023-04-18 19:13:30 +00:00
)
2023-04-19 19:28:59 +00:00
context["cafe"] = cafe
context["device"] = device
2023-04-25 07:14:26 +00:00
context["mail"] = mail
2023-04-19 19:28:59 +00:00
return context
2023-04-21 16:24:31 +00:00
def register_device_final(request, cafe, device_identifier):
2023-04-24 19:03:56 +00:00
"""
View to confirm device registration.
"""
cafe = get_object_or_404(Cafe, pk=cafe)
2023-04-21 16:24:31 +00:00
device = get_object_or_404(Device, identifier=device_identifier)
2023-04-19 19:28:59 +00:00
return render(
request,
"repapp/register_device_final.html",
2023-04-21 16:24:31 +00:00
{"device": device, 'cafe': cafe},
)
2023-04-23 11:56:52 +00:00
@login_required
2023-04-21 16:24:31 +00:00
def device_view(request, device_identifier):
2023-04-24 19:03:56 +00:00
"""
View for showing device details.
"""
2023-04-23 11:56:52 +00:00
user = request.user
if not user:
raise PermissionDenied()
2023-04-21 16:24:31 +00:00
device = get_object_or_404(Device, identifier=device_identifier)
2023-04-23 11:56:52 +00:00
if not device.guest:
raise PermissionDenied()
2023-04-24 16:02:43 +00:00
if not (is_member(user) or device.guest.mail == user.email):
raise PermissionDenied()
return render(
request,
"repapp/device_view.html",
{"device": device},
)
2023-04-23 11:56:52 +00:00
@login_required
def profile(request):
2023-04-24 19:03:56 +00:00
"""
View for showing guest details.
"""
2023-04-23 11:56:52 +00:00
user = request.user
if not user:
raise PermissionDenied()
guest = get_object_or_404(Guest, mail=user.email)
return render(
request,
"repapp/guest_profile.html",
{
'guest': guest
}
)
2023-04-23 15:40:19 +00:00
def member_login(request):
2023-04-24 19:03:56 +00:00
"""
Login page for repair cafe members, using OIDC.
"""
2023-04-23 15:40:19 +00:00
return render(
request,
"repapp/member_login.html"
)
2023-04-24 16:02:43 +00:00
def cron(request):
2023-04-24 19:03:56 +00:00
"""
View to trigger automated regular tasks.
"""
2023-05-03 06:26:24 +00:00
return HttpResponse('done.')
2023-04-24 16:02:43 +00:00
def process_mails(request):
2023-04-24 19:03:56 +00:00
"""
View to trigger processing of email in inbox.
"""
2023-05-03 06:26:24 +00:00
count = mail_interface.process_mails()
return HttpResponse(f'Done. Processed {count} messages.')
2023-04-24 16:02:43 +00:00
2023-04-25 12:04:26 +00:00
def one_time_login(request, secret: str):
2023-04-24 19:03:56 +00:00
"""
View for one time login.
"""
# waste a little time as brute force protection
2023-04-24 16:02:43 +00:00
time.sleep(1)
2023-04-25 19:04:13 +00:00
secret_hash = sha256(secret.encode('utf-8')).hexdigest()
otl = get_object_or_404(OneTimeLogin, secret=secret_hash)
2023-04-24 16:02:43 +00:00
if otl.login_used:
messages.add_message(request, messages.ERROR,
'Der Einmal-Login wurde schon verwendet und ist nichtmehr gültig.')
2023-04-25 19:04:13 +00:00
new_secret = create_one_time_login(otl.user, otl.url)
send_one_time_login_mail(new_secret, otl.user.email, request)
2023-04-24 16:02:43 +00:00
return HttpResponseRedirect(reverse_lazy('index'))
else:
otl.login_used = True
otl.login_date = now()
otl.save()
2023-04-25 19:04:13 +00:00
user = authenticate(request, username=secret_hash, password=None)
2023-04-24 16:02:43 +00:00
if user is not None:
login(request, user)
messages.add_message(request, messages.INFO, 'Login erfolgreich!')
return HttpResponseRedirect(otl.url)
else:
messages.add_message(request, messages.ERROR, 'Login fehlgeschlagen.')
return HttpResponseRedirect(reverse_lazy('index'))
2023-04-28 09:54:13 +00:00
def bootstrap(request):
"""
Bootstrap design test page.
"""
return render(
request,
"repapp/bootstrap.html"
)