LiveTest register device
This commit is contained in:
parent
f852a89e8a
commit
904d009fc9
13 changed files with 213 additions and 68 deletions
24
README.md
24
README.md
|
|
@ -7,13 +7,13 @@ For documentation see https://makes-hacks-hip.github.io/RepApp/.
|
||||||
## Setup for development
|
## Setup for development
|
||||||
|
|
||||||
- Clone repo
|
- Clone repo
|
||||||
- python -m venv venv
|
- `python -m venv venv`
|
||||||
- source venv/bin/activate
|
- `source venv/bin/activate`
|
||||||
- cd rc_hip
|
- `cd rc_hip`
|
||||||
- python manage.py makemigrations
|
- `python manage.py makemigrations`
|
||||||
- python manage.py migrate
|
- `python manage.py migrate`
|
||||||
- python manage.py createsuperuser
|
- `python manage.py createsuperuser`
|
||||||
- python manage.py runserver
|
- `python manage.py runserver`
|
||||||
|
|
||||||
## Build Docker image
|
## Build Docker image
|
||||||
|
|
||||||
|
|
@ -30,3 +30,13 @@ docker run --rm -d \
|
||||||
repapp`
|
repapp`
|
||||||
```
|
```
|
||||||
- Open `http://127.0.0.1:8020/` in your browser
|
- Open `http://127.0.0.1:8020/` in your browser
|
||||||
|
|
||||||
|
## Run tests
|
||||||
|
|
||||||
|
### Unit tests
|
||||||
|
|
||||||
|
Run `python manage.py test`
|
||||||
|
|
||||||
|
### LiveServer Tests
|
||||||
|
|
||||||
|
Run `python manage.py test repapp.live_tests.WorkflowTests`
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,21 @@ from .models import (Organisator, Cafe, Question, Device, CustomUser,
|
||||||
Reparateur, Appointment, Guest, Candidate, OneTimeLogin, Message)
|
Reparateur, Appointment, Guest, Candidate, OneTimeLogin, Message)
|
||||||
|
|
||||||
|
|
||||||
|
class CafeResource(resources.ModelResource):
|
||||||
|
"""
|
||||||
|
Resource wrapper for Cafe model.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
"""
|
||||||
|
Meta data for resource model.
|
||||||
|
"""
|
||||||
|
model = Cafe
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_display_name():
|
||||||
|
return "Repair-Café"
|
||||||
|
|
||||||
|
|
||||||
class GuestResource(resources.ModelResource):
|
class GuestResource(resources.ModelResource):
|
||||||
"""
|
"""
|
||||||
Resource wrapper for Guest model.
|
Resource wrapper for Guest model.
|
||||||
|
|
@ -93,17 +108,18 @@ class ExportAdmin(ImportExportModelAdmin, ExportActionMixin):
|
||||||
QuestionResource,
|
QuestionResource,
|
||||||
AppointmentResource,
|
AppointmentResource,
|
||||||
CandidateResource,
|
CandidateResource,
|
||||||
|
CafeResource,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
# Register all models of RepApp for the admin interface.
|
# Register all models of RepApp for the admin interface.
|
||||||
admin.site.register(Organisator)
|
admin.site.register(Organisator)
|
||||||
admin.site.register(Cafe)
|
|
||||||
admin.site.register(Reparateur)
|
admin.site.register(Reparateur)
|
||||||
admin.site.register(CustomUser)
|
admin.site.register(CustomUser)
|
||||||
admin.site.register(OneTimeLogin)
|
admin.site.register(OneTimeLogin)
|
||||||
admin.site.register(Message)
|
admin.site.register(Message)
|
||||||
# Make the "data" models exportable.
|
# Make the "data" models exportable.
|
||||||
|
admin.site.register(Cafe, ExportAdmin)
|
||||||
admin.site.register(Question, ExportAdmin)
|
admin.site.register(Question, ExportAdmin)
|
||||||
admin.site.register(Guest, ExportAdmin)
|
admin.site.register(Guest, ExportAdmin)
|
||||||
admin.site.register(Device, ExportAdmin)
|
admin.site.register(Device, ExportAdmin)
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ class OneTimeLoginBackend(ModelBackend):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def authenticate(self, request, username=None, password=None, **kwargs):
|
def authenticate(self, request, username=None, password=None, **kwargs):
|
||||||
print(f'authenticate {username}')
|
|
||||||
login = OneTimeLogin.objects.filter(secret=username).first()
|
login = OneTimeLogin.objects.filter(secret=username).first()
|
||||||
if login:
|
if login:
|
||||||
return login.user
|
return login.user
|
||||||
|
|
|
||||||
BIN
rc_hip/repapp/fixtures/Hummingbird.jpg
Normal file
BIN
rc_hip/repapp/fixtures/Hummingbird.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 572 KiB |
BIN
rc_hip/repapp/fixtures/Nut.jpg
Normal file
BIN
rc_hip/repapp/fixtures/Nut.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 933 KiB |
11
rc_hip/repapp/fixtures/cafe-data.json
Normal file
11
rc_hip/repapp/fixtures/cafe-data.json
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"model": "repapp.cafe",
|
||||||
|
"pk": 1,
|
||||||
|
"fields": {
|
||||||
|
"location": "Haus Einstein' (beim Gymnasium)",
|
||||||
|
"address": "Patersholzer Weg 19, Hilpoltstein",
|
||||||
|
"event_date": "2023-05-13"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
112
rc_hip/repapp/live_tests.py
Normal file
112
rc_hip/repapp/live_tests.py
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
import time
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
|
||||||
|
from selenium.webdriver.common.by import By
|
||||||
|
from selenium.webdriver.firefox.webdriver import WebDriver
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowTests(StaticLiveServerTestCase):
|
||||||
|
fixtures = ["cafe-data.json"]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super().setUpClass()
|
||||||
|
cls.selenium = WebDriver()
|
||||||
|
cls.selenium.implicitly_wait(10)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
cls.selenium.quit()
|
||||||
|
super().tearDownClass()
|
||||||
|
|
||||||
|
def test_register_device(self):
|
||||||
|
# Open landing page
|
||||||
|
self.selenium.get(f"{self.live_server_url}/")
|
||||||
|
# Click on a register link
|
||||||
|
self.selenium.find_element(By.CLASS_NAME, "register_link").click()
|
||||||
|
time.sleep(1)
|
||||||
|
# Enter new device
|
||||||
|
# Enter guest mail address
|
||||||
|
mail = self.selenium.find_element(By.NAME, "mail")
|
||||||
|
mail.send_keys("guest@example.com")
|
||||||
|
# Enter device
|
||||||
|
device = self.selenium.find_element(By.NAME, "device")
|
||||||
|
device.send_keys("A Test Device")
|
||||||
|
# Enter manufacturer
|
||||||
|
manufacturer = self.selenium.find_element(By.NAME, "manufacturer")
|
||||||
|
manufacturer.send_keys("A Test Manufacturer")
|
||||||
|
# Enter a error description
|
||||||
|
error = self.selenium.find_element(By.NAME, "error")
|
||||||
|
error.send_keys("A description of the issue\nof the device.")
|
||||||
|
# Skip device picture and device type plate
|
||||||
|
# No follow up repair
|
||||||
|
# Accept repair conditions
|
||||||
|
self.selenium.find_element(By.NAME, "confirm_repair").click()
|
||||||
|
# Accept data protection notice
|
||||||
|
self.selenium.find_element(By.NAME, "confirm_data").click()
|
||||||
|
time.sleep(1)
|
||||||
|
# Send form
|
||||||
|
self.selenium.find_element(By.CLASS_NAME, 'submit_button').click()
|
||||||
|
time.sleep(1)
|
||||||
|
# Register new guest
|
||||||
|
# Enter guest name
|
||||||
|
name = self.selenium.find_element(By.NAME, "name")
|
||||||
|
name.send_keys("A Guest")
|
||||||
|
# Enter guest phone
|
||||||
|
phone = self.selenium.find_element(By.NAME, "phone")
|
||||||
|
phone.send_keys("0911 123 456")
|
||||||
|
# Enter guest residence
|
||||||
|
residence = self.selenium.find_element(By.NAME, "residence")
|
||||||
|
residence.send_keys("Musterstadt")
|
||||||
|
time.sleep(1)
|
||||||
|
# Send form
|
||||||
|
self.selenium.find_element(By.CLASS_NAME, 'submit_button').click()
|
||||||
|
time.sleep(1)
|
||||||
|
self.assertTrue("confirm" in self.selenium.current_url)
|
||||||
|
self.assertTrue("Anmeldung erfolgreich!" in self.selenium.page_source)
|
||||||
|
# Register second device
|
||||||
|
# Open landing page again
|
||||||
|
self.selenium.get(f"{self.live_server_url}/")
|
||||||
|
# Click on a register link
|
||||||
|
self.selenium.find_element(By.CLASS_NAME, "register_link").click()
|
||||||
|
time.sleep(1)
|
||||||
|
# Enter new device
|
||||||
|
# Enter guest mail address
|
||||||
|
mail = self.selenium.find_element(By.NAME, "mail")
|
||||||
|
mail.send_keys("guest@example.com")
|
||||||
|
# Enter device
|
||||||
|
device = self.selenium.find_element(By.NAME, "device")
|
||||||
|
device.send_keys("Another Test Device")
|
||||||
|
# Enter manufacturer
|
||||||
|
manufacturer = self.selenium.find_element(By.NAME, "manufacturer")
|
||||||
|
manufacturer.send_keys("Another Test Manufacturer")
|
||||||
|
# Enter a error description
|
||||||
|
error = self.selenium.find_element(By.NAME, "error")
|
||||||
|
error.send_keys("Another issue description.")
|
||||||
|
# Add device picture
|
||||||
|
test_script_folder = Path(__file__).resolve().parent
|
||||||
|
fixtures_folder = os.path.join(test_script_folder, "fixtures")
|
||||||
|
device_picture_file = os.path.join(fixtures_folder, "Hummingbird.jpg")
|
||||||
|
device_picture = self.selenium.find_element(By.NAME, "device_picture")
|
||||||
|
device_picture.send_keys(device_picture_file)
|
||||||
|
print(f"Using device picture {device_picture_file}")
|
||||||
|
# Add type plate picture
|
||||||
|
type_plate_picture_file = os.path.join(fixtures_folder, "Nut.jpg")
|
||||||
|
type_plate_picture = self.selenium.find_element(
|
||||||
|
By.NAME, "type_plate_picture")
|
||||||
|
type_plate_picture.send_keys(type_plate_picture_file)
|
||||||
|
print(f"Using type plate picture {type_plate_picture_file}")
|
||||||
|
# Select as follow up repair
|
||||||
|
self.selenium.find_element(By.NAME, "follow_up").click()
|
||||||
|
# Accept repair conditions
|
||||||
|
self.selenium.find_element(By.NAME, "confirm_repair").click()
|
||||||
|
# Accept data protection notice
|
||||||
|
self.selenium.find_element(By.NAME, "confirm_data").click()
|
||||||
|
time.sleep(1)
|
||||||
|
# Send form
|
||||||
|
self.selenium.find_element(By.CLASS_NAME, 'submit_button').click()
|
||||||
|
time.sleep(1)
|
||||||
|
# Guest is known, no guest registration needed.
|
||||||
|
self.assertTrue("confirm" in self.selenium.current_url)
|
||||||
|
self.assertTrue("Anmeldung erfolgreich!" in self.selenium.page_source)
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
{% extends "repapp/base.html" %}
|
{% extends "repapp/base.html" %}
|
||||||
{% block title %}
|
{% block title %}
|
||||||
- Repair-Cafés
|
- Repair-Cafés
|
||||||
{% endblock title %}
|
{% endblock title %}
|
||||||
{% block head %}
|
{% block head %}
|
||||||
<style>
|
<style>
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family:'Source Sans Pro';
|
font-family:'Source Sans Pro';
|
||||||
font-style:normal;
|
font-style:normal;
|
||||||
|
|
@ -280,45 +280,45 @@
|
||||||
color: #ffffff!important;
|
color: #ffffff!important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock head %}
|
{% endblock head %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1 class="mt-2">Repair-Cafés</h1>
|
<h1 class="mt-2">Repair-Cafés</h1>
|
||||||
<hr class="mt-0 mb-4">
|
<hr class="mt-0 mb-4">
|
||||||
<ul class="list-group">
|
<ul class="list-group">
|
||||||
{% for cafe in object_list %}
|
{% for cafe in object_list %}
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
<div class="news-list-view eventhome snipcss-aSNzE" id="news-container-82">
|
<div class="news-list-view eventhome snipcss-aSNzE" id="news-container-82">
|
||||||
<div class="rc-date">
|
<div class="rc-date">
|
||||||
<div class="article event rc-date "
|
<div class="article event rc-date "
|
||||||
itemscope="itemscope"
|
itemscope="itemscope"
|
||||||
itemtype="http://schema.org/Article">
|
itemtype="http://schema.org/Article">
|
||||||
<div class="dtdate">
|
<div class="dtdate">
|
||||||
<time itemprop="datePublished" datetime="13.05.2023">
|
<time itemprop="datePublished" datetime="13.05.2023">
|
||||||
{{ cafe.event_date|date:"l" }}, {{ cafe.event_date|date }}, 14.00 - 17.00 Uhr
|
{{ cafe.event_date|date:"l" }}, {{ cafe.event_date|date }}, 14.00 - 17.00 Uhr
|
||||||
</time>
|
</time>
|
||||||
</div>
|
</div>
|
||||||
<div class="ort">
|
<div class="ort">
|
||||||
{{ cafe.location }}
|
{{ cafe.location }}
|
||||||
<br>
|
<br>
|
||||||
{{ cafe.address }}
|
{{ cafe.address }}
|
||||||
</div>
|
</div>
|
||||||
<div class="teaser-text">
|
<div class="teaser-text">
|
||||||
<div itemprop="description">
|
<div itemprop="description">
|
||||||
<p>
|
<p>
|
||||||
<strong>
|
<strong>
|
||||||
Nur per Voranmeldung.
|
Nur per Voranmeldung.
|
||||||
<a href="{% url 'register_device' cafe.id %}">Zum Anmeldeformular</a>
|
<a class="register_link" href="{% url 'register_device' cafe.id %}">Zum Anmeldeformular</a>
|
||||||
</strong>
|
</strong>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</div>
|
||||||
{% empty %}
|
</div>
|
||||||
<li class="list-group-item">Es gibt keine geplanten Repair-Cafés.</li>
|
</div>
|
||||||
{% endfor %}
|
</div>
|
||||||
</ul>
|
</li>
|
||||||
|
{% empty %}
|
||||||
|
<li class="list-group-item">Es gibt keine geplanten Repair-Cafés.</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,6 @@
|
||||||
enctype="multipart/form-data">
|
enctype="multipart/form-data">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form|crispy }}
|
{{ form|crispy }}
|
||||||
<button type="submit" class="btn btn-primary">Absenden</button>
|
<button type="submit" class="btn btn-primary submit_button">Absenden</button>
|
||||||
</form>
|
</form>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
{% extends "repapp/base.html" %}
|
{% extends "repapp/base.html" %}
|
||||||
|
|
||||||
{% load crispy_forms_tags %}
|
{% load crispy_forms_tags %}
|
||||||
|
{% block title %}
|
||||||
{% block title %}- Gast anmelden{% endblock title %}
|
- Gast anmelden
|
||||||
|
{% endblock title %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1 class="mt-2">Ihre Daten</h1>
|
<h1 class="mt-2">Ihre Daten</h1>
|
||||||
<hr class="mt-0 mb-4">
|
<hr class="mt-0 mb-4">
|
||||||
<form action="{% url 'register_guest' cafe.id device.identifier mail %}" method="post">
|
<form action="{% url 'register_guest' cafe.id device.identifier mail %}"
|
||||||
{% csrf_token %}
|
method="post">
|
||||||
{{ form|crispy }}
|
{% csrf_token %}
|
||||||
<button type="submit" class="btn btn-primary">Absenden</button>
|
{{ form|crispy }}
|
||||||
</form>
|
<button type="submit" class="btn btn-primary submit_button">Absenden</button>
|
||||||
{% endblock content %}
|
</form>
|
||||||
|
{% endblock content %}
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,11 @@ Tests for RepApp.
|
||||||
"""
|
"""
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import django.utils.timezone
|
import django.utils.timezone
|
||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.test import Client
|
from django.test import Client
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from .backends import OneTimeLoginBackend, EmailBackend
|
from .backends import OneTimeLoginBackend, EmailBackend
|
||||||
from .models import OneTimeLogin, CustomUser, Cafe
|
from .models import OneTimeLogin, CustomUser, Cafe
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,23 +65,17 @@ class RegisterDeviceFormView(generic.edit.FormView):
|
||||||
|
|
||||||
guest = Guest.objects.filter(mail=mail).first()
|
guest = Guest.objects.filter(mail=mail).first()
|
||||||
|
|
||||||
print(f'Register: Dev: {device}, Guest: {guest}')
|
|
||||||
|
|
||||||
if guest:
|
if guest:
|
||||||
print('Guest already known')
|
|
||||||
device.guest = guest
|
device.guest = guest
|
||||||
device.save()
|
device.save()
|
||||||
|
|
||||||
send_confirmation_mails(device, guest, cafe, self.request)
|
send_confirmation_mails(device, guest, cafe, self.request)
|
||||||
|
|
||||||
print('Mails sent')
|
|
||||||
|
|
||||||
resp = HttpResponseRedirect(
|
resp = HttpResponseRedirect(
|
||||||
reverse_lazy('register_device_final', kwargs={
|
reverse_lazy('register_device_final', kwargs={
|
||||||
'cafe': cafe.pk,
|
'cafe': cafe.pk,
|
||||||
'device_identifier': device.identifier})
|
'device_identifier': device.identifier})
|
||||||
)
|
)
|
||||||
print(resp)
|
|
||||||
return resp
|
return resp
|
||||||
else:
|
else:
|
||||||
return HttpResponseRedirect(
|
return HttpResponseRedirect(
|
||||||
|
|
|
||||||
|
|
@ -9,3 +9,4 @@ crispy-bootstrap4==2022.1
|
||||||
mozilla-django-oidc==3.0.0
|
mozilla-django-oidc==3.0.0
|
||||||
djlint==1.23.3
|
djlint==1.23.3
|
||||||
django-import-export==3.2.0
|
django-import-export==3.2.0
|
||||||
|
selenium==4.9.0
|
||||||
|
|
|
||||||
Reference in a new issue