Bootstrap

Django框架基本语法(一)

Django 框架(一)

一、 基础介绍

1、 简介

使用 Django,只要很少的代码,Python 的程序开发人员就可以轻松地完成一个正式网站所需要的大部分内容,并进一步开发出全功能的 Web 服务 Django 本身基于 MVC 模型,即 Model(模型)+ View(视图)+ Controller(控制器)设计模式,MVC 模式使后续对程序的修改和扩展简化,并且使程序某一部分的重复利用成为可能。

MVC 优势:

  • 低耦合
  • 开发快捷
  • 部署方便
  • 可重用性高
  • 维护成本低

MVC 以一种插件式的、松耦合的方式连接在一起

  • 模型(M)- 编写程序应有的功能,负责业务对象与数据库的映射(ORM)
  • 视图(V)- 图形界面,负责与用户的交互(页面)
  • 控制器(C)- 负责转发请求,对请求进行处理

MTV 模式本质上和 MVC 是一样的,也是为了各组件间保持松耦合关系,只是定义上有些许不同

  • M 表示模型(Model):编写程序应有的功能,负责业务对象与数据库的映射(ORM)。
  • T 表示模板 (Template):负责如何把页面(html)展示给用户。
  • V 表示视图(View):负责业务逻辑,并在适当时候调用 Model和 Template

2、 项目搭建

2.1 虚拟环境

复制系统python,不同项目使用不同的虚拟环境

创建虚拟环境

mkvirtualenv -p /usr/bin/python3 djangoApp

查看虚拟环境

workon

切换虚拟环境

workon djangoApp

移除虚拟环境

rmvirtualenv djangoApp
2.2 创建项目的步骤
  1. 创建虚拟环境

    mkvirtualenv -p /usr/bin/python3 djangoApp
    
  2. 安装对应的库

    pip install django -i https://pypi.douban.com/simple
    
  3. 创建工作目录

    mkdir djangoProject  # 创建存放Django的目录
    django-admin startproject CRM  # 使用Django创建一个Django目录
    
  4. 设置端口映射(22、3306、8000)

https://images.cnblogs.com/cnblogs_com/blogs/722174/galleries/2074790/o_220630040322_sort.png

  1. 编写代码

3、 启动服务

cd CRM  # 进入到工作目录内部
python manage.py runserver 0:8000  # 开启服务,在主机访问使用127.0.0.1:1236

4、 与pycharm同步

  1. 本地创建一个空项目

  2. 连接远程解释器(注意:一定要是项目的解释器)

  3. 文件映射(注意:设置成默认)

https://images.cnblogs.com/cnblogs_com/blogs/722174/galleries/2074790/o_220630050638_1656563605291.png

1656563581245

  1. 设置自动同步文件

https://images.cnblogs.com/cnblogs_com/blogs/722174/galleries/2074790/o_220630050734_1656564029689.png

5、 快捷启动

5.1 配置接口

https://images.cnblogs.com/cnblogs_com/blogs/722174/galleries/2074790/o_220630053137_sort.png

5.2 项目配置

https://images.cnblogs.com/cnblogs_com/blogs/722174/galleries/2074790/o_220630053340_sort.png

5.3 开启服务

https://images.cnblogs.com/cnblogs_com/blogs/722174/galleries/2074790/o_220630053555_sort.png

二、 第一个项目

1、 创建项目

django-admin startproject CRM

则,创建项目后,其树形结构为:

.
├── CRM
│   ├── asgi.py
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── db.sqlite3
└── manage.py  

2、 创建视图函数

在CRM文件夹里面,创建一个views.py

在文件里面添加:

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
__author__ = "A.L.Kun"
__file__ = "views.py"
__time__ = "2022/6/30 13:59"
__email__ = "[email protected]"

from django.http import HttpResponse


def index(request):
    return HttpResponse("hello world")

3、 路由注册

CRM.urls.py里面添加代码

from django.contrib import admin
from django.urls import path
from . import views


urlpatterns = [
    path('admin/', admin.site.urls),
    path("index/", views.index),  # 将index注册到路由中
]

4、 修改配置文件

CRM.settings.py中,修改ALOWED_HOSTS变量,这变量一般都要修改

ALLOWED_HOSTS = ["*"]  # 允许所有的ip访问这个网址

LANGUAGE_CODE = 'zh_hans'  # 设置语言为中文简体

TIME_ZONE = 'Asia/Shanghai'  # 设置时区为上海时区

5、 访问网站

开启服务,在浏览器输入127.0.0.1:1236/index/既可以访问刚才写的网站

6、 第一个应用

  1. 创建app

    在终端中输入,来创建app,一个项目可以拥有很多的app

    python manage.py startapp student
    touch student/urls.py
    
  2. 注册app

    settings.py文件中,在INSTALLED_APPS中添加app

    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'student'  # 注册app
    ]
    
  3. 添加分路由

    student.urls.py中添加

    from django.urls import path
    app_name = "student"  # 配置app的名字为student
    
    urlpatterns = [
        # 里面添加分路由
    ]
    
  4. 配置总路由

    CRM.urls.py中添加

    from django.contrib import admin
    from django.urls import path, include
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path("student/", include('student.urls')),  # 分发路由
    ]
    

三、 路由系统

1、 url

url是全球资源统一定位符,格式:

协议://域名(ip地址:端口)/路径/?参数
schema://host[:port]/path/[?query-string][#anchor]

schema:指定使用的协议(例如:http, https, ftp)

host:Http服务器的IP地址或者域名

port:端口号,http默认是80端口

path:访问资源的路径

