學習Django(2)

先把github鏈接放上來:
https://github.com/q100036q/python_demo_learning_logs
上一篇文章從創(chuàng)建一個django環(huán)境開始,一直到制作了一個網(wǎng)站,可以保存主題和內(nèi)容。這篇文章主要描述使用賬號來管理這個網(wǎng)站。

第一:增加新的主題和內(nèi)容:

1.首先引入forms模塊中的ModelForm來創(chuàng)建一個新的表單格式,代碼如下:

 #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})};#這段代碼作用是

這里針對主題和內(nèi)容分別定制了兩個表單,它內(nèi)嵌Meta 類,告訴Django根據(jù)哪個模型創(chuàng)建表單,以及在表單中包含哪些字段。

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'),這段代碼之后再編輯內(nèi)容的時候去解開注釋
]

這里就是分別對http://localhost:8000/new_topic/http://localhost:8000/new_entry/1/分別進行匹配,然后去視圖層注冊這兩個函數(shù)。

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':
    # 未提交數(shù)據(jù):創(chuàng)建一個新表單
       form = TopicForm()
    else:
    # POST提交的數(shù)據(jù),對數(shù)據(jù)進行處理
         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);

這兩個函數(shù)的最終的作用就是當新的表單內(nèi)容提交后,通過HttpResponseRedirect模塊分別重新定向到topics.html和topic.html頁面。首先對請求類型進行判斷,是GET請求,就創(chuàng)建一個可被用戶編輯的空表單,經(jīng)過字典的方式傳遞給模板。若是POST請求,則對表單數(shù)據(jù)進行處理,使用用戶輸入的數(shù)據(jù)并保存,在定向到之前顯示數(shù)據(jù)的界面。
關于new_entry函數(shù)內(nèi)容,稍有些不同的地方:調(diào)用save() 時,我們傳遞了實參commit=False ,讓Django創(chuàng)建一個新的條目對象,并將其存儲到new_entry 中,但不將它保存到數(shù)據(jù)庫中。我們將new_entry 的屬性topic 設置為在這個函數(shù)開頭從數(shù)據(jù)庫中獲取的主題,然后調(diào)用save() ,且不指定任何實參。這將把條目保存到數(shù)據(jù)庫,并將其與正確的主題相關聯(lián)。
我們將用戶重定向到顯示相關主題的頁面。調(diào)用reverse() 時,需要提供兩個實參:要根據(jù)它來生成URL的URL模式的名稱;列表args ,其中包含要包含在URL中的所有實參。在這里,列表args 只有一個元素——topic_id 。接下來,調(diào)用HttpResponseRedirect() 將用戶重定向到顯示新增條目所屬主題的頁面,用戶將在該頁面的條目列表中看到新添加的條目。

4.創(chuàng)建模板頁面

根據(jù)以往經(jīng)驗,視圖層結束之后就是模板了,下面把兩個新增加內(nèi)容代碼寫出來:

<!--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的表單,參數(shù)action告訴我們將表單傳遞到哪,參數(shù)method告知這次傳遞使用的是POST的方式,{% csrf_token %}標簽防止攻擊者利用表單來對服務器進行未授權的訪問,模板變量{{ form.as_p }} 可讓Django自動創(chuàng)建顯示表單所需的全部字段。修飾符as_p 讓Django以段落格式渲
染所有表單元素。最后在創(chuàng)建一個提交按鈕來執(zhí)行這些內(nèi)容。
new_entry.html的內(nèi)容和之前類似,多了一個超鏈接回到上一層頁卡,action中多傳遞了一個參數(shù),給表單進行使用,能在第一步的第3條中通過它獲得指定的數(shù)據(jù)。

5.編輯內(nèi)容文本

為了給每個條目的內(nèi)容進行修改,新增加一個模塊來完成它,老規(guī)矩,先去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中增加視圖函數(shù):

#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);

這個函數(shù)和上面new_entry()函數(shù)很像,先通過id獲得數(shù)據(jù)庫內(nèi)容,然后判斷請求來創(chuàng)建表單,不同的是,這次,創(chuàng)建表單的函數(shù)傳遞了instance參數(shù),目的是根據(jù)這個參數(shù)內(nèi)容來填充表單。因為這個內(nèi)容肯定是指定了條目topic的,所以不需要和之前那樣保存本地指定條目再保存,可以直接保存到數(shù)據(jù)庫。接下來,去編寫新的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 %}

內(nèi)容和之前差不多,多了一個鏈接去返回到條目的頁面。
最后給用戶提供一個編輯已有內(nèi)容的入口,修改下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 %}

至此,給我們的項目進行新增條目和新增內(nèi)容,并編輯內(nèi)容都完成了,接下來將為項目增加賬戶控制。

第二:創(chuàng)建用戶賬戶

