Merge pull request #44 from makes-hacks-hip/26-orga-create-cafe

26 orga create cafe
This commit is contained in:
Thomas Irgang 2023-05-18 09:19:34 +02:00 committed by GitHub
commit 068c626a87
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 433 additions and 393 deletions

View file

@ -18,7 +18,10 @@
"Keycloak", "Keycloak",
"tabindex", "tabindex",
"onetimelogin", "onetimelogin",
"Ammeldelink" "Ammeldelink",
"orga",
"repa",
"organisators"
], ],
"ignoreWords": [], "ignoreWords": [],
"import": [] "import": []

View file

@ -179,10 +179,10 @@ OIDC_OP_TOKEN_ENDPOINT = "https://sso.makes-hacks-hip.de/realms/Makes-Hacks-Hip/
OIDC_OP_USER_ENDPOINT = "https://sso.makes-hacks-hip.de/realms/Makes-Hacks-Hip/protocol/openid-connect/userinfo" OIDC_OP_USER_ENDPOINT = "https://sso.makes-hacks-hip.de/realms/Makes-Hacks-Hip/protocol/openid-connect/userinfo"
if DEBUG: if DEBUG:
LOGIN_REDIRECT_URL = "http://127.0.0.1:8000/" LOGIN_REDIRECT_URL = "http://127.0.0.1:8000/member"
LOGOUT_REDIRECT_URL = "http://127.0.0.1:8000/" LOGOUT_REDIRECT_URL = "http://127.0.0.1:8000/"
else: else:
LOGIN_REDIRECT_URL = "https://anmeldung.repaircafe-hilpoltstein.de/" LOGIN_REDIRECT_URL = "https://anmeldung.repaircafe-hilpoltstein.de/member"
LOGOUT_REDIRECT_URL = "https://anmeldung.repaircafe-hilpoltstein.de/" LOGOUT_REDIRECT_URL = "https://anmeldung.repaircafe-hilpoltstein.de/"

View file

