《Python编程:从入门到实践》笔记。html
从本篇开始将是该书的最后一个项目,将用3篇文章来介绍Django的基础。完成一个“学习笔记”的小网站。python
在本篇中,咱们将:linux
不过在开始以前,请先新建一个虚拟环境并安装Django。若是没有虚拟环境,经过pip
安装的全部库都会保存到python的site-packages
目录下。开发多个项目时,都会用同一个python,而某些项目并不须要其中的全部第三方库,但若是将这些不须要库的删除,又会影响到其余项目。并且,若是A项目须要Django2.0.4,B项目须要Django2.0.0,这该怎么办?此时就须要虚拟环境。它其实就至关于一个新的文件夹,将新项目与其余项目隔离,本项目的库不与其余项目的库相关联,相似于操做系统的多用户概念。程序员
若是使用的是Python 3,可使用命令:web
python -m venv ll_env
复制代码
若是该命令不成功,多是没有安装virtualenv
模块:正则表达式
pip install virtualenv
复制代码
而后建立并激活虚拟环境:sql
virtualenv ll_env
# linux:
source ll_env/bin/activate
# windows:
ll_env\Scripts\activate
# 停用:
deactivate
复制代码
管理虚拟环境的库还有不少,有兴趣的话能够到网上搜一搜。shell
若是你使用的是新版的PyCharm,那么它在新建项目的时候默认就会建立新的虚拟环境。数据库
激活虚拟环境后就能够安装Django了:django
pip install django
复制代码
在虚拟环境中执行以下命令:
# 主要最后有个实心句号!
# 这个句点让新项目使用合适的目录结构,这样开发完成后能够轻松地将应用程序部署到服务器
django-admin startproject learning_log .
复制代码
执行上述命令后,将多出一个manage.py
文件和一个learning_log
文件夹,固然还有自己的一个ll_env
文件夹。
而在learning_log
文件夹中,又有四个文件:__init__.py
,settings.py
,urls.py
,wsgi.py
。
manage.py
是一个简单的程序,它接收命令并将其交给Django的相关部分去运行;settings.py
指定Django如何与你的系统交互以及如何管理项目,其实就是配置文件;urls.py
告诉Django应建立哪些网页来响应浏览器请求;wsgi.py
是web server gateway interface(Web服务器网关接口)的缩写,帮助Django提供它建立的文件。至于__init__.py
,它是个空文件,Python的每一个模块下必需要有这个文件。
Django将大部分与项目相关的信息都存储在数据库中,全部还须要建立一个供Django使用的数据库。依然是在虚拟环境下执行以下命令:
python manage.py migrate
复制代码
在PyCharm中的话,能够经过点击工具栏Tools中的Run manage.py Task(Ctrl+Alt+R),在弹出的命令行中直接输入原命令中manage.py
后面的部分,后面的命令也能够这样执行([appname]
是自动提示)。
"migrate"这个单词实际上是迁移的意思,并非“建立(create)”。之因此使用这个词,是由于通常将修改数据库的过程称为迁移数据库(笔者数据库学得渣,这段解释直接从书里照搬的,但愿哪位大神在留言区解释一波)。若是是刚建立的项目,而且第一次执行,将会获得以下输出:
Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
-- snip --
Applying sessions.0001_initial... OK
复制代码
从第2行结果能够看出,Django将建立和修改数据库看作是对数据库的迁移,Apply all migrations确保数据库结构与当前代码匹配(好比你修改了类的结构,添加了属性,这就至关于修改了数据表)。
执行命令后,项目的根目录下会多出一个名为db.sqlite3
的数据库文件。SQLite是一种使用单个文件的轻量级数据库,经常使用于开发简单应用程序,它让你不用太关注数据库管理的问题。
依然在项目的虚拟环境下输入以下命令:
python manage.py runserver
复制代码
获得以下输出:
Performing system checks...
System check identified no issues (0 silenced).
April 21, 2018 - 20:46:48
Django version 2.0.4, using settings 'learning_log.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
复制代码
如今在浏览器中地址栏输入localhost:8000
(或者127.0.0.1:8000
),将获得以下页面:
这是最新版的Django的默认启动界面。
Django项目由一系列应用程序组成,它们协同工做,让项目成为一个总体。咱们在项目根目录下执行以下命令,建立一个名为learning_logs的应用程序:
python manage.py startapp learning_logs
复制代码
执行命令后,根目录下会多出一个名为learning_logs
的文件夹(笔者第一次接触Django的时候发现这玩意儿竟然叫作APP,和平时用的手机上的各类APP相差也太大了,很不适应),它的结构以下:
重要的是其中的models.py
、admin.py
和views.py
文件,咱们将使用models.py
来定义咱们要在应用程序中管理的数据。另外两个文件稍后再介绍。
打开models.py
文件,发现其中自带两行代码:
from django.db import models
# Create your models here.
复制代码
在代码层面,模型就是一个类,和以前的文章中的类同样,包含属性和方法。下面建立一个“主题”类:
from django.db import models
class Topic(models.Model):
"""用户学习的主题"""
text = models.CharField(max_length=200)
date_added = models.DateTimeField(auto_now_add=True)
def __str__(self):
"""返回模型的字符串表示"""
return self.text
复制代码
Model
类是Django中的一个定义了模型基本功能的类。Topic
类只有两个属性text
和date_added
。模型中如CharField
,DateTimeField
这些字段还有不少,Django自动根据数据库的不一样调用不一样的SQL语句建立数据表,即屏蔽底层数据库的差别。
同时还重写了__str__()
方法,之因此说“重写”是由于每一个类都有这个方法,当直接将一个类A
放入print()
之类的语句中时,就会调用A
的__str__()
方法。若是没有重写这个方法,通常会输出这个对象的内存地址之类的,你们能够苏随便写个类试一试。
使用模型前,必须将APP包含到项目中,打开settings.py
文件,将APP添加到INSTALLED_APPS
中:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# 添加你的app,上面的都是自带的
"learning_logs.apps.LearningLogsConfig",
]
复制代码
这里有个须要注意的地方,因为这本书的2016年出版的,当时Django尚未到达2.0版本,因此在书中,注册APP是这样写的:
INSTALLED_APPS = [
-- snip --
# 添加你的app,上面的都是自带的
"learning_logs",
]
复制代码
如今官方文档中是按照第一种方式注册APP,而且,最新版的Django在新建APP后,在APP的目录下还多了一个apps.py
文件,该文件默认有一个根据APP名称建立的类,此处为LearningLogsConfig
,内容以下:
from django.apps import AppConfig
class LearningLogsConfig(AppConfig):
name = 'learning_logs'
复制代码
回到主线,在终端中输入:
python manage.py makemigrations learning_logs
# 输出
Migrations for 'learning_logs':
learning_logs\migrations\0001_initial.py
- Create model Topic
复制代码
makemigrations
让Django
肯定该如何修改数据库,使其可以存储与咱们定义的新模型相关联的数据。从输出能够看出,在APP目录下的migrations
文件夹中建立了一个名为0001_initial.py
的迁移文件,该文件将在数据库中为模型Topic
建立一个表。
最后,在命令行中输入:
python manage.py migrate
# 输出:
Running migrations:
Applying learning_logs.0001_initial... OK
复制代码
总结:每当须要修改模型时,都采起以下三个步骤:修改models.py
,对你的APP调用makemigrations
,让Django迁移项目migrate
。
Django自带管理后台。首先为网站建立一个超级用户。在中断输入:
python manage.py createsuperuser
复制代码
随后按提示输入用户名和密码便可,邮箱地址能够留空(直接回车)。
Django自动在管理网站中添加一些模型,如User
和Group
,但对于咱们建立的模型,必须手工注册。
注意前面提到的和models.py
在同一目录的admin.py
文件,这就是注册自行编写的模型的地方,在该文件中加入后两行代码:
# 第一行代码是自带的
from django.contrib import admin
from learning_logs.models import Topic
admin.site.register(Topic)
复制代码
如今登陆Django自带的网站管理页面 http://localhost:8000/admin/ 登陆刚才建立的超级用户和密码后将出现以下界面:
在这里你能够管理用户和组,以及和模型Topic相关的数据。
如今先手动添加两个主题:点击Add
建立Chess
和Rock Climbing
。
为使能在每一个主题下添加条目,须要定义Entry
模型,Entry
与Topic
的关系是多对一。一样是在models.py
中添加模型:
from django.db import models
class Topic(models.Model):
-- snip --
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):
"""返回模型的字符串表示"""
# 因为条目包含的文本可能很长,只显示前50个字符
return self.text[:50] + "..."
复制代码
注意其中嵌套了一个Meta
类,它用于管理模型的额外信息。它让咱们可以设置一个特殊属性,让Django在须要时使用Entries
来表示多个条目。若是没有这个类,Django将使用Entrys
来表示多个条目(保证英语语法正确......不得不说,本书做者仍是很细心的)。
添加了新模型,因此须要再次迁移数据库,过程就是前面讲的三个步骤中的后两步。而后在admin.py
中注册Entry
。
为了让这个网站有一些初试数据,添加三个条目:两个Chess
的,一个Rock Climbing
的。在管理页面中点击Entries
的Add
按钮,你将看到一个下拉列表,用于选择Topic
,还有个文本框,用于输入内容。随便输入一点内容就能够,具体内容再也不详细列出。
输入一些数据后,可经过交互式终端会话以编程方式查看这些数据。这种交互式环境称为Django shell,经常使用语测试项目和排除故障。如下是在shell中的一些操做:
(ll_env)learning_log$ python manage.py shell # 启动shell
>>> from learning_logs.models import Topic
>>> Topic.objects.all() # 得到模型Topic的全部实例
<QuerySet [<Topic: Chess>, <Topic: Rock Climbing>]> # 返回了一个查询集QuerySet
>>> topics = Topic.objects.all() # 查询每一个Topic对象
>>> for topic in topics:
... print(topic.id, topic)
...
1 Chess
2 Rock Climbing
>>> t = Topic.objects.get(id=1) # 根据id查看Chess模型的具体内容
>>> t.text
'Chess'
>>> t.date_added
datetime.datetime(2018, 4, 22, 2, 4, 3, 723045, tzinfo=<UTC>)
>>> t.entry_set.all() # 查看该主题下的全部条目,经过外键查询,进行了人为换行
<QuerySet [<Entry: The opening is the first part of the game, roughly...>,
<Entry: In the opening phase of the game, it's important t...>]>
复制代码
为了经过外键获取数据,注意查询时的语法:模型小写名称+下划线+set
,如第19行代码。编写用户可请求的网页时将使用这样的语法。若是代码在shell中的行为符合预期,那么它们在项目文件中也能正确工做。
这一部分,书中内容和新版的Django出入比较多。
Django建立网页的过程一般分三个阶段:定义URL、编写视图和编写模板。
URL模式描述了URL是如何设计的,让Django知道如何将浏览器请求与网站URL匹配,以肯定返回哪一个网页。每一个URL都被映射到特定的视图——视图函数获取并处理网页所需的数据。视图函数一般调用一个模板,后者生成浏览器可以理解的网页。
目前,基础URL(http://localhost:8000/ )返回默认的Django页面,如今修改这个映射,将其映射到咱们本身编写的主页。
打开learning_log
文件夹中的urls.py
,将看到以下内容:
from django.contrib import admin
from django.urls import path
urlpatterns = [
path('admin/', admin.site.urls),
]
# 书中的内容和实际的内容有些出入,如下是书中内容:
# 新版Django简化了URL路由写法
# from django.conf.urls import include, url
# from django.contrib import admin
#
# from django.conf.urls import include, url
# from django.contrib import admin
#
# urlpatterns = [
# url(r'^admin/', include(admin.site.urls)),
# ]
复制代码
变量urlpatterns
包含项目中的APP的URL,admin.site.urls
模块定义了可在管理网站中请求的全部URL。如今添加代码:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path("", include("learning_logs.urls")),
# 书中代码:
# url(r"", include("learning_logs.urls", namespace="learning_logs")),
]
复制代码
注意:书中在此处的include()
函数中传入了关键字参数namespace="learning_logs"
,但在新版中,命名空间(namespace) 是在APP的urls.py
中设置的:在urlpatterns
变量前新建一个值为"learning_logs"
的app_name
变量。
namespace
让learning_logs
的URL同项目中的其余URL区分开,对项目进行扩展时,这样作十分有用。
还须要在learning_logs
中建立另外一个urls.py
文件:
"""定义learning_logs的URL模式"""
from django.urls import path
# 从当前的urls.py模块所在的文件夹中导入视图
from . import views
# 该变量包含该APP中可请求的网页
urlpatterns = [
# 主页
path("", views.index, name="index"),
]
复制代码
path()
的第一个参数是正则表达式,第二个参数是要调用的视图函数(当请求的URL和第一个参数匹配时调用),第三个参数为这个URL模式指定一个名字,至关于将这个模式保存在变量index
中,之后每当须要提供这个主页的链接时都使用这个名字,而不用再编写URL。
视图函数接收请求中的信息,准备好生成网页所需的数据,再将这些数据发送给浏览器,在发送以前,还套用了网页的模板(若是有模板的话)。
当咱们在建立APP时,它的文件夹中有一个views.py
文件,该文件默认只有一个导入语句,导入了函数render()
,如今编写这个文件:
from django.shortcuts import render
def index(request):
"""学习笔记主页"""
# 将请求的数据套用到模板中,而后返回给浏览器
# 第一个参数是原始请求对象,第二是可用于建立网页的模板
return render(request, "learning_logs/index.html")
复制代码
模板定义了网页的结构,指定了网页时什么样的。每当网页被请求时,Django将填入相关的数据。模板中能访问视图提供的任何数据。
如今建立上面代码中的index.html
模板:在learning_logs
文件夹中新建一个templates
文件夹,再在这个文件夹中新建一个和APP同名的文件夹,即learning_logs
文件夹,最后,在这个learning_logs
文件夹中新建index.html
文件。看起来好像有点多余,但这是Django可以解析的目录结构。index.html
文件的内容以下:
<p>Learning Log</p>
<p>Learning Log helps you keep track of your learning,
for any topic your're learning about.</p>
复制代码
如今当你访问http://localhost:8000 时,将看到以下主页:
建立网页的过程看起来可能很复杂,实际上这就是一个简单的MVC
(Model
,View
,Controller
)模型,但在Django中被称为MVT
(Model
,View
,Template
)
这种方式使代码结构清晰,修改方便,也让咱们能各司其职,好比,数据库专家就专一于Model
,程序员专一于View
,Web
设计人员专一于Template
。
咱们继续扩展咱们的项目。建立两个用于显示数据的网页,其中给一个列出全部的主题,另外一个显示特定主题的全部条目。对于每一个网页都指定URL模式,编写一个视图函数,并编写一个模板。但这么作以前,咱们先建立一个父模板,项目中的其余模板都将继承它。
<p>
<a href="{% url 'learning_logs:index' %}">Learning Log</a>
</p>
{% block content %}{% endblock content %}
复制代码
该文件存储在index.html
所在的目录中。该文件包含全部页面都有的元素,其余模板继承自它。目前为止,全部页面共有的元素还只有顶端的标题。
咱们将这个标题设置为到主页的连接。为了建立这个连接,使用了模板标签(花括号加百分号的组合),其中learning_logs
是项目的命名空间,index就是这个项目主页的URL模式名(注意翻看前面小节的代码)。
还建立了一个块标签(block
),这个块名为content
,是一个占位符,其中包含的信息将由子模板指定。
重新编写index.html
,使其继承base.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 %}
复制代码
第1行代码导入父模板中的全部内容;extends
后面跟父模板所在的路径,虽然这俩文件都在同一目录下,但不能省略掉前面的learning_logs/
。
3到6行代码插入了一个名为content
的块标签,不是从父模板继承的内容都包含在content
块中。第6行代码指出内容定义的结束位置。
注意:在大型项目中,一般有一个用于整个网站的父模板base.html
,且网站的每一个主要部分都有一个父模板。每一个主要部分的父模板又都继承自base.html
,而网站的每一个网页都继承相应部分的父模板。
该页面显示用户建立的全部主题。他是第一个须要使用数据的页面。
在APP的urls.py
中添加能转到topics.html
的URL模式
urlpatterns = [
# 主页
path("", views.index, name="index"),
path("topics/", views.topics, name="topics")
]
复制代码
在views.py
中添加相应的函数:
from django.shortcuts import render
from .models import Topic
def index(request):
-- snip --
def topics(request):
"""显示全部的主题"""
topics = Topic.objects.order_by("date_added")
# 一个上下文字典,传递给模板
context = {"topics": topics}
return render(request, "learning_logs/topics.html", context)
复制代码
建立一个模板,用于显示全部的主题:
{% extends "learning_logs/base.html" %}
{% block content %}
<p>Topics</p>
<ul>
{% for topic in topics %}
<li>{{ topic }}</li>
{% empty %}
<li>No topics have been added yet.</li>
{% endfor %}
</ul>
{% endblock content %}
复制代码
在这个模板中,咱们使用了一个至关于for
循环的模板标签,它遍历字典context
中的列表topics
。而且注意,该模板中没有出现context
字样,至关于模板自动从context
取得topics
的内容。
模板中使用的代码与Python代码存在一些重要差异:Python使用缩进来指出哪些代码行是for
循环的组成部分,而在模板中,每一个for
循环都必须明确的指出结束为止。
要在模板中打印变量,须要将变量名用双花括号括起来,每次循环时,该代码都会被替换为topic
的当前值。
还使用了空模板标签(empty),它告诉Django在列表topics
为空时该怎么办。
这些花括号都不会出如今网页中,它们只是用于告诉Django咱们使用了一个模板变量。
修改父模板base.html
,使其包含能转到主题页面的链接(第3行为添加的代码,第2行最后添加了一个连字符):
<p>
<a href="{% url 'learning_logs:index' %}">Learning Log</a> -
<a href="{% url 'learning_logs:topics' %}">Topics</a>
</p>
复制代码
如今再次输入http://localhost:8000 将看到咱们添加的Topics
连接,点击它,将跳转到以下页面:
该页面用于显示该主题下的全部条目。和上面的步骤同样,定义URL模式,编写views.py
中的处理函数,编写网页模板。
urlpatterns = [
-- snip --
path("topics/<int:topic_id>/", views.topic, name="topic"),
]
复制代码
def topic(request, topic_id):
"""显示单个主题及其全部的条目"""
# 经过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)
复制代码
第4,6行的代码叫作查询,若是你和笔者同样是个初学者,比起代码都写完了,最后发现查询语句有问题,再来修改,那么先在Django shell中运行下代码看看结果,这样更高效。
{% 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 %}
复制代码
在Django模板中,竖线(|)表示模板过滤器(对模板变量的值进行修改的函数)。过滤器date: 'M d, Y H:i'以这样的格式显示时间戳:April 22, 2018 16:09。接下来的一行显示txt的完整值,而不只仅是entry
的前50个字符。过滤器linebreaks
将包含换行符的长条目转换成浏览器可以理解的格式,以避免显示一个不换行的文本块。
-- snip --
{% for topic in topics %}
<li>
<a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}</a>
</li>
{% empty %}
-- snip --
复制代码
如今咱们回到http://localhost:8000/topics/ 页面,随便点击一个主题,好比第一个,将获得如下界面:
上述内容主要有:
下一篇中,咱们将:
迎你们关注个人微信公众号"代码港" & 我的网站 www.vpointer.net ~