Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • stustanet/wahlfang
  • 011892/wahlfang
  • 014449/wahlfang
  • 015384/wahlfang
4 results
Show changes
Commits on Source (30)
Showing
with 464 additions and 186 deletions
[flake8]
exclude =
migrations,
.venv,
venv,
.git,
__pycache__
max-line-length = 120
......@@ -2,4 +2,7 @@
*.pyc
.idea
*.sqlite3
/media
\ No newline at end of file
/media
/build
/dist
wahlfang.egg-info
\ No newline at end of file
......@@ -2,9 +2,13 @@ image: python:3.8-buster
stages:
- test
- package
- upload
- release
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
PACKAGE_REGISTRY_URL: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic"
all_tests:
stage: test
......@@ -52,3 +56,53 @@ pylint:
- pip install -U -r requirements_dev.txt
script:
- make pylint
package:
stage: package
before_script:
- pip3 install virtualenv
- virtualenv -q .venv
- source .venv/bin/activate
- pip install -U build
script:
- make package
artifacts:
paths:
- dist/
expire_in: 2 months
upload_job:
stage: upload
image: curlimages/curl:latest
needs:
- job: package
artifacts: true
rules:
- if: $CI_COMMIT_TAG # only run when we publish a new tag
script:
- 'ls dist'
- 'curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file dist/*.whl "${PACKAGE_REGISTRY_URL}/${CI_COMMIT_TAG}/wahlfang.whl"'
- 'curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file dist/*.tar.gz "${PACKAGE_REGISTRY_URL}/${CI_COMMIT_TAG}/wahlfang.tar.gz"'
release_job:
stage: release
image: registry.gitlab.com/gitlab-org/release-cli:latest
needs:
- job: upload_job
rules:
- if: $CI_COMMIT_TAG # only run when we publish a new tag
script:
- echo 'running release_job'
release:
name: 'Release $CI_COMMIT_TAG'
description: 'Created using the release-cli'
tag_name: '$CI_COMMIT_TAG'
ref: '$CI_COMMIT_TAG'
assets:
links:
- name: "wahlfang-${CI_COMMIT_TAG}.whl"
url: "${PACKAGE_REGISTRY_URL}/${CI_COMMIT_TAG}/wahlfang.whl"
- name: "wahlfang-${CI_COMMIT_TAG}.tar.gz"
url: "${PACKAGE_REGISTRY_URL}/${CI_COMMIT_TAG}/wahlfang.tar.gz"
# TODO: automatically push to pypi.org when generating a release
\ No newline at end of file
[MASTER]
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code.
extension-pkg-whitelist=
# Add files or directories to the blacklist. They should be base names, not
# paths.
ignore=migrations
# Add files or directories matching the regex patterns to the blacklist. The
# regex matches against base names, not paths.
ignore-patterns=
# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
# number of processors available to use.
jobs=1
# List of plugins (as comma separated values of python module names) to load,
# usually to register additional checkers.
load-plugins=pylint_django
[MESSAGES CONTROL]
disable=print-statement,
missing-function-docstring,
missing-module-docstring,
missing-class-docstring,
line-too-long,
invalid-name,
unused-argument,
too-many-locals,
too-many-statements,
too-many-instance-arguments,
too-few-public-methods,
too-many-arguments,
too-many-instance-attributes,
too-many-branches,
too-many-lines,
too-many-public-methods,
bad-indentation,
bad-continuation,
import-error,
wildcard-import,
no-self-use,
duplicate-code,
wrong-import-position,
no-member,
unused-import,
fixme, # no TODOs
include requirements.txt
recursive-include vote *.py *.html *.js *.css *.png *.svg *.po *.txt *.tex
graft vote/static
graft vote/templates
graft vote/static
graft vote/templatetags
graft vote/management
recursive-include management *.py *.html *.js *.css *.png *.svg *.po *.txt *.tex
graft management/static
graft management/templates
graft management/static
graft management/templatetags
graft management/management
......@@ -15,4 +15,9 @@ bandit:
.PHONY: test
test:
python3 manage.py test
# a bit hacky due to the manage.py script now living in the main module but it werks, meh ...
PYTHONPATH="${PYTHONPATH}:$(pwd)" WAHLFANG_DEBUG=True python3 wahlfang/manage.py test
.PHONY: package
package:
python3 -m build
\ No newline at end of file
......@@ -10,25 +10,7 @@ votes and elections - Wahlfang does it all.
If you would like a new feature or have a bug to report please open an [issue](https://github.com/stustanet/wahlfang/issues).
## Getting Started
To just get the current version up and running simply
```bash
$ git clone https://gitlab.stusta.de/stustanet/wahlfang.git
$ cd wahlfang
$ pip3 install -r requirements.txt
$ python3 manage.py migrate
$ python3 manage.py runserver localhost:8000
```
For detailed instructions on how to setup your own wahlfang instance for productive use see [deploying](docs/deploying.md).
### Management Access
Creating a local election management user:
```bash
$ python3 manage.py create_admin
```
Login to the management interface running at [http://127.0.0.1:8000/management/](http://127.0.0.1:8000/management/).
To setup your own wahlfang instance for productive use see [deploying](docs/deploying.md).
### Metrics
......@@ -39,11 +21,25 @@ in the application settings.
We use the [django-prometheus](https://github.com/korfuri/django-prometheus) project to export our exports.
## Contributing
Install the development requirements in addition to the standard dependencies:
To just get the current version up and running simply
```bash
$ git clone https://gitlab.stusta.de/stustanet/wahlfang.git
$ cd wahlfang
$ pip3 install -r requirements.txt
$ pip3 install -r requirements_dev.txt
$ export WAHLFANG_DEBUG=True
$ export PYTHONPATH="$PYTHONPATH:."
$ python3 wahlfang/manage.py migrate
$ python3 wahlfang/manage.py runserver localhost:8000
```
Creating a local election management user:
```bash
$ python3 wahlfang/manage.py create_admin
```
Login to the management interface running at [http://127.0.0.1:8000/management/](http://127.0.0.1:8000/management/).
Run the linting and test suite
```bash
$ make lint
......@@ -52,11 +48,11 @@ $ make test
If some model changed, you might have to make and/or apply migrations again:
```bash
$ python3 manage.py makemigrations
$ python3 manage.py migrate
$ python3 wahlfang/manage.py makemigrations
$ python3 wahlfang/manage.py migrate
```
Don't forget to add the new migration file to git. If the CI pipeline fails this is most likely the reason for it.
## Development References
- Django 3: https://docs.djangoproject.com/en/3.0/
- Django 3: https://docs.djangoproject.com/en/3.2/
## Deploying Wahlfang for Production Use
Clone the repository. This guide will assume it has been cloned to `/srv/wahlfang/repo`.
If you choose some another location make sure to adapt all paths in the following config files.
# Deploying Wahlfang for Production Use
Additionally this guide uses `virtualenv` to manage python dependencies. If you cannot or do not want to use it
you'll have to adapt some of the following parts.
## Install
To install `wahlfang` simply install it from PyPi
```bash
mkdir /srv/wahlfang
cd /srv/wahlfang
git clone https://gitlab.stusta.de/stustanet/wahlfang.git repo
virtualenv -p /usr/bin/python3 venv
source venv/bin/activate
pip install repo/requirements.txt
```shell
$ pip install wahlfang
```
### Non-Python Requirements
## Database
We recommend setting up a postgresql database for production use, although django allows mysql and sqlite
(please do not use this one for production use, please) as well. All database backends supported by django
can be used.
* Nginx
* Daphne
* Redis
* PDFLatex (only needed when you want to print invite lists for elections)
## Settings
Wahlfang can be customized using a configuration file at `/etc/wahlfang/settings.py`.
The path to this configuration file can be changed by setting the `WAHLFANG_CONFIG` environment variable.
### Channels integration
A starting point for a minimum production ready settings file can be found [here](settings.py).
In settings.py change the django channels' backend to redis in order to have self refreshing pages and other dynamic
frontend features
After configuring your database make sure to not forget the required database migrations. Simply run
```python
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [("127.0.0.1", 6379)],
},
},
}
```shell
$ wahlfang migrate
```
### Configuration
* `EXPORT_PROMETHEUS_METRICS`: Export application statistics such as http request duration / latency. This will also
export the amount of manager accounts, the amount of sessions and the amount of elections. The metrics will be
reported in /metrics.
* `URL`: Base URL address such as `vote.stusta.de`
* `AUTHENTICATION_BACKENDS`: Comment out `management.authentication.ManagementBackendLDAP` if you do not want to use
LDAP as authentication backend for management account. Otherwise have a look at the `LDAP_*` options in the
configuration.
After configuring a suitable `STATIC_ROOT` for your deployment which will contain all static assets served by your webserver run
### Database
We recommend setting up a postgresql database for production use, although django allows mysql and sqlite
(please do not use this one for production use, please) as well.
### E-Mail
In order to send out session invitation you need to configure a SMTP sever. Have a look at the
[django doku](https://docs.djangoproject.com/en/3.2/topics/email/) for reference on how to configure your SMTP server in
the django doku.
### Periodic tasks
Create a systemd service to run periodic tasks such as sending reminder e-mails for elections where this feature has
been enabled.
#### `wahlfang-reminders.timer`
```ini
[Unit]
Description=Wahlfang Election Reminders Timer
[Timer]
OnCalendar=*:0/10
[Install]
WantedBy=timers.target
```shell
$ wahlfang collectstatic
```
#### `wahlfang-reminders.service`
```ini
[Unit]
Description=Wahlfang Election reminders
[Service]
# the specific user that our service will run as
User = wahlfang
Group = wahlfang
Environment = DJANGO_SETTINGS_MODULE=wahlfang.settings
WorkingDirectory = /srv/wahlfang/repo/
ExecStart = /srv/wahlfang/venv/bin/python manage.py process_reminders
TimeoutStopSec = 5
PrivateTmp = true
### Management commands
You can create a local election management user with:
```bash
$ wahlfang create_admin
```
### Nginx + Daphne
### Non-Python Requirements
* Nginx
* Daphne
* Redis
* PDFLatex (only needed when you want to print invite lists for elections)
Example daphne systemd service. Assumes wahlfang has been cloned to `/srv/wahlfang/repo` with a virtualenv containing
all requirements and daphne in `/srv/wahlfang/venv`.
## Nginx + Daphne
#### `daphne.service`
### `daphne.service`
```ini
[Unit]
......@@ -102,12 +53,10 @@ Description = daphne daemon
After = network.target
[Service]
User = wahlfang
Group = wahlfang
Environment = DJANGO_SETTINGS_MODULE=wahlfang.settings
User = www-data
Group = www-data
RuntimeDirectory = daphne
WorkingDirectory = /srv/wahlfang/repo/
ExecStart = /srv/wahlfang/venv/bin/daphne wahlfang.asgi:application
ExecStart = daphne wahlfang.asgi:application
ExecReload = /bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
......@@ -119,7 +68,7 @@ WantedBy=multi-user.target
Example nginx config.
#### `nginx`
### `nginx`
```
upstream daphne_server {
......@@ -142,11 +91,13 @@ server {
charset utf-8;
location /static {
alias /srv/wahlfang/static;
# as configured in settings.py under STATIC_ROOT
alias /var/www/wahlfang/static;
}
location /media {
alias /srv/wahlfang/media;
# as configured in settings.py under MEDIA_ROOT
alias /var/www/wahlfang/media;
}
location / {
......@@ -158,7 +109,37 @@ server {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
```
## Periodic tasks
Create a systemd service to run periodic tasks such as sending reminder e-mails for elections where this feature has
been enabled.
### `wahlfang-reminders.timer`
```ini
[Unit]
Description=Wahlfang Election Reminders Timer
[Timer]
OnCalendar=*:0/10
[Install]
WantedBy=timers.target
```
### `wahlfang-reminders.service`
```ini
[Unit]
Description=Wahlfang Election reminders
[Service]
# the specific user that our service will run as
User = www-data
Group = www-data
ExecStart = wahlfang process_reminders
TimeoutStopSec = 5
PrivateTmp = true
```
# Wahlfang configuration file.
# /etc/wahlfang/settings.py
from wahlfang.settings.base import *
from wahlfang.settings.wahlfang import *
#: Default list of admins who receive the emails from error logging.
ADMINS = (
('Mailman Suite Admin', 'root@localhost'),
)
# Postgresql datbase setup.
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': '<db_name>',
'USER': '<db_username>',
'PASSWORD': '<password>',
'HOST': 'localhost',
'PORT': '5432',
}
}
# 'collectstatic' command will copy all the static files here.
# Alias this location from your webserver to `/static`
STATIC_ROOT = '/var/www/wahlfang/static'
# Alias this location from your webserver to `/media`
MEDIA_ROOT = '/var/www/wahlfang/media'
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [("127.0.0.1", 6379)],
},
},
}
# Make sure that this directory is created or Django will fail on start.
LOGGING['handlers']['file']['filename'] = '/var/log/wahlfang/wahlfang.log'
#: See https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts
ALLOWED_HOSTS = [
"localhost", # Good for debugging, keep it.
# "lists.your-domain.org",
# Add here all production domains you have.
]
SECRET_KEY = 'MyVerrySecretKey' # FIXME: PLEASE CHANGE ME BEFORE DEPLOYING TO PRODUCTION
# Mail, see https://docs.djangoproject.com/en/3.2/topics/email/#email-backends for more options
EMAIL_HOST = '<your mail server>'
EMAIL_SENDER = '<your mail sender>'
EMAIL_PORT = 25
# LDAP, leave commented out to not use ldap authentication
# for more details see https://django-auth-ldap.readthedocs.io/en/latest/example.html
# AUTHENTICATION_BACKENDS = {
# 'vote.authentication.AccessCodeBackend',
# 'management.authentication.ManagementBackend',
# 'management.authentication.ManagementBackendLDAP', # this authentication backend must be enabled for ldap auth
# 'django.contrib.auth.backends.ModelBackend'
# }
# AUTH_LDAP_SERVER_URI = "ldap://ldap.stusta.de"
# AUTH_LDAP_USER_DN_TEMPLATE = "cn=%(user)s,ou=account,ou=pnyx,dc=stusta,dc=mhn,dc=de"
# AUTH_LDAP_START_TLS = True
# AUTH_LDAP_USER_ATTR_MAP = {'email': 'mail'}
# AUTH_LDAP_BIND_AS_AUTHENTICATING_USER = True
# Wahlfang specific settings
# Whether to send election invitations / reminders from the Election Managers E-Mail or from `EMAIL_SENDER`
SEND_FROM_MANAGER_EMAIL = True
# List of valid domain names for your election managers emails in case `SEND_FROM_MANAGER_EMAIL` is True
# This has to be configured such that the mail server can actually send mails from the valid manager emails.
VALID_MANAGER_EMAIL_DOMAINS = []
URL = '<domain of your web server, e.g. "vote.stustanet.de">'
{% extends 'management/base.html' %}
{% load static %}
{% load crispy_forms_filters %}
{% block back_url %}
{{ back_url }}
{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-12">
<div class="card shadow">
<div class="card-header bg-dark text-light">
<h3 class="d-inline">Help Page - Management</h3>
</div>
<div class="card-body">
<h3>Contents</h3>
<ol>
<li>Create an Account</li>
<li>Sessions and Elections</li>
<li>Advanced Features</li>
</ol>
<p class="just">This is the management documentation. In case you are a voter and just want to know how to
participate in a vote please consider <a href="{% url 'vote:help' %}">the voter help page</a>.
</p>
<h2>Create an Account</h2>
<p class="just">
If you are a member of StuStaNet you are free to create an account for Wahlfang. The following steps
will guide you to create your own account:</p>
<ol class="just">
<li>Get your membership card at our <a href="https://stustanet.de/en/officehours/">office hours</a></li>
<li>On the membership card is a number and a password which you can use to login on <a
href="https://account.stustanet.de">accounts.stusta.de</a>.
</li>
<li>Go to "Services", click the checkbox for "wahlfang (online voting tool)" and click the reset button.
</li>
<li>Remember the password and username which should be firstname.lastname</li>
<li>Then you might need to wait a few minutes until your account gets activated</li>
</ol>
Now, you can go to <a href="{% url 'management:index' %}">the management login page</a> and use your
credentials to login.
<h2>Sessions and Elections</h2>
<p>Our system is structured into voting sessions which have several elections and the same set of voters. A
common case for this setup is given during organizational meetings like Heimräte where members decide about
new offices or spendings of the organization. Voters are usually invited to sessions via email but can
also be invited via mail with a QR-code or with a token.</p>
<h3>Session</h3>
<p>Thus, the first thing is to create a new Session. The session can have a name, a starting date and a
meeting
URL. The starting date and meeting URL will be display in the invitation. Furthermore, the meeting URL will
be displayed for the voters in the election view. The session setting can also be changed later on.</p>
<h3>Election</h3>
<p>After a session has been created one can start adding elections. However, if they are not known beforehand
it possible to add
a new election at any point. An election can have a name a start time an end time and a maximal number of
yes votes. Start and end time are both optional.
An election can be started/stopped at any time before the actual given times. If no end time is given the
default election length is 5 minutes. The maximal number of yes votes is also optional. If no number is
given
the participants can vote yes for every option/candidate.</p>
<p>Once an election is created, one can click on the election to add options/applicants for the election. This
view also allows to start and stop the election. When the election is over due to the time limit or has been
stopped manually the results can be published to the voters.</p>
<h3>Voters</h3>
<p>One can add voters to the election on the session's view. There are three ways to add voters:</p>
<ol>
<li>Via E-Mail invitation: Gather all emails and paste them into the "Add Voters" page one by one</li>
<li>Via CSV: Similar to email but can be used to add a lot more participants. This method also allows to
specify the voters name for a personal invitation
</li>
<li>Via Token: Just add the number of voters where you do not have an email and click "Download Tokens" on
the hamburger menu. This will create a pdf file that you can print out or send the code via messenger.
</li>
</ol>
<p>Furthermore, it is possible to create a spectator link for all people who are not allowed to cast a vote
themselves but still want to observe the election's outcome. To create a public link to a side that just
displays the published results (voters can also only see the results once they are published) go to the
upper hamburger menu in the session's view and click "Public Spectator Link".
You can now create a link (to which you can also remove the access later on) and distribute it.</p>
<h2>Advanced Features</h2>
<p>The creation pages of sessions and elections have a drop down menu for advanced options which will be
explained in this section</p>
<h3>Sessions</h3>
<p>In the advanced options of the creation of a session (and also in the edit page) it is possible to
customize the invite text which is sent to the voters. The table shows variables which can be used like the
meeting url. Variables shall be used in python format string snytax in the text e.g. {variable_name}. Once
finished a test email can be send to your email address to check if
all variables render correctly.</p>
<h3>Elections</h3>
<p>When creating an election you can also specify if abstention votes should be disabled. Furthermore, you can
allow voters to apply themselves. In this way they dont need to be added maually which is useful for
elections where applicants can apply during the meeting. Furthermore, it is also possible to send a reminder
email when the election starts. This, however, is only really
useful when the election starts several days after the invitation e.g. because people are welcomed to apply
for the election. As for sessions it is again possible to customize this remind email with the same syntax
explained above.</p>
</div>
</div>
</div>
</div>
{% endblock %}
......@@ -20,7 +20,7 @@ session_gauge.set_function(lambda: Session.objects.all().count())
urlpatterns = [
path('', views.index, name='index'),
path('help', vote.views.help_page, name='help'),
path('help', views.help_page, name='help'),
# Session
path('meeting/<int:pk>', views.session_detail, name='session'),
......
......@@ -452,3 +452,7 @@ def spectator(request, pk):
'pk': session.pk,
}
return render(request, template_name='management/spectator_settings.html', context=context)
def help_page(request):
return render(request, template_name='management/help.html')
[mypy]
plugins =
mypy_django_plugin.main
ignore_missing_imports = True
pretty = True
[mypy.plugins.django-stubs]
django_settings_module = "wahlfang.settings"
[mypy-*.migrations.*]
ignore_errors = True
[build-system]
requires = [
'setuptools>=42',
'wheel'
]
build-backend = 'setuptools.build_meta'
[tool.pylint]
[tool.pylint.master]
ignore = ['migrations', 'settings.py']
jobs = 1
load-plugins = 'pylint_django'
django-settings-module = 'wahlfang.settings.development'
[tool.pylint.'MESSAGES CONTROL']
disable = [
'print-statement',
'missing-function-docstring',
'missing-module-docstring',
'missing-class-docstring',
'line-too-long',
'invalid-name',
'unused-argument',
'too-many-locals',
'too-many-statements',
'too-many-instance-arguments',
'too-few-public-methods',
'too-many-arguments',
'too-many-instance-attributes',
'too-many-branches',
'too-many-lines',
'too-many-public-methods',
'bad-indentation',
'bad-continuation',
'import-error',
'wildcard-import',
'no-self-use',
'duplicate-code',
'wrong-import-position',
'no-member',
'unused-import'
]
[tool.mypy]
plugins = [
'mypy_django_plugin.main'
]
ignore_missing_imports = true
pretty = true
#[tool.mypy.plugins.'django-stubs'] # FIXME: this does not work with toml apparently
#django_settings_module = 'wahlfang.settings.development'
[tool.'mypy-*'.'migrations.*']
ignore_errors = true
bandit==1.6.2
pylint==2.7.*
pylint-django~=2.3.0
pylint~=2.8
pylint-django~=2.4
django-stubs
build
[metadata]
name = wahlfang
author = StuStaNet e. V.
version = attr: wahlfang.__version__
author_email = admins@stustanet.de
description = Wahlfang - a simple, feature complete online voting platform.
long_description = file: README.md
long_description_content_type = text/markdown
license = MIT
url = https://gitlab.stusta.de/stustanet/wahlfang
classifiers =
Operating System :: OS Independent
Programming Language :: Python :: 3
License :: OSI Approved :: MIT License
Topic :: Internet :: WWW/HTTP :: WSGI :: Application
Development Status :: 4 - Beta
Framework :: Django
[options]
python_requires = >=3.6
setup_requires =
setuptools
install_requires =
Django~=3.2
django-crispy-forms~=1.11
django-csp~=3.7
django-ratelimit~=3.0
pillow~=8.2
argon2-cffi~=20.1
django-auth-ldap~=2.4
qrcode~=6.1
latex~=0.7
django_prometheus~=2.1
channels~=3.0
channels-redis~=3.2
djangorestframework~=3.12
djangorestframework-simplejwt~=4.7
packages = find:
include_package_data = True
zip_safe = False
[options.entry_points]
console_scripts =
wahlfang = wahlfang.manage:main
static/img/help_page/2020-11-20-Nov-11-1605830012-%N_794x629.png

46.9 KiB

static/img/help_page/2020-11-20-Nov-11-1605830031-%N_765x798.png

52.3 KiB

static/img/help_page/2020-11-20-Nov-11-1605830035-%N_775x811.png

52.9 KiB

static/img/help_page/2020-11-20-Nov-11-1605830060-%N_695x686.png

61 KiB