Web应用程序的核心是让任何用户都能够注册账户并能够使用它,不管用户身处何方

1、让用户能够输入数据

建立用于创建用户的身份验证系统之前,我们先来添加几个页面,让用户能够输入数据。当前,只有超级用户能够通过管理网站输入数据。我们不想让用户与管理网站交互,因此我们将使用Django的表单创建工具来创建让用户能够输入数据的页面

1.添加新主题:

首先让用户能够添加新主题。创建基于表单的页面的方法几乎与前面创建网页一样:定义一个URL,编写一个视图函数并编写一个模板。主要差别是,需要导入包含表单的模块forms.py

用于添加主题的表单

在Django中,创建表单最简单的方法是使用ModelForm,它根据我们定义的模板中的信息自动创建表单。

首先,创建一个名为在应用程序目录下创建forms.py



我们首先导入了模板forms以及要使用的模板Topic,然后定义了一个名为TopicForm的类,继承forms.ModelForm。最简单的ModelForm只包含一个内嵌的Meta类,它告诉Django根据哪个模型创建表单,以及在表单中包含哪些字段。上例中,我们根据Topic创建一个表单,该表单包含text字段,且不用为text字段生成标签

URL模式new_topic

当用户要添加新主题时,我们将切换到http://localhost:8000/new_topic/ ,我们在learning_logs/urls.py中定义网页new_topic的URL模式



URL模式将请求交给视图函数new_topic().接下来编写这个函数

视图函数new_topic()

函数new_topic()需要处理两种情形:刚进入new_topic网页(在这种情况下,他应该显示一个空表单);对提交的表单数据进行处理,并将用户重定向到网页topics

首先我们要导入HttpResponseRedirect类,用户提交主题后,使用这个类将用户重定向到网页topics。函数reverse()根据指定的URL模型确定URL,这意味着Django将在页面被请求时生成URL。我们还导入来了刚才创建的表单TopicForm

  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. from django.shortcuts import render
  4. from django.http import HttpResponseRedirect
  5. from django.core.urlresolvers import reverse
  6. from .models import Topic
  7. from .forms import TopicForm
  8. # Create your views here.
  9. --snip--
  10. def new_topic(request):
  11. """添加新主题"""
  12. if request.method != 'POST':
  13. # 未提交数据:创建一个新表单
  14. form = TopicForm()
  15. else:
  16. # POST提交的数据,对数据进行处理
  17. form = TopicForm(request.POST)
  18. if form.is_valid():
  19. form.save()
  20. return HttpResponseRedirect(reverse('learning_logs:topics'))
  21. context = {'form': form}
  22. return render(request, 'learning_logs/new_topic.html', context)

GET请求和POST请求

对于只是从服务器读取数据的页面,使用GET请求;在用户需要通过表单提交信息时,通常使用POST请求

函数new_topic()将请求对象作为参数。用户首次请求网页时,浏览器发送GET请求,当用户填写并提交表单时,浏览器将发送POST请求。根据请求类型可以确定用户请求是空表单(GET请求)还是对填写好的表单进行处理(POST请求)

如果请求方法不是POST,请求就可能是GET,我们返回一个空表单。如果请求方法是POST,执行else代码块,对提交的表单数据进行处理。我们根据用户输入(它们存储在request.POST中)创建一个TopicForm实例,这样form包含了用户提交的信息

要将提交的信息保存到数据库,必须先通过检查它们是不是有效的。函数is_valid()合适用户填写了所有必不可少的字段(表单字段默认都是必不可少的),且输入的数据与要求的字段类型一致(例如,字段text少于200个字符,这是我们之前在models.py中指定的)。这种自动验证避免了我们去做大量的工作。如果字段都有效,我们调用save()将表单的数据写入数据库。保存数据后,就可以离开这个页面了。我们使用reverse()获取页面topics的URL,并将其传递给HttpResponseRedirect(),将用户的浏览器重定向到页面topics

模板new_topic

下面来创建新模板new_topic.html,用于显示我们刚创建的表单



这个模板继承了base.html,我们定义了一个HTML表单,action告诉服务器将提交的表单数据发送到哪里,这里我们将它发回给new_topic(),method让浏览器以POST请求的方式提交数据

