• 用户账户——《Python编程从入门到实践》


    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

    # -*- coding: utf-8 -*-
    from __future__ import unicode_literals
    
    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.
    
    --snip--
    
    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)
    
    

    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的链接:

    {% extends "learning_logs/base.html" %}
    
    {% block content %}
    <p>Topics</p>
    
    <ul>
    	--snip--
    </ul>
    
    <a href="{% url 'learning_logs:new_topic' %}">Add a new topic:</a>
    
    {% endblock content %}
    

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

    2.添加新条目:

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

    用于添加新条目的表单

    # -*- coding:utf-8 -*-
    from django import forms
    from .models import Topic, Entry
    
    --snip--
    
    class EntryForm(forms.ModelForm):
        class Meta:
            model = Entry
            fields = ['text']
            labels = {'text': ''}
            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

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

    这个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很像

    # -*- coding: utf-8 -*-
    from __future__ import unicode_literals
    
    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.
    
    --snip--
    
    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请求,将执行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

    {% 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 entry</button>
    </form>
    
    {% endblock content %}
    

    我们在页面顶端显示了主题,让用户知道他是在那个主题中添加条目;该主题名也是一个链接,可用于返回到该主题的主页面
    表单的action包含URL中的topic_id值,让视图函数能够将新条目关联到正确的主题

    链接到页面new_entry

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

    {% 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>
       --snip--
    </ul>
    
    {% endblock content %}
    

    尝试添加新条目

    3.编辑条目:

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

    URL模式edit_entry

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

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

    视图函数edit_entry()

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

    # -*- coding: utf-8 -*-
    from __future__ import unicode_literals
    
    from django.shortcuts import render
    from django.http import HttpResponseRedirect
    from django.core.urlresolvers import reverse
    
    from .models import Topic, Entry
    from .forms import TopicForm, EntryForm
    
    
    # Create your views here.
    
    --snip--
    
    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)
    
    

    我们要导入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类似

    {% 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 changes</button>
    </form>
    
    {% endblock content %}
    
    

    链接到页面edit_entry

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

    --snip--
        {% 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>
    --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/中

    {% 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 in</button>
        <input type="hidden" name="next" value="{% url 'learning_logs:index' %}"/>
    </form>
    
    {% endblock content %}
    

    这个模板继承了base.html,保证在登录页面的外观与网站的其他页面相同。注意:一个应用程序中的模板可继承另一个应用程序的模板
    如果表单errors属性被设置,就显示一条错误信息,提示用户用户名密码不匹配,请重试
    action设置为登录页面URL让登录视图处理表单,登录视图返回一个表单发送给模板,在模板中我们显示该表单,并添加提交按钮。另外,我们包含了一个隐藏的表单元素——‘next',其中value告诉Django在用户成功登录后将其重定向到什么地方

    链接到登录页面
    下面在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 }}
        {% else %}
            <a href="{% url 'users:login' %}">log in</a>
        {% endif %}
    </p>
    
    {% 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

    --snip--
    urlpatterns = [
        # 登陆页面
        --snip--
        # 注销
        url(r'^logout/$', views.logout_view, name='logout')
    ]
    
    

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

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

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

    --snip--
        {% 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>
    --snip--
    

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

    4.注册页面

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

    注册页面的URL模式

    --snip--
    urlpatterns = [
        # 登陆页面
        --snip--
        # 注册页面
        url(r'^register/$', views.register, name='register'),
    ]
    
    

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

    视图函数register()

    # -*- coding: utf-8 -*-
    from __future__ import unicode_literals
    
    from django.shortcuts import render
    from django.http import HttpResponseRedirect
    from django.core.urlresolvers import reverse
    from django.contrib.auth import logout, login, authenticate
    from django.contrib.auth.forms import UserCreationForm
    
    def logout_view(request):
        --snip--
    
    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)
    
    

    我们导入了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在一个目录下:

    {% 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="next" value="{% url 'learning_logs:index' %}"/>
    </form>
    
    {% endblock content %}
    

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

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

    --snip--
        {% 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 %}
    --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让你能够轻松地限制对页面的访问,但你必须针对要保护哪些页面做出决定。最好先确定项目的哪些页面不需要保护,再限制对其他所有页面的访问。你可以轻松地修改过于严格的访问限制,其风险比不限制对敏感页面地访问更低
    在“学习笔记”项目中,我们不限制对主页、注册页面和注销页面的访问,并限制对其他所有页面的访问

    --snip--
    @login_required
    def topics(request):
    --snip
    
    
    @login_required
    def topic(request, topic_id):
    --snip--
    
    
    @login_required
    def new_topic(request):
    --snip--
    
    
    @login_required
    def new_entry(request, topic_id):
    --snip--
    
    
    @login_required
    def edit_entry(request, entry_id):
    --snip--
    

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

    2.将数据关联到用户

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

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

    # -*- coding: utf-8 -*-
    from __future__ import unicode_literals
    
    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 __unicode__(self):
            """返回模型的字符串表示"""
            return self.text
    
    --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()做如下修改:

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

    用户登录后,request对象将有一个user属性,这个属性存储了有关该用户的信息。Topic.objects.filter(owner=request.user)让Django只从数据库中获取owner属性为当前用户的Topic对象
    要验证结果,我们以既有主题关联到的用户身份登录,访问topics页面,将看到所有的主题

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

    4.保护用户的主题

    我们还没限制对显示单个主题的页面的访问,因此任何已登录的用户都可以输入类似于:http://localhost:8000/topics/1/ 的URL,来访问显示相应主题的页面
    为了修复这种问题,我们在视图函数topic()获取请求的条目前执行检察:

    from django.shortcuts import render
    from django.http import HttpResponseRedirect, Http404
    from django.core.urlresolvers import reverse
    --snip--
    
    @login_required
    def 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)
    --snip--
    

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

    5.保护页面edit_entry

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

    --snip--
    @login_required
    def 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':
            # 初次请求,使用当前条目填充表单
            --snip--
    

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

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

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

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

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

    我们首先调用form.save(),传递实参commit=False,因为我们先修改新主题,再对其保存到数据库中。接下来,将新主题的owner属性设置为当前用户。最后对刚定义的主题实例调用save()。现在新主题包含所有必不可少的数据,将被成功地保存。
    现在,这个项目允许任何用户注册,而且用户想添加多少新主题都可以。每个用户都只能访问自己的数据,无论是查看数据、输入新数据还是修改旧数据时都如此。

  • 相关阅读:
    如何用视频云技术,搞一个爆红的 “反应视频” 项目?
    停车场事故频频,AI 达人将摄像头变身安全卫士
    WebRTC 传输安全机制第二话:深入显出 SRTP 协议
    阿里云视频云 Retina 多媒体 AI 体验馆开张啦!
    20 岁发表 SCI 的学霸,梦想用算法改变世界
    阿里绩效考核,简单到不可思议,员工死心塌地跟你干!(转)
    【官方】阿里巴巴合伙人制度全文(转)
    blob视频地址如何下载(转)
    软件开发项目规划时,SA、SD与SE的区别与重要性 【转】
    一分钟看懂公有云和私有云的区别
  • 原文地址:https://www.cnblogs.com/xiaobing666/p/11167500.html
Copyright © 2020-2023  润新知