Django入门与实践

Django是一个功能强大的Python的高级Web框架,这是学习慕课网django入门与实践的课程笔记。
通过一个小型博客功能的实现来学习Django的使用。

简介

  • Django是一个基于Python的高级Web开发框架,它能够让开发人员进行高效且快速的开发。
  • 是高度集成的,不用自己造轮子,只需专注于网站本身的开发
  • Django免费且开源

浏览器上网基本原理

上网过程:
浏览器输入网址 –> 回车(向目标URL发送一个HTTP请求) –> 看到网页(服务器响应请求,把代码返回给浏览器解析成看到的页面

  • 本质是网络通信,即通过网络进行数据传递
  • 浏览器经过通信后获取到该页面的源代码文档(HTML等)
  • 浏览器解析文档后以适当的形式展现给用户

总之,请求响应过程就是浏览器发送HTTP请求给网站服务器,服务器通过后台代码处理请求,然后返回HTTP响应,把HTML文档等返回给浏览器,通过浏览器解析成用户看到的页面。

环境搭建

安装Python

windows下访问官网点击下载直接安装,课程里用的是Python2.7,我这里是Python3.6
安装时选上Add python.exe to PATH

安装Django

课程里用的是Django1.10.2,我这里是Django1.11.7
最简单方便的安装方式,可以指定安装版本:

1
pip install Django==2.0.2

或者使用源码安装,下载源码,进入根目录:

1
python setup.py install

具体可以点击这里查看官网的说明

使用以下命令可以查看安装的版本:

1
python -m django --version

开发工具

使用的是Pycharm,也可以根据自己习惯选择Eclipse+Pydev、Sublime Text、Atom或Visual Studio Code等。

创建项目

创建项目,了解目录下文件的作用

在想要放置项目的目录里打开cmd命令行,执行以下命令:

1
django-admin startproject project_name

django-admin startproject myblog,然后可以看到项目的目录结构:

1
2
3
4
5
6
├─manage.py
└─myblog
├─__init__.py
├─settings.py
├─urls.py
└─wsgi.py

  • manage.py:与项目进行交互的命令行工具集的入口,相当于项目管理器,执行python manage.py来查看所有命令。
    • python manage.py runserver启动自带服务器
    • python manage.py runserver 9999指定端口号
  • myblog目录:项目的一个容器,包含项目最基本的一些配置。目录名称允许修改,但不建议修改,因为许多配置文件里已经使用了这个目录名称做配置。
  • wsgi.py:WSGI(Python Web Server Gateway Interface)中文叫Python服务器网关接口。就是Python应用与Web服务器之间的接口
  • urls.py:URL配置文件,Django项目中所有的地址(页面)都需要我们自己去配置其URL
  • settings.py:项目的总配置文件,里面包含了数据库、Web应用、时间等各种配置:
    • BASE_DIR:项目的根目录
    • SECRET_KEY:项目自动生成的安全码
    • DEBUG:调试模式,生产环境中不要打开
    • ALLOWED_HOSTS:Django只允许通过这里面的地址访问网站,屏蔽其他所有地址,使其变成Bad Request (400)
    • INSTALLED_APPS:已安装应用,Django项目是由许多应用组成的,生成项目时会有一些自动生成的应用,自行创建的用用要添加到这里
    • MIDDLEWARE:中间件,Django自带的一些工具集
    • ROOT_URLCONF:URL的根文件,这里指向urls.py
    • TEMPLATES:模板的一些配置
    • WSGI_APPLICATIONwsgi.py相关,暂时不管
    • DATABASES:数据库配置
    • AUTH_PASSWORD_VALIDATORS:与密码认证有关,暂时不管
    • LANGUAGE_CODE = 'en-us'TIME_ZONE = 'UTC'USE_I18N = TrueUSE_L10N = TrueUSE_TZ = True:这五个是国际化相关的配置,语言、时区等
    • STATIC_URL:静态文件目录
  • __init__.py:Python中声明模块的文件,内容默认为空

创建应用,了解应用目录下各文件的作用

应用创建步骤:
进入manage.py同级目录,打开命令行输入:

1
python manage.py startapp app_name

python manage.py startapp blog。(注意:命名不能与Python内置模块重名,会报错。)
添加应用名到settings.py中的INSTALLED_APPS里。
目录结构:

1
2
3
4
5
6
7
8
9
10
11
├─manage.py
├─……
└─blog
├─migrations
├─__init__.py
├─__init__.py
├─admin.py
├─apps.py
├─models.py
├─tests.py
└─views.py

  • migrations目录:数据移植(迁移)模块,内容自动生成
  • admin.py:该应用的后台管理系统配置
  • apps.py:该应用的一些配置,Django1.9以后自动生成
  • models.py:数据模块,使用ORM框架,类似于MVC结构中的Models(模型)
  • tests.py:自动化测试模块,Django提供了自动化测试功能,可以在这里编写测试脚本(语句)
  • views.py:执行响应的代码所在的模块,代码逻辑处理的主要地点,项目中的大部分代码均在这里编写

创建第一个页面响应

编辑blog目录下的views.py文件:

1
2
3
4
from django.http import HttpResponse

def index(request):
return HttpResponse("Hello, World!")

  • 每个响应对应一个函数,函数必须返回一个响应
  • 函数必须存在一个参数,一般约定为request
  • 每一个响应(函数)对应一个URL

然后修改urls.py配置URL:

1
2
3
4
5
6
import blog.views as bv

urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^index/', bv.index), # 增加这一行
]

  • 每个URL都一url的形式写出来
  • url函数放在urlpatterns列表中
  • url函数三个参数:URL(正则)、对应方法、名称

