第十一篇 Django 【进阶篇】

django相关知识点

补充

1 .离线脚本

-插入单条数据

创建文件夹->项目目录/scripts
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import os
import sys
import django

base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(base_dir)
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "s25.settings")
django.setup()  # os.environ['DJANGO_SETTINGS_MODULE']


from web import models
# 往数据库添加数据:链接数据库、操作、关闭链接
models.UserInfo.objects.create(username='陈硕', email='chengshuo@live.com', mobile_phone='13838383838', password='123123')
插入单条数据

封装一下

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import os
import sys
import django

base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(base_dir)
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "s25.settings")
django.setup()  # os.environ['DJANGO_SETTINGS_MODULE']
base.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import base
from web import models


def run():
    models.PricePolicy.objects.create(
        title='VIP',
        price=100,
        project_num=50,
        project_member=10,
        project_space=10,
        per_file_size=500,
        category=2
    )

    models.PricePolicy.objects.create(
        title='SVIP',
        price=200,
        project_num=150,
        project_member=110,
        project_space=110,
        per_file_size=1024,
        category=2
    )

    models.PricePolicy.objects.create(
        title='SSVIP',
        price=500,
        project_num=550,
        project_member=510,
        project_space=510,
        per_file_size=2048,
        category=2
    )


if __name__ == '__main__':
    run()
create_price_police.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import base
from web import models


def run():
    exists = models.PricePolicy.objects.filter(category=1, title="个人免费版").exists()
    if not exists:
        models.PricePolicy.objects.create(
            category=1,
            title="个人免费版",
            price=0,
            project_num=3,
            project_member=2,
            project_space=20,
            per_file_size=5
        )


if __name__ == '__main__':
    run()
init_price_police.py

-插入多条数据

#!UsersLocalProgramsPython37
# -*- coding: utf-8 -*-

import base
import os,json
from yuqing import models


