Django/用戶認證

Django自帶了一個用戶授權認證系統。它可以處理用戶帳戶、組、權限和基於cookie的用戶會話。本篇文檔將解釋它是如何工作的。

概覽

編輯

認證系統包括:

  • 用戶(Users)
  • 權限(Permissions):二進制 (yes/no) 的標誌,用來指明用戶都能做哪些事情。
  • 組(Groups):向多個用戶應用標籤和權限的通用方法。
  • 消息(Messages):為給定的用戶排隊消息的一個簡單的方法。

安裝

編輯

認證支持作為Django的一個應用被綁定在 django.contrib.auth 中。安裝方法如下:

  1. 把 'django.contrib.auth' 放到你的 INSTALLED_APPS 設置中。
  2. 運行命令 manage.py syncdb 。

注意,默認情況下,通過使用 django-admin.py startproject 來創建的工程已經在 settings.py 中的 INSTALLED_APPS 包含了 'django.contrib.auth' 。如果你的 INSTALLED_APPS 中已經包含了 'django.contrib.auth' ,你也可以再次運行 manage.py syncdb 。你可以隨意運行多少次都無所謂,每一次它都僅僅安裝需要的部分。

syncdb 創建必要的數據表,同時也為已經安裝的apps創建他們需要用到的權限對象。當你第一次運行這個命令的時候,它還會提示你創建一個超級用戶的帳戶。

當你做完以上這些步驟之後,認證系統就安裝好了。

用戶(Users)

編輯

用戶(Users) 表現為一個標準的Django模型,他在 django/contrib/auth/models.py 中。

API參考

編輯

字段

編輯

User 對象包含如下字段:

  • username -- 必須。小於等於30個字符。(字母、數字和下劃線)
  • first_name -- 可選。小於等於30個字符。
  • last_name -- 可選。小於等於30個字符。
  • email -- 可選。電子郵件地址。Optional. E-mail address.
  • password -- 必須。密碼的hash值。(Django不保存原始密碼。)原始密碼可以是任意長的並且可以包含任意字符。請看下面的 「密碼」 節。
  • is_staff -- 布爾型。標識用戶能否訪問admin界面。
  • is_active -- 布爾型。標識用戶能否登錄到admin界面。如果不想刪除用戶請把它設為 False
  • is_superuser -- 布爾型。標識用戶可以得到所有的權限。
  • last_login -- 用戶上一次登錄的日期時間。默認設置為當前的日期和時間。
  • date_joined -- 用戶帳戶創建的日期。默認設置為帳戶創建時的日期和時間。

方法

編輯

User 對象有2個多對多(many-to-many)的字段:groups 和 user_permissions 。 User 對象可以像其他Django對象(Django model)那樣訪問他們關聯的對象。

myuser.objects.groups = [group_list]
myuser.objects.groups.add(group, group,...)
myuser.objects.groups.remove(group, group,...)
myuser.objects.groups.clear()
myuser.objects.permissions = [permission_list]
myuser.objects.permissions.add(permission, permission, ...)
myuser.objects.permissions.remove(permission, permission, ...]
myuser.objects.permissions.clear()

除了這些自動生成的API方法外, User 對象還有如下的自定義的方法:

  • is_anonymous() -- 總是返回 False 。這是區別 User and AnonymousUser 對象的一個方法。通常你應該使用 is_authenticated() 而不是這個方法。
  • is_authenticated() -- 總是返回 True 。這是測試用戶是否被認證了。
  • get_full_name() -- 返回 first_name 加 last_name,中間用空格隔開。
  • set_password(raw_password) -- 用給定的字符串設定用戶密碼,並且處理密碼的hash值。不保存 User 對象。
  • check_password(raw_password) -- 如果給定的用戶密碼是正確的,那麼返回 True 。(通過比較密碼的hash值來實現的。)
  • get_group_permissions() -- 返回從用戶所在的組裏面獲取的權限列表。
  • get_all_permissions() -- 返回用戶擁有的所有的權限。包括組權限和用戶權限。
  • has_perm(perm) -- 當參數的格式為 "package.codename" 的時候,並且用戶擁有特殊權限的時候,返回 True 。
  • has_perms(perm_list) -- 同上。用戶對列表中每一個參數都有特殊權限的時候。每一個參數的格式都是 "package.codename" 。
  • has_module_perms(package_name) -- 當用戶對給定的包(Django app label)有權限的時候返回 True 。
  • get_and_delete_messages() -- 返回用戶對列中的 Message 對象列表並且從對列中刪除 Message 對象。
  • email_user(subject, message, from_email=None) -- 向用戶發送e-mail。如果 from_email 是 None, Django 使用 DEFAULT_FROM_EMAIL 設置。
  • get_profile() -- 返回站點特定的用戶檔案。如果當前站點不允許查詢檔案的話,Django將拋出 django.contrib.auth.models.SiteProfileNotAvailable 異常。

