Django是一个高级的Python Web框架,它支持快速开发和简洁实用的设计。这篇文章是看了Django官方文档并进行练习之后总结的笔记,主要总结入门需要了解的几个知识点:
- 使用Django创建项目。
- 路径匹配,一个请求路径是如何映射到对应的回调函数。
- Model,用于以面向对象的方式来操作数据库。
- View,接收一个Web请求,然后返回一个Web响应。
使用Django创建项目
1.准备工作
安装Python和使用MySQL数据库。
这部分可以查看之前写的文章Python入门和MySQL入门。在这里不再赘述。
不同Django版本可以使用的对应的Python版本
数据库安装(包含除MySQL外的其他数据库)
2.安装Django
先创建一个虚拟环境并切换到该虚拟环境中,这样保证将Django安装在该虚拟环境中。
代码语言:javascript复制mkvirtualenv demo_env
复制代码
安装正式发布的版本:
代码语言:javascript复制pip3 install Django
复制代码
使用以下指令可以看到下载的Django的版本:
代码语言:javascript复制python3 -m django --version
复制代码
(这里下载的是3.2.7版本的Django)
3.创建项目
cd
到你想存放代码的位置,执行以下指令:
django-admin startproject demo
复制代码
就会自动生成一些Django项目需要的代码。
django-admin
是一个Django的管理任务的命名行应用程序。
manage.py
是每一个Django项目自动创建的文件,它和django-admin
一样也是管理任务用的,但是manage.py
还会设置 DJANGO_SETTINGS_MODULE
环境变量,这个环境变量指向项目的settings.py
文件,告诉Django你需要使用哪些设置。
官网详情: django-admin and manage.py、settings。
4.设置数据库
(1)安装数据库API驱动mysqlclient
:
pip3 install mysqlclient
复制代码
(2)在数据库服务中创建名为demo
的数据库
create database demo;
复制代码
(3)通过demo.settings.py
文件中的DATABASES
设置数据库:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'demo',
'USER': 'root',
'PASSWORD': 'secret',
'HOST': '127.0.0.1',
'PORT': '3306'
}
}
复制代码
官网详情:database setup
5.运行开发服务器
cd demo
到创建的项目目录下,执行以下指令运行开发服务器:
python3 manage.py runserver
复制代码
在浏览器中打开http://127.0.0.1:8000/
能看到一个界面。
6.创建App
刚才我们执行django-admin startproject demo
创建了一个名为demo
的项目。一个项目中可能包含多个应用(App),一个应用可能在多个项目中。
在manage.py
文件路径下执行:
python3 manage.py startapp todo
复制代码
来创建一个名为todo
的应用。创建时完成后,在demo.settings
文件的 INSTALLED_APPS
中,添加todo
应用的信息,表明demo
项目中包含todo
应用:
INSTALLED_APPS = [
...
'todo.apps.TodoConfig',
]
复制代码
这个todo
应用将会实现以下这些接口:
(1)创建/更新 待办事项
(2)获取待办事项详情
(3)获取待办事项列表
路径匹配
到目前为止,项目的文件目录是这样的:
代码语言:javascript复制.
├── db.sqlite3
├── demo
│ ├── __init__.py
│ ├── __pycache__
│ │ ├── __init__.cpython-37.pyc
│ │ ├── settings.cpython-37.pyc
│ │ ├── urls.cpython-37.pyc
│ │ └── wsgi.cpython-37.pyc
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── manage.py
└── todo
├── __init__.py
├── admin.py
├── apps.py
├── migrations
│ └── __init__.py
├── models.py
├── tests.py
└── views.py
复制代码
demo/settings.py
文件中放的是项目的相关设置,settings.py
文件中设置了ROOT_URLCONF
:
ROOT_URLCONF = 'demo.urls'
复制代码
当网站的一个接口被请求的时候,Django会找到ROOT_URLCONF
设置的模块中名为urlpatterns
的变量,并且按顺序转化模式。也就是说找到某种模式的接口要执行的Python代码(一个Python函数或者一个基于类的视图(View
))。
1.不含参数的url
创建的todo
应用文件夹下,已经有views.py
文件,在views
文件夹下添加以下内容:
from django.http import HttpResponse
def temp(request):
return HttpResponse('<h1>演示url匹配用的临时代码</h1>')
复制代码
这是一个简单返回一个 HttpResponse
对象的函数。在demo.urls
的urlpatterns
列表中,添加如下内容:
from django.urls import path
from todo import views as todo_views
urlpatterns = [
path('temp/', todo_views.temp),
]
复制代码
在浏览器中访问http://127.0.0.1:8000/temp/
,就能看到返回的html
字符。
当匹配到temp/
这样的接口的时候,会调用todo_views.temp(request)
。request
是一个HttpRequest对象,包含请求相关的信息。
2.含参数的url
修改temp
函数如下:
def temp(request, **kwargs):
return HttpResponse(f'<h1>演示url匹配用的临时代码</h1><p>传递的参数{str(kwargs)}</p>')
复制代码
修改demo.urls
中的urlpatterns
如下:
from django.urls import path
from todo import views as todo_views
urlpatterns = [
path('temp/<int:temp_id>/', todo_views.temp),
]
复制代码
在浏览器中访问http://127.0.0.1:8000/temp/123/
,能看到:
图-1
<int:temp_id>
中,int
是转换器类型,表明匹配的这部分是整型。Django自带的转换器有str
(没有设置转换器时的默认类型)、int
、slug
、uuid
、path
。也可以自定义转换器。
3. 命名URL模式
在 path()
或者 re_path()
中使用name
参数能够命名URL模式,这样在单元测试的时候,就能方便地使用 reverse()
拿到对应的url。比如:
urlpatterns = [
path('temp/<int:temp_id>/', todo_views.temp, name='index'),
]
复制代码
在单元测试时,使用如下的方式测试该接口是否返回状态码200。
代码语言:javascript复制from django.urls import reverse
response = client.get(reverse('index'))
self.assertEqual(response.status_code, 200)
复制代码
4. 调整结构
目前为止我们的todo
应用的url模式都是写在项目的urls
文件中的,如果有多个应用,都挤在这个文件中就不是很清晰。在todo
文件夹下新建一个urls.py
文件,添加如下内容:
from django.urls import path
from . import views
urlpatterns = [
path('temp/<int:temp_id>/', views.temp, name='index'),
]
复制代码
修改demo
项目的urls
文件如下:
from django.urls import path, include
urlpatterns = [
path('todo/',include('todo.urls')),
]
复制代码
在浏览器中访问http://127.0.0.1:8000/todo/temp/123/
能看到 图-1 相同的页面。
5.URL名称命名空间
上文的path('temp/<int:temp_id>/', views.temp, name='index'),
中,该的URL模式的名称为index
,如果我们在别的应用中也需要使用index
作为名称,要么使用another-index
命名避免冲突,要么使用命名空间:
...
app_name = 'todo'
urlpatterns = [
path('temp/<int:temp_id>/', views.temp, name='index'),
]
复制代码
使用self.client.get(reverse('todo:index'))
。
官网详情:URL dispatcher
模型 (Model)
模型包含存储的数据的基本字段和行为,通常,一个模型对应一个数据库表。
1.创建Model
在创建模型之前,先要想好数据库的表结构。
代码语言:javascript复制from django.db import models
class Todo(models.Model):
content = models.CharField('待办事项内容', max_length=200)
remark = models.CharField('备注', max_length=500)
PRIORITY_CHOICES = [
(1, '低优先级'),
(2, '中低优先级'),
(3, '中优先级'),
(4, '中高优先级'),
(5, '高优先级'),
]
priority = models.IntegerField('优先级', choices=PRIORITY_CHOICES, default=1)
completed = models.BooleanField('是否已完成', default=False)
created_time = models.DateTimeField('创建的时间', auto_now_add=True)
modified_time = models.DateTimeField('最后一次更新的时间', auto_now=True)
复制代码
字段类型。
choices中,元组的第一个值是实际要赋给某字段的值,第二个值是便于阅读的内容。
(1) 执行以下指令,把对model
的修改存储为migration
(迁移)。
python3 manage.py makemigrations todo
复制代码
会发现在应用todo
的migrations
文件夹下面多处了一个0001_initial.py
文件,这就是存储的迁移文件。
└── todo
├── __init__.py
...
├── admin.py
├── apps.py
├── migrations
│ ├── 0001_initial.py
│ ├── __init__.py
│ └── __pycache__
├── models.py
复制代码
(2) 执行以下指令:
代码语言:javascript复制python3 manage.py sqlmigrate todo 0001
复制代码
查看对model的修改对应的SQL语句(0001_initial.py迁移文件对应的SQL):
代码语言:javascript复制python3 manage.py sqlmigrate todo 0001
--
-- Create model Todo
--
CREATE TABLE `todo_todo` (`id` bigint AUTO_INCREMENT NOT NULL PRIMARY KEY, `content` varchar(200) NOT NULL, `remark` varchar(500) NOT NULL, `priority` integer NOT NULL, `completed` bool NOT NULL, `created_time` datetime(6) NOT NULL, `modified_time` datetime(6) NOT NULL);
复制代码
可以看到在models.py
文件中我们并没有声明id
字段,但是对应的SQL创建了id字段作为自增主键。也可以自己通过设置 primary_key=True
来设置某个字段作为主键。
从CREATE TABLE todo_todo
中可以看出,Django将应用名todo
和model
名称的小写结合起来作为表的名称,如果要自定义对应的表的名称,需要使用Meta选项中的db_table
属性:
class Todo(models.Model):
content = models.CharField('待办事项内容', max_length=200)
...
modified_time = models.DateTimeField('最后一次更新的时间', auto_now=True)
class Meta:
db_table = 'todo'
复制代码
这样修改后再执行一遍python3 manage.py makemigrations todo
,新的修改被存储为了0002_alter_todo_table.py
文件,执行python3 manage.py sqlmigrate todo 0002
,就能看到如下内容:
--
-- Rename table for todo to todo
--
RENAME TABLE `todo_todo` TO `todo`;
复制代码
(3) 执行migrate
进行迁移,也就是将代码中对数据库的表述,应用到实际的数据库上:
python3 manage.py migrate
复制代码
到数据库中查看,就会发现已经新创建了一张todo
表。
2. 添加数据
当创建了数据模型之后,Django会自动给到一个数据库抽象的API,用于进行数据的增删改查。一个模型类表示一个数据库表,一个模型类实例代表一个数据库表中的记录。
使用指令python3 manage.py shell
调起Python命令行,在Python命令行执行代码查看效果。
(1) 可以通过实例化一个模型类,然后调用 save()
将数据保存到数据库中:
>>> from todo.models import Todo
>>> todo = Todo(content='第一件事就是写文', remark='写文使人快乐', priority=3)
>>> todo.save()
复制代码
在MySQL中查找数据:
代码语言:javascript复制use demo;
select * from todo limit 20;
复制代码
发现已经出现了一条数据:
图-2
(2) 使用objects
的 create()
方法。
这里先简单了解一下:为了从数据库中获取对象,需要使用模型类的一个 Manager
构造 QuerySet
,一个 QuerySet
代表从数据库的一个对象的集合。每一个模型至少有一个Manager,并且默认情况下,它被称为objects
。
>>> from todo.models import Todo
>>> Todo.objects.create(content='第二件事就是打游戏', remark='打游戏同样使人快乐', priority=3)
<Todo: Todo object (2)>
复制代码
在MySQL中使用select * from todo limit 20;
查找数据,得到:
图-3
3.查找数据
(1) 使用 all()
方法查找所有的数据:
>>> Todo.objects.all()
<QuerySet [<Todo: Todo object (1)>, <Todo: Todo object (2)>]>
复制代码
这里发现打印出来的内容不大直观,给模型类添加__str__
方法:
class Todo(models.Model):
content = models.CharField('待办事项内容', max_length=200)
...
def __str__(self):
return self.content
复制代码
Ctrl D
退出Python命令行,再执行python3 manage.py shell
重新调起,重新执行代码:
>>> from todo.models import Todo
>>> Todo.objects.all()
<QuerySet [<Todo: 第一件事就是写文>, <Todo: 第二件事就是打游戏>]>
复制代码
(2) 使用filter返回包含匹配查询参数的对象的结果集:
代码语言:javascript复制>>> Todo.objects.filter(created_time__year=2021)
<QuerySet [<Todo: 第一件事就是写文>, <Todo: 第二件事就是打游戏>]>
复制代码
查找创建时间为2021年的数据。
(3) 使用exclude返回一个不包含给定查询参数的结果集:
代码语言:javascript复制>>> Todo.objects.exclude(created_time__year=2021)
<QuerySet []>
复制代码
查找创建时间不是2021年的数据。
(4) 使用 get()
方法返回匹配到的唯一的一个对象。
>>> Todo.objects.get(pk=1)
<Todo: 第一件事就是写文>
复制代码
查找主键为1的数据。
(5) 限制查询结果集
代码语言:javascript复制>>> Todo.objects.all()[1:5]
<QuerySet [<Todo: 第二件事就是打游戏>]>
复制代码
等同于OFFSET 1 LIMIT 5
,返回从偏移位置1开始的前5条数据。
官网详情:字段查找
4.更新数据
(1) 更新一个对象,使用save()
>>> todo = Todo.objects.get(pk=1)
>>> todo.completed = True
>>> todo.save()
复制代码
图-4
(2) 更新一个或者多个对象,使用update()
update
是QuerySet
的方法,只有结果集有update
方法,因为get()
拿到的是一个对象,所以是没有update
方法的。
>>> Todo.objects.filter(pk=2).update(completed=True)
1
复制代码
update
返回的是匹配的行的数量。
图-5
5.删除数据
使用delete()删除QuerySet
的所有行,返回的是删除的行的数量,以及包含删除的每个对象类型的数量信息的一个字典。
Todo.objects.filter(pk=2).delete()
(1, {'todo.Todo': 1})
复制代码
图-6
官网详情:字段类型、Model实例、QuerySet API、查询、数据模型
视图 (View)
视图是一个Python函数,它接收一个Web请求,然后返回一个Web响应。这个响应可能是一个Web页面的HTML内容、一个重定向、一个404错误、或者一个XML文档,一个图片等。请求和响应的细节,可以看官网文档:Request and Response objects。
可以使用templates(模板)动态生成HTML作为响应返回,但因为实际工作中前后端分离,基本上不会用到模板,所以这个练习中只是实现接口,在Postman中观察效果,不实现界面交互。
View 函数
上文的练习中我们已经写过如下代码:
代码语言:javascript复制from django.http import HttpResponse
def temp(request, **kwargs):
return HttpResponse(f'<h1>演示url匹配用的临时代码</h1><p>传递的参数{str(kwargs)}</p>')
复制代码
url模式是这样写的:
代码语言:javascript复制urlpatterns = [
path('temp/<int:temp_id>/', views.temp, name='index'),
]
复制代码
views.temp
是url匹配上之后要调用的函数。
不论是什么方法的请求(GET、POST、PUT...),只要匹配到模式temp/<int:temp_id>/
,都会调用views.temp
函数,所以需要在方法内部进行一些处理,比如:
def temp(request, **kwargs):
if request.method == 'GET':
# GET 请求的处理
elif request.method == 'POST':
# POST 请求的处理
复制代码
我们可以使用基于类的视图,基于类的视图会做好不同方法的组织,而不用我们手动写if ... elif
。
基于类的View
所有的基于类的View都继承自 View
。
class Temp(View):
def get(self, request, **kwargs):
return HttpResponse(f'<p>get请求,{str(kwargs)}</p>')
def post(self, request, **kwargs):
return HttpResponse(f'post请求,{str(kwargs)}')
复制代码
这里的POST请求不能直接返回HttpResponse
,否则会报错CSRF cookie not set
。使用csrf_exempt装饰器将接口设置为不受CSRF保护(仅为了简化练习这样设置)。
todo.urls
中的内容修改为:
from django.urls import path
from django.views.decorators.csrf import csrf_exempt
from .views import Temp
app_name = 'todo'
urlpatterns = [
path('temp/<int:temp_id>/', csrf_exempt(Temp.as_view()), name='index'),
]
复制代码
基于类的View包含一个as_view()
方法,返回匹配到url的时候要调用的回调函数。
在Postman中用GET请求访问http://127.0.0.1:8000/todo/temp/123/
,得到:
图-7
用POST请求访问http://127.0.0.1:8000/todo/temp/123/
,得到:
图-8
实现清单列表的接口
用基于类的View实现以下接口:
(1)创建/更新 待办事项
(2)获取待办事项详情
(3)获取待办事项列表
代码语言:javascript复制from django.core import serializers
from django.http import HttpResponse, JsonResponse, QueryDict
from django.views import View
from .models import Todo
import json
class TodoView(View):
'''清单
post:创建清单;put:更新清单;get:获取清单详情
'''
def format_request_data(self, request_data):
'''整理请求传递的数据'''
todo_data = {}
todo_data['content'] = request_data.get('content', '')
todo_data['remark'] = request_data.get('remark', '')
todo_data['priority'] = int(request_data.get('priority', 1))
return todo_data
def get(self, request, pk):
try:
todo = Todo.objects.filter(pk=pk)
todo_list_str = serializers.serialize('json', todo) # 将QuerySet序列化为JSON数据
todo_list = json.loads(todo_list_str)
todo_data = todo_list[0]['fields']
return JsonResponse(todo_data, status=200)
except Exception as e:
return HttpResponse(f'获取清单失败,{e.__str__()}', status=400)
def post(self, request):
try:
data = QueryDict(request.body)
todo_data = self.format_request_data(data)
todo = Todo(**todo_data)
todo.save()
return HttpResponse('创建清单成功', status=201)
except Exception as e:
return HttpResponse(f'创建清单失败,{e.__str__()}', status=400)
def put(self, request, pk):
try:
data = QueryDict(request.body)
todo_data = self.format_request_data(data)
Todo.objects.filter(pk=pk).update(**todo_data)
return HttpResponse('更新清单成功', status=200)
except Exception as e:
return HttpResponse(f'更新清单失败,{e.__str__()}', status=400)
class TodoListView(View):
'''清单列表
get:获取清单列表
'''
def get(self, request):
try:
todo_list = Todo.objects.all()
todo_list_str = serializers.serialize('json', todo_list) # 将QuerySet序列化为JSON数据
todo_list_data = json.loads(todo_list_str)
res_data = []
for todo in todo_list_data:
todo_data = { **todo.get('fields') }
todo_data.update(id=todo.get('pk'))
res_data.append(todo_data)
return JsonResponse(res_data, safe=False, status=200) # 设置safe为False是为了返回非字典类型的数据
except Exception as e:
return HttpResponse(f'获取清单列表失败,{e.__str__()}', status=400)
复制代码
修改todo/urls.py
文件如下:
from django.urls import path
from django.views.decorators.csrf import csrf_exempt
from .views import TodoView, TodoListView
app_name = 'todo'
urlpatterns = [
path('create/', csrf_exempt(TodoView.as_view()), name='create'),
path('edit/<int:pk>/', csrf_exempt(TodoView.as_view()), name='edit'),
path('<int:pk>/', csrf_exempt(TodoView.as_view()), name='detail'),
path('list/', csrf_exempt(TodoListView.as_view()), name='list'),
]
复制代码
测试接口:http://127.0.0.1:8000/todo/create/
、http://127.0.0.1:8000/todo/edit/1/
、http://127.0.0.1:8000/todo/1/
、http://127.0.0.1:8000/todo/list/
。
在Postman中请求接口,在MySQL数据库中查看数据是否正确。这里的练习实现的是最简化的内容,参考即可,可以自行对代码进行完善。
简单地使用generic.ListView
实现列表接口(和使用django.views.View
实现的区别不大):
from django.core import serializers
from django.views import generic
from django.http import JsonResponse
from .models import Todo
import json
class TodoListView(generic.ListView):
model = Todo
def render_to_response(self, context):
todo_list = context.get('object_list')
todo_list_str = serializers.serialize('json', todo_list) # 将QuerySet序列化为JSON数据
todo_list_data = json.loads(todo_list_str)
res_data = []
for todo in todo_list_data:
todo_data = { **todo.get('fields') }
todo_data.update(id=todo.get('pk'))
res_data.append(todo_data)
return JsonResponse(res_data, safe=False, status=200) # 设置safe为False是为了返回非字典类型的数据
复制代码
官网详情:Built-in class-based views API、CSRF、便捷函数、序列化Django对象