@ -5,7 +5,7 @@
"fields": { "fields": {
"location": "Haus Einstein' (beim Gymnasium)", "location": "Haus Einstein' (beim Gymnasium)",
"address": "Patersholzer Weg 19, Hilpoltstein", "address": "Patersholzer Weg 19, Hilpoltstein",
"event_date": "2023-05-13" "event_date": "2024-05-13"
} }
}, },
{ {

View file

@ -19,13 +19,13 @@ class HoneypotWidget(forms.TextInput):
def __init__(self, attrs=None, html_comment=False, *args, **kwargs): def __init__(self, attrs=None, html_comment=False, *args, **kwargs):
self.html_comment = html_comment self.html_comment = html_comment
super(HoneypotWidget, self).__init__(attrs, *args, **kwargs) super(HoneypotWidget, self).__init__(attrs, *args, **kwargs)
if not 'class' in self.attrs: if 'class' not in self.attrs:
self.attrs['style'] = 'display:none' self.attrs['style'] = 'display:none'
def render(self, *args, **kwargs): def render(self, *args, **kwargs):
value = super(HoneypotWidget, self).render(*args, **kwargs) value = super(HoneypotWidget, self).render(*args, **kwargs)
if self.html_comment: if self.html_comment:
value = '<!-- %s -->' % value value = f'<!-- {value} -->'
return value return value

View file

@ -5,7 +5,6 @@ import random
from pathlib import Path from pathlib import Path
from hashlib import sha256 from hashlib import sha256
from django.contrib.staticfiles.testing import StaticLiveServerTestCase from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from django.contrib.auth import get_user_model
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.webdriver import WebDriver from selenium.webdriver.chrome.webdriver import WebDriver
from selenium.webdriver import ActionChains, Keys from selenium.webdriver import ActionChains, Keys
@ -202,11 +201,3 @@ class WorkflowTests(StaticLiveServerTestCase):
self.assertTrue("Test Hersteller" in self.selenium.page_source) self.assertTrue("Test Hersteller" in self.selenium.page_source)
self.assertTrue("/device/" in self.selenium.current_url) self.assertTrue("/device/" in self.selenium.current_url)
time.sleep(1) time.sleep(1)
def test_member_login(self):
self.selenium.get(
f"{self.live_server_url}/member/login/")
time.sleep(1)
self.selenium.find_element(By.CLASS_NAME, 'login_link').click()
time.sleep(1)
self.assertTrue('sso.makes-hacks-hip.de' in self.selenium.current_url)

View file

@ -33,7 +33,7 @@
<a href="https://www.repaircafe-hilpoltstein.de/" class="nav-link">Repair-Café</a> <a href="https://www.repaircafe-hilpoltstein.de/" class="nav-link">Repair-Café</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a href="{% url 'index' %}" class="nav-link">Anmeldung</a> <a href="{% url 'index' %}" class="nav-link">Geräteanmeldung</a>
</li> </li>
{% if user.is_authenticated %} {% if user.is_authenticated %}
<li class="nav-item"> <li class="nav-item">
@ -41,12 +41,12 @@
</li> </li>
{% else %} {% else %}
<li class="nav-item"> <li class="nav-item">
<a href="{% url 'login' %}" class="nav-link">Gast Anmeldung</a> <a href="{% url 'login' %}" class="nav-link">Gast Login</a>
</li>
<li class="nav-item">
<a href="{% url 'member_login' %}" class="nav-link">Mitglieder Anmeldung</a>
</li> </li>
{% endif %} {% endif %}
<li class="nav-item">
<a href="{% url 'member' %}" class="nav-link">Mitglieder</a>
</li>
</ul> </ul>
</header> </header>
</div> </div>

View file

@ -45,7 +45,7 @@
<a href="{% url 'login' %}" class="nav-link">Gast Anmeldung</a> <a href="{% url 'login' %}" class="nav-link">Gast Anmeldung</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a href="{% url 'member_login' %}" class="nav-link">Mitglieder Anmeldung</a> <a href="{% url 'member' %}" class="nav-link">Mitglieder</a>
</li> </li>
{% endif %} {% endif %}
</ul> </ul>

View file

@ -2,326 +2,35 @@
{% block title %} {% block title %}
- Repair-Cafés - Repair-Cafés
{% endblock title %} {% 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;
}
.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 page_title %} {% block page_title %}
Anmeldung Anmeldung
{% endblock page_title %} {% endblock page_title %}
{% block content %} {% block content %}
<h1 class="mt-2">Repair-Café auswählen</h1> <table class="table">
<hr class="mt-0 mb-4"> <thead>
<ul class="list-group"> <tr>
{% for cafe in object_list %} <th scope="col">Datum</th>
<li class="list-group-item"> <th scope="col">Ort</th>
<div class="news-list-view eventhome snipcss-aSNzE" id="news-container-82"> <th scope="col">Adresse</th>
<div class="rc-date"> <th scope="col">Gerät anmelden</th>
<div class="article event rc-date " </tr>
itemscope="itemscope" </thead>
itemtype="http://schema.org/Article"> <tbody>
<div class="dtdate"> {% for cafe in object_list %}
<time itemprop="datePublished" datetime="13.05.2023"> <tr>
{{ cafe.event_date|date:"l" }}, {{ cafe.event_date|date }}, 14.00 - 17.00 Uhr <td>{{ cafe.event_date|date:"l" }}, {{ cafe.event_date|date }}</td>
</time> <td>{{ cafe.location }}</td>
</div> <td>{{ cafe.address }}</td>
<div class="ort"> <td>
{{ cafe.location }} <a href="{% url 'register_device' cafe.pk %}"
<br> class="btn btn-primary register_link">Gerät anmelden</a>
{{ cafe.address }} </td>
</div> </tr>
<div class="teaser-text"> {% empty %}
<div itemprop="description"> <tr>
<p> <td colspan="4">Es sind keine Repair-Cafés angelegt.</td>
<strong> </tr>
Nur per Voranmeldung. {% endfor %}
<a class="register_link" href="{% url 'register_device' cafe.id %}">Zum Anmeldeformular</a> </tbody>
</strong> </table>
</p>
</div>
</div>
</div>
</div>
</div>
</li>
{% empty %}
<li class="list-group-item">Es gibt keine geplanten Repair-Cafés.</li>
{% endfor %}
</ul>
{% endblock content %} {% endblock content %}

View file

@ -1,25 +0,0 @@
{% extends "repapp/base.html" %}
{% block title %}
Mitarbeiter Anmeldung
{% endblock title %}
{% block page_title %}
Mitarbeiter
{% if user.is_authenticated %}
Anmelden
{% else %}
Abmelden
{% endif %}
{% endblock page_title %}
{% block content %}
<div>
{% if user.is_authenticated %}
<p>Angemeldeter Benutzer: {{ user.email }}</p>
<form action="{% url 'oidc_logout' %}" method="post">
{% csrf_token %}
<button type="submit" class="btn btn-primary">Abmelden</button>
</form>
{% else %}
<a class="login_link" href="{% url 'oidc_authentication_init' %}">Anmelden mit Makes-Hacks-Hip</a>
{% endif %}
</div>
{% endblock content %}

View file

@ -0,0 +1,45 @@
{% extends "repapp/base.html" %}
{% block title %}
- Repair-Cafés
{% endblock title %}
{% block page_title %}
Repair-Cafés
{% endblock page_title %}
{% block content %}
<p>
<a href="{% url 'create_cafe' %}" class="btn btn-primary">Neues Repair-Cafés anlegen</a>
<a href="javascript:history.back()" class="btn btn-primary">Zurück</a>
</p>
<table class="table">
<thead>
<tr>
<th scope="col">ID</th>
<th scope="col">Datum</th>
<th scope="col">Ort</th>
<th scope="col">Adresse</th>
<th scope="col">Bearbeiten</th>
<th scope="col">Löschen</th>
</tr>
</thead>
<tbody>
{% for cafe in object_list %}
<tr>
<th scope="row">{{ cafe.pk }}</th>
<td>{{ cafe.event_date|date:"l" }}, {{ cafe.event_date|date }}</td>
<td>{{ cafe.location }}</td>
<td>{{ cafe.address }}</td>
<td>
<a href="{% url 'edit_cafe' cafe.pk %}" class="btn btn-primary">Bearbeiten</a>
</td>
<td>
<a href="{% url 'delete_cafe' cafe.pk %}" class="btn btn-danger">Löschen</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="4">Es sind keine Repair-Cafés angelegt.</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock content %}

