美文网首页
学习Django(2)

学习Django(2)

作者: 夏洛伊de风 | 来源:发表于2019-05-15 00:29 被阅读0次

先把github链接放上来:
https://github.com/q100036q/python_demo_learning_logs
上一篇文章从创建一个django环境开始,一直到制作了一个网站,可以保存主题和内容。这篇文章主要描述使用账号来管理这个网站。

第一:增加新的主题和内容:

1.首先引入forms模块中的ModelForm来创建一个新的表单格式,代码如下:

 #forms.py
 from django import forms;

 from .models import Topic,Entry;

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

class EntryForm(forms.ModelForm):
    class Meta:
        model = Entry;
        fields = ['text'];
        labels = {'text':''};
        widgets = {'text':forms.Textarea(attrs = {'cols':80})};#这段代码作用是

这里针对主题和内容分别定制了两个表单,它内嵌Meta 类,告诉Django根据哪个模型创建表单,以及在表单中包含哪些字段。

2.接下来,修改urls文件去添加主题:

#urls.py
from django.conf.urls import url;

from . import views;
app_name='learning_logs'

urlpatterns = [
    url(r'^$',views.index,name = 'index'),
    url(r'^topics/$',views.topics,name = 'topics'),
    url(r'^topics/(?P<topic_id>\d+)/$',views.topic,name = 'topic'),
    url(r'^new_topic/&',views.new_topic,name = 'new_topic'),
    url(r'^new_entry/(?P<topic_id>\d+)/$',views.new_entry,name = 'new_entry'),
    #url(r'^edit_entry/(?P<entry_id>\d+)/$',views.edit_entry,name = 'edit_entry'),这段代码之后再编辑内容的时候去解开注释
]

这里就是分别对http://localhost:8000/new_topic/http://localhost:8000/new_entry/1/分别进行匹配,然后去视图层注册这两个函数。

3.修改视图层

#views.py
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from .models import Topic,Entry;
from .forms import TopicForm,EntryForm;
--snip--#由于代码太多,这里使用这个表示之前的其他代码
def new_topic(request):
"""添加新主题""" 
    if request.method != 'POST':
    # 未提交数据:创建一个新表单
       form = TopicForm()
    else:
    # POST提交的数据,对数据进行处理
         form = TopicForm(request.POST) 
         if form.is_valid(): 
             form.save() 
             return HttpResponseRedirect(reverse('learning_logs:topics'))
    context = {'form': form}
    return render(request, 'learning_logs/new_topic.html', context)
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);

这两个函数的最终的作用就是当新的表单内容提交后,通过HttpResponseRedirect模块分别重新定向到topics.html和topic.html页面。首先对请求类型进行判断,是GET请求,就创建一个可被用户编辑的空表单,经过字典的方式传递给模板。若是POST请求,则对表单数据进行处理,使用用户输入的数据并保存,在定向到之前显示数据的界面。
关于new_entry函数内容,稍有些不同的地方:调用save() 时,我们传递了实参commit=False ,让Django创建一个新的条目对象,并将其存储到new_entry 中,但不将它保存到数据库中。我们将new_entry 的属性topic 设置为在这个函数开头从数据库中获取的主题,然后调用save() ,且不指定任何实参。这将把条目保存到数据库,并将其与正确的主题相关联。
我们将用户重定向到显示相关主题的页面。调用reverse() 时,需要提供两个实参:要根据它来生成URL的URL模式的名称;列表args ,其中包含要包含在URL中的所有实参。在这里,列表args 只有一个元素——topic_id 。接下来,调用HttpResponseRedirect() 将用户重定向到显示新增条目所属主题的页面,用户将在该页面的条目列表中看到新添加的条目。

4.创建模板页面

根据以往经验,视图层结束之后就是模板了,下面把两个新增加内容代码写出来:

<!--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 %}

<!--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 %}