def save_data_jsonl(data_dict_list,path="./new_id_source.jsonl"):
    """将数据保存为jsonl格式"""
    with open(path,mode='a',encoding="utf-8") as fp:
        for data_dict in data_dict_list:
            fp.write(json.dumps(data_dict,ensure_ascii=False)+"
")
def read_jsonl(path="./baidu.jsonl"):
    """读取jsonl文件,返回字典对象列表"""
    data_dict_list=[]
    with open(path,mode="r",encoding="utf-8") as fp:
        lines =fp.readlines()
        for line in lines:
            data_dict =json.loads(line)
            data_dict_list.append(data_dict)
    return data_dict_list

def run():

    """批量插入新闻详情数据"""
    file_list = os.listdir('F:/yuqing/hspublic_sentiment/crawl_spider/data') # 文件列表
    for item in file_list:
        print(item)
        news_obj_list =[]
        obj_list =read_jsonl(path=f"F:/yuqing/hspublic_sentiment/crawl_spider/data/{item}")
        for obj in obj_list:
            # web_source_id = obj.pop('web_source')
            # hospital_id =obj.pop("hospital")
            # obj["web_source_id"] =int(web_source_id) # 将字典中的web_source改为web_source_id
            # obj["hospital_id"] =int(hospital_id)
            news_obj_list.append(models.NewsDetail(**obj))
        models.NewsDetail.objects.bulk_create(news_obj_list)

    print("搞定")
# def test():
#     news_obj =[]
#     obj={"title": "这种肝病发病率已超甲肝 专家:接种疫苗是最好的预防方式", "url": "https://baijiahao.baidu.com/s?id=1694666959262889051&wfr=spider&for=pc", "release_time": "2021-03-19", "web_source": 1, "hospital": 7, "source": "潇湘名医", "article_abstract": "“爱肝护肝、防治结合、遏制肝炎”义诊活动现场 见圳客户端·深圳新闻网2021年3月19日讯(记者 刘梦婷 实习记者 王莉琳)为保障人民身体健康,推动肝病防治工作,2021年3月18日,深圳市中国科学院大学深圳医院(光明)楼村工业社区健康..."}
#     obj2 ={"title": "中科院深理工用地方案获批 位于光明新湖街道,用地面积逾……", "url": "https://baijiahao.baidu.com/s?id=1696520284290792070&wfr=spider&for=pc", "release_time": "2021-04-09", "web_source": 1, "hospital": 7, "source": "二三里资讯深圳", "article_abstract": "中国科学院深圳理工大学位于新湖街道,为公共管理与公共服务用地,用地面积474466.65平方米,转用面积24.6220公顷。中山大学附属第七医院(深圳)二期位于新湖街道,为公共管理与公共服务用地,转用面积1.3215公顷。来源:深圳新闻网声明:..."}
#
#     web_source_id = obj.pop('web_source')
#     hospital_id = obj.pop("hospital")
#     obj["web_source_id"] = int(web_source_id)
#     obj["hospital_id"] = int(hospital_id)
#     print(obj)
#     news_obj.append(models.NewsDetail(**obj))
#
#     web_source_id = obj2.pop('web_source')
#     hospital_id = obj2.pop("hospital")
#     obj2["web_source_id"] = int(web_source_id)
#     obj2["hospital_id"] = int(hospital_id)
#     print(obj2)
#     news_obj.append(models.NewsDetail(**obj2))
#     print(news_obj)
#
#     models.NewsDetail.objects.bulk_create(news_obj)

if __name__ == '__main__':
    # run()
    # test()
    pass
buik_create批量插入


1 路由--视图

  1 4.1 简单的路由配置(下拉-->4.6为path匹配)
  2 from django.urls import path,re_path
  3 from app01 import views
  4 
  5 urlpatterns = [
  6 re_path(r'^articles/2003/$', views.special_case_2003),
  7 re_path(r'^articles/([0-9]{4})/$', views.year_archive),
  8 re_path(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive),
  9 re_path(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail),
 10 ]
 11 注意:一对圆括号 -->>从URL 中捕获一个值
 12 
 13 4.2 有名分组 ?P<name>
 14 
 15 from django.urls import path,re_path
 16 from app01 import views
 17 
 18 urlpatterns = [
 19 re_path(r'^articles/2003/$', views.special_case_2003),
 20 re_path(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
 21 re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),
 22 re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$', views.article_detail),
 23 ]
 24 解释:
 25 /articles/2005/03/ 请求将调用views.month_archive(request, year='2005', month='03')函数,而不是views.month_archive(request, '2005', '03')。
 26 /articles/2003/03/03/ 请求将调用函数views.article_detail(request, year='2003', month='03', day='03')。
 27 
 28 4.3 分发
 29 
 30 from django.urls import path,re_path,include
 31 from app01 import views
 32 
 33 urlpatterns = [
 34 re_path(r'^admin/', admin.site.urls),
 35 re_path(r'^blog/', include('blog.urls')),
 36 ]
 37 
 38 4.4 反向解析
 39 在使用Django 项目时,一个常见的需求是获得URL 的最终形式,以用于嵌入到生成的内容中(视图中和显示给用户的URL等)或者用于处理服务器端的导航(重定向等)。人们强烈希望不要硬编码这些URL(费力、不可扩展且容易产生错误)或者设计一种与URLconf 毫不相关的专门的URL 生成机制,因为这样容易导致一定程度上产生过期的URL。 在需要URL 的地方,对于不同层级,Django 提供不同的工具用于URL 反查:
 40 
 41 在模板中:使用url 模板标签。
 42 在Python 代码中:使用from django.urls import reverse()函数 urls.py:
 43 from django.conf.urls import url
 44 from . import views
 45 
 46 urlpatterns = [
 47 #...
 48 re_path(r'^articles/([0-9]{4})/$', views.year_archive, name='news-year-archive'),
 49 #...
 50 ]
 51 
 52 在模板中:
 53 
 54 <a href="{% url 'news-year-archive' 2012 %}">2012 Archive</a>
 55 
 56 <ul>
 57 {% for yearvar in year_list %}
 58 <li><a href="{% url 'news-year-archive' yearvar %}">{{ yearvar }} Archive</a></li>
 59 {% endfor %}
 60 </ul>
 61 在python中:
 62 
 63 from django.urls import reverse
 64 from django.http import HttpResponseRedirect
 65 
 66 def redirect_to_year(request):
 67 # ...
 68 year = 2006
 69 # ...
 70 return HttpResponseRedirect(reverse('news-year-archive', args=(year,))) # 同redirect("/path/")
 71 
 72 4.5 名称空间
 73 
 74 project的urls.py:
 75 
 76 urlpatterns = [
 77 re_path(r'^admin/', admin.site.urls),
 78 re_path(r'^app01/', include(("app01.urls","app01"))),  #传递一个元组
 79 re_path(r'^app02/', include("app02.urls","app02")),
 80 ]
 81 app01.urls:
 82 
 83 urlpatterns = [
 84 re_path(r'^index/', index,name="index"),
 85 ]
 86 app02.urls:
 87 
 88 urlpatterns = [
 89 re_path(r'^index/', index,name="index"),
 90 ]
 91 app01.views
 92 
 93 from django.core.urlresolvers import reverse
 94 def index(request):
 95 return HttpResponse(reverse("app01:index"))
 96 app02.views
 97 
 98 from django.core.urlresolvers import reverse
 99 def index(request):
100 return HttpResponse(reverse("app02:index"))
101 
102 4.6 django2.0版的path
103 
104 基本示例:
105 from django.urls import path  
106 from . import views  
107 urlpatterns = [  
108     path('articles/2003/', views.special_case_2003),  
109     path('articles/<int:year>/', views.year_archive),  
110     path('articles/<int:year>/<int:month>/', views.month_archive),  
111     path('articles/<int:year>/<int:month>/<slug>/', views.article_detail),  
112 ]
113 基本规则:
114 
115 使用尖括号(<>)从url中捕获值。
116 捕获值中可以包含一个转化器类型(converter type),比如使用 捕获一个整数变量。若果没有转化器,将匹配任何字符串,当然也包括了 / 字符。
117 无需添加前导斜杠。
118 以下是根据https://docs.djangoproject.com/en/2.0/topics/http/urls/#example而整理的示例分析表:
119 
120 
121 
122 4.7path转化器
123 文档原文是Path converters,暂且翻译为转化器。
124 Django默认支持以下5个转化器:
125 
126 str,匹配除了路径分隔符(/)之外的非空字符串,这是默认的形式
127 int,匹配正整数,包含0。
128 slug,匹配字母、数字以及横杠、下划线组成的字符串。
129 uuid,匹配格式化的uuid,如 075194d3-6885-417e-a8a8-6c931e272f00。
130 path,匹配任何非空字符串,包含了路径分隔符
131 注册自定义转化器
132 对于一些复杂或者复用的需要,可以定义自己的转化器。转化器是一个类或接口,它的要求有三点:
133 
134 regex 类属性,字符串类型
135 to_python(self, value) 方法,value是由类属性 regex 所匹配到的字符串,返回具体的Python变量值,以供Django传递到对应的视图函数中。
136 to_url(self, value) 方法,和 to_python 相反,value是一个具体的Python变量值,返回其字符串,通常用于url反向引用。
137 例子:
138 
139 class FourDigitYearConverter:  
140     regex = '[0-9]{4}'  
141     def to_python(self, value):  
142         return int(value)  
143     def to_url(self, value):  
144         return '%04d' % value
145 使用register_converter 将其注册到URL配置中:
146 
147 from django.urls import register_converter, path  
148 from . import converters, views  
149 register_converter(converters.FourDigitYearConverter, 'yyyy')  
150 urlpatterns = [  
151     path('articles/2003/', views.special_case_2003),  
152     path('articles/<yyyy:year>/', views.year_archive),  
153     ...  
154 ]
路由
视图层
#1视图响应常用示例:
小知识:make_safe 的使用,不转义

from django.shortcuts import render, HttpResponse, HttpResponseRedirect, redirect
from django.utils.safestring import mark_safe
import datetime

def current_datetime(request):
    now = datetime.datetime.now()
    html = mark_safe("<html><body>It is now %s.</body></html>" % now)
    return HttpResponse(html)

#响应对象主要有三种形式:
-- HttpResponse()
-- render()
-- redirect()

#2request属性与方法

1.HttpRequest.GET

2.HttpRequest.POST
       # if contentType==urlencoded ,request.POST才有数据

3.HttpRequest.body

4.HttpRequest.path
  一个字符串,表示请求的路径组件(不含域名)。
  例如:"/music/bands/the_beatles/"

5.HttpRequest.method
  一个字符串,表示请求使用的HTTP 方法。必须使用大写。
  例如:"GET""POST"

6.HttpRequest.encoding

7.HttpRequest.META
   一个标准的Python 字典,包含所有的HTTP 首部。具体的头部信息取决于客户端和服务器,下面是一些示例:
    CONTENT_LENGTH —— 请求的正文的长度(是一个字符串)。
    CONTENT_TYPE —— 请求的正文的MIME 类型。
    HTTP_ACCEPT —— 响应可接收的Content-Type。
    HTTP_ACCEPT_ENCODING —— 响应可接收的编码。
    HTTP_ACCEPT_LANGUAGE —— 响应可接收的语言。
    HTTP_HOST —— 客服端发送的HTTP Host 头部。
    HTTP_REFERER —— Referring 页面。
    HTTP_USER_AGENT —— 客户端的user-agent 字符串。
    QUERY_STRING —— 单个字符串形式的查询字符串(未解析过的形式)。
    REMOTE_ADDR —— 客户端的IP 地址。
    REMOTE_HOST —— 客户端的主机名。
    REMOTE_USER —— 服务器认证后的用户。
    REQUEST_METHOD —— 一个字符串,例如"GET""POST"。
    SERVER_NAME —— 服务器的主机名。
    SERVER_PORT —— 服务器的端口(是一个字符串)。
   从上面可以看到,除 CONTENT_LENGTH 和 CONTENT_TYPE 之外,请求中的任何 HTTP 首部转换为 META 的键时,
    都会将所有字母大写并将连接符替换为下划线最后加上 HTTP_  前缀。
    所以,一个叫做 X-Bender 的头部将转换成 META 中的 HTTP_X_BENDER 键。

8.HttpRequest.FILES
  一个类似于字典的对象,包含所有的上传文件信息。
   FILES 中的每个键为<input type="file" name="" /> 中的name,值则为对应的数据。
  注意,FILES 只有在请求的方法为POST 且提交的<form> 带有enctype="multipart/form-data" 的情况下才会
   包含数据。否则,FILES 将为一个空的类似于字典的对象。

9.HttpRequest.COOKIES
 
10.HttpRequest.session

11.HttpRequest.user(用户认证组件下使用)
  一个 AUTH_USER_MODEL 类型的对象,表示当前登录的用户。
  如果用户当前没有登录,user 将设置为 django.contrib.auth.models.AnonymousUser 的一个实例。你可以通过 is_authenticated() 区分它们。

    例如:
    if request.user.is_authenticated():
        # Do something for logged-in users.
    else:
        # Do something for anonymous users.
       user 只有当Django 启用 AuthenticationMiddleware 中间件时才可用。
     -------------------------------------------------------------------------------------
    匿名用户
    class models.AnonymousUser
    django.contrib.auth.models.AnonymousUser 类实现了django.contrib.auth.models.User 接口,但具有下面几个不同点:
    id 永远为None。
    username 永远为空字符串。
    get_username() 永远返回空字符串。
    is_staff 和 is_superuser 永远为False。
    is_active 永远为 False。
    groups 和 user_permissions 永远为空。
    is_anonymous() 返回True 而不是False。
    is_authenticated() 返回False 而不是True。
    set_password()、check_password()、save() 和delete() 引发 NotImplementedError。
    New in Django 1.8:
    新增 AnonymousUser.get_username() 以更好地模拟 django.contrib.auth.models.User。


# request常用方法

1.HttpRequest.get_full_path()
  返回 path,如果可以将加上查询字符串。
  例如:"/music/bands/the_beatles/?print=true"
2.HttpRequest.is_ajax()
  如果请求是通过XMLHttpRequest 发起的,则返回True,方法是检查 HTTP_X_REQUESTED_WITH 相应的首部是否是字符串'XMLHttpRequest'。
  大部分现代的 JavaScript 库都会发送这个头部。如果你编写自己的 XMLHttpRequest 调用(在浏览器端),你必须手工设置这个值来让 is_ajax() 可以工作。
  如果一个响应需要根据请求是否是通过AJAX 发起的,并且你正在使用某种形式的缓存例如Django 的 cache middleware,
   你应该使用 vary_on_headers('HTTP_X_REQUESTED_WITH') 装饰你的视图以让响应能够正确地缓存。
视图参数
from django.shortcuts import render,HttpResponse

# Create your views here.

from django.urls import reverse # 反向解析
def special_case_2003(request):
    url= reverse('s_c_2005')
    url2 = reverse('y',args=(1993,))
    print(url) # 反向解析
    print(url2)

    return HttpResponse("special_case_2003")

def year_archive(request,y):
    return HttpResponse(y)

def month_archive(request,year,month):
    return HttpResponse(month+"-"+year)

def login(request):
    print(request.method)
    if request.method=='GET':
        return render(request, 'login.html')
    else:
        print(request.POST)
        user = request.POST.get('user')
        pwd = request.POST.get('pwd')

        if user =='HW' and pwd =='123':
            return HttpResponse('登陆成功')
        else:
            return  HttpResponse('账号或者密码错误哦!!')

def index(request):
    return HttpResponse(reverse('app01:index'))

def path_year(request,year):
    print(type(year))
    return HttpResponse('path_year...')
def path_month(request,month):
    return  HttpResponse('path_month...')
视图示例

2 模板

django 模板

#6.1 模板语法之变量
在 Django 模板中遍历复杂数据结构的关键是句点字符, 语法:
{{var_name}}

views.py:

def index(request):
    import datetime
    s="hello"
    l=[111,222,333]    # 列表
    dic={"name":"yuan","age":18}  # 字典
    date = datetime.date(1993, 5, 2)   # 日期对象

    class Person(object):
        def __init__(self,name):
            self.name=name

    person_yuan=Person("yuan")  # 自定义类对象
    person_egon=Person("egon")
    person_alex=Person("alex")
    person_list=[person_yuan,person_egon,person_alex]

    #return render(request,'index.html',locals())
    return render(request,"index.html",{"l":l,"dic":dic,"date":date,"person_list":person_list})
template:

<h4>{{s}}</h4>
<h4>列表:{{ l.0 }}</h4>
<h4>列表:{{ l.2 }}</h4>
<h4>字典:{{ dic.name }}</h4>
<h4>日期:{{ date.year }}</h4>
<h4>类对象列表:{{ person_list.0.name }}</h4>
<h4>字典:{{ dic.name.upper }}</h4>

#6.2 模板之过滤器
语法:
{{obj|filter__name:param}}

1default
如果一个变量是false或者为空,使用给定的默认值。否则,使用变量的值。例如:
{{ value|default:"nothing" }}

2length
返回值的长度。它对字符串和列表都起作用。例如:
{{ value|length }}
如果 value 是 ['a', 'b', 'c', 'd'],那么输出是 4。

3date
如果 value=datetime.datetime.now()
{{ value|date:"Y-m-d" }}

4slice
如果 value="hello world"
{{ value|slice:"2:-1" }}

5truncatechars
如果字符串字符多于指定的字符数量,那么会被截断。截断的字符串将以可翻译的省略号序列(“...”)结尾。
参数:要截断的字符数
例如:
{{ value|truncatechars:9 }}

6safe
Django的模板中会对HTML标签和JS等语法标签进行自动转义,原因显而易见,这样是为了安全。但是有的时候我们可能不希望这些HTML元素被转义,比如我们做一个内容管理系统,后台添加的文章中是经过修饰的,这些修饰可能是通过一个类似于FCKeditor编辑加注了HTML修饰符的文本,如果自动转义的话显示的就是保护HTML标签的源文件。为了在Django中关闭HTML的自动转义有两种方式,如果是一个单独的变量我们可以通过过滤器“|safe”的方式告诉Django这段代码是安全的不必转义。比如:
value="<a href="">点击</a>"
{{ value|safe}}

##6.3 模板之标签 
标签看起来像是这样的: {% tag %}

1for标签
a.遍历每一个元素:
b.反向完成循环。{% for obj in list reversed %}
c.遍历一个字典:
{% for key,val in dic.items %}
    <p>{{ key }}:{{ val }}</p>
{% endfor %}
注:循环序号可以通过{{forloop}}显示 
forloop.counter            The current iteration of the loop (1-indexed)
forloop.counter0           The current iteration of the loop (0-indexed)
forloop.revcounter         The number of iterations from the end of the loop (1-indexed)
forloop.revcounter0        The number of iterations from the end of the loop (0-indexed)
forloop.first              True if this is the first time through the loop
forloop.last               True if this is the last time through the loop
d.for ... empty
for 标签带有一个可选的{% empty %} 从句,以便在给出的组是空的或者没有被找到时,可以有所操作。

{% for person in person_list %}
    <p>{{ person.name }}</p>
{% empty %}
    <p>sorry,no person here</p>
{% endfor %}

2if 标签
{% if %}

{% if num > 100 or num < 0 %}
    <p>无效</p>
{% elif num > 80 and num < 100 %}
    <p>优秀</p>
{% else %}
    <p>凑活吧</p>
{% endif %}

3with
使用一个简单地名字缓存一个复杂的变量,当你需要使用一个“昂贵的”方法(比如访问数据库)很多次的时候是非常有用的
例如:
{% with total=business.employees.count %}
    {{ total }} employee{{ total|pluralize }}
{% endwith %}

4csrf_token
这个标签用于跨站请求伪造保护
{% csrf_token}

#6.4 自定义标签和过滤器
1、在settings中的INSTALLED_APPS配置当前app,不然django无法找到自定义的simple_tag.
2、在app中创建templatetags模块(模块名只能是templatetags)
3、创建任意 .py 文件,如:my_tags.py

  from django import template
  from django.utils.safestring import mark_safe
  register = template.Library()   #register的名字是固定的,不可改变  
  @register.filter
  def filter_multi(v1,v2):
      return  v1 * v2

  @register.simple_tag
  def simple_tag_multi(v1,v2):
      return  v1 * v2

  @register.simple_tag
  def my_input(id,arg):
      result = "<input type='text' id='%s' class='%s' />" %(id,arg,)
      return mark_safe(result)
4、在使用自定义simple_tag和filter的html文件中导入之前创建的 my_tags.py

      {% load my_tags %}
5、使用simple_tag和filter(如何调用)

  -------------------------------.html
  {% load xxx %}        
  # num=12
  {{ num|filter_multi:2 }} #24
  {{ num|filter_multi:"[22,333,4444]" }}
  {% simple_tag_multi 2 5 %}  参数不限,但不能放在if for语句中
  {% simple_tag_multi num 5 %}
注意:filter可以用在if等语句后,simple_tag不可以

{% if num|filter_multi:30 > 100 %}
    {{ num|filter_multi:30 }}
{% endif %}

6.5 模板继承 (extend)
示例:
base.html:
<!DOCTYPE html>
<html lang="en">
<head>
    <link rel="stylesheet" href="style.css" />
    <title>{% block title %}My amazing site{% endblock %}</title>
</head>

<body>
    <div id="sidebar">
        {% block sidebar %}
        <ul>
            <li><a href="/">Home</a></li>
            <li><a href="/blog/">Blog</a></li>
        </ul>
        {% endblock %}
    </div>

    <div id="content">
        {% block content %}{% endblock %}
    </div>
</body>
</html>

子模版:
{% extends "base.html" %}

{% block title %}My amazing blog{% endblock %}

{% block content %}
{% for entry in blog_entries %}
    <h2>{{ entry.title }}</h2>
    <p>{{ entry.body }}</p>
{% endfor %}
{% endblock content %}

注意:子模版并没有定义 sidebar block,所以系统使用了父模版中的值。父模版的 {% block %} 标签中的内容总是被用作备选内容(fallback)。
模板
----- inclution_tag的使用 (文章详情页与个人站点共用一套动态渲染的模板)
应用:个人站点页面设计(ORM跨表与分组查询)
    1 inclution_tag的使用 (文章详情页与个人站点共用一套动态渲染的模板)
    2 orm查询中使用sql语句(extra方法的使用)


使用:
1 在当前应用app文件目录下--->创建一个templatetags文件夹----->创建一个py文件,比如my_tags.py
2 my_tags.py 中自定义标签

from django import template
from django.db.models import Count
from blog import models
register=template.Library()

@register.inclusion_tag("classification.html")
def get_classification_style(username):

    user = models.UserInfo.objects.filter(username=username).first()
    blog = user.blog

    cate_list=models.Category.objects.filter(blog=blog).values("pk").annotate(c=Count("article__title")).values_list("title","c")

    tag_list=models.Tag.objects.filter(blog=blog).values("pk").annotate(c=Count("article")).values_list("title","c")

    date_list=models.Article.objects.filter(user=user).extra(select={"y_m_date":"date_format(create_time,'%%Y/%%m')"}).values("y_m_date").annotate(c=Count("nid")).values_list("y_m_date","c")


    return {"blog":blog,"cate_list":cate_list,"date_list":date_list,"tag_list":tag_list}

3 在模板下创建classification.html文件
# classification.html文件
 <div>
    <div class="panel panel-warning">
                <div class="panel-heading">我的标签</div>
                <div class="panel-body">
                    {% for tag in tag_list %}
                        <p><a href="/{{ username }}/tag/{{ tag.0 }}">{{ tag.0 }}({{ tag.1 }})</a></p>
                    {% endfor %}

                </div>
            </div>

    <div class="panel panel-danger">
        <div class="panel-heading">随笔分类</div>
        <div class="panel-body">
            {% for cate in cate_list %}
                <p><a href="/{{ username }}/category/{{ cate.0 }}">{{ cate.0 }}({{ cate.1 }})</a></p>
            {% endfor %}
        </div>
    </div>

    <div class="panel panel-success">
        <div class="panel-heading">随笔归档</div>
        <div class="panel-body">
            {% for date in date_list %}
                <p><a href="/{{ username }}/archive/{{ date.0 }}">{{ date.0 }}({{ date.1 }})</a></p>
            {% endfor %}
        </div>
    </div>
 </div>

4 使用标签
     {% load my_tags %}
     {% get_classification_style username %}

解释:这个自定义的标签get_classification_style一旦在模板中被调用,
首先会执行get_classification_style函数内的逻辑然后将返回的数据传送给模板classification.html去渲染,
渲染完的结果就是这次get_classification_style标签调用的返回值。
incluttion_tag

3  orm


from django.db import models
from django.contrib.auth.models import AbstractUser
# 使用django自带的user表,增加字段
class UserInfo(AbstractUser):
"""
用户信息
注意:用到django认证组件,需要继承AbstractUser
头像上传(media使用)
"""
nid = models.AutoField(primary_key=True)
telephone = models.CharField(max_length=11, null=True, unique=True)
avatar = models.FileField(verbose_name='头像图片文件',upload_to='avatars/', default="/avatars/default.png")
create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)

