一、Django快速入门

  1. 使用最新版本 Django4.2LTS 版本,3 年内不需要更换版本
  2. 由浅入深讲解,浅显易懂
  3. 课程大纲全面包含 Django 框架知识点,内容丰富全面细致
  4. 知识点结合项目实战实现全栈项目应用

视频教程

Django的HTML自动补全功能与VSCode中的Django扩展无法正常工作|极客教程

VSCode中安装Django插件后实现html语法提示_vscode html提示-CSDN博客

Django 官网(文档): https://docs.djangoproject.com/zh-hans/

Django 是一个开放源代码的 Web 应用框架,由 Python 写成。采用了 MTV 的框架模式,即模型M,视图V和模版T

它最初是被开发来用于管理劳伦斯出版集团旗下的一些以新闻内容为主的网站的,即是 CMS(内容管理系统)软件。

并于 2005 年 7 月在 BSD 许可证下发布。这套框架是以比利时的吉普赛爵士吉他手 Django Reinhardt 来命名的。

2020 年,Django 3.0 发布

2022 年,Django 4.0 发布。Django4.0 将支持 Python3.8、3.9 与 3.10

2023 年,Django 4.2LTS 发布,长期支持版本,会维护 3 年,在企业中我们建议使用 LTS 版本

Django 每 2 年会推出全新的大版本。

1. 创建虚拟环境

  1. 打开cmd

  2. 安装virtualenv,使用pip install virtualenv virtualenvwrapper-win,Linux 下不用写-win

  3. workon 查看虚拟环境

    workon
  4. mkvirtualenv创建新的虚拟环境

    mkvirtualenv env
  5. rmvirtualenv删除虚拟环境

  6. workon env进入虚拟环境(使用 cmd 才行)

python -m venv

  • 内置方法venv 是 Python 标准库中的模块,因此不需要额外安装。

  • 基本功能:创建一个轻量级的虚拟环境,隔离项目的依赖包。

  • 命令示例

    python -m venv myenv
  • 平台兼容性:可以在所有支持的 Python 环境中使用。

  • 灵活性:适用于简单的虚拟环境需求,使用简单明了。

mkvirtualenv

  • 来自第三方库mkvirtualenvvirtualenvwrapper 的一部分,virtualenvwrapper 是一个为 virtualenv 提供更高层次的封装的工具。

  • 附加功能:提供了更多的功能,如项目管理、容易的环境切换、环境的创建和删除等,支持管理多个虚拟环境。

  • 依赖:需要先安装 virtualenvvirtualenvwrapper,这可能需要额外的配置。

    mkvirtualenv myenv

    或者指定你要创建虚拟环境的目录

    virtualenv /path/to/your/folder/env
    运行 \path\to\your\folder\env\Scripts\activate  # 激活虚拟环境(遇到权限问题Set-ExecutionPolicy RemoteSigned --Windows环境下)
    deactivate # 退出虚拟环境
  • 命名规则:自带命名规则和其他一些便利的功能,使得管理虚拟环境更加便捷。

2. 安装 django(可以在虚拟环境中)

pip install django==4.2 -i https://pypi.tuna.tsinghua.edu.cn/simple

(4.2 是 LTS 长期支持版本,推荐使用,如不写则会安装最新版本)

测试是否安装成功

pip show django

3. 创建 Django 项目

方式一: 进入到指定要存放项目的目录,执行 django-admin startproject HelloDjango 来创建一个名字为 HelloDjango 的工程

方式二:使用 Pycharm 专业版创建 Django 项目.

image-20241123150020267

manage.py:
	这是Django项目的核心命令行工具。通过这个文件,您可以启动开发服务器、运行数据库迁移、创建数据库表、清理数据库等。例如,python manage.py runserver 会启动开发服务器。

HelloDjango/:
	这是项目的根目录,通常与项目的主模块同名。

HelloDjango/__init__.py:
	这个文件使得Python将HelloDjango目录视为一个包。即使这个文件是空的,它也是必需的,因为它告诉Python解释器这个目录应该被视为Python的包。

HelloDjango/settings.py:
	这个文件包含了项目的配置信息。它定义了Django如何运行,包括数据库配置、时区、使用的应用程序、中间件、模板配置等。

HelloDjango/urls.py:
	这个文件定义了项目的URL路由。它告诉Django对于每个请求的URL,应该调用哪个视图函数来处理。

HelloDjango/wsgi.py:
	这个文件是Web服务器网关接口(WSGI)的入口点,用于部署Django项目到生产服务器。WSGI是一个Python标准,用于在服务器和Web应用程序之间传递信息。

HelloDjango/asgi.py:
	这个文件定义了异步服务器网关接口(ASGI)的入口点。ASGI是Django 3.0引入的,用于支持异步Web应用,这对于提高性能和处理大量并发连接非常有用,特别是在需要长轮询和WebSockets的应用中(类似Tornado异步框架)。

创建带应用的项目

  1. 使用 Django 创建一个带有应用的项目

    # 安装 Django
    pip install django
    
    # 创建一个 Django 项目
    django-admin startproject myproject
    
    # 进入项目目录
    cd myproject
    
    # 创建一个 Django 应用
    python manage.py startapp myapp
  2. 使用 Flask 创建一个带有应用的项目

    # 安装 Flask
    pip install Flask
    
    # 创建项目目录
    mkdir myproject
    cd myproject
    
    # 创建应用文件
    touch app.py
  3. 使用 Node.js 和 Express 创建一个应用项目

    # 安装 Express 生成器
    npm install -g express-generator
    
    # 创建一个 Express 应用
    express myapp
    
    # 进入应用目录并安装依赖
    cd myapp
    npm install
  4. 使用 Vue CLI 创建一个 Vue.js 项目

    # 安装 Vue CLI
    npm install -g @vue/cli
    
    # 创建一个 Vue 项目
    vue create myproject

settings.py

from pathlib import Path

# 项目根目录
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
# 密钥
SECRET_KEY = 'django-insecure-*x+ln#hax3c-a26$cc*cl2+w#h(6^=%_0reqp^ju!fr$galzd6'

# SECURITY WARNING: don't run with debug turned on in production!
# 是否使用调试模式, 开发环境建议设置为True, 生产(上线部署)环境设置为False
DEBUG = True

# 允许访问的主机
# * 表示允许所有主机访问,允许任何电脑访问(局域网)
# 上线后可以指定哪些允许
ALLOWED_HOSTS = ["*"]


# Application definition
# 定义应用
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    # 自定义应用
    "user",
]

# 中间件
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',
]

# 根路由路径
ROOT_URLCONF = 'DjangoPro1.urls'

# 模板配置
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [], # 如果你要在其他地方创建前端文件,请把路径加上,如BASE_DIR/"templates",
        'APP_DIRS': True,
        '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',
            ],
        },
    },
]

# wsgi目录
WSGI_APPLICATION = 'DjangoPro1.wsgi.application'


# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
# 数据库
DATABASES = {
    'default': {
        # 只支持关系型数据库
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}


# Password validation
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
# 密码验证
AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/4.2/topics/i18n/
# 国际化
LANGUAGE_CODE = 'en-us' # zh-hans 中文

TIME_ZONE = 'UTC'

USE_I18N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.2/howto/static-files/
# 静态文件
STATIC_URL = 'static/'

# Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
# 默认主键类型a
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

4. 测试服务器的启动

python manage.py runserver [ip:port] 记得关防火墙

可以直接进行服务运行 默认执行起来的端口是8000

也可以自己指定ip和端口:
1.监听机器所有可用 ip(电脑可能有多个内网ip或多个外网ip)
	python manage.py runserver 0.0.0.0:8000

2.同时在settings.py中将
	ALLOWED_HOSTS=['*']

3.在其他局域网电脑上可以通过在浏览器输入 Django项目所在电脑的 IP:8000 来访问

5. 数据迁移

迁移的概念:就是将模型迁移到数据库的过程

生成迁移文件: python manage.py makemigrations (可加具体应用)

执行迁移: python manage.py migrate (可加具体应用)

不需要初始化迁移文件夹,每个应用默认有迁移文件夹 migrations

6. 创建应用

python manage.py startapp App

创建名称为 App 的应用
使用应用前需要将应用配置到项目中,settings.py中将应用加入到INSTALLED_APPS选项中

应用目录介绍:
__init__·py:
	其中暂无内容,使得app成为一个包
admin.py:
	管理站点模型的声明文件,默认为空
apps.py:
	应用信息定义文件,在其中生成了AppConfig,该类用于定义应用名等数据
models.py:
	添加模型层数据类文件
views.py:
	定义URL相应函数
migrations包:
	自动生成,生成迁移文件的
tests.py:
	测试代码文件

7. 基本视图

首先我们在应用的views.py中建立一个路由响应函数

from django.http import HttpResponse
def welcome(request): # 视图函数需要带参数request
	return HttpResponse('HelloDjango');
	# 渲染模板的方式(在你的应用新建一个templates文件夹,里面放index.html)
    # return render(request, 'index.html')

然后在项目根目录下的urls.py文件中注册路由函数,当有前端请求对应路径时,会交给路由函数处理

from django.contrib import admin
from django.urls import path,include
from user.views import *

urlpatterns = [
    # 视图函数对应路由
    # 直接访问视图函数,没有访问子路由
    path('welcome/', welcome),  # welcome指你的路由响应函数
    path('admin/', admin.site.urls),
]

这样当请求127.0.0.1:8000/welcome/时,会获得 HelloDjango 响应

使用子路由的响应

在应用app中新建urls.py,结构与根目录下urls.py类似

app/urls.py

from django.urls import path
from user.views import *
# 子路由

urlpatterns = [
    # url路由写法(正则)
    # url(r'^index/', index),

    # v2.0,v3.0,v4.x版本的路由写法
    path('welcome/',welcome,name='welcome'),
    path('welcome2/',welcome2,name='welcome2'),
]

# 子路由写法如下
urlpatterns =[
	# django1.8,2.0正则表达式写法:不再使用,不推荐
    # url(r'^index/$',index),
	# Djangov2.0,v3.0,v4.0写法:常用
    path(r'hello/',hello,name='hello'), # name是这条路由的名字,以后做重定向会用

接着我们在 urls.py 中进行注册

# 1.直接访问视图
# path(r'hello/',hello, name='hello'),
# 2.导入App中的子路由urls.py文件
path('app/',include('App.urls'))
# 3.使用命名空间
#path('app/',include(('App.urls','App'),namespace='App'))

这样当我们访问127.0.0.1:8000/app/welcome会返回 welcome,访问127.0.0.1:8000/app/welcome2会返回 welcome2

前后端交互

image-20241123155328362

8. 基本模板

模板文件夹templates

模板实际上就是我们用HTML写好的页面
创建模板文件夹templates,在模板文件夹中创建模板文件
在views中去加载渲染模板,使用render函数:
return render(request,'index.html')

9. 定义模型

在models.py中引入models

from django.db import models

创建自己的模型类,但切记要继承自 models.Model

案例驱动: 使用模型定义班级,并在模板上显示班级列表

  1. 修改user/models.py (__str__函数是用于规定打印输出时转换成什么字符串)
from django.db import models

# Create your models here.

# 模型 <==> 数据库表   相对应
# 模型类属性 <==> 数据库表字段   相对应
# 模型类实例 <==> 数据库表记录   相对应
# 模型类方法 <==> 数据库表记录   相对应
class UserModel(models.Model):
    name = models.CharField(max_length=32, unique=True)  # 姓名 字符串类型 最大长度32 name varchar(32)
    age = models.IntegerField(default=18)  # 年龄 整型 age int default 18
    sex = models.CharField(max_length=10, choices=(('male', '男'), ('female', '女')))  # 性别 字符串类型 最大长度10 sex varchar(10) 
    is_delete = models.BooleanField(default=False)  # 是否删除 布尔类型 is_delete boolean default false

	def __str__(self) -> str: # 修改__str__方法, 方便在admin中查看对象信息
        return f"{self.name} - {self.age} - {self.sex} - {self.is_delete}"
# 表字段
# name - 用户名称
# age - 年龄
# sex - 性别
# is_delete - 是否删除

# 注意:
# 数据迁移 models对应的表结构一旦改变, 必须执行迁移命令才会更新数据库表结构
# `迁移的概念:就是将模型迁移到数据库的过程`
# 生成迁移文件: python manage.py makemigrations
# 执行迁移: python manage.py migrate
  1. 新增视图函数
# 视图函数
def get_user(request):
    # 获取所有用户信息
    users = UserModel.objects.all()
    # 传递给模板
    return render(request, 'users.html', {'users': users})
  1. templates文件夹中新增users.html对应于视图函数,注意模板语法

假如是pycharm开发,记得在设置中 找到template语法设置为 Django

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>所有用户</title>
  </head>
  <body>
    <h2>所有用户</h2>
    <ul>
      {% for user in users %}

      <li>{{ user.name }}, {{ user.age }}</li>

      {% endfor %}
    </ul>
  </body>
</html>
  1. 最后,更改路由设置

path("users/",get_user,name="users")

10. admin后台管理系统

在admin.py中将model加入后台管理

admin.site.register(GradeModel)

创建超级用户: python manage.py create superuser(相应的,在数据库中的auth_user表也会多一个超级管理员记录)

访问admin后台:http://127.0.0.1:8000/admin/

image-20241123204004601

可以在settings中设置LANGUAGE_CODE = zh-hans设置中文

二、Django路由Router

在实际开发过程中,一个Django 项目会包含很多的 app ,这时候如果我们只在主路由里进行配置就会显得杂乱无章,所以通常会在每个 app 里,创建各自的urls.py路由模块,然后从根路由出发,将 app 所属的 url 请求,全部转发到相应的 urls.py 模块中。而这个从主路由转发到各个应用路由的过程叫做路由的分发

re_path正则路由的写法

# re_path正则写法 ()表示正则分组为一个参数 (?P<参数名>regex)
re_path(r'^user_view_ab/(?P<a>\d+)/(?P<b>\d+)/$', user_view_ab, name="user_view_ab")

这样就是匹配user_view_ab/1/2等数字参数

路由匹配

# 使用url给视图函数传参数
path('index/', index)
path('detail/<int:id>/', detail)


# 给url取别名,那么在使用此url的地方(例如页面模板反向解析)可以使用别名。比如:
path('index/',index,name='index')
path('detail/<int:id>/', detail, name='detail')

根据路由的name属性,获取路由url
reverse('index') => 路由'index/'

反向解析

Django路由反向解析是一个非常重要的功能,它可以让我们在代码中使用路由别名替代URL路径,在修改URL时避免代码中的硬编码依赖,同时也可以提高可读性和可维护性。

# 在视图函数中,反向解析url:
from django.shortcuts import render,redirect,reverse
def buy(request):
	return redirect(reverse('index'))
	return redirect(reverse('detail',args=[2]))
	return redirect(reverse('detail',kwargs={"id":2}))

#在templates中,使用别名:
	{% url 'detail' stu.id %}
    
# 使用命名空间:
# 指定命令空间后,使用反向解析时需要加上命名空间,比如:
#	1.在视图函数中:
	return redirect(reverse('App:index'))
#	2.在templates中:
	{% url 'App:index'%}

页面之间相互跳转(通过html)

  1. 在根目录下的urls.py添加如下代码
from django.contrib import admin
from django.urls import include, path
from App.views import *
urlpatterns = [
    # 使用子路由: 
    path("user/", include("App.urls")),

    # 使用子路由: 使用命名空间
    path("user/", include(("App.urls","App"), namespace="App")),

    path('admin/', admin.site.urls),
]
  1. App/urls.py子路由文件
from tkinter.font import names
from django.urls import path
from App.views import *
urlpatterns = [
    # 首页
    path('index/', index, name='index'),
    # 用户列表
    path('user_list/', user_list, name='user_list'),
]
  1. 定义视图函数user_list.html文件
# 用户列表
def user_list(request):
    return render(request, 'user_list.html')
  1. 在想要添加跳转链接的页面(这里只使用后端的Django跳转方式,不使用前端超链接)添加代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Index</title>
  </head>
  <body>
    <h2>首页</h2>
    <hr />
    {% comment %} url路由来跳转 {% endcomment %}
    <a href="/user/user_list/">url路由跳转: 进入用户列表</a>
    <hr />

    {% comment %} 反向解析来跳转 {% endcomment %}
    <a href="{% url 'user_list' %}">反向解析跳转: 进入用户列表</a>
    user_list是路由路径的name值
      url 是Django的模板,用于生成url
    <hr />

    {% comment %} 带命名空间的反向解析 {% endcomment %}
    <a href="{% url 'App:user_list' %}">带命名空间的反向解析: 进入用户列表</a>
  </body>
</html>
  • url路由跳转方式: 链接路径为 127.0.0.1:8000后面的内容
  • 反向解析来跳转: 反向解析就是从 html页面(点击) -> 找到路由 -> 找到视图函数(user_list是路由路径的name值)
  • 带命名空间的反向解析: 命名空间就是根/urls.py文件include方法中指定的namespace,命名空间就是为了使不同的应用之间可以使用相同的子路由路径名(例如上面的user_list)

路由传参(单个参数)

App的子路由中新增以下路由规则

# 详情页 <> 号内为参数传给user_detail视图函数
path('user_detail/<int:uid>/', user_detail, name='user_detail'),

定义视图函数user_detail

# 用户列表
def user_list(request):
    # 获取所有用户数据
    users = UserModel.objects.all()
    return render(request, 'user_list.html',{"users":users})
def user_detail(request,uid): # uid从路由参数中获取
    # print("uid",uid)
    # 获取单个用户数据
    user = UserModel.objects.get(pk=uid)    # pk是主键primary key
    return render(request, 'user_detail.html',{"user":user})  # 传递参数到模板中

在user_list.html文件中接收user_list传过来的users参数,遍历(利用模板语法添加li>a标签)

image-20241124122311334

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>用户列表</title>
        <style>
            a {
                text-decoration: none;
            }
        </style>
    </head>
    <body>
        <h2>用户列表</h2>
        <hr />
        <ul>
            {% for user in users %} {% comment %} 反向解析 {% endcomment %}
            <li>
                <a href="{%url 'App:user_detail' user.id%}">{{user.name}} - {{user.age}}</a>
            </li>
            {% endfor %}
        </ul>
    </body>
</html>

所以,最终生成的a标签变成了<a href="/user/user_detail/1/">张三 - 19</a>

整个过程就是按顺序发生

  • 首先用户访问user/user_list.html界面,程序调用视图函数获取所有用户列表并传参
  • user_list函数中,程序渲染本地的user_list.html返回,在渲染的过程中,将uset_list()中获取的用户列表逐个渲染成具体的链接
  • 这是对模板语法的解释
  • url 代表当前界面的url, App:user_detail表示到根路由path("user/", include(("App.urls","App"), namespace="App"))中在逐层(include(("App.urls","App"),从应用App的urls.py子路由文件)找到名为user_detail的路由项
  • 找到如下所示path('user_detail/<int:uid>/', user_detail, name='user_detail')
  • 然后user.id就是把id属性传给路由参数,相对于变成``path(‘user_detail/user.id/‘, user_detail, name=’user_detail’)`

路由参数支持的类型

Django支持多种类型的路由参数,常见的有:

  1. int:接收整数,如 <int:user_id>
  2. str:接收字符串,如 <str:username>
  3. slug:接收短横线连接的字符串,如 <slug:slug>
  4. uuid:接收UUID,如 <uuid:uuid>
  5. path:接收包含斜杠的字符串,如 <path:subpath>

路由传参(多参数)

视图函数

def user_view_ab(request,a,b):
    return HttpResponse(f"a={a},b={b}")

子路由

# 带参数的视图函数,注意,参数的名字必须和视图函数中参数的名字一致
path("user_view_ab/<int:a>/<int:b>/", user_view_ab, name="user_view_ab")

注意!!! 参数名称要对应

重定向redirect函数

在视图函数中,可以重定向到别的网页或者路径

path('user_detail/<int:a>/',user_detail,name='userdetail'),def user_detail(a):
    return HttpResponse("user_detail")
# 重定向
def my_redirect(request):
    return redirect('http://www.baidu.com')

def my_redirect2(request):
    return redirect('/user/user_list/')

def my_redirect3(request):
    # reverse反向解析(根据路由name属性找到对应url)
    return redirect(reverse('user_list')) # 相当于  return redirect(/user/user_list/)
	return redirect(reverse('App:userdetail', args=(1,))) # 传参给path('user_detail/<int:a>/',user_detail,name='userdetail'),args=元组或列表,kawrgs=字典
	return redirect(reverse('App:userdetail', kwargs={'a':2}))

在子路由中增加重定向部分

# 用户列表(原有)
path('user_list/', user_list, name='user_list'),
path('user_detail/<int:a>/',user_detail,name='userdetail'),
# 重定向
path('myredirect/', my_redirect),
path('myredirect2/', my_redirect2),
path('myredirect3/', my_redirect3),

render和redirect的区别

在Django中,redirectrender 是两个用于处理HTTP请求并返回响应的函数,但它们的作用和使用场景有所不同。

  1. redirect 函数:

    • redirect 函数用于重定向到另一个URL。它接受一个URL作为参数,可以是一个绝对URL,也可以是一个相对URL。
    • 当使用 redirect 时,Django会向客户端发送一个HTTP重定向响应,告诉客户端去访问指定的URL。
    • 重定向通常用于用户完成某个操作后,将其引导到另一个页面,例如登录成功后重定向到用户的个人资料页面。

    示例代码:

    from django.shortcuts import redirect
    
    def my_view(request):
        # 重定向到另一个视图
        return redirect('/some/other/view/')
  2. render 函数:

    • render 函数用于渲染一个模板并返回一个HTTP响应。它接受三个参数:请求对象、模板名称和一个包含模板上下文数据的字典。
    • 当使用 render 时,Django会将模板上下文数据填充到模板中,然后将渲染后的HTML内容作为响应返回给客户端。
    • render 通常用于显示页面内容,例如显示一个表单、一个列表或者一个详细信息页面。

    示例代码:

    from django.shortcuts import render
    
    def my_view(request):
        # 渲染模板并返回响应
        context = {'name': 'John Doe'}
        return render(request, 'my_template.html', context)

总结:

  • redirect 用于重定向到另一个URL,发送一个HTTP重定向响应。
  • render 用于渲染模板并返回一个HTTP响应,包含渲染后的HTML内容。

在你的代码中,如果你想在用户上传图片后重定向到 showicon 页面,你应该使用 redirect 函数,因为你需要告诉客户端去访问另一个URL。如果你使用 render 函数,它将渲染 showicon.html 模板并返回响应,但这不会改变URL,用户仍然停留在当前页面。

所以,正确的代码应该是:

return redirect(reverse('showicon'))

这样,当用户上传图片后,他们将被重定向到 showicon 页面。

三、Django模板

模板Templates

在Django框架中,模板是可以帮助开发者快速生成呈现给用户页面的工具

模板的设计方式实现了我们MVT中VT的解耦(M:Model,V:View,T:Template),VT有着N:M的关系,一个V可以调用任意T,一个T可以供任意V使用

模板处理分为两个过程

  • 加载HTML
  • 渲染数据 render()

模板主要有两个部分

  • HTML静态代码

  • 模板语言,动态插入的代码段(挖坑,填坑)

  • 模板中的动态代码段除了做基本的静态填充,还可以实现一些基本的运算,转换和逻辑

静态页面: 页面数据是本地固定的
动态页面: 页面数据来源于后台服务器

模板中的变量:视图传递给模板的数据,遵守标识符规则

​ 语法:
​ 如果变量不存在,则插入空字符串

方法不能有参数

​ # 不能带括号

列表,使用索引,不允许使用负索引

​ items = [‘apple’ ‘bananas’]

{{ items.1 }}

  • MVC软件设计模式: Model(数据库),View(界面),Controller(控制器,相当于Django的视图函数)
  • MVT : M:Model(数据库),V:View视图函数,T:Template(界面)

image-20241124210843757

模板中的标签

语法: {{% tag %}}

作用:

  1. 加载外部传入的变量
  2. 在输出中创建文本
  3. 控制循环或逻辑
if 语句:

  格式: 
    if单分支
	{% if表达式 %} 语句   {% endif %}

   if双分支
	{% if表达式 %} 语句 {% else %} 语句2  {% endif %}

   if多分支
	{% if表达式 %} 语句 {% elif表达式 %} 语句2 {% else %} 语句3 {% endif %}

判断true或者false(可以使用and or not in,语法与python类似)

	{% if tody_is_weekend %}
		\<p>Welcome to the weekend!!!\</p>
	{% endif %}

使用 in 和 not in
	{% if user in users %}
		\<p>User已经存在</p>
	{% endif %}
for 语句
	{% for 变量 in 列表	%}
		语句1
	{% empty %}
		语句2
	{% endfor %}
	当列表为空或者不存在的时候,执行empty后的语句

===================forloop.counter示例=================
	{% forloop.counter %} 表示当前是第几次循环,从1开始
{% for item in todo_list %}
	<p> {{forloop.counter}} : {{ item }}	</p>
{% endfor %}

	{{ forloop.counter0 }}表示当前是第几次循环,从0开始
	{{ forloop.revcounter }}表示当前是第几次循环,倒着数数,到1停
	{{ for1oop.revcounter }}表示当前第几次循环,倒着数,到0停
	{{ forloop.first }}是否是第一个 布尔值

{% for object in objects %}
    {% if forloop.first %}
        <li class="first">{{ object }}</li>
    {% else %}
        <li>{{ object }}</li>
    {% endif %}
{% endfor %}

{% for link in links %}
    {{ link }}{% if not forloop.last %}|{% endif %}
{% endfor %}

===================forloop.parentloop示例=================
{% for country in countries %}
<table>
    {% for city in country.city_list %}
    <tr>
        <td>Country #{{ forloop.parentloop.counter }}</td>
        <td>City #{{ forloop.counter }}</td>
        <td>{{ city }}</td>
    </tr>
    {% endfor %}
</table>
{% endfor %}

image-20241124221016900

注释:
	单行注释
	{# 被注释掉的内容 #}
	多行注释
	{% comment %} 多行内容 {% endcomment %}
过滤器:
	{{ var|过滤器 }}
	作用: 在变量显示前修改

add:
	{{ value|add:2 }}
	没有减法过滤器,但加法里可以加负数:
	{{ value|add:-2 }}
lower:
	{{ name|lower }}
upper:
	{{ my_list|fisrt|upper }} 将my_list的第一个值变成大写
截断:
	{{ bio|truncatechars:30 }}
	
过滤器可以传递参数,参数需要使用引号引起来,
	比如join:{{ students|join:'=' }}

默认值: default,格式{{ var|default:value }}
	如果变量没有被提供或者为False,空,会使用默认值

根据指定格式转换日期为字符串,处理时间的
	就是针对date进行的转换{{ dateVal | date:'y-m-d'}}
HTML转义:
	将接收到的数据当成普通字符串处理还是当成HTML代码来渲染的一个问题

渲染成html:
	{{ code|safe }}
关闭自动转义 
	{% autoescape off %}
		code
	{% endautoescape %}
打开自动转义转义
	{% autoescape on %}
	code
	{% endautoescape %}

image-20241125113906892

模板继承:
	block:
		{% block XXX %}
			code
		{% endblock %}

	extends 继承,写在开头位置
		{% extends'父模板路径'%}
	
	include:加载模板进行渲染
		{% include'模板文件'%}
	
	{{ block.super }}:获取父模板中block中的内容

image-20241125115722854

image-20241125115904169

在Django模板中使用jinja2模板引擎

Jinja2是之前我们在Flask框架讲过的一个模板引擎,是模仿Django默认模板引擎基础上开发的,比Django模板引擎性能更好,功能更全.

jinja2宣称比django默认模板引擎快10-20倍。Django也支持jinja2

1.安装jinja2模块

pip install jinja2

2.在settings.py所在的目录中创建jinja2_env.py文件,并写入以下内容

from django.templatetags.static import static
from django.urls import reverse
from jinja2 import Environment

def environment(**options):
	env = Environment(**options)
    env.globals.update({
		'static': static,
		'url': reverse,
	})
	return env

3.修改项目的settings.py文件

原本的不能删除

TEMPLATES = [
    # 使用jinja2模板引擎
    {
        'BACKEND': 'django.template.backends.jinja2.Jinja2',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            # 这里要添加environment变量,指向自定义的jinja2环境,DjangoPro2项目目录下的jinja2_env.py文件里面的environment函数
            "environment": "DjangoPro2.jinja2_env.environment",
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
    # 原来自带的Django模板引擎
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        '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',
            ],
        },
    },
]

4.在修改Pycharm中settings的模板语言为Jinja2

image-20241125130810745

创建html文件,使用Jinja2语法

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Jinja2模板语言</title>
</head>
<body>
    <h2>Jinja2模板语言</h2>
    <hr>

    <p>name = {{ name }}</p>

    {% for n in name %}
    <b>{{n}}</b>
    {% endfor %}
    <hr>

    {% for n in name %}
    <div>{{loop.index}} : {{n}}</div>
    {% endfor %}

    {# Jinja2是可以使用带括号的函数调用的 #}
    {% for i in range(1, 10) %}
    <div>{{i}}</div>
    {% endfor %}
    
</body>
</html>

注意: 这只是一个简单的Jinja2模板语法示例,要使用更多语法,可以查阅以下资料

四、Django模型基础

1. models字段类型

概述

django根据属性的类型确定以下信息

  • 当前选择的数据库支持字段的类型
  • 渲染管理表单时使用的默认html控件
  • 在管理站点最低限度的验证

django会为表增加自动增长的主键列,每个模型只能有一个主键列,如果使用选项设置某属性为主键列后则django不会再生成默认的主键列

属性命名限制

  • 遵循标识符规则

  • 由于django的查询方式,不允许使用连续的下划线定义属性时,

需要字段类型,字段类型被定义在diango.db.models.fields目录下,为了方便使用被导入到django.db.models中

使用方式

  • 导入from django.db import models
  • 通过models.Field创建字段类型的对象,赋值给属性

逻辑删除和物理删除

对于重要数据都做逻辑删除,不做物理删除,实现方法是定义is_delete属性,类型为BooleanField,默认值为False
is_delete = models.BooleanField(default=False)

常用字段类型

  • AutoField: 一个根据实际ID自动增长的IntegerField,通常不指定,如果不指定,主键字段id将自动添加到模型中

  • CharField(max_length=字符长度)

  • 字符串,默认的表单样式是 Input

  • TextField
    大文本字段,一般超过4000使用,默认的表单控件是Textarea

  • IntegerField

  • 整数

  • DecimalField(max_digits=None,decimal_places=None)

  • 使用python的Decimal实例表示的十进制浮点数

  • 参数说明

    • DecimalField.max_digits -> 位数总数
    • DecimalField.decimal_places -> 小数点后的数字位数
  • FloatField: 用Python的float实例来表示的浮点数

  • BooleanField

  • True/False 字段,此字段的默认表单控制是CheckboxInput

  • DateField([auto_now=False,auto_now_add=False])

  • 使用Python的datetime.date实例表示的日期参数说明

  • 参数说明

    • DateField.auto_now
      每次保存对象时,自动设置该字段为当前时间,用于**”最后一次修改”**的时间戳,它总是使用当前日期,默认为false
    • DateField.auto_now_add
      当对象第一次被创建时自动设置当前时间,用于创建的时间戳,它总是使用创建时的日期,默认为false
    • 注意:auto_now_add,auto_now,and_default 这些设置是相互排斥的他们之间的任何组合将会发生错误的结果
  • TimeField: 使用Python的datetime.time实例表示的时间,参数同DateField

  • DateTimeField: 使用Python的datetime.datetime实例表示的日期和时间,参数同DateField

  • FileField

  • 一个上传文件的字段

  • ImageField:

  • 继承了FileField的所有属性和方法,但对上传的对象进行校验,确保它是个有效的image

  • 需要安装Pillow: “pip install Pillow”

2. 常用字段参数

# 常用字段选项(通过字段选项,可以实现对字段的约束)
	1. null=True
		数据库中字段是否可以为空
	2. blank=True
		django的 Admin 中添加数据时是否可允许空值

	一般null=True & blank=True 搭配着用,出现null=True就用上blank=True
    
	3. primary_key = True
    	主键,对AutoField设置主键后,就会代替原来的自增id4. auto_now和auto_now_add
        auto_now 自动创建 --- 无论添加或修改,都是当前操作的时间
        auto_now_add 自动创建 --- 永远是创建时的时间
        
    5. choices (后台admin下拉菜单)
        USER_TYPE_LIST =(
        (1'超级用户'),
        (2'普通用户'), 
        )
		user_type = models.IntegerField(choices=USER_TYPE_LIST,default=1,,verbose_name='用户类型')
        
    6. max_length 最大长度
	7. default 	  默认值
	8. verbose_name		Admin(后台显示的名称)中字段的显示名称
	9. name|db_column	数据库中的字段名称
	10. unique=True		不允许重复
	11. db_index = True	数据库索引,例如:如果你想通过name查询的更快的话,给他设置为索引即可
    12.	editable=True 	在Admin里是否可编辑,不可编辑则不显示
	13. 设置表名
	class Meta:
		db_table ='person'

在创建模型迁移之时,经常会遇到以下问题,这是以为原先数据库中已经有数据,这时创建新的数据字段,原先的记录不知道填充什么内容进新增字段(假设不是所有新增字段都有默认值)

image-20241125145725199

模型在admin后台默认的html控件 –> 渲染管理表单时使用的默认html控件

image-20241125150605940

3.迁移怎么回滚

完成迁移之后,会在应用文件夹App/migrations文件夹下创建迁移文件,并且在数据库表django_migrations中创建对应记录

想要回滚,可以删除对应的迁移python文件以及对应数据库记录

image-20241126165647058

image-20241126170751229

image-20241126171433600

4. models基本操作

一般的数据库操作流程:

  1. 创建数据库,设计表结构和字段
  2. 连接Mysql数据库,并编写数据访问层代码
  3. 业务逻辑层去调用数据访问层执行数据库操作

Django通过Model操作数据库,不管你数据库的类型是MySql或者Sqlite,Django自动帮你生成相应数据库类型的SQL语句,所以不需要关注SQL语句和类型,对数据的操作Django帮我们自动完成。

只要会写Model就可以了。django使用对象关系映射(Object Relational Mapping,简称ORM)框架去操控数据库。ORM(Object Relational Mapping)对象关系映射,是一种程序技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。

在Django模型中可以使用内部类Meta为模型提供选项,具体请见文档

增删改查

ORM

  • 模型 <=> 表
  • 类结构 -> 表结构
  • 对象 -> 表的一条数据
  • 类属性 -> 表的字段

模型基本操作

  1. 增: (假设模型是Author)

    1. 创建对象实例,然后调用save方法
    obj = Author()
    obj.first_name = "zhang"
    obj.last_name = 'san'
    obj.save()
    1. 创建对象并初始化,在调用save方法
    obj = Author(first_name = "zhang",last_name = 'san')
    obj.save()
    1. 使用create方法
    Author.objects.create(first_name = "zhang",last_name = 'san')
    1. 使用get_or_create方法,可以防止重复
    Author.objects.get_or_create(first_name = "zhang",last_name = 'san')

image-20241126174245659

注意: 添加失败是因为PersonModel模型中name属性设置的是unique

# 增加数据
def add_person(request):
    # # 方式1
    # try:
    #     p = PersonModel(name='张三', age=20)
    #     p.save()
    # except Exception as e:
    #     print(e)
    #     return HttpResponse('添加失败')

    # return HttpResponse('添加成功')

    # 方式2
    # PersonModel.objects.create(name='李四', age=25)
    # return HttpResponse('添加成功')

    # 方式3
    ret = PersonModel.objects.get_or_create(name='李四', age=25)
    print("ret",ret)
    # ret:(<PersonModel:PersonModelobject(2)>,True)
    # 如果是第一次创建: 则是True,如果已经存在则是False
    return HttpResponse('添加成功')
  1. 删:

    1. 获取单个对象并调用Queryset的delete()方法
    p = PersonModel.objects.first() # 获取第一个数据
    p.delete()
    p = PersonModel.objects.get(name="李四")   # 根据对应的id获取数据
    p.delete()

    注意: objects不能直接调用delete()方法

    1. 使用模型过滤filter(),再对过滤结果进行删除
    # 使用过滤器
    PersonModel.objects.filter(age__gt = 15).delete()   # 删除年龄大于15的数据        
  2. :

    1. 直接使用对象.属性修改
    # 获取数据
    p = PersonModel.objects.first()
    p.age = 25
    p.save()
    
    # 修改多条数据
    p_list = PersonModel.objects.all()
    
    for p in p_list:
    	p.age = 25
       	p.save()
    1. 修改多条数据
    PersonModel.objects.all().update(age=25)  # 修改所有数据

    save()更新时,会对所有字段进行更新操作,如果想要只更新某个字段,减少数据库操作,可以这么做:

    obj.first_name = "zhang"
    obj.save(update_fields = ['first_name'])
  3. 查:

对查询集可使用的函数

  • get():获取单条数据 Author.objects.get(id=123)

    • 如果没有找到符合条件的对象,会引发模型类.DoesNotExist异常
    • 如果找到多个,会引发模型类.MultipleObjectsReturned异常
  • first():返回査询集(Queryset)中的第一个对象

  • last():返回查询集中的最后一个对象

  • count():返回当前查询集中的对象个数

  • exists():判断查询集中是否有数据,如果有数据返回True没有反之

  • all()获取全部数据: Author.objects.all()

  • values():获取指定列的值,可以传多个参数! 返回包含字典的列表(保存了字段名和对应的值) —>

    Author.objects.all().values(‘password’)

  • values_list():获取指定列的值,可以传多个参数! 返回包含元组列表(只保存值) —>

    Author.objects.all().values_list(‘password’)

  • filter()

查询集可以被链式调用,Author.objects.filter(name="seven").filter(age=18)

进阶操作

# 获取个数
Author.objects.filter(name="seven").count()

# 获取id大于1的值
Author.objects.filter(id__gt=1)	# SELET * FROM Author WHERE id > 1
# 获取id大于或等于1的值
Author.objects.filter(id__gte=1)	# SELET * FROM Author WHERE id >= 1
# 获取id小于10的值
Author.objects.filter(id__lt=10)	# SELET * FROM Author WHERE id < 10
# 获取id小于或等于10的值
Author.objects.filter(id__lte=10)	# SELET * FROM Author WHERE id >= 1

# 获取id大于1且小于10的值
Author.objects.filter(id__lt=10,id__gt=1)	# SELET * FROM Author WHERE id > 1 AND id < 10
# 获取id在11,22,33的数据
Author.objects.filter(id__in=[11,22,33])	# SELET * FROM Author WHERE id IN (11,22,33)

# 获取id`不在`11,22,33的数据
Author.objects.exclude(id__in=[11,22,33])	# SELET * FROM Author WHERE id NOT IN (11,22,33)

# 获取name包含"ven"的数据 (和数据库中like语法相似)
Author.objects.filter(name__contains="ven")	# SELET * FROM Author WHERE name LIKE '%ven%'

# icontains()大小写不敏感
Author.objects.filter(name__icontains="ven")

Author.objects.filter(name_regex="^ven")	# 正则匹配
Author.objects.filter(name_iregex="^ven")	# 正则匹配,忽略大小写
Author.objects.filter(age_range=[10,20])   	# 范围bettwen and

# startswith,istartswith,endswith,iendswith:
# 以什么开始,以什么结束,和上面一样带i的是大小写不敏感的, 其实不带i的也忽略大小写

Author.objects.filter(name='seven').order_by('id','age')	# asc升序, 多个属性->id相同时,按照age来排序
Author.objects.filter(name='seven').order_by('-id')	# desc降序

Author.objects.all()[18:20] # 切片,取所有数据的18条到20条,分页的时候用的到
# 下标从0开始,不能为负数,可以实现分页

# 手动分页
page 页码 
per_page 每页数量 = 51(page=1): 0-4 =>[0:5]2(page=2): 5-9 =>[5:10]3(page=3): 10-14 =>[10:15]4(page=4): 15-19 =>[15:20]

每一页数据范围:[(page-1) * per_page : page * per_page]

# 聚合
使用aggregate()函数返回聚合函数的值
- Avg:平均值
- Count:数量
- Max:最大
- Min:最小
- Sum:求和
from django.db.models import Count,Min,Max,Sum
Author.objects.aggregate(Max('age'))

5. views.py

from django.http import HttpResponse
from django.shortcuts import render
from django.db.models import Avg, Max, Min, Sum
# Create your views here.

from App.models import *


# 增加数据
def add_person(request):
    # # 方式1
    # try:
    #     p = PersonModel(name='张三', age=20)
    #     p.save()
    # except Exception as e:
    #     print(e)
    #     return HttpResponse('添加失败')

    for i in range(10):
        p = PersonModel(name='张三'+str(i), age=20+i)
        p.save()

    # return HttpResponse('添加成功')

    # 方式2
    # PersonModel.objects.create(name='李四', age=25)
    # return HttpResponse('添加成功')

    # 方式3
    # ret = PersonModel.objects.get_or_create(name='李四', age=26)
    # print("ret",ret)
    # ret:(<PersonModel:PersonModelobject(5)>,True)
    # 如果是第一次创建: 则是True,如果已经存在则是False
    return HttpResponse('添加成功')


# 删除数据
def del_person(request):
    try:
        # p = PersonModel.objects.first() # 获取第一个数据
        # p = PersonModel.objects.get(name="李四")   # 根据对应的id获取数据
        # p.delete()
        # 使用过滤器
        PersonModel.objects.filter(age__gt = 15).delete()   # 删除年龄大于15的数据
        
    except Exception as e:
        print(e)
        return HttpResponse('删除失败')
    
    return HttpResponse('删除成功')

# 修改数据
def update_person(request):
    try:
        # 获取数据
        # p = PersonModel.objects.first()
        # p.age = 25
        # p.save()

        # 修改多条数据
        p_list = PersonModel.objects.all()
        # PersonModel.objects.all().update(age=25)  # 修改所有数据
        for p in p_list:
            p.age = 25
            p.save()
    except Exception as e:
        print(e)
        return HttpResponse('修改失败')
    
    return HttpResponse('修改成功')

        
def get_person(response):
    # get() 方法获取单个数据
    # p = PersonModel.objects.get(id=1)

    # p = PersonModel.objects.get(pk=18)    # primary key等于18
    # p = PersonModel.objects.get(age=100)  # 找不到对应的数据时会报错,找到多条数据时也会报错   
    # print(p,type(p))    # <PersonModel: PersonModelobject(1)> <class 'App.models.PersonModel'>
    # print(p.name,p.age)

    # all() 方法返回所有数据
    p_list = PersonModel.objects.all()
    print(p_list,type(p_list))  #  <PersonModel: PersonModel object (25)>]> <class 'django.db.models.query.QuerySet'> 查询集,可以遍历

    # first() 方法获取第一个数据
    # p = PersonModel.objects.first()

    # last() 方法获取最后一个数据   
    # p = PersonModel.objects.last()

    # exists() 方法判断数据是否存在
    # if PersonModel.objects.filter(name='张三').exists():
    #     print('存在')
    # else:
    #     print('不存在')

    # values() 方法返回指定字段的数据
    # p_list = PersonModel.objects.values('name','age')   #返回包含字典的列表
    p_list = PersonModel.objects.values_list('name','age')   # 返回包含元组列表
    print(p_list,type(p_list))

    # filter() 方法过滤数据,返回一个```查询集````,参数为空时返回所有数据
    # p_list = PersonModel.objects.filter(age__gt=15)   # 年龄大于15的数据
    # p_list = PersonModel.objects.filter(name__contains='三')   # 名字包含'三'的数据
    # p_list = PersonModel.objects.filter(name__startswith='张')   # 名字以'张'开头的数据

    # exclude() 方法排除数据,返回一个```查询集````,参数为空时返回所有数据
    # p_list = PersonModel.objects.exclude(age__gt=15)   # 年龄不大于15的数据

    # order_by() 方法排序数据,返回一个```查询集````,参数为空时返回所有数据
    # p_list = PersonModel.objects.order_by('age')   # 按年龄排序
    # p_list = PersonModel.objects.order_by('-age')   # 按年龄倒序排序

    # contains() 方法查找字符串是否在指定字段中,返回一个```查询集````,参数为空时返回所有数据
    p_list = PersonModel.objects.filter(name__contains='三')   # 名字包含'三'的数据
    print(p_list,type(p_list))

    # regex() 方法查找正则表达式是否匹配指定字段,返回一个```查询集````,参数为空时返回所有数据
    # p_list = PersonModel.objects.filter(name__regex=r'^张')   # 名字以'张'开头的数据

    # 聚合函数
    # count() 方法计算数据条数
    # count = PersonModel.objects.count()
    # print(count)

    # max() 方法获取最大值
    # max_age = PersonModel.objects.all().aggregate(Max('age'))
    # print(max_age)

    # min() 方法获取最小值
    # min_age = PersonModel.objects.all().aggregate(Min('age'))
    # print(min_age)

    # sum() 方法求和
    # total_age = PersonModel.objects.all().aggregate(Sum('age'))
    # print(total_age)

    # avg() 方法求平均值
    result = PersonModel.objects.all().aggregate(Avg('age'))
    print(result)

    return HttpResponse('获取成功')


# 分页数据
def paginate(request,page=1):
    # 页码: page
    # 每页显示条数: per_page
    per_page = 10
    # 数据范围的计算公式 -> (页码-1)*每页显示条数 ~ 页码*每页显示条数

    all = PersonModel.objects.all()
    start = (page-1) * per_page
    end = page * per_page
    p_list = all[start:end]
    return render(request,"page_list.html",{'p_list':p_list})


# 自动分页器
def auto_paginate(request):
    # 自动分页器
    from django.core.paginator import Paginator
    # 实例化分页器
    paginator = Paginator(PersonModel.objects.all(), 10)
    page = 1
    persons = paginator.page(page) # 获取第page页的数据
    paginator.page_range   # 页码范围,可以遍历

五、Django模型进阶

1. 配置MySql

  • 安装mysql

  • MySql驱动

    pip install mysqlclient
    如果上面的命令安装失败,则尝试使用国内豆瓣源安装:
    pip install -i https://pypi.douban.com/simple mysqlclient
    
    (Linux Ubuntu下需要
    先安装:apt install libmysqld-dev
    再安装:apt install libmysqld-dev)
  • 在Django中配置和使用mysql数据库

    使用mysql数据库,settings.py文件配置如下

    'default':{
            'ENGINE' : 'django.db.backends.mysql',
            'NAME' : 'DjangoModeldb_1',    # 数据库名字 
            'USER' : 'root',    # 数据库用户名 
            'PASSWORD' : '123456',  # 数据库密码 
            'HOST' : '127.0.0.1',   # 数据库主机地址 
            'PORT' : '3306',    # 数据库端口号 mysql默认是3306
        }

2. 多模块关联关系(多表操作)

多个模块关联

关联分类

  • ForeignKey: 一对多,将字段定义在多的端中
  • ManyToManyField: 多对多,将字段定义在两端的任意一端中
  • OneToOneField: 一对一,将字段定义在任意一端中

多表关系

多个模块关联
  关联分类
    · ForeignKey:一对多,将字段定义在多的端中
    · ManyToManyField:多对多,将字段定义在两端的任意一端中
    · OneToOneField:一对一,将字段定义在任意一端中
        
一对多关系,举例说明(一对一, 多对多类似): 
一个班级可以有多个学生, 一个学生只能属于一个班级
      class Grade(models.Model):
      	name = models.CharField(max_length=20)
      class Student(models.Model):
        name = models.CharField(max_length=20)
        grade = models.ForeignKey(Grade, on_delete=)
一对多关联  
    对象的使用:
        正向(在Student这边,有grade属性的这一边):
              获取学生所在班级(对象): stu.grade
              获取学生所在班级的属性: stu.grade.name
        反向(在Grade这边):
              获取班级的所有学生(获取Manager对象):grade.student_set
         	  获取班级的所有学生(获取QuerySet查询集): grade.student_set.all()
                
	filter(),get()等操作中的使用:
        正向(在Student这边,有grade属性的这一边):
                 Student.objects.filter(属性__name='1')
              如:Student.objects.filter(grade__name='1')
        反向(在Grade这边):
                 Grade.objects.filter(类名小写__id=7)
              如:Grade.objects.filter(student__id=7)

3. Model连表关系

  • 一对多:models.ForeignKey(其他表)
  • 多对多:models.ManyToManyField(其他表)
  • 一对一:models.OneToOneField(其他表)

应用场景:

  • 一对多:当一张表中创建一行数据时,有一个单选的下拉框(可以被重复选择)
    • 例如:创建用户信息时候,需要选择一个用户类型【普通用户】【金牌用户】【铂金用户】
  • 多对多:在某表中创建一行数据时,有一个可以多选的下拉框。(猫眼App, 淘票票,格拉瓦电影)
    • 例如:创建用户信息,需要为用户指定多个爱好。
  • 一对一:在某表中创建一行数据时,有一个单选的下拉框(下拉框中的内容被用过一次就消失了)
    • 例如:有个身份证表,有个person表。每个人只能有一张身份证,一张身份证也只能对应一个人,这就是一对一关系

4. 一对多关系,即外键

为什么要用一对多

先来看一个例子。有一个用户信息表,其中有个用户类型字段,存储用户的用户类型。

class UserInfo(models.Model):
    username = models.CharField(max_length=32)
    age = models.IntegerField()
    user_type = models.CharField(max_length=10)

不使用外键时用户类型存储在每一行数据中。如使用外键则只需要存储关联表的id即可,能够节省大量的存储空间。同时使用外键有利于维持数据完整性一致性。当然也有缺点,数据库设计变的更复杂了。每次做 DELETE 或者 UPDATE 都必须考虑外键约束。

刚才的例子使用外键的情况

单独定义一个用户类型表:

class UserType(models.Model):
    caption = models.CharField(max_length=32)

class UserInfo(models.Model):
    user_type = models.ForeignKey('UserType')
    username = models.CharField(max_length=32)
    age = models.IntegerField()

我们约定

  • 正向操作: ForeignKey在UserInfo表里,如果根据UserInfo去操作就是正向操作。
  • 反向操作: ForeignKey不在UserType里,如果根据UserType去操作就是反向操作。

一对多的关系的增删改查

正向操作

  1. 创建对象实例,然后调用save方法:

    obj = UserInfo(name='li', age=44, user_type_id=2)
    obj.save()
  2. 使用create方法:

    UserInfo.objects.create(name='li', age=44, user_type_id=2)
  3. 使用get_or_create方法,可以防止重复:

    UserInfo.objects.get_or_create(name='li', age=55, user_type_id=2)
  4. 使用字典:

    dic = {'name':'zhangsan','age':18,'user_type_id':3}
    UserInfo.objects.create(**dic)
  5. 通过对象添加:

    usertype = UserType.objects.get(id=1)
    UserInfo.objects.create(name='li', age=55, user_type=usertype)

和普通模式一样删除即可。如:

UserInfo.objects.filter(id=1).delete()

和普通模式一样修改即可。如:

UserInfo.objects.filter(id=2).update(user_type_id=4)

正向查找所有用户类型为钻石用户的用户,使用双下划线:

users = UserInfo.objects.filter(user_type__caption__contains='钻石')

正向获取关联表中的属性可以直接使用点.语法,比如:获取users查询集中第一个用户的caption:

users[0].user_type.caption
反向操作

通过usertype来创建userinfo:

  1. 通过userinfo_set的create方法:

    ut = UserType.objects.get(id=2)
    ut.userinfo_set.create(name='smith',age=33)

删除操作可以在定义外键关系的时候,通过on_delete参数来配置删除时做的操作。**(修改on_delete参数之后要重新同步数据库)**

on_delete参数主要有以下几个可选值:
- models.CASCADE  表示级联删除,即删除UserType时,相关联的UserInfo也会被删除。
- models.PROTECT  保护模式,阻止级联删除。
- models.SET_NULL 置空模式,设为null,null=True参数必须具备
- models.SET_DEFAULT 置默认值 设为默认值,default参数必须具备
- models.SET()  删除的时候重新动态指向一个实体访问对应元素,可传函数
- models.DO_NOTHING   什么也不做。

和普通模式一样,不会影响级联表。

通过usertype对象来查用户类型为1的用户有哪些:

obj=UserType.objects.get(id=1)
obj.userinfo_set.all()

可以通过在定义foreign key时指定related_name来修改默认的userinfo_set,比如指定related_name为info:

user_type = models.ForeignKey('UserType', related_name='info')

指定related_name之后,反向查的时候就变成了:

obj.info.all()

获取用户类型为1且用户名为shuaige的用户:

obj.info.filter(username='shuaige')

外键关系中,django自动给usertype加了一个叫做userinfo的属性。使用双下划线,可以通过userinfo提供的信息来查usertype (了解):

user_type_obj = UserType.objects.get(userinfo__username='zs')
models.py
from django.db import models

def set_user_type():
    return UserType.objects.create(name='超级用户')

# 定义一对多关系的模型 1 : N
# 用户类型 1对多 用户
class UserType(models.Model):
    name = models.CharField(max_length=20)
   
class User(models.Model):
    name = models.CharField(max_length=20)
    age = models.IntegerField(default=18)
    # 外键
    # user_type = models.ForeignKey(UserType, on_delete=models.CASCADE)    # 级联删除

    # 自定义的删除策略,当删除UserType时,将其关联的UserInfo的user_type字段设置为set_user_type()函数返回的UserType对象
    # user_type = models.ForeignKey(UserType, on_delete=models.SET(set_user_type))
    # 保护模式
    user_type = models.ForeignKey(UserType, on_delete=models.PROTECT)
    # 置空模式
    # user_type3 = models.ForeignKey(UserType, on_delete=models.SET_NULL, null=True)  # null=True参数必须具备
    # 置默认值模式
    # user_type4 = models.ForeignKey(UserType, on_delete=models.SET_DEFAULT, default=UserType.objects.create(name='普通用户'))    # 需要先创建默认值对象

    # user_type = models.ForeignKey(UserType, on_delete=models.PROTECT, related_name='users')   # 反向查询时,可通过user_type.users获取所有关联的UserInfo对象



"""
on_delete参数主要有以下几个可选值:
- models.CASCADE  表示级联删除,即删除UserType时,相关联的UserInfo也会被删除。
- models.PROTECT  保护模式,阻止级联删除。
- models.SET_NULL 置空模式,设为null,null=True参数必须具备
- models.SET_DEFAULT 置默认值 设为默认值,default参数必须具备
- models.SET()  删除的时候重新动态指向一个实体访问对应元素,可传函数
- models.DO_NOTHING   什么也不做。
"""
views.py
from os import name
from django.http import HttpResponse
from django.shortcuts import render
from One2Many.models import *

# 一对多关系

# 添加数据
def add_user(request):
    # 给user_type添加数据
    # user_type = ['普通用户', 'VIP', 'SVIP']
    # for name in user_type:
    #     UserType.objects.create(name=name)

    # 给user添加数据
    for i in range(10):
        # 使用外键关联UserType的主键
        # user = User.objects.create(name='user' + str(i), age=i, user_type_id= i % 3 + 1)

        # 使用外键关联UserType的对象
        user = User.objects.create(name='user' + str(i), age=i, user_type = UserType.objects.get(id= i % 3 + 1))

    return HttpResponse('添加成功')


# 删除数据
def del_user(request):
    # 删除User数据
    User.objects.filter(id=5).delete()

    # 删除UserType数据
    UserType.objects.filter(name="SVIP").delete()  # 因为在设置模型时,UserType作为User的外键,on_delete=models.CASCADE,所以删除UserType会自动删除User(级联删除)

    return HttpResponse('删除成功')

# 修改数据
def update_user(request):
    
    # 修改用户类型
    UserType.objects.filter(id=1).update(name='SVIP')

    # 修改用户
    User.objects.filter(id=2).update(name="super_user", age=20, user_type_id=4)

    return HttpResponse('修改成功')

# 查询数据
def get_user(request):
    # 正向查询: 在User模型中根据user_type外键查找到对应的UserType对象
    user = User.objects.get(id=2)
    print(user.name,user.age,user.user_type,user.user_type_id)  # super_user 20 UserType object (4) 4
    print(user.user_type.name)  # 超级用户

    # 反向查询: 在UserType模型中根据user外键查找到对应的User对象
    utype = UserType.objects.get(pk=4)
    print(utype.id,utype.name)  # UserType自己的属性 4 超级用户
    print(utype.user_set.all())  # <QuerySet [<User: User object (2)>]>
    # user_set是反向查询的属性,返回的是一个Manager管理对象,可以用all()方法获取QuerySet集合对象,默认生成的,可以在Model定义related_name='related_name'来自定义名称


    print("*" * 50)
    for user in utype.user_set.all():
        print(user.name, user.age, user.user_type, user.user_type_id)

    print("=" * 50)
    # 在filter()方法
    # users = User.objects.filter(user_type=UserType.objects.get(name='VIP'))
    # users = User.objects.filter(user_type_id=4)
    # users = User.objects.filter(user_type__name='SVIP') # 传入UserType的name属性作为参数
    users = User.objects.filter(user_type__id=4) # 传入UserType的id属性作为参数

    print(users)
    return HttpResponse("查询成功")

5. 多对多关系

针对多对多关系django会自动创建第三张表。也可以通过through参数指定第三张表。

用户和组是典型的多对多关系:

class Group(models.Model):
    name = models.CharField(max_length=20)

    def __str__(self):
        return self.name

class User(models.Model):
    name = models.CharField(max_length=64)
   	password = models.CharField(max_length=64)
    groups = models.ManyToManyField(Group) # 设置多对多关系

    def __str__(self):
      	return self.name

操作

  • 增:

  • 先分别创建user和group, 再使用add关联

def add(request):
    u = User(name='aa', password='123')
    u.save()
    g = Group(name='g5')
    g.save()
  • 通过Manager对象使用add()方法
u.groups.add(g)  或  g.user_set.add(u)
  • 删:

    • delete()方法, 和一对多类似,删除user或group会级联删除user_groups表中的关联数据

      # 删除中间表
      user = User.objects.get(id=1, name='张三1')
      # delete会级联删除(假如电影1 只被张三1收藏,则电影1在电影表中也会被删除)
      user.movies.filter(id=1, name='电影1').delete()  # 删除张三1 收藏 电影1
    • remove()方法

      # 使用remove方法
      movie = Movie.objects.get(id=1, name='电影1')
      user.movies.remove(movie)  # 删除张三1 收藏 电影1
  • 改:
    和一对多类似,只修改当前表

  • 查:

    • 正向:

      # 查询id=2的用户所在的所有组group
      u = User.objects.get(id=2)
      u.groups.all()
    • 反向:

      # 查询id=1的组中包含的所有用户
      g = Group.objects.get(id=1)
      g.user_set.all()

models.py

from django.db import models

# Create your models here.

# 多对多模型 N : M

# 一个用户可以收藏多个电影
# 一个电影可以被多个用户收藏

# 电影
class Movie(models.Model):
    name = models.CharField(max_length=50)
    duration = models.IntegerField(default=90)

# 用户
class User(models.Model):
    name = models.CharField(max_length=50)
    age = models.IntegerField(default=18)
    # 多对多关系
    movies = models.ManyToManyField(Movie)

views.py

from django.http import HttpResponse
from django.shortcuts import render
from Many2Many.models import * 
# Create your views here.


# 多对多
# 增加数据

def add(request):
    # 添加User数据
    # for i in range(1, 10):
    #     User.objects.create(name='张三' + str(i))

    # # 添加Movie数据
    # for i in range(1, 10):
    #     Movie.objects.create(name='电影' + str(i))

    # 添加User和Movie的关系数据
    # 张三1 收藏 电影1
    user = User.objects.get(id=1, name='张三1')
    movie = Movie.objects.get(id=1, name='电影1')

    # 添加收藏
    # user.movies.add(movie)  # 用户收藏电影  
    movie.user_set.add(user)  # 反向引用
    return HttpResponse("增加数据成功")

# 删除数据

def delete(request):
    # 删除User数据
    # User.objects.filter(id=9).delete()
    # 删除Movie数据
    # Movie.objects.filter(id=9).delete()

    # 删除中间表
    user = User.objects.get(id=1, name='张三1')
    # delete会级联删除(假如电影1 只被张三1收藏,则电影1在电影表中也会被删除)
    # user.movies.filter(id=1, name='电影1').delete()  # 删除张三1 收藏 电影1

    # 使用remove方法
    movie = Movie.objects.get(id=1, name='电影1')
    user.movies.remove(movie)  # 删除张三1 收藏 电影1

    return HttpResponse("删除数据成功")

# 查询数据
def get(request):
    # 获取用户收藏的电影
    user = User.objects.get(id=1)
    print(user.movies.all())  # 获取张三1 收藏的所有电影


    # 获取电影被哪些用户收藏
    movie = Movie.objects.get(id=4)
    print(movie.user_set.all())  # 获取电影4 被哪些用户收藏

    return HttpResponse("查询数据成功")

6. 一对一关系

一对一不是数据库的一个连表操作,而是Django独有的一个连表操作。

一对一关系相当于是特殊的一对多关系,只是相当于加了unique=True

image-20241128150631042

一个人只能有一张身份证,一张身份证对应一个人,是一个典型的一对一关系。
class IDCard(models.Model):
        idnum = models.IntegerField()
        
        def __str__(self):
            return str(self.idnum)
        
class Person(models.Model):
        idcard = models.OneToOneField(IdCard)
        name = models.CharField(max_length=20)
        
        def __str__(self):
            return self.name

一对一关系比较简单。两种表互相都有对方。

\>>> lisi = Person.objects.get(id=3)
\>>> lisi.idcard
<IdCard: 123456>
\>>> ids = IdCard.objects.get(id=3)
\>>> ids.person
<Person: lisi>

models.py

from django.db import models

# Create your models here.

# 身份证
class IdCard(models.Model):
    idcard_num = models.CharField(max_length=18, unique=True)
    address = models.CharField(max_length=50)


class User(models.Model):
    name = models.CharField(max_length=50,unique=True)
    age = models.IntegerField(default=18)
    sex = models.BooleanField(default=True)
    # 1对1关系,一个用户对应一个身份证
    idcard = models.OneToOneField(IdCard, on_delete=models.PROTECT)

views.py

from django.http import HttpResponse
from One2One.models import *
# Create your views here.

# 一对一

# 查询
def get(request):
    # 查找某用户的身份证信息
    user = User.objects.get(id=1)
    print(user.idcard)  # 对象(因为是1对1关系,所以返回的是单个对象)
    print(user.idcard.idcard_num, user.idcard.address)  # 属性

    # 查找身份证信息对应的用户
    idcard = IdCard.objects.get(idcard_num='111')
    print(idcard.user)  # 对象
    print(idcard.user.name, idcard.user.age, idcard.user.sex)  # 属性

    return HttpResponse("查询成功")

六、图书馆综合项目

创建一个项目, 用来说明出版社, 书籍和作者的关系。
假定关系:作者:书籍 => 1:n (一本书由一个作者完成, 一个作者可以创作多本书)
出版社:书籍 => n:n (一个出版社可以出版多本书, 一本书可以由多个出版社出版)
要求:

  1. 在书籍的book_index.html中有一个”查看所有书籍”的超链接按钮,点击进入书籍列表book_list.html⻚面.
  2. 在书籍的book_list.html中显示所有书名,点击书名可以进入书籍详情book_detail.html(通过书籍id)
  3. 在书籍book_detail.html中可以点击该书的作者和出版社,进入作者详情的author_detail.html和出版社详情的publisher_detail.html⻚面
# 出版社

class Publisher(models.Model):
    name = models.CharField(max_length=30)
    address = models.CharField(max_length=100)
    city = models.CharField(max_length=30)
    state_province = models.CharField(max_length=30)
    country = models.CharField(max_length=20)
    website = models.URLField()

    def __str__(self):
        return self.name
    
# 书籍

class Book(models.Model):
    title = models.CharField(max_length=100, verbose_name='书名')
    publish_date = models.DateField(verbose_name='出版时间')
    # 外键,book:author = N:1
    # on_delete=models.CASCADE: 级联删除

    # 主表 : Author 从表 : Book, Author是Book的外键 -> 一本书只能有一个作者
    author = models.ForeignKey(Author, on_delete=models.CASCADE,verbose_name='作者')
    # 多对多关系,book:publisher=N:N
    publishers = models.ManyToManyField(Publisher, verbose_name='出版社')
 
    def __str__(self):
        return self.title

# 出版社

class Publisher(models.Model):
    name = models.CharField(max_length=30)
    address = models.CharField(max_length=100)
    city = models.CharField(max_length=30)
    state_province = models.CharField(max_length=30)
    country = models.CharField(max_length=20)
    website = models.URLField()

    def __str__(self):
        return self.name

在网页中插入静态文件

如何管理静态文件(如图片、JavaScript、CSS) | Django 文档 | Django

  1. 在项目根目录(settings.py的上上层)下创建static存放静态文件,可以根据127.0.0.1/static/bg.jpg访问
  2. settings.py文件中新增以下选项(根据实际自己定义)
STATICFILES_DIRS = [
    BASE_DIR / "static",
    "/var/www/static/",
]
  1. 在模板文件中,使用{% load static %}语法加载静态文件

七、Django视图view

HttpRequest 和 HttpResponse

Django中的视图主要用来接受Web请求,并做出响应。

视图的本质就是一个Python中的函数

视图的响应分为两大类

  1. Json数据形式返回(JsonResponse)
  2. 以网页的形式返回
    1. 重定向到另一个网页 (HttpResponseRedirect)
    2. 错误视图(4XX,5XX) (HttpResponseNotFound, HttpResponseForbidden, HttpResponseNotAllowed 等)
  3. 视图响应过程:
    1. 浏览器输入-> urls路由匹配 -> 视图响应 -> 回馈到浏览器
  4. 视图参数:
    1. 一个HttpRequest的实例,一般命名为request
    2. 通过ur1正则表达式传递过来的参数
  5. 位置:

通常在应用下的views.py中定义

  1. 错误视图:
    1. 404视图(页面没找到)
    2. 400视图(客户操作错误)
    3. 500视图(服务器内部错误)

HttpRequest

服务器在接收到Http请求后,会根据报文创建HttpRequest对象

视图中的第一个参数就是HttpRequest对象

Django框架接收到http请求之后会将http请求包装为HttpRequest对象,之后传递给视图。

request常用属性和方法:
  属性:
    path    请求的完整路径
    method  请求的方法,常用GET, POST
    GET     类似字典的参数,包含了get的所有参数
    POST    类似字典的参数,包含了post所有参数
    FILES   类似字典的参数,包含了上传的文件
    COOKIES 字典,包含了所有COOKIE
    session 类似字典,表示会话
    META    一个字典,包含了请求的元信息,例如:
            'REMOTE_ADDR' 客户端的IP地址
  方法:
    is_ajax()    判断是否是ajax请求,通常用在移动端和JS中
    get_full_path()  返回包含参数字符串的请求路径

QueryDict:
  类似字典的对象。与字典的区别:可以存在相同的键。
  QueryDict中数据获取方式:
    dict['uname']dict.get('uname')    获取指定key对应的第一个值
    dict.getlist('uname')                获取指定key对应的所有值

HttpResponse

HttpResponse由程序员自己创建:
  1)不使用模板,直接调用HttpResponse(),返回HttpResponse对象。
  2)调用模板,进行渲染。
    使用render
      render(request, template_name[, context])
      request     请求体对象
      template_name 模板路径
      context   字典参数,用来填坑
属性:
  content      返回的内容
  charset    编码格式
  status_code  响应状态码(2xx, 3xx, 4xx, 5xx)
方法:
  write(xxx)      直接写出文本
  flush()       冲刷缓冲区
  set_cookie(key, value='xxx', max_age=None)  设置cookie
  delete_cookie(key)   删除cookie

HttpResponse子类
  HttpResponseRedirect
    响应重定向:可以实现服务器内部跳转
    return HttpResponseRedirect('/grade/2030')
    使用的时候推荐使用反向解析
  JsonResponse
    返回Json数据的请求,通常用在异步请求上
      JsonResponse(dict)
      返回json数据时, Content-type是application/json

views.py

from django.http import HttpResponse, HttpResponseRedirect, JsonResponse
from django.shortcuts import redirect, render

# Create your views here.


def myrequest(request):
    print(request)  # <WSGIRequest: GET '/request/?name=lisi&age=18'>

    # request对象的属性和方法
    print(request.method)  # GET / POST/ PUT/ DELETE
    print(request.GET)  # QueryDict对象(类字典对象,可以使用字典方法),包含GET请求参数

    print(request.GET['name'])  # 如果name不存在,会报错
    print( request.GET.get('name'))  # 如果name不存在,返回None或者默认值
    print(request.GET.getlist('name'))  # 如果name不存在,返回空列表, 否则返回列表

    print(request.POST)  # QueryDict对象,包含POST请求参数
    
    print(request.path)  # 请求路径/request/
    print(request.get_full_path())  # 请求完整路径 (包含查询字符串)/request/?name=lisi&age=18

    print(request.COOKIES)  # 字典对象,包含cookie信息
    print(request.session)  # 字典对象,包含session信息

    print(request.FILES) # 如果是上传文件,返回一个文件对象,否则返回None,前端上传的文件
    # print(request.META) # 字典对象,包含HTTP请求头信息
    print(request.META['REMOTE_ADDR']) 

    return HttpResponse("Hello Django!")


def myresponse(request):
    # 1. 直接返回字符串
    # return HttpResponse("Hello Django!")

    # 2. 传入模板文件, 前后端不分离
    # return render(request, 'index.html')

    # 3. 重定向, redirect, reverse
    # return redirect('/request/')
    # return HttpResponseRedirect('/request/')

    # 4. 返回json数据, 前后端分离的方式
    # data = {'name': 'lisi', 'age': 18}
    # return JsonResponse(data)

    response = HttpResponse()
    response.content = 'Hello Django!'
    response.status_code = 200
    return response

八、Cookie和Session技术

注册小案例

  1. 新建templates/register.html

**{% csrf_token %}**是用于防止请求被csrf安全验证拒绝的代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>注册</title>
</head>
<body>
    <h2>注册</h2>
    <hr>

    <form action="" method="post"> {# action为空表示默认表单提交到本网址url,所以返回的HttpRequest可以在register视图函数中接收到 #}
        {% csrf_token %}
        <p>用户名:<input type="text" name="uname"></p>
        <p>密码:<input type="password" name="pwd"></p>
        <p>年龄:<input type="text" name="age"></p>
        <p><button>注册</button></p>
    </form>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h2>
        首页
    </h2>
    <hr>
    {% if user %}
        当前登陆的用户: {{ user.username }}
        <a href="{%url 'logout'%}">注销</a>
    {% else %}
        <a href="{%url 'register'%}">注册</a>
        <a href="{%url 'login'%}">登录</a>
    {% endif %}

    
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Login</title>
</head>
<body>
    <h2>登陆</h2>
    <hr>
    <form action="" method="post">
        {% csrf_token %}
        <p>用户名:<input type="text" name="uname"></p>
        <p>密码:<input type="password" name="pwd"></p>
        <p><button>登陆</button></p>
    </form>
</body>
</html>
  1. views.py中接收前端发送的数据
import datetime
from django.http import HttpResponse
from django.shortcuts import redirect, render
from django.urls import reverse
from User.models import *
# Create your views here.

# 首页
def index(request):
    # cookie区别登陆状态
    # userid = request.COOKIES.get('userid', 0) # 获取cookie, 设置默认值0
    # session区别登陆状态
    userid = request.session.get('userid', 0) # 获取session, 设置默认值0

    if userid:
        user = UserModel.objects.filter(id=userid).first() # 根据id获取用户信息
    else:
        user = None
    return render(request, 'index.html', {'user': user})

# 登录页面
def login(request):
    if request.method == 'GET':
        return render(request, 'login.html')
    elif request.method == 'POST':
        # 登陆功能

        # 1. 先接收前端表单提交的数据
        uname = request.POST.get('uname')
        pwd = request.POST.get('pwd')

        # 2. 验证用户名和密码是否正确
        users = UserModel.objects.filter(username=uname, password=pwd)
        if users.exists():
            # 获取登陆的用户对象
            user = users[0]

            response = redirect(reverse('index'))
            # 3. 设置cookie
            # 注意:  a. cookie 存储在浏览器本地
            #       b. cookie 不能跨域
            #       c. cookie 存储的内容是字符串,不能为中文,一般大小不要超过4kb
            #       d. cookie 一般由后端设置,前端只需要读取即可
            
            # 设置过期时间为1天
            # response.set_cookie('username', user.username, max_age=3600*24)
            # response.set_cookie('username', user.username, expires=datetime.datetime(2024,1,1,1,1,1))
            # response.set_cookie('userid', user.id) # 设置cookie

            # 3. 设置session
            request.session['userid'] = user.id
            request.session.set_expiry(3600*24) # 设置session过期时间为1天

            # 跳转到登陆页面
            return response

# 注册页面
def register(request):
    if request.method == 'POST':
        # 处理表单提交的数据
        username = request.POST.get('uname')
        password = request.POST.get('pwd')
        age = request.POST.get('age')
        print(username, password, age, sep="----")

        # 保存用户信息到数据库
        # 先判断用户是否存在
        users = UserModel.objects.filter(username=username)
        if not users.exists():
            UserModel.objects.create(username=username, password=password, age=age)
        else:
            return HttpResponse('用户已存在')
        return render(request, 'login.html')
    elif request.method == 'GET':
        # 显示注册页面
        return render(request,'register.html')
    

# 退出登录
def logout(request):
    response = redirect(reverse('index'))

    # 清除cookie
    response.delete_cookie('userid')
    # 清除session
    session_key = request.session.session_key   # 当前会话的session_key
    request.session.delete(session_key)

    return response

​ 理论上,一个用户的所有请求操作都应该属于同一个会话,而另一个用户的所有请求操作则应该属于另一个会话,二者不能混淆。而Web应用程序是使用HTTP协议传输数据的。HTTP协议是无状态的协议。一旦数据交换完毕,客户端与服务器端的连接就会关闭,再次交换数据需要建立新的连接。这就意味着服务器无法从连接上跟踪会话。要跟踪该会话,必须引入一种机制。

Cookie就是这样的一种机制。它可以弥补HTTP协议无状态的不足。在Session出现之前,基本上所有的网站都采用Cookie来跟踪会话。

​ Cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户状态。服务器还可以根据需要修改Cookie的内容。

​ 由于HTTP是一种无状态的协议,服务器单从网络连接上无从知道客户身份。怎么办呢?就给客户端们颁发一个通行证吧,每人一个,无论谁访问都必须携带自己通行证。这样服务器就能从通行证上确认客户身份了。这就是Cookie的工作原理。

cookie本身由服务器生成,通过Response将cookie写到浏览器上,下一次访问,浏览器会根据不同的规则携带cookie过来。

注意:cookie不能跨浏览器,一般不跨域

设置cookie(使用response设置):
response.set_cookie(key,value[max_age=None,expires=None])
  max_age: 整数 单位为秒,指定cookie过期时间
            设置为None:浏览器关闭失效,默认值
  expires: 指定过期时间,还支持datetime或timedelta,可以指定一个具体日期时间
            expires=datetime.datetime(2030, 1, 1, 2, 3, 4)
            或 datetime.datetime.now() + datetime.timedelta(days=10)

        注意:max_age和expires两个选一个指定
      # response.set_cookie('username', username, max_age=10) 
      # response.set_cookie("username", username1, expires=d)

获取cookie(使用request获取):
request.COOKIES.get('username')

删除cookie(使用response删除):
response.delete_cookie('username')

cookie存储到客户端
优点:
    数据存在在客户端,减轻服务器端的压力,提高网站的性能。
缺点:
    安全性不高:在客户端机很容易被查看或破解用户会话

image-20241130114511200

Session

Session技术使得用户在登陆之后,浏览器端保存sessionid,django_session数据库表保存一条(以sessionid为主键session_key的)记录,session_data存放加密处理后的数据

服务器端会话技术,依赖于cookie。
  django中启用SESSION
    settings中
      INSTALLED_APPS:
        'django.contrib.sessions'
      MIDDLEWARE:
        'django.contrib.sessions.middleware.SessionMiddleware'

基本操作
  设置Sessions值(使用request设置)
    request.session['user_id'] = user.id
    request.session.set_expiry(86400)  # 设置过期时间
  
  获取Sessions值 
    get(key,default=None) 根据键获取会话的值
    username = request.session.get("user_id")  
    # 或 session_name = request.session["session_name"]
  
  删除Sessions值
    # 获取当前请求的session的key
    session_key = request.session.session_key
    del request.session[session_key]
    # request.session.delete(session_key)
    flush() 删除当前的会话数据并删除会话的cookie
  
  clear() 清除所有会话
  数据存储到数据库中会进行编码,使用的是Base64
每个HttpRequest对象都有一个session属性,也是一个类字典对象

CSRF

  • CSRF全拼为 Cross Site Request Forgery,跨站请求伪造。
  • CSRF指攻击者盗用了你的身份,以你的名义发送恶意请求
    • 包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账…
  • 造成的问题:个人隐私泄露以及财产安全。

image-20241130141851252

如何防止CSRF ?

Django下的CSRF预防机制

  • django 第一次响应来自某个客户端的请求时,会在服务器端随机生成一个 token,把这个 token 放在 cookie 里。然后每次 POST 请求都会带上这个 token,这样就能避免被 CSRF 攻击。
  • 在POST请求时,表单中添加 {% csrf_token %}
<form action="" method="post">
     {% csrf_token %}
     ...
</form

九、Django媒体文件&静态文件&文件上传

1.静态文件和媒体文件

  • 媒体文件: 用户上传的文件, 叫做media
  • 静态文件: 存放在服务器的 css, js, image等,叫做static

在Django中使用静态文件

  • {% static 'img/example.jpg' %} => static模板关键字就是在settings.py中指定的静态文件夹中(应用下的static文件夹和STATICFILES_DIR寻找到符合条件的静态文件
  • 然后将相对路径写入html标签中
  • 注意: 应用下的static文件夹里的文件(包括STATICFILES_DIR下的文件) 似乎在网站生成时都统一放在了http://127.0.0.1:8000/static/
 首先确保django.contrib.staticfiles在INSTELLED_APPS中
2) 在 setting.py中定义 SRARIC_URL
		STATIC_URL = '/static/'
3) 在你的app的static目录中存放静态文件,比如
		APP/ static/ example.jpg
4) 如果有别的静态文件,不在app的static目录下,可以通过 STATICFILES_DIRS来指定额外的静态搜索目录
		STATICFILES_DIR = [
            os.path.join(BASE_DIR, 'static'),
            ...
        ]
