Django – #39 – REST API cz. 14 – E-mail aktywujący konto

Wprowadzenie.

Jak wiemy z naszego doświadczenia, gdy się rejestrujemy do aplikacji, często zdarza się, że wysyłany jest do nas e-mail aktywujący nasze konto. Nieraz mechanizm ten jest tak skonstruowany, że wystarczy jedynie nacisnąć na link w mailu, w innych przypadkach dodatkowo musimy na przykład podać / zmienić hasło. Metod aktywacji oraz implementacji jest wiele i każda ma swoje plusy i minusy. We wpisie tym przedstawiłem, w jaki sposób zaimplementować funkcjonalność w postaci wysłania na maila linku aktywującego konto nowo zarejestrowanego użytkownika.

Zakres artykułu.

  • E-mail aktywujący konto

E-mail aktywujący konto

W pierwszej kolejności w pliku settings.py musimy skonfigurować host adresu e-mail, port oraz dane do konta e-mail, z którego będą wysyłane wiadomości. Ponieważ host serwera adresów mailowych w moim przypadku, znajduje się na serwerach ovh, dlatego musiałem dowiedzieć się, jaki jest adres hosta serwera e-mail oraz jaki wykorzystywany jest port.

# E-MAIL Configuration
EMAIL_USE_TLS = True
EMAIL_HOST = 'ssl0.ovh.net' # ovh server host
EMAIL_HOST_USER = 'youremail@inthou.pl'
EMAIL_HOST_PASSWORD = 'yourPasswordToEmail'
EMAIL_PORT = 587

W następnym kroku, w celu sprawdzenia, czy działa nam wysyłanie wiadomości, zmodyfikujmy widok odpowiedzialny za tworzenie nowego użytkownika, tak aby wysyłał nam maila z informacją, że zarejestrował się nowy użytkownik.

Zaimportujmy z modułu django.core.mail metodę send_mail.

from django.core.mail import send_mail 

Następnie w nadpisanej metodzie perform_create() klasy UserCreate(), dodajmy następującą linijkę kodu send_mail(‘New user’,’A new user has registered.’,’info@inthou.pl’, [‘dominik.bednarski@inthou.pl’], fail_silently=False,). Szczegółowe informacje o tej metodzie znajdziemy w dokumentacji na stronie Django