blog = models.OneToOneField(to='Blog', to_field='nid', null=True, on_delete=models.CASCADE)

def __str__(self):
return self.username

class Employee(models.Model): id
=models.AutoField(primary_key=True) name=models.CharField(max_length=32) gender=models.BooleanField() birthday=models.DateField() department=models.CharField(max_length=32) salary=models.DecimalField(max_digits=8,decimal_places=2) #python的类对象 #添加一条表纪录: emp=Employee(name="alex",gender=True,birthday="1985-12-12",epartment="保洁部") emp.save() #查询一条表纪录: Employee.objects.filter(age=24) #更新一条表纪录: Employee.objects.filter(id=1).update(birthday="1989-10-24") #删除一条表纪录: Employee.objects.filter(name="alex").delete()
字段
<1> CharField
       字符串字段, 用于较短的字符串.
       CharField 要求必须有一个参数 maxlength, 用于从数据库层和Django校验层限制该字段所允许的最大字符数.

<2> IntegerField
      #用于保存一个整数.

<3> FloatField
       一个浮点数. 必须 提供两个参数:

       参数    描述
       max_digits    总位数(不包括小数点和符号)
       decimal_places    小数位数
               举例来说, 要保存最大值为 999 (小数点后保存2位),你要这样定义字段:

               models.FloatField(..., max_digits=5, decimal_places=2)
               要保存最大值一百万(小数点后保存10位)的话,你要这样定义:

               models.FloatField(..., max_digits=19, decimal_places=10)
               admin 用一个文本框(<input type="text">)表示该字段保存的数据.

<4> AutoField
       一个 IntegerField, 添加记录时它会自动增长. 你通常不需要直接使用这个字段;
       自定义一个主键:my_id=models.AutoField(primary_key=True)
       如果你不指定主键的话,系统会自动添加一个主键字段到你的 model.

<5> BooleanField
       A true/false field. admin 用 checkbox 来表示此类字段.

<6> TextField
       一个容量很大的文本字段.
       admin 用一个 <textarea> (文本区域)表示该字段数据.(一个多行编辑框).

<7> EmailField
       一个带有检查Email合法性的 CharField,不接受 maxlength 参数.

<8> DateField
       一个日期字段. 共有下列额外的可选参数:
       Argument    描述
       auto_now    当对象被保存时,自动将该字段的值设置为当前时间.通常用于表示 "last-modified" 时间戳.
       auto_now_add    当对象首次被创建时,自动将该字段的值设置为当前时间.通常用于表示对象创建时间.
       (仅仅在admin中有意义...)

<9> DateTimeField
        一个日期时间字段. 类似 DateField 支持同样的附加选项.

<10> ImageField
       类似 FileField, 不过要校验上传对象是否是一个合法图片.#它有两个可选参数:height_field和width_field,
       如果提供这两个参数,则图片将按提供的高度和宽度规格保存.    
