Django学习笔记

参考书籍:《Django 3 By Example Build powerful and reliable Python web applications from scratch》
《Django 基础教程 by Leif Azzopardi David Maxwell (z-lib.org)(中译版)》
参考网站:https://docs.djangoproject.com/zh-hans/5.0/

理解Django基本的运行流程

上面我们提到了,Django设计模式是”模型-视图-模板”,具体来说,它们三兄弟的天赋点各不相同:

  • 模型:定义数据的单元,位于models.py
  • 视图:实现前后端互动响应,同时生成并传递上下文字典,位于views.py
  • 模板:即前端代码,在Django前期学习中一般都是html代码,位于/project_name/template/app_name目录中(这里project_name和app_name都是指具体的项目和应用名)

这三兄弟联动,其实就可以抽象成后端-前后端交互-前端的框架,当然实际上后端还要和数据库交互,不过在Django中这不是我们想关注的细节

实例讲解

定义模型以及数据

说了这么多,我们用具体的例子来讲解一下,理解之后你会发现,其实Django前期的很多东西都是这个思路,其他诸如相对url,正则表达式,模板标签其实需要的时候problem-driven learning就可以
好,接下来我们定义两个模型,用于我们元数据的定义
@rango/models.py

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
27
28
from django.db import models
from django.template.defaultfilters import slugify
# Create your models here.
#分类模型
class Category(models.Model):
name=models.CharField(max_length=128,unique=True)
views=models.IntegerField(default=0)
likes=models.IntegerField(default=0)
slug = models.SlugField(default='',blank=True)
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(Category, self).save(*args, **kwargs)
class Meta:
verbose_name_plural='Categories'

def __str__(self):
return self.name
#ForeignKey建立一对多关系 字段需要一个 on_delete 参数,
# 这是 Django 2.0 以后的要求。on_delete
# 参数用于指定当关联的 Category 对象被删除时,应该如何处理关联的 Page 对象
class Page(models.Model):
category=models.ForeignKey(Category,models.CASCADE)
title=models.CharField(max_length=128)
url=models.URLField()
views=models.IntegerField(default=0)

def __str__(self):
return self.title

这里我们可以先把一对多,super,Meta之类可能看起来一头雾水的东西放在一边,关注数据本身,其中Category有name,views,likes,slug几种属性,Page有title,url,views几种属性,同时我们通过ForeignKey实现了一个Category对应多个Pages(但这里还没指定谁对应谁,后面会实现)

导入数据

在项目根目录,创立一个名为populate_rango.py的文件

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tango_with_django_project.settings')

import django
django.setup()
from rango.models import Category, Page

def populate():
# 首先创建一些字典,列出想添加到各分类的网页
# 然后创建一个嵌套字典,设置各分类
# 这么做看起来不易理解,但是便于迭代,方便为模型添加数据
python_pages = [
{"title": "Official Python Tutorial",
"url":"http://docs.python.org/2/tutorial/"},
{"title":"How to Think like a Computer Scientist",
"url":"http://www.greenteapress.com/thinkpython/"},
{"title":"Learn Python in 10 Minutes",
"url":"http://www.korokithakis.net/tutorials/python/"} ]
django_pages = [
{"title":"Official Django Tutorial",
"url":"https://docs.djangoproject.com/en/1.9/intro/tutorial01/"},
{"title":"Django Rocks",
"url":"http://www.djangorocks.com/"},
{"title":"How to Tango with Django",
"url":"http://www.tangowithdjango.com/"} ]
other_pages = [
{"title":"Bottle",
"url":"http://bottlepy.org/docs/dev/"},
{"title":"Flask",
"url":"http://flask.pocoo.org"} ]


cats = {
"Python": {"pages": python_pages
,"views":128,"likes":64},
"Django": {"pages": django_pages
,"views":64,"likes":32},
"Other Frameworks": {"pages": other_pages
,"views":32,"likes":6}
}

for cat, cat_data in cats.items():
c = add_cat(cat,cat_data["views"],cat_data["likes"])
for p in cat_data["pages"]:
add_page(c, p["title"], p["url"])
#打印添加的分类
for c in Category.objects.all():
for p in Page.objects.filter(category=c):
print("- {0} - {1}".format(str(c), str(p)))

