赞
踩
pip install django
(1)终端django admin startproject 项目名称(推荐)
(2)pycharm专业版,直接创建,项目的目录不要放在python的安装目录
项目名
----manage.py (项目的管理,启动项目,创建app、数据管理)
----项目名文件夹
----init_.py
----asgi.py (接收网络请求)
----settings.py (项目配置)
----urls.py (url和函数的对应关系)
----wsgi.py (接收网络请求)
创建:python manage.py startapp app名
app名
----migrations (数据库变更记录)
----init.py
----init.py
----admin.py (django默认提供了admin后台管理)
----apps.py (app启动类)
----models.py (对数据库操作)
----tests.py (单元测试)
----views.py (函数)
1.确保app已注册(settings.py)‘app名.apps.App01Config’
2.编写url和视图函数对应关系(urls.py)from appname import views
3.编写试图函数(views.py) from django.shortcuts import render,HttpResponse(request)
4.启动django项目
(1) python manage.py runserver
(2)直接运行
1.templates存入html文件
2.static存入图片、css、js、plugins
Django官网:https://www.djangoproject.com/
Django 采用了 MVT 的软件设计模式,即模型(Model),视图(View)和模板(Template)。
建议图:
用户操作流程图:
Django 本身基于 MVC 模型,即 Model(模型)+ View(视图)+ Controller(控制器)设计模式
简易图
pip install django
script:pip.exe\django-admin.exe
[创建django项目中的文件和文件夹]
lib:内置模块:openpyxl\django\time\flask
1.打开终端
2.进入某个目录(项目放在哪里)
3.执行命令创建项目
django-admin startproject myproject
#此处myproject为项目名
cd 到项目所在目录,即与manage.py同级,输入如下指令来创建应用:
python manage.py startapp appname
#blog为应用名
manage.py 是一个工具脚本,用作项目管理的。会使用它执行管理操作。
里面的 mydjangowebsite/ 目录是python包。 里面包含项目的重要配置文件。这个目录名字不能随便改,因为manage.py 要用到它。
mydjangowebsite/settings.py 是 Django 项目的配置文件. 包含了非常重要的配置项,以后我们可能需要修改里面的配置。
mydjangowebsite/urls.py 里面存放了 一张表, 声明了前端发过来的各种http请求,分别由哪些函数处理.
mydjangowebsite/wsgi.py
要了解这个文件的作用, 我们必须明白wsgi 是什么意思:python 组织制定了 web 服务网关接口(Web Server Gateway Interface) 规范 ,简称wsgi。参考文档 https://www.python.org/dev/peps/pep-3333/
遵循wsgi规范的 web后端系统, 我们可以理解为 由两个部分组成wsgi web server 和 wsgi web application它们通常是运行在一个python进程中的两个模块,或者说两个子系统。wsgi web server(
提供高效的http请求处理环境) 接受到前端的http请求后,会调用 wsgi web application (
处理 业务逻辑)的接口( 比如函数或者类方法)方法,由wsgi web application 具体处理该请求。然后再把处理结果返回给 wsgi web server, wsgi web server再返回给前端。
python manage.py runserver 3344(默认是8000)
python manage.py runserver 0.0.0.0:3344.
ALLOWED_HOSTS = [‘*’]
sudo lsof -i:5000
sudo kill -9 pid
python manage.py startapp 创建应用
python manage.py migrate 数据库迁移
直接执行python manage.py 可列出所有的Django子命令
公有配置项 - Django官方提供的基础配置:https://docs.djangoproject.com/en/3.2/ref/settings/
语法
from django.http import HttpResponse
def xxx_view(request,[其他参数]):
return HttpResponse对象
# 在url路由模块添加url(url,RegisterView.as_view())
from django.views.generic import View
class RegisterView(View):
def get(self,request):
return render(request,'register.html')
def post(self,request);
return httpresponse('post')
from django.urls import path
语法 - path(route,views,name=None)
参数:
1. route:字符串类型,匹配的请求路径
2. views:指定路径所对应的试图处理函数的名称
3. name :为地址起别名,在模板中地址反向解析时使用
转换器类型 | 作用 | 样例 |
---|---|---|
str | 匹配除了’/'之外的非空字符串 | username |
int | 匹配0或任何正整数。返回一个int | 100 |
slug | 匹配任意由ASCLL字母或数字以及连字符和下划线组成的短标签 | this-is-django |
path | 匹配非空字段,包括路径分隔符‘/’ | a/c |
re_path()
在url的匹配过程中可以使用正则表达式进行精确匹配
语法:
XXXX年xx月xx日
re_path(r"^birthday/(?P<y>\d{4})/(?P<m>\d{1,2})/(?P<d>\d{1,2})$",views.birthday_view)
状态码
-200 - 请求成功
-301 - 永久重定向-资源(网页等)被永久转移到其他URL
-302 - 临时重定向
-404 - 请求的资源不存在
-500 - 内部服务器错误
Content_type:
'text/html' (默认,html文件)
‘text/plain’ 纯文本
‘text/css’css 文件
‘text/javascript’js 文件
‘multipart/form-data’ 文件提交
‘application/json’ json传输
‘application/xml’ xml文件
HttpResponseRedirect 301 重定向
HttpResponseNotModified 304
HttpResponseBadRequset 400
HttpResponseNotFound 404
HttpResponseForbidden 403
HttpResponseNotAllowed 405
HttpResponseGone 410
HttpResponseServerError 50
# 直接将dict作为json返回,将content_type直接设为application/json
content = {
'msg': 'invalid method',
}
return JsonResponse(content)
取消csrf验证
禁止settings.py中MIDDLEWARE中的CsrfViewsMiddleWare的中间件
- MTV
from django.shortcuts import render
return render(request,'模板文件名','字典数据')
{% if 条件表达式1 %}
{% elif 条件表达式 %}
{% else %}
{% endif % }
{% for 变量 in 可迭代对象%}
循环语句
{% empty %}
可迭代对象无数据时填充的语句
{% endfor %}
变量 | 描述 |
---|---|
forloop.counter | 循环的当前迭代索引(从1开始索引) |
forloop.counter0 | 循环的当前迭代索引(从0开始索引) |
forloop.revcounter | counter值得倒序 |
forloop.revcounter0 | recounter值得倒序 |
forloop.fisrt | 如果这是第一次通过循环,则为真 |
forloop.last | 如果这是最后一次循环,则为真 |
forloop.parentloop | 当嵌套循环,parentloop表示外层循环 |
过滤器 | 说明 |
---|---|
lower | 字符串全变为小写 |
upper | 字符串全变为大写 |
safe | 默认不对变量内得字符串进行html转义,不变为字符串 |
add:“n” | 将value值加n |
用path定义的名称来动态查找或计算出相应的路由
from django.urls import reverse
reverse('别名',args=[],kwargs={})
#file:setting.py
STATICFILES_DIRS = (
os.path.join(BASE_DIR,"static"),
)
1.加载static{% load static %}
2.使用静态资源-{% static '静态资源路径'%}
python manage.py startapp music
INSTALLED_APPS = [
#.....
'user' ,#用户信息模块,
'music',
]
path('music/',include('music.urls'))
应用下templates和外层templates都存在时,django得查找模板规则
模型层即ORM
1. sudo apt list --install | grep -E 'libmysqlclient-dev | python3-dev'
2. 若命令无输出则需要安装 - sudo apt-get install python3-dev default-libmysqlclient-dev
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'django',
'USER': 'root',
'PASSWORD': '123456',
'HOST': '47.97.118.247',
'POST': '3306'
}
}
python3 manage.py makemigrations
将应用下的models.py文件生成一个中间文件,并保存在migrations文件夹中python3 manage.py migrate
执行迁移程序实现迁移。将每个应用下的migrations目录中的中间文件同步到数据库中模型类的创建:
from django.db import models
class 模型类名(models.Model):
字段名 = models.字段类型(字段选项)
基础字段及选项
必须要指定max_length参数值
模型类-字段选项
null=True 表示在数据库层面允许为空。 blank=True 表示在前端表单层面允许为空。所以在使用这两个参数时需要注意搭配,不要使blank为True而null为False,这样的结果就是表单提交成功而操作数据库失败,对用户来说不太友好。
name = models.CharField(max_length=30, unique = True,null = False,db_index = True)
修改过字段选项【添加或者修改】均要执行makemigrations 和 migrate
实例:
class Book(models.Model):
class Meta:
db_table = 'book' #可改变当前模型类对应的表名
verbose_name ='图书' #修改admin模型管理中的表名,后面有s,复数
verbose_name_plural = verbose_name #单数
常见问题
ORM操作
class MyModel(models.model):
...
MyModel.objects.create(...) #objects是管理器对象
Django Shell
python3 manage.py Shell
方法 | 说明 |
---|---|
all() | 查询全部记录,返回QuerySet查询对象 |
get() | 查询符合条件的单一记录 |
filter() | 查询符合条件的多条记录 |
exclude() | 查询符合条件之外的全部记录 |
values(列1’,‘列2’) | 查询部分列的数据并返回一个QuerySet,容器里面是字典 |
values_list(‘列1’,‘列2’) | 查询部分列的数据,返回的是QuerySet,容器里面是元组 |
order_by(‘-列’,‘列’) | 默认是按照升序排序,降序排序则需要在列前增加’-'表示 |
Author.objects.filter(id__exact=1)
#等同于select * from author where id=1
author.objects.filter(name__contaions='w')
#等同于 select * from author where name like '%w%'
author.objects.filter(age__gt=50)
#等同于 select * from author where age>50
author.objects.filter(country__in=['china','japan','korea'])
# 等同于select * from author where country in ('china','japan','korea')
#查找年龄在某一区间内的所有作者
author.objects.filter(age__range=(35,50))
# 等同于 select ...where author between 35 and 50;
# 将id大于3的所有图书价格定为0元
books = Bookes.objects.filter(id__gt=3)
books.update(price=0)
try:
auth = author.objects.get(id=1)
auth.delete()
except:
print('删除失败')
# 删除全部作者中,年龄大于65的全部信息
auths = author.objects,filter(age__gt=65)
auths.delete()
伪删除
在模型类中定义,可以直接打印详细信息
def __str__(self):
return '%S%S'%(self.title,self.pub)
查看sql语句
print(a5.query)
F对象
一个F对象代表数据库中某条记录的字段的信息
作用:
- 通常是对数据库中的字段值在不获取的情况下进行的操作
- 用于类属性(字段)之间的比较
语法:
from django.db.models import F
F('列名')
示例:
# 更新Book实例中所有的零食价涨10元
Book.objects.all().update(market_price=F('market_price')+10)
sql:'UPDATE' bookstore_book SET 'market_price'=('bookstore_book'.'market_price'+10)
# 以上做法好于如下代码
books = Book.objects.all()
for book in books:
book.market_price = book.market_price+10
#topic.like = F('like')+1
book.save()
Q对象
在获取查询结果集使用复杂的逻辑或|、逻辑非~等操作时可以借助于Q对象进行操作
如:想找出定价低于20元或清华大学出版社的全部书,可以写成
from django.db.models import Q
Book.objects.filter(Q(price_lt=20) | Q(pub = “清华大学出版社”))
Q(条件1) | Q(条件2) #条件1成立或条件2成立
Q(条件1) & Q(条件2) #条件1和条件2同时成立
Q(条件1) & ~Q(条件2) #条件1成立且条件2不成立
整表聚合
导入:from django.db.models import *
聚合函数:Sum,Avg,Count,Max,Min
语法:MyModel.objects.aggregate(结果变量名 = 聚合函数(‘列’))
返回结果:结果变量名和值组成的字典{“结果变量名”:值}
分组聚合
分组聚合时指通过计算查询结果中每一个对象所关联的对象集合,从而得出总计值(也可以是平均值或总和),即为查询集的每一项生成聚合。
语法:QuerySet.annotate(结果变量名 = 聚合函数(‘列’))
返回值:QuerySet
1 .通过先用查询结果MYModel.objects.values查找查询要分组聚合的列
pub_set = book.objects.values('pub')
pub_set.annotate(mycount = Count('pub'))
from django.db import connection
with connection.cursor() as cur:
cur.execute('执行SQL语句','拼接参数')
class XXXXXManager(admin.ModelAdmin):
.......见下面实例
from django.contrib import admin
from .models import *
admin.site.register(YYYY,XXXXManger)#绑定 YYYY模型
类与管理器类XXXXManager
#示例:
list_display 哪些字段显示在admin的修改表页面中
list_display_links 可以控制list_display中的字段是否应该链接到对象的“更改”页面
list_filter 设置激活admin修改列表页面右侧栏中的过滤器
search_fields 设置启用admin更改页面上的搜索框
list_editable 设置为模型上的字段名称列表,这将允许在更改列表页面上进行编辑
http://docs.djangoproject.com/en/2.2/ref/contrib/admin/
在关系型数据库中,通常不会把所有数据都放在同一张表中,不易扩展,常见关系映射
删除主表数据时连通一起删除外键表中数据
通过抛出ProtectedError异常,来阻止删除主表中被外键应用的数据
设置为NULL,仅在该字段null=True允许为null时可用
,表示外键允许为空设置为默认值,仅在该字段设置了默认值时可用
设置为特定值或者调用特定方法
不做任何操作,如果数据库前置指明联级性,此选项会抛出IntegrityError异常
从打开浏览器访问一个网站,到关闭浏览器结束此次访问,称之为一次会话
http协议是无状态的,导致会话状态难以保持
cookies和session就是为了保持会话状态而诞生的两个存储技术
response.set_cookie(key,value)
HttpResponse.set_cookie(key,value,max_age(最大保存时间,以秒为单位,默认值是会话关闭时自动删除),expires=None)
- key :cookie的名字
- value : cookie的值
- max_age: cookie存活时间,秒为单位
- expires:具体过期时间
- 当不指定max_age和expires时,关闭浏览器时此数据失效。
INSTALLED_APPS = [
#启用 sessions应用
'django.contrib.sessions',
]
MIDDLEWARE = [
#启动session中间件
'django.contrib.sessions.middleware.SessionMiddleware'
]
可以搜索conf查看django默认的系统设置---双击shift,输入global_settings查看配置
注意:Django中的session数据存储在数据库中,所以使用session前需要确保已经执行过migrate
定义:缓存是一类可以更快的读取数据的介质统称,也指其他可以加快数据读取的存储方式。一般用来存储临时数据,常用介质的是读取数据很快的内存
意义:视图渲染有一定成本,数据库的频繁查询过高;所以对于低频变动的页面可以考虑使用缓存技术,减少实际渲染次数;用户拿到响应的时间成本会更低。
CACHES={
'default':{
'BACKEND':'django.core.cache.backends.db.DatabaseCache',
'LOCATION':'my_cache_table',
'TIMEOUT':300, #缓存保存时间单位秒,默认值为300,
'OPTIONS':{
'MAX_ENTRIES':300, #缓存最大数据条数
'CULL_FREQUENCY':2, #缓存条数达到最大值时删除1/x的缓存数据
}
}
}
Cache-Control:max-age=120
代表请求创建时间后的120秒,缓存失效定义;
自定义中间件
def my_middleware(get_response):
print("init中间件初始化")
def middleware(request):
print("视图处理request之前执行")
response = get_response(request)
print("视图产生response后执行")
return response
return middleware
编写中间件类
注:中间件中的大多数方法在返回None时表示忽略当前操作进行下一项事件,当返回HttpResponse对象时表示此请求结束,直接返回给客户端
注册中间件
新建python文件夹,项目路径下,添加middleware的python文件,且注册在setting中。
中间件的执行顺序:进入试图函数之前(包括),按注册顺序。在试图函数之后,从下向上。犹如栈。
CSRF-攻击跨站伪造请求攻击
某些恶意网站上包含链接、表单按钮或者JavaScript,它们会利用登录过的用户在浏览器中的认证信息试图在你的网站上完成某些操作,这就是跨站请求伪造(Cross-Site Request Forgey)
CSRF防范
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def my_view(request)
return HttpResponse("Hello World")
Django提供了Paginator类可以方便的实现分页功能
Paginator类位于’django.core.Paginator’模块中
import csv
with open('eggs.csv','w',newline='') as csvfile:
writer = csv.writer(csvfile)
writer.writerow(['a','b','c'])
writer.writerow(['d','e','freeze'])
定义:django带有一个用户认证系统。他处理用户长账号、组、权限以及基于cookie的用户会话。用户可以直接使用django自带的用户表
@login_required
def index_view(request):
# 未登录跳转到setting.LOGIN_URL,在setting中配置
#当前登录用户可通过request.user获取
login_user = request.user
注意:此操作要在第一次migrate之前进行
定义:用户可以通过浏览器将图片等文件传至网站
场景:用户上传头像;上传流程性的文档【pdf,txt等】
#file : setting.py
MEDIA_URL = '/media/'
MEDIA_ROOT = [BASE_DIR,'media']
#主路由中添加路由
from django.conf import settings
from django.conf.urls.static import static
urlpatterns += static(setting.MEDIA_URL,document_root=settings.MEDIA_ROOT)
在模型类
picture = models.FileField(upload_to ='picture')
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.qq.com' #腾讯qq邮箱smtp服务器地址
EMAIL_PORT = 25 #SMTP服务的端口号
EMAIL_HOST_USER = 'XXX@QQ.COM' #发送邮件的qq邮箱
EMAIL_HOST_PASSWORD = '****' #在pop3/IMAP的密钥
EMAIL_USE_TLS = False #与SMTP服务通信时,是否启动TLS链接(安全)默认是False
RPC(Remote Processdure Call)
远程过程调用[远程服务调用],这种接口一般以服务或者过程式代码提供。
基本的数据格式:protobuf(gRPC)、json、xml
缺点:rpc接口多了,对应函数名和参数就多了,前端在请求api接口时就难找,对于年代久远的rpc服务端的代码也容易出现重复的接口
restful(Representational State Transfer)
资源状态转换(表征性状态转移)
1.把资源的名称写在url地址
2.http请求动词来说明对该资源进行哪一种操作,post、get、delete、put、patch
区别:
1.restful是以资源为主的api接口规范,(url不同,url相同的动作区分)
2.rpc则以动作为主的api接口规范,(url全部一样,操作都写在参数中)
1.域名
应该尽量将api部署在专用域名之下。
https://www.example.com/api
https://api.example.com
2.版本(version)
应该将api的版本号放在url中
http://www.example.com/api/v1/foo
http://www.example.com/api/v2/foo
3.路径(Endpoint)
表示api的具体网址,每个网址代表一种资源(resource)
使用名词,不要用动词;名词最好用复数
4.HTTP动词
GET=select
POST=create
PUT = UPDATE
DELETE = DELETE
不常用:patch(更新部分信息)、head(获取资源的元数据)、options(获取可以可以更改的属性)
CURD create、update、read、delete 增删改查,这四个数据库的常用操作
5.过滤信息(Filtering)
完整的url地址格式:
协议://域名(ip):端口/app/?查询字符串(query_string)#锚点
6.状态码(status codes)
1XX 表示当前本次请求还是持续,没结束
2XX 表示当前本次请求成功/完成了
3XX 表示当前本次请求成功,但是服务器进行代理操作/重定向
4xx 表示当前本次请求失败,主要是客户端发生了错误
5xx 表示当前本次请求失败,主要是服务器发生了错误
7.错误处理(Error handling)
{
error: “xxxx”
}
8.返回结果
GET /collections:返回资源对象的列表(数组)
GET /collection/ID: 返回单个资源的对象或字典(json)
POST /collections: 返回新生成的资源对象(json)
PUT /collection/ID: 返回修改后的资源对象(json)
DELETE /collection/ID: 返回一个空文档(空字符串,空字典)
9.超媒体(Hypermedia API)
RESTful API最好做到Hypermedia(即返回结果中提供链接,连向其他API方法),使得用户不查文档,也知道下一步应该做什么。
10.其他
服务器返回的数据格式,应该尽量使用json,避免使用XML。
XML(eXtend Markup Language,可扩展标记语言)是W3C为了替换HTML研发出来的,但是现在很明显失败了。
语法:
1.xml常用场景:配置文件、微信开发、小程序、安卓 2.xml是网页文档,文件以.xml结尾 3.xml文档必须以文档声明开头,所有的xml文档内容都必须写在自定义的根标签内,xml文档有且只有一个根标签。 <xml version="1.0" charset="utf-8"> <根标签> .....xml文档内容 </根标签> 4.xml里面是由标签组成页面的。标签分单标签和双标签。其中, 单标签写法:<标签名/> 双标签写法: <标签名></标签名> 5.xml标签名除了文档声明,其他标签名和属性全部是开发人员自己自定义的 6.标签有0个或多个属性,属性必须有属性值。而且属性值必须使用引号圈起来。 <标签名 属性名="属性值" .../> <标签名 属性名="属性值" ...>标签内容</标签名>
json是目前市面上最流行的数据传输格式。javaScript Object Notation js对象表示法
语法:
# 1.json文件以.json结尾,一个json文件中只能保存一个json对象或者一个json数组
# 2.json中的属性类型:
数组 []
对象 {}
数值 整型,浮点型,布尔值
字符串 双引号圈起来的文本内容
null 空
# 3. 数组和对象的成员之间必须以英文逗号隔开,并且最后一个子成员后面不能出现逗号
# 4. json对象只能有属性不能出现方法,而且属性名必须以字符串格式来编写
接口实现过程中,会存在幂等性。所谓幂等性是指代客户端发送多次同样请求时,是否对于服务端里面的资源产生不同结果。如果多次请求,服务端结果还是一样,则属于幂等接口,如果多次请求,服务端产生结果是不一样的,则属于非幂等接口。
请求方式 | 是否幂等 | 是否安全 |
---|---|---|
GET | 幂等 | 安全 |
POST | 不幂等 | 不安全 |
PUT/PATCH | 幂等 | 不安全 |
DELETE | 幂等 | 不安全 |
对于非幂等和不安全的请求需要添加防抖操作(再次验证操作)
Django RestFramework 是一个建立在django基础之上的web应用开发框架,可以快速的开发REST API接口应用。在REST framework中,提供了序列化器Serialzier的定义,可以帮助我们简化序列化与反序列化的过程,不仅如此,还提供丰富的类视图、扩展类、视图集来简化视图的编写工作。REST Framework还提供了认证、权限、过滤、分页、接口文档等功能支持。REST framework还提供了一个调试API接口的Web可视化界面来方便查看测试接口。
# 必须有django的情况下安装
# conda create -n center python=3.10
# pip install django=4.1 -i 镜像
pip install djangorestframework -i ..
最后,在settings.py 的INSTALLED_APPS加入’rest_framework’,
接下来就可以使用DRF提供的功能进行api接口开发了。在项目中如果使用rest_framework框架实现api接口,主要有以下三个步骤:
1.将请求的数据(如json格式)转换为模型类对象
2.操作数据库
3.将模型类对象转换为响应的数据(如json格式)
1.首先创建一个序列化.py文件StudentModelSerializer用于序列化与反序列化
# 创建序列化器类,回头会在视图中被调用
class UserModelSerializer(serializers.ModelSerializer):
class Meta:
model = User
# fields = "__all__"
fields = ["id", "realname"]
# model指明该序列化器处理的数据字段从模型类Bookinfo参考生成
# fields指明该序列化器包含模型类中的哪些字段,“all”指明包含所以字段
2.编写视图
# DRFTest
from rest_framework.viewsets import ModelViewSet
from .serializer import *
class UserModelViewSet(ModelViewSet):
query =User.objects.all()
serializer_class = UserModelSerializer
# queryset指明该视图集在查询数据时使用的查询集
# serializer_class 指明该视图在进行序列化或反序列化时使用的序列化器
3.定义路由
# DRFTest
from rest_framework.routers import DefaultRouter
router = DefaultRouter() # 可以处理视图的路由器
router.register("user2",UserModelViewSet,basename="user2") # 向路由器中注册视图集
# urlpatterns += router.urls 将路由器中的所有路由信息追加到django的路由列表中
4.将子应用中的路由文件加载到总路由中
5. 运行测试
python manage.py runserver 0.0.0.0:3344
在浏览器中输入网址127.0.0.1:3344,可以看到DRF提供的API Web浏览页面
反射
字符串反射为一个函数,再去调用函数的方法
class Animal(object):
def __init__(self,func_str)
func=getattr(self.func_str) # self.sleep
func()
def sleep(self):
print("睡觉....")
alex = Animal("sleep")
class Fly(object):
def fly(self):
print("fly...")
class Animal(object):
def sleep(self):
print("睡觉....")
class Bird(Fly,Animal) #同时继承了Fly类和Animal类的属性和方法
pass
bird= Bird()
bird.fly
bird.sleep
# self\cls:谁调用的这个函数或者类,那么self就是谁,self.func()就是从谁开始,一层一层的往父类去找。所以如果在调用的类中,重写与父类一样的函数,那么就会覆盖父类中的同名函数。
# 一旦用户get访问user,访问的 /user =》 view() =》return self.dispatch() => return get()
class Userview(View):
def dispatch(self,request,*args,**kwargs):
print("hello world")
ret = super().dispatch(request,*args,**kwargs)
return ret
def get(self,request):
return HttpResponse("get")
def post(self,request):
return HttpResponse("post")
‘contentType:urlencoded \r\n\r\na=1&b=2’ # request.POST
‘contentType:json \r\n\r\n{“a”:1,“b”:2}’ # request.POST不解析json数据
class APIView: #重写了as_view函数 def as_view(cls): # 调用了父类的as_view()函数 view = super().as_view() return view() # 重写了dispatch方法 def dispatch(self,reqeust,*args,**kwargs): # 构建新的request对象!!!!!!!! # post:变为request.data # get : request.query_params # django原生: request._request request= self.initialize_request(request.*args.**kwargs) self.request = request # 初始化:认证、权限、限流组件三件套 self.initial(request,*args,**kwargs) # 分发逻辑与父类一样,只是改变了request结构体
# DRFtest # from rest_framework import serializers # """ # serializers 是drf提供给开发者调用的序列化器模块,里面声明了所有的可用序列化器的基类: # Serializer 序列化基类,drf中所有的序列化器类都必须继承于Serializer # ModelSerializer 模型序列化器基类,是序列化器基类的子类,在工作中,除了Serializer基类以外,最常用的序列化器基类 # """ # # 创建序列化器类,回头会在视图中被调用 # class UserModelSerializer(serializers.ModelSerializer): # 1.转换的字段声明 # 字段 = serializers.字段类型(选项=选项值,) # 2.如果当前序列化器类继承的是ModelSerializer,则需要声明调用的模型信息 # class Meta: # model = 模型 # fields = [“字段1”,“字段2”] # # exlucde = ["pub_date"] # 排除哪些字段,注意fields和exclude不能同时使用 # model指明该序列化器处理的数据字段从模型类参考生成 # fields指明该序列化器包含模型类中的哪些字段,“__all__”指明包含所以字段 # 3.验证代码的对象方法 def validate(self,attrs):# validate是固定的 """验证所有字段""" pass return attrs # 必须返回 def validate_<字段名>(self,data):#方法名的格式必须以validate_<字段名为名称>,否则序列化器不识别! """验证单个字段""" if 不满足条件: # 在序列化器中,验证失败可以通过抛出异常的方式来告知 is_valid() raise serializer.ValidationError(detail="...",code="validate_name") return data #!!!必须返回,不然在最终的验证结果中,就不会出现这个数据了 # 4.模型操作的方法 # def create(self,validated_date): # 添加数据操作,添加数据以后,就自动实现了从字典变成模型对象的过程 # pass # # def update(self,instance,validate_data): # 更新数据操作,更新数据以后,就自动实现了从字典变成模型对象的过程 # pass
字段选项
id = serializer.IntegerField(read_only=True) # 在客户端提交数据【反序列化阶段不会要求id字段】
requied=True # 反序列化阶段必填
default=True # 反序列化阶段,客户端没有提交,则默认为True
max_value=100,min_value=0 #必须是0<=age<=100
allow_null=True,allow_blank=True # 允许客户端不填写内容(None),或者值为“”
error_messages: {
‘min_length’: ‘仅允许6-20个字符的密码’,
‘max_length’: ‘仅允许6-20个字符的密码’,
} # 自定义校验出错后的错误信息提示
validators=[check_name] #validators外部验证函数选项,值是一个列表,列表得到成员是函数名,不能是字符串
验证
validate_字段名
validate
validator:写在字段选项中
def check_name(data):
“”“外部验证函数”“”
if …:
raise
return data
from rest_framework import serializers from rest_framework response import Respose # 针对模型涉及序列化器 class UserSerializers(serializers.Serializer): # name = models.name date = model.pub_date # 括号中可以写传入数据的校验规则和数据库中对应序列化的字段,也可以将变量的名字直接写的和数据库中的字段名一样,即无需再指定source。 name = serializers.CharField(max_length=32) price = serializers.IntegerField(required=False) # 可以为空 date = serializers.DateField(source="pub_date") # 重写父类(ModelSerializer)的create方法,继承的是Serializer需要自己写 def create(self,validated_data): # 添加数据 new_user = User.objects.create(**self.validated_data) return new_user # 给self.instance赋值 # 重写父类(ModelSerializer)的update方法,继承的是Serializer需要自己写 def update(self,instance,validated_data): # 更新逻辑 user= User.objects.filter(pk=id).update(**serializer.validated_date) updated_book = Book.objects.get(pk=id) return updated_book # 给self.instance赋值
from django.urls import path,re_path
from .views import *
urlpatterns=[
# 序列化器
path("sers/user",UserView.as_view()),
re.path("sers/user/(\d+) ",UserDetailView.as_view())
]
不需要再自己重写create、update,因为ModelSerializer已经帮我们写了,而且更好
# serializers 是drf提供给开发者调用的序列化器模块,里面声明了所有的可用序列化器的基类: # Serializer 序列化基类,drf中所有的序列化器类都必须继承于Serializer # ModelSerializer 模型序列化器基类,是序列化器基类的子类,在工作中,除了Serializer基类以外,最常用的序列化器基类 # # 创建序列化器类,回头会在视图中被调用 # class UserModelSerializer(serializers.ModelSerializer): # date = serializers.DateField(source="pub_date") #自定义返回字段的key # nickname = serializers.CharField(read_only=True) # 数据库中没有的,序列化器中有的,只执行序列化。 # class Meta: # model = User # # fields = "__all__" # # exlucde = ["pub_date"] # 排除哪些字段,注意fields和exclude不能同时使用 # fields = ["id", "realname",'password'] # # model指明该序列化器处理的数据字段从模型类Bookinfo参考生成 # fields指明该序列化器包含模型类中的哪些字段,“all”指明包含所以字段
class AchievementModelSerializer(serializers.ModelSerializer):
class Meta:
model =Achievement
fields=["id","course_id"]
class StudentModelSerializer(serializers.ModelSerializer):
s_achievment = AchievementModelSerializer(many=True) # s_achievment是模型中声明的外键字段,非外键字段不能指定序列化器字段releated_name
class Meta:
model = Student
fields = ['id','s_achievment']
class AchievementModelSerializer(serializers.ModelSerializer):
course_name = serializer.CharField(source="course.name")
teacher_name = serializer.CharField(source="course.teacher.name")
class Meta:
model =Achievement
fields=["id","course_id","course_name","teacher_name"]
class AchievementModelSerializer(serializers.ModelSerializer):
class Meta:
model =Achievement
fields=["id","course_id"]
depth =1 #显示的外键深度1、2、3
class StudentModelSerializer(serializers.ModelSerializer): class Meta: model = Student fields = ['id','achievment'] # models.py class Student(models.Model): pass # 属性方法 @property def achievement(self): # return self.s_achievement.all() 不行,模型对象不是json # return self.s_achievement.values() return self.s_achievement.values("id","name","...")
1.使用序列化器的时候一定要注意,序列化器声明了以后,不会自动执行,需要我们在视图中进行调用才可以
2.序列化器无法接收数据,需要我们在视图中实例化序列化器对象时把使用的数据传递过来
3.序列化器的字段声明类似于我们前面使用过的模型
4.开发restful api时,序列化器会帮我们把模型对象转换为字典
5.如果数据从mysql中获取,那么就用modelSerializer,否则就用serializer
# post:变为request.data
# get : request.query_params
# django原生: request._request
REST framework提供了一个响应类Response,使用该类构造响应对象时,响应的具体数据内容会被转换(renderer渲染器)成符合前端需求的类型。
RESR framework提供了Renderer渲染器,用来根据请求中的Accept(接收数据类型声明)来自的转换响应数据对应格式。如果前端请求中未进行声明Accept,则会采用Content-Type方式处理响应数据,我们可以通过配置来修改响应格式。
可以在rest_framework.settings查找所有的drf默认配置项
REST_FRAMEWORK={
'DEFAULT_RENDERER_CLASSES':{#默认响应渲染类
'rest_framework.renderers.JSONRenderer', #json渲染器,返回json数据
'rest_framework.renderers.BrowsableAPIRenderer', # 浏览器API渲染器,返回调试界面。
}
}
增加了类属性
authentication_classes列表或元组,身份认证类
permission_classes列表或元组,权限检查类
throrrle_classes列表或元组,流量控制类
只需要重写两个变量:
queryset = Publish.objects.all() # 模型类queryset
serializer_class = PublishSerializers # 序列化器
新增一个表单提交
from rest_framework.mixins import …
CreateModelMixin,
ListModelMixin,
DestroyModelMixin,
RetrieveModelMixin,
UpdateModelMixin
视图子类是通用视图类和模型扩展类的子类:drf在使用GenericAPIView和Mixins进行组合以后,还提供了视图子类,提供了各种的视图方法调用mixins操作
ListAPIView = GenericAPIView + ListModelMixin
CreateAPIView = GenericAPIView + CreateModelMixin
RetrieveAPIView = GenericAPIView + RetrieveModelMixin
UpdateAPIView = GenericAPIView + UpdateModelMixin
DestroyAPIView = GenericAPIView + DestroyModelMixin
组合视图子类
ListCreateAPIView
RetrieveUpdateAPIView
RetrieveDestroyAPIView
RetrieveUpdateDestroyAPIView
1.路由的合并问题
2.get方法重复问题
ViewSet --->基本视图集 解决APIView中的代码重复问题
GenericViewSet ---> 通过是图集 解决APIView中的代码重复问题同时让代码更加通用
class DemoView(View): def get(self, request): """序列化""" # 1.获取数据集 studentqueryset_list = 模型.objects.all() # 2.实例化序列化器,得到序列化对象【传递到序列化器的模型对象如果是多个,务必使用many=True】 # 序列化时,传instance,反序列化传data, # 在构造serilizer对象时,还可通过context参数额外添加数据,通过context参数附加的数据,可以通过serializer对象的context属性获取 serializer = 序列化器(instance=studentqueryset_list,many=True,context={‘request’:request}) serializer = 序列化器(instance=studentqueryset_list, many=True) # 3. 调用序列化对象的data属性方法获取转换后的数据 data = serializer.data # 4. 响应数据(django原生的view:safe=False) def post(self,request): """反序列化,采用字段选项来验证数据,并写入数据库""" 1.接收客户端提交的数据 data = json.dumps(reqeust.body) 2.实例化序列化器,获取序列化对象 serializer = 序列化器(data=data) 3.调用序列化器的方法进行数据验证 ret = serializer.is_valid() # serializer.is_valid(raise_exception=True) 错误会自己抛出异常,正常就写return Response(serializer.validated_data) 4.获取结果,如果验证通过,就操作数据库,不通过就返回错误 if ret: serializer.save() #会根据实例化序列化器的时候,是否传入instance属性来自动调用create或者update方法。传入instance属性,自动调用update方法;没有传instance属性,则调用create。 # serializer.save(ower=request.user) 可以在save中,传递一些不需要验证的数据到模型里面 requset.user是django记录当前登录用户的模型对象 return Response(serializer.validated_data) else: return Response(serializer.errors) def put(self,request): ""反序列化,验证成功后,更新数据入库" 1.根据客户端访问的url地址,获取pk值,查找数据库中的数据 student = filter(id=pk) 2.获取客户端提交的数据 data 3.修改操作中的实例化序列化器对象 serializer = 序列化器(instance=student,data=data)# 只更新name,不需要验证其他的字段,可以设置(instance=student,partial=True) 4.验证数据 serializer.is_valid() 5.入库 serializer.save() # 不是模型类的save()
from rest_framework.views import APIView class Userview(APIView): def get(self,request): # 获取所有 users = User.objects.all() # queryset # 构建序列化对象(如果是多个对象,那么many=True)(istance是序列化,data是反序列化) serializer = UserSerializers(instance=users,many=True) return Response(serializer.data) def post(self,request): # 获取请求数据 # 构建序列化器对象(转化为model模型类queryset) serializer = UserSerializers(data=request.data) # 校验数据 if serializer.is_valid(): #返回一个布尔值,所有字段皆通过才返回True。正确的数据会保存在serializer.validated_data,错误的字段key和错误信息会存入serializer.errors # 数据校验通过,将数据插入到数据库中 # new_user = User.objects.create(**serializer.validated_data) # 但是这样的话代码有耦合性 serializer.save() # 会调用serializer父类(UserSerializers)重写的create函数,其中返回值就是instance return Response(serializer.data) pass else: # 校验失败 return Response(serializer.errors) class UserDetailview(APIView): def put(self.request,id): # 获取提交数据 # 构建序列化对象 serializer = UserSerializers(instance=update_book,data=reqeust.data) # 两个都传,因为这样save()的话,instance不为空,那么就会走update方法,为空会走create方法 if serializer.is_valid(): # 更新逻辑 #user= User.objects.filter(pk=id).update(**serializer.#validated_date) #updated_book = Book.objects.get(pk=id) #serializer.instance = updated_book # 没有这句话,返回的是旧的serializer的instance serializer.save() return Response(serializer.date) # 针对seializer.instance序列化 else: return Response(serializer.errors) def delete(self,request,pk): userdetailquery_list = User.objects.filter(id=pk) userdetailquery_list.delete() return Response(status=status.HTTP_204_NO_CONTENT)
from rest_framework.generics import GenericAPIView class PublishSerializers(serializers.ModelSerializer): class Meta: model = Publish fields = "__all__" class PublishView(GenericAPIView): # 这两个的变量名不能变,因为GenericAPIView写了关于这两个变量的方法,写其他的表方法,只需要改变这两个变量的值就行了。 ## 1.指定查询集 ## 2.指定序列化器 queryset = Publish.objects.all() # 模型类queryset serializer_class = PublishSerializers # 序列化器 def get(self,request): serializer = self.get_serializer(instance=self.get_queryset(),many=True) return Response(serializer.data) # 针对serializer.instance def post(self,request): # 获取请求数据 # 构建序列化器对象(转化为model模型类queryset) serializer = self.get_serializer(data=request.data) # 校验数据 if serializer.is_valid(): #返回一个布尔值,所有字段皆通过才返回True。正确的数据会保存在serializer.validated_data,错误的字段key和错误信息会存入serializer.errors # 数据校验通过,将数据插入到数据库中 # new_user = User.objects.create(**serializer.validated_data) # 但是这样的话代码有耦合性 serializer.save() # 会调用serializer父类(UserSerializers)重写的create函数,其中返回值就是instance return Response(serializer.data) pass else: # 校验失败 return Response(serializer.errors)
# url的有名分组,根据pk分组 re.path(r"^sers/user/(?P<pk>\d+)$ ",UserDetailView.as_view()) # re.path(r"^sers/user/(?P<title>\d+)$ ",UserDetailView.as_view()) class PublishDetailview(GenericAPIView): query = Publish.objects.all() # 模型类queryset serializer_class = PublishSerializers # 序列化器 lookup_field = "title" # 可以作为get_object()的对象。 def get(self,reqeust,pk): # self.get_object()在源代码中,等于 # try: user = User.objects.get(pk=pk) # except User.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) serializer =self.get_serializer(instance=self.get_object(),many=False) return Response(serializer.data) def put(self.request,pk): # 1.根据pk获取模型对象 # 2.获取客户端提交的数据 # 3.反序列化保存 # 4.返回 serializer = self.get_serializer(instance=self.get_object(),data= reqeust.data) if serializer.is_valid(): serializer.save() return Response(serializer.data) else: return Response(serializer.errors) def delete(self,request,pk): self.get_object().delete() return Response()
from rest_framework.generics import GenericAPIView from rest_framework.mixins import ListModelMixin,CreateModelMixin,UPdateModelMixin,DestroyModelMixin class PublishDetailview(GenericAPIView,listModelMixin,CreateModelMixin): query = Publish.objects.all() # 模型类queryset serializer_class = PublishSerializers # 序列化器 def get(self,request): return self.list(request) def post(self,request): return self.create(request) class PublishDetailview(RetrieveModelMixin,UpdateModelMixin,DestroyModelMixin,GenericAPIView): query = Publish.objects.all() # 模型类queryset serializer_class = PublishSerializers # 序列化器 def get(self,request,pk) return self.retrieve(request,pk) def put(self,request,pk) return self.update(request,pk) def delete(self,request,pk) return self.destroy(request,pk)
class PublishDetailview(ListCreateAPIView):
queryset = Publish.objects.all() # 模型类queryset
serializer_class = PublishSerializers # 序列化器
# RetriveUpdateAPIView代表检索、更新,
# RetriveUpdateDestroyAPIView代表检索、更新、删除
class PublishDetailview(RetrieveUpdateAPIView):
queryset = Publish.objects.all() # 模型类queryset
serializer_class = PublishSerializers # 序列化器
path("sers/publish",PublishView.as_view({"get":"get_all","post":"add"})),
re_path("sers/publish/(?<pk>\d+)"),PublishView.as_view({"get":"get_one"})
class PublishView(ViewSet):
def get_all(self,request):
pass
def add(self,request):
pass
def get_one(self,request,pk):
pass
def update(self,request,pk):
pass
def delete(self,request,pk):
pass
path("sers/publish",PublishView.as_view({"get":"list","post":"create"})),
re_path("sers/publish/(?<pk>\d+)"),PublishView.as_view({"get":"retrieve",""....})
class PublishView(genericviewset、listmodelmixin,createmodelmixin........):
queryset = Publish.objects.all() # 模型类queryset
serializer_class = PublishSerializers # 序列化器
ModelViewSet
# from rest_framework.viewsets import ReadOnlyModelViewSet
#ReadOnlyModelViewSet
class UserModelViewSet(ModelViewSet):
queryset = User.objects.all()
serializer_class = UserModelSerializer
注意:基于视图类使用的!!
### 这样等于上面写的url路由
# from rest_framework.routers import DefaultRouter
# router = DefaultRouter() # 可以处理视图的路由器
# SimpleRouter()少一个 api.root 的urlpatterns
# router.register("user2",UserModelViewSet,basename="user2") # 向路由器中注册视图集
# urlpatterns += router.urls 将路由器中的所有路由信息追加到django的路由列表中
在视图集中附加action的声明:
在视图集中,如果想要让Router自动帮助我们为自定义的动作生成路由信息,需要使用rest_framework.decorators.action 装饰器。
以action装饰器装饰的方法名会作为action动作名,与list、retrieve等同。
action装饰器可以接收两个参数:
# 路由对象给视图集生成路由信息时,只会生成5个基本api接口,这主要时router只识别5个混入类的原因。
# 而针对我们开发者自定义的视图方法,路由对象不会自动生成路由信息
# 所以下面这个login,如果希望被外界访问到,则必须通过action装饰器告诉路由对象要给她生成一个路由信息。
@action(methods=["get","post"],detail=False,url_path="login")
# methods,列表,指定允许哪些http请求方法可以访问当前视图方法
# detail ,bool值,告诉路由对象在生成路由信息时,是否要自动生成pk值,True表示需要,False表示不需要
# url_path, 字符串,访问视图的url地址,如果不设置,则默认采用方法名作为访问后缀
def login(self.request):
"""登录视图"""
return Response(xxx)
认证权限和限流在dispatch之前完成
# 在视图类中配置的是局部优先的 1
# 在settings中配置的是全局的 2
# 不配置是默认的 3
只对genericapiview及其继承的子类有作用,apiview需要自己写
REST_FRAMEWORK={
'DEFAULT_PAGINATION_CLASS':'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE':8 # 每页数目
}
class SetPagination(PageNumberPagination)
page_size = 2 # 默认每页显示多少条数据
max_page_size = 5 # 前端在控制每页显示多少条时,最多不超过5
# page_query_param = 'page' # 前端在查询字符串的关键字,指定显示第几页,默认是page
page_size_query_param ='page_size' # 前端在查询字符串关键字名字,是用来控制每页显示多少条
class BookView(ModeiViewSet):
# 指定分页类
# pagination_class = None 关闭分页功能
pagination_class = SetPagination
# 下载
pip install django-filter
# 配置文件中增加过滤后端的设置
INSTALLED_APPS=[
...,
'django_filters', # 需要注册应用
]
## 指定过滤后端
REST_FRAMEWORK= {
'DEFAULT_FILTER_BACKENDS':['django_filters.rest_framework.DjangoFilterBackend',]
}
# 在视图中添加filter_fields属性,指定可以过滤的字段
class BookView(ModeiViewSet):
# 过滤
filterset_fields=('pk','name')
pip install coreapi
# 指定用于支持coreapi的Schema
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
from rest_framework.documentation import include_docs_urls
url(r'^docs/',include_docs_urls(title="API文档"))
# 建立 python3.7 环境 FROM python:3.10.1 # 镜像作者sobermh MAINTAINER sobermh # 设置容器内工作目录 WORKDIR /home # 将项目文件拷贝到工作目录下(拷贝的主机内容,最好是相对路径,绝对路径会有报错 ) COPY ./djangoProject /home/djangoProject # 安装项目所包含的库 # pip install upgrade RUN cd /home/djangoProject \ && pip install --upgrade pip \ && pip freeze >requirement.txt \ && pip install -r requirement.txt # 在容器内安装django # RUN pip install Django==4.0.4 -i https://pypi.douban.com/simple/ # 在容器内安装uwsgi RUN pip install uwsgi -i https://pypi.douban.com/simple/ #对外暴露端口 EXPOSE 80 8080 8000 5000 # #用uwsgi运行django项目(这里运行的进程不要以后台运行比如nohup,uwsgi -d等) # #为什么不要用后台方式运行? 因为后台运行方式启动,容器会认为服务没起来,从而导致容器停止 # ENTRYPOINT uwsgi /home/djangoProject/uwsgi.ini # docker build -f ./dockerfile -t python:1.0 . # docker run --name 容器name -d 镜像name [uwsgi] # 使用Nginx连接时使用,Django程序所在服务器地址(docker + nginx uwsgi_pass 127.0.0.1:8000; include uwsgi_params; 只能通过nginx转发才能访问) #socket=0.0.0.0:8000 # 直接做web服务器使用,Django程序所在服务器地址(proxy_pass http://127.0.0.1:8000;nginx转发端口和直接宿主机ip端口都可以访问) http=0:5000 # 项目目录,manage.py 同级目录 chdir=/home/djangoProject # 项目中wsgi.py文件的目录,相对于项目目录 wsgi-file=/home/djangoProject/djangoProject/wsgi.py # 进程数 processes=2 # 线程数 threads=2 # uwsgi服务器的角色 master=True # 存放进程编号的文件 pidfile=uwsgi.pid # 日志文件 daemonize=uwsgi.log # 指定依赖的虚拟环境 #virtualenv=/home/python/Python-3.8.6/bin/crm_test/ # 静态文件,先执行python manage.py collectstatic ,setting中指定静态文件STATIC_ROOT=os.path.join(BASE_DIR, 'static') #static-map=/static=/home/python/crm_django/crm_management/static #uwsgi --ini uwsgi.ini 启动uwsgi #uwsgi --stop uwsgi.pid 关闭uwsgi 若报错,执行ps -ef|grep uwsgi 将pid写进uwsgi.pid再执行 #uwsgi --reload uwsgi.pid 重启uwsgi 若报错,执行ps -ef|grep uwsgi 将pid写进uwsgi.pid再执行,注意只有开启uwsgi才能重启
单独放在服务器上
和django项目放在一个docker容器中,需要把文件挂载出来,因为docker关闭,文件就丢失了
单独放在一个docker容器中,需要配置网络,才可以让另一个容器访问到,且文件最好挂载出来
一.进入mysql官网进行mysql服务器的下载
二.使用压缩包进行安装
1.wget 压缩包下载位置(或传入压缩包)
2.解压:tar -zxvf 压缩包 -C /usr/local/
3.重命名:mv /usr/local/解压后的名字 /usr/local/mysql
登录mysql并开启远程连接
:见csdn博客
# 先查看当前python的指向 ls -l /usr/bin | grep python 1.wget python官网的指定版本压缩包的下载地址 $ wget https://www.python.org/ftp/python/3.10.11/Python-3.10.11.tgz 2.解压 tar -zxvf 压缩包 -C /usr/local sudo tar -zxvf Python-3.10.11.tgz -C /usr/local 3.检查安装依赖:设置编译参数,即输出文件目录: sudo ./configure --prefix=/usr/local/Python-3.10.11 --enable-optimizations 4.预编译&编译与安装 make && make install sudo make 、make install、(出现问题就使用:sudo make altinstall) 5.修改pip源 在家目录(~)创建文件夹.pip,然后创建文件pip.conf ~/.pip/pip.conf [global] index-url = https://pypi.tuna.tsinghua.edu.cn/simple 6.删除之前的python和pip的软链接 # 删除python软链接 $ rm -rf /usr/bin/python # 删除pip软链接 $ rm -rf /usr/bin/pip 7.添加软链接(快捷访问方式) ln -s /usr/local/Python-3.10.11/bin/python3.10 /usr/bin/python ln -s /usr/local/python3/bin/pip3 /usr/bin/pip3 8.如果没有下载到pip sudo apt-get install python3-pip
进入docker官网安装服务器环境进行安装
docker images 查看docker镜像
# 建立 python3.7 环境 FROM python:3.10.1 # 镜像作者sobermh MAINTAINER sobermh # 设置容器内工作目录 WORKDIR /home # 将项目文件拷贝到工作目录下(拷贝的主机内容,最好是相对路径,绝对路径会有报错 ) COPY ./djangoProject /home/djangoProject # 安装项目所包含的库 # pip install upgrade RUN cd /home/djangoProject \ && pip install --upgrade pip \ && pip freeze >requirement.txt \ && pip install -r requirement.txt # 在容器内安装django # RUN pip install Django==4.0.4 -i https://pypi.douban.com/simple/ # 在容器内安装uwsgi RUN pip install uwsgi -i https://pypi.douban.com/simple/ #对外暴露端口 EXPOSE 80 8080 8000 5000 # #用uwsgi运行django项目(这里运行的进程不要以后台运行比如nohup,uwsgi -d等) # #为什么不要用后台方式运行? 因为后台运行方式启动,容器会认为服务没起来,从而导致容器停止 # ENTRYPOINT uwsgi /home/djangoProject/uwsgi.ini # docker build -f ./dockerfile -t python:1.0 . # docker run --name 容器name -d 镜像name
在项目同级目录下:vim uwsgi.ini
[uwsgi] # 使用Nginx连接时使用,Django程序所在服务器地址(docker + nginx uwsgi_pass 127.0.0.1:8000; include uwsgi_params; 只能通过nginx转发才能访问) #socket=0.0.0.0:8000 # 直接做web服务器使用,Django程序所在服务器地址(proxy_pass http://127.0.0.1:8000;nginx转发端口和直接宿主机ip端口都可以访问) http=0.0.0.0:5000 # 项目目录,manage.py 同级目录 chdir=/home/djangoProject # 项目中wsgi.py文件的目录,相对于项目目录 wsgi-file=/home/djangoProject/djangoProject/wsgi.py # 进程数 processes=2 # 线程数 threads=2 # uwsgi服务器的角色 master=True # 指定socker文件,但是我没用这个也可以启动,所以猜测可能是用nginx启动时才需要吧 # socket = uwsgi.sock # 存放进程编号的文件 pidfile=uwsgi.pid # 日志文件 daemonize=uwsgi.log # 指定依赖的虚拟环境 #virtualenv=/home/python/Python-3.8.6/bin/crm_test/ # 静态文件,先执行python manage.py collectstatic ,setting中指定静态文件STATIC_ROOT=os.path.join(BASE_DIR, 'static') #static-map=/static=/home/python/crm_django/crm_management/static #uwsgi --ini uwsgi.ini 启动uwsgi #uwsgi --stop uwsgi.pid 关闭uwsgi 若报错,执行ps -ef|grep uwsgi 将pid写进uwsgi.pid再执行 #uwsgi --reload uwsgi.pid 重启uwsgi 若报错,执行ps -ef|grep uwsgi 将pid写进uwsgi.pid再执行,注意只有开启uwsgi才能重启
sudo scp /home/** root@47.77.66.66:/home/XXX
先走wsgi再走中间件
)注意大小写
)特殊说明:django的setting.py需要关闭DEBUG模式,ALLOWED_HOST改为['网站域名']或['服务监听的ip地址']
uWSGI常见问题
正向代理:为了从目标服务器取得内容,客户端向代理服务器发送一个请求,并且指定目标服务器,之后代理向目标服务器转发请求,将获得的内容返回给客户端。正向代理是代理客户端,为客户端收发请求,使真实客户端对服务器不可见。
反向代理:客户端访问代理服务器,代理服务器从提供内容的服务器获取内容,并返回给客户端,这就是反向代理,对客户端完全透明。好处:一个端口,代理多个服务,达到负载均衡的效果。反向代理是代理服务器,为服务器收发请求,使真实服务器对客户端不可见。
负载均衡:将负载较为平均分配到各服务器。
1.先下载nginx
/etc/nginx/nginx.conf
# 需要实现负载均衡时 # 负载均衡的服务器,写在server外面 # upstream ego { # server 192.168.30.44:3333; # server 192.168.30.43.:3355; # } server{ listen 3355 ;# 监听端口号 server_name 0.0.0.0; # 访问ip charset utf-8 # nginx编码 # 指定项目路径uwsgi location / { include uwsgi_params; # 导入一个nginx模块用来和uwsgi进行通讯 # 指定uwsgi的sock文件,所有动态请求都会直接丢给它 uwsgi_pass unix:/home/uwsgi.sock uwsgi_read_timeout 30 # 设置连接uwsgi超时时间 # 需要实现负载均衡时 # proxy_pass http://ego } # 指定静态文件路径 location /static { alias /home/static/ } }
nginx是轻量级的高性能web服务器,提供了诸如http代理和反向代理、负载均衡等一系列中重要特性。c语言编写,执行效率高
vim etc/apt/sources.list
更改国内源
sudo apt-get update
#在server 节点中添加新的location项,指向uwsgi的ip端口。
将try_file $uri $uri/ =404 #####
server {
...
location / {
uwsgi_pass 127.0.0.1:8000; #重定向到127.0.0.1的8000端口
include / etc/nginx/uwsgi_params; #将所有的参数转到uwsgi下
}
...
}
[uwsgi]
# 去掉如下
# http= 127.0.0.1:8000
# 改为
socket =127.0.0.1:8000
STATIC_ROOT = '/home/项目_static/static'
#注意 此配置路径为存放所有正式环境中需要的静态文件
# file :/etc/nginx/sites-enabled/default
# 新添加location /static 路由配置,重定向到指定的 第一步创建的路径即可
server{
...
location /static{
#root 第一步创建文件夹的绝对路径,如:
root /home/项目_static;
}
...
}
# 关闭调试模式
DEBUG = False
#错误报告接收方
ADMINS = [('guoxiaonano','XXXX@qq.com'),('dsad','dsad@qq.com')]
#发送错误报告方,默认是root@localhost账户,所以要修改
SERVER_EMAIL = 'email配置中的邮箱'
- 过滤敏感信息(局部变量)
```
from django.views.decorators.debug import sensitive_variables
@sensitive_variables('user','pw','cc')
def process_info(user):
pw = user.pass_word
cc = dsa
```
说明:
1.局部变量的值会被替换成****
2.多个装饰器时,需要将其放在最顶部
3.若不传参,则过滤所有局部变量
django=admin startproject cloud_note
- 配置常规配置项
- 禁止csrf[post提交403问题]
- 语言、时区
- 数据库配置
python manage.py startapp user
create database wx_wcx default charset utf8;
python manage.py runserver 3344
conda create -n store python=3.10
conda activate store
pip install django
django-admin startproject store
python manage.py runserver 0.0.0.0:9999
pip freeze > requirements.txt
pip install -r requirements.txt
.idea/ db.sqlite3 #bin/: 忽略当前路径下的bin文件夹,该文件夹下的所有内容都会被忽略,不忽略 bin 文件 #/bin: 忽略根目录下的bin文件 #/*.c: 忽略 cat.c,不忽略 build/cat.c #debug/*.obj: 忽略 debug/io.obj,不忽略 debug/common/io.obj 和 tools/debug/io.obj #**/foo: 忽略/foo, a/foo, a/b/foo等 #a/**/b: 忽略a/b, a/x/b, a/x/y/b等 #!/bin/run.sh: 不忽略 bin 目录下的 run.sh 文件 #*.log: 忽略所有 .log 文件 #config.php: 忽略当前路径的 config.php 文件 ## 为注释 #*.txt #忽略所有.txt结尾的文件 #!lib.txt #但lib.txt除外 #/temp # 仅忽略项目根目录下的TODO文件,不包括其他目录temp #build/ # 忽略build/目录下的所有文件 #doc/*.txt # 会忽略doc/noes.txt 但不包括doc/server/arch.txt
由于在开发环境用的是manage.py 启动环境,而生产环境使用的是uwsgi部署之后,使用的是wsgi.py启动环境。两个启动方式的settings的文件相同,所以重新设置启动的settings文件路径
- settings
- dev.py
- prod.py
# 将setting的文件复制到上面上个文件之后,删除原来的setting文件,然后
manage.py中修改为:os.environ.setdefault("DJANGO_SETTINGS_MODULE", "store.settings.dev")
wsgi.py中修改为:os.environ.setdefault("DJANGO_SETTINGS_MODULE", "store.settings.prod")
pip install djangorestframework
# 注册应用
INSTALLED_APPS = [
...
'rest_framework', # DRF]
# 连接数据库
mysql -uroot -P
create database store charset =utf8mb4;
# 为本项目创建数据库用户(不使用root账户)
# 创建不成功可能是用户已经存在,(可以使用drop user 'sober'@'%';删除用户)
create user sober identified by '123456';
# 给sober授权所有的store库的权限,且在所有的ip上都可以连接
grant all on store.* to 'sober'@'%';
flush privileges;
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'store', 'USER': 'root', 'PASSWORD': '123456', 'HOST': '47.97.118.247', 'POST': '3306' } } # 报错errror:django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module. pip install pymysql # 在store-store-__init__.py中写入 import pymysql pymysql.install_as_MySQLdb() # 报错errror:'cryptography' package is required for sha256_password or caching_sha2_password auth methods pip install cryptography
pip install django-redis # redis配置 CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://127.0.0.1:6379/0", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", "CONNECTION_POOL_KWARGS": {"max_connections": 1000}, "password": "" # redis密码 } }, ## session引擎 "session": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://127.0.0.1:6379/1", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", "CONNECTION_POOL_KWARGS": { "max_connections": 1000, "encoding": 'utf-8' }, "password": "" # redis密码 } }, } # session设置 # 设置admin用户登录时,登录信息session保存到redis(加上下面两句,才能生效) SESSION_ENGINE = "django.contrib.sessions.backends.cache" # 配置生效 SESSION_CACHE_ALIAS = "session" # REDIS_TIMEOUT = 7 * 24 * 60 * 60 # CUBES_REDIS_TIMEOUT = 60 * 60 # NEVER_REDIS_TIMEOUT = 365 * 24 * 60 * 60
# 配置日志系统 LOGGING = { 'version': 1, # 指明dictConfig的版本 'disable_existing_loggers': False, # 设置已存在的logger不失效 # 'incremental': True, # 默认为False。True:是将配置解释为现有配置的增量。False:配置会覆盖已有默认配置。一般此项不用配置 'filters': { 'require_debug_true': { # django在debug模式下才输出 '()': 'django.utils.log.RequireDebugFalse', }, }, # 'filters': { # 过滤器:用于提供对日志记录从logger传递到handler的附加控制 # # 'require_debug_true': { # # '()': 'django.utils.log.RequireDebugTrue', # # }, 'formatters': { # 格式器 'standard': { 'format': '[%(asctime)s][%(levelname)s][%(filename)s:%(lineno)d:%(funcName)s]:%(message)s', 'datefmt': '%Y-%m-%d %H:%M:%S' }, 'simple': { 'format': '[%(asctime)s][%(levelname)s]:%(message)s', 'datefmt': '%Y-%m-%d %H:%M:%S' # style:'%','{' 或 '$',3选一: # # '%':默认是这个,使用python的%格式化 , 如: %(levelname)s # # '{':使用 str.format格式化(django框架使用这个), 如:{levelname} # # '$':使用类string.Template格式化,如:\$levelname # # datefmt:日期格式化字符串,为None则使用ISO8601格式化,如:'2010-01-01 08:03:26,870' # # %(name)s:记录器logger的名称 # # %(levelno)s:日志级别对应的数字 # # %(levelname)s:日志级别名称 # # %(pathname)s:日志记录调用的源文件的完整路径 # # %(filename)s:日志记录调用的源文件名 # # %(module)s:模块名 # # %(lineno)d:日志调用的行数 # # %(funcName)s:函数名 # # %(created)f:日志创建时间,time.time() # # %(asctime)s:日志创建时间,文本类型 # # %(msecs)d:日志创建时间的毫秒部分 # # %(relativeCreated)d:日志创建时间 - 加载日志模块的时间 的 毫秒数 # # %(thread)d:线程ID # # %(threadName)s:线程名 # # %(process)d:进程ID # # %(processName)s:进程名 # # %(message)s:日志消息 } }, 'handlers': { # 处理器 'console': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', 'formatter': 'simple' 'filters': ['require_debug_true'], }, 'default': { 'level': 'DEBUG', 'class': 'logging.handlers.RotatingFileHandler', 'filename': os.path.join(os.path.dirname(BASE_DIR), 'log/info.log'), # 日志文件的位置 'maxBytes': 1024 * 1024 * 50, # 日志大小50M 'backupCount': 5, 'formatter': 'standard', 'encoding': 'utf-8', }, # 处理程序类的名称 # # StreamHandler:输出到stream,未指定则使用sys.stderr输出到控制台 # # FileHandler:继承自StreamHandler,输出到文件,默认情况下,文件无限增长 # # 初始化参数:filename,mode ='a',encoding = None,delay = False # # delay如果为True,那么会延迟到第一次调用emit写入数据时才打开文件 # # RotatingFileHandler:自动按大小切分的log文件(常用) # # 初始化参数:filename,mode ='a',maxBytes = 0,backupCount = 0,encoding = None,delay = False # # maxBytes:最大字节数,超过时创建新的日志文件,如果backupCount或maxBytes有一个为0,那么就一直使用一个文件 # # backupCount:最大文件个数,新文件的扩展名是指定的文件后加序号".1"等,譬如:backupCount=5,基础文件名为:app.log, # # 那么达到指定maxBytes之后,会关闭文件app.log,将app.log重命名为app.log.1,如果app.log.1存在,那么就顺推, # # 先将 app.log.1重命名为app.log.2,再将现在的app.log命名为app.log.1,最大创建到app.log.5(旧的app.log.5会被删除), # # 然后重新创建app.log文件进行日志写入,也就是永远只会对app.log文件进行写入。 # # TimedRotatingFileHandler:按时间自动切分的log文件,文件后缀 %Y-%m-%d_%H-%M-%S # # 初始化参数:filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False, atTime=None # # when:时间间隔类型,不区分大小写 # # interval:间隔的数值 # # backupCount: 文件个数 # # encoding:编码 # # delay:True是写入文件时才打开文件,默认False,实例化时即打开文件 # # utc:False则使用当地时间,True则使用UTC时间 # # atTime:必须是datetime.time实例,指定文件第一次切分的时间,when设置为S,M,H,D时,该设置会被忽略 # # SMTPHandler:通过email发送日志记录消息 # # 初始化参数:mailhost, fromaddr, toaddrs, subject, credentials=None, secure=None, timeout=5.0 # # mailhost:发件人邮箱服务器地址(默认25端口)或地址和指定端口的元组,如:('smtp.163.com', 25) # # fromaddr:发件人邮箱 # # toaddrs:收件人邮箱列表 # # subject:邮件标题 # # credentials:如果邮箱服务器需要登录,则传递(username, password)元组 # # secure:使用TLS加密协议 }, 'loggers': { # 记录器 'django': { 'handlers': ['console', 'default'], 'level': 'INFO', 'propagate': True, }, # # level:指定记录日志的级别,没有配置则处理所有级别的日子 # # propagate:设置该记录器的日志是否传播到父记录器,不设置则是True # # filters:指定过滤器列表 # # handlers:指定处理器列表 # # 'django.request': { # django记录器的子记录器,处理ERROR级别及以上的日志,propagate设置为 False, # # # 表明不传播日志给 "django",该logger传递日志到mail_admins控制器 # # 'handlers': ['mail_admins'], # # 'level': 'ERROR', # # 'propagate': False, # # }, }, }
# 在utils文件夹下新建一个exceptions.py的文件 import logging from django.db import DatabaseError from redis.exceptions import RedisError from rest_framework.response import Response from rest_framework import status from rest_framework.views import exception_handler as drf_exception_handler # 获取在配置文件中定义的logger,用来记录日志 logger = logging.getLogger('django') def exception_handler(exc,context): # 调用drf框架原生的异常处理方法pi response = drf_exception_handler(exc,context) if response is None: view = context['view'] if isinstance(exc,DatabaseError) or isinstance(exc,RedisError): # 数据库异常 logger.error('[%s]%s'%(view,exc)) response = Response({'msg':'服务器内部错误'},status=status.HTTP_507_INSUFFICIENT_STORAGE) return response
# DRF配置
REST_FRAMEWORK = {
# 异常处理
'EXCEPTION_HANDLER' : 'utils.exceptions.exception_handler'
}
# 下载live-server
npm install -g live-server
# 进入前端html文件夹
cd front_end_pc
# live-server
# manage.py同级目录下新建一个apps放子应用
cd apps
python ../manage.py startapp users
# 注册apps
print(sys.path) # 查看导包路径
INSTALLED_APPS = [
....
'apps.users',
]
# 修改apps.users.apps.py 的name = "apps.users"
# 对导包路径进行操作
sys.path.insert(0,os.path.join(BASE_DIR.parent,'apps')) # 向第一个位置插入导包路径,防止过多的递归带来的加载过慢
print(os.path.join(BASE_DIR.parent,'apps'))
print(sys.path)
# user的模型
from django.db import models
from django.contrib.auth.models import AbstractUser
# Create your models here.
class User(AbstractUser):
"""自定义用户模型类"""
mobile = models.CharField(max_length=11,unique=True,verbose_name="手机号")
class Meta: #配置数据库表名,及设置配置在admin站点显示的中文名
db_table = 'users_user'
verbose_name = '用户'
verbose_name_plural = verbose_name
sys.path.insert(0,os.path.join(BASE_DIR.parent,'apps')) # 向第一个位置插入导包路径,防止过多的递归带来的加载过慢
# 修改Django认证系统的用户模型类 应用.模型名
AUTH_USER_MODEL = 'users.User'
# ValueError: Dependency on app with no migrations: users
这个代表已经将Django认证系统的用户模型类修改为自己自定义的user类了
python ..manage.py makemigrations
python ..manage.py migrate
同源 ---- 协议:ip/域名:端口
如:http://127.0.0.1:8080 ----> 请求源 http://127.0.01:8000 跨域 请求方法就是options
跨域是因为浏览器
默认遵守同源策略
(后端不存在跨域,跨域是浏览器请求,接受不到响应)。
NO “Access-Control-Allow- Origin:*” 使用cors(跨域资源共享)解决办法
pip install django-cors-headers # 设置settings INSTALLED_APPS = [ 'corsheaders', # 解决跨域 ] MIDDLEWARE = [ "corsheaders.middleware.CorsMiddleware", # 解决跨域,必须放最前面" ] # 跨域配置 # ORS_ORIGIN_WHITELIST = [ #只有localhost:8000才可以跨域访问 # # 'localhost:8000' # ] CORS_ALLOW_CREDENTIALS = True # 运行跨域携带cookie CORS_ORIGIN_ALLOW_ALL = True # 所有域名都可以跨域访问
1.编写路由
2.编写视图函数
(1) 连接redis,判断60s内是否已经发过短信了
(1) 生成验证码
(2) 连接redis,存入验证码
(3) 调用aliyun的func发送短信
3.解决用户60s刷新页面,导致的频繁发送短信
通过在redis中对手机号存一个标记,过期时间为60秒,在发送短信之前验证,,如果存在该标记,直接返回错误。不存在,则执行下面的代码
4. 创建一个constants.py文件,存放此子应用所有的常量
使用redis的管道减少redis的操作次数,一般只对写入操作用管道技术,查看操作不用管道
(将redis连接操作存入管道中,再一起执行)# 创建redis管道
pl = conn.pipeline()
pl.set(mobile, str_sms_code, SMS_CODE_REDIS_EXPIRES)
# 存储一个标记,表示此手机号已发送过短信,有效期为60s
pl.setex('send_flag_%s' % mobile, SMS_CODE_SEND_FLAG_INTERVAL, 1)
# 执行管道
pl.execute()
类似于消费者生产者模式
1.新建python文件夹
celery_tasks
-sms文件夹 异步任务包
-tasks.py (Mandatory name)任务会自动找tasks的文件
-config.py celery配置文件(arbitrary name)
-main.py celery启动文件(arbitrary name)
## 启动!!!!!!!
# celery -A celery_tasks.main worker -l info
# -A add
# -l 输出日志级别
## windows注意:如果是windos系统需要先安装pip install eventlet ,然后celery -A celery_tasks.main worker -l info -P eventlet
pip install djangorestframework-jwt
但是好像用simple-jwt好一些
session和cookies不适合前后端分离,且多服务器的系统。(容易收到CSRF攻击,以及session验证不方便)
header+payload+signature
echo -n xxxxx | base64 -D
解密base64加密的内容
对于signature中的secret,该私有出现泄露,马上更换,最好半夜的时候换。
local storage永远都在
session storge关闭页面就消失了
localStorage.a = 20 # 存储
localStorage.a # 取值
使用simplejwt进行状态保持
https://blog.csdn.net/qq_52385631/article/details/126840331
from django.core.mail import send_mail
from djangoProject.settings import EMAIL_HOST_USER
# send_mail的参数分别是 邮件标题,邮件内容,发件箱(settings.py中设置过的那个),收件箱列表(可以发送给多个人),失败静默(若发送失败,报错提示我们)
send_mail(subject,message,from_email,recipient_list,html_message=None)
message:普通邮件正文,普通字符串
html_message 多媒体邮件正文,可以时html字符串
send_mail('authcode', str_email_code, EMAIL_HOST_USER,
[revicename], fail_silently=False)
1.发送激活邮箱的url(‘http://127.0.0.1:8080/success_verify_email.html?access=’ + access)
2.根据点击url,对access进行解码处理,找到jwt中封装的email和id
3.去数据库查找是否存在该用户,并激活邮箱的状态。
1.先成为开发者
2.申请appid和appkey
3.获取access token
4.获取到用户的openid
全部放在一个表,用自关联外键的方式处理
# 直接导入sql文件
mysql -uroot -p123456 store < areas.sql
用用户查询一次,就将结果保存到redis中,后面的用户直接去redis中找,减少I/O缓存,
pip install drf-extensions
1.可以使用装饰器
@cache_response(timeout=60*60,cache='default')
如果未指明timeout和cache参数,则会使用配置文件中的默认配置
## DRF扩展
REST_FRAMEWORK_EXTENSIONS = {
# 缓存时间
'DEFAULT_CACHE_RESPONSE_TIMEOUT':60*60,
# 缓存存储
'DEFAULT_USER_CACHE':'default',
}
注意:cache_response装饰器都是装饰在get方法上的,既可以装饰在类试图的get方法上,也可以装饰在REST FRAMEWORK扩展类提供的list或retrieve方法上。且不需要使用method_decorator进行转换
2.使用drf-extensions提供的扩展类
from rest_framework_extentions.cache.mixins
ListCacheResponseMiXin
#用于缓存返回列表数据的视图,与ListModelMixin扩展类配合使用,实际是为list方法添加了cache_response装饰器
RetrieveCacheResponseMiXin
#用于缓存返回列表数据的视图,与RetrieveModelMixin扩展类配合使用,实际是为retrieve方法添加了cache_response装饰器
CacheResponseMixin
# both上者
# 继承的CacheResponseMixin需要放在最前面
class AreaListView(CacheResponseMixin,ReadOnlyModelViewSet):
pass
FastDFS是用c语言编写的一款开源的分布式文件系统,FastDFS为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标,使用FastDFS很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。
FastDFS架构包括Tracker server 和Storage server,客户端请求Tracker server进行文件上传、下载,通过Tracker server调度最终由Storage server完成文件上传和下载。
# 避免每次命令都输入sudo,可以设置用户权限,注意执行后须重新登录
sudo usermod -a -G docker $USER
1.数据库中的charfield在进行order_by时,'100’反而比’22’小。所以需要比较的字段,在数据库最好设置为IntegerField。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。