query-string:发送给http服务器的数据

anchor:锚点#

2、 路由配置

2.1 语法

路径表达式对视图函数的映射

语法规则:

path(route, view, kwargs=None, name=None)

参数:

  • route:字符串,url规则
  • view:视图函数
  • kwargs:额外参数,此参数将传递给模板文件
  • name:url的别名

注意:

  • url规则要和设置一致才能正确访问
  • url规则指向是哪个视图,访问哪个视图
2.2 参数捕获
2.2.1 普通捕获

<参数名>:捕获到的值默认是字符串

使用路径转换器:

str  # 匹配除了路径分隔符(/)之外的非空字符串,这是默认的形式
int  # 匹配正整数,包含0。
slug  # 匹配字母、数字以及横杠、下划线组成的字符串。
uuid  # 匹配格式化的uuid,如 075194d3-6885-417e-a8a8-6c931e272f00。
path  # 匹配任何非空字符串,包含了路径分隔符

如,视图函数为:

def index(request, name):
    return HttpResponse(f"hello {name}")

路由配置为:

from django.contrib import admin
from django.urls import path
from . import views


urlpatterns = [
    path('admin/', admin.site.urls),
   	path("index/<str:name>/", views.index)
]

使用127.0.0.1:1236/index/lihua/访问

2.2.2 正则捕获

语法:

re_path(route, view, kwargs=None, name=None)

如,我们要接收一个参数,其为整数,并且大于0小于20,且命名为age

re_path("index/(?P<age>[0-9] | 1[0-9])", views.index)

3、 包含其他路由配置

当我们还有其他应用时,需要包含其他的路由配置

from django.urls import path, include

path("app1/", include("app1.urls"))

包含app1里面的路由配置,其映射app1里面的路由配置,即app1里面的urls.py

4、 命名

4.1 url命名
path("index/", views.index, name="index"),  # 这个name参数就是重定向的地址

重定向时,避免因为路径的修改而导致需要修改所有引用了该路径的地方

4.2 app命名

在对应app.urls.py中,添加对应app_name = appName,避免照成url命名的冲突

app_name = "student"
urlpatterns = [
    path('admin/', admin.site.urls),
    path("index/", views.index, name="index")
]

当我们使用重定向时:

def test(request):
    return redirect("student:index")  # 定向到student这个app里面的index路由

5、 重定向

在视图函数中,添加一个test路由

from django.http import HttpResponse
from django.shortcuts import redirect, reverse


def test(request):
 	# 使用reverse可以解析出name对应的路径,当在app中时,需要
    url = reverse("index")
    print(url)
    return redirect("index")  # 即为path里面的name参数
	# return redirect("/index")  # 这样也可以,其为硬编码,如果路径改变,就要修改,同时如果路径过长,我们就需要使用name这个软编码


def index(request):
    return HttpResponse(f"hello")

在路由配置中

from django.contrib import admin
from django.urls import path, re_path
from . import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path("index/", views.index, name="index"),  # 这个name参数就是重定向的地址
    path("test/", views.test)
]

四、 模板系统

这里我们使用应用来演示

1、 模板设置

有两种方法,第一种方法是自定义一个公共的templates集中存放,第二种方法是放在app目录下

1.1 公共模板
  1. 在项目的根目录创建一个punlicTemplates的文件夹

  2. settings.py中,找到TEMPLATES变量

    TEMPLATES = [
        {
            'BACKEND': 'django.template.backends.django.DjangoTemplates',
            'DIRS': [os.path.join(BASE_DIR, 'publicTemplates'), ],  # BASE_DIR 即为项目的路径,这里为导入模板文件
            'APP_DIRS': True,  # 这个为True的话,其会在app目录下查找模板文件
            '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',
                ],
            },
        },
    ]
    
  3. publicTemplates中,根据应用名称,来创建不同的文件夹,来区分,避免不同的app出现相同的模板文件

  4. 在视图函数中渲染

    from django.shortcuts import render
    from django.http import HttpResponse
    
    
    # Create your views here.
    def index(request):
        # from django.template.loader import get_template
        # html = get_template("student/index.html").render()
        # return HttpResponse(html)
        # 相当于
        return render(request, "student/index.html")
    

    目录结构为:

    publicTemplates -> student -> index.html
    
1.2 应用模板
  1. student目录下创建一个templates文件夹,同时这个文件夹的名字最好是固定的

  2. 直接在视图函数中渲染

    from django.shortcuts import render
    
    
    # Create your views here.
    def index(request):
        return render(request, "index.html")
    
1.3 模板查找顺序

先查找项目根目录,如果项目的公共目录没找到,同时APP_DIRS=True,则会查找项目对应app下的目录

一般选择集中存放,每个app下面的模板文件,使用不同的文件夹分隔

如果分开存放,其模板文件的复用性更强

2、 模板变量

2.1 动态页面

创建一个函数,将一些信息通过后台传递给前端页面,我们使用context上下文管理来进行传递

views.py中,添加:同时注册路由

def test(request):
    from datetime import datetime
    now = datetime.now()
    lis = [1, 2, 3]
    dic = {"name": "李华", "age": 23}

    def func():
        return "你好,我是函数!"

    return render(
        request, 
        "student/get_time.html", {
        "now": now, 
        "lis": lis, 
        "dic": dic, 
        "fun": func
    })