5) 在模板中使用load标签去加载文件
		{% load static %}
    	<img src="/static/index.css" />
    	<img src="{% static 'img/example.jpg' %}" />

在django中使用媒体文件

在settings中配置 MEDIA_ROOT, 就是指定文件上传的路径选项

MEDIA_ROOT = os.path.join(BASE_DIR,"media")

2. 文件上传

单文件上传

文件上传要求form表单存在enctype="multipart/form-data"属性,并且提交方法是post。
	<form enctype="multipart/form-data" action="/uploadFile/" method="post">
        <input type="file" name="myfile" />
        <br/>
		<input type="submit" value="upload"/>
    </form>
        
最简单的文件上传:
def file_upload(request):
	if request.method =='POST':
        # 获取上传的文件,如果没有文件,则默认为None
        myFile = request.FILES.get('myfile', None)
            if not myFile:
                return HttpResponse("no files for upload")
            file_path = os.path.join(settings.MEDIA_ROOT, '1.jpg')
            with open(file_path, 'ab') as fp:
                for part in myFile.chunks():
                    fp.write(part)
            return HttpResponse("上传成功!")
        else:
            return render(request, 'index.html')
单文件上传案例

views.py

import os
import uuid
from django.conf import settings
from django.shortcuts import redirect, render
from django.urls import reverse
from App.models import *
# Create your views here.


def index(request):
    return render(request, 'index.html')

def index2(request):
    return render(request, 'index2.html')


# 单文件上传
def upload1(request):
    if request.method == 'GET':
        return render(request, 'upload1.html')
    elif request.method == 'POST':
        # 单文件上传
        uname = request.POST.get('uname')
        icon = request.FILES.get('icon') # 单个文件
        print(uname, icon, type(icon))

        # 1.将上传的图片存储到后台对应的媒体文件中
        # file_name = icon.name   # 尽量不要使用图片的原始名称, 避免重复  
        # file_name = get_uuid() + icon.name[icon.name.rfind('.'):] # 获取后缀名
        file_name = get_uuid() + os.path.splitext(icon.name)[-1] # 获取后缀名
        file_path = os.path.join(settings.MEDIA_ROOT , file_name)
        print(file_path)
        with open(file_path, 'wb') as f:
            for chunk in icon.chunks(): # 分块写入
                f.write(chunk)
                f.flush()  # 立即写入

        # 2.将该媒体文件的路径,存入到数据库中
        user = UserModel()
        user.name = uname
        user.icon = 'uploads/' + file_name # 注意: 存储的是相对路径, 而不是绝对路径,相对于static的路径
        user.save()
        return render(request, 'upload1.html')
    

# 获取uuid, 用于生成唯一的文件名
def get_uuid():
    return str(uuid.uuid4())


def showicon(request, uid):
    if uid == None: # 如果没有传uid, 则默认为1
        uid = 1
    # 显示用户的头像
    user = UserModel.objects.filter(id=uid).first()
    print(user, user.name, user.icon)
    return render(request, 'showicon.html', {'user': user})    

# 多文件上传
def upload2(request):
    if request.method == 'GET':
        return render(request, 'upload2.html')
    elif request.method == 'POST':
        # 接收前端的参数
        uname = request.POST.get('uname')
        uid = UserModel.objects.filter(name=uname).first().id
        imgs = request.FILES.getlist('imgs') # 多个文件
        print(uname, imgs, type(imgs))
        # [<InMemoryUploadedFile: chapter11_主界面.png (image/png)>, <InMemoryUploadedFile: chapter12_对话框.png (image/png)>] <class 'list'>

        # 遍历文件列表, 依次上传
        for img in imgs:
            # 1.将上传的图片存储到后台对应的媒体文件中
            file_name = get_uuid() + os.path.splitext(img.name)[-1] # 获取后缀名
            file_path = os.path.join(settings.MEDIA_ROOT, file_name)
            with open(file_path, 'wb') as f:
                for chunk in img.chunks(): # 分块写入
                    f.write(chunk)
                    f.flush()  # 立即写入

            # 2.将该媒体文件的路径,存入到数据库中
            photo = PhotoModel()
            photo.img = 'uploads/' + file_name
            photo.user = UserModel.objects.filter(name=uname).first()
            photo.save()

        return redirect(reverse('showicon',kwargs={'uid': uid}))

