赞
踩
django-admin startproject name
python manage.py runserver 127.0.0.1:8000 # 以 127.0.0.1:8000,为地址启动服务
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 # 主文件
""" 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 的路径
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','')
原子性: 事务要么全部成功要么全部失败
一致性: 相互关联的字段要同步变化
隔离性:
脏读->读取到另一个未提交事务处理过程中修改过的数据
不可重复读->查询间隔中,其他事务修改了数据
幻读->全表修改后插入了新行,新行并不会被修改
持久性
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,)))
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'] # 可以定义的请求方法
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
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
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() # 对象保存到数据库
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() """
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 包含横夺内置验证器,用于验证各种类型的数据 """
<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':'河北科技大学'}
映射现有表到数据库(新建的模型都要先迁移再映射)
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 # 跨文件导入
所有不是字段的东西
<!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>
<!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>
<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%}
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)
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()
当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>
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) {
// ...
});
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) // 开始倒计时 }
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>
https://github.com/pylixm/django-mdeditor
X_FRAME_OPTIONS = 'SAMEORIGIN' # django3 使用mdeditor(本地app)
INSTALLED_APPS = [
'mdeditor', # markdown编辑器
]
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media') # 定义资源路径
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)
from mdeditor.fields import MDTextField
class ExampleModel(models.Model):
content = MDTextField() # 后台就会显示markdown编辑器
<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>
<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>
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 # 全局设置包
# 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'
}
}
后台管理
创建超级用户
python manage.py createsuperuser
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)
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') # 创建并保存用户
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"
}
}
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 ]) ), })
就类似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格式后,传给前端的接收器
<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>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。