在模板文件中,创建test.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>测试模板编程案例</title>
</head>
<body>
    <p>现在的时间为:{{ now }}</p>
    <p>传入的列表为:{{ lis }}</p>
    <p>我是列表的第一个值:{{ lis.0 }}</p>
    <p>我是一个字典:{{ dic }}</p>
    <p>我是一个字典里面的一个值:{{ dic.name }}</p>
    {% for i, j in dic.items %}
        <p>{{ i }}: {{ j }}</p>
    {% endfor %}
    <p>函数调用结果为:{{ func }}</p>
</body>
</html>
2.2 过滤器
2.2.1 语法

作用: 对变量进行过滤。在真正渲染出来之前,过滤器会根据功能处理好变量,然后得出结果后再替换掉原来的变量展示出来。

管道符号进行链式调用,比如实现一个功能,先把所有字符变成小写,把第一个字符转换成大写

语法:

{{fruits|lower|capfirst}}

使用参数:过滤器可以使用参数,在过滤器名称后面使用冒号”:”再加上参数,比如要把一个字符串中所有的空格去掉,则可以使用cut过滤器

{{fruits|cut:" "}}

注意:使用参数的时候,冒号和参数之间不能有任何空格,一定要紧挨着。

2.2.2 常用过滤器

https://images.cnblogs.com/cnblogs_com/blogs/722174/galleries/2074790/o_220630102355_sort.png

时间过滤器格式

https://images.cnblogs.com/cnblogs_com/blogs/722174/galleries/2074790/o_220630102642_sort.png

使用示例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>测试模板编程案例</title>
</head>
<body>
    <p>现在的时间是:{{ now|date:"Y年m月d日 H:i:s" }}</p>
    <p>获取两个元素:{{ lis|slice:":2" }}</p>
    <p>列表第一个元素相加的结果为:{{ lis.0|add:2 }}</p>
    <p>列表第一个元素不能相加的结果为:{{ lis.0|add:"a"|default:"error" }}</p>
    <p>列表的第一个元素:{{ lis|first }}</p>
    <p>js脚本为:{{ js }}</p>  <!--xss跨域脚本攻击,其中js ='<script>alert("你好呀,我是xss攻击")</script>',django默认会对代码进行转义 -->
    <p>js脚本为:{{ js|safe }}</p>  <!--如果代码是安全的,则可以取消转义-->  
</body>
</html>

3、 静态文件

和模板文件类似,有两种方法,第一种方法是自定义一个公共的static集中存放,第二种方法是放在app目录下分别存放

这里就使用公共的静态文件来演示

创建步骤:

  1. 创建一个static文件夹

  2. 注册静态文件,在settings.py

    STATIC_URL = '/static/'  # 用于拼接静态文件的存储路径
    # 创建一个列表,存放路径
    STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
    
  3. 同时以app为名,去创建不同的静态文件

  4. 在模板文件中导入static文件

    {% load static %}  <!--导入静态文件,一定要有这句话-->
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>学生管理系统</title>
        <link rel="stylesheet" href="{% static 'student/index.css' %}">
        <!-- <link rel="stylesheet" href="/static/student/index.css"> 这样导入也可以,这属于硬连接,不推荐,无法避免静态文件前缀的修改-->
    </head>
    <body>
    	<p>欢迎来到学生管理系统</p>
    </body>
    </html>
    

    如果要使用第一种方式导入,则要使用{% load static %}

4、 模板标签

4.1 使用语法

作用:可以在模板中进行各种逻辑操作,比如,循环、判断等

使用模板标签的语法:

{% load static %}  <!--加载模板标签-->
{% tag %}  {% endTag %}
4.2 常用标签

常用模板标签:

https://images.cnblogs.com/cnblogs_com/blogs/722174/galleries/2074790/o_220731033504_%E5%9B%BE%E7%89%871.png

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>学生管理系统</title>
    <link rel="stylesheet" href="{% static 'student/index.css' %}">  <!--导入我们的样式表-->
