0

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

 1 year ago
source link: https://www.cnblogs.com/eisenji/p/16526726.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

接上篇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根据哪个模型创建表单,以及在表单中包含哪些字段。

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

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

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

    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>

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

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查看现在有哪些用户:

2321451-20220727220637409-2086417860.png
迁移数据库

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。

2321451-20220727220649874-1716206489.png

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

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

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

2321451-20220727220705762-1871065699.png

做点修改即可,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'))

于是就成功了。

最后贴几张图:

2321451-20220727220715324-1292034540.png
2321451-20220727220722946-1167350801.png
2321451-20220727220731978-268810315.png
2321451-20220727220738793-1508513386.png

2321451-20220727220747713-1310912607.png
2321451-20220727220754276-1458739374.png
2321451-20220727220800333-1826152526.png

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK