Django
manage.py
创建项目
django-admin startproject name
启动服务
python manage.py runserver 127.0.0.1:8000 # 以 127.0.0.1:8000,为地址启动服务
创建 app
python manage.py startapp appname
映射现有表到数据库
python manage.py makemigrations name # 创建迁移文件
python manage.py migrate # 迁移文件映射到数据库 -> db.sqlite3
错误记录
ModuleNotFoundError: No module named 'xxx.settings' -> 在根目录创建了__init__.py
目录结构
project
app1 # 创建的应用
__inti__.py
admin.py
apps.py
models.py
tests.py
urls.py
views.py
app2
project
__init__.py # 初始化文件
settings.py # 设置文件
urls.py # 配置路由
views.py # 配置视图
asgi.py #
wsgi.py
templates # 模板文件
login.html # html 模板
main.html
db.sqlite3 # 默认生成的数据库
manage.py # 主文件
urls.py # 配置路由
"""
Function views
from app import views # 导入视图文件
path('', views.home, name='home') # 映射视图里的方法
Class-based views
from app.views import Home
path('', Home.as_view(), name='home')
Including another URLconf # 映射路由到应用
from django.urls import include, path, re_path
path('blog/', include('app.urls')) # 映射 app 子路由,app 里也有 urls.py
路径装换器
<int:year> 类型:形参
str匹配/以外的非空字符, int[]配0和正整数, slug字母数据连字符下划线, uuid -分隔的uuid, path匹配完整url
"""
class Trans: # 自定义路径转换器
regex = '[a-zA-Z]{4}' # 用于匹配路径
def to_python(self,value): # 用于处理匹配到的字符
return value.lower()
def to_url(self,value): # 返回数值给转换器的
retuen value
register_converter(Trans,'trans') # 注册自己的转换器
# 配置路由,映射路径
urlpatterns = [
path('admin/', admin.site.urls), # http://www.web.com/admin/ 后台
path('path/<str:value>', views.fun, name='path1') # 设置名字,用于逆向访问
path('path/,include(moive.urls,namespace="moive-path")')
re_path('^movie/(?P<page>[0-9]{4})$',views.page), # 关键字正则
re_path('^movie/([0-9]{4})$',views.page), # 位置正则,不能使用关键字正则
# 用 re匹配,并使用路径转换器,把斜杠后面的匹配结果,当作名字为 page 的参数传给 views.page
re_path(r'^movie/(?:page+(?P<num>\d[]))',views.page) # (?: )非捕捉参数
re_path(r'^movie/(?:page+(?P<num1>\d[]))',views.page, {'num2':'123'}) # 使用字典直接传参
path('movie/<trans:name>',views.page) # 使用自定义转换器
path('', views.IndexView.as_view(), name='index'), # 使用通用视图
path('<int:pk>/', views.DetailView.as_view(), name='detail'), # 使用通用视图
path('<int:pk>/result/', views.ResultView.as_view(), name='result'), # 使用通用视图
path('<int:q_id>/vote/', views.vote, name='vote'),
]
# 可以为空,代表主路径,之所以交主路径而不叫根路径,是因为在 app 里设置为空时,代表根映射文件所映射该 qpp.urls 的路径 可以为空,代表主路径,之所以交主路径而不叫根路径,是因为在 app 里设置为空时,代表根映射文件所映射该 qpp.urls 的路径
views 视图文件,用于显示网页
from django.http import HttpResponse
from django.shortcuts import render
from .models import Movie_model, Student, Book, People, Home, Place, House # 导入 models.py 里的模型
获取请求数据
method = request.method # 获取请求方式
name = request.GET.get('name')
account = request.POST.get('account','')
数据库
事务
原子性: 事务要么全部成功要么全部失败
一致性: 相互关联的字段要同步变化
隔离性:
脏读->读取到另一个未提交事务处理过程中修改过的数据
不可重复读->查询间隔中,其他事务修改了数据
幻读->全表修改后插入了新行,新行并不会被修改
持久性
查询 QuerySet API
from django.db import connection
query = connection.queries # 获取所有数据库操作语句
Movie_model.objects.get(name="mike") # 获取 name 为 mike 的数据,返回对象实例,查询不到报错
Movie_model.objects.filter(name="mike") # 获取 name 为 mike 的数据,返回列表,查询不到返回空列表
Movie_model.objects.filter(name__contains="mike") # 获取 name 包含 mike 的数据
Movie_model.objects.filter(name__icontains="mike") # 获取 name 包含 mike 的数据,忽略大小写
Movie_model.objects.filter(name__startswith="mike") # 获取 name 以 mike 开头的数据
Movie_model.objects.filter(name__endswith="mike") # 获取 name 以 mike 结尾的数据
Movie_model.objects.filter(name__isnull) # 获取 name 为空的项
Movie_model.objects.filter(name__contains="mike",name__endswith='e') # 多条件查询,以逗号分割
Movie_model.objects.values('name','age').filter(name="mike") # 部分字段查询
Movie_model.objects.filter(name="mike").exclude(name__startswith='s')# 排除 name 以 s 结尾结果
Movie_model.objects.filter(name="mike").order_by('id') # id 升序排序
Movie_model.objects.filter(name="mike").order_by('-id') # id 降序排序
Movie_model.objects.filter(id__gt=2) # id>2
Movie_model.objects.filter(id__lt=2) # id<2
Movie_model.objects.filter(id__in=(2,5)) # id 是 2 或 5 的
Movie_model.objects.filter(id__range=(2,5)) # 2<=id<=5
Movie_model.objects.filter(created__range=('2020-1-10','2020-2-10')) # 时间范围比较
Movie_model.objects.first() # 获取第一个数据
Movie_model.objects.last() # 获取最后一个数据
Movie_model.objects.all() # 获取所有数据,可以切片[0:10]不支持负数
# 关联查询
courses = Student.frist().course_set.all() # 获取所有与第一个学生关联的课程,返回querySet表
home = Student.first().home
# 聚合查询
from django.db.models import Max, Min, Count, Sum, Avg, Mean
# 整体聚合aggregate
sum = Student.objects.aggregate(avg = Avg('score'),min=Min('score')) # 返回字典 ->{'avg':75,'min':55}
# annotate(注解), 对某个对象执行查询,无法组合多个聚合,结果还可以被查询,只不过是在原来的基础上添加了聚合的结果
# 除了 Count('author',distinct=True)
# 注解可以和filter混用
group = Student.objects.annotate(count=Count('classman'))
g = Class.objects.annotate(Count('student__book')) # 关联查询,计算班级里每个学生书的数量
g[0].student__book__count
g = Class.objects.annotate(book_count = Count('student__book')) # 对聚合结果指定别名
g[0].book_count
group = Student.objects.values('name').annotate(count=Count('classman'))# 分组聚合查询, 先对结果根据name分组,然后每个分组结果聚合, 因此name重复的就会被计算到一起
# 原生查询 raw
s = Student.objects.raw('select * from main_student where name=%s', ['Jack']) # 查询列必须包含主键, 返回学生对象列表, 可以用参数化查询, 返回对象实例的列表
# 原生查询connection
cursor= connection.cursor()
cursor.execute('select name,score from main_student hwere name=%s', ['Jack']) # %字符需要使用两个 %% 表示
result = cursor.fetchall() # 获取所有查询信息,返回元祖构成的元组,没有字段名
cursor.close()
# Q 查询
from django.db.models inport Q, F
s = Student.objects.filter(~Q(Q(name='Jack')&Q(classname='181'))) # &(与) |(或) ~(非)
s = Student.objects.filter(~Q(name='Jack')) # 名字不等于Jack的
# F 查询(获取查询结果的某项值)
from django.db.models inport Q, F
s = Student.objects.filter(name='Jack').update(score=F('score')+10) # F('score')代表搜索结果原来的值
# get_object_or_404()
s = get_object_or_404(Student, pk=1) # 查询主键是1的学生,查不到就返回Htto404,而不是DoesNotExist, 第二个参数还可以是,QuerySet的查询结果,或者filter里的查询条件肉 name__startswith="刘"
# get_list_or_404()
s = get_list_or_404(Student, classname='181') # 获取所有181班的学生, 结果为空时引发Http404
查询表达式
# 内置表达式
import datetime
from django.db.models import DateTimeField, ExpressionWrapper, F
F('score') # 代表对象实例 name字段的值, 直接操作数据库, 减少查询次数,F的赋值对每个save都有效,即每次save都会执行一次F的操作
s = Student(name='jack')
s.score = F('score') + 1 # 在原有的基础上+1
s.update(score=F('score')+1) # 用于update
s.update(name=F('blog__name')) # 查询name=blog.name的学生, 关联查询
s.update(born=F('bore')+datetime.timedelta(days=3)) # 时间的计算
# 位操作
F('field').bitand() # 与
.bitor() # 或
.bitxor() # 非
.bitrightshift() # 按位右移
.bitleftshift() # 按位左移
pk
s= Student(pk=1) # pk代表主键
like查询
a = Student.objects.filter(name__contains='刘')
iexact,
contains 包含
icontains 包含(大小写不敏感)
startswith 开头
istartswith 结开头(大小写不敏感)
endswith 结尾
iendswith 结尾(大小写不敏感)
queryset与缓存
result = Entry.objects.all() # 每次都只拿部分数据,就不会把查询结果填充到缓存中, 这样每次使用result都会重新查询
jsonfield
Moive.objects.creates(name='长津湖',data={'time':175,publish='中国','roles':['吴京','李晨','易烊千玺']})
m = Moive.objects.filter(data__time__gt=130)
m = Moive.objects.filter(data__contains={'time':'175'}) # 搜索data里time为175的
m = Moive.objects.filter(data__contained_by={'time':'175','publish':'美国'}) # 搜索data里time为175的,或publish为美国的
m = Moive.objects.filter(data__has_key='name') # 搜索顶层包含name这个key的
m = Moive.objects.filter(data__has_keys=['name','publish']) # 搜索顶层同时包含name和publish这些key的
m = Moive.objects.filter(data__has_any_keys=['name','publish']) # 搜索顶层包含name或publish这些key的
Q(name__startwith='刘')
# 有查询的地方就能用Q()
Q(question__startswith='Who') | Q(question__startswith='What')
Q(question__startswith='Who') | ~Q(pub_date__year=2005)
添加
movie = Movie_model(name="mike",age=18)
movie.save()
movie = Movie_model.create(name="mike",age=18) # 直接保存,省略了保存的步骤
删除
# 删除搜索结果(删除 model 实例)
Movie_model.objects.filter(id=2).delete() # 删除过滤出来的结果
# 根据多对多,删除子对象
book.students.remove(student)
# 逻辑删除
修改
Movie_model.objects.filter(id=2).update(name='mike') # 修改过滤出来结果的 name 为 mike
比较
moive1 == moive2 -> moive.id == moive2.id # model的比较就是主键的比较
外键
多对多
book = Book(name='洞见') # 创建一本书
student = Student(name='Mike') # 创建一个学生
book.save() # 关联外健前要先保存(已保存的对象才能被关联)
student.save()
book.students.add(student) # 学生添加借的书, 可以多次参加
student.books.add(book) # 书添加借阅学生
多对一
home = Home(location='hebei')
home.save()
people = People(name='mike', home=home) # 关联的对象(home)必须要先保存
people.save()
people.home # 获取 home 值,
一对一
place = Place(name='hebie')
place.save()
house = House(place=place)
house.member.add(people)
分页
from django.core.paginator import Paginator,PageNotAnInteger,EmptyPage # 导入分页器
movies = Movie_model.objects.all() # 代表模型在数据里的所有数据
pager = Paginator(movies, 5) # 创建分页器
pager.page_range # 获取页码列表
page = pager.page(n) # 获取第 n 页的数据
page.next_page_number 下一页页码
page.previous_page_number 上一页页码
#html
<div class="page">
<ul>
<li>
{% if blogs.has_previous %}
<a href="blogs/{{ category }}/{{ blogs.previous_page_number }}">上一页</a>
{% else %}
<a>上一页</a>
{% endif %}
</li>
{% for i in pagerange %}
{% if i == blogs.number %}
<li class="curentpage page"><a href="blogs/{{ categury }}/{{ i }}">{{ i }}</a></li>
{% else %}
<li class="page"><a href="blogs/{{ category }}/{{ i }}">{{ i }}</a></li>
{% endif %}
{% endfor %}
<li>
{% if blogs.has_next %}
<a href="blogs/{{ category }}/{{ blogs.next_page_number }}">下一页</a>
{% else %}
<a>下一页</a>
{% endif %}
</li>
</ul>
</div>
# 不用分页器
<div class="pager">
{% if page > 1 %}
<a href="/city/{{ city }}/label/{{ label }}/?p={{ page|add:-1 }}">上一页</a>
{% else %}
<a>上一页</a>
{% endif %}
{% for i in page_range %}
{% if i == blogs.number %}
<a class="curentpage page" href="/city/{{ city}}/label/{{ label }}/?p={{ i }}">{{ i }}</a>
{% else %}
<a class="page" href="/city/{{ city}}/label/{{ label }}/?p={{ i }}">{{ i }}</a>
{% endif %}
{% endfor %}
{% if page < count %}
<a href="/city/{{ city}}/label/{{ label }}/?p={{ page|add:1 }}">下一页</a>
{% else %}
<a>下一页</a>
{% endif %}
</div>
页码生成
def pages_divider(page, page_range): # page 当前页, page_range 页码列表, 针对django分页器使用
perpage = 5 # 每页的页码数量
page = int(page)
if len(page_range)<perpage:
return page_range
else:
start = page-2
end = page+2
if start<1:
start = 1
end = 5
if end>page_range[-1]:
end = page_range[-1]
start = end-4
return range(start, end + 1)
def pages_devider(page, count): # page 当前页, count 数据总数
perpage = 5 # 每页的页码数量
num = 5 # 每页文章数量
page_range = range(1,int(count/num)+1)
page = int(page)
if len(page_range)<perpage:
return page_range
else:
start = page-2
end = page+2
if start<1:
start = 1
end = 5
if end>page_range[-1]:
end = page_range[-1]
start = end-4
return range(start, end + 1)
请求
def index(request):
method = request.method # 获取请求方式
path = request.path # 请求地址
cookies = request.COOKIES # 获取cookies
file = request.FILES # 获取上传的file, post enctype='multipart/form-data'
body = request.body # 请求的实体内容
usar_agent = request.META # 返回请求报文
host = request.get_host() # 请求主机及端口号
full_path = request.get_full_path() # 返回请求地址及参数
scheme = request.scheme # 协议类型HTTP/https
响应
return render(request,'login.html'{'page':page,'pager':pager},content_type="text/html",status="200",using="engin_name")
# 打开 html 页面,并把字典里的数据传递给 html, ,content_type="text/html"响应类型, status状态码 using加载模板引擎的名字
# html的地址不能以 / 开头 !!!!!!
return redirect('http://127.0.0.1:8000/', permanent=True) # 301重定向(永久) # 有利于网站排名, 旧网站的权重会继承在新网站之上
return redirect('http://127.0.0.1:8000/') # 302重定向(临时), 参数可以是一个模型( get_absolute_url() ) 一个视图名 一个url
return HtmlResponse()
return HttpResponseRedirect('http://127.0.0.1:8000/') # 302重定向(临时跳转) 不被搜索引擎友好,不利于网站排名
return HttpResponseRedirect(reverse('path1'),args=('moive',))
# 逆向访问urlpatterns中name="path1" 的url, args为传入的参数
return HttpResponse() # 返回文字或html源码,自定义响应
from django.shortcuts import render, redirect
from django.http import HttpResponse, HttpResponseRedirect
def login_in(request):
id = request.POST.get('id', '')
p = request.POST.get('psd', '')
try:
s = Student.objects.get(id=id)
except Student.DoesNotExist:
print('不存在')
return HttpResponseRedirect('http://127.0.0.1:8000/login')
if s.psd == md5(p.encode('utf-8')).hexdigest():
# return HttpResponseRedirect('http://127.0.0.1:8000/') # 302重定向(临时跳转) 不被搜索引擎友好,不利于网站排名
# return redirect('http://127.0.0.1:8000/', permanent=True) # 301重定向(永久) # 有利于网站排名, 旧网站的权重会继承在新网站之上
# return redirect('http://127.0.0.1:8000/') # 302重定向(临时)
rp = HttpResponse() # 自定义响应
rp.set_cookie('id', str(id),
max_age=60 * 60) # 默认保存在浏览器缓存中,max_age=60 cookie过期时间(秒),之后cookie就会保存到硬盘中, cookie不能包含中文
"""
def set_cookie(self, key, value='', max_age=生效时长, expires=None, path='生效子目录',
domain='域名', secure=是否只支持https, httponly=是否只支持http, samesite=None):
"""
# rp.set_signed_cookie('id',str(id),salt='1456') # 加密cookie,salt 加密基底, 这种基本的加密容易被破解
rp.status_code = 301 # 修改响应状态码, 不是301或302不会跳转
rp.setdefault('Location', 'http://127.0.0.1:8000') # 修改网页链接
return rp
else:
return render(request,)
rp = HttpResponse() # 自定义响应
rp.status_code = 301 # 修改响应状态码
rp.setdefault('location', 'http://127.0.0.1:8000/login') # 修改网页链接
return rp
def img_view(request, img=None): # 图片预览
url = os.path.join(MEDIA_ROOT, img)
print(url)
with open(url, 'rb') as img:
img = img.read() # 读取二进制图片
rp = HttpResponse(img) # 把img当做响应内容
rp['content-type'] = 'image/png' # 修改响应内容类型为图片
rp['content-disposition'] = 'attachment;filename=img' # 图片下载, 不加这句话就是图片预览
return rp
通用视图(基于类的视图)
def vote(request, q_id):
question = get_object_or_404(Question, id=q_id)
choice = question.choice_set.get(id=request.POST[‘choice’])
choice.votes += 1
choice.save()
return HttpResponseRedirect(reverse(‘polls:result’, args=(q_id,)))
ListView
class IndexView(generic.ListView): # 通用视图
# model = Question # 处理的模型类
template_name = 'polls/index.html' # 使用自己的模板
paginate_by = 5 # 分页,每页的数量
page_kwarg = 'page' # 页码就是get的page参数
"""
<table class="data">
{% for i in object_list %}
<tr>
<td>{{ i.id }}</td> <td>{{ i.name }}</td> <td>{{ i.company }}</td>
<td>
{% for j in i.label_set.all %}
{{ j }}
{% endfor %}
</td>
</tr>
{% endfor %}
</table>
模板分页
page_obj # 页码数据
page_obj.number # 当前页
page_obj.has_previous # 是否有上一页
page_obj.previous_page_number # 上一页
page_obj.has_next # 是否有下一页
page_obj.next_page_number # 下一页
page_obj.paginator.num_pages # 页数/最后一页
<div class="pagination">
<span class="step-links">
{% if page_obj.has_previous %}
<a href="?page=1">« first</a>
<a href="?page={{ page_obj.previous_page_number }}">previous</a>
{% endif %}
{% for i in page_range %} {# page_range为自定义参数 #}
{% if i == page_obj.number %}
<a href="{% url "job:index" %}?page={{ i }}" class="current">{{ i }}</a>
{% else %}
<a href="{% url "job:index" %}?page={{ i }}" title="">{{ i }}</a>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">next</a>
<a href="?page={{ page_obj.paginator.num_pages }}">last »</a>
{% endif %}
</span>
</div>
"""
# context_object_name = 'latest_question_list' # 定义传入模板的变量名,默认为 modelname_list
queryset = Question.objects.order_by('id')[:5] # 等于get_queryset,只是get这个可以分多步写
def get_queryset(self): # 设定处理那些数据, 会覆盖model而只传本方法返回的模型实例
return Question.objects.order_by('id')[:5] # 返回显示的数据, 模板中使用object_list 访问
def get_context_data(self, **kwargs): # 添加额外参数,会调用get_queryset把数据添加到context中
context = super().get_context_data(**kwargs)
context['now'] = timezone.now() # context为参数字典, 在里面添加参数,可以直接在模板里使用
return context
"""
context
{'paginator': None, 'page_obj': None, 'is_paginated': False, 'object_list': <QuerySet [<Jobs: Jobs object (00018e71bca90ceacb1bacde563bd236)>]>, 'jobs_list': <QuerySet [<Jobs: Jobs object (00018e71bca90ceacb1bacde563bd236)>]>, 'view': <job.views.Index object at 0x0000022DCD9883D0>, 'another': '其他的参数'}
"""
def get(self,request): # get默认会自动调用get_context_data等方法,返回数据,和template_name 的模板,获取页数,所以正常是不用重写的
return render(request,'index.html')
def post(self,request): # 没有默认post方法
return HttpResponse('sucess')
# ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'] # 可以定义的请求方法
DetailView
class DetailView(generic.DetailView):
# context_object_name = 'q' # 定义传入模板的变量名,默认modelname_detail -> question_detail
model = Question # 作用的模型实例
template_name = 'polls/detail.html' # 指定使用的模板,不指定就是默认<app name>/<model name>_detail.html
ResultView
class ResultView(generic.DetailView):
model = Question
template_name = 'polls/result.html'
异步视图
视图装饰器
from django.views.decorators.http import require_http_methods
@require_http_methods(["GET"]) # 定义视图接受的方法
def my_view(request):
# I can assume now that only GET or POST requests make it this far
# ...
pass
文件上传
会话session
request.session['member_id'] = m.id
del request.session['member_id']
缓存
整站缓存
# 整个站点缓存
MIDDLEWARE = [
'django.middleware.cache.UpdateCacheMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.cache.FetchFromCacheMiddleware',
]
视图缓存
# 视图(views.py)缓存
@cache_page(60*15, key_prefix='blog') # 使用视图缓存, key_prefix设置缓存前缀会和设置里CACHE的key_prefix连接起来
def blog(request, id):
try:
blog = Blog.objects.get(id=id)
except Blog.DoesNotExist:
return HttpResponse('文章不存在')
review_count = math.ceil(Comment.objects.filter(blog=blog).count() / 10)
return render(request, 'blog.html', {'blog': blog, 'reviewcount': review_count})
# urlconf中指定视图缓存
from django.views.decorators.cache import cache_page
urlpatterns = [
path('foo/<int:code>/', cache_page(60 * 15)(my_view)),
]
模板缓存
# 模板中缓存
{% load cache %} # 放在模板顶部,用于加载缓存
{% cache 500 sidebar request.user.username %} # 过期时间, 缓存名 额外缓存标识(可以添加多个,保证唯一性)
.. sidebar for logged in user ..
{% endcache %}
变量缓存
# 底层缓存API
from django.core.cache import cache
cache.set('my_key', 'value', 30) # 创建
cache.get('my_key', 'default_value') # 获取
cache.add('add_key', 'New value') # 不存在时创建
cache.get_or_set('my_new_key', 'my new value', 100) # 获取,不存在时创建
cache.get_many(['key1', 'key2', 'key3']) # 获取多个值,返回键值字典
cache.set_many({'a': 1, 'b': 2, 'c': 3}) # 设置多个
cache.delete('a') # 删除
cache.delete_many(['a', 'b', 'c']) # 删除多个
cache.clear() # 删除所有
cache.touch('a', 10) # 更新过期时间
条件视图(最后修改时间,判断请求数据是否变化)
from django.views.decorators.http import condition
# condition(etag_func=None, last_modified_func=None)
def latest_entry(request, blog_id): # 参数与被装饰参数相同
return Entry.objects.filter(blog=blog_id).latest("published").published # 返回datetime时间
@condition(last_modified_func=latest_entry)
def front_page(request, blog_id):
...
加密签名
from django.core.signing import Signer
signer = Signer(salt='extra') # salt可选参数, 不同的值加密不同的结果,不需要保密
value = signer.sign('My string')
# value = 'My string:Ee7vGi-ING6n02gkcJ-QLHg6vFw'
发送邮件
# settings
EMAIL_HOST = 'smtp.163.com' # 邮箱服务器
EMAIL_HOST_USER = '15631177345@163.com' # 账号
EMAIL_HOST_PASSWORD = 'JJWYNEANJKNKGXNW' # 密码/授权码
EMAIL_PORT = 25 # 端口
# views
from django.core.mail import send_mail
send_mail('标题', content, '15631177345@163.com',['389656169@qq.com'],fail_silently=False)
# 发送邮件,需要在settings设置参数
序列化
把查询结果转换为字典
from django.core import serializers # 导入序列化器
blogs = Blog.objects.all()
data = serializers.serialize('json',blogs,fields=('name','age')) # 转化为json格式
# [{"model": "post.blog", "pk": 4680896, "fields": {"title": "ssss", "author": null}},{...}]
# 主键都会序列化为pk,fields中没有主键, fields可以选定序列化的字段
data - serialize('xml', blogs) # 转化为xml格式
# 基类的字段不会被序列化,要序列化需要显式的指出
反序列化
model = serializers.deserialize("xml", data)
model.save() # 对象保存到数据库
Django 的验证系统
登录
form.py
from django import forms
import re
from django.core.exceptions import ValidationError
class Register(forms.Form): # 创建form类,字段顺序就是表单顺序
name = forms.CharField(label='用户名',max_length=100, min_length=2, error_messages={'min_length': '名字过短', 'required': '名字不能为空'}) # 定义form字段
psd = forms.CharField(label='密码', min_length=8, error_messages={'min_length': '密码过短', 'required': '密码不能为空'})
c_psd = forms.CharField(label='确认密码', min_length=8, error_messages={'min_length': '密码过短', 'required': '密码不能为空'})
hoboy = form.Select()
auto_id =
def clean_psd(self): # 钩子 用于验证数据
psd = self.cleaned_data.get('psd')
if not re.search('[A-Z]', psd) or not re.search('[a-z]', psd) or not re.search('[0-9]', psd):
raise ValidationError('密码必须包含大小写字母及数字')
return psd
def clean_c_psd(self):
c_psd = self.cleaned_data.get('c_psd')
psd = self.cleaned_data.get('psd')
if psd != c_psd:
raise ValidationError('两次密码不一致')
return c_psdfrom django import forms
class StudentRegister(Register): # 继承,在父类的基础上添加字段,可以多继承
priority = forms.CharField()
class Login(forms.Form):
name = forms.CharField(label='用户名', max_length=100, min_length=10, required=True, errror_message={'max_length':'名称过长', 'required':'名字不能为空'})
psd = forms.CharField(label='密码', required=True, widget=forms.Passwordinput)
"""
字段参数: required label label_suffix后缀 initial初始值 widget渲染部件
help_text提示文本 error_messages覆盖字段错误的默认消息 disabled禁止修改
auto_id 是否自动生成id(=true->"name"(默认) ="id_%s"->id_name)
字段方法: has_changed()字段是否变化
内置Field: BooleanField CharField ChoiceField TypedChoiceField DateField
DateTimeField DecimalField DurationField EmailField FileField
FilePathField FloatField ImageField IntegerField JSONField GenericIPAddressField
MultipleChoiceField TypedMultipleChoiceField NullBooleanField RegexField
SlugField TimeField URLField UUIDField
ComboField MultiValueField SplitDateTimeField # 较复杂
ModelChoiceField ModelMultipleChoiceField
ModelChoiceIterator
ModelChoiceIteratorValue
自定义表单字段:
"""
"""
f.as_table() as_ul as_p # form转化为table ul p
f.is_multipart() # 判断表单是否enctype = "multipart/form-data", 用于构造html表单时,创建不同类型表单
f.prefix() # 添加前缀 mother = PersonForm(prefix="mother") ->id="id_mother-first_name"
Register(prefix="student") 或 form 添加 prefix="student" 字段
f.is_hidden label name widget_type initial id_for_label(表单html的id) label_tag(label)
html_name help_text errors form field data hidden_fields visible_fields
fields has_changed()
"""
views
from .forms import Register, Login
from django.contrib.auth import authenticate, login
def register(request):
print('register')
if request.method == "GET":
form = Register() # 访问时传入空form
return render(request, 'main/register.html', {'form': form}, status=200)
elif request.method == "POST":
form = Register(request.POST) # 提交表单时传入POST
if form.is_valid(): # 验证数据,然后数据就会放在form.cleaned_data中
name = form.name
psd = form.psd
Student.objects.create(name=name,psd=md5(psd.encdde()).hexdigest())
return render(request, 'main/login.html')
else:
clear_errors = form.errors.get("__all__")
print(clear_errors)
return render(request, 'main/register.html', {'form': form, 'clear_errors': clear_errors})
def login(request):
if request.method == "GET":
form = Login()
return render(request,'main/login.html',{'form':form})
elif request.method == 'POST':
form = Login(request.POST)
if form.is_valid(): # 是用自带方法验证登录
if authenticate(name=form.cleaned_data['name'], psd=form.cleaned_data['psd']):
return HttpResponse('登陆成功')
else:
return render(request, 'main/login.html', {'form':form})
"""
f.as_table() as_ul as_p # form转化为table ul p
f.is_multipart() # 判断表单是否enctype = "multipart/form-data", 用于构造html表单时,创建不同类型表单
f.prefix() # 添加前缀 mother = PersonForm(prefix="mother") ->id="id_mother-first_name"
Register(prefix="student") 或 form 添加 prefix="student" 字段
f.is_hidden label name widget_type initial id_for_label
html_name help_text errors form field data
fields has_changed()
"""
"""
表单字段验证:
引发 ValidationError(验证失败)
from django.core.exceptions import ValidationError
raise ValidationError(
_('Invalid value: %(value)s'), # 错误信息
params={'value': '42'}, # %对应的参数
code='invalid',
)
raise ValidationError([ # 引发多个错误
ValidationError(_('Error 1'), code='error1'),
ValidationError(_('Error 2'), code='error2'),
])
验证器: # https://docs.djangoproject.com/zh-hans/3.2/ref/validators/
from django.core import validators
validators 包含横夺内置验证器,用于验证各种类型的数据
"""
html
<form action="{% url 'register' %}" method="post">
{% csrf_token %}
{# 直接使用form 显示效果差 #}
{# {{ form }}#}
{# 使用form字段自定义表单
form.name.name name字段的字段名
form.name name字段的表单代码
from.name.errors.0 name字段的错误
#}
<label for="id_{{ form.name.name }}">用户名</label>{{ form.name }}<span>{{ from.name.errors.0|striptags }}</span><br> {# striptags 去除标签 #}
<label for="id_{{ form.psd.name }}">密码</label>{{ form.psd }}<span>{{ form.psd.errors.0 }}{{ form.psd.errors.1 }}{{ form.clean_psd.errors.0 }}</span><br>
<label for="id_{{ form.c_psd.name }}">确认密码</label>{{ form.c_psd }}<span>{{ form.c_psd.errors.0 }}</span><br>
<input type="submit" value="提交">
</form>
从模型创建表单
https://www.cnblogs.com/yangyangming/p/11157755.html
models.py
from django.db import models
from django import forms
class Student(models.Model):
name = models.CharField(verbose_name = '姓名', max_length=64,null=False,unique=True,blank=False)
phone = models.CharField(verbose_name = '手机', max_length=11,null=False,blank=False)
psd = models.CharField(verbose_name = '密码', max_length=200,null=False,blank=False)
def __str__(self):
return self.name
class StudentForm(forms.ModelForm):
class Meta:
model = Student # 绑定模型
fields = ['name','psd'] # 表单绑定的模型字段
# model 的 verbose_name就是label标签
labels = {'phone':'电话', 'name':'姓名','icon':'头像'}
error_messages = {'name':{'required':'姓名不能为空'}}
widgets = {'psd':widgets.PasswordInput}
help_texts = {'phone':'手机号'}
全局上下文
def global_info(request): # 全局上下文,在settings的template中引入这个函数,就可以在模板上直接使用返回字典里的内容 -> {{ school }}
return {'school':'河北科技大学'}
models (orm 对象关系映射)
映射现有表到数据库(新建的模型都要先迁移再映射)
python manage.py makemigrations [app_name] # [在app下]]创建迁移文件
python manage.py migrate # 迁移文件映射到数据库
python manage.py sqlmigrate app 0001 # 查询操作
数据库映射到 model
未看
常用类型
DateTimeField(auto_now_add=True,auto_now=True)
根据网络法数据不能直接在数据库删除, 要在用户删除一定时间后再在数据库删除.
from django.db import models
from django.db.models import Manager
from django.utils.functional import cached_property
# Manager 就是操作数据库的基本类
class SelfManager(Manager): # 必须继承Manager, 不然会失去其他方法
def all(self): # 重写all方法, 输出所有未被删除的结果
return Manager.all(self).filter(isdelete=False) # 调用父类的方法的基础上过滤
def filter(self,*args,**kwargs):
result = Manager.filter(self,*args,**kwargs)
def delete(result):
for i in result:
i.isdelete = 1
i.save
import new # 安装不了,尴尬
# 因为无法调用方法内的方法, 就需要用instancemethod, 把内部方法绑定到对象身上
result.delete = new.instancemethod(delete,result,QuerySet)
return delete
class Movie_model(models.Model): # 创建 model 对象
name = models.CharField(max_length=50,unique=True) # 创建字段,及特性
file = models.FilePathField(upload_to='media')
data = models.JsonField(null=True) # 顶级值的null会被解析为None,但是其他级别的还是null
objects = SelfManager() # 覆盖原有管理器
objects_self = SelfManager() # 使用在原有的基础上自建管理器
@property # 把方法的返回值当做属性,可以moive.name_data访问而不用加(),但是不能设置
def name_data(self):
return self.name+self.data
@name_data.setter # 额外添加设置值得方法
def name_data(self,v):
self.__name_data=v
def delete(self): # 针对单个实例
self. isdellete = 1 # 逻辑删除, 数据库不删除只是标记为删除
class Meta: # 公共属性
db_table = 'Object' # 自定义数据库表名
def __str__(self): # 打印时输出的内容,后台数据显示相关
return self.name
@cached_property # 缓存, 一个实例的生命周期内多次访问只查询一次
def get_date(self, name):
事务
from django.db.transaction import atomic # 导入事务装饰器
from django.db import models
from django.db.models import Manager, QuerySet
from hashlib import md5
def get_course(name):
try:
return Course.objects.get(name=name)
except Course.DoesNotExist:
return Course.objects.create(name=name)
def get_class(name):
try:
return Class.objects.get(name=name)
except Class.DoesNotExist:
return Class.objects.create(name=name)
def get_student(name, classname):
student = Student.objects.filter(name=name, classname=classname)
if student:
student = student[0]
else:
student = Student.objects.create(name=name, classname=classname)
return student
class StudentManager(Manager): # 必须继承Manager, 不然会失去其他方法
@atomic # 添加事务装饰器,语句出现异常时会回滚
def insert(self, **kwargs): # 同时添加本身及外键关联的对象,kwargs参数键值对
classname = kwargs['classname'] # 获取传入班级名称
classname = get_class(classname) # 获取班级model对象
kwargs['classname'] = classname # 把参数的class对象名改为class的model对象
kwargs['psd'] = md5(kwargs['psd'].encode()).hexdigest()
courses = kwargs.pop('courses') # 获取课程名元组,并在kwargs中移除课程元组
try:
student = Student.objects.get(name=kwargs['name'])
student.phone = kwargs['phone'] # 更新数据
student.psd = kwargs['psd']
student.save()
except Student.DoesNotExist:
student = Manager.create(self, **kwargs) # 通过kwargs创建学生
student.save()
print('new student join : '+kwargs['name'])
courses = [get_course(i) for i in courses] # 根据课程名元组创建课程model对象
for course in courses: # 循环创建关联关系
score = Score(student=student,course=course,score=-1) # 自定义中间表没有create方法
score.save()
class Student(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=64,null=False,unique=True,blank=False)
phone = models.CharField(max_length=11,null=False,blank=False)
psd = models.CharField(max_length=200,null=False,blank=False)
SHIRT_SIZES = (
('S', 'Small'),
('M', 'Medium'),
('L', 'Large'),
)
shirt_size = models.CharField(max_length=1, choices=SHIRT_SIZES) # choices多选一,他的值为S M L,要获取后面的,要用student.get_shirt_size_display()
classname = models.ForeignKey(Class, on_delete=models.CASCADE)
courses = models.ManyToManyField(Course,through='Score') # 自建中间表
objects = StudentManager()
def __str__(self):
return 'student: ' + self.name
自定义管理器及方法
from django.db import models
from django.db.models import Manager, QuerySet
from hashlib import md5
def get_course(name):
try:
return Course.objects.get(name=name)
except Course.DoesNotExist:
return Course.objects.create(name=name)
def get_class(name):
try:
return Class.objects.get(name=name)
except Class.DoesNotExist:
return Class.objects.create(name=name)
def get_student(name, classname):
student = Student.objects.filter(name=name, classname=classname)
if student:
student = student[0]
else:
student = Student.objects.create(name=name, classname=classname)
return student
class StudentManager(Manager): # 必须继承Manager, 不然会失去其他方法
def insert(self, **kwargs): # 同时添加本身及外键关联的对象,kwargs参数键值对
classname = kwargs['classname'] # 获取传入班级名称
classname = get_class(classname) # 获取班级model对象
kwargs['classname'] = classname # 把参数的class对象名改为class的model对象
kwargs['psd'] = md5(kwargs['psd'].encode()).hexdigest()
courses = kwargs.pop('courses') # 获取课程名元组,并在kwargs中移除课程元组
try:
student = Student.objects.get(name=kwargs['name'])
student.phone = kwargs['phone'] # 更新数据
student.psd = kwargs['psd']
student.save()
except Student.DoesNotExist:
student = Manager.create(self, **kwargs) # 通过kwargs创建学生
student.save()
print('new student join : '+kwargs['name'])
courses = [get_course(i) for i in courses] # 根据课程名元组创建课程model对象
for course in courses: # 循环创建关联关系
score = Score(student=student,course=course,score=-1) # 自定义中间表没有create方法
score.save()
# student.courses.add(*courses) # 为学生添加课程
class ClassManager(Manager):
def insert(self, name, students): # 同时添加班级和学生
classname = Manager.filter(self, name=name)
if classname:
classname = classname[0]
else:
classname = Manager.create(self, name=name)
for i in students:
classname.student_set.add(get_student(i, classname))
return classname
class Class(models.Model):
name = models.CharField(max_length=64)
objects = ClassManager()
def __str__(self):
return 'class: ' + self.name
class Course(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=64)
def __str__(self):
return 'course: ' + self.name
class Student(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=64,null=False,unique=True,blank=False)
phone = models.CharField(max_length=11,null=False,blank=False)
psd = models.CharField(max_length=200,null=False,blank=False)
classname = models.ForeignKey(Class, on_delete=models.CASCADE)
courses = models.ManyToManyField(Course,through='Score') # 自建中间表
objects = StudentManager()
def __str__(self):
return 'student: ' + self.name
class Score(models.Model): # 自定义中间表类
student = models.ForeignKey(Student,on_delete=models.CASCADE) # 关联字段
course = models.ForeignKey(Course,on_delete=models.CASCADE) # 关联字段
score = models.SmallIntegerField() # 自定义字段
@staticmethod
def get(student=None, course=None): # 查询分数 自定义静态方法
if not student and course:
course = Course.objects.get(name=course)
score = Score.objects.filter(course=course)
elif not course and student:
student = Student.objects.get(name=student)
score = Score.objects.filter(student=student)
elif course and student:
print('both')
student = Student.objects.get(name=student)
course = Course.objects.get(name=course)
score = Score.objects.filter(student=student, course=course)
else:
return None
return score # 返回Score的QuerySet
@staticmethod
def set(student, course, num): # 设置分数
student = Student.objects.get(name=student)
course = Course.objects.get(name=course)
score = Score.objects.get(student=student,course=course)
score.score = num
score.save()
return score
多对多
# 多对多关系会创建一个中间表用于储存两者的关联
class Student(models.Model):
name = models.CharField(max_length=50) # 名字
age = models.SmallIntegerField() # -32768 - 32767 年龄
books = ManyToManyField(Book) # 借的书(列表)
books = ManyToManyField(Book,through='Middle') # 自定义中间表
class Book(models.Model):
name = models.CharField(max_length=50) # 书名
class Middle(models.Model):
# 自定义了中间表,表本身无法使用 add remove set create方法了
# 如果ManyToManyField(Book,through='Middle') ,这样定义中间表,就只能有两个外键指明关联的两个表
# 想要加额外的外键,就要 ManyToManyField(Book,through='Middle',through_field=('student','book'))指定关联表的两个字段
student = models.ForeignKey(Student,on_delete=models.CASCADE)
book = models.ForeignKey(Book,on_delete=models.CASCADE)
num = models.IntegerField() # 借阅次数
多对一
就是给people加个外键,值为home的id(默认)
class People(models.Model): # 多对一中的多
name = models.CharField(mac_length=30)
home = models.ForeignKey(Home, on_delete = models.CASCADE, related_name="people_set") # on_delete
# on_delete = models.SET_NULL(外键Home被删除时,此处home字段设置为空)
# CASCADE(外键Home被删除时, 所有以Home为外键的People也会被删除,级联删除,默认)
# PROTECT想要删除Home时,如果有People以此为外键就会报错
# SET_DEFAULT(外键Home被删除时, 所有以Home为外键的People的home字段设置为Home的默认值
# SET('value') 外键Home被删除时, 所有以Home为外键的People的home字段设置为自定义的值 value
# related_name 外键关联查询时名字, 由home(Home实例)查people -> home.people_set
class Home(models.Model): # 多对一中的一
location = models.CharField(max_length)
一对一(类似于继承)
class Place(models.Model):
name = models.CharField()
class House(models.Model):
place = models.OneToOneField(Place,on_delete=models.CASCADE,primary_key=True)
member = models.ManyToMany(People)
模型继承
抽象基类
from django.db import models
class Location(models.Model):
name = models.CharField(max_length=64)
class People(models.Model): # 抽象基类在迁移时不会创建数据表,没有管理器,不能实例化
name = models.CharField(max_length=64,null=False)
age = models.intergerField()
home = models.CharField(max_length)
location = models.ForeignKey(Location,
on_delete = models.CASCADE,
related_name = "%(app_label)_%(class)_set", # app_label应用名 class模型类名, 默认-> 类名_set 小写
related_query_name="%(app_label)_%(class)") # 默认 类名小写
# related_name 反响查询的名字,
# related_query_name 用作filter等查询时的字段名-> Location.objects.filter(people_related_query_name__name='learn_python')
# 如果使用了这两个参数,就要用这种方式,不能用固定的文字, 不然所有继承的子类都使用了相同的反向查询,查到的就是多种子类,而不是你想要某个子类
class Meta:
abstract = True # 抽象基类的标志
class Boy(models.Model):
hair = models.CharField(max_length=64)
hobby_list = [
('g','game'),
('m','music'),
('s','sport')
]
hobby = models.ChrField(max_length=64, choices=hobby_list)
class Meta:
abstract=True # 抽象基类标志
class Student(People): # 继承抽象基类
school = models.CharField(max_length=64)
classname = models.CharField(max_length=64)
class Childern(People,Boy): # 多继承
favoutite_foot = models.CharField(max_length=64)
class Meta(People,Boy): # meta也必须多继承, 而且他只继承第一个父类的meta
abstract=True
db_table = 'childern'
多表继承
from django.db import models
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
class Restaurant(Place): # 多表继承(每个子类都会创建一个新表),就是最直接的继承
"""
自动创建与Place的OneToOneField()
place_ptr = models.OneToOneField(Place, on_delete=models.CASCADE, parent_link=True, primary_key=True,)
parent_link 当 True 并用于从另一个 concrete model 继承的模型中时,表示该字段应被用作回到父类的链接,而不是通常通过子类隐含创建的额外 OneToOneField
"""
serves_hot_dogs = models.BooleanField(default=False)
serves_pizza = models.BooleanField(default=False)
customers = models.ManyToManyField(Place, related_name='customer_set') # 继承的类的其他关联必须设置related_name
class Meta: # 不会继承父类的Meta
ordering = [] # 排序如果不设置,会按照父类排序
get_latest_by = 'date' # 代表时间或数字的字段, 不设置就会使用父类的
# Restaurant可以直接使用Place(父类)的字段
r = place.restaurant # 反向查询, 不存在时报错 Restaurant.DoesNotExist
代理模型
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
class MyPerson(Person): # 代理模型必须继承自一个非抽象模型,但是可以任意数量抽象模型
class Meta:
proxy = True # 代理模型标志,代理模型在使用上与原模型相同,只是增加了方法或meta参数
ordering = ['last_name']
def do_something(self): # 代理模型没有自己的字段,但是有额外的方法用于处理字段
# ...
pass
多重继承
class Article(models.Model):
article_id = models.AutoField(primary_key=True) # 被继承的如果多个包含id主键就会报错,因此不能使用默认的主键,而是自己定义能够区分的主键
...
class Book(models.Model):
book_id = models.AutoField(primary_key=True)
...
class BookReview(Book, Article):
pass
模型的继承是不能重写非抽象子类的字段的
跨文件模型
from app.models import Student # 跨文件导入
Meta选项
所有不是字段的东西
main.html # 所有 html 模板文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>movies</title>
<style>
li{
display: inline;
}
</style>
</head>
<body>
<ul type="none">
{% for i in content %}
<a href="{% url 'path1' 'moive' %}"逆向访问urlpatterns中name="path1" 的url,'moive'为传入的参数 </a>
<a href="{% url 'path1' i.name %}"逆向访问urlpatterns中name="path1" 的url,i.name 为传入的参数 </a>
<li>{{ i.1 }}使用索引</li>
{% endfor %}
</ul>
<ul>
{% for i in q.choice_set.all %} # all方法没有参数,不需要加括号
<li>{{ forloop.counter }}.{{ i.text }}</li>
{% endfor %}
</ul>
{% if content.has_previous %}
<a href="/movie?a=flip&page={{ content.previous_page_number }}">上一页</a>
{% else %}
<a>上一页</a>
{% endif %}
{% if content.has_next %}
<a href="/movie?a=flip&page={{ content.next_page_number }}">下一页</a>
{% else %}
<a>下一页</a>
{% endif %}
</body>
<script>
window.location.href = "http://www.php.cn"; //跳转
window.location.replace("http://www.php.cn");
$(location).attr('href', 'http://www.php.cn');
$(window).attr('location','http://www.php.cn');
$(location).prop('href', 'http://www.php.cn')
</script>
</html>
模板继承
基础模板(base.html)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}{% endblock %}</title> {# 定义填充位置 #}
</head>
{% include 'head.html' %} {# 引入局部模板 #}
<body>
{% block body %}{% endblock %}
</body>
</html>
局部模板(head.html)
<h1>学生选课系统</h1>
实际模板
{% extens 'base.html' %} {# 引入模板 #}
{% block title %} <div> 标题 </div> {% endblock %}
{% block body %} <div> 主体内容 </div> {% endblock %}
计算
% if sort == 'hot' %
#加法:
{{value|add:value2}}
#减法
{{value|add -value2}}
#乘法
{% widthratio value1 value2 value3%}
#除法
{% widthratio value1 value2 value3%}
CSRF
单独指定csrf验证需要
from django.shortcuts import render
from django.views.decorators.csrf import csrf_protect
@csrf_protect
def my_view(request):
c = {}
# ...
return render(request, "a_template.html", c)
单独指定忽略csrf验证
from django.views.decorators.csrf import csrf_exempt, csrf_protect
@csrf_exempt # 外面忽略csrf
def my_view(request):
@csrf_protect # 里面需要csrf
def protected_path(request):
do_something()
if some_condition():
return protected_path(request)
else:
do_something_else()
Ajax
获取cookie
当CSRF_USE_SESSIONS 和CSRF_COOKIE_HTTPONLY 都是False的时候
在模板中添加csrf_token令牌, csrf中间件就会把csrf_token值直接写入cookie 默认键为csrftoken
注意,为了保证在没有表单系统的时候,Django向cookie中写入了CSRF令牌,你需要在视图上使用装饰器django.views.decorators.csrf.ensure_csrf_cookie。
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
const csrftoken = getCookie('csrftoken');
当CSRF_USE_SESSIONS 和CSRF_COOKIE_HTTPONLY 有一个是True的时候
{% csrf_token %} {# 显式的在模板中添加csrf_token #}
<script>
const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value; // css选择器匹配第一个
</script>
发送请求
es6(不会)
const request = new Request(
/* URL */,
{headers: {'X-CSRFToken': csrftoken}}
);
fetch(request, {
method: 'POST',
mode: 'same-origin' // Do not send CSRF token to another domain.
}).then(function(response) {
// ...
});
jquery
const csrftoken = getCookie('csrftoken');
$.ajaxSetup({
data: {csrfmiddlewaretoken: '{{ csrf_token }}' }, // 用于ajax 设置post请求的csrftoken
});
// 自己写的
$.ajax({
type: "post", //请求方式
url: "/sign/", //请求地址
headers: { //请求头
'X-CSRFToken': csrftoken //设置csrf的cookie
},
data:{user:user,name:name,psd:psd,code:code}, //正常post数据格式
success: function(data){ //请求结果会返回到这
if (data==='1'){
window.location.href='/login/'
}
else {
alert('验证码不正确')
}
},
})
验证码
function send(){
let user = $("#user").val() //获取验证码发送对象
$.get('/send/', {user:user}) //发送验证码发送请求
let send = $("#send") //获取验证码标签
send.attr('onclick','') //防止重复发送验证码
function setTime() { // 设置倒计时
let d = send.text()
if(d>0){
send.text(d-1) // 倒计时减一
}
else {
send.attr('onclick','send()') //恢复点击
send.text('发送') //回复发送文字
clearInterval(codeTime) //停止定时任务
}
}
send.text(60) //设置倒计时间
let codeTime = window.setInterval(setTime, 1000) // 开始倒计时
}
cookie
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') { //判断cookie不为空
const cookies = document.cookie.split(';'); //获取cookie列表
for (let i = 0; i < cookies.length; i++) { //遍历cookie
const cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) { //查找到想要的cookie
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue; //返回cookie
}
注册
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>注册</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
</head>
<body>
<form action="{% url 'sign' %}">
{% csrf_token %}
<label for="user">用户名</label><input type="text" name="user" placeholder="手机号/邮箱" id="user"><br>
<label for="name">昵称</label><input type="text" name="name" id="name"><br>
<label for="psd">密码</label><input type="password" name="psd" id="psd"><br>
<label for="code">验证码</label><input type="text" name="code" id="code"><button class="send" type="button" οnclick="send()" id="send">发送</button><br>
<input type="button" οnclick="sign()" value="注册">
</form>
<script>
//获取cookie
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') { //判断cookie不为空
const cookies = document.cookie.split(';'); //获取cookie列表
for (let i = 0; i < cookies.length; i++) { //遍历cookie
const cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) { //查找到想要的cookie
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue; //返回cookie
}
function send(){
let user = $("#user").val() //获取验证码发送对象
$.get('/send/', {user:user}) //发送验证码发送请求
let send = $("#send") //获取验证码标签
send.attr('onclick','') //防止重复发送验证码
function setTime() { // 设置倒计时
let d = send.text()
if(d>0){
send.text(d-1) // 倒计时减一
}
else {
send.attr('onclick','send()') //恢复点击
send.text('发送') //回复发送文字
clearInterval(codeTime) //停止定时任务
}
}
send.text(60) //设置倒计时间
let codeTime = window.setInterval(setTime, 1000) // 开始倒计时
}
function sign(){
let user = $("#user").val()
let name = $("#name").val()
let psd = $("#psd").val()
let code = $("#code").val()
const csrftoken = getCookie('csrftoken');
$.ajax({
type: "post", //请求方式
url: "/sign/", //请求地址
headers: { //请求头
'X-CSRFToken': csrftoken //设置csrf的cookie
},
data:{user:user,name:name,psd:psd,code:code}, //正常post数据格式
success: function(data){ //请求结果会返回到这
alert(data)
if (data==='1'){
window.location.href='/login/'
}
else {
alert('验证码不正确')
}
},
})
}
</script>
</body>
</html>
MarkDown
1 下载mdeditor
https://github.com/pylixm/django-mdeditor
2 配置settings
X_FRAME_OPTIONS = 'SAMEORIGIN' # django3 使用mdeditor(本地app)
INSTALLED_APPS = [
'mdeditor', # markdown编辑器
]
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media') # 定义资源路径
3配置根路由
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
path('mdeditor/', include('mdeditor.urls')),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
4 models
from mdeditor.fields import MDTextField
class ExampleModel(models.Model):
content = MDTextField() # 后台就会显示markdown编辑器
5 模板配置(编辑修改)
<link rel="stylesheet" href="/static/mdeditor/css/editormd.css"> <— 引入mdeditor的js文件 —>
<script src="http://libs.baidu.com/jquery/2.1.4/jquery.min.js"></script>
<textarea name="article" id="post-md" class="editormd-markdown-textarea"></textarea> <--编辑区域-->
<textarea id="post-html" class="editormd-html-textarea"></textarea> <--预览区域-->
<button οnclick="submit()" class="submit">提交</button>
<script>
$.ajaxSetup({
data: {csrfmiddlewaretoken: '{{ csrf_token }}' },
});
let editor;
$(function () {
editor = editormd('post', { // 渲染文章的方法, 赋值给editor, 以便于后边获取数据
width: '90%',
height: '640',
syncScrolling: "single",
path: '/static/mdeditor/js/lib/', //codemirror路径,尾部必须有斜杠
saveHTMLToTextarea: true,//用于表单提交
//用于文件图片上传
imageUpload: true,
imageFormats: ["jpg", "jpeg", "gif", "png", "bmp", "webp"],
imageUploadURL: "uploadimg",//请求地址
})
})
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
const csrftoken = getCookie('csrftoken');
function submit(){
let title = $('#title').val()
let md = editor.getMarkdown()
let html = editor.getHTML()
$.ajax({
type:'post',
url:'/post/',
headers:{
'X-CSRFToken':csrftoken
},
data: {data:JSON.stringify({'title':title,'md':md,'html':html})},
success: function (d){
if (d==='1'){
$('#title').val('')
editor.clear()
alert('success')
}
else {
alert('field')
}
}
})
}
$(function (){ //加载完,运行markdown解析
mdeditor()
})
</script>
6 模板配置(单纯显示)
<link rel="stylesheet" href="/static/mdeditor/css/editormd.css"> <--引入mdeditor的js文件-->
<script src="http://libs.baidu.com/jquery/2.1.4/jquery.min.js"></script> <--引入jquery-->
<textarea id="t-content" style="display: none">{{ blog.content }}</textarea>
<script>
$.ajaxSetup({
data: {csrfmiddlewaretoken: '{{ csrf_token }}' },
});
function mdeditor(){
editormd.markdownToHTML('content',{
htmlDecode: "style,script,iframe", //可以过滤标签解码
emoji: true,
taskList:true,
tex: true, // 默认不解析
flowChart:true, // 默认不解析
sequenceDiagram:true,
})
}
$(function (){ //加载完运行markdown解析
mdeditor()
})
</script>
settings.py
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent # 项目根目录
SECRET_KEY = 'django-insecure-mg8-zp$)_3620wee4vs5=*)fn4$t&p=ru6p+++gq6xqo5f%hiw'
DEBUG = True
APPEND_SLASH=True # 连接结尾是否需要加 / , 默认需要
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app', # 把新建的应用加入到这
'movie',
]
MIDDLEWARE = [ # 中间件, 对请求与响应处理
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', # 打开会话
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', # 跨站攻击检测,没有 token 的 post 请求会被拦截
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'note.urls' # 跟路由文件路径
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'], # 公共模板路径, 首先到DIRS查找,然后到APP_DIRS查找
'APP_DIRS': True, # app私有路径,必须INSTALLED_APPS 中添加app,app文件夹创建tamlpates/app_name的文件夹, 使用模板 app_name/index.html
'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',
'main.context.global_info', # 引入全局上下文
],
},
},
]
WSGI_APPLICATION = 'note.wsgi.application'
# 数据库配置信息
DATABASES = {
'default': {
# 'ENGINE': 'django.db.backends.sqlite3', # 连接 sqlite3
# 'NAME': BASE_DIR / 'db.sqlite3', # 数据库位置
'ENGINE': 'django.db.backends.mysql', # 连接 mysql
'NAME': 'objects', # 数据库名
'HOST': 'localhost',
'PORT': 3306,
'USER': 'root',
'PASSWORD': '123456'
}
}
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
LANGUAGE_CODE = 'zh-Hans' # 语言
TIME_ZONE = 'UTC' # 时区
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.2/howto/static-files/
STATIC_URL = '/static/' # 访问静态文件的起始url
STATIC_ROOT = os.path.join(BASE_DIR, "static") # python manage.py collectstatic 部署时执行一收集所有静态文件
# 生产环境必须配置指定STATIC_ROOT位置, 收集就会把STATICFILES_DIRS里的,和app.static里的,都复制到STATIC_ROOT里
STATICFILES_DIRS = [ # 额外放置公共静态文件的目录,只是便于管理, 收集后都会放在STATIC_ROOT文件夹里
os.path.join(BASE_DIR, "public_static", "css"),
os.path.join(BASE_DIR, "public_static", "js"),
os.path.join(BASE_DIR, "public_static", "img"),
]
MEDIA_URL = '/media/' # 媒体文件起始url
MEDIA_ROOT = os.path.join(BASE_DIR, 'media') # 媒体文件搜索路径
# Default primary key field type
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
from django.contrib.sessions.backends import db, cache, cached_db, file
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db' # 设置session(会话)保存方式->db, cache, cached_db, file
SESSION_FILE_PATH = '/temp' # 以file保存session时的文件路径
SESSION_CACHE_ALIAS = 'session' # 定义CACHES中session对应的缓存名
# session 设置
SESSION_COOKIE_NAME = "sessionid" # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认)
SESSION_COOKIE_PATH = "/" # Session的cookie保存的路径(默认)
SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名(默认)
SESSION_COOKIE_SECURE = False # 是否Https传输cookie(默认)
SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http传输(默认)
SESSION_COOKIE_AGE = 1209600 # Session的cookie失效日期(2周)(数字为秒数)(默认)
SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否关闭浏览器使得Session过期(默认)
SESSION_SAVE_EVERY_REQUEST = False # 是否每次请求都保存Session,默认修改之后才保存(默认)
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', # 缓存保存方式
},
'session': { # 以cache保存session是的配置
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': 'redis://127.0.0.1:11211' # 缓存保存位置(地址,文件夹位置,数据库名)
}
}
# global_settings # 全局设置包
缓存
Memcached
# localhost
# pip install python-memcached
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': '127.0.0.1:11211',
}
}
# unix soket
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': 'unix:/tmp/memcached.sock',
}
}
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': [
'172.19.26.240:11211',
'172.19.26.242:11211',
]
# 我们也可以给缓存机器加权重,权重高的承担更多的请求,如下
'LOCATION': [
('172.19.26.240:11211',5),
('172.19.26.242:11211',1),
]
}
}
数据库缓存
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
'LOCATION': 'my_cache_table',
}
}
文件系统缓存
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': '/var/tmp/django_cache',#这个是文件夹的路径
#'LOCATION': 'c:\foo\bar',#windows下的示例
}
}
本地内存缓存(用于测试)
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'unique-snowflake'
}
}
admin.py
后台管理
创建超级用户
python manage.py createsuperuser
后台管理对应的models
from django.db import models
from django.db.models import Manager, QuerySet
from hashlib import md5
def get_course(name):
try:
return Course.objects.get(name=name)
except Course.DoesNotExist:
return Course.objects.create(name=name)
def get_class(name):
try:
return Class.objects.get(name=name)
except Class.DoesNotExist:
return Class.objects.create(name=name)
def get_student(name, classname):
student = Student.objects.filter(name=name, classname=classname)
if student:
student = student[0]
else:
student = Student.objects.create(name=name, classname=classname)
return student
class StudentManager(Manager): # 必须继承Manager, 不然会失去其他方法
def insert(self, **kwargs): # 同时添加本身及外键关联的对象,kwargs参数键值对
classname = kwargs['classname'] # 获取传入班级名称
classname = get_class(classname) # 获取班级model对象
kwargs['classname'] = classname # 把参数的class对象名改为class的model对象
kwargs['psd'] = md5(kwargs['psd'].encode()).hexdigest()
courses = kwargs.pop('courses') # 获取课程名元组,并在kwargs中移除课程元组
try:
student = Student.objects.get(name=kwargs['name'])
student.phone = kwargs['phone'] # 更新数据
student.psd = kwargs['psd']
student.save()
except Student.DoesNotExist:
student = Manager.create(self, **kwargs) # 通过kwargs创建学生
student.save()
print('new student join : ' + kwargs['name'])
courses = [get_course(i) for i in courses] # 根据课程名元组创建课程model对象
for course in courses: # 循环创建关联关系
score = Score(student=student, course=course, score=-1) # 自定义中间表没有create方法
score.save()
class ClassManager(Manager):
def insert(self, name, students): # 同时添加班级和学生
classname = Manager.filter(self, name=name)
if classname:
classname = classname[0]
else:
classname = Manager.create(self, name=name)
for i in students:
classname.student_set.add(get_student(i, classname))
return classname
class Class(models.Model):
name = models.CharField(max_length=64)
objects = ClassManager()
def __str__(self):
return 'class: ' + self.name
class Course(models.Model):
name = models.CharField(max_length=64)
def __str__(self):
return 'course: ' + self.name
class Student(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=64, null=False, unique=True, blank=False)
icon = models.ImageField(default='icon/default.ico', upload_to='icon/') # upload_to 文件在MEDIA_ROOT目录的保存位置
phone = models.CharField(max_length=11, null=False, blank=False)
psd = models.CharField(max_length=200, null=False, blank=False)
classname = models.ForeignKey(Class, on_delete=models.CASCADE)
courses = models.ManyToManyField(Course, through='Score') # 自建中间表
objects = StudentManager()
def __str__(self):
return 'student: ' + self.name
@staticmethod
def get(*args, **kwargs): # 可以直接根据班级名课程名查询
if 'courses' in kwargs.keys(): # 只能查询单个课程
kwargs['courses'] = Course.objects.get(name=kwargs['courses'])
if 'classname' in kwargs.keys():
kwargs['classname'] = Class.objects.get(name=kwargs['classname'])
return Student.objects.filter(**kwargs)
class Score(models.Model):
student = models.ForeignKey(Student, on_delete=models.CASCADE)
course = models.ForeignKey(Course, on_delete=models.CASCADE)
score = models.SmallIntegerField(default=None)
@staticmethod
def get(student=None, course=None): # 查询分数
if not student and course:
course_m = Course.objects.get(name=course)
score = Score.objects.filter(course=course_m)
elif not course and student:
student_m = Student.objects.get(name=student)
score = Score.objects.filter(student=student_m)
elif course and student:
print('both')
student_m = Student.objects.get(name=student)
course_m = Course.objects.get(name=course)
score = Score.objects.filter(student=student_m, course=course_m)
else:
score = Score.objects.filter()
return score # 返回Score的QuerySet
@staticmethod
def set(student, course, num): # 设置分数
try:
student_m = Student.objects.get(name=student)
course_m = Course.objects.get(name=course)
score = Score.objects.get(student=student_m, course=course_m)
score.score = num
score.save()
return score
except Student.DoesNotExist or Course.DoesNotExist:
raise Exception(f'学生({student})或课程({course})不存在')
自定义后台管理页面
from django.contrib import admin
from .models import *
# Register your models here.
class StudentInline(admin.TabularInline): # 创建行内元素, 该对象必须有一个的外键是主对象
"""
admin.StackedInline 分块显示
admin.TabularInline 表格显示
"""
model = Student # 显示的对象
extra = 0 # 额外显示的空行数
class ScoreInline(admin.TabularInline):
model = Score
extra = 0
class ClassAdmin(admin.ModelAdmin):
inlines = [StudentInline]
@admin.display(description='courses') # 自定义列表页字段, description字段名
def showcourse(obj): # obj传入的为对象的实例
courses = obj.courses.all()
return '|'.join([i.name for i in courses]) # 返回字段显示的内容
class StudentAdmin(admin.ModelAdmin):
# 详情页布局, 一个元组为一个版块, 第一个为版块标题, 后面的字典为显示的内容
fieldsets = [(None, {'fields': ['name', 'classname']}),
('message', {'fields': ['phone']}), ]
list_display = ('name', 'classname', 'phone', showcourse) # 问题列表页显示的字段,引号的为模型的字段,没有的为方法, 外键显示__str__, 多对多不支持
list_display_links = ('name', 'phone') # 让别的字段也能被点击链接到详情页
list_filter = ('classname',) # 在问题列表添加过滤器,用于筛选结果,类型->BooleanField`、CharField`、DateField、DateTimeField、IntegerField、ForeignKey、ManyToManyField
show_full_result_count = True # 过滤后页面显示总数
inlines = [ScoreInline] # 问题详情页添加行内外键元素(附加版块)
list_max_show_all = 20
list_per_page = 20 # 每页显示数量
list_select_related = True
ordering = ('name', 'classname') # 排序
raw_id_fields = ('classname',)
readonly_fields = ('name', 'classname') # fieldsets 中的那个字段不可修改
save_as = True # 修改页面把保存并增加另一个,改为,保存为新的
search_fields = ['name', 'classname__name'] # 添加搜索框,只能搜索(或者外键的)CharField 或 TextField
# radio_fields = {'classname': admin.VERTICAL} # 把外键或choice字段的显示方式,由下拉选择框变为圆点单选
class CourseAdmin(admin.ModelAdmin):
fields = ['name']
inlines = [ScoreInline]
admin.site.register(Student, StudentAdmin) # 绑定Model与类, 后台添加对于question的修改选项
admin.site.register(Class, ClassAdmin)
用户系统
urls.py
urlpatterns = [
path('', include('django.contrib.auth.urls')),
]
# 系统默认的用户系统url
urlpatterns = [
path('', wrap(self.index), name='index'),
path('login/', self.login, name='login'),
path('logout/', wrap(self.logout), name='logout'),
path('password_change/', wrap(self.password_change, cacheable=True), name='password_change'),
path(
'password_change/done/',
wrap(self.password_change_done, cacheable=True),
name='password_change_done',
),
path('autocomplete/', wrap(self.autocomplete_view), name='autocomplete'),
path('jsi18n/', wrap(self.i18n_javascript, cacheable=True), name='jsi18n'),
path(
'r/<int:content_type_id>/<path:object_id>/',
wrap(contenttype_views.shortcut),
name='view_on_site',
),
]
创建
from django.contrib.auth.models import User
User.objects,create_user('user_name','email','psd') # 创建并保存用户
WebSoket(基于channels异步双向通讯)
1 /project/settings.py
ASGI_APPLICATION = "project.asgi.application" # asgi应用位置,可用于channels的get_default_application获取app位置
# CHANNEL_LAYERS = { # channels通道,用于不通实例之间的通信
# 'default': {
# 'BACKEND': 'channels_redis.core.RedisChannelLayer', # 使用redis后台, 用与存储消息
# 'CONFIG': {
# "hosts": ["redis://127.0.0.1:6379/0"],
# },
# },
# }
CHANNEL_LAYERS = { # 本地开发调试使用
"default": {
"BACKEND": "channels.layers.InMemoryChannelLayer"
}
}
2 /project/asgi.py
import os
import django
from django.urls import path
from channels.routing import ProtocolTypeRouter, URLRouter
from myapp.consumers import ChatConsumer
from channels.sessions import SessionMiddlewareStack
django.setup()
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project.settings')
application = ProtocolTypeRouter({ # 可以直接把routing的app写到这,就不用get_default_application和在settings设置ASGI_APPLICATION了
'websocket': SessionMiddlewareStack( # websocket连接类型,类似http
URLRouter([ # 绑定websocket地址
path('ws/group/<slug:group>/<str:user>/', ChatConsumer.as_asgi()), # 不知道为啥要用as_asgi
])
),
})
3 /project/consumers.py
就类似views文件
import json
from channels.exceptions import InvalidChannelLayerError, AcceptConnection, DenyConnection
from channels.generic.websocket import AsyncJsonWebsocketConsumer
# self.scope
# scope = {'type': 'websocket', # 协议类型
# 'path': '/ws/group/2/', # 访问地址
# 'raw_path': b'/ws/group/2/',
# 'headers': [(b'host', b'127.0.0.1:8000'), (b'connection', b'Upgrade'), (b'pragma', b'no-cache'),
# (b'cache-control', b'no-cache'), (b'user-agent',
# b'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36 Edg/95.0.1020.30'),
# (b'upgrade', b'websocket'), (b'origin',
# b'http://127.0.0.1:8000'),
# (b'sec-websocket-version',
# b'13'), (b'accept-encoding', b'gzip, deflate, br'),
# (b'accept-language', b'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6'), (b'cookie',
# b'csrftoken=UGPZ42LfQW4zNWk7PRfl99SQjcO31Sd3VxwpfaemC2BHNfaunMJDOaGEHodNo9JW; sessionid=ud440kh2pyat2yayoqoz34yx0f54bvft'),
# (b'sec-websocket-key', b'MJstdH1WPn2+QjIPAnfjKA=='),
# (b'sec-websocket-extensions', b'permessage-deflate; client_max_window_bits')],
# 'query_string': b'',
# 'client': ['127.0.0.1', 50977], # 客户端
# 'server': ['127.0.0.1', 8000], # 服务器
# 'subprotocols': [],
# 'asgi': {'version': '3.0'}, # asgi版本
# 'cookies': {'csrftoken': 'UGPZ42LfQW4zNWk7PRfl99SQjcO31Sd3VxwpfaemC2BHNfaunMJDOaGEHodNo9JW',
# 'sessionid': 'ud440kh2pyat2yayoqoz34yx0f54bvft'}, # cookie
# 'session': '<django.utils.functional.LazyObject object at 0x000002111B742130>', # session
# 'path_remaining': '',
# 'url_route': {'args': (), 'kwargs': {'chat_id': '2'}}, # 路由参数
# }
consumers = {}
class ChatConsumer(AsyncJsonWebsocketConsumer):
# 创建consumer 继承AsyncConsumer的类必须所有方法用 async def 定义, 继承SyncConsumer的就用 def 定义
group = None
user = None
async def connect(self): # 重写方法, 创建连接
self.group = self.scope['url_route']['kwargs']['group'] # group就相当于群号
self.user = self.scope['url_route']['kwargs']['user']
# 向组(self.table)里添加一个channel连接(self.channel_name),
await self.channel_layer.group_add(self.group, self.channel_name)
# self.channel_layer(Consumer实例的指针), self.table(组名), self.channel_name(通道名称)
consumers['user'] = self
await self.accept() # 返回接受连接的信息
async def disconnect(self, close_code): # 断开连接
# 从组中移除channel
await self.channel_layer.group_discard(self.group, self.channel_name)
async def receive(self, text_data=None, bytes_data=None, **kwargs): # 消息接收器
# 根据消息的不同参数,用不同的消息发送器,发送不同的消息
data = {'message': text_data, 'user': self.user}
# 向组发送消息
# 接受前端发送的消息,然后传给发送器
await self.channel_layer.group_send(self.group, {'type': 'group.message', 'data': data})
# type消息类型(决定把消息给那个函数), 会把结果返给对应类型名字的函数, 即不同的type不同的发送器 名字中的.会被替换为_
# 'type': 'message' 的消息发送器 # event 消息接收器接收到的消息
async def group_message(self, event):
data = event['data'] # send_json是把数据json话, 而不是需要传入json格式数据
await self.send_json(data) # 发送格式化消息为json格式后,传给前端的接收器
4 模板
<div id="chat">
<div id="chat-box">
</div>
<input id="message-input" type="text" size="100"/><br/>
<input id="message-submit" type="button" value="Send"/>
</div>
<script>
let group = '{{ group }}';
let user = '{{ user }}'
//创建WebSocket连接, 浏览器默认支持 ws://定义连接类型
let Socket = new WebSocket('ws://' + window.location.host + '/ws/group/' + group +'/'+user+'/');
Socket.onmessage = function (e) { //接收消息
let data = JSON.parse(e.data); // {'type':'message', 'key':'value'}, 根据不同的type做不同的处理
if(data['user'] === user){
document.querySelector('#chat-box').innerHTML += '<div class="message-self">'+data['message']+"</div>";
}else {
document.querySelector('#chat-box').innerHTML += '<div class="message-other">'+data['message']+"</div>";
}
};
document.querySelector('#message-submit').onclick = function (e) {
const messageInputDom = document.querySelector('#message-input');
const data = messageInputDom.value;
Socket.send(data); //发送消息,给后端的接收器
messageInputDom.value = '';
};
Socket.onclose = function (e) { //socket关闭后的提示
console.error('Chat socket closed unexpectedly');
};
document.querySelector('#message-input').focus();
document.querySelector('#message-input').onkeyup = function (e) {
if (e.keyCode === 13) { // enter, return
document.querySelector('#chat-message-submit').click();
}
};
</script>