管理功能

編輯

User 模型有一個自定義的管理器,它有如下的函數:

  • create_user(username, email, password) -- 創建,保存並返回一個 User 。 username, email 和 password 被設置為給定的值,並且 User 設置了 is_active=True 。
    請看下面的 創建用戶 中的例子。
  • make_random_password(length=10, allowed_chars='abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789')返回一個給定長度的並且是包含給定字符的隨機密碼。(注意:allowed_chars 的默認值不包括 "I" 或者類似的字符,這是為了避免字符分辨不清而產生的抱怨。)

基本用法

編輯

創建用戶

編輯

創建用戶最基本的方法就是使用Django提供的 create_user 函數:

>>> from django.contrib.auth.models import User
>>> user = User.objects.create_user('john', 'lennon@thebeatles.com', 'johnpassword')

# 在这里,User对象已经可以保存到数据库中了。
# 你还可以改变它的其它属性。
>>> user.is_staff = True
>>> user.save()

更改密碼

編輯

使用 set_password() 更改密碼:

>>> from django.contrib.auth.models import User
>>> u = User.objects.get(username__exact='john')
>>> u.set_password('new password')
>>> u.save()

除非你很清楚的知道你在做什麼,否則不要直接設置 password 屬性。這將在下一節中闡釋。

密碼

編輯

User 的 password 屬性是一個如下格式的字符串:

hashtype$salt$hash

它們是 hashtype, salt 和 hash,用一個美元符號來分隔的。

Hashtype是 sha1(默認)或 md5 -- 單向加密的方法。Salt 是一個隨機字符串用來為密碼的原文創建hash。

例如:

sha1$a1976$a36cc8cbf81742a8fb52e221aaeab48ed7f58ab4

User.set_password() 和 User.check_password() 函數負責處理設置和檢測這些密碼。

在Django先前的版本中,例如 0.90,僅使用MD5而不使用salt。為了向後兼容,這些依然被支持。他們會在第一次使用 check_password() 來成功檢測用戶密碼的時候自動將其轉換為新的格式。

匿名用戶

編輯

django.contrib.auth.models.AnonymousUser 是一個類,它實現了 django.contrib.auth.models.User 接口,有如下的不同點:

  • id 總是 None.
  • is_anonymous() 返回 True 而不是 False.
  • is_authenticated() 返回 False 而不是 True.
  • has_perm() 總是返回 False.
  • set_password(), check_password(), save(), delete(), set_groups() and set_permissions() 拋出 NotImplementedError 異常。

在實際應用中,你可能並不需要 AnonymousUser 對象。但是他們被應用到網絡請求(Web request)中,這將在下面的章節中講述。

創建超級用戶

編輯

在你將 'django.contrib.auth' 添加到 INSTALLED_APPS 中之後,並且第一次運行 manage.py syncdb 命令時,它將提示你創建一個超級用戶。但是如果你需要在之後的操作過程中用命令行創建超級用戶的話,你可以使用 create_superuser.py 。命令如下:

python /path/to/django/contrib/auth/create_superuser.py

請確認你已經將/path/to/加入到PYTHON_PATH中。

Web請求中的認證

編輯

到目前為止,本文檔已經介紹了操作認證相關的對象的低級的API。在高一級上,Django可以將認證框架掛接到它本身系統的請求對象(request objects)中。

首先,安裝 SessionMiddleware 和 AuthenticationMiddleware 中間件。把他們加入到 MIDDLEWARE_CLASSES 設置中即可。更多信息請看 session documentation 。

