Merge branch 'signup-duplicate-reduction' into 'main'

Add submit_id to signup model

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!115
This commit is contained in:
Justus Ojala
2025-10-13 19:45:46 +03:00
9 changed files with 178 additions and 30 deletions
@@ -0,0 +1,65 @@
# Generated by Django 4.2.24 on 2025-10-13 14:48
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("kaehmy", "0011_delete_kaehmybaserole"),
]
operations = [
migrations.AlterField(
model_name="baserole",
name="category",
field=models.CharField(
choices=[
("board", "Board"),
("corporate", "Corporate affairs"),
("freshman", "Freshmen"),
("international", "International"),
("siwa", "SIK's free time"),
("media", "Media"),
("tech", "Technology"),
("wellbeing", "Wellbeing"),
("sikpaja", "Sik-paja"),
("ceremonies", "Ceremonies"),
("studies", "Studies"),
("sosso", "Sössö magazine"),
("pota", "PoTa"),
("alumni", "Alumni relations"),
("n", "N"),
("others", "Others"),
],
default="others",
max_length=255,
verbose_name="Category",
),
),
migrations.AlterField(
model_name="customrole",
name="baserole_ptr",
field=models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="kaehmy.baserole",
),
),
migrations.AlterField(
model_name="presetrole",
name="baserole_ptr",
field=models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="kaehmy.baserole",
),
),
]
@@ -0,0 +1,32 @@
# Generated by Django 4.2.24 on 2025-10-13 14:48
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
("contenttypes", "0002_remove_content_type_name"),
("webapp", "0082_delete_baserole"),
]
operations = [
migrations.AddField(
model_name="signup",
name="submit_id",
field=models.UUIDField(default=uuid.uuid4, editable=False, null=True),
),
migrations.AlterField(
model_name="basewebhook",
name="polymorphic_ctype",
field=models.ForeignKey(
editable=False,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="polymorphic_%(app_label)s.%(class)s_set+",
to="contenttypes.contenttype",
),
),
]
@@ -0,0 +1,18 @@
# Generated by Django 4.2.24 on 2025-10-13 15:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("webapp", "0083_signup_submit_id_alter_basewebhook_polymorphic_ctype"),
]
operations = [
migrations.AlterField(
model_name="signup",
name="submit_id",
field=models.UUIDField(null=True),
),
]
@@ -0,0 +1,18 @@
# Generated by Django 4.2.24 on 2025-10-13 15:46
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("webapp", "0084_alter_signup_submit_id"),
]
operations = [
migrations.AlterField(
model_name="signup",
name="submit_id",
field=models.CharField(null=True),
),
]
@@ -0,0 +1,18 @@
# Generated by Django 4.2.24 on 2025-10-13 15:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("webapp", "0085_alter_signup_submit_id"),
]
operations = [
migrations.AlterField(
model_name="signup",
name="submit_id",
field=models.UUIDField(editable=False, null=True),
),
]
@@ -0,0 +1,18 @@
# Generated by Django 4.2.24 on 2025-10-13 15:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("webapp", "0086_alter_signup_submit_id"),
]
operations = [
migrations.AlterField(
model_name="signup",
name="submit_id",
field=models.UUIDField(editable=False, null=True, unique=True),
),
]
+2
View File
@@ -195,6 +195,8 @@ class Signup(models.Model):
email = models.EmailField(blank=True, null=True)
# Random unique identifier. Used for signup editing by the user.
uuid = models.UUIDField(default=uuid4, editable=False)
# Random unique identifier generated by browser upon opening signup form. Used to prevent duplicate signups.
submit_id = models.UUIDField(null=True, editable=False, unique=True)
deleted = models.BooleanField(default=False)
def __str__(self):
+2 -1
View File
@@ -7,6 +7,7 @@ class SignupSerializer(serializers.ModelSerializer):
source="signupForm", queryset=SignupForm.objects.all()
)
list_name = serializers.CharField(read_only=True)
submit_id = serializers.UUIDField(required=False)
def add_extra_fields(self, validated_data):
questions = validated_data["signupForm"].questions
@@ -34,7 +35,7 @@ class SignupSerializer(serializers.ModelSerializer):
class Meta:
model = Signup
fields = ("id", "signupForm_id", "answer", "list_name")
fields = ("id", "submit_id", "signupForm_id", "answer", "list_name")
extra_kwargs = {
"url": {
"view_name": "signup-detail",
+5 -29
View File
@@ -12,6 +12,7 @@ from django.views.decorators.http import require_http_methods
from django_filters import rest_framework as filters
from django.db.models import Prefetch
from django.core.exceptions import ObjectDoesNotExist
from django.db.utils import IntegrityError
from rest_framework import routers
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
@@ -201,29 +202,6 @@ class SignupViewSet(ModelViewSet):
serializer_class = SignupSerializer
permission_classes = [SignupPermission]
submit_keys = (
{}
) # Dictionary for currently invalid submission keys; {key: timestamp}
def key_is_unique(self, submitKey):
current_time = time.time()
print("Checking", submitKey)
# Remove expired keys from dict (older than 1 h)
# A key that expires as the function is called is considered valid
print("Keys", SignupViewSet.submit_keys)
SignupViewSet.submit_keys = {
key: time
for key, time in SignupViewSet.submit_keys.items()
if time + 3600 >= current_time
}
print("After", SignupViewSet.submit_keys)
if submitKey not in SignupViewSet.submit_keys: # Key is unique; valid
SignupViewSet.submit_keys[submitKey] = current_time
return True
else: # Key is not unique; invalid, refresh timestamp
SignupViewSet.submit_keys[submitKey] = current_time
return False
@action(detail=True, methods=["get", "put"], permission_classes=[AllowAny])
def edit(self, request, pk=None, *args, **kwargs):
uuid = request.query_params.get("uuid", None)
@@ -238,12 +216,6 @@ class SignupViewSet(ModelViewSet):
def create(self, request, *args, **kwargs):
id = request.data["signupForm_id"]
try:
submitKey = request.data.get("submitKey")
if submitKey is not None and not self.key_is_unique(submitKey):
return JsonResponse(
status=200, data={"message": "Ignored repeated request"}
)
answer = request.data["answer"]
form = SignupForm.objects.get(id=id)
if form.isOpen:
@@ -256,6 +228,10 @@ class SignupViewSet(ModelViewSet):
return JsonResponse(
status=404, data={"error": f"SignupForm {id} not found"}
)
except IntegrityError:
return JsonResponse(
status=200, data={"message": "The submission had already been received"}
)
else:
return JsonResponse(
status=404, data={"error": f"SignupForm {id} not found"}