UUID, email fields and receiver for sending them, /edit API for modifying signup with ID and UUID
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 2.1.5 on 2020-06-22 15:42
|
||||
|
||||
from django.db import migrations, models
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('webapp', '0063_signup_list_name'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='signup',
|
||||
name='uuid',
|
||||
field=models.UUIDField(default=uuid.uuid4, editable=False),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 2.1.5 on 2020-06-22 18:24
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('webapp', '0064_signup_uuid'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='signup',
|
||||
name='email',
|
||||
field=models.EmailField(blank=True, max_length=254, null=True),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 2.1.5 on 2020-06-22 20:02
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('webapp', '0065_signup_email'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='signupForm',
|
||||
field=models.ManyToManyField(blank=True, related_name='event', to='webapp.SignupForm'),
|
||||
),
|
||||
]
|
||||
+33
-5
@@ -1,21 +1,23 @@
|
||||
"""Webapp app models."""
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
# from datetime import timedelta
|
||||
from django.contrib.auth.models import User
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
from webapp.utils import month_from_now
|
||||
from webapp.utils import month_from_now, send_signup_email
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from auditlog.registry import auditlog
|
||||
from phonenumber_field.modelfields import PhoneNumberField
|
||||
from django.contrib.postgres.fields import JSONField
|
||||
|
||||
# import logging
|
||||
|
||||
from uuid import uuid4
|
||||
import logging
|
||||
from smtplib import SMTPAuthenticationError
|
||||
|
||||
VERBOSE_NAME = _('Webapp')
|
||||
EMAIL_REGEX = r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)"
|
||||
|
||||
|
||||
class Tag(models.Model):
|
||||
@@ -64,7 +66,7 @@ class Event(BaseFeed):
|
||||
start_time = models.DateTimeField(default=timezone.now)
|
||||
end_time = models.DateTimeField(default=timezone.now)
|
||||
signupForm = models.ManyToManyField(
|
||||
'SignupForm', blank=True)
|
||||
'SignupForm', blank=True, related_name="event")
|
||||
location = models.CharField(max_length=255, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
@@ -116,6 +118,13 @@ class SignupForm(models.Model):
|
||||
properties[id] = {
|
||||
"type": "string"
|
||||
}
|
||||
elif question_type == "email":
|
||||
# Format is just a "FYI" field, so we also have pattern.
|
||||
properties[id] = {
|
||||
"type": "string",
|
||||
"format": "email",
|
||||
"pattern": EMAIL_REGEX
|
||||
}
|
||||
elif question_type == "radiobutton":
|
||||
options = q["options"]
|
||||
regexes = map(lambda x: f"^{x}$", options)
|
||||
@@ -163,6 +172,10 @@ class Signup(models.Model):
|
||||
answer = JSONField()
|
||||
# Answer we use in signupForm signups field. Frontend uses first questions answer as this value.
|
||||
list_name = models.CharField(_('Name'), max_length=255)
|
||||
# If there is email in questions, we save it as own field
|
||||
email = models.EmailField(blank=True, null=True)
|
||||
# Random unique identifier. Used for signup editing by the user.
|
||||
uuid = models.UUIDField(default=uuid4, editable=False)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.signupForm}: {self.list_name} ({self.pk})"
|
||||
@@ -172,6 +185,21 @@ class Signup(models.Model):
|
||||
verbose_name_plural = _('Sign-ups')
|
||||
|
||||
|
||||
@receiver(post_save, sender=Signup)
|
||||
def email_on_singup(sender, instance, created, **kwargs):
|
||||
"""Send email validation."""
|
||||
if not settings.ENABLE_AUTOMATIC_EMAILS:
|
||||
return
|
||||
|
||||
try:
|
||||
if created and instance.email:
|
||||
# TODO: Possible bug due to many-to-many relationship with events and forms.
|
||||
subject = _(f"Olet ilmoittautunut tapahtumaan {instance.signupForm.event.first().title}")
|
||||
send_signup_email(instance.email, subject, instance.id, instance.uuid)
|
||||
except SMTPAuthenticationError:
|
||||
logging.error('Failed to send email to signup')
|
||||
|
||||
|
||||
class BaseRole(models.Model):
|
||||
"""Base model for occupations/roles."""
|
||||
|
||||
|
||||
@@ -8,6 +8,24 @@ class SignupSerializer(serializers.ModelSerializer):
|
||||
queryset=SignupForm.objects.all()
|
||||
)
|
||||
answer = serializers.JSONField()
|
||||
list_name = serializers.CharField(read_only=True)
|
||||
|
||||
def add_extra_fields(self, validated_data):
|
||||
questions = validated_data["signupForm"].questions
|
||||
validated_data["list_name"] = validated_data["answer"].get(questions[0]["id"], "")
|
||||
|
||||
email_fields = list(filter(lambda x: x["type"] == "email", questions))
|
||||
if (len(email_fields) > 0):
|
||||
email_value = validated_data["answer"].get(email_fields[0]["id"], None)
|
||||
validated_data["email"] = email_value
|
||||
|
||||
def create(self, validated_data):
|
||||
self.add_extra_fields(validated_data)
|
||||
return super().create(validated_data)
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
self.add_extra_fields(validated_data)
|
||||
return super().update(instance, validated_data)
|
||||
|
||||
class Meta:
|
||||
model = Signup
|
||||
|
||||
+14
-2
@@ -6,6 +6,8 @@ from django.core.mail import send_mail
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from django.conf import settings
|
||||
from django.template.loader import render_to_string
|
||||
from sikweb.settings import URL
|
||||
|
||||
|
||||
def month_from_now():
|
||||
@@ -13,17 +15,27 @@ def month_from_now():
|
||||
return timezone.now() + timedelta(days=30)
|
||||
|
||||
|
||||
def send_email(to, subject, body):
|
||||
def send_email(to, subject, body, fail_silently=False):
|
||||
try:
|
||||
success = send_mail(
|
||||
subject,
|
||||
body,
|
||||
settings.DEFAULT_EMAIL_FROM,
|
||||
[to],
|
||||
fail_silently=False,
|
||||
fail_silently=fail_silently,
|
||||
)
|
||||
if success == 0:
|
||||
raise Exception('Failed to send email!')
|
||||
|
||||
except Exception as ex:
|
||||
logging.exception('Failed to send email.')
|
||||
|
||||
|
||||
def send_signup_email(to, subject, id, uuid):
|
||||
message = render_to_string(
|
||||
'webapp:signup_email.html', {
|
||||
'url': f"https://{URL}/api/signup/{id}/edit/?uuid={uuid}",
|
||||
}
|
||||
)
|
||||
|
||||
return send_email(to, subject, message, fail_silently=True)
|
||||
|
||||
+34
-13
@@ -6,13 +6,14 @@ from django.utils import timezone
|
||||
from dealer.git import git
|
||||
from django.conf import settings
|
||||
from django.http import HttpResponse, JsonResponse
|
||||
from django.shortcuts import render
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
from django.views.decorators.http import require_http_methods
|
||||
from django_filters import rest_framework as filters
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from rest_framework import routers, viewsets
|
||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from rest_framework.permissions import IsAuthenticatedOrReadOnly, BasePermission
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import IsAuthenticatedOrReadOnly, BasePermission, AllowAny
|
||||
from jsonschema import validate
|
||||
from jsonschema.exceptions import ValidationError
|
||||
|
||||
@@ -22,7 +23,7 @@ from webapp.serializers import (EventSerializer, SignupFormSerializer, SignupSer
|
||||
OccupationSerializer, TagSerializer)
|
||||
|
||||
|
||||
class IsPostOrIsAuthenticated(BasePermission):
|
||||
class SignupPermission(BasePermission):
|
||||
|
||||
def has_permission(self, request, view):
|
||||
if request.method == 'POST':
|
||||
@@ -58,10 +59,6 @@ class SignupFormViewSet(viewsets.ModelViewSet):
|
||||
queryset = SignupForm.objects.all()
|
||||
serializer_class = SignupFormSerializer
|
||||
permission_classes = [IsAuthenticatedOrReadOnly]
|
||||
# Throws errors with JSONFIeld. Modify __all__ to not use JSONField if filters are enadbled
|
||||
# filter_backends = (filters.DjangoFilterBackend, SearchFilter, OrderingFilter)
|
||||
# filter_fields = '__all__'
|
||||
# search_fields = '__all__'
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
try:
|
||||
@@ -82,10 +79,18 @@ class SignupFormViewSet(viewsets.ModelViewSet):
|
||||
class SignupViewSet(viewsets.ModelViewSet):
|
||||
queryset = Signup.objects.all()
|
||||
serializer_class = SignupSerializer
|
||||
permission_classes = [IsPostOrIsAuthenticated]
|
||||
# filter_backends = (filters.DjangoFilterBackend, SearchFilter, OrderingFilter)
|
||||
# filter_fields = '__all__'
|
||||
# search_fields = '__all__'
|
||||
permission_classes = [SignupPermission]
|
||||
|
||||
@action(detail=True, methods=['get', 'post'], permission_classes=[AllowAny])
|
||||
def edit(self, request, pk=None, *args, **kwargs):
|
||||
uuid = request.query_params.get("uuid", None)
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
filter = {'pk': pk, 'uuid': uuid}
|
||||
signup = get_object_or_404(queryset, **filter)
|
||||
if request.method == 'GET':
|
||||
return self.retrieve(request, *args, **kwargs)
|
||||
elif request.method == 'POST':
|
||||
return self.partial_update(request, *args, **kwargs)
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
id = request.data["signupForm_id"]
|
||||
@@ -103,8 +108,24 @@ class SignupViewSet(viewsets.ModelViewSet):
|
||||
else:
|
||||
return JsonResponse(status=404, data={"error": f"SignupForm {id} not found"})
|
||||
|
||||
def update(self, request, *args, **kwargs):
|
||||
return super().update(request, *args, **kwargs)
|
||||
def partial_update(self, request, pk=None, *args, **kwargs):
|
||||
try:
|
||||
# ID & UUID validated in edit @action for normal users.
|
||||
# This is otherwise open for authenticated users.
|
||||
signup = self.get_object()
|
||||
answer = request.data["answer"]
|
||||
form = SignupForm.objects.get(id=signup.signupForm_id)
|
||||
|
||||
if (form.visible):
|
||||
# Throws ValidationError if not valid
|
||||
validate(instance=answer, schema=form.schema)
|
||||
return super().partial_update(request, *args, **kwargs)
|
||||
except ValidationError as inst:
|
||||
return JsonResponse(status=400, data={"error": inst.message})
|
||||
except ObjectDoesNotExist:
|
||||
return JsonResponse(status=404, data={"error": f"SignupForm {id} not found"})
|
||||
else:
|
||||
return JsonResponse(status=404, data={"error": f"SignupForm {id} not found"})
|
||||
|
||||
|
||||
class SavedQuestionsViewSet(viewsets.ModelViewSet):
|
||||
|
||||
Reference in New Issue
Block a user