Django使用模板标签{% csrf_token %}来防止攻击者利用表单类获取对服务器未经授权的访问(这种攻击被称为跨站请求伪造)。我们用模板变量{{ form.as_p }}让Django自动创建显示表单所需的全部字段。修饰符as_p让Django以段落格式渲染所有表单元素,这是一种整洁地显示表单的简单方法

Django不会为表单创建提交按钮,因此我们还定义了一个按钮

链接到页面new_topic

接下来,我们在页面topics中添加一个到页面new_topic的链接:

  1. {% extends "learning_logs/base.html" %}
  2. {% block content %}
  3. <p>Topics</p>
  4. <ul>
  5. --snip--
  6. </ul>
  7. <a href="{% url 'learning_logs:new_topic' %}">Add a new topic:</a>
  8. {% endblock content %}

链接到new_topic页面后,可以利用这个表单添加几个新主题

2.添加新条目:

现在用户可以添加新主题了,但他们还想添加新条目。

用于添加新条目的表单

  1. # -*- coding:utf-8 -*-
  2. from django import forms
  3. from .models import Topic, Entry
  4. --snip--
  5. class EntryForm(forms.ModelForm):
  6. class Meta:
  7. model = Entry
  8. fields = ['text']
  9. labels = {'text': ''}
  10. widgets = {'text': forms.Textarea(attrs={'cols': 80})}

我们导入Entry。新类EntryForm继承forms.ModelForm,它包含的Meta类指出了表单基于的模型以及要在表单中包含哪些字段。这里也给text指定了一个空标签

另外,我们定义了属性widgets。小部件(widget)是一个HTML表单元素,如单行文本框、多行文本框或下拉列表。通过设置属性widgets,可以覆盖Django选择的默认小部件。通过让Django使用forms.Textarea,我们定制了字段’text'的输入 小部件,将文本区域的宽度设置为80列。这给用户提供了足够的空间,可以编写有意义的条目

URL模式new_entry

  1. --snip--
  2. urlpatterns=[
  3. --snip--
  4. # 用于添加新条目的网页
  5. url(r'^new_entry/(?P<topic_id>\d+)/$', views.new_entry, name='new_entry')
  6. ]

这个URL模式与形如:http://localhost:8000/new_entry/id/ 的URL匹配,其中id是一个与主题id匹配的数字。代码(?P<topic_id>\d+)捕获一个数字值,将其存在topic_id中。当请求匹配这个模式时,Django将请求和主题ID发送给函数new_entry()

视图函数new_entry()

视图new_entry与视图new_topic很像

  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. from django.shortcuts import render
  4. from django.http import HttpResponseRedirect
  5. from django.core.urlresolvers import reverse
  6. from .models import Topic
  7. from .forms import TopicForm, EntryForm
  8. # Create your views here.
  9. --snip--
  10. def new_entry(request, topic_id):
  11. """在特定的主题中添加新条目"""
  12. topic = Topic.objects.get(id=topic_id)
  13. if request.method != 'POST':
  14. # 未提交数据:创建一个空表单
  15. form = EntryForm()
  16. else:
  17. # POST提交的数据,对数据进行处理
  18. form = EntryForm(data=request.POST)
  19. if form.is_valid():
  20. new_entry = form.save(commit=False)
  21. new_entry.topic = topic
  22. new_entry.save()
  23. return HttpResponseRedirect(reverse('learning_logs:topic', args=[topic_id]))
  24. context = {'topic': topic, 'form': form}
  25. return render(request, 'learning_logs/new_entry.html', context)

我们导入了刚创建的EntryForm。new_entry()的定义包含形参topic_id,用于存储从URL中获得的值。我们通过topic_id来获得正确的主题

我们检查请求方法时POST还是GET。如果是GET请求,将执行if代码块:创建一个空的EntryForm实例。如果是POST请求,我们对数据进行处理:创建一个EntryForm实例,使用request对象中的POST数据填充它;再检查表单是否有效,如果有效,就设置条目对象的topic属性,再将条目对象保存到数据库

调用save()时,我们传递了实参commit=False,让Django创建一个新的条目对象,并将其存储到new_entry中,然后将new_entry的属性topic设置为从数据库中找到的主题,然后再调用save(),把条目保存到数据库,并将其与正确的主题关联

然后将用户重定向到显示相关主题的页面。调用reverse()时,提供了两个参数:根据它来生成URL的URL模式的名称;列表args,其中包含要包含在URL中的所有实参。这里,列表args只有topic_id一个元素

模板new_entry

