From: Radek Czajka Date: Mon, 24 Feb 2025 15:41:05 +0000 (+0100) Subject: registration X-Git-Url: https://git.mdrn.pl/wolnelektury.git/commitdiff_plain/605af0880e55eebe3b5db50c97712c9e17448579 registration --- diff --git a/src/api/migrations/0008_alter_token_token_type.py b/src/api/migrations/0008_alter_token_token_type.py new file mode 100644 index 000000000..b2ba7bab8 --- /dev/null +++ b/src/api/migrations/0008_alter_token_token_type.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0.8 on 2025-02-24 15:19 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0007_alter_token_consumer'), + ] + + operations = [ + migrations.AlterField( + model_name='token', + name='token_type', + field=models.IntegerField(choices=[(1, 'Request'), (2, 'Access'), (3, 'Refresh')]), + ), + ] diff --git a/src/api/serializers.py b/src/api/serializers.py index 4ba660e26..8c008926d 100644 --- a/src/api/serializers.py +++ b/src/api/serializers.py @@ -37,8 +37,12 @@ class LoginSerializer(serializers.Serializer): class RegisterSerializer(serializers.Serializer): email = serializers.CharField() password = serializers.CharField(style={'input_type': 'password'}) - options = serializers.ListField(child=serializers.IntegerField()) + options = serializers.ListField(child=serializers.IntegerField(), required=False) class RefreshTokenSerializer(serializers.Serializer): refresh_token = serializers.CharField(style={'input_type': 'password'}) + + +class RequestConfirmSerializer(serializers.Serializer): + email = serializers.CharField() diff --git a/src/api/views.py b/src/api/views.py index 08d1650e7..0451c7760 100644 --- a/src/api/views.py +++ b/src/api/views.py @@ -4,6 +4,7 @@ from time import time from django.contrib.auth import authenticate from django.contrib.auth.decorators import login_required +from django.contrib.auth.models import User from django import forms from django.http import HttpResponse from django.http import Http404 @@ -18,6 +19,7 @@ from rest_framework.views import APIView from rest_framework.generics import GenericAPIView, RetrieveAPIView, get_object_or_404 from catalogue.models import Book from .models import BookUserData, KEY_SIZE, SECRET_SIZE, Token +from social.models import UserConfirmation from . import serializers from .request_validator import PistonRequestValidator from .utils import oauthlib_request, oauthlib_response, vary_on_auth @@ -227,7 +229,7 @@ class BlogView(APIView): -class RegisterView(APIView): +class RegisterView(GenericAPIView): serializer_class = serializers.RegisterSerializer def get(self, request): @@ -245,8 +247,30 @@ class RegisterView(APIView): }) def post(self, request): - pass - + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + d = serializer.validated_data + + user = User( + username=d['email'], + email=d['email'], + is_active=False + ) + user.set_password(d['password']) + + try: + user.save() + except: + return Response( + { + "detail": "Nie można utworzyć konta.", + }, + status=400 + ) + + UserConfirmation.request(user) + return Response({}) + class RefreshTokenView(APIView): serializer_class = serializers.RefreshTokenSerializer @@ -284,4 +308,21 @@ class RefreshTokenView(APIView): class RequestConfirmView(APIView): - pass + serializer_class = serializers.RequestConfirmSerializer + + def post(self, request): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + d = serializer.validated_data + + try: + user = User.objects.get( + username=d['email'], + is_active=False + ) + except User.DoesNotExist: + raise Http404 + + UserConfirmation.request(user) + return Response({}) + diff --git a/src/social/migrations/0017_userconfirmation.py b/src/social/migrations/0017_userconfirmation.py new file mode 100644 index 000000000..fe39336e1 --- /dev/null +++ b/src/social/migrations/0017_userconfirmation.py @@ -0,0 +1,25 @@ +# Generated by Django 4.0.8 on 2025-02-24 15:19 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('social', '0016_alter_bannergroup_options_alter_carousel_options_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='UserConfirmation', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('key', models.CharField(max_length=128, unique=True)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/src/social/models.py b/src/social/models.py index cb1326b15..17fe7d07a 100644 --- a/src/social/models.py +++ b/src/social/models.py @@ -1,10 +1,13 @@ # This file is part of Wolne Lektury, licensed under GNU Affero GPLv3 or later. # Copyright © Fundacja Wolne Lektury. See NOTICE for more information. # +from oauthlib.common import urlencode, generate_token from random import randint from django.db import models from django.conf import settings +from django.contrib.auth.models import User from django.core.exceptions import ValidationError +from django.core.mail import send_mail from django.urls import reverse from catalogue.models import Book from wolnelektury.utils import cached_render, clear_cached_renders @@ -170,3 +173,30 @@ class CarouselItem(models.Model): def get_banner(self): return self.banner or self.banner_group.get_banner() + + +class UserConfirmation(models.Model): + user = models.ForeignKey(User, models.CASCADE) + created_at = models.DateTimeField(auto_now_add=True) + key = models.CharField(max_length=128, unique=True) + + def send(self): + send_mail( + 'Potwierdź konto w bibliotece Wolne Lektury', + f'https://beta.wolnelektury.pl/ludzie/potwierdz/{self.key}/', + settings.CONTACT_EMAIL, + [self.user.email] + ) + + def use(self): + user = self.user + user.is_active = True + user.save() + self.delete() + + @classmethod + def request(cls, user): + cls.objects.create( + user=user, + key=generate_token() + ).send() diff --git a/src/social/templates/social/user_confirmation.html b/src/social/templates/social/user_confirmation.html new file mode 100644 index 000000000..f56d2f259 --- /dev/null +++ b/src/social/templates/social/user_confirmation.html @@ -0,0 +1,12 @@ +{% extends "base_simple.html" %} +{% load i18n %} + + +{% block body %} +

{% trans "Konto potwierdzone" %}

+ +

+ {% blocktrans with user=user %}Konto {{ user }} zostało potwierdzone. Możesz się teraz zalogować.{% endblocktrans %} +

+{% endblock body %} + diff --git a/src/social/urls.py b/src/social/urls.py index 299f62058..10d9240d2 100644 --- a/src/social/urls.py +++ b/src/social/urls.py @@ -7,6 +7,7 @@ from . import views urlpatterns = [ + path('potwierdz//', views.confirm_user, name='social_confirm_user'), path('lektura//lubie/', views.like_book, name='social_like_book'), path('dodaj-tag/', views.AddSetView.as_view(), name='social_add_set_tag'), path('usun-tag/', views.RemoveSetView.as_view(), name='social_remove_set_tag'), diff --git a/src/social/views.py b/src/social/views.py index 989771a6c..3dfcd9e2b 100644 --- a/src/social/views.py +++ b/src/social/views.py @@ -10,7 +10,7 @@ from django.views.generic.edit import FormView from catalogue.models import Book, Tag import catalogue.models.tag -from social import forms +from social import forms, models from wolnelektury.utils import is_ajax @@ -125,3 +125,12 @@ def my_tags(request): t.name for t in tags ], safe=False ) + + +def confirm_user(request, key): + uc = get_object_or_404(models.UserConfirmation, key=key) + user = uc.user + uc.use() + return render(request, 'social/user_confirmation.html', { + 'user': user, + })