然后python manage.py runserver启动服务器,浏览器访问http://localhost:8000/index/就会出现经典的Hello, World!了。

第一个Template

配置URL的第二种方法(包含其他URL配置)

修改根urls.py,引入include,url函数第二个参数改为include('blog.urls')

1
2
3
4
5
6
7
from django.conf.urls import url, include
from django.contrib import admin

urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^blog/', include('blog.urls')),
]

在应用目录blog目录下创建urls.py文件,格式与根urls.py相同:

1
2
3
4
5
6
from django.conf.urls import url
from . import views

urlpatterns = [
url(r'^index/$', views.index), # 限制index,注意加一个‘/’,否则访问“http://localhost:8000/blog/index/”会出现404
]

注意事项:

  • urls.py针对APP配置的URL名称,是该APP所有URL的总路径,即在blog.urls配置的URL前都要加上根urls.py配置的URL
  • 配置URL为空时不要直接r'',因为这样为空时可以访问,输入任意字符也可以访问,要使用r'^$'
  • 配置URL时注意正则表达式结尾符号$/

开发第一个Template

  • Django里的Templates其实就是HTML文件
  • Templates是用来Django模版语言(Django Template Language, DTL)
  • 也可以使用第三方模版(如Jinja2)

要修改模版引擎,在settings.py文件中找到TEMPLATES修改键值对'BACKEND': 'django.template.backends.django.DjangoTemplates',改为要使用的模版引擎就可以了。

步骤:

  • 在APP的根目录下创建名叫templates的目录,如myblog/blog/templates
  • 在templates目录下创建HTML文件,如index.html
1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Blog</title>
</head>
<body>
<h1>Hello, Blog!</h1>
</body>
</html>
  • views.py中返回render()
1
2
3
4
from django.shortcuts import render

def index(request):
return render(request, 'index.html')

然后访问http://localhost:8000/blog/index/就会出现index.html页面了。

DTL初步使用:

  • render()函数中支持一个dict类型参数
  • 该字典是后台传递到模版的参数,参数名
  • 在模版中用来直接使用

所以,上面的views.py可以改成:

1
2
3
4
from django.shortcuts import render

