跟书《python编程:从入门到实践》,学习用Django编写名为“学习笔记”的Web应用程序。html
Web应用程序的核心是让任何用户都可以注册帐户并可以使用它,无论用户身处何方。咱们能够建立一些表单,让用户可以添加主题和条目,以及编辑既有的条目。python
先来添加几个页面,让用户可以输入数据。可让用户可以添加新主题、添加新条目以及编辑既有条目,但不能经过管理网站来输入,由于只有超级用户才能够这样作。sql
首先来让用户可以添加新主题。建立基于表单的页面的方法几乎与前面建立网页同样:定义一个URL,编写一个视图函数并编写一个模板。一个主要差异是,须要导入包含表单的模块forms.py。shell
在Django中,建立表单的最简单方式是使用ModelForm。建立一个名为forms.py的文件,将其存储到models.py所在的目录中,并在其中编写第一个表单。数据库
# vim learning_logs/forms.py
from django import formsfrom .models import Topicclass TopicForm(forms.ModelForm): class Meta: model = Topic fields = ['text'] labels = {'text': ''}
首先导入了模块forms以及要使用的模型Topic。而后定义了一个名为TopicForm的类,它继承了forms.ModelForm。django
最简单的ModelForm版本只包含一个内嵌的Meta类,它告诉Django根据哪一个模型建立表单,以及在表单中包含哪些字段。咱们根据模型Topic建立一个表单,该表单只包含字段text,且让Django不要为字段text生成标签。编程
new_topic
:# vim learning_logs/urls.py
"""定义learning_logs的URL模式"""from django.urls import path, re_pathfrom . import views app_name='learning_logs'urlpatterns = [ # 主页 path('', views.index, name='index'), # 显示全部的主题 path('topics/', views.topics, name='topics'), # 特定主题的详细页面 re_path(r'^topics/(?P\d+)/$', views.topic, name='topic'), # 用于添加新主题的页面 path('new_topic/', views.new_topic, name='new_topic'),]
这个URL模式会将请求交给视图函数new_topic()
。vim
new_topic()
:函数new_topic()
须要处理两种情形:刚进入new_topic
网页,它应显示一个空表单;对提交的表单数据进行处理,并将用户重定向到网页topics。浏览器
# vim learning_logs/views.py
from django.shortcuts import renderfrom django.http import HttpResponseRedirectfrom django.urls import reversefrom .models import Topicfrom .forms import TopicFormdef index(request): """学习笔记的主页""" return render(request, 'learning_logs/index.html')def topics(request): """显示全部主题""" topics = Topic.objects.order_by('date_added') context = {'topics': topics} return render(request, 'learning_logs/topics.html', context)def topic(request, topic_id): """显示一个主题及其详细页面""" topic = Topic.objects.get(id=topic_id) entries = topic.entry_set.order_by('-date_added') context = {'topic': topic, 'entries': entries} return render(request, 'learning_logs/topic.html', context)def new_topic(request): """添加新主题""" if request.method != 'POST': # 未提交数据:建立一个新表单 form = TopicForm() else: # POST提交的数据,对数据进行处理 form = TopicForm(request.POST) if form.is_valid(): form.save() return HttpResponseRedirect(reverse('learning_logs:topics')) context = {'form': form} return render(request, 'learning_logs/new_topic.html', context)
导入了HttpResponseRedirect
类和刚才建立的表单TopicForm
,用户提交主题后咱们将使用HttpResponseRedirect
类将用户重定向到网页topics。函数reverse()
根据指定的URL模型肯定URL,这意味着Django将在页面被请求时生成URL。服务器
首先判断请求方法是不是POST——若是请求方法不是POST,返回一个空表单;若是请求方法是POST,将使用用户输入的数据(存储在request.POST中)建立一个TopicForm
实例,这样对象form将包含用户提交的信息。
而后要将提交的信息保存到数据库,必须先经过检查肯定它们是有效的。函数is_valid()
核实用户填写了全部必不可少的字段(表单字段默认都是必不可少的),且输入的数据与要求的字段类型一致。若是全部字段都有效,咱们就调用save()
将表单中的数据写入数据库。
最后使用reverse()
获取页面topics的URL,并将其传递给HttpResponseRedirect()
,后者将用户的浏览器重定向到页面topics。在页面topics中,用户将在主题列表中看到他刚输入的主题。
建立Web应用程序时,将用到的两种主要请求类型是GET请求和POST请求。对于只是从服务器读取数据的页面,使用GET请求;在用户须要经过表单提交信息时,一般使用POST请求。处理全部表单时,咱们都将指定使用POST方法。
函数new_topic()
将请求对象做为参数。用户初次请求该网页时,其浏览器将发送GET请求;用户填写并提交表单时,其浏览器将发送POST请求。根据请求的类型,咱们能够肯定用户请求的是空表单(GET请求)仍是要求对填写好的表单进行处理(POST请求)。
new_topic
:# vim learning_logs/templates/learning_logs/new_topic.html
{% extends "learning_logs/base.html" %} {% block content %} <p>Add a new topic:p> <form action="{% url 'learning_logs:new_topic' %}" method='post'> {% csrf_token %} {{ form.as_p }} <button name="submit">add topicbutton> form> {% endblock content %}
继承了base.html,而后定义了一个HTML表单。实参action告诉服务器将提交的表单数据发送到哪里,这里咱们将它发回给视图函数
new_topic()
。实参method让浏览器以POST请求的方式提交数据。Django使用模板标签
{% csrf_token %}
来防止***者利用表单来得到对服务器未经受权的访问(这种***被称为跨站
请求伪造)。接着显示表单,只需包含模板变量{{ form.as_p }}
,就可以让Django自动建立显示表单所需的所有字段。修
饰符as_p
让Django以段落格式渲染全部表单元素。
new_topic
:# vim learning_logs/templates/learning_logs/topics.html
{% extends "learning_logs/base.html" %} {% block content %} <p>Topicsp> <ul> {% for topic in topics %} <li> <a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}a> li> {% empty %} <li>No topics have been added yet.li> {% endfor %} ul> <a href="{% url 'learning_logs:new_topic' %}">Add a new topic:a>{% endblock content %}
如今已经能够添加新主题了,可是还不能添加新条目,再次定义URL,编写视图函数和模板,并连接到添加新条目的网页。
建立一个与模型Entry相关联的表单。
# vim learning_logs/forms.py
from django import formsfrom .models import Topic, Entryclass TopicForm(forms.ModelForm): class Meta: model = Topic fields = ['text'] labels = {'text': ''}class EntryForm(forms.ModelForm): class Meta: model = Entry fields = ['text'] labels = {'text': ''} wgdgets = {'text': forms.Textarea(attrs={'cols': 80})}
除了导入Topic外,还须要导入Entry。新类EntryForm继承了forms.ModelForm,它包含的Meta类指出了表单基于的模型以及要在表单中包含哪些字段。
后面定义了属性widgets
,小部件(widget)是一个HTML表单元素,如单行文本框、多行文本区域或下拉列表。经过设置属性widgets
,可覆盖Django选择的默认小部件。经过让Django使用forms.Textarea,咱们定制了字段'text'
的输入小部件,将文本区域的宽度设置为80列,而不是默认的40列。
new_entry
:在用于添加新条目的页面的URL模式中,须要包含实参topic_id ,由于条目必须与特定的主题相关联。
# vim learning_logs/urls.py
"""定义learning_logs的URL模式"""from django.urls import path, re_pathfrom . import views app_name='learning_logs'urlpatterns = [ # 主页 path('', views.index, name='index'), # 显示全部的主题 path('topics/', views.topics, name='topics'), # 特定主题的详细页面 re_path(r'^topics/(?P\d+)/$', views.topic, name='topic'), # 用于添加新主题的页面 path('new_topic/', views.new_topic, name='new_topic'), # 用于添加新条目的页面 re_path(r'^new_entry/(?P\d+)/$', views.new_entry, name='new_entry'),]
这个URL模式与形式为http://localhost:8000/new_entry/id/
的URL匹配,其中id是一个与主题ID匹配的数字。代码(?P
捕获一个数字值,并将其存储在变量topic_id
中。请求的URL与这个模式匹配时,Django将请求和主题ID发送给函数new_entry()
。
new_entry()
:视图函数new_entry()
与视图函数new_topic()
比较相似。
# vim learning_logs/views.py
from django.shortcuts import renderfrom django.http import HttpResponseRedirectfrom django.urls import reversefrom .models import Topicfrom .forms import TopicForm, EntryFormdef index(request): """学习笔记的主页""" return render(request, 'learning_logs/index.html')def topics(request): """显示全部主题""" topics = Topic.objects.order_by('date_added') context = {'topics': topics} return render(request, 'learning_logs/topics.html', context)def topic(request, topic_id): """显示一个主题及其详细页面""" topic = Topic.objects.get(id=topic_id) entries = topic.entry_set.order_by('-date_added') context = {'topic': topic, 'entries': entries} return render(request, 'learning_logs/topic.html', context)def new_topic(request): """添加新主题""" if request.method != 'POST': # 未提交数据:建立一个新表单 form = TopicForm() else: # POST提交的数据,对数据进行处理 form = TopicForm(request.POST) if form.is_valid(): form.save() return HttpResponseRedirect(reverse('learning_logs:topics')) context = {'form': form} return render(request, 'learning_logs/new_topic.html', context)def new_entry(request, topic_id): """在特定的主题中添加新条目""" topic = Topic.objects.get(id=topic_id) if request.method != 'POST': # 未提交数据,建立一个空表单 form = EntryForm() else: # POST提交的数据,对数据进行处理 form = EntryForm(data=request.POST) if form.is_valid(): new_entry = form.save(commit=False) new_entry.topic = topic new_entry.save() return HttpResponseRedirect(reverse('learning_logs:topic', args=[topic_id])) context = {'topic': topic, 'form': form} return render(request, 'learning_logs/new_entry.html', context)
首先导入了刚建立的EntryForm,new_entry()
的定义包含形参topic_id
,用于存储从URL中得到的值。渲染页面以及处理表单数据时,都须要知道针对的是哪一个主题,所以咱们使用topic_id
来得到正确的主题。
而后仍是检查请求方法是POST仍是GET。若是是GET请求,将建立一个空的EntryForm实例。若是请求方法为POST,咱们就对数据进行处理:建立一个EntryForm实例,使用request对象中的POST数据来填充它;再检查表单是否有效,若是有效则设置条目对象的属性topic
,再将条目对象保存到数据库。
在调用save()
时,传递了实参commit=False
,让Django建立一个新的条目对象,并将其存储到new_entry
中,但不将它保存到数据库中。咱们将new_entry
的属性topic
设置为在这个函数开头从数据库中获取的主题,而后调用save()
,且不指定任何实参。这将把条目保存到数据库,并将其与正确的主题相关联。
最后,将用户重定向到显示相关主题的页面。调用reverse()
时,须要提供两个实参:要根据它来生成URL的URL模式的名称;列表args ,其中包含要包含在URL中的全部实参。
new_entry
:模板new_entry
与模板new_topic
也比较相似。
# vim learning_logs/templates/learning_logs/new_entry.html
{% extends "learning_logs/base.html" %} {% block content %} <p><a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}a>p> <p>Add a new entry:p> <form action="{% url 'learning_logs:new_entry' topic.id %}" method='post'> {% csrf_token %} {{ form.as_p }} <button name='submit'>add entrybutton> form> {% endblock content %}
首先继承base.html。接着在页面顶端显示了主题,让用户知道是在哪一个主题中添加条目;该主题名也是一个连接,可用于返回该主题的主页面。
表单的实参action 包含URL中的
topic_id
值,让视图函数可以将新条目关联到正确的主题。除此以外,这个模板与模板new_topic.html彻底相同。
new_entry
:# vim learning_logs/templates/learning_logs/topic.html
{% extends 'learning_logs/base.html' %} {% block content %} <p>Topic: {{ topic }}p> <p>Entries:p> <p> <a href="{% url 'learning_logs:new_entry' topic.id %}">add new entrya> p> <ul> {% for entry in entries %} <li> <p>{{ entry.date_added|date:'M d, Y H:i' }}p> <p>{{ entry.text|linebreaks }}p> li> {% empty %} <li> There are no entries for this topic yet. li> {% endfor %} ul>{% endblock content %}
在显示条目前添加连接,由于在这种页面中,执行的最多见的操做是添加新条目。
下面来建立一个页面,让用户能够编辑既有的条目。
edit_entry
:# vim learning_logs/urls.py
"""定义learning_logs的URL模式"""from django.urls import path, re_pathfrom . import views app_name='learning_logs'urlpatterns = [ # 主页 path('', views.index, name='index'), # 显示全部的主题 path('topics/', views.topics, name='topics'), # 特定主题的详细页面 re_path(r'^topics/(?P\d+)/$', views.topic, name='topic'), # 用于添加新主题的页面 path('new_topic/', views.new_topic, name='new_topic'), # 用于添加新条目的页面 re_path(r'^new_entry/(?P\d+)/$', views.new_entry, name='new_entry'), # 用于编辑条目的页面 re_path(r'^edit_entry/(?P\d+)/$', views.edit_entry, name='edit_entry'),]
在URL(如http://localhost:8000/edit_entry/1/
)中传递的ID存储在形参entry_id
中。这个URL模式将预期匹配的请求发送给视图函数edit_entry()
。
edit_entry()
:页面edit_entry
收到GET请求时,edit_entry()
将返回一个表单,让用户可以对条目进行编辑。该页面收到POST请求(条目文本通过修订)时,它将修改后的文本保存到数据库中。
# vim learning_logs/views.py
from django.shortcuts import renderfrom django.http import HttpResponseRedirectfrom django.urls import reversefrom .models import Topic, Entryfrom .forms import TopicForm, EntryFormdef index(request): """学习笔记的主页""" return render(request, 'learning_logs/index.html')def topics(request): """显示全部主题""" topics = Topic.objects.order_by('date_added') context = {'topics': topics} return render(request, 'learning_logs/topics.html', context)def topic(request, topic_id): """显示一个主题及其详细页面""" topic = Topic.objects.get(id=topic_id) entries = topic.entry_set.order_by('-date_added') context = {'topic': topic, 'entries': entries} return render(request, 'learning_logs/topic.html', context)def new_topic(request): """添加新主题""" if request.method != 'POST': # 未提交数据:建立一个新表单 form = TopicForm() else: # POST提交的数据,对数据进行处理 form = TopicForm(request.POST) if form.is_valid(): form.save() return HttpResponseRedirect(reverse('learning_logs:topics')) context = {'form': form} return render(request, 'learning_logs/new_topic.html', context)def new_entry(request, topic_id): """在特定的主题中添加新条目""" topic = Topic.objects.get(id=topic_id) if request.method != 'POST': # 未提交数据,建立一个空表单 form = EntryForm() else: # POST提交的数据,对数据进行处理 form = EntryForm(data=request.POST) if form.is_valid(): new_entry = form.save(commit=False) new_entry.topic = topic new_entry.save() return HttpResponseRedirect(reverse('learning_logs:topic', args=[topic_id])) context = {'topic': topic, 'form': form} return render(request, 'learning_logs/new_entry.html', context)def edit_entry(request, entry_id): """编辑既有条目""" entry = Entry.objects.get(id=entry_id) topic = entry.topic if request.method != 'POST': # 初次请求,使用当前条目填充表单 form = EntryForm(instance=entry) else: # POST提交的数据,对数据进行处理 form = EntryForm(instance=entry, data=request.POST) if form.is_valid(): form.save() return HttpResponseRedirect(reverse('learning_logs:topic', args=[topic.id])) context = {'entry': entry, 'topic': topic, 'form': form} return render(request, 'learning_logs/new_entry.html', context)
首先导入模型Entry 。接着获取用户要修改的条目对象,以及与该条目相关联的主题。在请求方法为GET时,咱们使用实参instance=entry
建立一个EntryForm实例。该实参让Django建立一个表单,并使用既有条目对象中的信息填充它。用户将看到既有的数据,并可以编辑它们。
处理POST请求时,传递实参instance=entry
和data=request.POST
,让Django根据既有条目对象建立一个表单实例,并根据request.POST中的相关数据对其进行修改。而后检查表单是否有效,若是有效就调用save()
且不指定任何实参。接下来重定向到显示条目所属主题的页面,用户将在其中看到其编辑的条目的新版本。
edit_entry
:# vim learning_logs/templates/learning_logs/edit_entry.html
{% extends "learning_logs/base.html" %} {% block content %} <p><a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}a>p> <p>Edit entry:p> <form action="{% url 'learning_logs:edit_entry' entry.id %}" method='post'> {% csrf_token %} {{ form.as_p }} <button name="submit">save changesbutton> form>{% endblock content %}
edit_entry
:# vim learning_logs/templates/learning_logs/topic.html
{% extends 'learning_logs/base.html' %} {% block content %} <p>Topic: {{ topic }}p> <p>Entries:p> <p> <a href="{% url 'learning_logs:new_entry' topic.id %}">add new entrya> p> <ul> {% for entry in entries %} <li> <p>{{ entry.date_added|date:'M d, Y H:i' }}p> <p>{{ entry.text|linebreaks }}p> <p> <a href="{% url 'learning_logs:edit_entry' entry.id %}">edit entrya> p> li> {% empty %} <li> There are no entries for this topic yet. li> {% endfor %} ul>{% endblock content %}
将编辑连接放在每一个条目的日期和文本后面。在循环中,使用模板标签
{% url %}
根据URL模式edit_entry
和当前条目的ID属性entry.id
来肯定URL。连接文本为"edit entry"
,它出如今页面中每一个条目的后面。
应用路径:users
接下来创建一个用户注册和身份验证系统,让用户可以注册帐户,进而登陆和注销。
# python manage.py startapp users# lsdb.sqlite3 learning_log learning_logs ll_env manage.py users# ls users/admin.py apps.py __init__.py migrations models.py tests.py views.py
能够看到, users目录的结构与应用程序learning_logs相同。
# vim learning_log/settings.pyINSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', # 个人应用程序 'learning_logs', 'users', #增长这行]
# vim learning_log/urls.py
from django.contrib import adminfrom django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('users/', include('users.urls', namespace='users')), #增长这行 path('', include('learning_logs.urls', namespace='learning_logs')),]
一样的,这里保存完文件以后会报错ModuleNotFoundError: No module named 'users.urls'
,不用理会,继续往下作。
如今来实现登陆页面的功能,可使用Django提供的默认登陆视图。
# vim users/urls.py
"""定义users的URL模式"""from django.urls import path, re_pathfrom django.contrib.auth.views import LoginViewfrom . import views app_name='users'urlpatterns = [ # 登陆主页 re_path(r'^login/$', LoginView.as_view(template_name='users/login.html'), name='login'),]
login.html
:# mkdir -p users/templates/users# vim users/templates/users/login.html
{% extends "learning_logs/base.html" %} {% block content %} {% if form.errors %} <p>Your username and password didn't match. Please try again.p> {% endif %} <form method="post" action="{% url 'users:login' %}"> {% csrf_token %} {{ form.as_p }} <button name="submit">log inbutton> <input type="hidden" name="next" value="{% url 'learning_logs:index' %}" /> form> {% endblock content %}
先继承base.html,请注意,一个应用程序中的模板可继承另外一个应用程序中的模板。
若是表单的errors属性被设置,咱们就显示一条错误消息,指出输入的用户名—密码对与数据库中存储的任何用户名—密码对都不匹配。
咱们要让登陆视图处理表单,所以将实参
action
设置为登陆页面的URL。登陆视图将一个表单发送给模板,在模板中,咱们显示这个表单并添加一个提交按钮。后面包含了一个隐藏的表单元素’next
’,其中的实参value
告诉Django在用户成功登陆后将其重定向到主页。
# vim learning_logs/templates/learning_logs/base.html
<p> <a href="{% url 'learning_logs:index' %}">Learning Loga> - <a href="{% url 'learning_logs:topics' %}">Topicsa> - {% if user.is_authenticated %} Hello, {{ user.username }}. {% else %} <a href="{% url 'users:login' %}">log ina> {% endif %}p>{% block content %}{% endblock content %}
在Django身份验证系统中,每一个模板均可使用变量
user
,该变量有一个is_authenticated
属性:若是用户已登陆,该属性将为True,不然为False。在这里向已登陆的用户显示一条问候语。对于已经过身份验证的用户设置了属性
username
,咱们使用这个属性来个性化问候语,让用户知道他已经登陆。而对于还未经过身份验证的用户,咱们再显示一个到登陆页面的连接。
访问192.168.30.128:8000/users/login/
。若是以前使用超级用户登陆的,注销以后再访问。
如今须要提供一个让用户注销的途径。咱们不须要建立用于注销的页面,而让用户只需单击一个连接就能注销并返回到主页。为此,咱们将为注销连接定义一个URL模式,编写一个视图函数,并在base.html中添加一个注销连接。
# vim users/urls.py
"""定义users的URL模式"""from django.urls import path, re_pathfrom django.contrib.auth.views import LoginViewfrom . import views app_name='users'urlpatterns = [ # 登陆主页 re_path(r'^login/$', LoginView.as_view(template_name='users/login.html'), name='login'), # 注销 re_path(r'^logout/$', views.logout_view, name='logout'),]
这个URL模式将请求发送给函数logout_view()
,要将其与咱们下面在其中调用的函数logout()
区分开来。
logout_view()
:# vim users/views.py
from django.shortcuts import renderfrom django.http import HttpResponseRedirectfrom django.urls import reversefrom django.contrib.auth import logoutdef logout_view(request): """注销用户""" logout(request) return HttpResponseRedirect(reverse('learning_logs:index'))
先从django.contrib.auth中导入了函数logout()
;而后调用了函数logout()
,它要求将request对象做为实参;最后重定向到主页。
如今须要在base.html中添加注销连接,让每一个页面都包含它;将它放在标签{% if user.is_authenticated %}
中,使得仅当用户登陆后才能看到它。
# vim learning_logs/templates/learning_logs/base.html
<p> <a href="{% url 'learning_logs:index' %}">Learning Loga> - <a href="{% url 'learning_logs:topics' %}">Topicsa> - {% if user.is_authenticated %} Hello, {{ user.username }}. <a href="{% url 'users:logout' %}">log outa> {% else %} <a href="{% url 'users:login' %}">log ina> {% endif %}p>{% block content %}{% endblock content %}
使用超级用户登陆
下面使用Django提供的表单UserCreationForm来建立一个让新用户可以注册的页面。
# vim users/urls.py
"""定义users的URL模式"""from django.urls import path, re_pathfrom django.contrib.auth.views import LoginViewfrom . import views app_name='users'urlpatterns = [ # 登陆主页 re_path(r'^login/$', LoginView.as_view(template_name='users/login.html'), name='login'), # 注销 re_path(r'^logout/$', views.logout_view, name='logout'), # 注册页面 re_path(r'^register/$', views.register, name='register'),]
这个模式与URLhttp://localhost:8000/users/register/
匹配,并将请求发送给咱们即将编写的函数register()
。
register()
:在注册页面首次被请求时,视图函数register()
须要显示一个空的注册表单,并在用户提交填写好的注册表单时对其进行处理。若是注册成功,这个函数还需让用户自动登陆。
# vim users/views.py
from django.shortcuts import renderfrom django.http import HttpResponseRedirectfrom django.urls import reversefrom django.contrib.auth import login, logout, authenfrom django.contrib.auth.forms import UserCreationFormdef logout_view(request): """注销用户""" logout(request) return HttpResponseRedirect(reverse('learning_logs:index')) def register(request): """注册新用户""" if request.method != 'POST': # 显示空的注册表单 form = UserCreationForm() else: # 处理填写好的表单 form = UserCreationForm(data=request.POST) if form.is_valid(): new_user = form.save() # 让用户自动登陆,再重定向到主页 authenticated_user = authenticate(username=new_user.username, password = request.POST['password1']) login(request, authenticated_user) return HttpResponseRedirect(reverse('learning_logs:index')) context = {'form': form} return render(request, 'users/register.html', context)
首先导入了函数login()
和authenticate()
,以便在用户正确地填写了注册信息时让其自动登陆。还导入了默认表单UserCreationForm
。在函数register()
中,咱们检查要响应的是不是POST请求。若是不是,就建立一个UserCreationForm实例,且不给它提供任何初始数据。
若是响应的是POST请求,咱们就根据提交的数据建立一个UserCreationForm实例,并检查这些数据是否有效:就这里而言,是用户名未包含非法字符,输入的两个密码相同,以及用户没有试图作恶意的事情。若是提交的数据有效,咱们就调用表单的方法save()
,将用户名和密码的散列值保存到数据库中。方法save()
返回新建立的用户对象,咱们将其存储在new_user
中。
保存用户的信息后,咱们让用户自动登陆,这包含两个步骤。首先,咱们调用authenticate()
,并将实参new_user.username
和密码传递给它。用户注册时,被要求输入密码两次;因为表单是有效的,咱们知道输入的这两个密码是相同的,所以可使用其中任何一个。在这里,咱们从表单的POST数据中获取与键’password1’相关联的值。若是用户名和密码无误,方法authenticate()
将返回一个经过了身份验证的用户对象,而咱们将其存储在变量authenticated_user
中。
接下来,咱们调用函数login()
,并将对象request
和authenticated_user
传递给它,这将为新用户建立有效的会话。最后,咱们将用户重定向到主页,其页眉中显示了一条个性化的问候语,让用户知道注册成功了。
register.html
:# vim users/templates/users/register.html
{% extends "learning_logs/base.html" %} {% block content %} <form method="post" action="{% url 'users:register' %}"> {% csrf_token %} {{ form.as_p }} <button name="submit">registerbutton> <input type="hidden" name="next" value="{% url 'learning_logs:index' %}" /> form> {% endblock content %}
# vim learning_logs/templates/learning_logs/base.html
<p> <a href="{% url 'learning_logs:index' %}">Learning Loga> - <a href="{% url 'learning_logs:topics' %}">Topicsa> - {% if user.is_authenticated %} Hello, {{ user.username }}. <a href="{% url 'users:logout' %}">log outa> {% else %} <a href="{% url 'users:register' %}">registera> - <a href="{% url 'users:login' %}">log ina> {% endif %}p>{% block content %}{% endblock content %}
如今,已登陆的用户看到的是个性化的问候语和注销连接,而未登陆的用户看到的是注册连接和登陆连接。
须要注意的是,这里的注册系统容许用户建立任意数量的帐户。实际状况下的系统会要求用户确认其身份:发送一封确认邮件或者用手机验证码,用户回复后其帐户才生效。经过这样作,系统生成的垃圾帐户将比这里使用的简单系统少。
用户应该可以输入其专有的数据,所以咱们将建立一个系统,肯定各项数据所属的用户,再限制对页面的访问,让用户只能使用本身的数据。
下面将修改模型Topic ,让每一个主题都归属于特定用户。这也将影响条目,由于每一个条目都属于特定的主题。
Django提供了装饰器@login_required
,让你可以轻松地实现这样的目标:对于某些页面,只容许已登陆的用户访问它们。装饰器(decorator)是放在函数定义前面的指令,Python在函数运行前,根据它来修改函数代码的行为。
# vim learning_logs/views.py
from django.shortcuts import renderfrom django.http import HttpResponseRedirectfrom django.urls import reversefrom django.contrib.auth.decorators import login_requiredfrom .models import Topic, Entryfrom .forms import TopicForm, EntryFormdef index(request): """学习笔记的主页""" return render(request, 'learning_logs/index.html')@login_requireddef topics(request): """显示全部主题""" topics = Topic.objects.order_by('date_added') context = {'topics': topics} return render(request, 'learning_logs/topics.html', context)def topic(request, topic_id): """显示一个主题及其详细页面""" topic = Topic.objects.get(id=topic_id) entries = topic.entry_set.order_by('-date_added') context = {'topic': topic, 'entries': entries} return render(request, 'learning_logs/topic.html', context)def new_topic(request): """添加新主题""" if request.method != 'POST': # 未提交数据:建立一个新表单 form = TopicForm() else: # POST提交的数据,对数据进行处理 form = TopicForm(request.POST) if form.is_valid(): form.save() return HttpResponseRedirect(reverse('learning_logs:topics')) context = {'form': form} return render(request, 'learning_logs/new_topic.html', context)def new_entry(request, topic_id): """在特定的主题中添加新条目""" topic = Topic.objects.get(id=topic_id) if request.method != 'POST': # 未提交数据,建立一个空表单 form = EntryForm() else: # POST提交的数据,对数据进行处理 form = EntryForm(data=request.POST) if form.is_valid(): new_entry = form.save(commit=False) new_entry.topic = topic new_entry.save() return HttpResponseRedirect(reverse('learning_logs:topic', args=[topic_id])) context = {'topic': topic, 'form': form} return render(request, 'learning_logs/new_entry.html', context)def edit_entry(request, entry_id): """编辑既有条目""" entry = Entry.objects.get(id=entry_id) topic = entry.topic if request.method != 'POST': # 初次请求,使用当前条目填充表单 form = EntryForm(instance=entry) else: # POST提交的数据,对数据进行处理 form = EntryForm(instance=entry, data=request.POST) if form.is_valid(): form.save() return HttpResponseRedirect(reverse('learning_logs:topic', args=[topic.id])) context = {'entry': entry, 'topic': topic, 'form': form} return render(request, 'learning_logs/new_entry.html', context)
首先导入函数login_required()
,将login_required()
做为装饰器用于视图函数topics()
——在它前面加上符号@
和login_required
,让Python在运行topics()
的代码前先运行login_required()
的代码。
login_required()
的代码检查用户是否已登陆,仅当用户已登陆时,Django才运行topics()
的代码。若是用户未登陆,就重定向到登陆页面。
为了实现未登陆的重定向,须要修改settings.py。
# vim learning_log/settings.py #末尾添加# 个人设置LOGIN_URL = '/users/login/'
能够看到,如今直接点击topics
会直接跳转到登陆页。
在项目“学习笔记”中,咱们将不限制主页、注册页面和注销页面的访问,同时限制其它全部页面的访问。
# vim learning_logs/views.py
from django.shortcuts import renderfrom django.http import HttpResponseRedirectfrom django.urls import reversefrom django.contrib.auth.decorators import login_requiredfrom .models import Topic, Entryfrom .forms import TopicForm, EntryFormdef index(request): """学习笔记的主页""" return render(request, 'learning_logs/index.html')@login_requireddef topics(request): """显示全部主题""" topics = Topic.objects.order_by('date_added') context = {'topics': topics} return render(request, 'learning_logs/topics.html', context)@login_requireddef topic(request, topic_id): """显示一个主题及其详细页面""" topic = Topic.objects.get(id=topic_id) entries = topic.entry_set.order_by('-date_added') context = {'topic': topic, 'entries': entries} return render(request, 'learning_logs/topic.html', context)@login_requireddef new_topic(request): """添加新主题""" if request.method != 'POST': # 未提交数据:建立一个新表单 form = TopicForm() else: # POST提交的数据,对数据进行处理 form = TopicForm(request.POST) if form.is_valid(): form.save() return HttpResponseRedirect(reverse('learning_logs:topics')) context = {'form': form} return render(request, 'learning_logs/new_topic.html', context)@login_requireddef new_entry(request, topic_id): """在特定的主题中添加新条目""" topic = Topic.objects.get(id=topic_id) if request.method != 'POST': # 未提交数据,建立一个空表单 form = EntryForm() else: # POST提交的数据,对数据进行处理 form = EntryForm(data=request.POST) if form.is_valid(): new_entry = form.save(commit=False) new_entry.topic = topic new_entry.save() return HttpResponseRedirect(reverse('learning_logs:topic', args=[topic_id])) context = {'topic': topic, 'form': form} return render(request, 'learning_logs/new_entry.html', context)@login_requireddef edit_entry(request, entry_id): """编辑既有条目""" entry = Entry.objects.get(id=entry_id) topic = entry.topic if request.method != 'POST': # 初次请求,使用当前条目填充表单 form = EntryForm(instance=entry) else: # POST提交的数据,对数据进行处理 form = EntryForm(instance=entry, data=request.POST) if form.is_valid(): form.save() return HttpResponseRedirect(reverse('learning_logs:topic', args=[topic.id])) context = {'entry': entry, 'topic': topic, 'form': form} return render(request, 'learning_logs/new_entry.html', context)
若是你在未登陆的状况下尝试访问这些页面,将被重定向到登陆页面。此外,你还不能单击到new_topic等页面的连接,若是你输入URL http://192.168.30.128:8000/new_topic/
,将重定向到登陆页面。对于全部与私有用户数据相关的URL,都应限制对它们的访问。
如今将数据关联到提交它们的用户。咱们只需将最高层的数据关联到用户,这样更低层的数据将自动关联到用户。在项目“学习笔记”中,应用程序的最高层数据是主题,而全部条目都与特定主题相关联。只要每一个主题都归属于特定用户,咱们就能肯定数据库中每一个条目的全部者。
# vim learning_logs/models.py
from django.db import modelsfrom django.contrib.auth.models import Userclass Topic(models.Model): """用户学习的主题""" text = models.CharField(max_length=200) date_added = models.DateTimeField(auto_now_add=True) owner = models.ForeignKey(User, on_delete=models.CASCADE) def __str__(self): """返回模型的字符串表示""" return self.text class Entry(models.Model): """学到的有关某个主题的具体知识""" topic = models.ForeignKey(Topic, on_delete=models.CASCADE) text = models.TextField() date_added = models.DateTimeField(auto_now_add=True) class Meta: verbose_name_plural = 'entries' def __str__(self): """返回模型的字符串表示""" return self.text[:50] + "..."
首先导入了django.contrib.auth
中的模型User,而后在Topic中添加了字段owner,它创建到模型User的外键关系。
迁移数据库时,Django将对数据库进行修改,使其可以存储主题和用户之间的关联。为执行迁移,Django须要知道该将各个既有主题关联到哪一个用户。最简单的办法是,将既有主题都关联到同一个用户,如超级用户。为此,咱们须要知道用户的ID。
# python manage.py shell>>> from django.contrib.auth.models import User>>> User.objects.all()<QuerySet [<User: ll_admin>, <User: lzx>]>>>> for user in User.objects.all():... print(user.username, user.id)... ll_admin 1 lzx 2
# python manage.py makemigrations learning_logsSelect an option: 1 #选择第1个选项——如今提供默认值>>> 1 #输入用户IDMigrations for 'learning_logs': learning_logs/migrations/0003_topic_owner.py - Add field owner to topic
将全部既有主题都关联到管理用户ll_admin,我输入了用户ID值1。并不是必须使用超级用户,也可以使用已建立的任何用户的ID。
# python manage.py migrate #执行迁移# python manage.py shell>>> from learning_logs.models import Topic>>> for topic in Topic.objects.all():... print(topic, topic.owner)... Chess ll_admin Rock Climbing ll_admin
能够看到,每一个主题的全部者是超级用户ll_admin。
注意:也能够重置数据库而不是迁移它,但若是这样作,既有的数据都将丢失。若是确实想要一个新的数据库,能够执行命令
python manage.py flush
,这会重建数据库的结构。若是你这样作,就须要从新建立超级用户,且原来的全部数据都将丢失。
views.py
:# vim learning_logs/views.py
@login_requireddef topics(request): """显示全部主题""" topics = Topic.objects.filter(owner=request.user).order_by('date_added') #修改 context = {'topics': topics} return render(request, 'learning_logs/topics.html', context)
用户登陆后,request对象将有一个user
属性,这个属性存储了有关该用户的信息。代码Topic.objects.filter(owner=request.user)
让Django只从数据库中获取owner
属性为当前用户的Topic对象。
要查看结果,以全部既有主题关联到的用户的身份登陆(这里咱们是ll_admin)并访问topics页面,你将看到全部的主题。而后,注销并以另外一个用户的身份登陆,topics页面将不会列出任何主题。
接下来限制对单个主题的页面的访问。
views.py
:# vim learning_logs/views.py
from django.http import HttpResponseRedirect, Http404 #导入Http404@login_requireddef topic(request, topic_id): """显示一个主题及其详细页面""" topic = Topic.objects.get(id=topic_id) # 确认请求的主题属于当前用户 if topic.owner != request.user: #修改 raise Http404 entries = topic.entry_set.order_by('-date_added') context = {'topic': topic, 'entries': entries} return render(request, 'learning_logs/topic.html', context)
服务器上没有请求的资源时,标准的作法是返回404响应。首先导入了异常Http404,并在用户请求它不能查看的主题时引起这个异常。收到主题请求后,在渲染网页前检查该主题是否属于当前登陆的用户。若是请求的主题不归当前用户全部,咱们就引起Http404异常,让Django返回一个404错误页面。
edit_entry
页面edit_entry
的URL为http://192.168.30.128:8000/edit_entry/entry_id/
,其中entry_id
是一个数字。下面来保护这个页面,禁止用户经过输入相似于前面的URL来访问其余用户的条目。
views.py
:# vim learning_logs/views.py
@login_requireddef edit_entry(request, entry_id): """编辑既有条目""" entry = Entry.objects.get(id=entry_id) topic = entry.topic if topic.owner != request.user: #修改 raise Http404 if request.method != 'POST': # 初次请求,使用当前条目填充表单 form = EntryForm(instance=entry) else: # POST提交的数据,对数据进行处理 form = EntryForm(instance=entry, data=request.POST) if form.is_valid(): form.save() return HttpResponseRedirect(reverse('learning_logs:topic', args=[topic.id])) context = {'entry': entry, 'topic': topic, 'form': form} return render(request, 'learning_logs/new_entry.html', context)
获取指定的条目以及与之相关联的主题,而后检查主题的全部者是不是当前登陆的用户,若是不是,就引起Http404异常。
当前,用于添加新主题的页面存在问题。若是你尝试添加新主题,将看到错误消息IntegrityError,指出learning_logs_topic.user_id
不能为NULL 。Django的意思是说,建立新主题时,你必须指定其owner字段的值。
views.py
:# vim learning_logs/views.py
@login_requireddef new_topic(request): """添加新主题""" if request.method != 'POST': # 未提交数据:建立一个新表单 form = TopicForm() else: # POST提交的数据,对数据进行处理 form = TopicForm(request.POST) if form.is_valid(): #修改 new_topic = form.save(commit=False) new_topic.owner = request.user new_topic.save() return HttpResponseRedirect(reverse('learning_logs:topics')) context = {'form': form} return render(request, 'learning_logs/new_topic.html', context)
首先调用form.save()
,并传递实参commit=False
,由于咱们先修改新主题,再将其保存到数据库中。
接下来,将新主题的owner
属性设置为当前用户。最后,对刚定义的主题实例调用save()
。如今主题包含全部必不可少的数据,将被成功地保存。
如今,这个项目容许任何用户注册,而每一个用户想添加多少新主题均可以。但每一个用户都只能访问本身的数据,不管是查看数据、输入新数据仍是修改旧数据时都如此。