django 实现用户账户功能

教程来自 Python Crash Course.

主要内容:创建表单,实现用户身份验证系统。

设置成中文界面:将setting.py中的LANGUAGE_CODE设置成zh-hans。(默认是en-us)

1. 让用户能够输入数据

当前只有超级用户能够通过管理网站输入数据,为了让普通用户能够输入数据,需要使用表单创建工具,让用户能够添加新主题、新条目、编辑既有条目。

1.1 添加新主题

1.1.1 用于添加主题的表单

新建文件forms.py,使用辅助类ModelForm来创建一个Form类。

from django import forms

from .models import Topic


class TopicForm(forms.ModelForm):
    class Meta:
        model = Topic # 根据Topic模型创建表单
        fields = ['text'] # 只包含字段text
        labels = {'text': ''} # 不生成标签
1.1.2 URL模式
# ...
urlpatterns = [
	# ...
    path('new_topic/', views.new_topic, name='new_topic'),
]
1.1.3 视图 new_topic
from django.shortcuts import render, redirect

from .models import Topic
from .forms import TopicForm

# ...
def new_topic(request):
    # 添加新主题
    if request.method != 'POST':
        # 刚进入new_topic页面,GET方法,未提交数据,则创建一个空表单
        form = TopicForm()
    else:
        # POST提交了数据,对表单数据进行处理,并将用户重定向到topics页面
        form = TopicForm(data=request.POST)
        # 检查表单是否有效
        if form.is_valid():
            form.save()
            return redirect('learning_logs:topics')

    # 不是POST请求,显示空表单或无效表单
    context = {'form':form}
    return render(request, 'learning_logs/new_topic.html', context)
1.1.4 新主题模板

new_topic.htmlas_p让Django以段落格式渲染所有表单元素。

{% extends "learning_logs/base.html" %}

{% block content %}
  <p>添加新主题</p>

  <form action="{% url 'learning_logs:new_topic' %}" method="post">
      {% csrf_token %}
      {{ form.as_p }}
      <button name="submit">添加主题</button>
  </form>

{% endblock content %}
1.1.5 从topics链接到新主题页面

topics.html

  ...

  </ul>

  <a href="{% url 'learning_logs:new_topic' %}">添加一个新主题</a>

{% endblock content %}
1.1.6 页面效果

1.2 添加新条目

1.2.1 用于添加新条目的表单
# forms.py
from django import forms

from .models import Topic, Entry


class TopicForm(forms.ModelForm):
    class Meta:
        model = Topic # 根据Topic模型创建表单
        fields = ['text'] # 只包含字段text
        labels = {'text': ''} # 不生成标签


class EntryForm(forms.ModelForm):
    class Meta:
        model = Entry
        fields = ['text']
        labels = {'text': ''}
        # 定义text字段的widgets小部件,将文本区域的宽度设置为80列
        widgets = {'text': forms.Textarea(attrs={'cols': 80})}
1.2.2 URL模式
# ...
urlpatterns = [
	# ...
    path('new_entry/<int:topic_id>/', views.new_entry, name='new_entry'),
]
1.2.3 视图new_entry
from django.shortcuts import render, redirect

from .models import Topic
from .forms import TopicForm, EntryForm

# ...
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,需要添加topic属性,所以commit=False表示不提交
            new_entry = form.save(commit=False)
            # 设置对应主题
            new_entry.topic = topic
            # 存入数据库
            new_entry.save()
            return redirect('learning_logs:topic', topic_id=topic_id)

    context = {'topic': topic, 'form': form}
    return render(request, 'learning_logs/new_entry.html', context)
1.2.4 新条目模板

new_entry.html

{% extends "learning_logs/base.html" %}

{% block content %}
  <p><a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}</a></p>

  <p>添加新条目:</p>

  <form action="{% url 'learning_logs:new_entry' topic.id %}" method="post">
      {% csrf_token %}
      {{ form.as_p }}
      <button name="submit">添加条目</button>
  </form>

{% endblock content %}
1.2.5 从topic链接到新条目页面

topic.html

{% extends "learning_logs/base.html" %}

{% block content %}

  <p>主题: {{ topic }}</p>

  <p>条目: </p>

  <p>
      <a href="{% url 'learning_logs:new_entry' topic.id %}">添加新条目</a>
  </p>

  <ul>
...
1.2.6 页面效果

1.3 编辑条目

1.3.1 URL模式
# ...
urlpatterns = [
	# ...
    path('edit_entry/<int:entry_id>/', views.edit_entry, name='edit_entry'),
]
1.3.2 视图edit_entry
from django.shortcuts import render, redirect

from .models import Topic, Entry
from .forms import TopicForm, EntryForm

# ...
def edit_entry(request, entry_id):
    # 编辑已经存在的条目
    # 根据entry_id获取entry和相应topic
    entry = Entry.objects.get(id=entry_id)
    topic = entry.topic

    if request.method != 'POST':
        # 初次请求,使用当前条目填充表单
        form = EntryForm(instance=entry)
    else:
        # POST请求,根据既有条目对象创建一个表单实例,并根据request.POST对它进行修改
        form = EntryForm(instance=entry, data=request.POST)
        if form.is_valid():
            # 表单有效,则保存
            form.save()
            # 重定向到该主题页面
            return redirect('learning_logs:topic', topic_id=topic_id)

    context = {'entry': entry, 'topic': topic, 'form': form}
    return render(request, 'learning_logs/edit_entry.html', context)
