• 项目3 Web应用程序(第19章:用户账户)


    19.1 用户能够输入数据

      我们不想让用户与管理网站交互,因此我们将使用Django的表单来创建让用户能够输入数据的页面。

    19.1.1 添加新主题

      创建基于表单的页面的方法交互与前面创建网页一样:定义一个URL,编写一个视图函数并编写一个模板。一个主要差别是,需要导入包含表单的模块forms.py。

      1、用于添加主题的表单

      在Django中,创建表单的最简单的方式是使用ModelForm。创建一个名为forms.py所在的目录中,并在其中编写你的第一个表单:

    from django import forms
    from .models import Topic
    
    class TopicForm(forms.ModelForm):
        class Meta:
            model = Topic
            fields = ['text']
            labels = {'text':''}

      最简单的ModelForm版本只包含一个内嵌的Meta类,它告诉Django根据哪个模型创建表单,以及在表单中包含哪些字段。

      2、URL模式new_topic

      这个新网页的URL应简短而具有描述性,因此当用户要添加新主题时,我们将切换到http://localhost:8000/new_topic。

    """定义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'),
    ]

      3、视图函数

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

    from django.shortcuts import render
    from .models import Topic
    from django.http import HttpResponseRedirect
    from django.core.urlresolvers import reverse
    from .forms import TopicForm
    # Create your views here.
    --ship--
    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)

      4、模板new_topic

    {% extends "learning_logs/base.html" %}
    
    {% block content %}
        <p>Add a new topic:</p>
        <form action="{% url 'learning_logs:new_topic' %}" method="post">
            {% csrf_token %}
            {{ form.as_p}}
            <button name="submit">add topic</button>
        </form>
    
    {% endblock content %}

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

      6、链接到页面new_topic

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

    {% extends "learning_logs/base.html" %}
    
    {% block content %}
        <p>Topics</p>
        <ul>
            {% for topic in topics %}
                <li>
                    <a href="{% url 'learning_logs:topic' topic.id %}">{{topic}}</a>
                </li>
            {% empty %}}
                <li>No topics have been added yet.</li>
            {% endfor %}
        </ul>
        <a href="{% url 'learning_logs:new_topic' %}">Add a new topic:</a>
    {% endblock content %}

    19.1.2 添加新条目

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

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

    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':''}
            widgets = {'text': forms.Textarea(attrs={'cols':80})}
            

      小部件(widget)是一个HTML表单元素,如单行文本框、多行文本区域或下拉列表。通过让Django使用forms.Textarea,我们定制了字段‘text’的输入小部件,将文本区域的宽度设置为80,而不是默认的40列。

      2、URL模式new_entry

    # 用于添加新条目的页面
        url(r'^new_entry/(?Ptopic_id>d+)/$',views.new_entry,name='new_entry')

      3、视图函数new_entry

    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)

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

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

      4、模板new_entry.html

    {% extends "learning_logs/base.html" %}
    
    {% block content %}
        <p><a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}</a> </p>
        <p>Add a new entry:</p>
    <form action="{% url 'learning_logs:new_entry' topic.id %}" method="post">
        {% crsf_token %}
        {{ form.as_p}}
        <button name="submit">add entry</button>
    </form>
    
    {% endblock content %}}

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

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

      5、链接到页面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</a>
        </p>
        <ul>
           --ship--</ul>
    {% endblock content %}

    19.1.3 编辑条目

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

      1、URL模式edit_entry

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

        # 用于编辑条目的页面
        url(r'^edit_entry/(P<entry_id>d+)/$',views.edit_entry,
            name='edit_entry'),

      2、视图函数edit_entry()

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

    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.himl',context)

      3、模板edit_entry

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

      4、链接到页面edit_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>
            {% 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 %}

      将编辑链接放在每个条目的日期和文本后面。

    19.2 创建用户账户

     19.2.1 应用程序user

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

    (11_env) D:learning_log>python manage.py startapp users

      这个命令新建了一个名为user的目录,其结构与应用程序learning_logs相同。

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

     # My apps
        'learning_logs',
        'users',

      2、包含应用程序users的URL

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

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

    19.2.2 登录页面

      首先实现登录页面,为此使用Django提供的默认登录视图,因此URL模式会稍有不同。早目录learning_log/users/中,新建一个名为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')
    ]

      首先导入默认视图login。登录页面的URL模式与URL http://localhost:8000/users/login匹配。这个URL中的单词users让那个Django在users/urls.py中查找,而单词login让它将请求发送给Django默认视图login(视图实参为login,而不是views.login)。

      1、模板login.html

      在目录learning_log/users/中,创建一个名为templates的目录,并在其中创建一个名为users的目录。一下是模板login.html,你应将其存储到目录learning_log/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 %}

      2、链接到登录页面

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

    <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 'user.login' %}}">log in</a>
        {% endif %}
    </p>
    
    {% block content %}{% endblock content %}

      3、使用登录页面

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

      注销后,访问http://localhost:8000/users/login,再登录。

     19.2.3 注销

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

      1、注销URL

        # 注销
        url(r'^logout/$',views.logout_view,name='logout'),

      2、视图函数logout_view()

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

      3、链接到注销视图

    <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.2.4 注册页面

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

      1、注册页面的URL模式

        # 注册页面
        url(r'^register/$',views.register,name='register'),

      2、视图函数register()

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

    def logout_view(request):
        """注销用户"""
        logout(request)
        return HttpResponseRedirect(reverse('learning_logs:index'))
    def register(request):
        """注册新用户"""
        if request.method !='POST':
            # 显示空的注册表单
            form = UserCreationForm(date=request.POST)
        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)

      3、注册模板

    {% 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在表单中正确地显示所有的字段,包括错误消息——如果用户没有正确地填写表单。  

      4、链接到注册页面

    <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
            <a href="{% url 'users:login' %}">log in</a>
        {% endif %}
    </p>
    
    {% block content %}{% endblock content %}

     19.3 让用户拥有自己的数据

    19.3.1 使用@login_required限制访问

      Django提供了装饰器@login_required:对于某些页面,只允许已登陆的用户访问它们。装饰器是放在函数定义前面的命令,Python在函数运行前,根据它来修改函数代码的行为:

      1、限制对topics页面的访问

      在learning_logs/views.py中添加如下代码:

    --ship--
    from .forms import TopicForm,EntryForm
    from django.contrib.auth.decorators import login_required
    # Create your views here.
    def index(request):
        """学习笔记的主页"""
        return render(request,'learning_logs/index.html')
    @login_required
    def topics(request):
        --ship--

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

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

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

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

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

          

      如果你在未登录的情况下尝试访问这些页面,将被重定向到登录页面。另外,你还不能单击到new_topic等页面的链接。

    19.3.2 将数据关联到用户

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

      1、修改模型Topic

    from django.db import models
    from django.contrib.auth.models import User
    # 定义了一个名为Topic的类,它继承了Model—Django中一个定义了模型基本功能的类
    # 属性text是一个CharField—由字符或文本组成的数据。需要存储少量的文本,如名称、标题或城市时,可以使用
    #  date_added是一个DateTimeField—记录日期和时间的数据。
    class Topic(models.Model):
        """用户学习主题"""
        text = models.CharField(max_length=200)
        date_added = models.DateTimeField(auto_now_add=True)
        owner = models.ForeignKey(User)
    --ship--

      2、确定当前有哪些用户

      

      3、迁移数据库

      

           

      为验证迁移符合预期,可在Shell会话中像下面这样做:

           

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

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

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

    19.3.4 保护用户的主题

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

    19.3.5 保护页面edit_entry

    @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':
        --ship--

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

    @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)
  • 相关阅读:
    java Socket Tcp 浏览器和服务器(二)
    java Socket Tcp 浏览器和服务器(一)
    java Socket Tcp示例三则(服务端处理数据、上传文件)
    java Socket Udp聊天
    centos 图形界面和命令行界面切换
    python 文件中的中文编码解决方法
    PostgreSQL与MySQL比较(转)
    Python 的开发环境
    Windows 下 pip和easy_install 的安装与使用
    Linux 下的下载文件命令
  • 原文地址:https://www.cnblogs.com/cathycheng/p/11260562.html
Copyright © 2020-2023  润新知