Python基础知识19——Django用户账户

本章内容来自书籍,记录下来仅方便复习,如有侵权,请联系作者删除。
+++python学习笔记+++

在本章,你将创建一些表单,让用户能够添加主题和条目,编辑已有的条目。实现一个用户身份验证系统,创建一个注册页面,供用户创建账户。

一、 让用户能够输入数据

超级用户:添加新主题,新条目。编辑已有条目。

1. 添加新主题
定义一个URL,编写一个视图函数,编写一个模板。
需要导入包含表单的模块forms.py

  • (1) 用于添加主题的表单
    使用ModelForm创建表单。创建forms.py文件,存储到models.py所在目录(learning_log/learning_logs)中。并编写第一个表单
forms.py

from django import forms

from .models import Topic

class TopicForm(forms.ModelForm): #code1
    class Meta: 
        model = Topic #code2
        fields = ['text'] #code3
        labels = {'text':''} #code4

分析:
首先导入模块forms和需要使用的模型Topic
code1:继承forms.ModelForm,最简单的ModelForm版本是内嵌一个Meta类。
code2:根据模型Topic创建一个表单,该表单只包含text字段,见code3
code4:让Django不要为字段text生成标签

  • (2) URL模式new_topic
    用户要添加新主题时,切换到http://localhost:8000/new_topic/
    将nes_topic添加到learning_logs/urls中。
urls.py
'''定义learning_logs的URL模式'''

from django.urls import path
from . import views

app_name='learning_logs'
urlpatterns = [
    --snip--
    # 用于添加新主题的网页
    path('new_topic/',views.new_topic,name = 'new_topic'),
]

这个URL模式将请求交给视图函数new_topic()。

  • (3) 视图函数new_topic()
    函数new_topic()需要处理两种情形:刚进入new_topic网页,对提交表单数据进行处理,并将用户重定向到网页topics:
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.urls import reverse

from .models import Topic
from .forms import TopicForm

--snip--

def new_topic(request):
    """添加新主题"""
    if request.method != 'POST': #code1
        #未提交数据:创建一个新表单
        form = TopicForm() #code2
    else:
        # POST提交的数据,对数据进行处理
        form = TopicForm(request.POST) #code3
        if form.is_valid(): #code4
            form.save() #code5
            return HttpResponseRedirect(reverse('learning_logs:topics')) #code6

    context = {'form':form} #code7
    return render(request, 'learning_logs/new_topic.html', context)

导入HttpResponseRedirect类,用户提交主题后将使用这个类将用户重定向到网页topics。函数reverse()根据指定的URL模型确定URL,这意味着Django将在页面被请求时生成URL。

  • (4) get请求和post请求

get请求:用于只是从服务器读取数据的页面
post请求:用户需要通过表单提交信息时使用

函数new_topic()将请求对象作为参数。用户初次请求网页时,浏览器发送get请求;用户填写表单并提交时,浏览器发送post请求。

code1:测试确定请求的方法时GET还是POST。
code2:如果不是POST请求,返回一个空表单,我们创建一个TopicForm实例存储在变量form中,再通过上下文字典将该表单发送给模板。见code7
code3:如果是POST请求,用户输入的数据存储在request.POST中,创建一个TopicForm实例,对象form将包含用户提交的信息。

code4:检查提交的信息是否有效,is_valid()核实用户填写了所有必不可少的字段,且输入的数据与要求的字段类型保持一致。
code5:如果字段都有效,调用save(),将表单中的数据写入数据库。

保存数据后,使用reverse()获取页面topics的URL,并将其传递给HttpResponseRedirect(),后者将用户的浏览器重定向到页面topics。可以在页面topics中看到刚刚添加的主题。

  • (5) 模板new_topic
    创建新模板new_topic.html,用于显示我们刚创建的表单。
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'>   #code1
    {% csrf_token %}  #code2
	{{ form.as_p }}  #code3
	<button name ="submit">add topic</button>  #code4
  </form>

{% endblock content %}

分析:
code1:定义了HTML表单,实参action告诉服务器将提交的表单数据发送到哪里,我们将它发回给视图函数new_topic()。实参method让浏览器以POST请求的方式提交数据。
code2:Django使用模板标签{% csrf_token %}来房子攻击者利用表单来获取服务器未经授权的访问。
code3:包含模板变量{{% form.as_p %}},Django显示表单,修饰符as_p,让Django以段落格式渲染所有表单元素。
code4:定义提交按钮

  • (6) 链接到页面new_topic

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

