Django入門(二)

跟書《python編程:從入門到實踐》,學習用Django編寫名爲「學習筆記」的Web應用程序。html

Web應用程序的核心是讓任何用戶都可以註冊帳戶並可以使用它,無論用戶身處何方。咱們能夠建立一些表單,讓用戶可以添加主題和條目,以及編輯既有的條目。python


讓用戶可以輸入數據

先來添加幾個頁面,讓用戶可以輸入數據。可讓用戶可以添加新主題、添加新條目以及編輯既有條目,但不能經過管理網站來輸入,由於只有超級用戶才能夠這樣作。sql


添加新主題

首先來讓用戶可以添加新主題。建立基於表單的頁面的方法幾乎與前面建立網頁同樣:定義一個URL,編寫一個視圖函數並編寫一個模板。一個主要差異是,須要導入包含表單的模塊forms.pyshell

  • 用於添加主題的表單:

在Django中,建立表單的最簡單方式是使用ModelForm。建立一個名爲forms.py的文件,將其存儲到models.py所在的目錄中,並在其中編寫第一個表單。數據庫

# vim learning_logs/forms.py

from django import formsfrom .models import Topicclass TopicForm(forms.ModelForm):
    class Meta:
        model = Topic
        fields = ['text']
        labels = {'text': ''}

首先導入了模塊forms以及要使用的模型Topic。而後定義了一個名爲TopicForm的類,它繼承了forms.ModelForm。django

最簡單的ModelForm版本只包含一個內嵌的Meta類,它告訴Django根據哪一個模型建立表單,以及在表單中包含哪些字段。咱們根據模型Topic建立一個表單,該表單只包含字段text,且讓Django不要爲字段text生成標籤。編程

  • URL模式new_topic
# vim learning_logs/urls.py

"""定義learning_logs的URL模式"""from django.urls import path, re_pathfrom . import views

app_name='learning_logs'urlpatterns = [
    # 主頁
    path('', views.index, name='index'),

    # 顯示全部的主題
    path('topics/', views.topics, name='topics'),

    # 特定主題的詳細頁面
    re_path(r'^topics/(?P\d+)/$', views.topic, name='topic'),
    
    # 用於添加新主題的頁面
    path('new_topic/', views.new_topic, name='new_topic'),]

這個URL模式會將請求交給視圖函數new_topic()vim

  • 視圖函數new_topic()

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

# vim learning_logs/views.py

from django.shortcuts import renderfrom django.http import HttpResponseRedirectfrom django.urls import reversefrom .models import Topicfrom .forms import TopicFormdef index(request):
    """學習筆記的主頁"""
    return render(request, 'learning_logs/index.html')def topics(request):
    """顯示全部主題"""
    topics = Topic.objects.order_by('date_added')
    context = {'topics': topics}
    return render(request, 'learning_logs/topics.html', context)def topic(request, topic_id):
    """顯示一個主題及其詳細頁面"""
    topic = Topic.objects.get(id=topic_id)
    entries = topic.entry_set.order_by('-date_added')
    context = {'topic': topic, 'entries': entries}
    return render(request, 'learning_logs/topic.html', context)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)

導入了HttpResponseRedirect類和剛纔建立的表單TopicForm,用戶提交主題後咱們將使用HttpResponseRedirect類將用戶重定向到網頁topics。函數reverse()根據指定的URL模型肯定URL,這意味着Django將在頁面被請求時生成URL。服務器

首先判斷請求方法是不是POST——若是請求方法不是POST,返回一個空表單;若是請求方法是POST,將使用用戶輸入的數據(存儲在request.POST中)建立一個TopicForm實例,這樣對象form將包含用戶提交的信息。

而後要將提交的信息保存到數據庫,必須先經過檢查肯定它們是有效的。函數is_valid()覈實用戶填寫了全部必不可少的字段(表單字段默認都是必不可少的),且輸入的數據與要求的字段類型一致。若是全部字段都有效,咱們就調用save()將表單中的數據寫入數據庫。