def index(request):
return render(request, 'index.html', {'hello': 'Hello, Blog!'})

然后,index.html中的<h1>可以改成:

1
<h1>{{ hello }}</h1>

注意
Django按照INSTALLED_APPS中的添加顺序查找templates,不同APP下templs目录中的同名HTML文件会造成冲突

解决templates冲突方案:
APP的templates目录下创建以APP名为名称的目录,将html文件放入新创建的目录下。所以,新建templates目录时都在该目录下新建一个与应用名同名的目录。

Models

Django中的Models:

  • 通常,一个Model对应数据库的一张表
  • Django中的Models以的形式表现
  • 它包含了一些基本字段以及数据的一些行为

ORM

  • 对象关系映射(Object Relation Mapping)
  • 实现了对象数据库之间的映射
  • 隐藏了数据访问的细节,不需要编写SQL语句

编写Models

  • 在应用根目录下创建models.py,并引入models模块
  • 创建类,继承models.Model,该类即是一张数据表
  • 在类中创建字段

字段即类里面的属性(变量),字段创建:

1
attr = models.CharField(max_length=64)

关于类的字段及可选参数等设置可以点击官网查看

生成数据表

命令行中进入manage.py同级目录执行以下命令:

1
python manage.py makemigrations app_name(可选)

不写应用名app_name默认该项目下所有应用都生成数据迁移。
执行之后可以看到:

1
2
3
Migrations for 'blog':
blog\migrations\0001_initial.py
- Create model Arcticle

之后,再执行以下命令:

1
python manage.py migrate

可以看到结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Operations to perform:
Apply all migrations: admin, auth, blog, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying blog.0001_initial... OK
Applying sessions.0001_initial... OK

上面那些authadmin等是settingsINSTALLED_APPS自带应用的数据迁移。

查看

Django会自动在app_name/migrations/目录下生成移植文件。
可以看到在blog\migrations\0001_initial.py文件里的fields有多一个:

1
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),

这是因为在创建模型时我们没有人为添加主键,所以Django为数据表创建了这个主键,如果人为添加主键就不会有这个id了。

执行以下命令查看SQL语句:

1
python manage.py sqlmigrate 应用名 文件id

如:python manage.py sqlmigrate blog 0001可以看到:

1
2
3
4
5
6
BEGIN;
--
-- Create model Arcticle
--
CREATE TABLE "blog_arcticle" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "title" varchar(32) NOT NULL, "content" text NULL);
COMMIT;

默认sqllite3的数据库在项目根目录下db.sqlite3

查看并编辑db.sqlite3使用轻量级第三方免费软件:SQLite Expert Personal
就可以在blog_article表中增加一条测试数据。

页面呈现数据

后台步骤:
views.pyimport models
获取模型累的一个具体对象:

1
article = models.Article.objects.get(pk=1)

get()的参数就是要指定的数据的标识,除了pk也可以用titlecontent。其实就相当于SELECT语句。
之后使用render()对象传递到前端:

1
render(request, page, {'article': article})

前端步骤:
模版可直接使用对象以及对象的“.”操作,如:

1
2
<h1>{{ article.title }}</h1>
<h3>{{ article.content }}</h3>

Admin

  • Admin是Django自带的一个功能强大的自动化数据管理界面
  • 被授权的用户可直接在Admin中管理数据库
  • Django提供了许多针对Admin的定制功能

创建超级用户

执行以下命令:

1
python manage.py createsuperuser

然后输入用户名、密码,邮箱可以留空。

通过http://localhost:8000/admin访问管理系统,系统默认是英文的,要改成中文可以修改settings.py中的LANGUAGE_CODE

1
LANGUAGE_CODE = 'zh-Hans'

配置应用

在应用下admin.py中引入自身的models模块(或里面的模型类),然后编辑admin.py

1
admin.site.register(models.Article)

然后Admin管理系统里就会出现Blog管理,包括Article的增删改查操作。

