• 《Python编程:从入门到实践》第19章笔记:用户/用户注册/身份验证


    接上篇django最基本的一些日常用法,这是第19章笔记,希望在做“动手试一试”的时候可以让自己方便参考。

    这一章实现了两个功能:

    1、让用户能够添加主题Topic和条目Entry,以及编辑既有的条目。

    2、建立一个用户注册和身份验证系统,让用户能够注册账户,进而登录和注销。

    让用户能够输入数据

    一、让用户能够添加新主题(Topic)

    关于表单

    让用户输入并提交信息的页面都是表单。用户输入时,我们需要进行验证,确认提供的信息是正确的数据类型,而不是恶意的信息。然后我们再对这些有效信息进行处理,并将其保存到数据库的合适地方。

    在Django中,可使用ModelForm创建表单

    在models.py所在目录创建forms.py文件。

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

    其中内嵌的Meta类告诉Django根据哪个模型创建表单,以及在表单中包含哪些字段。

    URL

    在learning_logs/urls.py中

    path('new_topic/', views.new_topic, name='new_topic'),

    视图

    views.py中加入如下内容

    from django.http import HttpResponseRedirect
    from django.urls import reverse
    from .forms import TopicForm
    
    
    def new_topic(request):
        # 如果未提交数据,创建一个新表单
        if request.method != 'POST':
            form = TopicForm()
            
        # 否则对数据进行处理
        else:
            # 用户输入的数据存在request.POST中
            form = TopicForm(request.POST)
            # 检查是否有效,有效就保存
            if form.is_valid():
                form.save()
                #保存后就可离开这个页面了,用reverse()获取也页面topics的URL
                return HttpResponseRedirect(reverse('learning_logs:topics'))
    
        context = {'form': form}
        return render(request, 'learning_logs/new_topic.html', context)
    
    
    • 导入部分

      • 导入HttpResponseRedirect类,用户提交主题后使用这个类将用户重定向到网页topics。
      • 函数reverse()根据指定的URL模型确定URL。
    • 关于GET请求和POST请求

      ​ 从服务器读取数据的页面,使用GET请求;需要通过表单提交信息时,通常使用POST请求。

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

    模板

    创建new_topic.html

    {% extends "learning_logs/base.html" %}
    
    {% block content %}
      <p>Add a new topic:p>
    
    <form action="{% url 'learning_logs:new_topic' %}" method="post">
      {% csrf_token %}
      {{ form.as_p }}
      <button name="submit">add topicbutton>
    form>
    {% endblock content %}
    
    • Django使用模板标签{% csrf_token %}来防止攻击者利用表单来获取对服务器未经授权的访问(跨站请求伪造)
    • 修饰符as_p让Django以段落格式渲染所有表单元素。

    链接

    在页面topics中添加一个到页面new_topic的链接

    加入

    Add a new topic:

    二、让用户能够添加新条目(Entry)

    表单

    form.py

    class EntryForm(forms.ModelForm):
        class Meta:
            model = Entry
            fields = ['text']
            labels = {'text': ''}
            widgets = {'text': forms.Textarea(attrs={'cols': 80})}
    
    

    其中设置属性widgets可以覆盖Django选择的默认小部件。这里让Django使用forms.Textarea,定制来字段'text'的输入小部件,将文本区域的宽度设置为80列。

    URL

    在learning_logs/urls.py中加入:

    path('new_entry/', views.new_entry, name='new_entry'),

    视图

    views.py

    def new_entry(request, topic_id):
        _topic = Topic.objects.get(id=topic_id)
    
        if request.method != 'POST':
            form = EntryForm()
        else:
            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)
    
    

    这个与前面new_topic()类似,区别是

    • 调用save()时,传递来实参commit=False,让Django创建一个新的Entry对象,并将其存储到new_entry中,但不将它保存到数据库中。
    • 调用reverse()时,列表args,其中包含在URL中的所有实参,这里列表中只有一个元素topic_id。

    模板

    new_entry.html

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

    其中,表单的实参action包含URL中的topic_id,让视图函数能够将新entry关联到正确的主题。

    链接

    在topic.html中加入

    <p>
        <a href="{% url 'learning_logs:new_entry' topic.id %}">add new entrya>
    p>
    

    三、让用户能够编辑既有条目

    URL

        path('edit_entry/', views.edit_entry, name='edit_entry'),
    

    视图

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

    模板

    edit_entry.html

    {% extends "learning_logs/base.html" %}
    
    {% block content %}
    
    <P><a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}a>P>
    
    <p>Edit entry:p>
    
    <form action="{% url 'learning_logs:edit_entry' entry.id %}" method="post">
        {% csrf_token %}
        {{ form.as_p }}
        <button name="submit">save changesbutton>
    form>
    {% endblock content %}
    

    链接

    在topic.html中加入

    <p>
         <a href="{% url 'learning_logs:edit_entry' entry.id %}">edit entrya>
    p>
    

    用户注册和身份验证系统

    一、创建用户账户

    创建应用程序users

    输入命令:python manage.py startapp users

    将users添加到settings.py中:在INSTALLED_APPS加入 'users'

    应用程序users的URL:在项目根目录的urls.py 加入path('users/', include('users.urls')),

    登录页面

    在users中新建一个urls.py
    from django.urls import path
    from django.contrib.auth.views import LoginView
    
    from . import views
    
    app_name = 'users'
    urlpatterns = [
        path('login/', LoginView.as_view(template_name='users/login.html'),
             name='login'),
    ]
    

    这里书上是旧版,前面引用的是from django.contrib.auth.views import login,而新版的Django中内置登录视图不再是函数了,而是类。类视图有个as_view方法,template_name是类视图中的一个变量,默认值是"registration/login.html"。

    模板

    在users中创建一个名为templates的目录,再在该目录下创建一个名为users的目录。在其中login.html

    {% extends "learning_logs/base.html" %}
    
    {% block content %}
    {% if form.errors %}
    <p>Your username and password didn't match. Please try again.p>
    {% endif %}
    
    <form method="post" action="{% url 'users:login' %}">
        {% csrf_token %}
        {{ form.as_p }}
    
        <button name="submit">log inbutton>
        <input type="hidden" name="next" value="{% url 'learning_logs:index' %}"/>
    form>
    
    {% endblock content %}
    
    链接

    在base.html中添加登录页面的链接,改成这样

    <p>
        <a href="{% url 'learning_logs:index' %}">Learning Loga> -
        <a href="{% url 'learning_logs:topics' %}">Topicsa>
        {% if user.is_authenticated %}
        Hello, {{user.username}}.
        {% else %}
        <a href="{% url 'users:login' %}">log ina>
        {% endif%}
    p>
    

    用户注销

    让用户只需单击一个链接就能注销并返回到主页。

    URL

    users/urls.py中:path('logout/', views.logout_view, name='logout'),

    视图

    users/views.py

    from django.shortcuts import render
    from django.http import HttpResponseRedirect
    from django.urls import reverse
    from django.contrib.auth import logout
    
    def logout_view(request):
        logout(request)
        return HttpResponseRedirect(reverse('learning_logs:index'))
    
    链接

    在在base.html中添加注销链接,改成这样

    <p>
        <a href="{% url 'learning_logs:index' %}">Learning Loga> -
        <a href="{% url 'learning_logs:topics' %}">Topicsa>
        {% if user.is_authenticated %}
        Hello, {{user.username}}.
        <a href="{% url 'users:logout' %}">log outa>
        {% else %}
        <a href="{% url 'users:login' %}">log ina>
        {% endif%}
    p>
    

    注册界面

    url:path('register/', views.register, name='register'),

    视图

    views.py变成这样

    from django.shortcuts import render
    from django.http import HttpResponseRedirect
    from django.urls import reverse
    
    from django.contrib.auth import login, logout, authenticate
    from django.contrib.auth.forms import UserCreationForm
    
    
    # Create your views here.
    def logout_view(request):
        logout(request)
        return HttpResponseRedirect(reverse('learning_logs:index'))
    
    
    def register(request):
        if request.method != 'POST':
            form = UserCreationForm()
        else:
            form = UserCreationForm(data=request.POST)
    
            if form.is_valid():
                new_user = form.save()
                authenticated_user = authenticate(username=new_user.username,
                                                  password=request.POST['password1'])
                login(request, authenticated_user)
                return HttpResponseRedirect(reverse('learning_logs:index'))
    
        context = {'form': form}
        return render(request, 'users/register.html', context)
    
    
    折叠
    模板

    register.html

    {% extends "learning_logs/base.html" %}
    
    {% block content %}
    
    <form method="post" action="{% url 'users:register' %}">
        {% csrf_token %}
        {{ form.as_p }}
    
        <button name="submit">registerbutton>
        <input type="hidden" name="next" value="{% url 'learning_logs:index' %}"/>
    form>
    
    {% endblock content %}
    
    链接

    base.html加入register

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

    在这里要确定各项数据所属的用户,再限制对页面的访问,让用户只能使用自己的数据。

    使用@login_required限制访问

    在函数前面加上这个装饰器。它的代码会检查用户是否已经登录,仅当登录时,才运行下面函数的代码。如果未登录就重定向到登录界面。

    为了实现重定向,修改settings.py,在末尾添加:LOGIN_URL = '/users/login/'

    然后把learning_logs/views.py中除了index的每个函数都加上这个装饰器。

    将数据关联到用户

    修改models.py中的Topic

    from django.contrib.auth.models import User

    再在Topic类中加上字段owner:owner = models.ForeignKey(User)

    可以通过shell查看现在有哪些用户:

    迁移数据库

    python manage.py makemigrations learning_logs

    出现提示,选1

    It is impossible to add a non-nullable field 'owner' to topic without specifying a default. This is because the database needs something to populate existing rows.
    Please select a fix:
     1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
     2) Quit and manually define a default value in models.py.
    Select an option: 
    

    下面输入的值是将主题(Topic)关联到的用户id号,这里1,关联到eisen

    Please enter the default value as valid Python.
    The datetime and django.utils.timezone modules are available, so it is possible to provide e.g. timezone.now as a value.
    Type 'exit' to exit this prompt
    >>> 1
    

    然后python manage.py migrate

    然后可通过shell看到每个Topic所属的User了,都是eisen。

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

    经过上面操作,虽然......

    但是登录了eisen后,复制这个页面的URLlocalhost:8000/topics/2,登录JX帐号后再输入这个URL也看到下面的内容了。

    做点修改即可,views.py中topics函数中,加个fliter,让Django只从数据库中获取owner属性为当前用户的Topic对象。

    _topics = Topic.objects.filter(owner=request.user).order_by('date_added')

    再限制用户对单个主题的页面的访问。在topic函数中,加个判断,如果不属于这个用户就404

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

    edit_entry中也一样加入这个判断if _topic.owner != request.user:raise Http404

    新主题创建时也应关联到当前用户

    new_topic函数中,判断有效后,先调用form.save并传递实参commit=False,先修该新主题,在将其保存到数据库中。再将owner属性设置为当前用户。

            if form.is_valid():
                _new_topic = form.save(commit=False)
                _new_topic.owner = request.user
                _new_topic.save()
                return HttpResponseRedirect(reverse('learning_logs:topics'))
    

    于是就成功了。

    最后贴几张图:


  • 相关阅读:
    【C++语言】继承
    儿童剧本杀兴起,为少儿教育增色还是添乱?
    C语言实现通讯录(超详细)
    简单实现接口自动化测试(基于python+unittest)
    浅析微前端框架 single-spa
    npp各个平台npp数据比较
    爱奇艺视频怎么转换成mp4格式,爱奇艺qsv转换mp4最简单方法
    图解TCP/IP(第五版)&& 计算机网络 -- 学习笔记和读后感(学习补充中~~~)
    华为eNSP配置专题-VRRP的配置
    【Promise】一文带你了解promise并解决回调地狱
  • 原文地址:https://www.cnblogs.com/eisenji/p/16526726.html