Aki Ma
Aki Ma

Reputation: 13

Django coding - Why is it necessary to return two identical arguments?

I am looking to add email account verification in Django and found a nice open source code that I can adopt. But there is one line which I'm not familiar with.

Why does the function "password_reset_confirm" need to return two **kwargs in separate brackets and how each of them is used in the class "PasswordResetConfirm"?

This question might be related to Python rather than Django. But anyway, thank you for your help!

urls.py

url(r"^password/reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za- 
z]{1,13}-[0-9A-Za-z]{1,20})/$",views.password_reset_confirm, name="reset- 
password-confirm",)

views.py

from django.contrib.auth import views as django_views

class PasswordResetConfirm(django_views.PasswordResetConfirmView):
    template_name = "account/password_reset_from_key.html"
    success_url = reverse_lazy("account:reset-password-complete")
    token = None
    uidb64 = None

    def form_valid(self, form):
        response = super(PasswordResetConfirm, self).form_valid(form)
        account_events.customer_password_reset_event(user=self.user)
        return response

def password_reset_confirm(request, uidb64=None, token=None):
    kwargs = {
        "template_name": "account/password_reset_from_key.html",
        "success_url": reverse_lazy("account:reset-password-complete"),
        "token": token,
        "uidb64": uidb64,
    }
    return PasswordResetConfirm.as_view(**kwargs)(request, **kwargs)

Upvotes: 1

Views: 145

Answers (1)

GwynBleidD
GwynBleidD

Reputation: 20569

First variable (uidb64) is just base64-encoded ID of user, extracted from database. By this field Django can determine which user is requesting password reset.

Second variable (token) is an actual reset token that is used to verify user request. User can get this token only from email that was sent to him, so this verifies that user has access to provided email address, so we can proceed with password reset.

Why we can't use token alone? There are 2 reasons for that

  1. Django doesn't store password reset tokens in database. A lot of other frameworks will do that and after user clicks password reset URL, token will be found in database and from that database entry, user requesting reset will be determined. Instead, Django uses some clever cryptography methods and generates this token from SECRET_KEY stored in database, current user password hash and time of generating this token. After that, only time can be extracted from token and by fetching user data together with SECRET_KEY from settings, Django can verify that password reset token is valid for specified user. Time of token generation is here, so every token can expire after some time.
  2. User ID cannot be easily embedded in token, as time is, as User ID format can be customized (you can embed your own user model that uses for example UUID instead of numerical value). That's why Django is encoding this ID using Base64 - it guarantees that every format of user ID can be easily embedded in URL. For that reason, User ID can have different length and format, so it won't be easy to extract it from token string.

As for passing kwargs twice, here is quick explanation by example:

In python, you can return function from calling any other function or method (most often those kind of functions are called factories):

def some_factory():
    def some_function():
        print('abc')
    return some_function

my_function = some_factory()

In this example, print won't be called as we are not executing some_function, some_factory returns it, so we can use it later:

my_function()

Now this print will be called and we will see abc in console output. But instead of assigning returned function to some variable or passing it somewhere, you can call it immediately:

some_factory()()

This is where the second parentheses come from. Of course both functions can take some arguments, then you will provide arguments for factory inside first pair of parentheses and arguments to some_function in second.

Back to your example, it is actually invalid, you shouldn't pass full kwargs to as_view in PasswordResetConfirm. It should take only first two (template_name and success_url). Actual view (second parentheses) should take other two (token and uidb64).

2nd thing that is wrong in your code is that you're calling as_view on every request. It is designed to be called only once, when creating this view. Instead of wrapping it in separate function, use it directly in your urls.py:

url(
    r"^password/reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$",
    PasswordResetConfirm.as_view(
        template_name="account/password_reset_from_key.html"
        success_url=reverse_lazy("account:reset-password-complete"),
    ), name="reset-password-confirm",
)

Upvotes: 1

Related Questions