def add_page(cat, title, url, views=0):
p = Page.objects.get_or_create(category=cat, title=title)[0]
p.url=url
p.views=views
p.save() #p.save():这行代码将 Page 对象保存到数据库。
return p

def add_cat(name,views,likes):
c = Category.objects.get_or_create(name=name,views=views,likes=likes)[0]
c.save()
return c

# 从这开始执行
if __name__ == '__main__':
print("Starting Rango population script...")
populate()

利用Model.object.get_or_create()函数,通过循环遍历add_cat,add_page创建,通过save函数导入数据库,在之后我们通过调用模型来调用其背后数据库中的一个个Category/Page了

前端

html如果没学的可以在网上随便找个教程,学习模式与markdown一样,大概花个一两个小时大概了解一下,边学边敲点例子就可以了,这里我们创建一个主页

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
27
28
29
30
31
32
<!DOCTYPE html>
<html>

<head>
<title>Rango</title>
</head>

<body>
<h1>Rango says...</h1>

<div>
hey there partner!
<br />
</div>

<div>
<h2>Category</h2>
{% if categories %}
<ul>
{% for category in categories %}
<li>
<a href="{% url 'show_category' category.slug %}">{{ category.name }}</a>
</li>
{% endfor %}
</ul>
{% else %}
<strong>There are no categories present.</strong>
{% endif %}
</div>

</body>
</html>

这里我们前端想呈现出各个Category的名字,把鼠标放在上面还可以跳转至某个地方(也就是该category的域名)
仔细想想,我们有哪些需要解决?

  1. 这里{}中间包着的% %是什么?
  2. 我们如何获取到category的数据?我们又是从哪里获取到代码中categories的信息的?
  3. 我们如何跳转到”url ‘show_category’ category.slug”这个地方,这里具体是指什么,category.slug是作为变量吗?用来干嘛的?
    通过回答这几个问题,我们就可以大概理解前端-前后端交互-后端的流程了

第一个问题

实际上,在Django中,Django模板系统的语言嵌入到了html中,感兴趣的可以阅读
https://docs.djangoproject.com/zh-hans/5.0/ref/templates/language/

第二个问题

既然是前端-前后端交互-后端,必然要层层递进获取数据,这里我们通过urls.py和views.py来实现前后端交互
@urls.py

1
2
3
4
5
6
7
8
9
10
11
12
from django.urls import path
from django.urls import re_path #用于正则表达式
from rango import views
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
#只要匹配到"rango/ "空字符串,就会调用views.index()视图函数
path('', views.index, name='index'),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

@views.py

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
from django.shortcuts import render
from django.http import HttpResponse
from rango.models import Category
from rango.models import Page

def index(request):
# 查询数据库,获取目前存储的所有分类
# 按点赞次数倒序排列分类
# 获取前 5 个分类(如果分类数少于 5 个,那就获取全部)
# 把分类列表放入 context_dict 字典
# 稍后传给模板引擎
category_list = Category.objects.order_by('-likes')[:5]
pages_list = Page.objects.order_by('-views')[:5]
context_dict = {'categories': category_list,
'most_liked_category':category_list.first(),
'most_viewd_pages':pages_list}
# 渲染响应,发给客户端
return render(request, 'rango/index.html', context_dict)

def about(request):
#打印请求方法,GET/POST
print(request.method)
#打印用户名
print(request.user)
return render(request, "rango/about.html",{})

def show_category(request, category_name_slug):
# 创建上下文字典,稍后传给模板渲染引擎
context_dict = {}
try:
# 能通过传入的分类别名找到对应的分类吗?
# 如果找不到,.get() 方法抛出 DoesNotExist 异常
# 因此 .get() 方法返回一个模型实例或抛出异常
category = Category.objects.get(slug=category_name_slug)
# 检索关联的所有网页
# 注意,filter() 返回一个网页对象列表或空列表
pages = Page.objects.filter(category=category)
# 把得到的列表赋值给模板上下文中名为 pages 的键
context_dict['pages'] = pages
# 也把从数据库中获取的 category 对象添加到上下文字典中
# 我们将在模板中通过这个变量确认分类是否存在
context_dict['category'] = category
except Category.DoesNotExist:
# 没找到指定的分类时执行这里
# 什么也不做
# 模板会显示消息,指明分类不存在
context_dict['category'] = None
context_dict['pages'] = None
# 渲染响应,返回给客户端
return render(request, 'rango/category.html', context_dict)