topics.html

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

{% block content %}
	<p>Topics</p>
	
	<ul>
	  --snip--
	</ul>
	
	<a href="{% url 'learning_logs:new_topic' %}">Add a new_topic:</a>
	
{% endblock content%}

2. 添加新条目
再次定义URL,编写视图函数和模板,链接到添加新条目的网页。
需要在forms.py中再添加一个类。

  • (1) 用于添加新条目的表单
    创建于模型Entry相关联的表单,制定程度比TopicForm要高:
forms.py

from django import forms

from .models import Topic,Entry

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

分析:
类EntryForm也继承了forms.ModelForm,包含的Meta类指出表单基于的模型以及要在表单中包含哪些字段。
code1:给字段text定义一个空标签
code2:定义widgets属性。小部件(widget)是一个HTML表单元素。覆盖Django的默认小部件。Django使用forms.Textarea,指定字段text的输入小部件,设置文本框的宽度为80列。

  • (2) URL模式new_entry

用于添加新条目的页面URL模式中,需要包含实参topic_id,条目必须与特定的主题想关联。

learning_logs/urls.py

'''定义learning_logs的URL模式'''

from django.urls import path
from . import views

app_name='learning_logs'
urlpatterns = [

    --snip--
    
    # 用于添加新条目的页面
    path('new_entry/<topic_id>/',views.new_entry,name = 'new_entry'),
]

这个URL模式为http://localhost:8000/new_entry/id/
id是与主题ID匹配的数字,<topic_id>捕获一个数值,存储在变量topic_id中。请求的URL与这个模式匹配时,Django将请求和主题ID发送给函数new_entry()。

  • (3) 视图函数new_entry()
    视图new_entry()与函数new_topic()很像
views.py

from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.urls import reverse

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

# Create your views here.

--snip--
    
def new_entry(request, topic_id):  #code1
    """在特定主题中添加新条目"""
    topic = Topic.objects.get(id=topic_id)
    
    if request.method != 'POST': #code2
        # 未提交数据,创建一个空表单
        form = EntryForm()
    else:
        # POST提交的数据,对数据进行处理
        form = EntryForm(data=request.POST)  #code3
        if form.is_valid():
            new_entry = form.save(commit=False)  #code4
            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)#code5

code1: new_entry()的定义包含形参topic_id,用于存储从URL中获取的值。渲染页面和处理表单数据时,都需要知道针对的是哪一个主题,因此我们使用topic_id来获取正确的主题。
code2:检查请求时get或post,如果不是post请求,创建一个空的EntryForm实例。
code3:如果是post,创建一个EntryForm实例,使用request对象中的POST数据来填充它。再检查表单是否有效,如果有效,就设置条目对象的属性topic,然后保存到数据库。
code4:调用save()时,传递实参commit = False,让Django创建一个新的条目对象,并存储到new_entry中。
code5:将用户重定向到显示相关主题的页面。调用reserve()时,需要提供两个实参,URL模式的名称,列表args,包含在URL中的所有实参。调用HttpResponseRedirect(),将用户重定向到显示新 条目所属主题的页面,用户将在该页面的条目列表中看到新添加的条目。

  • (4) 模板new_entry
    模板new_entry类似于模板new_topic
new_entry.html

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

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

  <p>Add a new entry:</p>
  <form action="{% url 'learning_logs:new_entry' topic.id%}" method = 'post'>  #code2
    {% csrf_token %}
	{{ form.as_p }}
	<button name='submit'>add entry</button>
  </form>

{% endblock content %}

分析:
code1:显示主题,让用户知到是在哪个主题下添加条目,点击可返回。
code2:表单的实参action包含URL中的topic_id值,让视图函数能够将新条目关联到正确的主题。

  • (5) 链接到页面new_entry()
    在显示特定的主题中添加new_entry的链接
topic.html

