auth模块实现功能
通过入口url.py文件中定义的urlpatterns可以看出,auth模块共定义了8个url,分别用于:
- 登录
- 注销
- 修改密码
- 修改密码完成
- 密码重置
- 密码重置完成
- 密码重置验证
- 密码重置结束
from django.conf.urls import urlfrom django.contrib.auth import views# urlpatterns直接是一个list即可urlpatterns = [ url(r'^login/$', views.login, name='login'), url(r'^logout/$', views.logout, name='logout'), url(r'^password_change/$', views.password_change, name='password_change'), url(r'^password_change/done/$', views.password_change_done, name='password_change_done'), url(r'^password_reset/$', views.password_reset, name='password_reset'), url(r'^password_reset/done/$', views.password_reset_done, name='password_reset_done'), url(r'^reset/(?P[0-9A-Za-z_\-]+)/(?P [0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', views.password_reset_confirm, name='password_reset_confirm'), url(r'^reset/done/$', views.password_reset_complete, name='password_reset_complete'),]
这篇文章主要学习登录和注销相关代码
login 登录
def login(request, template_name='registration/login.html', redirect_field_name=REDIRECT_FIELD_NAME, authentication_form=AuthenticationForm, extra_context=None): """ Displays the login form and handles the login action. """ redirect_to = request.POST.get(redirect_field_name, request.GET.get(redirect_field_name, '')) if request.method == "POST": form = authentication_form(request, data=request.POST) if form.is_valid(): # Ensure the user-originating redirection url is safe. if not is_safe_url(url=redirect_to, host=request.get_host()): redirect_to = resolve_url(settings.LOGIN_REDIRECT_URL) # Okay, security check complete. Log the user in. auth_login(request, form.get_user()) return HttpResponseRedirect(redirect_to) else: form = authentication_form(request) current_site = get_current_site(request) context = { 'form': form, redirect_field_name: redirect_to, 'site': current_site, 'site_name': current_site.name, } if extra_context is not None: context.update(extra_context) return TemplateResponse(request, template_name, context)
登录处理视图函数依据功能分为:
- 展示登录页面供用户提供认证信息进行登录操作(
request.method == "GET"
) - 对用户提交的认证信息进行认证,并做出相应反映(
request.method == "POST"
)
request.method == "GET"
- 实例化一个
authentication_form
实例(通过传入request将request绑定到form实例上),供用户输入认证信息 - 获取站点信息,供页面显示
- 生成
context
,并使用关键词参数extra_context
进行拓展 - 返回页面(
TemplateResponse
)
此场景处理逻辑较简单
request.method == "POST"
- 通过获取到的用户数据(request.POST)实例化authentication_form
- 判断用户输入数据是否有效合理(检查是否有输入传入;并且调用full_clean方法来进行验证)。默认的AuthenticationForm中的clean方法中会调用
django.contrib.auth.authenticate
方法对用户提供的认证信息进行校验。而authenticate
方法会遍历settings中定义的AUTHENTICATION_BACKENDS
模块,并调用模块的authenticate方法进行认证,直到成功,否则出发user_login_failed singnal
- 调用
django.contrib.auth.login
方法使得用户登录成功,并返回HttpResponseRedirect
响应,重定向到redirect_field_name
指向地址。
step2 form.is_valid()调用过程# django.forms.forms.pyclass BaseForm(object): .... @property def errors(self): "Returns an ErrorDict for the data provided for the form" if self._errors is None: self.full_clean() return self._errors def is_valid(self): """ Returns True if the form has no errors. Otherwise, False. If errors are being ignored, returns False. """ return self.is_bound and not self.errors def full_clean(self): """ Cleans all of self.data and populates self._errors and self.cleaned_data. """ self._errors = ErrorDict() if not self.is_bound: # Stop further processing. return self.cleaned_data = {} # If the form is permitted to be empty, and none of the form data has # changed from the initial data, short circuit any validation. if self.empty_permitted and not self.has_changed(): return self._clean_fields() self._clean_form() self._post_clean() def _clean_fields(self): for name, field in self.fields.items(): # value_from_datadict() gets the data from the data dictionaries. # Each widget type knows how to retrieve its own data, because some # widgets split data over several HTML fields. if field.disabled: value = self.initial.get(name, field.initial) else: value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name)) try: if isinstance(field, FileField): initial = self.initial.get(name, field.initial) value = field.clean(value, initial) else: value = field.clean(value) self.cleaned_data[name] = value ## 如果有clean_filedname方法的话,调用之 if hasattr(self, 'clean_%s' % name): value = getattr(self, 'clean_%s' % name)() self.cleaned_data[name] = value except ValidationError as e: self.add_error(name, e) def _clean_form(self): try: cleaned_data = self.clean() except ValidationError as e: self.add_error(None, e) else: if cleaned_data is not None: self.cleaned_data = cleaned_data def _post_clean(self): """ An internal hook for performing additional cleaning after form cleaning is complete. Used for model validation in model forms. """ pass
step2 django.contrib.auth.authenticate方法def load_backend(path): return import_string(path)()def _get_backends(return_tuples=False): backends = [] for backend_path in settings.AUTHENTICATION_BACKENDS: backend = load_backend(backend_path) backends.append((backend, backend_path) if return_tuples else backend) if not backends: raise ImproperlyConfigured( 'No authentication backends have been defined. Does ' 'AUTHENTICATION_BACKENDS contain anything?' ) return backendsdef authenticate(**credentials): """ If the given credentials are valid, return a User object. """ for backend, backend_path in _get_backends(return_tuples=True): try: inspect.getcallargs(backend.authenticate, **credentials) except TypeError: # This backend doesn't accept these credentials as arguments. Try the next one. continue try: user = backend.authenticate(**credentials) except PermissionDenied: # This backend says to stop in our tracks - this user should not be allowed in at all. return None if user is None: continue # Annotate the user object with the path of the backend. user.backend = backend_path return user # The credentials supplied are invalid to all backends, fire signal user_login_failed.send(sender=__name__, credentials=_clean_credentials(credentials))
step3 django.contrib.auth.login方法1. request.user = user2. 触发user_logged_in singnaldef login(request, user): """ Persist a user id and a backend in the request. This way a user doesn't have to reauthenticate on every request. Note that data set during the anonymous session is retained when the user logs in. """ session_auth_hash = '' if user is None: user = request.user if hasattr(user, 'get_session_auth_hash'): session_auth_hash = user.get_session_auth_hash() if SESSION_KEY in request.session: if _get_user_session_key(request) != user.pk or ( session_auth_hash and request.session.get(HASH_SESSION_KEY) != session_auth_hash): # To avoid reusing another user's session, create a new, empty # session if the existing session corresponds to a different # authenticated user. request.session.flush() else: request.session.cycle_key() request.session[SESSION_KEY] = user._meta.pk.value_to_string(user) request.session[BACKEND_SESSION_KEY] = user.backend request.session[HASH_SESSION_KEY] = session_auth_hash if hasattr(request, 'user'): request.user = user rotate_token(request) user_logged_in.send(sender=user.__class__, request=request, user=user)
logout 注销
@deprecate_current_appdef logout(request, next_page=None, template_name='registration/logged_out.html', redirect_field_name=REDIRECT_FIELD_NAME, extra_context=None): """ Logs out the user and displays 'You are logged out' message. """ auth_logout(request) if next_page is not None: next_page = resolve_url(next_page) if (redirect_field_name in request.POST or redirect_field_name in request.GET): next_page = request.POST.get(redirect_field_name, request.GET.get(redirect_field_name)) # Security check -- don't allow redirection to a different host. if not is_safe_url(url=next_page, host=request.get_host()): next_page = request.path if next_page: # Redirect to this page until the session has been cleared. return HttpResponseRedirect(next_page) current_site = get_current_site(request) context = { 'site': current_site, 'site_name': current_site.name, 'title': _('Logged out') } if extra_context is not None: context.update(extra_context) return TemplateResponse(request, template_name, context)
实现了登出用户,并跳转到指定(next_page或者request.GET, request.POST中携带的redicted_field_name)页面功能
登出用户 django.contrib.auth.logout1. 出发user_logged_out singnal2. request.session.flush()3. request.user = AnonymousUser() if hasattr(request, 'user')def logout(request): """ Removes the authenticated user's ID from the request and flushes their session data. """ # Dispatch the signal before the user is logged out so the receivers have a # chance to find out *who* logged out. user = getattr(request, 'user', None) if hasattr(user, 'is_authenticated') and not user.is_authenticated(): user = None user_logged_out.send(sender=user.__class__, request=request, user=user) # remember language choice saved to session language = request.session.get(LANGUAGE_SESSION_KEY) request.session.flush() if language is not None: request.session[LANGUAGE_SESSION_KEY] = language if hasattr(request, 'user'): from django.contrib.auth.models import AnonymousUser request.user = AnonymousUser()