从下面的代码可知,模板new_entry类似于模板new_entry

  1. {% extends "learning_logs/base.html" %}
  2. {% block content %}
  3. <p><a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}</a> </p>
  4. <p>Add a new entry:</p>
  5. <form action="{% url 'learning_logs:new_entry' topic.id %}" method="post">
  6. {% csrf_token %}
  7. {{ form.as_p }}
  8. <button name="submit">add entry</button>
  9. </form>
  10. {% endblock content %}

我们在页面顶端显示了主题,让用户知道他是在那个主题中添加条目;该主题名也是一个链接,可用于返回到该主题的主页面

表单的action包含URL中的topic_id值,让视图函数能够将新条目关联到正确的主题

链接到页面new_entry

接下来,我们需要在显示特定主题的页面中添加到new_entry的链接:

  1. {% extends 'learning_logs/base.html' %}
  2. {% block content %}
  3. <p>Topic:{{ topic }}</p>
  4. <p>Entries:</p>
  5. <p>
  6. <a href="{% url 'learning_logs:new_entry' topic.id %}">add new entry</a>
  7. </p>
  8. <ul>
  9. --snip--
  10. </ul>
  11. {% endblock content %}

尝试添加新条目

3.编辑条目:

下面创建一个页面,让用户能够编辑既有的条目

URL模式edit_entry

  1. # -*- coding:utf-8 -*-
  2. """定义learning_logs的URL模式"""
  3. from django.conf.urls import url
  4. from . import views
  5. urlpatterns = [
  6. --snip--
  7. # 用于编辑条目的页面
  8. url(r'^edit_entry/(?P<entry_id>\d+)/$', views.edit_entry, name='edit_entry'),
  9. ]

该模式匹配如:http://localhost:8000/edit_entry/1/ 的请求,发送给视图函数edit_entry()

视图函数edit_entry()

页面edit_entry收到GET请求,edit_entry()返回一个表单,让用户能够对条目进行编辑。收到POST请求,将修改后的文本保存到数据库中

  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. from django.shortcuts import render
  4. from django.http import HttpResponseRedirect
  5. from django.core.urlresolvers import reverse
  6. from .models import Topic, Entry
  7. from .forms import TopicForm, EntryForm
  8. # Create your views here.
  9. --snip--
  10. def edit_entry(request, entry_id):
  11. """编辑既有条目"""
  12. entry = Entry.objects.get(id=entry_id)
  13. topic = entry.topic
  14. if request.method != 'POST':
  15. # 初次请求,使用当前条目填充表单
  16. form = EntryForm(instance=entry)
  17. else:
  18. # POST提交的数据,对数据进行处理
  19. form = EntryForm(instance=entry, data=request.POST)
  20. if form.is_valid():
  21. form.save()
  22. return HttpResponseRedirect(reverse('learning_logs:topic', args=[topic.id]))
  23. context = {'entry': entry, 'topic': topic, 'form': form}
  24. return render(request, 'learning_logs/edit_entry.html', context)

我们要导入Entry模型,先获取用户要修改的条目对象,以及该条目相关联的主题

在请求方法为GET时将执行if代码块,我们使用实参instance=entry创建一个EntryForm实例,这个实参让Django创建一个表单,并使用既有的条目对象中的信息填充它。用户将看到既有的数据,并能编辑它们

处理POST请求时,我们传递实参instance=entry和data=request.POST,让Django根据既有条目对象创建一个表单实例,并根据request.POST中的相关数据对其进行修改。然后我们验证表单是否有效,有效就save(),接下来我们重定向到显示条目所属主题的页面,用户将在其中看到其编辑的条目的新版本

模板edit_entry

下面是模板edit_entry.html,它与模板new_entry.html类似

  1. {% extends "learning_logs/base.html" %}
  2. {% block content %}
  3. <p><a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}</a></p>
  4. <p>Edit entry:</p>
  5. <form action="{% url 'learning_logs:edit_entry' entry.id %}" method='post'>
  6. {% csrf_token %}
  7. {{ form.as_p }}
  8. <button name="submit">save changes</button>
  9. </form>
  10. {% endblock content %}

链接到页面edit_entry

现在在特定的主题页面中,需要给每个条目添加到页面edit_entry的链接:

  1. --snip--
  2. {% for entry in entries %}
  3. <li>
  4. <p>{{ entry.date_added|date:'M d, Y H:i' }}</p>
  5. <p>{{ entry.text|linebreaks }}</p>
  6. <p>
  7. <a href="{% url 'learning_logs:edit_entry' entry.id %}">edit entry</a>
  8. </p>
  9. </li>
  10. --snip--

