注册
让我们从创建注册视图开始。 首先,在urls.py文件中创建一个新路由:
myproject / urls.py
from django.conf.urls import url from django.contrib import admin from accounts import views as accounts_views from boards import views urlpatterns = [ url ( r'^$' , views . home , name = 'home' ), url ( r'^signup/$' , accounts_views . signup , name = 'signup' ), url ( r'^boards/(?P<pk> \ d+)/$' , views . board_topics , name = 'board_topics' ), url ( r'^boards/(?P<pk> \ d+)/new/$' , views . new_topic , name = 'new_topic' ), url ( r'^admin/' , admin . site . urls ), ]
请注意,我们如何以不同的方式从帐户应用程序导入视图模块:
from accounts import views as accounts_views
我们使用别名是因为否则会与董事会的观点冲突。 稍后我们可以改进urls.py设计。 但是现在,让我们集中讨论身份验证功能。
现在,在帐户应用程序中编辑views.py并创建一个名为signup的新视图:
account / views.py
from django.shortcuts import render def signup ( request ): return render ( request , 'signup.html' )
创建新模板,名为signup.html :
templates / signup.html
{% extends 'base.html' %} {% block content %} <h2> Sign up </h2> {% endblock %}
在浏览器中打开URL http://127.0.0.1:8000/signup/ ,检查是否一切正常:
是时候编写一些测试了:
account / tests.py
from django.core.urlresolvers import reverse from django.urls import resolve from django.test import TestCase from .views import signup class SignUpTests ( TestCase ): def test_signup_status_code ( self ): url = reverse ( 'signup' ) response = self . client . get ( url ) self . assertEquals ( response . status_code , 200 ) def test_signup_url_resolves_signup_view ( self ): view = resolve ( '/signup/' ) self . assertEquals ( view . func , signup )
测试状态代码(200 =成功),以及URL / signup /是否返回正确的视图功能。
python manage.py test
Creating test database for alias 'default'... System check identified no issues (0 silenced). .................. ---------------------------------------------------------------------- Ran 18 tests in 0.652s OK Destroying test database for alias 'default'...
对于身份验证视图(注册,登录,密码重置等),我们将不使用顶部栏或面包屑。 我们仍然可以使用base.html模板。 它只需要一些调整:
templates / base.html
{% load static %}<!DOCTYPE html> <html> <head> <meta charset= "utf-8" > <title> {% block title %} Django Boards {% endblock %} </title> <link href= "https://fonts.googleapis.com/css?family=Peralta" rel= "stylesheet" > <link rel= "stylesheet" href= " {% static 'css/bootstrap.min.css' %} " > <link rel= "stylesheet" href= " {% static 'css/app.css' %} " > {% block stylesheet %}{% endblock %} <!-- HERE --> </head> <body> {% block body %} <!-- HERE --> <nav class= "navbar navbar-expand-lg navbar-dark bg-dark" > <div class= "container" > <a class= "navbar-brand" href= " {% url 'home' %} " > Django Boards </a> </div> </nav> <div class= "container" > <ol class= "breadcrumb my-4" > {% block breadcrumb %} {% endblock %} </ol> {% block content %} {% endblock %} </div> {% endblock body %} <!-- AND HERE --> </body> </html>
我用注释标记了base.html模板中的新位。 区块{ % block stylesheet % }{ % endblock % }
{ % block stylesheet % }{ % endblock % }
{ % block stylesheet % }{ % endblock % }
将用于添加特定于某些页面的额外CSS。
区块{ % block body % }
{ % block body % }
{ % block body % }
包装了整个HTML文档。 我们可以利用它来利用base.html的头来创建一个空文档。 注意我们如何命名结尾块{ % endblock body % }
{ % endblock body % }
{ % endblock body % }
。 在这种情况下,为结束标记命名是一种好习惯,因此更容易识别结束位置。
现在使用signup.html模板,而不要使用{ % block content % }
{ % block content % }
{ % block content % }
,我们可以使用{ % block body % }
{ % block body % }
{ % block body % }
。
templates / signup.html
{% extends 'base.html' %} {% block body %} <h2> Sign up </h2> {% endblock %}
是时候创建注册表单了。 Django有一个名为UserCreationForm的内置表单。 让我们使用它:
account / views.py
from django.contrib.auth.forms import UserCreationForm from django.shortcuts import render def signup ( request ): form = UserCreationForm () return render ( request , 'signup.html' , { 'form' : form })
templates / signup.html
{% extends 'base.html' %} {% block body %} <div class= "container" > <h2> Sign up </h2> <form method= "post" novalidate > {% csrf_token %} {{ form.as_p }} <button type= "submit" class= "btn btn-primary" > Create an account </button> </form> </div> {% endblock %}
看起来有点乱,对吗? 我们可以使用form.html模板使它看起来更好:
templates / signup.html
{% extends 'base.html' %} {% block body %} <div class= "container" > <h2> Sign up </h2> <form method= "post" novalidate > {% csrf_token %} {% include 'includes/form.html' %} <button type= "submit" class= "btn btn-primary" > Create an account </button> </form> </div> {% endblock %}
嗯,快到了。 当前,我们的form.html部分模板正在显示一些原始HTML。 这是一项安全功能。 默认情况下,Django将所有字符串视为不安全,转义所有可能引起麻烦的特殊字符。 但是在这种情况下,我们可以信任它。
template / includes / form.html
{% load widget_tweaks %} {% for field in form %} <div class= "form-group" > {{ field.label_tag }} <!-- code suppressed for brevity --> {% if field.help_text %} <small class= "form-text text-muted" > {{ field.help_text | safe }} <!-- new code here --> </small> {% endif %} </div> {% endfor %}
基本上,在前面的模板中,我们向field.help_text
添加了safe
选项field.help_text
: { { field.help_text|safe } }
{ { field.help_text|safe } }
{ { field.help_text|safe } }
。
保存form.html文件,然后再次检查注册页面:
现在,让我们在注册视图中实现业务逻辑:
account / views.py
from django.contrib.auth import login as auth_login from django.contrib.auth.forms import UserCreationForm from django.shortcuts import render , redirect def signup ( request ): if request . method == 'POST' : form = UserCreationForm ( request . POST ) if form . is_valid (): user = form . save () auth_login ( request , user ) return redirect ( 'home' ) else : form = UserCreationForm () return render ( request , 'signup.html' , { 'form' : form })
基本表单处理,其中包含少量细节: 登录功能(重命名为auth_login,以避免与内置登录视图冲突)。
注意:我将login
功能重命名为auth_login
,但后来我意识到Django 1.11为登录视图LoginView
提供了一个基于类的视图,因此没有冲突名称的风险。
在较旧的版本上,存在auth.login
和auth.view.login
,这常常引起一些混乱,因为一个是登录用户的功能,另一个是视图。
长话短说:您可以根据需要将其作为login
导入,这不会造成任何问题。
如果表单有效,则使用user = form.save()
创建一个User实例。 然后将创建的用户作为参数传递给auth_login函数,以手动验证用户。 之后,该视图会将用户重定向到主页,从而保持应用程序的流程。
让我们尝试一下。 首先,提交一些无效数据。 空表单,不匹配的字段或现有的用户名:
现在填写表格并提交,检查用户是否已创建并重定向到主页:
在模板中引用经过身份验证的用户
我们怎么知道它是否有效? 好吧,我们可以编辑base.html模板以在顶部栏中添加用户名称:
templates / base.html
{% block body %} <nav class= "navbar navbar-expand-sm navbar-dark bg-dark" > <div class= "container" > <a class= "navbar-brand" href= " {% url 'home' %} " > Django Boards </a> <button class= "navbar-toggler" type= "button" data-toggle= "collapse" data-target= "#mainMenu" aria-controls= "mainMenu" aria-expanded= "false" aria-label= "Toggle navigation" > <span class= "navbar-toggler-icon" ></span> </button> <div class= "collapse navbar-collapse" id= "mainMenu" > <ul class= "navbar-nav ml-auto" > <li class= "nav-item" > <a class= "nav-link" href= "#" > {{ user.username }} </a> </li> </ul> </div> </div> </nav> <div class= "container" > <ol class= "breadcrumb my-4" > {% block breadcrumb %} {% endblock %} </ol> {% block content %} {% endblock %} </div> {% endblock body %}
测试注册视图
现在让我们改进测试用例:
account / tests.py
from django.contrib.auth.forms import UserCreationForm from django.core.urlresolvers import reverse from django.urls import resolve from django.test import TestCase from .views import signup class SignUpTests ( TestCase ): def setUp ( self ): url = reverse ( 'signup' ) self . response = self . client . get ( url ) def test_signup_status_code ( self ): self . assertEquals ( self . response . status_code , 200 ) def test_signup_url_resolves_signup_view ( self ): view = resolve ( '/signup/' ) self . assertEquals ( view . func , signup ) def test_csrf ( self ): self . assertContains ( self . response , 'csrfmiddlewaretoken' ) def test_contains_form ( self ): form = self . response . context . get ( 'form' ) self . assertIsInstance ( form , UserCreationForm )
我们对SignUpTests类进行了一些更改。 定义了一个setUp方法,将响应对象移到了那里。 然后,我们现在还要测试响应中是否包含表单和CSRF令牌。
现在,我们将测试成功注册。 这次,让我们创建一个新类来更好地组织测试:
account / tests.py
from django.contrib.auth.models import User from django.contrib.auth.forms import UserCreationForm from django.core.urlresolvers import reverse from django.urls import resolve from django.test import TestCase from .views import signup class SignUpTests ( TestCase ): # code suppressed... class SuccessfulSignUpTests ( TestCase ): def setUp ( self ): url = reverse ( 'signup' ) data = { 'username' : 'john' , 'password1' : 'abcdef123456' , 'password2' : 'abcdef123456' } self . response = self . client . post ( url , data ) self . home_url = reverse ( 'home' ) def test_redirection ( self ): ''' A valid form submission should redirect the user to the home page ''' self . assertRedirects ( self . response , self . home_url ) def test_user_creation ( self ): self . assertTrue ( User . objects . exists ()) def test_user_authentication ( self ): ''' Create a new request to an arbitrary page. The resulting response should now have a `user` to its context, after a successful sign up. ''' response = self . client . get ( self . home_url ) user = response . context . get ( 'user' ) self . assertTrue ( user . is_authenticated )
运行测试。
使用类似的策略,现在让我们创建一个新类,用于在数据无效时进行注册测试:
from django.contrib.auth.models import User from django.contrib.auth.forms import UserCreationForm from django.core.urlresolvers import reverse from django.urls import resolve from django.test import TestCase from .views import signup class SignUpTests ( TestCase ): # code suppressed... class SuccessfulSignUpTests ( TestCase ): # code suppressed... class InvalidSignUpTests ( TestCase ): def setUp ( self ): url = reverse ( 'signup' ) self . response = self . client . post ( url , {}) # submit an empty dictionary def test_signup_status_code ( self ): ''' An invalid form submission should return to the same page ''' self . assertEquals ( self . response . status_code , 200 ) def test_form_errors ( self ): form = self . response . context . get ( 'form' ) self . assertTrue ( form . errors ) def test_dont_create_user ( self ): self . assertFalse ( User . objects . exists ())
将电子邮件字段添加到表单
一切正常,但是… 电子邮件地址字段丢失。 很好, UserCreationForm不提供电子邮件字段。 但是我们可以扩展它。
在帐户文件夹中创建一个名为forms.py的文件:
account / forms.py
from django import forms from django.contrib.auth.forms import UserCreationForm from django.contrib.auth.models import User class SignUpForm ( UserCreationForm ): email = forms . CharField ( max_length = 254 , required = True , widget = forms . EmailInput ()) class Meta : model = User fields = ( 'username' , 'email' , 'password1' , 'password2' )
现在,让我们导入新表单SignUpForm ,而不是在views.py中使用UserCreationForm :
account / views.py
from django.contrib.auth import login as auth_login from django.shortcuts import render , redirect from .forms import SignUpForm def signup ( request ): if request . method == 'POST' : form = SignUpForm ( request . POST ) if form . is_valid (): user = form . save () auth_login ( request , user ) return redirect ( 'home' ) else : form = SignUpForm () return render ( request , 'signup.html' , { 'form' : form })
有了这个小小的改动,一切都已经开始了:
请记住,将测试用例更改为使用SignUpForm而不是UserCreationForm :
from .forms import SignUpForm class SignUpTests ( TestCase ): # ... def test_contains_form ( self ): form = self . response . context . get ( 'form' ) self . assertIsInstance ( form , SignUpForm ) class SuccessfulSignUpTests ( TestCase ): def setUp ( self ): url = reverse ( 'signup' ) data = { 'username' : 'john' , 'email' : 'john@doe.com' , 'password1' : 'abcdef123456' , 'password2' : 'abcdef123456' } self . response = self . client . post ( url , data ) self . home_url = reverse ( 'home' ) # ...
由于SignUpForm扩展了UserCreationForm , 因此它是 UserCreationForm的一个实例, 因此先前的测试用例仍会通过。
现在让我们考虑一下发生了什么。 我们添加了一个新的表单字段:
fields = ( 'username' , 'email' , 'password1' , 'password2' )
它会自动反映在HTML模板中。 很好吧? 好吧,要看情况。 如果将来有新的开发人员想要将SignUpForm再次用于其他用途,并向其中添加一些额外的字段,该怎么办 ? 然后,这些新字段也将显示在signup.html中 ,这可能不是所需的行为。 这种变化可能会被忽略,我们不希望有任何意外。
因此,让我们创建一个新的测试,以验证模板中的HTML输入:
account / tests.py
class SignUpTests ( TestCase ): # ... def test_form_inputs ( self ): ''' The view must contain five inputs: csrf, username, email, password1, password2 ''' self . assertContains ( self . response , '<input' , 5 ) self . assertContains ( self . response , 'type="text"' , 1 ) self . assertContains ( self . response , 'type="email"' , 1 ) self . assertContains ( self . response , 'type="password"' , 2 )
改善测试布局
好了,因此我们正在测试输入和所有内容,但是我们仍然必须测试表单本身。 我们不仅仅将测试添加到accounts / tests.py文件中,还需要对项目设计进行一些改进。
在帐户文件夹中创建一个名为tests的新文件夹。 然后,在tests文件夹内,创建一个名为__init__.py的空文件。
现在,将tests.py文件移动到tests文件夹内,并将其重命名为test_view_signup.py 。
最终结果应为:
myproject/ |-- myproject/ | |-- accounts/ | | |-- migrations/ | | |-- tests/ | | | |-- __init__.py | | | +-- test_view_signup.py | | |-- __init__.py | | |-- admin.py | | |-- apps.py | | |-- models.py | | +-- views.py | |-- boards/ | |-- myproject/ | |-- static/ | |-- templates/ | |-- db.sqlite3 | +-- manage.py +-- venv/
请注意,由于我们在应用程序上下文中使用相对导入,因此我们需要在新的test_view_signup.py中修复导入:
帐户/测试/test_view_signup.py
from django.contrib.auth.models import User from django.core.urlresolvers import reverse from django.urls import resolve from django.test import TestCase from ..views import signup from ..forms import SignUpForm
我们在应用程序模块内部使用了相对导入,因此以后我们可以自由地重命名Django应用程序,而不必修复所有绝对导入。
现在让我们创建一个新的测试文件,以测试SignUpForm 。 添加一个名为test_form_signup.py的新测试文件:
帐户/测试/test_form_signup.py
from django.test import TestCase from ..forms import SignUpForm class SignUpFormTest ( TestCase ): def test_form_has_fields ( self ): form = SignUpForm () expected = [ 'username' , 'email' , 'password1' , 'password2' ,] actual = list ( form . fields ) self . assertSequenceEqual ( expected , actual )
看起来很严格吧? 例如,如果将来我们必须更改SignUpForm ,以包括用户的名字和姓氏,那么即使我们没有破坏任何内容,我们也可能最终不得不修复一些测试用例。
这些警报很有用,因为它们有助于提高知名度,特别是对于初次接触该代码的新手。 它帮助他们放心地编写代码。
改进注册模板
让我们做一点工作。 在这里,我们可以使用Bootstrap 4卡组件使其看起来不错。
转到https://www.toptal.com/designers/subtlepatterns/并找到一个很好的背景图案用作帐户页面的背景。 下载它,在静态文件夹中创建一个名为img的新文件夹,然后将图像放在此处。
然后,在static / css中创建一个名为account.css的新CSS文件。 结果应为以下内容:
myproject/ |-- myproject/ | |-- accounts/ | |-- boards/ | |-- myproject/ | |-- static/ | | |-- css/ | | | |-- accounts.css <-- here | | | |-- app.css | | | +-- bootstrap.min.css | | +-- img/ | | | +-- shattered.png <-- here ( the name may be different, depending on the patter you downloaded ) | |-- templates/ | |-- db.sqlite3 | +-- manage.py +-- venv/
现在编辑accounts.css文件:
static / css / accounts.css
body { background-image : url(../img/shattered.png) ; } .logo { font-family : 'Peralta' , cursive ; } .logo a { color : rgba ( 0 , 0 , 0 , .9 ); } .logo a :hover , .logo a :active { text-decoration : none ; }
在signup.html模板中,我们可以对其进行更改以使用新的CSS,还可以使用Bootstrap 4卡组件:
templates / signup.html
{% extends 'base.html' %} {% load static %} {% block stylesheet %} <link rel= "stylesheet" href= " {% static 'css/accounts.css' %} " > {% endblock %} {% block body %} <div class= "container" > <h1 class= "text-center logo my-4" > <a href= " {% url 'home' %} " > Django Boards </a> </h1> <div class= "row justify-content-center" > <div class= "col-lg-8 col-md-10 col-sm-12" > <div class= "card" > <div class= "card-body" > <h3 class= "card-title" > Sign up </h3> <form method= "post" novalidate > {% csrf_token %} {% include 'includes/form.html' %} <button type= "submit" class= "btn btn-primary btn-block" > Create an account </button> </form> </div> <div class= "card-footer text-muted text-center" > Already have an account? <a href= "#" > Log in </a> </div> </div> </div> </div> </div> {% endblock %}
这样,这应该是我们现在的注册页面: