kaixin
Published on 2023-05-09 / 33 Visits
1

Django框架

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  # django 3.0后新增,用于django运行在异步程序模式的一个web应用对象
    settings.py # 配置文件
    urls.py # 全局url文件
    wsgi.py # 项目运行在wsgi服务器时的入口文件
    init.py
-app01  # 子应用
    models.py # 模型类模块 数据库
    views.py # 视图逻辑 
    tests.py # 单元测试模块
    apps.py # 该应用的配置,自动生成,在项目启动前
    admin.py # 后台管理系统的配置文件
-templates # 模板文件夹
-static # 静态文件夹
-db.sqlite3  # settings.py默认配置的sqlite数据库,第一次执行时,就会生存
-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  # 导入django配置文件

1.设置路径 path属于url匹配命名,获取的参数  
2.导入内置处理静态资源的视图函数 
3.传入的指定参数(document_root在函数中属于关键字传参),导入的配置文件的中设置静态路径配置
# serve(request,path,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文件 #Django内部的配置文件
3.添加路径:STATICFILES_DIRS =[os.path.join(BASE_DIR,"static")]
路径的含义:
	1.变量名 STATICFILES_DIRS 固定写法,不能更改
    2.BASE_DIR django内部的变量 指向项目根目录路径,配置文件有说明
    3.os.path.join(BASE_DIR,'static')#通过os模块 进行拼接

放各个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 # 导出配置文件信息


# 1.STATIC_ROOT 是在部署静态文件时(pyhtonmanage.pycollectstatic)所有的静态文静聚合的目录
django会把所有的static文件都复制到STATIC_ROOT文件夹下
STATIC_ROOT  # 在部署是起作用


# 那现在的问题是如何让django知道你把一些静态文件放到app以外的公共文件夹中呢,那就需要配置STATICFILES_DIRS了
STATICFILES_DIRS
一种就是在每个app里面新建一个static文件夹,将静态文件放到里面,在加载静态文件时,比如要在模板中用到静态文件,django会自动在每个app里面搜索static文件夹(所以,不要把文件夹的名字写错, 否则django就找不到你的文件夹了)
另一种,就是在所有的app文件外面,建立一个公共的文件夹,
因为有些静态文件不是某个app独有的,那么就可以把它放到一个公共文件夹里面,方便管理(注意,建立一个公共的静态文件的文件夹只是一种易于管理的做法,但是不是必须的,app是可以跨app应用静态文件的,因为最后所有的静态文件都会在STATIC_ROOT里面存在

# 2.静态文件在网站上进行访问的设置,需要配置url路由                                       
STATIC_URL

# 3.设置时区与中文
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'

# 4.DEBUF
DEBUG = False 部署时设置参数
同时需要设置
ALLOWED_HOSTS = ['*']
DEBUG_PROPAGATE_EXCEPTIONS = True # 出现异常时出现的错误不会被django进行处理而是由web服务器进行处理
                                           
# 5.DATABASES配置 数据库
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, # 数据库的会话声明周期 默认0请求结束后立即关闭 none时会占用数据库链接资源 
    }
}

# 6.CACHES配置 缓存配置
缓存配置
采用的是django-redis进行的配置

pip install django-redis 安装
官方文档:https://django-redis-chs.readthedocs.io/zh_CN/latest/

from django.core.cache import cache # 使用dajngo的cache使用redis
from django_redis import get_redis_connection # 使用安装包中的使用
# 查看当前的配置参数
get_redis_connection('default')

# 设置了链接池可以用通过当前查看使用多少
get_redis_connection('default').connection_pool._created_connections

cache # 是一个可以调用的对象进行redis的缓存操作

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',  # 使用的是django_redis包
        'LOCATION': 'redis://127.0.0.1:6379',  # 链接地址
        'KEY_PREFIX': 'API_D',  # 设置后会将当前key作为前缀
        'TIMEOUT': 60,  # 缓存过期时间 默认为300秒 设置None 用不过期
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',  # 客户端类环境包
            'MAX_ENTRIES ': 300,  # 删除之前允许缓存的最大条目,默认300
            'CULL_REQUENCY': 3,  # 默认参数是3 当MAX_ENTRIES到达最大条数是,实际比例按照当前值相除进行淘汰缓存
            # 'PASSWORD': '',  # 密码
            "CONNECTION_POOL_KWARGS": {"max_connections": 10} # 设置连接池
        }
    }
}
  
# 7.路由ROOT_URLCONF
ROOT_URLCONF 是父级路由
ROOT_URLCONF = 'mydjango.urls'  # 当用户访问路由时会根据这个路径下寻找路由,并且匹配到第一个路由位置进行返回

# 8.app注册列表
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

# 创建app
python manage.py startapp app名称
新添加的app需要在settings.py 中的 INSTALLED_APPS配置中添加当前的新app

# 数据库迁移命令
python manage.py makemigrations 指定那个app # 检查模型文件的修改如果修改就会在migrations文件下创建一个py问津进行记录(如果带有app那么创建的py文件就会存在这个app下面migrations)
python manage.py migrate # 选中全部么有执行过迁移的,应用在数据库中(dajngo会在数据库中创建一张特殊的表django-migrations来跟踪那些迁移)

# 创建超级管理员
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 # sqlite报错 mysql需要登录

# 清除数据库数据
python manage.py flush # 保留在makemigrations之前

# 进入交互模式
python manage.py shell

# 发送邮件查看邮箱设置是否正确
1.需要配置邮箱的相应内容
EMAIL_HOST = 'smtp.qq.com'  # SMTP地址
EMAIL_HOST_USER = '565151759@qq.com'  # 发送邮件的邮箱
EMAIL_HOST_PASSWORD = '*******'  # 授权码
EMAIL_USE_TLS = True  # 与SMTP服务器通信时,是否启动TLS链接(安全链接)默认false
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):
    视图接受的参数必须时路由有名分组的名称
    # 在url中的命名分组需要与视图接受的参数名相同,否则出现Type错误
    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"))# 会根据全局路由去找到下面的局部urls的路由

自定义转换器设置

1.创建类

class A:
	regex = '[0-9]{2}' # 设置的规则(自定义转换器)
    
    # to_python 与 to_url是固定写法()
    def to_python(self, value):
        # 用来处理匹配的字符串转换为传递到函数的类型。如果没有转换为给定的值,它应该会引发 ValueError。
        return int(value)

    def to_url(self, value): #反向解析时使用
        # 方法,它将处理 Python 类型转换为字符串以用于 URL 中。如果不能转换给定的值,它应该引发 ValueError。
        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 # 导入模块reverse
#主要配合反向解析进行使用

def index(request):
    # 如果url中存在动态参数那么需要args进行传递
    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 # 导入模块reverse
url = reverse('app01:home',args=(18,)) # 解析app01的home路径并传入值

路由补充内容

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)
#执行  HttpResponse 将返回一个固定的内容(可以是字符串,也可以是html或者其他)。
  
    return HttpResponse('字符串')


2.render,作用和返回值

def A1(request):
# 视图函数需要接受最少一个返回值request
render只用两种形式
1. render(request,'模板.html') # 没有渲染的参数
2. render(request,'模板.html',{"xx":xx}) # 需要渲染的参数
   
    return render(request, "html页面")


3.redirect重定向

def A2(request):
# 当执行这个方法时,路由找到A2视图函数执行,当执行到 
# return redirect('/路径/')时,会找到对应的路径,执行路径内的视图函数,并且返回html页面
    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
# 这个方法不需要进行手动的转为json格式,使用JsonResponse自己会转换json格式,按照json格式去解
def index(request):
    
    return JsonResponse(xx) 

# 无法序列一个列表
xx = [{"xx":x,"zz":x},{"xx":x,"zz":x},{"xx":x,"zz":x}]
JsonResponse(xx,safe=False) # 需要将参数safe设置为false



render,redirect # 这个两个方法最终都是将 返回的内容转为字符串形式,给浏览器进行渲染执行底层机制HttpResponse一样

请求方法

视图:
def A1(request):
# 每一个视图函数都需要接受路由返回的request值
# request:存放着浏览器的全部请求和数值。

	return render(request, "html页面")

1.获取请求方式
print(request.method)
# 返回 post请求,get请求

2.获取post请求的属性
print(request.POST)
# 获取全部的 post 请求的属性内容
# post请求的内容属于保密的,都在请求体存放
# 只有json格式的数据

3.获取get请求的属性
print(request.GET)
# 获取全部的 get请求的属性内容
# get 请求,是在url中存放的 
# 存放形式: url?键&值 / url?变量=值

4.获取 post请求的值/get请求值
# 因为时 字典形式 可以利用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 # 获取当前请求的url
request.path # 获取当前路径   /home/timer/
request.get_full_path() # 获取路径带参数 /home/timer/?a=1&a=2
request.get_full_path_info() # 获取路径带参数 /home/timer/?a=1&a=2
request.get_raw_uri() # 获取完整路径  http://127.0.0.1:8000/home/timer/?a=1&a=2
request.get_host() # 获取网址+端口   127.0.0.1:8000
request.get_port() # 获取端口  8000

6.获取请求头
request.META  # 获取请求头的信息
request.META.get('键') # 获取值

7.body
request.body # urlencoded /json 都能取到值

其他方法-APPEND_SLASH

paht('index',views.index)  # 路径中不加/,搜索是也不能加/,加/报错
127.0.0.1/index

paht('index/',views.index) # 路径中加/,搜索时不加/,django会自动补全
127.0.0.1/index
# 补全
127.0.0.1/index/  # 这是django中自带的机制,当路径中带/,你没加/,系统就会重定向原地址 加上/

# 需要在配置文件中设置这个参数
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')] # django通过这个变量找到 templates文件夹\BASE_DIR项目的跟目录
        ,
        '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) # 返回给浏览器,重定向和reder本质上调用了 HttpResponse
    
2.封装为    
render(request,"模板。html",{"参数名":参数}) # 参数名是什么,模板中接受的参数名就是什么,关键字参数

模板语法

变量:{{  }}  渲染变量使用的
        1. 深度查询  句点符
        2. 过滤器 语法:{变量名val|内置过滤器:参数}

标签:{% %}  渲染标签

变量渲染-深度查询

locals() # 参数是将视图函数中的全部变量传入 不推荐使用

使用方式:
return render(request,"index.html",locals()) # 将视图函数中的全部变量传入到模板中

# 1.句点符的使用:

视图函数


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}}  # 33

2.获取第一个值
{{ name|length }} # 11

3.如果没有值 提示是default后面的内容,如果有值就显示变量自己的值
name = []
{{ book2|default:"没有值" }}  # 提示没有值,如果name中有值就会显示值

4.将字母变为大小写
{{ name|upper }}
{{ name|lower }}

5.设置时间格式
time = datetime.datetime.now()
{{ time|date:'Y-m-d '}} 自己设置的格式

6.显示文件大小
name = 1024
{{ name|filesizeformat }} # 显示1kb

7.截断功能
截断字符{{ content|truncatechars:10}}  # 显示设置后的字符数量,其他的都显示为...
截断单词{{ content|truncatewords:10}}

8.将字符串的标签在html页面显示正常
link =  link = "<a href='http://www.baidu.com'>百度</a>"
# djano会将标签转换为安全的内容标签,如果想展示这个安全标签就是用safe
{{ link|safe }} # 就会显示一个a标签,如果不使用safe,就显示安全的字符(django自带的安全机制)

9.加法过滤器
name = 10
{{name|add:100}} # 110

10.切片
按照范围进行切片 与python 数据切面语法一至
<p>{{ n5| slice:"2:-1" }}</p>


其他语法:

1.变量名字较长,取别名
#视图函数的局部变量
namesadaadzxcasdasdasda2 = 1

HTML页面:

{% with namesadaadzxcasdasdasda2 as n %}

# 将长度的变量名去个别名 n

2.{% csrf_token %}
# 实在发送post请求使用的,特殊方法,Django内部有将产生一个随机的键值,传入到服务器中,进行识别。

功能
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 # app01程序
		-templatetags # 自定义过滤器文件
			-__init__.py
			-poll.py # 存放自定义过滤器文件
			
2.需要检查当前app程序是否注册到框架INSTALLED_APPS中(如果没有请注册,不然找不到)

自定义过滤器

3.poll.py(定义)

from django import template  # 1.导入过滤器方法 /标签对象

register = template.Library()  # 2.这个是过滤器的注册对象

#### 定义过滤器 #####
@register.filter  # 3.没有设置名字,默认使用函数的名字作为过滤器的名字
def mobile_fim(content):  # 这个参数接受在模板中使用这个语法的变量
    print(content)
    return content + '666'


@register.filter(name='mobile_fim')  # 3.设置名字
def mobile_fim(content):
     print(content)
     return content + '666'

多个参数的情况下
def a(第一个参数,第二个参数)
# 第一个特点,接受两个参数
# 另一个特点,可以作为模板中 if 的条件

4.在html页面使用

{% load poll %} # 找到对应自定义模板语法的py文件
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
# 使用自定义过滤器
<div>{{ xxxx|mobile_fim }}</div> # {第一个参数|自定义filter函数名称:第2个参数}

</body>
</html>

自定义标签

3.poll.py(定义)

from django import template  # 1.导入过滤器方法/标签对象

register = template.Library()  # 2.这个是过滤器的注册对象


@register.simple_tag
#固定写法,写上之后就是自定义的标签
def  multi_tag(x,y):   # 函数名,可以设置多个参数
    return x * y # 函数体


在html页面使用

{% load poll %} # 找到对应自定义模板语法的py文件
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div>{% multi_tag 1 2 %}</div> # 使用自定义标签 对自定义标签传入2个值,获得的x+y

</body>
</html>

模板语法-标签

标签的语法:{% xxx %}

1. if else的在模板中的使用
# 必须要加上空格,结尾必须要有endif
{% if 逻辑 %}
	代码
{% endif %} 
# 可以是用and  or  != == > <  is

# 案例
{% if age > 18 and age < 100 %}
    <p>成年人电影</p>
{% elif  age < 18 and age > 1%}
    <p>未成年的电影</p>
{% else %}
    <p>看尼玛</p>
{% endif %}

# 应用场景 :登录显示用户名称等等使用 

2.for循环在模板中的使用

{{ forloop.first }} # 当循环第一个的时候是 True,其他的都是False
{{ forloop.counter }} # 序列号,从1开始计算循环次数
{{ forloop.counter0 }} # 序列号,从0开始计算循环次数

使用方式
{% 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> # 都可以使用for循环进行设置
{% 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}}

# base.html  父级页面
# 其他.html   需要继承父级页面

注意事项:
1. {% extends 'base.htm' %} # 子heml 需要在第一行
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): 
        # 按照需求,不需要返回值 默认返回none,如果返回其他的值,下面的流程就不在进行执行
        # 多应用场景流程
        print('中间件1')
    	# 存在返回值 就会在在执行下面的全部函数,依次按照中间件的顺序将返回值返回

    def process_view(self,request,callback,callback_args,callback_kwargs):
        # callback 视图函数
        # callback_args,callback_kwargs 视图函数的参数
        # 存在返回值,就不在执行视图函数,同时可以获取callback 视图函数 对象在返回
        print('中间件2')

    def process_exception(self,request,exception):
        # 代码正常时,不会执行
        # 视图函数出错,才会执行 捕获依次
        print('视图函数出错')
        # 存在返回值,就不会执行其他中间件的 process_exception 如果没有返回值就执行其他的process_exception 直到有返回值

    def process_response(self,request,response):
        # 请求回去 视图函数返回的执行传response 多
        print('中间件4')
        return response  
        # 当访问完视图函数时 必须返回的值 不加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'] # 非法ip列表
class CustomerMiddleware(MiddlewareMixin):

    def process_request(self,request):  # 请求来了 进入执行 传request 多
        ip = request.META.get('REMOTE_ADDR')
        if ip in IP_LIST:
            # return HttpResponse('非法ip') # 响应页面数据
            return JsonResponse({'code':200,'msg':'','error':'非法ip'}) # 响应js数据
        print('中间件1')
   

    def process_response(self,request,response):
        # 在响应体号携带token
        # response 响应体,response.content响应体内容
        uuid_token = uuid.uuid4()
        response['Token'] = f'token {uuid_token}'    
        return response

form组件使用

内置字段

Django 的内置字段

Field
        required=True#请求不能为空
        widget=None#HTML插件
        label=None#用于生成lable标签或显示内容
        initial=None#初始值
        help_text=''#帮助信息(在标签旁边显示)
        error_messages=None#(错误信息{‘required’:'不能为空',‘invalid’:‘格式错误’})
        show_hidden_initial=False#是否在当前插件后面加一个隐藏的并且有默认值的插件(可用于检验两次输入是否一致)
        validators=()#自定义验证规则
        localize=False#是否支持本地化
        disabled=False#是否可以编辑
        label_suffix=None#label内容后缀

 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,        # HTML中value的值对应的字段
    limit_choices_to=None      # ModelForm中对queryset二次筛选
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 # 导入 forms 组件
from django.forms import widgets # 导入widgets 组件
# 作用可以将标签修改和添加标签属性值
from django.core.exceptions import NON_FIELD_ERRORS,ValidationError # forms添加报错模块


# 创建一个效验的类 继承forms.Form类

class A(forms.Form):
# 创建字段和规则 1 用户名
	name = forms.CharField(min_length=4,)
# 创建字段和规则 2 密码
	pwd = forms.CharField(min_length=4,)
# 创建字段和规则 3 邮箱 
    email = forms.EmailField()
# 创建字段和规则 4 电话
	tel = forms.CharField()
 
# 参数

参数:min_length= # 长度最少多位数 

# 使用后可以在HTML页面不用创建标签,直接渲染
# 根据forms的特性直接创建
widgets 模块内的参数:
widget=widgets.TextInput(attrs={标签的属性}) # 明文
widget=widgets.PasswordInput(attrs={标签的属性}) # 匿文 不显示

起名字参数:label= # 给标签起名字
将英文错误转换中文参数:固定格式
error_messages={"required": "字段不能为空", 'invalid': "格式错误"}

# 将错误信息变为中文:error_messages={"required":"自定义设置"} 字段为空 固定格式
# 将错误信息变为中文:error_messages={'invalid':"自定义设置"} 格式错误
# widget=widgets.渲染什么标签(添加什么属性参数)
# 设置label 值 可以在将实例化的form对象 传到html时,对应的label 值呈现在页面中







# 视图函数
# 导入forms类
如何是用认证forms组件
def reg(request):
    # 获取用户输入的信息
    if request.method == 'POST':
        
        forms对象 = forms认证类(request.POST) #绑定数据的表单对象
        forms对象.is_valid() #进行认证  返回True/False
        
        if forms对象.is_valid(): #认证成功 返回True
            
            forms对象.cleaned_data # 获取认证成功的全部值
            
         else:
            forms对象.cleaned_data # 获取认证成功的值 返回正常字典
            forms对象.errors # 获取认证错误的值 返回字典类型
            # 返回的结果属于字典类型,但是比字典类型多一些功能
            forms对象.errors.get('pwd')[0] # 取出返回的错误类型
         	#errors 存放着全部的错误信息
            
            
        return render(request, 'reg.html', locals()) #将结果返回给原HTML页面
    	# 作用:返回的是绑定HTML页面的值根据内部的判断,传入值的,在经过post判断 后显示错误信息 并且保留输入的值。
    
    forms对象 = UserForm() # 未绑定数据的表单对象
    
    return render(request, 'reg.html', locals()) # 返回一个未绑定数据的HTML页面
	#返回的是一个空的不带任何值的HTML页面

    
# HTML页面:

<div class="container">
    <div class="row">
        <div class="col-md-6 col-lg-offset-3"></div>
            <form action="" method="post" novalidate>
            # 防止浏览器进行提示novalidate
            {% csrf_token %}
            #显示的错误信息都是英文的
                
            <p>用户名{{ form.name }}</p> <span>{{ form.name.errors.0 }}</span> 
            # 错误显示都是英文的格式,在formr认证 规则中加入error_messages={"required": "字段不能为空", 'invalid': "格式错误"},可以将英文转换为中文
            <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>

# 未绑定数据之前 显示页面为空
# 绑定数据之后,就会将 用户输入的内容报错,并且将获取错误信息           #获取错误信息  form对象.字段.errors.0 
                

HTML渲染

# 方式1  使用方式1 标签不固定,可以进行修改或者添加 格式
<form action="" method="post">
     {% csrf_token %}
    {% for foo in form对象 %}
        <label for="">{{ foo.label }}</label>需要在创建标签接受错误值
        <p>{{ foo }}</p>
    {% endfor %}
    <input type="submit">
</form>

# 使用方式1 需要在 forms 认证类中设置参数 label 和设置标签的属性 明文和匿文

# forms认证 类设置
from django import forms # 导入 forms 组件
from django.forms import widgets # 导入widgets 组件
# 作用可以将标签修改和添加标签属性值

class A(forms.Form):
	name = forms.CharField(min_length=4, label='用户名',widget=widgets.TextInput(attrs={"class":"form-control"})) # widgets.TextInput 设置为明文 type="text" 明文 attrs= 添加class属性
    
    pwd = forms.CharField(min_length=4, label='密码',widget=widgets.PasswordInput(attrs={"class": "form-control"}),) #设置为 widgets.PasswordInput 设置为匿文 type="password" 匿文 attrs= 添加class属性
	
    email = forms.EmailField(label='邮箱', error_messages={"required": "字段不能为空", 'invalid': "格式错误"},widget = widgets.TextInput(attrs={"class": "form-control"})
              # widgets.TextInput 设置为明文 type="text" 明文 attrs= 添加class属性  error_messages=设置参数将英文错误转换为中文可读             
# 视图函数
def A(request):
                             
       if request.method == 'POST':
              # 创建forms 对象 绑定数据的表单对象               
              form对象 = A(request.POST) # 将内容传入 进行认证
              form对象.is_valid() #执行进行匹配
              if form对象.is_valid(): # 获取返回值返回True/False form对象.cleaned_data # True打印全部符合的值
              else:              
                     form对象.cleaned_data # 打印部分符合      
                     form对象.errors # 打印不符合的        
                             
              return render(request, 'reg.html', locals())
              # 将内容全部返回,并且将不符合的错误信息显示form = UserForm() #未绑定数据的表单对象
      return render(request, 'reg.html', locals())                       

                             
                             
# 方式2
#渲染后标签固定了,不灵活 适用于简单的测试                             
<form action="" method="post">
     {% csrf_token %}
       {{ form.as_p }}
    <input type="submit">
</form>                             

设置钩子

from django import forms # forms 类
from django.core.exceptions import NON_FIELD_ERRORS,ValidationError # 报错类
from app.models import * # 导入数据库

# 创建一个forms认证类

class A(forms.Form):

	pass 认证方法
	name =..... #用户名
    pwd = ..... # 密码
    r_pwd = .... # 确认密码
	
# 设置局部钩子
    # 局部钩子 先走上面的设置校验字段,成功后走自己设置的局部钩子
    # 局部钩子时用数据库中取值 和效验成功的值进行对比
	def clean_name(self):  # 名称clean_跟上字段变量名 固定写法
        
        val = self.cleaned_data.get("name")  
        #字典形式存在 取到 用户输入的 用户名的值 name值
        # cleaned_data存放着认证通过的全部字段 字典类型
         ret = 数据名.objects.filter(name=val)
        # 在数据中进行比对,存在返回val有值,不存在就没有值
        
         if not ret:  # 没有值就 将数据返回
            return val 	
        
         else: # 存在值就 触发提示错误
            raise ValidationError("用户已经存在")
# 先执行认证方法,当全部字段都成功后存放在   cleaned_data 中通过get取值,在数据库中进行对比执行,如果值不存在,可以创建name,如果值存在返回 错误内容。
'''
执行流程:
先将浏览器的发送请求
视图函数判断是否时post请求
执行forms认证组件,当认证全部通过
执行局部钩子,进行确认。
'''

# 全局钩子 优先执行全局钩子
	  def clean(self): 
# 由于继承了forms 的方法 ,当自己类中定义了这个方法,就优先使用这个方法
        pwd = self.cleaned_data.get('pwd')     # 取出值
        r_pwd = self.cleaned_data.get('r_pwd') # 取出值
		# 取不出来就是None
        if pwd and r_pwd: # 当两个值都存在

            if pwd == r_pwd: #对比
                return self.changed_data 
            # 如果两次正确就就将正确的changed_data 字典返回

            else:
                raise  ValidationError("两次密码不一致") 
                # 不一样就捕获错误  错误存放在 __all__的列表中
        else:

            return  self.changed_data # 如果其中一个值存在None
        # 就将正确的返回

        
        
        
# 视图函数取全局钩子

def A(request):
    
    ....
    form对象 = 认证组件类(request.POST)
    
    #全局钩子错误存放在__all__的列表中
    print("全局钩子错误:",form.errors.get("__all__")[0]) #取
     error全局钩子错误对象 = form.errors.get("__all__")  
 # 全局的错误存放在__all__的列表中  全部正确的情况下,加入索引报错


# HTML取错误信息
<span>{{ form对象.name.errors.0 }}</span> # 局部钩子取错误信息
<span>{{ error全局钩子错误对象.0 }}</span> # 取全局钩子错误信息
在配置文件中修改LANGUAGE_CODE = 'zh-hans' #错误信息就是中文

# 补充
自定义局部钩子函数
clean_表中的某些字段这个字段开头,
错误信息就会显示到 生成的input标签后面
局部和全局的区别:
clean_字段名A  局部:只有后面的字段名A才会显示错误信息
clean  全局:整个字段都会显示错误信息

数据库更新

# 重置密码的form表单
class UserInfoPassWordModelForm(StarkForm):
    password = forms.CharField(label="密码")
    confirm_password = forms.CharField(label="确认密码")

    # 功能1:用于判断两次密码是否一样
    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

    # 功能2:将密码设置进行加密/在数据库中密码显示为匿文/密码加密
    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()  # 实例化form类
            # 与change页面相同,使用一个html页面
            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})  # 报错

ModelForm的使用方法

from django import forms  # 使用forms组件


# 创建类
class UserModelForm(forms.ModelForm):
  
    confirm_password = forms.CharField(label='确认密码')  # 添加一个确认密码字段

    class Meta:
        model = models.类名  # 使用的表结构
        # fields = '__all__' # 显示全部字段
        fields = ['name', 'email', 'password', 'confirm_password'] # 设置部分显示字段
        widget = { # 插件功能
            "字段名":froms.TextInput(attrs={"class": "form-control"})
        }

        
    # 自定义钩子函数确认密码 clean_ 这个字段开头,后面输入指定显示字段
    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
		# 错误信息就会显示到  confirm_password 字段后面


批量ModelForm生成的字段添加class属性/使用插件,重新写init方法
 # 批量给ModelForm字段添加样式
    def __init__(self, *args, **kwargs):
        # 使用supet找父类,初始化方法
        super(生成的form组类, self).__init__(*args, **kwargs)
        # 找到modelform生成的个字段,进行循环 name就是字符串 field生成就是类似CharField的对象
        for name, field in self.fields.items():
            # field中 插件 中的 属性 添加一个样式,使用插件中的属性,添加样式
            field.widget.attrs['class'] = 'form-control'

在配置文件中修改LANGUAGE_CODE = 'zh-hans' 错误信息就是中文




在视图中是用modelform

1.选着验证的数据/数据的提交是post请求/增加数据
# request.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(必须是数据库对象)})  

Form表单的批量使用案例

from django import forms
from rbac import models
# 1.创建modelform类
class RoleModelForm(forms.ModelForm):
    """
    角色操作表单
    """
    class Meta:
        model = models.Role
        fields = ["title", ]
        widgets = {
            "title": forms.TextInput(attrs={"class": "form-control"})
        }

# 2.对数据进行保存/添加
def role_add(request):
 
    if request.method == "GET": # 正常访问get请求
        form = RoleModelForm()
        #  返回一个空的form表单,进行添加
        return render(request, "rbac/change.html", {"form": form})
    
    #  post请求/对数据的保存
    form = RoleModelForm(data=request.POST) # 获取请求的数据:request.POST
    if form.is_valid(): # 验证
        form.save() # 保存
        return redirect(reverse("rbac:role_list")) # 反向解析,跳转显示页面
    return render(request, "rbac/change.html", {"form": form}) 
# form对象中存放着错误信息.eorre


# 3.对数据的编辑
def role_edit(request, pk): # 获取数据的主键id:pk
    role_obj = models.Role.objects.filter(pk=pk).first() # 拿到数据的对象
    if not role_obj: # 判断
        return HttpResponse("要修改的数据不存在,请重新选择")
    
    if request.method == "GET": # get请求没有提交数据
        form = RoleModelForm(instance=role_obj) # 在页面显示数据库中的信息
        return render(request, "rbac/change.html", {"form": form})
   
# post请求/将post请求中的数据进行效验获取对象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}) # 返回错误信息

django中对批量大数据进行修改方式formset方法

modelformset_factory 方法

# 导入
from django.forms import modelformset_factory  # 大量数据需要修改,在页面生成修改数据页面,并直接修改保存

# 使用步骤
1.创建一个modelform类
from django import forms # 使用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)    # 使用方法 modelformset_factory,参数1数据库 参数2创建的modelform类 参数3 设置行数
    formset = 数据库_fromset(queryset=数据的数据对象) # 作用为了显示数据库的原数据
    
    # 判断post方法
    if request.method == "POST":
        # 目的进行效验,将用户在前端输入的数据进行效验
        formset = 数据库_fromset(queryset=数据的数据对象,data=request.POST)
    	if formset.is_valid() # 效验
        	 formset.save()  # 保存
            return render(request, "attendance.html", {"formset": formset}) # post展示页面和效验失败返回页面
    return render(request, "html页面", {"formset": formset}) # get请求


# hrml页面
模板添加{{ formset.management_form }},固定写法
{{ field.id }}为标识数据,否则没法保存修改
instance为生成不可修改项,需要生成一个隐藏的原始数据,没法提交

案例
{% block content %}
    <div class="luffy-container">
        <form action="" method="post">
            {% csrf_token %}
            {#使用formset对象需要的固定格式#}
            {{ 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>
                        {#需要将id设置/如果不设置会报错#}
                        {{ form.id }}
                        {#form.instance.student/使用form对象的instance方法显示隐藏的字段#}
                        <td>{{ form.instance.student }}</td>
                        {#显示当前字段的错误#}
                        <td>{{ form.record }} <span>{{ form.record.errors.0 }}</span></td>
                    </tr>
                {% endfor %}
                </tbody>
            </table>
        </form>
    </div>
{% endblock %}

django中forms/modelform中在保存数据之前的操作

   例子使用stark组件:
   def save(self,request, form, is_update=False):
        # 1 获取当前登录用户的id
        current_user_id = self.request.session["user_info"]["id"]  # 从session中取
        # 2 将consultant默认设置为当前登录的用户id
        form.instiance.consultant_id = current_user_id
        # 3.进行保存
        form.save()
  
当时用save方法进行对前端post请求进行保存时,在保存之前的操作
 form(定制的modelform类/form类的对象).instiance.字段id = 值 在save之前操作
    

关于modelform表单

在表类中中的属性 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() # 设置cookie 需要借助HttpResponse对象
    # 1.设置cookie,需要將HttpResponse返回
    ret.set_cookie('url','www.baidu.com')
    # 2.设置带时间的cookie
    ret.set_cookie('url','www.baidu.com',max_age=5)
    # 取cookie 只针对1 or 2 的方式
    print(request.COOKIES.get('url'))
    
    
    # 3.设置加密cookie
    ret.set_signed_cookie('url2','www.baidu.com',salt='xxxxxx')
    # 取加密cookie
    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'

# 在对数据库进行迁移时,会在数据库中创建一个关于session的表
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' # 设置存储在浏览器上cooick的key
SECRET_KEY = 随机字符串(默认) # 每次创建项目自带的
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 # 默认的失效时间 2周
SESSION_COOKIE_AT_BROWSER_CLOSE =False # 是否关闭浏览器使session过期
SESSION_COOKIE_EVERY_REQUEST = False # 是否每次请求保存session,默认之后才保存

使用

from django.http import JsonResponse


def home(request):
    # 设置session
    request.session['url'] = 'www.baidu.com'
    # 读取session
    print(request.session.get('url'))
    # 删除session
    request.session.flush() # 删除全部,数据库表,浏览器记录
    # 删除指定的session
    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 # django 自带的模型类用户模型类,可以继承并且扩展
User模型类对应着是 django自带的数据库表 auth_user表

2.添加普通用户
User.objects.creat(username='用户名',password='密码') # 密码是明文的
User.objects.creat_user(username='用户名',password='密码') # 密码是匿文的

3.添加超级用户
User.objects.creat_superuser (username='用户名',password='密码',email='邮箱') 
# 创建后,auth_user表中的is_superuser字段就变为1,超级用户

4.校验密码方法
set_password('密码')
check_password('密码') # 如果是真实的用户密码,返回Ture 可以校验密码


5.怎么基于django自带的用户表和auth模块进行用户验证 # 用户认证
根据auto_user表和auth模块进行认证的
无法直接用对user表进行操作,因为passwodr字段是密文的,用户输入的是明文的,无法比对
from django.contrib import auth # 认证组件类
name = auth.authenticate(username=name,password=pwd) # 认证方法
# 将输入的密码变为密文,认证成功后返回True 失败返回None

6.用户认证组件怎么进行session的存储

auth.login(request, name)   # 参数1 请求对象  参数2 用户数据库model对象
# 将请求对象和认证对象:执行了session的操作,将用户对象id存储到session表中
# 核心就认证成功后,将一个键值对存放在session_data表中 存放的值{ures_id:user.id} # 默认就是认对象+_id


7.怎判断用户是否登录成功 获取session值 # 获取session值
# 方式1 使用session进行取值,取到的是,认证组件表中的用户id 值
request.session.get('_auth_user_id') 

# 方式2语法:
request.user  # 如果登录这个是存在值的,没有登陆就是none
#  request.user 这个是当前登录对象
执行流程
    1. AuthenticationMiddleware中间件方法中process_request方法中/在中间件使用的,所以全部的视图都可以是使用
        1.request.session.get('user_id') # 取当前登录对象session中存的id值
        2.from django.contrib.auth.models import User
        在这表中 进行user = User.objects.get(pk=user_id) # 对比
                if user:
                    request.user = user # 将取到对象进行赋值
                else:
                    request.user = 赋值一个匿名对象 # 全部的值都等于0值
    2.结论:在任何一个视图函数中都可以使用request.uers
        如果之前登录成功过,执行了auth.login() 那么 request.user=当前登录的用户对象
        如果没有登录过  就是没有执行auth.login() 那么 request.user=匿名用户对象
    
    3.如果登录成功就可以取到对应的id值和name值属性
    request.user.username 
    reuqest.user.id

8.aoth组件的退出 # 用户的退出
request.logout() # 相当执行了 request.session.flush() 将session表的记录进行删除

也可以使用
del request.session.get('_auth_user_id')
# 方法:
# 1.当前登录对象
request.用户名 # 登录就是用户名,没有登录时None
# 2.django自带的表进行比对
# 验证成功返回user对象,否则返回none
用户对象 = auth.authenticate(username=用户名,password=密码)
# 3.进行session,验证
auth.login(request,用户对象) # 参数1.request.user 当前登录对象 参数2.用户对象
# 4.用户注销
auth.logout(request) # 将用户注销
# 5.没有登录 下面的代码不会显示任何信息
request.用户名.username #  如果没有登录 就是一个空的字符串
request.用户名.id #  用户di 没有none
request.用户名.is_anonymous #  是否是匿名 返回 True
request.用户名 #  AnonymousUser 代表匿名用户对象
request.user.is_authenticated()  # 是否时登录状态

这个内部组件更为安全,当有新用户登录时 就会将django的自带的 session中的数据全部更新,而不是更新一部分
案例:
from django.contrib import auth # 用户认证模块
from django.contrib.auth.models import User # 取到用户表(django自带的表)

# 验证
def A(request):
        if request.method == 'POST':
        user = request.POST.get('user')
        pwd = request.POST.get('pwd')
        print(user,pwd)
# 用户认证组件
# 生成用户对象
        # 验证成功返回user对象,否则返回none
        # 传入参数 1.用户的用户名  2.用户的密码
        user_name = auth.authenticate(username=user,password=pwd)
# 给django表中这个user表接口 也无法验证,因为password经过加密存放的。
        if user_name: # 判断
            auth.login(request,user_name)
            # 参数1.request.user 当前登录对象 参数2.用户对象
            # 通过session   request.user 如果没有就去到时一个匿名对象
            # 永远带着登录对象的 session 字典内容
            return redirect('显示页面') # 登录成功页面
        
         return render(request,'/路径/') # 登录页面
        
        
# 获取用户登录组件
def B(request):
    
    # 判断是否 存在request.user值
    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')
            # 将获取的值进行存放 create_user 使用方法,与正常创建的方法不同
            # 因为django 自带的表中 密码是加密的
            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


# Create your views here.

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)  # 将请求对象和认证对象:执行了session的操作,将用户对象id存储到session表中
            # 核心就认证成功后,将一个键值对存放在session_data表中 存放的值{ures_id:user.id} # 默认就是认对象+_id
            return redirect('/home/index/')
        else:
            return redirect('/home/login/')


def index(request):
    # 怎么取出来,认证组件的session中的cookie值


    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)
    # 直接删除session表中的内容  相当与 request.session.flush()
    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') 
    # 接受Ajax中data字典n1
    n2 = request.POST.get('n2')
    # 接受Ajax中data字典n2
    n3 = int(n1)+int(n2) # 获取n1n2获得n3
    return HttpResponse(n3)
	#返回的值由Ajax中的data参数获取
    
    
# HTML页面 
# 标签
<input type="text" id="num1"> + 
<input type="text" id="num2"> =
<input type="text" id="num3"> 
<button class="cal">结果</button>

效果:
当点击‘结果按钮’将num1+num2的值计算并赋值给num3
# 给button按钮绑定事件
$('.cal').click(function () {
    # 使用ajax
     $.ajax({
        url:'/cal/',
        type: 'post', # post请求
        data: {
            "n1":$('#num1').val(),  
            #获取键值 = form表单中的name值
            "n2":$('#num2').val(),  
            #获取键值
        },
         # 在点击按钮时,将data字典的数据返回给视图函数
        success:function (data) {  
#视图的返回结果 由success的回调函数接受,data接受
            $(' #num3').val(data)
            # 将接受的值赋值给id=num3的标签
        }
    
})
#只会对标签的区域进行刷新,不会对整个页面进行刷新

实现用户验证功能


# 视图函数 带有数据库
import json # 导入json模块
from django.shortcuts import HttpResponse # 导入模块

def login(request):
    user = request.POST.get('user') # 取提交的用户名
     pwd = request.POST.get('pwd') # 取提交的密码
     # 让数据库中的name和pwd进行校验
     user = User.objects.filter(user=user,pwd=pwd).first() 
     # 数据库进行筛选,如果两个值都筛选正确,models对象就会有值
    # 取user models 对象
    # 进行判断:
     res = {"user":None,"msg":None}
    # 设置一个字典,存放用户名
     if user:
    # 判断筛选后存在值,就将用户名赋值给字典的user键
       res["user"] = user.name  
    # 登录成功后 显示当前用户的用户名,从数据库中取
     else: 
    # 不存在就赋值 msg 错误信息
       res["msg"] = "name or password 错误"
       
      return HttpResponse(json.dumps(res))
'''
执行流程:
1.接受前段ajax的用户名和密码,
2.在数据库中进行核对
3.设置一个字典对应用户/密码 起始值None
4.登录成功,将用户放到字典中/不成功将错误信息放到字典中
5.将数通过json,返回给ajax的回调函数data中
'''   
# HTML页面

<form>
用户名 <input type="text" id="user">
密码 <input type='password' id="pwd">
<input type="button" value="提交" class="login_btn">
#    点击标签使用button,不带任何事件,实现Ajax
 <span class="span"> </span> # 设置提示标签   
</form>

# ajax代码
$('.login_btn').click(function () {
    #绑定标签
	$.ajax({
        url:'/login/', # 返回的url
        type:'post', # 请求格式
        data:{ 
            'user':$("#user").val(),
            'pwd':$('#pwd').val(),
        },
        # 获取前段输入的用户名和密码
        
       success:function (data) {
# 接受返回值:data,数据已经被python json过需要进行反序列化
       var json = JSON.parse(data) 
#将python传入得 data 转换为object{}类型,反序列化
           if(json.user) { 
# 获取反序列化的 值,进行判断,如存在跳转百度
               location.href='http://www.baidu.com' 
           }
           else {
            $('.span').html(json.msg) 
# 如果值不存在 再登录后面 将登录失败的信息提示,赋值给提示标签前段进行提示
      	}
      	}
    })
})
    

Ajax上传

# 视图函数
ef A(request):
    # 功能4 文件上传
    if request.method =='POST':
        #上传的文件全部存入 fules中
        print(request.FILES)  # 只能基于fules取到上传的文件

     return render(request,'html页面') # 将页面返回给ajax回调函数中data


# HTML页面
# 当利用上传功能时
# 传图片 需要将form表单 设置属性 才能将上传的图片对象  enctype="multipart/form-data"

<form action="" method="post" enctype="multipart/form-data"> 

# 设置post请求,上传文件的属性

用户名<input type="text" id="user">
头像<input type="file" id="avatar">

# 当需要设置 上传文件是 input标签需要设置  type="file"

<input type="button" class="btn" value="Ajax">
# type="button" 按钮不带任何事件
</form>


# ajax代码
$('.btn').click(function () { # 给标签绑定事件
    
# 利用ajax上传文件的格式必须设置 构建一个formdata对象
    var formData=new FormData() # 构造forndata对象
    # 将昵称和头像添加到forndata对象中
    formData.append('user',$('#user').val());
    formData.append('avatar',$('#avatar')[0].files[0]);
    
     $.ajax({
         url:'', # 不带路径,原路返回
         type:'post',#设置请求方式
# 使用ajax传入文件是,必须要加入两个参数  
         contentType:false,  # 参数1
         processData:false, # 参数2
         data:formData, # 将值传入给视图函数 request
		 success:function (data) {
                console.log(data) # 接受视图函数返回的值
            }
        })
    })

分页器组件

from django.core.paginator import Paginator, EmptyPage  # 导入分页器

# 视图函数
def A(request):
    
    # 1.从数据空中取到数据库对象,需要进行排序,不然会出现无序的情况
    model_obj = modelclass.object.all().order_by('id')
    
    分页器功能
    # 1.获取分页对象 Paginator(数据库对象,每页展示的个数 )
    page_obj = Paginator(model_obj, 2)
    
    
    page_obj.count # 查看总数据个数
    page_obj.num_pages  # 用页码个数
    page_obj.page_range # 结果就和range(总页码)一样 产看页码的列数 顾头不顾尾 比总页码多一
    显示对象 = page_obj.page(整数值) # 显示对象,根据整数值,呈现页面的内容
   	显示对象.has_next()  # 显示对象判断是否有下一页 返回 True/False
    显示对象.has_previous() # 显示对象判断是否有下一页 返回 True/False
    显示对象.next_page_number() # 在当前显示页面的页码基础上 +1
    显示对象.previous_page_number() # 在当前显示页面的页码基础上 -1
	
# EmptyPage 方法     
    如果输入的页码超出或者小于最小页码,就会报错EmptyPage
    利用异常处理机制进行捕获,触发EmptyPage时,让内容显示第一页
    用来捕获异常
利用EmptyPage方法捕捉上一页错误信息
from django.core.paginator import EmptyPage 
# 使用try的原因,当出现错误显示页码不存在,自动捕捉错误,返回第一页
try:
    page01 = pag.page(页码)
 except EmptyPage:
        page01 = pag.page(1) 
# 当页码小于1时,报错被捕捉跳转到第
分页器案例

from django.core.paginator import Paginator, EmptyPage  # 导入分页器

# 视图函数

class A(request):
    
    # 1.读取数据库
    
    数据库对象 = 数据库.objects.all()
    
    # 2.设置分页器对象
     分页器对象 = Paginator(数据库对象, 显示页面数据条数)
    
    
    # 3.分页器显示方法,当页面码数过多,局部显示页码数
    
    page_range = None # 设置变量,页码呈现循环变量
    
    current_page_num = int(request.GET.get('page', 1)) # 获取当前页码 利用get请求,1代表,默认为第一个页面
# get请求 请求体在url内部 ?分割

	# 判断显示
    if 分页器对象.num_pagex> 11 : 
# 分页器 总页码数 大于11 时
		
    	if current_page_num-5>1:
# 如果获取当前页码 -5 小于1时
			page_range=range(1,12)
    
# 变量赋值 1-12的显示范围
		 elif current_page_num+5>分页器对象.num_pagex:
# 如果 当前页码+5 大于  当前总页码数
				page_range = range(分页器对象.num_pagex-10,分页器对象.num_pagex)
# 让变量赋值 总页数-10,总页数的范围
		
    	else:
            page_range = 页面对象.page_range  
# 如果不大于11 正常显示页面 的列数

	# 4.捕获异常
    
    try:
        #获取当前也
        current_page_num = int(request.GET.get('page', 1))
# get请求(当前页,默认为1) 进入页面 先显示第一页
		
        current_page = 页面对象.page(current_page_num)  # 显示对象
        
    except EmptyPage as a:
        # 如果 输入有误,或者页码不存在 跳转第一页
        current_page = 页面对象.page(1)
    
    
 return render(request, 'HTML页面', locals())            
    
# HTML代码    
    
<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 %}
# 根据页面显示对象获取上一页是否还有页码返回 True/False   
 	# 存在点击上一页,就在原有页码对象上数值-1 
<li><a href="?page= {{ 分页对象.previous_page_number }}" aria-label="Previous"><span aria-hidden="true">上一页</span>

{%else %}
	# 不存在 就无法点击  class="disabled" 样式
<li class="disabled"><a href="" aria-label="Previous"><span aria-hidden="true">上一页</span>


# 页码主要呈现的内容
        {% for item in  page_range %}
# current_paget 就是当前的页面数 如果页面数等于循环的item 就让按钮变颜色<li class="active"> 
            {% if  current_page_num == item %}
#?page={{ item }} 因为时get请求 ,就会取到 ?后面的全部值,按照字典形式取到    
                <li class="active"><a href="?page={{ item }}">{{ item }}</a></li>

# 根据页面的range 范围生成 a标签,每个表签都以一个对象的数值,当点击A标签是 触发get请求 将值返给视图函数,视图函数在根据数值,呈现页面
            {% else %}
# 如果不点击就不会显示颜色    
                <li><a href="?page={{ item }}">{{ item }}</a></li>
            {% endif %}
        {% endfor %}
        
        

        
# 下一页功能
{% if 分页对象.has_previous %}
# 存在 在原有基础上+1
<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)  # 每一页放10条

    try:
        num = int(request.GET.get('num', '1'))  # 获取get请求的数据 默认为1
        page01 = pag.page(num)  # 动态获取
        if num == 1:  # 当页码为1时,显示1-3 不显示后面的0 -1 就不会报错
            pag_list = range(1, 4)
        elif num == pag.num_pages:  # 当页码最大时50  48-51 的范围
            pag_list = range(num - 2, num + 1)  # 前取后不取 取三个值,所以数值的范围时循环的结果就是1 2 3
        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>
{#bug:当到最后一页和第一页时,也会显示比第一页小的页码,点击后会出现报错情况#}
        {% 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 # django的事务操作模块

try:
	with transaction.atomic():  # 要成功都成功,要失败都失败
    	代码1.....
        代码2.....
except Exception as e:
    return "出现错误"

出现错误,e捕捉到错误信息。成功时两个代码都会执行

内置邮件功能

from django.core.mail import send_mail  # django的发邮件功能
# 模块需要的参数:
"""
send_mail(subject='邮件主题',message='邮件内容',from_email='发送者邮箱',recipient_list='接受者',)

"""
在sttings.py配置文件中设置的参数
EMAIL_HOST='使用的邮箱' #服务器代理地址  qq:smtp.exmail.qq.com/163:smep.163.com
EMAIL_PORT='邮箱端口号'
EMAIL_HOST_USER='账号'
EMAIL_HOST_PASSWORD='密码' 授权码
# EMAIL_FROM_EMAIL=EMAIL_HOST_USER  # 加入参数就是用默认的设置的邮箱
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 # 引入bs4模块功能

soup = BeautifulSoup(文本信息(含字符串和标签/变量),"html.parser")
# html.parser Python标准库

desc = soup.text[起始:终止] # 取出来标签文本内容,截断内容

# 可以获取 用户输入标签的文信息对象soup,同时还可以截取 获得对象desc

BS4的模块防御

from bs4 import BeautifulSoup # 引入bs4模块功能

soup = BeautifulSoup(文本信息(含字符串和标签),"html.parser")

soup.find_all()  # 获取文本中的标签对象
# 每一个标签对象都有一个name属性

for tag in soup.find_all():
	print(tag.name)
	if tag.name == "非法标签": # 过滤标签 /script
		tag.decompose() # 将非法标签从soup对象中删除掉
	
str(soup) # 处理完毕后的对象	

# 通过这个方法将非法标签给过滤

Django-两类服务器

uwsgi服务器

快速,自我驱动,对于开发者和系统管理员友好的应用容器服务器,用于接收前端服务器转发的动态请求并处理发给web应用程序,完全由c编写,实现wsgi协议,uwsgi http协议,
uwsgi协议时uWSGI服务器自有的协议,用于定义传输信息的类型,常用于uWSGI服务器与其他网络服务器的数据通信


浏览器  -- wsig.py -- django框架(路由视图orm模型)

浏览器 -- uWSIG -- django框架(路由视图orm模型)

uWSIG替换了原本的django自带的web服务器



客户端发送请求  http或者https协议
nginx  反向代理 负载均衡 转发请求  
uWSGI  web服务器接受http请求按照网管端口协议 基于python下的wsig别写的应用程序,只适合于python使用
python脚本的程序

# 真正部署上线使用的软件 nginx

客户端发送http协议 --> nginx发送uwsgi协议 --> uWSGIweb服务器发送wsgi --> flask django

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异步服务器也可以 pip install uvicorn
# 不需要进行设置 INSTALLED_APPS 与 ASGI_APPLICATION
# 当前服务器只能在命令行启动
uvicorn 项目名称.asgi:application
uvicorn .... --host ip --port 端口 # 绑定端口ip

# 第二种
使用daphne asgi服务器
1.安装
pip install daphne

2.在配置文件中settings.py中设置
INSTALLED_APPS =[
	 'daphne', # 1.将异步服务器添加app
]

3.在配置文件中添加asgi应用启动
asgi.application 就是创建项目中 asgi.py文件 变量application 服务
ASGI_APPLICATION = '项目名称.asgi.application'.

4.使用命令行启动
daphne -b ip -p 端口 ... # 通过-b -p绑定ip端口

Django-CSRF_Token

是一种防御机制,防止ssh的攻击,用csrftoken进行验证,是否是合法的请求。


{% csrf_token %} 在模板语法中 使用后 post请求就不会再拦截 # 只有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  # token值模块
2.在指定需要token值的接口中设置token变量
    # 创建token值
    token = get_token(request) # 创建一个cookie 发送给浏览器 
3.将token返回给前端
return HttpResponse(token)  # 返回token值

前后端分离,前端如何使用ajax接受token值

    $.ajax({
        url:'/get/', # token值存放的api接口url
        success:function (data) {
            console.log(data)
        }
    })

    
# 在前后端分离时,ajax请求不会覆盖页面,指挥局部刷新



前后端分离怎么获取实现对token值的获取,和django的验证
1.导入模块
from django.middleware.csrf import get_token  # token值模块
2.在指定需要token值的接口中设置token变量
    # 创建token值
    token = get_token(request)
3.将token返回给前端
return HttpResponse(token)  # 返回token值

前后端分离,前端如何使用ajax接受token值
	# 专门的获取token的路由
    $.ajax({
        url:'/get/', # token值存放的api接口url
        success:function (data) {
            console.log(data)
            localStorage.setItem('token',data) # 临时存储token值
        }
    })

    # 在post请求时,将token值进行携带
    # 绑定一个ajax请求的时间 局部刷新
    # 绑定的按钮是没有事件的按钮 button
    $(".login_btn").click(function () { # 因为使用ajax请求登录,基于前后端分离操作,没有使用form表达
        $.ajax({
            url:'/login/', # 登录页面
            type:'post', # post请求
            # 第一种方式
            headers:{'X-CSRFToken':localStorage.getItem("token")}, # 存放在请求头中
            data:{
                # 第二中方式 携带token值
                csrfmiddlewaretoken:localStorage.getItem("token"), # 将临时存储的token值取来/必须设置一个键值对
                uers:$('.name').val() # 获取用户的密码和账户
            },
            success:function (data) {
                console.log(data) # 获取后端的数据值,进行操作
            }
        })
    })
 # django的两种取token方式 1.请求头 2.传入键值对 二选1
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  # 使用后表示可以规避django值自带的csrf的认证
视图
@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 不会再使用了


# CBV模式
from django.views import View  # 必须继承这个类
from django.core import serializers # django自带的序列化方法,序列django自己的数据QuerySet类型
普通的json语法自能处理python内的数据结构,不能处理django的数据结构
from app01 import models # 数据库类


class IndexView(View): # 先声明类
    # 名字不能错,当请求方式是 get 或者post 就会自动执行
    def get(self, request):  # 请求方式方法  如果时get请求自动调用
      	#  获取数据库的内容
        数据 = models.表名.objects.all() # QuerySet类型
     
        # 序列化 json
        data_json = serializers.serialize('json',数据)  # 参数1 序列化的方式 xml json  参数2 需要序列化的数据
        # 序列化xml
        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())  # 类试图 固定调用as_view()方法
# 当访问index时,不管时任何请求方式,就会进入视图类中来,由as_view()方法中的指派分发方法,自动做分发,获取请求方式,调用对应的方法


# 当访问到index页面时,根据请求条件(什么形式的请求方式),
# 触发as_view()方法中的自动分发方法
# 自动执行IndexView类中的请求方法,get就会执行get方法,将查询的数据全部返回

CBV源码解析

本质:还是fbv
path('index/', views.IndexView.as_view())
views.IndexView.as_view():到底是什么主要取决于.as_view()方法的返回值
# as_view 是一个类方法  IndexView没有加括号
IndexView自己写的接口类为什调用这个as_view()
from django.views import View  # 必须继承这个类
IndexView(View) # 因为自己写的接口没有as_view()方法,就会找到父类的View类中去找

在view类中http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'] 接口支持的请求方法




as_view() 的源码
    @classonlymethod # 这是类方法   cls 时什么:就是调用这个类方法的类
    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) # 这个就将api类进行实例化
            self.setup(request, *args, **kwargs) # 实例化后调用setup,现在自己的类中找,再去父类中找
            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

        # take name and docstring from class
        update_wrapper(view, cls, updated=())

        # and possible attributes set by decorators
        # like csrf_exempt from dispatch
        update_wrapper(view, cls.dispatch, assigned=())
        return view  # 将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):
        # Try to dispatch to the right method; if a method doesn't exist,
        # defer to the error handler. Also defer to the error handler if the
        # request method isn't on the approved list.
        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)]

    
# CBV模式底层还是用了FBV,多了一层筛选分发执行过程。    
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('上传成功')




# HTML文件的设置
<h3>基于form表单进行上传文件</h3>
<form action="/login/" method="post" enctype="multipart/form-data">
    {% csrf_token %}

    <input type="file" name="xxx"> # 键值对形式进行上传
    <input type="submit"> # 设置提交按钮
</form>

#文件上传格式input标签/必须键值形式/声明:enctype="multipart/form-data 才能上传文件
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()
       # 将上传文件和csrf传入
        formData.append('file2', $('.file2')[0].files[0]) # 定位到文件标签,组成键值对/先去dom在取第0个文件
        formData.append('csrfmiddlewaretoken', $('[name="csrfmiddlewaretoken"]').val()) # csrf认证
        $.ajax({
            url: "/ajax/",
            type: 'post', # post请求csrf认证
            data: formData, #存储对象
            contentType: false, # 传入文件必须设置的参数1
            processData: false,# 传入文件必须设置的参数2
            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) # 发送一个json的响应数据,给前端,根据响应数据判断内容

orm中的存储文件字段

imagefield 和 filefield 字段
imagefield 图片上传字段比filefield字段多了 图片大小的参数
filefield 上传指定的文件和图片
class Userinfo(models.Model):
    name = models.CharField(max_length=32)

    fath = models.FileField(upload_to='avatars/')  # 头像图片字段  upload_to参数负责存放文件的指定路径

    # 当对这个字段进行上传文件时,就会根据django内的MEDA_ROOT后面进行拼接字段,
    # MEDA_ROOT默认是django的根目录,是可以修改的
    # 根目录/upload_to参数的路径进行拼接
    # MEDA_ROOT = os.path.join(项目根目录的全局变量BASE_DIR,手动创建的文件夹)
    #  当进行对FileField字段进行上传文件时,django会自动创建upload_to参数的文件夹 /用户上传的文件会存放在avatars文件夹下
    #  BASE_DIR/手动创建的文件夹/upload_to=文件名
    # 将上传的文件存储到文件夹下,而对应表中的字段中,存储的时文件的相对路径,
    
    
 # 用户上传文件怎处理
1.先在配置文件中设置用户上传文件的文件夹地址
MEDIA_ROOT = os.path.join(BASE_DIR,'media') # 文件会存放在项目根目录/media下的文件中
2.在设置表字段
fath = models.FileField(upload_to='avatars/')
当对这个字段进行文件赋值。
1.找到配置文件的MEDA_ROOT文件夹
2.根据字段upload_to的 值 创建一个文件avatars文件
3.将用户文件存到avatars文件夹下面

视图函数的语法
1.取文件 = request.FILES.get('file2') # 基于ajax结合orm字段进行上传的文件
2.将取到的文件上传数据库 = models.Userinfo.objects.create(name=name,fath=file) # 将前端的文件与用户名上传到数据库中
3.获取文件
model对象.文件图片字段
model对象.文件图片字段.url # 获取上传文件的路径

user.fath # 取到时上传的文件
user.fath.url # 取到的时上传文件的路径/将路径传入到前端进行显示
    # orm通过文件路径取获取到文件


# 在前端怎么对文件进行上传
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) # 用户名字文件夹/avatars/图片文件

Django-文件功能下载

import mimetypes # 获取文件的格式
from django.http import FileResponse # 使用的django FileResponse方法


### 下载功能
def customer_tpl(request):
    """
    下载批量导入Excel列表
    :param request:
    :return:
    """
    # 拼接路径
    tpl_path = os.path.join(settings.BASE_DIR, 'web', 'files', '批量导入客户模板.xlsx')
    # 猜测文件的格式 mimetypes可以获取当前的文件格式
    content_type = mimetypes.guess_type(tpl_path)[0]
    # 使用django的FileResponse响应对象返回读取的文件以及content_type类型
    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)
	# 设置返回的响应字典,uploadJson:"/upload/",设置这个属性
    # uploadJson的原因:需要设置josn序列化上传
    response={
        "error":0,# 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返回的数据'}
    # cors的使用
    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')  # fk关联到table表
    object_id = models.IntegerField() # 当前的关联到table标下的table中的产品id


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)  # 存app的名称
    table_name = models.CharField(max_length=32)  # 存app下表名的名称

    
可以创建一张 table表 用来记录当前程序下创建 app  与 app的表
在创建优惠卷表  字段1 绑定当前的table表   字段2绑定 当前的商品id

将表就够通过 字段1 + 字段2 将就锁定了这个商品  减少字段的数量 将横向的变为纵向的

# django 内存在现成的表就是ContentType,配合GenericForeignKey(将两个字段关联不会创建字段) GenericRelation(反向查询不会创建字段) 实现这种优惠卷表与多张商品表的关系

案例

1.导入模块
from django.db import models
from django.contrib.contenttypes.models import ContentType # 导入django中的contentype
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)
    # 第1. 设置字段 与django内的ContentType表进行fk关联
    content_type = models.ForeignKey(to=ContentType,on_delete=models.CASCADE)
    # 第2. 创建一个对象字段主要记录 那个app下的那个表 下的那一列id
    object_id = models.IntegerField()  # 当前的关联到table标下的table中的产品id
    # 第3. 将两个字段进行关联关系绑定 关系型数据库多个表之间享用共同外键的问题时(不会创建字段只是为了方便绑定关系)
    content_object = GenericForeignKey('content_type','object_id')
    
    
3.正删改查GenericForeignKey/GenericRelation/ContentType的使用

class Users(APIView):

    def get(self, request, *args, **kwargs):
        # 1.个创建创建优惠卷
        food_obj = models.Food.objects.filter(id=1).first()

        # 1.创建
        # 直接将查询到的food对象对应到content_object字段中, GenericForeignKey 会自动的根据当前的对象取找对应关系
        models.Coupon.objects.create(title='双11全场优惠分享',content_object=food_obj)

        # 2.反向查询
        # 面包对象反向查询优惠卷表 使用GenericRelation字段绑定优惠卷表 不生产字段利用字段进行查询
        print(food_obj.coupons.all())

        # 3.正向查询
        # 通过优惠卷查询对象 通过content_object对象 进行反向查询 因为字段时GenericForeignKey属性
        coupon_obj = models.Coupon.objects.filter(id=1).first()
        print(coupon_obj.content_object)

        # 4.通过ContentType表找到表模型
        # 1.content对象
        content_obj = models.ContentType.objects.filter(app_label='app',model='food').first()
        # 2.通过content对象的model_class方法获取到当前的food对象
        model_class = content_obj.model_class()
        # print(model_class) # <class 'app.models.Food'>
        # 3.在查询当前全部的数据
        print(model_class.objects.all())
        return Response({'data': '跨域请求'})