我们将每个编辑链接放在每个条目的日期和文本后面,我们根据URL模式edit_entry和当前条目的id属性entry.id来确定URL

2、创建用户账户

接下来,我们就将建立一个用户注册和身份验证的系统,让用户能够注册账户,进而登录和注销。我们将新建一个应用程序,其中包含与处理用户账户相关的所有功能,我们还将对模型Topic稍做修改,让每个主题都归属于特定用户

1.应用程序users:

我们首先使用startapp命令来创建一个名为users的应用程序:

将应用程序users添加到setting.py中

包含应用程序users的URL

2.登录页面:

首先来实现登录页面的功能,为此我们将使用Django提供的默认的登录视图,因此需要对URL模式稍作修改。我们在应用程序users目录中,新建一个 urls.py文件,添加如下代码:



我们导入了默认视图login,匹配了该URL模式的请求将发送给Django默认视图login,注意:这里的视图实参是login,不是views.login。鉴于我们没有编写自己的视图函数,我们传递了一个字典,告诉Django去哪里查找我们将编写的模板

模板login.html

用于请求的登录页面时,Django将使用其默认视图login,但我们依然需要为这个页面提供模板。为此,在users中创建一个名为templates的目录,在其中创建一个名为users的目录。以下是模板login.html,它应该在users/templates/users/中

  1. {% extends "learning_logs/base.html" %}
  2. {% block content %}
  3. {% if form.errors %}
  4. <p>Your username and password didn't match. Please try again.</p>
  5. {% endif %}
  6. <form method="post" action="{% url 'users:login' %}">
  7. {% csrf_token %}
  8. {{ form.as_p }}
  9. <button name="submit">log in</button>
  10. <input type="hidden" name="next" value="{% url 'learning_logs:index' %}"/>
  11. </form>
  12. {% endblock content %}

这个模板继承了base.html,保证在登录页面的外观与网站的其他页面相同。注意:一个应用程序中的模板可继承另一个应用程序的模板

如果表单errors属性被设置,就显示一条错误信息,提示用户用户名密码不匹配,请重试

action设置为登录页面URL让登录视图处理表单,登录视图返回一个表单发送给模板,在模板中我们显示该表单,并添加提交按钮。另外,我们包含了一个隐藏的表单元素——‘next',其中value告诉Django在用户成功登录后将其重定向到什么地方

链接到登录页面

下面在base.html中添加到登录页面的链接,让所有页面都包含它。用户已登录,我们就不显示这个链接

  1. <p>
  2. <a href="{% url 'learning_logs:index' %}">Learning Log</a> -
  3. <a href="{% url 'learning_logs:topics' %}">Topics</a> -
  4. {% if user.is_authenticated %}
  5. Hello, {{ user.username }}
  6. {% else %}
  7. <a href="{% url 'users:login' %}">log in</a>
  8. {% endif %}
  9. </p>
  10. {% block content %}{% endblock content %}

在Django身份验证系统中,每个模板都可使用变量user,这个变量有一个is_authenticated属性:如果用户已登录,该属性为Ture,否则为Fals,我们通过该属性判断用户是否登录,显示不同消息

使用登录页面

前面建立一个用户账户,下面来登录一下,看看登录页面是否管用。请访问http://localhost:8000/admin/ ,如果依然是以管理员的身份登录的,先注销,注销后,访问http://localhost:8000/users/login/ ,你将看到如下界面,输入用户名密码,将进入页面index,在这个页面中,显示一条问候语,其中包含你的用户名



3.注销:

现在需要提供一个让用户注销的途径。我们不创建用于注销的页面,而让用户只需点击一个链接就能注销并返回到主页。为此我们为注销定义一个URL模式,编写一个视图函数,并在base.html中添加一个注销链接

注销URL

  1. --snip--
  2. urlpatterns = [
  3. # 登陆页面
  4. --snip--
  5. # 注销
  6. url(r'^logout/$', views.logout_view, name='logout')
  7. ]

视图函数logout_view()

