zoukankan      html  css  js  c++  java
  • 第 19 章 用户帐号

        Web应用程序的核心是让任何用户都能够注册并能够使用它,不管用户身处何方。在本章中,我们将创建一些表单,让用户能够添加主题和条目,以及编辑既有的条目。我们还将学习Django如何防范对基于表单的网页发起的常见攻击,这让我们无需花太多时间考虑确保应用程序安全的问题。

        然后,我们将实现一个用户身份验证系统。我们将创建一个注册页面,供用户创建账户,并让有些页面只能供已登录的用户访问。接下来,我们将修改一些视图函数,使得用户只能看到自己的数据。我们将学习如何确保用户数据的安全。

    19.1  让用户能够输入数据

        建立用于创建用户账户的身份验证系统之前,我们先来添加几个页面,让用户能够输入数据。我们将让用户能够添加新主题、添加新条目以及编辑既有条目。

        当前,只有超级用户能够通过管理网站输入数据。我们不想让用户与管理网站交互,因此我们将使用Django的表单创建工具来创建让用户能够输入的页面。

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

        1.用于添加主题的表单

        让用户输入并提交信息的页面都是表单,哪怕它看起来不像表单。用户输入信息时,我们需要进行验证,确认提供的信息是正确的数据类型,且不是恶意的信息,如中断服务器的代码。然后,我们再对这些有效信息进行处理,并将其保存到数据库的合适地方。这些工作很多都是Django自动完成的。

        在Django中,创建表单的最简单方式是使用ModelForm,它根据我们在第18章定义的模型中的信息自动创建表单创建一个名为forms.py的文件,将其存储到models.py所在的目录中,并在其中编写我们的第一个表单。

      forms.py

        from django import forms

      from .models import Topic

      class TopicForm(forms.ModelForm):                   --(1)

            class Meta:

          model = Topic                               --(2)

          fields = ["text"]                           --(3)

          labels = {"text":""}                        --(4)

        我们首先导入了模块forms以及要使用的模型Topic。在(1)处,我们定义了一个名为TopicForm的类,它继承了forms.ModelForm。

        最简单的ModelForm版本只包含一个内嵌的Meta类,它告诉Django根据哪个模型创建表单,以及在表单中包含哪些字段。在(2)处,我们根据模型Topic创建一个表单,该表单只包含字段text(见(3))。(4)处的代码让Django不要为字段text生成标签。

        2.URL模式new_topic

        这个新网页的URL应简短而具有描述行,因此当用户要添加新主题时,我们将切换到http://localhost:8000/new_topic/。下面是网页new_topic的URL模式,我们将其添加到learning_logs/urls.py中:

        urls.py

    '''定义learning_logs的URL模式'''
    from django.conf.urls import url
    from . import views

    urlpatterns = [
        #主页
        url(r'^$',views.index,name='index'),
            #显示所有的主题
        url(r'^topics/$', views.topics,name='topics'),
            #特定主题的详细页面
        url(r'^topics/(?P<topic_id>d+)/$',views.topic,name='topic'),
        #用于添加新主题的网页
        url(r'^new_topic/$',views.new_topic,name='new_topic'),
    ]
        这个URL模式将请求交给视图函数new_topic(),接下来我们将编写这个函数。

        3.视图函数new_topic()

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

        views.py

    from django.shortcuts import render
    from django.http import HttpResponseRedirect
    from django.core.urlresolvers import reverse
    from .models import Topic
    from .forms import TopicForm

    # Create your views here.
    def 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:                      (1)
            #未提交数据:创建一个新表单
        form = TopicForm()                                (2)
        else:
        """POST提交的数据,对数据进行处理"""
        form = TopicForm(request.POST)                     (3)
        if form.is_valid():                                (4)
            form.save()                                    (5)
                return HttpResponseRedirect(reverse('learning_logs:topics'))            (6)

    context = {'form':form}                                                (7)
    return render(request,"learning_logs/new_topic.html',context)

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

        4.GET请求和POST请求

        创建Web应用程序时,将用到的两种主要请求类型是GET请求和POST请求。对于只是从服务器读取数据的页面,使用GET请求;在用户需要通过表单提交信息时,通常使用POST请求。处理所有表单时,我们都将指定使用POST方法。还有一些其他类型的请求,但这个项目没有使用。

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

        (1)处的测试确定请求方法是GET还是POST。如果请求方法不是POST,请求就可能是GET,因此我们需要返回一个空表单(即便请求是其他类型的,返回一个空表单也不会有任何问题)。我们创建一个TopicForm实例(见2),将其存储在变量form中,再通过上下文字典将这个表单发送给模板(见(7))。由于实例化TopicForm时我们没有指定任何实参,Django将创建一个可供用户填写的空表单。

        如果请求方法是POST,将执行else代码块,对提交的表单进行数据处理。我们使用用户输入的数据(它们存储在request.POST中)创建一个TopicForm实例(见(3)),这样对象form将包含用户提交的信息。

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

        5.模板new_topic

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

        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">       --(1)

            {% csrf_token %}                                                   --(2)

        {{ form.as_p }}                                                       --(3)

        <button name="submit">add topic</button>                               --(4)

        </form>

        {% endblock content %}

        这个模板继承了base.html,因此其基本结构与项目"学习笔记"的其他页面相同。在(1)处,我们定义了一个HTML表单。实参action告诉服务器将提交的表单数据发送到哪里,这里我们将它发回给视图函数new_topic()。实参methon让浏览器以POST请求的方式提交数据。

        Django使用模板标签{% csrf_token %}(见2)来防止攻击者利用表单来获得对服务器未经授权的访问(这种攻击被称为跨站请求伪造).在(3)处,我们显示表单,从中可知Django使得完成显示表单等任务有多简单:我们只需包含模板变量{{ form.as_p }},就可让Django自动创建显示表单所需的全部字段。

    修饰符as_p让Django以段落格式渲染所有表单元素,这是一种整洁地显示表单的简单方式。

        Django不会为并表单创建提交按钮,因此我们在(4)处定义了一个这样的按钮。

        6.链接到页面new_topic

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

    topics.html

    from django.shortcuts import render
    from django.http import HttpResponseRedirect
    from django.core.urlresolvers import reverse
    from .models import Topic
    from .forms import TopicForm

    # Create your views here.
    def 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)

    19.1.2  添加新条目

        现在用户可以添加新主题了,但它们还想添加新条目。我们将再次定义URL,编写视图函数和模板,并链接到添加新条目的网页。但在此之前,我们需要在forms.py中再添加一个类。

        1.用于添加新条目的表单

        我们需要创建一个与模型Entry相关联的表单,但这个表单的定制程度比TopicForm要高些:

    forms.py

    from django import forms
    from .models import Topic,Entry

    class TopicForm(forms.ModelForm):
        class Meta:
        model = Topic
        fields = ["text"]
        labels = {"text": ""}

     class EntryForm(forms.ModelForm):
        class Meta:
            model = Entry
        fields = ["text"]
        labels = {"text":""}                                                         --(1)
        widgets = {'text':forms.Textarea(attrs={'cols':80})}                         --(2)
        我们首先修改了import语句,使其除了导入Topic外,还导入Entry。新类EntryForm继承了forms.ModelForm,它包含的Meta类指出了表单基于的模型以及要在表单中包含哪些字段。这里也给字段'text'指定了一个空标签。

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

        2.URL模式new_entry

        在用于添加新条目的页面的URL模式中,需要包含实参topic_id,因为条目必须与特定的主题相关联。该URL模式如下,我们将它添加到了learning_logs/urls.py中:

    ruls.py

    '''定义learning_logs的URL模式'''
    from django.conf.urls import url
    from . import views

    urlpatterns = [
        #主页
        url(r'^$',views.index,name='index'),
            #显示所有的主题
        url(r'^topics/$', views.topics,name='topics'),
            #特定主题的详细页面
        url(r'^topics/(?P<topic_id>d+)/$',views.topic,name='topic'),
        #用于添加新主题的网页
            url(r'^new_topic/$', views.new_topic,name='new_topic'),
            #用于添加新条目的页面
            url(r'^new_entry/(?P<topic_idd+/$',views.new_entry,name='new_entry'),
    ]

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

        3.视图函数new_entry()

        视图函数new_entry()与函数new_topic()很像:

    views.py

    from django.shortcuts import render
    from django.http import HttpResponseRedirect
    from django.core.urlresolvers import reverse
    from .models import Topic
    from .forms import TopicForm,EntryForm

    # Create your views here.
    def 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'))

    def new_entry(request,topic_id):
        """在特定的主题中添加新条目"""
        topic = Topic.objects.get(id=topic_id)                                                     (1)
        if request.method != "POST":                                                               (2)
        #未提交数据,创建一个空表单
        form = EntryForm()                                                                          (3)
        else:
        #POST提交的数据,对数据进行处理
        form = EntryForm(data=request.POST)                                                         (4)
            if form.is_valid():
            new_entry = form.save(commit=False)                                                     (5)
            new_entry.topic = topic                                                                  (6)
            new_entry.save()
            return HttpResponseRedirect(reverse('learning_logs:topic',args=[topic_id]))             (7)

    context = {'topic':topic,'form':form}
    return render(request,"learning_logs/new_entry.html',context)

        我们修改了import语句,在其中包含了刚创建的EntryForm。new_entry()的定义包含形参topic_id,用于存储从URL中获得的值。渲染页面以及处理表单数据时,都需要知道针对的时哪个主题,因此我们使用topic_id来获得正确的主题。

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

        调用save()时,我们传递了实参commit=False(见(5)),让Django创建一个新的条目对象,并将其存储到new_entry中,但不将它保存到数据库中。我们将new_entry的属性topic设置为这个函数开头从数据库中获取的主题,然后调用save(),且不指定任何实参。这将把条目保存到数据库,并将其与正确的主题相关联。

        在(7)处,我们将用户重定向到显示相关主题的页面。调用reverse()时,需要提供两个实参:要根据它来生成URL的URL模式的名称;列表args,其中包含要包含在URL中的所有实参。这里,列表args只有一个元素——topic_id。接下来,调用HttpResponseRedirect()将用户重定向到显示新增条目所属主题的页面,用户将在该页面的条目列表中看到新添加的条目。

        4.模板new_entry

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

    new_entry.html

    {% extends 'learning_logs/base.html' %}
    {% block content %}

      <p><a href="{% url 'learning_logs:topic' topic_id %}">{{ topic }}</a></p>              --(1)
      <p>Add a new entry:</p>
      <form action="{% url 'learning_logs:new_entry' topic.id %} method='post'>              —(2)
        {% csrf_token %}
        {{ form.as_p }}
        <buttom name="submit">add entry</button>
      </form>

    {% endblock content %}

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

        表单的实参action包含URL中的topic_id值,让视图函数能够将新条目关联到正确的主题(见(2))。除此之外,这个模板与模板new_topic.html完全相同。

        5.链接到页面new_entry

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

        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 entry</a>
      </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 %}

        我们在显示条目前添加链接,因为在这种页面中,执行的最常见的操作是添加新条目。

    19.1.3  编辑条目

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

        1.URL模式edit_entry

        这个页面的URL需要传递要编辑的条目的ID.修改后的learning_logs/ruls.py如下:

        urls.py

    '''定义learning_logs的URL模式'''
    from django.conf.urls import url
    from . import views

    urlpatterns = [
        #主页
        url(r'^$',views.index,name='index'),
            #显示所有的主题
        url(r'^topics/$', views.topics,name='topics'),
            #特定主题的详细页面
        url(r'^topics/(?P<topic_id>d+)/$',views.topic,name='topic'),
        #用于添加新主题的网页
            url(r'^new_topic/$', views.new_topic,name='new_topic'),
            #用于添加新条目的页面
            url(r'^new_entry/(?P<topic_idd+/$',views.new_entry,name='new_entry'),
            #用于编辑条目的页面
        url(r'^edit_entry/(?P<entry_id>d+)/$',views.edit_entry,name='edit_entry'),
    ]

        在URL(如http://localhost:8000/edit_entry/1/)中传递的ID存储在形参entry_id中。这个URL模式将预期匹配的请求发送给视图函数edit_entry().

        2.视图函数edit_entry()

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

        """显示单个主题及其所有的条目"""
        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'))

    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]))

    def edit_entry(request,entry_id):
        #编辑既有条目
        entry = Entry.objects.get(id=entry.id)                          (1)
        topic = entry.topic
        if request.method != "POST":
            #初次请求,使用当前条目填充表单
        form = EntryForm(instance=entry)                                (2)
        else:
        #POST提交的数据,对数据进行处理
        form = EntryForm(instance=entry,data=request.POST)              (3)
        if form.is_valid():
            form.save()                                                 (4)
            return HttpResponseRedirect(reverse('learning_logs:topic',args=[topic.id]))   (5)


    context = {'entry':entry,'topic':topic,'form':form}
    return render(request,"learning_logs/edit_entry.html',context)

        我们首先需要导入模型Entry。在(1)处,我们获取用户要修改的条目对象,以及与该条目相关联的主题。在请求方法为GET时将执行的if代码块中,我们使用实参instance=entry创建一个EntryForm实例。这个实参让Django创建一个表单,并使用既有条目对象中的信息填充它。用户将看到既有的数据,并能够编辑它们。

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

        3.模板edit_entry

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

        edit_entry.html

    {% entends "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 changes</button>
      </form>

    {% endblock content %}
        在(1)处,实参action将表单发回给函数edit_entry()进行处理。在标签{% url %}中,我们将条目ID作为一个实参,让视图对象能够修改正确的条目对象。我们将提交按钮命名为save changes,以提醒用户:单击该按钮将保存所做的编辑,而不是创建一个新条目。

        4.链接到页面edit_entry

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

    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 entry</a>
      </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 entry</a>
        </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",它出现在页面中每个条目的后面。

        至此,“学习笔记”已具备了需要的大部分功能。用户可添加主题和条目,还可根据需要查看任何一组条目。在下一节,我们将实现一个用户注册系统,让任何人都可向“学习笔记”申请账户,并创建自己的主题和条目。

    19.2  创建用户账户

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

    19.2.1  应用程序users

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

    (11_env)learning_log$ python3 manage.py startapp users
    (11_env)learning_log$ ls
    11_env  db.sqlite3  learning_log  learning_logs  manage.py  users
    (11_env)learning_log$ ls users
    admin.py  apps.py  __init__.py  migrations  models.py  tests.py  views.py
        这个命令新建一个名为users的目录(见(1)),其结构与应用程序learning_logs相同。

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

        在settings.py中,我们需要将这个新的应用程序添加到INSTALLED_APPS中,如下所示:

        settings.py

    --snip--

    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        #我的应用程序
        "learning_logs",
        'users',
    ]

        这样,Django将把应用程序users包含到项目中。

        2.包含应用程序users的URL

        接下来,我们需要修改项目根目录中的urls.py,使其包含我们将为应用程序users定义的URL:

    urls.py

    from django.conf.urls import include, url
    from django.contrib import admin

    urlpatterns = [
        url(r'^admin/', include(admin.site.urls)),
        url(r'users/',include('users.urls',namespace='users')),
        url(r'', include('learning_logs.urls',namespace='learning_logs')),
    ]

        我们添加了一行代码,以包含应用程序users中的文件urls.py。这行代码与任何以单词users打头的URL(如http://localhost:8000/users/login/)都匹配。我们还创建了命名空间'users',以便将应用程序learning_logs的URL同应用程序users的URL区分开来。

    19.2.2  登录页面

        我们首先来实现登录页面的功能。为此,我们将使用Django提供的默认登录视图,因此URL模式会稍有不同。在目录learning_log/users/中,新建一个名为urls.py的文件,并在其中添加如下代码:

    urls.py

    """为应用程序users定义URL模式"""
    from django.conf.urls import url
    from django.contrib.auth.views import login                  (1)

    from . import views

    urlpatterns = [
        #登录页面
        url(r'^login/$',login,{'template_name':'users/login.html'},name='login'),        (2)
    ]

        我们首先导入了默认视图login(见(1))。登录页面的URL模式与URL http://localhost:8000/users/login/匹配(见2).这个URL中的单词users让Django在users/urls.py中查找,而单词login让它将请求发送给Django默认视图login(请注意,视图参数为lonin,而不是views.login)。鉴于我们没有编写自己的视图函数,我们传递了一个字典,告诉Django去哪里查找我们将编写的模板。这个模板包含在应用程序users而不是learning_logs中。

        1.模板login.html

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

    login.html

    {% extends "learning_logs/base.html" %}

    {% block content %}

      {% if form.errors %}                                                             (1)
      <p>Your username and password didn't match. Please try again.</P>
      {% endif %}

      <form method="post" action="{% url 'users:login' %}>                              (2)
      {% csrf_token %}
      {{ form.as_p }}                                                                   (3)
     
      <button name="submit">log in</button>                                              (4)
      <input type="hidden" name="next" value="{% url "learning_logs:index" %}" />        (5)
      </form>
    {% endblock content %}

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

        如果表单的errors属性被设置,我们就显示一条错误消息(见(1)),指出输入的用户名--密码对与数据库中存储的任何用户名--密码对都不匹配。

        我们要让登录视图处理表单,因此将实参action设置为登录页面的URL(见(2))。登录视图将一个表单发送给模板,在模板中,我们显示这个表单(见(3))并添加一个提交按钮(见(4))。在(5)处,我们包含了一个隐藏的表单元素——'next',其中的实参value告诉Django在用户成功登录后将其重定向到什么地方——在这里是主页。

        2.链接到登录页面

        下面在base.html中添加到登录页面的链接,让所有页面都包含它。用户已登录时,我们不想显示这个链接,因此将它嵌套在一个{% if %}标签中:

    base.html

    <p>
      <a href="{% url 'learning_logs:index' %}">Learning Log</a> -
      <a href="{% url 'learning_logs:topics' %}">Topics</a> -
      {% if user.is_authenticated %}                                         (1)
        Hello,{{ user.username }}.                                           (2)
      {% else %}
        <a href="{% url 'users:login' %}">log in</a>                         (3)
      {% endif %}
    </p>

    {% block content %}{% endblock content %}

        在Django身份验证系统中,每个模板都可使用user,这个变量有一个is_authenticated属性:如果用户已登录,该属性将为True,否则为False。这让我们能够向已通过验证的用户显示一条消息,而向未通过身份验证的用户显示另一条消息。

        在这里,我们向已登录的用户显示一条问候语(见(1))。对于已通过身份验证的用户,还设置了属性username,我们使用这个属性来个性化问候语,让用户知道他已登录(见(2))。在(3)处,对于还未通过身份验证的用户,我们再提示一个到登录页面的链接。

        3.使用登录页面

        前面建立了一个用户账户,下面来登录一下,看看登录页面是否管用。请访问http://localhost:8000/admin/,如果我们依然是以管理员的身份登录的,请在页眉上找到注销链接并单击它。

        注销后,访问http://localhost:8000/users/login/,我们将看到类似于下图所示的登录页面。输入我们前面设置的用户名和密码,将进入页面index。在这个主页的页眉中,显示了一条个性化问候语,其中包含你的用户名。

    19.2.3  注销

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

        1.注销URL

        下面的代码为注销定义了URL模式,该模式与URL http://locallwst:8000/users/logout/匹配。修改后的users/urls.py如下:

    urls.py

    """为应用程序users定义URL模式"""
    from django.conf.urls import url
    from django.contrib.auth.views import login

    from . import views

    urlpatterns = [
        #登录页面
        url(r'^login/$',login,{'template_name':'users/login.html'},name='login'),
        #注销
        url(r'^logout/$',views.logout_view,name='logout'),
    ]

        这个URL模式将请求发送给函数logout_view()。这样给这个函数命名,旨在将其与我们将在其中调用的函数logout()区分开来(请确保我们修改的时users/urls.py,而不是learning_log/urls.py).

        2.视图函数logout_view()

        函数logout_view()很简单:只是导入Django函数logout(),并调用它,再重定向到主页。请打开users/views.py,并输入下面的代码:

    views.py

    from django.http import HttpResponseRedirect
    from django.core.urlresolvers import reverse
    #从Django核心解析器中导入模块
    from django.contrib.auth import logout                                          (1)
        def logout_view(request):
        """注销用户"""
        logout(request)                                                              (2)
        return HttpResponseRedirect(reverse('learning_logs:index'))                  (3)

        我们从django.contrib.auth中导入了函数logout()(见(1)).在(2)处,我们调用了函数logout(),它要求将request对象作为实参。然后,我们重定向到主页(见(3)).

        3.链接到注销视图

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

    base.html

    <p>
      <a href="{% url 'learning_logs:index' %}">Learning Log</a> -
      <a href="{% url 'learning_logs:topics' %}">Topics</a> -
      {% if user.is_authenticated %}
        Hello,{{ user.username }}.
        <a href="{% url 'users:logout' %}">log out</a>
      {% else %}
        <a href="{% url 'users:login' %}">log in</a>
      {% endif %}
    </p>

    {% block content %}{% endblock content %}

        图19-5显示了用户登录后看到的主页。这里的重点是创建能够正确工作的网站,因此没有设置任何样式。确定所需的功能都能正确运行后,我们将设置这个网站的样式,使其看起来更专业。

    19.2.4  注册页面

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

        1.注册页面的URL模式

        下面的代码定义了注册页面的URL模式,它也包含在users/urls.py中:

    urls.py

    """为应用程序users定义URL模式"""
    from django.conf.urls import url
    from django.contrib.auth.views import login

    from . import views

    urlpatterns = [
        #登录页面
        url(r'^login/$',login,{'template_name':'users/login.html'},name='login'),
        #注销
        url(r'^logout/$',views.logout_view,name='logout'),
        #注册页面
        url(r'^register/$',views.register,name='register'),
    ]

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

        2.视图函数register()

        在注册页面首次被请求时,视图函数register()需要显示一个空的注册表单,并在用户提交填写好的注册表单时对其进行处理。如果注册成功,这个函数还需让用户自动登录。请在users/views.py中添加如下代码:

    views.py

    from django.shortcuts import render
    from django.http import HttpResponseRedirect
    from django.core.urlresolvers import reverse
    #从Django核心解析器中导入模块
    from django.contrib.auth import logout,login,authenticate
    from django.contrib.auth.forms import UserCreationForm
        def logout_view(request):
        """注销用户"""
        logout(request)
        return HttpResponseRedirect(reverse('learning_logs:index'))

        def register(request):
        """注册新用户"""
        if request.method != "POST":
            form = UserCreationForm()                                            (1)
        else:
            #处理填写好的表单
            form = UserCreationForm(data=request.POST)                            (2)
       
            if form.is_valid():                                                    (3)
                new_user = form.save()                                             (4)
            #让用户自动登录,再重定向到主页
                authenticated_user = authenticate(username=new_user.username,password=request.POST['password1'])     (5)
                login(request,authenticated_user)                                                                    (6)
                return HttpRedponseRedirect(reverse('learning_logs:index'))                                           (7)

        context = {'form":from}
        return render(request,'users/register.html',context)
        我们首先导入了函数render(),然后导入了函数login()和authenticate(),以便在用户正确地填写了注册信息时让其自动登录。我们还导入了默认表单UserCreationForm.在函数register()中,我们检查要响应的是否是POST请求。如果不是,就创建一个UserCreationForm实例,且不给它提供任何初始数据(见(1)).

        如果响应的是POST请求,我们就根据提交的数据创建一个UserCreationForm实例(见(2)),并检查这些数据是否有效:就这里而言,是用户名未包含非法字符,输入的两个密码相同,以及用户没有视图做恶意的事情。

        如果提交的数据有效,我们就调用表单的方法save(),将用户名和密码的散列值保存到数据库中。方法save()返回新创建的用户对象,我们将其存储在new_user中。

        保存用户的信息后,我们让用户自动登录,这包含两个步骤。首先,我们调用authenticate(),并将实参new_user.username和密码传递给它(见(5))。用户注册时,被要求输入密码两次;由于表单是有效的,我们知道输入的这两个密码是相同的,因此可以使用其中任何一个。在这里,我们从表单的POST数据中获取与'password1'相关联的值。如果用户名和密码无误,方法authenticate()将返回一个通过了身份验证的用户对象,而我们将其存储在authenticated_user中。接下来,我们调用函数login(),并将对象request和authenticated_user传递给它,这将为新用户创建有效的会话。最后,我们将用户重定向到主页,其页眉中显示了一条个性化的问候语,让用户知道注册成功了。

        3.注册模板

        注册页面的模板与登录页面的模板类似,请务必将其保存到login.html所在的目录中:

    register.html

    {% extends 'learning_logs/base.html' %}

    {% block content %}

      <form method="post" action="{% url 'users:register' %}">
        {% csrf_token %}
        {{ form.as_p }}
        
        <button name="submit">register</button>
        <input type="hidden" name="text" value="{% url 'learning_logs:index' %}" />
      </form>

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

        4.链接到注册页面

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

        base.html

    <p>
      <a href="{% url 'learning_logs:index' %}">Learning Log</a> -
      <a href="{% url 'learning_logs:topics' %}">Topics</a> -
      {% if user.is_authenticated %}
        Hello,{{ user.username }}.
        <a href="{% url 'users:logout' %}">log out</a>
      {% else %}
        <a href="{% url 'users:register' %}">register</a> -
        <a href="{% url 'users:login' %}">log in</a>
      {% endif %}
    </p>

    {% block content %}{% endblock content %}

        现在,已登录的用户看到的是个性化问候语和注销链接,而未登录的用户看到的是注册和登录链接。请尝试使用注册页面创建几个用户名各不相同的用户账户。

        在下一节,我们将对一些页面进行限制,仅让已登录的用户访问它们,我们还将确保每个主题都属于特定用户。

    注意:这里的注册系统允许用户创建任意数量的账户。有些系统要求用户确认其身份:发送一封邮件,用户回复后其账户才生效。通过这样做,系统生成的垃圾账户将比这里使用的简单系统少。然而,学习创建应用程序时,完全可以像这里所做的那样,使用简单的用户注册系统。

    19.3  让用户拥有自己的数据

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

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

    19.3.1  使用@login_required限制访问

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

        1.限制对topics页面的访问

        每个主题都归特定用户所有,因此只允许已登录的用户请求topics页面。为此,在learning_logs/views.py中添加如下代码:

        views.py

    from django.shortcuts import render
    from django.http import HttpResponseRedirect
    from django.core.urlresolvers import reverse
    from django.contrib.auth.decorators import login_requried
    from .models import Topic,Entry
    from .forms import TopicForm,EntryForm

    # Create your views here.
    def index(request):
        """学习笔记的主页"""
        return render(request,'learning_logs/index.html')

    @login_required
    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'))

    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]))

    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/edit_entry.html',context)

        我们首先导入了函数login_required()。我们将login_required()作为装饰器用于视图函数topics()——在它前面加上@login_required,让Python在运行topics()的代码前先运行login_required()的代码。

        login_required()的代码检查用户是否已登录,仅当用户登录时,Django才运行topics()的代码。如果用户未登录,就重定向到登录页面。

        为实现这种重定向,我们需要修改settings.py,让Django知道到哪里去查找登录页面。请在settings.py末尾添加如下代码:

    settings.py

    """
    Django settings for learning_log project.

    Generated by 'django-admin startproject' using Django 1.11.

    For more information on this file, see
    https://docs.djangoproject.com/en/1.11/topics/settings/

    For the full list of settings and their values, see
    https://docs.djangoproject.com/en/1.11/ref/settings/
    """

    import os

    # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
    BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


    # Quick-start development settings - unsuitable for production
    # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/

    # SECURITY WARNING: keep the secret key used in production secret!
    SECRET_KEY = 'lw7xtyu&b2et$$!)$m*ja6flzz#1@-653ief@15p!5_%!(%d!b'

    # SECURITY WARNING: don't run with debug turned on in production!
    DEBUG = True

    ALLOWED_HOSTS = []


    # Application definition

    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        #我的应用程序
        "learning_logs",
        'users',
    ]

    MIDDLEWARE = [
        'django.middleware.security.SecurityMiddleware',
        'django.contrib.sessions.middleware.SessionMiddleware',
        'django.middleware.common.CommonMiddleware',
        'django.middleware.csrf.CsrfViewMiddleware',
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'django.contrib.messages.middleware.MessageMiddleware',
        'django.middleware.clickjacking.XFrameOptionsMiddleware',
    ]

    ROOT_URLCONF = 'learning_log.urls'

    TEMPLATES = [
        {
            'BACKEND': 'django.template.backends.django.DjangoTemplates',
            'DIRS': [],
            'APP_DIRS': True,
            'OPTIONS': {
                'context_processors': [
                    'django.template.context_processors.debug',
                    'django.template.context_processors.request',
                    'django.contrib.auth.context_processors.auth',
                    'django.contrib.messages.context_processors.messages',
                ],
            },
        },
    ]

    WSGI_APPLICATION = 'learning_log.wsgi.application'


    # Database
    # https://docs.djangoproject.com/en/1.11/ref/settings/#databases

    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.sqlite3',
            'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
        }
    }


    # Password validation
    # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators

    AUTH_PASSWORD_VALIDATORS = [
        {
            'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
        },
        {
            'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
        },
        {
            'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
        },
        {
            'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
        },
    ]


    # Internationalization
    # https://docs.djangoproject.com/en/1.11/topics/i18n/

    LANGUAGE_CODE = 'en-us'

    TIME_ZONE = 'UTC'

    USE_I18N = True

    USE_L10N = True

    USE_TZ = True


    # Static files (CSS, JavaScript, Images)
    # https://docs.djangoproject.com/en/1.11/howto/static-files/

    STATIC_URL = '/static/'
    #我的设置
    LOGIN_URL = '/users/login/'

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

        要测试这个设置,可注销并进入主页。然后,单击链接Topics,这将重定向到登录页面。接下来,使用我们的账户登录,并再次单击主页中的Topics链接,我们将看到topics页面。

        2.全面显示对项目"学习笔记"的访问

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

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

        在下面的learning_logs/views.py中,对除index()外的每个视图都应用了装饰器@login_required:

    views.py

    from django.shortcuts import render
    from django.http import HttpResponseRedirect
    from django.core.urlresolvers import reverse
    from django.contrib.auth.decorators import login_requried
    from .models import Topic,Entry
    from .forms import TopicForm,EntryForm

    # Create your views here.
    def index(request):
        """学习笔记的主页"""
        return render(request,'learning_logs/index.html')

    @login_required
    def topics(request):
        """显示所有的主题"""
        topics = Topic.objects.order_by("date_added")
        context = {'topics':topics}
        return render(request,'learning_logs/topics.html',context)

    @login_required
    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)

    @login_required
    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'))

    @login_required
    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]))

    @login_required
    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/edit_entry.html',context)

        如果我们在未登录的情况下尝试访问这些页面,将被重定向到登录页面。另外,我们还不能单击到new_topic等页面的链接。但如果输入URL http://localhost:8000/new_topic/,将重定向到登录页面。对于所有与私有用户数据相关的URL,都应限制对它们的访问。

    19.3.2  将数据关联到用户

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

        下面来修改模型Topic,在其中添加一个关联到用户的外键。这样做后,我们必须对数据库进行迁移。最后,我们必须对有些视图进行修改,使其只显示与当前登录的用户相关联的数据。

        1.修改模型Topic

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

    models.py

    from django.db import models
    from django.contrib.auth.models import User

    # Create your models here.
    class Topic(models.Model):
        '''用户学习的主题'''
        text = models.CharField(max_length=200)
        date_added = models.DateTimeField(auto_now_add=True)
        owner = models.ForeignKey(User)

        def __str__(self):
            '''返回模型的字符串表示'''
            return self.text

    class Entry(models.Model):
        """学到的有关某个主题的具体知识"""
        topic = models.ForeignKey(Topic)
        text = models.TextField()
        date_added = models.DateTimeField(auto_now_add=True)

        class Meta:
            verbose_name_plural = 'entries'

            def __str__(self):
                """返回模型的字符串表示"""
                return self.text[:50] + "..."

        2.确定当前有哪些用户

        我们迁移数据库时Django将对数据库进行修改,使其能够存储主题和用户之间的关联。为执行迁移,Django需要知道该将各个既有主题关联到哪个用户。最简单的方法是,将既有主题都关联到同一个用户,如超级用户。为此,我们需要知道该用户的ID。

        下面来查看已创建的所有用户的ID.为此,启动一个Django shell会话,并执行如下命令:

        3.迁移数据库

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

        我们首先执行了命令makemigrations.

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

        当前,不管我们以哪个用户的身份登录,都能够看到所有的主题。我们来改变这种情况,只向用户显示属于自己的主题。

        在views.py中,对函数topics()做出如下修改:

    views.py

    from django.shortcuts import render
    from django.http import HttpResponseRedirect
    from django.core.urlresolvers import reverse
    from django.contrib.auth.decorators import login_requried
    from .models import Topic,Entry
    from .forms import TopicForm,EntryForm

    # Create your views here.
    def index(request):
        """学习笔记的主页"""
        return render(request,'learning_logs/index.html')

    @login_required
    def topics(request):
        """显示所有的主题"""
        topics = Topic.objects.filter(owner=request.user).order_by("date_added")
        context = {'topics':topics}
        return render(request,'learning_logs/topics.html',context)

    @login_required
    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)

    @login_required
    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'))

    @login_required
    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]))

    @login_required
    def edit_entry(request,entry_id):
        #编辑既有条目
        entry = Entry.objects.get(id=entry.id)
        topic = entry.topic
        if request.method != "POST":
            #初次请求,使用当前条目填充表单
        orm = 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/edit_entry.html',context)

        用户登录后,request对象将有一个user属性,这个属性存储了有关该用户的信息。代码Topic.objects.filter(owner=request.user)让Django只从数据库中获取owner属性为当前用户Topic对象。由于我们没有修改主题的显示方式,因此无需对页面topics的模板做任何修改。

        要查看结果,以所有既有主题关联到的用户的身份登录,并访问topics页面,我们将看到所有的主题。然后,注销并以另一个身份登录,topics页面将不会列出任何主题。

    19.3.4  保护用户的主题

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

        我们自己试一试就明白了。以拥有所有主题的用户身份登录,访问特定的主题,并复制该网页的URL,或将其中的ID记录下来。然后,注销并以另一个用户的身份登录,再输入显示前述主题的页面的URL.虽然我们是以另一个用户登录的,但依然能够查看该主题的条目。

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

    views.py

    from django.shortcuts import render
    from django.http import HttpResponseRedirect, Http404                      (1)
    from django.core.urlresolvers import reverse
    from django.contrib.auth.decorators import login_required

    from .models import Topic, Entry
    from .forms import TopicForm, EntryForm

    def index(request):
        """The home page for Learning Log."""
        return render(request, 'learning_logs/index.html')

    @login_required
    def topics(request):
        """Show all topics."""
        topics = Topic.objects.filter(owner=request.user).order_by('date_added')
        context = {'topics': topics}
        return render(request, 'learning_logs/topics.html', context)

    @login_required
    def topic(request, topic_id):
        """Show a single topic, and all its entries."""
        topic = Topic.objects.get(id=topic_id)
        # 确认请求的主题属于当前用户
        if topic.owner != request.user:                                     (2)
            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(见(1)),并在用户请求它不能查看的主题时引发这个异常。收到主题请求后,我们在渲染网页前检查该主题是否属于当前登录的用户。如果请求的主题不归当前用户所有,我们 就引发Http404异常,让Django返回一个404错误页面。

        现在,如果我们视图查看其他用户的主题条目,将看到Django发送的Page Not Found。在第20章,我们将对这个项目进行配置,让用户看到更合适的错误页面。

    19.3.5  保护页面 edit_entry

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

    views.py

    @login_required
    def edit_entry(request, entry_id):
        """Edit an existing entry."""
        entry = Entry.objects.get(id=entry_id)
        topic = entry.topic
        if topic.owner != request.user:
            raise Http404

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

    19.3.6  将新主题关联到当前用户

        当前,用于添加新主题的页面存在问题,因此它没有将新主题关联到特定用户。如果我们尝试添加新主题,将看到错误消息IntegritError,指出learning_logs_topic.user_id不能为NULL。Django的意思是说,创建新主题时,我们必须指定其owner字段的值。

        由于我们可以通过request对象获悉当前用户,因此存在一个修复这种问题的简单方案。请添加下面的代码,将新主题关联到当前用户:

    views.py

    @login_required
    def new_topic(request):
        """Add a new topic."""
        if request.method != 'POST':
            # No data submitted; create a blank form.
            form = TopicForm()
        else:
            # POST data submitted; process data.
            form = TopicForm(request.POST)
            if form.is_valid():
                new_topic = form.save(commit=False)           (1)
                new_topic.owner = request.user                (2)
                new_topic.save()                              (3)
                return HttpResponseRedirect(reverse('learning_logs:topics'))

        context = {'form': form}
        return render(request, 'learning_logs/new_topic.html', context)

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

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

    19.4  小结

        在本章中,我们学习了如何使用表单来让用户添加新主题、添加新条目和编辑既有条目。接下来,我们学习了如何实现用户账户。我们让老用户能够登录和注销,并学习了如何使用Django提供的表单UserCreationForm让用户能够创建新账户。

        建立简单的用户身份验证和注册系统后,我们通常使用装饰器@login_requried禁止未登录的用户访问特定页面。然后,我们通过使用外键将数据关联到特定用户,我们还学习了如何执行要求指定默认数据的数据库迁移。

        最后,我们学习了如何修改视图函数,让用户只能看到属于他的数据。我们使用方法filter()来获取合适的数据,并学习了如何将请求的数据的所有者同当前登录的用户进行比较。

        该让哪些数据可随便访问,该队那些数据进行保护呢?这可能并非总是那么显而易见,但通过不断地练习就能掌握这种技能。在本章中,我们就该如何保护用户数据所做的决策表明,与人合作开发项目是个不错的注意:有人对项目进行检查的话,更容易发现其薄弱环节。

        至此,我们创建了一个功能齐备的项目,它运行在本地计算机上。在本书的最后一章,我们将设置这个项目的样式,使其更漂亮;我们还将把它部署到一台服务器上,让任何人都可通过互联网注册并创建账户。


     

     

     

     

       

       

       

       

       

     

  • 相关阅读:
    hdu 3342 Legal or Not 拓排序
    hdu 1596 find the safest road Dijkstra
    hdu 1874 畅通工程续 Dijkstra
    poj 2676 sudoku dfs
    poj 2251 BFS
    poj Prime Path BFS
    poj 3278 BFS
    poj 2387 Dijkstra 模板
    poj 3083 DFS 和BFS
    poj 1062 昂贵的聘礼 dijkstra
  • 原文地址:https://www.cnblogs.com/gengcx/p/6687003.html
Copyright © 2011-2022 走看看