Django中的Model中的Meta(原类)

Meta

是Django 模型类的Meta是一个内部类,它用于定义一些Django模型类的行为特性

1.abstract

class User(models.Model):
     name = models.CharField(max_length=100)
    class Meta(object):
        # 1. abstract 作用将当前表定义为是不是一个抽象类 Ture是(不会在数据库中创建表结构) 默认不是
        # 当前方法的作用就是,设置公共的属性,让其他的表进行继承当前的抽象表,那么就会由公共的字段和属性
        abstract = True 
        
# 那么当前这张表创建时,就会同时拥有User 表中的nam字段
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):
        # 例如 获取当前对象获取 name 和age记性判断
       	if self.name == 'wkx':
            if not self.age <= 18:
            	raise ValueError('抱歉年龄超过18岁')
        super(User,self).save(*args, **kwargs) # 执行父类的save方法

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) 
# 添加auto_now_add = True 添加当前创建对象的时间(只有一次)
# 添加auto_now = True 当前对象修改时就会更新一次时间

# ImageField 字段
# ImageField字段会根据upload属性在项目中进行chuang'jian
course_img = models.ImageField(upload_to="course/%Y-%m(路径)", verbose_name='课程的图片')

Django中关于orm字段属性的choices

# 关于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() # 获取中文