當你安裝好這些中間件之後,你就可以在視圖(view)中訪問 request.user 了。 request.user 將返回當前登錄的用戶的一個 User 對象。如果當前沒有用戶登錄,那麼 request.user 將返回一個 AnonymousUser 對象的實例。你可以通過 is_authenticated() 來判斷是否有用戶登錄,如下:

if request.user.is_authenticated():
    # 认证的用户
else:
    # 匿名用户

如何登錄一個用戶

編輯

Django 在 django.contrib.auth 提供了2個函數:authenticate() 和 login() 。

如果通過給定的用戶名和密碼做認證,請使用 authenticate() 函數。他接收2個參數,一個是 username 一個是 password 。如果認證成功,它返回一個 User 對象。如果密碼無效,它返回一個 None 。例如:

from django.contrib.auth import authenticate
user = authenticate(username='john', password='secret')
if user is not None:
    print "用户名、密码正确!"
else:
    print "用户名、密码错误!"

在視圖中登錄一個用戶的話,使用 login() 函數。它接收 HttpRequest 對象和一個 User 對象。 login() 通過Django的session框架把用戶的ID保存到session中。所以,你要確認你已經安裝了session中間件。

下面是合用 authenticate() 和 login() 的例子:

from django.contrib.auth import authenticate, login

def my_view(request):
    username = request.POST['username']
    password = request.POST['password']
    user = authenticate(username=username, password=password)
    if user is not None:
        login(request, user)
        # 转到成功页面
    else:
        # 返回错误信息

如何登出用戶

編輯

要登出使用 django.contrib.auth.login() 登錄的用戶的話,可以在視圖中使用 django.contrib.auth.logout() 。它接收一個 HttpRequest 參數,沒有返回值。例如:

from django.contrib.auth import logout

def logout_view(request):
    logout(request)
    # 转到成功页面

請注意:如果用戶沒有登錄的話, logout() 也不會拋出任何異常的。

限制已登錄用戶的訪問

編輯

原始的方法

編輯

最簡單、最原始的限制頁面訪問的方法是在每個頁面上加入 request.user.is_authenticated() 並且把它重定向到登錄頁面。

from django.http import HttpResponseRedirect

def my_view(request):
    if not request.user.is_authenticated():
        return HttpResponseRedirect('/login/?next=%s' % request.path)
    #...

或者顯示一條出錯信息

def my_view(request):
    if not request.user.is_authenticated():
        return render_to_response('myapp/login_error.html')
    #...

login_required修飾符

編輯

你可以使用 login_required 修飾符來作為一個快捷方式

from django.contrib.auth.decorators import login_required

def my_view(request):
    # ...
my_view = login_required(my_view)

下面是一個等價的例子,使用了Python 2.4的decorator樣式

from django.contrib.auth.decorators import login_required

@login_required
def my_view(request):
    # ...

login_required 作下面這些事情:

  • 如果用戶沒有登錄,那麼重定向到/accounts/login/ ,傳入當前的絕對URL路徑作為 query string next 的值。例如:/accounts/login/?next=/polls/3/。
  • 如果用戶已經登錄了,那麼就正常執行view的代碼。

請注意,你需要映射正確的處理登錄用的視圖(view)到/accounts/login/。把下面的行加入到你的URLconf中:

(r'^accounts/login/$', 'django.contrib.auth.views.login'),

django.contrib.auth.views.login 的作用是:

  • 如果通過 ``GET`` 方式調用的話,它顯示一個登錄表單並通過POST的方式登錄。
  • 如果通過 ``POST`` 方式調用的話,它試圖把用戶登錄進去。 如果登錄成功, 視圖(view)重定向到 ``/accounts/profile/``(目前是硬性編碼的,就是寫死的。)。如果登錄失敗,則繼續顯示登錄表單。

你需要自己提供一個登錄表單的模板,默認叫 registration/login.html 。這個模板需要獲得3個模板上下文的變量:

  • form:一個 FormWrapper 對象,用來顯示登錄表單。更多請看``FormWrapper`` 對象的 forms documentation 。
  • next:登錄成功後重定向的URL。也可能包含一個查詢字符串。
  • site_name:當前 Site 的名字。根據 SITE_ID 設置的信息獲取。參考 site framework docs 。

如果你不想使用 registration/login.html 這個模板,你可以為在URLconf中的視圖(view)傳入一個 template_name 作為擴展的參數。