{% 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>
   --snip--
  </ul>

{% endblock content %}

3. 编辑条目

  • (1) URL模式edit_entry
    这个页面的URL需要传递条目的ID,修改learning_logs/urls.py
urls.py

'''定义learning_logs的URL模式'''

from django.urls import path
from . import views

app_name='learning_logs'
urlpatterns = [
	--snip--
    #用于编辑条目的页面
    path('edit_entry/<entry_id>/',views.edit_entry,name = 'edit_entry')
]

在URL(http://127.0.0.1:8000/edit_entry/1/)中传递的ID存储在形参entry_id中。这个URL模式将预期匹配的请求发送给视图函数edit_entry()。

  • (2) 视图函数edit_entry()
    页面edit_entry收到get请求时,edit_entry()将返回一个表单,让用户能够对条目进行编辑。
    收到post请求时,将修改后的文本保存到数据库中。
views.py

from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.urls import reverse

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

# Create your views here.

def index(request): --snip--
def topics(request): --snip--
def topic(request, topic_id): --snip--
def new_topic(request): --snip--
def new_entry(request, topic_id): --snip--
    
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) #code1
        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)

code1:让Django根据已有条目对象创建一个表单实例,并根据request.POST中的数据,对条目进行修改。

  • (3) 模板edit_entry
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'> #code1
    {% csrf_token %}
	{{ form.as_p }}
	
	<button name="submit">save changes</button>
  
  </form>

{% endblock content %}

code1:实参action将表单发回给函数edit_entry()进行处理,在标签{% url %}中,将条目ID作为一个实参,让视图对象能够修改正确的条目对象。

  • (4) 链接到页面edit_entry
topic.html

{% 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> #code1
	</li>
  {% empty %}
    <li>
	  There are no entries for this topic yet.
	</li>	
  {% endfor %}
  </ul>

{% endblock content %}

code1:将编辑链接放在每个条目的日期和文本后面。使用模板标签{% url %}根据URL模式edit_entry和当前条目的ID属性(entry_id)来确定URL。

二、创建用户账户

建立一个用户注册和身份验证系统,让用户能够注册账户,进而登录和注销。修改模型Topic,让每个主题都归属于特定用户。

1. 应用程序users

使用startapp命令来创建一个名为users的应用程序。
(ll_env)learning_log> python manage.py statapp users
创建一个名为users的目录,用dir查看其目录结构,应用程序learning_logs相同。

(ll_env) D:\django\learning_log>python manage.py startapp users

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

settings.py

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    #我的应用程序
    'learning_logs',
    'users',
]

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

learning_log/urls.py

from django.contrib import admin
from django.urls import path,include

urlpatterns = [
    path('admin/', include(admin.site.urls)),
    path('users/', include('users.urls', namespace='users')), #code1
    path('', include(('learning_logs.urls','learning_logs'), namespace='learning_logs')),
]

code1:包含应用程序users文件中的urls.py,这行代码与任何以单词users打头的URL都匹配。(如http://localhost:8000/users/login/),创建了命名空间users,以便将应用程序learning_logs的URL与应用程序users中的URL区分开来。

2. 登录页面

实现登录页面功能,使用Django提供的默认登录视图。
在learning_log/users/中,新建urls.py文件.

learning_log/users/urls.py

"""为应用程序users定义URL模式"""

from django.urls import path
from django.contrib.auth import views as auth_views #code1

from . import views

app_name='users'
urlpatterns = [
    #登录页面
    path('login/', auth_views.LoginView.as_view(template_name='users/login.html'), name='login'),  #code2
]

code1:导入views,并重命名为auth_views
code2:登录页面的URL模式与URLhttp://localhost:8000.users/login.html匹配。我们没有编写自己的视图函数,我们传递了一个实参template_name,告诉Django去哪里查找我们编写的模板。

(1) 模板login.html

用户请求登录页面时,Django使用默认视图login,我们依然要为这个页面提供模板。
在learning_log/users中,创建目录templates,在templates中创建目录users,在users中创建文件login.html

learning_log/users/templates/users/login.html

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

{% block content %}
  {% if form.errors %}   <!--code1-->
  <p>Your username and password didn't mach. Please try again. </p>   <!--code2-->
  {% endif %}
  
  <form method = "post" action="{% url 'users:login' %}">
	{% csrf_token %}
	{{ form.as_p }}   <!--code3-->
	
	<button name="submit">log in</button>   <!--code4-->
	<input type="hidden" name="next" value="{% url 'learning_logs:index' %}"/>    <!--code5-->
  </form>

{% endblock content %}

code1:如果error属性被设置,我们就显示一条错误信息,指出输入的用户名-密码与数据库中的用户名-密码不匹配。
code2:要让视图处理表单,将实参action设置为登录页面的URL。
code3:显示表单
code4:添加提交按钮
code5:包含一个隐藏的表单元素——“next”,实参value告诉Django在用户成功登录后将其重定向到什么地方。(这里是主页)

(2) 链接到登录页面
下面在base.html中添加到登录页面的链接,让所有页面都包含它。
添加判断,让登录页面嵌套在一个{% if %}标签中,如果用户已经登录,则不显示这个链接。

base.html

<p>
  <a href="{% url 'learning_logs:index' %}">Learning Log</a>
  <a href="{% url 'learning_logs:topics' %}">Topics</a>
  
  {% if user.is_authenticated %}  <!--code1-->
    Hello,{{ user.username }}.  <!--code2-->
  {% else %}
	<a href="{% url 'users:login' %}">log in<a>  <!--code3-->
  {% endif %}  
</p>

{% block content %}{% endblock content %}

code1:每个模板都可使用变量user,其中的is_authenticated属性,判断了用户是否登录,并返回True/False

code2:我们向已登录的用户显示一条问候语,设置属性user.username。个性化问候语,让用户知道他已经登录。
code3:对于还未通过身份验证的用户,我们再显示一个到登录页面的链接。

(3) 使用登录页面
访问http://127.0.0.1:8000/admin/,注销用户。
访问http://localhost:8000/users/login,进入页面index。

未登录状态下显示log in
在这里插入图片描述
登录状态下显示Hello,ll_admin

3. 注销

提供一个让用户注销的途径。我们不创建用于注销的页面,让用户只需单击一个链接就能注销并返回到主页。

为注销定义一个URL模式,编写一个视图函数,并在base.html中添加一个注销链接。
(1) 注销URL
下面为注销定义了URL模式,该模式与URLhttp://localhost:8000/users/logout/匹配

users/urls.py

--snip--

urlpatterns = [
    # 登录页面
    path('login/',auth_views.LoginView.as_view(template_name='users/login.html'),name='login'), 
    # 注销
    path('logout/',views.logout_view, name='logout'),
]

这个URL模式将请求发送给函数logout_view()。

(2) 视图函数logout_view()
导入Django函数logout(),并调用它,再重定向到主页。

users/views.py

from django.shortcuts import render
from django.http import HttpResponseRedirect
#from django.core.urlresolvers import reverse
from django.urls  import reverse
from django.contrib.auth import logout

# Create your views here.

def logout_view(request):
    """注销用户"""
    logout(request)
    return HttpResponseRedirect(reverse('learning_logs:index'))

导入函数logout()并调用,将request对象作为实参,然后重定向到主页。

(3) 链接到注销视图

在base.html中添加注销链接,放在标签{% if user.is_authenticated %}中,使得只有用户登录后才能看到它。

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>

{% block content %}{% endblock content %}

在这里插入图片描述

4. 注册页面

创建注册页面,使用Django提供的表单UserCreationForm。

(1)注册页面的URL模式

‘’‘定义learning_logs的URL模式’’’

users/urls.py

from django.urls import path
from . import views

app_name='learning_logs'
urlpatterns = [
    # 主页
    path('', views.index, name = 'index'),
    # 显示所有的主题
    path('topics/',views.topics,name='topics'),
    # 特定主题的详细页面(?P<topic_id>\d+) 改为 /<topic_id>
    path('topics/<topic_id>/',views.topic,name='topic'),
    # 用于添加新主题的网页
    path('new_topic/',views.new_topic,name = 'new_topic'),
    # 用于添加新条目的页面
    path('new_entry/<topic_id>/',views.new_entry,name = 'new_entry'),
    #用于编辑条目的页面
    path('edit_entry/<entry_id>/',views.edit_entry,name = 'edit_entry')
]

(2)视图函数register()

views.py

from django.shortcuts import render
from django.http import HttpResponseRedirect
#from django.core.urlresolvers import reverse
from django.urls  import reverse
from django.contrib.auth import logout,login,authenticate
from django.contrib.auth.forms import UserCreationForm 
#from django.views.decorators.csrf import csrf_protect

# Create your views here.

def logout_view(request):
    """注销用户"""
    logout(request)
    return HttpResponseRedirect(reverse('learning_logs:index'))

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

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

(4)链接到注册页面

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

{% block content %}{% endblock content %}

在这里插入图片描述
在这里插入图片描述

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

修改模型Topic,让每个主题都归属于特定用户。这会影响到条目,我们来限制对一些用户的访问。

1. 使用@login_required限制访问

Django提供装饰器@login_required,对于某些页面,只允许已登录的用户访问它们。装饰器(decorator)是放在函数定义前面的指令。

(1)限制主题对topics页面的访问

learning_logs/views.py

from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.contrib.auth.decorators import login_required #code1

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

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

在末尾添加代码:

settings.py

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

如果未登录的用户请求装饰器@login_required的保护页面,Django将重定向至settings.py中LOGIN_URL指定的URL。

(2)全面限制对项目“学习笔记”的访问
learning_logs/views.py中,除了index()外的每个视图,都应用了装饰器@login_required。

views.py

@login_required
def topics(request):
	--snip--

@login_required
def topics(request):
	--snip--

@login_required    
def topic(request, topic_id):
	--snip--

@login_required    
def new_topic(request):
	--snip--

@login_required    
def new_entry(request, topic_id):
	--snip--

@login_required    
def edit_entry(request,entry_id):
	--snip--

2. 将数据关联到用户

(1) 修改模型Topic

model.py

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)
    owner = models.ForeignKey(User)
	--snip--

