1
0

Add authentication for voting

This commit is contained in:
Marcel Schwarz 2022-08-03 02:07:44 +02:00
parent 293c46631e
commit 242870aab2
10 changed files with 211 additions and 38 deletions

View File

@ -1,7 +1,7 @@
from django.contrib import admin from django.contrib import admin
# Register your models here. # Register your models here.
from .models import Tobacco, TobaccoCategory, ClubhausEvent, EventDate, EventDateVotes from .models import Tobacco, TobaccoCategory, ClubhausEvent, EventDate, EventDateVotes, VotingUser
@admin.register(Tobacco) @admin.register(Tobacco)
@ -23,9 +23,15 @@ class EventDateAdmin(admin.ModelAdmin):
ordering = ("id",) ordering = ("id",)
@admin.register(VotingUser)
class VotingUserAdmin(admin.ModelAdmin):
list_display = ("id", "name", "modify_key")
ordering = ("id",)
@admin.register(EventDateVotes) @admin.register(EventDateVotes)
class EventDateVotesAdmin(admin.ModelAdmin): class EventDateVotesAdmin(admin.ModelAdmin):
list_display = ("id", "name", "date") list_display = ("id", "voter", "date")
ordering = ("id",) ordering = ("id",)

View File

@ -0,0 +1,19 @@
# Generated by Django 4.0.5 on 2022-08-02 22:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('homepage', '0010_alter_eventdatevotes_unique_together_and_more'),
]
operations = [
migrations.AddField(
model_name='eventdatevotes',
name='modify_key',
field=models.TextField(default=''),
preserve_default=False,
),
]

View File

@ -0,0 +1,41 @@
# Generated by Django 4.0.5 on 2022-08-02 23:07
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('homepage', '0011_eventdatevotes_modify_key'),
]
operations = [
migrations.CreateModel(
name='VotingUser',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
],
),
migrations.RemoveConstraint(
model_name='eventdatevotes',
name='unique_name_date',
),
migrations.RemoveField(
model_name='eventdatevotes',
name='modify_key',
),
migrations.RemoveField(
model_name='eventdatevotes',
name='name',
),
migrations.AddConstraint(
model_name='eventdatevotes',
constraint=models.UniqueConstraint(fields=('date',), name='unique_voter_day'),
),
migrations.AddField(
model_name='eventdatevotes',
name='voter',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='homepage.votinguser'),
),
]

View File

@ -0,0 +1,29 @@
# Generated by Django 4.0.5 on 2022-08-02 23:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('homepage', '0012_votinguser_remove_eventdatevotes_unique_name_date_and_more'),
]
operations = [
migrations.AddField(
model_name='votinguser',
name='modify_key',
field=models.CharField(default='', max_length=200),
preserve_default=False,
),
migrations.AddField(
model_name='votinguser',
name='name',
field=models.CharField(default='', max_length=200),
preserve_default=False,
),
migrations.AddConstraint(
model_name='votinguser',
constraint=models.UniqueConstraint(fields=('name', 'modify_key'), name='unique_name_modify_key'),
),
]

View File

@ -0,0 +1,20 @@
# Generated by Django 4.0.5 on 2022-08-02 23:09
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('homepage', '0013_votinguser_modify_key_votinguser_name_and_more'),
]
operations = [
migrations.AlterField(
model_name='eventdatevotes',
name='voter',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='homepage.votinguser'),
preserve_default=False,
),
]

View File

