跟書《python編程:從入門到實踐》,學習用Django編寫名爲「學習筆記」的Web應用程序。html
項目路徑:learning_logpython
此處省略,參考這裏:http://www.javashuo.com/article/p-qplknasp-db.htmlweb
# cd /opt/# mkdir learning_log && cd learning_log# python -m venv 11_env
# source ll_env/bin/activate
進入虛擬環境終端。正則表達式
要中止使用虛擬環境,可執行:sql
# deactivate
# pip3 install Django
Django僅在虛擬環境處於活動狀態時纔可用。shell
下面與書中不一樣部分是因爲Django版本更新致使,照着操做便可。本人Django版本爲2.2.3,可經過命令
python -m django --version
查看Django版本。數據庫
# django-admin.py startproject learning_log .# lslearning_log ll_env manage.py# ls learning_log/__init__.py settings.py urls.py wsgi.py
Django新建了一個名爲learning_log
的目錄。它還建立了一個名爲manage.py
的文件,這是一個簡單的程序,它接受命令並將其交給Django的相關部分去運行。django
目錄learning_log
包含4個文件,其中最重要的是settings.py
、urls.py
和wsgi.py
。編程
文件settings.py指定Django如何與你的系統交互以及如何管理項目。
文件urls.py告訴Django應建立哪些網頁來響應瀏覽器請求。
文件wsgi.py幫助Django提供它建立的文件,這個文件名是web server gateway interface(Web服務器網關接口)的首字母縮寫。vim
# python manage.py migrate
報錯:
django.core.exceptions.ImproperlyConfigured: SQLite 3.8.3 or later is required (found 3.7.17).
查看系統的sqlite3版本:
# sqlite3 --version3.7.17 2013-05-20 00:56:22 118a3b35693b134d56ebd780123b7fd6f1497668
系統自帶的sqlite3版本比較低,須要更新版本。
#下載安裝# cd /software# wget https://www.sqlite.org/2019/sqlite-autoconf-3270200.tar.gz# tar -zxf sqlite-autoconf-3270200.tar.gz# cd sqlite-autoconf-3270200# ./configure --prefix=/usr/local/# make && make install
#更換版本# find /usr -name sqlite3/usr/bin/sqlite3 /usr/lib64/python2.7/sqlite3 /usr/local/bin/sqlite3 /usr/python/lib/python3.7/sqlite3# /usr/local/bin/sqlite3 --version3.27.2 2019-02-25 16:06:06 bd49a8271d650fa89e446b42e513b595a717b9212c91dd384aab871fc1d0f6d7# mv /usr/bin/sqlite3 /usr/bin/sqlite3.bak# ln -s /usr/local/bin/sqlite3 /usr/bin/sqlite3# sqlite3 --version3.27.2 2019-02-25 16:06:06 bd49a8271d650fa89e446b42e513b595a717b9212c91dd384aab871fc1d0f6d7
#路徑傳遞給共享庫# echo 'export LD_LIBRARY_PATH="/usr/local/lib"' >> ~/.bashrc# source !$# cd /opt/learning_log/# rm -rf /software/sqlite-autoconf-3270200*
# python manage.py migrate# lsdb.sqlite3 learning_log ll_env manage.py
對於虛擬機環境,建議指定本機IP和端口,不然默認是127.0.0.1:8000
,瀏覽器會沒法訪問。
# python manage.py runserver 192.168.30.128:8000# vim learning_log/settings.pyALLOWED_HOSTS = ['192.168.30.128'] #容許主機中,添加本機IP或*(任意主機)
Django啓動一個服務器,讓你可以查看系統中的項目,瞭解它們的工做狀況。當你在瀏覽器中輸入URL以請求網頁時,該Django服務器將進行響應:生成合適的網頁,並將其發送給瀏覽器。
打開瀏覽器,輸入192.168.30.128:8000
訪問Django項目,以下圖:
應用路徑:learning_logs
Django項目由一系列應用程序組成,它們協同工做,讓項目成爲一個總體。咱們暫時只建立一個應用程序,它將完成項目的大部分工做。
虛擬機再打開一個終端
# cd /opt/learning_log/# source ll_env/bin/activate
# python manage.py startapp learning_logs# lsdb.sqlite3 learning_log learning_logs ll_env manage.py# ls learning_logs/admin.py apps.py __init__.py migrations models.py tests.py views.py
上面新增了learning_logs
目錄,其中models.py
用來定義咱們要在應用程序中管理的數據。
# vim learning_logs/models.py
from django.db import modelsclass Topic(models.Model): """用戶學習的主題""" text = models.CharField(max_length=200) date_added = models.DateTimeField(auto_now_add=True) def __str__(self): """返回模型的字符串表示""" return self.text
建立了一個名爲Topic的類,它繼承了Model——Django中一個定義了模型基本功能的類。Topic類只有兩個屬性:text
和date_added
。
屬性text
是一個CharField——由字符或文本組成的數據,須要存儲少許的文本,如名稱、標題或城市時,可以使用CharField。定義CharField屬性時,須要告訴Django該在數據庫中預留多少空間,這裏設置爲200個字符。
屬性date_added
是一個DateTimeField——記錄日期和時間的數據。傳遞了實參auto_add_now=True
,每當用戶建立新主題時,這都讓Django將這個屬性自動設置成當前日期和時間。
要使用模型,必須讓Django將應用程序包含到項目中。
# 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', #增長該行]
還須要讓Django修改數據庫,使其可以存儲於模型Topic相關的信息。
# python manage.py makemigrations learning_logs# python manage.py migrate
在這裏,Django確認爲learning_logs 應用遷移時一切OK。
每當須要修改項目管理的數據時,都採起以下三個步驟:
models.py
;learning_logs
調用makemigrations
;# python manage.py createsuperuserUsername (leave blank to use 'root'): ll_admin #自定義用戶名Email address: #可爲空Password: #自定義密碼,不小於8位Password (again): Superuser created successfully.
# vim learning_logs/admin.py
from django.contrib import adminfrom learning_logs.models import Topic admin.site.register(Topic)
導入咱們要註冊的模型Topic,讓Django經過管理網站管理咱們的模型。使用超級用戶訪問管理網站:192.168.30.128:8000/admin/
。
要記錄學到的Chess和Rock Climbing知識,須要爲用戶可在「學習筆記」中添加的條目定義模型。每一個條目都與特定主題相關聯,這種關係被稱爲多對一關係,即多個條目可關聯到同一個主題。
# vim learning_logs/models.py
from django.db import modelsclass Topic(models.Model): """用戶學習的主題""" text = models.CharField(max_length=200) date_added = models.DateTimeField(auto_now_add=True) def __str__(self): """返回模型的字符串表示""" return self.textclass Entry(models.Model): """學到的有關某個主題的具體知識""" topic = models.ForeignKey(Topic) text = models.TextField() date_added = models.DateTimeField(auto_now_add=True) class Meta: verbose_name_plural = 'entries' def __str__(self): """返回模型的字符串表示""" return self.text[:50] + "..."
像Topic同樣,Entry也繼承了Django基類Model。屬性topic
是一個ForeignKey實例,外鍵引用了數據庫中的另外一條記錄,這些代碼將每一個條目關聯到特定的主題。每一個主題建立時,都給它分配了一個鍵(ID),在兩項數據之間須要創建聯繫時,Django使用與每項信息相關聯的鍵。
屬性text
是一個TextField實例,這個字段不須要限制長度。屬性date_added
可以按建立順序呈現條目,且在每一個條目旁邊放置時間戳。
在Entry類中嵌套Meta類,Meta類存儲用於管理模型的額外信息,使用一個特殊屬性讓Django在須要時使用Entries來表示多個條目;若是沒有這個類,Django將使用Entrys來表示多個條目。方法__str__()
告訴Django在呈現條目時應顯示text的前50個字符,超出則用...
顯示。
# python manage.py makemigrations learning_logs
報錯:
TypeError: __init__() missing 1 required positional argument: 'on_delete'
解決——修改models.py:
from django.db import modelsclass Topic(models.Model): """用戶學習的主題""" text = models.CharField(max_length=200) date_added = models.DateTimeField(auto_now_add=True) def __str__(self): """返回模型的字符串表示""" return self.textclass 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] + "..."
models.CASCADE
這個參數在老版本中是默認值。
on_delete有CASCADE、PROTECT、SET_NULL、SET_DEFAULT、SET()五個可選擇的值:
CASCADE:此值設置,是級聯刪除;
PROTECT:此值設置,是會報完整性錯誤;
SET_NULL:此值設置,會把外鍵設置爲null,前提是容許爲null;
SET_DEFAULT:此值設置,會把設置爲外鍵的默認值;
SET():此值設置,會調用外面的值,能夠是一個函數。
再次遷移模型Entry:
# python manage.py makemigrations learning_logs# python manage.py migrate
# vim learning_logs/admin.py
from django.contrib import adminfrom learning_logs.models import Topic, Entry admin.site.register(Topic)admin.site.register(Entry)
下拉選擇對應的Topic,Text添加任意內容
繼續添加主題Chess的條目,而後再添加主題Rock Climbing的條目
Django shell用於測試項目及排除項目故障,要退出shell會話,可按Ctr + D。
# python manage.py shell>>> from learning_logs.models import Topic>>> Topic.objects.all()<QuerySet [<Topic: Chess>, <Topic: Rock Climbing>]>
在活動的虛擬環境中執行時,命令python manage.py shell
啓動一個Python解釋器,可以使用它來探索存儲在項目數據庫中的數據。
在這裏,咱們導入了模塊learning_logs.models
中的模型Topic,而後使用方法Topic.objects.all()
來獲取模型Topic的全部實例;它返回的是一個列表,稱爲查詢集(queryset)。
>>> topics = Topic.objects.all()>>> for topic in topics:... print(topic.id, topic)... 1 Chess 2 Rock Climbing
能夠看到,主題Chess的ID爲1,而Rock Climbing的ID爲2。
知道對象的ID後,就能夠獲取該對象並查看其任何屬性。
>>> t = Topic.objects.get(id=1)>>> t.text'Chess'>>> t.date_added datetime.datetime(2019, 7, 9, 2, 5, 18, 289746, tzinfo=<UTC>)
>>> t.entry_set.all()<QuerySet [<Entry: The opening is thefirst part ofthe game, roughly t...>, <Entry: In the opening phase ofthe game, it's important to...>]>>>> t2 = Topic.objects.get(id=2)>>> t2.entry_set.all()<QuerySet [<Entry: One ofthe most importantconcepts in climbing is to...>]>
使用Django建立網頁的過程一般分三個階段:定義URL、編寫視圖和編寫模板。
首先,你必須定義URL模式。URL模式描述了URL是如何設計的,讓Django知道如何將瀏覽器請求與網站URL匹配,以肯定返回哪一個網頁。每一個URL都被映射到特定的視圖——視圖函數獲取並處理網頁所需的數據。視圖函數一般調用一個模板,後者生成瀏覽器可以理解的網頁。
用戶經過在瀏覽器中輸入URL以及單擊連接來請求網頁,所以咱們須要肯定項目須要哪些URL。主頁的URL最重要,它是用戶用來訪問項目的基礎URL。
# vim learning_log/urls.py
from django.contrib import adminfrom django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('', include('learning_logs.urls', namespace='learning_logs')),]
這裏保存完文件以後會報ModuleNotFoundError: No module named 'learning_logs.urls'
錯誤,不用理會,繼續往下作。
在文件夾learning_logs中建立另外一個urls.py文件
# vim learning_logs/urls.py
"""定義learning_logs的URL模式"""from django.urls import pathfrom . import views app_name='learning_logs'urlpatterns = [ # 主頁 path('', views.index, name='index'),]
視圖函數接受請求中的信息,準備好生成網頁所需的數據,再將這些數據發送給瀏覽器——這一般是使用定義了網頁是什麼樣的模板實現的。
# vim learning_logs/views.py
from django.shortcuts import renderfrom .models import Topicdef index(request): """學習筆記的主頁""" return render(request, 'learning_logs/index.html')
導入了函數render()
,它根據視圖提供的數據渲染響應。URL請求與剛纔定義的模式匹配時,Django將在文件views.py中查找函數index()
,再將請求對象傳遞給這個視圖函數。
模板定義了網頁的結構。模板指定了網頁是什麼樣的,而每當網頁被請求時,Django將填入相關的數據。模板讓你可以訪問視圖提供的任何數據。
# mkdir -p learning_logs/templates/learning_logs# vim learning_logs/templates/learning_logs/index.html
<p>Learning Logp><p>Learning Log helps you keep track of your learning, for any topic you're learning about.p>
標籤
標識段落;標籤
指出了段落的開頭位置,而標籤
指出了段落的結束位置。
訪問基礎URL——192.168.30.128:8000
,能夠看到剛剛定義的模板index.html。
在建立完主頁以後,繼續建立兩個顯示數據的網頁,其中一個列出全部的主題,另外一個顯示特定主題的全部條目。對於每一個網頁,咱們都將指定URL模式,編寫一個視圖函數,並編寫一個模板。
建立網站時,幾乎都有一些全部網頁都將包含的元素。在這種狀況下,可編寫一個包含通用元素的父模板,並讓每一個網頁都繼承這個模板。
# vim learning_logs/templates/learning_logs/base.html
<p> <a href="{% url 'learning_logs:index' %}">Learning Loga>p>{% block content %}{% endblock content %}
第一部分建立一個包含項目名的段落,該段落也是一個到主頁的連接,爲建立連接,咱們使用了一個模板標籤。模板標籤
{% url 'learning_logs:index' %}
生成一個URL,該URL與learning_logs/urls.py
中定義的名爲index的URL模式匹配。第二部分插入了一對塊標籤。這個塊名爲content,是一個佔位符,其中包含的信息將由子模板指定。
# vim learning_logs/templates/learning_logs/index.html
{% extends "learning_logs/base.html" %} {% block content %} <p>Learning Log helps you keep track of your learning, for any topic you're learning about.p>{% endblock content %}
子模板的第一行必須包含標籤
{% extends %}
,讓Django知道它繼承了哪一個父模板。而後插入了一個名爲content 的
{% block %}
標籤,以定義content塊,不是從父模板繼承的內容都包含在content塊中。
定義顯示全部主題的頁面的URL。
# vim learning_logs/urls.py
"""定義learning_logs的URL模式"""from django.urls import pathfrom . import views app_name='learning_logs'urlpatterns = [ # 主頁 path('', views.index, name='index'), # 顯示全部的主題 path('topics/', views.topics, name='topics'),]
只在用於主頁URL的正則表達式中添加了topics/
,Django檢查請求的URL時,這個模式與這樣的URL匹配:基礎URL後面跟着topics。其URL與該模式匹配的請求都將交給views.py中的函數topics()
進行處理。
函數topics()
須要從數據庫中獲取一些數據,並將其發送給模板。
# vim learning_logs/views.py
from django.shortcuts import renderfrom .models import Topicdef 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)
函數topics() 包含一個形參:Django從服務器那裏收到的request對象。而後查詢數據庫——請求提供Topic對象,並按屬性date_added
對它們進行排序。咱們將返回的查詢集存儲在topics中。
接着定義了一個將要發送給模板的上下文。上下文是一個字典,其中的鍵是咱們將在模板中用來訪問數據的名稱,而值是咱們要發送給模板的數據。在這裏,只有一個鍵—值對,它包含咱們將在網頁中顯示的一組主題。建立使用數據的網頁時,除對象request和模板的路徑外,咱們還將變量context傳遞給render()
。
顯示全部主題的頁面的模板接受字典context,以便可以使用topics()
提供的數據。
# vim learning_logs/templates/learning_logs/topics.html
{% extends "learning_logs/base.html" %} {% block content %} <p>Topicsp> <ul> {% for topic in topics %} <li>{{ topic }}li> {% empty %} <li>No topics have been added yet.li> {% endfor %} ul> {% endblock content %}
首先使用標籤
表示。{% extends %}
來繼承base.html,接着開始定義content塊。在標準HTML中,項目列表被稱爲無序列表,用標籤而後使用一個至關於for循環的模板標籤,它遍歷字典context中的列表topics。在模板中,每一個for循環都必須使用
{% endfor %}
標籤來顯式地指出其結束位置。在循環中,要將每一個主題轉換爲一個項目列表項。要在模板中打印變量,須要將變量名用雙花括號括起來。HTML標籤
表示一個項目列表項,在標籤對
內部,位於標籤
和之間的內容都是一個項目列表項。
修改父模板,使其包含到顯示全部主題的頁面的連接:
# vim learning_logs/templates/learning_logs/base.html
<p> <a href="{% url 'learning_logs:index' %}">Learning Loga> - <a href="{% url 'learning_logs:topics' %}">Topicsa>p>{% block content %}{% endblock content %}
在到主頁的連接後面添加了一個連字符
-
,而後添加了一個到顯示全部主題的頁面的連接,使用的也是模板標籤url。
訪問基礎URL——192.168.30.128:8000
,能夠看到定義的模板index.html及Topics連接。
點擊Topics連接,查看主題
接下來,重複以前步驟,建立一個專一於特定主題的頁面——顯示該主題的名稱及該主題的全部條目。一樣的,咱們將定義一個新的URL模式,編寫一個視圖並建立一個模板。
顯示特定主題的頁面的URL模式與前面的全部URL模式都稍有不一樣,由於它將使用主題的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'),]
這裏用到了python正則表達式,/(?P
與包含在兩個斜槓內的整數匹配,並將這個整數存儲在一個名爲topic_id
的實參中。?P
將匹配的值存儲到topic_id
中;而表達式\d+
與包含在兩個斜杆內的任何數字都匹配,無論這個數字爲多少位。
發現URL與這個模式匹配時,Django將調用視圖函數topic()
,並將存儲在topic_id
中的值做爲實參傳遞給它。
# vim learning_logs/views.py
from django.shortcuts import renderfrom .models import Topicdef 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)
第一個除request對象外還包含另外一個形參的視圖函數。這個函數接受正則表達式(?P
捕獲的值,並將其存儲到topic_id
中。
使用get()
來獲取指定的主題,獲取與該主題相關聯的條目,並將它們按date_added
排序:date_added
前面的減號指定按降序排列,即先顯示最近的條目。將主題和條目都存儲在字典context中,再將這個字典發送給模板topic.html。
# vim learning_logs/templates/learning_logs/topic.html
{% extends 'learning_logs/base.html' %} {% block content %} <p>Topic: {{ topic }}p> <p>Entries: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 %}
將顯示示全部主題的頁面中的每一個主題都設置爲連接
# 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>{% endblock content %}
使用模板標籤url 根據learning_logs中名爲topic的URL模式來生成合適的連接。這個URL模式要求提供實參
topic_id
,所以在模板標籤url中添加了屬性topic.id
。