(r'^accounts/login/$', 'django.contrib.auth.views.login', {'template_name': 'myapp/login.html'}),

下面是一個 registration/login.html 的例子,你可以以它為基礎來開始你的工作。它擴展自 base.html 並且定義了一個 content 塊:

{% extends "base.html" %}

{% block content %}

{% if form.has_errors %}
<p>用户名和密码不匹配。请重试。</p>
{% endif %}

<form method="post" action=".">
<table>
<tr><td><label for="id_username">用户名:</label></td><td>{{ form.username }}</td></tr>
<tr><td><label for="id_password">密码:</label></td><td>{{ form.password }}</td></tr>
</table>

<input type="submit" value="登录" />
<input type="hidden" name="next" value="{{ next }}" />
</form>

{% endblock %}

已登錄用戶通過通行測試(pass test)來限制訪問

編輯

為了實現基於特定的權限或其他的測試的訪問限制,你應該做同上一節一樣的基本事情。 To limit access based on certain permissions or some other test, you'd do essentially the same thing as described in the previous section.

簡單的方法是在view中直接運行測試 request.user 的代碼。例如,這個view檢測並確認用戶已經登陸並且擁有 polls.can_vote 的權限。

def my_view(request):
    if not (request.user.is_authenticated() and request.user.has_perm('polls.can_vote')):
        return HttpResponse("你不能投票。")
    # ...

你可以使用 user_passes_test 作為快捷方式

from django.contrib.auth.decorators import user_passes_test

def my_view(request):
    # ...
my_view = user_passes_test(lambda u: u.has_perm('polls.can_vote'))(my_view)

Python 2.4的寫法

from django.contrib.auth.decorators import user_passes_test

@user_passes_test(lambda u: u.has_perm('polls.can_vote'))
def my_view(request):
    # ...

user_passes_test() 需要一個必須的參數:一個包含 User 的可調用的對象,如果用戶被允許察看這個頁面的話,返回 True 。注意,至於 User 是不是匿名的, user_passes_test 並不自動監測。

user_passes_test() 需要一個可選的 login_url 參數,它可以讓你指定登錄表單的URL(默認是/accounts/login/)。

例子,Python 2.3寫法

from django.contrib.auth.decorators import user_passes_test

def my_view(request):
    # ...
my_view = user_passes_test(lambda u: u.has_perm('polls.can_vote'), login_url='/login/')(my_view)

例子,Python 2.4寫法

from django.contrib.auth.decorators import user_passes_test

@user_passes_test(lambda u: u.has_perm('polls.can_vote'), login_url='/login/')
def my_view(request):
    # ...

限制訪問 generic views

編輯

為了限制訪問 generic view,可以為view寫一個包裝器,並且在URLconf中指定為它。

例如

from django.views.generic.date_based import object_detail

@login_required
def limited_object_detail(*args, **kwargs):
    return object_detail(*args, **kwargs)

權限(Permissions)

編輯

Django自帶了一個簡單的權限系統。它為向用戶和用戶組賦予權限提供了一個途徑。

它被用在了Django的admin站點中,當然你也可以把它用在自己的代碼中。

Django的admin站點是這樣應用權限的:

  • 通過"add"權限來控制用戶是否可以訪問添加表單並添加一個指定類型的對象。
  • 通過"change"權限來控制用戶是否可以訪問指定類型對象的列表和修改表單。
  • 通過"delete"權限來控制用戶是否可以刪除指定類型的對象。

權限被賦予每種類型的對象,而不是對象的特定的實例。你可以說「瑪麗可以修改新的故事(stories)」,但是你不能說「瑪麗可以修改她創建的新的故事 」或者 「瑪麗只能修改特定狀態的、特定發佈時間的、特定ID的故事等等」。這些功能目前Django的開發人員還在討論之中。

默認權限

編輯

3個基本的權限 -- 添加(add),創建(create)和刪除(delete) -- 在創建包含有 class Admin 的Django模型的時候都自動被創建好了。在表面現象的後面,當你運行 manage.py syncdb 的時候,這些權限被添加到了 auth_permission 數據表中。

請注意,如果你的模型里沒有 class Admin 的話,當你運行 manage.py syncdb 的時候這些權限不會被創建出來。如果你初始化數據庫之後還想添加這些權限,可以在模型中加入 class Admin 然後再運行一次 manage.py syncdb 。