(2) 确定当前有哪些用户
在shell会话中,查看用户数及其ID

(ll_env) D:\django\learning_log>python manage.py shell
>>> from django.contrib.auth.models import User
>>> User.objects.all()
<QuerySet [<User: ll_admin>, <User: aigeqin>, <User: qinaige>]>
>>> for user in User.objects.all():
...     print(user.username, user.id)
...
ll_admin 1
aigeqin 2
qinaige 3
>>>

(3) 迁移数据库

通过用户ID迁移数据库,执行命令makemigrations,Django指出,我们试图给Topic添加一个必不可少(不可为空)的字段。
在(result1)处,Django给我们两个选择,给该字段赋默认值,或者退出并且在model.py中添加默认值。

code2:我们选择第一个选项
code3:Django让我们输入默认值,我们可以输入任何已经创建好的用户ID,在这里我们输入超级用户,所以输入了ID为1。

(ll_env) D:\django\learning_log>python manage.py makemigrations learning_logs   #code1
You are trying to add a non-nullable field 'owner' to topic without a default; w
e can't do that (the database needs something to populate existing rows).
Please select a fix: #result1
 1) Provide a one-off default now (will be set on all existing rows with a null
value for this column)
 2) Quit, and let me add a default in models.py
Select an option: 1  #code2
Please enter the default value now, as valid Python
The datetime and django.utils.timezone modules are available, so you can do e.g.
 timezone.now
Type 'exit' to exit this prompt
>>> 1   #code3
Migrations for 'learning_logs':
  learning_logs\migrations\0003_topic_owner.py
    - Add field owner to topic

(ll_env) D:\django\learning_log>

接下Django使用ID为1,来迁移数据库,生成迁移文件0003_topic_owner.py,它在模型中添加字段owner。

(ll_env) D:\django\learning_log>python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, learning_logs, sessions
Running migrations:
  Applying learning_logs.0003_topic_owner... OK

(ll_env) D:\django\learning_log>

在Django的shell会话中应用迁移,

(ll_env) D:\django\learning_log>python manage.py shell

>>> from learning_logs.models import Topic
>>> for topic in Topic.objects.all():
...     print(topic,topic.owner)
...
Chess ll_admin
Rock Climbing ll_admin
sdf ll_admin
Add Form1 ll_admin
Add Form2 ll_admin
>>>

从learning_logs.modelsh中导入Topic,遍历所有主题,打印每个主题及其所属的用户。

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

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

learning_logs/views.py

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

用户登录后,request对象将有一个user属性,存储有关用户的信息。

修改后的代码,让Django只从数据库中获取owner属性为当前用户的Topic对象。

结果:
(1) 以既有主题关联到用户的身份登录,访问topics页面,可以看到所有主题。
(2)以另一个用户的身份登录,topics页面不会列出任何主题。但是还有漏洞,如下:
http://localhost:8000/topics/ 不能看到主题内容
http://localhost:8000/topics/1/ 可以看到主题内容

4. 保护用户的主题
修改learning_logs/views.py 下的topic函数

learning_logs/views.py

from django.http import HttpResponseRedirect, Http404

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