函数logout_view()很简单:我们从django.contrib.auth中导入函数logout(),并调用它,再重定向到主页。我们再users/views.py中输入下面的代码

  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. from django.shortcuts import render
  4. from django.http import HttpResponseRedirect
  5. from django.core.urlresolvers import reverse
  6. from django.contrib.auth import logout
  7. # Create your views here.
  8. def logout_view(request):
  9. """注销用户"""
  10. logout(request)
  11. return HttpResponseRedirect(reverse('learning_logs:index'))

链接到注销视图

现在我们需要添加一个注销链接,我们在base.html中添加这种链接,让每个页面都包含它:我们将它放在标签{% if user.is_authenticated %}中,使得仅当用户登录后才能看到它:

  1. --snip--
  2. {% if user.is_authenticated %}
  3. Hello, {{ user.username }}
  4. <a href="{% url 'users:logout' %}">log out</a>
  5. {% else %}
  6. <a href="{% url 'users:login' %}">log in</a>
  7. {% endif %}
  8. </p>
  9. --snip--

然后我们登录就看到的是下面的样子:

4.注册页面

下面来创建一个新用户能够注册的页面。 我们将使用Django提供的表单UserCreationForm,但编写自己的视图函数和模板

注册页面的URL模式

  1. --snip--
  2. urlpatterns = [
  3. # 登陆页面
  4. --snip--
  5. # 注册页面
  6. url(r'^register/$', views.register, name='register'),
  7. ]

这个模式与URL http://lcalhost:8000/users/register/ 匹配,将请求发送给我们编写的函数register()

视图函数register()

  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. from django.shortcuts import render
  4. from django.http import HttpResponseRedirect
  5. from django.core.urlresolvers import reverse
  6. from django.contrib.auth import logout, login, authenticate
  7. from django.contrib.auth.forms import UserCreationForm
  8. def logout_view(request):
  9. --snip--
  10. def register(request):
  11. """注册新的用户"""
  12. if request.method != 'POST':
  13. # 显示空的注册表单
  14. form = UserCreationForm()
  15. else:
  16. # 处理填好的表单
  17. form = UserCreationForm(data=request.POST)
  18. if form.is_valid():
  19. new_user = form.save()
  20. # 让用户自动登录,再重定向到主页
  21. authenticated_user = authenticate(username=new_user.username, password=request.POST['password1'])
  22. login(request, authenticated_user)
  23. return HttpResponseRedirect(reverse('learning_logs:index'))
  24. context = {'form': form}
  25. return render(request, 'users/register.html', context)

我们导入了render(),然后导入了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传递给它,这将为新用户创建有效的会话

    最后,我们将用户重定向到主页,并显示注册成功的问候消息

注册模板

注册页面的模板与登录页面的模板类似,它跟login.html在一个目录下:

  1. {% extends "learning_logs/base.html" %}
  2. {% block content %}
  3. <form method="post" action="{% url 'users:register' %}">
  4. {% csrf_token %}
  5. {{ form.as_p }}
  6. <button name="submit">register</button>
  7. <input type="hidden" name="next" value="{% url 'learning_logs:index' %}"/>
  8. </form>
  9. {% endblock content %}

这里也用了as_p,让Django在表单中正确地显示所有的字段,包括错误消息——如果用户没有正确地填写表单

链接到注册页面

接下来我们,添加如下代码,即用户没有登录时显示到注册页面的链接:

  1. --snip--
  2. {% if user.is_authenticated %}
  3. Hello, {{ user.username }}
  4. <a href="{% url 'users:logout' %}">log out</a>
  5. {% else %}
  6. <a href="{% url 'users:register' %}">register</a> -
  7. <a href="{% url 'users:login' %}">log in</a>
  8. {% endif %}
  9. --snip--

现在,已登录的用户看到的是:



而未登录的用户看到的是:

3、让用户拥有自己的数据

用户应该能输入其专有的数据,因此我们将创建一个系统,确定各项数据所属的用户,再限制对页面的访问,让用户只能使用自己的数据

我们将修改模型Topic,让每个主题都归属于特定用户。这也将影响条目,因为每个条目都属于特定的主题。我们先来限制对一些页面的访问

1.使用@login_required限制访问

Django提供了装饰器@login_required,让你能够轻松地实现这样的目标:对于某些页面,只允许已登录的用户访问它们。装饰器(decorator)是放在函数定义前面的,Python在函数运行前,根据它来修改函数代码的行为

限制对topics页面的访问

每个主题都归特定的用户所有,因此应只允许已登录的用户请求topics页面。为此我们修改learning_logs/views.py的代码