首先定义了一个HTML的表单,参数action告诉我们将表单传递到哪,参数method告知这次传递使用的是POST的方式,{% csrf_token %}标签防止攻击者利用表单来对服务器进行未授权的访问,模板变量{{ form.as_p }} 可让Django自动创建显示表单所需的全部字段。修饰符as_p 让Django以段落格式渲
染所有表单元素。最后在创建一个提交按钮来执行这些内容。
new_entry.html的内容和之前类似,多了一个超链接回到上一层页卡,action中多传递了一个参数,给表单进行使用,能在第一步的第3条中通过它获得指定的数据。

5.编辑内容文本

为了给每个条目的内容进行修改,新增加一个模块来完成它,老规矩,先去urls.py中进行注册:

#urls.py
--snip--#这里偷下懒,不再将全部代码拷贝,后文都用这个代替已有代码
urlpatterns = [
    --snip--
    # 用于编辑条目的页面
   url(r'^edit_entry/(?P<entry_id>\d+)/$',views.edit_entry,name = 'edit_entry'),
]

上面代码匹配http://localhost:8000/edit_entry/1/这样的一个路径,接着,去views.py中增加视图函数:

#views.py
--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:
        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);

这个函数和上面new_entry()函数很像,先通过id获得数据库内容,然后判断请求来创建表单,不同的是,这次,创建表单的函数传递了instance参数,目的是根据这个参数内容来填充表单。因为这个内容肯定是指定了条目topic的,所以不需要和之前那样保存本地指定条目再保存,可以直接保存到数据库。接下来,去编写新的html文件使用它。

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

#topic.html
{% extends "learning_logs/base.html" %}
{% block content %}
<p>Topics: {{ 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_add|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>
    </li>
    {% empty %}
    <li>There are no entries for this topic yet.</li>
    {% endfor %}
</ul>
{% endblock content %}

至此,给我们的项目进行新增条目和新增内容,并编辑内容都完成了,接下来将为项目增加账户控制。

第二:创建用户账户

为了单独管理账户系统,我们新建一个应用程序来处理相关功能。回想一下之前创建learning_logs的方法,使用命令startapp 来创建一个名为users 的应用程序,其结构与应用程序learning_logs 相同:

python manage.py startapp users

接着修改主项目learning_log文件夹下的settings.py和urls.py,将users加入到项目中来。

#setting.py
--snip--
INSTALLED_APPS = (
--snip--
# 我的应用程序
    'learning_logs',
    'users',
)
--snip--
#urls.py
--snip--
url(r'^users/',include('users.urls',namespace='users')),
--snip--

下面我们将依次对登录,注销,注册三个功能进行处理。

1.登录

注意,之后基本都是修改users文件夹下的内容,若修改别的文件夹,会特殊说明。
修改urls.py添加登录页面的url模式:

#urls.py
from django.conf.urls import url
from django.contrib.auth.views import LoginView
from . import views
app_name='users'#一定要加这个
urlpatterns = [
    url(r'^login/$',LoginView.as_view(template_name='users/login.html'),name='login'),
    #url(r'^logout/$',views.logout_view,name='logout'),暂时注释注销路径
    #url(r'^register/$',views.register,name='register'),暂时注释注册路径
]

登录页面的URL模式与URL http://localhost:8000/users/login/匹配。这里我们使用django提供的默认登录视图,就不用为了登录再去创建修改views文件了,为此引入了模块LoginView,django的类视图拥有自动查找指定方法的功能, 通过调用是通过as_view()方法实现,所以,这里这么写的意思就是给默认的视图提供模板。
关于as_view()的工作流程可以看这个文章:https://www.jianshu.com/p/17860becea09
之后,去完成这个模板文件新建文件夹template,在其中建立文件夹users。新建文件login.html:

<!--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 %}

如果表单的errors 属性被设置,我们就显示一条错误消息,指出输入的用户名—密码对与数据库中存储的任何用户名—密码对都不匹配。最后面的input type="hidden"证明这是一个隐藏表单,传递的参数value告知django,登陆之后重定向到主页。
下面,修改一下base.html文件,增加一个登录的链接入口。

<!--learning_logs/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 %}