View file

@ -0,0 +1,15 @@
{% extends "repapp/base.html" %}
{% load crispy_forms_tags %}
{% block title %}
- Repair-Café anlegen
{% endblock title %}
{% block page_title %}
Repair-Café anlegen
{% endblock page_title %}
{% block content %}
<form action="{% url 'create_cafe' %}" method="post">
{% csrf_token %}
{{ form|crispy }}
<button type="submit" class="btn btn-primary submit_button">Absenden</button>
</form>
{% endblock content %}

View file

@ -0,0 +1,16 @@
{% extends "repapp/base.html" %}
{% load crispy_forms_tags %}
{% block title %}
- Repair-Café anlegen
{% endblock title %}
{% block page_title %}
Repair-Café anlegen
{% endblock page_title %}
{% block content %}
<form action="{% url 'delete_cafe' cafe.id %}" method="post">
{% csrf_token %}
<p>Soll das Repair-Café am {{ cafe.event_date|date:"l" }}, {{ cafe.event_date|date }} wirklich gelöscht werden?</p>
{{ form|crispy }}
<button type="submit" class="btn btn-primary submit_button">Löschen</button>
</form>
{% endblock content %}

View file

@ -0,0 +1,15 @@
{% extends "repapp/base.html" %}
{% load crispy_forms_tags %}
{% block title %}
- Repair-Café anlegen
{% endblock title %}
{% block page_title %}
Repair-Café anlegen
{% endblock page_title %}
{% block content %}
<form action="{% url 'edit_cafe' cafe.id %}" method="post">
{% csrf_token %}
{{ form|crispy }}
<button type="submit" class="btn btn-primary submit_button">Aktualisieren</button>
</form>
{% endblock content %}

View file

@ -0,0 +1,15 @@
{% extends "repapp/base.html" %}
{% load crispy_forms_tags %}
{% block title %}
- Repair-Café anlegen
{% endblock title %}
{% block page_title %}
Repair-Café anlegen
{% endblock page_title %}
{% block content %}
<form action="{% url 'edit_guest' guest.id %}" method="post">
{% csrf_token %}
{{ form|crispy }}
<button type="submit" class="btn btn-primary submit_button">Aktualisieren</button>
</form>
{% endblock content %}

View file

