From 986b52979485a78c35fbd4a8b3bbcc1da21cb64f Mon Sep 17 00:00:00 2001 From: Marcel Schwarz Date: Wed, 3 Aug 2022 21:00:33 +0200 Subject: [PATCH] Fix issue when adding a date when some votes already exist, Add 400 page --- clubhaus/clubhaus/settings.py | 2 +- clubhaus/clubhaus/urls.py | 2 ++ clubhaus/homepage/exceptions.py | 17 +++++++++ .../0017_remove_eventdatevotes_available.py | 17 +++++++++ clubhaus/homepage/models.py | 1 - clubhaus/homepage/templates/homepage/400.html | 16 +++++++++ .../homepage/templates/homepage/events.html | 9 +++-- clubhaus/homepage/urls.py | 2 +- clubhaus/homepage/views.py | 35 ++++++++++++++----- 9 files changed, 85 insertions(+), 16 deletions(-) create mode 100644 clubhaus/homepage/exceptions.py create mode 100644 clubhaus/homepage/migrations/0017_remove_eventdatevotes_available.py create mode 100644 clubhaus/homepage/templates/homepage/400.html diff --git a/clubhaus/clubhaus/settings.py b/clubhaus/clubhaus/settings.py index c226bfa..f4e6f74 100644 --- a/clubhaus/clubhaus/settings.py +++ b/clubhaus/clubhaus/settings.py @@ -24,7 +24,7 @@ BASE_DIR = Path(__file__).resolve().parent.parent SECRET_KEY = os.getenv("SECRET_KEY", "django-insecure-!@gqe(nmv4ylos+2p14nk+&8h$j7g%=n4sdrwqzvr6!8ee$y9@") # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = os.getenv("DEBUG_MODE", True) +DEBUG = os.getenv("DEBUG_MODE", "True") == "True" ALLOWED_HOSTS = os.getenv("ALLOWED_HOSTS", "* 192.168.235.51 127.0.0.1 localhost 192.168.0.52 192.168.235.35").split(" ") diff --git a/clubhaus/clubhaus/urls.py b/clubhaus/clubhaus/urls.py index d4528fa..dd19822 100644 --- a/clubhaus/clubhaus/urls.py +++ b/clubhaus/clubhaus/urls.py @@ -22,3 +22,5 @@ urlpatterns = [ path('', include('homepage.urls')), path('admin/', admin.site.urls), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + +handler400 = "homepage.views.bad_request" diff --git a/clubhaus/homepage/exceptions.py b/clubhaus/homepage/exceptions.py new file mode 100644 index 0000000..a8bc423 --- /dev/null +++ b/clubhaus/homepage/exceptions.py @@ -0,0 +1,17 @@ +from django.core import exceptions + + +class RateLimitHit(exceptions.SuspiciousOperation): + def __init__(self): + self.text = "You made to many request in a short period of time! Please try again later" + + def __str__(self): + return f"{self.__class__.__name__}: {self.text}" + + +class ProxyUsageDetected(exceptions.SuspiciousOperation): + def __init__(self): + self.text = "It appears you are using a proxy! We don't want you to do that!" + + def __str__(self): + return f"{self.__class__.__name__}: {self.text}" diff --git a/clubhaus/homepage/migrations/0017_remove_eventdatevotes_available.py b/clubhaus/homepage/migrations/0017_remove_eventdatevotes_available.py new file mode 100644 index 0000000..ceded76 --- /dev/null +++ b/clubhaus/homepage/migrations/0017_remove_eventdatevotes_available.py @@ -0,0 +1,17 @@ +# Generated by Django 4.0.5 on 2022-08-03 17:45 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('homepage', '0016_eventdatevotes_available'), + ] + + operations = [ + migrations.RemoveField( + model_name='eventdatevotes', + name='available', + ), + ] diff --git a/clubhaus/homepage/models.py b/clubhaus/homepage/models.py index b244311..9c2903c 100644 --- a/clubhaus/homepage/models.py +++ b/clubhaus/homepage/models.py @@ -45,7 +45,6 @@ class ClubhausEvent(models.Model): class EventDateVotes(models.Model): voter = models.ForeignKey(to="VotingUser", on_delete=models.CASCADE) date = models.ForeignKey(to="EventDate", to_field="date", on_delete=models.CASCADE) - available = models.BooleanField(blank=False) class Meta: constraints = [ diff --git a/clubhaus/homepage/templates/homepage/400.html b/clubhaus/homepage/templates/homepage/400.html new file mode 100644 index 0000000..afc5eb7 --- /dev/null +++ b/clubhaus/homepage/templates/homepage/400.html @@ -0,0 +1,16 @@ +{% extends 'homepage/base.html' %} + +{% block main %} +
+
+
+

