Compare commits
No commits in common. "main" and "gh-pages" have entirely different histories.
15
.github/workflows/main.yml
vendored
|
|
@ -1,15 +0,0 @@
|
|||
name: ci
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.x
|
||||
- run: pip install mkdocs-material
|
||||
- run: mkdocs gh-deploy --force
|
||||
506
.gitignore
vendored
|
|
@ -1,506 +0,0 @@
|
|||
|
||||
.env
|
||||
**/data.json
|
||||
|
||||
rc_hip/static
|
||||
rc_hip/static/**
|
||||
|
||||
rc_hip/data
|
||||
rc_hip/data/**
|
||||
|
||||
repapp/static
|
||||
repapp/static/**
|
||||
|
||||
repapp/data
|
||||
repapp/data/**
|
||||
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
pip-wheel-metadata/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/django,linux,windows,macos,python,visualstudiocode
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=django,linux,windows,macos,python,visualstudiocode
|
||||
|
||||
### Django ###
|
||||
*.log
|
||||
*.pot
|
||||
*.pyc
|
||||
__pycache__/
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
media
|
||||
|
||||
# If your build process includes running collectstatic, then you probably don't need or want to include staticfiles/
|
||||
# in your Git repository. Update and uncomment the following line accordingly.
|
||||
# <django-project-name>/staticfiles/
|
||||
|
||||
### Django.Python Stack ###
|
||||
# Byte-compiled / optimized / DLL files
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
|
||||
# Django stuff:
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
.pdm.toml
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
### Linux ###
|
||||
*~
|
||||
|
||||
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||
.fuse_hidden*
|
||||
|
||||
# KDE directory preferences
|
||||
.directory
|
||||
|
||||
# Linux trash folder which might appear on any partition or disk
|
||||
.Trash-*
|
||||
|
||||
# .nfs files are created when an open file is removed but is still being accessed
|
||||
.nfs*
|
||||
|
||||
### macOS ###
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
### macOS Patch ###
|
||||
# iCloud generated files
|
||||
*.icloud
|
||||
|
||||
### Python ###
|
||||
# Byte-compiled / optimized / DLL files
|
||||
|
||||
# C extensions
|
||||
|
||||
# Distribution / packaging
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
|
||||
# Installer logs
|
||||
|
||||
# Unit test / coverage reports
|
||||
|
||||
# Translations
|
||||
|
||||
# Django stuff:
|
||||
|
||||
# Flask stuff:
|
||||
|
||||
# Scrapy stuff:
|
||||
|
||||
# Sphinx documentation
|
||||
|
||||
# PyBuilder
|
||||
|
||||
# Jupyter Notebook
|
||||
|
||||
# IPython
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
|
||||
# Celery stuff
|
||||
|
||||
# SageMath parsed files
|
||||
|
||||
# Environments
|
||||
|
||||
# Spyder project settings
|
||||
|
||||
# Rope project settings
|
||||
|
||||
# mkdocs documentation
|
||||
|
||||
# mypy
|
||||
|
||||
# Pyre type checker
|
||||
|
||||
# pytype static type analyzer
|
||||
|
||||
# Cython debug symbols
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
|
||||
### Python Patch ###
|
||||
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
|
||||
poetry.toml
|
||||
|
||||
# ruff
|
||||
.ruff_cache/
|
||||
|
||||
# LSP config files
|
||||
pyrightconfig.json
|
||||
|
||||
### VisualStudioCode ###
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.code-snippets
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
|
||||
### VisualStudioCode Patch ###
|
||||
# Ignore all local history of files
|
||||
.history
|
||||
.ionide
|
||||
|
||||
### Windows ###
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
Thumbs.db:encryptable
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
# Dump file
|
||||
*.stackdump
|
||||
|
||||
# Folder config file
|
||||
[Dd]esktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/django,linux,windows,macos,python,visualstudiocode
|
||||
3
.gitmodules
vendored
|
|
@ -1,3 +0,0 @@
|
|||
[submodule "bootstrap"]
|
||||
path = bootstrap
|
||||
url = https://github.com/twbs/bootstrap.git
|
||||
5
.vscode/settings.json
vendored
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"cSpell.words": [
|
||||
"Reparateur"
|
||||
]
|
||||
}
|
||||
136
404.html
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
<!DOCTYPE html>
|
||||
<html class="writer-html5" lang="en" >
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="shortcut icon" href="/RepApp/img/favicon.ico" />
|
||||
<title>RepApp - Makespace Repair-Café Hilpoltstein</title>
|
||||
<link rel="stylesheet" href="/RepApp/css/theme.css" />
|
||||
<link rel="stylesheet" href="/RepApp/css/theme_extra.css" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/github.min.css" />
|
||||
|
||||
<!--[if lt IE 9]>
|
||||
<script src="/RepApp/js/html5shiv.min.js"></script>
|
||||
<![endif]-->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"></script>
|
||||
<script>hljs.highlightAll();</script>
|
||||
</head>
|
||||
|
||||
<body class="wy-body-for-nav" role="document">
|
||||
|
||||
<div class="wy-grid-for-nav">
|
||||
<nav data-toggle="wy-nav-shift" class="wy-nav-side stickynav">
|
||||
<div class="wy-side-scroll">
|
||||
<div class="wy-side-nav-search">
|
||||
<a href="/RepApp/." class="icon icon-home"> RepApp - Makespace Repair-Café Hilpoltstein
|
||||
</a><div role="search">
|
||||
<form id ="rtd-search-form" class="wy-form" action="/RepApp//search.html" method="get">
|
||||
<input type="text" name="q" placeholder="Search docs" aria-label="Search docs" title="Type search term here" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="Navigation menu">
|
||||
<p class="caption"><span class="caption-text">RepApp</span></p>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="/RepApp/.">Beschreibung</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="caption"><span class="caption-text">Developer documentation</span></p>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="/RepApp/dev/">Overview</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="/RepApp/dev/users/">Users</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="/RepApp/dev/stories/">Stories</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="/RepApp/dev/use_cases/">Use cases</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="/RepApp/dev/apps/">Apps</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="/RepApp/dev/objects/">Objects</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="/RepApp/dev/configuration/">Repapp configuration</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="caption"><span class="caption-text">Repair-Café & Makespace</span></p>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="" href="https://www.repaircafe-hilpoltstein.de">Repair-Café</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="" href="https://makes-hacks-hip.de">Makespace</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="" href="https://makes-hacks-hip.github.io">Makerspace@Github</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
|
||||
<nav class="wy-nav-top" role="navigation" aria-label="Mobile navigation menu">
|
||||
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
|
||||
<a href="/RepApp/.">RepApp - Makespace Repair-Café Hilpoltstein</a>
|
||||
|
||||
</nav>
|
||||
<div class="wy-nav-content">
|
||||
<div class="rst-content"><div role="navigation" aria-label="breadcrumbs navigation">
|
||||
<ul class="wy-breadcrumbs">
|
||||
<li><a href="/RepApp/." class="icon icon-home" aria-label="Docs"></a> »</li>
|
||||
<li class="wy-breadcrumbs-aside">
|
||||
</li>
|
||||
</ul>
|
||||
<hr/>
|
||||
</div>
|
||||
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
|
||||
<div class="section" itemprop="articleBody">
|
||||
|
||||
|
||||
<h1 id="404-page-not-found">404</h1>
|
||||
|
||||
<p><strong>Page not found</strong></p>
|
||||
|
||||
|
||||
</div>
|
||||
</div><footer>
|
||||
|
||||
<hr/>
|
||||
|
||||
<div role="contentinfo">
|
||||
<!-- Copyright etc -->
|
||||
</div>
|
||||
|
||||
Built with <a href="https://www.mkdocs.org/">MkDocs</a> using a <a href="https://github.com/readthedocs/sphinx_rtd_theme">theme</a> provided by <a href="https://readthedocs.org">Read the Docs</a>.
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="rst-versions" role="note" aria-label="Versions">
|
||||
<span class="rst-current-version" data-toggle="rst-current-version">
|
||||
|
||||
<span>
|
||||
<a href="https://github.com/makes-hacks-hip/RepApp/" class="fa fa-github" style="color: #fcfcfc"> GitHub</a>
|
||||
</span>
|
||||
|
||||
|
||||
|
||||
</span>
|
||||
</div>
|
||||
<script src="/RepApp/js/jquery-3.6.0.min.js"></script>
|
||||
<script>var base_url = "/RepApp/";</script>
|
||||
<script src="/RepApp/js/theme_extra.js"></script>
|
||||
<script src="/RepApp/js/theme.js"></script>
|
||||
<script src="/RepApp/search/main.js"></script>
|
||||
<script>
|
||||
jQuery(function () {
|
||||
SphinxRtdTheme.Navigation.enable(true);
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
33
Dockerfile
|
|
@ -1,33 +0,0 @@
|
|||
FROM python:3-bullseye
|
||||
|
||||
# install nginx
|
||||
RUN apt-get update && apt-get install nginx vim -y --no-install-recommends
|
||||
COPY nginx.default /etc/nginx/sites-available/default
|
||||
RUN ln -sf /dev/stdout /var/log/nginx/access.log \
|
||||
&& ln -sf /dev/stderr /var/log/nginx/error.log
|
||||
|
||||
# copy source and install dependencies
|
||||
RUN mkdir -p /opt/app
|
||||
RUN mkdir -p /opt/app/pip_cache
|
||||
RUN mkdir -p /opt/app/rc_hip
|
||||
RUN mkdir -p /opt/app/rc_hip/data
|
||||
RUN mkdir -p /opt/app/rc_hip/media
|
||||
RUN mkdir -p /opt/app/rc_hip/static
|
||||
|
||||
COPY rc_hip /opt/app/rc_hip
|
||||
RUN rm -f /opt/app/rc_hip/data/db.sqlite3
|
||||
|
||||
WORKDIR /opt/app
|
||||
|
||||
COPY requirements_prod.txt /opt/app/
|
||||
RUN pip install -r requirements_prod.txt --cache-dir /opt/app/pip_cache
|
||||
|
||||
COPY start_server.sh /opt/app/
|
||||
RUN chmod +x /opt/app/start_server.sh
|
||||
|
||||
RUN chown -R www-data:www-data /opt/app
|
||||
|
||||
# start server
|
||||
EXPOSE 8020
|
||||
STOPSIGNAL SIGTERM
|
||||
CMD ["/opt/app/start_server.sh"]
|
||||
201
LICENSE
|
|
@ -1,201 +0,0 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
47
README.md
|
|
@ -1,47 +0,0 @@
|
|||
# RepApp
|
||||
|
||||
Web App um Reparaturtermine zu vereinbaren.
|
||||
|
||||
For documentation see https://makes-hacks-hip.github.io/RepApp/.
|
||||
|
||||
## Setup for development
|
||||
|
||||
- Clone repo
|
||||
- `python -m venv venv`
|
||||
- `source venv/bin/activate`
|
||||
- `cd rc_hip`
|
||||
- `python manage.py makemigrations`
|
||||
- `python manage.py migrate`
|
||||
- `python manage.py createsuperuser`
|
||||
- `python manage.py runserver`
|
||||
|
||||
## Build Docker image
|
||||
|
||||
- Build the image: `docker build -t repapp .`
|
||||
- Run the image:
|
||||
|
||||
```bash
|
||||
docker run --rm -d \
|
||||
-p 127.0.0.1:8020:8020 \
|
||||
--name repapp \
|
||||
--env DJANGO_SUPERUSER_USERNAME=suba \
|
||||
--env DJANGO_SUPERUSER_PASSWORD=ThePassword \
|
||||
--env DJANGO_SUPERUSER_EMAIL=noreply@example.com \
|
||||
repapp`
|
||||
```
|
||||
- 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`
|
||||
|
||||
### Coverage
|
||||
|
||||
- `coverage run --source='.' manage.py test`
|
||||
- `coverage report` or `coverage html`
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 803617e9c595adebf8a43a560fcfe8d439424f94
|
||||
30
cspell.json
|
|
@ -1,30 +0,0 @@
|
|||
{
|
||||
"version": "0.2",
|
||||
"ignorePaths": [],
|
||||
"dictionaryDefinitions": [],
|
||||
"dictionaries": [],
|
||||
"words": [
|
||||
"Ammeldelink",
|
||||
"anmeldung",
|
||||
"hilpoltstein",
|
||||
"Keycloak",
|
||||
"KeycloakOIDCAB",
|
||||
"oidc",
|
||||
"onetimelogin",
|
||||
"orga",
|
||||
"organisator",
|
||||
"organisators",
|
||||
"repa",
|
||||
"repaircafe",
|
||||
"repapp",
|
||||
"reparateur",
|
||||
"Reparateur",
|
||||
"Reparateure",
|
||||
"reparateurs",
|
||||
"Reparateurs",
|
||||
"tabindex",
|
||||
"johnpassword"
|
||||
],
|
||||
"ignoreWords": [],
|
||||
"import": []
|
||||
}
|
||||
BIN
css/fonts/Roboto-Slab-Bold.woff
Normal file
BIN
css/fonts/Roboto-Slab-Bold.woff2
Normal file
BIN
css/fonts/Roboto-Slab-Regular.woff
Normal file
BIN
css/fonts/Roboto-Slab-Regular.woff2
Normal file
BIN
css/fonts/fontawesome-webfont.eot
Normal file
2671
css/fonts/fontawesome-webfont.svg
Normal file
|
After Width: | Height: | Size: 434 KiB |
BIN
css/fonts/fontawesome-webfont.ttf
Normal file
BIN
css/fonts/fontawesome-webfont.woff
Normal file
BIN
css/fonts/fontawesome-webfont.woff2
Normal file
BIN
css/fonts/lato-bold-italic.woff
Normal file
BIN
css/fonts/lato-bold-italic.woff2
Normal file
BIN
css/fonts/lato-bold.woff
Normal file
BIN
css/fonts/lato-bold.woff2
Normal file
BIN
css/fonts/lato-normal-italic.woff
Normal file
BIN
css/fonts/lato-normal-italic.woff2
Normal file
BIN
css/fonts/lato-normal.woff
Normal file
BIN
css/fonts/lato-normal.woff2
Normal file
13
css/theme.css
Normal file
191
css/theme_extra.css
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
* Wrap inline code samples otherwise they shoot of the side and
|
||||
* can't be read at all.
|
||||
*
|
||||
* https://github.com/mkdocs/mkdocs/issues/313
|
||||
* https://github.com/mkdocs/mkdocs/issues/233
|
||||
* https://github.com/mkdocs/mkdocs/issues/834
|
||||
*/
|
||||
.rst-content code {
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
padding: 2px 5px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make code blocks display as blocks and give them the appropriate
|
||||
* font size and padding.
|
||||
*
|
||||
* https://github.com/mkdocs/mkdocs/issues/855
|
||||
* https://github.com/mkdocs/mkdocs/issues/834
|
||||
* https://github.com/mkdocs/mkdocs/issues/233
|
||||
*/
|
||||
.rst-content pre code {
|
||||
white-space: pre;
|
||||
word-wrap: normal;
|
||||
display: block;
|
||||
padding: 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix code colors
|
||||
*
|
||||
* https://github.com/mkdocs/mkdocs/issues/2027
|
||||
*/
|
||||
.rst-content code {
|
||||
color: #E74C3C;
|
||||
}
|
||||
|
||||
.rst-content pre code {
|
||||
color: #000;
|
||||
background: #f8f8f8;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fix link colors when the link text is inline code.
|
||||
*
|
||||
* https://github.com/mkdocs/mkdocs/issues/718
|
||||
*/
|
||||
a code {
|
||||
color: #2980B9;
|
||||
}
|
||||
a:hover code {
|
||||
color: #3091d1;
|
||||
}
|
||||
a:visited code {
|
||||
color: #9B59B6;
|
||||
}
|
||||
|
||||
/*
|
||||
* The CSS classes from highlight.js seem to clash with the
|
||||
* ReadTheDocs theme causing some code to be incorrectly made
|
||||
* bold and italic.
|
||||
*
|
||||
* https://github.com/mkdocs/mkdocs/issues/411
|
||||
*/
|
||||
pre .cs, pre .c {
|
||||
font-weight: inherit;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fix some issues with the theme and non-highlighted code
|
||||
* samples. Without and highlighting styles attached the
|
||||
* formatting is broken.
|
||||
*
|
||||
* https://github.com/mkdocs/mkdocs/issues/319
|
||||
*/
|
||||
.rst-content .no-highlight {
|
||||
display: block;
|
||||
padding: 0.5em;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Additions specific to the search functionality provided by MkDocs
|
||||
*/
|
||||
|
||||
.search-results {
|
||||
margin-top: 23px;
|
||||
}
|
||||
|
||||
.search-results article {
|
||||
border-top: 1px solid #E1E4E5;
|
||||
padding-top: 24px;
|
||||
}
|
||||
|
||||
.search-results article:first-child {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
form .search-query {
|
||||
width: 100%;
|
||||
border-radius: 50px;
|
||||
padding: 6px 12px; /* csslint allow: box-model */
|
||||
border-color: #D1D4D5;
|
||||
}
|
||||
|
||||
/*
|
||||
* Improve inline code blocks within admonitions.
|
||||
*
|
||||
* https://github.com/mkdocs/mkdocs/issues/656
|
||||
*/
|
||||
.rst-content .admonition code {
|
||||
color: #404040;
|
||||
border: 1px solid #c7c9cb;
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
background: #f8fbfd;
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
/*
|
||||
* Account for wide tables which go off the side.
|
||||
* Override borders to avoid weirdness on narrow tables.
|
||||
*
|
||||
* https://github.com/mkdocs/mkdocs/issues/834
|
||||
* https://github.com/mkdocs/mkdocs/pull/1034
|
||||
*/
|
||||
.rst-content .section .docutils {
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
display: block;
|
||||
border: none;
|
||||
}
|
||||
|
||||
td, th {
|
||||
border: 1px solid #e1e4e5 !important; /* csslint allow: important */
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
/*
|
||||
* Without the following amendments, the navigation in the theme will be
|
||||
* slightly cut off. This is due to the fact that the .wy-nav-side has a
|
||||
* padding-bottom of 2em, which must not necessarily align with the font-size of
|
||||
* 90 % on the .rst-current-version container, combined with the padding of 12px
|
||||
* above and below. These amendments fix this in two steps: First, make sure the
|
||||
* .rst-current-version container has a fixed height of 40px, achieved using
|
||||
* line-height, and then applying a padding-bottom of 40px to this container. In
|
||||
* a second step, the items within that container are re-aligned using flexbox.
|
||||
*
|
||||
* https://github.com/mkdocs/mkdocs/issues/2012
|
||||
*/
|
||||
.wy-nav-side {
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
|
||||
/*
|
||||
* The second step of above amendment: Here we make sure the items are aligned
|
||||
* correctly within the .rst-current-version container. Using flexbox, we
|
||||
* achieve it in such a way that it will look like the following:
|
||||
*
|
||||
* [No repo_name]
|
||||
* Next >> // On the first page
|
||||
* << Previous Next >> // On all subsequent pages
|
||||
*
|
||||
* [With repo_name]
|
||||
* <repo_name> Next >> // On the first page
|
||||
* <repo_name> << Previous Next >> // On all subsequent pages
|
||||
*
|
||||
* https://github.com/mkdocs/mkdocs/issues/2012
|
||||
*/
|
||||
.rst-versions .rst-current-version {
|
||||
padding: 0 12px;
|
||||
display: flex;
|
||||
font-size: initial;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
line-height: 40px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Please note that this amendment also involves removing certain inline-styles
|
||||
* from the file ./mkdocs/themes/readthedocs/versions.html.
|
||||
*
|
||||
* https://github.com/mkdocs/mkdocs/issues/2012
|
||||
*/
|
||||
.rst-current-version span {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
219
dev/apps/index.html
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
<!DOCTYPE html>
|
||||
<html class="writer-html5" lang="en" >
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><link rel="canonical" href="https://makes-hacks-hip.github.io/RepApp/dev/apps/" />
|
||||
<link rel="shortcut icon" href="../../img/favicon.ico" />
|
||||
<title>Apps - RepApp - Makespace Repair-Café Hilpoltstein</title>
|
||||
<link rel="stylesheet" href="../../css/theme.css" />
|
||||
<link rel="stylesheet" href="../../css/theme_extra.css" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/github.min.css" />
|
||||
|
||||
<script>
|
||||
// Current page data
|
||||
var mkdocs_page_name = "Apps";
|
||||
var mkdocs_page_input_path = "dev/apps.md";
|
||||
var mkdocs_page_url = "/RepApp/dev/apps/";
|
||||
</script>
|
||||
|
||||
<!--[if lt IE 9]>
|
||||
<script src="../../js/html5shiv.min.js"></script>
|
||||
<![endif]-->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"></script>
|
||||
<script>hljs.highlightAll();</script>
|
||||
</head>
|
||||
|
||||
<body class="wy-body-for-nav" role="document">
|
||||
|
||||
<div class="wy-grid-for-nav">
|
||||
<nav data-toggle="wy-nav-shift" class="wy-nav-side stickynav">
|
||||
<div class="wy-side-scroll">
|
||||
<div class="wy-side-nav-search">
|
||||
<a href="../.." class="icon icon-home"> RepApp - Makespace Repair-Café Hilpoltstein
|
||||
</a><div role="search">
|
||||
<form id ="rtd-search-form" class="wy-form" action="../../search.html" method="get">
|
||||
<input type="text" name="q" placeholder="Search docs" aria-label="Search docs" title="Type search term here" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="Navigation menu">
|
||||
<p class="caption"><span class="caption-text">RepApp</span></p>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="../..">Beschreibung</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="caption"><span class="caption-text">Developer documentation</span></p>
|
||||
<ul class="current">
|
||||
<li class="toctree-l1"><a class="reference internal" href="../">Overview</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="../users/">Users</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="../stories/">Stories</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="../use_cases/">Use cases</a>
|
||||
</li>
|
||||
<li class="toctree-l1 current"><a class="reference internal current" href="./">Apps</a>
|
||||
<ul class="current">
|
||||
<li class="toctree-l2"><a class="reference internal" href="#one-time-login">One time login</a>
|
||||
<ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="#configuration">Configuration</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="#email-interface">Email interface</a>
|
||||
<ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="#configuration_1">Configuration</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="../objects/">Objects</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="../configuration/">Repapp configuration</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="caption"><span class="caption-text">Repair-Café & Makespace</span></p>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="" href="https://www.repaircafe-hilpoltstein.de">Repair-Café</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="" href="https://makes-hacks-hip.de">Makespace</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="" href="https://makes-hacks-hip.github.io">Makerspace@Github</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
|
||||
<nav class="wy-nav-top" role="navigation" aria-label="Mobile navigation menu">
|
||||
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
|
||||
<a href="../..">RepApp - Makespace Repair-Café Hilpoltstein</a>
|
||||
|
||||
</nav>
|
||||
<div class="wy-nav-content">
|
||||
<div class="rst-content"><div role="navigation" aria-label="breadcrumbs navigation">
|
||||
<ul class="wy-breadcrumbs">
|
||||
<li><a href="../.." class="icon icon-home" aria-label="Docs"></a> »</li>
|
||||
<li>Developer documentation »</li>
|
||||
<li class="breadcrumb-item active">Apps</li>
|
||||
<li class="wy-breadcrumbs-aside">
|
||||
<a href="https://github.com/makes-hacks-hip/RepApp/edit/main/docs/dev/apps.md" class="icon icon-github"> Edit on GitHub</a>
|
||||
</li>
|
||||
</ul>
|
||||
<hr/>
|
||||
</div>
|
||||
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
|
||||
<div class="section" itemprop="articleBody">
|
||||
|
||||
<h1 id="apps">Apps</h1>
|
||||
<p>The repapp is split into different modules, realized as Django apps, to improve the maintainability.</p>
|
||||
<h2 id="one-time-login">One time login</h2>
|
||||
<p>The one time login module provides a login for guests using a one time secret as part of an URL.</p>
|
||||
<ul>
|
||||
<li>The templates use the <code>settings.ORGANIZATION</code> variable to fill the organization name.</li>
|
||||
<li>The Django mail module is used to send emails. See https://docs.djangoproject.com/en/4.2/topics/email/ for details how to configure the mail host.</li>
|
||||
</ul>
|
||||
<h3 id="configuration">Configuration</h3>
|
||||
<p>To enable the one time login links, an additional authentication backend must get added:</p>
|
||||
<pre><code class="language-Python"># Repapp specific authentication backends
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
# Backend for one time logins
|
||||
"one_time_login.authentication_backends.OneTimeLoginBackend",
|
||||
]
|
||||
</code></pre>
|
||||
<p>The one time login urls must get added using namespace <code>one_time_login</code>:</p>
|
||||
<pre><code class="language-Python">urlpatterns = i18n_patterns(
|
||||
...
|
||||
path(_('one_time_login/'),
|
||||
include('one_time_login.urls', namespace='one_time_login')),
|
||||
...
|
||||
)
|
||||
</code></pre>
|
||||
<h2 id="email-interface">Email interface</h2>
|
||||
<p>The email interface modules supports handling of mails. It support sending of mails, including HTML formatting and attachments, and it supports receiving of plain text and HTML formatted mails, including attachments.</p>
|
||||
<ul>
|
||||
<li>The Django mail module is used to send emails. See https://docs.djangoproject.com/en/4.2/topics/email/ for details how to configure the mail host.</li>
|
||||
<li>CKEditor is used for email content editing. See https://django-ckeditor.readthedocs.io/en/latest/ for details.</li>
|
||||
<li>Crispy forms is used for form rendering. See https://django-crispy-forms.readthedocs.io/en/latest/ for details.</li>
|
||||
<li>Bootstrap 5 theme for crispy forms is used. See https://github.com/django-crispy-forms/crispy-bootstrap5 for details.</li>
|
||||
</ul>
|
||||
<h3 id="configuration_1">Configuration</h3>
|
||||
<p>The email interface urls must get added using namespace <code>email_interface</code>:</p>
|
||||
<pre><code class="language-Python">urlpatterns = i18n_patterns(
|
||||
...
|
||||
path(_('emails/'),
|
||||
include('email_interface.urls', namespace='email_interface')),
|
||||
...
|
||||
)
|
||||
</code></pre>
|
||||
<p>To use CKEditor as authenticated user, the following additional urls must get added:</p>
|
||||
<pre><code class="language-Python">urlpatterns = i18n_patterns(
|
||||
...
|
||||
# CKEditor upload views
|
||||
path('upload/', login_required(ckeditor_views.upload), name="ckeditor_upload"),
|
||||
path('browse/', never_cache(login_required(ckeditor_views.browse)),
|
||||
name="ckeditor_browse"),
|
||||
...
|
||||
)
|
||||
</code></pre>
|
||||
<p>To be abel to use CKEditor also the static an media content must get served. For development this is done with:</p>
|
||||
<pre><code class="language-Python">if settings.DEBUG:
|
||||
urlpatterns += static(settings.MEDIA_URL,
|
||||
document_root=settings.MEDIA_ROOT)
|
||||
</code></pre>
|
||||
|
||||
</div>
|
||||
</div><footer>
|
||||
<div class="rst-footer-buttons" role="navigation" aria-label="Footer Navigation">
|
||||
<a href="../use_cases/" class="btn btn-neutral float-left" title="Use cases"><span class="icon icon-circle-arrow-left"></span> Previous</a>
|
||||
<a href="../objects/" class="btn btn-neutral float-right" title="Objects">Next <span class="icon icon-circle-arrow-right"></span></a>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<div role="contentinfo">
|
||||
<!-- Copyright etc -->
|
||||
</div>
|
||||
|
||||
Built with <a href="https://www.mkdocs.org/">MkDocs</a> using a <a href="https://github.com/readthedocs/sphinx_rtd_theme">theme</a> provided by <a href="https://readthedocs.org">Read the Docs</a>.
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="rst-versions" role="note" aria-label="Versions">
|
||||
<span class="rst-current-version" data-toggle="rst-current-version">
|
||||
|
||||
<span>
|
||||
<a href="https://github.com/makes-hacks-hip/RepApp/" class="fa fa-github" style="color: #fcfcfc"> GitHub</a>
|
||||
</span>
|
||||
|
||||
|
||||
<span><a href="../use_cases/" style="color: #fcfcfc">« Previous</a></span>
|
||||
|
||||
|
||||
<span><a href="../objects/" style="color: #fcfcfc">Next »</a></span>
|
||||
|
||||
</span>
|
||||
</div>
|
||||
<script src="../../js/jquery-3.6.0.min.js"></script>
|
||||
<script>var base_url = "../..";</script>
|
||||
<script src="../../js/theme_extra.js"></script>
|
||||
<script src="../../js/theme.js"></script>
|
||||
<script src="../../search/main.js"></script>
|
||||
<script>
|
||||
jQuery(function () {
|
||||
SphinxRtdTheme.Navigation.enable(true);
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
284
dev/configuration/index.html
Normal file
|
|
@ -0,0 +1,284 @@
|
|||
<!DOCTYPE html>
|
||||
<html class="writer-html5" lang="en" >
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><link rel="canonical" href="https://makes-hacks-hip.github.io/RepApp/dev/configuration/" />
|
||||
<link rel="shortcut icon" href="../../img/favicon.ico" />
|
||||
<title>Repapp configuration - RepApp - Makespace Repair-Café Hilpoltstein</title>
|
||||
<link rel="stylesheet" href="../../css/theme.css" />
|
||||
<link rel="stylesheet" href="../../css/theme_extra.css" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/github.min.css" />
|
||||
|
||||
<script>
|
||||
// Current page data
|
||||
var mkdocs_page_name = "Repapp configuration";
|
||||
var mkdocs_page_input_path = "dev/configuration.md";
|
||||
var mkdocs_page_url = "/RepApp/dev/configuration/";
|
||||
</script>
|
||||
|
||||
<!--[if lt IE 9]>
|
||||
<script src="../../js/html5shiv.min.js"></script>
|
||||
<![endif]-->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"></script>
|
||||
<script>hljs.highlightAll();</script>
|
||||
</head>
|
||||
|
||||
<body class="wy-body-for-nav" role="document">
|
||||
|
||||
<div class="wy-grid-for-nav">
|
||||
<nav data-toggle="wy-nav-shift" class="wy-nav-side stickynav">
|
||||
<div class="wy-side-scroll">
|
||||
<div class="wy-side-nav-search">
|
||||
<a href="../.." class="icon icon-home"> RepApp - Makespace Repair-Café Hilpoltstein
|
||||
</a><div role="search">
|
||||
<form id ="rtd-search-form" class="wy-form" action="../../search.html" method="get">
|
||||
<input type="text" name="q" placeholder="Search docs" aria-label="Search docs" title="Type search term here" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="Navigation menu">
|
||||
<p class="caption"><span class="caption-text">RepApp</span></p>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="../..">Beschreibung</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="caption"><span class="caption-text">Developer documentation</span></p>
|
||||
<ul class="current">
|
||||
<li class="toctree-l1"><a class="reference internal" href="../">Overview</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="../users/">Users</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="../stories/">Stories</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="../use_cases/">Use cases</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="../apps/">Apps</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="../objects/">Objects</a>
|
||||
</li>
|
||||
<li class="toctree-l1 current"><a class="reference internal current" href="./">Repapp configuration</a>
|
||||
<ul class="current">
|
||||
<li class="toctree-l2"><a class="reference internal" href="#debug-mode">Debug mode</a>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="#organization-name">Organization name</a>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="#email-settings">Email settings</a>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="#translations">Translations</a>
|
||||
<ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="#rosetta">Rosetta</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="#one-time-login">One time login</a>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="#email-interface">Email interface</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="caption"><span class="caption-text">Repair-Café & Makespace</span></p>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="" href="https://www.repaircafe-hilpoltstein.de">Repair-Café</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="" href="https://makes-hacks-hip.de">Makespace</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="" href="https://makes-hacks-hip.github.io">Makerspace@Github</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
|
||||
<nav class="wy-nav-top" role="navigation" aria-label="Mobile navigation menu">
|
||||
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
|
||||
<a href="../..">RepApp - Makespace Repair-Café Hilpoltstein</a>
|
||||
|
||||
</nav>
|
||||
<div class="wy-nav-content">
|
||||
<div class="rst-content"><div role="navigation" aria-label="breadcrumbs navigation">
|
||||
<ul class="wy-breadcrumbs">
|
||||
<li><a href="../.." class="icon icon-home" aria-label="Docs"></a> »</li>
|
||||
<li>Developer documentation »</li>
|
||||
<li class="breadcrumb-item active">Repapp configuration</li>
|
||||
<li class="wy-breadcrumbs-aside">
|
||||
<a href="https://github.com/makes-hacks-hip/RepApp/edit/main/docs/dev/configuration.md" class="icon icon-github"> Edit on GitHub</a>
|
||||
</li>
|
||||
</ul>
|
||||
<hr/>
|
||||
</div>
|
||||
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
|
||||
<div class="section" itemprop="articleBody">
|
||||
|
||||
<h1 id="repapp-configuration">Repapp configuration</h1>
|
||||
<h2 id="debug-mode">Debug mode</h2>
|
||||
<p>The Django debug mode is activated if there is a environment variable <code>DJANGO_DEBUG</code> with the value "true", "1" or "t". By default, the debug mode is disabled. </p>
|
||||
<h2 id="organization-name">Organization name</h2>
|
||||
<p>Some templates use and organization name. This value is taken from <code>settings.ORGANIZATION</code> and defaults to "Repair-Café Hilpoltstein".</p>
|
||||
<h2 id="email-settings">Email settings</h2>
|
||||
<p>The following environment variables are used to configure the Django email feature:</p>
|
||||
<pre><code class="language-Python">EMAIL_HOST = os.getenv("DJANGO_EMAIL_HOST", "")
|
||||
EMAIL_PORT = (int)(os.getenv("DJANGO_EMAIL_PORT", "25"))
|
||||
EMAIL_HOST_USER = os.getenv("DJANGO_EMAIL_HOST_USER", None)
|
||||
EMAIL_HOST_PASSWORD = os.getenv("DJANGO_EMAIL_HOST_PASSWORD", None)
|
||||
EMAIL_USE_TLS = os.getenv("DJANGO_EMAIL_USE_TLS", "true") in ("true", "1", "t")
|
||||
DEFAULT_FROM_EMAIL = os.getenv("DJANGO_SENDER_ADDRESS", "")
|
||||
</code></pre>
|
||||
<p>See https://docs.djangoproject.com/en/4.2/topics/email/ for more details.</p>
|
||||
<h2 id="translations">Translations</h2>
|
||||
<p>Repapp is using localization, and comes with german and english translations.</p>
|
||||
<p>Enable the <code>LocaleMiddleware</code>:</p>
|
||||
<pre><code class="language-Python">MIDDLEWARE = [
|
||||
...
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
# enable translation
|
||||
'django.middleware.locale.LocaleMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
...
|
||||
]
|
||||
</code></pre>
|
||||
<p>We use the following localization settings:</p>
|
||||
<pre><code class="language-Python">LANGUAGE_CODE = 'de'
|
||||
TIME_ZONE = 'Europe/Berlin'
|
||||
|
||||
USE_I18N = True
|
||||
USE_L10N = True
|
||||
USE_TZ = True
|
||||
|
||||
LANGUAGES = [
|
||||
('de', _('German')),
|
||||
('en', _('English')),
|
||||
]
|
||||
|
||||
LOCALE_PATHS = [
|
||||
BASE_DIR / 'locale',
|
||||
]
|
||||
</code></pre>
|
||||
<p>Also the URLs are localized, and the language is choosen using i18n URL patterns:</p>
|
||||
<pre><code class="language-Python">...
|
||||
from django.conf.urls.i18n import i18n_patterns
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
urlpatterns = i18n_patterns(
|
||||
path('admin/', admin.site.urls),
|
||||
path(_('one_time_login/'),
|
||||
include('one_time_login.urls', namespace='one_time_login')),
|
||||
# URLs for rosetta translation interface
|
||||
path('rosetta/', include('rosetta.urls')),
|
||||
)
|
||||
</code></pre>
|
||||
<h3 id="rosetta">Rosetta</h3>
|
||||
<p>For translating the interface, we use rosetta. For more details see https://pypi.org/project/django-rosetta/.</p>
|
||||
<p>You need to install rosetta (<code>pip install django-rosetta</code>) and enable it in the apps:</p>
|
||||
<pre><code class="language-Python">INSTALLED_APPS = [
|
||||
...
|
||||
# enable rosetta online translation interface
|
||||
'rosetta',
|
||||
...
|
||||
]
|
||||
</code></pre>
|
||||
<p>Rosatta is made availabel at <code>rosetta/</code>.</p>
|
||||
<h2 id="one-time-login">One time login</h2>
|
||||
<p>To enable the one time login links, an additional authentication backend must get added:</p>
|
||||
<pre><code class="language-Python"># Repapp specific authentication backends
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
# Backend for one time logins
|
||||
"one_time_login.authentication_backends.OneTimeLoginBackend",
|
||||
]
|
||||
</code></pre>
|
||||
<h2 id="email-interface">Email interface</h2>
|
||||
<p>The email interface uses CKEditor, crispy forms and easy thumbnails. The apps are enabled with:</p>
|
||||
<pre><code class="language-Python">INSTALLED_APPS = [
|
||||
...
|
||||
# CKEditor for mail content editing
|
||||
"ckeditor",
|
||||
"ckeditor_uploader",
|
||||
# Crispy forms for bootstrap 5 form design
|
||||
"crispy_forms",
|
||||
"crispy_bootstrap5",
|
||||
# for creating thumbnails, used by email_interface demo views
|
||||
'easy_thumbnails',
|
||||
...
|
||||
]
|
||||
</code></pre>
|
||||
<p>For CKEditor, we use the following settings:</p>
|
||||
<pre><code class="language-Python"># editor upload path, used e.g. for send mail demo
|
||||
CKEDITOR_UPLOAD_PATH = "editor_uploads/"
|
||||
|
||||
CKEDITOR_IMAGE_BACKEND = 'pillow'
|
||||
|
||||
# CKEditor default config
|
||||
CKEDITOR_CONFIGS = {
|
||||
'default': {
|
||||
'toolbar': 'full',
|
||||
'height': 300,
|
||||
'width': '100%',
|
||||
},
|
||||
}
|
||||
</code></pre>
|
||||
<p>To enable the image upload of CKEditor, also the Django media paths and urls must get configured:</p>
|
||||
<pre><code class="language-Python"># Django media and static file config, required for CKEditor
|
||||
STATIC_URL = "static/"
|
||||
MEDIA_URL = "media/"
|
||||
# Use subfolders of project folder
|
||||
STATIC_ROOT = os.path.join(BASE_DIR, "static")
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
|
||||
</code></pre>
|
||||
<p>For crispy forms, we use the bootstrap 5 theme:</p>
|
||||
<pre><code class="language-Python"># Crispy forms settings
|
||||
CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"
|
||||
CRISPY_TEMPLATE_PACK = "bootstrap5"
|
||||
</code></pre>
|
||||
<p>The static files for bootstrap must also be made available.</p>
|
||||
|
||||
</div>
|
||||
</div><footer>
|
||||
<div class="rst-footer-buttons" role="navigation" aria-label="Footer Navigation">
|
||||
<a href="../objects/" class="btn btn-neutral float-left" title="Objects"><span class="icon icon-circle-arrow-left"></span> Previous</a>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<div role="contentinfo">
|
||||
<!-- Copyright etc -->
|
||||
</div>
|
||||
|
||||
Built with <a href="https://www.mkdocs.org/">MkDocs</a> using a <a href="https://github.com/readthedocs/sphinx_rtd_theme">theme</a> provided by <a href="https://readthedocs.org">Read the Docs</a>.
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="rst-versions" role="note" aria-label="Versions">
|
||||
<span class="rst-current-version" data-toggle="rst-current-version">
|
||||
|
||||
<span>
|
||||
<a href="https://github.com/makes-hacks-hip/RepApp/" class="fa fa-github" style="color: #fcfcfc"> GitHub</a>
|
||||
</span>
|
||||
|
||||
|
||||
<span><a href="../objects/" style="color: #fcfcfc">« Previous</a></span>
|
||||
|
||||
|
||||
</span>
|
||||
</div>
|
||||
<script src="../../js/jquery-3.6.0.min.js"></script>
|
||||
<script>var base_url = "../..";</script>
|
||||
<script src="../../js/theme_extra.js"></script>
|
||||
<script src="../../js/theme.js"></script>
|
||||
<script src="../../search/main.js"></script>
|
||||
<script>
|
||||
jQuery(function () {
|
||||
SphinxRtdTheme.Navigation.enable(true);
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
205
dev/index.html
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
<!DOCTYPE html>
|
||||
<html class="writer-html5" lang="en" >
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><link rel="canonical" href="https://makes-hacks-hip.github.io/RepApp/dev/" />
|
||||
<link rel="shortcut icon" href="../img/favicon.ico" />
|
||||
<title>Overview - RepApp - Makespace Repair-Café Hilpoltstein</title>
|
||||
<link rel="stylesheet" href="../css/theme.css" />
|
||||
<link rel="stylesheet" href="../css/theme_extra.css" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/github.min.css" />
|
||||
|
||||
<script>
|
||||
// Current page data
|
||||
var mkdocs_page_name = "Overview";
|
||||
var mkdocs_page_input_path = "dev/index.md";
|
||||
var mkdocs_page_url = "/RepApp/dev/";
|
||||
</script>
|
||||
|
||||
<!--[if lt IE 9]>
|
||||
<script src="../js/html5shiv.min.js"></script>
|
||||
<![endif]-->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"></script>
|
||||
<script>hljs.highlightAll();</script>
|
||||
</head>
|
||||
|
||||
<body class="wy-body-for-nav" role="document">
|
||||
|
||||
<div class="wy-grid-for-nav">
|
||||
<nav data-toggle="wy-nav-shift" class="wy-nav-side stickynav">
|
||||
<div class="wy-side-scroll">
|
||||
<div class="wy-side-nav-search">
|
||||
<a href=".." class="icon icon-home"> RepApp - Makespace Repair-Café Hilpoltstein
|
||||
</a><div role="search">
|
||||
<form id ="rtd-search-form" class="wy-form" action="../search.html" method="get">
|
||||
<input type="text" name="q" placeholder="Search docs" aria-label="Search docs" title="Type search term here" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="Navigation menu">
|
||||
<p class="caption"><span class="caption-text">RepApp</span></p>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="..">Beschreibung</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="caption"><span class="caption-text">Developer documentation</span></p>
|
||||
<ul class="current">
|
||||
<li class="toctree-l1 current"><a class="reference internal current" href="./">Overview</a>
|
||||
<ul class="current">
|
||||
<li class="toctree-l2"><a class="reference internal" href="#local-development-setup">Local development setup</a>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="#docker-image">Docker image</a>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="#tests">Tests</a>
|
||||
<ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="#coverage">Coverage</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="users/">Users</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="stories/">Stories</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="use_cases/">Use cases</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="apps/">Apps</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="objects/">Objects</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="configuration/">Repapp configuration</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="caption"><span class="caption-text">Repair-Café & Makespace</span></p>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="" href="https://www.repaircafe-hilpoltstein.de">Repair-Café</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="" href="https://makes-hacks-hip.de">Makespace</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="" href="https://makes-hacks-hip.github.io">Makerspace@Github</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
|
||||
<nav class="wy-nav-top" role="navigation" aria-label="Mobile navigation menu">
|
||||
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
|
||||
<a href="..">RepApp - Makespace Repair-Café Hilpoltstein</a>
|
||||
|
||||
</nav>
|
||||
<div class="wy-nav-content">
|
||||
<div class="rst-content"><div role="navigation" aria-label="breadcrumbs navigation">
|
||||
<ul class="wy-breadcrumbs">
|
||||
<li><a href=".." class="icon icon-home" aria-label="Docs"></a> »</li>
|
||||
<li>Developer documentation »</li>
|
||||
<li class="breadcrumb-item active">Overview</li>
|
||||
<li class="wy-breadcrumbs-aside">
|
||||
<a href="https://github.com/makes-hacks-hip/RepApp/edit/main/docs/dev/index.md" class="icon icon-github"> Edit on GitHub</a>
|
||||
</li>
|
||||
</ul>
|
||||
<hr/>
|
||||
</div>
|
||||
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
|
||||
<div class="section" itemprop="articleBody">
|
||||
|
||||
<h1 id="repapp-developer-documentation">Repapp - developer documentation</h1>
|
||||
<p>The Repapp is a repair assignment booking and management tool.
|
||||
It tries to support all common workflows happening while organizing a repair café event.
|
||||
It's build using the Django web framework.</p>
|
||||
<p>The code of the Repapp is hosted on Github: https://github.com/makes-hacks-hip/RepApp</p>
|
||||
<h2 id="local-development-setup">Local development setup</h2>
|
||||
<ul>
|
||||
<li>Clone the Github repository</li>
|
||||
<li>Create a Python environment: <code>python -m venv venv</code></li>
|
||||
<li>Activate the Python environment: <code>source venv/bin/activate</code></li>
|
||||
<li>Install the dependencies: <code>pip install -r requirements_prod.txt</code> and <code>pip install -r requirements_dev.txt</code></li>
|
||||
<li>Change to the Django project folder: <code>cd repapp</code></li>
|
||||
<li>Setup the database:</li>
|
||||
<li><code>python manage.py makemigrations</code></li>
|
||||
<li><code>python manage.py migrate</code></li>
|
||||
<li>Setup an admin user: <code>python manage.py createsuperuser</code></li>
|
||||
<li>Run the local test server: <code>python manage.py runserver</code></li>
|
||||
</ul>
|
||||
<h2 id="docker-image">Docker image</h2>
|
||||
<ul>
|
||||
<li>Build the image: <code>docker build -t repapp .</code></li>
|
||||
<li>Run the image:</li>
|
||||
</ul>
|
||||
<pre><code class="language-bash">docker run --rm -d \
|
||||
-p 127.0.0.1:8020:8020 \
|
||||
--name repapp \
|
||||
--env DJANGO_SUPERUSER_USERNAME=suba \
|
||||
--env DJANGO_SUPERUSER_PASSWORD=ThePassword \
|
||||
--env DJANGO_SUPERUSER_EMAIL=noreply@example.com \
|
||||
repapp`
|
||||
</code></pre>
|
||||
<ul>
|
||||
<li>Open http://127.0.0.1:8020/ in your browser</li>
|
||||
</ul>
|
||||
<h2 id="tests">Tests</h2>
|
||||
<ul>
|
||||
<li>Unit tests: <code>python manage.py test</code></li>
|
||||
<li>LiveServer tests: <code>python manage.py test repapp.live_tests</code></li>
|
||||
</ul>
|
||||
<h3 id="coverage">Coverage</h3>
|
||||
<ul>
|
||||
<li><code>coverage run --source='.' manage.py test</code></li>
|
||||
<li><code>coverage report</code> or <code>coverage html</code></li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div><footer>
|
||||
<div class="rst-footer-buttons" role="navigation" aria-label="Footer Navigation">
|
||||
<a href=".." class="btn btn-neutral float-left" title="Beschreibung"><span class="icon icon-circle-arrow-left"></span> Previous</a>
|
||||
<a href="users/" class="btn btn-neutral float-right" title="Users">Next <span class="icon icon-circle-arrow-right"></span></a>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<div role="contentinfo">
|
||||
<!-- Copyright etc -->
|
||||
</div>
|
||||
|
||||
Built with <a href="https://www.mkdocs.org/">MkDocs</a> using a <a href="https://github.com/readthedocs/sphinx_rtd_theme">theme</a> provided by <a href="https://readthedocs.org">Read the Docs</a>.
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="rst-versions" role="note" aria-label="Versions">
|
||||
<span class="rst-current-version" data-toggle="rst-current-version">
|
||||
|
||||
<span>
|
||||
<a href="https://github.com/makes-hacks-hip/RepApp/" class="fa fa-github" style="color: #fcfcfc"> GitHub</a>
|
||||
</span>
|
||||
|
||||
|
||||
<span><a href=".." style="color: #fcfcfc">« Previous</a></span>
|
||||
|
||||
|
||||
<span><a href="users/" style="color: #fcfcfc">Next »</a></span>
|
||||
|
||||
</span>
|
||||
</div>
|
||||
<script src="../js/jquery-3.6.0.min.js"></script>
|
||||
<script>var base_url = "..";</script>
|
||||
<script src="../js/theme_extra.js"></script>
|
||||
<script src="../js/theme.js"></script>
|
||||
<script src="../search/main.js"></script>
|
||||
<script>
|
||||
jQuery(function () {
|
||||
SphinxRtdTheme.Navigation.enable(true);
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
196
dev/objects/index.html
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
<!DOCTYPE html>
|
||||
<html class="writer-html5" lang="en" >
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><link rel="canonical" href="https://makes-hacks-hip.github.io/RepApp/dev/objects/" />
|
||||
<link rel="shortcut icon" href="../../img/favicon.ico" />
|
||||
<title>Objects - RepApp - Makespace Repair-Café Hilpoltstein</title>
|
||||
<link rel="stylesheet" href="../../css/theme.css" />
|
||||
<link rel="stylesheet" href="../../css/theme_extra.css" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/github.min.css" />
|
||||
|
||||
<script>
|
||||
// Current page data
|
||||
var mkdocs_page_name = "Objects";
|
||||
var mkdocs_page_input_path = "dev/objects.md";
|
||||
var mkdocs_page_url = "/RepApp/dev/objects/";
|
||||
</script>
|
||||
|
||||
<!--[if lt IE 9]>
|
||||
<script src="../../js/html5shiv.min.js"></script>
|
||||
<![endif]-->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"></script>
|
||||
<script>hljs.highlightAll();</script>
|
||||
</head>
|
||||
|
||||
<body class="wy-body-for-nav" role="document">
|
||||
|
||||
<div class="wy-grid-for-nav">
|
||||
<nav data-toggle="wy-nav-shift" class="wy-nav-side stickynav">
|
||||
<div class="wy-side-scroll">
|
||||
<div class="wy-side-nav-search">
|
||||
<a href="../.." class="icon icon-home"> RepApp - Makespace Repair-Café Hilpoltstein
|
||||
</a><div role="search">
|
||||
<form id ="rtd-search-form" class="wy-form" action="../../search.html" method="get">
|
||||
<input type="text" name="q" placeholder="Search docs" aria-label="Search docs" title="Type search term here" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="Navigation menu">
|
||||
<p class="caption"><span class="caption-text">RepApp</span></p>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="../..">Beschreibung</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="caption"><span class="caption-text">Developer documentation</span></p>
|
||||
<ul class="current">
|
||||
<li class="toctree-l1"><a class="reference internal" href="../">Overview</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="../users/">Users</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="../stories/">Stories</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="../use_cases/">Use cases</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="../apps/">Apps</a>
|
||||
</li>
|
||||
<li class="toctree-l1 current"><a class="reference internal current" href="./">Objects</a>
|
||||
<ul class="current">
|
||||
<li class="toctree-l2"><a class="reference internal" href="#repair_cafe">Repair_Cafe</a>
|
||||
<ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="#fields">Fields</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="#device">Device</a>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="#appointment">Appointment</a>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="#guest">Guest</a>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="#repairer">Repairer</a>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="#organizer">Organizer</a>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="#consultation">Consultation</a>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="#comment">Comment</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="../configuration/">Repapp configuration</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="caption"><span class="caption-text">Repair-Café & Makespace</span></p>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="" href="https://www.repaircafe-hilpoltstein.de">Repair-Café</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="" href="https://makes-hacks-hip.de">Makespace</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="" href="https://makes-hacks-hip.github.io">Makerspace@Github</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
|
||||
<nav class="wy-nav-top" role="navigation" aria-label="Mobile navigation menu">
|
||||
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
|
||||
<a href="../..">RepApp - Makespace Repair-Café Hilpoltstein</a>
|
||||
|
||||
</nav>
|
||||
<div class="wy-nav-content">
|
||||
<div class="rst-content"><div role="navigation" aria-label="breadcrumbs navigation">
|
||||
<ul class="wy-breadcrumbs">
|
||||
<li><a href="../.." class="icon icon-home" aria-label="Docs"></a> »</li>
|
||||
<li>Developer documentation »</li>
|
||||
<li class="breadcrumb-item active">Objects</li>
|
||||
<li class="wy-breadcrumbs-aside">
|
||||
<a href="https://github.com/makes-hacks-hip/RepApp/edit/main/docs/dev/objects.md" class="icon icon-github"> Edit on GitHub</a>
|
||||
</li>
|
||||
</ul>
|
||||
<hr/>
|
||||
</div>
|
||||
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
|
||||
<div class="section" itemprop="articleBody">
|
||||
|
||||
<h1 id="objects">Objects</h1>
|
||||
<p>A <strong>device</strong> is an object owned by a <strong>guest</strong> which has some issue. To get support for fixing the issue, a <strong>guest</strong> provides the information about the <strong>device</strong> and requests a repair <strong>appointment</strong> during a <strong>repair_cafe</strong>.</p>
|
||||
<p><img alt="Objects" src="../uml/objects_other.png" /></p>
|
||||
<p>A <strong>repair_cafe</strong> happens at a specific date and location. It's an event which consists of <strong>appointments</strong> of <strong>repairers</strong> with <strong>guests</strong> to fix a <strong>device</strong>.</p>
|
||||
<p>To organize a <strong>repair_cafe</strong>, a <strong>organizer</strong> needs to know which <strong>repairer</strong> will attend a <strong>repair_cafe</strong>, to schedule repair <strong>appointments</strong>.</p>
|
||||
<p><img alt="Appointment" src="../uml/objects_appointment.png" /></p>
|
||||
<p>In case of ambiguities, a <strong>organizer</strong> or a <strong>repairer</strong> can raise a <strong>consultation</strong> request. This <strong>consultation</strong> request is sent to the <strong>guest</strong> to get the needed information. In addition, a <strong>organizer</strong> or a <strong>repairer</strong> can add an <strong>comment</strong> to a <strong>device</strong>.</p>
|
||||
<p><img alt="Consultation" src="../uml/objects_consultation.png" /></p>
|
||||
<p><img alt="Comment" src="../uml/objects_comment.png" /></p>
|
||||
<h2 id="repair_cafe">Repair_Cafe</h2>
|
||||
<p>A <strong>repair_cafe</strong> is a event happening at a specific date at a specific location. During a <strong>repair_cafe</strong> <strong>appointments</strong> of <strong>guests</strong> with <strong>repairers</strong> happens to fix broken <em>devices</em>.</p>
|
||||
<h3 id="fields">Fields</h3>
|
||||
<ul>
|
||||
<li><em>location</em>: Char (200) -- location of the event</li>
|
||||
<li><em>address</em>: Char (200) -- address where the event happens</li>
|
||||
<li><em>event_date</em>: Date -- date when the event happens</li>
|
||||
<li><em>repairer</em>: many to many -- will attend the event</li>
|
||||
</ul>
|
||||
<h2 id="device">Device</h2>
|
||||
<h2 id="appointment">Appointment</h2>
|
||||
<h2 id="guest">Guest</h2>
|
||||
<h2 id="repairer">Repairer</h2>
|
||||
<h2 id="organizer">Organizer</h2>
|
||||
<h2 id="consultation">Consultation</h2>
|
||||
<h2 id="comment">Comment</h2>
|
||||
|
||||
</div>
|
||||
</div><footer>
|
||||
<div class="rst-footer-buttons" role="navigation" aria-label="Footer Navigation">
|
||||
<a href="../apps/" class="btn btn-neutral float-left" title="Apps"><span class="icon icon-circle-arrow-left"></span> Previous</a>
|
||||
<a href="../configuration/" class="btn btn-neutral float-right" title="Repapp configuration">Next <span class="icon icon-circle-arrow-right"></span></a>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<div role="contentinfo">
|
||||
<!-- Copyright etc -->
|
||||
</div>
|
||||
|
||||
Built with <a href="https://www.mkdocs.org/">MkDocs</a> using a <a href="https://github.com/readthedocs/sphinx_rtd_theme">theme</a> provided by <a href="https://readthedocs.org">Read the Docs</a>.
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="rst-versions" role="note" aria-label="Versions">
|
||||
<span class="rst-current-version" data-toggle="rst-current-version">
|
||||
|
||||
<span>
|
||||
<a href="https://github.com/makes-hacks-hip/RepApp/" class="fa fa-github" style="color: #fcfcfc"> GitHub</a>
|
||||
</span>
|
||||
|
||||
|
||||
<span><a href="../apps/" style="color: #fcfcfc">« Previous</a></span>
|
||||
|
||||
|
||||
<span><a href="../configuration/" style="color: #fcfcfc">Next »</a></span>
|
||||
|
||||
</span>
|
||||
</div>
|
||||
<script src="../../js/jquery-3.6.0.min.js"></script>
|
||||
<script>var base_url = "../..";</script>
|
||||
<script src="../../js/theme_extra.js"></script>
|
||||
<script src="../../js/theme.js"></script>
|
||||
<script src="../../search/main.js"></script>
|
||||
<script>
|
||||
jQuery(function () {
|
||||
SphinxRtdTheme.Navigation.enable(true);
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
230
dev/stories/index.html
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
<!DOCTYPE html>
|
||||
<html class="writer-html5" lang="en" >
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><link rel="canonical" href="https://makes-hacks-hip.github.io/RepApp/dev/stories/" />
|
||||
<link rel="shortcut icon" href="../../img/favicon.ico" />
|
||||
<title>Stories - RepApp - Makespace Repair-Café Hilpoltstein</title>
|
||||
<link rel="stylesheet" href="../../css/theme.css" />
|
||||
<link rel="stylesheet" href="../../css/theme_extra.css" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/github.min.css" />
|
||||
|
||||
<script>
|
||||
// Current page data
|
||||
var mkdocs_page_name = "Stories";
|
||||
var mkdocs_page_input_path = "dev/stories.md";
|
||||
var mkdocs_page_url = "/RepApp/dev/stories/";
|
||||
</script>
|
||||
|
||||
<!--[if lt IE 9]>
|
||||
<script src="../../js/html5shiv.min.js"></script>
|
||||
<![endif]-->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"></script>
|
||||
<script>hljs.highlightAll();</script>
|
||||
</head>
|
||||
|
||||
<body class="wy-body-for-nav" role="document">
|
||||
|
||||
<div class="wy-grid-for-nav">
|
||||
<nav data-toggle="wy-nav-shift" class="wy-nav-side stickynav">
|
||||
<div class="wy-side-scroll">
|
||||
<div class="wy-side-nav-search">
|
||||
<a href="../.." class="icon icon-home"> RepApp - Makespace Repair-Café Hilpoltstein
|
||||
</a><div role="search">
|
||||
<form id ="rtd-search-form" class="wy-form" action="../../search.html" method="get">
|
||||
<input type="text" name="q" placeholder="Search docs" aria-label="Search docs" title="Type search term here" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="Navigation menu">
|
||||
<p class="caption"><span class="caption-text">RepApp</span></p>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="../..">Beschreibung</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="caption"><span class="caption-text">Developer documentation</span></p>
|
||||
<ul class="current">
|
||||
<li class="toctree-l1"><a class="reference internal" href="../">Overview</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="../users/">Users</a>
|
||||
</li>
|
||||
<li class="toctree-l1 current"><a class="reference internal current" href="./">Stories</a>
|
||||
<ul class="current">
|
||||
<li class="toctree-l2"><a class="reference internal" href="#repairer-cancels-on-short-notice">Repairer cancels on short notice</a>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="#guest-cancels-a-repair-appointment-on-short-notice">Guest cancels a repair appointment on short notice</a>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="#guest-misses-a-repair-appointment-or-causes-trouble">Guest misses a repair appointment or causes trouble</a>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="#the-last-repair-appointment-of-a-repair-cafe-must-get-canceled">The last repair appointment of a repair-cafe must get canceled</a>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="#all-slots-for-a-repair-cafe-are-very-early-assigned">All slots for a repair-cafe are very early assigned</a>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="#all-slots-for-some-device-types-are-already-very-relay-assigned">All slots for some device types are already very relay assigned</a>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="#creation-and-handling-of-waiting-lists">Creation and handling of waiting lists</a>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="#the-guest-doesnt-confirm-the-appointment-and-doesnt-reply-to-mails">The guest doesn't confirm the appointment and doesn't reply to mails</a>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="#some-device-types-are-not-repaired-during-a-repair-cafe">Some device types are not repaired during a repair-cafe</a>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="#strategic-overbooking">strategic overbooking</a>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="#communication-with-guests">Communication with guests</a>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="#where-is-the-device">Where is the device?</a>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="#criteria-for-repair-assignments">Criteria for repair-assignments</a>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="#paperwork-during-the-event">Paperwork during the event</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="../use_cases/">Use cases</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="../apps/">Apps</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="../objects/">Objects</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="../configuration/">Repapp configuration</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="caption"><span class="caption-text">Repair-Café & Makespace</span></p>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="" href="https://www.repaircafe-hilpoltstein.de">Repair-Café</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="" href="https://makes-hacks-hip.de">Makespace</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="" href="https://makes-hacks-hip.github.io">Makerspace@Github</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
|
||||
<nav class="wy-nav-top" role="navigation" aria-label="Mobile navigation menu">
|
||||
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
|
||||
<a href="../..">RepApp - Makespace Repair-Café Hilpoltstein</a>
|
||||
|
||||
</nav>
|
||||
<div class="wy-nav-content">
|
||||
<div class="rst-content"><div role="navigation" aria-label="breadcrumbs navigation">
|
||||
<ul class="wy-breadcrumbs">
|
||||
<li><a href="../.." class="icon icon-home" aria-label="Docs"></a> »</li>
|
||||
<li>Developer documentation »</li>
|
||||
<li class="breadcrumb-item active">Stories</li>
|
||||
<li class="wy-breadcrumbs-aside">
|
||||
<a href="https://github.com/makes-hacks-hip/RepApp/edit/main/docs/dev/stories.md" class="icon icon-github"> Edit on GitHub</a>
|
||||
</li>
|
||||
</ul>
|
||||
<hr/>
|
||||
</div>
|
||||
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
|
||||
<div class="section" itemprop="articleBody">
|
||||
|
||||
<h1 id="stories">Stories</h1>
|
||||
<p>User stories describing real world repair-cafe situations and workflows.</p>
|
||||
<h2 id="repairer-cancels-on-short-notice">Repairer cancels on short notice</h2>
|
||||
<p>One of the repairers cancels his attendance to a repair-cafe on short notice.</p>
|
||||
<p>If possible, assign existing repair appointments to other repairers, and align new time schedule with the guests using phone calls. If not possible, cancel repair assignments using phone calls.</p>
|
||||
<h2 id="guest-cancels-a-repair-appointment-on-short-notice">Guest cancels a repair appointment on short notice</h2>
|
||||
<p>A guest cancels a repair appointment on short notice. This frees a timeslot for an additional repair appointment.</p>
|
||||
<p>If there are fitting devices on the waiting list, the owner is called and a repair appointment is offered. If no fitting devices are available, the last appointment of the day is re-scheduled to the free slot, in alignment with the guest.</p>
|
||||
<h2 id="guest-misses-a-repair-appointment-or-causes-trouble">Guest misses a repair appointment or causes trouble</h2>
|
||||
<p>The guest is added to a blacklist.</p>
|
||||
<h2 id="the-last-repair-appointment-of-a-repair-cafe-must-get-canceled">The last repair appointment of a repair-cafe must get canceled</h2>
|
||||
<p>Because of delays or other issues, the last repair appointment of a repair-cafe event cannot happen anymore.</p>
|
||||
<p>If possible, the last repair appointment is cancelled using a phone call with the guest.</p>
|
||||
<h2 id="all-slots-for-a-repair-cafe-are-very-early-assigned">All slots for a repair-cafe are very early assigned</h2>
|
||||
<p>Deactivate registration form and give a hint on the homepage that the event is already fully booked.</p>
|
||||
<h2 id="all-slots-for-some-device-types-are-already-very-relay-assigned">All slots for some device types are already very relay assigned</h2>
|
||||
<p>Further registration is still possible, but a hint is added that repair assignments for devices types XYZ are not possible anymore.</p>
|
||||
<h2 id="creation-and-handling-of-waiting-lists">Creation and handling of waiting lists</h2>
|
||||
<p>If a repair appointment is not possible for a device, because of high demand for this device type of cancellation of a repairer, the guest is contacted and asked to register the device again for a later repair-cafe, or if the device shall be registered for a later repair-cafe.</p>
|
||||
<p>Guest registers again: It's possible that again no slots are free, and the repair appointment is canceled again. The information that the guest had the device already registered for a repair-cafe is lost.</p>
|
||||
<p>Organizer registers device: Increased organization effort and there is a high probability that the guest doesn't keep the repair appointment.</p>
|
||||
<p>All canceled devices are moved to the "canceled devices" section of the forum manually. These devices are considered during the planning of the next repair-cafe, and the forum entry is moved to the section of the next event.</p>
|
||||
<h2 id="the-guest-doesnt-confirm-the-appointment-and-doesnt-reply-to-mails">The guest doesn't confirm the appointment and doesn't reply to mails</h2>
|
||||
<p>Try to call guest by phone.</p>
|
||||
<p>If the organizers cannot reach the guest, the repair-appointment is canceled by mail. If possible, another guest form the waiting list is contacted.</p>
|
||||
<h2 id="some-device-types-are-not-repaired-during-a-repair-cafe">Some device types are not repaired during a repair-cafe</h2>
|
||||
<p>Some device types are not repaired during a repair-cafe. These devices are assigned to repairers for "offline" repair.</p>
|
||||
<p>Align with repairer: If the repairer accepts the device, the contact data of repairer are given to the guest.</p>
|
||||
<p>For TV: Type plate and issue description is given to a repairer, and options are evaluated. If a repair is possible, the guest shall bring the TV to the Haus Einstein. When the repair is done, the guest get's an update by mail.</p>
|
||||
<h2 id="strategic-overbooking">strategic overbooking</h2>
|
||||
<p>If all slots are booked, further devices are accepted on best effort. These devices are brought by the guests at the beginning of a repair-cafe and get collected at the end of a repair-cafe. When a repairer has some time between two repair-assignments, he can pick such a device.Having such devices is wanted by the repairers.</p>
|
||||
<p>This is only used for guests living very close to the event, and if the repair effort should be very limited.</p>
|
||||
<h2 id="communication-with-guests">Communication with guests</h2>
|
||||
<p>Communication shall primary happen by e-mail. If further information is needed for a device, a mail is written to the guest. This communication often involves embedded images and attachments.</p>
|
||||
<p>A FAQ on the website of the repair-cafe, which can get referenced, may help for this communication.</p>
|
||||
<h2 id="where-is-the-device">Where is the device?</h2>
|
||||
<p>It can happen that it's not clear where a device is located, for example if a device stays with the repair-cafe team for a follow up repair, or if a device is accepted outside of an repair-cafe event.</p>
|
||||
<p>Current workflow:</p>
|
||||
<p>For devices which get brought by the owner and collected later: If there was no time to repair the device, or if spare parts were missing, we may offer the guest to keep the device till the next repair-cafe event happens. Such devices are kept in Haus Einstein.</p>
|
||||
<p>Another such case may be that a guest brings a device a few days before the repair-cafe event, because of personal availability. Such devices are also stored in Haus Einstein.</p>
|
||||
<p>It may also happen that a repairer agrees with a guest to keep the device and continue with the repair activity outside of an repair-cafe event. Especially for such cases, the current location of a device may be not defined and a documentation is missing.</p>
|
||||
<h2 id="criteria-for-repair-assignments">Criteria for repair-assignments</h2>
|
||||
<ul>
|
||||
<li>Will a skilled repairer be available?</li>
|
||||
<li>Point of time of registration (waiting lists and registration date)</li>
|
||||
<li>Is the device and failure a candidate for an overbooking device?</li>
|
||||
<li>Is there a feedback of a repairer that he wants to take a look at the device?</li>
|
||||
<li>Is all needed information available?</li>
|
||||
</ul>
|
||||
<h2 id="paperwork-during-the-event">Paperwork during the event</h2>
|
||||
<p>Based on an Excel sheet, repair slips (guest data and signature) and routing slips (device data and fields for notes) are created. Both slips have the same date and id, so it's possible to match them if needed. This is required in case of issues or complaints of the guest.</p>
|
||||
|
||||
</div>
|
||||
</div><footer>
|
||||
<div class="rst-footer-buttons" role="navigation" aria-label="Footer Navigation">
|
||||
<a href="../users/" class="btn btn-neutral float-left" title="Users"><span class="icon icon-circle-arrow-left"></span> Previous</a>
|
||||
<a href="../use_cases/" class="btn btn-neutral float-right" title="Use cases">Next <span class="icon icon-circle-arrow-right"></span></a>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<div role="contentinfo">
|
||||
<!-- Copyright etc -->
|
||||
</div>
|
||||
|
||||
Built with <a href="https://www.mkdocs.org/">MkDocs</a> using a <a href="https://github.com/readthedocs/sphinx_rtd_theme">theme</a> provided by <a href="https://readthedocs.org">Read the Docs</a>.
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="rst-versions" role="note" aria-label="Versions">
|
||||
<span class="rst-current-version" data-toggle="rst-current-version">
|
||||
|
||||
<span>
|
||||
<a href="https://github.com/makes-hacks-hip/RepApp/" class="fa fa-github" style="color: #fcfcfc"> GitHub</a>
|
||||
</span>
|
||||
|
||||
|
||||
<span><a href="../users/" style="color: #fcfcfc">« Previous</a></span>
|
||||
|
||||
|
||||
<span><a href="../use_cases/" style="color: #fcfcfc">Next »</a></span>
|
||||
|
||||
</span>
|
||||
</div>
|
||||
<script src="../../js/jquery-3.6.0.min.js"></script>
|
||||
<script>var base_url = "../..";</script>
|
||||
<script src="../../js/theme_extra.js"></script>
|
||||
<script src="../../js/theme.js"></script>
|
||||
<script src="../../search/main.js"></script>
|
||||
<script>
|
||||
jQuery(function () {
|
||||
SphinxRtdTheme.Navigation.enable(true);
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.9 KiB |
210
dev/use_cases/index.html
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
<!DOCTYPE html>
|
||||
<html class="writer-html5" lang="en" >
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><link rel="canonical" href="https://makes-hacks-hip.github.io/RepApp/dev/use_cases/" />
|
||||
<link rel="shortcut icon" href="../../img/favicon.ico" />
|
||||
<title>Use cases - RepApp - Makespace Repair-Café Hilpoltstein</title>
|
||||
<link rel="stylesheet" href="../../css/theme.css" />
|
||||
<link rel="stylesheet" href="../../css/theme_extra.css" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/github.min.css" />
|
||||
|
||||
<script>
|
||||
// Current page data
|
||||
var mkdocs_page_name = "Use cases";
|
||||
var mkdocs_page_input_path = "dev/use_cases.md";
|
||||
var mkdocs_page_url = "/RepApp/dev/use_cases/";
|
||||
</script>
|
||||
|
||||
<!--[if lt IE 9]>
|
||||
<script src="../../js/html5shiv.min.js"></script>
|
||||
<![endif]-->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"></script>
|
||||
<script>hljs.highlightAll();</script>
|
||||
</head>
|
||||
|
||||
<body class="wy-body-for-nav" role="document">
|
||||
|
||||
<div class="wy-grid-for-nav">
|
||||
<nav data-toggle="wy-nav-shift" class="wy-nav-side stickynav">
|
||||
<div class="wy-side-scroll">
|
||||
<div class="wy-side-nav-search">
|
||||
<a href="../.." class="icon icon-home"> RepApp - Makespace Repair-Café Hilpoltstein
|
||||
</a><div role="search">
|
||||
<form id ="rtd-search-form" class="wy-form" action="../../search.html" method="get">
|
||||
<input type="text" name="q" placeholder="Search docs" aria-label="Search docs" title="Type search term here" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="Navigation menu">
|
||||
<p class="caption"><span class="caption-text">RepApp</span></p>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="../..">Beschreibung</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="caption"><span class="caption-text">Developer documentation</span></p>
|
||||
<ul class="current">
|
||||
<li class="toctree-l1"><a class="reference internal" href="../">Overview</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="../users/">Users</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="../stories/">Stories</a>
|
||||
</li>
|
||||
<li class="toctree-l1 current"><a class="reference internal current" href="./">Use cases</a>
|
||||
<ul class="current">
|
||||
<li class="toctree-l2"><a class="reference internal" href="#guest">Guest</a>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="#repairer">Repairer</a>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="#organizer">Organizer</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="../apps/">Apps</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="../objects/">Objects</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="../configuration/">Repapp configuration</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="caption"><span class="caption-text">Repair-Café & Makespace</span></p>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="" href="https://www.repaircafe-hilpoltstein.de">Repair-Café</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="" href="https://makes-hacks-hip.de">Makespace</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="" href="https://makes-hacks-hip.github.io">Makerspace@Github</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
|
||||
<nav class="wy-nav-top" role="navigation" aria-label="Mobile navigation menu">
|
||||
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
|
||||
<a href="../..">RepApp - Makespace Repair-Café Hilpoltstein</a>
|
||||
|
||||
</nav>
|
||||
<div class="wy-nav-content">
|
||||
<div class="rst-content"><div role="navigation" aria-label="breadcrumbs navigation">
|
||||
<ul class="wy-breadcrumbs">
|
||||
<li><a href="../.." class="icon icon-home" aria-label="Docs"></a> »</li>
|
||||
<li>Developer documentation »</li>
|
||||
<li class="breadcrumb-item active">Use cases</li>
|
||||
<li class="wy-breadcrumbs-aside">
|
||||
<a href="https://github.com/makes-hacks-hip/RepApp/edit/main/docs/dev/use_cases.md" class="icon icon-github"> Edit on GitHub</a>
|
||||
</li>
|
||||
</ul>
|
||||
<hr/>
|
||||
</div>
|
||||
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
|
||||
<div class="section" itemprop="articleBody">
|
||||
|
||||
<h1 id="use-cases">Use cases</h1>
|
||||
<p>The repapp shall support the following use cases:</p>
|
||||
<h2 id="guest">Guest</h2>
|
||||
<ul>
|
||||
<li>[ ] As a guest, I want to register a device for a repair-cafe event.</li>
|
||||
<li>[ ] As a guest, I want to get a confirmation for my device registration.</li>
|
||||
<li>[ ] As a guest, I want to get notified about a repair-assignment or a rejection of my device.</li>
|
||||
<li>[ ] As a guest, I want to get notified about information request for my device.</li>
|
||||
<li>[ ] As a guest, I want to be able to answer information requests for my device.</li>
|
||||
<li>[ ] As a guest, I want to be able to contact the repair-cafe organizers.</li>
|
||||
<li>[ ] As a guest, I want to be able to do all interaction by mail only (except the device registration).</li>
|
||||
<li>[ ] As a guest, I want to confirm a repair-assignment by mail.</li>
|
||||
<li>[ ] As a guest, I want to be able to see my device data and the status of my device registration.</li>
|
||||
<li>[ ] As a guest, I want to be able to register my device for a follow up repair.</li>
|
||||
<li>[ ] As a guest, I want to be able to view my personal data.</li>
|
||||
<li>[ ] As a guest, I want to be able to view my device data.</li>
|
||||
<li>[ ] As a guest, I want to be able to edit my personal data.</li>
|
||||
<li>[ ] As a guest, I want to be able to edit my device data.</li>
|
||||
<li>[ ] As a guest, I want to be able to delete my personal data.</li>
|
||||
<li>[ ] As a guest, I want to be able to delete my device data.</li>
|
||||
<li>[ ] As a guest, I want to be able to cancel my device registration.</li>
|
||||
</ul>
|
||||
<h2 id="repairer">Repairer</h2>
|
||||
<ul>
|
||||
<li>[ ] As a repairer, I want to be able to view the registered devices.</li>
|
||||
<li>[ ] As a repairer, I want to show interest for a device.</li>
|
||||
<li>[ ] As a repairer, I want to request additional information for a device.</li>
|
||||
<li>[ ] As a repairer, I want to enable or disable notifications.</li>
|
||||
<li>[ ] As a repairer, I want to get notified when an information request gets answered.</li>
|
||||
<li>[ ] As a repairer, I want to get notified when a device gets assigned to me.</li>
|
||||
<li>[ ] As a repairer, I want to confirm the participation at a repair-cafe.</li>
|
||||
<li>[ ] As a repairer, I want to decline the participation at a repair-cafe.</li>
|
||||
<li>[ ] As a repairer, I want to reject a device.</li>
|
||||
<li>[ ] As a repairer, I want to get notified if a device assigned to me gets canceled.</li>
|
||||
</ul>
|
||||
<h2 id="organizer">Organizer</h2>
|
||||
<ul>
|
||||
<li>[ ] As a organizer, I want to get notified when a new device is registered.</li>
|
||||
<li>[ ] As a organizer, I want to schedule new repair-cafe events.</li>
|
||||
<li>[ ] As a organizer, I want to view device details. </li>
|
||||
<li>[ ] As a organizer, I want to request further information for a device.</li>
|
||||
<li>[ ] As a organizer, I want to reject a device.</li>
|
||||
<li>[ ] As a organizer, I want to accept a device.</li>
|
||||
<li>[ ] As a organizer, I want to create a repair-assignment.</li>
|
||||
<li>[ ] As a organizer, I want to confirm the participation of a repairer for a repair-cafe event.</li>
|
||||
<li>[ ] As a organizer, I want to decline the participation of a repairer for a repair-cafe event.</li>
|
||||
<li>[ ] As a organizer, I want to edit the device data.</li>
|
||||
<li>[ ] As a organizer, I want to edit the guest data.</li>
|
||||
<li>[ ] As a organizer, I want to unregister a device.</li>
|
||||
<li>[ ] As a organizer, I want to get notified if a guest deletes a device registration.</li>
|
||||
<li>[ ] As a organizer, I want to get export all data for a repair-cafe.</li>
|
||||
<li>[ ] As a organizer, I want to assign devices to repair-cafes.</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div><footer>
|
||||
<div class="rst-footer-buttons" role="navigation" aria-label="Footer Navigation">
|
||||
<a href="../stories/" class="btn btn-neutral float-left" title="Stories"><span class="icon icon-circle-arrow-left"></span> Previous</a>
|
||||
<a href="../apps/" class="btn btn-neutral float-right" title="Apps">Next <span class="icon icon-circle-arrow-right"></span></a>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<div role="contentinfo">
|
||||
<!-- Copyright etc -->
|
||||
</div>
|
||||
|
||||
Built with <a href="https://www.mkdocs.org/">MkDocs</a> using a <a href="https://github.com/readthedocs/sphinx_rtd_theme">theme</a> provided by <a href="https://readthedocs.org">Read the Docs</a>.
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="rst-versions" role="note" aria-label="Versions">
|
||||
<span class="rst-current-version" data-toggle="rst-current-version">
|
||||
|
||||
<span>
|
||||
<a href="https://github.com/makes-hacks-hip/RepApp/" class="fa fa-github" style="color: #fcfcfc"> GitHub</a>
|
||||
</span>
|
||||
|
||||
|
||||
<span><a href="../stories/" style="color: #fcfcfc">« Previous</a></span>
|
||||
|
||||
|
||||
<span><a href="../apps/" style="color: #fcfcfc">Next »</a></span>
|
||||
|
||||
</span>
|
||||
</div>
|
||||
<script src="../../js/jquery-3.6.0.min.js"></script>
|
||||
<script>var base_url = "../..";</script>
|
||||
<script src="../../js/theme_extra.js"></script>
|
||||
<script src="../../js/theme.js"></script>
|
||||
<script src="../../search/main.js"></script>
|
||||
<script>
|
||||
jQuery(function () {
|
||||
SphinxRtdTheme.Navigation.enable(true);
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
156
dev/users/index.html
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
<!DOCTYPE html>
|
||||
<html class="writer-html5" lang="en" >
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><link rel="canonical" href="https://makes-hacks-hip.github.io/RepApp/dev/users/" />
|
||||
<link rel="shortcut icon" href="../../img/favicon.ico" />
|
||||
<title>Users - RepApp - Makespace Repair-Café Hilpoltstein</title>
|
||||
<link rel="stylesheet" href="../../css/theme.css" />
|
||||
<link rel="stylesheet" href="../../css/theme_extra.css" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/github.min.css" />
|
||||
|
||||
<script>
|
||||
// Current page data
|
||||
var mkdocs_page_name = "Users";
|
||||
var mkdocs_page_input_path = "dev/users.md";
|
||||
var mkdocs_page_url = "/RepApp/dev/users/";
|
||||
</script>
|
||||
|
||||
<!--[if lt IE 9]>
|
||||
<script src="../../js/html5shiv.min.js"></script>
|
||||
<![endif]-->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"></script>
|
||||
<script>hljs.highlightAll();</script>
|
||||
</head>
|
||||
|
||||
<body class="wy-body-for-nav" role="document">
|
||||
|
||||
<div class="wy-grid-for-nav">
|
||||
<nav data-toggle="wy-nav-shift" class="wy-nav-side stickynav">
|
||||
<div class="wy-side-scroll">
|
||||
<div class="wy-side-nav-search">
|
||||
<a href="../.." class="icon icon-home"> RepApp - Makespace Repair-Café Hilpoltstein
|
||||
</a><div role="search">
|
||||
<form id ="rtd-search-form" class="wy-form" action="../../search.html" method="get">
|
||||
<input type="text" name="q" placeholder="Search docs" aria-label="Search docs" title="Type search term here" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="Navigation menu">
|
||||
<p class="caption"><span class="caption-text">RepApp</span></p>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="../..">Beschreibung</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="caption"><span class="caption-text">Developer documentation</span></p>
|
||||
<ul class="current">
|
||||
<li class="toctree-l1"><a class="reference internal" href="../">Overview</a>
|
||||
</li>
|
||||
<li class="toctree-l1 current"><a class="reference internal current" href="./">Users</a>
|
||||
<ul class="current">
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="../stories/">Stories</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="../use_cases/">Use cases</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="../apps/">Apps</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="../objects/">Objects</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="../configuration/">Repapp configuration</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="caption"><span class="caption-text">Repair-Café & Makespace</span></p>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="" href="https://www.repaircafe-hilpoltstein.de">Repair-Café</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="" href="https://makes-hacks-hip.de">Makespace</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="" href="https://makes-hacks-hip.github.io">Makerspace@Github</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
|
||||
<nav class="wy-nav-top" role="navigation" aria-label="Mobile navigation menu">
|
||||
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
|
||||
<a href="../..">RepApp - Makespace Repair-Café Hilpoltstein</a>
|
||||
|
||||
</nav>
|
||||
<div class="wy-nav-content">
|
||||
<div class="rst-content"><div role="navigation" aria-label="breadcrumbs navigation">
|
||||
<ul class="wy-breadcrumbs">
|
||||
<li><a href="../.." class="icon icon-home" aria-label="Docs"></a> »</li>
|
||||
<li>Developer documentation »</li>
|
||||
<li class="breadcrumb-item active">Users</li>
|
||||
<li class="wy-breadcrumbs-aside">
|
||||
<a href="https://github.com/makes-hacks-hip/RepApp/edit/main/docs/dev/users.md" class="icon icon-github"> Edit on GitHub</a>
|
||||
</li>
|
||||
</ul>
|
||||
<hr/>
|
||||
</div>
|
||||
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
|
||||
<div class="section" itemprop="articleBody">
|
||||
|
||||
<h1 id="users">Users</h1>
|
||||
<p>Repapp distinguishes two user categories.</p>
|
||||
<p>A <em>guest</em> is a owner of an device. The <em>guest</em> is booking a repair appointment to get support from a repair café member to fix his device.</p>
|
||||
<p>A <em>member</em> can be an <em>organizer</em> or a <em>repairer</em>. A <em>organizer</em> is taking care of arranging the repair assignments, and needs to access the private contact data of a guest to do this job. A <em>repairer</em> is supporting the <em>guest</em> in fixing his device. The <em>repairer</em> only needs the details of the device in advance, but not the private contact data of the guest.</p>
|
||||
<p><img alt="Users" src="../uml/users.png" /></p>
|
||||
|
||||
</div>
|
||||
</div><footer>
|
||||
<div class="rst-footer-buttons" role="navigation" aria-label="Footer Navigation">
|
||||
<a href="../" class="btn btn-neutral float-left" title="Overview"><span class="icon icon-circle-arrow-left"></span> Previous</a>
|
||||
<a href="../stories/" class="btn btn-neutral float-right" title="Stories">Next <span class="icon icon-circle-arrow-right"></span></a>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<div role="contentinfo">
|
||||
<!-- Copyright etc -->
|
||||
</div>
|
||||
|
||||
Built with <a href="https://www.mkdocs.org/">MkDocs</a> using a <a href="https://github.com/readthedocs/sphinx_rtd_theme">theme</a> provided by <a href="https://readthedocs.org">Read the Docs</a>.
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="rst-versions" role="note" aria-label="Versions">
|
||||
<span class="rst-current-version" data-toggle="rst-current-version">
|
||||
|
||||
<span>
|
||||
<a href="https://github.com/makes-hacks-hip/RepApp/" class="fa fa-github" style="color: #fcfcfc"> GitHub</a>
|
||||
</span>
|
||||
|
||||
|
||||
<span><a href="../" style="color: #fcfcfc">« Previous</a></span>
|
||||
|
||||
|
||||
<span><a href="../stories/" style="color: #fcfcfc">Next »</a></span>
|
||||
|
||||
</span>
|
||||
</div>
|
||||
<script src="../../js/jquery-3.6.0.min.js"></script>
|
||||
<script>var base_url = "../..";</script>
|
||||
<script src="../../js/theme_extra.js"></script>
|
||||
<script src="../../js/theme.js"></script>
|
||||
<script src="../../search/main.js"></script>
|
||||
<script>
|
||||
jQuery(function () {
|
||||
SphinxRtdTheme.Navigation.enable(true);
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
# Apps
|
||||
|
||||
The repapp is split into different modules, realized as Django apps, to improve the maintainability.
|
||||
|
||||
## One time login
|
||||
|
||||
The one time login module provides a login for guests using a one time secret as part of an URL.
|
||||
|
||||
- The templates use the `settings.ORGANIZATION` variable to fill the organization name.
|
||||
- The Django mail module is used to send emails. See https://docs.djangoproject.com/en/4.2/topics/email/ for details how to configure the mail host.
|
||||
|
||||
### Configuration
|
||||
|
||||
To enable the one time login links, an additional authentication backend must get added:
|
||||
|
||||
```Python
|
||||
# Repapp specific authentication backends
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
# Backend for one time logins
|
||||
"one_time_login.authentication_backends.OneTimeLoginBackend",
|
||||
]
|
||||
```
|
||||
|
||||
The one time login urls must get added using namespace `one_time_login`:
|
||||
|
||||
```Python
|
||||
urlpatterns = i18n_patterns(
|
||||
...
|
||||
path(_('one_time_login/'),
|
||||
include('one_time_login.urls', namespace='one_time_login')),
|
||||
...
|
||||
)
|
||||
```
|
||||
|
||||
## Email interface
|
||||
|
||||
The email interface modules supports handling of mails. It support sending of mails, including HTML formatting and attachments, and it supports receiving of plain text and HTML formatted mails, including attachments.
|
||||
|
||||
- The Django mail module is used to send emails. See https://docs.djangoproject.com/en/4.2/topics/email/ for details how to configure the mail host.
|
||||
- CKEditor is used for email content editing. See https://django-ckeditor.readthedocs.io/en/latest/ for details.
|
||||
- Crispy forms is used for form rendering. See https://django-crispy-forms.readthedocs.io/en/latest/ for details.
|
||||
- Bootstrap 5 theme for crispy forms is used. See https://github.com/django-crispy-forms/crispy-bootstrap5 for details.
|
||||
|
||||
### Configuration
|
||||
|
||||
The email interface urls must get added using namespace `email_interface`:
|
||||
|
||||
```Python
|
||||
urlpatterns = i18n_patterns(
|
||||
...
|
||||
path(_('emails/'),
|
||||
include('email_interface.urls', namespace='email_interface')),
|
||||
...
|
||||
)
|
||||
```
|
||||
|
||||
To use CKEditor as authenticated user, the following additional urls must get added:
|
||||
|
||||
```Python
|
||||
urlpatterns = i18n_patterns(
|
||||
...
|
||||
# CKEditor upload views
|
||||
path('upload/', login_required(ckeditor_views.upload), name="ckeditor_upload"),
|
||||
path('browse/', never_cache(login_required(ckeditor_views.browse)),
|
||||
name="ckeditor_browse"),
|
||||
...
|
||||
)
|
||||
```
|
||||
|
||||
To be abel to use CKEditor also the static an media content must get served. For development this is done with:
|
||||
|
||||
```Python
|
||||
if settings.DEBUG:
|
||||
urlpatterns += static(settings.MEDIA_URL,
|
||||
document_root=settings.MEDIA_ROOT)
|
||||
```
|
||||
|
|
@ -1,163 +0,0 @@
|
|||
# Repapp configuration
|
||||
|
||||
## Debug mode
|
||||
|
||||
The Django debug mode is activated if there is a environment variable `DJANGO_DEBUG` with the value "true", "1" or "t". By default, the debug mode is disabled.
|
||||
|
||||
## Organization name
|
||||
|
||||
Some templates use and organization name. This value is taken from `settings.ORGANIZATION` and defaults to "Repair-Café Hilpoltstein".
|
||||
|
||||
## Email settings
|
||||
|
||||
The following environment variables are used to configure the Django email feature:
|
||||
|
||||
``` Python
|
||||
EMAIL_HOST = os.getenv("DJANGO_EMAIL_HOST", "")
|
||||
EMAIL_PORT = (int)(os.getenv("DJANGO_EMAIL_PORT", "25"))
|
||||
EMAIL_HOST_USER = os.getenv("DJANGO_EMAIL_HOST_USER", None)
|
||||
EMAIL_HOST_PASSWORD = os.getenv("DJANGO_EMAIL_HOST_PASSWORD", None)
|
||||
EMAIL_USE_TLS = os.getenv("DJANGO_EMAIL_USE_TLS", "true") in ("true", "1", "t")
|
||||
DEFAULT_FROM_EMAIL = os.getenv("DJANGO_SENDER_ADDRESS", "")
|
||||
```
|
||||
See https://docs.djangoproject.com/en/4.2/topics/email/ for more details.
|
||||
|
||||
## Translations
|
||||
|
||||
Repapp is using localization, and comes with german and english translations.
|
||||
|
||||
Enable the `LocaleMiddleware`:
|
||||
|
||||
```Python
|
||||
MIDDLEWARE = [
|
||||
...
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
# enable translation
|
||||
'django.middleware.locale.LocaleMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
We use the following localization settings:
|
||||
|
||||
```Python
|
||||
LANGUAGE_CODE = 'de'
|
||||
TIME_ZONE = 'Europe/Berlin'
|
||||
|
||||
USE_I18N = True
|
||||
USE_L10N = True
|
||||
USE_TZ = True
|
||||
|
||||
LANGUAGES = [
|
||||
('de', _('German')),
|
||||
('en', _('English')),
|
||||
]
|
||||
|
||||
LOCALE_PATHS = [
|
||||
BASE_DIR / 'locale',
|
||||
]
|
||||
```
|
||||
|
||||
Also the URLs are localized, and the language is choosen using i18n URL patterns:
|
||||
|
||||
```Python
|
||||
...
|
||||
from django.conf.urls.i18n import i18n_patterns
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
urlpatterns = i18n_patterns(
|
||||
path('admin/', admin.site.urls),
|
||||
path(_('one_time_login/'),
|
||||
include('one_time_login.urls', namespace='one_time_login')),
|
||||
# URLs for rosetta translation interface
|
||||
path('rosetta/', include('rosetta.urls')),
|
||||
)
|
||||
```
|
||||
|
||||
### Rosetta
|
||||
|
||||
For translating the interface, we use rosetta. For more details see https://pypi.org/project/django-rosetta/.
|
||||
|
||||
You need to install rosetta (`pip install django-rosetta`) and enable it in the apps:
|
||||
|
||||
```Python
|
||||
INSTALLED_APPS = [
|
||||
...
|
||||
# enable rosetta online translation interface
|
||||
'rosetta',
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
Rosatta is made availabel at `rosetta/`.
|
||||
|
||||
## One time login
|
||||
|
||||
To enable the one time login links, an additional authentication backend must get added:
|
||||
|
||||
```Python
|
||||
# Repapp specific authentication backends
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
# Backend for one time logins
|
||||
"one_time_login.authentication_backends.OneTimeLoginBackend",
|
||||
]
|
||||
```
|
||||
|
||||
## Email interface
|
||||
|
||||
The email interface uses CKEditor, crispy forms and easy thumbnails. The apps are enabled with:
|
||||
|
||||
```Python
|
||||
INSTALLED_APPS = [
|
||||
...
|
||||
# CKEditor for mail content editing
|
||||
"ckeditor",
|
||||
"ckeditor_uploader",
|
||||
# Crispy forms for bootstrap 5 form design
|
||||
"crispy_forms",
|
||||
"crispy_bootstrap5",
|
||||
# for creating thumbnails, used by email_interface demo views
|
||||
'easy_thumbnails',
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
For CKEditor, we use the following settings:
|
||||
|
||||
```Python
|
||||
# editor upload path, used e.g. for send mail demo
|
||||
CKEDITOR_UPLOAD_PATH = "editor_uploads/"
|
||||
|
||||
CKEDITOR_IMAGE_BACKEND = 'pillow'
|
||||
|
||||
# CKEditor default config
|
||||
CKEDITOR_CONFIGS = {
|
||||
'default': {
|
||||
'toolbar': 'full',
|
||||
'height': 300,
|
||||
'width': '100%',
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
To enable the image upload of CKEditor, also the Django media paths and urls must get configured:
|
||||
|
||||
```Python
|
||||
# Django media and static file config, required for CKEditor
|
||||
STATIC_URL = "static/"
|
||||
MEDIA_URL = "media/"
|
||||
# Use subfolders of project folder
|
||||
STATIC_ROOT = os.path.join(BASE_DIR, "static")
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
|
||||
```
|
||||
|
||||
For crispy forms, we use the bootstrap 5 theme:
|
||||
|
||||
```Python
|
||||
# Crispy forms settings
|
||||
CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"
|
||||
CRISPY_TEMPLATE_PACK = "bootstrap5"
|
||||
```
|
||||
|
||||
The static files for bootstrap must also be made available.
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
# Repapp - developer documentation
|
||||
|
||||
The Repapp is a repair assignment booking and management tool.
|
||||
It tries to support all common workflows happening while organizing a repair café event.
|
||||
It's build using the Django web framework.
|
||||
|
||||
The code of the Repapp is hosted on Github: https://github.com/makes-hacks-hip/RepApp
|
||||
|
||||
## Local development setup
|
||||
|
||||
- Clone the Github repository
|
||||
- Create a Python environment: `python -m venv venv`
|
||||
- Activate the Python environment: `source venv/bin/activate`
|
||||
- Install the dependencies: `pip install -r requirements_prod.txt` and `pip install -r requirements_dev.txt`
|
||||
- Change to the Django project folder: `cd repapp`
|
||||
- Setup the database:
|
||||
- `python manage.py makemigrations`
|
||||
- `python manage.py migrate`
|
||||
- Setup an admin user: `python manage.py createsuperuser`
|
||||
- Run the local test server: `python manage.py runserver`
|
||||
|
||||
## Docker image
|
||||
|
||||
- Build the image: `docker build -t repapp .`
|
||||
- Run the image:
|
||||
|
||||
```bash
|
||||
docker run --rm -d \
|
||||
-p 127.0.0.1:8020:8020 \
|
||||
--name repapp \
|
||||
--env DJANGO_SUPERUSER_USERNAME=suba \
|
||||
--env DJANGO_SUPERUSER_PASSWORD=ThePassword \
|
||||
--env DJANGO_SUPERUSER_EMAIL=noreply@example.com \
|
||||
repapp`
|
||||
```
|
||||
|
||||
- Open http://127.0.0.1:8020/ in your browser
|
||||
|
||||
## Tests
|
||||
|
||||
- Unit tests: `python manage.py test`
|
||||
- LiveServer tests: `python manage.py test repapp.live_tests`
|
||||
|
||||
### Coverage
|
||||
|
||||
- `coverage run --source='.' manage.py test`
|
||||
- `coverage report` or `coverage html`
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
# Objects
|
||||
|
||||
A **device** is an object owned by a **guest** which has some issue. To get support for fixing the issue, a **guest** provides the information about the **device** and requests a repair **appointment** during a **repair_cafe**.
|
||||
|
||||

|
||||
|
||||
A **repair_cafe** happens at a specific date and location. It's an event which consists of **appointments** of **repairers** with **guests** to fix a **device**.
|
||||
|
||||
To organize a **repair_cafe**, a **organizer** needs to know which **repairer** will attend a **repair_cafe**, to schedule repair **appointments**.
|
||||
|
||||

|
||||
|
||||
In case of ambiguities, a **organizer** or a **repairer** can raise a **consultation** request. This **consultation** request is sent to the **guest** to get the needed information. In addition, a **organizer** or a **repairer** can add an **comment** to a **device**.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## Repair_Cafe
|
||||
|
||||
A **repair_cafe** is a event happening at a specific date at a specific location. During a **repair_cafe** **appointments** of **guests** with **repairers** happens to fix broken *devices*.
|
||||
|
||||
### Fields
|
||||
|
||||
- *location*: Char (200) -- location of the event
|
||||
- *address*: Char (200) -- address where the event happens
|
||||
- *event_date*: Date -- date when the event happens
|
||||
- *repairer*: many to many -- will attend the event
|
||||
|
||||
## Device
|
||||
|
||||
## Appointment
|
||||
|
||||
## Guest
|
||||
|
||||
## Repairer
|
||||
|
||||
## Organizer
|
||||
|
||||
## Consultation
|
||||
|
||||
## Comment
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
# Stories
|
||||
|
||||
User stories describing real world repair-cafe situations and workflows.
|
||||
|
||||
## Repairer cancels on short notice
|
||||
|
||||
One of the repairers cancels his attendance to a repair-cafe on short notice.
|
||||
|
||||
If possible, assign existing repair appointments to other repairers, and align new time schedule with the guests using phone calls. If not possible, cancel repair assignments using phone calls.
|
||||
|
||||
## Guest cancels a repair appointment on short notice
|
||||
|
||||
A guest cancels a repair appointment on short notice. This frees a timeslot for an additional repair appointment.
|
||||
|
||||
If there are fitting devices on the waiting list, the owner is called and a repair appointment is offered. If no fitting devices are available, the last appointment of the day is re-scheduled to the free slot, in alignment with the guest.
|
||||
|
||||
## Guest misses a repair appointment or causes trouble
|
||||
|
||||
The guest is added to a blacklist.
|
||||
|
||||
## The last repair appointment of a repair-cafe must get canceled
|
||||
|
||||
Because of delays or other issues, the last repair appointment of a repair-cafe event cannot happen anymore.
|
||||
|
||||
If possible, the last repair appointment is cancelled using a phone call with the guest.
|
||||
|
||||
## All slots for a repair-cafe are very early assigned
|
||||
|
||||
Deactivate registration form and give a hint on the homepage that the event is already fully booked.
|
||||
|
||||
## All slots for some device types are already very relay assigned
|
||||
|
||||
Further registration is still possible, but a hint is added that repair assignments for devices types XYZ are not possible anymore.
|
||||
|
||||
## Creation and handling of waiting lists
|
||||
|
||||
If a repair appointment is not possible for a device, because of high demand for this device type of cancellation of a repairer, the guest is contacted and asked to register the device again for a later repair-cafe, or if the device shall be registered for a later repair-cafe.
|
||||
|
||||
Guest registers again: It's possible that again no slots are free, and the repair appointment is canceled again. The information that the guest had the device already registered for a repair-cafe is lost.
|
||||
|
||||
Organizer registers device: Increased organization effort and there is a high probability that the guest doesn't keep the repair appointment.
|
||||
|
||||
All canceled devices are moved to the "canceled devices" section of the forum manually. These devices are considered during the planning of the next repair-cafe, and the forum entry is moved to the section of the next event.
|
||||
|
||||
## The guest doesn't confirm the appointment and doesn't reply to mails
|
||||
|
||||
Try to call guest by phone.
|
||||
|
||||
If the organizers cannot reach the guest, the repair-appointment is canceled by mail. If possible, another guest form the waiting list is contacted.
|
||||
|
||||
## Some device types are not repaired during a repair-cafe
|
||||
|
||||
Some device types are not repaired during a repair-cafe. These devices are assigned to repairers for "offline" repair.
|
||||
|
||||
Align with repairer: If the repairer accepts the device, the contact data of repairer are given to the guest.
|
||||
|
||||
For TV: Type plate and issue description is given to a repairer, and options are evaluated. If a repair is possible, the guest shall bring the TV to the Haus Einstein. When the repair is done, the guest get's an update by mail.
|
||||
|
||||
## strategic overbooking
|
||||
|
||||
If all slots are booked, further devices are accepted on best effort. These devices are brought by the guests at the beginning of a repair-cafe and get collected at the end of a repair-cafe. When a repairer has some time between two repair-assignments, he can pick such a device.Having such devices is wanted by the repairers.
|
||||
|
||||
This is only used for guests living very close to the event, and if the repair effort should be very limited.
|
||||
|
||||
## Communication with guests
|
||||
|
||||
Communication shall primary happen by e-mail. If further information is needed for a device, a mail is written to the guest. This communication often involves embedded images and attachments.
|
||||
|
||||
A FAQ on the website of the repair-cafe, which can get referenced, may help for this communication.
|
||||
|
||||
## Where is the device?
|
||||
|
||||
It can happen that it's not clear where a device is located, for example if a device stays with the repair-cafe team for a follow up repair, or if a device is accepted outside of an repair-cafe event.
|
||||
|
||||
Current workflow:
|
||||
|
||||
For devices which get brought by the owner and collected later: If there was no time to repair the device, or if spare parts were missing, we may offer the guest to keep the device till the next repair-cafe event happens. Such devices are kept in Haus Einstein.
|
||||
|
||||
Another such case may be that a guest brings a device a few days before the repair-cafe event, because of personal availability. Such devices are also stored in Haus Einstein.
|
||||
|
||||
It may also happen that a repairer agrees with a guest to keep the device and continue with the repair activity outside of an repair-cafe event. Especially for such cases, the current location of a device may be not defined and a documentation is missing.
|
||||
|
||||
## Criteria for repair-assignments
|
||||
|
||||
- Will a skilled repairer be available?
|
||||
- Point of time of registration (waiting lists and registration date)
|
||||
- Is the device and failure a candidate for an overbooking device?
|
||||
- Is there a feedback of a repairer that he wants to take a look at the device?
|
||||
- Is all needed information available?
|
||||
|
||||
## Paperwork during the event
|
||||
|
||||
Based on an Excel sheet, repair slips (guest data and signature) and routing slips (device data and fields for notes) are created. Both slips have the same date and id, so it's possible to match them if needed. This is required in case of issues or complaints of the guest.
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
# Use cases
|
||||
|
||||
The repapp shall support the following use cases:
|
||||
|
||||
## Guest
|
||||
|
||||
- [ ] As a guest, I want to register a device for a repair-cafe event.
|
||||
- [ ] As a guest, I want to get a confirmation for my device registration.
|
||||
- [ ] As a guest, I want to get notified about a repair-assignment or a rejection of my device.
|
||||
- [ ] As a guest, I want to get notified about information request for my device.
|
||||
- [ ] As a guest, I want to be able to answer information requests for my device.
|
||||
- [ ] As a guest, I want to be able to contact the repair-cafe organizers.
|
||||
- [ ] As a guest, I want to be able to do all interaction by mail only (except the device registration).
|
||||
- [ ] As a guest, I want to confirm a repair-assignment by mail.
|
||||
- [ ] As a guest, I want to be able to see my device data and the status of my device registration.
|
||||
- [ ] As a guest, I want to be able to register my device for a follow up repair.
|
||||
- [ ] As a guest, I want to be able to view my personal data.
|
||||
- [ ] As a guest, I want to be able to view my device data.
|
||||
- [ ] As a guest, I want to be able to edit my personal data.
|
||||
- [ ] As a guest, I want to be able to edit my device data.
|
||||
- [ ] As a guest, I want to be able to delete my personal data.
|
||||
- [ ] As a guest, I want to be able to delete my device data.
|
||||
- [ ] As a guest, I want to be able to cancel my device registration.
|
||||
|
||||
## Repairer
|
||||
|
||||
- [ ] As a repairer, I want to be able to view the registered devices.
|
||||
- [ ] As a repairer, I want to show interest for a device.
|
||||
- [ ] As a repairer, I want to request additional information for a device.
|
||||
- [ ] As a repairer, I want to enable or disable notifications.
|
||||
- [ ] As a repairer, I want to get notified when an information request gets answered.
|
||||
- [ ] As a repairer, I want to get notified when a device gets assigned to me.
|
||||
- [ ] As a repairer, I want to confirm the participation at a repair-cafe.
|
||||
- [ ] As a repairer, I want to decline the participation at a repair-cafe.
|
||||
- [ ] As a repairer, I want to reject a device.
|
||||
- [ ] As a repairer, I want to get notified if a device assigned to me gets canceled.
|
||||
|
||||
## Organizer
|
||||
|
||||
- [ ] As a organizer, I want to get notified when a new device is registered.
|
||||
- [ ] As a organizer, I want to schedule new repair-cafe events.
|
||||
- [ ] As a organizer, I want to view device details.
|
||||
- [ ] As a organizer, I want to request further information for a device.
|
||||
- [ ] As a organizer, I want to reject a device.
|
||||
- [ ] As a organizer, I want to accept a device.
|
||||
- [ ] As a organizer, I want to create a repair-assignment.
|
||||
- [ ] As a organizer, I want to confirm the participation of a repairer for a repair-cafe event.
|
||||
- [ ] As a organizer, I want to decline the participation of a repairer for a repair-cafe event.
|
||||
- [ ] As a organizer, I want to edit the device data.
|
||||
- [ ] As a organizer, I want to edit the guest data.
|
||||
- [ ] As a organizer, I want to unregister a device.
|
||||
- [ ] As a organizer, I want to get notified if a guest deletes a device registration.
|
||||
- [ ] As a organizer, I want to get export all data for a repair-cafe.
|
||||
- [ ] As a organizer, I want to assign devices to repair-cafes.
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
# Users
|
||||
|
||||
Repapp distinguishes two user categories.
|
||||
|
||||
A *guest* is a owner of an device. The *guest* is booking a repair appointment to get support from a repair café member to fix his device.
|
||||
|
||||
A *member* can be an *organizer* or a *repairer*. A *organizer* is taking care of arranging the repair assignments, and needs to access the private contact data of a guest to do this job. A *repairer* is supporting the *guest* in fixing his device. The *repairer* only needs the details of the device in advance, but not the private contact data of the guest.
|
||||
|
||||

|
||||
|
|
@ -1,35 +0,0 @@
|
|||
# Projekt RepApp
|
||||
|
||||
RepApp ist eine Web App um Reparaturen im Rahmen eines Repair-Cafés zu organisieren.
|
||||
|
||||
## Benutzer
|
||||
|
||||
RepApp unterscheidet zwischen den folgenden Benutzer-Typen:
|
||||
|
||||
### Gast
|
||||
|
||||
Ein Gast ist ein Gerätebesitzer der ein oder mehrere Geräte zur Reparatur anmelden möchte.
|
||||
|
||||
### Reparateur
|
||||
|
||||
Ein Reparateur ist ein Mitglied des Repair-Cafés das die Gäste bei der Reparatur ihrer Geräte unterstützt.
|
||||
|
||||
### Organisator
|
||||
|
||||
Ein Organisator ist Mitglied des Repair-Cafés das die Termine mit den Gästen organisiert und die Reparateure zu den Geräten zuordnet.
|
||||
|
||||
## Anmelde Konzept
|
||||
|
||||
RepApp unterstützt pro Benutzer-Gruppe verschiedenen Anmelde-Konzepte.
|
||||
|
||||
### Gäste
|
||||
|
||||
Für Gäste gibt es Einmal-Logins. Ein Einmal-Login ist ein Geheimnis und eine Ziel-URL, das dem Gast in From eines Anmelde-Links in einer eMail mitgeteilt wird. Mit diesem Link kann der Gast sich einmalig anmelden, da das Geheimnis danach potentiell Dritten bekannt ist, z.B. über die Browser History.
|
||||
|
||||
Nach dem erfolgreichen Login wird der Gast automatisch zu der hinterlegten URL weitergeleitet. Dieser Mechanismus erlaubt es einem Gast "per Klick" Zugriff auf geschützte Daten zu geben.
|
||||
|
||||
Wenn ein Gast versucht den Einmal-Link nochmals zu verwenden, schlägt dies Fehl, und dem Gast wird automatisch ein neuer Einmal-Link per eMail mitgeteilt. Dies wird dem Gast auch über entsprechende Nachrichten mitgeteilt.
|
||||
|
||||
### Mitarbeiter
|
||||
|
||||
Der Login für Mitarbeiter ist nur über das Single-Sign-On der Repair-Cafés erlaubt (Keycloak), das mittels OIDC angebunden ist. Alle Mitarbeiter im Repair-Café haben dort bereits einen Benutzer, was zum einen sicherstellt dass es sich um einen Mitarbeiter handelt, und zum anderen auch die Veraltung der Benutzer in RepApp vereinfacht.
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
|
||||
export DJANGO_EMAIL_HOST=""
|
||||
export DJANGO_EMAIL_PORT="25"
|
||||
export DJANGO_EMAIL_HOST_USER=""
|
||||
export DJANGO_EMAIL_HOST_PASSWORD=""
|
||||
export DJANGO_EMAIL_USE_TLS="true"
|
||||
export DJANGO_SENDER_ADDRESS=""
|
||||
BIN
img/favicon.ico
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
184
index.html
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
<!DOCTYPE html>
|
||||
<html class="writer-html5" lang="en" >
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><link rel="canonical" href="https://makes-hacks-hip.github.io/RepApp/" />
|
||||
<link rel="shortcut icon" href="img/favicon.ico" />
|
||||
<title>Beschreibung - RepApp - Makespace Repair-Café Hilpoltstein</title>
|
||||
<link rel="stylesheet" href="css/theme.css" />
|
||||
<link rel="stylesheet" href="css/theme_extra.css" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/github.min.css" />
|
||||
|
||||
<script>
|
||||
// Current page data
|
||||
var mkdocs_page_name = "Beschreibung";
|
||||
var mkdocs_page_input_path = "index.md";
|
||||
var mkdocs_page_url = "/RepApp/";
|
||||
</script>
|
||||
|
||||
<!--[if lt IE 9]>
|
||||
<script src="js/html5shiv.min.js"></script>
|
||||
<![endif]-->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"></script>
|
||||
<script>hljs.highlightAll();</script>
|
||||
</head>
|
||||
|
||||
<body class="wy-body-for-nav" role="document">
|
||||
|
||||
<div class="wy-grid-for-nav">
|
||||
<nav data-toggle="wy-nav-shift" class="wy-nav-side stickynav">
|
||||
<div class="wy-side-scroll">
|
||||
<div class="wy-side-nav-search">
|
||||
<a href="." class="icon icon-home"> RepApp - Makespace Repair-Café Hilpoltstein
|
||||
</a><div role="search">
|
||||
<form id ="rtd-search-form" class="wy-form" action="./search.html" method="get">
|
||||
<input type="text" name="q" placeholder="Search docs" aria-label="Search docs" title="Type search term here" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="Navigation menu">
|
||||
<p class="caption"><span class="caption-text">RepApp</span></p>
|
||||
<ul class="current">
|
||||
<li class="toctree-l1 current"><a class="reference internal current" href=".">Beschreibung</a>
|
||||
<ul class="current">
|
||||
<li class="toctree-l2"><a class="reference internal" href="#benutzer">Benutzer</a>
|
||||
<ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="#gast">Gast</a>
|
||||
</li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="#reparateur">Reparateur</a>
|
||||
</li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="#organisator">Organisator</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="#anmelde-konzept">Anmelde Konzept</a>
|
||||
<ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="#gaste">Gäste</a>
|
||||
</li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="#mitarbeiter">Mitarbeiter</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="caption"><span class="caption-text">Developer documentation</span></p>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="dev/">Overview</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="dev/users/">Users</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="dev/stories/">Stories</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="dev/use_cases/">Use cases</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="dev/apps/">Apps</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="dev/objects/">Objects</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="dev/configuration/">Repapp configuration</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="caption"><span class="caption-text">Repair-Café & Makespace</span></p>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="" href="https://www.repaircafe-hilpoltstein.de">Repair-Café</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="" href="https://makes-hacks-hip.de">Makespace</a>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="" href="https://makes-hacks-hip.github.io">Makerspace@Github</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
|
||||
<nav class="wy-nav-top" role="navigation" aria-label="Mobile navigation menu">
|
||||
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
|
||||
<a href=".">RepApp - Makespace Repair-Café Hilpoltstein</a>
|
||||
|
||||
</nav>
|
||||
<div class="wy-nav-content">
|
||||
<div class="rst-content"><div role="navigation" aria-label="breadcrumbs navigation">
|
||||
<ul class="wy-breadcrumbs">
|
||||
<li><a href="." class="icon icon-home" aria-label="Docs"></a> »</li>
|
||||
<li>RepApp »</li>
|
||||
<li class="breadcrumb-item active">Beschreibung</li>
|
||||
<li class="wy-breadcrumbs-aside">
|
||||
<a href="https://github.com/makes-hacks-hip/RepApp/edit/main/docs/index.md" class="icon icon-github"> Edit on GitHub</a>
|
||||
</li>
|
||||
</ul>
|
||||
<hr/>
|
||||
</div>
|
||||
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
|
||||
<div class="section" itemprop="articleBody">
|
||||
|
||||
<h1 id="projekt-repapp">Projekt RepApp</h1>
|
||||
<p>RepApp ist eine Web App um Reparaturen im Rahmen eines Repair-Cafés zu organisieren.</p>
|
||||
<h2 id="benutzer">Benutzer</h2>
|
||||
<p>RepApp unterscheidet zwischen den folgenden Benutzer-Typen:</p>
|
||||
<h3 id="gast">Gast</h3>
|
||||
<p>Ein Gast ist ein Gerätebesitzer der ein oder mehrere Geräte zur Reparatur anmelden möchte.</p>
|
||||
<h3 id="reparateur">Reparateur</h3>
|
||||
<p>Ein Reparateur ist ein Mitglied des Repair-Cafés das die Gäste bei der Reparatur ihrer Geräte unterstützt.</p>
|
||||
<h3 id="organisator">Organisator</h3>
|
||||
<p>Ein Organisator ist Mitglied des Repair-Cafés das die Termine mit den Gästen organisiert und die Reparateure zu den Geräten zuordnet.</p>
|
||||
<h2 id="anmelde-konzept">Anmelde Konzept</h2>
|
||||
<p>RepApp unterstützt pro Benutzer-Gruppe verschiedenen Anmelde-Konzepte.</p>
|
||||
<h3 id="gaste">Gäste</h3>
|
||||
<p>Für Gäste gibt es Einmal-Logins. Ein Einmal-Login ist ein Geheimnis und eine Ziel-URL, das dem Gast in From eines Anmelde-Links in einer eMail mitgeteilt wird. Mit diesem Link kann der Gast sich einmalig anmelden, da das Geheimnis danach potentiell Dritten bekannt ist, z.B. über die Browser History.</p>
|
||||
<p>Nach dem erfolgreichen Login wird der Gast automatisch zu der hinterlegten URL weitergeleitet. Dieser Mechanismus erlaubt es einem Gast "per Klick" Zugriff auf geschützte Daten zu geben.</p>
|
||||
<p>Wenn ein Gast versucht den Einmal-Link nochmals zu verwenden, schlägt dies Fehl, und dem Gast wird automatisch ein neuer Einmal-Link per eMail mitgeteilt. Dies wird dem Gast auch über entsprechende Nachrichten mitgeteilt.</p>
|
||||
<h3 id="mitarbeiter">Mitarbeiter</h3>
|
||||
<p>Der Login für Mitarbeiter ist nur über das Single-Sign-On der Repair-Cafés erlaubt (Keycloak), das mittels OIDC angebunden ist. Alle Mitarbeiter im Repair-Café haben dort bereits einen Benutzer, was zum einen sicherstellt dass es sich um einen Mitarbeiter handelt, und zum anderen auch die Veraltung der Benutzer in RepApp vereinfacht.</p>
|
||||
|
||||
</div>
|
||||
</div><footer>
|
||||
<div class="rst-footer-buttons" role="navigation" aria-label="Footer Navigation">
|
||||
<a href="dev/" class="btn btn-neutral float-right" title="Overview">Next <span class="icon icon-circle-arrow-right"></span></a>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<div role="contentinfo">
|
||||
<!-- Copyright etc -->
|
||||
</div>
|
||||
|
||||
Built with <a href="https://www.mkdocs.org/">MkDocs</a> using a <a href="https://github.com/readthedocs/sphinx_rtd_theme">theme</a> provided by <a href="https://readthedocs.org">Read the Docs</a>.
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="rst-versions" role="note" aria-label="Versions">
|
||||
<span class="rst-current-version" data-toggle="rst-current-version">
|
||||
|
||||
<span>
|
||||
<a href="https://github.com/makes-hacks-hip/RepApp/" class="fa fa-github" style="color: #fcfcfc"> GitHub</a>
|
||||
</span>
|
||||
|
||||
|
||||
|
||||
<span><a href="dev/" style="color: #fcfcfc">Next »</a></span>
|
||||
|
||||
</span>
|
||||
</div>
|
||||
<script src="js/jquery-3.6.0.min.js"></script>
|
||||
<script>var base_url = ".";</script>
|
||||
<script src="js/theme_extra.js"></script>
|
||||
<script src="js/theme.js"></script>
|
||||
<script src="search/main.js"></script>
|
||||
<script>
|
||||
jQuery(function () {
|
||||
SphinxRtdTheme.Navigation.enable(true);
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
4
js/html5shiv.min.js
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
|
||||
*/
|
||||
!function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x<style>"+b+"</style>",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="<xyz></xyz>",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document);
|
||||
2
js/jquery-3.6.0.min.js
vendored
Normal file
2
js/theme.js
Normal file
8
js/theme_extra.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* Assign 'docutils' class to tables so styling and
|
||||
* JavaScript behavior is applied.
|
||||
*
|
||||
* https://github.com/mkdocs/mkdocs/issues/2028
|
||||
*/
|
||||
|
||||
$('div.rst-content table').addClass('docutils');
|
||||
20
mkdocs.yml
|
|
@ -1,20 +0,0 @@
|
|||
site_name: RepApp - Makespace Repair-Café Hilpoltstein
|
||||
site_url: https://makes-hacks-hip.github.io/RepApp/
|
||||
nav:
|
||||
- RepApp:
|
||||
- Beschreibung: 'index.md'
|
||||
- 'Developer documentation':
|
||||
- Overview: 'dev/index.md'
|
||||
- Users: 'dev/users.md'
|
||||
- Stories: 'dev/stories.md'
|
||||
- 'Use cases': 'dev/use_cases.md'
|
||||
- Apps: 'dev/apps.md'
|
||||
- Objects: 'dev/objects.md'
|
||||
- 'Repapp configuration': 'dev/configuration.md'
|
||||
- 'Repair-Café & Makespace':
|
||||
- Repair-Café: https://www.repaircafe-hilpoltstein.de
|
||||
- Makespace: https://makes-hacks-hip.de
|
||||
- Makerspace@Github: https://makes-hacks-hip.github.io
|
||||
theme: readthedocs
|
||||
repo_url: https://github.com/makes-hacks-hip/RepApp/
|
||||
edit_uri: edit/main/docs/
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
server {
|
||||
listen 8020;
|
||||
server_name example.org;
|
||||
|
||||
client_max_body_size 20M;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8010;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
location /static {
|
||||
root /opt/app/rc_hip;
|
||||
}
|
||||
location /media {
|
||||
root /opt/app/rc_hip;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
"""Django's command-line utility for administrative tasks."""
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
"""Run administrative tasks."""
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'rc_hip.settings')
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
"available on your PYTHONPATH environment variable? Did you "
|
||||
"forget to activate a virtual environment?"
|
||||
) from exc
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
"""
|
||||
ASGI config for rc_hip project.
|
||||
|
||||
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'rc_hip.settings')
|
||||
|
||||
application = get_asgi_application()
|
||||
|
|
@ -1,208 +0,0 @@
|
|||
"""
|
||||
Django settings for rc_hip project.
|
||||
|
||||
Generated by "django-admin startproject" using Django 4.2.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/4.2/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/4.2/ref/settings/
|
||||
"""
|
||||
import mimetypes
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
mimetypes.add_type("text/css", ".css", True)
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / "subdir".
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
STATIC_ROOT = os.path.join(BASE_DIR, "static")
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
# SECRET_KEY = "django-insecure-ura&_-(r(&&b$0s!a=07c@k6c!8=8m7$5(4#w8m@79-e%rn6s*"
|
||||
SECRET_KEY = os.getenv(
|
||||
"DJANGO_SECRET_KEY", "django-insecure-ura&_-(r(&&b$0s!a=07c@k6c!8=8m7$5(4#w8m@79-e%rn6s*")
|
||||
|
||||
# SECURITY WARNING: don"t run with debug turned on in production!
|
||||
DEBUG = os.getenv("DJANGO_DEBUG", "true").lower() in ("true", "1", "t")
|
||||
|
||||
ALLOWED_HOSTS = [
|
||||
"127.0.0.1",
|
||||
"localhost",
|
||||
"repapp.rc-hip.de",
|
||||
"anmeldung.repaircafe-hilpoltstein.de",
|
||||
]
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
"repapp.apps.RepappConfig",
|
||||
"django.contrib.admin",
|
||||
"django.contrib.auth",
|
||||
"mozilla_django_oidc",
|
||||
"django.contrib.contenttypes",
|
||||
"django.contrib.sessions",
|
||||
"django.contrib.messages",
|
||||
"django.contrib.staticfiles",
|
||||
"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",
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.middleware.csrf.CsrfViewMiddleware",
|
||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||
"django.contrib.messages.middleware.MessageMiddleware",
|
||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||
]
|
||||
|
||||
ROOT_URLCONF = "rc_hip.urls"
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||
"DIRS": [],
|
||||
"APP_DIRS": True,
|
||||
"OPTIONS": {
|
||||
"context_processors": [
|
||||
"django.template.context_processors.debug",
|
||||
"django.template.context_processors.request",
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = "rc_hip.wsgi.application"
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.sqlite3",
|
||||
"NAME": BASE_DIR / "data" / "db.sqlite3",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
||||
},
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
||||
},
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
|
||||
},
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/4.2/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = "de"
|
||||
|
||||
TIME_ZONE = "Europe/Berlin"
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/4.2/howto/static-files/
|
||||
|
||||
STATIC_URL = "static/"
|
||||
MEDIA_URL = "media/"
|
||||
|
||||
# Default primary key field type
|
||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
|
||||
|
||||
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||
|
||||
|
||||
CSRF_TRUSTED_ORIGINS = [
|
||||
"http://127.0.0.1:8020",
|
||||
"http://localhost:8020",
|
||||
"http://127.0.0.1:8000",
|
||||
"http://localhost:8000",
|
||||
"https://repapp.rc-hip.de",
|
||||
"https://anmeldung.repaircafe-hilpoltstein.de",
|
||||
]
|
||||
|
||||
EMAIL_HOST = os.getenv("DJANGO_EMAIL_HOST", "")
|
||||
EMAIL_PORT = (int)(os.getenv("DJANGO_EMAIL_PORT", "25"))
|
||||
EMAIL_HOST_USER = os.getenv("DJANGO_EMAIL_HOST_USER", None)
|
||||
EMAIL_HOST_PASSWORD = os.getenv("DJANGO_EMAIL_HOST_PASSWORD", None)
|
||||
EMAIL_USE_TLS = os.getenv("DJANGO_EMAIL_USE_TLS", "true") in ("true", "1", "t")
|
||||
|
||||
AUTH_USER_MODEL = "repapp.CustomUser"
|
||||
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
"repapp.backends.EmailBackend",
|
||||
"django.contrib.auth.backends.ModelBackend",
|
||||
"repapp.backends.KeycloakOIDCAB",
|
||||
"repapp.backends.OneTimeLoginBackend",
|
||||
]
|
||||
|
||||
LOGIN_REDIRECT_URL = "/guest/"
|
||||
|
||||
OIDC_RP_CLIENT_ID = "repapp"
|
||||
OIDC_RP_CLIENT_SECRET = os.getenv("DJANGO_OIDC_RP_CLIENT_SECRET", None)
|
||||
|
||||
OIDC_RP_SIGN_ALGO = "RS256"
|
||||
OIDC_OP_JWKS_ENDPOINT = "https://sso.makes-hacks-hip.de/realms/Makes-Hacks-Hip/protocol/openid-connect/certs"
|
||||
|
||||
OIDC_OP_AUTHORIZATION_ENDPOINT = "https://sso.makes-hacks-hip.de/realms/Makes-Hacks-Hip/protocol/openid-connect/auth"
|
||||
OIDC_OP_TOKEN_ENDPOINT = "https://sso.makes-hacks-hip.de/realms/Makes-Hacks-Hip/protocol/openid-connect/token"
|
||||
OIDC_OP_USER_ENDPOINT = "https://sso.makes-hacks-hip.de/realms/Makes-Hacks-Hip/protocol/openid-connect/userinfo"
|
||||
|
||||
if DEBUG:
|
||||
LOGIN_REDIRECT_URL = "http://127.0.0.1:8000/member"
|
||||
LOGOUT_REDIRECT_URL = "http://127.0.0.1:8000/"
|
||||
else:
|
||||
LOGIN_REDIRECT_URL = "https://anmeldung.repaircafe-hilpoltstein.de/member"
|
||||
LOGOUT_REDIRECT_URL = "https://anmeldung.repaircafe-hilpoltstein.de/"
|
||||
|
||||
|
||||
LOG_LEVEL = "INFO"
|
||||
if DEBUG:
|
||||
LOG_LEVEL = "DEBUG"
|
||||
|
||||
LOGGING = {
|
||||
"version": 1,
|
||||
"disable_existing_loggers": False,
|
||||
"handlers": {
|
||||
"console": {
|
||||
"class": "logging.StreamHandler",
|
||||
},
|
||||
},
|
||||
"root": {
|
||||
"handlers": ["console"],
|
||||
"level": LOG_LEVEL,
|
||||
},
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
"""
|
||||
URL configuration for rc_hip project.
|
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||
https://docs.djangoproject.com/en/4.2/topics/http/urls/
|
||||
Examples:
|
||||
Function views
|
||||
1. Add an import: from my_app import views
|
||||
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||
Class-based views
|
||||
1. Add an import: from other_app.views import Home
|
||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||
Including another URLconf
|
||||
1. Import the include() function: from django.urls import include, path
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from django.urls import path, include
|
||||
from django.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path("", include("repapp.urls")),
|
||||
path('oidc/', include('mozilla_django_oidc.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)
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
"""
|
||||
WSGI config for rc_hip project.
|
||||
|
||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'rc_hip.settings')
|
||||
|
||||
application = get_wsgi_application()
|
||||
|
|
@ -1,127 +0,0 @@
|
|||
"""
|
||||
Admin interface configuration for RepApp.
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from import_export.admin import ImportExportModelAdmin, ExportActionMixin
|
||||
from import_export import resources
|
||||
from .models import (Organisator, Cafe, Question, Device, CustomUser,
|
||||
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):
|
||||
"""
|
||||
Resource wrapper for Guest model.
|
||||
"""
|
||||
class Meta:
|
||||
"""
|
||||
Meta data for resource model.
|
||||
"""
|
||||
model = Guest
|
||||
|
||||
@staticmethod
|
||||
def get_display_name():
|
||||
return "Gäste"
|
||||
|
||||
|
||||
class DeviceResource(resources.ModelResource):
|
||||
"""
|
||||
Resource wrapper for Device model.
|
||||
"""
|
||||
class Meta:
|
||||
"""
|
||||
Meta data for resource model.
|
||||
"""
|
||||
model = Device
|
||||
|
||||
@staticmethod
|
||||
def get_display_name():
|
||||
return "Geräte"
|
||||
|
||||
|
||||
class QuestionResource(resources.ModelResource):
|
||||
"""
|
||||
Resource wrapper for Question model.
|
||||
"""
|
||||
class Meta:
|
||||
"""
|
||||
Meta data for resource model.
|
||||
"""
|
||||
model = Question
|
||||
|
||||
@staticmethod
|
||||
def get_display_name():
|
||||
return "Fragen"
|
||||
|
||||
|
||||
class AppointmentResource(resources.ModelResource):
|
||||
"""
|
||||
Resource wrapper for Appointment model.
|
||||
"""
|
||||
class Meta:
|
||||
"""
|
||||
Meta data for resource model.
|
||||
"""
|
||||
model = Appointment
|
||||
|
||||
@staticmethod
|
||||
def get_display_name():
|
||||
return "Termine"
|
||||
|
||||
|
||||
class CandidateResource(resources.ModelResource):
|
||||
"""
|
||||
Resource wrapper for Candidate model.
|
||||
"""
|
||||
class Meta:
|
||||
"""
|
||||
Meta data for resource model.
|
||||
"""
|
||||
model = Candidate
|
||||
|
||||
@staticmethod
|
||||
def get_display_name():
|
||||
return "Kandidaten"
|
||||
|
||||
|
||||
class ExportAdmin(ImportExportModelAdmin, ExportActionMixin):
|
||||
"""
|
||||
Admin model for data export, supporting buttons and action.
|
||||
"""
|
||||
resource_classes = [
|
||||
DeviceResource,
|
||||
GuestResource,
|
||||
QuestionResource,
|
||||
AppointmentResource,
|
||||
CandidateResource,
|
||||
CafeResource,
|
||||
]
|
||||
|
||||
|
||||
# Register all models of RepApp for the admin interface.
|
||||
admin.site.register(Organisator)
|
||||
admin.site.register(Reparateur)
|
||||
admin.site.register(CustomUser)
|
||||
admin.site.register(OneTimeLogin)
|
||||
admin.site.register(Message)
|
||||
# Make the "data" models exportable.
|
||||
admin.site.register(Cafe, ExportAdmin)
|
||||
admin.site.register(Question, ExportAdmin)
|
||||
admin.site.register(Guest, ExportAdmin)
|
||||
admin.site.register(Device, ExportAdmin)
|
||||
admin.site.register(Appointment, ExportAdmin)
|
||||
admin.site.register(Candidate, ExportAdmin)
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
"""
|
||||
RepApp application.
|
||||
"""
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class RepappConfig(AppConfig):
|
||||
"""
|
||||
Django config for RepApp application.
|
||||
"""
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'repapp'
|
||||
|
|
@ -1,120 +0,0 @@
|
|||
"""
|
||||
Authentication backends for RepApp.
|
||||
"""
|
||||
import unicodedata
|
||||
import logging
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.backends import ModelBackend
|
||||
from mozilla_django_oidc.auth import OIDCAuthenticationBackend
|
||||
from .models import OneTimeLogin, Organisator, Reparateur
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def generate_username(email):
|
||||
"""
|
||||
Generate a valid user name form the mail address.
|
||||
"""
|
||||
# Using Python 3 and Django 1.11+, usernames can contain alphanumeric
|
||||
# (ascii and unicode), _, @, +, . and - characters. So we normalize
|
||||
# it and slice at 150 characters.
|
||||
return unicodedata.normalize('NFKC', email)[:150]
|
||||
|
||||
|
||||
def create_repapp_user(user):
|
||||
"""
|
||||
Create the RepApp user objects for the new user.
|
||||
If a user logs in using OIDC it is a member of the Repair-Café, so it can be
|
||||
either a Reparateur or an Organisator. Organisators have access to the private
|
||||
data form the guests and must be nominated by an admin.
|
||||
"""
|
||||
organisator = Organisator.objects.filter(mail=user.email).first()
|
||||
if not organisator:
|
||||
# no organisator, create or update reparateur
|
||||
reparateur = Reparateur.objects.filter(mail=user.email).first()
|
||||
if not reparateur:
|
||||
# create new reparateur
|
||||
reparateur = Reparateur(
|
||||
name=user.username,
|
||||
mail=user.email,
|
||||
)
|
||||
reparateur.save()
|
||||
else:
|
||||
# update name of existing reparateur
|
||||
reparateur.name = user.username
|
||||
reparateur.save()
|
||||
else:
|
||||
# update name of organisator
|
||||
organisator.name = user.username
|
||||
organisator.save()
|
||||
|
||||
|
||||
class OneTimeLoginBackend(ModelBackend):
|
||||
"""
|
||||
OneTimeLoginBackend implements a login using a onetime secret.
|
||||
"""
|
||||
|
||||
def authenticate(self, request, username=None, password=None, **kwargs):
|
||||
login = OneTimeLogin.objects.filter(secret=username).first()
|
||||
if login:
|
||||
return login.user
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class EmailBackend(ModelBackend):
|
||||
"""
|
||||
EmailBackend allows a login using email address as username.
|
||||
"""
|
||||
|
||||
def authenticate(self, request, username=None, password=None, **kwargs):
|
||||
user_model = get_user_model()
|
||||
try:
|
||||
user = user_model.objects.get(email=username)
|
||||
except user_model.DoesNotExist:
|
||||
return None
|
||||
else:
|
||||
if user.check_password(password):
|
||||
return user
|
||||
return None
|
||||
|
||||
|
||||
class KeycloakOIDCAB(OIDCAuthenticationBackend):
|
||||
"""
|
||||
KeycloakOIDCAB allows a login using Open ID Connect
|
||||
(with the Repair-Café Keycloak Single Sign On server)
|
||||
"""
|
||||
|
||||
def create_user(self, claims):
|
||||
user = super(KeycloakOIDCAB, self).create_user(claims)
|
||||
logger.info('Create user %s' % user.email)
|
||||
|
||||
fallback_name = generate_username(user.email)
|
||||
try:
|
||||
user.username = claims.get('preferred_username', fallback_name)
|
||||
user.save()
|
||||
except Exception as exception:
|
||||
logger.error(exception)
|
||||
logger.warning(
|
||||
'Update username failed! Using fallback name %s.' % fallback_name)
|
||||
user.username = fallback_name
|
||||
user.save()
|
||||
|
||||
logger.debug(f'Updated username {user.username}')
|
||||
|
||||
create_repapp_user(user)
|
||||
|
||||
return user
|
||||
|
||||
def update_user(self, user, claims):
|
||||
logger.debug(f'Update user {user.email} ({user.username})')
|
||||
try:
|
||||
user.username = claims.get(
|
||||
'preferred_username', generate_username(user.email))
|
||||
user.save()
|
||||
except Exception as exception:
|
||||
logger.error(exception)
|
||||
|
||||
create_repapp_user(user)
|
||||
|
||||
return user
|
||||
|
Before Width: | Height: | Size: 572 KiB |
|
Before Width: | Height: | Size: 933 KiB |
|
|
@ -1,58 +0,0 @@
|
|||
[
|
||||
{
|
||||
"model": "repapp.cafe",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"location": "Haus Einstein' (beim Gymnasium)",
|
||||
"address": "Patersholzer Weg 19, Hilpoltstein",
|
||||
"event_date": "2024-05-13"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "repapp.customuser",
|
||||
"pk": 3,
|
||||
"fields": {
|
||||
"password": "pbkdf2_sha256$600000$O70jrsL2vmSssirJPvWBMw$lbvFy/OI7mUfz26Qvh+bxnsp19v+xsETJydvsgLxE4E=",
|
||||
"last_login": "2023-04-25T18:47:07.685Z",
|
||||
"is_superuser": false,
|
||||
"username": "tom",
|
||||
"first_name": "",
|
||||
"last_name": "",
|
||||
"is_staff": false,
|
||||
"is_active": true,
|
||||
"date_joined": "2023-04-25T07:10:40.972Z",
|
||||
"email": "thomas@irgang.eu",
|
||||
"groups": [],
|
||||
"user_permissions": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "repapp.guest",
|
||||
"pk": 4,
|
||||
"fields": {
|
||||
"identifier": "272623d8b634cef82ba1c8981593cd827a373226c492b328671d31418a19bcc1",
|
||||
"name": "Thomas",
|
||||
"phone": "012345678",
|
||||
"residence": "Allersberg",
|
||||
"mail": "thomas@irgang.eu",
|
||||
"user": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "repapp.device",
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"identifier": "5f3d413409ca9c90c99540fd5677cc1147156f100d7bd29d3a1c1940e5f2c6ac",
|
||||
"date": "2023-04-25",
|
||||
"device": "Test Gerät",
|
||||
"manufacturer": "Test Hersteller",
|
||||
"error": "Fehler",
|
||||
"follow_up": false,
|
||||
"device_picture": "",
|
||||
"type_plate_picture": "",
|
||||
"confirmed": true,
|
||||
"guest": 4,
|
||||
"cafe": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -1,142 +0,0 @@
|
|||
"""
|
||||
Django forms for RepApp.
|
||||
"""
|
||||
from django import forms
|
||||
from django.core.exceptions import ValidationError
|
||||
from ckeditor.widgets import CKEditorWidget
|
||||
from .models import Question, Device
|
||||
|
||||
|
||||
EMPTY_VALUES = (None, '')
|
||||
|
||||
|
||||
class HoneypotWidget(forms.TextInput):
|
||||
"""
|
||||
This widget shows a by CSS hidden input field. If this form gets manipulated
|
||||
by a bot the form is rejected.
|
||||
"""
|
||||
is_hidden = True
|
||||
|
||||
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:
|
||||
self.attrs['style'] = 'display:none'
|
||||
|
||||
def render(self, *args, **kwargs):
|
||||
value = super(HoneypotWidget, self).render(*args, **kwargs)
|
||||
if self.html_comment:
|
||||
value = f'<!-- {value} -->'
|
||||
return value
|
||||
|
||||
|
||||
class HoneypotField(forms.Field):
|
||||
"""
|
||||
This widget implements a simple honey pot field as spam protection.
|
||||
"""
|
||||
widget = HoneypotWidget
|
||||
|
||||
def clean(self, value):
|
||||
if self.initial in EMPTY_VALUES and value in EMPTY_VALUES or value == self.initial:
|
||||
return value
|
||||
raise ValidationError('Anti-spam field changed in value.')
|
||||
|
||||
|
||||
class RegisterDevice(forms.Form):
|
||||
"""
|
||||
Form for registering a device for a repair cafe.
|
||||
"""
|
||||
mail = forms.EmailField(
|
||||
label="eMail Adresse",
|
||||
help_text="Diese eMail-Adresse wird für Aktualisierungen"
|
||||
" zur Anfrage verwendet.",
|
||||
max_length=200)
|
||||
device = forms.CharField(
|
||||
label="Art des Geräts",
|
||||
help_text="z.B. Küchenmaschine, Smartphone, Stehlampe, Drucker, ...",
|
||||
max_length=200
|
||||
)
|
||||
manufacturer = forms.CharField(
|
||||
label="Hersteller & Modell/Typ",
|
||||
max_length=200
|
||||
)
|
||||
error = forms.CharField(
|
||||
label="Fehlerbeschreibung",
|
||||
help_text="Beschreiben Sie hier das Problem ihres Gerätes.",
|
||||
widget=forms.Textarea
|
||||
)
|
||||
device_picture = forms.FileField(
|
||||
label="Foto vom Gerät", required=False
|
||||
)
|
||||
type_plate_picture = forms.FileField(
|
||||
label="Foto vom Typenschild", required=False
|
||||
)
|
||||
follow_up = forms.BooleanField(
|
||||
label="Folgetermin",
|
||||
help_text="Kreuzen Sie diese Kästchen an wenn sie mit diesem Gerät "
|
||||
"bereits bei einem Repair-Café Termin waren.",
|
||||
required=False
|
||||
)
|
||||
confirm_repair = forms.BooleanField(
|
||||
label="Informationen zur Reparaturabwicklung",
|
||||
help_text="Ich bin mit den <a href="
|
||||
"\"https://www.repaircafe-hilpoltstein.de/fileadmin/repaircafe-hilpoltstein/images/"
|
||||
"repaircafe-hilpoltstein.de/docs/Infos_Reparatur-Abwicklung_v2_2023-02.pdf\">"
|
||||
"Allgemeinen Bedingungen zur Reparaturabwicklung</a> einverstanden.",
|
||||
required=True
|
||||
)
|
||||
confirm_data = forms.BooleanField(
|
||||
label="Datenschutz",
|
||||
help_text=" Ich bin mit der Erhebung, Verarbeitung und Speicherung meiner Daten für "
|
||||
"die Bearbeitung meiner Anfrage gemäß der "
|
||||
"<a href=\"https://www.repaircafe-hilpoltstein.de/impressum#c1375\">"
|
||||
"Datenschutzerklärung</a> einverstanden.",
|
||||
required=True
|
||||
)
|
||||
accept_agb = HoneypotField(label="")
|
||||
|
||||
|
||||
class RegisterGuest(forms.Form):
|
||||
"""
|
||||
Form for registering new guests.
|
||||
"""
|
||||
name = forms.CharField(
|
||||
label="Name",
|
||||
max_length=200
|
||||
)
|
||||
phone = forms.CharField(
|
||||
label="Telefonnummer",
|
||||
max_length=200,
|
||||
help_text="Diese Telefonnummer wird für Rückfragen verwendet."
|
||||
)
|
||||
residence = forms.CharField(
|
||||
label="Wohnort",
|
||||
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")
|
||||
|
||||
|
||||
class MemberSettings(forms.Form):
|
||||
"""
|
||||
Form for editing member settings.
|
||||
"""
|
||||
notifications = forms.BooleanField(
|
||||
label="Benachrichtigungen",
|
||||
help_text="Kreuzen Sie diese Kästchen an wenn sie Benachrichtigungen erhalten möchten.",
|
||||
required=False
|
||||
)
|
||||
|
|
@ -1,203 +0,0 @@
|
|||
import time
|
||||
import os
|
||||
import datetime
|
||||
import random
|
||||
from pathlib import Path
|
||||
from hashlib import sha256
|
||||
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.chrome.webdriver import WebDriver
|
||||
from selenium.webdriver import ActionChains, Keys
|
||||
from .models import CustomUser, OneTimeLogin
|
||||
|
||||
|
||||
class WorkflowTests(StaticLiveServerTestCase):
|
||||
"""
|
||||
Tests for workflows.
|
||||
"""
|
||||
fixtures = ["cafe-data.json"]
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.selenium = WebDriver()
|
||||
cls.selenium.implicitly_wait(50)
|
||||
cls.selenium.maximize_window()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.selenium.quit()
|
||||
super().tearDownClass()
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.action_chains = ActionChains(self.selenium)
|
||||
|
||||
self.password = "ATestPassword"
|
||||
user = CustomUser.objects.get(pk=3)
|
||||
user.set_password(self.password)
|
||||
user.save()
|
||||
self.user = user
|
||||
print(f'Password of user {user.email} updated to {self.password}.')
|
||||
|
||||
url = "/guest/profile/"
|
||||
secret = sha256(
|
||||
f'{user.email}{url}{datetime.datetime.now()}{random.randint(0,9999999)}'.encode(
|
||||
'utf-8')
|
||||
).hexdigest()
|
||||
self.secret = secret
|
||||
secret_hash = sha256(secret.encode('utf-8')).hexdigest()
|
||||
one_time_login = OneTimeLogin(
|
||||
secret=secret_hash,
|
||||
user=user,
|
||||
url=url,
|
||||
)
|
||||
one_time_login.save()
|
||||
self.one_time_login = one_time_login
|
||||
|
||||
def test_register_device(self):
|
||||
"""
|
||||
Test device registration flows.
|
||||
"""
|
||||
# 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(2)
|
||||
# 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.")
|
||||
# Scroll to the bottom of the page
|
||||
self.selenium.find_element(
|
||||
By.TAG_NAME, "body").send_keys(Keys.CONTROL, Keys.END)
|
||||
time.sleep(1)
|
||||
# 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.")
|
||||
# Scroll to the bottom of the page
|
||||
self.selenium.find_element(
|
||||
By.TAG_NAME, "body").send_keys(Keys.CONTROL, Keys.END)
|
||||
time.sleep(1)
|
||||
# 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)
|
||||
|
||||
def test_login_and_view_profile(self):
|
||||
"""
|
||||
Test login and profile page.
|
||||
"""
|
||||
# Open login form
|
||||
self.selenium.get(f"{self.live_server_url}/accounts/login/")
|
||||
# Enter login data
|
||||
# Enter guest mail address
|
||||
username = self.selenium.find_element(By.NAME, "username")
|
||||
username.send_keys(self.user.email)
|
||||
# Enter password
|
||||
password = self.selenium.find_element(By.NAME, "password")
|
||||
password.send_keys(self.password)
|
||||
time.sleep(1)
|
||||
# Submit form
|
||||
self.selenium.find_element(
|
||||
By.XPATH, '//button[@type="submit"]').click()
|
||||
time.sleep(1)
|
||||
self.selenium.get(f"{self.live_server_url}/guest/profile/")
|
||||
time.sleep(1)
|
||||
self.assertTrue(self.user.email in self.selenium.page_source)
|
||||
self.assertTrue("/guest/profile/" in self.selenium.current_url)
|
||||
time.sleep(1)
|
||||
|
||||
def test_one_time_login_and_view_device(self):
|
||||
"""
|
||||
Test one time login and device page.
|
||||
"""
|
||||
# Open login form
|
||||
self.selenium.get(
|
||||
f"{self.live_server_url}/onetimelogin/{self.secret}/")
|
||||
time.sleep(1)
|
||||
self.assertTrue(self.user.email in self.selenium.page_source)
|
||||
self.assertTrue("Login erfolgreich" in self.selenium.page_source)
|
||||
self.assertTrue("/guest/profile/" in self.selenium.current_url)
|
||||
time.sleep(1)
|
||||
self.selenium.get(
|
||||
f"{self.live_server_url}/device/5f3d413409ca9c90c99540fd5677cc1147156f100d7bd29d3a1c1940e5f2c6ac/")
|
||||
time.sleep(1)
|
||||
self.assertTrue("Test Gerät" in self.selenium.page_source)
|
||||
self.assertTrue("Test Hersteller" in self.selenium.page_source)
|
||||
self.assertTrue("/device/" in self.selenium.current_url)
|
||||
time.sleep(1)
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
import os
|
||||
import re
|
||||
import logging
|
||||
from imap_tools import MailBox, AND, MailMessage
|
||||
from django.core.mail import send_mail
|
||||
from .models import Guest, Message, Question, Organisator
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def send_message_notification(message: Message, guest: Guest):
|
||||
"""
|
||||
Send new message notification.
|
||||
"""
|
||||
organizers = []
|
||||
for organizer in Organisator.objects.all():
|
||||
organizers.append(organizer.mail)
|
||||
|
||||
subject = f'Neue Nachricht von Gast {guest.name} ({guest.mail})'
|
||||
text = f'Hallo,\n'
|
||||
f'der Gast {guest.name} ({guest.mail}) hat folgende Nachricht gesendet:\n\n'
|
||||
f'{message.message}'
|
||||
# TODO: HTML message
|
||||
# TODO: add attachments
|
||||
# TODO: add link to message
|
||||
|
||||
send_mail(
|
||||
subject=subject,
|
||||
message=text,
|
||||
from_email=os.getenv("DJANGO_SENDER_ADDRESS", ""),
|
||||
recipient_list=organizers,
|
||||
fail_silently=True
|
||||
)
|
||||
|
||||
|
||||
def process_message(message: MailMessage, guest: Guest):
|
||||
"""
|
||||
Process a valid message from a guest.
|
||||
"""
|
||||
content = message.text
|
||||
if message.html:
|
||||
content = message.html
|
||||
|
||||
result = re.search('Q#([0-9]+):', message.subject)
|
||||
if result:
|
||||
question_pk = result.group(1)
|
||||
question = Question.objects.filter(pk=question_pk).first()
|
||||
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(
|
||||
'Valid answer for Question %s from %s received.'
|
||||
% (question_pk, message.from_))
|
||||
# TODO: send notifications
|
||||
else:
|
||||
logger.warning(
|
||||
'Answer for Question %s from %s received,'
|
||||
' but the question was not for this guest. Message was ignored.'
|
||||
% (question_pk, message.from_))
|
||||
# TODO: send reply that mail address was wrong
|
||||
else:
|
||||
logger.warning(
|
||||
'Answer for Question %s from %s received,'
|
||||
' but there is no such question. Message was ignored.'
|
||||
% (question_pk, message.from_))
|
||||
else:
|
||||
db_message = Message(
|
||||
message=content,
|
||||
guest=guest,
|
||||
)
|
||||
db_message.save()
|
||||
send_message_notification(message, guest)
|
||||
|
||||
|
||||
def process_mails():
|
||||
"""
|
||||
Check for new messages and handle valid messages.
|
||||
"""
|
||||
host = os.getenv("DJANGO_EMAIL_HOST", "")
|
||||
user = os.getenv("DJANGO_EMAIL_HOST_USER", None)
|
||||
password = os.getenv("DJANGO_EMAIL_HOST_PASSWORD", None)
|
||||
mailbox = MailBox(host).login(user, password)
|
||||
messages = mailbox.fetch(criteria=AND(seen=False),
|
||||
mark_seen=True, bulk=True)
|
||||
|
||||
message_count = 0
|
||||
for message in messages:
|
||||
message_count += 1
|
||||
sender = message.from_
|
||||
guest = Guest.objects.filter(mail=sender).first()
|
||||
if guest:
|
||||
process_message(message, guest)
|
||||
else:
|
||||
logger.warning(
|
||||
'Mail form unknown sender %s with subject %s ignored.'
|
||||
% (sender, message.subject))
|
||||
|
||||
return message_count
|
||||
|
|
@ -1,183 +0,0 @@
|
|||
# Generated by Django 4.2 on 2023-04-24 15:16
|
||||
|
||||
from django.conf import settings
|
||||
import django.contrib.auth.models
|
||||
import django.contrib.auth.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
import repapp.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('auth', '0012_alter_user_first_name_max_length'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='CustomUser',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
||||
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
||||
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
|
||||
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
|
||||
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
|
||||
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
|
||||
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
||||
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
||||
('email', models.EmailField(max_length=254, unique=True, verbose_name='eMail Adresse')),
|
||||
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
|
||||
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Benutzer',
|
||||
'verbose_name_plural': 'Benutzer',
|
||||
},
|
||||
managers=[
|
||||
('objects', django.contrib.auth.models.UserManager()),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Cafe',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('location', models.CharField(max_length=200, verbose_name='Ort')),
|
||||
('address', models.CharField(max_length=200, verbose_name='Adresse')),
|
||||
('event_date', models.DateField(verbose_name='Datum')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Repair-Café',
|
||||
'verbose_name_plural': 'Repair-Cafés',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Device',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('identifier', models.CharField(max_length=200, verbose_name='ID')),
|
||||
('device', models.CharField(max_length=200, verbose_name='Art des Geräts')),
|
||||
('manufacturer', models.CharField(max_length=200, verbose_name='Hersteller & Modell/Typ')),
|
||||
('error', models.TextField(verbose_name='Fehlerbeschreibung')),
|
||||
('follow_up', models.BooleanField(verbose_name='Folgetermin')),
|
||||
('device_picture', models.FileField(null=True, upload_to=repapp.models.device_directory_path, verbose_name='Foto vom Gerät')),
|
||||
('type_plate_picture', models.FileField(null=True, upload_to=repapp.models.device_directory_path, verbose_name='Foto vom Typenschild')),
|
||||
('confirmed', models.BooleanField(verbose_name='Bestätigung gesendet?')),
|
||||
('date', models.DateField(default=django.utils.timezone.now, verbose_name='Erstellungsdatum')),
|
||||
('cafe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='repapp.cafe', verbose_name='Repair-Café')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Gerät',
|
||||
'verbose_name_plural': 'Geräte',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Guest',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('identifier', models.CharField(max_length=200, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=200, verbose_name='Name')),
|
||||
('phone', models.CharField(max_length=200, verbose_name='Telefonnummer')),
|
||||
('residence', models.CharField(max_length=200, verbose_name='Wohnort')),
|
||||
('mail', models.CharField(max_length=200, verbose_name='eMail')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Gast',
|
||||
'verbose_name_plural': 'Gäste',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Organisator',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=200, verbose_name='Name')),
|
||||
('mail', models.CharField(max_length=200, verbose_name='eMail')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Organisator',
|
||||
'verbose_name_plural': 'Organisatoren',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Reparateur',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=200, verbose_name='Name')),
|
||||
('mail', models.CharField(max_length=200, verbose_name='eMail')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Reparateur',
|
||||
'verbose_name_plural': 'Reparateure',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Question',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('question', models.TextField(verbose_name='Frage')),
|
||||
('answer', models.TextField(verbose_name='Antwort')),
|
||||
('date', models.DateField(default=django.utils.timezone.now, verbose_name='Erstellungsdatum')),
|
||||
('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='repapp.device', verbose_name='Gerät')),
|
||||
('organisator', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='repapp.organisator', verbose_name='Organisator')),
|
||||
('reparateur', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='repapp.reparateur', verbose_name='Reparateur')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Frage',
|
||||
'verbose_name_plural': 'Fragen',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='OneTimeLogin',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('secret', models.CharField(max_length=200, unique=True, verbose_name='secret')),
|
||||
('url', models.CharField(max_length=200, verbose_name='URL')),
|
||||
('created', models.DateField(default=django.utils.timezone.now, verbose_name='Erstellungsdatum')),
|
||||
('login_used', models.BooleanField(default=False, verbose_name='Login benutzt?')),
|
||||
('login_date', models.DateField(null=True, verbose_name='Login Datum')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Benutzer')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Einmal-Login',
|
||||
'verbose_name_plural': 'Einmal-Login',
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='device',
|
||||
name='guest',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='repapp.guest', verbose_name='Gast'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Candidate',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('confirmed', models.BooleanField(verbose_name='bestätigt')),
|
||||
('cafe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='repapp.cafe', verbose_name='Repair-Café')),
|
||||
('device', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='repapp.device', verbose_name='Gerät')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Kandidat',
|
||||
'verbose_name_plural': 'Kandidaten',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Appointment',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('time', models.TimeField(verbose_name='Zeit')),
|
||||
('confirmed', models.BooleanField(verbose_name='bestätigt')),
|
||||
('cafe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='repapp.cafe', verbose_name='Repair-Café')),
|
||||
('device', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='repapp.device', verbose_name='Gerät')),
|
||||
('reparateur', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='repapp.reparateur', verbose_name='Reparateur')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Termin',
|
||||
'verbose_name_plural': 'Termine',
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
# Generated by Django 4.2 on 2023-04-24 17:25
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('repapp', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Message',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('message', models.TextField(verbose_name='Nachricht')),
|
||||
('answer', models.TextField(verbose_name='Antwort')),
|
||||
('date', models.DateField(default=django.utils.timezone.now, verbose_name='Erstellungsdatum')),
|
||||
('guest', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='repapp.guest', verbose_name='Gast')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Nachricht',
|
||||
'verbose_name_plural': 'Nachrichten',
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
# Generated by Django 4.2 on 2023-04-25 07:05
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('repapp', '0002_message'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='guest',
|
||||
name='user',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Benutzer'),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
# Generated by Django 4.2 on 2023-04-25 12:08
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('repapp', '0003_guest_user'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='onetimelogin',
|
||||
name='secret',
|
||||
field=models.CharField(max_length=200, unique=True, verbose_name='Geheimnis'),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
# 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'),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
# 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'),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
# 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'),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
# 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'),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
# 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'),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
# Generated by Django 4.2 on 2023-06-05 16:38
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('repapp', '0009_question_answered'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='customuser',
|
||||
name='notifications',
|
||||
field=models.BooleanField(default=True, verbose_name='Benachrichtigungen'),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
# Generated by Django 4.2 on 2023-06-18 07:46
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('repapp', '0010_customuser_notifications'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='reparateur',
|
||||
name='cafe',
|
||||
field=models.ManyToManyField(to='repapp.cafe'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='device',
|
||||
name='guest',
|
||||
field=models.ForeignKey(choices=[(-1, 'abgelehnt'), (0, 'neu'), (1, 'in Orga Rückfrage'), (2, 'in Orga Rückfrage beantwortet'), (3, 'in der Warteliste')], null=True, on_delete=django.db.models.deletion.CASCADE, to='repapp.guest', verbose_name='Gast'),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
# Generated by Django 4.2 on 2023-06-18 13:59
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('repapp', '0011_reparateur_cafe_alter_device_guest'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='reparateur',
|
||||
name='cafe',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='cafe',
|
||||
name='reparateur',
|
||||
field=models.ManyToManyField(to='repapp.reparateur'),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
# Generated by Django 4.2 on 2023-06-18 14:25
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('repapp', '0012_remove_reparateur_cafe_cafe_reparateur'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='device',
|
||||
name='reparateur',
|
||||
field=models.ManyToManyField(to='repapp.reparateur'),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
# Generated by Django 4.2 on 2023-06-18 15:51
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('repapp', '0013_device_reparateur'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='cafe',
|
||||
name='reparateur',
|
||||
field=models.ManyToManyField(null=True, to='repapp.reparateur'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='device',
|
||||
name='guest',
|
||||
field=models.ForeignKey(choices=[(-1, 'abgelehnt'), (0, 'neu'), (1, 'in Orga Rückfrage'), (2, 'in Orga Rückfrage beantwortet'), (3, 'in der Warteliste'), (4, 'in Repa Rückfrage'), (5, 'in Repa Rückfrage beantwortet')], null=True, on_delete=django.db.models.deletion.CASCADE, to='repapp.guest', verbose_name='Gast'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='device',
|
||||
name='reparateur',
|
||||
field=models.ManyToManyField(null=True, to='repapp.reparateur'),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,259 +0,0 @@
|
|||
"""
|
||||
This module implements the models of RepApp.
|
||||
"""
|
||||
import django.utils.timezone
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
|
||||
|
||||
def device_directory_path(instance, filename):
|
||||
"""
|
||||
device_directory_path generates a device-specific storage path for file uploads.
|
||||
"""
|
||||
return f'device_{instance.identifier}/{filename}'
|
||||
|
||||
|
||||
class CustomUser(AbstractUser):
|
||||
"""
|
||||
Custom user object with unique email.
|
||||
"""
|
||||
email = models.EmailField(unique=True, verbose_name=_("eMail Adresse"))
|
||||
notifications = models.BooleanField(
|
||||
verbose_name=_("Benachrichtigungen"), default=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Benutzer')
|
||||
verbose_name_plural = _('Benutzer')
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.email}'
|
||||
|
||||
|
||||
class Reparateur(models.Model):
|
||||
"""
|
||||
A Reparateur is a member of the Repair-Café who supports guests with fixing their devices.
|
||||
"""
|
||||
name = models.CharField(max_length=200, verbose_name=_("Name"))
|
||||
mail = models.CharField(max_length=200, verbose_name=_("eMail"))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Reparateur')
|
||||
verbose_name_plural = _('Reparateure')
|
||||
|
||||
def __str__(self):
|
||||
return f'Reparateur {self.name} (eMail: {self.mail})'
|
||||
|
||||
|
||||
class Cafe(models.Model):
|
||||
"""
|
||||
A Cafe is a "Repair-Café event.
|
||||
"""
|
||||
location = models.CharField(
|
||||
max_length=200, verbose_name=_("Ort"))
|
||||
address = models.CharField(
|
||||
max_length=200, verbose_name=_("Adresse"))
|
||||
event_date = models.DateField(verbose_name=_("Datum"))
|
||||
reparateur = models.ManyToManyField(Reparateur, null=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Repair-Café')
|
||||
verbose_name_plural = _('Repair-Cafés')
|
||||
|
||||
def __str__(self):
|
||||
return f'Repair-Café am {self.event_date} (Ort: {self.location})'
|
||||
|
||||
|
||||
class Organisator(models.Model):
|
||||
"""
|
||||
A Organisator is a member of the Repair-Café who arranges repair appointments with guests.
|
||||
"""
|
||||
name = models.CharField(max_length=200, verbose_name=_("Name"))
|
||||
mail = models.CharField(max_length=200, verbose_name=_("eMail"))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Organisator')
|
||||
verbose_name_plural = _('Organisatoren')
|
||||
|
||||
def __str__(self):
|
||||
return f'Organisator {self.name} (eMail: {self.mail})'
|
||||
|
||||
|
||||
class Guest(models.Model):
|
||||
"""
|
||||
A guest is a owner of a broken device who wants support in context of a Repair-Café.
|
||||
"""
|
||||
identifier = models.CharField(max_length=200, verbose_name=_("ID"))
|
||||
name = models.CharField(max_length=200, verbose_name=_("Name"))
|
||||
phone = models.CharField(max_length=200, verbose_name=_("Telefonnummer"))
|
||||
residence = models.CharField(max_length=200, verbose_name=_("Wohnort"))
|
||||
mail = models.CharField(max_length=200, verbose_name=_("eMail"))
|
||||
user = models.ForeignKey(
|
||||
CustomUser, on_delete=models.CASCADE, null=True, verbose_name=_("Benutzer"))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Gast')
|
||||
verbose_name_plural = _('Gäste')
|
||||
|
||||
def __str__(self):
|
||||
return f'Gast {self.name} (eMail: {self.mail})'
|
||||
|
||||
|
||||
class Device(models.Model):
|
||||
"""
|
||||
A Device is a broken device owned by a guest which shall be repaired during a Repair-Café.
|
||||
"""
|
||||
STATUS_REJECTED = -1
|
||||
STATUS_NEW = 0
|
||||
STATUS_ORGA_QUESTION = 1
|
||||
STATUS_ORGA_QUESTION_ANSWERED = 2
|
||||
STATUS_WAITING_LIST = 3
|
||||
STATUS_REPA_QUESTION = 4
|
||||
STATUS_REPA_QUESTION_ANSWERED = 5
|
||||
STATUS = (
|
||||
(STATUS_REJECTED, 'abgelehnt'),
|
||||
(STATUS_NEW, 'neu'),
|
||||
(STATUS_ORGA_QUESTION, 'in Orga Rückfrage'),
|
||||
(STATUS_ORGA_QUESTION_ANSWERED, 'in Orga Rückfrage beantwortet'),
|
||||
(STATUS_WAITING_LIST, 'in der Warteliste'),
|
||||
(STATUS_REPA_QUESTION, 'in Repa Rückfrage'),
|
||||
(STATUS_REPA_QUESTION_ANSWERED, 'in Repa Rückfrage beantwortet'),
|
||||
)
|
||||
|
||||
identifier = models.CharField(max_length=200, verbose_name=_("ID"))
|
||||
date = models.DateField(verbose_name=_(
|
||||
"Erstellungsdatum"), default=django.utils.timezone.now)
|
||||
device = models.CharField(max_length=200, verbose_name=_("Art des Geräts"))
|
||||
manufacturer = models.CharField(
|
||||
max_length=200, verbose_name=_("Hersteller & Modell/Typ"))
|
||||
error = models.TextField(verbose_name=_("Fehlerbeschreibung"))
|
||||
follow_up = models.BooleanField(verbose_name=_("Folgetermin"))
|
||||
device_picture = models.FileField(
|
||||
upload_to=device_directory_path, null=True, verbose_name=_("Foto vom Gerät"))
|
||||
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"), choices=STATUS)
|
||||
cafe = models.ForeignKey(
|
||||
Cafe, on_delete=models.CASCADE, null=False, verbose_name=_("Repair-Café"))
|
||||
reparateur = models.ManyToManyField(Reparateur, null=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Gerät')
|
||||
verbose_name_plural = _('Geräte')
|
||||
|
||||
def __str__(self):
|
||||
return f'Gerät {self.device} - {self.manufacturer}'
|
||||
|
||||
|
||||
class Appointment(models.Model):
|
||||
"""
|
||||
A Appointment is a match of a broken Device and a Reparateur during a Repair-Café
|
||||
with a defined time slot.
|
||||
"""
|
||||
time = models.TimeField(verbose_name=_("Zeit"))
|
||||
confirmed = models.BooleanField(verbose_name=_("bestätigt"))
|
||||
cafe = models.ForeignKey(
|
||||
Cafe, on_delete=models.CASCADE, verbose_name=_("Repair-Café"))
|
||||
reparateur = models.ForeignKey(
|
||||
Reparateur, on_delete=models.CASCADE, verbose_name=_("Reparateur"))
|
||||
device = models.ForeignKey(
|
||||
Device, on_delete=models.CASCADE, null=True, verbose_name=_("Gerät"))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Termin')
|
||||
verbose_name_plural = _('Termine')
|
||||
|
||||
def __str__(self):
|
||||
return f'Termin {self.cafe.event_date} {self.time} für Gerät {self.device.device}'
|
||||
|
||||
|
||||
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"), null=True)
|
||||
date = models.DateField(verbose_name=_(
|
||||
"Erstellungsdatum"), default=django.utils.timezone.now)
|
||||
organisator = models.ForeignKey(
|
||||
Organisator, on_delete=models.CASCADE, null=True, verbose_name=_("Organisator"))
|
||||
reparateur = models.ForeignKey(
|
||||
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')
|
||||
verbose_name_plural = _('Fragen')
|
||||
|
||||
def __str__(self):
|
||||
return f'Frage vom {self.date} zum Gerät {self.device.device}'
|
||||
|
||||
|
||||
class Candidate(models.Model):
|
||||
"""
|
||||
A Candidate is a match of a broken Device and a Repair-Café without a fixed timeslot
|
||||
or pre-assigned Reparateur.
|
||||
"""
|
||||
confirmed = models.BooleanField(verbose_name=_("bestätigt"))
|
||||
cafe = models.ForeignKey(
|
||||
Cafe, on_delete=models.CASCADE, verbose_name=_("Repair-Café"))
|
||||
device = models.ForeignKey(
|
||||
Device, on_delete=models.CASCADE, null=True, verbose_name=_("Gerät"))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Kandidat')
|
||||
verbose_name_plural = _('Kandidaten')
|
||||
|
||||
def __str__(self):
|
||||
return f'Kandidat {self.cafe.event_date} für Gerät {self.device.device}'
|
||||
|
||||
|
||||
class OneTimeLogin(models.Model):
|
||||
"""
|
||||
A OneTimeLogin is a secret which can be used once to login a user.
|
||||
"""
|
||||
secret = models.CharField(max_length=200, verbose_name=_(
|
||||
"Geheimnis"), unique=True, null=False)
|
||||
url = models.CharField(max_length=200, verbose_name=_("URL"))
|
||||
created = models.DateField(verbose_name=_(
|
||||
"Erstellungsdatum"), default=django.utils.timezone.now)
|
||||
login_used = models.BooleanField(
|
||||
verbose_name=_("Login benutzt?"), default=False)
|
||||
login_date = models.DateField(verbose_name=_(
|
||||
"Login Datum"), null=True)
|
||||
user = models.ForeignKey(
|
||||
CustomUser, on_delete=models.CASCADE, null=False, verbose_name=_("Benutzer"))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Einmal-Login')
|
||||
verbose_name_plural = _('Einmal-Login')
|
||||
|
||||
def __str__(self):
|
||||
return f'Einmal-Login {self.user.email}'
|
||||
|
||||
|
||||
class Message(models.Model):
|
||||
"""
|
||||
A Message is a request from a guest.
|
||||
"""
|
||||
message = models.TextField(verbose_name=_("Nachricht"))
|
||||
answer = models.TextField(verbose_name=_("Antwort"))
|
||||
date = models.DateField(verbose_name=_(
|
||||
"Erstellungsdatum"), default=django.utils.timezone.now)
|
||||
guest = models.ForeignKey(
|
||||
Guest, on_delete=models.CASCADE, null=True, verbose_name=_("Gast"))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Nachricht')
|
||||
verbose_name_plural = _('Nachrichten')
|
||||
|
||||
def __str__(self):
|
||||
return f'Nachricht vom {self.date} von {self.guest.mail}'
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
.django-ckeditor-widget {
|
||||
display: block !important
|
||||
}
|
||||
|
Before Width: | Height: | Size: 93 KiB |