@ -0,0 +1,44 @@
{% extends "repapp/base.html" %}
{% block title %}
- Gäste
{% endblock title %}
{% block page_title %}
Gäste
{% endblock page_title %}
{% block content %}
<p>
<a href="{% url 'create_guest' %}" class="btn btn-primary">Neuen Gast anlegen</a>
<a href="javascript:history.back()" class="btn btn-primary">Zurück</a>
</p>
<table class="table">
<thead>
<tr>
<th scope="col">ID</th>
<th scope="col">Name</th>
<th scope="col">Telefon</th>
<th scope="col">Wohnort</th>
<th scope="col">eMail Adresse</th>
<th scope="col">Bearbeiten</th>
<th scope="col">Löschen</th>
</tr>
</thead>
<tbody>
{% for guest in object_list %}
<tr>
<th scope="row">{{ guest.pk }}</th>
<td>{{ guest.name }}</td>
<td>{{ guest.phone }}</td>
<td>{{ guest.residence }}</td>
<td>{{ guest.mail }}</td>
<td>
<a href="{% url 'edit_guest' guest.pk %}" class="btn btn-primary">Bearbeiten</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="4">Es sind keine Gäste verfügbar.</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock content %}

View file

@ -0,0 +1,27 @@
{% extends "repapp/base.html" %}
{% load crispy_forms_tags %}
{% block title %}
- Organisation
{% endblock title %}
{% block page_title %}
Organisation
{% endblock page_title %}
{% block content %}
<p>
<a href="{% url 'cafe' %}" class="btn btn-primary">Repair-Cafés</a>
<a href="{% url 'guest' %}" class="btn btn-primary">Gäste</a>
</p>
<hr class="mt-0 mb-4">
<h1 class="mt-2">Repair-Cafés</h1>
<a href="{% url 'create_cafe' %}" class="btn btn-primary">Neues Repair-Café anlegen</a>
// TODO: Button nächstes Repair-Café
<hr class="mt-0 mb-4">
<h1 class="mt-2">Geräte</h1>
// TODO: Liste mit neuen Geräten
<hr class="mt-0 mb-4">
<h1 class="mt-2">Rückfragen</h1>
// TODO: Liste mit Antworten auf Rückfragen
<hr class="mt-0 mb-4">
<h1 class="mt-2">Nachrichten</h1>
// TODO: Liste mit neuen Nachrichten
{% endblock content %}

View file

@ -0,0 +1,16 @@
{% extends "repapp/base.html" %}
{% load crispy_forms_tags %}
{% block title %}
- Organisation
{% endblock title %}
{% block page_title %}
Organisation
{% endblock page_title %}
{% block content %}
<p>
<a href="{% url 'orga' %}" class="btn btn-primary">Weiter als Organisator</a>
</p>
<p>
<a href="{% url 'repa' %}" class="btn btn-primary">Weiter als Reparateur</a>
</p>
{% endblock content %}

View file

@ -0,0 +1,10 @@
{% extends "repapp/base.html" %}
{% load crispy_forms_tags %}
{% block title %}
- Reparateur
{% endblock title %}
{% block page_title %}
Reparateur
{% endblock page_title %}
{% block content %}
{% endblock content %}

View file

@ -271,27 +271,6 @@ class ViewsTest(TestCase):
response, 'type="text" name="accept_agb" style="display:none"') response, 'type="text" name="accept_agb" style="display:none"')
self.assertContains(response, 'csrfmiddlewaretoken') self.assertContains(response, 'csrfmiddlewaretoken')
def test_member_login(self):
"""
Test member login view
"""
client = Client(enforce_csrf_checks=True)
response = client.get(reverse('member_login'))
self.assertEqual(response.status_code, 200)
# ensure OIDC link is displayed
self.assertContains(response, "Anmelden mit Makes-Hacks-Hip")
client.login(username="testuser@example.com", password="aTestPassword")
response = client.get(reverse('member_login'))
self.assertEqual(response.status_code, 200)
# ensure logout button is displayed
self.assertContains(response, "Abmelden")
# ensure user is displayed
self.assertContains(response, "testuser@example.com")
def test_send_one_time_login_mail(self): def test_send_one_time_login_mail(self):
""" """
Test that a mail is sent with the right link included. Test that a mail is sent with the right link included.

View file

