diff --git a/api/__init__.py b/api/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/api/apps.py b/api/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..6d524a7ff40db97c096b761f978a4bbaee89784d --- /dev/null +++ b/api/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class HeliosConfig(AppConfig): + name = "api" + verbose_name = "API" diff --git a/api/templates/api/index.html b/api/templates/api/index.html new file mode 100644 index 0000000000000000000000000000000000000000..08263747170fce6900ba5b8ec31fc2ab0e6617f6 --- /dev/null +++ b/api/templates/api/index.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<html> +<body> + +<h1>Helios API</h1> +<h2>Examples</h2> +<hr> +<pre> +GET /api/user/<username>/elections/ +GET /api/user/<username>/elections/?limit=10 + +GET /api/elections/ +GET /api/elections/?limit=10 + +GET /api/elections/<uuid>/voters/ +</pre> +<hr> +<p>* default limits are 100 objects</p> + +</body> +</html> diff --git a/api/urls.py b/api/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..531a45acf3541965a5e9ff45bdc0e1025c4285f2 --- /dev/null +++ b/api/urls.py @@ -0,0 +1,11 @@ +from django.conf.urls import url + +from . import views + + +urlpatterns = [ + url(r"^$", views.IndexView.as_view()), + url(r"^user/(?P<username>[^/]+)/elections/$", views.UserElectionsView.as_view()), + url(r"^elections/$", views.ElectionsView.as_view()), + url(r"^elections/(?P<uuid>[^/]+)/voters/$", views.ElectionVotersView.as_view()), +] diff --git a/api/views.py b/api/views.py new file mode 100644 index 0000000000000000000000000000000000000000..e6fc9f7727f55a71a9efb0a7b091a712d688bff1 --- /dev/null +++ b/api/views.py @@ -0,0 +1,103 @@ +import pytz +from django.http import JsonResponse +from django.views.generic import TemplateView, View + +from helios.models import Election +from helios_auth.models import User + + +DEFAULT_LIMIT = 100 + + +class JsonView(View): + def get_payload(self, request, *args, **kwargs): + raise NotImplementedError + + def get(self, request, *args, **kwargs): + return JsonResponse(self.get_payload(request, *args, **kwargs)) + + +class IndexView(TemplateView): + template_name = "api/index.html" + + +def election_as_dict(election): + voting_start_at = ( + election.voting_start_at.replace(tzinfo=pytz.UTC) + if election.voting_start_at + else None + ) + voting_end_at = ( + election.voting_end_at.replace(tzinfo=pytz.UTC) + if election.voting_end_at + else None + ) + return { + "name": election.name, + "uuid": election.uuid, + "short_name": election.short_name, + "url": election.url, + "created_at": election.created_at, + "voting_has_started": election.voting_has_started(), + "voting_has_stopped": election.voting_has_stopped(), + "voting_start_at": voting_start_at, + "voting_end_at": voting_end_at, + } + + +class ElectionsView(JsonView): + def get_payload(self, request, *args, **kwargs): + limit = int(request.GET.get("limit", DEFAULT_LIMIT)) + qs = Election.objects.exclude(frozen_at=None).order_by("-created_at")[:limit] + + elections = [] + for election in qs: + elections.append(election_as_dict(election)) + + return {"elections": elections} + + +class UserElectionsView(JsonView): + def get_payload(self, request, username, *args, **kwargs): + try: + user = User.objects.get(user_id__iexact=username) + except User.DoesNotExist: + return {} + + limit = int(request.GET.get("limit", DEFAULT_LIMIT)) + qs = ( + user.voter_set.all() + .order_by("-election__created_at") + .select_related("election")[:limit] + ) + + elections = [] + for voter in qs: + election = election_as_dict(voter.election) + election["user_has_voted"] = voter.vote_hash is not None + elections.append(election) + + return {"username": username, "elections": elections} + + +class ElectionVotersView(JsonView): + def get_payload(self, request, uuid, *args, **kwargs): + try: + election = Election.objects.get(uuid=uuid) + except Election.DoesNotExist: + return {} + + result = election_as_dict(election) + result["voters"] = [] + + voters = ( + election.voter_set.all() + .values_list("user__user_id", "vote_hash") + .order_by("user__user_id") + ) + for user_id, vote_hash in voters: + result["voters"].append( + {"username": user_id, "has_voted": vote_hash is not None} + ) + + return result diff --git a/manage.py b/manage.py old mode 100644 new mode 100755 diff --git a/settings.py b/settings.py index a158572929f1ffd11b7e3b7080c92d33aa8e2894..8b25fc25a73d6601022edd633721475869dc8366 100644 --- a/settings.py +++ b/settings.py @@ -166,6 +166,7 @@ INSTALLED_APPS = ( 'helios_auth', 'helios', 'server_ui', + 'api', ) ANYMAIL = { diff --git a/urls.py b/urls.py index 8c29e83ea1a7b309247f81caf3dc6a423d6e6cd7..05ea89adb3e7acc1d5ba05b36d306ff9af5c89bb 100644 --- a/urls.py +++ b/urls.py @@ -15,5 +15,7 @@ urlpatterns = [ url(r'static/helios/(?P<path>.*)$', serve, {'document_root' : settings.ROOT_PATH + '/helios/media'}), url(r'static/(?P<path>.*)$', serve, {'document_root' : settings.ROOT_PATH + '/server_ui/media'}), + url(r'^api/', include('api.urls')), + url(r'^', include('server_ui.urls')), ]