項目3 Web應用程序(第19章:用戶帳戶)

 

19.1 用戶可以輸入數據html

  咱們不想讓用戶與管理網站交互,所以咱們將使用Django的表單來建立讓用戶可以輸入數據的頁面。python

19.1.1 添加新主題數據庫

  建立基於表單的頁面的方法交互與前面建立網頁同樣:定義一個URL,編寫一個視圖函數並編寫一個模板。一個主要差異是,須要導入包含表單的模塊forms.py。django

  一、用於添加主題的表單服務器

  在Django中,建立表單的最簡單的方式是使用ModelForm。建立一個名爲forms.py所在的目錄中,並在其中編寫你的第一個表單:app

from django import forms
from .models import Topic

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

  最簡單的ModelForm版本只包含一個內嵌的Meta類,它告訴Django根據哪一個模型建立表單,以及在表單中包含哪些字段。函數

  二、URL模式new_topicpost

  這個新網頁的URL應簡短而具備描述性,所以當用戶要添加新主題時,咱們將切換到http://localhost:8000/new_topic。學習

"""定義learning_logs的URL模式"""
from django.conf.urls import url
from .import views

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'),
]

  三、視圖函數網站

  函數new_topic()須要處理兩種情形:剛進入new_topic網頁(應顯示一個空表單),對提交的表單數據進行處理,並將用戶重定向到網頁topics:

from django.shortcuts import render
from .models import Topic
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from .forms import TopicForm
# Create your views here.
--ship--
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)

  四、模板new_topic

{% 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 %}來防止攻擊者利用表單來得到對服務器未經受權的訪問(這種攻擊被稱爲跨站請求僞造)。 只須要包含模板變量{{ form.as_p}},就可以讓Django自動建立顯示錶單所需的所有字段。修飾符as_p讓Django以段落格式渲染全部表單元素,這是一種整潔地顯示錶單的簡單方式。

  六、連接到頁面new_topic

  接下來,咱們在頁面topics中添加一個到頁面new_topic的連接:

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

{% block content %}
    <p>Topics</p>
    <ul>
        {% for topic in topics %}
            <li>
                <a href="{% url 'learning_logs:topic' topic.id %}">{{topic}}</a>
            </li>
        {% empty %}}
            <li>No topics have been added yet.</li>
        {% endfor %}
    </ul>
    <a href="{% url 'learning_logs:new_topic' %}">Add a new topic:</a>
{% endblock content %}

19.1.2 添加新條目

  一、用於添加新條目的表單

  咱們須要建立一個與模型Entry相關聯的表單,但這個表單的定製程度比TopicForm要高一些:

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})}
        

  小部件(widget)是一個HTML表單元素,如單行文本框、多行文本區域或下拉列表。經過讓Django使用forms.Textarea,咱們定製了字段‘text’的輸入小部件,將文本區域的寬度設置爲80,而不是默認的40列。

  二、URL模式new_entry

# 用於添加新條目的頁面
    url(r'^new_entry/(?Ptopic_id>\d+)/$',views.new_entry,name='new_entry')

  三、視圖函數new_entry

def new_entry(request,topic_id):
    """在特定的主題中添加新條目"""
    topic = Topic.objects.get(id=topic_id)
    if request.method !='POST':
        # 未提交數據,建立一個空表單
        form = EntryForm()
    else:
        # POST提交的數據,對數據進行處理
        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)

  調用save()時,咱們傳遞了實參commit=False,讓Django建立一個新的條目對象,並將其存儲到new_entry中,但不將它保存到數據庫中。咱們將new_entry的屬性topic設置爲在這個函數開頭從數據庫中獲取的主題,而後調用save(),且不指定任何參數。這將把條目保存到數據庫中,並將其與正確的主題相關聯。

  從新定向顯示相關主題的頁面、調用reverse()時,須要提供兩個實參:要根據它來生成URL模式的URL模式的名稱;列表args,其中包含要包含在URL中的全部實參。在這裏,列表args只有一個元素——topic_id。接下來調用HttpResponseRedirect()將用戶從新定向到顯示增長條目所屬主題的頁面,用戶將在該頁面的條目列表中看到新添加的條目。

  四、模板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">
    {% crsf_token %}
    {{ form.as_p}}
    <button name="submit">add entry</button>