settings.py

STATIC_URL = 'static/'
STATICFILES_DIRS = [
    BASE_DIR / 'static'
]
MEDIA_ROOT = BASE_DIR /'static/uploads'

models.py

from django.db import models

# Create your models here.

class UserModel(models.Model):
    name = models.CharField(max_length=32, unique=True)
    # 头像(存储头像的路径)
    icon = models.CharField(max_length=128)

upload1.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>单文件上传</title>
</head>
<body>
    <form action="" method="post" enctype="multipart/form-data">
        {% csrf_token %}
        <p>用户名: <input type="text" name='uname'></p>
        <p>头像: <input type="file" name="icon"></p>
        <button>上传</button>
    </form>
</body>
</html>

showicon.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>显示图标</title>
</head>
<body>
    <h1>显示图标</h1>
    <hr>
    {% load static  %}
    <p>{{user.name}}</p>
    <img src="{% static user.icon %}" alt="">
</body>
</html>

多文件上传

多文件上传和单文件上传类似
1.需要在模板文件的form表单input中添加multiple
2.后台获取时使用request.FILES.getlist('myfile', None)
def file_upload2(request):
    if request.method == 'POST':
        # 获取上传的文件,如果没有文件,则默认为None
        myFiles = request.FILES.getlist('myfile', None)
        for myFile in myFiles:
            if not myFile:
                return HttpResponse("no files for upload")

            file_path = os.path.join(settings.MEDIA_ROOT, myFile.name)
            with open(file_path, 'ab') as fp:
                for part in myFile.chunks():
                    fp.write(part)

                    return HttpResponse("上传成功!")
                else:
                    return render(request, 'index.html')