@ -24,8 +24,17 @@ urlpatterns = [
views.profile, name="guest_profile"), views.profile, name="guest_profile"),
path("onetimelogin/<str:secret>/", path("onetimelogin/<str:secret>/",
views.one_time_login, name="one_time_login"), views.one_time_login, name="one_time_login"),
path("member/login/", path("member/", views.member, name="member"),
views.member_login, name="member_login"), path("orga/select/", views.select_role, name="select_role"),
path("orga/", views.orga, name="orga"),
path("repa/", views.repa, name="repa"),
path("cafe/", views.CafeView.as_view(), name="cafe"),
path("cafe/create", views.CafeCreateView.as_view(), name="create_cafe"),
path("cafe/<int:pk>/edit", views.CafeUpdateView.as_view(), name="edit_cafe"),
path("cafe/<int:pk>/delete",
views.CafeDeleteView.as_view(), name="delete_cafe"),
path("guest/", views.GuestView.as_view(), name="guest"),
path("guest/<int:pk>/edit", views.GuestUpdateView.as_view(), name="edit_guest"),
path("cron", views.cron, name="cron"), path("cron", views.cron, name="cron"),
path("bootstrap", views.bootstrap, name="bootstrap"), path("bootstrap", views.bootstrap, name="bootstrap"),
path("process_mails", views.process_mails, name="process_mails"), path("process_mails", views.process_mails, name="process_mails"),

View file