在Django身份验证系统中,每个模板都可使用变量user ,这个变量有一个is_authenticated 属性:如果用户已登录,该属性将为True ,否则为False 。
现在可以访问http://localhost:8000/users/login/,若登录了显示的是你的用户名,否则,点击登录按钮跳转到登录界面试试吧。若已经登录了,可以先注销,一般保存在浏览器的设置里面,清空之后再进去就等点击登录了。

2.注销

修改urls.py,指定索引url路径,解开上面第1段中的注释。下面去编写视图函数logout_view()。

#views.py
from django.shortcuts import render
from django.http import HttpResponseRedirect as HRR
from django.urls import reverse
from django.contrib.auth import logout
# Create your views here.
def logout_view(request):
    logout(request)
    return HRR(reverse('learning_logs:index'))

我们从django.contrib.auth中导入了函数logout() ,它要求将request 对象作为实参,会将存储在用户session的数据全部清空。然后,我们重定向到主页。接着修改base.html,增加注销的入口链接。这个在第1段登录的里面已经写过被注释了,解开就可以。

3.注册

虽然注册比上面麻烦一些,但是基础流程是没有变化的,首先去改urls.py,这里也把上面的注释解开就行,匹配的是http://localhost:8000/users/register/
修改views.py,这里面要导入一些别的模块:

 #views.py
--snip--
from django.contrib.auth import logout,login,authenticate
from django.contrib.auth.forms import UserCreationForm
--snip--
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 HRR(reverse('learning_logs:index'))
    context = {'form':form}
    return render(request,'users/register.html',context)

使用 authenticate() 函数认证给出的用户名和密码, 而要登录一个用户,使用 login() 。该函数接受一个 HttpRequest 对象和一个 User 对象作为参数并使用Django的会话( session )框架把用户的ID保存在该会话中。UserCreationForm是提供的默认表,包括username和两个password。
关于login函数和authenticate函数看这里:https://www.cnblogs.com/ccorz/p/6357815.html
下面,给这个注册视图增加模板:

<!--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 %}

和login.html几乎一样,最后去修改base.html,解开注释的代码,增加注册账号的入口。

第三:属于用户自己的数据

1.装饰器@login_required限制访问

我们将修改模型Topic ,让每个主题都归属于特定用户。Django提供了装饰器@login_required ,对于某些页面,只允许已登录的用户访问它们。这里开始,代码回到learning_logs应用程序内,先去给views.py里面除了index()主页函数之外的每一个函数的头部都增加装饰器:

#views.py
--snip--
from django.contrib.auth.decorators import login_required
--snip--
@login_required
def topics(request):
"""显示所有的主题"""
--snip--

login_required() 的代码检查用户是否已登录,仅当用户已登录时,Django才运行topics() 的代码。如果用户未登录,就重定向到登录页面。
为实现这种重定向,我们需要修改settings.py,让Django知道到哪里去查找登录页面。请在settings.py末尾添加如下代码:

#learning_log/setting.py
""" 项目learning_log的Django设置
--snip--
# 我的设置
LOGIN_URL = '/users/login/'

现在,如果未登录的用户请求装饰器@login_required 的保护页面,Django将重定向到settings.py中的LOGIN_URL 指定的URL。现在在未登录的情况下尝试访问这些页面,将被重定向到登录页面。

2.关联用户主题

下面来修改模型Topic ,在其中添加一个关联到用户的外键。因为涉及到修改models,我们必须对数据库进行迁移。最后,我们必须对有些视图进行修改,使其只显示与当前登录的用户相关联的数据。

#models.py
from django.db import models
from django.contrib.auth.models import User
# Create your models here.
class Topic(models.Model):
    text = models.CharField(max_length = 200);
    date_added = models.DateTimeField(auto_now_add = True);
    owner = models.ForeignKey(User, on_delete=models.CASCADE);

    def __str__(self):
        return self.text;
--snip--

这里引入User模型,并且作为一个外键生成owner字段。
注意:python3.7下使用models.ForeignKey时一定要传入实参on_delete=models.CASCADE,作用是删除主表,数据跟着一起删除。接下来进行数据库迁移:

python manage.py makemigrations learning_logs

此时因为我们代码中没有给owner一个默认值,会有报错提示,提供默认值或者退出回到models.py中填写默认值,这里输入1,立刻给一个默认数据,再输入一次1,将之前创建的超级用户作为owner,这里可以不使用超级用户,在shell会话中可以查看之前注册过的用户名和id,新建一个工作窗口,输入以下shell语句,查看用户id:

python manage.py shell
from django.contrib.auth.models import User
User.objects.all()

若是觉得之前创建的用户过多,使用如下语句重置数据库,并在这之后重新去创建超级用户:

python manage.py flush

3.访问自己的主题

当前,不管你以哪个用户的身份登录,都能够看到所有的主题。我们来改变这种情况,只向用户显示属于自己的主题。现在修改views.py的topics()函数:

#views.py
--snip--
@login_required
def topics(request):
    topics = Topic.objects.filter(owner=request.user).order_by('date_added');
    context = {'topics':topics}
    return render(request,'learning_logs/topics.html',context)
--snip--

用户登录后,request 对象将有一个user 属性,这个属性存储了有关该用户的信息。代码Topic.objects.filter(owner=request.user) 让Django只从数据库中获取owner 属性为当前用户的Topic 对象。现在主题列表页面已经被限制了,只能显示当前登录用户所有的主题,但是如果更换用户,使用url的方式访问这个主题的内容页面,比如http://localhost:8000/topics/1/还是可以进入,所以下面对每个主题的内容页面进行限制:

#views.py
from django.shortcuts import render;
from django.http import HttpResponseRedirect,Http404;
from django.urls import reverse;
# Create your views here.
from django.contrib.auth.decorators import login_required;

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


def check_topic_owner(request,topic):
    if topic.owner != request.user:
        raise Http404;
@login_required
def topic(request,topic_id):
    topic = Topic.objects.get(id = topic_id);
    check_topic_owner(request,topic);
    entries = topic.entry_set.order_by('-date_added');
    context = {'topic':topic,'entries':entries}
    return render(request,'learning_logs/topic.html',context);

引入Http404的目的是为了访问失败的时候给一个异常报错,这里当该主题的用户不是登录用户,就报这个异常。edit_entry这个函数也和topic函数一样需要限制,这里就不重复了。

4.将新增加的主题关联用户

创建新主题时,你必须指定其owner 字段的值,否则会报错。这里要修改new_topic函数:

#views.py
--snip--
@login_required
def new_topic(request):
    if request.method != 'POST':
        form = TopicForm();
    else:
        form = TopicForm(data = request.POST);
        if form.is_valid():
            new_topic = form.save(commit=False);
            new_topic.owner = request.user;
            new_topic.save();
            return HttpResponseRedirect(reverse('learning_logs:topics'));
    context = {'form':form};
    return render(request,'learning_logs/new_topic.html',context);
--snip--

这里对比之前写的new_topic函数修改了保存数据库内表单的代码,具体参考第一第3段。原来代码表单如果正确就保存进数据库,现在为了增加一个owner的值,save函数内传递commit=False字段,先保存到new_topic里,设置好owner之后在保存。
new_entry函数也同样进行限制,只需要把check_topic_owner这个函数调用就行。至此,给这个项目创建一个账户管理系统就完成了。

相关文章

网友评论

      本文标题:学习Django(2)

      本文链接:https://www.haomeiwen.com/subject/uuzlpqtx.html