《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 topic</button>
</form>
{% endblock content %}
  • Django使用模板标签{% csrf_token %}来防止攻击者利用表单来获取对服务器未经授权的访问(跨站请求伪造)
  • 修饰符as_p让Django以段落格式渲染所有表单元素。

链接

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

加入

<a href="{% url 'learning_logs:new_topic' %}">Add a new topic:</a>

二、让用户能够添加新条目(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/<int:topic_id>', 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 entry</button>
</form>
{% endblock content %}

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

链接

在topic.html中加入

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

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

URL

    path('edit_entry/<int:entry_id>', 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 changes</button>
</form>
{% endblock content %}

链接

在topic.html中加入

<p>
     <a href="{% url 'learning_logs:edit_entry' entry.id %}">edit entry</a>
</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 in</button>
    <input type="hidden" name="next" value="{% url 'learning_logs:index' %}"/>
</form>

{% endblock content %}
链接

在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>

用户注销

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

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

注册界面

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

{% endblock content %}
链接

base.html加入<a href="{% url 'users:register' %}">register</a>

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

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

使用@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'))

于是就成功了。

最后贴几张图:


posted @ 2022-07-27 22:11  EisenJi  阅读(764)  评论(0编辑  收藏  举报