為了單獨管理賬戶系統(tǒng),我們新建一個應用程序來處理相關功能。回想一下之前創(chuàng)建learning_logs的方法,使用命令startapp 來創(chuàng)建一個名為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文件夾下的內(nèi)容,若修改別的文件夾,會特殊說明。
修改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提供的默認登錄視圖,就不用為了登錄再去創(chuàng)建修改views文件了,為此引入了模塊LoginView,django的類視圖擁有自動查找指定方法的功能, 通過調(diào)用是通過as_view()方法實現(xiàn),所以,這里這么寫的意思就是給默認的視圖提供模板。
關于as_view()的工作流程可以看這個文章:http://www.itdecent.cn/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 屬性被設置,我們就顯示一條錯誤消息,指出輸入的用戶名—密碼對與數(shù)據(jù)庫中存儲的任何用戶名—密碼對都不匹配。最后面的input type="hidden"證明這是一個隱藏表單,傳遞的參數(shù)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身份驗證系統(tǒng)中,每個模板都可使用變量user ,這個變量有一個is_authenticated 屬性:如果用戶已登錄,該屬性將為True ,否則為False 。
現(xiàn)在可以訪問http://localhost:8000/users/login/,若登錄了顯示的是你的用戶名,否則,點擊登錄按鈕跳轉到登錄界面試試吧。若已經(jīng)登錄了,可以先注銷,一般保存在瀏覽器的設置里面,清空之后再進去就等點擊登錄了。

2.注銷

修改urls.py,指定索引url路徑,解開上面第1段中的注釋。下面去編寫視圖函數(shù)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中導入了函數(shù)logout() ,它要求將request 對象作為實參,會將存儲在用戶session的數(shù)據(jù)全部清空。然后,我們重定向到主頁。接著修改base.html,增加注銷的入口鏈接。這個在第1段登錄的里面已經(jīng)寫過被注釋了,解開就可以。

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() 函數(shù)認證給出的用戶名和密碼, 而要登錄一個用戶,使用 login() 。該函數(shù)接受一個 HttpRequest 對象和一個 User 對象作為參數(shù)并使用Django的會話( session )框架把用戶的ID保存在該會話中。UserCreationForm是提供的默認表,包括username和兩個password。
關于login函數(shù)和authenticate函數(shù)看這里: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,解開注釋的代碼,增加注冊賬號的入口。

第三:屬于用戶自己的數(shù)據(jù)

1.裝飾器@login_required限制訪問

我們將修改模型Topic ,讓每個主題都歸屬于特定用戶。Django提供了裝飾器@login_required ,對于某些頁面,只允許已登錄的用戶訪問它們。這里開始,代碼回到learning_logs應用程序內(nèi),先去給views.py里面除了index()主頁函數(shù)之外的每一個函數(shù)的頭部都增加裝飾器:

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

login_required() 的代碼檢查用戶是否已登錄,僅當用戶已登錄時,Django才運行topics() 的代碼。如果用戶未登錄,就重定向到登錄頁面。
為實現(xiàn)這種重定向,我們需要修改settings.py,讓Django知道到哪里去查找登錄頁面。請在settings.py末尾添加如下代碼:

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

現(xiàn)在,如果未登錄的用戶請求裝飾器@login_required 的保護頁面,Django將重定向到settings.py中的LOGIN_URL 指定的URL?,F(xiàn)在在未登錄的情況下嘗試訪問這些頁面,將被重定向到登錄頁面。

2.關聯(lián)用戶主題

下面來修改模型Topic ,在其中添加一個關聯(lián)到用戶的外鍵。因為涉及到修改models,我們必須對數(shù)據(jù)庫進行遷移。最后,我們必須對有些視圖進行修改,使其只顯示與當前登錄的用戶相關聯(lián)的數(shù)據(jù)。

#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,作用是刪除主表,數(shù)據(jù)跟著一起刪除。接下來進行數(shù)據(jù)庫遷移:

python manage.py makemigrations learning_logs

此時因為我們代碼中沒有給owner一個默認值,會有報錯提示,提供默認值或者退出回到models.py中填寫默認值,這里輸入1,立刻給一個默認數(shù)據(jù),再輸入一次1,將之前創(chuàng)建的超級用戶作為owner,這里可以不使用超級用戶,在shell會話中可以查看之前注冊過的用戶名和id,新建一個工作窗口,輸入以下shell語句,查看用戶id:

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

若是覺得之前創(chuàng)建的用戶過多,使用如下語句重置數(shù)據(jù)庫,并在這之后重新去創(chuàng)建超級用戶:

python manage.py flush

3.訪問自己的主題

當前,不管你以哪個用戶的身份登錄,都能夠看到所有的主題。我們來改變這種情況,只向用戶顯示屬于自己的主題?,F(xiàn)在修改views.py的topics()函數(shù):

#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只從數(shù)據(jù)庫中獲取owner 屬性為當前用戶的Topic 對象?,F(xiàn)在主題列表頁面已經(jīng)被限制了,只能顯示當前登錄用戶所有的主題,但是如果更換用戶,使用url的方式訪問這個主題的內(nèi)容頁面,比如http://localhost:8000/topics/1/還是可以進入,所以下面對每個主題的內(nèi)容頁面進行限制:

#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這個函數(shù)也和topic函數(shù)一樣需要限制,這里就不重復了。

4.將新增加的主題關聯(lián)用戶

創(chuàng)建新主題時,你必須指定其owner 字段的值,否則會報錯。這里要修改new_topic函數(shù):

#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函數(shù)修改了保存數(shù)據(jù)庫內(nèi)表單的代碼,具體參考第一第3段。原來代碼表單如果正確就保存進數(shù)據(jù)庫,現(xiàn)在為了增加一個owner的值,save函數(shù)內(nèi)傳遞commit=False字段,先保存到new_topic里,設置好owner之后在保存。
new_entry函數(shù)也同樣進行限制,只需要把check_topic_owner這個函數(shù)調(diào)用就行。至此,給這個項目創(chuàng)建一個賬戶管理系統(tǒng)就完成了。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容