微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!
django三种文件下载方式
一、概述在实际的项目中很多时候需要用到下载功能,如导excel、pdf或者文件下载,当然你可以使用web服务自己搭建可以用于下载的资源服务器,如nginx,这里我们主要介绍django中的文件下载。实现方式:a标签+响应头信息(当然你可以选择form实现)<div class="col-md-4"><a href="{% url 'download' %}">点我下载</a></div> 方式一:使用HttpResponse路由url:url(r'^download/',views.download,name="download"),views.py代码from django.shortcuts import HttpResponsedef download(request):file = open('crm/models.py', 'rb')response = HttpResponse(file)response['Content-Type'] = 'application/octet-stream' #设置头信息,告诉浏览器这是个文件response['Content-Disposition'] = 'attachment;filename="models.py"'return response 方式二:使用StreamingHttpResponse其他逻辑不变,主要变化在后端处理from django.http import StreamingHttpResponsedef download(request):file=open('crm/models.py','rb')response =StreamingHttpResponse(file)response['Content-Type']='application/octet-stream'response['Content-Disposition']='attachment;filename="models.py"'return response方式三:使用FileResponsefrom django.http import FileResponsedef download(request):file=open('crm/models.py','rb')response =FileResponse(file)response['Content-Type']='application/octet-stream'response['Content-Disposition']='attachment;filename="models.py"'return response 使用总结三种http响应对象在django官网都有介绍.入口:https://docs.djangoproject.com/en/1.11/ref/request-response/推荐使用FileResponse,从源码中可以看出FileResponse是StreamingHttpResponse的子类,内部使用迭代器进行数据流传输。 
Django中使用Celery
一、前言Celery是一个基于python开发的分布式任务队列,如果不了解请阅读笔者上一篇博文Celery入门与进阶,而做python WEB开发最为流行的框架莫属Django,但是Django的请求处理过程都是同步的无法实现异步任务,若要实现异步任务处理需要通过其他方式(前端的一般解决方案是ajax操作),而后台Celery就是不错的选择。倘若一个用户在执行某些操作需要等待很久才返回,这大大降低了网站的吞吐量。下面将描述Django的请求处理大致流程(图片来源于网络):请求过程简单说明:浏览器发起请求-->请求处理-->请求经过中间件-->路由映射-->视图处理业务逻辑-->响应请求(template或response)二、配置使用celery很容易集成到Django框架中,当然如果想要实现定时任务的话还需要安装django-celery-beta插件,后面会说明。需要注意的是Celery4.0只支持Django版本>=1.8的,如果是小于1.8版本需要使用Celery3.1。配置新建立项目taskproj,目录结构(每个app下多了个tasks文件,用于定义任务):taskproj├── app01│   ├── __init__.py│   ├── apps.py│   ├── migrations│   │   └── __init__.py│   ├── models.py│   ├── tasks.py│   └── views.py├── manage.py├── taskproj│   ├── __init__.py│   ├── settings.py│   ├── urls.py│   └── wsgi.py└── templates在项目目录taskproj/taskproj/目录下新建celery.py:#!/usr/bin/env python3# -*- coding:utf-8 -*-# Author:wdfrom __future__ import absolute_import, unicode_literalsimport osfrom celery import Celeryos.environ.setdefault('DJANGO_SETTINGS_MODULE', 'taskproj.settings') # 设置django环境app = Celery('taskproj')app.config_from_object('django.conf:settings', namespace='CELERY') # 使用CELERY_ 作为前缀,在settings中写配置app.autodiscover_tasks() # 发现任务文件每个app下的task.pytaskproj/taskproj/__init__.py:from __future__ import absolute_import, unicode_literalsfrom .celery import app as celery_app__all__ = ['celery_app']taskproj/taskproj/settings.pyCELERY_BROKER_URL = 'redis://10.1.210.69:6379/0' # Broker配置,使用Redis作为消息中间件CELERY_RESULT_BACKEND = 'redis://10.1.210.69:6379/0' # BACKEND配置,这里使用redisCELERY_RESULT_SERIALIZER = 'json' # 结果序列化方案进入项目的taskproj目录启动worker:celery worker -A taskproj -l debug定义与触发任务任务定义在每个tasks文件中,app01/tasks.py:from __future__ import absolute_import, unicode_literalsfrom celery import shared_task@shared_taskdef add(x, y):return x + y@shared_taskdef mul(x, y):return x * y视图中触发任务from django.http import JsonResponsefrom app01 import tasks# Create your views here.def index(request,*args,**kwargs):res=tasks.add.delay(1,3)#任务逻辑return JsonResponse({'status':'successful','task_id':res.task_id})访问http://127.0.0.1:8000/index 若想获取任务结果,可以通过task_id使用AsyncResult获取结果,还可以直接通过backend获取: 扩展除了redis、rabbitmq能做结果存储外,还可以使用Django的orm作为结果存储,当然需要安装依赖插件,这样的好处在于我们可以直接通过django的数据查看到任务状态,同时为可以制定更多的操作,下面介绍如何使用orm作为结果存储。1.安装pip install django-celery-results2.配置settings.py,注册appINSTALLED_APPS = (...,'django_celery_results',)4.修改backend配置,将redis改为django-db#CELERY_RESULT_BACKEND = 'redis://10.1.210.69:6379/0' # BACKEND配置,这里使用redisCELERY_RESULT_BACKEND = 'django-db' #使用django orm 作为结果存储5.修改数据库python3 manage.py migrate django_celery_results此时会看到数据库会多创建: 当然你有时候需要对task表进行操作,以下源码的表结构定义:class TaskResult(models.Model):"""Task result/status."""task_id = models.CharField(_('task id'), max_length=255, unique=True)task_name = models.CharField(_('task name'), null=True, max_length=255)task_args = models.TextField(_('task arguments'), null=True)task_kwargs = models.TextField(_('task kwargs'), null=True)status = models.CharField(_('state'), max_length=50,default=states.PENDING,choices=TASK_STATE_CHOICES)content_type = models.CharField(_('content type'), max_length=128)content_encoding = models.CharField(_('content encoding'), max_length=64)result = models.TextField(null=True, default=None, editable=False)date_done = models.DateTimeField(_('done at'), auto_now=True)traceback = models.TextField(_('traceback'), blank=True, null=True)hidden = models.BooleanField(editable=False, default=False, db_index=True)meta = models.TextField(null=True, default=None, editable=False)objects = managers.TaskResultManager()class Meta:"""Table information."""ordering = ['-date_done']verbose_name = _('task result')verbose_name_plural = _('task results')def as_dict(self):return {'task_id': self.task_id,'task_name': self.task_name,'task_args': self.task_args,'task_kwargs': self.task_kwargs,'status': self.status,'result': self.result,'date_done': self.date_done,'traceback': self.traceback,'meta': self.meta,}def __str__(self):return '<Task: {0.task_id} ({0.status})>'.format(self) 三、Django中使用定时任务如果想要在django中使用定时任务功能同样是靠beat完成任务发送功能,当在Django中使用定时任务时,需要安装django-celery-beat插件。以下将介绍使用过程。安装配置1.beat插件安装pip3 install django-celery-beat2.注册APPINSTALLED_APPS = [....'django_celery_beat',]3.数据库变更python3 manage.py migrate django_celery_beat4.分别启动woker和betacelery -A proj beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler #启动beta 调度器使用数据库celery worker -A taskproj -l info #启动woker5.配置adminurls.py# urls.pyfrom django.conf.urls import urlfrom django.contrib import adminurlpatterns = [url(r'^admin/', admin.site.urls),]6.创建用户python3 manage.py createsuperuser7.登录admin进行管理(地址http://127.0.0.1:8000/admin)并且还可以看到我们上次使用orm作为结果存储的表。http://127.0.0.1:8000/admin/login/?next=/admin/  使用示例:       查看结果: 二次开发django-celery-beat插件本质上是对数
django实时通讯--channels2.x使用
一、背景在最近的项目中的一个需求是消息实时推送消息以及通知功能,项目使用django写的所以决定采用django-channels来实现websocket进行实时通讯。目前官方已经更新到2.1版本,相对于老的channels 1.x版本有了很大变化,无论是使用方式还是功能,其中最大的变化莫过于2.x版本中带来的asyncio特性,可使用异步处理模式。本文内容将介绍channels2版本使用,由于项目django是1.11,其中也遇到了一些坑,比如在channels在处理一次请求后hang住然后报错,后面修改了下django1.11版本的一点源码得以解决,2.0版本应该不会有问题。二、channels介绍channels是以django插件的形式存在,它不仅能处理http请求,还提供对websocket、MQTT等长连接支持。不仅如此,channels在保留了原生django的同步和易用的特性上还带来了异步处理方式(channels2.X版本),并且将django自带的认证系统以及session集成到模块中,扩展性非常强。官方文档:https://channels.readthedocs.io/en/latest/index.html 三、安装以及安装需求channels2.0最低django版本要求是1.11+,python3.5+。笔者的版本是django1.11,直接安装可能有问题,以下是测试通过的版本。笔者的相关版本如下:Django==1.11.10channels==2.1.4channels-redis==2.3.1asgiref==2.1.6asgi-redis==1.4.3如果django版本比较高直接采用pip安装:pip3 install channelspip3 install channels-redis #可选的,官方推荐如果使用redis作为channel layerredis安装可以参考博客:https://www.cnblogs.com/wdliu/p/9360286.html四、开始使用一、配置settings.py笔者采用的redis作为channel layer(关于其介绍请移步至https://channels.readthedocs.io/en/latest/topics/channel_layers.html),它是实现消息推送的核心,在项目的settings.py中:注册channles app:INSTALLED_APPS = ['django.contrib.admin','django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles','cmdb','channels', #注册app]配置channels layer:ASGI_APPLICATION = 'devops.routing.application'CHANNEL_LAYERS = {'default': {'BACKEND': 'channels_redis.core.RedisChannelLayer','CONFIG': {"hosts": [('10.1.210.33', 6379)], #需修改},},} 二、路由配置在项目settings文件同级目录中新增routing.py#!/usr/bin/env python3# -*- coding:utf-8 -*-# Author:wdfrom channels.auth import AuthMiddlewareStackfrom channels.routing import ProtocolTypeRouter, URLRouterimport deploy.routingapplication = ProtocolTypeRouter({'websocket': AuthMiddlewareStack(URLRouter(deploy.routing.websocket_urlpatterns# 指明路由文件是devops/routing.py)),})最后在app里配置路由和对应的消费者,笔者这里是devops下的routing.py:#!/usr/bin/env python3# -*- coding:utf-8 -*-# Author:wdfrom django.conf.urls import urlfrom . import consumerswebsocket_urlpatterns = [url(r'^ws/deploy/(?P<service_name>[^/]+)/$', consumers.DeployResult), #consumers.DeployResult 是该路由的消费者]项目目录结构如下:  三、编写webscoket消息处理方法(消费者)首先说明,消费者是Channels代码的基本单元,当一个新的Socket进入的时候,Channels会根据路由表找到正确的消费者,以下代码中每个方法都可以看作一个消费者,他们消费不同的event,比如刚刚接受连接时候connect方法进行消费处理并接受连接,关闭websocket时候使用disconnect进行消费处理。deploy/consumers.py:#!/usr/bin/env python3# -*- coding:utf-8 -*-# Author:wdfrom channels.generic.websocket import AsyncWebsocketConsumerimport jsonclass DeployResult(AsyncWebsocketConsumer):async def connect(self):self.service_uid = self.scope["url_route"]["kwargs"]["service_uid"]self.chat_group_name = 'chat_%s' % self.service_uid# 收到连接时候处理,await self.channel_layer.group_add(self.chat_group_name,self.channel_name)await self.accept()async def disconnect(self, close_code):# 关闭channel时候处理await self.channel_layer.group_discard(self.chat_group_name,self.channel_name)# 收到消息async def receive(self, text_data):text_data_json = json.loads(text_data)message = text_data_json['message']print("收到消息--》",message)# 发送消息到组await self.channel_layer.group_send(self.chat_group_name,{'type': 'client.message','message': message})# 处理客户端发来的消息async def client_message(self, event):message = event['message']print("发送消息。。。",message)# 发送消息到 WebSocketawait self.send(text_data=json.dumps({'message': message}))以上代码部分说明:1.self.scope是单个连接传入的详细信息,其中包含了请求的session、以及django认证系统中的用户信息等;2.async...await 是python3.5之后的新异步特性,基于asyncio模块;四、发起webscoket请求利用js发起websocket请求function InitWebSocket() {var websocket = new WebSocket('ws://' + window.location.host + '/ws/deploy/tasks/' );websocket.onmessage = function (e) {var data = JSON.parse(e.data);var message = 'n' + data['message'];document.querySelector('#deploy-res').innerText += (message + 'n');};} 五、发送消息到channel无论是消息的推送或者消息的接受,都是经过channel layer进行传输,以下是发送消息示例,from channels.layers import get_channel_layerfrom asgiref.sync import async_to_syncchannel_layer = get_channel_layer()def send_channel_msg(channel_name, msg):"""send msg to channel:param channel_name::param msg::return:"""async_to_sync(channel_layer.group_send)(channel_name,{"type": "deploy.run", "text": msg})六、生产部署大多数django的应用部署方式都采用的是nginx+uwsgi进行部署,当django集成channels时候,由于uwsgi不能处理websocket请求,所以我们需要asgi服务器来处理websocket请求,官方推荐使用daphne。下一篇文章将介绍nginx+supervisor+daphne+uwsgi进行生产部署。   
django+uwsgi+daphne+supervisor生产环境部署
一、前言在上一篇文章中项目中使用了webscoket进行实时通讯,但是生产环境又使用了django+nginx+uwsgi的部署方式,我们都知道uwsgi并不能处理websocket请求,所以需要asgi服务器来处理websocket请求,官方推荐的asgi服务器是daphne,下面将介绍详细的部署步骤。 二、软件安装之前已经写过一一篇关于django+nginx+uwsgi的部署方式地址:https://www.cnblogs.com/wdliu/p/8932816.html,这里就不多说了,以下介绍daphne、supervisor、以及nginx代理websocket的安装配置。1.部署daphne项目配置文件目录(wsgi.py同级)下创创建文件asgi.py,加入应用:"""ASGI entrypoint. Configures Django and then runs the applicationdefined in the ASGI_APPLICATION setting."""import osimport djangofrom channels.routing import get_default_applicationos.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")django.setup()application = get_default_application()启动daphne 测试是否正常运行(成功以后退出)daphne -p 8001 devops.asgi:application 2.安装supervisorsupervisor是由python实现的一个进程管理工具,可以确保所管理的进程一直运行,当进程一点中断supervisord会自动进行重启。安装步骤:#yum安装:yum install python-setuptoolseasy_install supervisor或者yum install -y epel-releaseyum install -y supervisor#手动安装:wget https://pypi.python.org/packages/source/s/supervisor/supervisor-3.1.3.tar.gztar zxf supervisor-3.1.3.tar.gzcd supervisorpython setup.py install#pip安装:pip install supervisor生成配置文件echo_supervisord_conf > /etc/supervisord.conf 3.使用supervisor管理daphne进程编辑/etc/supervisord.conf加入配置[program:daphne]directory=/opt/app/devops #项目目录command=daphne -b 127.0.0.1 -p 8001 --proxy-headers devops.asgi:application #启动命令autostart=trueautorestart=truestdout_logfile=/tmp/websocket.log #日志redirect_stderr=true启动supervisorsupervisord -c /etc/supervisord.conf启动或者停止daphnesupervisorctl start daphnesupervisorctl stop daphne 三、代理webscoket修改nginx配置文件#####转发配置upstream wsbackend {server 127.0.0.1:8001;}######location配置location /ws/deploy {proxy_pass http://wsbackend;proxy_http_version 1.1;proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection "upgrade";proxy_redirect off;proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Host $server_name;} 
python的Web框架:初识Django
 web应用程序 本质 socket服务端 浏览器本质是一个socket客户端1. 服务器程序 socket请求 接受HTTP请求,发送HTTP响应。比较底层,繁琐,有专用的服务器软件,如:Apache Nginx2. 应用程序,实现具体逻辑WSGI:应用程序和服务器程序之间的标准符合WSGI:需要开发者实现一个响应environ:包含了所有HTTP请求信息的dict对象start_response:一个发送HTTP响应的函数简单的符合WSGI的样式code1 def app(environ, strat_response):2 strat_response('200 OK',[('Content-Type','text/html')])3 return[b'<h1>Hello world</h1>'] web 应用框架的设计模式 MTY MVCMTV:M:models,模型:负责业务数据对象与数据库对象T:templates,模板:负责如何把数据展示给用户V:views,视图:负责业务逻辑 MVC:M:models,模型:负责业务数据对象与数据库对象V:views,视图:负责与用户交互的页面C: 控制器:负责接收用户的输入调用模型与视图**都是为了解耦**Django工作准备1、环境准备:  - Ubuntu  - python 3.5 +  - pycharm 2017.1.5 +  - MySQL2、python虚拟环境的重要性  - 什么是python虚拟环境    通过工具,将系统上的python复制了一份  - 为什么要用虚拟    1.不会污染系统环境    2.不同的项目环境的隔离- 创建虚拟环境linux-code:提供的Ubuntu设置好了,默认会创建在根目录下的virtualenvs目录下whereis <appname> 查看软件安装的路径1.查看当前有哪些虚拟环境: workon2.创建虚拟环境: mkvirtualenv -p /usr/bin/python3(path) envname3.进入虚拟环境: workon envname4.退出虚拟环境: deactivate5.删除虚拟环境: rmvirtualenv envname 3、Django安装pip install django4、简单操作1.创建项目django-admin startproject <project name>查看 crm文件(可能需要安装): tree项目根目录:manage.py项目内文件的作用:manage:Django的命令行工具settings:项目的配置文件。urls:路由申明wsgi:接口2.启动项目:项目的根目录下运行python manage.py runserver 0.0.0.0:8000如果访问不了,需要再settings内配置:ALLOWEN_HOSTS = ['*'],代表允许的域名此时可以在网页上打开127.0.0.1::8000,会出现Django的界面,说明配置成功5.manage管理项目创建应用,在根目录下创建应用python manage.py startapp teacher(应用名称) code区域新建views.py文件,并写上第视图函数1 from django.http import HttpResponse23 视图函数4 def index(request):5 return HttpResponse('hello,这是我的第一个django项目!!') 在urls.py内操作导入视图,添加一条路由1 from . import views23 urlpaterns = [4 path('index/', views.index)5 #index后需要加上/,后面参数对应的views内的函数6 ]  现在可以在浏览器打开对应的地址,http://127.0.0.1:8000/index打开后可以看到下面这段文字。说明我们的代码没有问题。 hello,这是我的第一个django项目!! 初识Django,到此。                   
python的Web框架:Django路由系统以及模板导入
Django的路由系统当一个请求来到时当一个请求来到时1、首先到项目目录下的urls.py(根URLconf模块)中,查找路由规则;2、根URELcof模块,里面定义了 urlpatterns 变量;3、urlpatterns是一个(django.urls.path, dango.urls.re_path 对象)列表;4、按顺序运行没个url模式,在第一个匹配的模式停止;5、一旦匹配,django导入并调用给定的视图;6、如果中间出错,或者没有匹配到,返回404 path方法,及默认参数默认参数需要配置的是路由(route),视图(view),kwargs和name是可选参数1 - path(route, view, kwargs=None, name=None)2 - route 是一个 字符串的url规则3 - view 是个视图4 - kwargs 额外参数,传递给view, 必须是一个字典5 - name url的命名 正则表达式 re_path,可以配置需要的格式,如日期等1 - re_path(route, view, kwargs=None, name=None)23 python 中正则表达式的分组命名 : (?<name>pattern)45 例:6 re_path(r'students/(?P<year>d{4})/(?P<mouth>[0-9]|1[0-2])', views.students) 在url中捕获参数,参数的传递在path中配置1 - 在url中捕获参数2 在url规则中使用<变量名>可以捕获url中的值3 path('index/<变量名(key1)>/<变量名(key2)>', view.index)4 变量名还可以使用-连接: <key>-<key>56 views.py内的函数需要写上参数,需要一一对应7 def detail(request, key1, key2)89 传递给视图10 ** 捕获的值是'字符串'1112 在浏览器输入的时候也是相对应的:127.0.0.1:8000/index/'key1'/'key2'/ 路径转换器    - 路径转换器 例子 <int:pk>常用的转换器:- str 匹配除了'/'路径分隔符之外的所有字符串- int 匹配任意整数- slug 匹配任意ASCII字符,加上连字符和下划线- uuid 格式化id- path 匹配任意非空字符例:path('index/<(int:key)>/', view.inedex)   django 搜索 url搜索的是什么?只搜索路径部分,和参数,已经请求方法(get, post)无关同一个url 可以匹配 get,post 包含其他URLconfs,在项目中添加(app 应用)下级urls的方法,include1 include方法2 from django.urls import include34 path('teacher/', include('teacher.urls'))56 同样也可以接受参数,但接受参数后,teacher下面的app都默认接受参数,会影响到views内的函数,也需要添加参数。所以需要谨慎使用。 传递额外参数当kwargs中的key与url捕获中的key一致的时候,以kwargs为准1 path,re_path 方法中,传递一个kwargs的字典参数23 kwargs={key:value} 默认参数45 *****当kwargs中的key与url捕获中的key一致的时候,以kwargs为准*****6 如果是在项目主目录下的urls中include里添加,则是给项目下的某个app下面的所有urls添加额外默认参数78 include同上代码9 path('teacher/', include('teacher.urls'),kwargs={'key':'value'} url命名,重定向1 from django.shortcuts import render, redirect, reverse2 views.py文件内操作34 重定向5 在函数内返回: return redirect('http://www.baidu.com')6 跳转7 return redirect('/teacher/index/') 硬编码89 在urls文件内path内定义name参数10 path('home/', views.index, name='index')1112 动态解析重定向13 在views文件函数内需要添加一个变量1415 url = reverse('index') 关键操作只要reverse的内容正确,url内的path内的路径可以动态变动16 return redirect(url) 1 app_name2 模块内的命名,防止混淆,写在urls文件内3 app_name = 'teacher'4 在views文件内的函数内添加或者修改reverse5 def login(request):6 url = reverse('teacher:index')78 重定向9 redirect(url) HTML模板导入setting.py中设置模板的导入1 在TEMPLATES中的 'DIRS':[os.path.join(BASE_DIR,'templates(html模板位置的文件夹名)'] 渲染 views导入12 第一种写法34 from django.template.loader import get_template56 #def index(request):7 tp = get_template('teacher/index.html') #获取模板下的文件8 html = tp.render() #渲染html页面9 return HttpResponse(html) 返回html页面  1 第二种写法(快捷方式:推荐)23 from django.shortcuts import render45 #def index(request):6 return render(request, 'teacher/index.html')  此处完,谢谢观看…… 
python的Web框架,Django模板变量,过滤器和静态文件引入
HTML模板的路径查找在setting中设置查找路径:1 #默认的查找在此处填写,优先级最高,为在manage.py的同级路径中,添加(常规是template)文件夹,在(template)文件夹中配置HTML文件23 默认路径地址:4 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))1 TEMPLATES = [2 {3 'BACKEND': 'django.template.backends.django.DjangoTemplates',45 #优先查找的路径'DIR',对其进行拼接。6 'DIRS':[os.path.join(BASE_DIR,'templates')]78 #如果此处为True,则会查找INSTALLED_APPS内注册的目录路径下,会在app下的templas文件夹中寻找9 'APP_DIRS':Ture1011 'OPTIONS': {12 'context_processors': [13 'django.template.context_processors.debug',14 'django.template.context_processors.request',15 'django.contrib.auth.context_processors.auth',16 'django.contrib.messages.context_processors.messages',17 ],18 },19 },20 ] 如果APP_DIRS为True,则会在'DIRS'找完之后,在'INSALLSE_APPS'的列表目录下寻找1 INSTALLED_APPS = [2 #注册目录3 'teacher.apps.TeacherConfig'4 'teacher':可以如此简写56 'django.contrib.admin',7 'django.contrib.auth',8 'django.contrib.contenttypes',9 'django.contrib.sessions',10 'django.contrib.messages',11 'django.contrib.staticfiles',12 ] 只要找到了符合的模板就返回,不再向下继续找了,所以优先级和路径很重要 静态页面,动态页面静态页面是没有改变,获取到网页后,是固定样式的; 动态页面是和服务器有交互的页面,在与服务器交互后,返回的页面会有不同的页面返回给浏览器context 模板变量view中传递数据给html页面,命名规则不能以下划线开头,其他同python,变量的值可以是任何数据类型context={'key':value}例:#view文件内配置传送:def index(request):now = datetime.now()return render(request, 'index.html',context={'nows':now#此处对应的字典传送给index.html})# index模板文件内配置接受:<body><h2>当前时间为:{{nows}}</h2>#此处的nows是对应的view文件中的key</body> 时间格式设置,设置的时间格式并不是我们想要的时区时间,就需要修改时间的参数。setting中设置:1 TIME_ZONE = 'Asia/Shanghai' 模板变量各种方法:html接受的语法及输出的结果:下列的列表、字典、函数等需要再views文件中配置才可以接受1 <body>2 列表:lt = [1,2,3]3 <p>我是一个列表:{{lt}}</p>4 输出:[1,2,3]5 <p>我是列表中的第二个值:{{lt.2}}</p>6 输出:2789 函数:10 <p>我是一个函数:{{func}}</p>11 输出:函数的结果121314 字典:dt = {'name':'hong','age':18,'items':'abc'}15 <p>我是一个字典:{{dt}}</p>16 输出:{'name':'张三','age':18}17 <p>我是字典的一个值:{{dt.name}}</p>18 输出:张三19202122 其他异议的字典输出:23 <p>调用字典的一个方法{{dt.items}}</p>24 输出:abc; 如果字典中没有'items'这个键,则返回的是items的这个字典方法25 1.首先键值对查找;然后属性或方法调用26272829 总结:30 1、计算变量,将其替换为结果31 2、遇到(.)的时候,按照以下顺序查找:32 -1.字典的键值对33 -2.属性或者方法查找34 -3.数字索引查找35 3、如果结果是可调用的,则调用它时不带参数,调用的结果为模板的值3637 **渲染失败返回空**38 </body> 模板过滤器传递的参数例子,为一下内容提供参数例子1 now = datetime.now()23 lt = [1,2,3]45 dt = {'name':'张三','age':18,'items':'aBc','text':'i an hua'}67 def func()8 return '我是一个函数' 日期,时间的格式化date,time以上列代码传送的参数 时间now为例:1 <body>2 <p>当前日期时间{{now|date:"Y年m月d日 H时i分s秒"}}</p>3 <p>当前时间{{now|time:"H时i分s秒"}}</p>4 </body>输出格式的结果为:1 当前日期时间2019年02月20日 11时53分11秒2 当前时间11时53分11秒 date和time过滤器格式Y:四位数的年。如:1999y:两位数的年。如:99m:两位数的月。如:01, 09n:一位数的月。如:1, 9, 12d:两位数的日。如:01, 09, 31j:一位数的日。如:1, 9, 12g:12小时制的一位数小时。如:1, 9 ,12G:24小时制的一位数小时。如:0, 8 ,23h:12小时制的两位数小时。如:01, 09, 12H:24小时制的两位数小时。如:01, 13, 24i:分钟。从00-59s:秒。从00-59 add:将参数与值相加,首先尝试转换成整数相加,如果失败,则尝试其他多有可能,{{value|add:'value'}}1 <p>1列表值的相加:{{ lt.1|add:'3' }}</p>23 <p>2列表值的相加:{{ lt.1|add:'3.5' }}</p>4 <p>2列表值的相加:{{ lt.1|add:3.5 }}</p>56 <p>函数的值为{{ func|add:'haha' }}</p> 输出结果1 1列表值的相加:523 2列表值的相加:4 2列表值的相加:55 #add后面的值如果是str类型不是整数则渲染失败,返回为空,如果是int类型则会转成整数再相加67 3函数的值为:我是一个函数haha capfirst:首字母大写1 <p>首字母的大小写方法:{{ dt.items|capfirst }}</p> 输出结果1 首字母的大小写方法:ABc default:如果变量解析失败,则返回给定的默认值,当value是''空字符串,也会输出默认值。1 例子2 def func(aa):3 return '带参数的函数'4 def func():5 return '''6 def func(aa):7 return None8910 <p>解析失败则返回默认值:{{ func|default:"nothing" }}</p>输出结果1 解析失败则返回默认值:nothing first,last:第一个元素和最后一个元素1 <p>列表的第一个元素:{{ lt|first }}</p>2 <p>列表的最后一个元素:{{lt|last}}</p> 输出结果1 列表的第一个元素:12 列表的最后一个元素:3 slice:切片1 <p>我是列表的倒序:{{ lt|slice:"::-1"}}</p> 输出结果:1 我是列表的倒序:[3,2,1] join:连接字符串列表,与str.join(list)一样1 <p>把字典的value连接起来:{{ dt.name|join:"xxx"}}</p> 输出结果1 把字典的value连接起来:张xxx三 floatformat:浮点数格式化,不指定小数位参数,默认保留一位value Template Output(结果)34.23234 {{ value|floatformat}} 34.234.23234 {{ value|floatformat:3}} 34.232 length,length_is:返回字符串或列表的长度1 <p>列表的长度是:{{ lt|length}}</p>2 <p>列表的长度是{{ le|length }}?:{{ lt|length_is:3 }}</p> 输出结果1 列表的长度是:32 列表的长度是3吗?:True lo
python的Web框架,Django模板标签及模板的继承
模板标签在传递数据的时候,会有大量的数据展示在浏览器上,而数据会是动态变化的,在html的编写中,数据也是需要动态的,而不能是写死的,如果动态展示呢。给定的例子数据views传递数据给html1 from django.shortcuts import render23 def index(request):45 students = [6 {'id':12, 'name':'张三', 'age':19, 'sex':'男'}7 {'id':22, 'name':'李思', 'age':19, 'sex':'女'}8 {'id':25, 'name':'王五', 'age':19, 'sex':'男'}9 {'id':43, 'name':'赵柳', 'age':19, 'sex':'女'}10 {'id':88, 'name':'孙奇', 'age':19, 'sex':'男'}11 ]1213 return render(request,'teacher/index.html',context={14 'students':students,15 }) for循环标签1 <body>2 <table>3 <tr>4 <th>序列</th>5 <th>姓名</th>6 <th>年龄</th>7 <th>性别</th>8 </tr>910 <!--#for循环写在{% %} 内-->11 {% for stu in students %}12 <tr>13 <!--#把循环迭代的数据,用可以取出值即可-->14 <td>{{ stu.id}}</td>15 <td>{{ stu.name }}</td>16 <td>{{ stu.age }}</td>17 <td>{{ stu.sex }}</td>18 </tr>19 <!--#结尾需要end-->20 {% endfor %}21 </table>22 </body> 输出的结果为: css样式有过调整,请忽略样式  forloop:序号排列1 <body>2 <table>3 <tr>4 <th>从1开始的正向排序</th>5 <th>从0开始的正向排序</th>6 <th>以1结尾的倒序</th>7 <th>以0结尾的倒序</th>8 </tr>9 {% for stu in students %}10 <tr>11 <!--序号排列各式样式-->12 <td>{{ forloop.counter}}</td>13 <td>{{ forloop.counter0 }}</td>14 <td>{{ forloop.revcounter }}</td>15 <td>{{ forloop.revcounter0 }}</td>16 </tr>17 {% endfor %}18 </table>19 </body> 输出的结果为:  if:if判断1 <body>2 <table>3 <tr>4 <th>从1开始的正向排序</th>5 <th>从0开始的正向排序</th>6 <th>以1结尾的倒序</th>7 <th>以0结尾的倒序</th>8 </tr>9 {% for stu in students %}1011 <!--if判断和for循环一样,也有结尾的endif,条件写在中间。-->12 == 两头需要空格13 <tr {% if stu.sex == '女' %}style="color:red"{% endif %}>14 <td>{{ forloop.counter}}</td>15 <td>{{ stu.name }}</td>16 <td>{{ stu.age }}</td>17 <td>{{ stu.sex }}</td>18 </tr>19 {% endfor %}20 </table>21 </body> 输出结果为:  简单获取urlviews简单配置点击id所展示的页面1 def st_id(request, pk):2 return HttpResponse('学生ID为%s的详情页' % pk) 对序号设置点击的跳转url1 <body>2 <table>3 <tr>4 <th>从1开始的正向排序</th>5 <th>从0开始的正向排序</th>6 <th>以1结尾的倒序</th>7 <th>以0结尾的倒序</th>8 </tr>9 {% for stu in students %}10 <tr {% if stu.sex == '女' %}style="color:red"{% endif %}>1112 <!--因为要点击url,所有把标签放在a标签内,在路径的配置上,写上路径然后把字典中的id做动态匹配就会得到需要的id。-->13 <td><a href="/teacher/st_id/{{ stu.id }}">{{ forloop.counter}}</a></td>14 <td>{{ stu.name }}</td>15 <td>{{ stu.age }}</td>16 <td>{{ stu.sex }}</td>17 </tr>18 {% endfor %}19 </table>20 </body> 输出结果:  点击序列为1的网址显示: url标签:动态获取url,返回一个命名(views中path里面的name的值)了的URL的绝对路径1 <body>2 <table>3 <tr>4 <th>从1开始的正向排序</th>5 <th>从0开始的正向排序</th>6 <th>以1结尾的倒序</th>7 <th>以0结尾的倒序</th>8 </tr>9 {% for stu in students %}10 <tr {% if stu.sex == '女' %}style="color:red"{% endif %}>1112 通过url标签,动态生成对应的url网址,此处的'teacher:st_id'13 **需要在views中配置其中的path的name,name和此处的value需要一一对应。**14 <td><a href="{% url 'teacher:st_id' stu.id %}">{{ forloop.counter}}</a></td>15
python的Web框架,Django自定义过滤器及标签
代码布局有的时候框架给的过滤器不够用,需要自定义一些过滤器,所以就需要我们自己来定义一些过滤器等自定义代码放置的路径某个app特用(独有)的 - app 目录下的 templatetags文件夹内(文件夹内需要有__init__.py文件)公用的 - 再创建一个新的app,然后在新的app中创建templatetags(固定的单词写法)文件夹,但是需要在setting文件中的INSTALL_APPS中注册,然后就可以应用了 自定义模板过滤器定义过滤器在templatetags的文件夹下创建py文件(一般是叫customer_filters),过滤器写在文件内:模板过滤器是一个函数,有一个或者两个参数- 第一个参数,是传递进来的模板变量- 第二个参数,是普通的参数,也可以是默认的,也可以不要1 过滤器范式:2 def func(value,arg='zh'):3 map ={4 'zh':('女','男'),5 'en':('female','male')6 }7 return map[arg][value]89 实现需要返回的值 注册通过 django.template.Library 它的实例的filter 方法1 filter 有两个参数:2 - name:过滤器的名称,是个字符串,可以不写,默认使用方法名(函数名)作为过滤器的名称3 - filter_func 定义的过滤器的函数45 #导包6 from django.template import Library78 #生成实例9 register = Library() 第一种方法:1 def func():2 return '我是一个例子'34 #把函数丢进来。即注册成功,可以简写成register.filter(func)5 register.filter('funcname',func)6 #第一个参数表示调用的name,第二个参数表示函数名 第二种方法:通过装饰器方法来注册1 #name这个参数可以不写,默认调用函数名2 @register.filter(name=funcname)3 def func()4 return '我是一个例子' 模板中使用第一步,需要先load一下,通过python模板名,在网页中load在templatetags文件夹下的这个过滤器函数的py文件 需要查看下settings.py文件中的INSTALLED_APPS 是否有注册,如没有就需要注册。1 {% load customer_filters %} 自定义模板标签简单标签: django.template.Library.simple_tag()定义简单标签 simple_tag在templatetags的文件夹下创建py文件(一般是叫customer_tags)1 简单标签范式:显示当前时间的标签2 from datetime import datetime34 #注册方法5 from django.template import Library6 register = Library()78 #当前时间的函数标签9 def current_time(format):10 return datetime.now().strftime(format) 注册的第一种方式:1 #注册简单标签:第一种方式注册2 register.simple_tag(current_time, name='current') 注册的第二种方式:1 #注册简单标签:第二种方式注册,装饰器方式的写法,写在函数上即可2 @register.simple_tag(name='current') 在html中使用1 #load下templatetags的文件夹下创建的标签函数的py文件名2 {% load customer_tags%} 1 #应用简单标签,名字和参数用空格隔开即可,后面是参数2 {% current '%Y-%m-%d %H:%M:%S %}  引用上下文变量,此处简单标签的参数,也可以使用成views中render传递到模板中的context里面的数据。 需要在simple_tag中,设置参数take_context=True1 标签函数的文件中,注册的时候添加takes_context参数2 @register.simple_tag(name='current',takes_context=True)34 在定义函数的时候,第一个参数必须且一定是context;5 def current_time(context,format_str): inclusion包含标签 inclusion-包含标签,通过渲染另外一个html模板来展示数据 django.template.Library.inclusion_tag()需要创建一个html模板文件,可以创建在templates文件夹下的同级html文件中,也可以在其下面创建一个新的文件夹来存放html模板。1 渲染的html模板2 <ul>3 <!--这里的ls对应的下面函数返回标签内的key-->4 {% for i in ls %}5 <li>{{ i }}</li>6 {% endfor %}7 </ul> 内容写在简单标签customer_tags的文件中1 #注册方法2 from django.template import Library3 register = Library()45 #注册,并把需要渲染的模板丢进来,6 @register.inclusion_tag('teacher/show_list_as_ul.html')78 def show_list_as_ur(value):9 return {'ls':value} 在html中使用1 #load在templatetags的文件夹下创建的标签函数的py文件名2 {% load customer_tags%}34 #应用简单标签,名字和参数用空格隔开即可,后面是参数5 {% show_list_as_ul stu.course %} 例子: 原本的课程展现方式  通过包含标签渲染后: 根据自定义的方式展现出来。  带参数的包含标签 inclusion:标签函数1 from django.template import Library2 register = Library()34 @register.inclusion_tag('teacher/show_list_as_ul.html')5 def show_list_as_ur(value, style):6 return {'ls':value,'style':style}html模板语法:1 函数内的参数在此处的对应2 {% if style == 'button' %}3 <div class="list-gtoup">4 {% for i in ls %}5 <button type="button" class="list-group-item">{{ i }}<button>6 {% endfor %7 </div>8 {% endif %}910 #还可以写上elif和else11 {% endif %} html中的用法标签函数+空格+数据+空格+标签中的参数<td>{% show_list_as_ul stu.course ''%}</td>渲染之后的效果:  
python的Web框架,Django的ORM,模型基础,MySQL连接配置及增删改查
Django中的ORM简介ORM概念:对象关系映射(Object Relational Mapping,简称ORM):用面向对象的方式描述数据库,去操作数据库,甚至可以达到不用编写SQL语句就能够对数据库进行增删改查,进行各种操作。我们只需要对python的面向对象熟悉,就可以很清晰的知道各种数据之间的关系。 django模型映射关系:  数据库连接配置Django支持主流的数据库,都有配置,在setting中配置即可,下面我们看下如何配置MySQL。db.sqlite3文件的说明db.sqlite3文件也是数据库文件,Django默认情况下,会配置这个文件,这个文件很小,很多单机、net的用的是这个  Django 连接MySQL的配置流程:安装 pymysql,用于python操作mysql数据库pip install pymysql创建数据库用户,需要有创建数据库权限的用户创建数据库#进入数据库,此命令用的root账户。正常是用数据库管理员给的数据库账号>>>(django) pyvip@Vip:~$ mysql -uroot -pqwe123#创建一个叫crm的数据库>>>mysql> create database crm;Query OK, 1 row affected (0.00 sec)修改setting配置,1 默认的是:'ENGINE': 'django.db.backends.sqlite3',需要修改为mysql2 DATABASES = {3 'default': {4 'ENGINE': 'django.db.backends.mysql',5 'NAME': 'crm',6 'USER': 'root',7 'PASSWORD': 'qwe123',8 'HOST': '127.0.0.1',9 #POST官方推荐的是字符串。10 'POST': '3306',11 }12 } 修改项目文件夹(和setting.py文件所在的目录)下的__init__.py文件因为历史原因,python2用的模块是MySQLdb这个模块,所以需要如此写。#在__init__文件中配置1 import pymysql23 pymysql.install_as_MySQLdb() 设置时区1 setting.py文件中设置时区23 TIME_ZONE = 'Asia/ShangHai' #北京时间 模型的创建与映射(激活)定义模型模型类必须写在app下的models.py文件中。模型如果需要映射到数据库,所在的app必须被安装。一个数据表对应一个模型类,表中的字段,对应模型中的类属性。在models文件中创建模型:1 from django.db import models23 #必须继承'models.Model'4 class Students(models.Model):56 #模型会自动创建主键,djiano会自动创建一个字段为'id'的主键。7 id一般不用创建89 name = models.CharField(max_length=20)10 age = models.SmallIntegerField(default=0)11 sex = models.SmallIntegerField(default=1)12 qq = models.CharField(max_length=20, default='')13 phone = models.CharField(max_length=20, default='')14 c_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)1516 #__str__代码是为了在ipython调试中方便查看数据,对数据库不造成任何影响,不用做数据库迁移17 def __str__(self):18 return '%s-%s' % (self.name, self.age)解析:CharField:字符字段,必须提供max_length这个参数,CharField的defaul最好不要给None。SmallIntegerField:int类型max_length:长度default:默认值,可以给'',如果不填,就是默认的。auto_now_add=True 在创建对象的时候,自动把当前时间记录下来。verbose_name:第一个位置参数,人类看的,如果没有提供这个字典,则直接使用它的变量qq、phone给的CharField不用SmallIntegerField类型的,是因为字符串更好操作,不容易出错。 每一个模型都是django.db.models.Model的子类(类的继承)。每个模型都有许多的类变量,它会表示模型中的数据库字段。每一个字段都由一个字段类的实例来表示。激活模型:在项目中注册app1 INSTALLED_APPS = [2 'teacher.apps.TeacherConfig',3 ]运行数据库迁移命令(一定要在项目根目录下) 告诉django,我们做了哪些数据库的更改'python manage.py makemigrations teacher'#如果不写teacher这个,则是运行INSTALLED_APPS下面所有注册的app>>> (django) pyvip@Vip:~/code/crm$ python manage.py makemigrations teacherMigrations for 'teacher':teacher/migrations/0001_initial.py- Create model Students 运行成功后,会生成一个数据库迁移文件现在还没有真正的操作数据库 sqlmigrate,从迁移获取SQL语句'python manage.py sqlmigrate teacher 0001'>>>(django) pyvip@Vip:~/code/crm$ python manage.py sqlmigrate teacher 0001BEGIN;---- Create model Students--CREATE TABLE `teacher_students` (`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `name` varchar(20) NOT NULL, `age` smallint NOT NULL, `sex` smallint NOT NULL, `qq` varchar(20) NOT NULL, `phone` varchar(20) NOT NULL, `c_time` datetime(6) NOT NULL);COMMIT;#创建的表名是:appname_模型name.lower(小写) 运行migrate命令,使迁移生效#这里的teacher不写则默认生成所有的表'python manage.py migrate teacher'>>>(django) pyvip@Vip:~/code/crm$ python manage.py migrate teacherOperations to perform:Apply all migrations: teacherRunning migrations:Applying teacher.0001_initial... OK#执行了teacher.0001_initial这个里面的tables。 我们查看下是否创建了>>>mysql> show tables;+-------------------+| Tables_in_crm |+-------------------+| django_migrations || teacher_students |+-------------------+2 rows in set (0.00 sec)这里生成了两张表:第一张表用来记录django的migrations第二张表是我们需要的数据表 查看表结构:>>>mysql> desc teacher_students;+--------+-------------+------+-----+---------+----------------+| Field | Type | Null | Key | Default | Extra |+--------+-------------+------+-----+---------+----------------+| id | int(11) | NO | PRI | NULL | auto_increment || name | varchar(20) | NO | | NULL | || age | smallint(6) | NO | | NULL | || sex | smallint(6) | NO | | NULL | || qq | varchar(20) | NO | | NULL | || phone | varchar(20) | NO | | NULL | || c_time | datetime(6) | NO | | NULL | |+--------+-------------+------+-----+---------+----------------+7 rows in set (0.00 sec)#此处表可以看到会根据我们的定义的模型来创建。 MySQL数据库的增删改查django shell调试工具在进入之前建议安装ipython(pip安装),使用会更方便进入到调试工具>>>python manage.py shell 增加#导入模型#导入我们需要调试的数据模型>>>from teacher.models import Students 查询模型中是否有数据objects是模型的一个属性(方法),object的管理器>>>Students.objects<django.db.models.manager.Manager at 0xb2e0fe0c> 查询模型中的所有的值>>>Students.objects.all()<QuerySet []>#此时我们还未添加数据,所以此处为空返回的值是QuerySet的一个查询集第一种方式,创建一条数据有default的不写默认是他自定的数据。>>>s1 = Students(name=
python的Web框架,Django模型系统二,模型属性,及数据库进阶查询
原始数据接上篇文章来操作。可能需要查看后才能懂。点击这里查看1.常用的模型字段类型官方文档:https://docs.djangoproject.com/en/2.1/ref/models/fields/#field-types定义的模型中,类名对应的表名,类属性对应的表的字段,我们在上节内容有说过,可以查看。这里我们详细了解。  django和mysql的对应类型,models.Model下的方法django类型映射到mysql类型IntegerField : 整型映射到数据库中的int类型。CharField: 字符类型映射到数据库中的varchar类型,通过max_length指定最大长度。TextField: 文本类型映射到数据库中的text类型。BooleanField: 布尔类型映射到数据库中的tinyint类型,在使用的时候,传递True/False进去。如果要可以为空,则用NullBooleanField。DateField: 日期类型没有时间。映射到数据库中是date类型在使用的时候,可以设置DateField.auto_now每次保存对象时,自动设置该字段为当前时间。设置DateField.auto_now_add当对象第一次被创建时自动设置当前时间。DateTimeField: 日期时间类型映射到数据库中的是datetime类型,在使用的时候,传递datetime.datetime()进去。2.字段的常用参数官方文档:https://docs.djangoproject.com/en/2.1/ref/models/fields/#field-optionsfield常用参数参数名作用primary_key:指定是否为主键。unique:指定是否唯一。null:指定是否为空,默认为False。blank:等于True时form表单验证时可以为空,默认为False。default:设置默认值。DateField.auto_now:每次修改都会将当前时间更新进去,只有调用,QuerySet.update方法将不会调用。这个参数只是Date和DateTime以及TimModel.save()方法才会调用e类才有的。DateField.auto_now_add:第一次添加进去,都会将当前时间设置进去。以后修改,不会修改这个值主键的设置第一种方式设置主键,指定int类型,设置为主键,使用自增长。id = models.IntegerField(primary_key=True, auto_created=True)第二种方式:使用AutoField方法,这个方法是自增长且为int类型id = models.AutoField(primary_key=True使用AutoField方法,这个方法是自增长且为int类型) unique设置唯一,注意的事项1 qq = models.CharField(max_length=20, unique=True, null=True)当你设置的这个字段是唯一了,就代表必须和其他的不一样,但是有的时候用户没有提供,可以不填的,就可以为空,不然就会出错3.常用查询管理器理解objects是Students的对象,是django.db.models.manager.Manager的一个实例。>>>Students.objects<django.db.models.manager.Manager at 0xb36edaec> QuerySet表示数据库中对象的集合,可以等同于select的语句。它是惰性的。单条数据查询:排序:默认按照主键id排序,可以通过模型中的_meta属性设置排序问题。first 获取第一条,返回的是一个对象,默认按照主键id排序>>>Students.objects.first()<Students: 小明-16> last 获取最后一条,默认按照主键id排序>>>Students.objects.last()<Students: 刘一-19> get(**kwargs) 根据给定的条件,获取一个对象,如果有多个对象符合,则会报错。>>>Students.objects.get(name='刘一')<Students: 刘一-19> 多条数据查询all() 获取所有记录,返回的是QuerySet>>> Students.objects.all()<QuerySet [<Students: 小明-16>, <Students: XiaoHong-16>, <Students: 王五-24>, <Students: 赵柳-22>, <Students: 张三-23>, <Students: 李思-17>, <Students: 赵柳-19>, <Students: 孙奇-29>, <Students: Ats: abc-6>, <Students: 刘一-19>]> filter(**kwargs):过滤,根据给定的条件,获取一个过滤后的QuerySet,多个条件的QuerySet语句是and连接>>>res = Students.objects.filter(sex=1,age=16)>>>print(res.query)SELECT `teacher_students`.`id`, `teacher_students`.`name`, `teacher_students`.`age`, `teacher_students`.`sex`, `teacher_students`.`qq`, `teacher_students`.`phone`, `teacher_students`.`c_time` FROM `teacher_students` WHERE (`teacher_students`.`age` = 16 AND `teacher_students`.`sex` = 1) exclude(**kwargs) 排除,和filter使用方法一致,作用相反,根据给定的条件,获取一个排除后的QuerySet,可以多个条件>>>res = Students.objects.exclude(sex=1)>>>print(res.query)SELECT `teacher_students`.`id`, `teacher_students`.`name`, `teacher_students`.`age`, `teacher_students`.`sex`, `teacher_students`.`qq`, `teacher_students`.`phone`, `teacher_students`.`c_time` FROM `teacher_students` WHERE NOT (`teacher_students`.`sex` = 1) Q:或者,多条件查询,相当于MySQL中的or,这个方法需要单独导入需要导包from django.db.models import Q 语法: Q(*args) |>>> res =Students.objects.filter(Q(age=0)|Q(age=1))SELECT `teacher_students`.`id`, `teacher_students`.`name`, `teacher_students`.`age`, `teacher_students`.`sex`, `teacher_students`.`qq`, `teacher_students`.`phone`, `teacher_students`.`c_time` FROM `teacher_students` WHERE (`teacher_students`.`age` = 0 OR `teacher_students`.`age` = 1) values(*fields),字段查询。可以多个查询,返回一个QuerySet,返回一个字典列表,而不是数据对象>>> res = Students.objects.values('name')>>>print(res.query)SELECT `teacher_students`.`name` FROM `teacher_students`>>>res<QuerySet [{'name': '小明'}, {'name': 'XiaoHong'}, {'name': '王五'}, {'name': '赵柳'}, {'name': '张三'}, {'name': '李思'}, {'name': '赵柳'}, {'name': '孙奇'}, {'name': 'ABC'}, {'name': 'abc'}, {>>>>res[0]['name']'小明'#可以多条查询 res = Students.objects.values('name','age')#可以增加过滤 res = Students.objects.values('name').filter(age=0) only(*field) 返回QuerySet,是一个对象列表,不是字典,而且only一定包含主键字段。此方法用的更多些。因为是一个对象列表,所以可以有后期的其他操作,我们可以指定很少的字段后再后期继续获取,效率较高,还可以动态的拿到其他数据。>>> res = Students.objects.only('name')>>> print(res.query)SELECT `teacher_students`.`id`, `teacher_students`.`name` FROM `teacher_students`#会默认拿到id主键>>> res[0].c_timedatetime.datetime(2019, 2, 26, 8, 4, 57, 955584, tzinfo=<UTC>)#没有获取这个字段也一样可以拿到,这就是only的作用#其他写法:res = Students.objects.only('name','age').filter(age=16) defer(*fields) 返回一个QuerySet,和only一样用法,作用相反>>>res = Students.objects.defer('c_time','age')>>>print(res.query)SELECT `teacher_students`.`id`, `teacher_students`.`name`, `teacher_students`.`sex`, `teacher_students`.`qq`, `teacher_students`.`phone` FROM `teacher_students`>>>res<QuerySet [<Students: 小明-16>, <Students: XiaoHong-16>, <Students: 王五-24>, <S
Django:web框架本质
一,web框架本质我们可以这样理解:所有的Web应用本质上就是一个socket服务端,而用户的浏览器就是一个socket客户端。 这样我们就可以自己实现Web框架了。1,自定义web框架import socketsk = socket.socket()sk.bind(("127.0.0.1", 80))sk.listen()while True:conn, addr = sk.accept()data = conn.recv(8096)conn.send(b"OK")conn.close()2,HTTP协议每个HTTP请求和响应都遵循相同的格式,一个HTTP包含Header和Body两部分,其中Body是可选的。 HTTP响应的Header中有一个 Content-Type表明响应的内容格式。如 text/html表示HTML网页。1)HTTP GET请求的格式:GET /path HTTP/1.1header1:v1rnheader2:v2rn使用 rn分隔多个header2)HTTP POST请求格式:POST /path HTTP/1.1header1:v1rnheader2:v2rnrnrn请求体...当遇到连续两个 rnrn时,表示Header部分结束了,后面的数据是Body。3)HTTP响应的格式:200 OKHeader1:v1rnHeader2:v2rnrnrn响应体...3,根据不同的路径返回不同的内容"""根据URL中不同的路径返回不同的内容--函数版"""import socketsk = socket.socket()sk.bind(("127.0.0.1", 8080)) # 绑定IP和端口sk.listen() # 监听# 将返回不同的内容部分封装成函数def index(url):s = "这是{}页面!".format(url)return bytes(s, encoding="utf8")def home(url):s = "这是{}页面!".format(url)return bytes(s, encoding="utf8")while 1:# 等待连接conn, add = sk.accept()data = conn.recv(8096) # 接收客户端发来的消息# 从data中取到路径data = str(data, encoding="utf8") # 把收到的字节类型的数据转换成字符串# 按rn分割data1 = data.split("rn")[0]url = data1.split()[1] # url是我们从浏览器发过来的消息中分离出的访问路径conn.send(b'HTTP/1.1 200 OKrnrn') # 因为要遵循HTTP协议,所以回复的消息也要加状态行# 根据不同的路径返回不同内容,response是具体的响应体if url == "/index/":response = index(url)elif url == "/home/":response = home(url)else:response = b"404 not found!"conn.send(response)conn.close()4,返回具体的HTML页面"""根据URL中不同的路径返回不同的内容--函数进阶版返回HTML页面让网页动态起来"""import socketimport timesk = socket.socket()sk.bind(("127.0.0.1", 8080)) # 绑定IP和端口sk.listen() # 监听# 将返回不同的内容部分封装成函数def index(url):with open("index.html", "r", encoding="utf8") as f:s = f.read()now = str(time.time())s = s.replace("@@oo@@", now) # 在网页中定义好特殊符号,用动态的数据去替换提前定义好的特殊符号return bytes(s, encoding="utf8")def home(url):with open("home.html", "r", encoding="utf8") as f:s = f.read()return bytes(s, encoding="utf8")# 定义一个url和实际要执行的函数的对应关系list1 = [("/index/", index),("/home/", home),]while 1:# 等待连接conn, add = sk.accept()data = conn.recv(8096) # 接收客户端发来的消息# 从data中取到路径data = str(data, encoding="utf8") # 把收到的字节类型的数据转换成字符串# 按rn分割data1 = data.split("rn")[0]url = data1.split()[1] # url是我们从浏览器发过来的消息中分离出的访问路径conn.send(b'HTTP/1.1 200 OKrnrn') # 因为要遵循HTTP协议,所以回复的消息也要加状态行# 根据不同的路径返回不同内容func = None # 定义一个保存将要执行的函数名的变量for i in list1:if i[0] == url:func = i[1]breakif func:response = func(url)else:response = b"404 not found!"# 返回具体的响应消息conn.send(response)conn.close()5,服务器程序与应用程序对于真实开发中的python web程序来说,一般会分为两部分:服务器程序和应用程序。服务器程序负责对socket服务器进行封装,并在请求到来时,对请求的各种数据进行整理。应用程序则负责具体的逻辑处理。为了方便应用程序的开发,就出现了众多的Web框架,例如:Django、Flask、web.py 等。不同的框架有不同的开发方式,但是无论如何,开发出的应用程序都要和服务器程序配合,才能为用户提供服务。 这样,服务器程序就需要为不同的框架提供不同的支持。这样混乱的局面无论对于服务器还是框架,都是不好的。对服务器来说,需要支持各种不同框架,对框架来说,只有支持它的服务器才能被开发出的应用使用。这时候,标准化就变得尤为重要。我们可以设立一个标准,只要服务器程序支持这个标准,框架也支持这个标准,那么他们就可以配合使用。一旦标准确定,双方各自实现。这样,服务器可以支持更多支持标准的框架,框架也可以使用更多支持标准的服务器。WSGI(Web Server Gateway Interface)就是一种规范,它定义了使用Python编写的web应用程序与web服务器程序之间的接口格式,实现web应用程序与web服务器程序间的解耦。常用的WSGI服务器有uwsgi、Gunicorn。而Python标准库提供的独立WSGI服务器叫wsgiref,1)wsgiref"""我们利用wsgiref模块来替换我们自己写的web框架的socket server部分:根据URL中不同的路径返回不同的内容--函数进阶版返回HTML页面让网页动态起来wsgiref模块版"""import timefrom wsgiref.simple_server import make_server# 将返回不同的内容部分封装成函数def index(url):with open("index.html", "r", encoding="utf8") as f:s = f.read()now = str(time.time())s = s.replace("@@oo@@", now)return bytes(s, encoding="utf8")def home(url):with open("home.html", "r", encoding="utf8") as f:s = f.read()return bytes(s, encoding="utf8")# 定义一个url和实际要执行的函数的对应关系list1 = [("/index/", index),("/home/", home),]def run_server(environ, start_response):start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ]) # 设置HTTP响应的状态码和头信息url = environ['PATH_INFO'] # 取到用户输入的urlfunc = Nonefor i in list1:if i[0] == url:func = i[1]breakif func:response = func(url)else:response = b"404 not found!"return [response, ]if __name__ == '__main__':httpd = make_server('127.0.0.1', 8090, run_server)print("我在8090等你哦...")httpd.serve_forever()2)jinja2从数据库中查询数据,然后去替换我html中的对应内容,然后再发送给浏览器完成渲染。 这个过程就相当于HTML模板渲染数据。 本质上就是HTML内容中利用一些特殊的符号来替换要展示的数据,模板渲染的工具: jinja2from wsgiref.simple_server import make_serverfrom jinja2 import Templatedef index():with open("index2.html", "r") as f:data = f.read()template = Template(data) # 生成模板文件ret = template.render({"name": "Alex", "hobby_list": ["烫头", "泡吧"]}) # 把数据填充到模板里面return [bytes(ret, encoding="utf8"), ]def home():with open("home.html", "rb") as f:data = f.read()return [data, ]# 定义一个url和函数的对应关系URL_LIST = [("/index/", index),("/home/", home),]def run_server(environ, start_response):start_response('200 OK', [('Content-Type', '
Django:模板系统
一,常用语法只需要记两种特殊符号:{{  }}和 {% %}变量相关的用{{}},逻辑相关的用{%%}。二,常量{{ 变量名 }}变量名由字母数字和下划线组成。点(.)在模板语言中有特殊的含义,用来获取对象的相应属性值。view中代码def template_test(request):l = [11, 22, 33]d = {"name": "alex"}class Person(object):def __init__(self, name, age):self.name = nameself.age = agedef dream(self):return "{} is dream...".format(self.name)Alex = Person(name="Alex", age=34)Egon = Person(name="Egon", age=9000)Eva_J = Person(name="Eva_J", age=18)person_list = [Alex, Egon, Eva_J]return render(request, "template_test.html", {"l": l, "d": d, "person_list": person_list})模板中支持的写法:{# 取l中的第一个参数 #}{{ l.0 }}{# 取字典中key的值 #}{{ d.name }}{# 取对象的name属性 #}{{ person_list.0.name }}{# .操作只能调用不带参数的方法 #}{{ person_list.0.dream }}三,Filters语法: {{ value|filter_name:参数 }}default{{ value:default: "nothing"}}length{{ value|length }}'|'左右没有空格没有空格没有空格返回value的长度,如 value=['a', 'b', 'c', 'd']的话,就显示4.filesizeformat将值格式化为一个 “人类可读的” 文件尺寸 (例如 '13 KB', '4.1 MB', '102 bytes', 等等)。例如:{{ value|filesizeformat }}如果 value 是 123456789,输出将会是 117.7 MB。slice切片{{value|slice:"2:-1"}}date格式化{{ value|date:"Y-m-d H:i:s"}}safeDjango的模板中会对HTML标签和JS等语法标签进行自动转义,原因显而易见,这样是为了安全。但是有的时候我们可能不希望这些HTML元素被转义,比如我们做一个内容管理系统,后台添加的文章中是经过修饰的,这些修饰可能是通过一个类似于FCKeditor编辑加注了HTML修饰符的文本,如果自动转义的话显示的就是保护HTML标签的源文件。为了在Django中关闭HTML的自动转义有两种方式,如果是一个单独的变量我们可以通过过滤器“|safe”的方式告诉Django这段代码是安全的不必转义。比如:value = "<a href='#'>点我</a>"{{ value|safe}}四,truncatechars如果字符串字符多于指定的字符数量,那么会被截断。截断的字符串将以可翻译的省略号序列(“...”)结尾。参数:截断的字符数{{ value|truncatechars:9}}五,自定义filter自定义过滤器只是带有一个或两个参数的Python函数:变量(输入)的值 - -不一定是一个字符串参数的值 - 这可以有一个默认值,或完全省略例如,在过滤器{{var | foo:“bar”}}中,过滤器foo将传递变量var和参数“bar”。自定义filter代码文件摆放位置:app01/__init__.pymodels.pytemplatetags/ # 在app01下面新建一个package package__init__.pyapp01_filters.py # 建一个存放自定义filter的文件views.py编写自定义filterfrom django import templateregister = template.Library()@register.filter(name="cut")def cut(value, arg):return value.replace(arg, "")@register.filter(name="addSB")def add_sb(value):return "{} SB".format(value)使用自定义filter{# 先导入我们自定义filter那个文件 #}{% load app01_filters %}{# 使用我们自定义的filter #}{{ somevariable|cut:"0" }}{{ d.name|addSB }}六,Tagsfor<ul>{% for user in user_list %}<li>{{ user.name }}</li>{% endfor %}</ul>for循环可用的一些参数:VariableDescriptionforloop.counter当前循环的索引值(从1开始)forloop.counter0当前循环的索引值(从0开始)forloop.revcounter当前循环的倒序索引值(从1开始)forloop.revcounter0当前循环的倒序索引值(从0开始)forloop.first当前循环是不是第一次循环(布尔值)forloop.last当前循环是不是最后一次循环(布尔值)forloop.parentloop本层循环的外层循环for ... empty<ul>{% for user in user_list %}<li>{{ user.name }}</li>{% empty %}<li>空空如也</li>{% endfor %}</ul>if,elif和else{% if user_list %}用户人数:{{ user_list|length }}{% elif black_list %}黑名单数:{{ black_list|length }}{% else %}没有用户{% endif %}当然也可以只有if和else{% if user_list|length > 5 %}七座豪华SUV{% else %}黄包车{% endif %}if语句支持 and 、or、==、>、<、!=、<=、>=、in、not in、is、is not判断。with定义一个中间变量{% with total=business.employees.count %}{{ total }} employee{{ total|pluralize }}{% endwith %}七,csrf_token这个标签用于跨站请求伪造保护。在页面的form表单里面写上{% csrf_token %}注释{# ... #}八,模板<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="x-ua-compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Title</title>{% block page-css %}{% endblock %}</head><body><h1>这是母板的标题</h1>{% block page-main %}{% endblock %}<h1>母板底部内容</h1>{% block page-js %}{% endblock %}</body></html>注意:我们通常会在母板中定义页面专用的CSS块和JS块,方便子页面替换。九,继承母版在子页面中在页面最上方使用下面的语法来继承母板。{% extends 'layouts.html' %}十,block块通过在母板中使用{% block  xxx %}来定义"块"。在子页面中通过定义母板中的block名来对应替换母板中相应的内容。{% block page-main %}<p>世情薄</p><p>人情恶</p><p>雨送黄昏花易落</p>{% endblock %}十一,组件可以将常用的页面内容如导航条,页尾信息等组件保存在单独的文件中,然后在需要使用的地方按如下语法导入即可。{% include 'navbar.html' %}十二,静态文件相关{% load static %}<img src="{% static "images/hi.jpg" %}" alt="Hi!" />引用JS文件时使用:{% load static %}<script src="{% static "mytest.js" %}"></script>某个文件多处被用到可以存为一个变量{% load static %}{% static "images/hi.jpg" as myphoto %}<img src="{{ myphoto }}"></img>使用get_static_prefix{% load static %}<img src="{% get_static_prefix %}images/hi.jpg" alt="Hi!" />或者{% load static %}{% get_static_prefix as STATIC_PREFIX %}<img src="{{ STATIC_PREFIX }}images/hi.jpg" alt="Hi!" /><img src="{{ STATIC_PREFIX }}images/hi2.jpg" alt="Hello!" /> 自定义simpletag和自定义filter类似,只不过接收更灵活的参数。定义注册simple [email protected]_tag(name="plus")def plus(a, b, c):return "{} + {} + {}".format(a, b, c)使用自定义simple tag{% load app01_demo %}{# simple tag #}{% plus "1" "2" "abc" %}inclusion_tag多用于返回html代码片段示例:templatetags
Django:Django的路由系统
一,URLconf配置1,基本格式from django.conf.urls import urlurlpatterns = [url(正则表达式, views视图函数,参数,别名),]2,参数说明正则表达式:一个正则表达式字符串views视图函数:一个可调用对象,通常为一个视图函数或一个指定视图函数路径的字符串参数:可选的要传递给视图函数的默认参数(字典形式)别名:一个可选的name参数二,正则表达式详解1,基本配置from django.conf.urls import urlfrom . import viewsurlpatterns = [url(r'^articles/2003/$', views.special_case_2003),url(r'^articles/([0-9]{4})/$', views.year_archive),url(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive),url(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail),]2,注意事项urlpatterns中的元素按照书写顺序从上往下逐一匹配正则表达式,一旦匹配成功则不再继续。若要从URL中捕获一个值,只需要在它周围放置一对圆括号(分组匹配)。不需要添加一个前导的反斜杠,因为每个URL 都有。例如,应该是^articles 而不是 ^/articles。每个正则表达式前面的'r' 是可选的但是建议加上。3,补充说明# 是否开启URL访问地址后面不为/跳转至带有/的路径的配置项APPEND_SLASH=TrueDjango settings.py配置文件中默认没有 APPEND_SLASH 这个参数,但 Django 默认这个参数为 APPEND_SLASH = True。 其作用就是自动在网址结尾加'/'。如果在settings.py中设置了 APPEND_SLASH=False,此时我们请求 http://www.example.com/blog 时就会提示找不到页面三,分组命名匹配在更高级的用法中,可以使用分组命名匹配的正则表达式组来捕获URL中的值并以关键字参数形式传递给视图。在Python的正则表达式中,分组命名正则表达式组的语法是(?P<name>pattern),其中name是组的名称,pattern是要匹配的模式。from django.conf.urls import urlfrom . import viewsurlpatterns = [url(r'^articles/2003/$', views.special_case_2003),url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$', views.article_detail),]在实际应用中,使用分组命名匹配的方式可以让你的URLconf 更加明晰且不容易产生参数顺序问题的错误,但是有些开发人员则认为分组命名组语法太丑陋、繁琐。至于究竟应该使用哪一种,你可以根据自己的喜好来决定。4,URLconf匹配的位置URLconf 在请求的URL 上查找,将它当做一个普通的Python 字符串。不包括GET和POST参数以及域名。例如,http://www.example.com/myapp/ 请求中,URLconf 将查找myapp/。在http://www.example.com/myapp/?page=3 请求中,URLconf 仍将查找myapp/。URLconf 不检查请求的方法。换句话讲,所有的请求方法 —— 同一个URL的POST、GET、HEAD等等 —— 都将路由到相同的函数。5,捕捉到的参数永远都是字符串每个在URLconf中捕获的参数都作为一个普通的Python字符串传递给视图,无论正则表达式使用的是什么匹配方式。例如,下面这行URLconf 中:url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),传递到视图函数views.year_archive() 中的year 参数永远是一个字符串类型。6,视图中指定默认值# urls.py中from django.conf.urls import urlfrom . import viewsurlpatterns = [url(r'^blog/$', views.page),url(r'^blog/page(?P<num>[0-9]+)/$', views.page),]# views.py中,可以为num指定默认值def page(request, num="1"):pass7,include其他的URLconfs#At any point, your urlpatterns can “include” other URLconf modules. This#essentially “roots” a set of URLs below other ones.#For example, here’s an excerpt of the URLconf for the Django website itself.#It includes a number of other URLconfs:from django.conf.urls import include, urlurlpatterns = [url(r'^admin/', admin.site.urls),url(r'^blog/', include('blog.urls')), # 可以包含其他的URLconfs文件]四,命名URL和URL的反向解析在使用Django 项目时,一个常见的需求是获得URL的最终形式,以用于嵌入到生成的内容中(视图中和显示给用户的URL等)或者用于处理服务器端的导航(重定向等)。人们强烈希望不要硬编码这些URL(费力、不可扩展且容易产生错误)或者设计一种与URLconf 毫不相关的专门的URL 生成机制,因为这样容易导致一定程度上产生过期的URL。换句话讲,需要的是一个DRY 机制。除了其它有点,它还允许设计的URL 可以自动更新而不用遍历项目的源代码来搜索并替换过期的URL。获取一个URL 最开始想到的信息是处理它视图的标识(例如名字),查找正确的URL 的其它必要的信息有视图参数的类型(位置参数、关键字参数)和值。Django 提供一个办法是让URL 映射是URL 设计唯一的地方。你填充你的URLconf,然后可以双向使用它:根据用户/浏览器发起的URL 请求,它调用正确的Django 视图,并从URL 中提取它的参数需要的值。根据Django 视图的标识和将要传递给它的参数的值,获取与之关联的URL。第一种方式是我们在前面的章节中一直讨论的用法。第二种方式叫做反向解析URL、反向URL 匹配、反向URL 查询或者简单的URL 反查。在需要URL 的地方,对于不同层级,Django 提供不同的工具用于URL 反查:在模板中:使用url模板标签。在Python 代码中:使用django.core.urlresolvers.reverse() 函数。在更高层的与处理Django 模型实例相关的代码中:使用get_absolute_url() 方法。上面说了一大堆,你可能并没有看懂。(那是官方文档的生硬翻译)。咱们简单来说就是可以给我们的URL匹配规则起个名字,一个URL匹配模式起一个名字。这样我们以后就不需要写死URL代码了,只需要通过名字来调用当前的URL。举个简单的例子:url(r'^home', views.home, name='home'), # 给我的url匹配模式起名为 homeurl(r'^index/(d*)', views.index, name='index'), # 给我的url匹配模式起名为index这样:在模板里面可以这样引用:{% url 'home' %}在views函数中可以这样引用:from django.urls import reversereverse("index", args=("2018", )) 例子:考虑下面的URLconf:from django.conf.urls import urlfrom . import viewsurlpatterns = [# ...url(r'^articles/([0-9]{4})/$', views.year_archive, name='news-year-archive'),# ...]根据这里的设计,某一年nnnn对应的归档的URL是/articles/nnnn/。你可以在模板的代码中使用下面的方法获得它们:<a href="{% url 'news-year-archive' 2012 %}">2012 Archive</a><ul>{% for yearvar in year_list %}<li><a href="{% url 'news-year-archive' yearvar %}">{{ yearvar }} Archive</a></li>{% endfor %}</ul>在Python 代码中,这样使用:from django.urls import reversefrom django.shortcuts import redirectdef redirect_to_year(request):# ...year = 2006# ...return redirect(reverse('news-year-archive', args=(year,)))如果出于某种原因决定按年归档文章发布的URL应该调整一下,那么你将只需要修改URLconf 中的内容。在某些场景中,一个视图是通用的,所以在URL 和视图之间存在多对一的关系。对于这些情况,当反查URL 时,只有视图的名字还不够。注意:为了完成上面例子中的URL 反查,你将需要使用命名的URL 模式。URL 的名称使用的字符串可以包含任何你喜欢的字符。不只限制在合法的Python 名称。当命名你的URL 模式时,请确保使用的名称不会与其它应用中名称冲突。如果你的URL 模式叫做comment,而另外一个应用中也有一个同样的名称,当你在模板中使用这个名称的时候不能保证将插入哪个URL。在URL 名称中加上一个前缀,比如应用的名称,将减少冲突的可能。我们建议使用myapp-comment 而不是comment。五,命名空间模式即使不同的APP使用相同的URL名称,URL的命名空间模式也可以让你唯一反转命名的URL。project中的urls.pyfrom django.conf.urls import url, includeurlpatterns = [url(r'^app01/',
Django:视图
Django的View(视图)一,一个简单的视图下面是一个以HTML文档的形式返回当前日期和时间的视图:from django.http import HttpResponseimport datetimedef current_datetime(request):now = datetime.datetime.now()html = "<html><body>It is now %s.</body></html>" % nowreturn HttpResponse(html)让我们来逐行解释下上面的代码:首先,我们从 django.http模块导入了HttpResponse类,以及Python的datetime库。接着,我们定义了current_datetime函数。它就是视图函数。每个视图函数都使用HttpRequest对象作为第一个参数,并且通常称之为request。注意,视图函数的名称并不重要;不需要用一个统一的命名方式来命名,以便让Django识别它。我们将其命名为current_datetime,是因为这个名称能够比较准确地反映出它实现的功能。这个视图会返回一个HttpResponse对象,其中包含生成的响应。每个视图函数都负责返回一个HttpResponse对象。Django使用请求和响应对象来通过系统传递状态。当浏览器向服务端请求一个页面时,Django创建一个HttpRequest对象,该对象包含关于请求的元数据。然后,Django加载相应的视图,将这个HttpRequest对象作为第一个参数传递给视图函数。每个视图负责返回一个HttpResponse对象。二、CBV和FBV1,基于函数的view,就叫FBV。# FBV版添加班级def add_class(request):if request.method == "POST":class_name = request.POST.get("class_name")models.Classes.objects.create(name=class_name)return redirect("/class_list/")return render(request, "add_class.html")2,基于类的view,就叫CBV# CBV版添加班级from django.views import Viewclass AddClass(View):def get(self, request):return render(request, "add_class.html")def post(self, request):class_name = request.POST.get("class_name")models.Classes.objects.create(name=class_name)return redirect("/class_list/")三、给视图添加装饰器1,使用装饰器装饰FBVFBV本身就是一个函数,所以和给普通的函数加装饰器无差:def wrapper(func):def inner(*args, **kwargs):start_time = time.time()ret = func(*args, **kwargs)end_time = time.time()print("used:", end_time-start_time)return retreturn inner# FBV版添加班级@wrapperdef add_class(request):if request.method == "POST":class_name = request.POST.get("class_name")models.Classes.objects.create(name=class_name)return redirect("/class_list/")return render(request, "add_class.html")2,使用装饰器装饰CBV类中的方法与独立函数不完全相同,因此不能直接将函数装饰器应用于类中的方法 ,我们需要先将其转换为方法装饰器。Django中提供了method_decorator装饰器用于将函数装饰器转换为方法装饰器。# CBV版添加班级from django.views import Viewfrom django.utils.decorators import method_decoratorclass AddClass(View):@method_decorator(wrapper)def get(self, request):return render(request, "add_class.html")def post(self, request):class_name = request.POST.get("class_name")models.Classes.objects.create(name=class_name)return redirect("/class_list/")# 使用CBV时要注意,请求过来后会先执行dispatch()这个方法,如果需要批量对具体的请求处理方法,如get,post等做一些操作的时候,这里我们可以手动改写dispatch方法,这个dispatch方法就和在FBV上加装饰器的效果一样。class Login(View):def dispatch(self, request, *args, **kwargs):print('before')obj = super(Login,self).dispatch(request, *args, **kwargs)print('after')return objdef get(self,request):return render(request,'login.html')def post(self,request):print(request.POST.get('user'))return HttpResponse('Login.post')四,request对象1,请求相关的常用值path_info     返回用户访问url,不包括域名method        请求中使用的HTTP方法的字符串表示,全大写表示。GET              包含所有HTTP  GET参数的类字典对象POST           包含所有HTTP POST参数的类字典对象body            请求体,byte类型 request.POST的数据就是从body里面提取到的2,属性所有的属性应该被认为是只读的,除非另有说明属性:django将请求报文中的请求行、头部信息、内容主体封装成 HttpRequest 类中的属性。除了特殊说明的之外,其他均为只读的。0.HttpRequest.scheme表示请求方案的字符串(通常为http或https)1.HttpRequest.body一个字符串,代表请求报文的主体。在处理非 HTTP 形式的报文时非常有用,例如:二进制图片、XML,Json等。但是,如果要处理表单数据的时候,推荐还是使用 HttpRequest.POST 。另外,我们还可以用 python 的类文件方法去操作它,详情参考 HttpRequest.read() 。2.HttpRequest.path一个字符串,表示请求的路径组件(不含域名)。例如:"/music/bands/the_beatles/"3.HttpRequest.method一个字符串,表示请求使用的HTTP 方法。必须使用大写。例如:"GET"、"POST"4.HttpRequest.encoding一个字符串,表示提交的数据的编码方式(如果为 None 则表示使用 DEFAULT_CHARSET 的设置,默认为 'utf-8')。这个属性是可写的,你可以修改它来修改访问表单数据使用的编码。接下来对属性的任何访问(例如从 GET 或 POST 中读取数据)将使用新的 encoding 值。如果你知道表单数据的编码不是 DEFAULT_CHARSET ,则使用它。5.HttpRequest.GET一个类似于字典的对象,包含 HTTP GET 的所有参数。详情请参考 QueryDict 对象。6.HttpRequest.POST一个类似于字典的对象,如果请求中包含表单数据,则将这些数据封装成 QueryDict 对象。POST 请求可以带有空的 POST 字典 —— 如果通过 HTTP POST 方法发送一个表单,但是表单中没有任何的数据,QueryDict 对象依然会被创建。因此,不应该使用 if request.POST 来检查使用的是否是POST 方法;应该使用 if request.method == "POST"另外:如果使用 POST 上传文件的话,文件信息将包含在 FILES 属性中。7.HttpRequest.COOKIES一个标准的Python 字典,包含所有的cookie。键和值都为字符串。8.HttpRequest.FILES一个类似于字典的对象,包含所有的上传文件信息。FILES 中的每个键为<input type="file" name="" /> 中的name,值则为对应的数据。注意,FILES 只有在请求的方法为POST 且提交的<form> 带有enctype="multipart/form-data" 的情况下才会包含数据。否则,FILES 将为一个空的类似于字典的对象。9.HttpRequest.META一个标准的Python 字典,包含所有的HTTP 首部。具体的头部信息取决于客户端和服务器,下面是一些示例:CONTENT_LENGTH —— 请求的正文的长度(是一个字符串)。CONTENT_TYPE —— 请求的正文的MIME 类型。HTTP_ACCEPT —— 响应可接收的Content-Type。HTTP_ACCEPT_ENCODING —— 响应可接收的编码。HTTP_ACCEPT_LANGUAGE —— 响应可接收的语言。HTTP_HOST —— 客服端发送的HTTP Host 头部。HTTP_REFERER —— Referring 页面。HTTP_USER_AGENT —— 客户端的user-agent 字符串。QUERY_STRING —— 单个字符串形式的查询字符串(未解析过的形式)。REMOTE_ADDR —— 客户端的IP 地址。REMOTE_HOST —— 客户端的主机名。REMOTE_USER —— 服务器认证后的用户。REQUEST_METHOD —— 一个字符串,例如"GET" 或"POST"。SERVER_NAME —— 服务器的主机名。SERVER_PORT —— 服务器的端口(是一个字符串)。从上面可以看到,除 CONTENT_LENGTH 和 CONTENT_TYPE 之外,请求中的任何 HTTP 首部转换为 META 的键时,都会将所有字母大写并将连接符替换为下划线最后加上 HTTP_ 前缀。所以,一个叫做 X-Bender 的头部将转换成 META 中的 HTTP_X_BENDER 键。10.HttpRequest.user一个 AUTH_USER_MODEL 类型的对象,表示当前登录的用户。如果用户当前没有登录,user 将设置为 django.contrib.auth.models.AnonymousUser 的一个实例。你可以通过
Django:Django中的ORM
一、Django项目使用MySQL数据库1,在Django项目的settings.py,文件中,配置数据库连接信息:DATABASES = {"default": {"ENGINE": "django.db.backends.mysql","NAME": "你的数据库名称", # 需要自己手动创建数据库"USER": "数据库用户名","PASSWORD": "数据库密码","HOST": "数据库IP","POST": 3306}}2,在Django项目中__init__.py文件中写如下代码,告诉Django使用pymysql模块连接MySQL数据库:import pymysqlpymysql.install_as_MySQLdb()二,Model在Django中model是你数据的单一、明确的信息来源。它包含了你存储的数据的重要字段和行为。通常,一个模型(model)映射到一个数据库表,基本情况:每个模型都是一个Python类,它是django.db.models.Model的子类。模型的每个属性都代表一个数据库字段。综上所述,Django为您提供了一个自动生成的数据库访问API三,快速入门下面这个例子定义了一个 Person 模型,包含 first_name 和 last_name。from django.db import modelsclass Person(models.Model):first_name = models.CharField(max_length=30)last_name = models.CharField(max_length=30)first_name 和 last_name 是模型的字段。每个字段被指定为一个类属性,每个属性映射到一个数据库列。上面的 Person 模型将会像这样创建一个数据库表:CREATE TABLE myapp_person ("id" serial NOT NULL PRIMARY KEY,"first_name" varchar(30) NOT NULL,"last_name" varchar(30) NOT NULL);一些说明:表myapp_person的名称是自动生成的,如果你要自定义表名,需要在model的Meta类中指定 db_table 参数,强烈建议使用小写表名,特别是使用MySQL作为后端数据库时。id字段是自动添加的,如果你想要指定自定义主键,只需在其中一个字段中指定 primary_key=True 即可。如果Django发现你已经明确地设置了Field.primary_key,它将不会添加自动ID列。本示例中的CREATE TABLE SQL使用PostgreSQL语法进行格式化,但值得注意的是,Django会根据配置文件中指定的数据库后端类型来生成相应的SQL语句。Django支持MySQL5.5及更高版本。四,字段AutoField(Field)- int自增列,必须填入参数 primary_key=TrueBigAutoField(AutoField)- bigint自增列,必须填入参数 primary_key=True注:当model中如果没有自增列,则自动会创建一个列名为id的列from django.db import modelsclass UserInfo(models.Model):# 自动创建一个列名为id的且为自增的整数列username = models.CharField(max_length=32)class Group(models.Model):# 自定义自增列nid = models.AutoField(primary_key=True)name = models.CharField(max_length=32)SmallIntegerField(IntegerField):- 小整数 -32768 ~ 32767PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)- 正小整数 0 ~ 32767IntegerField(Field)- 整数列(有符号的) -2147483648 ~ 2147483647PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)- 正整数 0 ~ 2147483647BigIntegerField(IntegerField):- 长整型(有符号的) -9223372036854775808 ~ 9223372036854775807BooleanField(Field)- 布尔值类型NullBooleanField(Field):- 可以为空的布尔值CharField(Field)- 字符类型- 必须提供max_length参数, max_length表示字符长度TextField(Field)- 文本类型EmailField(CharField):- 字符串类型,Django Admin以及ModelForm中提供验证机制IPAddressField(Field)- 字符串类型,Django Admin以及ModelForm中提供验证 IPV4 机制GenericIPAddressField(Field)- 字符串类型,Django Admin以及ModelForm中提供验证 Ipv4和Ipv6- 参数:protocol,用于指定Ipv4或Ipv6, 'both',"ipv4","ipv6"unpack_ipv4, 如果指定为True,则输入::ffff:192.0.2.1时候,可解析为192.0.2.1,开启此功能,需要protocol="both"URLField(CharField)- 字符串类型,Django Admin以及ModelForm中提供验证 URLSlugField(CharField)- 字符串类型,Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号)CommaSeparatedIntegerField(CharField)- 字符串类型,格式必须为逗号分割的数字UUIDField(Field)- 字符串类型,Django Admin以及ModelForm中提供对UUID格式的验证FilePathField(Field)- 字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能- 参数:path, 文件夹路径match=None, 正则匹配recursive=False, 递归下面的文件夹allow_files=True, 允许文件allow_folders=False, 允许文件夹FileField(Field)- 字符串,路径保存在数据库,文件上传到指定目录- 参数:upload_to = "" 上传文件的保存路径storage = None 存储组件,默认django.core.files.storage.FileSystemStorageImageField(FileField)- 字符串,路径保存在数据库,文件上传到指定目录- 参数:upload_to = "" 上传文件的保存路径storage = None 存储组件,默认django.core.files.storage.FileSystemStoragewidth_field=None, 上传图片的高度保存的数据库字段名(字符串)height_field=None 上传图片的宽度保存的数据库字段名(字符串)DateTimeField(DateField)- 日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]DateField(DateTimeCheckMixin, Field)- 日期格式 YYYY-MM-DDTimeField(DateTimeCheckMixin, Field)- 时间格式 HH:MM[:ss[.uuuuuu]]DurationField(Field)- 长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型FloatField(Field)- 浮点型DecimalField(Field)- 10进制小数- 参数:max_digits,小数总长度decimal_places,小数位长度BinaryField(Field)- 二进制类型字段相关内容字段1,常用字段1)AutoFieldint自增列,必须填入参数 primary_key=True。当model中如果没有自增列,则自动会创建一个列名为id的列。2)IntegerField一个整数类型,范围在 -2147483648 to 21474836473)CharField字符类型,必须提供max_length参数, max_length表示字符长度。4)DateField日期字段,日期格式  YYYY-MM-DD,相当于Python中的datetime.date()实例5)DateTimeField日期时间字段,格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ],相当于Python中的datetime.datetime()实例。五,自定义字段class FixedCharField(models.Field):"""自定义的char类型的字段类"""def __init__(self, max_length, *args, **kwargs):self.max_length = max_lengthsuper(FixedCharField, self).__init__(max_length=max_length, *args, **kwargs)def db_type(self, connection):"""限定生成数据库表的字段类型为char,长度为max_length指定的值"""return 'char(%s)' % self.max_lengthclass Class(models.Model):id = models.AutoField(primary_key=True)title = models.CharField(max_length=25)# 使用自定义的char类型的字段cname = FixedCharField(max_length=25)自定义字段六,注意事项1.触发Model中的验证和错误提示有两种方式:a. Django Admin中的错误信息会优先根据Admiin内部的ModelForm错误信息提示,如果都成功,才来检查Model的字段并显示指定错误信息b. 使用ModelFormc. 调用Model对象的 clean_fields 方法,如:# models.pyclass UserInfo(models.Model):nid = models.AutoField(primary_key=True)username = models.CharField(max_length=32)email = models.EmailField(error_messages={'invalid': '格式错了.'})# views.pydef index(request):obj = models.UserInf
Django:ORM关系字段
一,ForeignKey外键类型在ORM中用来表示外键关联关系,一般把ForeignKey字段设置在 '一对多'中'多'的一方。ForeignKey可以和其他表做关联关系同时也可以和自身做关联关系。1,字段参数to设置要关联的表to_field设置要关联的表的字段related_name反向操作时,使用的字段名,用于代替原反向查询时的'表名_set'。例如:class Classes(models.Model):name = models.CharField(max_length=32)class Student(models.Model):name = models.CharField(max_length=32)theclass = models.ForeignKey(to="Classes")当我们要查询某个班级关联的所有学生(反向查询)时,我们会这么写:models.Classes.objects.first().student_set.all()当我们在ForeignKey字段中添加了参数 related_name 后,class Student(models.Model):name = models.CharField(max_length=32)theclass = models.ForeignKey(to="Classes", related_name="students")当我们要查询某个班级关联的所有学生(反向查询)时,我们会这么写:models.Classes.objects.first().students.all()related_query_name反向查询操作时,使用的连接前缀,用于替换表名。on_delete当删除关联表中的数据时,当前表与其关联的行的行为。models.CASCADE删除关联数据,与之关联也删除models.DO_NOTHING删除关联数据,引发错误IntegrityErrormodels.PROTECT删除关联数据,引发错误ProtectedErrormodels.SET_NULL删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空)models.SET_DEFAULT删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值)models.SET删除关联数据,a. 与之关联的值设置为指定值,设置:models.SET(值)b. 与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象)def func():return 10class MyModel(models.Model):user = models.ForeignKey(to="User",to_field="id",on_delete=models.SET(func))db_constraint是否在数据库中创建外键约束,默认为True。二,OneToOneField一对一字段。通常一对一字段用来扩展已有字段。1,字段参数to设置要关联的表。to_field设置要关联的字段。on_delete同ForeignKey字段。三,ManyToManyField用于表示多对多的关联关系。在数据库中通过第三张表来建立关联关系。1,字段参数to设置要关联的表related_name同ForeignKey字段。related_query_name同ForeignKey字段。symmetrical仅用于多对多自关联时,指定内部是否创建反向操作的字段。默认为True。举个例子:class Person(models.Model):name = models.CharField(max_length=16)friends = models.ManyToManyField("self")此时,person对象就没有person_set属性。class Person(models.Model):name = models.CharField(max_length=16)friends = models.ManyToManyField("self", symmetrical=False)此时,person对象现在就可以使用person_set属性进行反向查询。through在使用ManyToManyField字段时,Django将自动生成一张表来管理多对多的关联关系。但我们也可以手动创建第三张表来管理多对多关系,此时就需要通过through来指定第三张表的表名。through_fields设置关联的字段。db_table默认创建第三张表时,数据库中表的名称。2,元信息ORM对应的类里面包含另一个Meta类,而Meta类封装了一些数据库的信息。主要字段如下:db_tableORM在数据库中的表名默认是 app_类名,可以通过db_table可以重写表名。index_together联合索引。unique_together联合唯一索引。ordering指定默认按什么字段排序。只有设置了该属性,我们查询到的结果才可以被reverse()。
Django:cookie
一,Django中操作cookie1,获取cookierequest.COOKIES['key']request.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None)参数:default: 默认值salt: 加密盐max_age: 后台控制过期时间2,设置cookierep = HttpResponse(...)rep = render(request, ...)rep.set_cookie(key,value,...)rep.set_signed_cookie(key,value,salt='加密盐',...)参数:key, 键value='', 值max_age=None, 超时时间expires=None, 超时时间(IE requires expires, so set it if hasn't been already.)path='/', Cookie生效的路径,/ 表示根路径,特殊的:根路径的cookie可以被任何url的页面访问domain=None, Cookie生效的域名secure=False, https传输httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖3,删除cookiedef logout(request):rep = redirect("/login/")rep.delete_cookie("user") # 删除用户浏览器上之前设置的usercookie值return repdef check_login(func):@wraps(func)def inner(request, *args, **kwargs):next_url = request.get_full_path()if request.get_signed_cookie("login", salt="SSS", default=None) == "yes":# 已经登录的用户...return func(request, *args, **kwargs)else:# 没有登录的用户,跳转刚到登录页面return redirect("/login/?next={}".format(next_url))return innerdef login(request):if request.method == "POST":username = request.POST.get("username")passwd = request.POST.get("password")if username == "xxx" and passwd == "dashabi":next_url = request.GET.get("next")if next_url and next_url != "/logout/":response = redirect(next_url)else:response = redirect("/class_list/")response.set_signed_cookie("login", "yes", salt="SSS")return responsereturn render(request, "login.html")cookie版登录cookie版登录二、Django中Session相关方法def index(request):# 获取、设置、删除Session中数据request.session['k1']request.session.get('k1',None)request.session['k1'] = 123request.session.setdefault('k1',123) # 存在则不设置del request.session['k1']# 所有 键、值、键值对request.session.keys()request.session.values()request.session.items()request.session.iterkeys()request.session.itervalues()request.session.iteritems()# 用户session的随机字符串request.session.session_key# 将所有Session失效日期小于当前日期的数据删除request.session.clear_expired()# 检查 用户session的随机字符串 在数据库中是否request.session.exists("session_key")# 删除当前用户的所有Session数据request.session.delete()request.session.set_expiry(value)* 如果value是个整数,session会在些秒数后失效。* 如果value是个datatime或timedelta,session就会在这个时间后失效。* 如果value是0,用户关闭浏览器session就会失效。* 如果value是None,session会依赖全局session失效策略。session版登录验证from functools import wrapsdef check_login(func):@wraps(func)def inner(request, *args, **kwargs):next_url = request.get_full_path()if request.session.get("user"):return func(request, *args, **kwargs)else:return redirect("/login/?next={}".format(next_url))return innerdef login(request):if request.method == "POST":user = request.POST.get("user")pwd = request.POST.get("pwd")if user == "alex" and pwd == "alex1234":# 设置sessionrequest.session["user"] = user# 获取跳到登陆页面之前的URLnext_url = request.GET.get("next")# 如果有,就跳转回登陆之前的URLif next_url:return redirect(next_url)# 否则默认跳转到index页面else:return redirect("/index/")return render(request, "login.html")@check_logindef logout(request):# 删除所有当前请求相关的sessionrequest.session.delete()return redirect("/login/")@check_logindef index(request):current_user = request.session.get("user", None)return render(request, "index.html", {"user": current_user})Session版登录验证2,Django中的session配置Django中默认支持Session,其内部提供了5种类型的Session供开发者使用。1. 数据库SessionSESSION_ENGINE = 'django.contrib.sessions.backends.db' # 引擎(默认)2. 缓存SessionSESSION_ENGINE = 'django.contrib.sessions.backends.cache' # 引擎SESSION_CACHE_ALIAS = 'default' # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置3. 文件SessionSESSION_ENGINE = 'django.contrib.sessions.backends.file' # 引擎SESSION_FILE_PATH = None # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir()4. 缓存+数据库SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db' # 引擎5. 加密Cookie SessionSESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies' # 引擎其他公用设置项:SESSION_COOKIE_NAME = "sessionid" # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认)SESSION_COOKIE_PATH = "/" # Session的cookie保存的路径(默认)SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名(默认)SESSION_COOKIE_SECURE = False # 是否Https传输cookie(默认)SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http传输(默认)SESSION_COOKIE_AGE = 1209600 # Session的cookie失效日期(2周)(默认)SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否关闭浏览器使得Session过期(默认)SESSION_SAVE_EVERY_REQUEST = False # 是否每次请求都保存Session,默认修改之后才保存(默认)Django中Session相关设置3,CBV中加装饰器class LoginView(View):def get(self, request):"""处理GET请求"""return render(request, 'login.html')def post(self, request):"""处理POST请求"
Django:中间件
一,中间件介绍1,什么是中间件?中间件是一个用来处理Django的请求和响应的框架级别的钩子。它是一个轻量、低级别的插件系统,用于在全局范围内改变Django的输入和输出。每个中间件组件都负责做一些特定的功能。是帮助我们在视图函数执行之前和执行之后都可以做一些额外的操作,它本质上就是一个自定义类,类中定义了几个方法,Django框架会在请求的特定的时间去执行这些方法.MIDDLEWARE = ['django.middleware.security.SecurityMiddleware','django.contrib.sessions.middleware.SessionMiddleware','django.middleware.common.CommonMiddleware','django.middleware.csrf.CsrfViewMiddleware','django.contrib.auth.middleware.AuthenticationMiddleware','django.contrib.messages.middleware.MessageMiddleware','django.middleware.clickjacking.XFrameOptionsMiddleware',]二,自定义中间件中间件可以定义五个方法,分别是:(主要的是process_request和process_response)process_request(self,request)process_view(self, request, view_func, view_args, view_kwargs)process_template_response(self,request,response)process_exception(self, request, exception)process_response(self, request, response)以上方法的返回值可以是None或一个HttpResponse对象,如果是None,则继续按照django定义的规则向后继续执行,如果是HttpResponse对象,则直接将该对象返回给用户。from django.utils.deprecation import MiddlewareMixinclass MD1(MiddlewareMixin):def process_request(self, request):print("MD1里面的 process_request")def process_response(self, request, response):print("MD1里面的 process_response")return response1,process_requestprocess_request有一个参数,就是request,这个request和视图函数中的request是一样的。它的返回值可以是None也可以是HttpResponse对象。返回值是None的话,按正常流程继续走,交给下一个中间件处理,如果是HttpResponse对象,Django将不执行视图函数,而将相应对象返回给浏览器。我们来看看多个中间件时,Django是如何执行其中的process_request方法的。from django.utils.deprecation import MiddlewareMixinclass MD1(MiddlewareMixin):def process_request(self, request):print("MD1里面的 process_request")class MD2(MiddlewareMixin):def process_request(self, request):print("MD2里面的 process_request")pass在settings.py的MIDDLEWARE配置项中注册上述两个自定义中间件:MIDDLEWARE = ['django.middleware.security.SecurityMiddleware','django.contrib.sessions.middleware.SessionMiddleware','django.middleware.common.CommonMiddleware','django.middleware.csrf.CsrfViewMiddleware','django.contrib.auth.middleware.AuthenticationMiddleware','django.contrib.messages.middleware.MessageMiddleware','django.middleware.clickjacking.XFrameOptionsMiddleware','middlewares.MD1', # 自定义中间件MD1'middlewares.MD2' # 自定义中间件MD2]总结:中间件的process_request方法是在执行视图函数之前执行的。当配置多个中间件时,会按照MIDDLEWARE中的注册顺序,也就是列表的索引值,从前到后依次执行的。不同中间件之间传递的request都是同一个对象2,process_response它有两个参数,一个是request,一个是response,request就是上述例子中一样的对象,response是视图函数返回的HttpResponse对象。该方法的返回值也必须是HttpResponse对象。给上述的M1和M2加上process_response方法:from django.utils.deprecation import MiddlewareMixinclass MD1(MiddlewareMixin):def process_request(self, request):print("MD1里面的 process_request")def process_response(self, request, response):print("MD1里面的 process_response")return responseclass MD2(MiddlewareMixin):def process_request(self, request):print("MD2里面的 process_request")passdef process_response(self, request, response):print("MD2里面的 process_response")return response看结果可知:process_response方法是在视图函数之后执行的,并且顺序是MD1比MD2先执行。(此时settings.py中 MD2比MD1先注册)多个中间件中的process_response方法是按照MIDDLEWARE中的注册顺序倒序执行的,也就是说第一个中间件的process_request方法首先执行,而它的process_response方法最后执行,最后一个中间件的process_request方法最后一个执行,它的process_response方法是最先执行。3,process_viewprocess_view(self, request, view_func, view_args, view_kwargs)该方法有四个参数request是HttpRequest对象。view_func是Django即将使用的视图函数。 (它是实际的函数对象,而不是函数的名称作为字符串。)view_args是将传递给视图的位置参数的列表.view_kwargs是将传递给视图的关键字参数的字典。 view_args和view_kwargs都不包含第一个视图参数(request)。Django会在调用视图函数之前调用process_view方法。它应该返回None或一个HttpResponse对象。 如果返回None,Django将继续处理这个请求,执行任何其他中间件的process_view方法,然后在执行相应的视图。 如果它返回一个HttpResponse对象,Django不会调用适当的视图函数。 它将执行中间件的process_response方法并将应用到该HttpResponse并返回结果。 给MD1和MD2添加process_view方法:from django.utils.deprecation import MiddlewareMixinclass MD1(MiddlewareMixin):def process_request(self, request):print("MD1里面的 process_request")def process_response(self, request, response):print("MD1里面的 process_response")return responsedef process_view(self, request, view_func, view_args, view_kwargs):print("-" * 80)print("MD1 中的process_view")print(view_func, view_func.__name__)class MD2(MiddlewareMixin):def process_request(self, request):print("MD2里面的 process_request")passdef process_response(self, request, response):print("MD2里面的 process_response")return responsedef process_view(self, request, view_func, view_args, view_kwargs):print("-" * 80)print("MD2 中的process_view")print(view_func, view_func.__name__)process_view方法是在process_request之后,视图函数之前执行的,执行顺序按照MIDDLEWARE中的注册顺序从前到后顺序执行的4,process_exceptionprocess_exception(self, request, exception)该方法两个参数:一个HttpRequest对象一个exception是视图函数异常产生的Exception对象。这个方法只有在视图函数中出现异常了才执行,它返回的值可以是一个None也可以是一个HttpResponse对象。如果是HttpResponse对象,Django将调用模板和中间件中的process_response方法,并返回给浏览器,否则将默认处理异常。如果返回一个None,则交给下一个中间件的process_exception方法来处理异常。它的执行顺序也是按照中间件注册顺序的倒序执行。 给MD1和MD2添加上这个方法: from django.utils.deprecation import MiddlewareMixinclass MD1(MiddlewareMixin):def process_request(self, request):print("MD1里面的 process_request")def process_response(s
Django:认证系统
一,cookie和sessioncookie不属于http协议范围,由于http协议无法保持状态,但实际情况,我们却又需要“保持状态”,因此cookie就是在这样一个场景下诞生。cookie的工作原理是:由服务器产生内容,浏览器收到请求后保存在本地;当浏览器再次访问时,浏览器会自动带上cookie,这样服务器就能通过cookie的内容来判断这个是“谁”了。cookie虽然在一定程度上解决了“保持状态”的需求,但是由于cookie本身最大支持4096字节,以及cookie本身保存在客户端,可能被拦截或窃取,因此就需要有一种新的东西,它能支持更多的字节,并且他保存在服务器,有较高的安全性。这就是session。问题来了,基于http协议的无状态特征,服务器根本就不知道访问者是“谁”。那么上述的cookie就起到桥接的作用。我们可以给每个客户端的cookie分配一个唯一的id,这样用户在访问时,通过cookie,服务器就知道来的人是“谁”。然后我们再根据不同的cookie的id,在服务器上保存一段时间的私密资料,如“账号密码”等等。总结而言:cookie弥补了http无状态的不足,让服务器知道来的人是“谁”;但是cookie以文本的形式保存在本地,自身安全性较差;所以我们就通过cookie识别不同的用户,对应的在session里保存私密的信息以及超过4096字节的文本。另外,上述所说的cookie和session其实是共通性的东西,不限于语言和框架二,Django实现的cookie1,获取cookierequest.COOKIES['key']request.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None)#参数:default: 默认值salt: 加密盐max_age: 后台控制过期时间2,设置cookierep = HttpResponse(...) 或 rep = render(request, ...) 或 rep = redirect()rep.set_cookie(key,value,...)rep.set_signed_cookie(key,value,salt='加密盐',...)'''def set_cookie(self, key, 键value='', 值max_age=None, 超长时间expires=None, 超长时间path='/', Cookie生效的路径,浏览器只会把cookie回传给带有该路径的页面,这样可以避免将cookie传给站点中的其他的应用。/ 表示根路径,特殊的:根路径的cookie可以被任何url的页面访问domain=None, Cookie生效的域名你可用这个参数来构造一个跨站cookie。如, domain=".example.com"所构造的cookie对下面这些站点都是可读的:www.example.com 、 www2.example.com和an.other.sub.domain.example.com 。如果该参数设置为 None ,cookie只能由设置它的站点读取。secure=False, 如果设置为 True ,浏览器将通过HTTPS来回传cookie。httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖)): pass'''参数<script src='/static/js/jquery.cookie.js'></script> $.cookie("key", value,{ path: '/' });三,Django实现的session1,基本操作1、设置Sessions值request.session['session_name'] ="admin"2、获取Sessions值session_name = request.session["session_name"]3、删除Sessions值del request.session["session_name"]4、检测是否操作session值if "session_name" is request.session :四,用户认证1,auth模块from django.contrib import auth(1)authenticate()提供了用户认证,即验证用户名以及密码是否正确,一般需要username  password两个关键字参数user = authenticate(username='someone',password='somepassword')(2)login(HttpRequest,user)该函数接受一个HttpRequest对象,以及一个认证了的User对象此函数使用django的session框架给某个已认证的用户附加上session id等信息。from django.contrib.auth import authenticate, logindef my_view(request):username = request.POST['username']password = request.POST['password']user = authenticate(username=username, password=password)if user is not None:login(request, user)# Redirect to a success page....else:# Return an 'invalid login' error message....(3)logout(request)注销用户from django.contrib.auth import logoutdef logout_view(request):logout(request)# Redirect to a success page.(4)user对象的is_authenticated()要求:1  用户登陆后才能访问某些页面,2  如果用户没有登录就访问该页面的话直接跳到登录页面3  用户在跳转的登陆界面中完成登陆后,自动访问跳转到之前访问的地址方法1:def my_view(request):if not request.user.is_authenticated():return redirect('%s?next=%s' % (settings.LOGIN_URL, request.path))方法2:django已经为我们设计好了一个用于此种情况的装饰器:login_requierd()from django.contrib.auth.decorators import login_required@login_requireddef my_view(request):...二,user对象(1)is_authenticated()如果是真正的 User 对象,返回值恒为 True 。 用于检查用户是否已经通过了认证。通过认证并不意味着用户拥有任何权限,甚至也不检查该用户是否处于激活状态,这只是表明用户成功的通过了认证。 这个方法很重要, 在后台用request.user.is_authenticated()判断用户是否已经登录,如果true则可以向前台展示request.user.name(2)创建用户使用 create_user 辅助函数创建用户:from django.contrib.auth.models import Useruser = User.objects.create_user(username='',password='',email='')(3)check_password(passwd)用户需要修改密码的时候 首先要让他输入原来的密码 ,如果给定的字符串通过了密码检查,返回 True(4)修改密码#使用 set_password() 来修改密码user = User.objects.get(username='')user.set_password(password='')user.save由于cookie保存在客户端的电脑上,所以,JavaScript和jquery也可以操作cookie。123<script src='/static/js/jquery.cookie.js'> </script> $.cookie("key", value,{ path: '/' });
Django的时区设置问题
1.Django的时区问题django默认的时区是UTC,平时是没有什么影响的,但是在需要将时间戳转换成本时区的时间或者是获取当前的本地的localtime的时候就出现了问题。之前程序在测试时是运行在Windows环境,所以即使settings.py中的TIME_ZONE使用默认时区,Django也会根据本机的时区使用当前时区时间。然而程序放到linux运行程序时,Django的时区会使用settings.py中的TIME_ZONE设置的时区,所以这时就出现了问题。再有当我用脚本在linux上测试或者直接进入python环境的时候,运行time.localtime(),显示本机所在时区的当前时间。'''我的django程序中的一部分,这部分功能是将用户的聊天时间戳转化为格式化时间。由于我使用的默认时区UTC,原以为在linux环境中会像windows环境中一样会使用机器设置的时区的时间,结果并不是,而是使用了默认时区的时间。'''import timesend_time = 1543524392day = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(send_time))print day # 2018-11-29 20:46:32--->UTC 代码中打印出来的时间是UTC时间2.Django时区设置在Django的settings中,有两个配置参数是跟时间与时区有关---->TIME_ZONE和USE_TZUSE_TZ为True,Django使用系统默认时区,即America/Chicago,此时TIME_ZONE不管有没有设置都不起作用。USE_TZ为False,TIME_ZONE为None,Django会使用默认的America/Chicago时间。USE_TZ为False,TIME_ZONE设置为其它时区,则要具体的程序运行环境。如果是Windows系统,则TIME_ZONE设置是没用的,Django会使用本机的所使用的时区。如果为其他系统,则使用TIME_ZONE设置的时区------>即USE_TZ = False, TIME_ZONE = 'Asia/Shanghai', 使用上海的UTC时间。
Cookie、Session和自定义分页
cookieCookie的由来大家都知道HTTP协议是无状态的。无状态的意思是每次请求都是独立的,它的执行情况和结果与前面的请求和之后的请求都无直接关系,它不会受前面的请求响应情况直接影响,也不会直接影响后面的请求响应情况。一句有意思的话来描述就是人生只如初见,对服务器来说,每次的请求都是全新的。状态可以理解为客户端和服务器在某次会话中产生的数据,那无状态的就以为这些数据不会被保留。会话中产生的数据又是我们需要保存的,也就是说要“保持状态”。因此Cookie就是在这样一个场景下诞生。什么是CookieCookie具体指的是一段小信息,它是服务器发送出来存储在浏览器上的一组组键值对,下次访问服务器时浏览器会自动携带这些键值对,以便服务器提取有用信息。Cookie的原理cookie的工作原理是:由服务器产生内容,浏览器收到请求后保存在本地;当浏览器再次访问时,浏览器会自动带上Cookie,这样服务器就能通过Cookie的内容来判断这个是“谁”了。查看Cookie我们使用Chrome浏览器,打开开发者工具。 Django中操作Cookie获取Cookierequest.COOKIES['key']request.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None)参数:default: 默认值salt: 加密盐max_age: 后台控制过期时间设置Cookierep = HttpResponse(...)rep = render(request, ...)rep.set_cookie(key,value,...)rep.set_signed_cookie(key,value,salt='加密盐', max_age=None, ...)参数:key, 键value='', 值max_age=None, 超时时间expires=None, 超时时间(IE requires expires, so set it if hasn't been already.)path='/', Cookie生效的路径,/ 表示根路径,特殊的:根路径的cookie可以被任何url的页面访问domain=None, Cookie生效的域名secure=False, https传输httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖)删除Cookiedef logout(request):rep = redirect("/login/")rep.delete_cookie("user") # 删除用户浏览器上之前设置的usercookie值return repCookie版登陆校验def check_login(func):@wraps(func)def inner(request, *args, **kwargs):next_url = request.get_full_path()if request.get_signed_cookie("login", salt="SSS", default=None) == "yes":# 已经登录的用户...return func(request, *args, **kwargs)else:# 没有登录的用户,跳转刚到登录页面return redirect("/login/?next={}".format(next_url))return innerdef login(request):if request.method == "POST":username = request.POST.get("username")passwd = request.POST.get("password")if username == "xxx" and passwd == "dashabi":next_url = request.GET.get("next")if next_url and next_url != "/logout/":response = redirect(next_url)else:response = redirect("/class_list/")response.set_signed_cookie("login", "yes", salt="SSS")return responsereturn render(request, "login.html")cookie版登陆 SessionSession的由来Cookie虽然在一定程度上解决了“保持状态”的需求,但是由于Cookie本身最大支持4096字节,以及Cookie本身保存在客户端,可能被拦截或窃取,因此就需要有一种新的东西,它能支持更多的字节,并且他保存在服务器,有较高的安全性。这就是Session。问题来了,基于HTTP协议的无状态特征,服务器根本就不知道访问者是“谁”。那么上述的Cookie就起到桥接的作用。我们可以给每个客户端的Cookie分配一个唯一的id,这样用户在访问时,通过Cookie,服务器就知道来的人是“谁”。然后我们再根据不同的Cookie的id,在服务器上保存一段时间的私密资料,如“账号密码”等等。总结而言:Cookie弥补了HTTP无状态的不足,让服务器知道来的人是“谁”;但是Cookie以文本的形式保存在本地,自身安全性较差;所以我们就通过Cookie识别不同的用户,对应的在Session里保存私密的信息以及超过4096字节的文本。另外,上述所说的Cookie和Session其实是共通性的东西,不限于语言和框架。Django中Session相关# 获取、设置、删除Session中数据request.session['k1']request.session.get('k1',None)request.session['k1'] = 123request.session.setdefault('k1',123) # 存在则不设置del request.session['k1']# 所有 键、值、键值对request.session.keys()request.session.values()request.session.items()request.session.iterkeys()request.session.itervalues()request.session.iteritems()# 会话session的keyrequest.session.session_key# 将所有Session失效日期小于当前日期的数据删除request.session.clear_expired()# 检查会话session的key在数据库中是否存在request.session.exists("session_key")# 删除当前会话的所有Session数据request.session.delete()# 删除当前的会话数据并删除会话的Cookie。request.session.flush()这用于确保前面的会话数据不可以再次被用户的浏览器访问例如,django.contrib.auth.logout() 函数中就会调用它。# 设置会话Session和Cookie的超时时间request.session.set_expiry(value)* 如果value是个整数,session会在些秒数后失效。* 如果value是个datatime或timedelta,session就会在这个时间后失效。* 如果value是0,用户关闭浏览器session就会失效。* 如果value是None,session会依赖全局session失效策略。Session流程解析Session版登陆验证from functools import wrapsdef check_login(func):@wraps(func)def inner(request, *args, **kwargs):next_url = request.get_full_path()if request.session.get("user"):return func(request, *args, **kwargs)else:return redirect("/login/?next={}".format(next_url))return innerdef login(request):if request.method == "POST":user = request.POST.get("user")pwd = request.POST.get("pwd")if user == "alex" and pwd == "alex1234":# 设置sessionrequest.session["user"] = user# 获取跳到登陆页面之前的URLnext_url = request.GET.get("next")# 如果有,就跳转回登陆之前的URLif next_url:return redirect(next_url)# 否则默认跳转到index页面else:return redirect("/index/")return render(request, "login.html")@check_logindef logout(request):# 删除所有当前请求相关的sessionrequest.session.delete()return redirect("/login/")@check_logindef index(request):current_user = request.session.get("user", None)return render(request, "index.html", {"user": current_user})Session版登陆验证Django中的Session配置1. 数据库SessionSESSION_ENGINE = 'django.contrib.sessions.backends.db' # 引擎(默认)2. 缓存SessionSESSION_ENGINE = 'django.contrib.sessions.backends.cache' # 引擎SESSION_CACHE_ALIAS = 'default' # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置3. 文件SessionSESSION_ENGINE = 'django.contrib.sessions.backends.file' # 引擎SESSION_FILE_PATH = None # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir()4. 缓存+数据库SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db' # 引擎5. 加密Co
Django 中间件
中间件中间件介绍官方的说法:中间件是一个用来处理Django的请求和响应的框架级别的钩子。它是一个轻量、低级别的插件系统,用于在全局范围内改变Django的输入和输出。每个中间件组件都负责做一些特定的功能。但是由于其影响的是全局,所以需要谨慎使用,使用不当会影响性能。说的直白一点中间件是帮助我们在视图函数执行之前和执行之后都可以做一些额外的操作,它本质上就是一个自定义类,类中定义了几个方法,Django框架会在请求的特定的时间去执行这些方法。我们一直都在使用中间件,只是没有注意到而已,打开Django项目的Settings.py文件,看到下图的MIDDLEWARE配置项。MIDDLEWARE = ['django.middleware.security.SecurityMiddleware','django.contrib.sessions.middleware.SessionMiddleware','django.middleware.common.CommonMiddleware','django.middleware.csrf.CsrfViewMiddleware','django.contrib.auth.middleware.AuthenticationMiddleware','django.contrib.messages.middleware.MessageMiddleware','django.middleware.clickjacking.XFrameOptionsMiddleware',]MIDDLEWARE配置项是一个列表,列表中是一个个字符串,这些字符串其实是一个个类,也就是一个个中间件。我们之前已经接触过一个csrf相关的中间件了?我们一开始让大家把他注释掉,再提交post请求的时候,就不会被forbidden了,后来学会使用csrf_token之后就不再注释这个中间件了。那接下来就学习中间件中的方法以及这些方法什么时候被执行。 自定义中间件中间件可以定义五个方法,分别是:(主要的是process_request和process_response)process_request(self,request)process_view(self, request, view_func, view_args, view_kwargs)process_template_response(self,request,response)process_exception(self, request, exception)process_response(self, request, response)以上方法的返回值可以是None或一个HttpResponse对象,如果是None,则继续按照django定义的规则向后继续执行,如果是HttpResponse对象,则直接将该对象返回给用户。自定义一个中间件示例from django.utils.deprecation import MiddlewareMixinclass MD1(MiddlewareMixin):def process_request(self, request):print("MD1里面的 process_request")def process_response(self, request, response):print("MD1里面的 process_response")return responseprocess_requestprocess_request有一个参数,就是request,这个request和视图函数中的request是一样的。它的返回值可以是None也可以是HttpResponse对象。返回值是None的话,按正常流程继续走,交给下一个中间件处理,如果是HttpResponse对象,Django将不执行视图函数,而将相应对象返回给浏览器。我们来看看多个中间件时,Django是如何执行其中的process_request方法的。from django.utils.deprecation import MiddlewareMixinclass MD1(MiddlewareMixin):def process_request(self, request):print("MD1里面的 process_request")class MD2(MiddlewareMixin):def process_request(self, request):print("MD2里面的 process_request")pass在settings.py的MIDDLEWARE配置项中注册上述两个自定义中间件:MIDDLEWARE = ['django.middleware.security.SecurityMiddleware','django.contrib.sessions.middleware.SessionMiddleware','django.middleware.common.CommonMiddleware','django.middleware.csrf.CsrfViewMiddleware','django.contrib.auth.middleware.AuthenticationMiddleware','django.contrib.messages.middleware.MessageMiddleware','django.middleware.clickjacking.XFrameOptionsMiddleware','middlewares.MD1', # 自定义中间件MD1'middlewares.MD2' # 自定义中间件MD2]此时,我们访问一个视图,会发现终端中打印如下内容:MD1里面的 process_requestMD2里面的 process_requestapp01 中的 index视图把MD1和MD2的位置调换一下,再访问一个视图,会发现终端中打印的内容如下:MD2里面的 process_requestMD1里面的 process_requestapp01 中的 index视图看结果我们知道:视图函数还是最后执行的,MD2比MD1先执行自己的process_request方法。在打印一下两个自定义中间件中process_request方法中的request参数,会发现它们是同一个对象。由此总结一下:中间件的process_request方法是在执行视图函数之前执行的。当配置多个中间件时,会按照MIDDLEWARE中的注册顺序,也就是列表的索引值,从前到后依次执行的。不同中间件之间传递的request都是同一个对象 多个中间件中的process_response方法是按照MIDDLEWARE中的注册顺序倒序执行的,也就是说第一个中间件的process_request方法首先执行,而它的process_response方法最后执行,最后一个中间件的process_request方法最后一个执行,它的process_response方法是最先执行。process_response它有两个参数,一个是request,一个是response,request就是上述例子中一样的对象,response是视图函数返回的HttpResponse对象。该方法的返回值也必须是HttpResponse对象。给上述的M1和M2加上process_response方法:from django.utils.deprecation import MiddlewareMixinclass MD1(MiddlewareMixin):def process_request(self, request):print("MD1里面的 process_request")def process_response(self, request, response):print("MD1里面的 process_response")return responseclass MD2(MiddlewareMixin):def process_request(self, request):print("MD2里面的 process_request")passdef process_response(self, request, response):print("MD2里面的 process_response")return response访问一个视图,看一下终端的输出:MD2里面的 process_requestMD1里面的 process_requestapp01 中的 index视图MD1里面的 process_responseMD2里面的 process_response看结果可知:process_response方法是在视图函数之后执行的,并且顺序是MD1比MD2先执行。(此时settings.py中 MD2比MD1先注册)多个中间件中的process_response方法是按照MIDDLEWARE中的注册顺序倒序执行的,也就是说第一个中间件的process_request方法首先执行,而它的process_response方法最后执行,最后一个中间件的process_request方法最后一个执行,它的process_response方法是最先执行。process_viewprocess_view(self, request, view_func, view_args, view_kwargs)该方法有四个参数request是HttpRequest对象。view_func是Django即将使用的视图函数。 (它是实际的函数对象,而不是函数的名称作为字符串。)view_args是将传递给视图的位置参数的列表.view_kwargs是将传递给视图的关键字参数的字典。 view_args和view_kwargs都不包含第一个视图参数(request)。Django会在调用视图函数之前调用process_view方法。它应该返回None或一个HttpResponse对象。 如果返回None,Django将继续处理这个请求,执行任何其他中间件的process_view方法,然后在执行相应的视图。 如果它返回一个HttpResponse对象,Django不会调用适当的视图函数。 它将执行中间件的process_response方法并将应用到该HttpResponse并返回结果。 给MD1和MD2添加process_view方法:from django.utils.deprecation import MiddlewareMixinclass MD1(MiddlewareMixin):def process_request(self, request):print("MD1里面的 process_request")def process_response(self, request, response):print("MD1里面的 process_response")return responsedef process_view(self, request, view_func, view_args, view_kwargs):print("-" * 80)print("MD1 中的process_view")print(view_func, view_func.__name__)class MD2(MiddlewareMixin):def process_request(self, request):print("MD2里面的 proc
Django Form和ModelForm组件
Form介绍我们之前在HTML页面中利用form表单向后端提交数据时,都会写一些获取用户输入的标签并且用form标签把它们包起来。与此同时我们在好多场景下都需要对用户的输入做校验,比如校验用户是否输入,输入的长度和格式等正不正确。如果用户输入的内容有错误就需要在页面上相应的位置显示对应的错误信息.。Django form组件就实现了上面所述的功能。总结一下,其实form组件的主要功能如下:生成页面可用的HTML标签对用户提交的数据进行校验保留上次输入内容 1,普通方式手写注册功能views.py# 注册def register(request):error_msg = ""if request.method == "POST":username = request.POST.get("name")pwd = request.POST.get("pwd")# 对注册信息做校验if len(username) < 6:# 用户长度小于6位error_msg = "用户名长度不能小于6位"else:# 将用户名和密码存到数据库return HttpResponse("注册成功")return render(request, "register.html", {"error_msg": error_msg})login.html<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>注册页面</title></head><body><form action="/reg/" method="post">{% csrf_token %}<p>用户名:<input type="text" name="name"></p><p>密码:<input type="password" name="pwd"></p><p><input type="submit" value="注册"><p style="color: red">{{ error_msg }}</p></p></form></body></html> 2,使用form组件实现注册功能views.py先定义好一个RegFoem类:from django import forms# 按照Django form组件的要求自己写一个类class RegForm(forms.Form):name = forms.CharField(label="用户名")pwd = forms.CharField(label="密码")再写一个视图函数:# 使用form组件实现注册方式def register2(request):form_obj = RegForm()if request.method == "POST":# 实例化form对象的时候,把post提交过来的数据直接传进去form_obj = RegForm(request.POST)# 调用form_obj校验数据的方法if form_obj.is_valid():return HttpResponse("注册成功")return render(request, "register2.html", {"form_obj": form_obj})login2.html<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>注册2</title></head><body><form action="/reg2/" method="post" novalidate autocomplete="off">{% csrf_token %}<div><label for="{{ form_obj.name.id_for_label }}">{{ form_obj.name.label }}</label>{{ form_obj.name }} {{ form_obj.name.errors.0 }}</div><div><label for="{{ form_obj.pwd.id_for_label }}">{{ form_obj.pwd.label }}</label>{{ form_obj.pwd }} {{ form_obj.pwd.errors.0 }}</div><div><input type="submit" class="btn btn-success" value="注册"></div></form></body></html>看网页效果发现 也验证了form的功能:• 前端页面是form类的对象生成的                                      -->生成HTML标签功能• 当用户名和密码输入为空或输错之后 页面都会提示        -->用户提交校验功能• 当用户输错之后 再次输入 上次的内容还保留在input框   -->保留上次输入内容 Form那些事1,常用字段与插件创建Form类时,主要涉及到 【字段】 和 【插件】,字段用于对用户请求数据的验证,插件用于自动生成HTML;initial初始值,input框里面的初始值。class LoginForm(forms.Form):username = forms.CharField(min_length=8,label="用户名",initial="张三" # 设置默认值)pwd = forms.CharField(min_length=6, label="密码")error_messages重写错误信息。class LoginForm(forms.Form):username = forms.CharField(min_length=8,label="用户名",initial="张三",error_messages={"required": "不能为空","invalid": "格式错误","min_length": "用户名最短8位"})pwd = forms.CharField(min_length=6, label="密码")passwordclass LoginForm(forms.Form):...pwd = forms.CharField(min_length=6,label="密码",widget=forms.widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True))radioSelect单radio值为字符串class LoginForm(forms.Form):username = forms.CharField(min_length=8,label="用户名",initial="张三",error_messages={"required": "不能为空","invalid": "格式错误","min_length": "用户名最短8位"})pwd = forms.CharField(min_length=6, label="密码")gender = forms.fields.ChoiceField(choices=((1, "男"), (2, "女"), (3, "保密")),label="性别",initial=3,widget=forms.widgets.RadioSelect())单选Selectclass LoginForm(forms.Form):...hobby = forms.fields.ChoiceField(choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),label="爱好",initial=3,widget=forms.widgets.Select())多选Selectclass LoginForm(forms.Form):...hobby = forms.fields.MultipleChoiceField(choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),label="爱好",initial=[1, 3],widget=forms.widgets.SelectMultiple())单选checkboxclass LoginForm(forms.Form):...keep = forms.fields.ChoiceField(label="是否记住密码",initial="checked",widget=forms.widgets.CheckboxInput())多选checkboxclass LoginForm(forms.Form):...hobby = forms.fields.MultipleChoiceField(choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),label="爱好",initial=[1, 3],widget=forms.widgets.CheckboxSelectMultiple())关于choice的注意事项:在使用选择标签时,需要注意choices的选项可以从数据库中获取,但是由于是静态字段 ***获取的值无法实时更新***,那么需要自定义构造方法从而达到此目的。方式一:from django.forms import Formfrom django.forms import widgetsfrom django.forms import fieldsclass MyForm(Form):user = fields.ChoiceField(# choices=((1, '上海'), (2, '北京'),),initial=2,widget=widgets.Select)def __init__(self, *args, **kwargs):super(MyForm,self).__init__(*args, **kwargs)# self.fiel
Django auth认证系统
Django自带的用户认证我们在开发一个网站的时候,无可避免的需要设计实现网站的用户系统。此时我们需要实现包括用户注册、用户登录、用户认证、注销、修改密码等功能,这还真是个麻烦的事情呢。Django作为一个完美主义者的终极框架,当然也会想到用户的这些痛点。它内置了强大的用户认证系统--auth,它默认使用 auth_user 表来存储用户数据。auth模块from django.contrib import authauth中提供了许多实用方法:authenticate()   提供了用户认证功能,即验证用户名以及密码是否正确,一般需要username 、password两个关键字参数。如果认证成功(用户名和密码正确有效),便会返回一个 User 对象。authenticate()会在该 User 对象上设置一个属性来标识后端已经认证了该用户,且该信息在后续的登录过程中是需要的。用法:user = authenticate(username='theuser',password='thepassword')login(HttpRequest, user)该函数接受一个HttpRequest对象,以及一个经过认证的User对象。该函数实现一个用户登录的功能。它本质上会在后端为该用户生成相关session数据。用法:from django.contrib.auth import authenticate, logindef my_view(request):username = request.POST['username']password = request.POST['password']user = authenticate(username=username, password=password)if user is not None:login(request, user)# Redirect to a success page....else:# Return an 'invalid login' error message....logout(request) 该函数接受一个HttpRequest对象,无返回值。当调用该函数时,当前请求的session信息会全部清除。该用户即使没有登录,使用该函数也不会报错。用法:from django.contrib.auth import logoutdef logout_view(request):logout(request)# Redirect to a success page.is_authenticated()用来判断当前请求是否通过了认证。用法:def my_view(request):if not request.user.is_authenticated():return redirect('%s?next=%s' % (settings.LOGIN_URL, request.path))login_requierd()auth 给我们提供的一个装饰器工具,用来快捷的给某个视图添加登录校验。用法:from django.contrib.auth.decorators import login_required@login_requireddef my_view(request):...若用户没有登录,则会跳转到django默认的 登录URL '/accounts/login/ ' 并传递当前访问url的绝对路径 (登陆成功后,会重定向到该路径)。如果需要自定义登录的URL,则需要在settings.py文件中通过LOGIN_URL进行修改。示例:LOGIN_URL = '/login/' # 这里配置成你项目登录页面的路由create_user()auth 提供的一个创建新用户的方法,需要提供必要参数(username、password)等。用法:from django.contrib.auth.models import Useruser = User.objects.create_user(username='用户名',password='密码',email='邮箱',...)create_superuser()auth 提供的一个创建新的超级用户的方法,需要提供必要参数(username、password)等。用法:from django.contrib.auth.models import Useruser = User.objects.create_superuser(username='用户名',password='密码',email='邮箱',...)check_password(password)auth 提供的一个检查密码是否正确的方法,需要提供当前请求用户的密码。密码正确返回True,否则返回False。用法:ok = user.check_password('密码')set_password(password)auth 提供的一个修改密码的方法,接收 要设置的新密码 作为参数。注意:设置完一定要调用用户对象的save方法!!!用法:user.set_password(raw_password)user.save()一个修改密码功能的简单示例@login_requireddef set_password(request):user = request.usererr_msg = ''if request.method == 'POST':old_password = request.POST.get('old_password', '')new_password = request.POST.get('new_password', '')repeat_password = request.POST.get('repeat_password', '')# 检查旧密码是否正确if user.check_password(old_password):if not new_password:err_msg = '新密码不能为空'elif new_password != repeat_password:err_msg = '两次密码不一致'else:user.set_password(new_password)user.save()return redirect("/login/")else:err_msg = '原密码输入错误'content = {'err_msg': err_msg,}return render(request, 'set_password.html', content)修改密码示例User对象的属性User对象属性:username, passwordis_staff : 用户是否拥有网站的管理权限.is_active : 是否允许用户登录, 设置为 False,可以在不删除用户的前提下禁止用户登录。 扩展默认的auth_user表这内置的认证系统这么好用,但是auth_user表字段都是固定的那几个,我在项目中没法拿来直接使用啊!比如,我想要加一个存储用户手机号的字段,怎么办?聪明的你可能会想到新建另外一张表然后通过一对一和内置的auth_user表关联,这样虽然能满足要求但是有没有更好的实现方式呢?答案是当然有了。我们可以通过继承内置的 AbstractUser 类,来定义一个自己的Model类。这样既能根据项目需求灵活的设计用户表,又能使用Django强大的认证系统了。from django.contrib.auth.models import AbstractUserclass UserInfo(AbstractUser):"""用户信息表"""nid = models.AutoField(primary_key=True)phone = models.CharField(max_length=11, null=True, unique=True)def __str__(self):return self.username注意:按上面的方式扩展了内置的auth_user表之后,一定要在settings.py中告诉Django,我现在使用我新定义的UserInfo表来做用户认证。写法如下:# 引用Django自带的User表,继承使用时需要设置AUTH_USER_MODEL = "app名.UserInfo"再次注意:一旦我们指定了新的认证系统所使用的表,我们就需要重新在数据库中创建该表,而不能继续使用原来默认的auth_user表了。 附加在settings.py中修改配置:# LANGUAGE_CODE = 'en-us'LANGUAGE_CODE = 'zh-hans' # django项目变成中文# TIME_ZONE = 'UTC'TIME_ZONE = 'Asia/Shanghai' # 时区变成上海 
Django admin
Django admin使用admin appDjango 提供了基于 web 的管理工具。Django 自动管理工具是 django.contrib 的一部分。你可以在项目的 settings.py 中的 INSTALLED_APPS 看到它:settings.py中INSTALLED_APPS = ['django.contrib.admin','django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles',"app01"]django.contrib是一套庞大的功能集,它是Django基本代码的组成部分。 启用admin管理工具要启动admin管理工具,只需要在 urls.py 中启动admin对应的路由配置项即可。通常我们正在使用PyCharm生成项目时会在 urls.py 中自动设置好。from django.conf.urls import urlfrom django.contrib import adminurlpatterns = [url(r'^admin/', admin.site.urls),] 使用admin管理工具首先先启动我们的Django项目,然后在浏览器中访问 http://127.0.0.1:8000/admin/,就能看到登陆界面。(具体网址和端口根据自己环境输入)使用 python manage.py createsuperuser 来创建管理员账号,来登录admin管理后台。想要在admin 界面管理某个数据模型(model),我们需要先将该数据模型(model)注册到 admin。admin.site.register(models.Publisher)admin.site.register(models.Book)admin.site.register(models.Author)在admin.py中只要按照上面的方式将某个模型类(model class)注册,即可在Admin中实现该model对应的数据表增删改查的功能。 admin的定制这种方式的注册比较简单,如果想要进行更多的定制操作,需要利用ModelAdmin进行操作,如:# 注册方式1class PublisherAdmin(admin.ModelAdmin):list_display = ("name", "address")admin.site.register(models.Publisher, PublisherAdmin)# 注册方式[email protected](models.Book)class BookAdmin(admin.ModelAdmin):list_display = ("title", "price", "publish_date", "publisher")ModelAdmin支持的定制项ModelAdmin中提供了大量的可定制功能,如 1. list_display,列表时,定制显示的列。@admin.register(models.UserInfo)class UserAdmin(admin.ModelAdmin):list_display = ('user', 'pwd', 'xxxxx')def xxxxx(self, obj):return "xxxxx"2. list_display_links,列表时,定制列可以点击跳转。@admin.register(models.UserInfo)class UserAdmin(admin.ModelAdmin):list_display = ('user', 'pwd', 'xxxxx')list_display_links = ('pwd',)3. list_filter,列表时,定制右侧快速筛选。4. list_select_related,列表时,连表查询是否自动select_related5. list_editable,列表时,可以编辑的列 @admin.register(models.UserInfo)class UserAdmin(admin.ModelAdmin):list_display = ('user', 'pwd','ug',)list_editable = ('ug',)6. search_fields,列表时,模糊搜索的功能@admin.register(models.UserInfo)class UserAdmin(admin.ModelAdmin):search_fields = ('user', 'pwd')7. date_hierarchy,列表时,对Date和DateTime类型进行搜索@admin.register(models.UserInfo)class UserAdmin(admin.ModelAdmin):date_hierarchy = 'ctime'8 . inlines,详细页面,如果有其他表和当前表做FK,那么详细页面可以进行动态增加和删除class UserInfoInline(admin.StackedInline): # TabularInlineextra = 0model = models.UserInfoclass GroupAdminMode(admin.ModelAdmin):list_display = ('id', 'title',)inlines = [UserInfoInline, ]9 action,列表时,定制action中的操作@admin.register(models.UserInfo)class UserAdmin(admin.ModelAdmin):# 定制Action行为具体方法def func(self, request, queryset):print(self, request, queryset)print(request.POST.getlist('_selected_action'))func.short_description = "中文显示自定义Actions"actions = [func, ]# Action选项都是在页面上方显示actions_on_top = True# Action选项都是在页面下方显示actions_on_bottom = False# 是否显示选择个数actions_selection_counter = True --------------------------------------------------------------------------------------------------------------------------------- 10. 定制HTML模板add_form_template = Nonechange_form_template = Nonechange_list_template = Nonedelete_confirmation_template = Nonedelete_selected_confirmation_template = Noneobject_history_template = None11. raw_id_fields,详细页面,针对FK和M2M字段变成以Input框形式@admin.register(models.UserInfo)class UserAdmin(admin.ModelAdmin):raw_id_fields = ('FK字段', 'M2M字段',)12. fields,详细页面时,显示字段的字段@admin.register(models.UserInfo)class UserAdmin(admin.ModelAdmin):fields = ('user',)13. exclude,详细页面时,排除的字段@admin.register(models.UserInfo)class UserAdmin(admin.ModelAdmin):exclude = ('user',)14. readonly_fields,详细页面时,只读字段@admin.register(models.UserInfo)class UserAdmin(admin.ModelAdmin):readonly_fields = ('user',)15. fieldsets,详细页面时,使用fieldsets标签对数据进行分割显示@admin.register(models.UserInfo)class UserAdmin(admin.ModelAdmin):fieldsets = (('基本数据', {'fields': ('user', 'pwd', 'ctime',)}),('其他', {'classes': ('collapse', 'wide', 'extrapretty'), # 'collapse','wide', 'extrapretty''fields': ('user', 'pwd'),}),)16. 详细页面时,M2M显示时,数据移动选择(方向:上下和左右)@admin.register(models.UserInfo)class UserAdmin(admin.ModelAdmin):filter_vertical = ("m2m字段",) # 或filter_horizontal = ("m2m字段",)17. ordering,列表时,数据排序规则@admin.register(models.UserInfo)class UserAdmin(admin.ModelAdmin):ordering = ('-id',)或def get_ordering(self, request):return ['-id', ]18. radio_fields,详细页面时,使用radio显示选项(FK默认使用select)radio_fields = {"ug": admin.VERTICAL} # 或admin.HORIZONTAL19. form = ModelForm,用于定制用户请求时候表单验证from app01 import modelsfrom django.forms import ModelFormfrom django.forms import fieldsclass MyForm(ModelForm):others = fields.CharField()class Meta:model = models = models.UserInfofields = "__all__"@admin.register(models.UserInfo)class UserAdmin(admin.ModelAdmin):form = MyForm20. empty_value_display =
Django logging配置
做开发离不开日志,以下是我在工作中写Django项目常用的logging配置。BASE_LOG_DIR = os.path.join(BASE_DIR, "log")LOGGING = {'version': 1,'disable_existing_loggers': False,'formatters': {'standard': {'format': '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]''[%(levelname)s][%(message)s]'},'simple': {'format': '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'},'collect': {'format': '%(message)s'}},'filters': {'require_debug_true': {'()': 'django.utils.log.RequireDebugTrue',},},'handlers': {'console': {'level': 'DEBUG','filters': ['require_debug_true'], # 只有在Django debug为True时才在屏幕打印日志'class': 'logging.StreamHandler','formatter': 'simple'},'SF': {'level': 'INFO','class': 'logging.handlers.RotatingFileHandler', # 保存到文件,根据文件大小自动切'filename': os.path.join(BASE_LOG_DIR, "xxx_info.log"), # 日志文件'maxBytes': 1024 * 1024 * 50, # 日志大小 50M'backupCount': 3, # 备份数为3 xx.log --> xx.log.1 --> xx.log.2 --> xx.log.3'formatter': 'standard','encoding': 'utf-8',},'TF': {'level': 'INFO','class': 'logging.handlers.TimedRotatingFileHandler', # 保存到文件,根据时间自动切'filename': os.path.join(BASE_LOG_DIR, "xxx_info.log"), # 日志文件'backupCount': 3, # 备份数为3 xx.log --> xx.log.2018-08-23_00-00-00 --> xx.log.2018-08-24_00-00-00 --> ...'when': 'D', # 每天一切, 可选值有S/秒 M/分 H/小时 D/天 W0-W6/周(0=周一) midnight/如果没指定时间就默认在午夜'formatter': 'standard','encoding': 'utf-8',},'error': {'level': 'ERROR','class': 'logging.handlers.RotatingFileHandler', # 保存到文件,自动切'filename': os.path.join(BASE_LOG_DIR, "xxx_err.log"), # 日志文件'maxBytes': 1024 * 1024 * 5, # 日志大小 50M'backupCount': 5,'formatter': 'standard','encoding': 'utf-8',},'collect': {'level': 'INFO','class': 'logging.handlers.RotatingFileHandler', # 保存到文件,自动切'filename': os.path.join(BASE_LOG_DIR, "xxx_collect.log"),'maxBytes': 1024 * 1024 * 50, # 日志大小 50M'backupCount': 5,'formatter': 'collect','encoding': "utf-8"}},'loggers': {'': { # 默认的logger应用如下配置'handlers': ['SF', 'console', 'error'], # 上线之后可以把'console'移除'level': 'DEBUG','propagate': True,},'collect': { # 名为 'collect'的logger还单独处理'handlers': ['console', 'collect'],'level': 'INFO',}},}# 日志文件的配置BASE_LOG_DIR = os.path.join(BASE_DIR, "log") # 项目日志文件存放的目录LOGGING = { # 日志的配置项'version': 1, # 保留字 默认就是1'disable_existing_loggers': False, # 是否禁用Django框架开发的时候已经存在的logger实例'formatters': { # 格式化器'standard': { # 标准的格式 (这些格式化的键 都是自定义命名的)'format': '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]''[%(levelname)s][%(message)s]'},'simple': { # 简单的格式'format': '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'},'collect': { # collect格式'format': '%(message)s'}},'filters': { # 过滤器'require_debug_true': {'()': 'django.utils.log.RequireDebugTrue',},},'handlers': { # 处理器'console': { # 定义一个将日志在终端输出的处理器'level': 'DEBUG','filters': ['require_debug_true'], # 只有在Django debug为True时才在屏幕打印日志'class': 'logging.StreamHandler', # 日志流'formatter': 'simple' # 用简单的格式打印日志},'SF': { # 定义一个名为SF的日志处理器(名字自定义)'level': 'INFO','class': 'logging.handlers.RotatingFileHandler', # 保存到文件,根据文件大小自动切'filename': os.path.join(BASE_LOG_DIR, "xxx_info.log"), # 日志文件'maxBytes': 1024 * 1024 * 50, # 日志大小 50M'backupCount': 3, # 备份数为3 xx.log --> xx.log.1 --> xx.log.2 --> xx.log.3'formatter': 'standard', # 使用标准格式记录日志'encoding': 'utf-8', # 日志文件的编码},'TF': { # 定义一个名为TF的日志处理器(名字自定义)'level': 'INFO','class': 'logging.handlers.TimedRotatingFileHandler', # 保存到文件,根据时间自动切'filename': os.path.join(BASE_LOG_DIR, "xxx_info.log"), # 日志文件'backupCount': 3, # 备份数为3 xx.log --> xx.log.2018-08-23_00-00-00 --> xx.log.2018-08-24_00-00-00 --> ...'when': 'D', # 每天一切, 可选值有S/秒 M/分 H/小时 D/天 W0-W6/周(0=周一) midnight/如果没指定时间就默认在午夜'formatter': 'standard','encoding': 'utf-8',},'error': {'level': 'ERROR','class': 'logging.handlers.RotatingFileHandler', # 保存到文件,自动切'filename': os.path.join(BAS
Django RBAC用户权限设计方案
RBAC基于用户权限系统设置方案 RBAC(Role-Based Access Control,基于角色的访问控制),就是用户通过角色与权限进行关联。简单地说,一个用户拥有若干角色,每一个角色拥有若干权限。这样,就构造成“用户-角色-权限”的授权模型。在这种模型中,用户与角色之间,角色与权限之间,一般者是多对多的关系。(如下图)角色是什么?可以理解为一定数量的权限的集合,权限的载体。例如:一个论坛系统,“超级管理员”、“版主”都是角色。版主可管理版内的帖子、可管理版内的用户等,这些是权限。要给某个用户授予这些权限,不需要直接将权限授予用户,可将“版主”这个角色赋予该用户。当用户的数量非常大时,要给系统每个用户逐一授权(授角色),是件非常烦琐的事情。这时,就需要给用户分组,每个用户组内有多个用户。除了可给用户授权外,还可以给用户组授权。这样一来,用户组拥有的所有权限,就是用户个人拥有的权限与该用户所在用户组拥有的权限之和。(下图为用户组、用户与角色三者的关联关系)在应用系统中,权限表现成什么?对功能模块的操作,对上传文件的删改,菜单的访问,甚至页面上某个按钮、某个图片的可见性控制,都可属于权限的范畴。有些权限设计,会把功能操作作为一类,而把文件、菜单、页面元素等作为另一类,这样构成“用户-角色-权限-资源”的授权模型。而在做数据表建模时,可把功能操作和资源统一管理,也就是都直接与权限表进行关联,这样可能更具便捷性和易扩展性。(见下图)请留意权限表中有一列“权限类型”,我们根据它的取值来区分是哪一类权限,如“MENU”表示菜单的访问权限、“OPERATION”表示功能模块的操作权限、“FILE”表示文件的修改权限、“ELEMENT”表示页面元素的可见性控制等。这样设计的好处有二。其一,不需要区分哪些是权限操作,哪些是资源,(实际上,有时候也不好区分,如菜单,把它理解为资源呢还是功能模块权限呢?)。其二,方便扩展,当系统要对新的东西进行权限控制时,我只需要建立一个新的关联表“权限XX关联表”,并确定这类权限的权限类型字符串。这里要注意的是,权限表与权限菜单关联表、权限菜单关联表与菜单表都是一对一的关系。(文件、页面权限点、功能操作等同理)。也就是每添加一个菜单,就得同时往这三个表中各插入一条记录。这样,可以不需要权限菜单关联表,让权限表与菜单表直接关联,此时,须在权限表中新增一列用来保存菜单的ID,权限表通过“权限类型”和这个ID来区分是种类型下的哪条记录。到这里,RBAC权限模型的扩展模型的完整设计图如下:随着系统的日益庞大,为了方便管理,可引入角色组对角色进行分类管理,跟用户组不同,角色组不参与授权。例如:某电网系统的权限管理模块中,角色就是挂在区局下,而区局在这里可当作角色组,它不参于权限分配。另外,为方便上面各主表自身的管理与查找,可采用树型结构,如菜单树、功能树等,当然这些可不需要参于权限分配。以上,是从基本的RBAC模型进行了扩展,具体的设计要根据项目业务的需要作调整。
Django ContentType组件
ContentType组件引入现在我们有这样一个需求~我们的商城里有很多的商品~~节日要来了~我们要搞活动~~那么我们就要设计优惠券~~优惠券都有什么类型呢~~满减的~折扣的~立减的~~我们对应着我们活动类型~对我们的某类商品设计优惠券~~比如~~家电是一类商品~~食物是一类商品~那么我们可以设计家电折扣优惠券~~以及食物满减优惠券等~那么我们看表结构怎么设计from django.db import modelsclass Appliance(models.Model):"""家用电器表id name1 冰箱2 电视3 洗衣机"""name = models.CharField(max_length=64)class Food(models.Model):"""食物表id name1 面包2 牛奶"""name = models.CharField(max_length=32)class Fruit(models.Model):"""水果表id name1 苹果2 香蕉"""name = models.CharField(max_length=32)class Coupon(models.Model):"""优惠券表id name appliance_id food_id fruit_id1 通用优惠券 null null null2 冰箱折扣券 1 null null3 电视折扣券 2 null null4 苹果满减卷 null null 1我每增加一张表就要多增加一个字段"""name = models.CharField(max_length=32)appliance = models.ForeignKey(to="Appliance", null=True, blank=True)food = models.ForeignKey(to="Food", null=True, blank=True)fruit = models.ForeignKey(to="Fruit", null=True, blank=True)<br># 实际上我们商品的种类会特别的多,导致我们这张表外键越来越多 遇到这种一张表要跟多张表进行外键关联的时候~我们Django提供了ContentType组件~ContentType组件ContentType是Django的内置的一个应用,可以追踪项目中所有的APP和model的对应关系,并记录在ContentType表中。当我们的项目做数据迁移后,会有很多django自带的表,其中就有django_content_type表,我们可以去看下 ContentType组件应用:-- 在model中定义ForeignKey字段,并关联到ContentType表,通常这个字段命名为content-type-- 在model中定义PositiveIntergerField字段, 用来存储关联表中的主键,通常我们用object_id-- 在model中定义GenericForeignKey字段,传入上面两个字段的名字--  方便反向查询可以定义GenericRelation字段代码如下:from django.db import modelsfrom django.contrib.contenttypes.models import ContentTypefrom django.contrib.contenttypes.fields import GenericForeignKey, GenericRelationclass Appliance(models.Model):"""家用电器表id name1 冰箱2 电视3 洗衣机"""name = models.CharField(max_length=64)coupons = GenericRelation(to="Coupon")class Food(models.Model):"""食物表id name1 面包2 牛奶"""name = models.CharField(max_length=32)class Fruit(models.Model):"""水果表id name1 苹果2 香蕉"""name = models.CharField(max_length=32)class Coupon(models.Model):"""优惠券表id name appliance_id food_id fruit_id1 通用优惠券 null null null2 冰箱折扣券 1 null null3 电视折扣券 2 null null4 苹果满减卷 null null 1我每增加一张表就要多增加一个字段"""name = models.CharField(max_length=32)# appliance = models.ForeignKey(to="Appliance", null=True, blank=True)# food = models.ForeignKey(to="Food", null=True, blank=True)# fruit = models.ForeignKey(to="Fruit", null=True, blank=True)# 第一步content_type = models.ForeignKey(to=ContentType)# 第二步object_id = models.PositiveIntegerField()# 第三步content_object = GenericForeignKey('content_type', 'object_id')数据迁移后~添加数据~我们看下增删改查的操作~~基本的使用~from django.http import HttpResponsefrom rest_framework.views import APIViewfrom rest_framework.response import Responsefrom django.contrib.contenttypes.models import ContentTypefrom .models import Appliance, Coupon# Create your views here.class Test(APIView):def get(self, request):# 通过ContentType获得表名content = ContentType.objects.filter(app_label="app01", model="appliance").first()# 获得表model对象 相当于models.Applicancemodel_class = content.model_class()ret = model_class.objects.all()# 为海尔冰箱创建一条优惠记录ice_box = Appliance.objects.filter(id=1).first()Coupon.objects.create(name="海尔冰箱折扣券", content_object=ice_box)# 查询优惠券id=1绑定了哪个商品coupon_obj = Coupon.objects.filter(id=1).first()goods_obj = coupon_obj.content_objectprint(goods_obj.name)# 查询海尔冰箱的所有优惠券 id=1# 我们定义了反向查询results = ice_box.coupons.all()print(results[0].name)# 如果没定义反向查询content = ContentType.objects.filter(app_label="app01", model="appliance").first()result = Coupon.objects.filter(content_type=content, object_id=1).all()print(result[0].name)return HttpResponse(ret)