自定義權限

編輯

為了給指定的模型自定義權限,可以使用 權限(permissions) 的 model Meta attribute 。

這個例子創建了3個自定義的權限

class USCitizen(models.Model):
    # ...
    class Meta:
        permissions = (
            ("can_drive", "Can drive"),
            ("can_vote", "Can vote in elections"),
            ("can_drink", "Can drink alcohol"),
        )

接下來的事情就是運行 syncdb 來創建這些權限。

API參考

編輯

就像用戶對象,權限也是實現自Django的模型 django/contrib/auth/models.py.

字段

編輯

Permission 有如下這些字段:

  • name -- 必須。小於等於50個字符。例如:'Can vote' 。
  • content_type -- 必須。 引用自 django_content_type 數據表,它包含了已經安裝的Django模型的類型。
  • codename -- 必須。小於等於100個字符。例如:'can_vote' 。

方法

編輯

Permission 有着與其它 Django model 一樣的數據訪問方法。

模板中的認證數據

編輯

如果你使用 RequestContext 的話,已經登錄的用戶的user和權限對象就保存在 template context 。

技术细节
在技术上,只有当你使用 RequestContext 的时候 并且 你的 TEMPLATE_CONTEXT_PROCESSORS 设置包含 "django.core.context_processors.auth" 的时候,这个变量才是有效的。更多参见 RequestContext docs.

用戶(Users)

編輯

當前登錄的用戶,不管是否是匿名的,存儲在模板變量 {{ user }} 中。

{% if user.is_authenticated %}
    <p>欢迎, {{ user.username }}。谢谢您的来访。</p>
{% else %}
    <p>欢迎,请登录。</p>
{% endif %}

權限(Permissions)

編輯

當前登錄用戶的權限存儲在模板變量 {{ perms }} 中。他是 django.core.context_processors.PermWrapper 的實例。

在 {{ perms }} 對象中,單個屬性的查找是使用 User.has_module_perms 的。下面這個例子中,如果用戶對 foo 這個app有任何權限的話,它就返回True。

{{ perms.foo }}

二級屬性查找是使用 User.has_perm 。下面這個例子中,如果用戶有 foo.can_vote 權限的話,它就返回True。

{{ perms.foo.can_vote }}

因此,你可以在模板中用 {% if %} 語句來判斷權限

{% if perms.foo %}
    <p>你有操作foo的权限。</p>
    {% if perms.foo.can_vote %}
        <p>你可以投票。</p>
    {% endif %}
    {% if perms.foo.can_drive %}
        <p>你可以开车。</p>
    {% endif %}
{% else %}
    <p>你没有操作foo的权限。</p>
{% endif %}

組(Groups)

編輯

組通常用來歸類用戶,這樣你就可以為這些組裏面的用戶應用權限或者貼其他的標籤。一個用戶可以屬於任意數量的組。

組中的用戶自動獲得賦予組的權限。例如,如果組 Site editors 有 can_edit_home_page 的權限,那麼任何加入這個組的用戶都自動擁有這個權限。

組也是歸類用戶並給他們貼標籤或擴展功能的一個方便的途徑。例如,你創建一個 'Special users' 的組,你可以寫代碼來讓他們訪問網站的會員專區或者發送給他們會員專用的電子郵件。

消息(Messages)

編輯

消息系統是為給定用戶排隊消息的輕量級的途徑。

一個消息關聯到一個 User 對象。沒有過期標誌和時間戳。

Django的admin站點用它來發送成功操作後的消息。例如, "poll成功被創建。" 就是一條消息。

API很簡單

  • 創建消息用 ``user_obj.message_set.create(message='message_text')`` 。
  • 檢索/刪除消息用 ``user_obj.get_and_delete_messages()``,他返回用戶消息隊列(如果有的話)中的一個 ``Message`` 列表,並且從隊列中刪除這些消息。

下面的例子中,用戶創建一個播放列表之後系統把消息保存下來

def create_playlist(request, songs):
    # 用给定的歌曲创建一个播放列表
    # ...
    request.user.message_set.create(message="播放列表添加成功。")
    return render_to_response("playlists/create.html",
        context_instance=RequestContext(request))