Django中关于orm中属性related_name

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表查到age表时使用的)
user_obj= User.object.filter(id=1).first()
user_obj.user_and_age.age # 就可反向查询主表的一些字段

补充知识点orm字段小技巧

# 可以根据当全部表中的对象order_by() 字段进行排序()
# 1.这种排序时可控制的
# 2.随时插入一个新的
order = models.PositiveSmallIntegerField(default=1)

Django配置补充

Django中admin账户注册和注册自创表

1.在命令行输入命令
python manage.py createsuperuser # 注册dajngo 的超级用户

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')
    # 满50页自动分页
    list_per_page = 50
    # 排序规则
    ordering = ('name',)
    #  设置那些字段可以在后台被修改
    list_display_links = ('name',)

3.通过admin/路由根据注册进行登录admin后台管理页面



注意事项
# 1.设置的类名不能和model表名相同
# 2.设置的:list_display,ordering,list_display_links 3个变量必须是元组和列表
# 3.不能将1对1,多对多,1对多的关系字段在list_display 中显示,会报错,在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文件夹,在执行上述命令
 
# 创建新app
python manage.py startapp app名称

# 如果需要在某个app下创建新的djangoapp
  1.python manage.py startapp 'app名称' '目录父级/目录'
    例如: 
    项目中的存放app的文件:		apps
    其中的一个文件:    			base
    python manage.py startapp base apps/base # 也就将app放到base目录中
  2.在配置文件中进行注册
  3.修改多层级目录下的app 中的apps.py 文件的name(添加父级或者以上的名字的前缀)

Django路由分发

# include(("rbac.urls", "rbac")) #参数 1是分发的路径 参数2 是反向url名称
# include("rbac.urls") # 字符串
源码内部逻辑
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') # 处理字段中的choices 获取中文
# source 属性不仅仅处理choices 还可以执行orm语句(因为可以接受self对象也就是序列化类的中显示字段查询的对象) 可以理解为接受一个函数或者对象类型(如果是对象那么会在底层进行反射执行)
source = course字段在表中是一个fk关联对象 (必须是存在关系的才能这样操作)
study_num = serializers.IntegerField(source='course.study_num') # course字段在表中是一个fk关联对象



# 方法字段 序列化多对多的字段信息或者复杂的信息
字段名 = 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
    

Django中media配置(静态资源配置)

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
# 静态资源media/(?P<path>.*) url路径
# {'document_root':settings.MEDIA_ROOT} # 静态文件路径
# serve 会将静态资源路径进行拼接找到返回给前端
 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 新建文件夹

命令

python manage.py check --deploy 检查项目中的不足

后端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 # 存储图片到 ImageField 值得upload_to 指定路径下
            ser_obj.save() # 在图片进行存储
     return Response(ser_obj.data) 返回

购物车

1.加入购物车

1.考虑前端出入的数据是什么
	1.价格id
	2.数量
	3.商品id
2.需要考虑将数据存储在那个地方
	redis首选
3.需要考虑redis存储的数据结构
	考虑问题设计数据结构:
		用户在添加购物车时,会不会直接结算
		用户在添加购物车时,如果不直接结算
在购物车中进行展示的无非就是
	1.商品图片
	2.商品价格
	3.商品数量

关于存储在redis中的key的选择

# 拼接redis key 至关重要(最好使用当前用户id与商品id作为redis key中的一部分)
标记信息_用户id_商品id:{商品的详情数据} 这种结构显示

2.查看购物车

1.需要拼接 redis 的 key
2. 从redis中获取
3.构建数据结构展示给前端显示

# 注意:redis 是支持模糊匹配的
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找,找到就有,找不到就没有返回结果)
    # conn.exists(key) redis的exists方法查看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) # 查看当前的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 # api 自带的状态码
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  # 缓存对象 也就是django 绑定缓存后的对象
from rest_framework.permissions import BasePermission # api 权限父类
from rest_framework.authentication import BaseAuthentication  # api 的认证类
from rest_framework.versioning import URLPathVersioning # 版本控制类
from rest_framework.throttling import SimpleRateThrottle  # api 的限流类
from rest_framework.exceptions import AuthenticationFailed, NotAuthenticated, NotFound, PermissionDenied,NotAcceptable # api 自带的异常类

class Authentication(BaseAuthentication):
    # 认证错误4xx
    def authenticate(self, request):  # 负责认证 具体认证方法
        token = 123456
        if not token:
            raise NotAuthenticated({'error': '未认证'})  # 认证不通过,会将错误信息给前端 终止 4001
        if not token == 123456:
            raise AuthenticationFailed({'error': '认证失败'})
        return '用户对象', 'token'  # 返回元组 (None,None) 无论返回什么都可以 必须是两个值 要不然就会报错

    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})  # 抛出版本异常 404
        return version


class BaseP(BasePermission):

    def has_permission(self, request, view):

        name = 1
        if name == 1:  # 如果当前name = 1 那么就有权限 否则就没有权限
            return True  # 有权限,api才能访问
        else:
            return False  # 无权访问, api无权访问

    def has_object_permission(self, request, view, obj):
        return False






# 2.编写类继承限流父类
class ThrottledException(NotAcceptable):  # 自定义异常类
    '''自定义的异常类 用来抛出限流类的限流信息'''
    pass


class MyS(SimpleRateThrottle):
    '''自定义限流类'''
    cache = cache_redis

    cache_format = 'throttle_%(scope)s_%(ident)s'  # 构建缓存中的key
    scope = 'user'  # 表示

    # 当前变量主要进行设置频率限制,当前字典的key 需要和scope变量进行对应(源码设置好了通过THROTTLE_RATES['scope'])取值
    # 10/m 1分钟访问10次  's' 'sec' 'm' 'min' 'h' 'hour' 'd' 'day'
    THROTTLE_RATES = {'user': '2/m'}

    def get_cache_key(self, request, view):
        '''重写父类的方法作用(返回唯一标识)如果唯一标识需要修改,就在当前方法中进行修改'''
        if str(request.user) != 'AnonymousUser' and request.user:  # 判断当前是否时登录用户(1.不能时匿名用户 2.必须是正常用户)
            ident = 1  # 是获取他的id
        else:
            ident = self.get_ident(request)  # 不是获取他的ip(当前方法在父类中从请求头中获取)
        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):
    # authentication_classes = [Authentication, ]
    # versioning_class = PathVersion
    # permission_classes = [BaseP]
    # throttle_classes = [MyS]

    def get(self, request, *args, **kwargs):
        # print(request.version)  # 获取版本
        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":  # 出现复杂请求需要OPTIONS预检
            # response["Access-Control-Allow-Headers"] = "Origin, X-Requested-With, Content-Type, Accept_Token" # 请求头要放开 前端token使用什么就要放开什么

            response[
                "Access-Control-Allow-Headers"] = "Content-Type"  # 比如前端有token 那么 response["Access-Control-Allow-Headers"] = "Content-Type,token" 不然请求会出错

            # 允许那些复杂的请求
            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