最後使用reverse()獲取頁面topics的URL,並將其傳遞給HttpResponseRedirect(),後者將用戶的瀏覽器重定向到頁面topics。在頁面topics中,用戶將在主題列表中看到他剛輸入的主題。

  • GET請求和POST請求:

建立Web應用程序時,將用到的兩種主要請求類型是GET請求和POST請求。對於只是從服務器讀取數據的頁面,使用GET請求;在用戶須要經過表單提交信息時,一般使用POST請求。處理全部表單時,咱們都將指定使用POST方法。

函數new_topic()將請求對象做爲參數。用戶初次請求該網頁時,其瀏覽器將發送GET請求;用戶填寫並提交表單時,其瀏覽器將發送POST請求。根據請求的類型,咱們能夠肯定用戶請求的是空表單(GET請求)仍是要求對填寫好的表單進行處理(POST請求)。

  • 模板new_topic
# vim learning_logs/templates/learning_logs/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 topicbutton>
  form>
    {% endblock content %}

繼承了base.html,而後定義了一個HTML表單。實參action告訴服務器將提交的表單數據發送到哪裏,這裏咱們將它發回給視圖函數new_topic()。實參method讓瀏覽器以POST請求的方式提交數據。

Django使用模板標籤{% csrf_token %}來防止***者利用表單來得到對服務器未經受權的訪問(這種***被稱爲跨站
請求僞造)。接着顯示錶單,只需包含模板變量{{ form.as_p }},就可以讓Django自動建立顯示錶單所需的所有字段。修
飾符as_p讓Django以段落格式渲染全部表單元素。

  • 連接到頁面new_topic
# vim learning_logs/templates/learning_logs/topics.html

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

{% block content %}  <p>Topicsp>

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

  • 訪問網頁:

在這裏插入圖片描述

在這裏插入圖片描述


添加新條目

如今已經能夠添加新主題了,可是還不能添加新條目,再次定義URL,編寫視圖函數和模板,並連接到添加新條目的網頁。

  • 用於添加新條目的表單:

建立一個與模型Entry相關聯的表單。

# vim learning_logs/forms.py

from django import formsfrom .models import Topic, Entryclass TopicForm(forms.ModelForm):
    class Meta:
        model = Topic
        fields = ['text']
        labels = {'text': ''}class EntryForm(forms.ModelForm):
    class Meta:
        model = Entry
        fields = ['text']
        labels = {'text': ''}
        wgdgets = {'text': forms.Textarea(attrs={'cols': 80})}

除了導入Topic外,還須要導入Entry。新類EntryForm繼承了forms.ModelForm,它包含的Meta類指出了表單基於的模型以及要在表單中包含哪些字段。

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

  • URL模式new_entry

在用於添加新條目的頁面的URL模式中,須要包含實參topic_id ,由於條目必須與特定的主題相關聯。

# vim learning_logs/urls.py

"""定義learning_logs的URL模式"""from django.urls import path, re_pathfrom . import views