修改数据默认显示名称

可以看到在Article管理界面,数据默认显示名称是Arcticle object,要修改成显示文章title:

  • Article类下添加一个方法
  • 方法名:Python3:__str__(self)、Python2:__unicode__(self)
  • return self.title

完善博客

页面概要:

  • 博客主页面
  • 博客文章内容页面
  • 博客撰写页面

博客页面开发

博客主页面:

  • 文章标题列表,超链接
  • 发表博客按钮(超链接)

文章标题列表编写思路:

  • 取出数据库中所有文章对象
  • 将文章对象打包成列表,传递到前端
  • 前端页面把文章以标题超链接的形式逐个排列,模版for循环(注意这里不是两个花括号!):
    1
    2
    3
    {% for xx in xxs %}
    HTML语句
    {% endfor %}

views.pyindex()函数改为:

1
2
3
def index(request):
articles = models.Arcticle.objects.all() # 获取所有文章
return render(request, 'blog/index.html', {'articles': articles})

index.html页面内容改成:

1
2
3
4
5
6
<h1>
<a href="">新增文章</a>
</h1>
{% for article in articles %}
<a href="">{{ article.title }}</a><br/>
{% endfor %}

博客文章页面开发

页面内容:

  • 标题
  • 文章内容
  • 修改文章按钮(超链接)

views.py新增函数article_page()

1
2
3
def article_page(request, article_id):
article = models.Arcticle.objects.get(pk=article_id)
return render(request, 'blog/article_page.html', {'article': article})

新增一个templates/blog/article_page.html填如下内容:

1
2
3
4
5
<h1>{{ article.title }}</h1>
<br>
<h3>{{ article.content }}</h3>
<br><br>
<a href="">修改文章</a>

修改blog/urls.py是应用里自行新建的urls.py增加文章的URL配置:

1
2
3
4
urlpatterns = [
url(r'^index/$', views.index),
url(r'^article/(?P<article_id>[0-9]+)/$', views.article_page),
]

注意写法:把匹配到的数字以article_id作为组名,组名必须和响应函数参数名一致,否则会报错。

Django模版中的超链接配置

超链接目标地址:

  • href后面是目标地址
  • template中可以用以下语句配置:

    1
    {% url 'app_name:url_name' param %}
  • 其中app_nameurl_name都在url中配置

url()函数的名称参数:

  • urls.py,写在include()的第二个参数位置,namespace='blog'
  • 应用下则写在url()的第三个参数位置,name='article'
  • 主要取决于是否使用include引用了另一个URL配置文件

所以我们先修改根urls.py,include()的第二个参数写上namespace

1
2
3
4
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^blog/', include('blog.urls', namespace='blog')),
]

然后修改应用blog下的urls,py,在url()的第三个参数位置,写上name

1
2
3
4
urlpatterns = [
url(r'^index/$', views.index),
url(r'^article/(?P<article_id>[0-9]+)/$', views.article_page, name='article_page'),
]

index.html页面for循环里的<a>标签做些修改:

1
2
3
4
5
6
<h1>
<a href="">新增文章</a>
</h1>
{% for article in articles %}
<a href="{% url 'blog:article_page' article.id %}">{{ article.title }}</a><br/>
{% endfor %}

博客撰写页面

页面内容:

  • 标题编辑栏
  • 文章内容编辑区域
  • 提交按钮

编辑响应函数

  • 使用request.POST['参数名']获取表单数据,或者request.POST.get('title', 'TITLE')TITLE为设置的默认值
  • 使用以下语句创建对象:
    1
    models.Article.objects.create(title,content)

修改views.py添加edit_page()函数用于显示一个edit_page.html修改页面:

1
2
def edit_page(request):
return render(request, 'blog/edit_page.html')

修改views.py添加edit_action()函数:

