如何学习框架
Django文档
Django开发包
# Django
pip install django=1.11.11
# djangorestframework
pip install djangorestframework
# PyMySQL
pip install PyMySQL
# redis
pip install django-redis
# 图片相关
pip install pillow
通过bash搭建
mkvirtualenv django_py3_1.11 --python=/usr/bin/python3 # 创建独立环境
pip install django==1.11.11 # 安装django包
django-admin startproject hello_world # 创建工程
python manage.py startapp users # 创建应用
# users/apps.py
class UserConfig(AppConfig):
name = 'users' # 用于注册应用
verbose_name = '用户管理' # 用于admin站点的管理
Django工程的启动
python manage.py runserver ip:端口
Django工程目录结构调整
- celery_tasks # 异步任务
- front_end_pc # 前端资源
- django_project # 后端工程
- docs # 文档
- logs # 日志
- django_project # 与项目同名子应用
- apps # 项目应用
- users
- libs # 第三方
- settings # 应用配置
- utils # 工具
- scripts # 脚本
- manage.py
manage.py
# 指定项目的配置文件
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "meiduo_mall.settings.dev")
settings.py
# 指定项目以什么目录为根目录 dirname:获取目录路径,abspath:获取文件路径
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # 获取项目目录
# 添加导包路径, 导包时可从新增加的路径向下寻找,如: BASE_DIR/apps
import sys
sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))
# 项目开启DEBUG模式
DEBUG = True # 项目开启DEBUG模式
# 注册app
INSTALL_APPS = [
# 指定session的保存位置,3选1
SESSION_ENGINE = 'django.contrib.sessions.backends.db' # session默认保存在数据库中
SESSION_ENGINE = 'django.contrib.sessions.backends.cache' # session保存在缓存中
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db' # 先缓存,没有再数据库
'rest_framework',
'users.apps.UsersConfig',
]
# 允许在哪些服务器上访问项目接口, '*' 表示任意
ALLOWED_HOSTS = ['api.meiduo.site', '127.0.0.1', 'localhost']
# 设置语言与时区
LANGUAGE_CODE = 'zh-hans' # 设置语言
TIME_ZONE = 'Asia/Shanghai' # 设置时区
# 设置静态文件访问
STATIC_URL = '/static/' # 通过静态路径访问静态资源 非DEBUG模式下不提供访问静态文件的功能
STATICFILES_DIRS = [ # 决定静态资源的文件夹
os.path.join(BASE_DIR, 'static_files')
] # 在根目录下创建static_files文件夹
# 中间件
MIDDLEWARE = [
'django.midd.....csrf' # 默认开启CSRF, 注释关闭
'django.contrib.sessions...' # 默认开启session
]
# redis存储session
# pip install django-redis
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/1",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"PASSWORD": ""
}
}
}
SESSEION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "default"
# 数据库配置
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'HOST': '127.0.0.1',
'PORT': 3306,
'USER': 'root',
'PASSWORD': 'mysql',
'NAME': 'django_demo'
}
}
# 配置日志
LOGGING = {
'version': 1,
'disable_existing_loggers': False, # 是否禁用已经存在的日志器
'formatters': {
# 日志信息显示的格式
'verbose': {
'format': '%(levelname)s %(asctime)s %(module)s %(lineno)d %(message)s'
},
'simple': {
'format': '%(levelname)s %(module)s %(lineno)d %(message)s'
},
},
'filters': {
# 对日志进行过滤
'require_debug_true': {
# django在debug模式下才输出日志
'()': 'django.utils.log.RequireDebugTrue',
},
},
'handlers': {
# 日志处理方法
'console': {
# 向终端中输出日志
'level': 'INFO',
'filters': ['require_debug_true'],
'class': 'logging.StreamHandler',
'formatter': 'simple'
},
'file': {
# 向文件中输出日志
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
'filename': os.path.join(os.path.dirname(BASE_DIR), "logs/meiduo.log"), # 日志文件的位置
'maxBytes': 300 * 1024 * 1024,
'backupCount': 10,
'formatter': 'verbose'
},
},
'loggers': {
# 日志器
'django': {
# 定义了一个名为django的日志器
'handlers': ['console', 'file'], # 可以同时向终端与文件中输出日志
'propagate': True, # 是否继续传递日志信息
'level': 'INFO', # 日志器接收的最低日志级别
},
}
}
# DRF异常处理
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'meiduo_mall.utils.exceptions.exception_handler',
}
AUTH_USER_MODEL = 'users.User' # 告知Django认证系统使用我们自定义的模型
项目同名子目录下__init__.py
增加数据库的配置
import pymysql
pymysql.install_as_MySQLdb()
utils/exceptions.py
修改Django REST framework的默认异常处理方法,补充处理数据库异常和Redis异常。
from rest_framework.views import exception_handler as drf_exception_handler
import logging
from django.db import DatabaseError
from redis.exceptions import RedisError
from rest_framework.response import Response
from rest_framework import status
# 获取在配置文件中定义的logger,用来记录日志
logger = logging.getLogger('django')
def exception_handler(exc, context):
"""
自定义异常处理
:param exc: 异常
:param context: 抛出异常的上下文
:return: Response响应对象
"""
# 调用drf框架原生的异常处理方法
response = drf_exception_handler(exc, context)
if response is None:
view = context['view']
if isinstance(exc, DatabaseError) or isinstance(exc, RedisError):
# 数据库异常
logger.error('[%s] %s' % (view, exc))
response = Response({
'message': '服务器内部错误'}, status=status.HTTP_507_INSUFFICIENT_STORAGE)
return response
定义路由
# url包含关系写法
urlpatterns = [
url(r'^', include('users.urls')) # 不使用前缀的写法
url(r'^users/', include('users.urls', namespace='users')) # 包含命名空间
]
# 定义具体路由
# 工程/应用/urls.py
from . import views
urlpatterns = [
# 定义函数视图路由
url(r'^index/$', views.index, name='index') # 定义普通路由,及路由名称
# 定义类视图路由
url(r'^index/$', views.RegisterView.as_view(), name='register')
]
反解析路由
# 路由反解析, 可以没有命名空间
url = reverse("index")
url = reverse("users:index")
函数视图
def index(request)
"""
:params request: 用于接收请求HttpRequest对象
:return: 返回响应HttpResponse对象
"""
return HttpResponse('hello Django')
类视图
from django.views.generic import View
class RegisterView(View):
def get(self, request):
"""处理GET请求"""
return render(request, 'register.html')
def post(self, request):
"""处理POST请求"""
return HttpResponse('pose注册逻辑')
装饰器装饰视图
# 装饰器
def my_decorator(view_func):
def wrapper(request, *args, **kwargs):
print('装饰器被调用')
return view_func(request, *args, **kwargs)
return wrapper
class RegisterView(View):
# 装饰到一个指定函数中
@method_decorator(my_decorator)
def get(self, request):
return render(request, 'register.html')
def post(self, request):
return HttpResponse('pose注册逻辑')
class RegisterView(View):
# 重写dispatch方法实现全部装饰
@method_decorator(my_decorator)
def dispatch(self, request, *args, **kwargs)
return super().dispatcher(request, *args, **kwargs)
def get(self, request):
return render(request, 'register.html')
def post(self, request):
return HttpResponse('pose注册逻辑')
# 在class上实现装饰
@method_decorator(my_decorator, name='get') # 在get上添加装饰
@method_decorator(my_decorator, name='dispatcher') # 在所有方法上添加装饰
class RegisterView(View):
def dispatch(self, request, *args, **kwargs)
return super().dispatcher(request, *args, **kwargs)
def get(self, request):
"""处理GET请求"""
return render(request, 'register.html')
def post(self, request):
return HttpResponse('pose注册逻辑')
构造Mixin扩展类装饰视图
class BaseViewMixin(object):
@classmethod
def as_view(cls, *args, **kwargs):
view = mydecorator(super().as_view(cls, *args, **kwargs))
return view
class BaseView2Mixin(object):
@classmethod
def as_view(cls, *args, **kwargs):
view = mydecorator2(super().as_view(cls, *args, **kwargs))
return view
class RegisterView(BaseViewMixin, BaseView2Mixin, View):
pass
路由中的as_view方法会找到BaseViewMixin的as_view重写方法,由于python的多继承机制,BaseViewMixin和BaseView2Mixin和view都继承自object同一个父类,所以又会调用BaseView2Mixin的as_view方法,最后调用View的as_view方法,从而保证所有装饰器的调用
封装的request对象
视图中
def index(request):
return HttpResponse('index')
获取URL路径参数
# 不命名参数提取参数
url(r'^weather/([a-z]+)/(\d{4})/$', views.weather)
def weather(request, city, year): # 按顺序一个一个提取
return HttpResponse("%s %s"%(city, year))
# 命名参数写法 ?P 提取参数
url(r'^weather/(?P[a-z]+)/(?P\d{4})/$' , views.weather)
def weather(request, year, city): # 顺序不定,可以不用按照规定顺序
retrun
获取查询字符串参数
# /weather/beijing/2018/?key1=1&key1=11&key2=2
value2 = request.GET.get('key2') # value2:
value2_list = request.GET.getlist('key1') # ['1', '11']
获得表单格式的请求体数据
value1 = request.POST.get('key1') # value1:1
value1_list = request.POST.getlist('key1') # a:['1', '2']
获取非表单格式请求体数据 如:json/xml
等格式的数据
json_bytes = request.body
json_str = json_bytes.decode()
req_dict = json.loads(json_str)
value3 = req_dict.get('c')
value4 = req_dict.get('d')
获取请求头数据
request_header_data = request.META.get('CONTENT_TYPE')
常用请求头
- CONTENT_LENGTH – The length of the request body (as a string).
- CONTENT_TYPE – The MIME type of the request body.
- HTTP_ACCEPT – Acceptable content types for the response.
- HTTP_ACCEPT_ENCODING – Acceptable encodings for the response.
- HTTP_ACCEPT_LANGUAGE – Acceptable languages for the response.
- HTTP_HOST – The HTTP Host header sent by the client.
- HTTP_REFERER – The referring page, if any.
- HTTP_USER_AGENT – The client’s user-agent string.
- QUERY_STRING – The query string, as a single (unparsed) string.
- REMOTE_ADDR – The IP address of the client.
- REMOTE_HOST – The hostname of the client.
- REMOTE_USER – The user authenticated by the Web server, if any.
- REQUEST_METHOD – A string such as “GET” or “POST”.
- SERVER_NAME – The hostname of the server.
- SERVER_PORT – The port of the server (as a string).
用户自定义请求头a, 获取方式为:
- HTTP_A
获取其它请求信息
request.method # GET, POST
request.user # 请求用户对象
request.path # str请求页面的完整路径,不包含域名和参数部分
request.encoding # 一个提交数据的编码方式
HttpResponse
响应
import django.http.HttpResponse
resp = HttpResponse(content='{"name":"zs"}', # 响应体
content_type='application/json', # 响应体数据类型
status=400) # 状态码
resp['it'] = 'python' # 设置响应头
content_type:
- text/html html
- text/plain 普通文本
- application/json json
HttpResponseRedirect
重定向
from django.shortcuts import redirect
def resp(request):
url = reverse('index') # 动态生成URL地址,解决url硬编码维护麻烦的问题。
return redirect(url)
JsonResponse
返回json数据
帮助我们将dict转换为json字符串,再返回给客户端
自动设置响应头 Content-Type 为 application/json
from django.http import JsonResponse
resp = JsonResponse({
'city': 'beijing', 'subject': 'python'},
json_dumps_params={
'ensure_ascii':False}) # 返回中文,而非中文编码
中文: 张三
中文编码: \u5f20\u4e09
Cookie
# 设置Cookie
HttpResponse.set_cookie(key, value, max_age=6) # 设置名为key的cookie值为value,有效期为6s
# 读取Cookie
request.COOKIES.get('key')
# 删除Cookie
response.delete_cookie('mark')
# 是否有某个cookie
request.COOKIES.has_key('mark') # true False
Session
配置请参照工程配置部分
# 存储session
request.session['key1'] = value1
# 读取session
request.session.get('key1', default1) # 没有对应key1的session时返回默认值default1
# 清除所有session 值部分
request.session.clear()
# 清除所有session 数据
request.session.flush()
# 删除指定session
del request.session['key1']
# 设置有效期
request.session.set_expiry(time)
# time为一个整数,默认None 2周过期,0表示浏览器关闭时过期
# 在settings.py中设置SESSION_COOKIE_AGE来设置全局默认值,单位s
定义中间件
middleware.py
def simple_middleware(get_response):
# 此处处理Django第一次配置和初始化的时候执行
def middleware(request):
# 此处编写代码会在每个请求处理视图前被调用
response = get_response(request)
# 此处编写的代码会在每次请求处理之后被调用
return response
return middleware
注册中间件
settings.py
多个中间件的执行顺序: 视图执行前,自上而下调用, 视图执行后,自下而上调用
MIDDLEWARE = [
'middleware.simple_middleware'
]
使用示例
settings.py
TEMPLATES = [
'DIRS': [os.path.join(BASE_DIR, 'templates')],
]
视图
# Django原始方法
def index(request):
# 1.获取模板
template = loader.get_template('booktest/index.html')
# 2.定义上下文 (django1.11版本,此步不需要, context直接是一个字典)
context = RequestContext(request, {
'city':'beijing'}
# 3.渲染模板
return HttpResponse(template.render(context))
# Django封装了 render(request, 模板文件路径, 模板数据字典) 返回模板
def index(request):
data = {
'city': 'beijing',
'alist': [1,2,3],
'adict': {
'name': 'python'
}
'adate': datetime.datetime.now()
}
render(request, index.html, data)
模板语法
模板变量
{
{ city }}
{
{ alist.0 }} # 列表元素的取值
{
{ adict.0 }} # 字典元素取值
{
{ adict.name }} # 字典元素取值
if, for
控制语句
{% for item in 列表 %}
{
{forloop.counter}} # 表示循环到第几次,从1开始,forloop.counter0表示从0开始
{% empty %}
# 列表为空或不存在时执行此逻辑
{% endfor %}
{% if ... %}
{% elif ... %}
{% else %}
{% endif %}
# if的条件: == != > < >= <= and or not
注意if控制中必须格式正确, {% if num==1 %}报错,必须写成{% if num == 1 %}
过滤器
{# 语法: 变量|过滤器:参数 #}{# #}
{
{ city|safe }} {# safe: 禁止html转义(显示html标签样式,而不是字符串) #}
{
{ city|length }} {# 长度(str,list,dict个数或长度) #}
{
{ city|default:'beijing' }} {# 给变量设置默认值 #}
{
{ adate|date:"Y年m月j日 H时i分s秒" }} {# Y(2019)y(19)m(01-12)d(01-31)j(1-31)H(0-23)h(0-12)i(0-59)s(0-59) #}
注释
{# 单行注释 #}
{% comment %}
多选注释
{% endcomment %}
继承
{% block b1 %}
父模板block
{% endblock b1 %}
{% extends 父模板 %}
{% block b1 %}
# 子模板显示父模板的内容
{
{ block.super }}
# 子模板重写父模板内容
{% endblock b1 %}
使用shell工具
python manage.py shell
# 配置好环境,可直接对数据库进行CRUD操作 类似于ipython3
数据库配置
# 安装mysql数据库驱动 pip install PyMySQL
# 在Django工程同名子目录__init__.py中添加如下代码
from pymysql import install_as_MySQLdb
install_as_MySQLdb()
# settings.py中配置DABABASES,详情查看工程配置部分
数据库迁移
python manage.py makemigrations
python manage.py migrate
定义模型类
django自动生成主键,若自定义则覆盖默认
字段类型 | 特殊参数 | 说明 |
---|---|---|
AutoField | 主键, 通常不用指定,Django自动会生成主键 | |
BooleanField | True或False | |
NullBooleanField | Null,True,False三种字段 | |
CharField | max_length=10 | |
TextField | 一般超过4000时使用 | |
IntegerField | ||
DecimalField | max_digits=None decimal_places=None |
max_digists:总位数 decimal_places:小数位数 |
FloatField | ||
DateField | auto_now=False auto_now_add=False |
auto_now:修改时间 auto_now_add:创建的时间 两者不可同时指定 |
TimeField | 同上 | |
DateTimeField | 同上 | |
FileField | ||
ImageField | upload_to=‘booktest’ | |
ForeignKey | 一对多 | |
ManyToManyField | 多对多 |
选项(参数) | 含义 | 注意 |
---|---|---|
null | 是否允许为空 | True允许,False不允许,默认False |
blank | 是否允许为空白 | 同上 |
db_column | 字段名称 | 默认为属性名称 |
db_index | 是否在表中为此字段创建索引 | 默认False |
default | 字段默认值 | |
primary_key | 是否为主键 | 默认False,一般不用指明,Django自动生成主键 |
unique | 是否唯一 | 默认False(不唯一) |
on_delete | 是否级联删除 | CASCADE:级联删除, PROTECT:保护,删除时抛出异常, |
class BookInfo(models.Model):
btitle = models.CharField(max_length=20, verbose_name='名称') # verbose_name admin显示名称
bpub_date = models.DateField(verbose_name='发布日期')
bread = models.IntegerField(default=0, verbose_name='阅读量')
bcomment = models.IntegerField(default=0, verbose_name='评论量')
is_delete = models.BooleanField(default=False, verbose_name='逻辑删除')
class Meta: #元信息类
db_table = 'tb_books' # 数据库表名
verbose_name = '图书' # admin显示表名
verbose_name_plural = verbose_name # 指明人性化复数显示
class HeroInfo(models.Model):
GENDER_CHOICES = (
(0, 'male')
(1, 'femle')
)
hgender = models.SmallIntegerField(choices=GENDER_CHOICES, default=0, verbose_name='性别')
hbook = models.ForeignKey(BookInfo, on_delete=models.CASCADE, verbose_name='图书') # CASCADE级联删除,默认不允许直接删除
操作模型类
模型类管理器: objects实现对数据的操作
objects方法 | 返回类型 | 作用 |
---|---|---|
模型类.objects.create() | 模型类对象 | 新增一条数据 |
模型类.objects.get() | 模型类对象 | 查询一个对象 若查到多个或空,则抛出异常 |
模型类.objects.all() | QuerySet | 查询所有的对象 |
模型类.objects.count() | 数字 | 查询总共有多少条数据 |
模型类.objects.filter() | QuerySet | 查询满足条件的对象 |
模型类.objects.exclude() | QuerySet | 查询不满条件的对象 |
模型类.objects.order_by() | QuerySet | 对查询结果集进行排序 |
模型类.objects.aggregate() | 字典,例如: | 进行聚合操作 Sum, Count, Max, Min, Avg {‘salary__avg’: 9500.0} |
class BookInfoManager(models.Manager):
# 自定义管理器类,修改过滤方法
def all(self):
retrun super().filter(is_delete=False)
def create_book(self, title, pubdate):
# 设置特定的过滤方法
book = self.model() # 创建模型类对象
book.btitle = title
book.bpub_date = pub_date
book.bread = 0
book.bcomment = 0
book.is_delete = False
book.save() # 将数据插入数据库表
return book
class BookInfo():
objs = BookInfoManager() # 自定义管理器
创建自定义用户模型类
models.py
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
"""用户模型类"""
mobile = models.CharField(max_length=11, unique=True,
verbose_name='手机号')
class Meta:
db_table = 'tb_users'
verbose_name = '用户'
verbose_name_plural = verbose_name
settings.py
AUTH_USER_MODEL = 'users.User' # 告知Django认证系统使用我们自定义的模型
增加
# 方法一: save
hero = HeroInfo(hname='zhangsan') # 模型类对象
hero.save() # 保存模型类对象到数据库相应数据
# 方法二: create
HeroInfo.objects.create(
hname='zhangsan'
)
修改
# 方法一: save
hero = HeroInfo.objects.get(hname='song')
hero.hname = 'wu'
hero.save()
# 方法二: update
HeroInfo.objects.filter(hname='song').update(hname='wu')
删除
# 方法一: delete
hero.delete()
# 方法二: delete
HeroInfo.objects.filter(id=1).delete()
基本查询
all, get, count
all_book = BookInfo.objects.all() # all查询所有结果
b1 = all_book[0] # 取出查询集中第一个元素
bs = all_book[0:2] # 切片操作取出元素
b2 = BookInfo.objects.get(pk=1)
b2 = BookInfo.objects.get(id=1) # 与pk一样
b2 = BookInfo.objects.get(btitle='xiyou')
bcount = BookInfo.objects.count() # 返回个数
过滤查询
filter
# filter
books = BookInfo.objects.filter(id__exact=1) # 相等exact
books = BookInfo.objects.filter(id=1) # 效果同上
books = BookInfo.objects.filter(btitle__contains='you')[0] # 包含:contains
books = BookInfo.objects.filter(btitle__startswith='you') # startswith endswith
books = BookInfo.objects.filter(btitle__isnull=False) # 判空: isnull
books = BookInfo.objects.filter(id__in=[1,2,3,4]) # 范围查询: in
books = BookInfo.objects.filter(id__gt=10) # 比较查询 gt, gte, lt, lte (> >= < <=)
books = BookInfo.objects.filter(id__exclude=10) # 不等 exclude
books = BookInof.objects.filter(bpub_date__year=1980) # 日期查询 year month, day, week_day, hour, minute, second
from datetime import date
books = BookInfo.objects.filter(bpub_date__gt=date(1990,1,1))
F对象Q对象
F对象: 比较两个字段的值
Q对象: 多个查询条件
from django.db.models import F, Q
# F()
BookInfo.objects.filter(bread_gte=F('bcomment'))
BookInfo.objects.filter(bread_gte=F('bcomment')*2)
# 与 Q() & Q()
BookOnfo.objects.filter(id=5, btitle__contains='you')
BookInfo.objects.filter(Q(id=5) & Q(bread__gte=5))
# 或 Q() | Q()
BookInfo.objects.filter(Q(id=5) | Q(bread__gte=5))
# 非 !Q()
BookInfo.objects.filter(~Q(id=5))
聚合函数
# 聚合函数: Avg,Max,Min,Sum,Count
BookInfo.objects.aggregate(Sum('bread'))
排序查询
BookInfo.objects.all().order_by('bread') # 升序
BookInfo.objects.all().order_by('-bread') # 降序
关联查询
# 一查多 一的一方模型类对象.多的一方模型类名小写_set.all()
heros = book.heroinfo_set.all()
# 多查一 多的一方模型类对象.多的一方定义的一的一方的属性名
book = hero.hbook
# 跨表关联查询
books = BookInfo.objects.filter(heroinfo__hname__contains('song'))
heros = HeroInfo.objects.filter(hbook__btitle__contails('you'))
创建Admin超级管理员
python manage.py createsuperuser
注册模型类
admin.py
admin.site.register(BookInfo, BookInfoAdmin)
admin.site.register(HeroInfo)
class BookInfoAdmin(admin.ModelAdmin): # @admin.register(BookInfo) 也可以装饰器注册
pass
自定义管理页面
展示对象信息
class BookInfo():
def __str__(self):
return self.btitle
调整列表展示页面
class BookInfoAdmin(admin.ModelAdmin)
# 每页展示的数据个数
list_per_page = 10
# `操作选项` 的位置
action_on_top = True
action_on_bottom = True # top和bottom可以同时存在
# 展示信息字段
list_display = ['id', 'btitle']
# 把方法当为一列展示
list_display = ['id', 'btitle', 'pub_date']
# 模型类中定义方法
class BookInfo():
def pub_date(self):
return self.bpub_date.strftime('%Y年%m月%d日') # strftime时间转为字符串
# 给函数对象添加属性改变展示名称
pub_date.short_description = '发布日期'
# 给函数对象添加属性使其可排序
pub_date.admin_order_field = 'bpub_date'
# 展示关联对象
Class HeroInfo():
list_display = ['id', 'hbook', 'read']
def read(self):
return self.hbook.bread
read.short_description= ''
# 右侧栏过滤器
# list_filter = []
Class HeroInfoAdmin():
list_filter = ['hbook', 'hgender']
# 搜索框
Class HeroInfoAdmin():
search_fields = ['hname']
调整编辑页面
# 控制编辑显示字段
class BookInfoAdmin():
fields = ['btitle']
# 分组显示
fieldset = (
('组1标题', {
'fields':('字段1','字段2')}),
('组2标题', {
'fields':('字段1','字段2')}
'classes': ('collapse') # 是否折叠显示
)
)
# 关联对象
# StackedInline 以块形式嵌入
# TabularInline 以表格的形式嵌入
class HeroInfoStackInline(admin.StackedInline):
model = HeroInfo # 要编辑的对象
extra = 1 # 附加编辑的数量
class BookInfoAdmin(admin.ModelAdmin):
inlines = [HeroInfoStackInline]
调整整体页面标题等
admin.site.site_header # 设置网站页头
admin.site.site_title # 设置页面标题
admin.site.index_title # 设置首页标语
定义表单
forms.py
from django import forms
class BookForm(forms.Form):
title = forms.ChaiField(label='书名', required=True, max_length=50')
pub_date = forms.DateField(label='出版日期', required=True)
class Meta:
model = BookInfo # 指明属于哪个模型类
field = ('btitle', 'bpub_date') # 指明向表单中添加模型类的哪个字段
视图处理
class BookView(View):
def get(self, request):
book_form = BookForm()
return render(request, 'book.html', {
'form': book_form})
def post(self, request):
book_form = BookForm(request.POST)
if form.is_valid(): # 验证表单数据
data = form.cleaned_data # 获取验证后的表单数据
return HttpResponse('OK')
else:
return render(request, 'book.html', {
'form': book_form})
模板处理
视图中使用Redis
from django_redis import get_redis_connection
# 获取redis连接对象
redis_conn = get_redis_connection('verify_codes') # 参数verify_codes在settings.py中指定
# 新增数据
redis_conn.setex("img_%s" % image_code_id,
constants.IMAGE_CODE_REDIS_EXPIRES,
text)
# 查询数据
real_image_code_text = redis_conn.get('img_%s' % image_code_id)
# 删除数据
redis_conn.delete('img_%s' % image_code_id)
# 同时执行多条命令用管道
pl = redis_conn.pipeline()
pl.setex('sms_%s' % mobile, constants.SMS_CODE_REDIS_EXPIRES, sms_code)
pl.setex('send_flag_%s' % mobile, constants.SEND_SMS_CODE_INTERVAL, 1)
pl.execute()
real_image_code_text = redis_conn.get(‘img_%s’ % image_code_id)
DRF --> Django REST framework
官方文档
Github源码
Web应用模式
RESTful设计方法
域名
在API下部署域名
https://api.example.com
# 如果API简单,可放在主域名下
https://example.com/api/
版本
应该将API的版本号放在URL中
http://www.example.com/api/1.0/foo
http://www.example.com/api/1.1/foo
# 也可以将版本号放在RequestHead中 (github使用此模式)
Accept: vnd.example-com.foo+json; version=1.0
Accept: vnd.example-com.foo+json; version=1.1
路径
路径又称为终点(Endpoint),是一种资源, 往往与数据库中的表对应
API中只能有名词不能有动词, 且为名词复数
使用HTTP方法分离网址中的资源名称的操作
GET /products :将返回所有产品清单
POST /products :将产品新建到集合
GET /products/4 :将获取产品 4
PATCH(或)PUT /products/4 :将更新产品 4
HTTP动词
还有三个不常用的HTTP动词。
GET /zoos:列出所有动物园
POST /zoos:新建一个动物园(上传文件)
GET /zoos/ID:获取某个指定动物园的信息
PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)
PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)
DELETE /zoos/ID:删除某个动物园
GET /zoos/ID/animals:列出某个指定动物园的所有动物
DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物
过滤信息
常见的过滤参数
?limit=10:指定返回记录的数量
?offset=10:指定返回记录的开始位置。
?page=2&per_page=100:指定第几页,以及每页的记录数。
?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
?animal_type_id=1:指定筛选条件
状态码
200 OK - [GET]:服务器成功返回用户请求的数据
201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
204 NO CONTENT - [DELETE]:用户删除数据成功。
400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作
401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。
错误处理(Error handling)
如果状态码是4xx,服务器就应该向用户返回出错信息。
一般来说,返回的信息中将error作为键名,出错信息作为键值
{
error: "Invalid API key"
}
返回结果
服务器返回的数据格式,应该尽量使用JSON,避免使用XML。
- GET /collection:返回资源对象的列表(数组)
- GET /collection/resource:返回单个资源对象
- POST /collection:返回新生成的资源对象
- PUT /collection/resource:返回完整的资源对象
- PATCH /collection/resource:返回完整的资源对象
- DELETE /collection/resource:返回一个空文档
超媒体(Hypermedia API)
参照api.github.com
REST接口开发的核心任务
序列化: 将程序中的一个数据结构类型转换为其他格式(字典、JSON、XML等)
反序列化: 将其他格式(字典、JSON、XML等)转换为程序中的数据
安装DRF
pip install djangorestframework
配置
settings.py
INSTALLED_APPS = [
'rest_framework',
]
创建序列化器
serializers.py
class BookInfoSerializer(serializers.ModelSerializer):
"""图书数据序列化器"""
class Meta:
model = BookInfo # 数据字段从模型类BookInfo参考生成
fields = '__all__' # 包含模型类中的哪些字段,'__all__'指明包含所有字段
编写视图
views.py
from rest_framework.viewsets import ModelViewSet
from .serializers import BookInfoSerializer
from .models import BookInfo
class BookInfoViewSet(ModelViewSet):
queryset = BookInfo.objects.all() # 指明该视图集在查询数据时使用的查询集
serializer_class = BookInfoSerializer # 指明该视图在进行序列化或反序列化时使用的序列化器
定义路由
urls.py
from . import views
from rest_framework.routers import DefaultRouter
router = DefaultRouter() # 可以处理视图的路由器
router.register('books', views.BookInfoViewSet, name='books') # 向路由器中注册视图集
urlpatterns += router.urls # 将路由器中的所以路由信息追到到django的路由列表中
测试
127.0.0.1:8000 可以获取所有数据的接口
定义Serializer
serializer不是只能为数据库模型类定义,也可以为非数据库模型类的数据定义。
models.py
class BookInfo(models.Model):
btitle = models.CharField(max_length=20, verbose_name='名称')
bpub_date = models.DateField(verbose_name='发布日期', null=True)
bread = models.IntegerField(default=0, verbose_name='阅读量')
bcomment = models.IntegerField(default=0, verbose_name='评论量')
image = models.ImageField(upload_to='booktest', verbose_name='图片', null=True)
serializer.py
class BookInfoSerializer(serializers.Serializer):
"""图书数据序列化器"""
id = serializers.IntegerField(label='ID', read_only=True)
btitle = serializers.CharField(label='名称', max_length=20)
bpub_date = serializers.DateField(label='发布日期', required=False)
bread = serializers.IntegerField(label='阅读量', required=False)
bcomment = serializers.IntegerField(label='评论量', required=False)
image = serializers.ImageField(label='图片', required=False)
定义模型类序列化器
class BookInfoSerializer(serializers.ModelSerializer):
"""图书数据序列化器"""
class Meta:
model = BookInfo # 指明参照哪个模型类
# fields = ('id', 'btitle', 'bpub_date')
fields = '__all__' # 指明为模型类的哪些字段生成
exclude = ('image',) # 排除掉哪些字段
depth = 1 # 关联层级 默认使用主键作为关联字段, 指定depth使用关联对象嵌套序列化
read_only_fields = ('id', 'bread', 'bcomment') # 指明仅用于序列化输出的字段
extra_kwargs = {
# 添加额外参数
'bread': {
'min_value': 0, 'required': True},
'bcomment': {
'min_value': 0, 'required': True},
}
字段与选项
常用字段类型:
字段 | 字段构造方式 |
---|---|
BooleanField | BooleanField() |
NullBooleanField | NullBooleanField() |
CharField | CharField(max_length=None, min_length=None, allow_blank=False, trim_whitespace=True) |
EmailField | EmailField(max_length=None, min_length=None, allow_blank=False) |
RegexField | RegexField(regex, max_length=None, min_length=None, allow_blank=False) |
SlugField | SlugField(max_length=50, minlength=None, allow_blank=False) 正则字段,验证正则模式 [a-zA-Z0-9-]+ |
URLField | URLField(max_length=200, min_length=None, allow_blank=False) |
UUIDField | UUIDField(format=‘hex_verbose’) format: 1) 'hex_verbose' 如"5ce0e9a5-5ffa-654b-cee0-1238041fb31a" 2) 'hex' 如 "5ce0e9a55ffa654bcee01238041fb31a" 3)'int' - 如: "123456789012312313134124512351145145114" 4)'urn' 如: "urn:uuid:5ce0e9a5-5ffa-654b-cee0-1238041fb31a" |
IPAddressField | IPAddressField(protocol=‘both’, unpack_ipv4=False, **options) |
IntegerField | IntegerField(max_value=None, min_value=None) |
FloatField | FloatField(max_value=None, min_value=None) |
DecimalField | DecimalField(max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None) max_digits: 最多位数 decimal_palces: 小数点位置 |
DateTimeField | DateTimeField(format=api_settings.DATETIME_FORMAT, input_formats=None) |
DateField | DateField(format=api_settings.DATE_FORMAT, input_formats=None) |
TimeField | TimeField(format=api_settings.TIME_FORMAT, input_formats=None) |
DurationField | DurationField() |
ChoiceField | ChoiceField(choices) choices与Django的用法相同 |
MultipleChoiceField | MultipleChoiceField(choices) |
FileField | FileField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL) |
ImageField | ImageField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL) |
ListField | ListField(child=, min_length=None, max_length=None) |
DictField | DictField(child=) |
选项参数:
参数名称 | 作用 |
---|---|
max_length | 最大长度 |
min_lenght | 最小长度 |
allow_blank | 是否允许为空 |
trim_whitespace | 是否截断空白字符 |
max_value | 最小值 |
min_value | 最大值 |
通用参数:
参数名称 | 说明 |
---|---|
read_only | 表明该字段仅用于序列化输出,默认False |
write_only | 表明该字段仅用于反序列化输入,默认False |
required | 表明该字段在反序列化时必须输入,默认True |
default | 反序列化时使用的默认值 |
allow_null | 表明该字段是否允许传入None,默认False |
validators | 该字段使用的验证器 |
error_messages | 包含错误编号与错误信息的字典 |
label | 用于HTML展示API页面时,显示的字段名称 |
help_text | 用于HTML展示API页面时,显示的字段帮助提示信息 |
使用Serializer
Serializer(instance=None, data=empty, **kwarg)
1)用于序列化时,将模型类对象传入instance参数
2)用于反序列化时,将要被反序列化的数据传入data参数
3)除了instance和data参数外,在构造Serializer对象时,还可通过context参数额外添加数据,如
serializer = AccountSerializer(account, context={'request': request})
通过context参数附加的数据,可以通过Serializer对象的context属性获取。
序列化
book = BookInfo.objects.get(id=2)
serializer = BookInfoSerializer(book)
serializer.data # 获取单个序列化数据
serializer = BookInfoSerializer(book_qs, many=True) # 获取QuerySet序列化数据
serializer.data
关联对象嵌套序列化定义
# PrimaryKeyRelatedField 此字段将被序列化为关联对象的主键
# 参数: read_only=True ==> 该字段该字段将不能用作反序列化使用
hbook = serializers.PrimaryKeyRelatedField(label='图书', read_only=True)
# 参数: queryset ==> 该字段被用作反序列化时参数校验使用
hbook = serializers.PrimaryKeyRelatedField(label='图书',
queryset=BookInfo.objects.all())
# 使用关联对象的序列化器
hbook = BookInfoSerializer()
# StringRelatedField 字段将被序列化为关联对象的字符串表示方式(即__str__方法的返回值)
hbook = serializers.StringRelatedField(label='图书')
关联对象有多个(如: 一对多时): 在以上三种方式中加参数
many=True
指明
验证
data = {
'bpub_date': 123} # 反序列化数据
serializer = BookInfoSerializer(data=data) # 构造反序列化器
serializer.is_valid() # 验证
serializer.errors # 验证失败的错误信息
serializer.validated_data # 验证通过的数据
保存
class BookInfoSerializer(serializers.Serializer):
def create(self, validated_data):
"""新建"""
return BookInfo.objects.create(**validated_data)
def update(self, instance, validated_data):
"""更新,instance为要更新的对象实例"""
instance.btitle = validated_data.get('btitle', instance.btitle)
instance.bpub_date = validated_data.get('bpub_date', instance.bpub_date)
instance.bread = validated_data.get('bread', instance.bread)
instance.bcomment = validated_data.get('bcomment', instance.bcomment)
instance.save()
return instance
# 如果创建序列化器对象的时候,没有传递instance实例,则调用save()方法的时候,create()被调用,相反,如果传递了instance实例,则调用save()方法的时候,update()被调用。
book = serializer.save()
# 创建
from db.serializers import BookInfoSerializer
data = {
'btitle': '封神演义'}
serializer = BookInfoSerializer(data=data)
serializer.is_valid() # True
serializer.save() #
# 更新
from db.models import BookInfo
book = BookInfo.objects.get(id=2)
data = {
'btitle': '倚天剑'}
serializer = BookInfoSerializer(book, data=data)
serializer.is_valid() # True
serializer.save() #
book.btitle # '倚天剑'
# 部分更新 默认序列化器必须传递所有required的字段 partial允许部分字段更新
serializer = CommentSerializer(comment, data={
'content': u'foo bar'}, partial=True)
APIView
继承自Django View 定义get(), post()等方法+序列化器+request,response实现
支持定义的属性:
- authentication_classes 列表或元祖,身份认证类
- permissoin_classes 列表或元祖,权限检查类
- throttle_classes 列表或元祖,流量控制类
from rest_framework.views import APIView
from rest_framework.response import Response
# url(r'^books/$', views.BookListView.as_view()),
class BookListView(APIView):
def get(self, request):
books = BookInfo.objects.all()
serializer = BookInfoSerializer(books, many=True)
return Response(serializer.data)
GenericAPIView
继承自APIView, 增加数据库查询方法, 配合Mixin扩展类使用
"""GenericAPIView提供的属性和方法"""
serializer_class # 指明视图使用的序列化器
get_serializer_class(self) # 返回序列化器类,默认返回serializer_class, 可重写
get_serializer(self, args, *kwargs) # 返回序列化器对象
queryset # 指明使用的数据查询集
get_queryset(self) # 返回视图查询集
get_object(self) # 返回详情视图所需的模型类数据对象
Mixin扩展类
ListModelMixin
"""ListModelMixin提供的方法"""
list(request, *args, **kwargs) # 快速实现列表视图,返回200状态码。会对数据进行过滤和分页。
使用示例:
from rest_framework.mixins import ListModelMixin
class BookListView(ListModelMixin, GenericAPIView):
queryset = BookInfo.objects.all()
serializer_class = BookInfoSerializer
def get(self, request):
return self.list(request)
CreateModelMixin
"""CreateModelMixin提供的方法"""
create(request, *args, **kwargs) # 快速实现创建资源的视图,成功返回201,序列化器对前端发送的数据验证失败,返回400错误。
示例 :
from rest_framework.mixins import CreateModelMixin
class BookListView(CreateModelMixin, GenericAPIView):
serializer_class = BookInfoSerializer
def create(self, request):
return self.create(request)
RetrieveModelMixin
"""RetrieveModelMixin提供的方法"""
retrieve(request, *args, **kwargs) # 返回一个存在的数据对象。如果存在返回200否则返回404。
示例:
class BookDetailView(RetrieveModelMixin, GenericAPIView):
queryset = BookInfo.objects.all()
serializer_class = BookInfoSerializer
def get(self, request, pk):
return self.retrieve(request)
UpdateModelMixin
"""UpdateModelMixin提供的方法"""
update(request, *args, **kwargs) # 更新一个存在的数据对象
partial_update(request, *args, **kwargs) # 实现局部更新
# 成功返回200,序列化器校验数据失败时,返回400错误。
示例:
class BookDetailView(UpdateModelMixin, UpdateModelMixin):
queryset = BookInfo.objects.all()
serializer_class = BookInfoSerializer
def get(self, request, pk):
return self.update(request)
DestroyModelMixin
"""DestroyModelMixin提供的方法"""
destroy(request, *args, **kwargs) # 删除一个存在的数据对象。成功返回204,不存在返回404。
示例:
class BookDetailView(DestroyModelMixin, UpdateModelMixin):
queryset = BookInfo.objects.all()
serializer_class = BookInfoSerializer
def get(self, request, pk):
return self.destroy(request)
继承自Mixin与GenericAPIView的子类视图
子类 | 继承的父类 | 提供的方法 |
---|---|---|
CreateAPIView | GenericAPIView、CreateModelMixin | post |
ListAPIView | GenericAPIView、ListModelMixin | get |
RetrieveAPIView | GenericAPIView、RetrieveModelMixin | get |
DestoryAPIView | GenericAPIView、DestoryModelMixin | delete |
UpdateAPIView | GenericAPIView、UpdateModelMixin | put 和 patch |
RetrieveUpdateAPIView | GenericAPIView、RetrieveModelMixin、UpdateModelMixin | get、put、patch |
RetrieveUpdateDestoryAPIView | GenericAPIView、RetrieveModelMixin、UpdateModelMixin、DestoryModelMixin | get、put、patch、delete |
ViewSet
mixin
+GenericViewSet
from rest_framework import mixins
from rest_framework.viewsets import GenericViewSet
from rest_framework.decorators import action
class BookInfoViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, GenericViewSet):
queryset = BookInfo.objects.all()
serializer_class = BookInfoSerializer
urlpatterns = [
url(r'^books/$', views.BookInfoViewSet.as_view({
'get': 'list'})),
url(r'^books/(?P\d+)/$' , views.BookInfoViewSet.as_view({
'get': 'retrieve'})),
]
ModelViewSet
继承自
GenericViewSet
,包括ListModelMixin、RetrieveModelMixin、CreateModelMixin、UpdateModelMixin、DestoryModelMixin。
ReadOnlyModelViewSet
继承自
GenericViewSet
,同时包括了ListModelMixin、RetrieveModelMixin。
自定义action
from rest_framework import mixins
from rest_framework.viewsets import GenericViewSet
from rest_framework.decorators import action
class BookInfoViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, GenericViewSet):
queryset = BookInfo.objects.all()
serializer_class = BookInfoSerializer
def latest(self, request):
"""
返回最新的图书信息
"""
book = BookInfo.objects.latest('id')
serializer = self.get_serializer(book)
return Response(serializer.data)
def read(self, request, pk):
"""
修改图书的阅读量数据
"""
book = self.get_object()
book.bread = request.data.get('read')
book.save()
serializer = self.get_serializer(book)
return Response(serializer.data)
urlpatterns = [
url(r'^books/$', views.BookInfoViewSet.as_view({
'get': 'list'})),
url(r'^books/latest/$', views.BookInfoViewSet.as_view({
'get': 'latest'})),
url(r'^books/(?P\d+)/$' , views.BookInfoViewSet.as_view({
'get': 'retrieve'})),
url(r'^books/(?P\d+)/read/$' , views.BookInfoViewSet.as_view({
'put': 'read'})),
]
根据action属性,判断action
def get_serializer_class(self):
if self.action == 'create':
return OrderCommitSerializer
else:
return OrderDataSerializer
SimpleRouter
对于视图集ViewSet,我们除了可以自己手动指明请求方式与动作action之间的对应关系外,还可以使用Routers来帮助我们快速实现路由信息。
urls.py
from rest_framework import routers
router = routers.SimpleRouter()
router.register(r'books', BookInfoViewSet, base_name='book')
urlpatterns += router.urls
# 或
url(r'^', include(router.urls))
'''形成路由如下
^books/$ name: book-list
^books/{pk}/$ name: book-detail
'''
DRF对request对象进行了再次封装
路径参数
# r'image_codes/(?P.*)/$'
class ImageCodes(APIView):
def get(self, request, image_codes_id):
pass
request.data
同django中的request.POST
request.query_params
同django中的request.GET
Response
修改响应数据格式
settings.py
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': ( # 默认响应渲染类
'rest_framework.renderers.JSONRenderer', # json渲染器
'rest_framework.renderers.BrowsableAPIRenderer', # 浏览API渲染器
)
}
构造响应
rest_framework.response.Response
Response(data, status=None, template_name=None, headers=None, content_type=None)