app_name='learning_logs'urlpatterns = [
    # 主頁
    path('', views.index, name='index'),

    # 顯示全部的主題
    path('topics/', views.topics, name='topics'),

    # 特定主題的詳細頁面
    re_path(r'^topics/(?P\d+)/$', views.topic, name='topic'),

    # 用於添加新主題的頁面
    path('new_topic/', views.new_topic, name='new_topic'),

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

這個URL模式與形式爲http://localhost:8000/new_entry/id/的URL匹配,其中id是一個與主題ID匹配的數字。代碼(?P捕獲一個數字值,並將其存儲在變量topic_id中。請求的URL與這個模式匹配時,Django將請求和主題ID發送給函數new_entry()

  • 視圖函數new_entry()

視圖函數new_entry()與視圖函數new_topic()比較相似。

# vim learning_logs/views.py

from django.shortcuts import renderfrom django.http import HttpResponseRedirectfrom django.urls import reversefrom .models import Topicfrom .forms import TopicForm, EntryFormdef index(request):
    """學習筆記的主頁"""
    return render(request, 'learning_logs/index.html')def topics(request):
    """顯示全部主題"""
    topics = Topic.objects.order_by('date_added')
    context = {'topics': topics}
    return render(request, 'learning_logs/topics.html', context)def topic(request, topic_id):
    """顯示一個主題及其詳細頁面"""
    topic = Topic.objects.get(id=topic_id)
    entries = topic.entry_set.order_by('-date_added')
    context = {'topic': topic, 'entries': entries}
    return render(request, 'learning_logs/topic.html', context)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:
        # 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)

首先導入了剛建立的EntryForm,new_entry()的定義包含形參topic_id,用於存儲從URL中得到的值。渲染頁面以及處理表單數據時,都須要知道針對的是哪一個主題,所以咱們使用topic_id來得到正確的主題。

而後仍是檢查請求方法是POST仍是GET。若是是GET請求,將建立一個空的EntryForm實例。若是請求方法爲POST,咱們就對數據進行處理:建立一個EntryForm實例,使用request對象中的POST數據來填充它;再檢查表單是否有效,若是有效則設置條目對象的屬性topic,再將條目對象保存到數據庫。

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

最後,將用戶重定向到顯示相關主題的頁面。調用reverse()時,須要提供兩個實參:要根據它來生成URL的URL模式的名稱;列表args ,其中包含要包含在URL中的全部實參。

  • 模板new_entry

模板new_entry與模板new_topic也比較相似。

# vim learning_logs/templates/learning_logs/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 entrybutton>
  form>
    {% endblock content %}

首先繼承base.html。接着在頁面頂端顯示了主題,讓用戶知道是在哪一個主題中添加條目;該主題名也是一個連接,可用於返回該主題的主頁面。

表單的實參action 包含URL中的topic_id值,讓視圖函數可以將新條目關聯到正確的主題。除此以外,這個模板與模板new_topic.html徹底相同。

  • 連接到頁面new_entry
# vim learning_logs/templates/learning_logs/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 entrya>
  p>
  <ul>
  {% for entry in entries %}    <li>
      <p>{{ entry.date_added|date:'M d, Y H:i' }}p>
      <p>{{ entry.text|linebreaks }}p>
    li>
  {% empty %}    <li>
      There are no entries for this topic yet.    li>
  {% endfor %}  ul>{% endblock content %}

在顯示條目前添加連接,由於在這種頁面中,執行的最多見的操做是添加新條目。

  • 訪問網頁:

在這裏插入圖片描述

在這裏插入圖片描述

在這裏插入圖片描述


編輯條目

下面來建立一個頁面,讓用戶能夠編輯既有的條目。

  • URL模式edit_entry
# vim learning_logs/urls.py

"""定義learning_logs的URL模式"""from django.urls import path, re_pathfrom . import views

app_name='learning_logs'urlpatterns = [
    # 主頁
    path('', views.index, name='index'),

    # 顯示全部的主題
    path('topics/', views.topics, name='topics'),

    # 特定主題的詳細頁面
    re_path(r'^topics/(?P\d+)/$', views.topic, name='topic'),

    # 用於添加新主題的頁面
    path('new_topic/', views.new_topic, name='new_topic'),

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

    # 用於編輯條目的頁面
    re_path(r'^edit_entry/(?P\d+)/$', views.edit_entry, name='edit_entry'),]

在URL(如http://localhost:8000/edit_entry/1/)中傳遞的ID存儲在形參entry_id中。這個URL模式將預期匹配的請求發送給視圖函數edit_entry()

  • 視圖函數edit_entry()

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

# vim learning_logs/views.py

from django.shortcuts import renderfrom django.http import HttpResponseRedirectfrom django.urls import reversefrom .models import Topic, Entryfrom .forms import TopicForm, EntryFormdef index(request):
    """學習筆記的主頁"""
    return render(request, 'learning_logs/index.html')def topics(request):
    """顯示全部主題"""
    topics = Topic.objects.order_by('date_added')
    context = {'topics': topics}
    return render(request, 'learning_logs/topics.html', context)def topic(request, topic_id):
    """顯示一個主題及其詳細頁面"""
    topic = Topic.objects.get(id=topic_id)
    entries = topic.entry_set.order_by('-date_added')
    context = {'topic': topic, 'entries': entries}
    return render(request, 'learning_logs/topic.html', context)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:
        # 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)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/new_entry.html', context)

首先導入模型Entry 。接着獲取用戶要修改的條目對象,以及與該條目相關聯的主題。在請求方法爲GET時,咱們使用實參instance=entry建立一個EntryForm實例。該實參讓Django建立一個表單,並使用既有條目對象中的信息填充它。用戶將看到既有的數據,並可以編輯它們。

處理POST請求時,傳遞實參instance=entrydata=request.POST,讓Django根據既有條目對象建立一個表單實例,並根據request.POST中的相關數據對其進行修改。而後檢查表單是否有效,若是有效就調用save()且不指定任何實參。接下來重定向到顯示條目所屬主題的頁面,用戶將在其中看到其編輯的條目的新版本。

  • 模板edit_entry
# vim learning_logs/templates/learning_logs/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 changesbutton>
  form>{% endblock content %}

  • 連接到頁面edit_entry
# vim learning_logs/templates/learning_logs/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 entrya>
  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 entrya>
      p>
    li>
  {% empty %}    <li>
      There are no entries for this topic yet.    li>
  {% endfor %}  ul>{% endblock content %}

將編輯連接放在每一個條目的日期和文本後面。在循環中,使用模板標籤{% url %}根據URL模式edit_entry和當前條目的ID屬性entry.id來肯定URL。連接文本爲"edit entry",它出如今頁面中每一個條目的後面。

  • 訪問網頁:

在這裏插入圖片描述


建立用戶帳戶

應用路徑:users

接下來創建一個用戶註冊和身份驗證系統,讓用戶可以註冊帳戶,進而登陸和註銷。

應用程序users

  • 建立應用程序:
# python manage.py  startapp users# lsdb.sqlite3  learning_log  learning_logs  ll_env  manage.py  users# ls users/admin.py  apps.py  __init__.py  migrations  models.py  tests.py  views.py

能夠看到, users目錄的結構與應用程序learning_logs相同。

  • 將應用程序users添加到settings.py中:
# vim learning_log/settings.pyINSTALLED_APPS = [
    'django.contrib.admin',    'django.contrib.auth',    'django.contrib.contenttypes',    'django.contrib.sessions',    'django.contrib.messages',    'django.contrib.staticfiles',    # 個人應用程序
    'learning_logs',    'users',                #增長這行]

  • 包含應用程序users的URL:
# vim learning_log/urls.py

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

urlpatterns = [
    path('admin/', admin.site.urls),
    path('users/', include('users.urls', namespace='users')),               #增長這行
    path('', include('learning_logs.urls', namespace='learning_logs')),]

一樣的,這裏保存完文件以後會報錯ModuleNotFoundError: No module named 'users.urls',不用理會,繼續往下作。


登陸頁面

如今來實現登陸頁面的功能,可使用Django提供的默認登陸視圖。

  • URL模式:
# vim users/urls.py

"""定義users的URL模式"""from django.urls import path, re_pathfrom django.contrib.auth.views import LoginViewfrom . import views

app_name='users'urlpatterns = [
    # 登陸主頁
    re_path(r'^login/$', LoginView.as_view(template_name='users/login.html'), name='login'),]

  • 模板login.html
# mkdir -p users/templates/users# vim users/templates/users/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 inbutton>
  <input type="hidden" name="next" value="{% url 'learning_logs:index' %}" />
  form>
    {% endblock content %}

先繼承base.html,請注意,一個應用程序中的模板可繼承另外一個應用程序中的模板。

若是表單的errors屬性被設置,咱們就顯示一條錯誤消息,指出輸入的用戶名—密碼對與數據庫中存儲的任何用戶名—密碼對都不匹配。

咱們要讓登陸視圖處理表單,所以將實參action設置爲登陸頁面的URL。登陸視圖將一個表單發送給模板,在模板中,咱們顯示這個表單並添加一個提交按鈕。後面包含了一個隱藏的表單元素’next’,其中的實參value告訴Django在用戶成功登陸後將其重定向到主頁。

  • 連接到登陸頁面:
# vim learning_logs/templates/learning_logs/base.html

<p>
  <a href="{% url 'learning_logs:index' %}">Learning Loga> -  <a href="{% url 'learning_logs:topics' %}">Topicsa> - 
  {% if user.is_authenticated %}
    Hello, {{ user.username }}.
  {% else %}    <a href="{% url 'users:login' %}">log ina>
  {% endif %}p>{% block content %}{% endblock content %}

在Django身份驗證系統中,每一個模板均可使用變量user,該變量有一個is_authenticated屬性:若是用戶已登陸,該屬性將爲True,不然爲False。

在這裏向已登陸的用戶顯示一條問候語。對於已經過身份驗證的用戶設置了屬性username,咱們使用這個屬性來個性化問候語,讓用戶知道他已經登陸。而對於還未經過身份驗證的用戶,咱們再顯示一個到登陸頁面的連接。

  • 使用登陸頁面:

訪問192.168.30.128:8000/users/login/。若是以前使用超級用戶登陸的,註銷以後再訪問。

在這裏插入圖片描述


註銷

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

  • 註銷URL:
# vim users/urls.py

"""定義users的URL模式"""from django.urls import path, re_pathfrom django.contrib.auth.views import LoginViewfrom . import views

app_name='users'urlpatterns = [
    # 登陸主頁
    re_path(r'^login/$', LoginView.as_view(template_name='users/login.html'), name='login'),

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

這個URL模式將請求發送給函數logout_view(),要將其與咱們下面在其中調用的函數logout()區分開來。

  • 視圖函數logout_view()
# vim users/views.py

from django.shortcuts import renderfrom django.http import HttpResponseRedirectfrom django.urls import reversefrom django.contrib.auth import logoutdef logout_view(request):
    """註銷用戶"""
    logout(request)
    return HttpResponseRedirect(reverse('learning_logs:index'))

先從django.contrib.auth中導入了函數logout();而後調用了函數logout(),它要求將request對象做爲實參;最後重定向到主頁。

  • 連接到註銷視圖:

如今須要在base.html中添加註銷連接,讓每一個頁面都包含它;將它放在標籤{% if user.is_authenticated %}中,使得僅當用戶登陸後才能看到它。

# vim learning_logs/templates/learning_logs/base.html

<p>
  <a href="{% url 'learning_logs:index' %}">Learning Loga> -  <a href="{% url 'learning_logs:topics' %}">Topicsa> -
  {% if user.is_authenticated %}
    Hello, {{ user.username }}.    <a href="{% url 'users:logout' %}">log outa>
  {% else %}    <a href="{% url 'users:login' %}">log ina>
  {% endif %}p>{% block content %}{% endblock content %}

  • 訪問網頁:

使用超級用戶登陸

在這裏插入圖片描述

在這裏插入圖片描述


註冊頁面

下面使用Django提供的表單UserCreationForm來建立一個讓新用戶可以註冊的頁面。

  • URL模式:
# vim users/urls.py

"""定義users的URL模式"""from django.urls import path, re_pathfrom django.contrib.auth.views import LoginViewfrom . import views

app_name='users'urlpatterns = [
    # 登陸主頁
    re_path(r'^login/$', LoginView.as_view(template_name='users/login.html'), name='login'),

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

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

這個模式與URLhttp://localhost:8000/users/register/匹配,並將請求發送給咱們即將編寫的函數register()

  • 視圖函數register()

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

# vim users/views.py

from django.shortcuts import renderfrom django.http import HttpResponseRedirectfrom django.urls import reversefrom django.contrib.auth import login, logout, authenfrom django.contrib.auth.forms import UserCreationFormdef logout_view(request):
    """註銷用戶"""
    logout(request)
    return HttpResponseRedirect(reverse('learning_logs:index'))
    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)

首先導入了函數login()authenticate(),以便在用戶正確地填寫了註冊信息時讓其自動登陸。還導入了默認表單UserCreationForm。在函數register()中,咱們檢查要響應的是不是POST請求。若是不是,就建立一個UserCreationForm實例,且不給它提供任何初始數據。

若是響應的是POST請求,咱們就根據提交的數據建立一個UserCreationForm實例,並檢查這些數據是否有效:就這裏而言,是用戶名未包含非法字符,輸入的兩個密碼相同,以及用戶沒有試圖作惡意的事情。若是提交的數據有效,咱們就調用表單的方法save(),將用戶名和密碼的散列值保存到數據庫中。方法save()返回新建立的用戶對象,咱們將其存儲在new_user中。

保存用戶的信息後,咱們讓用戶自動登陸,這包含兩個步驟。首先,咱們調用authenticate(),並將實參new_user.username 和密碼傳遞給它。用戶註冊時,被要求輸入密碼兩次;因爲表單是有效的,咱們知道輸入的這兩個密碼是相同的,所以可使用其中任何一個。在這裏,咱們從表單的POST數據中獲取與鍵’password1’相關聯的值。若是用戶名和密碼無誤,方法authenticate()將返回一個經過了身份驗證的用戶對象,而咱們將其存儲在變量authenticated_user中。

接下來,咱們調用函數login(),並將對象requestauthenticated_user傳遞給它,這將爲新用戶建立有效的會話。最後,咱們將用戶重定向到主頁,其頁眉中顯示了一條個性化的問候語,讓用戶知道註冊成功了。

  • 註冊模板register.html
# vim users/templates/users/register.html

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

{% block content %}  <form method="post" action="{% url 'users:register' %}">
    {% csrf_token %}
    {{ form.as_p }}        
    <button name="submit">registerbutton>
    <input type="hidden" name="next" value="{% url 'learning_logs:index' %}" />
  form>
    {% endblock content %}

  • 連接到註冊頁面:
# vim learning_logs/templates/learning_logs/base.html

<p>
  <a href="{% url 'learning_logs:index' %}">Learning Loga> -  <a href="{% url 'learning_logs:topics' %}">Topicsa> -
  {% if user.is_authenticated %}
    Hello, {{ user.username }}.    <a href="{% url 'users:logout' %}">log outa>
  {% else %}    <a href="{% url 'users:register' %}">registera> -    <a href="{% url 'users:login' %}">log ina>
  {% endif %}p>{% block content %}{% endblock content %}

  • 訪問網頁:

如今,已登陸的用戶看到的是個性化的問候語和註銷連接,而未登陸的用戶看到的是註冊連接和登陸連接。

在這裏插入圖片描述

在這裏插入圖片描述

在這裏插入圖片描述

在這裏插入圖片描述

須要注意的是,這裏的註冊系統容許用戶建立任意數量的帳戶。實際狀況下的系統會要求用戶確認其身份:發送一封確認郵件或者用手機驗證碼,用戶回覆後其帳戶才生效。經過這樣作,系統生成的垃圾帳戶將比這裏使用的簡單系統少。


讓用戶擁有本身的數據

用戶應該可以輸入其專有的數據,所以咱們將建立一個系統,肯定各項數據所屬的用戶,再限制對頁面的訪問,讓用戶只能使用本身的數據。

下面將修改模型Topic ,讓每一個主題都歸屬於特定用戶。這也將影響條目,由於每一個條目都屬於特定的主題。


使用@login_required限制

Django提供了裝飾器@login_required,讓你可以輕鬆地實現這樣的目標:對於某些頁面,只容許已登陸的用戶訪問它們。裝飾器(decorator)是放在函數定義前面的指令,Python在函數運行前,根據它來修改函數代碼的行爲。

  • 限制對topics頁面的訪問:
# vim learning_logs/views.py

from django.shortcuts import renderfrom django.http import HttpResponseRedirectfrom django.urls import reversefrom django.contrib.auth.decorators import login_requiredfrom .models import Topic, Entryfrom .forms import TopicForm, EntryFormdef index(request):
    """學習筆記的主頁"""
    return render(request, 'learning_logs/index.html')@login_requireddef topics(request):
    """顯示全部主題"""
    topics = Topic.objects.order_by('date_added')
    context = {'topics': topics}
    return render(request, 'learning_logs/topics.html', context)def topic(request, topic_id):
    """顯示一個主題及其詳細頁面"""
    topic = Topic.objects.get(id=topic_id)
    entries = topic.entry_set.order_by('-date_added')
    context = {'topic': topic, 'entries': entries}
    return render(request, 'learning_logs/topic.html', context)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:
        # 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)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/new_entry.html', context)

首先導入函數login_required(),將login_required()做爲裝飾器用於視圖函數topics()——在它前面加上符號@login_required,讓Python在運行topics()的代碼前先運行login_required()的代碼。

login_required()的代碼檢查用戶是否已登陸,僅當用戶已登陸時,Django才運行topics()的代碼。若是用戶未登陸,就重定向到登陸頁面。

爲了實現未登陸的重定向,須要修改settings.py

# vim learning_log/settings.py                  #末尾添加# 個人設置LOGIN_URL = '/users/login/'

  • 訪問網頁:

在這裏插入圖片描述

能夠看到,如今直接點擊topics會直接跳轉到登陸頁。

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

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

# vim learning_logs/views.py

from django.shortcuts import renderfrom django.http import HttpResponseRedirectfrom django.urls import reversefrom django.contrib.auth.decorators import login_requiredfrom .models import Topic, Entryfrom .forms import TopicForm, EntryFormdef index(request):
    """學習筆記的主頁"""
    return render(request, 'learning_logs/index.html')@login_requireddef topics(request):
    """顯示全部主題"""
    topics = Topic.objects.order_by('date_added')
    context = {'topics': topics}
    return render(request, 'learning_logs/topics.html', context)@login_requireddef topic(request, topic_id):
    """顯示一個主題及其詳細頁面"""
    topic = Topic.objects.get(id=topic_id)
    entries = topic.entry_set.order_by('-date_added')
    context = {'topic': topic, 'entries': entries}
    return render(request, 'learning_logs/topic.html', context)@login_requireddef 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)@login_requireddef 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)@login_requireddef 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/new_entry.html', context)

若是你在未登陸的狀況下嘗試訪問這些頁面,將被重定向到登陸頁面。此外,你還不能單擊到new_topic等頁面的連接,若是你輸入URL http://192.168.30.128:8000/new_topic/,將重定向到登陸頁面。對於全部與私有用戶數據相關的URL,都應限制對它們的訪問。

在這裏插入圖片描述


將數據關聯到用戶

如今將數據關聯到提交它們的用戶。咱們只需將最高層的數據關聯到用戶,這樣更低層的數據將自動關聯到用戶。在項目「學習筆記」中,應用程序的最高層數據是主題,而全部條目都與特定主題相關聯。只要每一個主題都歸屬於特定用戶,咱們就能肯定數據庫中每一個條目的全部者。

  • 修改模型Topic:
# vim learning_logs/models.py

from django.db import modelsfrom django.contrib.auth.models import Userclass 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        
class Entry(models.Model):
    """學到的有關某個主題的具體知識"""
    topic = models.ForeignKey(Topic, on_delete=models.CASCADE)
    text = models.TextField()
    date_added = models.DateTimeField(auto_now_add=True)

    class Meta:
        verbose_name_plural = 'entries'

    def __str__(self):
        """返回模型的字符串表示"""
        return self.text[:50] + "..."

首先導入了django.contrib.auth中的模型User,而後在Topic中添加了字段owner,它創建到模型User的外鍵關係。

  • 肯定當前有哪些用戶:

遷移數據庫時,Django將對數據庫進行修改,使其可以存儲主題和用戶之間的關聯。爲執行遷移,Django須要知道該將各個既有主題關聯到哪一個用戶。最簡單的辦法是,將既有主題都關聯到同一個用戶,如超級用戶。爲此,咱們須要知道用戶的ID。

# python manage.py shell>>> from django.contrib.auth.models import User>>> User.objects.all()<QuerySet [<User: ll_admin>, <User: lzx>]>>>> for user in User.objects.all():...     print(user.username, user.id)... 
ll_admin 1
lzx 2

  • 遷移數據庫:
# python manage.py makemigrations learning_logsSelect an option: 1                 #選擇第1個選項——如今提供默認值>>> 1               #輸入用戶IDMigrations for 'learning_logs':
  learning_logs/migrations/0003_topic_owner.py
    - Add field owner to topic

將全部既有主題都關聯到管理用戶ll_admin,我輸入了用戶ID值1。並不是必須使用超級用戶,也可以使用已建立的任何用戶的ID。

# python manage.py migrate                  #執行遷移# 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

能夠看到,每一個主題的全部者是超級用戶ll_admin。

注意:也能夠重置數據庫而不是遷移它,但若是這樣作,既有的數據都將丟失。若是確實想要一個新的數據庫,能夠執行命令python manage.py flush,這會重建數據庫的結構。若是你這樣作,就須要從新建立超級用戶,且原來的全部數據都將丟失。


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

  • 修改視圖函數views.py
# vim learning_logs/views.py

@login_requireddef topics(request):
    """顯示全部主題"""
    topics = Topic.objects.filter(owner=request.user).order_by('date_added')                #修改
    context = {'topics': topics}
    return render(request, 'learning_logs/topics.html', context)

用戶登陸後,request對象將有一個user屬性,這個屬性存儲了有關該用戶的信息。代碼Topic.objects.filter(owner=request.user)讓Django只從數據庫中獲取owner屬性爲當前用戶的Topic對象。

要查看結果,以全部既有主題關聯到的用戶的身份登陸(這裏咱們是ll_admin)並訪問topics頁面,你將看到全部的主題。而後,註銷並以另外一個用戶的身份登陸,topics頁面將不會列出任何主題。

在這裏插入圖片描述

在這裏插入圖片描述


保護用戶的主題

接下來限制對單個主題的頁面的訪問。

  • 修改視圖函數views.py
# vim learning_logs/views.py

from django.http import HttpResponseRedirect, Http404               #導入Http404@login_requireddef 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)

服務器上沒有請求的資源時,標準的作法是返回404響應。首先導入了異常Http404,並在用戶請求它不能查看的主題時引起這個異常。收到主題請求後,在渲染網頁前檢查該主題是否屬於當前登陸的用戶。若是請求的主題不歸當前用戶全部,咱們就引起Http404異常,讓Django返回一個404錯誤頁面。


保護頁面edit_entry

頁面edit_entry的URL爲http://192.168.30.128:8000/edit_entry/entry_id/,其中entry_id是一個數字。下面來保護這個頁面,禁止用戶經過輸入相似於前面的URL來訪問其餘用戶的條目。

  • 修改視圖函數views.py
# vim learning_logs/views.py

@login_requireddef 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':
        # 初次請求,使用當前條目填充表單
        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/new_entry.html', context)

獲取指定的條目以及與之相關聯的主題,而後檢查主題的全部者是不是當前登陸的用戶,若是不是,就引起Http404異常。


將新主題關聯到當前用戶

當前,用於添加新主題的頁面存在問題。若是你嘗試添加新主題,將看到錯誤消息IntegrityError,指出learning_logs_topic.user_id不能爲NULL 。Django的意思是說,建立新主題時,你必須指定其owner字段的值。

  • 修改視圖函數views.py
# vim learning_logs/views.py

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

首先調用form.save(),並傳遞實參commit=False,由於咱們先修改新主題,再將其保存到數據庫中。

接下來,將新主題的owner屬性設置爲當前用戶。最後,對剛定義的主題實例調用save()。如今主題包含全部必不可少的數據,將被成功地保存。

如今,這個項目容許任何用戶註冊,而每一個用戶想添加多少新主題均可以。但每一個用戶都只能訪問本身的數據,不管是查看數據、輸入新數據仍是修改舊數據時都如此。

相關文章
相關標籤/搜索