国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Python學習之路18-用戶賬戶

bovenson / 2614人閱讀

摘要:通過的定制字段的輸入小部件,將文本框的寬度設置為列,而不是默認的列。為此將創建一個新的應用程序,其中包含處理用戶賬戶相關的所有功能。該函數將會為通過了身份驗證的用戶對象創建會話。

《Python編程:從入門到實踐》筆記。
本篇記錄如何創建用戶注冊系統,如何實現用戶輸入自己的數據。
1. 前言

在本篇中,我們將:

創建一些表單,讓用戶能夠添加主題和條目,以及編輯既有的條目;

實現一個身份驗證系統。

2. 讓用戶能夠輸入數據

先添加幾個頁面,讓用戶能夠添加新主題,新條目以及編輯條目。

2.1 添加新主題

和之前創建網頁的步驟一樣:定義URL,編寫視圖函數,編寫模板。主要區別是,這里需要一個包含表單的模塊forms.py

2.1.1 創建forms.py模塊

用戶輸入信息時,需要進行驗證,確保提交的信息是正確的數據類型,且不是惡意信息,如中斷服務器的代碼。然后再處理信息,并保存到數據庫中。當然,這些工作很多都由Django自動完成。

models.py所在的目錄中新建forms.py模塊。創建表單的最簡單方法是繼承Django的ModelForm類:

from django import forms
from .models import Topic

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

最簡單的ModelForm版本只包含一個內嵌的Meta類,它告訴Django根據哪個模型創建表單,以及在表單中包含哪些字段。第6行,我們根據Topic創建一個表單,該表單只包含字段text(第7行),并不為該字段生成標簽(第8行)。

2.1.2 URL模式new_topic

當用戶要添加新主題時,將切換到http://localhost:8000/new_topic/ 。在learning_logs/urls.py中添加如下代碼:

urlpatterns = [
    -- snip --
    # 用于添加新主題的網站
    path("new_topic/", views.new_topic, name="new_topic"),
]
2.1.3 視圖函數new_topic()

該函數需要處理兩種情形:①剛進入new_topic網頁,顯示一個空表單;②對提交的表單數據進行處理,并將用戶重定向到網頁topics。修改views.py文件:

from django.http import HttpResponseRedirect
from django.urls import reverse
from .forms import TopicForm

def new_topic(request):
    """添加新主題"""
    if request.method != "POST":
        # 為提價數據:創建一個新表單
        form = TopicForm()
    else:
        # POST提交的數據,對數據進行處理
        form = TopicForm(request.POST)
        if form.is_valid():
            form.save()
            # 該類將用戶重定向到網頁topics,函數reverse()根據指定的URL模型確定URL
            return HttpResponseRedirect(reverse("learning_logs:topics"))

    context = {"form": form}
    return render(request, "learning_logs/new_topic.html", context)
2.1.4 GET請求和POST請求

創建Web應用程序時,將用到兩種主要數據請求類型:GET請求和POST請求。從這倆英文單詞可以看出,如果只從服務器讀取數據頁面,則使用GET請求;如果要提交用戶填寫的表單,通常使用POST請求。當然還有一些其他的請求類型,但這個項目中沒有使用。本項目中處理表單都使用POST方法。

request.method存儲了請求的類型(第7行代碼)。

當不是POST請求時,我們生成一個空表單傳遞給模板new_topic.html,然后返回給用戶;當請求是POST時,我們從request.POST這個變量中獲取用戶提交的數據,并暫存到form變量中。

通過is_valid()方法驗證表單數據是否滿足要求:用戶是否填寫了所有必不可少的字段(表單字段默認都是必填的),且輸入的數據與字段類型是否一致。當然這些驗證都是Django自動進行的。如果表單有效,在通過formsave()方法存儲到數據庫,然后通過reverse()函數獲取頁面topics的URL,并將其傳遞給HTTPResponseRedirect()以重定向到topics頁面。如果表單無效,把這些數據重新傳回給用戶。

2.1.5 模板new_topic.html
{% extends "learning_logs/base.html" %}

{% block content %}
  

Add a new topic:

{% csrf_token %} {{ form.as_p }}
{% endblock content %}

模板繼承了base.html,因此其基本結構和項目中的其他頁面相同。第6行中,參數action告訴服務器將提交的表單數據送到什么位置去處理,參數method瀏覽器POST請求的方式提交數據。

Django使用模板標簽csrf_token(第7行)來防止攻擊者利用表單獲得對服務器未經授權的訪問(跨站請求偽造)。

Django顯示表單非常方便:只需要使用模板變量form.as_p,修飾符as_p讓Django以段落格式渲染所有表單元素,這是一種整潔地顯示表單的簡單方法。

Django不自動創建提交表單的按鈕,需自行創建。

2.1.6 鏈接到頁面new_topic

在頁面topics.html中添加一個到頁面new_topic的鏈接:

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

{% block content %}
  -- snip --
  Add a new topic:
{% endblock content %}
2.1.7 效果

以下是實際效果圖:

通過這個頁面,隨意添加幾個主題,如下:

2.2 添加新條目

和前面的步驟相似:創建條目表單,添加URL,添加視圖,添加模板,鏈接到頁面

2.2.1 創建條目表單

創建一個與模型Entry相關聯的表單,但這個表單的自定義程度比TopicForm要高些,依然是在剛才創建的forms.py中添加:

from .models import Topic, Entry

class EntryForm(forms.ModelForm):
    class Meta:
        model = Entry
        fields = ["text"]
        labels = {"text": ""}
        widgets = {"text": forms.Textarea(attrs={"cols": 80})}

代碼中定義了屬性widgets。小部件(widget)是一個HTML表單元素,如單行文本框、多行文本框或下拉列表。通過設置屬性widgets可以覆蓋Django選擇的默認小部件。通過Django的forms.Textarea定制字段“text"的輸入小部件,將文本框的寬度設置為80列,而不是默認的40列。

2.2.2 添加URL模式new_entry

修改learning_logs/urls.py

urlpatterns = [
    -- snip --
    path("new_entry//", views.new_entry, name="new_entry"),
]

該URL模式與形式為http://localhost:8000/new_entry/topi_id/ 的URL匹配,其中topic_id是主題的ID。

2.2.3 視圖函數new_entry()

與函數new_topic()很像:

from .forms import TopicForm, EntryForm

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)

new_entry()的定義包含形參topic_id,用于存儲從URL中獲得的值。

在調用save()時傳遞了參數commit=False(第14行),它讓Django創建一個新的條目對象,但并不立刻提交數據庫,而是暫時存儲在變量new_entry中,待為這個新條目對象添加了屬性topic之后再提交數據庫。

在重定向時,reverse()函數中傳遞了兩個參數,URL模式的名稱以及列表argsargs包含要包含在URL中的所有參數。

2.2.4 模板new_entry.html

類似于new_topic

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

{% block content %}
  

{{ topic }}

Add a new entry:

{% csrf_token %} {{ form.as_p }}
{% endblock content %}

注意第4行代碼,改行代碼返回到特定主題頁面。

2.2.5 鏈接到頁面new_entry

在顯示特定主題的頁面中添加到頁面new_entry的鏈接,修改topic.html

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

{% block content %}

  

Topic: {{ topic }}

Entries:

add new entry

-- snip -- {% endblock content %}
2.2.6 效果

下圖是實際效果,請隨意添加一些條目:

2.3 編輯條目

創建一個頁面,讓用戶能編輯既有條目。順序是:添加URL,添加視圖,添加模板,鏈接到頁面。

2.3.1 URL模式edit_entry

修改learning_logs/urls.py

urlpatterns = [
    -- snip --
    path("edit_entry//", views.edit_entry, name="edit_entry"),
]
2.3.2 視圖函數edit_entry()
from .models import Topic, Entry

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.html", context)

首先獲取要被修改的entry以及與該條目相關的主題。處理GET請求時,通過參數instance=entry創建EntryForm實例,該參數讓Django創建一個表單,并使用既有條目對象中的信息填充它。處理POST請求時,還傳入了data=request.POST參數,Django根據POST中的相關數據對entry進行修改。

2.3.3 模板edit_entry.html
{% extends "learning_logs/base.html" %}

{% block content %}
  

{{ topic }}

Edit entry:

{% csrf_token %} {{ form.as_p }}
{% endblock content %}
2.3.4 鏈接到頁面edit_entry.html

在顯示特定主題的頁面中,需要給每個條目添加到頁面edit_entry.html的鏈接,為此,修改topic.html

-- snip --
    {% for entry in entries %}
      
  • {{ entry.date_added|date:"M d, Y H:i" }}

    {{ entry.text|linebreaks }}

    edit entry

  • -- snip --
    2.3.5 效果

    以下是實際效果圖:

    3. 創建用戶賬戶

    現在開始建立一個用戶注冊和身份驗證系統。為此將創建一個新的應用程序,其中包含處理用戶賬戶相關的所有功能。對Topic模型也要做稍許修改,讓每個主題都歸屬于特定用戶。

    3.1 創建應用程序users

    希望大家還記得如何使用startapp命令還創建APP:

    python manage.py startapp users

    將APP添加到settings.py中

    INSTALLED_APPS = [
        -- snip --
        "users.apps.UsersConfig",
    ]

    在APP根目錄下創建urls.py文件,并添加命名空間:

    app_name = "users"

    為APP定義URL,修改項目根目錄中的urls.py

    urlpatterns = [
        path("admin/", admin.site.urls),
        path("", include("learning_logs.urls")),
        path("users/", include("users.urls")),
    ]
    3.2 登陸頁面

    使用Django提供的默認登陸視圖,URL模式會有所不同。在users中的urls.py中添加如下代碼:

    """為應用程序users定義URL模式"""
    from django.contrib.auth.views import login
    from django.urls import path
    
    app_name = "users"
    
    urlpatterns = [
        # 登陸頁面
        path("login/", login, {"template_name": "users/login.html"}, name="login"),
    ]

    代碼中,我們使用Django自帶的login視圖函數(注意,參數是login,而不是views.login)。從之前的例子可以看出,我們渲染模板的代碼都是在自己寫的視圖函數中。但這里使用了自帶的視圖函數,無法自行編寫進行渲染的代碼。所以,我們還傳了一個字典給path,告訴Django到哪里查找我們要用到的模板。注意,該模板在users中,而不是在learning_logs中。

    3.2.1 新建模板login.html

    learning_log/users/templates/users中創建login.html

    {% extends "learning_logs/base.html" %}
    
    {% block content %}
    
      {% if form.errors %}
        

    Your username and password didn"t match. Please try again.

    {% endif %}
    {% csrf_token %} {{ form.as_p }}
    {% endblock content %}

    如果表單的errors屬性被設置,則顯示一條提示賬號密碼錯誤的信息。

    3.2.2 鏈接到登陸頁面

    base.html中添加到登陸頁面的鏈接,讓所有頁面都包含它。將這個鏈接嵌套在一個 if 標簽中,用戶已登錄時隱藏掉該鏈接:

    Learning Log - Topics {% if user.is_authenticated %} Hello, {{ user.username }} {% else %} log in {% endif %}

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

    在Django身份驗證系統中,每個模板都可使用變量user,這個變量有一個is_authenticated屬性:如果用戶已登錄,該屬性將為True,否則為False

    3.2.3 使用登陸頁面

    首先訪問localhost:8000/admin注銷超級用戶,再訪問localhost:8000/users/login/,得到如下頁面:

    3.3 注銷

    并不為注銷創建多帶帶的頁面,而是讓用戶單擊一個連接用于注銷并返回主頁。因此,需要做如下工作:注銷URL模式,新建視圖,鏈接到注銷視圖

    users/urls.py中添加與http://localhost:8000/users/logout/ 匹配的URL模式:

    -- snip --
    urlpatterns = [
        -- snip --
        path("logout/", views.logout_view, name="logout"),
    ]

    編寫視圖函數logout_view(),其中,直接調用Django自帶的logout()函數,該函數要求request作為參數:

    from django.contrib.auth import logout
    from django.http import HttpResponseRedirect
    from django.urls import reverse
    
    def logout_view(request):
        """注銷用戶"""
        logout(request)
        return HttpResponseRedirect(reverse("learning_logs:index"))

    base.html中添加注銷鏈接:

    -- snip --
      {% if user.is_authenticated %}
        Hello, {{ user.username }}
        log out
      {% else %}
        log in
      {% endif %}
    -- snip --
    3.4 注冊頁面

    使用Django提供的表單UserCreationFrom,但編寫自己的視圖函數和模板。URL->view->template->link

    首先,創建注冊頁面的URL模式,修改users/urls.py

    -- snip --
    urlpatterns = [
        -- snip --
        path("register/", views.register, name="register"),
    ]

    其次,創建視圖register()

    from django.contrib.auth import logout, authenticate, login
    from django.contrib.auth.forms import UserCreationForm
    from django.shortcuts import render
    -- snip --
    
    def register(request):
        """注冊新用戶"""
        if request.method != "POST":
            # 顯示空的注冊表單
            form = UserCreationForm()
        else:
            # 處理填寫好的表單
            form = UserCreationForm(data=request.POST)
    
            if form.is_valid():
                new_user = form.save()
                # 讓用戶自動登陸,再重定向到主頁
                # 注冊是要求輸入兩次密碼,所以有password1和password2
                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()函數實現。該函數將會為通過了身份驗證的用戶對象創建會話(session)。最后上述代碼重定向到主頁。

    然后,編寫注冊頁面的模板register.html

    {% extends "learning_logs/base.html" %}
    
    {% block content %}
      
    {% csrf_token %} {{ form.as_p }}
    {% endblock content %}

    最后,在頁面中顯示注冊鏈接,修改base.html,在用戶沒有登錄時顯示注冊鏈接:

    -- snip --
      {% if user.is_authenticated %}
        Hello, {{ user.username }}
        log out
      {% else %}
        register -
        log in
      {% endif %}
    -- snip --

    下面是實際效果:

    這是直接點register按鈕時的反饋,不過這里有點疑惑,從上面的register.html中看到,其實代碼很簡單,但這里有個浮動效果,而且在注冊模板中并沒有像前面那樣的form.errors模板標簽,但依然有未注冊成功時的反應,而且注冊的視圖函數也是自己寫的,并不是用的自帶的注冊函數,所以不知道是不是和form.as_p有關。之后再慢慢研究吧,

    4. 讓用戶擁有自己的數據

    用戶應該能夠輸入其專有的數據,所以應該創建一個系統,確定各項數據所屬的用戶,再限制對頁面的訪問,使得用戶只能使用自己的數據,即訪問控制。

    4.1 使用@login_required限制訪問

    Django提供了裝飾器@login_required,使得能輕松實現用戶只能訪問自己能訪問的頁面。

    限制對topics.html的訪問

    每個主題都歸特定用戶所有,所以需要加限制,修改learning_logs/views.py

    from django.contrib.auth.decorators import login_required
    
    @login_required
    def topics(request):
        """顯示所有的主題"""
        topics = Topic.objects.order_by("date_added")
        # 一個上下文字典,傳遞給模板
        context = {"topics": topics}
        return render(request, "learning_logs/topics.html", context)

    裝飾器也是一個函數,python在運行topics()前會先運行login_required()的代碼。

    login_required()函數檢查用戶是否登錄,僅當用戶已登錄時,Django才運行topics()函數,若未登錄,就重定向到登陸界面。而為了實現這個重定向,還需要修改項目settings.py文件,在該文件中添加這樣一個常量(其實也是變量),一般在文件末尾添加:

    -- snip --
    LOGIN_URL = "/users/login/"

    全面限制對項目“學習筆記”的訪問

    Django能輕松地限制對頁面的訪問,但得自己設計需要限制哪些頁面。一般先確定哪些頁面不需要保護,再限制對其他頁面的訪問。在該項目中,我們不限制對主頁、注冊頁面和注銷鏈接的訪問,其他頁面均限制。在learning_logs/views.py中,除了index()外,每個視圖函數都加上@login_required

    4.2 將數據關聯到用戶

    為了禁止用戶訪問其他用戶的數據,需要將用戶與數據關聯。只需要將最高層的數據關聯到用戶,這樣更低層的數據將自動關聯到用戶。下面修改Topic模型和相關視圖:

    from django.contrib.auth.models import User
    from django.db import models
    
    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)
        -- snip --

    修改模型后,還需要遷移數據庫。此時,需要將主題與用戶關聯。這里并沒有通過代碼進行關聯,我們在遷移數據庫時手動進行關聯。為此,我們需要先知道有哪些用戶,以及這些用戶的ID。我們通過Django shell查詢用戶信息,當然也可以直接查看數據庫,這里不再演示。我們將主題都關聯到超級用戶ll_admin上,它的ID是1。現在我們執行數據遷移:

    (venv)learning_logs$ python manage.py makeimgrations learning_logs
    You are trying to add a non-nullable field "owner" to topic without a default; 
    we can"t do that (the database needs something to populate existing rows).
    Please select a fix:
     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
    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
    Migrations for "learning_logs":
      learning_logsmigrations