</form>

{% endblock content %}}

  咱們在頁面頂端顯示了主題,讓用戶知道他是在哪一個主題中添加條目;該主題名也是一個連接,可用於返回該主題的主頁面。

  表單的實參action包含URL中的topic_id值,讓視圖函數可以添加新條目關聯到正確的主題。

  五、連接到頁面new_entry

{% 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</a>
    </p>
    <ul>
       --ship--</ul>
{% endblock content %}

19.1.3 編輯條目

  下面建立一個頁面,讓用戶可以編輯既有的條目。

  一、URL模式edit_entry

  這個頁面的URL須要傳遞要編輯的條目的ID。修改後delearning_logs/urls.py以下:

    # 用於編輯條目的頁面
    url(r'^edit_entry/(P<entry_id>\d+)/$',views.edit_entry,
        name='edit_entry'),

  二、視圖函數edit_entry()

  頁面edit_entry收到GET請求時,edit_entry()返回一個表單,讓用戶可以對條目進行編輯。該頁面收到POST請求時,將修改後的文本保存到數據庫中:

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)
        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.himl',context)

  三、模板edit_entry

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

  四、連接到頁面edit_entry

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

  將編輯連接放在每一個條目的日期和文本後面。

 

19.2 建立用戶帳戶

 19.2.1 應用程序user

  首先使用命令startapp來建立一個名爲user的應用程序:

(11_env) D:\learning_log>python manage.py startapp users

  這個命令新建了一個名爲user的目錄,其結構與應用程序learning_logs相同。

  一、將應用程序users添加到settings.py中

 # My apps
    'learning_logs',
    'users',

  二、包含應用程序users的URL

  接下來,須要修改項目根目錄中的urls.py,使其包含咱們將爲應用程序users定義的URL:

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^users/',include('user.urls',namespace='users')),
    url(r'',include('learning_logs.urls',namespace='learning_logs')),
    
]

19.2.2 登陸頁面

  首先實現登陸頁面,爲此使用Django提供的默認登陸視圖,所以URL模式會稍有不一樣。早目錄learning_log/users/中,新建一個名爲urls.py的文件,並在其中添加以下代碼:

"""爲應用程序users定義URL模式"""
from django.conf.urls import url
from  django.contrib.auth.views import login
from .import views

urlpatterns = [
    # 登陸頁面
    url(r'^login/$',login,{'template_name':'users/login.html'},name='login')
]

  首先導入默認視圖login。登陸頁面的URL模式與URL http://localhost:8000/users/login匹配。這個URL中的單詞users讓那個Django在users/urls.py中查找,而單詞login讓它將請求發送給Django默認視圖login(視圖實參爲login,而不是views.login)。

  一、模板login.html

  在目錄learning_log/users/中,建立一個名爲templates的目錄,並在其中建立一個名爲users的目錄。一下是模板login.html,你應將其存儲到目錄learning_log/users/templates/users中:

{% 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中添加到登陸頁面的連接,讓全部頁面都包含它。用戶已登陸時,咱們不想顯示這個連接,所以將它嵌套在一個{% if %}標籤中:

<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 'user.login' %}}">log in</a>
    {% endif %}
</p>

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

  三、使用登陸頁面

  前面創建了一個用戶帳戶,下面來登陸一下,看看登陸頁面是否管用。訪問http://localhost:8000/admin/,若是你依然是以管理員身份登陸的,請在頁眉上找到註銷連接並單擊。

  註銷後,訪問http://localhost:8000/users/login,再登陸。

 19.2.3 註銷

  不建立註銷的頁面,而讓用戶只需單擊一個連接就能註銷並返回到主頁。爲此,咱們將爲註銷連接定義一個URL模式,編寫一個視圖函數,並在base.html中添加一個註銷連接。

  一、註銷URL

    # 註銷
    url(r'^logout/$',views.logout_view,name='logout'),

  二、視圖函數logout_view()

from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.contrib.auth import logout
# Create your views here.