我们导入了函数login_required()。在视图函数topics()前加上符号@和login_required,让Python在运行topics()代码的前先运行login_required()的代码。login_required()的代码检查用户是否已经登录,仅当用户已登录时,Django才运行topics()的代码。如果用户未登录,就重定向到登录页面

为实现这种重定向,我们修改settings.py,绕过Django知道到哪里去查找登录页面



现在,未登录的用户请求修饰器@login_required的保护页面,Django将重定向到settings.py中的LOGIN_URL指定的URL

要测试这个设置,可注销进入主页,单击链接Topics,将重定向到登录页面。使用你的账号登录后,再次点击Topics链接,你将看到topics页面

全面限制对项目“学习笔记”的访问

Django让你能够轻松地限制对页面的访问,但你必须针对要保护哪些页面做出决定。最好先确定项目的哪些页面不需要保护,再限制对其他所有页面的访问。你可以轻松地修改过于严格的访问限制,其风险比不限制对敏感页面地访问更低

在“学习笔记”项目中,我们不限制对主页、注册页面和注销页面的访问,并限制对其他所有页面的访问

  1. --snip--
  2. @login_required
  3. def topics(request):
  4. --snip
  5. @login_required
  6. def topic(request, topic_id):
  7. --snip--
  8. @login_required
  9. def new_topic(request):
  10. --snip--
  11. @login_required
  12. def new_entry(request, topic_id):
  13. --snip--
  14. @login_required
  15. def edit_entry(request, entry_id):
  16. --snip--

未登录的情况下尝试访问这些页面,都将重定向到登录页面

2.将数据关联到用户

现在,需要将数据关联到提交它们的用户。我们只需将最高层的数据关联到用户,这样更低层的数据将自动关联到用户。例如,在项目“学习笔记”中,应用程序的最高数据是主题,所有条目都与特定主题相关联。只要每个主题都归属于特定用户,我们就能确定数据库中每个条目的所有者

修改模型Topic

对models.py的修改只涉及两行代码

  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. from django.db import models
  4. from django.contrib.auth.models import User
  5. # Create your models here.
  6. class Topic(models.Model):
  7. """用户学习的主题"""
  8. text = models.CharField(max_length=200)
  9. date_added = models.DateTimeField(auto_now_add=True)
  10. owner = models.ForeignKey(User)
  11. def __unicode__(self):
  12. """返回模型的字符串表示"""
  13. return self.text
  14. --snip--

我们导入了django.contrib.auth中的模型User,然后再Topic中添加了字段owner,它建立到模型User的外键关系

确定当前有哪些用户

在我们确定将各个既有主题关联到哪个用户上时,需要先知道关联用户的ID,为此我们打开一个Django Shell会话,执行如下命令,查看已创建的所有用户的ID



shell会话中输出了三个用户:hasee_z、eric、willie,并打印了它们的ID,Django询问要将既有主题关联到哪个用户时,我们将指定其中的一个ID值

迁移数据库

知道用户ID后,就可以迁移数据库了:



现在可以执行迁移了,我们继续执行命令python manage.py migrate



Django应用新的迁移,结果一切顺利。为验证符合预期,我们在Shell会话中这样做

3.只允许用户访问自己的主题

当前,不管你以哪个用户的身份登录,都能看到所有的主题。为只向用户显示属于自己的主题,我们对views.py中的topics()做如下修改:

  1. --snip--
  2. @login_required
  3. def topics(request):
  4. """显示所有的主题"""
  5. topics = Topic.objects.filter(owner=request.user).order_by('date_added')
  6. context = {'topics': topics}
  7. return render(request, 'learning_logs/topics.html', context)
  8. --snip--

用户登录后,request对象将有一个user属性,这个属性存储了有关该用户的信息。Topic.objects.filter(owner=request.user)让Django只从数据库中获取owner属性为当前用户的Topic对象

要验证结果,我们以既有主题关联到的用户身份登录,访问topics页面,将看到所有的主题



然后,注销并以另一个用户的身份登录,topics页面将不会列出任何主题

4.保护用户的主题

我们还没限制对显示单个主题的页面的访问,因此任何已登录的用户都可以输入类似于:http://localhost:8000/topics/1/ 的URL,来访问显示相应主题的页面