class UserCreate(generics.CreateAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    permission_classes = (AllowAny, )

    def perform_create(self, serializer):
        user = serializer.save()
        send_mail('New user','A new user has registered.','info@inthou.pl', ['dominik.bednarski@inthou.pl'],  fail_silently=False,)
        Token.objects.create(user=user)

Sprawdźmy, czy na ten moment wszystko działa poprawnie. Wejdźmy na stronę 127.0.0.1:8000/api/user/create. Następnie wprowadźmy dane do formularza i wyślijmy go, naciskając na przycisk POST.

Jeżeli wszystko przebiegnie pomyślnie, powinniśmy otrzymać informację zwrotną z danymi username oraz email

Teraz możemy zalogować się na naszego maila i sprawdzić, czy dostaliśmy wiadomość z informacją, że nowy użytkownik się zarejestrował.

Gdy już wiemy, że wysyłanie maili działa, to możemy zająć się naszym głównym celem.

Stwórzmy teraz nowy model, który będzie zawierał 4 pola. Pole key, które będzie zawierało token. Token ten będziemy generowali i umieszczali go w adresie url, odpowiedzialnym za aktywację konta. Token ten ma na celu znaczne utrudnienie potwierdzenia aktywacji konta przez osobę trzecią. Drugie pole user będzie przypisane do konkretnego zarejestrowanego użytkownika. Trzecie pole created będzie zawierało informację o zmianie danych w rekordzie (informację tą możemy na przykład wykorzystać w celu zdefiniowania maksymalnego dozwolonego czasu aktywacji konto. Tej funkcjonalności w tym wpisie nie będę implementował). Ostatnie czwarte pole info, będzie zawierało, krótką informację, którą sobie w późniejszych krokach zdefiniujemy.

W modelu nadpisałem metodę save(), w której to zawarłem mechanizm tworzenia tokena i przypisania go do pola key. Ten krok prawdopodobnie lepiej by pasował w innym miejscu, ale na potrzeby nauki chcę pokazać, że tu też możemy zamieszczać takie kroki. 

# new
class ActionAuthorization(models.Model):

    key = models.CharField(max_length=64)
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    created = models.DateTimeField(auto_now_add=False, auto_now=True)
    info = models.TextField(null=True, blank=True,)

    def save(self, *args, **kwargs):
        self.key = secrets.token_urlsafe(32)
        return super().save(*args, **kwargs)

    def __str__(self):
        return self.key

Następnie należy stworzyć nowy serializer, który będzie przypisany do nowo stworzonego modelu.

# new
class ActionAuthorizationSerializer(serializers.ModelSerializer):
    class Meta:
        model = ActionAuthorization
        fields = ('id', 'key', 'user', 'created', 'info')
        read_only_fields = ['key', 'user', 'created']

Teraz możemy dodać klasę nowego widoku. W klasie tej nadpiszemy trzy metody get_queryset(), put() oraz perform_update(). W metodzie get_queryset() przefiltrujemy obiekt po tokenie zamieszczonym w linku aktywacyjnym. W metodzie perform_update() przypiszemy dodatkowo do pola info informację, że konto zostało aktywowane, natomiast w metodzie put() wykonamy trochę więcej czynności, bo musimy po pierwsze przefiltrować obiekt po tokenie, następnie musimy znaleźć użytkownika, do którego jest przypisany ten token we wbudowanej tabeli User. Następnie w rekordzie znalezionego użytkownika z tabeli User musimy ustawić pole is_activate na True. Po tej czynności możemy dokonać zapisu tego rekordu.

# new       
class UserAccountActivation(generics.RetrieveUpdateAPIView):
    queryset = ActionAuthorization.objects.all()
    serializer_class = ActionAuthorizationSerializer
    permission_classes = [AllowAny]

    def get_queryset(self):
        return ActionAuthorization.objects.filter(key=self.kwargs['s'])

    def put(self, request, *args, **kwargs):
        try:
            token = ActionAuthorization.objects.get(key=kwargs['s'])
            user = User.objects.get(id=token.user_id)
            user.is_active = True
            user.save()
            return self.update(request, *args, **kwargs)
        except ObjectDoesNotExist:
            raise ValidationError('Error')

    def perform_update(self, serializer):
        serializer.save(info = "Account has been activated!")

Teraz stwórzmy ścieżkę łączącą adres URL z nowo stworzoną klasą widoku.

urlpatterns = [
    path('admin/', admin.site.urls),

    # API
    path('api/authors/', views.AuthorList.as_view()),
    path('api/books/', views.BookList.as_view()),
    path('api/borrows/', views.BorrowList.as_view()),
    path('api/borrows/<int:pk>/', views.BorrowRetrieveDestroy.as_view()),
    path('api/borrows/<int:pk>/return/', views.BorrowReturnBookUpdate.as_view()),
    path('api/borrows/<int:pk>/edit/', views.BorrowRetrieveUpdate.as_view()),

    path('api/user/create', views.UserCreate.as_view()),
    path('api/user/login', views.UserTokenList.as_view()),
    path('api/activate/<int:pk>/<str:s>', views.UserAccountActivation.as_view()), # new

    # DRF
    path('api-auth/', include('rest_framework.urls')),
]

Kolejnym krokiem jest poprawienie klasy widoku odpowiedzialnej za tworzenie nowego użytkownika. W klasie tej musimy napisać funkcjonalność, która będzie odpowiedzialna za tworzenie adresu aktywującego konto. Skorzystamy tu z metody render_to_string(), która jest odpowiedzialna za renderowanie szablonu do ciągu znakowego. Ten ciąg znakowy użyjemy jako naszą wiadomość przesyłaną na maila nowo zarejestrowanego użytkownika. Pierwszym argumentem metody render_to_string() jest nazwa szablonu, natomiast drugi są zmienne podstawiane w szablonie. Więcej o tej metodzie możecie przeczytać w dokumentacji Django po tym linkiem

class UserCreate(generics.CreateAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    permission_classes = (AllowAny, )

    def perform_create(self, serializer):
        user = serializer.save()
        # send_mail('New user','A new user has registered.','info@inthou.pl', ['dominik.bednarski@inthou.pl'],  fail_silently=False,)
        Token.objects.create(user=user)
        tokenAuth = ActionAuthorization.objects.create(user=user, info="Account is not activated!") # new
        message = render_to_string('account_activation.html', {
                'user': user,
                'domain': '127.0.0.1:8000',
                'token':tokenAuth.key,
                'token_id':tokenAuth.id,
            })
        send_mail('New user',message,'dominik.bednarski@inthou.pl', [user.email],  fail_silently=False,)

Teraz musimy stworzyć szablon, o którym mówiłem powyżej. Szablon ten to treść wiadomości, która będzie wysyłana na adres e-mail nowo zarejestrowanego użytkownika.

{% autoescape off %}
Hi {{ user.username }},
Please click on the link to confirm your registration,
http://{{ domain }}/api/activate/{{ token_id }}/{{ token }}
{% endautoescape %}

Teraz możemy przejść do przetestowania tego, co napisaliśmy.

Wejdźmy na stronę 127.0.0.1:8000/api/user/create, a następnie wypełnijmy formularz i prześlijmy go przyciskiem POST.

Po otrzymaniu danych zwrotnych efekt powinien wyglądać następująco.

Teraz wejdźmy na naszego maila i sprawdźmy czy otrzymaliśmy wiadomość.

W tym miejscu mogłem zmienić tytuł wiadomości, lecz jest to mało istotny szczegół. 

Jak możemy zobaczyć, treść wiadomości została wyrenderowana poprawnie oraz mamy załączony nasz adres aktywacyjny.

Zanim wejdziemy pod link aktywacyjny, przejdźmy do panelu admina, do tabeli Action authorizations.

W tym miejscu widzimy, że w polu info znajduje się informacja, że konto jest nieaktywne.

Przejdźmy jeszcze do tabeli Users.

W tym miejscu warto zwrócić uwagę, że check box Active jest odznaczony, co oznacza, że konto jest nieaktywne. 

W tej chwili możemy przejść do strony z linku aktywacyjnego. 

W tym miejscu po prostu naciśnijmy jedynie przycisk PUT. W tej części mogliśmy jedynie ustawić, aby była możliwość aktualizacji, natomiast metoda get() mogłaby być niedostępna. 

Po otrzymaniu danych zwrotnych możemy zauważyć, że zmieniła się wartość pola key oraz info. O ile info intuicyjnie wiemy, gdzie nadpisaliśmy, to z wartością key mogą być problemy, dlatego dla przypomnienia informuję, że wartość ta jest zmieniana w samym modelu, gdzie nadpisaliśmy metodę seve().

Przejdźmy teraz do panelu admina do tabeli User.

Jak widzimy, check box Active jest zaznaczone, co oznacza, że konto jest aktywne.

Na koniec przejdźmy jeszcze do tabeli Action authorizations.

W polu info zgodnie z naszymi oczekiwaniami znajduje się informacja, że konto zostało aktywowane. 

Autor artykułu
Dominik Bednarski

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.