1.3.3 编辑条目模板

edit_entry.html

{% extends "learning_logs/base.html" %}

{% block content %}
  <p><a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}</a></p>

  <p>编辑条目:</p>

  <form action="{% url 'learning_logs:edit_entry' entry.id %}" method="post">
      {% csrf_token %}
      {{ form.as_p }}
      <button name="submit">保存改动</button>
  </form>

{% endblock content %}
1.3.4 从topic链接到新条目页面

topic.html

...
      {% for entry in entries %}
        <li>
            <p>{{ entry.date_added|date:'Y-m-d H:m:s' }}</p>
            <p>{{ entry.text|linebreaks }}</p>
            <p>
                <a href="{% url 'learning_logs:edit_entry' entry.id %}">编辑条目</a>
            </p>
        </li>
...
1.3.5 页面效果

2. 创建用户账户

建立用户注册和身份验证系统,让用户能够注册、登录和注销。

创建一个应用程序,其中包含与处理用户账户相关的所有功能。

对Topic进行修改,让每个主题都归属于特定用户。

2.1 应用程序users

python manage.py startapp users

加入INSTALLED_APPS,注意顺序!!!users要在admin之前

# mysite/settings.py
INSTALLED_APPS = [
    'polls.apps.PollsConfig',
    'learning_logs',
    'users',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
# mysite/urls.py
urlpatterns = [
    path('admin/', admin.site.urls),
    path('users/', include('users.urls')),
    path('polls/', include('polls.urls')),
    path('learning_logs/', include('learning_logs.urls')),
]

2.2 登录页面

使用django自带的认证系统django.contrib.auth

# users/urls.py
"""为应用程序users定义URL模式"""

from django.urls import path, include

app_name = 'users'
urlpatterns = [
    path('', include('django.contrib.auth.urls')),
]
2.2.1 登录模板

用户请求登录页面时,Django会使用默认视图,但是仍需要提供模板login.html

默认的认证视图会在templates下的registration文件夹中查找模板,

users/templates/registration/login.html

{% extends "learning_logs/base.html" %}

{% block content %}

  {% if form.errors %}
    <p>用户名或密码错误,请重试</p>
  {% endif %}

  <form method="post" action="{% url 'users:login' %}">
      {% csrf_token %}
      {{ form.as_p }}

      <button name="submit">登录</button>
      <input type="hidden" name="next"
             value="{% url 'learning_logs:index' %}" />
  </form>

{% endblock content %}
2.2.2 链接到登录页面

在base.html中添加到登录页面的链接

<p>
    <a href="{% url 'learning_logs:index' %}">学习笔记</a> -
    <a href="{% url 'learning_logs:topics' %}">主题</a> -
    {% if user.is_authenticated %}
      你好,{{ user.username }}
    {% else %}
    <a href="{% url 'users:login' %}">登录</a>
    {% endif %}
</p>

{% block content %}{% endblock content %}
2.2.3 使用登录页面

登录URL:http://localhost:8000/users/login/

2.3 注销

2.3.1 链接到注销链接

base.html

...
    {% if user.is_authenticated %}
      你好,{{ user.username }}
      <a href="{% url 'users:logout' %}">注销</a>
    {% else %}
...
2.3.2 注销模板

users/templates/registration/logged_out.html

{% extends "learning_logs/base.html" %}

{% block content %}

  <p>你已注销成功,感谢你的访问!</p>

{% endblock content %}
2.3.3 注销页面

2.4 注册页面

使用Django自带的UserCreationForm创建新用户注册页面,但编写自己的视图函数和模板。

2.4.1 注册URL模式
# ...
from . import views
#...
urlpatterns = [
    # ...
    # 注册页面
    path('register/', views.register, name='register'),
]
2.4.2 视图 register
from django.shortcuts import render, redirect
from django.contrib.auth import login
from django.contrib.auth.forms import UserCreationForm

def register(request):
    """注册新用户"""
    if request.method != 'POST':
        # 显示空白注册表单
        form = UserCreationForm()
    else:
        # 处理填写好的表单
        form = UserCreationForm(data=request.POST)
        
        if form.is_valid():
            new_user = form.save()
            # 用户自动登录,重定向到主页
            login(request, new_user)
            return redirect('learning_logs:index')
    # 显示空白或无效表单
    context = {'form': form}
    return render(request, 'registration/register.html', context)
2.4.3 注册模板

register.html

{% extends "learning_logs/base.html" %}

{% block content %}

  <form method="post" action="{% url 'users:register' %}">
      {% csrf_token %}
      {{ form.as_p }}

      <button name="submit">注册</button>
      <input type="hidden" name="next" value="{% url 'learning_logs:index' %}" />
  </form>

{% endblock content %}
2.4.4 链接到注册页面

base.html

...
      <a href="{% url 'users:logout' %}">注销</a>
    {% else %}
      <a href="{% url 'users:register' %}">注册</a> - 
      <a href="{% url 'users:login' %}">登录</a>
...
2.4.5 页面显示

3. 让用户拥有自己的数据

创建一个系统,确定各项数据所属的用户,再限制对页面的访问,让用户只能使用自己的数据。

修改模型Topic,让每个主题都归属于特定用户,条目都属于特定主题,也需要限制访问。

3.1 使用@login_required限制访问

装饰器@login_required只允许已登录的用户访问。

3.1.1 限制对主题Topics页的访问

@login_required检查用户是否已登录,仅当用户已登录时,运行topics函数;否则,重定向到登录页面。

# learning_Logs/views.py
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required

#...

@login_required
def topics(request):
    #...

Django会重定向到settings.pyLOGIN_URL指定的URL:

# My settings for @login_required
LOGIN_URL = 'users:login'

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

限制所有与私有用户数据(主题、条目)相关的URL访问

# ...

@login_required
def topics(request):
    #...

@login_required
def topic(request, topic_id):
    #...
    
@login_required
def new_topic(request):
    #...

@login_required
def new_entry(request, topic_id):
    #...
    
@login_required
def edit_entry(request, entry_id):
    #...

3.2 将数据关联到用户

项目“学习笔记”中,应用程序的最高层数据是主题,所有条目都与特定主题相关联,只要每个主题都归属特定用户,就能确定数据库中每个条目的所有者。

修改模型Topic,在其中添加一个关联到用户的外键,迁移数据库。

修改视图,使其只显示与当前登录用户相关联的数据。

3.2.1 修改模型Topic

在Topic中添加onwer字段,通过外键关联到User。

删除用户时,所有关联主题也会被删除。

# models.py
from django.db import models
# new
from django.contrib.auth.models import User


class Topic(models.Model):
    """用户学习的主题"""
    text = models.CharField(max_length=200)
    date_added = models.DateTimeField(auto_now_add=True)
    # new
    owner = models.ForeignKey(User, on_delete=models.CASCADE)

    def __str__(self):
        return self.text
3.2.2 确定当前用户名单

为了保存当前已输入的主题数据,迁移数据库前要确认用户id:

3.2.3 迁移数据库

makemigrations时,Django指出owner是非空字段,但没有默认值,需要修复。

方式1修复直接指定用户id,迁移文件0004(如果之前没做额外模型修改,应该是0003)给主题topic添加字段owner。

验证迁移成功:

3.2.4 关于重置数据库

重置数据库,既有数据包括超级用户等都会丢失,最好是迁移数据库的同时确保用户数据的完整性。

python manage.py flush

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

当前所有用户都可以看到所有主题,需要修改视图,只向用户显示属于自己的主题。

Topic.objects.filter(owner=request.user)只获取owner属性为当前用户的对象。

# views.py
@login_required
def topics(request):
    """显示所有主题"""
    # modified
    topics = Topic.objects.filter(owner=request.user).order_by('date_added')
    context = {'topics': topics}
    return render(request, 'learning_logs/topics.html', context)

3.4 保护用户的主题

限制对显示单个主题页面的访问(当前可以通过输入主题URL如http://localhost:8000/topics/1/ 访问其他用户的主题)

修改topic视图,在获取请求的条目前执行检查,如果主题不属于当前用户,返回404错误。

# views.py
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from django.http import Http404

# ...
@login_required
def topic(request, topic_id):
    """显示单个主题和它的条目"""
    topic = Topic.objects.get(id=topic_id)
    # 确保主题属于当前用户
    if topic.owner != request.user:
        raise Http404
# ...

3.5 保护页面edit_entry

edit_entry页面的URL为http://localhost:8000/edit_entry/entry_id/,限制其他用户访问的方法:获取当前条目的主题,判断主题所有者是否为当前用户,不是则返回404错误。

#...
@login_required
def edit_entry(request, entry_id):
    # 编辑已经存在的条目
    # 根据entry_id获取entry和相应topic
    entry = Entry.objects.get(id=entry_id)
    topic = entry.topic
    # 确保条目对应主题属于当前用户
    if topic.owner != request.user:
        raise Http404
#...

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

当前新主题没有关联到任何用户,即添加新主题没有指定owner字段的值,需要修改new_topic视图。

@login_required
def new_topic(request):
    # 添加新主题
    if request.method != 'POST':
        # 刚进入new_topic页面,GET方法,未提交数据,则创建一个空表单
        form = TopicForm()
    else:
        # POST提交了数据,对表单数据进行处理,并将用户重定向到topics页面
        form = TopicForm(data=request.POST)
        # 检查表单是否有效
        if form.is_valid():
            new_topic = form.save(commit=False)
            new_topic.owner = request.user
            new_topic.save()
            return redirect('learning_logs:topics')

    # 不是POST请求,显示空表单或无效表单
    context = {'form':form}
    return render(request, 'learning_logs/new_topic.html', context)

先将表单信息保存到new_topic中,为其添加owner属性后,保存到数据库中。

posted @ 2021-09-08 15:08  ikventure  阅读(210)  评论(0编辑  收藏  举报