为了修复这种问题,我们在视图函数topic()获取请求的条目前执行检察:

  1. from django.shortcuts import render
  2. from django.http import HttpResponseRedirect, Http404
  3. from django.core.urlresolvers import reverse
  4. --snip--
  5. @login_required
  6. def topic(request, topic_id):
  7. """显示单个主题及其所有的条目"""
  8. topic = Topic.objects.get(id=topic_id)
  9. # 确认请求的主题属于当前用户
  10. if topic.owner != request.user:
  11. raise Http404
  12. entries = topic.entry_set.order_by('-date_added')
  13. context = {'topic': topic, 'entries': entries}
  14. return render(request, 'learning_logs/topic.html', context)
  15. --snip--

服务器上没有请求的资源时,标准做法是返回404响应。在这里,我们导入异常Http404,并在用户请求它不能查看的主题时引发这个异常。收到主题请求后,我们在渲染网页前检查该主题是否属于当前登录的用户。如果请求的主题不归当前用户所有,我们就引发Http404异常,让Django返回一个404错误页面

结果如下:

5.保护页面edit_entry

页面edit_entry的URL为http://localhost:8000/edit_entry/entry_id/ ,其中entry_id是一个数字。下面来保护这个页面,禁止用户通过输入类似前面的URL来访问其他用户的条目:

  1. --snip--
  2. @login_required
  3. def edit_entry(request, entry_id):
  4. """编辑既有条目"""
  5. entry = Entry.objects.get(id=entry_id)
  6. topic = entry.topic
  7. if topic.owner != request.user:
  8. raise Http404
  9. if request.method != 'POST':
  10. # 初次请求,使用当前条目填充表单
  11. --snip--

我们获取指定条目以及与之相关的主题,然后检查主题的所有者是否是当前登录的用户,如果不是,就引发Http404异常

6.将新主题关联到当前用户

当前,我们添加新主题的页面存在问题,没有将新主题关联到特定用户。尝试添加新主题,看到错误IntegrityError,指出learning_logs_topic.user_id不能为NULL,即创建新主题时,必须指定其owner字段的值



于是我们修改views.py中new_topic()的代码:

  1. --snip--
  2. @login_required
  3. def new_topic(request):
  4. """添加新主题"""
  5. if request.method != 'POST':
  6. # 未提交数据:创建一个新表单
  7. form = TopicForm()
  8. else:
  9. # POST提交的数据,对数据进行处理
  10. form = TopicForm(request.POST)
  11. if form.is_valid():
  12. new_topic=form.save(commit=False)
  13. new_topic.owner=request.user
  14. new_topic.save()
  15. return HttpResponseRedirect(reverse('learning_logs:topics'))
  16. context = {'form': form}
  17. return render(request, 'learning_logs/new_topic.html', context)
  18. --snip--

我们首先调用form.save(),传递实参commit=False,因为我们先修改新主题,再对其保存到数据库中。接下来,将新主题的owner属性设置为当前用户。最后对刚定义的主题实例调用save()。现在新主题包含所有必不可少的数据,将被成功地保存。

现在,这个项目允许任何用户注册,而且用户想添加多少新主题都可以。每个用户都只能访问自己的数据,无论是查看数据、输入新数据还是修改旧数据时都如此。