@ -6,6 +6,7 @@ import random
import string import string
import time import time
import os import os
import logging
from hashlib import sha256 from hashlib import sha256
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.urls import reverse from django.urls import reverse
@ -17,6 +18,7 @@ from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect, HttpResponse from django.http import HttpResponseRedirect, HttpResponse
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.contrib.auth.decorators import login_required 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
from django.utils.timezone import now from django.utils.timezone import now
from .forms import RegisterDevice, RegisterGuest from .forms import RegisterDevice, RegisterGuest
@ -25,6 +27,9 @@ from .models import (Cafe, Device, Guest, OneTimeLogin,
from . import mail_interface from . import mail_interface
logger = logging.getLogger(__name__)
def send_one_time_login_mail(secret, mail, request): def send_one_time_login_mail(secret, mail, request):
""" """
Send a mail with a one time login link. Send a mail with a one time login link.
@ -40,7 +45,7 @@ def send_one_time_login_mail(secret, mail, request):
'link': url, 'link': url,
}) })
ok = send_mail( send_ok = send_mail(
subject=subject, subject=subject,
message=text, message=text,
from_email=os.getenv("DJANGO_SENDER_ADDRESS", ""), from_email=os.getenv("DJANGO_SENDER_ADDRESS", ""),
@ -49,7 +54,7 @@ def send_one_time_login_mail(secret, mail, request):
html_message=html html_message=html
) )
if ok > 0: if send_ok > 0:
messages.add_message(request, messages.INFO, messages.add_message(request, messages.INFO,
'Sie haben einen neuen Login per eMail erhalten.') 'Sie haben einen neuen Login per eMail erhalten.')
@ -133,7 +138,7 @@ def send_guest_account_mail(guest, password, request):
'password': password, 'password': password,
}) })
ok = send_mail( send_ok = send_mail(
subject=subject, subject=subject,
message=text, message=text,
from_email=os.getenv("DJANGO_SENDER_ADDRESS", ""), from_email=os.getenv("DJANGO_SENDER_ADDRESS", ""),
@ -142,7 +147,7 @@ def send_guest_account_mail(guest, password, request):
html_message=html html_message=html
) )
if ok > 0: if send_ok > 0:
messages.add_message(request, messages.INFO, messages.add_message(request, messages.INFO,
'Sie haben ihre Benutzerdaten per eMail erhalten.') 'Sie haben ihre Benutzerdaten per eMail erhalten.')
@ -156,6 +161,22 @@ def is_member(user):
return organisator or reparateur return organisator or reparateur
def is_organisator(user):
"""
Test is a user is a Repair-Café organisator.
"""
organisator = Organisator.objects.filter(mail=user.email).first()
return organisator is not None
def is_reparateur(user):
"""
Test is a user is a Repair-Café reparateur.
"""
reparateur = Reparateur.objects.filter(mail=user.email).first()
return reparateur is not None
def create_one_time_login(user, url) -> str: def create_one_time_login(user, url) -> str:
""" """
create_one_time_login creates a one time login object for guest user logins. create_one_time_login creates a one time login object for guest user logins.
@ -397,16 +418,92 @@ def profile(request):
) )
def member_login(request): def member(request):
""" """
Login page for repair cafe members, using OIDC. Login page for repair cafe members, using OIDC.
""" """
# TODO: test
logger.debug('Member-View: User: %s', str(request.user))
if request.user.is_anonymous or not is_member(request.user):
return HttpResponseRedirect(reverse_lazy('oidc_authentication_init'))
if is_organisator(request.user) and is_reparateur(request.user):
return HttpResponseRedirect(reverse_lazy('select_role'))
if is_organisator(request.user):
return HttpResponseRedirect(reverse_lazy('orga'))
if is_reparateur(request.user):
return HttpResponseRedirect(reverse_lazy('repa'))
logger.warning('User %s is no member!', str(request.user))
raise PermissionDenied('No member!')
@login_required(login_url=reverse_lazy('member'))
def orga(request):
"""
Landing page for organisators.
"""
# TODO: test
if not is_organisator(request.user):
logger.warning('The user %s is no organisator!', str(request.user))
raise PermissionDenied('Not organisator!')
return render( return render(
request, request,
"repapp/member_login.html" "repapp/orga/main.html"
) )
@login_required(login_url=reverse_lazy('member'))
def select_role(request):
"""
Landing page for organisators.
"""
# TODO: test
if not is_organisator(request.user):
logger.warning('The user %s is no organisator!', str(request.user))
raise PermissionDenied('Not organisator!')
if not is_reparateur(request.user):
logger.warning('The user %s is no reparateur!', str(request.user))
raise PermissionDenied('Not reparateur!')
return render(
request,
"repapp/orga/select_role.html"
)
@login_required(login_url=reverse_lazy('member'))
def repa(request):
"""
Organisator main menu.
"""
# TODO: test
if not is_reparateur(request.user):
logger.warning('The user %s is no reparateur!', str(request.user))
raise PermissionDenied('Not reparateur!')
return render(
request,
"repapp/repa/main.html"
)
class CafeView(LoginRequiredMixin, generic.ListView):
"""
List of all Repair-Cafés.
"""
# TODO: test
login_url = reverse_lazy('member')
template_name = "repapp/orga/cafe.html"
def get_queryset(self):
return Cafe.objects.all()
def cron(request): def cron(request):
""" """
View to trigger automated regular tasks. View to trigger automated regular tasks.
@ -462,3 +559,77 @@ def bootstrap(request):
request, request,
"repapp/bootstrap.html" "repapp/bootstrap.html"
) )
class CafeCreateView(LoginRequiredMixin, UserPassesTestMixin, generic.edit.CreateView):
"""
Organisator view for creating a new Repair-Café.
"""
# TODO: create test
login_url = reverse_lazy('member')
template_name = "repapp/orga/create_cafe.html"
model = Cafe
fields = ['event_date', 'location', 'address']
success_url = reverse_lazy('cafe')
def test_func(self):
return is_organisator(self.request.user)
class CafeUpdateView(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_cafe.html"
model = Cafe
fields = ['event_date', 'location', 'address']
success_url = reverse_lazy('cafe')
def test_func(self):
logger.debug('User passes test: user: %s, result: %s.',
self.request.user, is_organisator(self.request.user))
return is_organisator(self.request.user)
class CafeDeleteView(LoginRequiredMixin, UserPassesTestMixin, generic.edit.DeleteView):
"""
Organisator view for edit a Repair-Café.
"""
# TODO: create test
login_url = reverse_lazy('member')
template_name = "repapp/orga/delete_cafe.html"
model = Cafe
success_url = reverse_lazy('cafe')
def test_func(self):
logger.debug('User passes test: user: %s, result: %s.',
self.request.user, is_organisator(self.request.user))
return is_organisator(self.request.user)
class GuestView(LoginRequiredMixin, generic.ListView):
"""
List of all Repair-Cafés.
"""
# TODO: test
login_url = reverse_lazy('member')
template_name = "repapp/orga/guest.html"
def get_queryset(self):
return Guest.objects.all()
class GuestUpdateView(LoginRequiredMixin, UserPassesTestMixin, generic.edit.UpdateView):
"""
Organisator view for edit a guest.
"""
# TODO: create test
login_url = reverse_lazy('member')
template_name = "repapp/orga/edit_guest.html"
model = Guest
success_url = reverse_lazy('guest')
def test_func(self):
return is_organisator(self.request.user)