Bad Request (400)

+
+
+ {% if exception %} +
{{ exception }}
+ {% endif %} +
+
+
+{% endblock %} \ No newline at end of file diff --git a/clubhaus/homepage/templates/homepage/events.html b/clubhaus/homepage/templates/homepage/events.html index f46ee89..b3da3bf 100644 --- a/clubhaus/homepage/templates/homepage/events.html +++ b/clubhaus/homepage/templates/homepage/events.html @@ -99,12 +99,11 @@ - {% regroup votes by voter as voter_grouped %} - {% for indv_voter, indv_votes in voter_grouped %} + {% for voter in votes.items %} - {{ indv_voter.name }} - {% for indv_vote in indv_votes %} - {% if indv_vote.available %} + {{ voter.0.name }} + {% for date in voter.1.items %} + {% if date.1 %} {% else %} diff --git a/clubhaus/homepage/urls.py b/clubhaus/homepage/urls.py index 00f548d..4f53a94 100644 --- a/clubhaus/homepage/urls.py +++ b/clubhaus/homepage/urls.py @@ -7,4 +7,4 @@ urlpatterns = [ path('event', views.events, name='events'), path('tobacco', views.tobacco, name='tobacco'), path('voting', views.voting, name='voting'), -] \ No newline at end of file +] diff --git a/clubhaus/homepage/views.py b/clubhaus/homepage/views.py index 8f96df5..3601039 100644 --- a/clubhaus/homepage/views.py +++ b/clubhaus/homepage/views.py @@ -1,13 +1,18 @@ import django.utils.timezone from django.core.cache import cache -from django.http import HttpRequest, HttpResponseRedirect, HttpResponseForbidden +from django.http import HttpRequest, HttpResponseRedirect from django.shortcuts import render from django.urls import reverse import clubhaus.settings as django_settings +from .exceptions import RateLimitHit, ProxyUsageDetected from .models import Tobacco, ClubhausEvent, EventDate, EventDateVotes, VotingUser +def bad_request(request, exception, template_name="homepage/400.html"): + return render(request, template_name, context={"exception": exception}, status=400) + + def index(request: HttpRequest) -> django.http.HttpResponse: return render(request, 'homepage/index.html', {}) @@ -24,7 +29,18 @@ def events(request: HttpRequest) -> django.http.HttpResponse: dates = EventDate.objects.filter(event=next_event).order_by("date") votes = EventDateVotes.objects.filter(date__event=next_event).order_by("voter_id", "date") - return render(request, 'homepage/events.html', {'next_event': next_event, "dates": dates, 'votes': votes}) + unique_voters = list(set(map(lambda v: v.voter, votes))) + vote_map = {} + for voter in unique_voters: + vote_map[voter] = {date.date.isoformat(): False for date in dates} + + for vote in votes: + voter = vote.voter + v_date = vote.date.date.isoformat() + if voter in vote_map and v_date in vote_map[voter]: + vote_map[voter][v_date] = True + + return render(request, 'homepage/events.html', {'next_event': next_event, "dates": dates, 'votes': vote_map}) else: return HttpResponseRedirect(reverse("index")) @@ -34,7 +50,7 @@ def voting(request: HttpRequest) -> django.http.HttpResponse: # Proxy use is forbidden if request.META.get("X-Forwarded-For"): - return HttpResponseForbidden() + raise ProxyUsageDetected() ip = request.META.get("REMOTE_ADDR") cache_key = f"voting_block_{ip}" @@ -43,9 +59,12 @@ def voting(request: HttpRequest) -> django.http.HttpResponse: rate_cache.add(cache_key, 0, django_settings.IP_RATE_LIMIT_TIME) rate_cache.incr(cache_key) - if request.method != "POST" and rate_cache.get(cache_key) > django_settings.IP_RATE_LIMIT_COUNT: + if request.method != "POST": return HttpResponseRedirect(reverse("events")) + if rate_cache.get(cache_key) > django_settings.IP_RATE_LIMIT_COUNT: + raise RateLimitHit() + modify_key = request.POST["modifyKey"] or "" if name := request.POST["name"]: request.session["name"] = name @@ -54,9 +73,9 @@ def voting(request: HttpRequest) -> django.http.HttpResponse: event_dates = EventDate.objects.filter(event__active=True).order_by("date") for date in event_dates: - date_in_request = date.date.isoformat() in request.POST - EventDateVotes.objects.update_or_create( - voter=user, date=date, - defaults={"voter": user, "date": date, "available": date_in_request}) + if date.date.isoformat() in request.POST: + EventDateVotes.objects.update_or_create(voter=user, date=date) + else: + EventDateVotes.objects.filter(voter=user, date=date).delete() return HttpResponseRedirect(reverse("events"))