<11> FileField
    一个文件上传字段.
    要求一个必须有的参数: upload_to, 一个用于保存上载文件的本地文件系统路径. 这个路径必须包含 strftime #formatting,
    该格式将被上载文件的 date/time
    替换(so that uploaded files don't fill up the given directory).
    admin 用一个<input type="file">部件表示该字段保存的数据(一个文件上传部件) .

    注意:在一个 model 中使用 FileField 或 ImageField 需要以下步骤:
           (1)在你的 settings 文件中, 定义一个完整路径给 MEDIA_ROOT 以便让 Django在此处保存上传文件.
           (出于性能考虑,这些文件并不保存到数据库.) 定义MEDIA_URL 作为该目录的公共 URL. 要确保该目录对
            WEB服务器用户帐号是可写的.
           (2) 在你的 model 中添加 FileField 或 ImageField, 并确保定义了 upload_to 选项,以告诉 Django
            使用 MEDIA_ROOT 的哪个子目录保存上传文件.你的数据库中要保存的只是文件的路径(相对于 MEDIA_ROOT).
            出于习惯你一定很想使用 Django 提供的 get_<#fieldname>_url 函数.举例来说,如果你的 ImageField
            叫作 mug_shot, 你就可以在模板中以 {{ object.#get_mug_shot_url }} 这样的方式得到图像的绝对路径.

<12> URLField
     用于保存 URL. 若 verify_exists 参数为 True (默认), 给定的 URL 会预先检查是否存在( 即URL是否被有效装入且
     没有返回404响应).
     admin 用一个 <input type="text"> 文本框表示该字段保存的数据(一个单行编辑框)

<13> NullBooleanField
      类似 BooleanField, 不过允许 NULL 作为其中一个选项. 推荐使用这个字段而不要用 BooleanField 加 null=True 选项
      admin 用一个选择框 <select> (三个可选择的值: "Unknown", "Yes" 和 "No" ) 来表示这种字段数据.

<14> SlugField
      "Slug" 是一个报纸术语. slug 是某个东西的小小标记(短签), 只包含字母,数字,下划线和连字符.#它们通常用于URLs
      若你使用 Django 开发版本,你可以指定 maxlength. 若 maxlength 未指定, Django 会使用默认长度: 50.  #在
      以前的 Django 版本,没有任何办法改变50 这个长度.
      这暗示了 db_index=True.
      它接受一个额外的参数: prepopulate_from, which is a list of fields from which to auto-#populate
      the slug, via JavaScript,in the object's admin form: models.SlugField
      (prepopulate_from=("pre_name", "name"))prepopulate_from 不接受 DateTimeFields.

<13> XMLField
       一个校验值是否为合法XML的 TextField,必须提供参数: schema_path, 它是一个用来校验文本的 RelaxNG schema #的文件系统路径.

<14> FilePathField
       可选项目为某个特定目录下的文件名. 支持三个特殊的参数, 其中第一个是必须提供的.
       参数    描述
       path    必需参数. 一个目录的绝对文件系统路径. FilePathField 据此得到可选项目.
       Example: "/home/images".
       match    可选参数. 一个正则表达式, 作为一个字符串, FilePathField 将使用它过滤文件名.
       注意这个正则表达式只会应用到 base filename 而不是
       路径全名. Example: "foo.*.txt^", 将匹配文件 foo23.txt 却不匹配 bar.txt 或 foo23.gif.
       recursive可选参数.要么 True 要么 False. 默认值是 False. 是否包括 path 下面的全部子目录.
       这三个参数可以同时使用.
       match 仅应用于 base filename, 而不是路径全名. 那么,这个例子:
       FilePathField(path="/home/images", match="foo.*", recursive=True)
       ...会匹配 /home/images/foo.gif 而不匹配 /home/images/foo/bar.gif

<15> IPAddressField
       一个字符串形式的 IP 地址, (i.e. "24.124.1.30").
<16> CommaSeparatedIntegerField
       用于存放逗号分隔的整数值. 类似 CharField, 必须要有maxlength参数.

更多参数:
(1)null

如果为True,Django 将用NULL 来在数据库中存储空值。 默认值是 False.

(1)blank

如果为True,该字段允许不填。默认为False。
要注意,这与 null 不同。null纯粹是数据库范畴的,而 blank 是数据验证范畴的。
如果一个字段的blank=True,表单的验证将允许该字段是空值。如果字段的blank=False,该字段就是必填的。

(2)default

字段的默认值。可以是一个值或者可调用对象。如果可调用 ,每有新对象被创建它都会被调用。

(3)primary_key

如果为True,那么这个字段就是模型的主键。如果你没有指定任何一个字段的primary_key=True,
Django 就会自动添加一个IntegerField字段做为主键,所以除非你想覆盖默认的主键行为,
否则没必要设置任何一个字段的primary_key=True。

(4)unique

如果该值设置为 True, 这个数据字段的值在整张表中必须是唯一的

(5)choices
由二元组组成的一个可迭代对象(例如,列表或元组),用来给字段提供选择项。 如果设置了choices ,默认的表单将是一个选择框而不是标准的文本框,<br>而且这个选择框的选项就是choices 中的选项。
字段-参数
#### 1.2 orm字段

需求:前端发送json `{'key':"email"}`或`{'key':"password"}`,后端接收到数据之后,去ORM类User中校验是否允许为空。

```
class UserInfo(models.Model):
    username = models.CharField(verbose_name='用户名', max_length=32, db_index=True)
    email = models.EmailField(verbose_name='邮箱', max_length=32,null=True)
    mobile_phone = models.CharField(verbose_name='手机号', max_length=32)
    password = models.CharField(verbose_name='密码', max_length=32)

def index(request):
    data_dict = json.loads(request.body.decode('utf-8'))
    
    field_object = models.UserInfo._meta.get_field(data_dict["key"])
    print( field_object.verbose_name ) # 邮箱 ; 密码
    print( field_object.null ) # True ; False
```
orm字段
from django.db import models


# Create your models here.

# 作者详情表
class AuthorDetail(models.Model):
    nid = models.AutoField(primary_key=True)
    birthday = models.DateField()
    telepnone = models.BigIntegerField()
    addr = models.CharField(max_length=64)


# 作者表
class Author(models.Model):
    nid = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    # 一对一
    authordetail = models.OneToOneField(to="AuthorDetail", to_field="nid", on_delete=models.CASCADE)

    def __str__(self):
        return self.name


# 出版社表
class Publish(models.Model):
    nid = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32)
    city = models.CharField(max_length=32)
    email = models.EmailField()

    def __str__(self):
        return self.name


# 图书表
class Book(models.Model):
    nid = models.AutoField(primary_key=True)
    title = models.CharField(max_length=32)
    publishDate = models.DateField()
    price = models.DecimalField(max_digits=5, decimal_places=2)

    # 一对多关系
    publish = models.ForeignKey(to=Publish, to_field='nid', on_delete=models.CASCADE)
    '''
            publish_id INT ,
            FOREIGN KEY (publish_id) REFERENCES publish(id)
    '''
    # 多对多(生成第三张表,并不会生成字段)
    authors = models.ManyToManyField(to="Author")
    '''
        CREATE  TABLE book_authors(
           id INT PRIMARY KEY auto_increment ,
           book_id INT ,
           author_id INT ,
           FOREIGN KEY (book_id) REFERENCES book(id),
           FOREIGN KEY (author_id) REFERENCES author(id)
            )
        '''

    # class Book2Author(models.Model):
    #
    #     nid = models.AutoField(primary_key=True)
    #     book=models.ForeignKey(to="Book")
    #     author=models.ForeignKey(to="Author")

    def __str__(self):
        return self.title
表关系

3 单表查询--多表查询

from django.shortcuts import render,HttpResponse

# Create your views here.
from app01.models import *


def add(request):

######################绑定一对多的关系##############################################
    #方式1:
    #为book表绑定出版社: book  ---    publish
    # book_obj=Book.objects.create(title="红楼梦",price=100,publishDate="2012-12-12",publish_id=1)
    # print(book_obj.title)

    #方式2:
    # pub_obj=Publish.objects.filter(nid=1).first()
    # book_obj=Book.objects.create(title="三国演绎",price=100,publishDate="2012-12-12",publish=pub_obj)
    # print(book_obj.title)
    # print(book_obj.price)
    # print(book_obj.publishDate)
    # print(book_obj.publish)       #  与这本书籍关联的出版社对象
    # print(book_obj.publish.name)
    # print(book_obj.publish.email)
    # print(book_obj.publish_id)


    # 查询西游记的出版社对应的邮箱()

    # book_obj = Book.objects.filter(title="西游记").first()
    # print(book_obj.publish.email)

######################绑定多对多的关系##############################################

    # book_obj = Book.objects.create(title="金妹妹", price=100, publishDate="2012-12-12", publish_id=1)
    #
    # alex = Author.objects.get(name='alex')
    # egon = Author.objects.get(name='egon')

    # 绑定多对多关系的API
    # book_obj.authors.add(egon,alex)
    # book_obj.authors.add(1,2)
    # book_obj.authors.add(*[1, 2])

    # 解除多对多关系
    book = Book.objects.filter(nid=5).first()
    # book.authors.remove(2)
    # book.authors.remove(*[1,2])
    # book.authors.clear()

    #查询主键为4的书籍的所有作者的名字
    book=Book.objects.filter(nid=5).first()
    print(book.authors.all()) # [obj1,obj2...] queryset: 与这本书关联的所有作者对象集合
    ret=book.authors.all().values("name")
    print(ret)
    print(book.authors.all())

    return HttpResponse('Ok')

def query(request):
    """
    跨表查询:
       1 基于对象查询 (子查询)
       2 基于双下划线查询(join查询)
       3 聚合和分组查询
       4 F 与 Q查询
    :param request:
    :return:
    """
# -------------------------基于对象的跨表查询(子查询)-----------------------
    # 一对多查询的正向查询 : 查询金妹妹这本书的出版社的名字
    # book= Book.objects.filter(title='金妹妹').first()
    # ret =book.publish.name
    # print(ret)
    # 对应sql:
    # select publish_id from Book where title="金妹妹"
    # select name from Publish where id=1

    # 一对多查询的反向查询 : 查询人民出版社出版过的书籍名称
    # publish = Publish.objects.filter(name='人民出版社').first()
    # names = publish.book_set.all()
    # print(names)

    # 多对多查询的正向查询 : 查询金妹妹这本书的所有作者的名字
    # book_obj=Book.objects.filter(title="金妹妹").first()
    # author_list=book_obj.authors.all() # queryset对象  [author_obj1,...]
    #
    # for author in author_list:
    #     print(author.name)

    # 多对多查询的反向查询 : 查询alex出版过的所有书籍名称
    # alex=Author.objects.filter(name="alex").first()
    #
    # book_list=alex.book_set.all()
    # for book in book_list:
    #     print(book.title)

    # 一对一查询的正向查询 : 查询alex的手机号
    # alex=Author.objects.filter(name="alex").first()
    # print(alex.authordetail.telephone)

    # # 一对一查询的反向查询 : 查询手机号为110的作者的名字和年龄
    # ad=AuthorDetail.objects.filter(telephone="110").first()
    # print(ad.author.name)
    # print(ad.author.age)


# -------------------------基于双下划线的跨表查询(join查询)-----------------------

    '''

    正向查询按字段,反向查询按表名小写用来告诉ORM引擎join哪张表

    '''

    # 一对多查询 : 查询金妹妹这本书的出版社的名字

    # 方式1:
    # ret= Book.objects.filter(title ='金妹妹').values("publish__name")
    # print(ret) # <QuerySet [{'publish__name': '南京出版社'}]>

    # 方式2:
    # ret=Publish.objects.filter(book__title="金妹妹").values("name")
    # print(ret)
    # 方式1:

    # 需求: 通过Book表join与其关联的Author表,属于正向查询:按字段authors通知ORM引擎join book_authors与author

    # ret=Book.objects.filter(title="金妹妹").values("authors__name")
    # print(ret) # <QuerySet [{'authors__name': 'alex'}, {'authors__name': 'egon'}]>

    # 方式2:
    # 需求: 通过Author表join与其关联的Book表,属于反向查询:按表名小写book通知ORM引擎join book_authors与book表
    # ret=Author.objects.filter(book__title="金妹妹").values("name")
    # print(ret) # <QuerySet [{'name': 'alex'}, {'name': 'egon'}]>

    # 一对一查询的查询 : 查询alex的手机号

    # 方式1:
    # 需求: 通过Author表join与其关联的AuthorDetail表,属于正向查询:按字段authordetail通知ORM引擎join Authordetail表

    # ret=Author.objects.filter(name="alex").values("authordetail__telephone")
    # print(ret) # <QuerySet [{'authordetail__telephone': 110}]>
    #
    # # 方式2:
    # # 需求: 通过AuthorDetail表join与其关联的Author表,属于反向查询:按表名小写author通知ORM引擎join Author表
    # ret=AuthorDetail.objects.filter(author__name="alex").values("telephone")
    # print(ret) # <QuerySet [{'telephone': 110}]>

    # 进阶练习:

    # 练习: 手机号以110开头的作者出版过的所有书籍名称以及书籍出版社名称

    # 方式1:
    # 需求: 通过Book表join AuthorDetail表, Book与AuthorDetail无关联,所以必需连续跨表
    # ret=Book.objects.filter(authors__authordetail__telephone__startswith="110").values("title","publish__name")
    # print(ret)
    #
    # # 方式2:
    # ret=Author.objects.filter(authordetail__telephone__startswith="110").values("book__title","book__publish__name")
    # print(ret)

# -------------------------聚合与分组查询---------------------------
    from django.db.models import Avg,Max,Min,Count
#
#     r# -------------------------聚合与分组查询---------------------------
#
#
#     # ------------------------->聚合 aggregate:返回值是一个字典,不再是queryset
#
#     # 查询所有书籍的平均价格
#     from django.db.models import Avg, Max, Min, Count
#
#     ret = Book.objects.all().aggregate(avg_price=Avg("price"), max_price=Max("price"))
#     print(ret)  # {'avg_price': 151.0, 'max_price': Decimal('301.00')}
#
#     # ------------------------->分组查询 annotate ,返回值依然是queryset
#
#
#     # ------------------------->单表分组查询:
#
#     # 示例1
#     # 查询每一个部门的名称以及员工的平均薪水
#
#     # select dep,Avg(salary) from emp group by dep
#
#     # ret=Emp.objects.values("dep").annotate(avg_salary=Avg("salary"))
#     # print(ret) # <QuerySet [{'avg_salary': 5000.0, 'dep': '保安部'}, {'avg_salary': 51000.0, 'dep': '教学部'}]>
#
#     # 单表分组查询的ORM语法: 单表模型.objects.values("group by的字段").annotate(聚合函数("统计字段"))
#
#     # 示例2
#     # 查询每一个省份的名称以及员工数
#
#     # ret=Emp.objects.values("province").annotate(c=Count("id"))
#     #
#     # print(ret) # <QuerySet [{'province': '山东省', 'c': 2}, {'province': '河北省', 'c': 1}]>
#
#     # 补充知识点:
#
#     # ret=Emp.objects.all()
#     # print(ret)  # select * from emp
#     # ret=Emp.objects.values("name")
#     # print(ret)  # select name from emp
#     #
#     # Emp.objects.all().annotate(avg_salary=Avg("salary"))
#
#
#     # ------------------------->多表分组查询:
#
#     ## 示例1 查询每一个出版社的名称以及出版的书籍个数
#
#     ret = Publish.objects.values("nid").annotate(c=Count("book__title"))
#     print(ret)  # <QuerySet [{'nid': 1, 'c': 3}, {'nid': 2, 'c': 1}]>
#
#     ret = Publish.objects.values("name").annotate(c=Count("book__title"))
#     print(ret)  # <QuerySet [{'name': '人民出版社', 'c': 3}, {'name': '南京出版社', 'c': 1}]>
#
#     ret = Publish.objects.values("nid").annotate(c=Count("book__title")).values("name", "c")
#     print(ret)  # <QuerySet [{'name': '人民出版社', 'c': 3}, {'name': '南京出版社', 'c': 1}]>
#
#     ## 示例2 查询每一个作者的名字以及出版过的书籍的最高价格
#     ret = Author.objects.values("pk").annotate(max_price=Max("book__price")).values("name", "max_price")
#     print(ret)
#
#     # 总结 跨表的分组查询的模型:
#     # 每一个后表模型.objects.values("pk").annotate(聚合函数(关联表__统计字段))
#
#     # 示例3 查询每一个书籍的名称以及对应的作者个数
#     ret = Book.objects.values("pk").annotate(c=Count("authors__name")).values("title", "c")
#     print(ret)
#
#     #################### 跨表分组查询的另一种玩法  ####################
#
#     # 示例1 查询每一个出版社的名称以及出版的书籍个数
#     # ret=Publish.objects.values("nid").annotate(c=Count("book__title")).values("name","email","c")
#     # ret=Publish.objects.all().annotate(c=Count("book__title")).values("name","c","city")
#     ret=Publish.objects.annotate(c=Count("book__title")).values("name","c","city")
#     print(ret)
#
#     ##################### 练习   ####################
#
#     # 统计每一本以py开头的书籍的作者个数:
#
#     # 每一个后的表模型.objects.values("pk").annotate(聚合函数(关联表__统计字段)).values("表模型的所有字段以及统计字段")
#     ret=Book.objects.filter(title__startswith="py").values("pk").annotate(c=Count("authors__name")).values("title","c")
#
#     # 统计不止一个作者的图书
#     ret=Book.objects.values("pk").annotate(c=Count("authors__name")).filter(c__gt=1).values("title","c")
#     print(ret)


# ------------------------- F查询与Q查询-----------------------
from django.db.models import F
from django.db.models import Q
# ------------F查询------------------
#
# F()可以比较同一model中不同字段的值。

# 查询评论数大于收藏数的书籍
   from django.db.models import F
   Book.objects.filter(commnetNum__lt=F('keepNum'))

#------------Q查询------------------
# filter() 等方法中的关键字参数查询都是一起进行“AND” 的。 如果你需要执行更复杂的查询(例如OR 语句),你可以使用Q 对象
# 示例:

#1 bookList=Book.objects.filter(Q(authors__name="yuan")|Q(authors__name="egon"))
"""
等同于下面的SQL WHERE 子句:
  WHERE name ="yuan" OR name ="egon"
""""
# 2 Q 对象可以使用& 和 |操作符组合起来 ,~ 操作符取反
# bookList=Book.objects.filter(Q(authors__name="yuan")|Q(authors__name="egon"))
# bookList=Book.objects.filter(Q(authors__name="yuan") & ~Q(publishDate__year=2017)).values_list("title")

# 3查询函数可以混合使用Q 对象和关键字参数.所有提供给查询函数的参数(关键字参数或Q 对象)都将"AND”在一起。但是,如果出现Q 对象,它必须位于所有关键字参数的前面。例如:
# bookList=Book.objects.filter(Q(publishDate__year=2016) | Q(publishDate__year=2017),
#                               title__icontains="python")
多表查询
from django.shortcuts import render,HttpResponse

# Create your views here.
from app01.models import Book
def index(request):
    # 添加表记录

    # # 批量导入
    # book_list=[]
    # for i in range(100):
    #     book = Book(title='book_%s'%i,price=i*i)
    #     book_list.append(book)
    # Book.objects.bulk_create(book_list)

    # 方式一
    # book_obj =Book(id =1,title='python',price=100,pub_date='2017-2-3',publish='人民出版社')
    # book_obj.save()

    # 方式二 create返回值就是当前生成的对象记录
    # book_obj =Book.objects.create(title='php',price=200,pub_date='2017-3-3',publish='人民出版社')
    # print(book_obj.title)
    # print(book_obj.price)
    # print(book_obj.pub_date)


    ############################ 查询表记录 #####################
    """
    1 方法的返回值
    2 方法的调用者

    """
    #(1)all方法: 返回值是一个queryset对象
    # book_list = Book.objects.all()
    # # print(book_list)  #数据类型:QuerySet [obj1,obj2,]
    # for obj in book_list:
    #     print(obj.title,obj.price)

    #(2) first,last :调用者:Queryset对象 返回值:model对象
    # book = Book.objects.all().first()
    # book = Book.objects.all().[0]
    # book = Book.objects.all().last()

    #(3) filter() 返回值:queryset对象
    # book_list= Book.objects.filter(price=100,title='go')
    # print(book_list)

    #(4) get() 有且只有一个查询结果时才有意义,返回值:model对象

    # Book.objects.get(title='python')

    #<5> exclude():  它包含了与所给筛选条件不匹配的对象(queryset对象)
    # ret= Book.objects.exclude(title='go')
    # <6> : 对查询结果排序
    # ret =Book.objects.all().order_by('id') #升序
    # ret =Book.objects.all().order_by('-id') #降序

    # <7> count(): 返回数据库中匹配查询(QuerySet)的对象数量。返回值:int类型
    # ret = Book.objects.all().count()
    # print(ret)

    # <8> exists(): 如果QuerySet包含数据,就返回True,否则返回False
    # ret = Book.objects.all().exists()

    # <9> values(*field):返回一个ValueQuerySet——一个特殊的QuerySet,运行后得到的并不是一系列
    #                             model的实例化对象,而是一个可迭代的字典序列
    # ret = Book.objects.all()
    # for i in ret:
    #     print(i.title)
    #
    # ret= Book.objects.all().values('price')
    # print(ret)

    # <10> values_list(*field): 它与values()
    # 非常相似,它返回的是一个元组序列,values返回的是一个字典序列
    # ret= Book.objects.all().values('price','title')

    # <14> distinct(): 从返回结果中剔除重复纪录
    # ret =Book.objects.all().values('price').distinct()
    # print(ret)

    ########################### 查询表记录-模糊查询 #####################
    # ret =Book.objects.filter(price__gt=100) # 大于
    # ret =Book.objects.filter(price__lt=200) # 小于
    # print(ret)

    # ret=Book.objects.filter(title__startswith='p')
    # print(ret)

    # ret =Book.objects.filter(title__contains='p')
    # ret =Book.objects.filter(title__icontains='p') # 不区分大小写
    # print(ret)

    # ret = Book.objects.filter(price__in=[100,200])
    # print(ret)
    # ret=Book.objects.filter(pub_date__year=2012)
    # print(ret)


    ########################### 查询表记录-删除和修改记录 #####################

    # delect/update :调用者:是一个 queryset对象 model对象
    ret = Book.objects.filter(price=300).delete()
    # ret = Book.objects.filter(price=200).first().delete() ##

    # ret2 =Book.objects.filter(title='python').update(title='ppy')
    print(ret,type(ret))

    return HttpResponse('Ok')

def query(request):
# # 1
# # 查询老男孩出版社出版过的价格大于200的书籍
#     ret =Book.objects.filter(publish='老男孩出版社',price__gt=200)
#
# # 2
# # 查询2017年8月出版的所有以py开头的书籍名称
#     ret2 = Book.objects.filter(pub_date__year=2017,pub_date__day=8,title__startswith='py').values('title')
# # 3
# # 查询价格为50, 100或者150的所有书籍名称及其出版社名称
#     ret3 =Book.objects.filter(price__in=[50,100,150]).values('title','publish')
# # 4
# # 查询价格在100到200之间的所有书籍名称及其价格
#     ret4 =Book.objects.filter(price__range=[100,200]).values('title','price')
# # 5
# # 查询所有人民出版社出版的书籍的价格(从高到低排序,去重)
#     ret5=Book.objects.filter(publish='人民出版社').values('price').distinct().order_by('-price')
    return HttpResponse('ok')
单表查询(批量添加记录)
---查询:

日期归档查询

   1    date_format

        ============date,time,datetime===========

        create table t_mul_new(d date,t time,dt datetime);

        insert into t_mul_new values(now(),now(),now());

        select * from t_mul;


        mysql> select * from t_mul;
        +------------+----------+---------------------+
        | d          | t        | dt                  |
        +------------+----------+---------------------+
        | 2017-08-01 | 19:42:22 | 2017-08-01 19:42:22 |
        +------------+----------+---------------------+
        1 row in set (0.00 sec)


        select date_format(dt,"%Y/%m/%d") from t_mul;


   2  extra (在orm中使用sql语句)

        extra(select=None, where=None, params=None, tables=None, order_by=None, select_params=None)

        有些情况下,Django的查询语法难以简单的表达复杂的 WHERE 子句,对于这种情况, Django 提供了 extra() QuerySet修改机制 — 它能在 QuerySet生成的SQL从句中注入新子句

        extra可以指定一个或多个 参数,例如 select, where or tables. 这些参数都不是必须的,但是你至少要使用一个!要注意这些额外的方式对不同的数据库引擎可能存在移植性问题.(因为你在显式的书写SQL语句),除非万不得已,尽量避免这样做

        参数之select

        The select 参数可以让你在 SELECT 从句中添加其他字段信息,它应该是一个字典,存放着属性名到 SQL 从句的映射。

        queryResult=models.Article
                   .objects.extra(select={'is_recent': "create_time > '2017-09-05'"})
        结果集中每个 Entry 对象都有一个额外的属性is_recent, 它是一个布尔值,表示 Article对象的create_time 是否晚于2017-09-05.

        练习:

        in sqlite:

            article_obj=models.Article.objects
                      .extra(select={"standard_time":"strftime('%%Y-%%m-%%d',create_time)"})
                      .values("standard_time","nid","title")
            print(article_obj)
            # <QuerySet [{'title': 'MongoDb 入门教程', 'standard_time': '2017-09-03', 'nid': 1}]>


   3 日期归档查询的方式2

       from django.db.models.functions import TruncMonth

       Sales.objects
            .annotate(month=TruncMonth('timestamp'))  # Truncate to month and add to select list
            .values('month')                          # Group By month
            .annotate(c=Count('id'))                  # Select the count of the grouping
            .values('month', 'c')                     # (might be redundant, haven't tested) select month and count
orm&sql&日期

4 Cookie-Session

from django.shortcuts import render,HttpResponse,redirect

from app01.models import UserInfo
# Create your views here.



def login(request):
    """
    # 设置cookie
     response.set_cookie('is_login',True)
    """
    if request.method=='POST':
        response =HttpResponse('登陆成功!!')
        response.set_cookie('is_login',True)
        response.set_cookie('username',request.POST.get('user'))
        # import datetime
        # date=datetime.datetime(year=2019,month=5,day=29,hour=14,minute=34) # 设置过期时间
        # response.set_cookie("username",user.user,expires=date)
        return response

    return render(request,'login.html')


def index(request):
    """
    # 获取COOKIES
    is_login= request.COOKIES.get('is_login')
    """
    print("index:", request.COOKIES)
    is_login= request.COOKIES.get('is_login')
    if is_login:
        username =request.COOKIES.get('username')

        import datetime

        now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        last_visit_time = request.COOKIES.get("last_visit_time", "")
        response = render(request, "index.html", {"username": username, "last_visit_time": last_visit_time})
        response.set_cookie("last_visit_time", now)
        return response
    else:
        return redirect("/login/")

# session
def login_session(request):
    """
   设置session
   request.session["username"] = user_obj.user
    """
    if request.method=='POST':
        user = request.POST.get('user')
        pwd = request.POST.get('pwd')
        user_obj =UserInfo.objects.filter(user=user,pwd=pwd).first()

        if user_obj:
            import datetime

            now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            request.session["is_login"] = True
            request.session["username"] = user_obj.user
            request.session["last_visit_time"] = now
            return HttpResponse("登录成功!")

    return render(request,'login.html')

def index_session(request):
    """
    获取session
     is_login = request.session.get("is_login")
    """
    print("is_login:", request.session.get("is_login"))
    is_login = request.session.get("is_login")
    if not is_login:
        return redirect("/login_session/")

    username = request.session.get("username")
    last_visit_time = request.session.get("last_visit_time")

    return render(request, "index.html", {"username": username, "last_visit_time": last_visit_time})


def logout(request):
    """
    删除session
    request.session.flush()
    """
    # del request.session["is_login"]

    request.session.flush() #删除当前的会话数据并删除会话的Cookie。

    '''
    1 randon_str=request.COOKIE.get("sessionid")

    2 django-session.objects.filter(session-key=randon_str).delete()

    3 response.delete_cookie("sessionid",randon_str)

    '''

    return redirect("/login/")
cookie-session

5 用户认证组件

from django.shortcuts import render,HttpResponse,redirect

# Create your views here.

from django.contrib import auth
from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required

def login(request):

    if request.method == "POST":
        user = request.POST.get("user")
        pwd = request.POST.get("pwd")

        # if 验证成功返回user对象,否则返回None
        user=auth.authenticate(username=user,password=pwd)

        if user:
            auth.login(request,user)   # request.user:当前登录对象

            next_url=request.GET.get("next","/index/")
            return  redirect(next_url)


    return render(request,"login.html")



@login_required
def index(request):

    # print("request.user:",request.user.username)
    # print("request.user:",request.user.id)
    # print("request.user:",request.user.is_anonymous)
    #
    # #if request.user.is_anonymous:
    # if not request.user.is_authenticated:
    #     return redirect("/login/")

    #username=request.user.username
    #return render(request,"index.html",{"username":username})

    return render(request,"index.html")


@login_required
def order(request):

    # if not request.user.is_authenticated:
    #     return redirect("/login/")


    return render(request,"order.html")







def logout(request):

    auth.logout(request) #当调用该函数时,当前请求的session信息会全部清除

    return redirect("/login/")




def reg(request):
    """
    注册
    """
    if request.method=="POST":
        user = request.POST.get("user")
        pwd = request.POST.get("pwd")

        #User.objects.create(username=user,password=pwd)
        user=User.objects.create_user(username=user,password=pwd)

        return redirect("/login/")


    return render(request,"reg.html")
用户认证组件.py
    用户认证组件:

         功能:用session纪录登陆验证状态

         前提:用户表:dajngo自带的auth_user
         
         创建超级用户:python3 manage.py createsuperuser


         API:
            from django.contrib import auth :

                1 # if 验证成功返回user对象,否则返回None                            
                   user=auth.authenticate(username=user,password=pwd)    

                 2 auth.login(request,user)   # request.user:当前登录对象 

                 3 auth.logout(request)    

            from django.contrib.auth.models import User # User==auth_user

                4 request.user.is_authenticated() 

                5 user = User.objects.create_user(username='',password='',email='')


            补充:
               匿名用户对象:
                    匿名用户
                    class models.AnonymousUser

                    django.contrib.auth.models.AnonymousUser 类实现了django.contrib.auth.models.User 接口,但具有下面几个不同点:

                    id 永远为None。
                    username 永远为空字符串。
                    get_username() 永远返回空字符串。
                    is_staff 和 is_superuser 永远为False。
                    is_active 永远为 False。
                    groups 和 user_permissions 永远为空。
                    is_anonymous() 返回True 而不是False。
                    is_authenticated() 返回False 而不是True。
                    set_password()、check_password()、save() 和delete() 引发 NotImplementedError。
                    New in Django 1.8:
                    新增 AnonymousUser.get_username() 以更好地模拟 django.contrib.auth.models.User。


            总结:

                 
                     if not: auth.login(request,user)   request.user == AnonymousUser()

                     else:request.user==登录对象
                     request.user是一个全局变量

                     在任何视图和模板直接使用
补充-说明

6 forms组件

from django.shortcuts import render,HttpResponse

# Create your views here.
from django import forms
from django.forms import widgets

wid_01=widgets.TextInput(attrs={"class":"form-control"})
wid_02=widgets.PasswordInput(attrs={"class":"form-control"})
class UserForm(forms.Form):
    name=forms.CharField(min_length=4,label='用户名',widget=wid_01,error_messages={"required":"该字段不能为空"})
    pwd=forms.CharField(min_length=4,label='密码',widget=wid_02)
    r_pwd=forms.CharField(min_length=4,widget=wid_01)
    email = forms.EmailField(label='邮箱',widget=wid_01,error_messages={"required":"该字段不能为空","invalid":"格式错误"},)


def reg(request):
    if request.method=='POST':
        # print(request.POST)
        form =UserForm(request.POST)
        print(form.is_valid()) # 返回布尔值

        if form.is_valid():
            print(form.cleaned_data)  # 所有干净的字段以及对应的值
        else:
            print(form.cleaned_data)
            print(form.errors)  # ErrorDict : {"校验错误的字段":["错误信息",]}

            print(form.errors.get("name"))  # ErrorList ["错误信息",]
            print(form.errors.get("name")[0])  # ErrorList ["错误信息",]

            return render(request, 'reg.html', locals())



    form =UserForm()
    return render(request,'reg.html',locals())
forms渲染页面-检验字段
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
     <!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>
<form action="" method="post">
    {% csrf_token %}
    <div>
        <label for="user">用户名</label>
        <p><input type="text" name="name" id="name"></p>
    </div>
    <div>
        <label for="pwd">密码</label>
        <p><input type="password" name="pwd" id="pwd"></p>
    </div>
    <div>
        <label for="r_pwd">确认密码</label>
        <p><input type="password" name="r_pwd" id="r_pwd"></p>
    </div>
     <div>
        <label for="email">邮箱</label>
        <p><input type="text" name="email" id="email"></p>
    </div>
    <input type="submit">
</form>

<hr>
<h3>form组件渲染方式1</h3>
<div class="container">
    <div class="row">
        <div class="col-md-6 col-lg-offset-3">

                <form action="" method="post">
                    {% csrf_token %}
                    <div>
                        <label for="">用户名</label>
                        {{ form.name }}
                    </div>
                    <div>
                        <label for="">密码</label>
                        {{ form.pwd }}
                    </div>
                    <div>
                        <label for="">确认密码</label>
                        {{ form.r_pwd }}
                    </div>
                    <div>
                        <label for=""> 邮箱</label>
                        {{ form.email }}
                    </div>

                    <input type="submit" class="btn btn-default pull-right">
                </form>
        </div>
    </div>
</div>

<h3>forms组件渲染方式2</h3>
            <form action="" method="post" novalidate>
                 {% csrf_token %}
                {% for field in form %}
                    <div>
                        <label for="">{{ field.label }}</label>
                        {{ field }} <span class="pull-right" style="color: red">{{ field.errors.0 }}</span>
                    </div>
                {% endfor %}

                 <input type="submit">
            </form>

<hr>
<p>不建议用,测试可使用</p>
 <h3>forms组件渲染方式3</h3>
            <form action="" method="post">
                 {% csrf_token %}

                 {{ form.as_p }}

                 <input type="submit">
            </form>

</body>
</html>
forms渲染页面.html
from django import forms

from django.forms import widgets
from app01.models import UserInfo

from django.core.exceptions import NON_FIELD_ERRORS, ValidationError

class UserForm(forms.Form):
    name=forms.CharField(min_length=4,label="用户名",error_messages={"required":"该字段不能为空"},
                         widget=widgets.TextInput(attrs={"class":"form-control"})
                         )
    pwd=forms.CharField(min_length=4,label="密码",
                        widget=widgets.PasswordInput(attrs={"class":"form-control"})
                        )
    r_pwd=forms.CharField(min_length=4,label="确认密码",error_messages={"required":"该字段不能为空"},widget=widgets.TextInput(attrs={"class":"form-control"}))
    email=forms.EmailField(label="邮箱",error_messages={"required":"该字段不能为空","invalid":"格式错误"},widget=widgets.TextInput(attrs={"class":"form-control"}))
    tel=forms.CharField(label="手机号",widget=widgets.TextInput(attrs={"class":"form-control"}))


    def clean_name(self):

        val=self.cleaned_data.get("name")

        ret=UserInfo.objects.filter(name=val)

        if not ret:
            return val
        else:
            raise ValidationError("该用户已注册!")

    def clean_tel(self):

        val=self.cleaned_data.get("tel")

        if len(val)==11:

            return val
        else:
            raise  ValidationError("手机号格式错误")

    def clean(self):
        pwd=self.cleaned_data.get('pwd')
        r_pwd=self.cleaned_data.get('r_pwd')

        if pwd and r_pwd:
            if pwd==r_pwd:
                return self.cleaned_data
            else:
                raise ValidationError('两次密码不一致')
        else:

            return self.cleaned_data
myforms全局与局部钩子

7 ajax示例 &文件上传

13.1 基于jquery的Ajax实现
<button class="send_Ajax">send_Ajax</button>
<script>
       $(".send_Ajax").click(function(){
           $.ajax({
               url:"/handle_Ajax/",
               type:"POST",
               data:{username:"Yuan",password:123},
               success:function(data){
                   console.log(data)
               },
               
               error: function (jqXHR, textStatus, err) {
                        console.log(arguments);
                    },
               complete: function (jqXHR, textStatus) {
                        console.log(textStatus);
                },
               statusCode: {
                    '403': function (jqXHR, textStatus, err) {
                          console.log(arguments);
                     },
                    '400': function (jqXHR, textStatus, err) {
                        console.log(arguments);
                    }
                }
           })
       })
</script>

13.2 基于form表单的文件上传
模板部分

<form action="" method="post" enctype="multipart/form-data">
      用户名 <input type="text" name="user">
      头像 <input type="file" name="avatar">
    <input type="submit">
</form>
视图部分

def index(request):
    print(request.body)   # 原始的请求体数据
    print(request.GET)    # GET请求数据
    print(request.POST)   # POST请求数据
    print(request.FILES)  # 上传的文件数据
    return render(request,"index.html")

13.3 基于Ajax的文件上传
模板

<form>
      用户名 <input type="text" id="user">
      头像 <input type="file" id="avatar">
     <input type="button" id="ajax-submit" value="ajax-submit">
</form>

<script>

    $("#ajax-submit").click(function(){
        var formdata=new FormData();
        formdata.append("user",$("#user").val());
        formdata.append("avatar_img",$("#avatar")[0].files[0]);
        $.ajax({

            url:"",
            type:"post",
            data:formdata,
            processData: false ,    // 不处理数据
            contentType: false,    // 不设置内容类型

            success:function(data){
                console.log(data)
            }
        })
    })
</script>
视图

def index(request):
    if request.is_ajax():
        print(request.body)   # 原始的请求体数据
        print(request.GET)    # GET请求数据
        print(request.POST)   # POST请求数据
        print(request.FILES)  # 上传的文件数据
        return HttpResponse("ok")
    return render(request,"index.html")

检查浏览器的请求头:

Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryaWl9k5ZMiTAzx3FT
ajax-文件上传

8 分页器

from django.shortcuts import render

# Create your views here.


from .models import Book

from django.core.paginator import Paginator,EmptyPage

def index(request):
    '''
    批量导入:
    book_list=[]
    for i in range(100):
        book=Book(title="book_%s"%i,price=i*i)
        book_list.append(book)

    Book.objects.bulk_create(book_list)
    :param request:
    :return:
    '''


    book_list=Book.objects.all()

    # 分页器

    paginator=Paginator(book_list,10)

    print("count:",paginator.count)           #数据总数
    print("num_pages",paginator.num_pages)    #总页数
    print("page_range",paginator.page_range)  #页码的列表

    current_page_num=int(request.GET.get("page",1))

    if paginator.num_pages>11:

        if current_page_num-5<1:
            page_range=range(1,12)
        elif current_page_num+5>paginator.num_pages:
            page_range=range(paginator.num_pages-10,paginator.num_pages+1)

        else:
            page_range=range(current_page_num-5,current_page_num+6)
    else:
        page_range=paginator.page_range



    try:


        current_page=paginator.page(current_page_num)

        # 显示某一页具体数据的两种方式:
        print("object_list",current_page.object_list)
        for i in current_page:
            print(i)

    except EmptyPage as e:
         current_page=paginator.page(1)


    return render(request,"index.html",locals())
分页.py
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>



<ul>
    {% for book in current_page %}
    <li>{{ book.title }}:{{ book.price }}</li>
    {% endfor %}

</ul>


<nav aria-label="Page navigation">
  <ul class="pagination">
    {% if current_page.has_previous %}
    <li><a href="?page={{ current_page.previous_page_number  }}" aria-label="Previous"><span aria-hidden="true">上一页</span></a></li>
    {% else %}
    <li class="disabled"><a href="" aria-label="Previous"><span aria-hidden="true">上一页</span></a></li>
    {% endif %}



    {% for item in page_range %}
        
        {% if current_page_num == item %}
            <li class="active"><a href="?page={{ item }}">{{ item }}</a></li>
        {% else %}
            <li><a href="?page={{ item }}">{{ item }}</a></li>
        {% endif %}

    {% endfor %}



    {% if current_page.has_next %}
        <li><a href="?page={{ current_page.next_page_number  }}" aria-label="Next"><span aria-hidden="true">下一页</span></a>
    {% else %}
                <li class="disabled"><a href="" aria-label="Next"><span aria-hidden="true">下一页</span></a>
    {% endif %}
</li>
  </ul>
</nav>

</body>
</html>
分页展示.html

9 中间件

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse

class CustomerMiddleware(MiddlewareMixin):

    def process_request(self,request):
        print("CustomerMiddleware1 process_request....")

        #return HttpResponse("forbidden....")


    def process_response(self,request,response):
        print("CustomerMiddleware1 process_response")

        return response
        #return HttpResponse("hello yuan")

    def process_view(self, request, callback, callback_args, callback_kwargs):
        print("CustomerMiddleware1 process_view")


    def process_exception(self, request, exception):

        print("CustomerMiddleware1 process_exception")
        return HttpResponse(exception)




class CustomerMiddleware2(MiddlewareMixin):

    def process_request(self,request):
        print("CustomerMiddleware2 process_request....")

    def process_response(self,request,response):
        print("CustomerMiddleware2 process_response")
        return response

    def process_view(self, request, callback, callback_args, callback_kwargs):
        # print("====>",callback(callback_args))
        print("CustomerMiddleware2 process_view")
        # ret=callback(callback_args)
        # return ret

    def process_exception(self, request, exception):

        print("CustomerMiddleware2 process_exception")

        #return HttpResponse(exception)
my_middlewares.py
1 在setting 中注册中间件
例如:
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    #新注册的
    "app01.my_middlewares.CustomerMiddleware",
    "app01.my_middlewares.CustomerMiddleware2"

]
2 在app01/my_middleearee.py中编写中间件逻辑

中间件中一共有四个方法:
process_request
       权限认证,白名单,登录认证。。
process_view
       csrf_toke认证
process_exception
process_response
    drf 解决跨域问题
中间件使用.txt

10 事务操作&发邮件

 

1 事务操作(多件事情保持原子性:共进退)
示例:
from django.db import transaction
with transaction.atomic():  # 添加事务(保证两个操作共同进行)
        comment_obj = Comment.objects.create(user_id=user_id, content=content, article_id=article_id,
                                             parent_comment_id=pid)
        Article.objects.filter(pk=article_id).update(comment_count=F('comment_count') + 1)

2发邮件
settings.py
# 发送邮件配置
EMAIL_HOST = 'smtp.qq.com'  # 如果是 163 改成 smtp.163.com
EMAIL_PORT = 465  # 163---25
EMAIL_HOST_USER = '1485571783@qq.com'  # 帐号
EMAIL_HOST_PASSWORD = 'wzjotnnanlyhbafg'  # IMAP/SMTP服务 密码
# EMAIL_HOST_PASSWORD = 'omurtfcglkuahfdi'  # 密码
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
EMAIL_USE_SSL = True

views.py
 # 发送邮件
    from django.core.mail import send_mail
    from cnblog import settings
    from threading import Thread

    t =Thread(target=send_mail, args=('您的文章%s新增了一条评论内容' % (article_obj.title),
                                   content,
                                   settings.EMAIL_HOST_USER,
                                   ['huawang400@gmail.com'],))
    t.start()
    # send_mail(
    #     '您的文章%s新增了一条评论内容' % (article_obj.title),
    #     content,
    #     settings.EMAIL_HOST_USER,
    #     ['huawang400@gmail.com'],
    # )

应用:cnblog下的文章详情页下的评论功能使用
事务操作&发邮件

11 基于admin组件录入数据

from django.contrib import admin

# Register your models here.
from . import models

for table in models.__all__:
    admin.site.register(getattr(models, table))
admin.py
from django.db import models
# Create your models here.

from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType

# Create your models here.
__all__ = ["Category", "Course", "CourseDetail", "Teacher", "DegreeCourse", "CourseChapter",
           "CourseSection", "PricePolicy", "OftenAskedQuestion", "Comment", "Account", "CourseOutline"]


class Category(models.Model):
    """课程分类表"""
    title = models.CharField(max_length=32, unique=True, verbose_name="课程的分类")

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = "01-课程分类表"
        db_table = verbose_name # 上线去掉
        verbose_name_plural = verbose_name


class Course(models.Model):
    """课程表"""
    title = models.CharField(max_length=128, unique=True, verbose_name="课程的名称")
    course_img = models.ImageField(upload_to="course/%Y-%m", verbose_name='课程的图片')
    category = models.ForeignKey(to="Category", verbose_name="课程的分类", on_delete=None)


    COURSE_TYPE_CHOICES = ((0, "付费"), (1, "vip专享"), (2, "学位课程"))
    course_type = models.SmallIntegerField(choices=COURSE_TYPE_CHOICES)
    degree_course = models.ForeignKey(to="DegreeCourse", blank=True, null=True, help_text="如果是学位课程,必须关联学位表", on_delete=None)


    brief = models.CharField(verbose_name="课程简介", max_length=1024)
    level_choices = ((0, '初级'), (1, '中级'), (2, '高级'))
    level = models.SmallIntegerField(choices=level_choices, default=1)

    status_choices = ((0, '上线'), (1, '下线'), (2, '预上线'))
    status = models.SmallIntegerField(choices=status_choices, default=0)
    pub_date = models.DateField(verbose_name="发布日期", blank=True, null=True)

    order = models.IntegerField("课程顺序", help_text="从上一个课程数字往后排")
    study_num = models.IntegerField(verbose_name="学习人数", help_text="只要有人买课程,订单表加入数据的同时给这个字段+1")

    # order_details = GenericRelation("OrderDetail", related_query_name="course")
    # coupon = GenericRelation("Coupon")
    # 只用于反向查询不生成字段
    price_policy = GenericRelation("PricePolicy")
    often_ask_questions = GenericRelation("OftenAskedQuestion")
    course_comments = GenericRelation("Comment")

    def save(self, *args, **kwargs):
        if self.course_type == 2:
            if not self.degree_course:
                raise ValueError("学位课必须关联学位课程表")
        super(Course, self).save(*args, **kwargs)

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = "02-课程表"
        db_table = verbose_name
        verbose_name_plural = verbose_name


class CourseDetail(models.Model):
    """课程详细表"""
    course = models.OneToOneField(to="Course", on_delete=None)
    hours = models.IntegerField(verbose_name="课时", default=7)
    course_slogan = models.CharField(max_length=125, blank=True, null=True, verbose_name="课程口号")
    video_brief_link = models.CharField(max_length=255, blank=True, null=True)
    summary = models.TextField(max_length=2048, verbose_name="课程概述")
    why_study = models.TextField(verbose_name="为什么学习这门课程")
    what_to_study_brief = models.TextField(verbose_name="我将学到哪些内容")
    career_improvement = models.TextField(verbose_name="此项目如何有助于我的职业生涯")
    prerequisite = models.TextField(verbose_name="课程先修要求", max_length=1024)
    recommend_courses = models.ManyToManyField("Course", related_name="recommend_by", blank=True)

    teachers = models.ManyToManyField("Teacher", verbose_name="课程讲师")

    def __str__(self):
        return self.course.title

    class Meta:
        verbose_name = "03-课程详细表"
        db_table = verbose_name
        verbose_name_plural = verbose_name


class Teacher(models.Model):
    """讲师表"""
    name = models.CharField(max_length=32, verbose_name="讲师名字")
    brief = models.TextField(max_length=1024, verbose_name="讲师介绍")

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = "04-教师表"
        db_table = verbose_name
        verbose_name_plural = verbose_name


class DegreeCourse(models.Model):
    """
    字段大体跟课程表相同,哪些不同根据业务逻辑去区分
    """
    title = models.CharField(max_length=32, verbose_name="学位课程名字")

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = "05-学位课程表"
        db_table = verbose_name
        verbose_name_plural = verbose_name


class CourseChapter(models.Model):
    """课程章节表"""
    course = models.ForeignKey(to="Course", related_name="course_chapters", on_delete=None)
    chapter = models.SmallIntegerField(default=1, verbose_name="第几章")
    title = models.CharField(max_length=32, verbose_name="课程章节名称")

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = "06-课程章节表"
        db_table = verbose_name
        verbose_name_plural = verbose_name
        unique_together = ("course", "chapter")


class CourseSection(models.Model):
    """课时表"""
    chapter = models.ForeignKey(to="CourseChapter", related_name="course_sections", on_delete=None)
    title = models.CharField(max_length=32, verbose_name="课时")
    section_order = models.SmallIntegerField(verbose_name="课时排序", help_text="建议每个课时之间空1至2个值,以备后续插入课时")
    section_type_choices = ((0, '文档'), (1, '练习'), (2, '视频'))
    free_trail = models.BooleanField("是否可试看", default=False)
    section_type = models.SmallIntegerField(default=2, choices=section_type_choices)
    section_link = models.CharField(max_length=255, blank=True, null=True, help_text="若是video,填vid,若是文档,填link")

    def course_chapter(self):
        return self.chapter.chapter

    def course_name(self):
        return self.chapter.course.title

    def __str__(self):
        return "%s-%s" % (self.chapter, self.title)

    class Meta:
        verbose_name = "07-课程课时表"
        db_table = verbose_name
        verbose_name_plural = verbose_name
        unique_together = ('chapter', 'section_link')


class PricePolicy(models.Model):
    """价格策略表"""
    content_type = models.ForeignKey(ContentType, on_delete=None)  # 关联course or degree_course
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

    valid_period_choices = ((1, '1天'), (3, '3天'),
                            (7, '1周'), (14, '2周'),
                            (30, '1个月'),
                            (60, '2个月'),
                            (90, '3个月'),
                            (120, '4个月'),
                            (180, '6个月'), (210, '12个月'),
                            (540, '18个月'), (720, '24个月'),
                            (722, '24个月'), (723, '24个月'),
                            )
    valid_period = models.SmallIntegerField(choices=valid_period_choices)
    price = models.FloatField()

    def __str__(self):
        return "%s(%s)%s" % (self.content_object, self.get_valid_period_display(), self.price)

    class Meta:
        verbose_name = "08-价格策略表"
        db_table = verbose_name
        verbose_name_plural = verbose_name
        unique_together = ("content_type", 'object_id', "valid_period")


class OftenAskedQuestion(models.Model):
    """常见问题"""
    content_type = models.ForeignKey(ContentType, on_delete=None)  # 关联course or degree_course
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

    question = models.CharField(max_length=255)
    answer = models.TextField(max_length=1024)

    def __str__(self):
        return "%s-%s" % (self.content_object, self.question)

    class Meta:
        verbose_name = "09-常见问题表"
        db_table = verbose_name
        verbose_name_plural = verbose_name
        unique_together = ('content_type', 'object_id', 'question')


class Comment(models.Model):
    """通用的评论表"""
    content_type = models.ForeignKey(ContentType, blank=True, null=True, on_delete=None)
    object_id = models.PositiveIntegerField(blank=True, null=True)
    content_object = GenericForeignKey('content_type', 'object_id')

    content = models.TextField(max_length=1024, verbose_name="评论内容")
    account = models.ForeignKey("Account", verbose_name="会员名", on_delete=None)
    date = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.content

    class Meta:
        verbose_name = "10-评价表"
        db_table = verbose_name
        verbose_name_plural = verbose_name


class Account(models.Model):
    username = models.CharField(max_length=32, verbose_name="用户姓名")
    pwd = models.CharField(max_length=32, verbose_name="密文密码")
    # head_img = models.CharField(max_length=256, default='/static/frontend/head_portrait/logo@2x.png',
    #                             verbose_name="个人头像")
    balance = models.IntegerField(verbose_name="贝里余额", default=0)

    def __str__(self):
        return self.username

    class Meta:
        verbose_name = "11-用户表"
        db_table = verbose_name
        verbose_name_plural = verbose_name


class CourseOutline(models.Model):
    """课程大纲"""
    course_detail = models.ForeignKey(to="CourseDetail", related_name="course_outline", on_delete=None)
    title = models.CharField(max_length=128)
    order = models.PositiveSmallIntegerField(default=1)
    # 前端显示顺序

    content = models.TextField("内容", max_length=2048)

    def __str__(self):
        return "%s" % self.title

    class Meta:
        verbose_name = "12-课程大纲表"
        db_table = verbose_name
        verbose_name_plural = verbose_name
        unique_together = ('course_detail', 'title')
models.py
基于admin组件录入数据

前期准备:
 创建超级用户:python manage.py createsuperuser
 访问:127.0.0.1/admin

示例一:
以项目下的course APP为例:
1 模型层处理  (course/model.py)
from django.db import models
# Create your models here.

from django.db import models

# 将表名写入__all__
__all__ = ["Category", "Course", "CourseDetail", "Teacher", "DegreeCourse", "CourseChapter",
           "CourseSection", "PricePolicy", "OftenAskedQuestion", "Comment", "Account", "CourseOutline"]
2 admin下注册 ( course/admin.py)
from django.contrib import admin

# Register your models here.
from . import models

for table in models.__all__:
    admin.site.register(getattr(models, table))


示例二:

在blog/admin.py文件:

from django.contrib import admin

# Register your models here.
from blog import models

admin.site.register(models.UserInfo)
admin.site.register(models.Blog)
admin.site.register(models.Category)
admin.site.register(models.Tag)
admin.site.register(models.Article)
admin.site.register(models.ArticleUpDown)
admin.site.register(models.Article2Tag)
admin.site.register(models.Comment)
admin录入数据.txt

12 inclution_tag&日期处理

----- inclution_tag的使用 (文章详情页与个人站点共用一套动态渲染的模板)
应用:个人站点页面设计(ORM跨表与分组查询)
    1 inclution_tag的使用 (文章详情页与个人站点共用一套动态渲染的模板)
    2 orm查询中使用sql语句(extra方法的使用)


使用:
1 在当前应用app文件目录下--->创建一个templatetags文件夹----->创建一个py文件,比如my_tags.py
2 my_tags.py 中自定义标签


from django.db.models import Count
from blog import models

from django import template
register=template.Library()

@register.inclusion_tag("classification.html")
def get_classification_style(username):

    user = models.UserInfo.objects.filter(username=username).first()
    blog = user.blog

    cate_list=models.Category.objects.filter(blog=blog).values("pk").annotate(c=Count("article__title")).values_list("title","c")

    tag_list=models.Tag.objects.filter(blog=blog).values("pk").annotate(c=Count("article")).values_list("title","c")

    date_list=models.Article.objects.filter(user=user).extra(select={"y_m_date":"date_format(create_time,'%%Y/%%m')"}).values("y_m_date").annotate(c=Count("nid")).values_list("y_m_date","c")


    return {"blog":blog,"cate_list":cate_list,"date_list":date_list,"tag_list":tag_list}

3 在模板下创建classification.html文件
# classification.html文件
 <div>
    <div class="panel panel-warning">
                <div class="panel-heading">我的标签</div>
                <div class="panel-body">
                    {% for tag in tag_list %}
                        <p><a href="/{{ username }}/tag/{{ tag.0 }}">{{ tag.0 }}({{ tag.1 }})</a></p>
                    {% endfor %}

                </div>
            </div>

    <div class="panel panel-danger">
        <div class="panel-heading">随笔分类</div>
        <div class="panel-body">
            {% for cate in cate_list %}
                <p><a href="/{{ username }}/category/{{ cate.0 }}">{{ cate.0 }}({{ cate.1 }})</a></p>
            {% endfor %}
        </div>
    </div>

    <div class="panel panel-success">
        <div class="panel-heading">随笔归档</div>
        <div class="panel-body">
            {% for date in date_list %}
                <p><a href="/{{ username }}/archive/{{ date.0 }}">{{ date.0 }}({{ date.1 }})</a></p>
            {% endfor %}
        </div>
    </div>
 </div>

4 使用标签
     {% load my_tags %}
     {% get_classification_style username %}

解释:这个自定义的标签get_classification_style一旦在模板中被调用,
首先会执行get_classification_style函数内的逻辑然后将返回的数据传送给模板classification.html去渲染,
渲染完的结果就是这次get_classification_style标签调用的返回值。
inclution_tag
2 日期处理 (model中使用select)
dashboard.py
def issues_chart(request, project_id):
    """ 在概览页面生成highcharts所需的数据 """
    today = datetime.datetime.now().date() #datetime.date(2021, 3, 10)
    date_dict = collections.OrderedDict()
    for i in range(0, 30):
        date = today - datetime.timedelta(days=i)
        date_dict[date.strftime("%Y-%m-%d")] = [time.mktime(date.timetuple()) * 1000, 0] # 时间戳*1000

    # select xxxx,1 as ctime from xxxx
    # select id,name,email from table;
    # select id,name, strftime("%Y-%m-%d",create_datetime) as ctime from table;
    # "DATE_FORMAT(web_transaction.create_datetime,'%%Y-%%m-%%d')"
    result = models.Issues.objects.filter(project_id=project_id,
                                          create_datetime__gte=today - datetime.timedelta(days=30)).extra(
        select={'ctime': "strftime('%%Y-%%m-%%d',web_issues.create_datetime)"}).values('ctime').annotate(ct=Count('id'))
    # print(result) ----><QuerySet [{'ctime': '2021-03-08', 'ct': 9}]>
    for item in result:
        date_dict[item['ctime']][1] = item['ct']

    return JsonResponse({'status': True, 'data': list(date_dict.values())})
model(select) 日期处理
---查询:

日期归档查询

   1    date_format

        ============date,time,datetime===========

        create table t_mul_new(d date,t time,dt datetime);

        insert into t_mul_new values(now(),now(),now());

        select * from t_mul;


        mysql> select * from t_mul;
        +------------+----------+---------------------+
        | d          | t        | dt                  |
        +------------+----------+---------------------+
        | 2017-08-01 | 19:42:22 | 2017-08-01 19:42:22 |
        +------------+----------+---------------------+
        1 row in set (0.00 sec)


        select date_format(dt,"%Y/%m/%d") from t_mul;


   2  extra (在orm中使用sql语句)

        extra(select=None, where=None, params=None, tables=None, order_by=None, select_params=None)

        有些情况下,Django的查询语法难以简单的表达复杂的 WHERE 子句,对于这种情况, Django 提供了 extra() QuerySet修改机制 — 它能在 QuerySet生成的SQL从句中注入新子句

        extra可以指定一个或多个 参数,例如 select, where or tables. 这些参数都不是必须的,但是你至少要使用一个!要注意这些额外的方式对不同的数据库引擎可能存在移植性问题.(因为你在显式的书写SQL语句),除非万不得已,尽量避免这样做

        参数之select

        The select 参数可以让你在 SELECT 从句中添加其他字段信息,它应该是一个字典,存放着属性名到 SQL 从句的映射。

        queryResult=models.Article
                   .objects.extra(select={'is_recent': "create_time > '2017-09-05'"})
        结果集中每个 Entry 对象都有一个额外的属性is_recent, 它是一个布尔值,表示 Article对象的create_time 是否晚于2017-09-05.

        练习:

        in sqlite:

            article_obj=models.Article.objects
                      .extra(select={"standard_time":"strftime('%%Y-%%m-%%d',create_time)"})
                      .values("standard_time","nid","title")
            print(article_obj)
            # <QuerySet [{'title': 'MongoDb 入门教程', 'standard_time': '2017-09-03', 'nid': 1}]>


   3 日期归档查询的方式2

       from django.db.models.functions import TruncMonth

       Sales.objects
            .annotate(month=TruncMonth('timestamp'))  # Truncate to month and add to select list
            .values('month')                          # Group By month
            .annotate(c=Count('id'))                  # Select the count of the grouping
            .values('month', 'c')                     # (might be redundant, haven't tested) select month and count
orm-日期.txt

相关配置(版本,数据库,静态文件等)

mysql & django

1settings配置

若想将模型转为mysql数据库中的表,需要在settings中配置:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME':'bms',           # 要连接的数据库,连接前需要创建好
        'USER':'root',        # 连接数据库的用户名
        'PASSWORD':'',        # 连接数据库的密码
        'HOST':'127.0.0.1',       # 连接主机,默认本级
        'PORT':3306            #  端口 默认3306
    }
}

