Django-基本介绍
文档:https://docs.djangoproject.com/zh-hans/4.1/#index-first-steps
| Django是python编写的开放的原代码web服务框架 |
| 解决了复杂的web实现过长,人员能专注于核心功能的编写。 |
| Django 本身 MTV 模型,即 Model(模型)+ Template(模板) + View(视图)设计模式,实现快速开发。基于MVC实现的,除了当前还有一个url分发器,将访问url的页面请求分发给不同的视图进行处理 |
| |
| MVC是什么:模型,视图,控制器 |
| |

Django-基本命令介绍
| 1.创建项目 |
| django-admin startproject 项目名称 |
| |
| 2.创建子应用程序[需要进入项目中创建,需要在配置文件中注册当前app程序] |
| django-admin manage.py startapp 应用名字 |
| |
| 3.超级用户注册 |
| django-admin manage.py createsuperuser |
| |
| 4.启动命令[指定端口一直] |
| python manage.py runserver 127.0.0.1:7000 |
| |
| 5.数据库迁移命令 |
| python manage.py makemigrations |
| $ python manage.py migrate |
Django-目录结构
| -dome/ |
| asgi.py |
| settings.py |
| urls.py |
| wsgi.py |
| init.py |
| -app01 |
| models.py |
| views.py |
| tests.py |
| apps.py |
| admin.py |
| -templates |
| -static |
| -db.sqlite3 |
| -manage.py |
Django-文件上传设置
| 可以配合orm字段进行使用 |
| 1.在配置文件中设置 |
| MEDIA_URL = '/media/' |
| MEDIA_ROOT = os.path.join(BASE_DIR, 'media') |
| |
| 2.在models中设置字段 |
| %Y代表年,%m代表月 |
| image = models.ImageField(upload_to="image/%Y/%m", blank=True) |
| |
| 3.设置url访问接口 |
| from django.urls import path,re_path |
| from django.views.static import serve |
| from cnblog import settings |
| |
| re_path(r'media/(?P<path>.*)$',serve,{'document_root':settings.MEDIA_ROOT}), |
| |
| 我们用FileField存放上传的文件,这里需要注意:media文件夹是我们上传文件的“根目录”,如果我们想再为这个“根目录”指定“子目录”的话需要通过参数upload_to去指定,也就是说,我们上传的文件会保存在/media/avatars目录下,后面的参数default表示默认图像————比如说我们想要上传头像,用户不指定头像的时候就用default参数指定的图片。 |
| 外部地址栏输入文件路径的 |
| 我们做程序的目的就是尽最大限度的解放用户的操作————那么,是否有一种方法能够让用户仅仅点击一下就能查看到对应的文件呢? |
| 然后我们在settings.py中加入MEDIA_URL的参数 |
| |
| |
| 用户上传的文件路径就会 |
| os.path.join(MEDIA_URL,upload_to,'文件的名称') |
Django-静态文件配置/静态文件外部访问路由
| 1.配置文件设置 设置静态路径 |
| MEDIA_ROOT = os.path.join(BASE_DIR,'static') |
| MEDIA_ROOT = '设置其他的指定文件夹' |
| |
| 2. url设置 |
| from django.views.static import serve |
| from django.conf import settings |
| |
| 1.设置路径 path属于url匹配命名,获取的参数 |
| 2.导入内置处理静态资源的视图函数 |
| 3.传入的指定参数(document_root在函数中属于关键字传参),导入的配置文件的中设置静态路径配置 |
| |
| |
| re_path(r'media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}), |
Django-配置静态文件/多APP
| 静态文件指的是:js,jq,前段框架文件bootstrap文件。这些文件只有通过配置,Django才能读取并且使用 |
| |
| 1.配置流程: |
| 1.在项目下创建 static 文件夹(存放外部引入的配置文件) |
| 2.找到settings.py文件 |
| 3.添加路径:STATICFILES_DIRS =[os.path.join(BASE_DIR,"static")] |
| 路径的含义: |
| 1.变量名 STATICFILES_DIRS 固定写法,不能更改 |
| 2.BASE_DIR django内部的变量 指向项目根目录路径,配置文件有说明 |
| 3.os.path.join(BASE_DIR,'static') |
| |
| 放各个app的static目录及公共的static目录 |
| STATICFILES_DIRS = [os.path.join(BASE_DIR,'web',"static"),os.path.join(BASE_DIR,'app01',"static")] |
| {% static 'imgs/study-logo.png' %} |
Django-settings.py参数
| from django.conf import settings |
| |
| |
| |
| django会把所有的static文件都复制到STATIC_ROOT文件夹下 |
| STATIC_ROOT |
| |
| |
| |
| STATICFILES_DIRS |
| 一种就是在每个app里面新建一个static文件夹,将静态文件放到里面,在加载静态文件时,比如要在模板中用到静态文件,django会自动在每个app里面搜索static文件夹(所以,不要把文件夹的名字写错, 否则django就找不到你的文件夹了) |
| 另一种,就是在所有的app文件外面,建立一个公共的文件夹, |
| 因为有些静态文件不是某个app独有的,那么就可以把它放到一个公共文件夹里面,方便管理(注意,建立一个公共的静态文件的文件夹只是一种易于管理的做法,但是不是必须的,app是可以跨app应用静态文件的,因为最后所有的静态文件都会在STATIC_ROOT里面存在 |
| |
| |
| STATIC_URL |
| |
| |
| LANGUAGE_CODE = 'zh-hans' |
| TIME_ZONE = 'Asia/Shanghai' |
| |
| |
| DEBUG = False 部署时设置参数 |
| 同时需要设置 |
| ALLOWED_HOSTS = ['*'] |
| DEBUG_PROPAGATE_EXCEPTIONS = True |
| |
| |
| DATABASES = { |
| 'default': { |
| 'ENGINE': 'django.db.backends.mysql', |
| sqlite3 oracle postgresql 都是mysql的引擎 |
| 'HOST':'127.0.0.1', |
| 'PORT':'3306', |
| 'USER':'root', |
| 'PASSWORD':'123', |
| 'NAME':'创建数据的名称' |
| 'CONN_MAX_AGE': 5*60, |
| } |
| } |
| |
| |
| 缓存配置 |
| 采用的是django-redis进行的配置 |
| |
| pip install django-redis 安装 |
| 官方文档:https://django-redis-chs.readthedocs.io/zh_CN/latest/ |
| |
| from django.core.cache import cache |
| from django_redis import get_redis_connection |
| |
| get_redis_connection('default') |
| |
| |
| get_redis_connection('default').connection_pool._created_connections |
| |
| cache |
| |
| get_redis_connection函数方法内部还是调用了django.core.cache |
| |
| |
| from redis.connection import ConnectionPool |
| |
| class MyOwnPool(ConnectionPool): |
| pass |
| |
| setting.py |
| "OPTIONS": { |
| "CONNECTION_POOL_CLASS": "myproj.mypool.MyOwnPool", |
| } |
| |
| CACHES = { |
| 'default': { |
| 'BACKEND': 'django_redis.cache.RedisCache', |
| 'LOCATION': 'redis://127.0.0.1:6379', |
| 'KEY_PREFIX': 'API_D', |
| 'TIMEOUT': 60, |
| 'OPTIONS': { |
| 'CLIENT_CLASS': 'django_redis.client.DefaultClient', |
| 'MAX_ENTRIES ': 300, |
| 'CULL_REQUENCY': 3, |
| |
| "CONNECTION_POOL_KWARGS": {"max_connections": 10} |
| } |
| } |
| } |
| |
| |
| ROOT_URLCONF 是父级路由 |
| ROOT_URLCONF = 'mydjango.urls' |
| |
| |
| INSTALLED_APPS = [ |
| 'django.contrib.admin', |
| 'django.contrib.auth', |
| 'django.contrib.contenttypes', |
| 'django.contrib.sessions', |
| 'django.contrib.messages', |
| 'django.contrib.staticfiles', |
| 'polis.apps.PolisConfig' |
| ] |
Django-命令行命令
| |
| django-admin startproject 项目名 |
| |
| |
| python manage.py runserver 可以跟上端口和ip |
| |
| |
| python manage.py startapp app名称 |
| 新添加的app需要在settings.py 中的 INSTALLED_APPS配置中添加当前的新app |
| |
| |
| python manage.py makemigrations 指定那个app |
| python manage.py migrate |
| |
| |
| python manage.py createsuperuser |
| |
| |
| django-admin check app程序(默认不设置全局) --settings=mydjango.settings(项目的配置文件) --pythonpath=C:\Users\wkx\Desktop\mydjango(项目的路径) |
| or |
| python manage.py check |
| |
| |
| django-admin dbshell --settings=mydjango.settings --pythonpath=C:\Users\wkx\Desktop\mydjango |
| or |
| python manage.py dbshell |
| |
| |
| python manage.py flush |
| |
| |
| python manage.py shell |
| |
| |
| 1.需要配置邮箱的相应内容 |
| EMAIL_HOST = 'smtp.qq.com' |
| EMAIL_HOST_USER = '565151759@qq.com' |
| EMAIL_HOST_PASSWORD = '*******' |
| EMAIL_USE_TLS = True |
| DEFAULT_FROM_EMAIL = '王开心 <565151759@qq.com>' |
| ADMINS = (('王开心', '565151759@qq.com'),) |
| |
| 2.执行 |
| python manage.py sendtestemail 发送的邮箱地址 |
| |
| |
| |
| 1.需要设置 |
| STATIC_ROOT = 'D:\static' |
| 2.执行命令 |
| python manage.py collectstatic |
| |
Django-路由
文档地址:https://docs.djangoproject.com/zh-hans/4.1/topics/http/urls/
| 创建'项目'的存在urls.py的文件---> 当前的文件数据 '全局路由' |
| 在settings.py配置文件中 |
| ROOT_URLCONF = '绑定的就是当前项目下的全局路由(默认绑定就是项目下的url.py文件)' |
| |
| 创建'app程序'的存在urls.py文件 ---> 属于'局部路由' |
| |
| 路由是什么: |
| 1. route路由是一种映射关系,路由把客户端请求的url路径和用户请求的应用程序 |
| 2. django中的路由就是将当前 路由与视图进行绑定。 |
| 3. 在django启动运行时,当客户端发送一个http请求到服务器中,服务器的web服务总http协议中获取url,通过url在内部的urlpatterns声明的路由中获取url进行匹配,成功后获取对应的视图函数,进行视图函数的内部逻辑的执行 |
| |
| |
| 在url.py文件中存在一个变量 'urlpatterns' 类型列表 每一个列表中的元素就存在一个对应关系 |
| |
| urlpatterns = [ |
| path('admin0/', admin.site.urls), 路由1 |
| path('admin1/', admin.site.urls), 路由2 |
| ] |
Django-路由对应关系与方式
| urls.py: |
| from django.urls import path, re_path,include |
| from app import views |
| |
| urlpatterns = [ |
| path('home/', admin.site.urls), |
| re_path('index/(\d+)/(\d+)/', admin.site.urls), |
| include('home2/',include('app02.urls')), |
| ] |
paht路由方法
| from django.urls impute path |
| |
| path的使用主要是,固定的路由固定的路径使用的方式/不适用于动态的获取参数 |
| |
| 1.不带参数 |
| 路由 |
| path('home/',viwes.home) |
| 视图 |
| def home(request): |
| pass |
| |
| |
| 2.带动态参数(路由转换器) |
| 路由 |
| path('home/<int:dey>/',viwes.home) |
| 视图 day接受了路由中<int:dey>的值 |
| def home(request,dey): |
| pass |
| |
| |
| 转换器类型: |
| str:匹配除了 '/' 之外的非空字符串。如果表达式内不包含转换器,则会默认匹配字符串 |
| int:匹配 0 或任何正整数。返回一个 int |
| slug:匹配任意由 ASCII 字母或数字以及连字符和下划线组成的短标签。比如,building-your-1st-django-site |
| uuid:匹配一个格式化的 UUID 。为了防止多个 URL 映射到同一个页面,必须包含破折号并且字符都为小写。比如,075194d3-6885-417e-a8a8-6c931e272f00。返回一个 UUID 实例 |
re_path路由方法
| from django.urls impute re_path |
| |
| 1.正则匹配(无名) |
| ([0-9]{2}):正则方式匹配0-9的两位数 |
| home/([0-9]{2})/: 正则的路由匹配规则 例如:home/11/ 或者 home/12/ |
| |
| 路由 |
| re_path(r'^home/([0-9]{2})/$',views.home) |
| 视图 |
| def home(request,num_int): |
| num_int: 接受([0-9]{2})匹配的参数 |
| pass |
| |
| |
| 2.正则匹配(有名) |
| 有名分组的设置:?P<名字> |
| (?P<year>[0-9]{4}):正则匹配0-9的4位数 |
| home/(?P<year>[0-9]{4})/: 正则的路由匹配规则 例如:home/1100/ 或者 home/1200/ |
| 路由: |
| re_path(r'^home/(?P<year>[0-9]{4})/$',viwes.home) |
| 视图: |
| def home(reuqest,year): |
| 视图接受的参数必须时路由有名分组的名称 |
| |
| pass |
| |
| 通过正则进行匹配 |
| |
| 有名分组与简单分组的区别: |
| 有名分组:按照的是关键字传参, |
| 1.给分组设置的名字,必须是视图函数接受的参数,不能变 |
| 2.位置变化,无所谓,因为按照关键字,已经赋值好了 |
| |
| 简单分组:按照的是位置传参 |
| 1.视图函数接受的参数设置什么都可以,但是位置不能变 |
| 2.因为时按照位置传参,一个位置对应一个值 |
include路由分发
| from django.urls import include,path |
| |
| |
| 当Django存在多个程序时,使用路由分发,可以将程序与程序间分割,方便管理 |
| |
| 1.分发路由: |
| 当访问home路由时,就会分发到app01的urls文件中的路由 |
| path('home/',include('app01.urls')) |
| |
| |
| 当请求来时,先到全局的urls文件下的路由,根据地址,分配到局部urls的路由,执行视图函数。 |
| |
| 2.app01程序urls.py |
| path('aaaa/',views.home) |
| |
| def home(request): |
| pass |
| |
| 当访问:home/aaaa/ 路由时就由app01下的视图home进行逻辑处理返回数据 |
| |
| |
| 3.使用re_path + include 设置为正则表达的以什么开头^ |
| re_path(r"^",include("app01.urls")) |
自定义转换器设置
| 1.创建类 |
| |
| class A: |
| regex = '[0-9]{2}' |
| |
| |
| def to_python(self, value): |
| |
| return int(value) |
| |
| def to_url(self, value): |
| |
| return str(value) |
| |
| 2.注册自定义转换器 |
| from django.urls import path, register_converter |
| register_converter('类名','自定义转换器名称') |
| |
| |
| 3.使用自定义转换器 |
| path('articles/<自定义转换器别名:参数名>/',views.函数名), |
路由的反向解析
| 反向解析:就是给路由设置一个别名,无论路由地址怎么更换,别名永远指向路由的最新地址 |
| |
| 在前后端不分离的在模板是使用 |
| |
| 1.通过模板反向解析 |
| |
| 路由 |
| from django.urls import path |
| |
| urls.py 设置name名为路由设置一个别名 |
| path('home/([0-9{4}])/', views.home,name='home') |
| |
| 模板:通过url进行反向解析路由 |
| |
| <a href="{% url home %}"> 点击进入home页面 </a> |
| |
| <a href="{% url home 参数1 %}"> 点击进入home页面 </a> |
| |
| <a href="{% url home 参数1名=参数1 %}"> 点击进入home页面 </a> |
| 视图函数在返回 render方法对html在内部进行处理,将模板语法读取和解析,django会进行反向解析,解析{% url "别名" %},找到别名指向的路由,视图函数在返回 |
| |
| |
| |
| 2.通过视图进行反向解析 |
| |
| 路由 |
| from django.urls import path |
| |
| urls.py 设置name名为路由设置一个别名 |
| path('home/([0-9{4}])/', views.home,name='home') |
| |
| 视图: |
| from django.urls import reverse |
| |
| |
| def index(request): |
| |
| url = reverse('home',args=('如果有参数')) |
路由命名空间
| 命名空间:主要用于反向解析中。 |
| 当django中存在两个程序,同时出现两个需要路由分发urls.py文件。同时两个路由路径的名字相同时,可以使用命名空间。 |
| |
| |
| |
| 1.当项目中存在多个app程序,同时当前的app的路由时相同 |
| app01.urls.py |
| path('home/([0-9]{2})/', views.函数,name='home') |
| |
| app02.urls.py |
| path('home/([0-9]{2})/', views.函数,name='home') |
| |
| 出现问题: |
| 当浏览器进行访问只能找到1个路由路径 app02,因为app02将app01覆盖。进行反向解析,也只能找到app02路径地址。 |
| |
| 2.那么需要通过路由分发进行处理 |
| 如果使用include分发方式,设置别名 |
| include(参数1:元祖[arg],参数2:分发的别名) |
| path('app01/',include(('app01.urls','app01'),namespace='app01')) |
| path('app02/',include(('app01.urls','app02'),namespace='app02')), |
| |
| 在源码中:元祖会被解构为 |
| urlconf_module, app_name = arg |
| |
| |
| 3.在模板中使用 |
| <a href="{% url 'app01:home' 19 %}">点击到home路由</a> |
| |
| 4.在视图中怎么解析 |
| from django.urls import reverse |
| url = reverse('app01:home',args=(18,)) |
路由补充内容
| 1.传入额外的值给url |
| 路由 |
| path('home/',views.home,{'name':'xxx'}) |
| |
| 视图 |
| def home(request,name): |
| pass |
Django-视图
| 是一种接受 Web 请求并返回 Web 响应的 Python 函数。这个响应可以是网页的 HTML 内容,也可以是重定向,也可以是 404 错误,也可以是 XML 文档,也可以是图像。. . 或任何东西,真的。视图本身包含返回该响应所需的任何任意逻辑 |
响应方法
| from django.shortcuts import render,HttpResponse,redirect |
| |
| |
| 1.HttpResponse作用 |
| django服务器接受到客户单发来的请求后,会将提交上来的这个数据封装为HttpResponse对象传给视图,视图处理完毕响应的逻辑,也需要返回一个响应给浏览器,而这个响应我们必须返回一个HttpResponseBase或者他的子类对象,HttpResponse是HttpResponseBase用的最多的子类 |
| |
| def A3(request) |
| |
| |
| return HttpResponse('字符串') |
| |
| |
| 2.render,作用和返回值 |
| |
| def A1(request): |
| |
| render只用两种形式 |
| 1. render(request,'模板.html') |
| 2. render(request,'模板.html',{"xx":xx}) |
| |
| return render(request, "html页面") |
| |
| |
| 3.redirect重定向 |
| |
| def A2(request): |
| |
| |
| return redirect('/路径/') |
| |
| 重定向可以配合着reverse方法一起使用 |
| 例如:通过反向解析 |
| 路由 |
| path(r'home/<int:year>/', views.home, name='home') |
| 视图 |
| def index(request): |
| url = reverse('home', args=(11,)) |
| return redirect(url) |
| |
| |
| 4.JsonResponse |
| from django.http import JsonResponse |
| |
| def index(request): |
| |
| return JsonResponse(xx) |
| |
| |
| xx = [{"xx":x,"zz":x},{"xx":x,"zz":x},{"xx":x,"zz":x}] |
| JsonResponse(xx,safe=False) |
| |
| |
| |
| render,redirect |
请求方法
| 视图: |
| def A1(request): |
| |
| |
| |
| return render(request, "html页面") |
| |
| 1.获取请求方式 |
| print(request.method) |
| |
| |
| 2.获取post请求的属性 |
| print(request.POST) |
| |
| |
| |
| |
| 3.获取get请求的属性 |
| print(request.GET) |
| |
| |
| |
| |
| 4.获取 post请求的值/get请求值 |
| |
| request.POST.post('键') |
| request.POST.get('键')/request.GET.get('键') |
| |
| 如果url:.../home/?p=10&a=20&p=30 就可以将全部的p值获取 |
| reqeuest.POST.getlist('键')/qeuest.GET.getlist('键') |
| |
| |
| 5.获取请求路径 |
| request.path_info |
| request.path |
| request.get_full_path() |
| request.get_full_path_info() |
| request.get_raw_uri() |
| request.get_host() |
| request.get_port() |
| |
| 6.获取请求头 |
| request.META |
| request.META.get('键') |
| |
| 7.body |
| request.body |
其他方法-APPEND_SLASH
| paht('index',views.index) |
| 127.0.0.1/index |
| |
| paht('index/',views.index) |
| 127.0.0.1/index |
| |
| 127.0.0.1/index/ |
| |
| |
| APPEND_SLASH = True |
| APPEND_SLASH = False |
Django-模板语法
| 模板语法就是将后端的数据填充到html页面进行渲染的技术。他实现了前端代码和服务端代码分离的作用,让业务代码和数据表现分离代码分离,让前端和后端开发能协同完成开发。 |
| django内置了一个著名的模板引擎dtl |
| 要想完成模板引擎的使用需要 |
| 1.在配置文件中保存模板文件的模板目录 |
| 2.在视图中基于django提供的渲染函数绑定模板文件和需要展示的数据 |
| 3.在模板目录下创建对应的模板文件,使用模板语法传入数据 |
| |
| |
| TEMPLATES = [ |
| { |
| 'BACKEND': 'django.template.backends.django.DjangoTemplates', |
| 'DIRS': [os.path.join(BASE_DIR, 'templates')] |
| , |
| 'APP_DIRS': True, |
| 'OPTIONS': { |
| 'context_processors': [ |
| 'django.template.context_processors.debug', |
| 'django.template.context_processors.request', |
| 'django.contrib.auth.context_processors.auth', |
| 'django.contrib.messages.context_processors.messages', |
| ], |
| }, |
| }, |
| ] |
| |
| |
本质
| 其实模板语法本质的上的步骤就是这样的。只不过被django封装了一个方法,将这几步给省了 |
| |
| from django.template.loader import get_template |
| 1.原 |
| html = get_template('index.html') |
| tate ={'name':name} |
| content = html.render(tate,request) |
| HttpResponse(content) |
| |
| 2.封装为 |
| render(request,"模板。html",{"参数名":参数}) |
模板语法
| 变量:{{ }} 渲染变量使用的 |
| 1. 深度查询 句点符 |
| 2. 过滤器 语法:{变量名val|内置过滤器:参数} |
| |
| 标签:{% %} 渲染标签 |
变量渲染-深度查询
| locals() |
| |
| 使用方式: |
| return render(request,"index.html",locals()) |
| |
| |
| |
| 视图函数 |
| |
| |
| from django.shortcuts import render |
| |
| class books: |
| def __init__(self,title): |
| self.title = title |
| |
| def index(request): |
| name = "root" |
| age = 22 |
| list = ['寒夜','西游记','s三国'] |
| dict = {'name':'张三','age':'33'} |
| books1 = books('1书') |
| books2 =books('3书') |
| book_list = [books1,books2,books3] |
| return render(request,"index.html",locals()) |
| |
| |
| html模板的操作 |
| |
| 取普通值 |
| {{ name }} |
| 取列表中的值 |
| {{list.1}} |
| 取字典的值 |
| {{dict.name}} |
| 取类对象值 |
| {{books1.title}} |
| 取对象列表 |
| {{book_list.1.title}} |
变量渲染-过滤器
| 语法: {{obj|过滤器}} |
| name = [11,22,33] |
| |
| 1.获取最后一个值 |
| {{name|last}} |
| |
| 2.获取第一个值 |
| {{ name|length }} |
| |
| 3.如果没有值 提示是default后面的内容,如果有值就显示变量自己的值 |
| name = [] |
| {{ book2|default:"没有值" }} |
| |
| 4.将字母变为大小写 |
| {{ name|upper }} |
| {{ name|lower }} |
| |
| 5.设置时间格式 |
| time = datetime.datetime.now() |
| {{ time|date:'Y-m-d '}} 自己设置的格式 |
| |
| 6.显示文件大小 |
| name = 1024 |
| {{ name|filesizeformat }} |
| |
| 7.截断功能 |
| 截断字符{{ content|truncatechars:10}} |
| 截断单词{{ content|truncatewords:10}} |
| |
| 8.将字符串的标签在html页面显示正常 |
| link = link = "<a href='http://www.baidu.com'>百度</a>" |
| |
| {{ link|safe }} |
| |
| 9.加法过滤器 |
| name = 10 |
| {{name|add:100}} |
| |
| 10.切片 |
| 按照范围进行切片 与python 数据切面语法一至 |
| <p>{{ n5| slice:"2:-1" }}</p> |
| |
| |
| 其他语法: |
| |
| 1.变量名字较长,取别名 |
| |
| namesadaadzxcasdasdasda2 = 1 |
| |
| HTML页面: |
| |
| {% with namesadaadzxcasdasdasda2 as n %} |
| |
| |
| |
| 2.{% csrf_token %} |
| |
| |
| 功能 |
| 1.能渲染input的标签 |
| 2.产生一个新的键值,django通过校验进行核对(提升安全性) |
| 使用方法: |
| <form action="" method="post"> |
| |
| {% csrf_token %} |
| |
| inpit标签.... |
| </form> |
自定义过滤器/标签
文档:https://docs.djangoproject.com/zh-hans/4.1/topics/http/file-uploads/
| 1.必须在app程序下在进行创建,不能与app创建的同级目录 |
| 项目名称: |
| -app01 |
| -templatetags |
| -__init__.py |
| -poll.py |
| |
| 2.需要检查当前app程序是否注册到框架INSTALLED_APPS中(如果没有请注册,不然找不到) |
| |
自定义过滤器
| 3.poll.py(定义) |
| |
| from django import template |
| |
| register = template.Library() |
| |
| |
| @register.filter |
| def mobile_fim(content): |
| print(content) |
| return content + '666' |
| |
| |
| @register.filter(name='mobile_fim') |
| def mobile_fim(content): |
| print(content) |
| return content + '666' |
| |
| 多个参数的情况下 |
| def a(第一个参数,第二个参数) |
| |
| |
| |
| 4.在html页面使用 |
| |
| {% load poll %} |
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Title</title> |
| </head> |
| <body> |
| |
| <div>{{ xxxx|mobile_fim }}</div> |
| |
| </body> |
| </html> |
自定义标签
| 3.poll.py(定义) |
| |
| from django import template |
| |
| register = template.Library() |
| |
| |
| @register.simple_tag |
| |
| def multi_tag(x,y): |
| return x * y |
| |
| |
| 在html页面使用 |
| |
| {% load poll %} |
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Title</title> |
| </head> |
| <body> |
| <div>{% multi_tag 1 2 %}</div> |
| |
| </body> |
| </html> |
模板语法-标签
| 标签的语法:{% xxx %} |
| |
| 1. if else的在模板中的使用 |
| |
| {% if 逻辑 %} |
| 代码 |
| {% endif %} |
| |
| |
| |
| {% if age > 18 and age < 100 %} |
| <p>成年人电影</p> |
| {% elif age < 18 and age > 1%} |
| <p>未成年的电影</p> |
| {% else %} |
| <p>看尼玛</p> |
| {% endif %} |
| |
| |
| |
| 2.for循环在模板中的使用 |
| |
| {{ forloop.first }} |
| {{ forloop.counter }} |
| {{ forloop.counter0 }} |
| |
| 使用方式 |
| {% for 变量名 in 循环的变量 %} |
| 逻辑 |
| {% endfor %} |
| |
| 案例 循环实例化的类对象 |
| |
| 视图函数中代码 |
| class books: |
| |
| def __init__(self, title): |
| self.title = title |
| |
| def __str__(self): |
| return self.title |
| |
| books1 = books('1书') |
| books2 = books('3书') |
| books3 = books('2书') |
| book_list = [books1, books2, books3] |
| |
| html页面中的语法 |
| {% for book in book_list %} |
| <li>{{ book.title }}</li> |
| {% endfor %} |
| |
模板语法-嵌套
| {% include '模板.html' %} |
| |
| 使用这个语法,可以将html页面中的重复的内容抽离出来,创建一个新的html页面,使用者语法,将抽离的内容在其他页面显示,起到了复用的作用 |
| |
| 比如: |
| 在home.html,与 index.html 需要用到 p标签 |
| 可以将p标签存在一个专门的 p.html文件中 |
| |
| home.html 就可以使用嵌套方法将复用的标签给在当前模板中使用 |
| |
| {% include 'p.html' %} |
模板语法-继承
| 就是,设置一个固定的模板base.html页面,内部设置一个盒子 |
| 盒子:是其他继承这个base.html页面的,空的一个区域,继承base.html的子html页面可以编写的位置。 |
| |
| 1.在base.html页面中设置一个盒子(父页面) |
| |
| 例如 |
| base.html 父盒子 |
| |
| {% block 盒子的名字 %} |
| 默认有自己的值 |
| {% endblock %} |
| |
| 2.其他继承base.html页面/ 子的html页面,需要是空 |
| 其他.html(空的页面) |
| |
| {% extends 'base.htm' %} |
| |
| {% block 盒子的名称 %} |
| {{ block.super}} |
| 如果不写,就会使用父级的盒子的内容 |
| {% endblock %} |
| |
| |
| 3. 获取父类中的盒子中的默认值 |
| {{ block.super}} |
| |
| |
| |
| |
| 注意事项: |
| 1. {% extends 'base.htm' %} |
| 2. base.html 中设置盒子越多越好,扩展越强 |
| 3. 不能给盒子的名称设置重复 |
Django-组件
中间件使用
| 什么是中间件:是一个介于request与response处理之间的一到工序,相对比较轻量级的框架,并且在全局上改变django的输入输出,改变全局,所以需要谨慎使用,用不好就会影响性能。 |
| |
| 中间件在哪个位置:在settings.py配置文件后,名为 MIDDLEWARE 变量的列表中存放。 |
| |
| 中间件功能是什么:在客户端请求来的时候,会经过中间件的request方法到路由到视图函数,当视图函数返回值时,也会经过中间件的response方法返回给客户端,可以在中间中设置一些参数进行请求来时和请求返回时的操作。 |
| 中间件: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 |
使用
| 1.导入中间件类 |
| from django.utils.deprecation import MiddlewareMixin |
| |
| 2.创建类继承中间件类 |
| class CustomerMiddleware(MiddlewareMixin): |
| |
| def process_request(self,request): |
| |
| |
| print('中间件1') |
| |
| |
| def process_view(self,request,callback,callback_args,callback_kwargs): |
| |
| |
| |
| print('中间件2') |
| |
| def process_exception(self,request,exception): |
| |
| |
| print('视图函数出错') |
| |
| |
| def process_response(self,request,response): |
| |
| print('中间件4') |
| return response |
| |
| |
| |
| |
| 3.将中间件添加到django配置文件中 |
| MIDDLEWARE = [ |
| ... |
| 'utilt.Middleware.CustomerMiddleware' |
| ... |
| ] |
| |
| |
| 注意在使用上: |
| process_request 与 process_response 才会使用 |

案例
| '''白名单,响应体中携带token值''' |
| |
| from django.utils.deprecation import MiddlewareMixin |
| from django.shortcuts import HttpResponse |
| from django.http import JsonResponse |
| |
| import uuid |
| |
| IP_LIST = ['10.10.10.30'] |
| class CustomerMiddleware(MiddlewareMixin): |
| |
| def process_request(self,request): |
| ip = request.META.get('REMOTE_ADDR') |
| if ip in IP_LIST: |
| |
| return JsonResponse({'code':200,'msg':'','error':'非法ip'}) |
| print('中间件1') |
| |
| |
| def process_response(self,request,response): |
| |
| |
| uuid_token = uuid.uuid4() |
| response['Token'] = f'token {uuid_token}' |
| return response |
| |
内置字段
| Django 的内置字段 |
| |
| Field |
| required=True |
| widget=None |
| label=None |
| initial=None |
| help_text='' |
| error_messages=None |
| show_hidden_initial=False |
| validators=() |
| localize=False |
| disabled=False |
| label_suffix=None |
| |
| 1,CharField(Field) |
| max_length=None, 最大长度 |
| min_length=None, 最小长度 |
| strip=True 是否移除用户输入空白 |
| |
| 2,IntegerField(Field) |
| max_value=None, 最大值 |
| min_value=None, 最小值 |
| |
| 3,FloatField(IntegerField) |
| ... |
| |
| 4,DecimalField(IntegerField) |
| max_value=None, 最大值 |
| min_value=None, 最小值 |
| max_digits=None, 总长度 |
| decimal_places=None, 小数位长度 |
| |
| 5,BaseTemporalField(Field) |
| input_formats=None 时间格式化 |
| |
| 6,DateField(BaseTemporalField) 格式:2015-09-01 |
| 7,TimeField(BaseTemporalField) 格式:11:12 |
| 8,DateTimeField(BaseTemporalField)格式:2015-09-01 11:12 |
| 9,DurationField(Field) 时间间隔:%d %H:%M:%S.%f |
| ... |
| |
| 10,RegexField(CharField) |
| regex, 自定制正则表达式 |
| max_length=None, 最大长度 |
| min_length=None, 最小长度 |
| error_message=None, 忽略,错误信息使用 error_messages={'invalid': '...'} |
| |
| 11,EmailField(CharField) |
| ... |
| |
| 12,FileField(Field) |
| allow_empty_file=False 是否允许空文件 |
| |
| 13,ImageField(FileField) |
| ... |
| 注:需要PIL模块,pip3 install Pillow |
| 以上两个字典使用时,需要注意两点: |
| - form表单中 enctype="multipart/form-data" |
| - view函数中 obj = MyForm(request.POST, request.FILES) |
| |
| 14,URLField(Field) |
| ... |
| |
| 15,BooleanField(Field) |
| ... |
| |
| 16,NullBooleanField(BooleanField) |
| ... |
| |
| 17,ChoiceField(Field) |
| ... |
| choices=(), 选项,如:choices = ((0,'上海'),(1,'北京'),) |
| required=True, 是否必填 |
| widget=None, 插件,默认select插件 |
| label=None, Label内容 |
| initial=None, 初始值 |
| help_text='', 帮助提示 |
| |
| 18,ModelChoiceField(ChoiceField) |
| ... django.forms.models.ModelChoiceField |
| queryset, |
| empty_label="---------", |
| to_field_name=None, |
| limit_choices_to=None |
| 19,ModelMultipleChoiceField(ModelChoiceField) |
| ... django.forms.models.ModelMultipleChoiceField |
| |
| 20,TypedChoiceField(ChoiceField) |
| coerce = lambda val: val 对选中的值进行一次转换 |
| empty_value= '' 空值的默认值 |
| |
| 21,MultipleChoiceField(ChoiceField) |
| ... |
| |
| 22,TypedMultipleChoiceField(MultipleChoiceField) |
| coerce = lambda val: val 对选中的每一个值进行一次转换 |
| empty_value= '' 空值的默认值 |
| |
| 23,ComboField(Field) |
| fields=() 使用多个验证,如下:即验证最大长度20,又验证邮箱格式 |
| fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),]) |
| 24,MultiValueField(Field) |
| PS: 抽象类,子类中可以实现聚合多个字典去匹配一个值,要配合MultiWidget使用 |
| |
| 25,SplitDateTimeField(MultiValueField) |
| input_date_formats=None, 格式列表:['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y'] |
| input_time_formats=None 格式列表:['%H:%M:%S', '%H:%M:%S.%f', '%H:%M'] |
| |
| 26,FilePathField(ChoiceField) 文件选项,目录下文件显示在页面中 |
| path, 文件夹路径 |
| match=None, 正则匹配 |
| recursive=False, 递归下面的文件夹 |
| allow_files=True, 允许文件 |
| allow_folders=False, 允许文件夹 |
| required=True, |
| widget=None, |
| label=None, |
| initial=None, |
| help_text='' |
| |
| 27,GenericIPAddressField |
| protocol='both', both,ipv4,ipv6支持的IP格式 |
| unpack_ipv4=False 解析ipv4地址,如果是::ffff:192.0.2.1时候,可解析为192.0.2.1, PS:protocol必须为both才能启用 |
| |
| 28,SlugField(CharField) 数字,字母,下划线,减号(连字符) |
| ... |
| |
| 29,UUIDField(CharField) uuid类型 |
| ... |
基本使用
| forms组件:和python re模块一样具有用用户输入认证功能 |
| |
| |
| |
| 创建认证组件可以 新创建一个py文件存放 |
| |
| |
| from django import forms |
| from django.forms import widgets |
| |
| from django.core.exceptions import NON_FIELD_ERRORS,ValidationError |
| |
| |
| |
| |
| class A(forms.Form): |
| |
| name = forms.CharField(min_length=4,) |
| |
| pwd = forms.CharField(min_length=4,) |
| |
| email = forms.EmailField() |
| |
| tel = forms.CharField() |
| |
| |
| |
| 参数:min_length= |
| |
| |
| |
| widgets 模块内的参数: |
| widget=widgets.TextInput(attrs={标签的属性}) |
| widget=widgets.PasswordInput(attrs={标签的属性}) |
| |
| 起名字参数:label= |
| 将英文错误转换中文参数:固定格式 |
| error_messages={"required": "字段不能为空", 'invalid': "格式错误"} |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 如何是用认证forms组件 |
| def reg(request): |
| |
| if request.method == 'POST': |
| |
| forms对象 = forms认证类(request.POST) |
| forms对象.is_valid() |
| |
| if forms对象.is_valid(): |
| |
| forms对象.cleaned_data |
| |
| else: |
| forms对象.cleaned_data |
| forms对象.errors |
| |
| forms对象.errors.get('pwd')[0] |
| |
| |
| |
| return render(request, 'reg.html', locals()) |
| |
| |
| forms对象 = UserForm() |
| |
| return render(request, 'reg.html', locals()) |
| |
| |
| |
| |
| |
| <div class="container"> |
| <div class="row"> |
| <div class="col-md-6 col-lg-offset-3"></div> |
| <form action="" method="post" novalidate> |
| |
| {% csrf_token %} |
| |
| |
| <p>用户名{{ form.name }}</p> <span>{{ form.name.errors.0 }}</span> |
| |
| <p>密码{{ form.pwd }}</p> <span>{{ form.pwd.errors.0 }}</span> |
| |
| <p>确认密码{{ form.r_pwd }}</p> <span>{{ form.r_pwd.errors.0 }}</span> <span>{{ error.0 }}</span> |
| <p>邮箱{{ form.email }}</p> <span>{{ form.email.errors.0 }}</span> |
| <p>电话{{ form.tel }}</p> <span>{{ form.tel.errors.0 }}</span> |
| <input type="submit"> |
| </form> |
| </div> |
| </div> |
| |
| |
| |
| |
HTML渲染
| |
| <form action="" method="post"> |
| {% csrf_token %} |
| {% for foo in form对象 %} |
| <label for="">{{ foo.label }}</label>需要在创建标签接受错误值 |
| <p>{{ foo }}</p> |
| {% endfor %} |
| <input type="submit"> |
| </form> |
| |
| |
| |
| |
| from django import forms |
| from django.forms import widgets |
| |
| |
| class A(forms.Form): |
| name = forms.CharField(min_length=4, label='用户名',widget=widgets.TextInput(attrs={"class":"form-control"})) |
| |
| pwd = forms.CharField(min_length=4, label='密码',widget=widgets.PasswordInput(attrs={"class": "form-control"}),) |
| |
| email = forms.EmailField(label='邮箱', error_messages={"required": "字段不能为空", 'invalid': "格式错误"},widget = widgets.TextInput(attrs={"class": "form-control"}) |
| |
| |
| def A(request): |
| |
| if request.method == 'POST': |
| |
| form对象 = A(request.POST) |
| form对象.is_valid() |
| if form对象.is_valid(): |
| else: |
| form对象.cleaned_data |
| form对象.errors |
| |
| return render(request, 'reg.html', locals()) |
| |
| return render(request, 'reg.html', locals()) |
| |
| |
| |
| |
| |
| <form action="" method="post"> |
| {% csrf_token %} |
| {{ form.as_p }} |
| <input type="submit"> |
| </form> |
设置钩子
| from django import forms |
| from django.core.exceptions import NON_FIELD_ERRORS,ValidationError |
| from app.models import * |
| |
| |
| |
| class A(forms.Form): |
| |
| pass 认证方法 |
| name =..... |
| pwd = ..... |
| r_pwd = .... |
| |
| |
| |
| |
| def clean_name(self): |
| |
| val = self.cleaned_data.get("name") |
| |
| |
| ret = 数据名.objects.filter(name=val) |
| |
| |
| if not ret: |
| return val |
| |
| else: |
| raise ValidationError("用户已经存在") |
| |
| ''' |
| 执行流程: |
| 先将浏览器的发送请求 |
| 视图函数判断是否时post请求 |
| 执行forms认证组件,当认证全部通过 |
| 执行局部钩子,进行确认。 |
| ''' |
| |
| |
| def clean(self): |
| |
| pwd = self.cleaned_data.get('pwd') |
| r_pwd = self.cleaned_data.get('r_pwd') |
| |
| if pwd and r_pwd: |
| |
| if pwd == r_pwd: |
| return self.changed_data |
| |
| |
| else: |
| raise ValidationError("两次密码不一致") |
| |
| else: |
| |
| return self.changed_data |
| |
| |
| |
| |
| |
| |
| |
| def A(request): |
| |
| .... |
| form对象 = 认证组件类(request.POST) |
| |
| |
| print("全局钩子错误:",form.errors.get("__all__")[0]) |
| error全局钩子错误对象 = form.errors.get("__all__") |
| |
| |
| |
| |
| <span>{{ form对象.name.errors.0 }}</span> |
| <span>{{ error全局钩子错误对象.0 }}</span> |
| 在配置文件中修改LANGUAGE_CODE = 'zh-hans' |
| |
| |
| 自定义局部钩子函数 |
| clean_表中的某些字段这个字段开头, |
| 错误信息就会显示到 生成的input标签后面 |
| 局部和全局的区别: |
| clean_字段名A 局部:只有后面的字段名A才会显示错误信息 |
| clean 全局:整个字段都会显示错误信息 |
数据库更新
| |
| class UserInfoPassWordModelForm(StarkForm): |
| password = forms.CharField(label="密码") |
| confirm_password = forms.CharField(label="确认密码") |
| |
| |
| def clean_confirm_password(self): |
| pwd = self.cleaned_data["password"] |
| re_pwd = self.cleaned_data["confirm_password"] |
| if pwd != re_pwd: |
| raise ValidationError("密码不一致") |
| return re_pwd |
| |
| |
| def clean(self): |
| password = self.cleaned_data["password"] |
| self.cleaned_data["password"] = gen_md5(password) |
| return self.cleaned_data |
| |
| |
| |
| |
| |
| def reset_password_view(self, request, pk): |
| ''' |
| 重置密码视图函数 |
| :param request: |
| :param pk: |
| :return: |
| ''' |
| |
| |
| pwd_object = models.UserInfo.objects.filter(id=pk).first() |
| |
| if not pwd_object: |
| return HttpResponse("用户不存在") |
| |
| if request.method == "GET": |
| form = UserInfoPassWordModelForm() |
| |
| return render(request, "stark/change.html", {"form": form}) |
| |
| |
| form = UserInfoPassWordModelForm(data=request.POST) |
| if form.is_valid(): |
| |
| pwd = form.cleaned_data["password"] |
| pwd_object.password = pwd |
| pwd_object.save() |
| return redirect(self.reverse_list_url()) |
| |
| return render(request, "stark/change.html", {"form": form}) |
| from django import forms |
| |
| |
| |
| class UserModelForm(forms.ModelForm): |
| |
| confirm_password = forms.CharField(label='确认密码') |
| |
| class Meta: |
| model = models.类名 |
| |
| fields = ['name', 'email', 'password', 'confirm_password'] |
| widget = { |
| "字段名":froms.TextInput(attrs={"class": "form-control"}) |
| } |
| |
| |
| |
| def clean_confirm_password(self): |
| |
| pwd = self.cleaned_data["password"] |
| r_pwd = self.cleaned_data["confirm_password"] |
| if pwd != r_pwd: |
| raise ValidationError('密码不一样') |
| return r_pwd |
| |
| |
| |
| 批量ModelForm生成的字段添加class属性/使用插件,重新写init方法 |
| |
| def __init__(self, *args, **kwargs): |
| |
| super(生成的form组类, self).__init__(*args, **kwargs) |
| |
| for name, field in self.fields.items(): |
| |
| field.widget.attrs['class'] = 'form-control' |
| |
| 在配置文件中修改LANGUAGE_CODE = 'zh-hans' 错误信息就是中文 |
| |
| |
| |
| |
| 在视图中是用modelform |
| |
| 1.选着验证的数据/数据的提交是post请求/增加数据 |
| |
| form = UserModelForm(data=request.POST) |
| |
| 2.验证表单/增加数据 |
| form.is_valid() 返回Teur/Faste |
| |
| 3.将数据保存在数据库/增加数据 |
| form.save() |
| |
| 4.编辑表单是显示默认值 |
| form = RoleModelForm(instance=从数据库获取的model需要对象) |
| |
| 5.选择验证并且编辑 |
| form = RoleModelForm(instance=从数据库获取的model需要对象, data=request.POST) |
| |
| 6.设置指定的默认值[可以为from表单中字典设置参数值] |
| menu_obj = Menu.objects.filter(id=menu_id).first() |
| form = PermissionsModelForm(initial={'menu':menu_obj(必须是数据库对象)}) |
| from django import forms |
| from rbac import models |
| |
| class RoleModelForm(forms.ModelForm): |
| """ |
| 角色操作表单 |
| """ |
| class Meta: |
| model = models.Role |
| fields = ["title", ] |
| widgets = { |
| "title": forms.TextInput(attrs={"class": "form-control"}) |
| } |
| |
| |
| def role_add(request): |
| |
| if request.method == "GET": |
| form = RoleModelForm() |
| |
| return render(request, "rbac/change.html", {"form": form}) |
| |
| |
| form = RoleModelForm(data=request.POST) |
| if form.is_valid(): |
| form.save() |
| return redirect(reverse("rbac:role_list")) |
| return render(request, "rbac/change.html", {"form": form}) |
| |
| |
| |
| |
| def role_edit(request, pk): |
| role_obj = models.Role.objects.filter(pk=pk).first() |
| if not role_obj: |
| return HttpResponse("要修改的数据不存在,请重新选择") |
| |
| if request.method == "GET": |
| form = RoleModelForm(instance=role_obj) |
| return render(request, "rbac/change.html", {"form": form}) |
| |
| |
| form = RoleModelForm(instance=role_obj, data=request.POST) |
| if form.is_valid(): |
| form.save() |
| return redirect(reverse("rbac:role_list")) |
| return render(request, "rbac/change.html", {"form": form}) |
| modelformset_factory 方法 |
| |
| |
| from django.forms import modelformset_factory |
| |
| |
| 1.创建一个modelform类 |
| from django import forms |
| form 程序 import models |
| class A(forms.ModelForm): |
| class Meta: |
| model = models.数据库名称 |
| fields = ["字段"] |
| |
| |
| 2.视图设置 |
| def 视图(request,pk) |
| |
| 数据库数据对象 = models.数据库名称 |
| 数据库_formset = modelformset_factory(models.数据库名称,form= A,extea=0) |
| formset = 数据库_fromset(queryset=数据的数据对象) |
| |
| |
| if request.method == "POST": |
| |
| formset = 数据库_fromset(queryset=数据的数据对象,data=request.POST) |
| if formset.is_valid() |
| formset.save() |
| return render(request, "attendance.html", {"formset": formset}) |
| return render(request, "html页面", {"formset": formset}) |
| |
| |
| |
| 模板添加{{ formset.management_form }},固定写法 |
| {{ field.id }}为标识数据,否则没法保存修改 |
| instance为生成不可修改项,需要生成一个隐藏的原始数据,没法提交 |
| |
| 案例 |
| {% block content %} |
| <div class="luffy-container"> |
| <form action="" method="post"> |
| {% csrf_token %} |
| { |
| {{ formset.management_form }} |
| <div style="margin: 5px 0"> |
| <input type="submit" value="提交" class="btn btn-primary"> |
| </div> |
| <table class="table table-bordered"> |
| <thead> |
| <tr> |
| <th>姓名</th> |
| <th>考勤</th> |
| </tr> |
| </thead> |
| <tbody> |
| {% for form in formset %} |
| <tr> |
| { |
| {{ form.id }} |
| { |
| <td>{{ form.instance.student }}</td> |
| { |
| <td>{{ form.record }} <span>{{ form.record.errors.0 }}</span></td> |
| </tr> |
| {% endfor %} |
| </tbody> |
| </table> |
| </form> |
| </div> |
| {% endblock %} |
| |
| |
| |
| 例子使用stark组件: |
| def save(self,request, form, is_update=False): |
| |
| current_user_id = self.request.session["user_info"]["id"] |
| |
| form.instiance.consultant_id = current_user_id |
| |
| form.save() |
| |
| 当时用save方法进行对前端post请求进行保存时,在保存之前的操作 |
| form(定制的modelform类/form类的对象).instiance.字段id = 值 在save之前操作 |
| |
| 在表类中中的属性 verbose_name |
| title = models.CharField(verbose_name='标题', max_length=32) |
| 会渲染为页面中label属性作为页面中显示的输入内容的名称 |
| {% for row in form %} |
| <label for="">{{ row.label }}</label> |
| row 就变为标签中每一个字段 input标签,输入 |
| <label for="">{{ row }}</label> |
| |
| |
| 在视图中传入的form中里面的label显示的时 verbose_name属性 |
| 每一个form就会在页面动态生产一个input框 |
| errors.0 属性显示的就是错误信息 |
cookie组件
| |
| HTTP 的无状态保存 |
| 两次请求之间没有关联关系 |
| 登录才能看到的页面 |
| |
| cookie 知道浏览器之前的操作 |
| 会话:一次请求 一次响应 就是一次会话 |
| cookie:具体的一个浏览器,针对的一个服务器的存储 key-value 技术{ } |
| |
| 在一个浏览器 向 服务器发送的登录请求时 内部会产生一个 {cookie:True} |
| 每一次访问 服务器内部的视图是,都会带这个 cookie 字典 用来判断是否登录 默认失效期 2周 |
| 如果换一个浏览器 都是未登录状态 默认 {cookie 是空的} |
| 如果换一个服务器 针对不同的服务器 就不同的 {cookie} |
| |
| 什么是cookie:cookie是key-value的结构,与python中的字典相似,将数据随着服务端响应发给客户端浏览器,客户端在将cookie保存起来,当下一次客户端访问服务器时,在把cookie发送给服务器,cookie时由服务器创建的,然后通过响应发送给客户段的一个键值对,客户端会保存起来并且标注来源,当客户端在访问服务器时,将cookie发送给服务器,这样服务器就会识别客户端。 |
| |
| 可以理解为:每一个浏览器针对每一个服务器创建一个key - value 结构的本地存储文件。 |
| 每个浏览器软件 针对 一个服务器创建一个文件 |
| 比如: |
| 利用谷歌浏览器登陆京东,那么就会创建一个cookie的本地文件,当在访问京东时,京东就会收到这个cookie文件,就知道是不是登录过等等 |
| 而火狐浏览器如果登录京东,就会在创建一个属于火狐的cookie本地文件。 |
| cookie文件之间是不通用的,属于安全层面。 |
| 浏览器是一个客户端,每一个客户端会创建一个cookie的本地文件,自己的就是自己的,别人不能用。 |
| |
| 缺点: |
| 用于登录时最多/缺陷不安全,数据由客户端保存,每次访问时携带数据。 |
使用
| from django.shortcuts import HttpResponse |
| |
| |
| |
| def home(request): |
| ret = HttpResponse() |
| |
| ret.set_cookie('url','www.baidu.com') |
| |
| ret.set_cookie('url','www.baidu.com',max_age=5) |
| |
| print(request.COOKIES.get('url')) |
| |
| |
| |
| ret.set_signed_cookie('url2','www.baidu.com',salt='xxxxxx') |
| |
| print(request.get_signed_cookie('url2',salt='xxxxxx')) |
| |
| return ret |
session组件
| 特点:无状态保存 |
| 在http协议中可以使用cookie来完成会话跟踪技术,session的底层依赖于cookie |
| 能让服务端记录自己的行为。比如登录时的账户密码的记录 |
| |
| |
| Session安全,数据会被服务端自己存储,而浏览器只有一个密钥。 |
| django提供对匿名会话完全支持,这个会话框架让你可以存储和取回每个站点访客任意数据,在服务器端进行存储数据,并且以cookies的形式进行发送和接受数据。 |
| |
| |
| 以什么形式保存: |
| 文件,redis mysql |
| 在配置文件中的 MIDDLEWARE 中实现的session的语法 |
| 'django.contrib.sessions.middleware.SessionMiddleware' |
| |
| |
| django_session的表这个表有3个字段 |
| 1.session_key 随机生成的密钥 |
| 2.seeion_data 创建的session的记录 加密的数据 |
| 3.expire_date 过期的时间 |
| |
| 在写session,3部曲 |
| 1.随机生成一个钥匙存在session_key中 |
| 2.将键值对放在 session_data 字段中 插入到session表中 |
| 3.设置一个键值对 键:sessionid 值:session表中的钥匙 {sessionid:session为的钥匙} |
| {sessionid:session为的钥匙} 给客户端 |
| |
| 读session 3部曲 |
| 1.将钥匙取到 |
| 2.在数据库中进行比对 钥匙不对返回none 值不对返回none |
| 3.将想要的键值对获取出来 |
| |
| 因为session是存储在后端,要不redis要不mysql或者其他,需要先迁移数据库 |
session参数
| SESSION_COOKIE_NAME = 'sessionid' |
| SECRET_KEY = 随机字符串(默认) |
| SESSION_COOKIE_PATH = '/' |
| SESSION_COOKIE_DOMAIN = None |
| SESSION_COOKIE_SECURE = False |
| SESSION_COOKIE_HTTPONLY = True |
| SESSION_COOKIE_AGE = 1209600 |
| SESSION_COOKIE_AT_BROWSER_CLOSE =False |
| SESSION_COOKIE_EVERY_REQUEST = False |
使用
| from django.http import JsonResponse |
| |
| |
| def home(request): |
| |
| request.session['url'] = 'www.baidu.com' |
| |
| print(request.session.get('url')) |
| |
| request.session.flush() |
| |
| del request.session.get('url') |
| return JsonResponse({'code':200}) |
认证组件
| 在数据空迁移中 有一个 auth_user 的组件表 |
| |
| 用户认证组件: |
| 功能:用户session记录登录状态 |
| 前提:用户表:dajango自带的auth_user 使用 |
| |
| 命令在终端执行:python manage.py createsuperuser 创建超级用户 |
| |
| 使用用户认证组件:需要时用Django 中带的auth_user表 |
| 表中的密码是加密处理 |
| 使用用户认证的组件需要使用的是django自带的数据库表 auth_user |
| 主要看 password 和 username字段 |
| is_superuser : 是不是超级用户 |
| 1.方法 |
| from django.contrib import auth |
| from django.contrib.auth.models import User |
| User模型类对应着是 django自带的数据库表 auth_user表 |
| |
| 2.添加普通用户 |
| User.objects.creat(username='用户名',password='密码') |
| User.objects.creat_user(username='用户名',password='密码') |
| |
| 3.添加超级用户 |
| User.objects.creat_superuser (username='用户名',password='密码',email='邮箱') |
| |
| |
| 4.校验密码方法 |
| set_password('密码') |
| check_password('密码') |
| |
| |
| 5.怎么基于django自带的用户表和auth模块进行用户验证 |
| 根据auto_user表和auth模块进行认证的 |
| 无法直接用对user表进行操作,因为passwodr字段是密文的,用户输入的是明文的,无法比对 |
| from django.contrib import auth |
| name = auth.authenticate(username=name,password=pwd) |
| |
| |
| 6.用户认证组件怎么进行session的存储 |
| |
| auth.login(request, name) |
| |
| |
| |
| |
| 7.怎判断用户是否登录成功 获取session值 |
| |
| request.session.get('_auth_user_id') |
| |
| |
| request.user |
| |
| 执行流程 |
| 1. AuthenticationMiddleware中间件方法中process_request方法中/在中间件使用的,所以全部的视图都可以是使用 |
| 1.request.session.get('user_id') |
| 2.from django.contrib.auth.models import User |
| 在这表中 进行user = User.objects.get(pk=user_id) |
| if user: |
| request.user = user |
| else: |
| request.user = 赋值一个匿名对象 |
| 2.结论:在任何一个视图函数中都可以使用request.uers |
| 如果之前登录成功过,执行了auth.login() 那么 request.user=当前登录的用户对象 |
| 如果没有登录过 就是没有执行auth.login() 那么 request.user=匿名用户对象 |
| |
| 3.如果登录成功就可以取到对应的id值和name值属性 |
| request.user.username |
| reuqest.user.id |
| |
| 8.aoth组件的退出 |
| request.logout() |
| |
| 也可以使用 |
| del request.session.get('_auth_user_id') |
| |
| |
| request.用户名 |
| |
| |
| 用户对象 = auth.authenticate(username=用户名,password=密码) |
| |
| auth.login(request,用户对象) |
| |
| auth.logout(request) |
| |
| request.用户名.username |
| request.用户名.id |
| request.用户名.is_anonymous |
| request.用户名 |
| request.user.is_authenticated() |
| |
| 这个内部组件更为安全,当有新用户登录时 就会将django的自带的 session中的数据全部更新,而不是更新一部分 |
| 案例: |
| from django.contrib import auth |
| from django.contrib.auth.models import User |
| |
| |
| def A(request): |
| if request.method == 'POST': |
| user = request.POST.get('user') |
| pwd = request.POST.get('pwd') |
| print(user,pwd) |
| |
| |
| |
| |
| user_name = auth.authenticate(username=user,password=pwd) |
| |
| if user_name: |
| auth.login(request,user_name) |
| |
| |
| |
| return redirect('显示页面') |
| |
| return render(request,'/路径/') |
| |
| |
| |
| def B(request): |
| |
| |
| if request.user.is_anonymous: |
| return redirect('/路径/') |
| return render(request,'显示页面') |
| |
| |
| def C(request): |
| auth.logout(request) |
| return redirect('/路径/') |
| |
| |
| def D(request): |
| if request.method =='POST': |
| user = request.POST.get('user') |
| pwd = request.POST.get('get') |
| |
| |
| user= User.objects.create_user(username=user,password=pwd) |
| return redirect('/路径/') |
| |
| return render(request,'注册') |
| 案例2 |
| from django.shortcuts import render, HttpResponse, redirect |
| from django.contrib import auth |
| from django.contrib.auth.models import User |
| |
| |
| |
| |
| def login(request): |
| if request.method == 'GET': |
| return render(request, 'login.html') |
| else: |
| name = request.POST.get('name') |
| pwd = request.POST.get('pwd') |
| |
| |
| name = auth.authenticate(username=name, password=pwd) |
| if name: |
| |
| auth.login(request, name) |
| |
| return redirect('/home/index/') |
| else: |
| return redirect('/home/login/') |
| |
| |
| def index(request): |
| |
| |
| |
| ret = request.user.username |
| print(ret) |
| if not ret: |
| return render(request, 'login.html') |
| return render(request, 'index.html', {'name': ret}) |
| |
| |
| def auths(request): |
| |
| auth.logout(request) |
| |
| return redirect('/home/login/') |
Ajax
| 1.局部刷新 |
| 2.异步请求 |
| 将Ajax 放到一个事件里面,点击一个按钮或者一个操作可以触发 |
| |
| 1.需要导入jq包进行使用 |
| https://code.jquery.com/jquery-3.6.3.min.js |
| |
| 2.ajax |
| $.ajax({ |
| url:'http://127.0.0.1:5000/', // 请求的地址 |
| type:'get', // 请求的类型 |
| data:JSON.stringify({'name':'wkx','aeg':123}), // 传后端的数据 |
| dataType:'JSON', // 接受后端数据从json转为对象类型 省略JSON.parse() |
| contentType:'application/json', // 发送后端的请求为json格式 |
| success:function (data){ |
| // 正确信息 |
| console.log(data) |
| }, |
| error:function (err){ |
| //错误信息 |
| } |
| |
| }) |
| |
| 执行流程: |
| 1.触发事件,执行ajax代码 |
| 2.根据url 知道路径, |
| 3.将传入参数返回给对应路径的视图函数 |
| 4.视图函数返回值别 ajax的回调函数参数data接受 |
局部刷新效果
| from django.shortcuts import HttpResponse |
| |
| class A(request): |
| n1 = request.POST.get('n1') |
| |
| n2 = request.POST.get('n2') |
| |
| n3 = int(n1)+int(n2) |
| return HttpResponse(n3) |
| |
| |
| |
| |
| |
| <input type="text" id="num1"> + |
| <input type="text" id="num2"> = |
| <input type="text" id="num3"> |
| <button class="cal">结果</button> |
| |
| 效果: |
| 当点击‘结果按钮’将num1+num2的值计算并赋值给num3 |
| |
| $('.cal').click(function () { |
| |
| $.ajax({ |
| url:'/cal/', |
| type: 'post', |
| data: { |
| "n1":$('#num1').val(), |
| |
| "n2":$('#num2').val(), |
| |
| }, |
| |
| success:function (data) { |
| |
| $(' #num3').val(data) |
| |
| } |
| |
| }) |
| |
实现用户验证功能
| |
| |
| import json |
| from django.shortcuts import HttpResponse |
| |
| def login(request): |
| user = request.POST.get('user') |
| pwd = request.POST.get('pwd') |
| |
| user = User.objects.filter(user=user,pwd=pwd).first() |
| |
| |
| |
| res = {"user":None,"msg":None} |
| |
| if user: |
| |
| res["user"] = user.name |
| |
| else: |
| |
| res["msg"] = "name or password 错误" |
| |
| return HttpResponse(json.dumps(res)) |
| ''' |
| 执行流程: |
| 1.接受前段ajax的用户名和密码, |
| 2.在数据库中进行核对 |
| 3.设置一个字典对应用户/密码 起始值None |
| 4.登录成功,将用户放到字典中/不成功将错误信息放到字典中 |
| 5.将数通过json,返回给ajax的回调函数data中 |
| ''' |
| |
| |
| <form> |
| 用户名 <input type="text" id="user"> |
| 密码 <input type='password' id="pwd"> |
| <input type="button" value="提交" class="login_btn"> |
| |
| <span class="span"> </span> |
| </form> |
| |
| |
| $('.login_btn').click(function () { |
| |
| $.ajax({ |
| url:'/login/', |
| type:'post', |
| data:{ |
| 'user':$("#user").val(), |
| 'pwd':$('#pwd').val(), |
| }, |
| |
| |
| success:function (data) { |
| |
| var json = JSON.parse(data) |
| |
| if(json.user) { |
| |
| location.href='http://www.baidu.com' |
| } |
| else { |
| $('.span').html(json.msg) |
| |
| } |
| } |
| }) |
| }) |
| |
Ajax上传
| |
| ef A(request): |
| |
| if request.method =='POST': |
| |
| print(request.FILES) |
| |
| return render(request,'html页面') |
| |
| |
| |
| |
| |
| |
| <form action="" method="post" enctype="multipart/form-data"> |
| |
| |
| |
| 用户名<input type="text" id="user"> |
| 头像<input type="file" id="avatar"> |
| |
| |
| |
| <input type="button" class="btn" value="Ajax"> |
| |
| </form> |
| |
| |
| |
| $('.btn').click(function () { |
| |
| |
| var formData=new FormData() |
| |
| formData.append('user',$('#user').val()); |
| formData.append('avatar',$('#avatar')[0].files[0]); |
| |
| $.ajax({ |
| url:'', |
| type:'post', |
| |
| contentType:false, |
| processData:false, |
| data:formData, |
| success:function (data) { |
| console.log(data) |
| } |
| }) |
| }) |
分页器组件
| from django.core.paginator import Paginator, EmptyPage |
| |
| |
| def A(request): |
| |
| |
| model_obj = modelclass.object.all().order_by('id') |
| |
| 分页器功能 |
| |
| page_obj = Paginator(model_obj, 2) |
| |
| |
| page_obj.count |
| page_obj.num_pages |
| page_obj.page_range |
| 显示对象 = page_obj.page(整数值) |
| 显示对象.has_next() |
| 显示对象.has_previous() |
| 显示对象.next_page_number() |
| 显示对象.previous_page_number() |
| |
| |
| 如果输入的页码超出或者小于最小页码,就会报错EmptyPage |
| 利用异常处理机制进行捕获,触发EmptyPage时,让内容显示第一页 |
| 用来捕获异常 |
| 利用EmptyPage方法捕捉上一页错误信息 |
| from django.core.paginator import EmptyPage |
| |
| try: |
| page01 = pag.page(页码) |
| except EmptyPage: |
| page01 = pag.page(1) |
| |
| 分页器案例 |
| |
| from django.core.paginator import Paginator, EmptyPage |
| |
| |
| |
| class A(request): |
| |
| |
| |
| 数据库对象 = 数据库.objects.all() |
| |
| |
| 分页器对象 = Paginator(数据库对象, 显示页面数据条数) |
| |
| |
| |
| |
| page_range = None |
| |
| current_page_num = int(request.GET.get('page', 1)) |
| |
| |
| |
| if 分页器对象.num_pagex> 11 : |
| |
| |
| if current_page_num-5>1: |
| |
| page_range=range(1,12) |
| |
| |
| elif current_page_num+5>分页器对象.num_pagex: |
| |
| page_range = range(分页器对象.num_pagex-10,分页器对象.num_pagex) |
| |
| |
| else: |
| page_range = 页面对象.page_range |
| |
| |
| |
| |
| try: |
| |
| current_page_num = int(request.GET.get('page', 1)) |
| |
| |
| current_page = 页面对象.page(current_page_num) |
| |
| except EmptyPage as a: |
| |
| current_page = 页面对象.page(1) |
| |
| |
| return render(request, 'HTML页面', locals()) |
| |
| |
| |
| <link rel="stylesheet" href="/static/css/bootstrap.css"> |
| |
| |
| |
| |
| <ul> |
| {% for book in current_page %} |
| <li>{{ book.title }}:{{ book.price }}</li> |
| {% endfor %} |
| </ul> |
| |
| |
| <nav aria-label="Page navigation"> |
| <ul class="pagination"> |
| |
| |
| |
| {% if 分页对象.has_previous %} |
| |
| |
| <li><a href="?page= {{ 分页对象.previous_page_number }}" aria-label="Previous"><span aria-hidden="true">上一页</span> |
| |
| {%else %} |
| |
| <li class="disabled"><a href="" aria-label="Previous"><span aria-hidden="true">上一页</span> |
| |
| |
| |
| {% for item in page_range %} |
| |
| {% if current_page_num == item %} |
| |
| <li class="active"><a href="?page={{ item }}">{{ item }}</a></li> |
| |
| |
| {% else %} |
| |
| <li><a href="?page={{ item }}">{{ item }}</a></li> |
| {% endif %} |
| {% endfor %} |
| |
| |
| |
| |
| |
| {% if 分页对象.has_previous %} |
| |
| <li><a href="?page={{ 分页对象.next_page_number }}" aria-label="Next"><span aria-hidden="true">下一页</span> |
| {%else%} |
| |
| <li class="disabled"><a href="" aria-label="Next"><span aria-hidden="true">下一页</span> |
| {% endif %} |
| 代码2 |
| 与博客园的的分页器一样 |
| 设置一个显示部分的列表 |
| def tianjia(request): |
| book_list = models.Book.objects.all() |
| pag = Paginator(book_list, 10) |
| |
| try: |
| num = int(request.GET.get('num', '1')) |
| page01 = pag.page(num) |
| if num == 1: |
| pag_list = range(1, 4) |
| elif num == pag.num_pages: |
| pag_list = range(num - 2, num + 1) |
| else: |
| pag_list = [num - 1, num, num + 1] |
| |
| except EmptyPage: |
| page01 = pag.page(1) |
| |
| return render(request, 'app02/index2.html', {"page01": page01, 'pag': pag, "pag_list": pag_list, 'num': num}) |
| |
| |
| <nav aria-label="Page navigation"> |
| <ul class="pagination"> |
| |
| <li> |
| <a href="/home2/tianjia/?num=1" aria-label="Next"> |
| <span aria-hidden="true">第一页</span> |
| </a> |
| </li> |
| { |
| {% for i in pag_list %} |
| {% if num == i %} |
| <li class="active"> |
| <a href="/home2/tianjia/?num={{ i }}">{{ i }}</a> |
| </li> |
| {% else %} |
| <li> |
| <a href="/home2/tianjia/?num={{ i }}">{{ i }}</a> |
| </li> |
| {% endif %} |
| |
| {% endfor %} |
| |
| |
| <li> |
| <a href="/home2/tianjia/?num={{ pag.num_pages }}" aria-label="Next"> |
| <span aria-hidden="true">最后一页</span> |
| </a> |
| </li> |
| |
| |
| </ul> |
| </nav> |
| |
装饰器跳转
| 1.导入 |
| from django.contrib.auth.decorators import login_required |
| |
| 2.在配置文件中进行配置 |
| LOGIN_URL="/登录页面/" |
| |
| 3.在视图中装饰 |
| 装饰器使用 |
| @login_required() |
| def xx(request): |
| request.GET.get('next') |
| request.GET.get('next',/第二个参数/) |
| return HttpResponse('登录成功') |
django内置事务/事务锁
| from django.db import transaction |
| |
| try: |
| with transaction.atomic(): |
| 代码1..... |
| 代码2..... |
| except Exception as e: |
| return "出现错误" |
| |
| 出现错误,e捕捉到错误信息。成功时两个代码都会执行 |
内置邮件功能
| from django.core.mail import send_mail |
| |
| """ |
| send_mail(subject='邮件主题',message='邮件内容',from_email='发送者邮箱',recipient_list='接受者',) |
| |
| """ |
| 在sttings.py配置文件中设置的参数 |
| EMAIL_HOST='使用的邮箱' |
| EMAIL_PORT='邮箱端口号' |
| EMAIL_HOST_USER='账号' |
| EMAIL_HOST_PASSWORD='密码' 授权码 |
| |
| EMAIL_USE-SSL=True |
| |
| |
| 邮件的操作: |
| 1.导入模块 |
| 2.在配置文件中设置参数 |
| 3.发送邮件 |
| |
| 发送邮件: |
| from django.conf import settings |
| send_mail( |
| 参数1:邮件主题 |
| 参数2:邮件内容 |
| 参数3:settings.发送邮件邮箱 |
| 参数4:["接受者邮箱"] |
| ) |
| |
| 使用时,会影响项目的速度,需要单独开创一条线程工作 |
| |
| import threading |
| t= threading.Thread(target=send_mail,args=(使用的参数)) |
| t = start() |
| |
BS4模块防御作用
模块截取功能
| from bs4 import BeautifulSoup |
| |
| soup = BeautifulSoup(文本信息(含字符串和标签/变量),"html.parser") |
| |
| |
| desc = soup.text[起始:终止] |
| |
| |
BS4的模块防御
| from bs4 import BeautifulSoup |
| |
| soup = BeautifulSoup(文本信息(含字符串和标签),"html.parser") |
| |
| soup.find_all() |
| |
| |
| for tag in soup.find_all(): |
| print(tag.name) |
| if tag.name == "非法标签": |
| tag.decompose() |
| |
| str(soup) |
| |
| |
Django-两类服务器
uwsgi服务器
| 快速,自我驱动,对于开发者和系统管理员友好的应用容器服务器,用于接收前端服务器转发的动态请求并处理发给web应用程序,完全由c编写,实现wsgi协议,uwsgi http协议, |
| uwsgi协议时uWSGI服务器自有的协议,用于定义传输信息的类型,常用于uWSGI服务器与其他网络服务器的数据通信 |
| |
| |
| 浏览器 |
| |
| 浏览器 |
| |
| uWSIG替换了原本的django自带的web服务器 |
| |
| |
| |
| 客户端发送请求 http或者https协议 |
| nginx 反向代理 负载均衡 转发请求 |
| uWSGI web服务器接受http请求按照网管端口协议 基于python下的wsig别写的应用程序,只适合于python使用 |
| python脚本的程序 |
| |
| # 真正部署上线使用的软件 nginx |
| |
| 客户端发送http协议 |
| |
| wsgi协议:web服务器和应用程序使用的协议 |
| uwsgi协议:是nginx程序与web服务器的使用协议 |
| # nginx以uwsgi协议发给了 uWSGIweb服务器 |
| # uWSGIweb服务器有以wsgi协议 发给python的应用程序 |
asgi异步服务器
| web服务器:是一个运行网站后台的软件,主要用于与网站浏览文件下载服务,它可以向浏览器客户端提供html文档,提供浏览和数据下载 |
| nginx 是主流的web服务器 |
| django自带的web服务器:wsgi.py就是一个web服务器,在django启动时,会构建soke接受基于http请求的信息,完成一个http响应的内容。 |
| 为什么只有一个webb程序就能跑起来: |
| 因为在djngao内部有一个提供测试的wsgi.py的web服务器 |
| |
| 网关接口:gi |
| 网管接口 cgi fastcgi asgi wsgi 协议 |
| web服务器 < --- > web应用程序 负责服务器与应用程序的相互通信 |
| 网管接口 不限语言 |
| |
| 1.cgi 和fastcgi |
| 属于远古时期的网关协议接口,wsgi的底层使用的就是cgi |
| |
| 2.wsgi |
| 仅限于Python语言web框架使用都是基于python网关接口 |
| 实现wsgi的web服务器:uWSGI,uvicorn,dunicorn |
| django在上线时使用时,不会用自带的wsgi web服务器,因为他是一个仅限于测试使用的。并发不行 |
| 在启动django时,内部就会启动一个wsgiref的模块作为web服务器运行的,wsgiref是一个内置的简单的遵循了wsgi接口协议的web服务程序 |
| wsgiref的模块充当了web服务器框架 |
| |
启动asgi服务
| https://www.django.cn/article/show-28.html |
| uvicorn django_dom.asgi:application --host 0.0.0.0 --port 8181 |
| https://docs.djangoproject.com/zh-hans/4.1/howto/deployment/asgi/ 官网 |
| |
| |
| 如何使用3.0的asgi服务 |
| |
| uvicorn asgi服务器启动: |
| |
| |
| |
| uvicorn 项目名称.asgi:application |
| uvicorn .... --host ip --port 端口 |
| |
| |
| 使用daphne asgi服务器 |
| 1.安装 |
| pip install daphne |
| |
| 2.在配置文件中settings.py中设置 |
| INSTALLED_APPS =[ |
| 'daphne', |
| ] |
| |
| 3.在配置文件中添加asgi应用启动 |
| asgi.application 就是创建项目中 asgi.py文件 变量application 服务 |
| ASGI_APPLICATION = '项目名称.asgi.application'. |
| |
| 4.使用命令行启动 |
| daphne -b ip -p 端口 ... |
Django-CSRF_Token
| 是一种防御机制,防止ssh的攻击,用csrftoken进行验证,是否是合法的请求。 |
| |
| |
| {% csrf_token %} 在模板语法中 使用后 post请求就不会再拦截 |
| 当django在读取模板时 {% csrf_token %}就会创建一个 <input type="hidden">类型的标签,是不可见的标签 |
| 将 |
| name="csrfmiddlewaretoken" values= csrf_token随机生产的字符串 密钥 |
| 在经过中间件 django.middleware.csrf.CsrfViewMiddleware 时进行匹配,查看是否合法 合法通过 不合法报错 |
基于ajax对csrftoken的使用
| 前后端分离时,如果将token发送 |
| 1.导入模块 |
| from django.middleware.csrf import get_token |
| 2.在指定需要token值的接口中设置token变量 |
| |
| token = get_token(request) |
| 3.将token返回给前端 |
| return HttpResponse(token) |
| |
| 前后端分离,前端如何使用ajax接受token值 |
| |
| $.ajax({ |
| url:'/get/', |
| success:function (data) { |
| console.log(data) |
| } |
| }) |
| |
| |
| |
| |
| |
| |
| 前后端分离怎么获取实现对token值的获取,和django的验证 |
| 1.导入模块 |
| from django.middleware.csrf import get_token |
| 2.在指定需要token值的接口中设置token变量 |
| |
| token = get_token(request) |
| 3.将token返回给前端 |
| return HttpResponse(token) |
| |
| 前后端分离,前端如何使用ajax接受token值 |
| |
| $.ajax({ |
| url:'/get/', |
| success:function (data) { |
| console.log(data) |
| localStorage.setItem('token',data) |
| } |
| }) |
| |
| |
| |
| |
| $(".login_btn").click(function () { |
| $.ajax({ |
| url:'/login/', |
| type:'post', |
| |
| headers:{'X-CSRFToken':localStorage.getItem("token")}, |
| data:{ |
| |
| csrfmiddlewaretoken:localStorage.getItem("token"), |
| uers:$('.name').val() |
| }, |
| success:function (data) { |
| console.log(data) |
| } |
| }) |
| }) |
| |
| HTML 的代码 |
| <h1>前后端分离</h1> |
| <div> |
| <input type="hidden" class="name"> |
| 名字 : <input type="text"> |
| <input type="button" value="ajax提交" class="login_btn"> |
| </div> |
| |
| |
| <h1>前后端不分离</h1> |
| <form action="" method="post"> |
| {% csrf_token %} |
| <input type="hidden"> |
| name : <input type="text"> |
| <input type="submit"> |
| </form> |
| |
关闭csrf的方式
| 在Django中对于基于函数的视图我们可以 @csrf_exempt 注解来标识一个视图可以被跨域访问。/免除认证 |
| from django.views.decorators.csrf import csrf_exempt |
| 视图 |
| @csrf_exempt |
| def xxx(request): |
| pass |
| 路由 |
| path('uers/', csrf_exempt(viesr.xxx)) |
| |
| |
| |
| MIDDLEWARE=[ |
| |
| 'django.middleware.csrf.CsrfViewMiddleware' 注调 |
| ] |
FBV与CBV模式
| 前后端分离模式: |
| FBV模式 基于函数实现的视图函数 |
| CBV模式,基于类实现的视图函数 |
| |
| CBV: |
| 单体模式:前后端不分离 |
| 前端看到的数据 是由后端django 视图 路由 中间件 从数据库orm获取内容呈现给前端。取数据和设置模板 |
| |
| 模式:前后端分离 |
| 前端的页面效果在另一个服务端,python服务只返回数据就可以 |
| 前端构建一个独立的网站,服务端构建一个独立的网站。 |
| 进行取出来的数据json格式化,做数据接口 API |
| 静态的服务器,客户端要什么数据,就会获取什么数据。 |
| nginx:属于一个代理的服务端 |
| 前端:js css html 通过 ajax从后端获取数据 |
| 后端只用返回json数据,其余的不用管 |
| |
API接口
| 应用程序编程接口API接口,对外提供一个操作的结构,对外是一个url地址或者网络地址,当客户端调用这个入口,应用程序会执行对应的代码操作,给客户端完成响应的对应功能 |
| |
| 接口规范开发:restful RPC |
| rbc:远程过程调用 |
| restful:资源状态转移,表征性状态转移 是一个协议,是一个规范 |
| |
| 服务端提供的所有的数据文件都被看成为# 资源 |
| 那么api接口本质上就是对资源的操作 restful 要求把当前接口对外提供资源进行操作,就把资源的 # 名字写在url地址 |
| web开发中常用的就是增删改查,所以restful要求在地址栏上什么要操作的资源是什么 |
| 通过http请求动词来说明对该资源进行的操作 |
| |
| # 请求方式 核心 |
| post 数据提交 |
| get 数据获取 |
| get 带pk值 获取某些数据 |
| delect 带pk值 删除数据 |
| put 带pk值 修改数据 |
| patch 带pk值 修改数据的部分信息 |
| |
| 结合资源名称和http请求动作,就可以说明当前api接口的功能是什么,restful是一个以资源为主的api接口规范,体现在地址上的是资源就是以名词表达,rbc以动作为主的api接口规范 体现在接口名称上往往附带操作数据的动作。 |
| |
| 不限语言和框架 |
| # 幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同 |
| 没有改变数据库的数据:就是幂等性 # get请求 数据安全 |
| 改变了数据库中的数据:不幂等性 # post 数据不安全 数据库的内容变化 |
| put patch delct 属于幂等性 # 数据不安全 比如张三 年龄改为 10岁 执行1次和执行100 都是10岁 只是修改了部分数据 |
CBV
| render 不会再使用了 |
| |
| |
| |
| from django.views import View |
| from django.core import serializers |
| 普通的json语法自能处理python内的数据结构,不能处理django的数据结构 |
| from app01 import models |
| |
| |
| class IndexView(View): |
| |
| def get(self, request): |
| |
| 数据 = models.表名.objects.all() |
| |
| |
| data_json = serializers.serialize('json',数据) |
| |
| data_xml = serializers.serialize('xml',数据) |
| return HttpResponse(data_json) |
| |
| def post(self, request): |
| return HttpResponse('CBV post') |
| |
| def delete(self, request): |
| return HttpResponse('CBV delete') |
| |
| def put(self, request): |
| return HttpResponse('CBV put') |
| |
| def patch(self, request): |
| return HttpResponse('CBV patch') |
| |
| urls.py的分发: |
| path('index/', views.IndexView.as_view()) |
| |
| |
| |
| |
| |
| |
CBV源码解析
| 本质:还是fbv |
| path('index/', views.IndexView.as_view()) |
| views.IndexView.as_view():到底是什么主要取决于.as_view()方法的返回值 |
| |
| IndexView自己写的接口类为什调用这个as_view() |
| from django.views import View |
| IndexView(View) |
| |
| 在view类中http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'] 接口支持的请求方法 |
| |
| |
| |
| |
| as_view() 的源码 |
| @classonlymethod |
| def as_view(cls, **initkwargs): |
| """Main entry point for a request-response process.""" |
| for key in initkwargs: |
| if key in cls.http_method_names: |
| raise TypeError( |
| 'The method name %s is not accepted as a keyword argument ' |
| 'to %s().' % (key, cls.__name__) |
| ) |
| if not hasattr(cls, key): |
| raise TypeError("%s() received an invalid keyword %r. as_view " |
| "only accepts arguments that are already " |
| "attributes of the class." % (cls.__name__, key)) |
| |
| def view(request, *args, **kwargs): |
| self = cls(**initkwargs) |
| self.setup(request, *args, **kwargs) |
| if not hasattr(self, 'request'): |
| raise AttributeError( |
| "%s instance has no 'request' attribute. Did you override " |
| "setup() and forget to call super()?" % cls.__name__ |
| ) |
| return self.dispatch(request, *args, **kwargs) |
| view.view_class = cls |
| view.view_initkwargs = initkwargs |
| |
| |
| update_wrapper(view, cls, updated=()) |
| |
| |
| |
| update_wrapper(view, cls.dispatch, assigned=()) |
| return view |
| |
| def setup(self, request, *args, **kwargs): |
| """Initialize attributes shared by all view methods.""" |
| if hasattr(self, 'get') and not hasattr(self, 'head'): |
| self.head = self.get |
| self.request = request |
| self.args = args |
| self.kwargs = kwargs |
| |
| def dispatch(self, request, *args, **kwargs): |
| |
| |
| |
| if request.method.lower() in self.http_method_names: |
| handler = getattr(self, request.method.lower(), self.http_method_not_allowed) |
| else: |
| handler = self.http_method_not_allowed |
| return handler(request, *args, **kwargs) |
| |
| def http_method_not_allowed(self, request, *args, **kwargs): |
| logger.warning( |
| 'Method Not Allowed (%s): %s', request.method, request.path, |
| extra={'status_code': 405, 'request': request} |
| ) |
| return HttpResponseNotAllowed(self._allowed_methods()) |
| |
| def options(self, request, *args, **kwargs): |
| """Handle responding to requests for the OPTIONS HTTP verb.""" |
| response = HttpResponse() |
| response.headers['Allow'] = ', '.join(self._allowed_methods()) |
| response.headers['Content-Length'] = '0' |
| return response |
| |
| def _allowed_methods(self): |
| return [m.upper() for m in self.http_method_names if hasattr(self, m)] |
| |
| |
| |
| IndexView.as_view() 派发原理: |
| IndexView 这个是一个api接口类/继承了View 父类 |
| as_view() 类方法 |
| |
| ''' |
| 类.as_view() 这个类执行类这个方法,先去类中这个方法,找不到就继承的父类中找as_view()方法 |
| 执行:as_view()返回的是一个view函数名 |
| 相当于 |
| path('index/', views.IndexView.as_view()) == path('index/', views.view) |
| |
| path('index/', views.view) 当访问index页面时就会执行view方法 和原来的路由分发访问路由执行视图函数一样 |
| |
| 在view方法中 |
| self = cls(**initkwargs) 因为as_view()是一个类方法进行赋值/就是对这个类进行了实例化 赋值给了self |
| cls 这个类就是 api类,因为继承了View 父类,先找自己在父类 |
| |
| view方法中返回值 |
| self.dispatch(request, *args, **kwargs) 先从调用这个.as_view()类中找,找不到就去view父类中找 |
| |
| dispatch方法中 |
| |
| |
| if request.method.lower() in self.http_method_names: # 将请求的方式 与 定义的列表中进行对比 |
| handler = getattr(self, request.method.lower(), self.http_method_not_allowed) 成功执行 |
| |
| 使用了反射语法 |
| self就是实例化的api类 |
| request.method.lower() 获取的请求方式 |
| |
| |
| else: # 不成功报错 |
| handler = self.http_method_not_allowed # 报错方法 |
| return handler(request, *args, **kwargs) # 成功后将这个进行返回 |
| |
| |
| path('index/', views.andler = getattr(self, request.method.lower(), self.http_method_not_allowed)) |
| path('index/', views.handler(request, *args, **kwargs)) |
| path('index/', views.api类实例化对象.请求方式(request, *args, **kwargs)) 所以为什在api类中要接收request参数 |
| ''' |
| |
Django-文件上传功能
from表单的上传文件
| django中的语法 |
| 文件对象 = request.FILES.get('上传文件的健') |
| 文件对象.name |
| |
| html的文件上传标签 |
| 使用的form标签上传,需要设置参数post,和enctype="multipart/form-data" |
| <input type="file" name="健"> |
| |
| |
| |
| def login(request): |
| |
| stu_excel = request.FILES.get('xxx') |
| name = stu_excel.name |
| |
| ''' |
| 1.创建一个文件夹,存放用户上传的文件 |
| media文件夹 |
| 2.持久化存储文件 |
| ''' |
| import os |
| path = os.path.join('media', name) |
| with open(path, 'wb') as f: |
| for line in stu_excel: |
| f.write(line) |
| |
| return HttpResponse('上传成功') |
| |
| |
| |
| |
| |
| <h3>基于form表单进行上传文件</h3> |
| <form action="/login/" method="post" enctype="multipart/form-data"> |
| {% csrf_token %} |
| |
| <input type="file" name="xxx"> |
| <input type="submit"> |
| </form> |
| |
| |
| 1.上传文件的标签为 input标签属性type为file |
| 2.上传文件一般为post操作 |
| 3.需要设置enctype属性 值为multipart/form-data,在浏览器中请求头中也有这个属性,这是一个文件 |
| 4. action= 设置上传文件的路径,专门开设一个视图函数用来接受上传文件的 |
ajax上传
| 局部刷线 |
| |
| HTML代码 |
| |
| |
| <h3>ajax上传文件</h3> |
| <input type="file" class="file2"> |
| <input type="button" value="提交上传" class="ajax_btn"> |
| |
| |
| <script> |
| $('.ajax_btn').click(function () { |
| |
| var formData = new FormData() |
| |
| formData.append('file2', $('.file2')[0].files[0]) |
| formData.append('csrfmiddlewaretoken', $('[name="csrfmiddlewaretoken"]').val()) |
| $.ajax({ |
| url: "/ajax/", |
| type: 'post', |
| data: formData, |
| contentType: false, |
| processData: false, |
| success: function (data) { |
| console.log(data) |
| } |
| }) |
| }) |
| </script> |
| |
| |
| |
| 视图函数中的代码 |
| def ajax(request): |
| stu_excel = request.FILES.get('file2') |
| name = stu_excel.name |
| |
| ''' |
| 1.创建一个文件夹,存放用户上传的文件 |
| media文件夹 |
| 2.持久化存储文件 |
| ''' |
| import os |
| path = os.path.join('media', name) |
| with open(path, 'wb') as f: |
| for line in stu_excel: |
| f.write(line) |
| ret = {'state': True, 'msg': ''} |
| return JsonResponse(ret) |
| |
orm中的存储文件字段
| imagefield 和 filefield 字段 |
| imagefield 图片上传字段比filefield字段多了 图片大小的参数 |
| filefield 上传指定的文件和图片 |
| class Userinfo(models.Model): |
| name = models.CharField(max_length=32) |
| |
| fath = models.FileField(upload_to='avatars/') |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 1.先在配置文件中设置用户上传文件的文件夹地址 |
| MEDIA_ROOT = os.path.join(BASE_DIR,'media') |
| 2.在设置表字段 |
| fath = models.FileField(upload_to='avatars/') |
| 当对这个字段进行文件赋值。 |
| 1.找到配置文件的MEDA_ROOT文件夹 |
| 2.根据字段upload_to的 值 创建一个文件avatars文件 |
| 3.将用户文件存到avatars文件夹下面 |
| |
| 视图函数的语法 |
| 1.取文件 = request.FILES.get('file2') |
| 2.将取到的文件上传数据库 = models.Userinfo.objects.create(name=name,fath=file) |
| 3.获取文件 |
| model对象.文件图片字段 |
| model对象.文件图片字段.url |
| |
| user.fath |
| user.fath.url |
| |
| |
| |
| |
| 1.form表单上传 |
| 2.ajax上传 |
| |
| |
| 1.在配置文件中设置参数 |
| MEDIA_URL = '/media/' |
| 当用户上传的文件路径是由upload_to='avatars/' |
| upload_to参数拼接上文件名存储的数据库 |
| 当设置MEDIA_URL时,就会将MEDIA_URL进行拼接 |
| 例如: |
| /media/avatars/用户上传的图片名字 |
| 2.需要设置一个对外开放的路径 |
| from django.views.static import serve |
| from form import settings |
| re_path(r'media/(?P<path>.*)',serve,{"document_root":settings.MEDIA_ROOT}) |
| |
| |
| |
| |
| 补充:将用户上传的内容进行分割 |
| fath = models.FileField(upload_to=可以使用回调函数) |
| |
| |
| def user_directory_path(instance, filename): |
| ''' |
| upload_to 参数可以使用回调函数 |
| :param instance: filename原文件名字,当前创建的实例对象,就是当前使用FileField文件上传字段的表对象 |
| :param filename: 当前创建实例对象的上传的文件 |
| ''' |
| return os.path.join(instance.name, 'avatars', filename) |
| |
Django-文件功能下载
| import mimetypes |
| from django.http import FileResponse |
| |
| |
| |
| def customer_tpl(request): |
| """ |
| 下载批量导入Excel列表 |
| :param request: |
| :return: |
| """ |
| |
| tpl_path = os.path.join(settings.BASE_DIR, 'web', 'files', '批量导入客户模板.xlsx') |
| |
| content_type = mimetypes.guess_type(tpl_path)[0] |
| |
| response = FileResponse(open(tpl_path, mode='rb'), content_type=content_type) |
| |
| response['Content-Disposition'] = "attachment;filename=%s" % 'customer_excel_tpl.xlsx' |
| print(response) |
| return response |
文本编辑器使用
文档:http://kindeditor.net/doc.php
| 1. http://kindeditor.net/down.php |
| 2. 引入/在当前的需要使用textarea标签的html中 |
| 使用方式: |
| <script charset="utf-8" src="项目路径"></script> |
| <script charset="utf-8" src="/editor/lang/zh-CN.js"></script> |
| <script> |
| KindEditor.ready(function(K) { |
| window.editor = K.create('写上textarea标签的class/id值',添加参数{参数属性:属性值}); |
| }); |
| </script> |
| |
| |
| 属性的设置: |
| 1.uploadJson:"/路由,专门为用户文件上传做的操作/" |
| 1.单独设置一个路由 |
| 2.单独设置一个视图函数处理 编辑器的文件上传 |
| 3.文本编辑器文件上传:post请求 |
| |
| 2.设置属性 |
| extraFileUploadParams:{ |
| csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val() |
| } |
| |
| 3.设置新的指定名字 |
| filePostName:"新名字" |
基本使用
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Title</title> |
| // 导入需要的路径 |
| <script charset="utf-8" src="../static/kindeditor/kindeditor-all.js"></script> |
| <script charset="utf-8" src="../static/kindeditor/lang/zh-CN.js"></script> |
| </head> |
| <body> |
| <div class="comment_content"></div> |
| |
| <script> |
| KindEditor.ready(function(K) { |
| window.editor = K.create('.comment_content'); |
| }); |
| </script> |
| </body> |
| </html> |
HTML
| <script charset="utf-8" src="/static/kindeditor/kindeditor-all.js"></script> |
| <script> |
| KindEditor.ready(function (K) { |
| window.editor = K.create('.comment_content',{ |
| width:"800", |
| height:"600", |
| uploadJson:"/upload/", //设置文件上传路径求情,同时也会接受参数文件 |
| extraFileUploadParams:{ |
| csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val() |
| }, |
| filePostName:"upload_img" |
| }); |
| }); |
| |
| textarea标签 才能使用文本编辑器 |
视图函数
| 1.设置专门的路径和视图函数 |
| def upload(request): |
| |
| print(request.FILES) |
| |
| img = request.FILES.get("upload_img") |
| print(img.name) |
| |
| path = os.path.join(settings.MEDIA_ROOT,"upload_img",img.name) |
| |
| with open(path,mode="wb") as f: |
| for line in img: |
| f.write(line) |
| |
| |
| response={ |
| "error":0, |
| "url":"/media/upload_img/{}".format(img.name), |
| } |
| impute josn |
| return HttpResponse(json.dumps( response)) |
| |
| |
同源测略是什么
| 由浏览器的导致的同源测略 |
| 指的是:同源 1.域名 2.协议 3.端口 |
| 1.域名 2.协议 3.端口 同源相同就会通过,不同源就会报错 |
| 请求的ip地址 和 所在ajax的地址不同 不同源就会报错 |
| |
| 怎么解决: |
| 1.jsonp |
| 2.cors |
| 3.前端代理 |
| |
| 使用的cors |
| 简单的处理方式 |
| 视图函数: 处理ajax代码的视图函数 |
| def add(request): |
| 代码 .... |
| res = {'给ajax返回的数据'} |
| |
| ret = JsonResponse( res ) |
| |
| ret['Access-Control-Allow-Origin'] = '*' *代表全部的ip地址都可以获取,还可以写规定ip地址 |
| return ret |
| |
Django-补充
ContentType组件创建表建构
| 是什么? |
| |
| 当进行数据库迁移时 |
| python manage.py makemigrations |
| python manage.py migrate |
| |
| 会出现一张django自动创建的一个表 |
| django_content_type表(自带进行记录的表) |
| 字段:id app_label model |
| app_label : 当前django下的每一个app 程序的名字 |
| model : 当前每个app程序下的表 |
| |
| |
| 这个表就是负责将当前项目下的app如果有表结构迁移就会记录当前的app名称与新建的表结构的名字进行存储到contenttype表中 |
| |
| 在项目中1张表对应到多表结构就可以使用这个组件( |
| ''' |
| 例如: |
| 熟食表 |
| 商品表 |
| 零食表 |
| 电器表 |
| 手机表 |
| .... |
| |
| 优惠卷表 怎么进行创建关系 |
| 难道...这样子进行关联吗那么存在100多张表,不就变成100个关联字段了吗 |
| id 熟食id 商品id 零食id 电器id 手机id title |
| 1 1 null null null null 8折 |
| 1 null 1 null null null 满50减2元 |
| |
| ''' |
| ) |
| |
| |
| 比如:这个是当前手写的,直接使用django内自带的ContentType表方便 |
| class Coupon(models.Model): |
| ''' |
| 优惠卷表 如果优惠卷商城的表有100张,那么当前的优惠卷表需要多少关联100个fk的id |
| |
| 通过这种方式将表关联起来 table_id与object_id 两个字段就可以将表进行定位 |
| id title table_id object_id |
| 1 面包95折 1(table的id) 1(食品表下的面包的id) |
| ''' |
| title = models.CharField(max_length=32) |
| table = models.ForeignKey(to='Table') |
| object_id = models.IntegerField() |
| |
| |
| class Table(models.Model): |
| ''' |
| 因为项目足够大的时候,项目中有多个app,这个表就存储当前app名字 与下面的 app表的名字 |
| id app_name table_name |
| 1 app food |
| 2 app fruit |
| ''' |
| app_name = models.CharField(max_length=32) |
| table_name = models.CharField(max_length=32) |
| |
| |
| 可以创建一张 table表 用来记录当前程序下创建 app 与 app的表 |
| 在创建优惠卷表 字段1 绑定当前的table表 字段2绑定 当前的商品id |
| |
| 将表就够通过 字段1 + 字段2 将就锁定了这个商品 减少字段的数量 将横向的变为纵向的 |
| |
| |
案例
| 1.导入模块 |
| from django.db import models |
| from django.contrib.contenttypes.models import ContentType |
| from django.contrib.contenttypes.fields import GenericForeignKey |
| from django.contrib.contenttypes.fields import GenericRelation |
| |
| 2.创建表 |
| class Food(models.Model): |
| ''' |
| 食品表 |
| id title |
| 1 面包 |
| ''' |
| title = models.CharField(max_length=32) |
| coupons = GenericRelation(to='Coupon') |
| |
| class Coupon(models.Model): |
| ''' |
| 优惠卷表 |
| ''' |
| title = models.CharField(max_length=32) |
| |
| content_type = models.ForeignKey(to=ContentType,on_delete=models.CASCADE) |
| |
| object_id = models.IntegerField() |
| |
| content_object = GenericForeignKey('content_type','object_id') |
| |
| |
| 3.正删改查GenericForeignKey/GenericRelation/ContentType的使用 |
| |
| class Users(APIView): |
| |
| def get(self, request, *args, **kwargs): |
| |
| food_obj = models.Food.objects.filter(id=1).first() |
| |
| |
| |
| models.Coupon.objects.create(title='双11全场优惠分享',content_object=food_obj) |
| |
| |
| |
| print(food_obj.coupons.all()) |
| |
| |
| |
| coupon_obj = models.Coupon.objects.filter(id=1).first() |
| print(coupon_obj.content_object) |
| |
| |
| |
| content_obj = models.ContentType.objects.filter(app_label='app',model='food').first() |
| |
| model_class = content_obj.model_class() |
| |
| |
| print(model_class.objects.all()) |
| return Response({'data': '跨域请求'}) |
| |
| |
| 是Django 模型类的Meta是一个内部类,它用于定义一些Django模型类的行为特性 |
1.abstract
| class User(models.Model): |
| name = models.CharField(max_length=100) |
| class Meta(object): |
| |
| |
| abstract = True |
| |
| |
| class User_2(User): |
| title = models.CharField(max_length=100) |
2.db_table
| class User(models.Model): |
| name = models.CharField(max_length=100,verbose_name='名字') |
| |
| class Meta(object): |
| db_table = 'Students' |
| verbose_name = '给当前的表设置可读性的名字一般时中文' |
| verbose_name_plural = '这个选项是指定,模型的复数形式是什么' |
| |
| db_table : 在数据库迁移时使用db_table 对应变量作为表名(如果不设置,那么就会根据app+类名进行设置) |
| verbose_name:是在admin页面中显示设置的变量值(一般用于替代英文表名) |
| verbose_name_plural:名称后面会加上s(只针对英文名称,中文不针对) |
| verbose_name和verbose_name_plural 都是在admin中显示一个可读的model表名(中文) |
| 字段中的verbose_name:会在admin中显示中文的提示信息 |
3.unique_together
| unique_together = ('course','chapter') 将表类中的两个字段设置联合唯一 |
Django中model知识点补充
Django中的Model中save方法使用
| 在更新数据和添加数据的时候都会调用这个save方法 |
| from django.core.exceptions import ObjectDoesNotExist |
| |
| class User(models.Model) |
| name = models.CharField(max_length=128) |
| age = mdoels.IntegerField() |
| |
| def save(self, *args, **kwargs): |
| |
| if self.name == 'wkx': |
| if not self.age <= 18: |
| raise ValueError('抱歉年龄超过18岁') |
| super(User,self).save(*args, **kwargs) |
| |
Django中model中定义方法
| class CourseSection(models.Model): |
| '''课时表''' |
| chapter = models.ForeignKey(to='CourseChapter',related_name='course_section', on_delete=models.CASCADE) |
| |
| '''获取第几章节(通过当前的方法可以直接CourseSection表结构进行调用当前方法获取章节信息)''' |
| def course_chapter(self): |
| return self.chapter.chapter |
| '''获取课程的名称(通过当前的方法可以直接CourseSection表结构进行调用当前方法获取课程的名称)''' |
| def course_name(self): |
| return self.chapter.course.title |
| |
| 在表类中定义方法,减少连表的次数。通过对象调用自定义的方法获取想要连表的信息 |
Django中orm时间字段参数
| date = models.DateTimeField(auto_now_add=True) |
| |
| |
| |
| |
| |
| course_img = models.ImageField(upload_to="course/%Y-%m(路径)", verbose_name='课程的图片') |
Django中关于orm字段属性的choices
| |
| class User(models.Model) |
| section_type_choices = ((0, '文档'), (1, '练习'), (2, '视频')) |
| section_type = models.SmallIntegerField(default=2, choices=section_type_choices) |
| |
| 是可以根据获取的对象执行get_'带有choices属性字段'_display()中文名称 |
| obj = models.Course.objects.filter(id=1).first() |
| obj.get_section_type_display() |
| class User(models.Model): |
| name = models.CharField(max_length=32) |
| |
| class Age(models.Model): |
| age = models.CharField(max_length=32) |
| user = models.ForeignKey(to="User", related_name="user_and_age", on_delete=models.CASCADE) |
| |
| |
| related_name : 反向查询时使用的字段 |
| |
| |
| user_obj= User.object.filter(id=1).first() |
| user_obj.user_and_age.age |
补充知识点orm字段小技巧
| |
| |
| |
| order = models.PositiveSmallIntegerField(default=1) |
Django配置补充
Django中admin账户注册和注册自创表
| 1.在命令行输入命令 |
| python manage.py createsuperuser |
| |
| 2.程序中将表类注册到admin路由中 |
| from django.contrib import admin |
| from app01 import models |
| |
| |
| @admin.register(models.DegreeCourse) |
| class D(admin.ModelAdmin): |
| pass |
| |
| |
| @admin.register(models.Category) |
| class D2(admin.ModelAdmin): |
| |
| list_display = ('name', 'password', 'email') |
| |
| list_per_page = 50 |
| |
| ordering = ('name',) |
| |
| list_display_links = ('name',) |
| |
| 3.通过admin/路由根据注册进行登录admin后台管理页面 |
| |
| |
| |
| 注意事项 |
| |
| |
| |
| |
| |
| |
| 当进行注册admin 时,可以使用反射进行注册 |
| 1.现将表的全部名称放到一个列表中 |
| __all__ = ['user','Course'...] |
| |
| 2.在admin.py文件中进行注册 |
| from django.contrib import admin |
| from app01 import models |
| |
| for table in models.__all__: |
| admin.site.register(getattr(models, table)) |
Django数据库迁移与创建新app
| python manage.py makemigrations |
| python manage.py migrate |
| |
| 如果在迁移时出现没有变化的问题(重新删除了迁移文件和数据库迁移的表) |
| python manage.py makemigrations --empty 当前程序的名称 |
| 就会新生成一个migrations文件夹,在执行上述命令 |
| |
| |
| python manage.py startapp app名称 |
| |
| |
| 1.python manage.py startapp 'app名称' '目录父级/目录' |
| 例如: |
| 项目中的存放app的文件: apps |
| 其中的一个文件: base |
| python manage.py startapp base apps/base |
| 2.在配置文件中进行注册 |
| 3.修改多层级目录下的app 中的apps.py 文件的name(添加父级或者以上的名字的前缀) |
| |
Django路由分发
| |
| |
| 源码内部逻辑 |
| from importlib import import_module |
| if isinstance(arg, tuple): |
| try: |
| urlconf_module, app_name = arg |
| .... |
| else: |
| urlconf_module = arg |
| |
| if isinstance(urlconf_module, str): |
| urlconf_module = import_module(urlconf_module) |
| patterns = getattr(urlconf_module, 'urlpatterns', urlconf_module) |
| app_name = getattr(urlconf_module, 'app_name', app_name) |
Django中Api框架中序列化器获取choices中的中文or钩子字段
| level = serializers.CharField(source='get_level_display') |
| |
| source = course字段在表中是一个fk关联对象 (必须是存在关系的才能这样操作) |
| study_num = serializers.IntegerField(source='course.study_num') |
| |
| |
| |
| |
| 字段名 = serializers.SerializerMethodField() |
| |
| def get_字段名(self,obj): |
| obj : 当前序列化器中的绑定表对象 |
| self :序列化对象 |
| return obj.price_policy.all().order_by('price').first().price |
| |
| def get_teachers(self, obj): |
| teachers_list = [] |
| |
| queryset_list = obj.teachers.all() |
| for i in queryset_list: |
| teachers_list.append({'id': i.id, 'name': i.name, 'brief': i.brief}) |
| return teachers_list |
| |
| |
| |
| 1.需要在配置文件中setings.py中进行配置 |
| MEDIA_URL = 'media/' |
| MEDIA_ROOT = os.path.join(BASE_DIR,'media') |
| |
| 2.给前端开放静态资源接口 |
| from django.views.static import serve |
| from luffyCity import settings |
| |
| |
| |
| re_path('media/(?P<path>.*)',serve,{'document_root':settings.MEDIA_ROOT}) |
| |
| |
| |
| django的imagefield来使用 |
| course_img = models.ImageField(upload_to="course/%Y-%m", verbose_name='课程的图片') |
| 自动根据当前的配置文件的文件夹upuload_to 新建文件夹 |
命令
后端API知识点补充
序列化器图片存储
| 1.model.py 类 |
| class pic(models.Model): |
| user_img = models.ImageField(upload_to="user/picture", verbose_name='个人头像', default='user/picture/default.png',null=True) |
| |
| |
| 2.序列化器 |
| class RegisterCreateSerializers(serializers.ModelSerializer): |
| class Meta: |
| model = models.pic |
| fields = ['user_img'] |
| |
| 3.API逻辑 |
| def post(self,request,...): |
| pic = request.FILES.get('user_img') |
| ser_obj = RegisterCreateSerializers(data=self.request.data) |
| if ser_obj.is_valid(): |
| ser_obj.user_img = pic |
| ser_obj.save() |
| return Response(ser_obj.data) 返回 |
购物车
1.加入购物车
| 1.考虑前端出入的数据是什么 |
| 1.价格id |
| 2.数量 |
| 3.商品id |
| 2.需要考虑将数据存储在那个地方 |
| redis首选 |
| 3.需要考虑redis存储的数据结构 |
| 考虑问题设计数据结构: |
| 用户在添加购物车时,会不会直接结算 |
| 用户在添加购物车时,如果不直接结算 |
| 在购物车中进行展示的无非就是 |
| 1.商品图片 |
| 2.商品价格 |
| 3.商品数量 |
| |
| 关于存储在redis中的key的选择 |
| |
| |
| 标记信息_用户id_商品id:{商品的详情数据} 这种结构显示 |
2.查看购物车
| 1.需要拼接 redis 的 key |
| 2. 从redis中获取 |
| 3.构建数据结构展示给前端显示 |
| |
| |
| SHOPPINGCAR_KEY = 'SHOPPINGCAR_%s_%s' |
| user_id = 1 |
| key = SHOPPINGCAR_KEY %(user_id,'*') |
| 拼接的结果就是:SHOPPINGCAR_1_* |
| |
| 通过 redis中的scan_iter 进行模糊查询获取查询到的全部的类似的key |
| all_keys = conn.scan_iter(key) |
| ''' |
| 需要通过循环获取每一个key,在get key 从redis中获取 |
| 在进行返回就可以了。 |
| ''' |
购物车更新
| 更新和添加购物车其实就是一样的 |
| |
| 1.获取当前用户的id 和 商品的id 还有 价格的id |
| 2. 拼接redis的key key_用户id_商品id |
| 2.1 验证用户购物车中有没有当前商品(从redis中根据拼接的key找,找到就有,找不到就没有返回结果) |
| |
| 2.2 验证当前商品是否存在价格套餐(也是从redis中找,找到就有找不到就返回结果) |
| 3. 从redis中查找 当前拼接key |
| 4. 根据redis 库中拼接的 key 获取取的val,修改当前用户默认选中的价格策略就可以 |
| 5. 再讲内容重新写到 redis中(就会覆盖原先的内容) |
| |
| |
购物车删除
| 1.前端需要传入商品的id就可以 |
| 注意:传入的商品id不是单独一个,而是多个id的列表(支持批量删除) |
| [商品id1,商品id2] |
| 2.循环当前的商品id列表 |
| 1.拼接当前的key |
| 3.根据拼接当前的key 在从redis中进行删除 |
| 1.通过conn.exists(key) |
| 不存在跳过(不要报错,也不要继续返回,如果是多个情况下,会终端下面的删除内容) |
| 存在在进行删除 |
| |
生成验证码图案例
| import os, random |
| from pathlib import Path |
| from PIL import Image, ImageDraw, ImageFont |
| import matplotlib.font_manager as fm |
| path = Path(__file__).parent |
| |
| |
| def bgcolor(): |
| '''随机背景颜色''' |
| return random.randint(64, 255), random.randint(64, 255), random.randint(64, 255) |
| |
| |
| def randomtxt(): |
| '''随机的验证码''' |
| txt_list = [] |
| txt_list.extend([i for i in range(65, 91)]) |
| txt_list.extend([i for i in range(97, 123)]) |
| txt_list.extend([i for i in range(48, 58)]) |
| return chr(txt_list[random.randint(0, len(txt_list) - 1)]) |
| |
| |
| def txtcolor(): |
| '''随机的字体颜色''' |
| return random.randint(32, 127), random.randint(32, 127), random.randint(32, 127) |
| |
| |
| def generatecode(): |
| width = 200 |
| height = 50 |
| code_num = '' |
| code_name = 'media\\code\\imgcode.png' |
| image_name = os.path.join(path, code_name) |
| image = Image.new('RGB', (width, height), (255, 255, 255)) |
| draw = ImageDraw.Draw(image) |
| for w in range(width): |
| for h in range(height): |
| draw.point((w, h), fill=bgcolor()) |
| myfont = ImageFont.truetype(fm.findfont(fm.FontProperties(family='DejaVu Sans')), 36) |
| for i in range(4): |
| text = randomtxt() |
| code_num += text |
| draw.text((50 * i + 10, 10), text, font=myfont, fill=txtcolor()) |
| with open(image_name, 'wb') as fp: |
| image.save(fp, 'PNG') |
| return code_num, code_name |
| |
| |
| generatecode() |
| |
补充
| 关于后端接口返回的code 是200,不返回其他的,防止暴露接口 |
关于后端api框架补充
| 关于前后端分类的框架限流,认证,版本,权限 都可以通过继承原来的类 改写成为自己想要的功能 |
| 例如: |
| |
| from rest_framework import status |
| from rest_framework.views import APIView |
| from rest_framework.response import Response |
| from django.utils.deprecation import MiddlewareMixin |
| |
| from django.core.cache import cache as cache_redis |
| from rest_framework.permissions import BasePermission |
| from rest_framework.authentication import BaseAuthentication |
| from rest_framework.versioning import URLPathVersioning |
| from rest_framework.throttling import SimpleRateThrottle |
| from rest_framework.exceptions import AuthenticationFailed, NotAuthenticated, NotFound, PermissionDenied,NotAcceptable |
| |
| class Authentication(BaseAuthentication): |
| |
| def authenticate(self, request): |
| token = 123456 |
| if not token: |
| raise NotAuthenticated({'error': '未认证'}) |
| if not token == 123456: |
| raise AuthenticationFailed({'error': '认证失败'}) |
| return '用户对象', 'token' |
| |
| def authenticate_header(self, request): |
| return 'Bearer realm=API' |
| |
| |
| class PathVersion(URLPathVersioning): |
| invalid_version_message = '版本有误,请选择正确的版本!' |
| |
| def determine_version(self, request, *args, **kwargs): |
| version = kwargs.get(self.version_param, self.default_version) |
| if version is None: |
| version = self.default_version |
| |
| if not self.is_allowed_version(version): |
| raise NotFound({'error': self.invalid_version_message}) |
| return version |
| |
| |
| class BaseP(BasePermission): |
| |
| def has_permission(self, request, view): |
| |
| name = 1 |
| if name == 1: |
| return True |
| else: |
| return False |
| |
| def has_object_permission(self, request, view, obj): |
| return False |
| |
| |
| |
| |
| |
| |
| |
| class ThrottledException(NotAcceptable): |
| '''自定义的异常类 用来抛出限流类的限流信息''' |
| pass |
| |
| |
| class MyS(SimpleRateThrottle): |
| '''自定义限流类''' |
| cache = cache_redis |
| |
| cache_format = 'throttle_%(scope)s_%(ident)s' |
| scope = 'user' |
| |
| |
| |
| THROTTLE_RATES = {'user': '2/m'} |
| |
| def get_cache_key(self, request, view): |
| '''重写父类的方法作用(返回唯一标识)如果唯一标识需要修改,就在当前方法中进行修改''' |
| if str(request.user) != 'AnonymousUser' and request.user: |
| ident = 1 |
| else: |
| ident = self.get_ident(request) |
| return self.cache_format % {'scope': self.scope, 'ident': ident} |
| |
| def throttle_failure(self): |
| '''如果超过当前的限制次数,那么就会调用这个方法,将限制时间返回给前端页面''' |
| wait = self.wait() |
| detail = { |
| 'code': 1005, |
| 'data': '访问限制', |
| 'detail': '需要等待%ss才能访问' % int(wait) |
| } |
| raise ThrottledException({'error':'限制访问,需要等待%ss才能访问' % int(wait)}) |
| |
| |
| class ApiView(APIView): |
| |
| |
| |
| |
| |
| def get(self, request, *args, **kwargs): |
| |
| return Response(data={'code': 10001, 'data': {'cccc'}}, status=status.HTTP_200_OK) |
| |
| def delete(self, request, *args, **kwargs): |
| return |
| |
| def permission_denied(self, request, message=None, code=None): |
| message = {'error': "你无权访问"} |
| if request.authenticators and not request.successful_authenticator: |
| raise NotAuthenticated() |
| raise PermissionDenied(detail=message, code=code) |
| |
| |
| class MyCors(MiddlewareMixin): |
| def process_response(self, request, response): |
| response["Access-Control-Allow-Origin"] = "*" |
| if request.method == "OPTIONS": |
| |
| |
| response[ |
| "Access-Control-Allow-Headers"] = "Content-Type" |
| |
| |
| response["Access-Control-Allow-Methods"] = "DELETE, PUT, POST" |
| return response |
| |
| |
| |
| from rest_framework import status |
| from rest_framework.exceptions import APIException |
| class BlogException(APIException): |
| status_code = status.HTTP_200_OK |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现