多文件上传案例

views.py

import os
import uuid
from django.conf import settings
from django.shortcuts import redirect, render
from django.urls import reverse
from App.models import *
# Create your views here.


def index(request):
    return render(request, 'index.html')

def index2(request):
    return render(request, 'index2.html')


# 单文件上传
def upload1(request):
    if request.method == 'GET':
        return render(request, 'upload1.html')
    elif request.method == 'POST':
        # 单文件上传
        uname = request.POST.get('uname')
        icon = request.FILES.get('icon') # 单个文件
        print(uname, icon, type(icon))

        # 1.将上传的图片存储到后台对应的媒体文件中
        # file_name = icon.name   # 尽量不要使用图片的原始名称, 避免重复  
        # file_name = get_uuid() + icon.name[icon.name.rfind('.'):] # 获取后缀名
        file_name = get_uuid() + os.path.splitext(icon.name)[-1] # 获取后缀名
        file_path = os.path.join(settings.MEDIA_ROOT , file_name)
        print(file_path)
        with open(file_path, 'wb') as f:
            for chunk in icon.chunks(): # 分块写入
                f.write(chunk)
                f.flush()  # 立即写入

        # 2.将该媒体文件的路径,存入到数据库中
        user = UserModel()
        user.name = uname
        user.icon = 'uploads/' + file_name # 注意: 存储的是相对路径, 而不是绝对路径,相对于static的路径
        user.save()
        return render(request, 'upload1.html')
    