2 项目名文件下的init,写入
import pymysql
pymysql.install_as_MySQLdb()

3两条数据库迁移命令即可在指定的数据库中创建表 :
python manage.py makemigrations
python manage.py migrate

注意3:如果报错如下:
django.core.exceptions.ImproperlyConfigured: mysqlclient 1.3.3 or newer is required; you have 0.7.11.None
解决:
MySQLclient目前只支持到python3.4,因此如果使用的更高版本的python,需要修改如下:
通过查找路径C:ProgramsPythonPython36-32Libsite-packagesDjango-2.0-py3.6.eggdjangodbackendsmysql 这个路径里的文件把
f version < (1, 3, 3):
     raise ImproperlyConfigured("mysqlclient 1.3.3 or newer is required; you have %s" % Database.__version__)
注释掉 就OK了。
    
注意4: 如果想打印orm转换过程中的sql,需要在settings中进行如下配置:
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console':{
            'level':'DEBUG',
            'class':'logging.StreamHandler',
        },
    },
    'loggers': {
        'django.db.backends': {
            'handlers': ['console'],
            'propagate': True,
            'level':'DEBUG',
        },
    }
}
1 django&mysql配置
django1 与django 3之间的修改

1 on_delete=models.CASCADE  # 表结构
2 url(r'^', include('web.urls')), # 路由分发
3 url(r'^rbac/', include(('rbac.urls','rbac'))) # 名称空间。传递一个元组
4 setting:
    TEMPLATES=[

    'libraries': {  # Adding this section should work around the issue.
        'staticfiles': 'django.templatetags.static',
    },

    ]