1
2
3
4
5
6
def edit_action(request):
title = request.POST.get('title', 'TITLE')
content = request.POST.get('content', 'CONTENT')
models.Arcticle.objects.create(title=title, content=content)
articles = models.Arcticle.objects.all()
return render(request, 'blog/index.html', {'articles': articles})

修改blog/urls.py的URL配置新增修改页面的配置:

1
2
url(r'^edit/$', views.edit_page, name='edit_page'),
url(r'^edit/action/$', views.edit_action, name='edit_action'),

新建一个templates/blog/edit_page.html

1
2
3
4
5
6
7
8
9
10
11
12
<form action="{% url 'blog:edit_action' %}" method="post">
{% csrf_token %}
<label>文章标题:
<input type="text" name="title"/>
</label>
<br/><br/>
<label>文章内容:
<input type="text" name="content"/>
</label>
<br/><br/>
<input type="submit" value="提交">
</form>

出于安全性,POST请求都需要加以下语句:

1
{% csrf_token %}

否则会报错403。

我们在新建博客成功之后,会跳转到首页,可以发现浏览器里地址还是指向刚才的提交表单地址,这时候刷新页面,会多添加一条数据。将edit_action()函数的:

1
2
articles = models.Arcticle.objects.all()
return render(request, 'blog/index.html', {'articles': articles})

改成:

1
return HttpResponseRedirect('/blog/index')

防止这种情况。

接下来看修改文章链接的完善,它与新文章编辑页面的区别:

  • 新文章为空,修改文章有内容
  • 修改文章页面有文章对象
  • 这个文章对象通过文章ID指定

修改数据:

  • article.title = title
  • article.content = content
  • article().save()

修改views.pyedit_page()函数,主键ID是从1开始的,所以新建文章时传入参数0,返回空的新建文章页面,而修改时则传入文章ID,找到这篇文章并将该对象返回前端页面显示:

1
2
3
4
5
def edit_page(request, article_id):
if str(article_id) == '0':
return render(request, 'blog/edit_page.html')
article = models.Arcticle.objects.get(pk=article_id)
return render(request, 'blog/edit_page.html', {'article': article})

修改views.pyedit_action()函数实现article_id0则新建文章,为已有文章ID则修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
def edit_action(request):
title = request.POST.get('title', 'TITLE') # 也可以用request.POST['title'],这里的TITLE为默认值
content = request.POST.get('content', 'CONTENT')
article_id = request.POST.get('article_id', '0')

if article_id == '0':
models.Arcticle.objects.create(title=title, content=content)
return HttpResponseRedirect('/blog/index')
article = models.Arcticle.objects.get(pk=article_id)
article.title = title
article.content = content
article.save()
return render(request, 'blog/article_page.html', {'article': article})

修改blog/urls.py的URL配置url(r'^edit/$', views.edit_page, name='edit_page'),改为:

1
url(r'^edit/(?P<article_id>[0-9]+)/$', views.edit_page, name='edit_page'),

修改article_page.html里的<a>标签:

1
<a href="{% url 'blog:edit_page' article.id %}">修改文章</a>

修改edit_page.html的form表单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<form action="{% url 'blog:edit_action' %}" method="post">
<!--出于安全性,POST请求都需要加这句话,否则会报错403-->
{% csrf_token %}
{% if article %}
<input type="hidden" name="article_id" value="{{ article.id }}"/>
<label>文章标题:
<input type="text" name="title" value="{{ article.title }}"/>
</label>
<br/><br/>
<label>文章内容:
<input type="text" name="content" value="{{ article.content }}"/>
</label>
<br/><br/>
{% else %}
<input type="hidden" name="article_id" value="0"/>
<label>文章标题:
<input type="text" name="title"/>
</label>
<br/><br/>
<label>文章内容:
<input type="text" name="content"/>
</label>
<br/><br/>
{% endif %}
<input type="submit" value="提交">
</form>

