diff --git a/docs/deploying.md b/docs/deploying.md index 68dd230e42b84515e1a15293f79f547946cac744..9a012bb9f28b8907662347988b7844a375763500 100644 --- a/docs/deploying.md +++ b/docs/deploying.md @@ -14,6 +14,11 @@ source venv/bin/activate pip install repo/requirements.txt ``` +### TODO channels integration + +- redis layer +- asgi webserver, like guvicorn, daphne, ... + ### Configuration TODO @@ -29,7 +34,7 @@ Create a systemd service to run periodic tasks such as sending reminder e-mails been enabled. #### `wahlfang-reminders.timer` -``` +```ini [Unit] Description=Wahlfang Election Reminders Timer @@ -41,7 +46,7 @@ WantedBy=timers.target ``` #### `wahlfang-reminders.service` -``` +```ini [Unit] Description=Wahlfang Election reminders @@ -61,7 +66,7 @@ Example gunicorn systemd service. Assumes wahlfang has been cloned to `/srv/wahl all requirements and gunicorn in `/srv/wahlfang/venv`. #### `gunicorn.service` -``` +```ini [Unit] Description=gunicorn daemon Requires=gunicorn.socket @@ -87,7 +92,7 @@ WantedBy=multi-user.target #### `gunicorn.socket` A corresponding systemd.socket file for socket activation. -``` +```ini [Unit] Description=gunicorn socket diff --git a/management/consumers.py b/management/consumers.py new file mode 100644 index 0000000000000000000000000000000000000000..3c662b4f94f4d954f2b3bd9ee1a7d5ed2390ced8 --- /dev/null +++ b/management/consumers.py @@ -0,0 +1,39 @@ +import json + +from channels.generic.websocket import AsyncWebsocketConsumer + + +class ElectionConsumer(AsyncWebsocketConsumer): + + async def connect(self): + self.group = "Election-" + self.scope['url_route']['kwargs']['pk'] # pylint: disable=W0201 + await self.channel_layer.group_add(self.group, self.channel_name) + await self.accept() + + async def disconnect(self, code): + await self.channel_layer.group_discard(self.group, self.channel_name) + + async def send_reload(self, event): + await self.send(text_data=json.dumps({ + 'reload': True, + })) + + +class SessionConsumer(AsyncWebsocketConsumer): + + async def connect(self): + session = self.scope['url_route']['kwargs']['pk'] + # reload if a new voter logged in, or if a election of the session was changed (like added) + self.groups = ["Login-Session-" + session, "Session-" + session] # pylint: disable=W0201 + for group in self.groups: + await self.channel_layer.group_add(group, self.channel_name) + await self.accept() + + async def disconnect(self, code): + for group in self.groups: + await self.channel_layer.group_discard(group, self.channel_name) + + async def send_reload(self, event): + await self.send(text_data=json.dumps({ + 'reload': True, + })) diff --git a/management/routing.py b/management/routing.py new file mode 100644 index 0000000000000000000000000000000000000000..51d0f8c2ccf77381eeee9b68be845b747301adf9 --- /dev/null +++ b/management/routing.py @@ -0,0 +1,8 @@ +from channels.routing import URLRouter +from django.urls import re_path +from . import consumers + +websocket_urlpatterns = URLRouter([ + re_path(r'election/(?P<pk>\d+)$', consumers.ElectionConsumer.as_asgi()), + re_path(r'meeting/(?P<pk>\d+)$', consumers.SessionConsumer.as_asgi()), +]) diff --git a/management/templates/management/add_election.html b/management/templates/management/add_election.html index 41a8ac047ad5adf18aaa27edcf9e8f306703a1a8..996ff478b119d1d86deb9f603bded80e13b7814a 100644 --- a/management/templates/management/add_election.html +++ b/management/templates/management/add_election.html @@ -94,6 +94,8 @@ </div> </div> </div> +{% endblock %} +{% block footer_scripts %} <script src="{% static "js/jquery-3.5.1.min.js" %}" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0="></script> <script src="{% static "bootstrap-4.5.3-dist/js/bootstrap.min.js" %}" diff --git a/management/templates/management/add_session.html b/management/templates/management/add_session.html index a031ebcb93114e7db54e1dd67f2fdb9012d9fbe2..b87df27e5e833c731329c947628be890491dfa74 100644 --- a/management/templates/management/add_session.html +++ b/management/templates/management/add_session.html @@ -85,6 +85,9 @@ </div> </div> </div> +{% endblock %} + +{% block footer_scripts %} <script src="{% static "js/jquery-3.5.1.min.js" %}" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0="></script> <script src="{% static "bootstrap-4.5.3-dist/js/bootstrap.min.js" %}" diff --git a/management/templates/management/application.html b/management/templates/management/application.html index ea113a88a9f0e5c24a929728cbe34d610e616220..8fb4da6a956e1d6b53e3833474ed302cd90f97af 100644 --- a/management/templates/management/application.html +++ b/management/templates/management/application.html @@ -82,6 +82,5 @@ {% endblock %} {% block footer_scripts %} - {{ block.super }} - <script src="{% static 'management/js/application.js' %}"></script> + <script src="{% static "js/application.js" %}"></script> {% endblock %} diff --git a/management/templates/management/election.html b/management/templates/management/election.html index d4a6b3a447111ff931593cab073b3f66544b15af..ffcbe4b110b1d671a7b8c72b8e4709c1fe9824fa 100644 --- a/management/templates/management/election.html +++ b/management/templates/management/election.html @@ -1,5 +1,6 @@ {% extends 'management/base.html' %} {% load crispy_forms_filters %} +{% load static %} {% block content %} <div class="row justify-content-center"> @@ -157,3 +158,11 @@ </div> </div> {% endblock %} + +{% block footer_scripts %} + {# Automatic reload of the page: #} + {# - if another vote was cast#} + <script src="{% static "js/jquery-3.5.1.min.js" %}" + integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0="></script> + <script src="{% static "js/reload.js" %}"></script> +{% endblock %} diff --git a/management/templates/management/index.html b/management/templates/management/index.html index d6fff7b3e30741b89e2c67b4b3b571f33253704e..4b888882945abd2982674655aab7a473ebde1684 100644 --- a/management/templates/management/index.html +++ b/management/templates/management/index.html @@ -54,7 +54,9 @@ </div> </div> {% endfor %} +{% endblock %} +{% block footer_scripts %} <script src="{% static "js/jquery-3.5.1.slim.min.js" %}" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"></script> <script src="{% static "bootstrap-4.5.3-dist/js/bootstrap.min.js" %}" diff --git a/management/templates/management/session.html b/management/templates/management/session.html index 479ba6a8ae3ced2f6470a49680d864cbe924ecf6..5d4ecd582b75ccffbb1e102bab12c5050a00f97c 100644 --- a/management/templates/management/session.html +++ b/management/templates/management/session.html @@ -152,9 +152,17 @@ </div> </div> </div> +{% endblock %} + +{% block footer_scripts %} + {# Automatic reload of the page: #} + {# - either if the start date / end date of a election is due#} + {# - or if the admin started / stopped one election and the page is notified with a websocket#} + {# - or if a voter has logged in #} + <script src="{% static "js/jquery-3.5.1.min.js" %}" + integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0="></script> + <script src="{% static "js/reload.js" %}"></script> - <script src="{% static "js/jquery-3.5.1.slim.min.js" %}" - integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"></script> <script src="{% static "js/popper-1.16.1.min.js" %}" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN"></script> <script src="{% static "bootstrap-4.5.3-dist/js/bootstrap.min.js" %}" diff --git a/management/templates/management/session_election_item.html b/management/templates/management/session_election_item.html index 8ef5ba290337a3529625cf5d060b5a905398f672..e3053942612cef25870225fa5e598507115d8354 100644 --- a/management/templates/management/session_election_item.html +++ b/management/templates/management/session_election_item.html @@ -10,12 +10,14 @@ <small class="float-right"> {% if not election.started and election.start_date %} <span class="right-margin">Starts at {{ election.start_date|date:"Y-m-d H:i:s" }}</span> + {# Time for automatic reload #} + <div class="d-none time">{{ election.start_date|date:"U" }}|</div> {% elif not election.started %} <span class="right-margin">Needs to be started manually</span> {% elif election.is_open and election.end_date %} - <span class="right-margin"> - Open until {{ election.end_date|date:"Y-m-d H:i:s" }} - </span> + <span class="right-margin">Open until {{ election.end_date|date:"Y-m-d H:i:s" }}</span> + {# Time for automatic reload #} + <div class="d-none time">{{ election.end_date|date:"U" }}|</div> {% elif election.closed %} <span class="right-margin">Closed</span> {% else %} diff --git a/management/templates/management/session_settings.html b/management/templates/management/session_settings.html index 95affadf354431d6d9afefec1d9e665c6378583f..190d56d199df24438f3c364df52a0de0866e254a 100644 --- a/management/templates/management/session_settings.html +++ b/management/templates/management/session_settings.html @@ -97,6 +97,9 @@ </div> </div> </div> +{% endblock %} + +{% block footer_scripts %} <script src="{% static "js/jquery-3.5.1.min.js" %}" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0="></script> <script src="{% static "bootstrap-4.5.3-dist/js/bootstrap.min.js" %}" diff --git a/requirements.txt b/requirements.txt index 091714695c1bca9a19ab0b3db424f9186df96fd7..5e827c6837b55a87d58f24a429d765c4fdae0939 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,4 +7,5 @@ django-ratelimit==3.0.* django-auth-ldap==2.2.* qrcode==6.1 latex==0.7.* -django_prometheus==2.1.* \ No newline at end of file +django_prometheus==2.1.* +channels==3.0.* \ No newline at end of file diff --git a/management/static/management/js/application.js b/static/js/application.js similarity index 100% rename from management/static/management/js/application.js rename to static/js/application.js diff --git a/static/js/reload.js b/static/js/reload.js new file mode 100644 index 0000000000000000000000000000000000000000..21e9c0eaf6be7358d75e93ed911fe129b71c13eb --- /dev/null +++ b/static/js/reload.js @@ -0,0 +1,48 @@ +$(document).ready(() => { + let timeout; + + function reload_callback() { + setup_date_reload(); + } + + function reload() { + console.log("Reloading") + //wait a random time, to not overload the server if everyone reloads at the same time + window.setTimeout(() => $("#content").load(location.pathname + " #content", reload_callback), Math.random() * 1000) + } + + function setup_date_reload() { + //setup a timer to reload the page if a start or end date of a election passed + clearTimeout(timeout); + const now_ms = new Date().getTime(); + const times = $(".time").text().split('|').map(u_time => parseInt(u_time)); + const wait_ms = times.map(time => (time + 5) * 1000 - now_ms).filter(t => t > 10 * 1000); + const min_ms = Math.min(...wait_ms); + if (min_ms < 24 * 60 * 60 * 1000) { + console.log("Reloading in " + (min_ms / 1000) + "s"); + timeout = setTimeout(reload, min_ms); + } + } + + function setup_websocket() { + const ws = new WebSocket(location.href.replace("http", "ws")); + ws.onmessage = function (e) { + const message = JSON.parse(e.data) + if (message.reload) { + reload(); + } + } + ws.onopen = function (e) { + console.log("Websocket connected"); + } + ws.onerror = function (e) { + console.error("Websocket ERROR. Site will not reload automatically"); + } + ws.onclose = function (e) { + console.error("Websocket Closed. Site will not reload automatically"); + } + } + + setup_date_reload(); + setup_websocket(); +}) diff --git a/vote/consumers.py b/vote/consumers.py new file mode 100644 index 0000000000000000000000000000000000000000..628fbfa62b79d011f2326bdd133c35203730e1d2 --- /dev/null +++ b/vote/consumers.py @@ -0,0 +1,30 @@ +import json + +from channels.generic.websocket import AsyncWebsocketConsumer +from channels.db import database_sync_to_async + +from vote.models import Session + + +class VoteConsumer(AsyncWebsocketConsumer): + + async def connect(self): + self.group = await database_sync_to_async(self.get_session_key)() # pylint: disable=W0201 + await self.channel_layer.group_add(self.group, self.channel_name) + await self.accept() + + async def disconnect(self, code): + await self.channel_layer.group_discard(self.group, self.channel_name) + + async def send_reload(self, event): + await self.send(text_data=json.dumps({ + 'reload': True, + })) + + def get_session_key(self): + if 'uuid' in self.scope['url_route']['kwargs']: + uuid = self.scope['url_route']['kwargs']['uuid'] + session = Session.objects.get(spectator_token=uuid) + else: + session = self.scope['user'].session + return "Session-" + str(session.pk) diff --git a/vote/forms.py b/vote/forms.py index 2f35889c15a89558fc137e15e4e3524cb1a2eedf..7d767f1b4f152367dca236a2ab262a0ac0accfd8 100644 --- a/vote/forms.py +++ b/vote/forms.py @@ -1,3 +1,5 @@ +from asgiref.sync import async_to_sync +from channels.layers import get_channel_layer from django import forms from django.contrib.auth import authenticate from django.db import transaction @@ -122,6 +124,12 @@ class VoteForm(forms.Form): with transaction.atomic(): Vote.objects.bulk_create(votes) can_vote.delete() + # notify manager that new votes were cast + group = "Election-" + str(self.election.pk) + async_to_sync(get_channel_layer().group_send)( + group, + {'type': 'send_reload'} + ) return votes diff --git a/vote/models.py b/vote/models.py index 435c559eca40e764e11381a3e14c047c85ca2c12..a142f0b574e11f390df9449fcc1f86eacc82d645 100644 --- a/vote/models.py +++ b/vote/models.py @@ -9,6 +9,8 @@ from io import BytesIO import PIL from PIL import Image +from asgiref.sync import async_to_sync +from channels.layers import get_channel_layer from django.conf import settings from django.contrib.auth import password_validation from django.contrib.auth.hashers import ( @@ -165,6 +167,15 @@ class Election(models.Model): return 0 return int(self.votes.count() / self.applications.count()) + def save(self, force_insert=False, force_update=False, using=None, update_fields=None): + super().save(force_insert, force_update, using, update_fields) + # notify users to reload their page + group = "Session-" + str(self.session.pk) + async_to_sync(get_channel_layer().group_send)( + group, + {'type': 'send_reload'} + ) + def __str__(self): return self.title @@ -201,6 +212,12 @@ class Voter(models.Model): if self._password is not None: password_validation.password_changed(self._password, self) self._password = None + # notify manager to reload their page, if the user logged in + group = "Login-Session-" + str(self.session.pk) + async_to_sync(get_channel_layer().group_send)( + group, + {'type': 'send_reload'} + ) def set_password(self, raw_password=None): if not raw_password: @@ -506,3 +523,5 @@ class Vote(models.Model): election = models.ForeignKey(Election, related_name='votes', on_delete=models.CASCADE) candidate = models.ForeignKey(Application, related_name='votes', on_delete=models.CASCADE) vote = models.CharField(choices=VOTE_CHOICES, max_length=max(len(x[0]) for x in VOTE_CHOICES)) + # save method is not called on bulk_create in forms.VoteForm. + # The model update listener for websockets is implemented in the form. diff --git a/vote/routing.py b/vote/routing.py new file mode 100644 index 0000000000000000000000000000000000000000..2bd2bca7598406a8dc5290b4a36a0abffd7c7621 --- /dev/null +++ b/vote/routing.py @@ -0,0 +1,8 @@ +from channels.routing import URLRouter +from django.urls import path, re_path +from . import consumers + +websocket_urlpatterns = URLRouter([ + path('', consumers.VoteConsumer.as_asgi()), + re_path(r'spectator/(?P<uuid>.+)$', consumers.VoteConsumer.as_asgi()), +]) diff --git a/vote/templates/vote/application.html b/vote/templates/vote/application.html index 6a95b212c3a384e7419da8ff7ed1574300f289f8..72e42dcfd264f65304cf6f15ad42e2d1329f7b80 100644 --- a/vote/templates/vote/application.html +++ b/vote/templates/vote/application.html @@ -77,6 +77,5 @@ {% endblock %} {% block footer_scripts %} - {{ block.super }} - <script src="{% static 'management/js/application.js' %}"></script> + <script src="{% static "js/application.js" %}"></script> {% endblock %} diff --git a/vote/templates/vote/base.html b/vote/templates/vote/base.html index 692269c3bce300cead636179ed1bc9ca0d8b04cd..69875796bcd0152d8167e62924bfd30e5dd52f55 100644 --- a/vote/templates/vote/base.html +++ b/vote/templates/vote/base.html @@ -79,12 +79,6 @@ </article> </div> -{% comment "Not needed :)" %} - <script src="{% static "js/jquery-3.5.1.slim.min.js" %}" - integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"></script> - <script src="{% static "bootstrap-4.5.3-dist/js/bootstrap.min.js" %}" - integrity="sha384-w1Q4orYjBQndcko6MimVbzY0tgp4pWB4lZ7lr30WKz0vr/aWKhXdBNmNb5D92v7s"></script> -{% endcomment %} {% block footer_scripts %} {% endblock %} </body> diff --git a/vote/templates/vote/index.html b/vote/templates/vote/index.html index 7fa80781df062961f09ad6ba0a5f90546bb1c66f..df56c2748eafdac5f73e1abf03e55433ec9eac91 100644 --- a/vote/templates/vote/index.html +++ b/vote/templates/vote/index.html @@ -64,3 +64,12 @@ </div> </div> {% endblock %} + +{% block footer_scripts %} + {# Automatic reload of the page: #} + {# - either if the start date / end date of a election is due#} + {# - or if the admin started / stopped one election and the page is notified with a websocket#} + <script src="{% static "js/jquery-3.5.1.slim.min.js" %}" + integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"></script> + <script src="{% static "js/reload.js" %}"></script> +{% endblock %} diff --git a/vote/templates/vote/index_election_item.html b/vote/templates/vote/index_election_item.html index 4e1b1cc78690a3a75f3ae2cb9f6a6d9c56dc0f44..d9ceaf28df2b99941e38912f6f77ba9da57d2d06 100644 --- a/vote/templates/vote/index_election_item.html +++ b/vote/templates/vote/index_election_item.html @@ -16,6 +16,9 @@ {% if election.end_date %} <small class="text-muted">Voting Period: {{ election.start_date|date:"D Y-m-d H:i:s" }} - {{ election.end_date|date:"D Y-m-d H:i:s" }} (UTC{{ election.end_date|date:"O" }})</small> + <!-- hidden start and end time for javascript | is used to delimit values--> + <div class="d-none time">{{ election.start_date|date:"U" }}|</div> + <div class="d-none time">{{ election.end_date|date:"U" }}|</div> {% endif %} <hr> <div class="list-group mt-3"> diff --git a/vote/templates/vote/spectator.html b/vote/templates/vote/spectator.html index d480e5e08ae113ad1cb7f273421c16ad62a50c20..6b352bde4a70d63846ef909a01c61cc2a15f97b3 100644 --- a/vote/templates/vote/spectator.html +++ b/vote/templates/vote/spectator.html @@ -65,3 +65,12 @@ </div> </div> {% endblock %} + +{% block footer_scripts %} + {# Automatic reload of the page: #} + {# - either if the start date / end date of a election is due#} + {# - or if the admin started / stopped one election and the page is notified with a websocket#} + <script src="{% static "js/jquery-3.5.1.slim.min.js" %}" + integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"></script> + <script src="{% static "js/reload.js" %}"></script> +{% endblock %} diff --git a/vote/templates/vote/spectator_election_item.html b/vote/templates/vote/spectator_election_item.html index fc217d9f208c03160573413f49fdba10dff9c38a..538b2943dc1463af9939a01f07bb0f87aae23c6d 100644 --- a/vote/templates/vote/spectator_election_item.html +++ b/vote/templates/vote/spectator_election_item.html @@ -4,6 +4,9 @@ {% if election.end_date %} <small class="text-muted">Voting Period: {{ election.start_date|date:"D Y-m-d H:i:s" }} - {{ election.end_date|date:"D Y-m-d H:i:s" }} (UTC{{ election.end_date|date:"O" }})</small> + <!-- hidden start and end time for javascript | is used to delimit values--> + <div class="d-none time">{{ election.start_date|date:"U" }}|</div> + <div class="d-none time">{{ election.end_date|date:"U" }}|</div> {% endif %} <hr> <div class="list-group mt-3"> diff --git a/vote/templates/vote/vote.html b/vote/templates/vote/vote.html index f7f3467a27b9bf40cf90ca643ae01733e3c5c27b..f055095442c7e8fbf6f59c568413c2de77578498 100644 --- a/vote/templates/vote/vote.html +++ b/vote/templates/vote/vote.html @@ -117,6 +117,5 @@ {% endblock %} {% block footer_scripts %} - {{ block.super }} <script src="{% static "vote/js/vote.js" %}"></script> {% endblock %} diff --git a/vote/views.py b/vote/views.py index 9aa170f7f5d26d883ef21f34dfdb075a37a7a744..dd68fcd040015c69b0f4003fb2ad7a0a51a28f2b 100644 --- a/vote/views.py +++ b/vote/views.py @@ -1,3 +1,5 @@ +import sys + from django.conf import settings from django.contrib import messages from django.contrib.auth import authenticate, login, views as auth_views @@ -60,10 +62,19 @@ def index(request): (e, voter.can_vote(e), voter.application.filter(election=e).exists()) for e in voter.session.elections.order_by('pk') ] - open_elections = [e for e in elections if e[0].is_open] - upcoming_elections = [e for e in elections if not e[0].started] - published_elections = [e for e in elections if e[0].closed and int(e[0].result_published)] - closed_elections = [e for e in elections if e[0].closed and not int(e[0].result_published)] + + def date_asc(e): + date = e[0].start_date + return date.timestamp() if date else sys.maxsize + + def date_desc(e): + date = e[0].start_date + return -date.timestamp() if date else -sys.maxsize + + open_elections = sorted([e for e in elections if e[0].is_open], key=date_desc) + upcoming_elections = sorted([e for e in elections if not e[0].started], key=date_asc) + published_elections = sorted([e for e in elections if e[0].closed and int(e[0].result_published)], key=date_desc) + closed_elections = sorted([e for e in elections if e[0].closed and not int(e[0].result_published)], key=date_desc) context = { 'title': voter.session.title, 'meeting_link': voter.session.meeting_link, @@ -166,10 +177,19 @@ def help_page(request): def spectator(request, uuid): session = get_object_or_404(Session.objects, spectator_token=uuid) elections = session.elections.all() - open_elections = [e for e in elections if e.is_open] - upcoming_elections = [e for e in elections if not e.started] - published_elections = [e for e in elections if e.closed and int(e.result_published)] - closed_elections = [e for e in elections if e.closed and not int(e.result_published)] + + def date_asc(e): + date = e.start_date + return date.timestamp() if date else sys.maxsize + + def date_desc(e): + date = e.start_date + return -date.timestamp() if date else -sys.maxsize + + open_elections = sorted([e for e in elections if e.is_open], key=date_desc) + upcoming_elections = sorted([e for e in elections if not e.started], key=date_asc) + published_elections = sorted([e for e in elections if e.closed and int(e.result_published)], key=date_desc) + closed_elections = sorted([e for e in elections if e.closed and not int(e.result_published)], key=date_desc) context = { 'title': session.title, 'meeting_link': session.meeting_link, diff --git a/wahlfang/asgi.py b/wahlfang/asgi.py index f29b0820da5c1b47fc9ef738f7c0a100a30bd78d..cfd595d791a2751a7786e67ac2b27f76f1bc6288 100644 --- a/wahlfang/asgi.py +++ b/wahlfang/asgi.py @@ -9,8 +9,14 @@ https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/ import os +from channels.auth import AuthMiddlewareStack +from channels.routing import ProtocolTypeRouter, URLRouter from django.core.asgi import get_asgi_application +import wahlfang.routing os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'wahlfang.settings') -application = get_asgi_application() +application = ProtocolTypeRouter({ + "https": get_asgi_application(), + "websocket": AuthMiddlewareStack(wahlfang.routing.websocket_urlpatterns), +}) diff --git a/wahlfang/routing.py b/wahlfang/routing.py new file mode 100644 index 0000000000000000000000000000000000000000..f2fb30158fc467e0a984392f22c883c470de64fa --- /dev/null +++ b/wahlfang/routing.py @@ -0,0 +1,10 @@ +from channels.routing import URLRouter +from django.urls import path + +import management.routing +import vote.routing + +websocket_urlpatterns = URLRouter([ + path('', vote.routing.websocket_urlpatterns), + path('management/', management.routing.websocket_urlpatterns), +]) diff --git a/wahlfang/settings.py b/wahlfang/settings.py index c7341182c89b40b2c80761cc132fde0a6c7d860c..965d5c4c7bbc9dd49492b4746e79035f1277e27d 100644 --- a/wahlfang/settings.py +++ b/wahlfang/settings.py @@ -52,6 +52,7 @@ INSTALLED_APPS = [ 'crispy_forms', 'vote', 'management', + 'channels', ] if EXPORT_PROMETHEUS_METRICS: @@ -99,7 +100,13 @@ AUTHENTICATION_BACKENDS = { 'django.contrib.auth.backends.ModelBackend' } -WSGI_APPLICATION = 'wahlfang.wsgi.application' +ASGI_APPLICATION = 'wahlfang.asgi.application' + +CHANNEL_LAYERS = { + "default": { + "BACKEND": "channels.layers.InMemoryChannelLayer" + } +} # Database # https://docs.djangoproject.com/en/3.0/ref/settings/#databases