@templates
我们需要在项目目录中建一个template文件夹并配置好它
settings.py 文件的顶部有个名为 BASE_DIR 的变量,它的值是 settings.py 文件所在目录的路径。这
里用到了 Python 的特殊属性 __file__,它的值是所在文件的绝对路径。调用 os.path.dirname()
的作用是获取 settings.py 文件所在目录的绝对路径,再调用一次 os.path.dirname() 又去掉一层,
因此 BASE_DIR 最终的值是 /tango_with_django_project/。如果你还不太理解这个过
程,可以把下面几行代码放到 settings.py 文件中:
print(file)
print(os.path.dirname(file))
print(os.path.dirname(os.path.dirname(file)))
有了 BASE_DIR 之后,我们便可以轻易引用 Django 项目中的文件和目录。我们可以定义一个名为
TEMPLATE_DIR 的变量,指向 templates 目录的位置。这里还要使用 os.path.join() 函数拼接多个
路径片段。TEMPLATE_DIR 变量的定义如下:
TEMPLATE_DIR = os.path.join(BASE_DIR, ‘templates’)
我们使用 os.path.join() 函数把 BASE_DIR 变量和 ‘templates’ 字符串拼接起来,得到
/tango_with_django_project/templates/。如此一来,我们便可以使用 TEMPLATE_DIR
变量替代前面在 TEMPLATES 中硬编码的路径。把 DIRS 键值对改成下面这样:
‘DIRS’: [TEMPLATE_DIR, ]
@templates/index.html

所以,我们根据个人爱好创立并编写index.html和about.html以及category.html
当html中存在变量时,通过__views__.py中的对应函数提供上下文字典,而上下文字典从数据库中的模型中调取,这样我们便实现了前后端的数据传递

第三个问题

在 Django 的模板中,% url ‘show_category’ category.slug % 是一个 URL 模板标签,它会生成一个 URL。这个 URL 是通过查找名为 ‘show_category’ 的 URL 模式并将 ‘category.slug’ 作为参数来生成的。

show_category 是在你的 urls.py 文件中定义的 URL 名称,它对应一个视图函数,这个视图函数负责处理请求并返回一个响应。当用户点击这个链接时,浏览器会向这个 URL 发送一个 GET 请求,然后 Django 会调用与这个 URL 关联的视图函数。

category.slug 是一个变量,它是 Category 对象的一个属性。在创建models时,初始函数会根据models的name来生成它的slug并保存。在这个上下文中,category 是一个 Category 对象,slug 是这个对象的一个字段,它通常包含一个用于 URL 的简短标签。在这个例子中,category.slug 的值会被插入到生成的 URL 中。

所以, url ‘show_category’ category.slug 、
这个模板标签会生成一个链接到特定分类页面的 URL,当用户点击这个链接时,他们会被带到这个分类的页面。
具体定义如下
@rango/models.py/class category

1
2
3
4
5
6
7
8
9
10
11
12
13
class Category(models.Model):
name=models.CharField(max_length=128,unique=True)
views=models.IntegerField(default=0)
likes=models.IntegerField(default=0)
slug = models.SlugField(default='',blank=True)
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(Category, self).save(*args, **kwargs)
class Meta:
verbose_name_plural='Categories'

def __str__(self):
return self.name

@rango.urls.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from django.urls import path
from django.urls import re_path #用于正则表达式
from rango import views
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
#只要匹配到"rango/ "空字符串,就会调用views.index()视图函数
path('', views.index, name='index'),
path('about/', views.about, name='about'),
re_path(r'^category/(?P<category_name_slug>[\w\-]+)/$',
views.show_category, name='show_category'),
#(?P<category_name_slug>[\w\-]+)/$ 是一个正则表达式,
# 它会匹配 URL 中的一部分,并将其作为 category_name_slug 参数传递给 show_category 视图函数。
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

其中引入了正则表达式的概念,感兴趣的同学可以去了解一下