也可以写的比较简洁:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<form action="{% url 'blog:edit_action' %}" method="post">
<!--出于安全性,POST请求都需要加这句话,否则会报错403-->
{% csrf_token %}
<input type="hidden" name="article_id" value="{% if article %}{{ article.id }}{% else %}0{% endif %}"/>
<label>文章标题:
<input type="text" name="title" value="{% if article %}{{ article.title }}{% endif %}"/>
</label>
<br/><br/>
<label>文章内容:
<input type="text" name="content" value="{% if article %}{{ article.content }}{% endif %}"/>
</label>
<br/><br/>
<input type="submit" value="提交">
</form>

要稍微注意的就是第一个用于传递article_id<input>标签里0左右不要有空格,否则会一起传值到后台产生错误。

补充

Templates过滤器

过滤器:

  • 写在模版中,属于Django模版语言
  • 可以修改模版中的变量,从而显示不同的内容

使用:

1
{{ value | filter }}

例:

1
{{ list_nums | length }}

过滤器可叠加:

1
{{ value | filter1 | filter2 |...}}

注意:如果模版中出现了不存在的变量,Django不会报错,只会给一个空值(空字符串)。

使用过滤器,前面edit_page.html的form表单可以更简单地改成:

1
2
3
4
5
6
7
8
9
10
<input type="hidden" name="article_id" value="{{ article.id | default:'0' }}"/>
<label>文章标题:
<input type="text" name="title" value="{{ article.title }}"/>
</label>
<br/><br/>
<label>文章内容:
<input type="text" name="content" value="{{ article.content }}"/>
</label>
<br/><br/>
<input type="submit" value="提交">

更多Django内建过滤器访问官方文档

Django Shell

Django Shell:

  • 它是一个Python的交互式命令行程序
  • 它自动引入了我们的项目环境
  • 我们可以使用它与我们的项目进行交互

使用:
运行以下命令:

1
python manage.py shell

如果安装了ipython,这个命令就会加载ipython解释器,否则会加载Python自带解释器。

然后可以在该环境中直接与项目交互:

1
2
from blog.models import Article
Article.objects.all()

以上命令会得到:

1
2
3
4
In [1]: from blog.models import Article

In [2]: Article.objects.all()
Out[2]: <QuerySet [<Article: 我的第一篇文章>, <Article: Title>]>

以title是因为我们之前在models.py写了__str__(self)方法设置显示title。

用途:

  • 可以使用Django Shell进行一些调试工作
  • 测试未知的(文档或网上得知的)方法或函数

如:

1
2
In [3]: Article.objects.all().values()
Out[3]: <QuerySet [{'id': 1, 'title': '我的第一篇文章', 'content': '太阳当空照,花儿对我笑,小鸟说早早早,你为什么背上炸药包?……'}, {'id': 2, 'title': 'Title', 'content': '哈哈哈哈wwww'}]>

Admin增强

我们可以自行修改Admin管理系统中的显示内容,比如除了显示文章的title,把内容等也在之后的列显示。方法如下:

创建admin配置类:

1
class ArticleAdmin(admin.ModelAdmin)

改变注册:

1
admin.site.register(Article, ArticleAdmin)

修改admin.py

1
2
3
4
5
6
7
from django.contrib import admin
from blog.models import Article

class ArticleAdmin(admin.ModelAdmin):
pass

admin.site.register(Article, ArticleAdmin)

这时候浏览器打开Admin管理系统,没报错就可以开始实现功能了。

显示其他字段:

1
list_display = ('title', 'content')

list_display属性的值是一个包含字符串格式的字段名tuplelist。建议使用tuple,因为tuple不可变,比较安全。

注意:字段名必须是字符串!而且要与Model一致不要写错。

过滤器:
和Templates过滤器没有关系。这是用于筛选数据方便管理的。

1
list_filter = ('pub_time', )  # tuple只有一个元素时记得加个逗号

更多关于Admin的功能可以查看官方文档

结语

教程到这里就结束了,代码可以点击我的GitHub查看。