如果你使用 RequestContext ,當前用戶和他的消息保存在 template context 的模板變量 {{ messages }} 里。 下面是在模板中顯示消息的例子

{% if messages %}
<ul>
    {% for message in messages %}
    <li>{{ message.message }}</li>
    {% endfor %}
</ul>
{% endif %}

注意, RequestContext 調用 get_and_delete_messages 。所以,就算你不顯示他們的話,他們也會被刪除的。

最後,請注意這個消息框架僅適用於在用戶數據庫中的用戶。如果要向匿名用戶發送消息的話,請使用 session framework 。

其他認證源

編輯

Django自帶的認證系統對於大多數情況來說已經足夠用了,但是你也可能需要使用其他的認證源。其他的用戶名和密碼或者認證方法等。

例如,你的公司可能已經有了LDAP,並且已經存儲了每一位僱員的用戶名和密碼。如果你讓網絡管理員和用戶在LDAP和基於Django的應用上使用不同的登錄帳戶的話,他們會非常不高興的。

所以,為了解決這個問題,Django的認證系統允許你插入其他的認證源。你可以重載Django默認的數據庫結構,或者你可以把默認的系統和其他的系統串聯起來。

指定認證後端

編輯

在暗中,Django維護一個"authentication backends"的列表用來測試認證。當某人調用 django.contrib.auth.authenticate() -- 上面提到的"如何登錄一個用戶" -- Django將嘗試所有的認證後端。如果第一個認證方法失敗了,Django將會繼續嘗試第二個,直到所有的都被嘗試過。

認證後端的列表在 AUTHENTICATION_BACKENDS 設置。內容應該是包含Python路徑的元組。

默認情況下, AUTHENTICATION_BACKENDS 設置為

('django.contrib.auth.backends.ModelBackend',)

這是檢測Django用戶數據庫的基本認證方案。

按照 AUTHENTICATION_BACKENDS 的排列順序,如果同樣的用戶名和密碼在第一次就匹配了,那麼Django將停止處理後面的東西。

編寫一個認證後端

編輯

一個認證後端是一個類,實現了2個方法:get_user(id) 和 authenticate(**credentials) 。

get_user 方法接受一個 id -- 可以是用戶名,數據庫ID或者其他的什麼 -- 並且返回一個 User 對象。

authenticate 方法接受字典型認證信息的參數。大多情況下是如下樣子的

class MyBackend:
    def authenticate(username=None, password=None):
        # 检测用户名和密码,并返回一个User。

他也可以處理一個代號(token),像這樣

class MyBackend:
    def authenticate(token=None):
        # 检测并返回User。

當 authenticate 接受的參數被驗證為有效的時候,應該返回一個 User 對象;如果無效的時候,應該返回 None 。

在本文當開頭提到,Django的admin系統緊密地與 User 對象綁定在一起。目前,最好的處理方法就是為你每一個現存的後端(例如,你的LDAP目錄或者你的外部SQL數據庫等等。)數據創建一個Django的 User 對象。你可以預先寫一個腳本來做這些事情,或者在用戶第一次登錄的時候在你的 authenticate 方法中做這些事情。

下面是一個例子,使用在 settings.py 文件里定義的用戶名和密碼並且在用戶第一次登錄的時候創建一個Django的 User 對象。

from django.conf import settings
from django.contrib.auth.models import User, check_password

class SettingsBackend:
    """
    Authenticate against the settings ADMIN_LOGIN and ADMIN_PASSWORD.

    Use the login name, and a hash of the password. For example:

    ADMIN_LOGIN = 'admin'
    ADMIN_PASSWORD = 'sha1$4e987$afbcf42e21bd417fb71db8c66b321e9fc33051de'
    """
    def authenticate(self, username=None, password=None):
        login_valid = (settings.ADMIN_LOGIN == username)
        pwd_valid = check_password(password, settings.ADMIN_PASSWORD)
        if login_valid and pwd_valid:
            try:
                user = User.objects.get(username=username)
            except User.DoesNotExist:
                # 创建新用户。
                # 我们可以设置任何新的密码,因为它不会被检测。
                # 在这里我们使用"get from settings.py"。
                user = User(username=username, password='get from settings.py')
                user.is_staff = True
                user.is_superuser = True
                user.save()
            return user
        return None

    def get_user(self, user_id):
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None