</head>
<body>
<p>欢迎来到学生管理系统</p>
<table>
    <thead>
        <tr><th>序号</th><th>姓名</th><th>年龄</th></tr>
    </thead>
    <tbody>
        {# 传入的数据为:{"data": [{"name": "李华", "age": 12, "sex": "男"}, {"name": "Lucy", "age": 23, "sex": "女"}]}#}
        {% for foo in data %}  <!--循环拿到传入前端页面的数据-->
            <tr {% if foo.age <= 13 %}style="color: #ff0000" {% endif %}>
            {# 进行条件判断,如果年龄小于13岁,则字体颜色改为红色 #}
                <td>
                    <!--路由为:path("detail/<str:name>", views.detail, name="detail"),-->
                    <a href="{% url 'student:detail' foo.name%}">
                        <!--foo.name是要传入路由的参数-->
                        {{ forloop.counter }}  <!--forloop为一个迭代对象,可以进行相应的操作-->
                    </a>
                </td>
                <td>{{ foo.name }}</td>
                <td>{{ foo.age }}</td>
        		<td>{{ foo.sex }}</td>
            </tr>
        {% endfor %}
    </tbody>
</table>
</body>
</html>
4.3 模板继承

用来解决代码冗余的问题

引用语法:{% include 'student/ad.html' %}

  • 作用在模板文件的某个特定的位置引入另一个模板的内容

继承语法:{% extends 'student/base.html' %}

{% extends 'student/base.html' %}  <!--继承base.html这个文件-->

语法:
{% block bolckname %}
	<title>登录界面<title>
{% endblock %}

比如,我们创建一个基类模板文件base.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    {% block title %}
		这里面的内容不会展示在子类文件中
    {% endblock %}
</head>
<body>
    <div>头部内容</div>
    <div>
        {% block content %}
        {% endblock %}
    </div>
    <div>尾部内容</div>
</body>
</html>

我们继承这个文件:

{% extends "student/base.html" %}  <!--继承父类的模板-->
{% block title %}  <!--在代码块中添加内容-->
<title>继承模板</title>
{% endblock %}
{% block content %}
    {% include "student/login.html" %}  <!--引入已经写好的html页面-->
{% endblock %}

5、 自定义

自定义模板标签和过滤器,官方文档地址:https://docs.djangoproject.com/zh-hans/4.0/howto/custom-template-tags/

5.1 路径配置
  • 如果创建的模板标签是公用的
    1. 创建一个新的app,然后将自定义的模板标签和过滤器的python文件放置在新的app下
    2. app注册
  • 如果创建的模板标签是特有的
    1. 该app目录下创建一个名为templatetags的文件夹,创建一个__init__.py,使得其变为一个包
    2. app注册

大概结构:

https://images.cnblogs.com/cnblogs_com/blogs/722174/galleries/2074790/o_220731060647_Snipaste_2022-07-31_14-06-27.png

自定义的 tags 和 filters 会保存在模块名为 templatetags 的目录内。模块文件的名字即稍候你用来加载 tags 的名字,所以小心不要采用一个可能与其它应用自定义的 tags 和 filters 冲突的名字。

添加 templatetags 模块后,你需要重启服务器,这样才能在模板中使用 tags 和 filters

5.2 自定义过滤器

原来我们的过滤器的使用方法:

{{ 模板变量|过滤器[:字符串参数] }}

<!--
过滤器是一个python函数
第一个参数:模板变量
第二个参数:可能有可能没有,即字符串参数
-->

如,一般来说,我们使用0和1来表示男性和女性存储在数据库中,我们可以定义一个过滤器来将这个0和1转换成汉字

  1. 定义函数

    #!/usr/bin/python3
    # -*- coding: UTF-8 -*-
    __author__ = "A.L.Kun"
    __file__ = "customer_filters.py"  # 这个文件的文件名可以随便,自定义
    __time__ = "2022/7/31 14:05"
    
    
    # 定义函数
    def to_sex(value, arg):
        """
        男是1,女是0
        :param value: 模板变量
        :param arg: 字符串参数
        :return: 处理后的结果
        """
        change = {
            "zh": ("女", "男"),
            "en": ("girl", "boy")
        }
        return change[arg][value]  # 根据语言选择返回对应的值
    
  2. 注册函数

    from django import template
    
    register = template.Library()  # register 其为固定值,一个字母都不能错
    
    
    register.filter(to_sex)  # 第一个参数是这个过滤器的名字,第二个参数是函数名字,我们使用第一个参数作为调用过滤器的名字,如果函数名和过滤器的名字相同,可以不用传入name参数
    
  3. 使用装饰器

    #!/usr/bin/python3
    # -*- coding: UTF-8 -*-
    __author__ = "A.L.Kun"
    __file__ = "customer_filters.py"  # 这个文件的文件名可以随便,自定义
    __time__ = "2022/7/31 14:05"
    
    from django import template
    
    register = template.Library()  # register 其为固定值,一个字母都不能错
    
    
    # 定义函数,且过滤器的名字和函数名一样
    @register.filter()
    def to_sex(value, arg='zh'):
        """
        男是1,女是0
        :param value: 模板变量
        :param arg: 字符串参数,注意,这个参数可以有默认参数
        :return: 处理后的结果
        """
        change = {
            "zh": ("女", "男"),
            "en": ("girl", "boy")
        }
        return change[arg][value]  # 根据语言选择返回对应的值
    
  4. 调用过滤器

    {% load customer_filters %}  <!--将模块导入-->
    
    <!--加载页面-->
    {# 传入的数据为:{"data": [{"name": "李华", "age": 12, "sex": "1"}, {"name": "Lucy", "age": 23, "sex": "0"}]}#}
    {% for foo in data %}  <!--循环拿到传入前端页面的数据-->
        <p>{{ foo.name }}</p>
        <p>{{ foo.age }}</p>
        <p>{{ foo.sex | to_sex:'zh'}}</p>
    {% endfor %}
    
5.3 自定义模板标签

模板标签可以做任何事情

5.3.1 简单标签

我们定义一个获取当前格式化时间的标签

  1. 定义函数

    #!/usr/bin/python3
    # -*- coding: UTF-8 -*-
    __author__ = "A.L.Kun"
    __file__ = "customer_tags.py"  # 文件名可以自定义
    __time__ = "2022/7/31 14:05"
    from datetime import datetime
    
    
    def current_time():
        return datetime.now().strftime("%Y年%m月%d日 %H:%M:%S")
    
  2. 注册标签

    from django import template
    
    register = template.Library()
    register.simple_tag(current_time, name="current_time")  # name参数和过滤器的name参数作用一样,使用方法也是一样的
    
  3. 结合装饰器

    #!/usr/bin/python3
    # -*- coding: UTF-8 -*-
    __author__ = "A.L.Kun"
    __file__ = "customer_tags.py"  # 文件名可以自定义
    __time__ = "2022/7/31 14:05"
    
    from datetime import datetime
    from django import template
    
    register = template.Library()
    
    
    @register.simple_tag()  # 注意,如果这个要自定义name的话,要指定 name='youName'
    def current_time():
        return datetime.now().strftime("%Y年%m月%d日 %H:%M:%S")
    
  4. 使用标签

    {% load customer_tags %}  <!--将标签导入-->
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>test</title>
    </head>
    <body>
        当前时间为:{% current_time %}  <!--直接使用标签,如果该标签要传入参数的话,我们加个空格,然后传入需要的参数就可以了-->
    </body>
    </html>
    

需要传入参数的一个标签:

#!/usr/bin/python3
# -*- coding: UTF-8 -*-
__author__ = "A.L.Kun"
__file__ = "customer_tags.py"  # 文件名可以自定义
__time__ = "2022/7/31 14:05"

from datetime import datetime
from django import template

register = template.Library()


@register.simple_tag()  
def current_time(format):
    return datetime.now().strftime(format)

# {% current_time format %}  format: "%Y年%m月%d日 %H:%M:%S"其是可以从视图函数中传入的

这个过程实现了:视图函数 -》模板文件 -》标签函数

那么,我们是否可以直接将参数:视图函数-》标签函数 呢?

答案是可以的,这个方法是最通用的,实现方法:

#!/usr/bin/python3
# -*- coding: UTF-8 -*-
__author__ = "A.L.Kun"
__file__ = "customer_tags.py"  # 文件名可以自定义
__time__ = "2022/7/31 14:05"

from datetime import datetime
from django import template

register = template.Library()


@register.simple_tag(takes_context=True)  # 使得标签函数可以从上下文管理中获取变量
def current_time(context):  # 第一个参数必须是context
    return datetime.now().strftime(context["format"])  # 像字典一样获取值
5.3.2 包含标签

有了上面的基础,我们直接使用装饰器来创建包含标签

#!/usr/bin/python3
# -*- coding: UTF-8 -*-
__author__ = "A.L.Kun"
__file__ = "customer_tags.py"  # 文件名可以自定义
__time__ = "2022/7/31 14:05"

from datetime import datetime
from django import template

register = template.Library()

@register.inclusion_tag(filename="student/show_list.html")  # takes_context=True 也可以使用
def show_list(value, style):  # 定义一个函数,接收模板变量
    return {"lis": value, "style": style}  # lis是传递给包含模板的参数

我们的包含模板的文件为,show_list.html

{#用于将列表数据展示为有序列表的模板文件,这里面可以使用if判断我们需要加载的样式#}
{% if style == "ul" %}
<ul>
    {% for foo in lis %}
        <li>{{ foo }}</li>
    {% endfor %}
</ul>
{% elif style == "ol" %}
<ol>
    {% for foo in lis %}
        <li>{{ foo }}</li>
    {% endfor %}
</ol>
{% endif %}

在主文件中写入主结构:

{% load customer_tags %}  <!--将标签导入-->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>test</title>
</head>
<body>
    {% show_list data style %}  
</body>
</html>

这个过程实现了:视图函数 -》模板文件 -》标签函数

那么,我们可以直接将参数:视图函数-》标签函数

实现方法:

#!/usr/bin/python3
# -*- coding: UTF-8 -*-
__author__ = "A.L.Kun"
__file__ = "customer_tags.py"  # 文件名可以自定义
__time__ = "2022/7/31 14:05"

from datetime import datetime
from django import template

register = template.Library()

@register.inclusion_tag(filename="student/show_list.html", takes_context=True)  
def show_list(context):  # 定义一个函数,接收模板变量
    return {"lis": context["data"], "style": context["style"]}

我们调用的话:

{% load customer_tags %}  <!--将标签导入-->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>test</title>
</head>
<body>
    {% show_list %}  
</body>
</html>

自定义标签特点:

  1. 可以接收任意位置、关键字参数
  2. 通过空格分隔
  3. 简单标签:return什么页面直接展示什么
  4. 包含标签:接受数据,把数据通过另一个模板进行渲染后再返回回来,哪里调用,哪里显示

五、 模型层

1、 模型基础

1.1 模型配置

模型层和SQLAchemy的操作类似:SQLAchemy语法

ORM:

  1. 使用面向对象的方式,描述数据库,操作数据库,达到不用编写SQL语句,就能对我们的数据库进行操作
  2. 数据库 -> 表(类) -> 表数据(实例对象) -> 表字段(对象属性)

环境配置:

  1. 安装pymysql:pip install pymysql -i https://pypi.douban.com/simple

  2. 配置数据库

    -- 创建项目数据库
    CREATE DATABASE CRM charset=utf8;  -- 注意charset要使用小写
    
    -- 创建一个管理员用户crm账号,密码为 crm:
    CREATE USER ‘crm'@'%'IDENTIFIED BY ‘crm';
    
    -- 给这个用户授予所有远程访问,这个用户主要用于管理整个数据库,备份,还原等操作,CRM这个数据库
    GRANT ALL ON CRM.* TO ‘crm'@'%';
    
    -- 也可以一步完成
    GRANT ALL PRIVILEGES ON CRM.* to 'crm'@'%' IDENTIFIED BY 'crm';
    
    -- 使授权立即生效:
    FLUSH PRIVILEGES;
    
  3. 修改配置文件

    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql',
            "NAME": "CRM", # 数据库名字
            "USER": "crm",  # 数据库登录用户
            "PASSWORD": "crm",  # 数据库登录木马
            "HOST": '127.0.0.1',  # 指定访问的主机
            "PORT": '3306',  # 指定访问的端口
        }
    }
    
  4. 设置连接器

    修改主目录下的__init__.py文件,设置连接器为pymysql,在文件中添加下面代码:

    import pymysql
    
    pymysql.install_as_MySQLdb()  # 将pymysql设置为我们的数据库连接器
    
1.2 创建模型

在app下的models.py文件中创建类,来创建模型

from django.db import models


# Create your models here.
class Student(models.Model):  # Student模型是models.Model的子类
    name = models.CharField(max_length=20)  # 创建一个name字段,其为char 20
    age = models.SmallIntegerField()  # 创建一个短整型的age字段
    sex = models.SmallIntegerField(default=1)  # 设置sex的默认值为1,短整型字段
    qq = models.CharField(max_length=20)  # 存储qq号
    c_time = models.DateTimeField(verbose_name="创建时间", auto_now_add=True)  # 日期时间字段,同时自动添加当前时间,verbose_name参数的作用是创建具体描述
1.3 激活模型

注意,一定要先注册app

生成迁移文件:

python manage.py makemigrations  # 创建全部App的迁移
python manage.py makemigrations student  # 创建student App的迁移
# 作用:告诉django我对模型做了一些修改,希望把这些修改存储起来
python manage.py sqlmigrate student 0001  # 查看迁移原生的SQL语句

python manage.py sqlmigrate 迁移序号     		# 查看迁移sql语句
python manage.py migrate student 0001       # 回滚回指定迁移

python manage.py migrate  # 让全部app迁移生效
python manage.py migrate student  # 让 student app迁移生效

运行完成后,我们来看一下我们的数据库:

https://images.cnblogs.com/cnblogs_com/blogs/722174/galleries/2074790/o_220731095622_Snipaste_2022-07-31_14-06-27.png

迁移的步骤:

  1. 模型创建
  2. 生成迁移文件
  3. 迁移生效

作用:实时升级数据库,而不会丢失数据

1.4 数据操作

首先,配置环境:

/* 打开python交互模式
第一种方法:
在目标环境下安装 */
pip install ipython
python manage.py shell
/*方法二
使用python console
*/
1.4.1 增

每个django模型类都有一个默认的管理器,objects

# 第一种
s1 = Student(name='Dou', age=15, qq='123456') # 实例化
s1.save() # 将实例对象保存到数据库

# 第二种
s2 = Student()
s2.name='Tea'
s2.age = 12
s2.save()

# 第三种
Student.objects.create(name='Guo')  # 但是这个方法会一直创建name

# 第四种,如果有,进行get查询,返回的元组的第二个元素就是False,如果没有,进行create新增
In [12]: Student.objects.get_or_create(name="Hai")
Out[13]: (<Student: Student object (4)>, True)
In [14]: Student.objects.get_or_create(name="Hai")
Out[15]: (<Student: Student object (4)>, False)
In [16]: Student.objects.get(name='Hai')
Out[17]: <Student: Student object (4)>
1.4.2 删
s1 = Student.objects.get(pk=4)
Out[19]: <Student: Hai>
        
s1.delete()
Out[20]: (1, {'teacher.Student': 1})
    
Student.objects.filter(age=20).delete()
Out[21]: (2, {'teacher.Student': 2})
    
Student.objects.all().delete()  # 全部删除
Out[22]: (1, {'teacher.Student': 1})
1.4.3 改
# 1.改单条,通过属性修改
s1 = Student.objects.get(pk=1)  # pk即为主键名,获取主键名为1的数据
s1.age=18
s1.save()

# 2.改多条,通过update
Student.objects.filter(age=18).update(age=20)
Out[17]: 2 # 修改数据的条数
1.4.4 查
1.查所有
res = Student.objects.all() 
print(res.query) # 查看sql语句

2.查一条
Student.objects.get(pk=1) # pk即为主键名,获取主键名为1的数据

3.带条件查询
Student.objects.filter(sex=1)
Out[8]: <QuerySet [<Student: Dou>, <Student: Tea>, <Student: Guo>, <Student: Hai>]>

总结:
1.get拿到是一个对象,集合对象
2.allfilter拿到的是查询集,querySet[]

QuerySet集合对象:

  1. 可以用list转成列表
  2. 可以迭代
  3. 可以切片(不支持负索引)

2、 查询及表关系

2.1 单表查询

常用查询方法:

https://images.cnblogs.com/cnblogs_com/blogs/722174/galleries/2074790/o_220801114103_%E5%9B%BE%E7%89%871.png

1.查询所有all
2.查询单条get
3.条件查询filter与排除exclude
4.获取第一条first或最后一条last
5.指定字段查询
    values:提升查询效率;仅能拿到指定字段的值,其他字段值无法获取
    + User.objects.values("name", "age")  # 相当于 SELECT `name`, `age` FROM `student`
    only:提升查询效率;能拿到指定字段的值,其他字段值也能获取,主键必带
    defer:排除指定字段,进行查询,主键必带,作用与only相反
6.排序
res = Student.objects.order_by('age')
res = Student.objects.order_by('-age')
7.切片
不支持负索引,数据量大时不要使用步长
8.多条件查询
	and查询
	res = Student.objects.filter(sex=1, age=18)  
    
    or查询
    from django.db.models import Q
    res = Student.objects.filter(Q(sex=1), Q(age=18) | Q(age=20))   values方法还可以指定字段查询

常用的查询条件:

https://images.cnblogs.com/cnblogs_com/blogs/722174/galleries/2074790/o_220801114109_%E5%9B%BE%E7%89%872.png

2.2 字段及其参数

官方文档: https://docs.djangoproject.com/zh-hans/4.0/ref/models/fields/

常见字段:

1. IntegerField : 整型,映射到数据库中的int类型。
    
2. CharField:  字符类型,映射到数据库中的varchar类型,通过max_length指定最大长度。
    
3. TextField:  文本类型,映射到数据库中的text类型。
    
4. BooleanField: 布尔类型,映射到数据库中的tinyint类型,在使用的时候,传递True/False进去。如果要可以为空,则用NullBooleanField。
    
5. DateField:  日期类型,没有时间。映射到数据库中是date类型,在使用的时候,可以设置DateField.auto_now每次保存对象时,自动设置该字段为当前时间(修改时间)。设置DateField.auto_now_add当对象第一次被创建时自动设置当前时间。
    
6. DateTimeField:   日期时间类型。映射到数据库中的是datetime类型,在使用的时候,传递datetime.datetime()进去。
    
其余的请到官方文章查阅

字段实例化常用参数:

primary_key:  指定是否为主键。
    
unique:  指定是否唯一。
    
null:  指定是否为空,默认为False。
    
blank: 等于True时form表单验证时可以为空,默认为False。
    
default:  设置默认值。
    
DateField.auto_now:  每次修改都会将当前时间更新进去,只有调用,QuerySet.update方法将不会调用。这个参数只是Date和DateTime以及TimModel.save()方法才会调用e类才有的。
    
DateField.auto_now_add:  第一次添加进去,都会将当前时间设置进去。以后修改,不会修改这个值
2.3 表关系
2.3.1 概述

https://images.cnblogs.com/cnblogs_com/blogs/722174/galleries/2074790/o_220801130823_Snipaste_2022-08-01_21-08-01.png

2.3.2 一对一

一对一存放的字段可以是双向的

from django.db import models


# Create your models here.
class Student(models.Model):  # Student模型是models.Model的子类
    name = models.CharField(verbose_name="学生姓名", max_length=20)  # 创建一个name字段,其为char 20
    age = models.SmallIntegerField(verbose_name="学生年龄", null=True)  # 创建一个短整型的age字段
    sex = models.SmallIntegerField(verbose_name="学生性别", default=1)  # 设置sex的默认值为1,短整型字段
    qq = models.CharField(verbose_name="学生qq", max_length=20, null=True)  # 存储qq号
    phone = models.CharField(verbose_name="学生手机号", max_length=12, null=True)  # 存储学生手机号
    c_time = models.DateTimeField(verbose_name="创建时间", auto_now_add=True)  # 日期时间字段
    # 这个表里面还有一个id字段,是自动生成的,作为主键
    detail = models.OneToOneField("StudentDetail",
                                  on_delete=models.SET_NULL, null=True)

    # 进行一对一映射,一名学生对应一个详情页数据;同时,如果这个详情表删除的话,就把这个字段置空,故要设置这个字段可以为空

    class Meta:
        db_table = "student"  # 设置表的名字

    def __str__(self):
        # 设置print输出的内容
        return self.name


class StudentDetail(models.Model):
    address = models.CharField(verbose_name="学生地址", max_length=20, null=True)  # 学生信息

    # student = models.OneToOneField("Student", on_delete=models.CASCADE)  # 一对一是双向的
    # 这里如果学生删了,地址也没必要存在了,设置为级联删除
    # 这个表里面还有一个id字段,是自动生成的,作为主键
    class Meta:
        db_table = "detail"

    def __str__(self):
        return self.address
2.3.3 一对多

一对多的表,最好将字段放在多的表中,这样更好查询

# Create your models here.
class Student(models.Model):  # Student模型是models.Model的子类
    name = models.CharField(verbose_name="学生姓名", max_length=20)  # 创建一个name字段,其为char 20
    age = models.SmallIntegerField(verbose_name="学生年龄", null=True)  # 创建一个短整型的age字段
    sex = models.SmallIntegerField(verbose_name="学生性别", default=1)  # 设置sex的默认值为1,短整型字段
    qq = models.CharField(verbose_name="学生qq", max_length=20, null=True)  # 存储qq号
    phone = models.CharField(verbose_name="学生手机号", max_length=12, null=True)  # 存储学生手机号
    c_time = models.DateTimeField(verbose_name="创建时间", auto_now_add=True)  # 日期时间字段
    # 这个表里面还有一个id字段,是自动生成的,作为主键
    detail = models.OneToOneField("StudentDetail",
                                  on_delete=models.SET_NULL, null=True)

    # 进行一对一映射,一名学生对应一个详情页数据;同时,如果这个详情表删除的话,就把这个字段置空,故要设置这个字段可以为空
    grade = models.ForeignKey("Grade", on_delete=models.SET_NULL, null=True)  # 我们使用外键约束来完成一对多,不进行级联删除,班级去除后,把这个字段置空    

    class Meta:
        db_table = "student"  # 设置表的名字

    def __str__(self):
        # 设置print输出的内容
        return self.name


class Grade(models.Model):
    name = models.CharField(verbose_name="班级名称", max_length=20)  # 班级信息,这里只设置班级的名称

    # 这个表里面还有一个id字段,是自动生成的,作为主键

    class Meta:
        db_table = "grade"  # 设置表名称

    def __str__(self):
        return self.name
2.3.4 多对多

多对多也是双向的,其会自动生成student和course的中间表

from django.db import models


# Create your models here.
class Student(models.Model):  # Student模型是models.Model的子类
    name = models.CharField(verbose_name="学生姓名", max_length=20)  # 创建一个name字段,其为char 20
    age = models.SmallIntegerField(verbose_name="学生年龄", null=True)  # 创建一个短整型的age字段
    sex = models.SmallIntegerField(verbose_name="学生性别", default=1)  # 设置sex的默认值为1,短整型字段
    qq = models.CharField(verbose_name="学生qq", max_length=20, null=True)  # 存储qq号
    phone = models.CharField(verbose_name="学生手机号", max_length=12, null=True)  # 存储学生手机号
    c_time = models.DateTimeField(verbose_name="创建时间", auto_now_add=True)  # 日期时间字段
    # 这个表里面还有一个id字段,是自动生成的,作为主键
    detail = models.OneToOneField("StudentDetail",
                                  on_delete=models.SET_NULL, null=True)

    # 进行一对一映射,一名学生对应一个详情页数据;同时,如果这个详情表删除的话,就把这个字段置空,故要设置这个字段可以为空
    grade = models.ForeignKey("Grade", on_delete=models.SET_NULL, null=True)  # 不进行级联删除,班级去除后,把这个字段置空

    class Meta:
        db_table = "student"  # 设置表的名字

    def __str__(self):
        # 设置print输出的内容
        return self.name


class Enroll(models.Model):
    """多对多的中间表"""
    student = models.ForeignKey("Student", on_delete=models.CASCADE)
    course = models.ForeignKey("Course", on_delete=models.CASCADE)
    c_time = models.DateTimeField(verbose_name="报名时间", auto_now_add=True)
    
    class Meta:
        db_table = "enroll"  


class Course(models.Model):
    name = models.CharField(verbose_name="课程名称", max_length=20)  # 设置学生对应的课程名称,一个学生有多个课程,同时一个课程有多个学生
    # 多对多映射,其也是双向的
    students = models.ManyToManyField("Student", through='Enroll')  # 关联Student这张表,同时指定中间表
    
    class Meta:
        db_table = "course"

    def __str__(self):
        return self.name

如果我们没有指定中间表,Django会自动帮助我们创建一个中间表

3、 多表查询

3.1 一对多
3.1.1 正向查询

一个模型如果有一个关联字段(外键字段),通过这个模型对外键关联的模型进行操作叫做正向。

g1 = Grade(name="数据库")
g2 = Grade(name="框架")

# 增、改:无则增,有则改
# 1.通过属性赋值的方式
s1 = Student(name='Arrogant', age=18, sex=0)
s1.grade = g1
s1.save()

# 2.通过主键的方式
s2 = Student(name='ljy', age=19)
s2.grade_id = g2.id
s2.save()

# 删
s2.grade = None
s2.save()

# 查
s1.grade
s1.grade.name
3.1.1 反向查询

一个模型如果被另一个模型关联,通过这个模型对关联它的模型进行操作就叫反向。

实际:如果一个模型(eg:Student)有关联字段(eg:ForeignKey),那么这个外键模型(grade)的实例(eg:g1)将可以访问一个管理器(eg:管理Student所有实例的管理器)。默认情况下,这个管理器名为有关联字段的模型小写名_set(eg:student_set)。

"""
In [27]: g[1].student_set
Out[27]: <django.db.models.fields.related_descriptors.create_reverse_many_to_one_manager.<locals>.RelatedManager at 0x7faccb88feb8>  # 关于多对一的方向管理器
"""
# 增
# 1.通过grade表创建student表数据
new_student = g2.student_set.create(name='哈哈', age=16)
# 2.增加多条数据
s3, s4, s5, s6 = Student.objects.filter(id__lte=8)
g1.student_set.add(s3,s4,s5,s6)

# 删
# 1. 从相关对象中移除指定的模型对象
g1.student_set.remove(s1)
g1.student_set.remove(s2,s4)
# 2. 从相关对象中删除所有的对象
g1.student_set.clear()

# 改,有则改,无则增
g1.student_set.set([s1, s2, s3, s4, s5])
s6, s7 = Student.objects.filter(grade_id = 2)
g1.student_set.set([s6, s7])  # 班里面只有两个学生

# 查
# 1. 查询所有
g1.student_set.all()
# 2. 条件查询
g1.student_set.filter(age=16)
# 自定义反向关系管理器student_set----->student
grade = models.ForeignKey('Grade', on_delete=models.SET_NULL, null=True, related_name='student') 

g1 = Grade.objects.get(pk=1)
g1.student  # 这个即为我们自定义的方向关系管理器,默认为 student_set

总结:

  • 正向:模型通过关联字段的属性名操作关联模型

  • 反向:被关联模型使用关联模型的小写模型名_set去操作

3.2 多对多
# 使用中间表来设置两个对象的关系
s1 = Student.objects.get(pk=6)
c1 = Course.objects.get(pk=1)

e = Enroll()
e.student = s1
e.course = c1
e.save()

# 正向查询
c1.students.all() # 关联字段属性名
# 反向查询
s1.course_set.all() # 使用的是源模型的小写模型名_set


# 通过关联字段中设置related_name修改反向管理器名
3.3 一对一
# 正向:通过关联字段的属性名
d2 = StudentDetail(address='湖南')
s2 = Student.objects.get(pk=7)
s2.detail = d2

s2.detail

# 反向:管理器对象不是一个对象的集合,而是一个单一的对象,名字叫源模型的小写模型名,注意所有字母都是小写哦,不是根据小驼峰命名法来的
d1 = StudentDetail(address='江西')
d1.student = s1
d1.save()
d1.student
3.4 跨表查询

跨模型的相关字段的字段名,以双下划线隔开,直到你想要达到的字段为止。

正向是相关字段的字段名;反向是模型的小写名。

# 多对多(学生和课程)
Course.objects.filter(students__sex=0)
Student.objects.filter(course__name__contains='python')
# 一对多
Student.objects.filter(grade__name = 'django框架').distinct()  # distinct() 方法有去重的作用
# 一对一
StudentDetail.objects.values('address', 'student__name', 'student__age')
Student.objects.values('name', 'age', 'studentdetail__address')
;