5 form组件 错误信息显示语言
    # LANGUAGE_CODE = 'en-us'
    LANGUAGE_CODE = 'zh-hans'

6
原语句if field.rel.limit_choices_to:
修改后:将rel修改为 remote_field

if field.remote_field.limit_choices_to:

##7反向解析示例:

record_url = reverse('stark:web_consultrecord_list', kwargs={'customer_id': obj.pk})
2 django-crm版本配置
静态文件配置

     /static/    :  js,css,img
     /media/      :   用户上传文件

访问方式:127.0.0.1:8000/static/app01/xxx.jpg
1 在项目目录下新建一个python包 /static
 需要的话:指定应用目录,例如:/static/app01
 将静态文件放置到此目录下


2setting配置:

STATIC_URL = '/static/'

STATICFILES_DIRS =[
    BASE_DIR/'static'
    ]
3 static静态文件配置
 Media 配置
    /static/    :  js,css,img
     /media/      :   用户上传文件

一旦配置了
        MEDIA_ROOT=os.path.join(BASE_DIR,"media")
Dajngo实现:
       会将文件对象下载到MEDIA_ROOT中avatars文件夹中(如果没有avatar文件夹,Django会自动创建),user_obj的avatar存的是文件的相对路径。

表模型字段:
      avatar = models.FileField(verbose_name='头像图片文件',upload_to='avatars/', default="/avatars/default.png")