@ -0,0 +1,21 @@
# Generated by Django 4.0.5 on 2022-08-02 23:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('homepage', '0014_alter_eventdatevotes_voter'),
]
operations = [
migrations.RemoveConstraint(
model_name='eventdatevotes',
name='unique_voter_day',
),
migrations.AddConstraint(
model_name='eventdatevotes',
constraint=models.UniqueConstraint(fields=('voter', 'date'), name='unique_voter_day'),
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 4.0.5 on 2022-08-02 23:40
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('homepage', '0015_remove_eventdatevotes_unique_voter_day_and_more'),
]
operations = [
migrations.AddField(
model_name='eventdatevotes',
name='available',
field=models.BooleanField(default=True),
preserve_default=False,
),
]

View File

@ -43,13 +43,27 @@ class ClubhausEvent(models.Model):
class EventDateVotes(models.Model): class EventDateVotes(models.Model):
name = models.CharField(max_length=200, blank=False) voter = models.ForeignKey(to="VotingUser", on_delete=models.CASCADE)
date = models.ForeignKey(to="EventDate", to_field="date", on_delete=models.CASCADE) date = models.ForeignKey(to="EventDate", to_field="date", on_delete=models.CASCADE)
available = models.BooleanField(blank=False)
class Meta: class Meta:
constraints = [ constraints = [
models.UniqueConstraint(fields=("name", "date"), name="unique_name_date") models.UniqueConstraint(fields=("voter", "date"), name="unique_voter_day")
] ]
def __str__(self): def __str__(self):
return f"{self.name} - {self.date.date.isoformat()}" return f"{self.voter.name}({self.voter.id}) - {self.date.date.isoformat()}"
class VotingUser(models.Model):
name = models.CharField(max_length=200, blank=False)
modify_key = models.CharField(max_length=200, blank=False)
class Meta:
constraints = [
models.UniqueConstraint(fields=("name", "modify_key"), name="unique_name_modify_key")
]
def __str__(self):
return f"{self.name}({self.id})"

View File

@ -99,11 +99,12 @@
</tr> </tr>
</thead> </thead>
<tbody class="table-group-divider"> <tbody class="table-group-divider">
{% for voter in votes.items %} {% regroup votes by voter as voter_grouped %}
{% for indv_voter, indv_votes in voter_grouped %}
<tr> <tr>
<td>{{ voter.0 }}</td> <td>{{ indv_voter.name }}</td>
{% for date in voter.1.items %} {% for indv_vote in indv_votes %}
{% if date.1 %} {% if indv_vote.available %}
<td><i class="fs-4 text-success bi-check-circle"></i></td> <td><i class="fs-4 text-success bi-check-circle"></i></td>
{% else %} {% else %}
<td><i class="fs-4 text-danger bi-x-circle"></i></td> <td><i class="fs-4 text-danger bi-x-circle"></i></td>
@ -175,22 +176,29 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
{% csrf_token %} {% csrf_token %}
<div class="mb-3">
<label for="nameInput" class="form-label">Name</label>
<input type="text" class="form-control" id="nameInput" name="name"
{% if request.session.name %} value="{{ request.session.name }}" {% endif %}>
</div>
{% language 'de' %} {% language 'de' %}
{% for dateOption in dates %} {% for dateOption in dates %}
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" <input class="form-check-input" type="checkbox"
id="{{ dateOption.date.isoformat }}"
name="{{ dateOption.date.isoformat }}"> name="{{ dateOption.date.isoformat }}">
<label class="form-check-label" for="flexCheckDefault"> <label class="form-check-label" for="{{ dateOption.date.isoformat }}">
{{ dateOption.date|date:'l, d. F H:i' }} {{ dateOption.date|date:'l, d. F H:i' }}
</label> </label>
</div> </div>
{% endfor %} {% endfor %}
{% endlanguage %} {% endlanguage %}
<div class="mb-3">
<label for="nameInput" class="form-label">Name</label>
<input type="text" class="form-control" id="nameInput" name="name" required
{% if request.session.name %} value="{{ request.session.name }}" {% endif %}>
</div>
<div class="mb-3">
<label for="modifyKeyInput" class="form-label">Passwort</label>
<input type="text" class="form-control" id="modifyKeyInput" name="modifyKey"
{% if request.session.modifyKey %} value="{{ request.session.modifyKey }}" {% endif %}>
<div id="modifyKeyHelp" class="form-text">Damit nur du deine Einträge ändern kannst, solltest du ein Passwort vergeben. Tust du dies nicht, kann jeder deine Einträge ändern und löschen!</div>
</div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="submit" class="btn btn-primary">Abstimmen</button> <button type="submit" class="btn btn-primary">Abstimmen</button>

View File

@ -1,11 +1,11 @@
import django.utils.timezone import django.utils.timezone
from django.core.cache import cache from django.core.cache import cache
from django.http import HttpRequest, HttpResponseRedirect from django.http import HttpRequest, HttpResponseRedirect, HttpResponseForbidden
from django.shortcuts import render from django.shortcuts import render
from django.urls import reverse from django.urls import reverse
from .models import Tobacco, ClubhausEvent, EventDate, EventDateVotes
import clubhaus.settings as django_settings import clubhaus.settings as django_settings
from .models import Tobacco, ClubhausEvent, EventDate, EventDateVotes, VotingUser
def index(request: HttpRequest) -> django.http.HttpResponse: def index(request: HttpRequest) -> django.http.HttpResponse:
@ -22,20 +22,9 @@ def events(request: HttpRequest) -> django.http.HttpResponse:
if len(next_events) == 1: if len(next_events) == 1:
next_event = next_events[0] next_event = next_events[0]
dates = EventDate.objects.filter(event=next_event).order_by("date") dates = EventDate.objects.filter(event=next_event).order_by("date")
votes = EventDateVotes.objects.filter(date__event=next_event) votes = EventDateVotes.objects.filter(date__event=next_event).order_by("voter_id", "date")
voter_names = sorted(set(map(lambda v: v.name, votes))) return render(request, 'homepage/events.html', {'next_event': next_event, "dates": dates, 'votes': votes})
vote_map = {}
for voter_name in voter_names:
vote_map[voter_name] = {date.date.isoformat(): False for date in dates}
for vote in votes:
v_name = vote.name
v_date = vote.date.date.isoformat()
if v_name in vote_map and v_date in vote_map[v_name]:
vote_map[v_name][v_date] = True
return render(request, 'homepage/events.html', {'next_event': next_event, "dates": dates, 'votes': vote_map})
else: else:
return HttpResponseRedirect(reverse("index")) return HttpResponseRedirect(reverse("index"))
@ -54,13 +43,20 @@ def voting(request: HttpRequest) -> django.http.HttpResponse:
rate_cache.add(cache_key, 0, django_settings.IP_RATE_LIMIT_TIME) rate_cache.add(cache_key, 0, django_settings.IP_RATE_LIMIT_TIME)
rate_cache.incr(cache_key) rate_cache.incr(cache_key)
if request.method == "POST" and rate_cache.get(cache_key) < 3: if request.method != "POST" and rate_cache.get(cache_key) > django_settings.IP_RATE_LIMIT_COUNT:
return HttpResponseRedirect(reverse("events"))
modify_key = request.POST["modifyKey"] or ""
if name := request.POST["name"]: if name := request.POST["name"]:
request.session["name"] = name request.session["name"] = name
for date in EventDate.objects.filter(event__active=True).order_by("date"): request.session["modifyKey"] = modify_key
if date.date.isoformat() in request.POST: user, _ = VotingUser.objects.get_or_create(name=name, modify_key=modify_key)
EventDateVotes.objects.update_or_create(name=name, date=date) event_dates = EventDate.objects.filter(event__active=True).order_by("date")
else:
EventDateVotes.objects.filter(name=name, date=date).delete() 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})
return HttpResponseRedirect(reverse("events")) return HttpResponseRedirect(reverse("events"))