# 获取uuid, 用于生成唯一的文件名
def get_uuid():
    return str(uuid.uuid4())


def showicon(request, uid):
    if uid == None: # 如果没有传uid, 则默认为1
        uid = 1
    # 显示用户的头像
    user = UserModel.objects.filter(id=uid).first()
    print(user, user.name, user.icon)
    return render(request, 'showicon.html', {'user': user})    

# 多文件上传
def upload2(request):
    if request.method == 'GET':
        return render(request, 'upload2.html')
    elif request.method == 'POST':
        # 接收前端的参数
        uname = request.POST.get('uname')
        uid = UserModel.objects.filter(name=uname).first().id
        imgs = request.FILES.getlist('imgs') # 多个文件
        print(uname, imgs, type(imgs))
        # [<InMemoryUploadedFile: chapter11_主界面.png (image/png)>, <InMemoryUploadedFile: chapter12_对话框.png (image/png)>] <class 'list'>

        # 遍历文件列表, 依次上传
        for img in imgs:
            # 1.将上传的图片存储到后台对应的媒体文件中
            file_name = get_uuid() + os.path.splitext(img.name)[-1] # 获取后缀名
            file_path = os.path.join(settings.MEDIA_ROOT, file_name)
            with open(file_path, 'wb') as f:
                for chunk in img.chunks(): # 分块写入
                    f.write(chunk)
                    f.flush()  # 立即写入

            # 2.将该媒体文件的路径,存入到数据库中
            photo = PhotoModel()
            photo.img = 'uploads/' + file_name
            photo.user = UserModel.objects.filter(name=uname).first()
            photo.save()

        return redirect(reverse('showicon',kwargs={'uid': uid}))

upload2.html文件多选 -> multiple

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>多文件上传</title>
</head>
<body>
    {% comment %} multipart/form-data支持文件上传 {% endcomment %}
    {% comment %} multiple: 支持文件多选 {% endcomment %}
    <form action="" method="post" enctype="multipart/form-data">
        {% csrf_token %}
        <p>用户名: <input type="text" name='uname'></p>
        <p>选择上传的图片: <input type="file" name="imgs" multiple></p>
        <button>上传</button>
    </form>
</body>
</html>

showicon.html添加

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>显示图标</title>
</head>
<body>
    <h1>显示图标</h1>
    <hr>
    {% load static  %}
    <p>{{user.name}}</p>
    <p>头像<img src="{% static user.icon %}" alt=""></p>
    
    <hr>
    <h3>{{user.name}}的相册</h3>
    {% for photo in user.photomodel_set.all  %}
    <img src="{% static photo.img %}" alt="" width=100>
    {% endfor %}

</body>
</html>