settings.py
     MEDIA_URL="/media/"
     import os 
     MEDIA_ROOT = os.path.join(BASE_DIR, "media")
     
urls.py:
from django.views.static import serve
from cnblog import  settings
 # media配置:
re_path(r"media/(?P<path>.*)$",serve,{"document_root":settings.MEDIA_ROOT})
4 media静态文件配置
基于admin组件录入数据

前期准备:
 创建超级用户:python manage.py createsuperuser
 访问:127.0.0.1/admin

示例一:
以项目下的course APP为例:
1 模型层处理  (course/model.py)
from django.db import models
# Create your models here.

from django.db import models

# 将表名写入__all__
__all__ = ["Category", "Course", "CourseDetail", "Teacher", "DegreeCourse", "CourseChapter",
           "CourseSection", "PricePolicy", "OftenAskedQuestion", "Comment", "Account", "CourseOutline"]
2 admin下注册 ( course/admin.py)
from django.contrib import admin

# Register your models here.
from . import models

for table in models.__all__:
    admin.site.register(getattr(models, table))


示例二:

在blog/admin.py文件:

from django.contrib import admin

# Register your models here.
from blog import models

admin.site.register(models.UserInfo)
admin.site.register(models.Blog)
admin.site.register(models.Category)
admin.site.register(models.Tag)
admin.site.register(models.Article)
admin.site.register(models.ArticleUpDown)
admin.site.register(models.Article2Tag)
admin.site.register(models.Comment)
5 admin录入数据
1.django时区

# datetime.datetime.now() / datetime.datetime.utcnow() => utc时间
# TIME_ZONE = 'UTC'

# datetime.datetime.now() - 东八区时间 / datetime.datetime.utcnow() => utc时间
TIME_ZONE = 'Asia/Shanghai'

# 影响自动生成数据库时间字段;
#       USE_TZ = True,创建UTC时间写入到数据库。
#       USE_TZ = False,根据TIME_ZONE设置的时区进行创建时间并写入数据库
USE_TZ = False
6 django时区问题
5 编辑器文件上传功能(views/wiki.py)
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.clickjacking import xframe_options_sameorigin

@csrf_exempt
@xframe_options_sameorigin
def wiki_upload(request, project_id):
    """ markdown插件上传图片 """
    pass
markdown等编辑器文件上传问题

相关项目 --Django markdown文档

点击下载项目

链接:https://pan.baidu.com/s/1reDH1z7e_4VRoQd1sEi7Lg
提取码:huaw

点击下载相关markdown文档 

链接:https://pan.baidu.com/s/1wFcrINQje66H67qxvPJERQ

 

作者:华王 博客:https://www.cnblogs.com/huahuawang/
原文地址:https://www.cnblogs.com/huahuawang/p/14806644.html