feat: Allow to confirm confidant
This commit is contained in:
parent
2bc9b5ba85
commit
73c6fbf165
8 changed files with 126 additions and 1 deletions
0
djeveric/__init__.py
Normal file
0
djeveric/__init__.py
Normal file
54
djeveric/rest_api.py
Normal file
54
djeveric/rest_api.py
Normal file
|
@ -0,0 +1,54 @@
|
|||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.tokens import default_token_generator
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import models
|
||||
from django.utils.http import urlsafe_base64_decode
|
||||
from rest_framework import serializers, status
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from djeveric.signals import user_confirmed, AlreadyConfirmed
|
||||
|
||||
|
||||
class ConfirmationSerializer(serializers.Serializer):
|
||||
uid = serializers.CharField()
|
||||
token = serializers.CharField()
|
||||
type_id = serializers.CharField()
|
||||
obj_id = serializers.CharField()
|
||||
|
||||
|
||||
class ConfirmationView(APIView):
|
||||
def post(self, request, format=None):
|
||||
serializer = ConfirmationSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
user = self.get_user(serializer.validated_data["uid"])
|
||||
token = serializer.validated_data["token"]
|
||||
if user is not None and default_token_generator.check_token(user, token):
|
||||
obj = self.get_obj(serializer.validated_data["type_id"], serializer.validated_data["obj_id"])
|
||||
if obj is not None:
|
||||
try:
|
||||
user_confirmed.send(sender=self.__class__, user=user, instance=obj, key=None)
|
||||
except AlreadyConfirmed as e:
|
||||
return Response({"message": str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response(serializer.data, status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def get_obj(self, type_id, obj_id):
|
||||
try:
|
||||
# urlsafe_base64_decode() decodes to bytestring
|
||||
type_pk = urlsafe_base64_decode(type_id).decode()
|
||||
obj_pk = urlsafe_base64_decode(obj_id).decode()
|
||||
obj = ContentType.objects.get_for_id(type_pk).get_object_for_this_type(pk=obj_pk)
|
||||
except (TypeError, ValueError, OverflowError, models.Model.DoesNotExist):
|
||||
obj = None
|
||||
return obj
|
||||
|
||||
def get_user(self, uid):
|
||||
try:
|
||||
# urlsafe_base64_decode() decodes to bytestring
|
||||
uid = urlsafe_base64_decode(uid).decode()
|
||||
user = get_user_model().objects.get(pk=uid)
|
||||
except (TypeError, ValueError, OverflowError, models.Model.DoesNotExist):
|
||||
user = None
|
||||
return user
|
34
djeveric/signals.py
Normal file
34
djeveric/signals.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
from django.contrib.auth.tokens import default_token_generator
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.contrib.sites.models import Site
|
||||
from django.core.mail import send_mail
|
||||
from django.dispatch import Signal
|
||||
from django.utils.encoding import force_bytes
|
||||
from django.utils.http import urlsafe_base64_encode
|
||||
|
||||
user_confirmed = Signal(providing_args=["user", "instance", "key"])
|
||||
|
||||
|
||||
class AlreadyConfirmed(Exception):
|
||||
"""The given resource has already been confirmed."""
|
||||
pass
|
||||
|
||||
|
||||
def encode_pk(obj):
|
||||
return urlsafe_base64_encode(force_bytes(obj.pk))
|
||||
|
||||
|
||||
def request_confirmation(user, instance, key=None):
|
||||
site = Site.objects.first()
|
||||
uid = encode_pk(user)
|
||||
token = default_token_generator.make_token(user)
|
||||
obj_type = ContentType.objects.get_for_model(instance)
|
||||
type_id = encode_pk(obj_type)
|
||||
obj_id = encode_pk(instance)
|
||||
confirmation_url = f"https://{site.domain}/confirm/{uid}/{token}/{type_id}/{obj_id}"
|
||||
send_mail(
|
||||
f"{site.name}: Bestätigung der Anfrage",
|
||||
f"Bitte bestätige, dass du deine E-Mail-Adresse auf der Seite {site.name} ({site.domain}) eingegeben hast. "
|
||||
f"Kopiere dazu folgende URL in deinen Webbrowser:\n\n{confirmation_url}",
|
||||
None, [user.email]
|
||||
)
|
|
@ -8,6 +8,8 @@ class UserActivationSerializer(serializers.Serializer):
|
|||
|
||||
|
||||
class UserSerializer(serializers.ModelSerializer):
|
||||
confidant_email = serializers.EmailField()
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ("pk", "email", "username", "confidant_email")
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
from django.urls import path
|
||||
from rest_framework import routers
|
||||
|
||||
from djeveric.rest_api import ConfirmationView
|
||||
from userausfall.rest_api.views import UserViewSet
|
||||
|
||||
router = routers.DefaultRouter(trailing_slash=True)
|
||||
router.register(r'users', UserViewSet, basename="user")
|
||||
|
||||
urlpatterns = [
|
||||
path("confirm", ConfirmationView.as_view())
|
||||
]
|
||||
|
||||
urlpatterns += router.urls
|
||||
|
|
|
@ -35,6 +35,7 @@ INSTALLED_APPS = [
|
|||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.sites',
|
||||
'django.contrib.staticfiles',
|
||||
'userausfall',
|
||||
'rest_framework',
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
from django.core.exceptions import PermissionDenied
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
|
||||
from djeveric.signals import request_confirmation, user_confirmed, AlreadyConfirmed
|
||||
from userausfall.models import User
|
||||
|
||||
|
||||
@receiver(post_save, sender=User)
|
||||
def user_saved(sender, instance: User, **kwargs):
|
||||
if (instance.confidant_unconfirmed is not None) and (instance.confidant_unconfirmed != instance.confidant):
|
||||
request_confirmation(instance.confidant_unconfirmed, instance)
|
||||
|
||||
|
||||
@receiver(user_confirmed)
|
||||
def user_confirmed(sender, user: User, instance: User, **kwargs):
|
||||
if user == instance.confidant_unconfirmed:
|
||||
if instance.confidant_unconfirmed != instance.confidant:
|
||||
# confirm the confidant
|
||||
instance.confidant = instance.confidant_unconfirmed
|
||||
instance.save()
|
||||
else:
|
||||
raise AlreadyConfirmed("The confidant has already been confirmed.")
|
||||
else:
|
||||
raise PermissionDenied()
|
|
@ -5,6 +5,6 @@ from userausfall.rest_api import urls as rest_api_urls
|
|||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
path('api/', include(rest_api_urls.router.urls)),
|
||||
path('api/', include("userausfall.rest_api.urls")),
|
||||
path("api-auth/", include("rest_framework.urls")),
|
||||
]
|
||||
|
|
Reference in a new issue