如何学习框架
-
如何学习框架
- 如何搭建工程程序
- 工程的组建
- 工程的配置
- 路由定义
- 视图函数定义
- 如何获取请求数据(request对象)
- 如何构造响应数据(response对象)
- 如何使用中间层
- 框架提供的其他功能组件的使用
- 数据库
- 模板
- 表单
- admin
- 如何搭建工程程序
-
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
1. 搭建工程
-
通过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 # 工具
- apps # 项目应用
- scripts # 脚本
- manage.py
2. 工程配置
-
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
3. 路由
-
定义路由
# 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")
4. 视图
-
函数视图
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方法,从而保证所有装饰器的调用
5. 获取请求参数
-
封装的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<year> 提取参数 url(r'^weather/(?P<city>[a-z]+)/(?P<year>\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 # 一个提交数据的编码方式
6. 构造响应数据
-
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
7. 中间件
7.1 状态保持
-
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
7.2 请求勾子
-
定义中间件
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' ]
8. 模板
-
使用示例
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 %}
-
9.数据库
9.1 配置迁移
-
使用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
9.2 ORM
-
定义模型类
django自动生成主键,若自定义则覆盖默认
- 字段类型
字段类型 特殊参数 说明 AutoField 主键, 通常不用指定,Django自动会生成主键 BooleanField True或False NullBooleanField Null,True,False三种字段 CharField max_length=10 TextField 一般超过4000时使用 IntegerField DecimalField max_digits=None
decimal_places=Nonemax_digists:总位数
decimal_places:小数位数FloatField DateField auto_now=False
auto_now_add=Falseauto_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() # 自定义管理器
9.3 Django用户模型类
-
创建自定义用户模型类
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认证系统使用我们自定义的模型
9.4 数据的增删改
-
增加
# 方法一: 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()
9.5 查询数据
-
基本查询
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'))
10. Admin站点
-
创建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 # 设置首页标语
-
11. 表单
-
定义表单
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})
-
模板处理
<form method='post'> {% csrf_token %} {{ form }} <input type='submit'> </form>
12. Redis的使用
-
视图中使用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)
如何学习框架
- 如何搭建工程程序
- 工程的组建
- 工程的配置
- 路由定义
- 视图函数定义
- 如何获取请求数据(request对象)
- 如何构造响应数据(response对象)
- 如何使用中间层
- 框架提供的其他功能组件的使用
- 数据库
- 模板
- 表单
- admin
1. DRF概述
-
DRF --> Django REST framework
-
Web应用模式
- 前后端不分离
- 浏览器请求动态页面 --> 应用服务器查询数据库,渲染模板,返回HTML页面或重定向页面
- 前后端分离
- 浏览器请求静态页面 --> 静态文件服务器返回静态文件
- 浏览器运行JS请求后端数据填充到页面中 --> 通过对外统一的API通过请求数据,返回JSON/XML数据
- 前后端不分离
-
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动词
- GET(SELECT):从服务器取出资源(一项或多项)。
- POST(CREATE):在服务器新建一个资源。
- PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
- DELETE(DELETE):从服务器删除资源。
还有三个不常用的HTTP动词。
- PATCH(UPDATE):在服务器更新(更新)资源(客户端提供改变的属性)。
- HEAD:获取资源的元数据。
- OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。
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)
-
-
REST接口开发的核心任务
序列化: 将程序中的一个数据结构类型转换为其他格式(字典、JSON、XML等)
反序列化: 将其他格式(字典、JSON、XML等)转换为程序中的数据
- 将数据库数据序列化为前端所需要的格式,并返回;
- 将前端发送的数据反序列化为模型类对象,并保存到数据库中。
2. 安装配置
-
安装DRF
pip install djangorestframework
-
配置
settings.py
INSTALLED_APPS = [ 'rest_framework', ]
3. DRF使用示例
-
创建序列化器
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 可以获取所有数据的接口
4. Serializer序列化器
-
定义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属性获取。
5. 序列化
-
序列化
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
指明
6. 反序列化
-
验证
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() # <BookInfo: 封神演义> # 更新 from db.models import BookInfo book = BookInfo.objects.get(id=2) data = {'btitle': '倚天剑'} serializer = BookInfoSerializer(book, data=data) serializer.is_valid() # True serializer.save() # <BookInfo: 倚天剑> book.btitle # '倚天剑' # 部分更新 默认序列化器必须传递所有required的字段 partial允许部分字段更新 serializer = CommentSerializer(comment, data={'content': u'foo bar'}, partial=True)
7. 视图
-
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<pk>\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<pk>\d+)/$', views.BookInfoViewSet.as_view({'get': 'retrieve'})), url(r'^books/(?P<pk>\d+)/read/$', views.BookInfoViewSet.as_view({'put': 'read'})), ]
根据action属性,判断action
def get_serializer_class(self): if self.action == 'create': return OrderCommitSerializer else: return OrderDataSerializer
8. 路由
-
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 '''
8. 获取请求参数
DRF对request对象进行了再次封装
-
路径参数
# r'image_codes/(?P<image_codes_id>.*)/$' class ImageCodes(APIView): def get(self, request, image_codes_id): pass
-
request.data
同django中的request.POST
-
request.query_params
同django中的request.GET
9. 构造响应
-
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)
-