def logout_view(request):
    """註銷用戶"""
    logout(request)
    return HttpResponseRedirect(reverse('learning_logs:index'))

  三、連接到註銷視圖

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

19.2.4 註冊頁面

  下面建立一個讓新用戶可以註冊的頁面。咱們將使用Django提供的表單UserCreationForm,但編寫本身的視圖函數和模板。

  一、註冊頁面的URL模式

    # 註冊頁面
    url(r'^register/$',views.register,name='register'),

  二、視圖函數register()

  在註冊頁面首次被請求時,視圖函數register()須要顯示一個空的註冊表單,並在用戶提交填寫好的註冊表單時對其進行處理。若是註冊成功,這個函數還須要讓用戶自動登陸。

def logout_view(request):
    """註銷用戶"""
    logout(request)
    return HttpResponseRedirect(reverse('learning_logs:index'))
def register(request):
    """註冊新用戶"""
    if request.method !='POST':
        # 顯示空的註冊表單
        form = UserCreationForm(date=request.POST)
    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)

  三、註冊模板

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

  這裏也使用了as_p,讓Django在表單中正確地顯示全部的字段,包括錯誤消息——若是用戶沒有正確地填寫表單。  

  四、連接到註冊頁面

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

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

 19.3 讓用戶擁有本身的數據

19.3.1 使用@login_required限制訪問

  Django提供了裝飾器@login_required:對於某些頁面,只容許已登錄的用戶訪問它們。裝飾器是放在函數定義前面的命令,Python在函數運行前,根據它來修改函數代碼的行爲:

  一、限制對topics頁面的訪問

  在learning_logs/views.py中添加以下代碼:

--ship--
from .forms import TopicForm,EntryForm
from django.contrib.auth.decorators import login_required
# Create your views here.
def index(request):
    """學習筆記的主頁"""
    return render(request,'learning_logs/index.html')
@login_required
def topics(request):
    --ship--

  爲實現這種重定向,咱們須要修改setting.py,讓Django知道到哪去查找登陸頁面。在settings.py末尾添加:

# 個人設置
LOGIN_URL = '/users/login/'

  二、全面限制對項目「學習筆記」的訪問

  在項目「學習筆記中」,咱們將不限制對主頁、註冊頁和註銷頁面的訪問,並限制對其餘全部頁面的訪問。

  在下面的learning_logs/views.py中,對除index()外的每一個視圖都應用了裝飾器@login_required:

      

  若是你在未登陸的狀況下嘗試訪問這些頁面,將被重定向到登陸頁面。另外,你還不能單擊到new_topic等頁面的連接。

19.3.2 將數據關聯到用戶

  下面修改模型Topic,在其中添加一個關聯到用戶的外鍵。這樣作後,咱們必須對數據庫進行遷移。最後,咱們必須對有些視圖進行修改,使其只顯示與當前登陸的用戶相關聯的數據。

  一、修改模型Topic

from django.db import models
from django.contrib.auth.models import User
# 定義了一個名爲Topic的類,它繼承了Model—Django中一個定義了模型基本功能的類
# 屬性text是一個CharField—由字符或文本組成的數據。須要存儲少許的文本,如名稱、標題或城市時,可使用
#  date_added是一個DateTimeField—記錄日期和時間的數據。
class Topic(models.Model):
    """用戶學習主題"""
    text = models.CharField(max_length=200)
    date_added = models.DateTimeField(auto_now_add=True)
    owner = models.ForeignKey(User)
--ship--

  二、肯定當前有哪些用戶

  

  三、遷移數據庫

  

       

  爲驗證遷移符合預期,可在Shell會話中像下面這樣作:

       

19.3.3 只容許用戶訪問本身的主題

  在views.py中,對函數topics()作以下修改:

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

19.3.4 保護用戶的主題

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

19.3.5 保護頁面edit_entry

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

19.3.6 將新主題關聯到當前用戶

@login_required
def new_topic(request):
    """添加新主題"""
    if request.method != 'POST':
        # 未提交數據:建立一個新表單
        form = TopicForm()
    else:
        # POST提交的數據,對數據處理
        form = TopicForm(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)
相關文章
相關標籤/搜索