首先导入异常Http404,在用户请求它不能查看的主题时,引发404异常。
收到主题请求后,在渲染网页前检查该主题是否属于当前登录的用户,如果不是,则引发Http404异常。

结果:
(2)以另一个用户的身份登录,topics页面不会列出任何主题。但是还有漏洞,如下:
http://localhost:8000/topics/ 不能看到主题内容
http://localhost:8000/topics/1/ 报404异常

5. 保护页面edit_entry

页面edit_entry的URL为http://localhost:8000/edit_entry/(entry_id)/,
其中entry_id是一个数字。下面来保护这个页面,进制用户通过熟人类似于前面的URL来访问其他用户的条目。

learning_logs/views.py

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

我们获取指定的条目以及与之相关联的主题,然后检查主题的所有者是否是当前登录的用户,如果不是,就引发Http404异常。

6. 将新主题关联到当前用户

当前,http://localhost:8000/new_topic/添加新主题时,点击add_topic按钮,引发异常。错误信息为IntegrityError ,指出learning_logs_topic.user_id不能为NULL,Django想让我们创建新主题时,必须指出其owner字段的值。

IntegrityError at /new_topic/
NOT NULL constraint failed: learning_logs_topic.owner_id
Request Method:	POST
Request URL:	http://localhost:8000/new_topic/
Django Version:	2.2.6
Exception Type:	IntegrityError
Exception Value:	
NOT NULL constraint failed: learning_logs_topic.owner_id

修改代码,通过request对象获取当前用户,可将新主题关联到当前用户。

learning_logs/views.py

@login_required    
def new_topic(request):
    --snip--
    else:
        # POST提交的数据,对数据进行处理
        form = TopicForm(request.POST)
        if form.is_valid():
            # form.save()
            new_topic = form.save(commit=False)  #code1
            new_topic.owner = request.user  #code2
            new_topic.save()  #code3
            return HttpResponseRedirect(reverse('learning_logs:topics'))

    --snip--

code1:首先调用form.save(),传递实参commit=False,先修改新主题并保存到数据库中。
code2:将新主题的owner()属性设置为当前用户
code3:用刚定义的主题实例调用save()。

现在主题包含所有必不可少的数据,将被成功的保存。每个用户只能访问自己的数据。

四、小结

学习使用表单来让用户添加新主题,新条目,编辑条目。
实现用户账户,登录,注销,使用UserCreationForm让用户能够创建新账户。
使用装饰器@login_required,禁止未登录的用户访问特定页面。
学习如何执行要求制定默认数据的数据迁移
学习修改视图函数,让用户只能看到属于他的数据。
使用方法fileter()来获取合适的数据
学习如何将请求的数据的所有者同当前登录的用户比较

五、遇到的问题:

(1)django2.x报错No module named ‘django.core.urlresolvers’
https://blog.csdn.net/weixin_35757704/article/details/78977753

原因就是:django2.0 把原来的 django.core.urlresolvers 包 更改为了 django.urls包,所以我们需要把导入的包都修改一下就可以了。
在这里插入图片描述

(2)Django报错 ‘learning_logs ’is not a registered namespace,
https://blog.csdn.net/mukvintt/article/details/80320027

(3)ImportError: cannot import name ‘login’ from ‘django.contrib.auth.views’
https://blog.csdn.net/steventian72/article/details/85225631

from django.contrib.auth.views import login

修改为:

from django.contrib.auth import login

(4)Django报错:login() got an unexpected keyword argument ‘template_name’
https://blog.csdn.net/steventian72/article/details/85225631
https://docs.djangoproject.com/en/2.2/topics/auth/default/

修改:

learning_log/users.py

"""为应用程序users定义URL模式"""

from django.urls import path
# from django.contrib.auth.views import login,修改
#from django.contrib.auth.login import login
#from django.contrib.auth.views import loginView 
from django.contrib.auth import views as auth_views

from . import views

app_name='users'
urlpatterns = [
    #登录页面
    #path('login/',login,{'template_name':'users/login.html'},name='login'),
    #path('login/',auth_views.loginView.as_view(template_name='users/login.html'),name='login'),
    path('login/',auth_views.LoginView.as_view(template_name='users/login.html'),name='login'),    
]

(5) 连接错误:ConnectionAbortedError: [WinError 10053] 您的主机中的软件中止了一个已建立的连接

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值