用户账户——《Python编程从入门到实践》的更多相关文章

  1. Python编程从入门到实践笔记——用户输入和while循环

    Python编程从入门到实践笔记——用户输入和while循环 #coding=utf-8 #函数input()让程序暂停运行,等待用户输入一些文本.得到用户的输入以后将其存储在一个变量中,方便后续使用 ...

  2. Python编程从入门到实践

    Python编程从入门到实践1 起步2 变量和简单数据类型3 列表简介4 操作列表5 if语句6 字典7 用户输入和while循环8 函数9 类10 文件和异常11 测试代码12 武装飞船13 外星人 ...

  3. Python编程从入门到实践笔记——异常和存储数据

    Python编程从入门到实践笔记——异常和存储数据 #coding=gbk #Python编程从入门到实践笔记——异常和存储数据 #10.3异常 #Python使用被称为异常的特殊对象来管理程序执行期 ...

  4. Python编程从入门到实践笔记——函数

    Python编程从入门到实践笔记——函数 #coding=gbk #Python编程从入门到实践笔记——函数 #8.1定义函数 def 函数名(形参): # [缩进]注释+函数体 #1.向函数传递信息 ...

  5. Python编程-从入门到实践 Eric Matthes 著 袁国忠 译 - - 第二章 动手试一试

    因为第一章的动手试一试基本都是探索性的,所以直接进入第二章. # 2.2 动手试一试 # 2_1 简单消息: 将一条消息存储到变量中,再将其打印出来. message = 'python 编程从入门到 ...

  6. 《Python编程从入门到实践》_第十章_文件和异常

    读取整个文件 文件pi_digits.txt #文件pi_digits.txt 3.1415926535 8979323846 2643383279 下面的程序打开并读取整个文件,再将其内容显示到屏幕 ...

  7. 《python编程从入门到实践》读书实践笔记(一)

    本文是<python编程从入门到实践>读书实践笔记1~10章的内容,主要包含安装.基础类型.函数.类.文件读写及异常的内容. 1 起步 1.1 搭建环境 1.1.1 Python 版本选择 ...

  8. Python编程从入门到实践笔记——文件

    Python编程从入门到实践笔记——文件 #coding=gbk #Python编程从入门到实践笔记——文件 #10.1从文件中读取数据 #1.读取整个文件 file_name = 'pi_digit ...

  9. Python编程从入门到实践笔记——类

    Python编程从入门到实践笔记——类 #coding=gbk #Python编程从入门到实践笔记——类 #9.1创建和使用类 #1.创建Dog类 class Dog():#类名首字母大写 " ...

  10. Python编程从入门到实践笔记——字典

    Python编程从入门到实践笔记——字典 #coding=utf-8 #字典--放在{}中的键值对:跟json很像 #键和值之间用:分隔:键值对之间用,分隔 alien_0 = {'color':'g ...

随机推荐

  1. mysql 根据日期进行查询数据,没有数据也要显示空

    写这篇博客主要是记录自己在对订单进行按日期查询时使用的一种查询的方法,这里的orders是订单表,你也可以改成别的什么表对于最终数据不会造成影响,除非你那个表的数据只有几条那样就会出现查不到日期的情况 ...

  2. linux查看log软件

    可以使用LNAV软件查看log,还是比较方便的 安装步骤 $ sudo apt install lnav 获取帮助信息 $ lnav -h 查看日志 $ lnav 查看指定日志(后面加上绝对路径) $ ...

  3. Java 缓存实例

    重复创建相同的对象没有太大的意义,反而加大了系统开销,某些情况下,可以缓存该类的实例,实现复用. 实现缓存实例:定义一个private static成员变量存储类的实例(多个可用数组)先检测上面的成员 ...

  4. selenium 滚动屏幕操作+上传文件

    执行js脚本来滚动屏幕: (x,y)x为0 纵向滚动,y为0横向滚动 负数为向上滚动 driver.execute_script('window.scrollBy(0,250)') 上传文件: 1.导 ...

  5. equals与hashCode

    当我们需要将自己的类存入HashMap或HashSet时一般都要重写其equals与hashCode方法,但在重写时要符合规范否则会出问题. 1.equals方法 首先equals方法需要满足如下几点 ...

  6. 异常检测(Anomaly detection): 什么是异常检测及其一些应用

    异常检测的例子: 如飞机引擎的两个特征:产生热量与振动频率,我们有m个样本画在图中如上图的叉叉所示,这时来了一个新的样本(xtest),如果它落在上面,则表示它没有问题,如果它落在下面(如上图所示), ...

  7. Linux配置静态IP以及解决配置静态IP后无法上网的问题

    式一.图形界面配置

  8. volatile 错误示范做线程同步 demo

    这里 http://hedengcheng.com/?p=725 有对volatile 非常详细的解释,看完之后,心里一惊,因为我刚好在一个项目里用了文中错误示范那种方式来做线程同步,场景如下: Th ...

  9. 含-SH的ACE抑制药的青霉胺样反应

    关于 含-SH的血管紧张素转化酶(ACE)抑制药如卡托普利具有青霉胺样反应.而依那普利则不含-SH. 青霉胺样反应 青霉胺样反应,指应用含-SH的ACE抑制药产生的皮疹.嗜酸性粒细胞(E)增多.味觉异 ...

  10. MySQL 硬链接删除大表

    在清理整个大表时,我们推荐使用drop,而非delete.但是如果表实在太大,即使是drop,也需要消耗一定的时间.这时可以利用linux的硬连接来快速删除大表,操作过程如下:有一个大表test,共有 ...