一篇文章带你了解Django

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>&nbsp;<td>{{ i.name }}</td>&nbsp;<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">&laquo; 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 &raquo;</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

用于生成表单的html代码
验证信息
保留上次的内容

表单字段
部件(输出字段的html标签)
表单和字段验证

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>
  • 20
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值