Django入门学习

Django学习

文档

  1. 大江狗的博客 | 大江狗的技术及生活博客 (pythondjango.cn) 最仔细
  2. http://shouce.jb51.net/django1.5/index.html#id2
  3. http://c.biancheng.net/django/ 很多细节问题
  4. http://www.doczj.com/doc/a78934716-2.html
  5. https://docs.djangoproject.com/zh-hans/3.0/topics/http/urls/

初始Django

​ Django最初是被设计来开发新闻类站点,目的是要实现简单快捷的网站开发

设计模型

django不需要数据库就可以使用,是因为它提供了对象关系映射器,可以使用python代码来描述数据库结构。

你可以使用数据-模型语句来描述你的数据模型,

from django.db import models

class Reporter(models.Model):#定义一个模型类
    full_name = models.CharField(max_length=70)

    def __str__(self):  
        return self.full_name

class Article(models.Model):
    pub_date = models.DateField()
    headline = models.CharField(max_length=200)
    content = models.TextField()
    reporter = models.ForeignKey(Reporter, on_delete=models.CASCADE)

    def __str__(self):
        return self.headline

应用数据模型

运行django命令行使用程序自动创建数据库表

python manage.py makemigrations

python manage.py migrate

makemigrations 命令查找所有可用的models 为任意一个在数据库中不存在对应数据表的model创建migrations 脚本文件migrate 命令则运行这些 migrations 自动创建数据库表

享用便捷的 API

# Import the models we created from our "news" app
>>> from news.models import Article, Reporter

# No reporters are in the system yet.
>>> Reporter.objects.all()
<QuerySet []>

# Create a new Reporter.
>>> r = Reporter(full_name='John Smith')

# Save the object into the database. You have to call save() explicitly.
>>> r.save()

# Now it has an ID.
>>> r.id
1

# Now the new reporter is in the database.
>>> Reporter.objects.all()
<QuerySet [<Reporter: John Smith>]>

# Fields are represented as attributes on the Python object.
>>> r.full_name
'John Smith'

# Django provides a rich database lookup API.
>>> Reporter.objects.get(id=1)
<Reporter: John Smith>
>>> Reporter.objects.get(full_name__startswith='John')
<Reporter: John Smith>
>>> Reporter.objects.get(full_name__contains='mith')
<Reporter: John Smith>
>>> Reporter.objects.get(id=2)
Traceback (most recent call last):
    ...
DoesNotExist: Reporter matching query does not exist.

# Create an article.
>>> from datetime import date
>>> a = Article(pub_date=date.today(), headline='Django is cool',
...     content='Yeah.', reporter=r)
>>> a.save()

# Now the article is in the database.
>>> Article.objects.all()
<QuerySet [<Article: Django is cool>]>

# Article objects get API access to related Reporter objects.
>>> r = a.reporter
>>> r.full_name
'John Smith'

# And vice versa: Reporter objects get API access to Article objects.
>>> r.article_set.all()
<QuerySet [<Article: Django is cool>]>

# The API follows relationships as far as you need, performing efficient
# JOINs for you behind the scenes.
# This finds all articles by a reporter whose name starts with "John".
>>> Article.objects.filter(reporter__full_name__startswith='John')
<QuerySet [<Article: Django is cool>]>

# Change an object by altering its attributes and calling save().
>>> r.full_name = 'Billy Goat'
>>> r.save()

# Delete an object with delete().
>>> r.delete()

一个动态管理接口:并非徒有其表

当你的模型完成定义,Django 就会自动生成一个专业的生产级 管理接口 ——一个允许认证用户添加、更改和删除对象的 Web 站点。你只需在 admin 站点上注册你的模型即可:

from django.db import models

class Article(models.Model):
    pub_date = models.DateField()
    headline = models.CharField(max_length=200)
    content = models.TextField()
    reporter = models.ForeignKey(Reporter, on_delete=models.CASCADE)
from django.contrib import admin

from . import models

admin.site.register(models.Article)

规划URLs

简洁优雅的 URL 规划对于一个高质量 Web 应用来说至关重要

为了设计你自己的 URLconf ,你需要创建一个叫做 URLconf 的 Python 模块。这是网站的目录,它包含了一张 URL 和 Python 回调函数之间的映射表。URLconf 也有利于将 Python 代码与 URL 进行解耦(译注:使各个模块分离,独立)。

下面这个 URLconf 适用于前面 Reporter/Article 的例子:

from django.urls import path

from . import views

urlpatterns = [
    path('articles//', views.year_archive),
    path('articles///', views.month_archive),
    path('articles////', views.article_detail),
]

将 URL 路径映射到了 Python 回调函数(“视图”)

路径字符串使用参数标签从URL中“捕获”相应值。当用户请求页面时,Django 依次遍历路径,直至初次匹配到了请求的 URL。

(如果无匹配项,Django 会调用 404 视图。) 这个过程非常快,因为路径在加载时就编译成了正则表达式

编写视图

视图函数的执行结果只可能有两种:

1.包含请求页面元素的 HttpResponse 对象

2.或者是抛出 Http404 这类异常

至于执行过程中的其它的动作则由你决定。

视图工作

从参数获取数据,装载一个模板,然后将根据获取的数据对模板进行渲染。下面是一个 year_archive 的视图样例:

from django.shortcuts import render

from .models import Article

def year_archive(request, year):
    a_list = Article.objects.filter(pub_date__year=year)
    context = {'year': year, 'article_list': a_list}
    return render(request, 'news/year_archive.html', context)

使用了 Django 模板系统 ,它有着很多强大的功能,而且使用起来足够简单,即使不是程序员也可轻松使用。

设计模板

上面的代码加载了 news/year_archive.html 模板。

Django 允许设置搜索模板路径 -----最小化模板之间的冗余

Django 设置中,你可以通过 DIRS 参数指定一个路径列表用于检索模板如果第一个路径中不包含任何模板,就继续检查第二个,以此类推。

{% extends "base.html" %}

{% block title %}Articles for {{ year }}{% endblock %}

{% block content %}
<h1>Articles for {{ year }}</h1>

{% for article in article_list %}
    <p>{{ article.headline }}</p>
    <p>By {{ article.reporter.full_name }}</p>
    <p>Published {{ article.pub_date|date:"F j, Y" }}</p>
{% endfor %}
{% endblock %}

上面 {{ article.headline }} 的意思是“输出 article 的 headline 属性,其中的点还有更多的用途,比如查找字典键值、查找索引和函数调用

{{ article.pub_date|date:"F j, Y" }} 使用了 Unix 风格的“管道符”(“|”字符)。这是一个模板过滤器,用于过滤变量值。在这里过滤器将一个 Python datetime 对象转化为指定的格式

你可以将多个过滤器连在一起使用。你还可以使用你 自定义的模板过滤器 。你甚至可以自己编写 自定义的模板标签 ,相关的 Python 代码会在使用标签时在后台运行。

Django 使用了 ‘‘模板继承’’ 的概念。这就是 {% extends "base.html" %} 的作用。它的含义是’‘先加载名为 base 的模板,并且用下面的标记块对模板中定义的标记块进行填充’'。简而言之,模板继承可以使模板间的冗余内容最小化:每个模板只需包含与其它文档有区别的内容。

下面是 base.html 可能的样子,它使用了 静态文件 :

{% load static %}
<html>
<head>
    <title>{% block title %}{% endblock %}</title>
</head>
<body>
    <img src="{% static "images/sitelogo.png" %}" alt="Logo">
    {% block content %}{% endblock %}
</body>
</html>

以上只是 Django 的功能性概述。Django 还有更多实用的特性:

  • 缓存框架 可以与 memcached 或其它后端集成。
  • 聚合器框架 可以通过编写一个小型 Python 类来创建 RRS 和 Atom摘要。
  • 功能丰富的自动生成的后台——这份概要只是简单介绍了下。

安装django网上有

编写你的第一个Django应用,第一部分

投票应用程序。

一个让人们查看和投票的公共站点。

一个让你能添加、修改和删除投票的管理站点。

创建项目

django-admin startproject mysite
mysite/
    manage.py
    mysite/
        __init__.py
        settings.py
        urls.py
        asgi.py
        wsgi.py

目录和文件的用处:

mysite/ 根目录只是你项目的容器

manage.py: 一个让你用各种方式管理 Django 项目的命令行工具

里面一层的mysite/ 目录包含你的项目,它是一个纯 Python 包

mysite/__init__.py:一个空文件,告诉 Python 这个目录应该被认为是一个 Python 包。

mysite/settings.py:Django 项目的配置文件。如果你想知道这个文件是如何工作的,请查看 Django 配置 了解细节。

mysite/urls.py:Django 项目的 URL 声明,就像你网站的“目录”。阅读 URL调度器 文档来获取更多关于 URL 的内容

mysite/asgi.py`:作为你的项目的运行在 ASGI 兼容的Web服务器上的入口。阅读 如何使用 WSGI 进行部署 了解更多细节。

mysite/wsgi.py`:作为你的项目的运行在 WSGI 兼容的Web服务器上的入口。阅读 如何使用 WSGI 进行部署 了解更多细节。

启动服务

python manage.py runserver

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S8Zv8L5p-1655293295133)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220608194124002.png)]

python manage.py runserver 8080

监听所有服务器的公开IP

python manage.py runserver 0:8000

将在你的 manage.py 同级目录下创建投票应用顶级模块导入而不是mysite子模块

python manage.py startapp polls

这样将会创建一个polls目录

polls/
    __init__.py
    admin.py
    apps.py
    migrations/
        __init__.py
    models.py
    tests.py
    views.py

编写第一个视图

polls/views.py,把下面这些 Python 代码输入进去:

from django.http import HttpResponse


def index(request):
    return HttpResponse("Hello, world. You're at the polls index.")

为了创建 URLconf,请在 polls 目录里新建一个 urls.py[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dH0kA0pN-1655293295134)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220608194507036.png)]

在其中输入代码

from django.urls import path

from . import views

urlpatterns = [
    path('', views.index, name='index'),
]

根 URLconf 文件中指定我们创建的 polls.urls 模块。在 mysite/urls.py 文件的 urlpatterns 列表里插入一个 include()

from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('polls/', include('polls.urls')),
    path('admin/', admin.site.urls),
]

函数 include() 允许引用其它 URLconfs。每当 Django 遇到 include() 时,它会截断与此项匹配的 URL 的部分,并将剩余的字符串发送到 URLconf

设计 include() 的理念是使其可以即插即用

投票应用有它自己的 URLconf( polls/urls.py ),他们能够被放在 “/polls/” , “/fun_polls/” ,“/content/polls/”,或者其他任何路径下,这个应用都能够正常工作。

再次启动服务

python manage.py runserver	

浏览器访问 http://localhost:8000/polls/

path

函数 path() 具有四个参数,两个必须参数:routeview,两个可选参数:kwargsname。现在,是时候来研究这些参数的含义了。

path 参数 router

route 是一个匹配 URL 的准则(类似正则表达式)。当 Django 响应一个请求时,它会从 urlpatterns 的第一项开始,按顺序依次匹配列表中的项,直到找到匹配的项。

这些准则不会匹配 GET 和 POST 参数或域名。例如,URLconf 在处理请求 https://www.example.com/myapp/ 时,它会尝试匹配 myapp/ 。处理请求 https://www.example.com/myapp/?page=3 时,也只会尝试匹配 myapp/

path参数 view

当 Django 找到了一个匹配的准则,就会调用这个特定的视图函数,并传入一个 HttpRequest 对象作为第一个参数,被“捕获”的参数以关键字参数的形式传入。稍后,我们会给出一个例子。

path参数 kwargs

任意个关键字参数可以作为一个字典传递给目标视图函数。

path参数 name

为你的 URL 取名能使你在 Django 的任意地方唯一地引用它,尤其是在模板中。这个有用的特性允许你只改一个文件就能全局地修改某个 URL 模式当你了解了基本的请求和响应流程后,请阅读 教程的第 2 部分 开始使用数据库.

编写你的第一个Django应用,第二部分

数据库配置

打开 mysite/settings.py 。这是个包含了 Django 项目设置的 Python 模块

改变设置文件中 DATABASES 'default' 项目中的一些键值:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'mydatabase',
        'USER': 'mydatabaseuser',
        'PASSWORD': 'mypassword',
        'HOST': '127.0.0.1',
        'PORT': '5432',
    }
}

mysite/settings.py 文件前,先设置 TIME_ZONE 为你自己时区。

文件头部的 INSTALLED_APPS 设置项。这里包括了会在你项目中启用的所有 Django 应用。应用能在多个项目中使用,你也可以打包并且发布应用,让别人使用它们。

INSTALLED_APPS 默认包括了以下 Django 的自带应用:

  • django.contrib.admin – 管理员站点, 你很快就会使用它。
  • django.contrib.auth – 认证授权系统。
  • django.contrib.contenttypes – 内容类型框架。
  • django.contrib.sessions – 会话框架。
  • django.contrib.messages – 消息框架。
  • django.contrib.staticfiles – 管理静态文件的框架。
python manage.py migrate

创建模型

编辑 polls/models.py 文件

from django.db import models


class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')


class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)

sqlmigrate 命令接收一个迁移的名称,然后返回对应的 SQL:

python manage.py sqlmigrate polls 0001
BEGIN;
--
-- Create model Question
--
CREATE TABLE "polls_question" (
    "id" serial NOT NULL PRIMARY KEY,
    "question_text" varchar(200) NOT NULL,
    "pub_date" timestamp with time zone NOT NULL
);
--
-- Create model Choice
--
CREATE TABLE "polls_choice" (
    "id" serial NOT NULL PRIMARY KEY,
    "choice_text" varchar(200) NOT NULL,
    "votes" integer NOT NULL,
    "question_id" integer NOT NULL
);
ALTER TABLE "polls_choice"
  ADD CONSTRAINT "polls_choice_question_id_c5b4b260_fk_polls_question_id"
    FOREIGN KEY ("question_id")
    REFERENCES "polls_question" ("id")
    DEFERRABLE INITIALLY DEFERRED;
CREATE INDEX "polls_choice_question_id_c5b4b260" ON "polls_choice" ("question_id");

COMMIT;
python manage.py migrate
  • 编辑 models.py 文件,改变模型。
  • 运行 python manage.py makemigrations 为模型的改变生成迁移文件。
  • 运行 python manage.py migrate 来应用数据库迁移。

初始API

python manage.py shell

我们使用这个命令而不是简单的使用 “Python” 是因为 manage.py 会设置 DJANGO_SETTINGS_MODULE 环境变量,这个变量会让 Django 根据 mysite/settings.py 文件来设置 Python 包的导入路径。

>>> from polls.models import Choice, Question  # Import the model classes we just wrote.

# No questions are in the system yet.
>>> Question.objects.all()
<QuerySet []>

# Create a new Question.
# Support for time zones is enabled in the default settings file, so
# Django expects a datetime with tzinfo for pub_date. Use timezone.now()
# instead of datetime.datetime.now() and it will do the right thing.
>>> from django.utils import timezone
>>> q = Question(question_text="What's new?", pub_date=timezone.now())

# Save the object into the database. You have to call save() explicitly.
>>> q.save()

# Now it has an ID.
>>> q.id
1

# Access model field values via Python attributes.
>>> q.question_text
"What's new?"
>>> q.pub_date
datetime.datetime(2012, 2, 26, 13, 0, 0, 775217, tzinfo=<UTC>)

# Change values by changing the attributes, then calling save().
>>> q.question_text = "What's up?"
>>> q.save()

# objects.all() displays all the questions in the database.
>>> Question.objects.all()
<QuerySet [<Question: Question object (1)>]>

Django的MVT设计模式

  1. 经典的MVC设计模式及其优点
  2. Django是如何遵循MVC设计模式的?
  3. 示例
    1. 新建app并注册
    2. 创建模型(M)
    3. 编写视图并配置路由URL(V)
    4. 编辑模板(T)

经典的MVC设计模式及其优点

MVC即 Model-View-Controller(模型-视图-控制器) ,是经典的软件开发设计模式。

  • **Model (模型) **: 简而言之即数据模型。模型不是数据本身(比如数据库里的数据),而是抽象的描述数据的构成和逻辑关系。通常模型包括了数据表的各个字段(比如人的年龄和出生日期)和相互关系(单对单,单对多关系等)。Web开发框架会根据模型的定义来自动生成数据表。
  • View (视图): 主要用于显示数据,用来展示用户可以看到的内容或提供用户可以输入或操作的界面。数据来源于哪里?当然是数据库啦。那么用户输入的数据给谁? 当然是给控制器啦。
  • Controller(控制器):应用程序中处理用户交互的部分。通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据(比如增加或更新数据表)。

如果把MVC比喻成一个粽子,那么View就是最外面一层的绿色玉米叶,是吃货们可以直接看到的。Controller就是中间那层熟糯米,而粽子的核心自然是最里面那一层的肉馅Model模型了。现在大家知道中学和大学数学建模的重要性了吧?

MVC最大的优点是实现了软件或网络应用开发过程中数据、业务逻辑和用户界面的分离,使软件开发更清晰,也是维护变得更容易。这与静态网页设计中使用html和css实现了内容和样式的分离是同一个道理。

Django是如何遵循MVC设计模式的?

Django的MVT设计模式由Model(模型), View(视图) 和Template(模板)三部分组成,分别对应单个app目录下的models.py, views.py和templates文件夹。它们看似与MVC设计模式不太一致,其实本质是相同的。Django的MVT设计模式与经典的MVC对应关系如下。

  • Django Model(模型): 这个与经典MVC模式下的模型Model差不多。
  • Django View(视图): 这个与MVC下的控制器Controller更像。视图不仅负责根据用户请求从数据库读取数据、指定向用户展示数据的方式(网页或json数据), 还可以指定渲染模板并处理用户提交的数据。
  • Django Template(模板): 这个与经典MVC模式下的视图View一致。模板用来呈现Django view传来的数据,也决定了用户界面的外观。Template里面也包含了表单,可以用来搜集用户的输入内容。

Django MVT设计模式中最重要的是视图(view), 因为它同时与模型(model)和模板(templates)进行交互。当用户发来一个请求(request)时,Django会对请求头信息进行解析,解析出用户需要访问的url地址,然后根据路由urls.py中的定义的对应关系把请求转发到相应的视图处理。视图会从数据库读取需要的数据,指定渲染模板,最后返回响应数据。这个过程如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-86tqMdRH-1655293295134)(https://pythondjango.cn/django/basics/2-installation-use.assets/Django-2.png)]

示例

现在我们以示例演示Django的MVT三部分是如何工作的。

新建app并注册

假如你有一个mysite项目,希望新增一个任务管理小应用,你首先要使用python manage.py startapp tasks的命令创建一个名为tasks的app,将它加入到settings.py中的INSTALLED_APP中去。

# mysite/settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'tasks',
]

然后把tasks应用的urls添加到到项目的urls中去。

# mysite/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('tasks/', include('tasks.urls'))
]

创建模型(M)

编辑tasks目录下models.py创建Task模型, Task模型包含里名称name和状态status两个字段。当你使用python manage.py makemigrationspython manage.py migrate命令时,Django会自动为你在数据库创建数据表(默认使用的数据库是免费的sqlite),表名为tasks_task

    # tasks/models.py
    
    from django.db import models
    
    class Status(models.TextChoices):
        UNSTARTED = 'u', "Not started yet"
        ONGOING = 'o', "Ongoing"
        FINISHED = 'f', "Finished"
    
    # Task模型
    class Task(models.Model):
        name = models.CharField(verbose_name="Task name", max_length=65, unique=True)
        status = models.CharField(verbose_name="Task status", max_length=1, choices=Status.choices)
    
        def __str__(self):
            return self.name

编写视图并配置路由URL(V)

接下来我们要编辑视图views.py,并新增一个视图函数 task_list, 用于展示任务清单。该视图函数从数据库读取了Task对象列表,指定了渲染模板并向模板传递了数据。

# tasks/views.py
from django.shortcuts import render
from .models import Task

# 任务清单
def task_list(request):
    # 从数据库获取Task对象列表
    tasks = Task.objects.all()
    # 指定渲染模板并向模板传递数据
    return render(request, "tasks/task_list.html", { "tasks": tasks,})

光编写视图(views.py)还不够,我们还得为写好的视图函数配置路由,这样才能将视图函数与用户的请求地址建立好对应关系。编辑或创建tasks/urls.py, 添加如下代码:

# tasks/urls.py
from django.urls import path
from . import views

# namespace
app_name = 'tasks'

urlpatterns = [
    # Retrieve task list
    path('', views.task_list, name='task_list'),
]

这样当用户访问/tasks/时,Django将调用task_list视图函数。这个视图函数将同时与数据库和模板进行交互。

编辑模板(T)

最后我们要创建task_list.html用于展示视图传来的任务列表数据。这个文件的完整路径为tasks/templates/tasks/task_list.html。至于模板为什么放这里,我们后续会专门介绍。Django还提供了自己的模板语言,包括常见的判断和循环,专门用来渲染模板。

# tasks/templates/tasks/task_list.html



    
    Task List


Task List

{% for task in tasks %}

{{ forloop.counter }}. {{ task.name }} - {{ task.get_status_display }}

{% endfor %}

当然此时如果你通过浏览器访问/tasks/, 还看不到任何内容,这是因为你的数据表里还没有任何数据。你可以通过django的admin添加或新增task_create视图实现。

一个任务管理CRUD小应用

  1. 第一步:创建tasks应用,加入INSTALLED_APPS
  2. 第二步:创建Task模型及其关联表单
  3. 第三步:编写路由URLConf及视图
  4. 第四步:编写模板
  5. 第五步:运行项目,查看效果
  6. GitHub源码地址

本例中我们只讲述核心逻辑,不浪费时间在前端样式上。文末有GitHub源码地址,里面同时包含了函数视图和基于类的视图, 具体演示效果如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rtf1nIEg-1655293295135)(https://pythondjango.cn/django/basics/2-installation-use.assets/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X2dpZi9idWFGTEZLaWNSb0RUQzlhMzBnRHNJamhyaDZVZW81UUFtdlQ0dWxKU2liczBxenFyV0I4dWhDZUlsdTJWTHdDQjRzRHVLY0VHOUF6Q0RHNmhWcVFpYU1LZy82NDA)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nsVua8Js-1655293295135)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

第一步:创建tasks应用,加入INSTALLED_APPS

本例假设你已经有了一个mysite的Django项目。我们首先使用 python manage.py startapp tasks 创建一个名为”tasks”的app,并把它计入到settings.py的INSTALLED_APPS中去。

# mysite/settings.py
INSTALLED_APPS = [
     'django.contrib.admin',
     'django.contrib.auth',
     'django.contrib.contenttypes',
     'django.contrib.sessions',
     'django.contrib.messages',
     'django.contrib.staticfiles',
     'tasks',
 ]

然后把app下的urls路径添加到项目文件夹的urls.py里去。

 from django.contrib import admin
 from django.urls import path, include

 urlpatterns = [
     path('admin/', admin.site.urls),
     path('tasks/', include('tasks.urls'))
 ]

第二步:创建Task模型及其关联表单

我们的Task模型非常简单,仅包含name和status两个字段。我们还使用ModelForm类创建了TaskForm,我们在创建任务或更新任务时需要用到这个表单。

# tasks/models.py
 from django.db import models

 class Status(models.TextChoices):
     UNSTARTED = 'u', "Not started yet"
     ONGOING = 'o', "Ongoing"
     FINISHED = 'f', "Finished"


 class Task(models.Model):
     name = models.CharField(verbose_name="Task name", max_length=65, unique=True)
     status = models.CharField(verbose_name="Task status", max_length=1, choices=Status.choices)

     def __str__(self):
         return self.name

 # tasks/forms.py
 from .models import Task
 from django import forms

 class TaskForm(forms.ModelForm):

     class Meta:
         model = Task
         fields = "__all__"

第三步:编写路由URLConf及视图

我们需要创建5个urls, 对应5个函数视图。这是因为对于Retrieve操作,我们需要编写两个函数视图,一个用户获取任务列表,一个用于获取任务详情。对于task_detail, task_updatetask_delete这个三个视图函数,我们还需要通过urls传递任务id或pk参数,否则它们不知道对哪个对象进行操作。

# tasks/urls.py
 from django.urls import path, re_path
 from . import views

 # namespace
 app_name = 'tasks'

 urlpatterns = [
     # Create a task
     path('create/', views.task_create, name='task_create'),

     # Retrieve task list
     path('', views.task_list, name='task_list'),

     # Retrieve single task object
     re_path(r'^(?P\d+)/$', views.task_detail, name='task_detail'),

     # Update a task
     re_path(r'^(?P\d+)/update/$', views.task_update, name='task_update'),

     # Delete a task
     re_path(r'^(?P\d+)/delete/$', views.task_delete, name='task_delete'),
 ]

下面5个函数视图代码是本应用的核心代码,请仔细阅读并去尝试理解每一行代码。

# tasks/views.py

 from django.shortcuts import render, redirect, get_object_or_404
 from django.urls import reverse
 from .models import Task
 from .forms import TaskForm
 
 # Create a task
 def task_create(request):
     # 如果用户通过POST提交,通过request.POST获取提交数据
     if request.method == "POST":
         # 将用户提交数据与TaskForm表单绑定
         form = TaskForm(request.POST)
         # 表单验证,如果表单有效,将数据存入数据库
         if form.is_valid():
             form.save()
             # 跳转到任务清单
             return redirect(reverse("tasks:task_list"))
     else:
         # 否则空表单
         form = TaskForm()
     return render(request, "tasks/task_form.html", { "form": form, })

 # Retrieve task list
 def task_list(request):
     # 从数据库获取任务清单
     tasks = Task.objects.all()
     # 指定渲染模板并传递数据
     return render(request, "tasks/task_list.html", { "tasks": tasks,})

 # Retrieve a single task
 def task_detail(request, pk):
     # 从url里获取单个任务的pk值,然后查询数据库获得单个对象
     task = get_object_or_404(Task, pk=pk)
     return render(request, "tasks/task_detail.html", { "task": task, })

 # Update a single task
 def task_update(request, pk):
     # 从url里获取单个任务的pk值,然后查询数据库获得单个对象实例
     task_obj = get_object_or_404(Task, pk=pk)
     if request.method == 'POST':
         form = TaskForm(instance=task_obj, data=request.POST)
         if form.is_valid():
             form.save()
             return redirect(reverse("tasks:task_detail", args=[pk,]))
     else:
         form = TaskForm(instance=task_obj)
     return render(request, "tasks/task_form.html", { "form": form, "object": task_obj})


 # Delete a single task
 def task_delete(request, pk):
     # 从url里获取单个任务的pk值,然后查询数据库获得单个对象
     task_obj = get_object_or_404(Task, pk=pk)
     task_obj.delete() # 删除然后跳转
     return redirect(reverse("tasks:task_list"))

第四步:编写模板

虽然我们有5个urls,但我们只需要创建3个模板:task_list.html, task_detail.htmltask_form.html。 最后一个模板由task_createtask_update 视图函数共享。我们在模板中对实例对象进行判断,如果对象已存在则模板对于更新任务,否则是创建任务。task_delete视图不需要模板。

 # tasks/templates/tasks/task_list.html
 <!DOCTYPE html>
 <html lang="en">
 <head>
     <meta charset="UTF-8">
     <title>Task List</title>
 </head>
 <body>
 <h3>Task List</h3>
 {% for task in tasks %}
     <p>{{ forloop.counter }}. {{ task.name }} - {{ task.get_status_display }}
         (<a href="{% url 'tasks:task_update' task.id %}">Update</a> |
         <a href="{% url 'tasks:task_delete' task.id %}">Delete</a>)
     </p>
 {% endfor %}
 <p> <a href="{% url 'tasks:task_create' %}"> + Add A New Task</a></p>
 </body>
 </html>


 # tasks/templates/tasks/task_detail.html
 <!DOCTYPE html>
 <html lang="en">
 <head>
     <meta charset="UTF-8">
     <title>Task Detail</title>
 </head>
 <body>
 <p> Task Name: {{ task.name }} | <a href="{% url 'tasks:task_update' task.id %}">Update</a> |
     <a href="{% url 'tasks:task_delete' task.id %}">Delete</a>
 </p>
 <p> Task Status: {{ task.get_status_display }} </p>
 <p> <a href="{% url 'tasks:task_list' %}">View All Tasks</a> |
     <a href="{% url 'tasks:task_create'%}">New Task</a>
 </p>
 </body>
 </html>


 # tasks/templates/tasks/task_form.html
 <!DOCTYPE html>
 <html lang="en">
 <head>
     <meta charset="UTF-8">
     <title>{% if object %}Edit Task {% else %} Create New Task {% endif %}</title>
 </head>
 <body>
 <h3>{% if object %}Edit Task {% else %} Create New Task {% endif %}</h3>
     <form action="" method="post" enctype="multipart/form-data">
         {% csrf_token %}
         {{ form.as_p }}
         <p><input type="submit" class="btn btn-success" value="Submit"></p>
     </form>
 </body>
 </html> 

第五步:运行项目,查看效果

运行如下命令,访问http://127.0.0.1:8000/tasks/就应该看到文初效果了。

 python manage.py makemigrations
 python manage.py migrate
 python manage.py runserver

GitHub源码地址

本项目源码地址,里面同时包含了函数视图和基于类的视图。

https://github.com/shiyunbo/django-crud-example

Django模型详解

每个Django的模型(model)实际上是个类,继承了models.Model。每个Model应该包括属性(字段),关系(比如单对单,单对多和多对多)和方法。当你定义好Model模型后,Django的接口会自动帮你在数据库生成相应的数据表(table)。这样你就不用自己用SQL语言创建表格或在数据库里操作创建表格了,是不是很省心?

模型定义小案例

假设你要开发一个名叫bookstore的应用,专门来管理书店里的书籍。我们首先要为书本和出版社创建模型。出版社有名字和地址。书有名字,描述和添加日期。我们还需要利用ForeignKey定义了出版社与书本之间单对多的关系,因为一个出版社可以出版很多书,每本书都有对应的出版社。我们定义了PublisherBook模型,它们都继承了models.Model。你能看出代码有什么问题吗?

# models.py
from django.db import models
 
class Publisher(models.Model):
    name = models.CharField(max_length=30)
    address = models.CharField()
 
    def __str__(self):
        return self.name
		
class Book(models.Model):
    name = models.CharField(max_length=30)
    description = models.TextField(blank=True, null=True)
    publisher = ForeignKey(Publisher)
    add_date = models.DateField()
 
    def __str__(self):
        return self.name

模型创建好后,当你运行python manage.py migrate 命令创建数据表的时候你会遇到错误,错误原因如下:

  • CharField里的max_length选项没有定义
  • ForeignKey(Publisher)里的on_delete选项有没有定义

所以当你定义Django模型Model的时候,你一定要十分清楚2件事:

  • 这个Field是否有必选项, 比如CharFieldmax_lengthForeignKeyon_delete选项是必须要设置的。
  • 这个Field是否必需(blank = True or False),是否可以为空 (null = True or False)。这关系到数据的完整性。

下面是订正错误后的Django模型:

# models.py
from django.db import models
 
class Publisher(models.Model):
    name = models.CharField(max_length=30)
    address = models.CharField(max_length=60)
 
    def __str__(self):
        return self.name
		
class Book(models.Model):
    name = models.CharField(max_length=30)
    description = models.TextField(blank=True, default='')
    publisher = ForeignKey(Publisher,on_delete=models.CASCADE)
    add_date = models.DateField(auto_now_add=True)
 
    def __str__(self):
        return self.name

修改模型后,你需要连续运行python manage.py makemigrationspython manage.py migrate这两个命令,前者检查模型有无变化,后者将变化迁移至数据表。如果一切顺利,Django会在数据库(默认sqlite)中生成或变更由appname_modelname组成的数据表,本例两张数据表分别为bookstore_publisherbookstore_book

模型的组成

一个标准的Django模型分别由模型字段、META选项和方法三部分组成。我们接下来对各部分进行详细介绍。Django官方编码规范建议按如下方式排列:

  • 定义的模型字段:包括基础字段和关系字段
  • 自定义的Manager方法:改变模型
  • class Meta选项: 包括排序、索引等等(可选)。
  • def __str__():定义单个模型实例对象的名字(可选)。
  • def save():重写save方法(可选)。
  • def get_absolute_url():为单个模型实例对象生成独一无二的url(可选)
  • 其它自定义的方法。

模型的字段

models.Model提供的常用模型字段包括基础字段和关系字段。

基础字段

**CharField() **

一般需要通过max_length = xxx 设置最大字符长度。如不是必填项,可设置blank = True和default = ‘‘。如果用于username, 想使其唯一,可以设置unique = True。如果有choice选项,可以设置 choices = XXX_CHOICES

**TextField() **

适合大量文本,max_length = xxx选项可选。

**DateField() 和DateTimeField() **

可通过default=xx选项设置默认日期和时间。

  • 对于DateTimeField: default=timezone.now - 先要from django.utils import timezone
  • 如果希望自动记录一次修改日期(modified),可以设置: auto_now=True
  • 如果希望自动记录创建日期(created),可以设置auto_now_add=True

**EmailField() **

如不是必填项,可设置blank = True和default = ‘。一般Email用于用户名应该是唯一的,建议设置unique = True

IntegerField(), SlugField(), URLField(),BooleanField()

可以设置blank = True or null = True。对于BooleanField一般建议设置defaut = True or False

**FileField(upload_to=None, max_length=100) - 文件字段 **

  • upload_to = “/some folder/”:上传文件夹路径
  • max_length = xxxx:文件最大长度

**ImageField (upload_to=None, max_length=100,)- 图片字段 **

  • upload_to = “/some folder/”: 指定上传图片路径

关系字段

OneToOneField(to, on_delete=xxx, options) - 单对单关系

  • to必需指向其他模型,比如 Book or ‘self’ .
  • 必需指定on_delete选项(删除选项): i.e, “on_delete = models.CASCADE” or “on_delete = models.SET_NULL” .
  • 可以设置 “related_name = xxx” 便于反向查询。

ForeignKey(to, on_delete=xxx, options) - 单对多关系

  • to必需指向其他模型,比如 Book or ‘self’ .
  • 必需指定on_delete选项(删除选项): i.e, “on_delete = models.CASCADE” or “on_delete = models.SET_NULL” .
  • 可以设置”default = xxx” or “null = True” ;
  • 如果有必要,可以设置 “limit_choices_to = “,
  • 可以设置 “related_name = xxx” 便于反向查询。

ManyToManyField(to, options) - 多对多关系

  • to 必需指向其他模型,比如 User or ‘self’ .
  • 设置 “symmetrical = False “ 表示多对多关系不是对称的,比如A关注B不代表B关注A
  • 设置 “through = 'intermediary model' “ 如果需要建立中间模型来搜集更多信息。
  • 可以设置 “related_name = xxx” 便于反向查询。

示例:一个人加入多个组,一个组包含多个人,我们需要额外的中间模型记录加入日期和理由。

from django.db import models

class Person(models.Model):
    name = models.CharField(max_length=128)

    def __str__(self):
        return self.name

class Group(models.Model):
    name = models.CharField(max_length=128)
    members = models.ManyToManyField(Person, through='Membership')

    def __str__(self):
        return self.name

class Membership(models.Model):
    person = models.ForeignKey(Person, on_delete=models.CASCADE)
    group = models.ForeignKey(Group, on_delete=models.CASCADE)
    date_joined = models.DateField()
    invite_reason = models.CharField(max_length=64)

对于OneToOneFieldForeignKey, on_delete选项和related_name是两个非常重要的设置,前者决定了了关联外键删除方式,后者决定了模型反向查询的名字。

on_delete删除选项

Django提供了如下几种关联外键删除选项, 可以根据实际需求使用。

  • CASCADE:级联删除。当你删除publisher记录时,与之关联的所有 book 都会被删除。
  • PROTECT: 保护模式。如果有外键关联,就不允许删除,删除的时候会抛出ProtectedError错误,除非先把关联了外键的记录删除掉。例如想要删除publisher,那你要把所有关联了该publisher的book全部删除才可能删publisher。
  • SET_NULL: 置空模式。删除的时候,外键字段会被设置为空。删除publisher后,book 记录里面的publisher_id 就置为null了。
  • SET_DEFAULT: 置默认值,删除的时候,外键字段设置为默认值。
  • SET(): 自定义一个值。
  • DO_NOTHING:什么也不做。删除不报任何错,外键值依然保留,但是无法用这个外键去做查询。

related_name选项

related_name用于设置模型反向查询的名字,非常有用。在文初的PublisherBook模型里,我们可以通过book.publisher获取每本书的出版商信息,这是因为Book模型里有publisher这个字段。但是Publisher模型里并没有book这个字段,那么我们如何通过出版商反查其出版的所有书籍信息呢?

Django对于关联字段默认使用模型名_set进行反查,即通过publisher.book_set.all查询。但是book_set并不是一个很友好的名字,我们更希望通过publisher.books获取一个出版社已出版的所有书籍信息,这时我们就要修改我们的模型了,将related_name设为books, 如下所示:

# models.py
from django.db import models
 
class Publisher(models.Model):
    name = models.CharField(max_length=30)
    address = models.CharField(max_length=60)
 
    def __str__(self):
        return self.name

# 将related_name设置为books
class Book(models.Model):
    name = models.CharField(max_length=30)
    description = models.TextField(blank=True, default='')
    publisher = ForeignKey(Publisher,on_delete=models.CASCADE, related_name='books')
    add_date = models.DateField(auto_now_add=True)
 
    def __str__(self):
        return self.name

我们再来对比一下如何通过publisher查询其出版的所有书籍,你觉得哪个更好呢?

  1. 设置related_name前:publisher.book_set.all
  2. 设置related_name后:publisher.books.all

模型的META选项

  • abstract=True: 指定该模型为抽象模型
  • proxy=True: 指定该模型为代理模型
  • verbose_name=xxxverbose_name_plural=xxx: 为模型设置便于人类阅读的别名
  • db_table= xxx: 自定义数据表名
  • odering=['-pub-date']: 自定义按哪个字段排序,-代表逆序
  • permissions=[]: 为模型自定义权限
  • managed=False: 默认为True,如果为False,Django不会为这个模型生成数据表
  • indexes=[]: 为数据表设置索引,对于频繁查询的字段,建议设置索引
  • constraints=: 给数据库中的数据表增加约束。

模型的方法

标准方法

以下三个方法是Django模型自带的三个标准方法:

  • def __str__():给单个模型对象实例设置人为可读的名字(可选)。
  • def save():重写save方法(可选)。
  • def get_absolute_url():为单个模型实例对象生成独一无二的url(可选)

除此以外,我们经常自定义方法或Manager方法

示例一:自定义方法

# 为每篇文章生成独一无二的url
def get_absolute_url(self):
    return reverse('blog:article_detail', args=[str(self.id)])

# 计数器
def viewed(self):
    self.views += 1
    self.save(update_fields=['views'])

示例二:自定义Manager方法

# First, define the Manager subclass.
class DahlBookManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().filter(author='Roald Dahl')

# Then hook it into the Book model explicitly.
class Book(models.Model):
    title = models.CharField(max_length=100)
    author = models.CharField(max_length=50)

    objects = models.Manager() # The default manager.
    dahl_objects = DahlBookManager() # The Dahl-specific manager.

完美的高级Django模型示例

一个完美的django高级模型结构如下所示,可以满足绝大部分应用场景,希望对你有所帮助。

from django.db import models
from django.urls import reverse
 
# 自定义Manager方法
class HighRatingManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().filter(rating=1)

# CHOICES选项
class Rating(models.IntegerChoices):
    VERYGOOD = 1, 'Very Good'
    GOOD = 2, 'Good'
    BAD = 3, 'Bad'

class Product(models.Model):
    # 数据表字段
    name = models.CharField('name', max_length=30)
    rating = models.IntegerField(max_length=1, choices=Rating.choices)
 
    # MANAGERS方法
    objects = models.Manager()
    high_rating_products =HighRatingManager()
 
    # META类选项
    class Meta:
        verbose_name = 'product'
        verbose_name_plural = 'products'
 
    # __str__方法
    def __str__(self):
        return self.name
 
    # 重写save方法
    def save(self, *args, **kwargs):
        do_something()
        super().save(*args, **kwargs) 
        do_something_else()
 
    # 定义单个对象绝对路径
    def get_absolute_url(self):
        return reverse('product_details', kwargs={'pk': self.id})
 
    # 其它自定义方法
    def do_something(self):

Django ORM数据增删改查接口

对于新增数据,Django提供了两种方法,save()create()方法。

方法一:save方法

from .models import Article

article = Article(title="My first article", body="My first article body")
article.save()

注意: 该方法如果不主动选择save(), 创建的对象实例只会存于内存之中,不会保存到数据库中去。正因为如此,Django还提供了更便捷的create方法。

方法二:create方法

article = Article.objects.create(title="My first article", body="My first article body")

为了避免重复创建数据表中已存在的条目,Django还提供了get_or_create方法。它会返回查询到的或新建的模型对象实例,还会返回这个对象实例是否是刚刚创建的。

obj, created = Article.objects.get_or_create(title="My first article", body="My first article body")

注意: 对Django自带auth模块中的User模型操作,比如创建新的用户时,请用create_user方法。该方法会将密码自动加Hash存储到数据库中, 如下所示:

from django.contrib.auth.models import User
user = User.objects.create_user(username='john, email='john@gmail.com',password='somepwd')

方法三:bulk_create方法

在Django中向数据库中插入多条数据时,每使用save或create方法保存一条就会执行一次SQL。而Django提供的bulk_create方法可以一次SQL添加多条数据,效率要高很多,如下所示:

# 内存生成多个对象实例
articles  = [Article(title="title1", body="body1"), Article(title="title2", body="body2"), Article(title="title3", body="body3")]

# 执行一次SQL插入数据
Article.objects.bulk_create(articles)

删即从数据表中删除一个已有条目。Django也允许同时删除一条或多条数据。

删除单条数据

# 删除第5篇文章
Article.objects.get(pk=5).delete() 

删除部分数据

# 删除标题含有python的文章
Article.objects.filter(title__icontains="python").delete() 

删除所有数据

# 慎用
Article.objects.all().delete() 

改既可以用save方法,也可以用update方法。其区别在于save方法不仅可以更新数据中现有对象数据,还可以创建新的对象。而update方法只能用于更新已有对象数据。一般来说,如果要同时更新多个对象数据,用update方法或bulk_update方法更合适。

方法一: save方法

article = Article.objects.get(id=1)
article.title = "New article title"
article.save()

方法二:update方法更新单篇文章

article = Article.objects.get(id=1).update(title='new title')

方法三:update方法同时更新多篇文章

# 更新所有文章标题
article = Article.objects.filter(title__icontains='python').update(title='Django')

方法四: bulk_update方法

bulk_create方法类似,Django还提供了bulk_update方法可以对数据库里的数据进行批量更新。

查主要使用get, filter及exclude方法,而且这些方法是可以联用的。

查询所有数据

# QuerySet类型,实例对象列表
Article.objects.all() 
# 字典列表
Article.objects.all().values() 
# 只获取title-字典形式
Article.objects.all().values('title') 
# 只获取title列表- 元组形式,只有value,没有key
Article.objects.all().values_list('title')
# 只获取title列表,只有value
Article.objects.all().values_list('title', flat=True)

查询单条数据

article = Article.objects.get(id=11)

当上述查询有个问题,如果id不存在,会抛出错误。还有一种方式是使用filter方法, 这样即使id不存在也不会报错。

article = Article.objects.filter(id=1).first()

一个更好的方式是使用Django提供的get_object_or_404方法,如下所示:

from django.shortcuts import get_object_or_404 
article = get_object_or_404(Article, pk=1) 

查询多条数据

按大于、小于及不等于查询

# gte:大于等于,lte:小于等于
articles = Article.objects.filter(id__gte=2).filter(id__lte=11)
# 不等于
articles = Article.objects.exclude(id=10)

按范围查询

# 按范围查询,in或者range
articles = Article.objects.filter(id__range=[2, 11])
articles = Article.objects.filter(id__in=[3, 6,9])

字符串模糊查询

#标题包含python,若忽略大小写使用icontains
articles = Article.objects.filter(title__contains='python')

#标题以python开头,若忽略大小写使用istartswith
articles = Article.objects.filter(title__startswith='python')

#标题是python结尾的,若忽略大小写使用__iendswith
articles = Article.objects.filter(title__endswith='python')

按日期时间查询

# 查询2021年发表的文章
Article.objects.filter(created__year=2021)

# 查询2021年3月19日发表的文章
import datetime
Article.objects.filter(created__date=datetime.date(2021,3,19))

# 查询2021年1月1日以后发表的文章
Article.objects.filter(created__gt=datetime.date(2021, 1, 1))

# 与当前时间相比,查询即将发表的文章
from django.utils import timezone
Article.objects.filter(created__gt=timezone.now())

# 按绝对时间范围查询,查询2021年1月1日到6月30日发表文章
article = Aritlce.objects.filter(created__gte=datetime.date(2021, 1, 1),
pub_date__lte=datetime.date(2021, 6, 30))

# 按相对时间范围查询,用range查询3月1日以后30天内发表文章
startdate = datetime.date(2021, 3, 1)
enddate = startdate + datetime.timedelta(days=30)
Article.objects.filter(pub_date__range=[startdate, enddate])

切片、排序、去重

# 切片
articles = Article.objects.filter(created__year=2021)[:5]

# 排序:created正序,-表示逆序
articles = Article.objects.all().order_by('-created')

# 去重
Article.objects.filter(title__icontains='python').distinct()

高级Q和F方法

Q方法

有时候我们需要执行or逻辑的条件查询,这时使用Q方法就可以了,它可以连接多个查询条件。Q对象前面加~可以表示否定。

from django.models import Q
# 查询标题含有python或Django的文章
article = Article.objects.filter(Q(title__icontains='python')|Q(title__icontains='django'))
# 查询标题含有python,不含有Django的文章
article = Article.objects.filter(Q(title__icontains='python')|~Q(title__icontains='django'))

F方法

使用F()方法可以实现基于自身字段值来过滤一组对象,它还支持加、减、乘、除、取模和幂运算等算术操作。

from django.db.models import F
Article.objects.filter(n_commnets__gt=F('n_pingbacks'))
Article.objects.filter(n_comments__gt=F('n_pingbacks') * 2)

Django 路由配置URLConf

URLconf是如何工作的?

假如我们有一个blog的博客应用,你需要编写两个视图函数,一个用于展示文章列表,一个用于展示文章详情,你的urls.pyviews.py正常情况下应如下所示:

# blog/urls.py
from django.urls import path
from . import views
 
urlpatterns = [
    path('blog/', views.index),
    path('blog/articles//', views.article_detail),
]
 
# blog/views.py
def index(request):
    # 展示所有文章
   
def article_detail(request, id):
    # 展示某篇具体文章

那么上面这段代码是如何工作的呢?

  • 当用户在浏览器输入/blog/时,URL收到请求后会调用视图views.py里的index方法,展示所有文章
  • 当用户在浏览器输入/blog/article//时,URL不仅调用了views.py里的article_detail方法,而且还把参数文章id通过<>括号的形式传递给了视图。int这里代表只传递整数,传递的参数名字是id。

在上述代码中,我们通过urlpatterns列表的url-视图映射关系列表起了决定性作用,起到了任务调度的作用。

注意:注意当你配置URL时,别忘了把你的应用(blog)的urls加入到项目的URL配置里(mysite/urls.py), 如下图所示:

from django.urls import include, path

urlpatterns = [
    path('', include('blog.urls')),
    ...
]

path和re_path方法

写个URL很简单,但如何通过URL把参数传递给给视图view是个技术活。Django提供了两种设计URL的方法: pathre_path,它们均支持向视图函数或类传递参数。path是正常参数传递,re_path是采用正则表达式regex匹配。pathre_path传递参数方式如下:

  • path方法:采用双尖括号<变量类型:变量名><变量名>传递,例如,
  • re_path方法: 采用命名组(?P<变量名>表达式)的方式传递参数。

下例中,我们分别以pathre_path 定以了两个urls,它们是等效的,把文章的id(整数类型)传递给了视图。re_path里引号前面的小写r表示引号里为正则表达式, ^代表开头,$代表以结尾,\d+代表正整数。

# blog/urls.py
from django.urls import path, re_path
from . import views
 
urlpatterns = [
    path('blog/articles//', views.article_detail, name = 'article_detail'),
    re_path(r'^blog/articles/(?P\d+)/$', views.article_detail, name='article_detail'),
]
 
# blog/views.py
def article_detail(request, id):
    # 展示某篇文章

在使用pathre_path方法设计urls需注意:

  • url中的参数名要用尖括号,而不是圆括号;
  • 匹配模式的最开头不需要添加斜杠/,但建议以斜杠结尾;
  • 使用re_path时不一定总是以$结尾,有时不能加。比如下例中把blog.urls通过re_path加入到项目urls中时就不能以$结尾,因为这里的blog/并不是完整的url,只是一个开头而已。
from django.urls import include, re_path

urlpatterns = [
    re_path(r'^blog/', include('blog.urls')),
    ...
]

更多URL配置示例

path支持匹配的数据类型只有str,int, slug, uuid四种。一般来说re_path更强大,但写起来更复杂一些,我们来看看更多案例。

# 示例一,PATH
from django.urls import path
from . import views

urlpatterns = [
    path('articles/2003/', views.special_case_2003),
    path('articles//', views.year_archive),
    path('articles///', views.month_archive),
    path('articles////', views.article_detail),
]

# 示例二:RE_PATH,与上例等同
from django.urls import path, re_path
from . import views

urlpatterns = [
    path('articles/2003/', views.special_case_2003),
    re_path(r'^articles/(?P[0-9]{4})/$', views.year_archive),
    re_path(r'^articles/(?P[0-9]{4})/(?P[0-9]{2})/$', views.month_archive),
    re_path(r'^articles/(?P[0-9]{4})/(?P[0-9]{2})/(?P[\w-]+)/$', views.article_detail),
]

同样以博客为例,如果你希望设计不同的urls分别对应负责增删改查操作的视图函数或类,你可以按如下设计:

# blog/urls.py
from django.urls import path, re_path
from . import views

# app_name = 'blog' # 命名空间,后面会用到。
urlpatterns = [
    path('blog/articles/', views.article_list, name = 'article_list'),
    path('blog/articles/create/', views.article_create, name = 'article_create'),
    path('blog/articles//', views.article_detail, name = 'article_detail'),
    path('blog/articles//update/', views.article_update, name = 'article_update'),
    path('blog/articles//delete/', views.article_update, name = 'article_delete'),
]

URL的命名及reverse()方法

你注意到没?在上述博客示例中,我们中还给每个URL取了一个名字,比如 article_listarticle_create。这个名字大有用处,相当于给每个URL取了个全局变量的名字。它可以让你能够在Django的任意处,尤其是模板内显式地引用它。假设你需要在模板中通过链接指向一篇具体文章,下面那种方式更好?

使用命名URL

{% for article in articles %}
    {{ article.title }}
{% endfor %}

url是个模板标签,其作用是对命名的url进行方向解析,动态生成链接。

注意:命名的url里有几个参数,使用url模板标签反向生成动态链接时,就需要向它传递几个参数。比如我们的命名urlarticle_detail里有整数型id这个参数,我们在模板中还需要传递article.id

硬编码URL - 不建议

{% for article in articles %}
    {{ article.title }}
{% endfor %}

如果你还没意识到方法1的好处,那么想想吧,假设老板让你把全部模板链接由blog/articles/id改为blog/article/id, 那种方法更快?更改所有html文件里的链接,还是只改URL配置里的一个字母?

那么问题来了。假设不同的app(比如news和blog)里都有article_detail这个命名URL, 我们怎么避免解析冲突呢? 这时我们只需要在blog/urls.py加上app_name='blog'这个命名空间即可,然后在模板中以blog:article_detail使用即可。

{% for article in articles %}
    {{ article.title }}
{% endfor %}

可惜的是命名的URL一般只在模板里使用,不能直接在视图里使用。如果我们有了命名的URL,我们如何把它转化成常规的URL在视图里使用呢?

Django提供的reverse()方法很容易实现这点。它在视图中可以对命名urls进行反向解析,生成动态链接。

from django.urls import reverse

# output blog/articles/id
reverse('blog:article_detail', args=[id]) 

URL指向基于类的视图(View)

目前pathre_path都只能指向视图view里的一个函数或方法,而不能直接指向一个基于类的视图(Class based view)。Django提供了一个额外as_view()方法,可以将一个类伪装成方法。这点在当你使用Django自带的类视图或自定义的类视图时非常重要。

具体使用方式如下:

# blog/urls.py
from django.urls import path, re_path
from . import views
 
urlpatterns = [
    # path('blog/articles/', views.article_list, name = 'article_list'),
    path('blog/articles/', views.ArticleList.as_view(), name='article_list'),
]
 
# View (in blog/views.py)
from django.views.generic import ListView
from .views import Article
 
class ArticleList(ListView):
    queryset = Article.objects.filter(date__lte=timezone.now()).order_by('date')[:5]
    context_object_name = 'article_list‘
    template_name = 'blog/article_list.html'

如果你对基于类的视图还比较困惑,没有关系,我们后面会做详细介绍。

通过URL传递额外的参数

在你配置URL时,你还可以通过字典的形式传递额外的参数给视图, 而不用把这个参数写在链接里。如下面案例所示:

# blog/urls.py
from django.urls import path, re_path
from . import views
 
urlpatterns = [
    path('', views.ArticleList.as_view(), name='article_list', {'blog_id': 3}),
]

Django函数视图及通用类视图

什么是视图(View)及其工作原理

Django的Web开发也遵循经典软件设计MVC模式开发的。View (视图) 主要根据用户的请求返回数据,用来展示用户可以看到的内容(比如网页,图片),也可以用来处理用户提交的数据,比如保存到数据库中。Django的视图(views.py)通常和URL路由(URLconf)一起工作的。服务器在收到用户通过浏览器发来的请求后,会根据用户请求的url地址和urls.py里配置的url-视图映射关系,去执行相应视图函数或视图类,从而返回给客户端响应数据。

我们先看一个最简单的函数视图。当用户发来一个请求request时,我们通过HttpResponse打印出Hello, World!

# views.py
from django.http import HttpResponse

def index(request):
    return HttpResponse("Hello, World!")

提示:每个视图函数的第一个默认参数都必需是request, 它是一个全局变量。Django把每个用户请求封装成了request对象,它包含里当前请求的所有信息,比如请求路径request.path, 当前用户request.user以及用户通过POST提交的数据request.POST

上面例子过于简单。在实际Web开发过程中,我们的View不仅要负责从数据库读写数据,还需要指定显示内容的模板,并提供模板渲染页面所需的内容对象(context object)。接下来我们要看一个更接近现实的案例。

接近现实的函数视图

我们依然以blog为例,需要编写两个视图函数,一个用于展示文章列表,一个用于展示文章详情,你的urls.pyviews.py正常情况下应如下所示:

# blog/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('blog/', views.index, name='index'),
    path('blog/articles//', views.article_detail, name='article_detail'),
]

# blog/views.py
from django.shortcuts import render, get_object_or_404
from .models import Article

# 展示所有文章
def index(request):
    latest_articles = Article.objects.all().order_by('-pub_date')
    return render(request, 'blog/article_list.html', {"latest_articles": latest_articles})

# 展示所有文章
def article_detail(request, id):
    article = get_object_or_404(Article, pk=id)
    return render(request, 'blog/article_detail.html', {"article": article})

那么上面这段代码是如何工作的呢?

  • 当用户在浏览器输入/blog/时,URL收到请求后会调用视图views.py里的index函数,展示所有文章。
  • 当用户在浏览器输入/blog/article/5/时,URL不仅调用了views.py里的article_detail函数,而且还把参数文章id通过括号的形式传递给了视图里的article_detail函数。。
  • views.py里的index函数先提取要展示的数据对象列表latest_articles, 然后通过render方法传递给模板blog/article_list.html.。
  • views.py里的article_detail方法先通过get_object_or_404方法和id调取某篇具体的文章对象article,然后通过render方法传递给模板blog/article_detail.html显示。

在本例中,我们使用了视图函数里常用的2个便捷方法render()get_object_or_404()

  • render方法有4个参数。第一个是request, 第二个是模板的名称和位置,第三个是需要传递给模板的内容, 也被称为context object。第四个参数是可选参数content_type(内容类型), 我们什么也没写。
  • get_object_or_404方法第一个参数是模型Models或数据集queryset的名字,第二个参数是需要满足的条件(比如pk = id, title = ‘python’)。当需要获取的对象不存在时,给方法会自动返回Http 404错误。

下图是模板的代码。模板可以直接调用通过视图传递过来的内容。

# blog/article_list.html
 
{% block content %}
{% for article in latest_articles %}
     {{ article.title }}
     {{ article.pub_date }}
{% endfor %}
{% endblock %}

# blog/article_detail.html
{% block content %}
   {{ article.title }}
   {{ article.pub_date }}
   {{ article.body }}
{% endblock %}

更复杂的案例: 视图处理用户提交的数据

视图View不仅用于确定给客户展示什么内容,以什么形式展示,而且也用来处理用户通过表单提交的数据。我们再来看两个创建和修改文章的视图函数article_createarticle_update,看看它们是如何处理用户通过表单POST提交过来的数据。

 from django.shortcuts import render, redirect, get_object_or_404
 from django.urls import reverse
 from .models import Article
 from .forms import ArticleForm
 
 # 创建文章
 def article_create(request):
     # 如果用户通过POST提交,通过request.POST获取提交数据
     if request.method == "POST":
         # 将用户提交数据与ArticleForm表单绑定
         form = ArticleForm(request.POST)
         # 表单验证,如果表单有效,将数据存入数据库
         if form.is_valid():
             form.save()
             # 创建成功,跳转到文章列表
             return redirect(reverse("blog:article_list"))
     else:
         # 否则空表单
         form = ArticleForm()
     return render(request, "blog/article_form.html", { "form": form, })

 # 更新文章
 def article_update(request, pk):
     # 从url里获取单篇文章的id值,然后查询数据库获得单个对象实例
     article = get_object_or_404(Article, pk=id)
     
     # 如果用户通过POST提交,通过request.POST获取提交数据
     if request.method == 'POST':
         # 将用户提交数据与ArticleForm表单绑定,进行验证
         form = ArticleForm(instance=article, data=request.POST)
         if form.is_valid():
             form.save()
             # 更新成功,跳转到文章详情
             return redirect(reverse("blog:article_detail", args=[pk,]))
     else:
         # 否则用实例生成表单
         form = ArticleForm(instance=article)

     return render(request, "blog/article_form.html", { "form": form, "object": article})

我们给每一行代码添加了说明。值得一提的是在创建和更新文章时我们向模板传递了form这个变量,模板会根据我们自定义的Form类自动生成表单。我们还使用了自定义的Form类对用户提交的数据(request.POST)进行验证,并将通过验证的数据存入数据库。

这里所使用ArticleForm实际上是非常简单的,仅包含了titlebody两个字段。

#blog/forms.py
from .models import Article
from django import forms

class ArticleForm(forms.ModelForm):
     class Meta:
         model = Article
         fields = ['title', 'body']

基于函数的视图和基于类的视图

Django提供了两种编写视图的方式: 基于函数的视图(Function Base View, FBV)和基于类的视图(Class Based View, CBV)。前面案例中的indexarticle_detailarticle_update的方法都是基于函数的视图。函数视图的优点是比较直接,容易读者理解, 缺点是不便于继承和重用。

基于类的视图以class定义,而不是函数视图的def定义。使用类视图后可以将视图对应的不同请求方式以类中的不同方法来区别定义(get方法处理GET请求,post方法处理POST请求),相对于函数视图逻辑更清晰,代码也有更高的复用性。

from django.views.generic import View

class MyClassView(View):
    """类视图"""
    def get(self, request):
        """处理GET请求"""
        return render(request, 'register.html')

    def post(self, request):
        """处理POST请求"""
        return ...

注意:在URL配置文件中使用类视图时,需要使用as_view()将其伪装成方法:

# blog/urls.py
from django.urls import path, re_path
from . import views
 
urlpatterns = [
    path('', views.MyClassView.as_view()),
]

Django通用类视图

在实际Web开发过程中,我们对不同的数据或模型总是反复进行以下同样的操作,使用通用的类视图可以大大简化我们的代码量。

  • 展示对象列表(比如所有用户,所有文章)
  • 查看某个对象的详细信息(比如用户资料,比如文章详情)
  • 通过表单创建某个对象(比如创建用户,新建文章)
  • 通过表单更新某个对象信息(比如修改密码,修改文字内容)
  • 用户填写表单提交后转到某个完成页面
  • 删除某个对象

Django提供了很多通用的基于类的视图,来帮我们简化视图的编写。这些View与上述操作的对应关系如下:

  • 展示对象列表(比如所有用户,所有文章)- ListView
  • 展示某个对象的详细信息(比如用户资料,比如文章详情) - DetailView
  • 通过表单创建某个对象(比如创建用户,新建文章)- CreateView
  • 通过表单更新某个对象信息(比如修改密码,修改文字内容)- UpdateView
  • 用户填写表单后转到某个完成页面 - FormView
  • 删除某个对象 - DeleteView

上述常用通用视图一共有6个,前2个属于展示类视图(Display view), 后面4个属于编辑类视图(Edit view)。下面我们就来看下这些通用视图是如何工作的,如何简化我们代码的。

注意:如果你要使用Edit view,请务必在模型里里定义get_absolute_url()方法,否则会出现错误。这是因为通用视图在对一个对象完成编辑后,需要一个返回链接。get_absolute_url()可以为某个对象生成独一无二的url。

ListView

ListView用来展示一个对象的列表。它只需要一个参数模型名称即可。比如我们希望展示所有文章列表,我们的views.py可以简化为:

# Create your views here.
from django.views.generic import ListView
from .models import Article

class IndexView(ListView):
    model = Article

上述代码等同于:

# 展示所有文章
def index(request):
    queryset = Article.objects.all()
    return render(request, 'blog/article_list.html', {"article_list": queryset})

尽管我们只写了一行model = Article, ListView实际上在背后做了很多事情:

  • 提取了需要显示的对象列表或数据集queryset: Article.objects.all()
  • 指定了用来显示对象列表的模板名称: 默认app_name/model_name_list.html, 即blog/article_list.html.
  • 指定了内容对象名称(context object name):默认值object_list

你或许已经注意到了2个问题:需要显示的文章对象列表并没有按发布时间逆序排列,默认内容对象名称object_list也不友好。或许你也不喜欢默认的模板名字,还希望通过这个视图给模板传递额外的内容(比如现在的时间)。你可以轻易地通过重写queryset, template_namecontext_object_name来完成ListView的自定义。

如果你还需要传递模型以外的内容,比如现在的时间,你还可以通过重写get_context_data方法传递额外的参数或内容。

# Create your views here.
from django.views.generic import ListView
from .models import Article
from django.utils import timezone

class IndexView(ListView):

    queryset = Article.objects.all().order_by("-pub_date")
    template_name = 'blog/article_list.html'
    context_object_name = 'latest_articles'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['now'] = timezone.now()
        return context

如果上述的queryset还不能满足你的要求,比如你希望一个用户只看到自己发表的文章清单,你可以通过更具体的get_queryset()方法来返回一个需要显示的对象列表。

# Create your views here.
from django.views.generic import ListView
from .models import Article
from django.utils import timezone

class IndexView(ListView):

    template_name = 'blog/article_list.html'
    context_object_name = 'latest_articles'

    def get_queryset(self):
        return Article.objects.filter(author=self.request.user).order_by('-pub_date')

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['now'] = timezone.now()
        return context

DetailView

DetailView用来展示一个具体对象的详细信息。它需要URL传递某个对象的具体参数(如id, pk, slug值)。本例中用来展示某篇文章详细内容的view可以简写为:

# Create your views here.
from django.views.generic import DetailView
from .models import Article

class ArticleDetailView(DetailView):
    model = Article

DetailView默认的模板是app/model_name_detail.html,默认的内容对象名字context_object_name是model_name。本例中默认模板是blog/article_detail.html, 默认对象名字是article, 在模板里可通过 ``获取文章标题。

你同样可以通过重写queryset, template_namecontext_object_name来完成DetailView的自定义。你还可以通过重写get_context_data方法传递额外的参数或内容。如果你指定了queryset, 那么返回的object是queryset.get(pk = id), 而不是model.objects.get(pk = id)。

# Create your views here.
from django.views.generic import ListView,DetailView
from .models import Article
from django.utils import timezone

class ArticleDetailView(DetailView):

    queryset = Article.objects.all().order_by("-pub_date") # 一般不写
    template_name = 'blog/article_detail.html'
    context_object_name = 'article'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['now'] = timezone.now()
        return context

CreateView

CreateView一般通过某个表单创建某个对象,通常完成后会转到对象列表。比如一个最简单的文章创建CreateView可以写成:

from django.views.generic.edit import CreateView
from .models import Article

class ArticleCreateView(CreateView):
    model = Article
    fields = ['title', 'body',]

CreateView默认的模板是model_name_form.html,article_form.html。这里CreateView还会根据fields自动生成表单字段。默认的context_object_name是form。模板代码如下图所示:

# blog/article_form.html

{% csrf_token %} {{ form.as_p }}

如果你不想使用默认的模板和默认的表单,你可以通过重写template_nameform_class来完成CreateView的自定义。

对于CreateView, 重写它的form_valid方法不是必需,但很有用。当用户提交的数据是有效的时候,执行该方法。你可以通过定义此方法做些别的事情,比如发送邮件,存取额外的数据。

from django.views.generic.edit import CreateView
from .models import Article
from .forms import ArticleCreateForm

class ArticleCreateView(CreateView):
    model = Article
    template_name = 'blog/article_create_form.html'
    form_class = ArticleCreateForm

    def form_valid(self, form):
       form.do_sth()
       return super(ArticleCreateView, self).form_valid(form)

form_valid方法一个常见用途就是就是将创建对象的用户与model里的user结合(需要用户先登录再提交)。见下面例子。

from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.edit import CreateView
from .models import Article

class ArticleCreate(LoginRequiredMixin, CreateView):
    model = Article
    fields = ['title', 'body']

    def form_valid(self, form):
        form.instance.author = self.request.user
        return super().form_valid(form)

UpdateView

UpdateView一般通过某个表单更新现有对象的信息,更新完成后会转到对象详细信息页面。它需要URL提供访问某个对象的具体参数(如pk, slug值)。比如一个最简单的文章更新的UpdateView如下所示。

from django.views.generic.edit import UpdateView
from .models import Article

class ArticleUpdateView(UpdateView):
    model = Article
    fields = ['title', 'body',]

UpdateView和CreateView很类似,比如默认模板都是model_name_form.html, 因此它们可以共用一个模板。但是区别有两点:

  • CreateView显示的表单是空表单,UpdateView中的表单会显示现有对象的数据。
  • 用户提交表单后,CreateView转向对象列表,UpdateView转向对象详细信息页面。

你可以通过重写template_nameform_class来完成UpdateView的自定义。

  • 本例中默认的模板是article_form.html, 你可以改为article_update_form.html
  • 虽然form_valid方法不是必需,但很有用。当用户提交的数据是有效的时候,你可以通过定义此方法做些别的事情,比如发送邮件,存取额外的数据。
from django.views.generic.edit import UpdateView
from .models import Article
from .forms import ArticleUpdateForm

class ArticleUpdateView(UpdateView):
    model = Article
    template_name = 'blog/article_update_form.html'
    form_class = ArticleUpdateForm

    def form_valid(self, form):
       form.do_sth()
       return super(ArticleUpdateView, self).form_valid(form)

另一个进行UpdateView的常用自定义方法是get_object方法。比如你希望一个用户只能编辑自己发表的文章对象。当一个用户尝试编辑别人的文章时,返回http 404错误。这时候你可以通过更具体的get_object()方法来返回一个更具体的对象。代码如下:

from django.views.generic.edit import UpdateView
from .models import Article
from .forms import ArticleUpdateForm

class ArticleUpdateView(UpdateView):
    model = Article
    template_name = 'blog/article_update_form.html'
    form_class = ArticleUpdateForm
 
    def get_object(self, queryset=None):
        obj = super().get_object(queryset=queryset)
        if obj.author != self.request.user:
            raise Http404()
        return obj

FormView

FormView一般用来展示某个表单,而不是用于创建或更新某个模型对象。当用户输入信息未通过表单验证,显示错误信息。当用户输入信息通过表单验证提交后,跳到其它页面。使用FormView一般需要定义template_name, form_classsuccess_url.

见下面代码。

# views.py - Use FormView
from myapp.forms import ContactForm
from django.views.generic.edit import FormView

class ContactView(FormView):
    template_name = 'contact.html'
    form_class = ContactForm
    success_url = '/thanks/'

    def form_valid(self, form):
        # This method is called when valid form data has been POSTed.
        # It should return an HttpResponse.
        form.send_email()
        return super().form_valid(form)

DeleteView

DeleteView一般用来删除某个具体对象。它要求用户点击确认后再删除一个对象。使用这个通用视图,你需要定义模型的名称model和成功删除对象后的返回的URL。默认模板是myapp/model_confirm_delete.html。默认内容对象名字是model_name。本例中为article。

本例使用了默认的模板blog/article_confirm_delete.html,删除文章后通过reverse_lazy方法返回到index页面。

from django.urls import reverse_lazy
from django.views.generic.edit import DeleteView
from .models import Article

class ArticleDelete(DeleteView):
    model = Article
    success_url = reverse_lazy('index')

模板内容如下:

# blog/article_confirm_delete.html

<form method="post">{% csrf_token %}
    <p>Are you sure you want to delete "{{ article }}"?</p>
    <input type="submit" value="Confirm" />
</form>

Django模板语言、常用标签与过滤器

变量(variables)

模板中的变量一般使用双括号``包围。在模板中你可以使用.获取一个变量(字典、对象和列表)的属性,如:

    {{ my_dict.key }}  
    {{ my_object.attribute }}
    {{ my_list.0 }}

标签 (tags)

Django的标签(tags)用双%括号包裹,常用Django标签包括:

# 内容块
{% block content %} 代码块 {% endblock %}

# 防csrf攻击
{% csrf_token %} 表单专用

# for循环
    {% for athlete in athlete_list %}
  • {{ forloop.counter }} - {{ athlete.name }}
  • {% empty %}
  • Sorry, no athletes。
  • {% endfor %}
# if判断 {% if title == "python" %} Say python. {% elif title == "django"} Say django. {% elif title == "django"} Say django. {% else %} Say java. {% endif %} # url反向解析 {% url 'blog:article_detail' article.id %} # with {% with total=business.employees.count %} {{ total }} employee{{ total|pluralize }} {% endwith %} # 载入模板标签过滤器 {% load sometags_library %} # 模板包含 {% include "header.html" %} # 模板继承 {% include "base.html" %} # 获取当前时间now {% now "jS F Y H:i" %}

过滤器(filters)

在模板中你可以使用过滤器(filter)来改变变量在模板中的显示形式。比如{{ article.title | lower }}中lower过滤器可以让文章的标题转化为小写。Django的模板提供了许多内置过滤器,你可以直接使用,非常方便。

过滤器 例子
lower, upper {{ article.title | lower }} 大小写
length {{ name | length }} 长度
default {{ value | default: “0” }} 默认值
date {{ picture.date | date:”Y-m-j “ }} 日期格式
dicsort {{ value | dicsort: “name” }} 字典排序
escape {{ title | escape }} 转义
filesizeformat {{ file | filesizeformat }} 文件大小
first, last {{ list | first }} 首或尾
floatformat {{ value | floatformat }} 浮点格式
get_digit {{ value | get_digit }} 位数
join {{ list | join: “,” }} 字符连接
make_list {{ value | make_list }} 转字符串
pluralize {{ number | pluralize }} 复数
random {{ list | random }} 随机
slice {{ list | slice: “:2” }} 切
slugify {{ title | slugify }} 转为slug
striptags {{ body | striptags }} 去除tags
time {{ value | time: “H:i” }} 时间格式
timesince {{ pub_date | timesince: given_date }}
truncatechars {{ title | truncatechars: 10 }}
truncatewords {{ title | truncatewords: 2 }}
truncatechars_html {{ title | truncatechars_html: 2 }}
urlencode {{ path | urlencode }} URL转义
wordcount {{ body | wordcount }} 单词字数

模板文件的放置路径

模板文件有两种, 一种是属于整个项目(project)的模板,一种是属于某个应用(app)的模板。模板文件的放置路径必需正确, 否则Django找不到模板容易出现TemplateDoesNotExist的错误。

项目模板

属于项目的模板文件路径一般是项目根目录下的templates文件夹。除此以外, 你还需要在settings.py种显示地将模板目录设置为BASE_DIR目录下的templates文件夹。

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(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',
            ],
        },
    },
]

应用模板

属于单个应用的模板文件路径一般是app目录下的app/templates/app文件夹, 这样做的好处是可以避免模板命名冲突。

下图以博客项目为例展示了项目模板文件和应用模板文件正确的存放路径。

myproject/ # 项目名
    manage.py
    myproject/
        __init__.py
        urls.py
        wsgi.py
        settings.py
    blog/ # 应用名
        __init__.py
        models.py
        managers.py
        views.py
        urls.py
        templates/ # 应用模板文件
            blog/
                base.html
                list.html
                detail.html
     templates/ # 项目模板文件
         base.html
         index.html
     requirements/
         base.txt
         dev.txt
         test.txt
         prod.txt

对于上面这个项目布局,在使用render方法指定渲染模板时,无需给出完整的路径,只给出相对于templates的路径即可,比如:

# 指定项目模板
return render(request, "index.html", { "msg": "hello world!",})

# 指定应用模板
return render(request, "blog/list.html", { "articles": articles,})

模板的继承

Django支持模板的继承。你需要使用extends标签。在下面经典模板继承案例中,index.html继承了base.html的布局,如sidebar和footer,但是content模块会替换掉base.html中的content模块。

# base.html

{% block sidebar %}
{% endblock %}

{% block content %}
{% endblock %}

{% block footer %}
{% endblock %}

# index.html
{% extends "base.html" %}
{% block content %}
     {{ some code }}
{% endblock }

extends标签支持相对路径,这就意味着当文件夹结构如下所示时:

dir1/
    index.html
    base2.html
    my/
        base3.html
base1.html

模板index.html使用以下继承方式都是可以的。.号代表当前目录, ..代表上层目录.

{% extends "./base2.html" %}
{% extends "../base1.html" %}
{% extends "./my/base3.html" %}

模板文件中加载静态文件

在模板文件我们要经常需要加载静态文件如css文件和js文件,操作方式如下所示:

第一步: 在myproject/settings.py设置静态文件目录STATIC_URL, 默认为static, 其作用是告诉Django静态文件会存放在各app下的static目录里。同时你还需确保django.contrib.staticfiles已经加入到INSTALLED_APPS里去了.

STATIC_URL = '/static/'

第二步: 先在你的app目录下新建一个static文件夹,然后再建一个app子目录,放入你需要的静态文件, 此时静态文件路径变为app/static/app/custom.css或则app/static/app/main.js

如果你需要使用的静态文件不属于某个app,而属于整个项目project,那么你还可以通过STATICFILES_DIRS定义静态文件文件夹列表。假设属于整个项目的静态文件放在根目录下的static文件夹和/var/www/static/里,那么myproject/settings.py可以加入下面两行代码:

STATICFILES_DIRS = [
    BASE_DIR / "static",
    '/var/www/static/',
]

第三步:在你的html模板里使用static标签,首先得载入这个标签, 如下所示:

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<title>{% block title %} Django Web Applications {% endblock %} </title>
    <link rel="stylesheet" href="{% static 'app/custom.css' %}">
    <script type='text/javascript' src="{% static 'app/main.js' %}"></script>  
</head>

注意:load static需要放在html的头部位置。如果extends标签和load同时存在,extends需要放在最上面,然后再放load等标签。

Django表单设计、验证与使用

自定义表单类

Django提供了两种自定义表单的方式:继承Form类和ModelForm类。前者你需要自定义表单中的字段,后者可以根据Django模型自动生成表单,如下所示:

# app/forms.py
# 自定义表单字段
from django import forms
from .models import Contact

class ContactForm1(forms.Form):
    name = forms.CharField(label="Your Name", max_length=255)
    email = forms.EmailField(label="Email address")

# 根据模型创建
class ContactForm2(forms.ModelForm):
    
    class Meta:
        model = Contact
        fields = ('name', 'email',)

注意:Django模型里用verbose_name来给字段添加一个别名或描述, 而表单用的是label

自定义的表单类一般位于app目录下的forms.py,这样方便集中管理表单。如果要使用上述表单,我们可以在视图里views.py里把它们像模型一样import进来直接使用。

自定义字段错误信息

对于每个字段你可以设置其是否为必需,最大长度和最小长度。你还可以针对每个字段自定义验证错误信息,见下面代码。

from django import forms

class LoginForm(forms.Form):  
    username = forms.CharField(
        required=True,
        max_length=20,
        min_length=6,
        error_messages={
            'required': '用户名不能为空',
            'max_length': '用户名长度不得超过20个字符',
            'min_length': '用户名长度不得少于6个字符',
        }
    )
    password = forms.CharField(
        required=True,
        max_length=20,
        min_length=6,
        error_messages={
            'required': '密码不能为空',
            'max_length': '密码长度不得超过20个字符',
            'min_length': '密码长度不得少于6个字符',
        }
    )

对于基继承ModelForm类的表单, 我们可以在Meta选项下widget中来自定义错误信息,如下面代码所示:

from django.forms import ModelForm, Textarea
from myapp.models import Author

class AuthorForm(ModelForm):
    class Meta:
        model = Author
        fields = ('name', 'title', 'birth_date')
        widgets = {
            'name': Textarea(attrs={'cols': 80, 'rows': 20}),  # 关键是这一行
        }
        labels = {
            'name': 'Author',
        }
        help_texts = {
            'name': 'Some useful help text.',
        }
        error_messages = {
            'name': {
                'max_length': "This writer's name is too long.",
            },
        }

自定义表单输入widget

Django表单的每个字段你都可以选择你喜欢的输入widget,比如多选,复选框。你还可以定义每个widget的css属性。如果你不指定,Django会使用默认的widget,有时比较丑。

比如下面这段代码定义了表单姓名字段的输入控件为Textarea,还指定了其样式css。

from django import forms

class ContactForm(forms.Form):
    name = forms.CharField(
        max_length=255,
        widget=forms.Textarea(
            attrs={'class': 'custom'},
        ),
    )

设置widget可以是你的表单大大美化,方便用户选择输入。比如下面案例里对年份使用了SelectDateWidget,对课程使用RadioSelect, 颜色则使用了复选框CheckboxSelectMultiple

from django import forms

BIRTH_YEAR_CHOICES = ('1980', '1981', '1982')
COLORS_CHOICES = (
    ('blue', 'Blue'),
    ('green', 'Green'),
    ('black', 'Black'),
)

class SimpleForm(forms.Form):
    birth_year = forms.DateField(
        widget=forms.SelectDateWidget(years=list(BIRTH_YEAR_CHOICES))
    )
    favorite_colors = forms.MultipleChoiceField(
        required=False,
        widget=forms.CheckboxSelectMultiple,
        choices=list(COLORS_CHOICES),
    )

表单实例化与初始化

定义好一个表单类后,你就可以对其进行实例化或初始化。下面方法可以实例化一个空表单,但里面没有任何数据,可以通过 {{ form }}在模板中渲染。

form = ContactForm() # 空表单

有时我们需要对表单设置一些初始数据,我们可以通过initial方法或设置default_data,如下所示。

# initial方法初始化
form = ContactForm(
    initial={
        'name': 'First and Last Name',
    },)

# default_data默认值
default_data = {'name': 'John', 'email': '[email protected]', }
form = ContactForm(default_data)

用户提交的数据可以通过以下方法与表单结合,生成与数据结合过的表单(Bound forms)。Django只能对Bound forms进行验证。

form = ContactForm(data=request.POST, files=request.FILES)

其编辑修改类应用场景中,我们还要给表单提供现有对象实例的数据,而不是渲染一张空表单,这时我们可这么做。该方法仅适用于由模型创建的ModelForm,而不适用于自定义的表单l。

contact = Contact.objects.get(id=1)
form =  ContactForm(instance = contact, data=request.POST)

表单的使用

Django的视图一般会将实例化/初始化后的表单以form变量传递给模板,在模板文件中我们可以通过{{ form.as_p }}, {{ form.as_li }}, {{ form.as_table }} 的方式渲染表单。如果你想详细控制每个字段field的格式,你可以采取以下方式。

{% block content %}
{% csrf_token %} {% for field in form %}
{{ field.errors }} {{ field.label_tag }} {{ field }} {% if field.help_text %}

{{ field.help_text|safe }}

{% endif %}
{% endfor %}
{% endblock %}

表单实际使用案例

我们现在需要设计一个表单让用户完成注册。我们先在app目录下新建forms.py, 然后创建一个RegistrationForm, 代码如下:

from django import forms
from django.contrib.auth.models import User

class RegistrationForm(forms.Form):
    username = forms.CharField(label='Username', max_length=50)
    email = forms.EmailField(label='Email',)
    password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
    password2 = forms.CharField(label='Password Confirmation', widget=forms.PasswordInput)

当然你也可以不用新建forms.py而直接在html模板里写表单,但我并不建议这么做。用forms.py的好处显而易见:

  • 所有的表单在一个文件里,非常便于后期维护,比如增添或修订字段。
  • forms.py可通过clean方法自定义表单验证,非常便捷(见后文)。

接下来我们要在视图views.py中使用这个表单,并将其向指定模板传递。

from django.shortcuts import render, get_object_or_404
from django.contrib.auth.models import User
from .forms import RegistrationForm
from django.http import HttpResponseRedirect

def register(request):
    if request.method == 'POST':
        # 将用户POST提交数据与表单结合,准备验证
        form = RegistrationForm(request.POST) 
        if form.is_valid():
            username = form.cleaned_data['username']
            email = form.cleaned_data['email']
            password = form.cleaned_data['password2']
            # 使用内置User自带create_user方法创建用户,不需要使用save()
            user = User.objects.create_user(username=username, password=password, email=email)
            # 如果直接使用objects.create()方法后不需要使用save()
            return HttpResponseRedirect("/accounts/login/")
    else:
        form = RegistrationForm()

    return render(request, 'users/registration.html', {'form': form})

模板是registration.html代码很简单,如下所示。如果你需要通过表单上传图片或文件,一定不要忘了给form加enctype="multipart/form-data"属性。

{{ form.as_p }}

我们来看下RegistrationForm是怎么工作的:

  • 当用户通过POST方法提交表单,我们将提交的数据与RegistrationForm结合,然后验证表单。
  • 如果表单数据有效,我们先用Django User模型自带的create_user方法创建user对象。
  • 如果用户注册成功,我们通过HttpResponseRedirect方法转到登陆页面。
  • 如果用户没有提交表单或不是通过POST方法提交表单,我们转到注册页面,渲染一张空表单。

** 提示 **:本例的RegistrationForm是自定义的表单,表单验证通过后我们显示地通过form.cleaned_data获取验证后的数据,然后手动地存入数据库。如果你的表单是通过继承ModelForm创建的,你可以直接通过form.save()方法将验证过的表单数据存入数据库。

if form.is_valid():
    form.save()

表单的验证

每个forms类可以通过clean方法自定义表单验证。如果你只想对某些字段进行验证,你可以通过clean_字段名方式自定义表单验证。如果用户提交的数据未通过验证,会将错误信息呈现给用户。如果用户提交的数据有效form.is_valid(),则会将数据存储在cleaned_data字典里。

在上述用户注册的案例里,我们在RegistrationForm通过clean方法添加了用户名验证,邮箱格式验证和密码验证。代码如下。

from django import forms
from django.contrib.auth.models import User
import re

def email_check(email):
    pattern = re.compile(r"\"?([-a-zA-Z0-9.`?{}]+@\w+\.\w+)\"?")
    return re.match(pattern, email)

class RegistrationForm(forms.Form):
    username = forms.CharField(label='Username', max_length=50)
    email = forms.EmailField(label='Email',)
    password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
    password2 = forms.CharField(label='Password Confirmation', widget=forms.PasswordInput)

    # 自定义验证方法
    def clean_username(self):
        username = self.cleaned_data.get('username')

        if len(username) < 6:
            raise forms.ValidationError("Your username must be at least 6 characters long.")
        elif len(username) > 50:
            raise forms.ValidationError("Your username is too long.")
        else:
            user = User.objects.filter(username__exact=username).first()
            if user.exists():
                raise forms.ValidationError("Your username already exists.")

        return username

    def clean_email(self):
        email = self.cleaned_data.get('email')

        if email_check(email):
            filter_result = User.objects.filter(email__exact=email)
            if len(filter_result) > 0:
                raise forms.ValidationError("Your email already exists.")
        else:
            raise forms.ValidationError("Please enter a valid email.")

        return email

    def clean_password1(self):
        password1 = self.cleaned_data.get('password1')

        if len(password1) < 6:
            raise forms.ValidationError("Your password is too short.")
        elif len(password1) > 20:
            raise forms.ValidationError("Your password is too long.")

        return password1

    def clean_password2(self):
        password1 = self.cleaned_data.get('password1')
        password2 = self.cleaned_data.get('password2')

        if password1 and password2 and password1 != password2:
            raise forms.ValidationError("Password mismatch. Please enter again.")

        return password2

通用类视图里使用表单

在Django基于类的视图(Class Based View)里使用表单也非常容易,你可以通过model + fields的方式定义或直接通过form_class设置自定义的表单类。下面是一个创建一篇新文章的例子,两种方式均可。

from django.views.generic.edit import CreateView
from .models import Article
from .forms import ArticleForm

# 方式一: 通过model和fields定义表单
class ArticleCreateView(CreateView):
    model = Article
    fields = ['title', 'body']
    template_name = 'blog/article_form.html'

# 方式二:使用form_class
class ArticleCreateView(CreateView):
    model = Article
    form_class = ArticleForm
    template_name = 'blog/article_form.html'

Formset的使用

有的时候用户需要在1个页面上使用多个表单,比如一次性提交添加多本书的信息,这时我们可以使用formset。这是一个表单的集合。

创建一个Formset我们可以这么做:

from django import forms

class BookForm(forms.Form):
    name = forms.CharField(max_length=100)
    title = forms.CharField()
    pub_date = forms.DateField(required=False)

# forms.py - build a formset of books
from django.forms import formset_factory
from .forms import BookForm

# extra: 额外的空表单数量
# max_num: 包含表单数量(不含空表单)
BookFormSet = formset_factory(BookForm, extra=2, max_num=1)

在视图文件views.py里,我们可以像使用form一样使用formset.

# views.py - formsets example.
from .forms import BookFormSet
from django.shortcuts import render

def manage_books(request):
    if request.method == 'POST':
        formset = BookFormSet(request.POST, request.FILES)
        if formset.is_valid():
            # do something with the formset.cleaned_data
            pass
    else:
        formset = BookFormSet()
    return render(request, 'manage_books.html', {'formset': formset})

在模板里也可以使用formset。

<form action=.” method=”POST”>
{{ formset }}
</form>

Django管理后台(Admin)入门

为什么要使用Django Admin

Django自带的admin管理后台就像诸葛亮的媳妇黄月英,虽然不漂亮,但却拥有和诸葛亮一样的才华。使用Django Admin,一行代码即可增加对一个模型(数据表)的增删查改。试想如果你要自己手动编写后台对一个模型进行增删查改,你一般需要4个urls, 4个视图函数或通用视图和4个模板。当一个项目比较大包含多个app时,而每个app又包含多个模型(数据表)时, 那么编写和维护整个项目管理后台的工作量可想而知。如果你的管理后台主要是内部人员使用,你完全不需要太过在意它的外观。

如何使用Django Admin

创建超级用户superuser

使用Django admin的第一步是创建超级用户(superuser)。进入你的项目文件夹, 使用如下命名,输入用户名和密码即可创建管理员。

$ python manage.py createsuperuser

此时你访问http://127.0.0.1:8000/admin/, 你就可以使用用户名和密码登录了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2fGW382q-1655293295136)(https://pythondjango.cn/django/basics/11-admin.assets/image-20210321131935321.png)]

注册模型

假设你有一个叫blog的APP, 里面包含了一个叫Article的模型, 你想对文章进行管理, 你只需找到blog目录下的admin.py,使用admin.site.register方法注册Article模型。代码如下所示:

#blog/admin.py
from django.contrib import admin
from .models import Article

# Register your models here.
admin.site.register(Article)

此时你登录admin后点击Article模型,你会看到如下管理界面,点击标题即可对文章进行修改。不过只有文章的标题title被显示,太简单,没有显示作者,没有显示发布日期,也没有分页,也没有过滤条件。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tceHEME6-1655293295136)(https://pythondjango.cn/django/basics/11-admin.assets/image-20210321132353304.png)]

自定义数据表显示选项

我们需要自定义数据表中哪些字段可以显示,哪些字段可以编辑,并对数据表中的条目进行排序,同时定义过滤选项。Django的ModelAdmin自带的list_display, list_filter, list_per_page, list_editable, date_hierarchyordering选项可以轻松帮我们做到。

要自定义数据表显示字段,我们只需对上述代码做出如下改进。我们先定义ArticleAdmin类,然后使用admin.site.register(Article, ArticleAdmin)方法即可。

# blog/admin.py
from django.contrib import admin
from .models import Article
from django.utils.html import format_html

class ArticleAdmin(admin.ModelAdmin):
    # 定制哪些字段需要展示
    list_display = ('title', 'author', 'status', 'create_date', )
    
    # list_display_links = ('title', ) # 默认
    # sortable_by # 排序

    '''定义哪个字段可以编辑'''
    list_editable = ('status', )

    '''分页:每页10条'''
    list_per_page = 5

    '''最大条目'''
    list_max_show_all = 200 #default

    '''搜索框 ^, =, @, None=icontains'''
    search_fields = ['title']

    '''按日期分组'''
    date_hierarchy = 'create_date'

    '''默认空值'''
    empty_value_display = 'NA'

    '''过滤选项'''
    list_filter = ('status', 'author__is_superuser', ) 

admin.site.register(Article, ArticleAdmin)

此时登录django后台访问Article模型你将看到如下基础显示效果。我们将从这里开始展示django自带admin后台更高级的用法。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q5ly3Iua-1655293295136)(https://pythondjango.cn/django/basics/11-admin.assets/2.png)]

自定义list_display显示字段

目前文章列表中的文章创建日期是英文字段,不是我们想要的。我们可以自定义一个时间格式(比如以2020-11-09 15:00)展示,并以红色标注。

此时修改我们的admin.py, 在ArticleAdmin类中新增一个custom_date方法,把其加入list_display选项,如下所示:

# Register your models here.
class ArticleAdmin(admin.ModelAdmin):


    # Custom admin list view
    list_display = ('title', 'author', 'status', 'create_date', 'custom_date', )
    '''中间省略'''    
    '''custom field on list view'''
    def custom_date(self, obj):
        return format_html(
            '{}',
            obj.create_date.strftime("%Y-%m-%d %H:%M:%S")
        )


    custom_date.short_description = '定制格式及颜色'

新的展示效果如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-18nbjGEe-1655293295137)(https://pythondjango.cn/django/basics/11-admin.assets/11.png)]

优化ForeignKey或多对多字段的选择

Django admin中添加或修改一个对象时,对ForeignKey字段默认使用下拉菜单显示。例如本例中文章模型的author是一个ForeignKey外键。当你在admin中创建新的文章时,你将看到如下界面。当作者很少的时候,你可以轻松选择文章作者。当用户非常多的时候,下拉菜单变得非常长,此时通过下拉菜单选择作者非常不方便,效率低下。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Pdj0F68K-1655293295137)(https://pythondjango.cn/django/basics/11-admin.assets/3.png)]

一个更好的方式是使用autocomplete_fields或者raw_id_fields。前者使用基于jquery select2带搜索框的下拉菜单,适合中等数量的选项。后者使用弹出窗口搜索对象,适合数量非常多的选项。

修改admin.py,添加autocomplete_fields

# Register your models here.
class ArticleAdmin(admin.ModelAdmin):

    list_display = ('title', 'author', 'status', 'create_date', 'custom_date', )
    '''中间省略'''
    autocomplete_fields = ['author'] # use select2 to select user    

autocomplete_fields的展示效果如下所示,效果好得惊人。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E8ldMm0E-1655293295137)(https://pythondjango.cn/django/basics/11-admin.assets/8.png)]

再修改admin.py, 使用raw_id_fields

# Register your models here.
class ArticleAdmin(admin.ModelAdmin):

    list_display = ('title', 'author', 'status', 'create_date', 'custom_date', )
    '''中间省略'''    
    raw_id_fields = ("author",) # use a pop-out search window for a foreign key   

raw_id_fields展示效果如下所示。ForeinKey字段旁多了个放大镜,展示效果也相当不错。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vxf49Lzr-1655293295137)(https://pythondjango.cn/django/basics/11-admin.assets/1.png)]

本例模型中不涉及多对多字段,但在实际应用中django admin对于多对多字段使用多选下拉菜单,也非常不方便,建议选择使用filter_horizontalfilter_vertical设置,其展示效果为双向穿梭选择器,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xgCyVdOB-1655293295138)(https://pythondjango.cn/django/basics/11-admin.assets/9.png)]

使用Inline表单

本例中author和article是单对多的关系,前面案例中文章都是逐一添加的。如果我们希望在创建或编辑用户的时候,就一次性添加多篇文章怎么操作呢? 答案是使用inline表单。

我们首先用Article模型创建两个Inline类,一个用于展示和编辑已创建的文章列表,一个用于一次性添加多篇文章。由于Article模型可能有多个ForeignKey(比如category或author), 我们这里必需通过fk_name选项指定在那个ForeinKey的模型里使用创建的inlines类。

from django.contrib.auth import get_user_model
User = get_user_model()


class ArticleListInline(admin.TabularInline):
    model = Article
    # Article模型可能有多个外键,这里指定使用author这个外键,
    fk_name = "author"
    # readonly_fields = ('title', 'body', 'status')
    # can_delete = False
    max_num = 3

    verbose_name = _('Edit Articles')
    verbose_name_plural = _('Edit Articles')
    extra = 2 # 

    """ 不允许这个inline类增加记录  """
    def has_add_permission(self, request, obj=None):
        return False

class ArticleAddInline(admin.TabularInline):
    model = Article
    fk_name = "author"
    extra = 2
    can_delete = False

    verbose_name = _('Add Articles')
    verbose_name_plural = _('Add Articles')

    """ 这时仅用于添加数据的Inline,返回空的queryset """
    def get_queryset(self, request):
        queryset = super().get_queryset(request)
        return queryset.none()

然后将创建的ArticleInline类与UserAdmin相关联。

class UserAdmin(admin.ModelAdmin):
    search_fields = ['username']

    inlines = [
        ArticleAddInline,
        ArticleListInline,
    ]

admin.site.unregister(User)
admin.site.register(User, UserAdmin)

此时当你创建或编辑用户时,你可以看到或编辑该用户已创建的文章。你还可以一次性为该用户添加多篇文章,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LZ433ZNf-1655293295138)(https://pythondjango.cn/django/basics/11-admin.assets/10.png)]

Django连接MySQL和PostgreSQL数据库

SQLite的应用场景及优缺点

SQLite是一个轻量级的开源免费的数据库。它是一种嵌入式数据库,只是一个.db格式的文件,无需安装,配置和启动。SQLite试图为单独的应用程序和设备提供本地的数据存储。

SQLite常见应用场景包括中小型网站,嵌入式设备和应用软件(如android),文件档案管理和桌面程序(exe)文件数据库。SQLite支持多种编程语言(如python)和操作系统(windows, iOS, unix, linux),移植性非常好。

如果你需要开发一个高流量或高并发的网站,SQLite将不能满足你的需求。同时如果你要开发一个Web APP, 不同用户通过网络对数据库进行读写操作,那么SQLite也将不能胜任(比如分布式数据库)。这时我们需要考虑企业级的专业数据库了,比如MySQL和Postgres。

如何使用MySQL数据库

MySQL是最流行的开源免费的关系型数据库,可作为客户端/服务器数据库提供企业级的数据库服务。Django项目中配置使用MySQL一共分四步:

第一步: 安装MySQL

Windows用户可以直接从MySQL网站上下载相应版本安装。Linux用户可以使用如下命令安装mysql-server

sudo apt-get install mysql-server # ubuntu系统

第二步 创建数据库名和用户

打开MySQL终端,输入以下命令先创建数据库和用户,并给创建的用户授权。数据库名字,用户名和密码待会会用到。第一步和第二步非常重要。 mydb.*表示授权操作mydb中所有的表。

# 创建数据库,设置字符
CREATE DATABASE mydb CHARACTER SET utf8;
# 创建用户及密码
CREATE USER 'myuser'@'localhost' IDENTIFIED BY 'mypass';
# 授权
GRANT ALL PRIVILEGES ON mydb.* TO 'myuser'@'localhost' IDENTIFIED BY 'mypass'

第三步 安装第三方库mysqlclient

Django项目中操作MySQL,官方推荐mysqlclient这个库。

pip install mysqlclient

第四步 修改配置文件settings.py

修改项目文件夹里的settings.py的文件,添加创建的数据库和用户信息。

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',   # 数据库引擎
        'NAME': 'mydb',         # 数据库名,Django不会帮你创建,需要自己进入数据库创建。
        'USER': 'myuser',       # 设置的数据库用户名
        'PASSWORD': 'mypass',   # 设置的密码
        'HOST': 'localhost',    # 本地主机或数据库服务器的ip
        'PORT': '3306',         # 数据库使用的端口
    }
}

另一种设置方式是使用OPTIONS和配置文件my.cnf进行设置。

# settings.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'OPTIONS': {
            'read_default_file': '/path/to/my.cnf',
        },
    }
}

# my.cnf
[client]
database = mydb
user = myuser
password = mypass
default-character-set = utf8

设置好后,连续使用如下命令如果没有出现错误,那么恭喜你已经在Django项目中使用MySQL数据库啦。

python manage.py makemigrations                                                              
python manage.py migrate

进入MySQL数据库终端,你会看见Django已经创建了很多数据表,比如auth_user表。

mysql> use mydb
Database changed

mysql> show tables;
+----------------------------+
| Tables_in_django_mysql     |
+----------------------------+
| auth_group                 |
| auth_group_permissions     |
| auth_permission            |
| auth_user                  |
| auth_user_groups           |
| auth_user_user_permissions |

如何使用PostgreSQL数据库

PostgreSQL在全球是仅次于MySQL的开源免费的关系型数据库,功能更加强大,是Django首选的关系型数据库。Django项目中配置使用PostgreSQL数据库一共分四步:

第一步: 安装PostgreSQL

Windows用户可以直接从https://www.postgresql.org/download/ 下载相应版本安装。Linux用户可以使用如下命令安装PostgreSQL及其所依赖的python环境。

sudo apt-get install postgresql postgresql-contrib # ubuntu系统
sudo apt-get install libpq-dev python3-dev #安装依赖环境

第二步 创建数据库名和用户

Postgres数据库的默认用户是postgres, 他有超级用户权限,相当于MySQL的root用户。

打开postgres命令行终端(Linux下输入: sudo -u postgres psql),连续输入以下命令先创建数据库和用户,并给创建的用户授权。数据库名字,用户名和密码待会会用到。

# 创建名为myapp的数据库
CREATE DATABASE mydb; 
# 创建用户名和密码
CREATE USER myuser WITH ENCRYPTED PASSWORD 'mypass'; 
# 给创建的用户授权
GRANT ALL PRIVILEGES ON DATABASE mydb TO myuser;

# 以下设置可手动进行设置,也可以在postgresql.conf中进行配置
# 设置客户端字符为utf-8,防止乱码
ALTER ROLE myuser SET client_encoding TO 'utf8';
# 事务相关设置 - 推荐
ALTER ROLE myuser SET default_transaction_isolation TO 'read committed';
# 设置数据库时区为UTC - 推荐
ALTER ROLE myuser SET timezone TO 'UTC';

第三步 安装第三方库psycopg2

Django项目中操作MySQL,官方推荐psycopg2 这个库。

pip install psycopg2

第四步 修改配置文件settings.py

修改项目文件夹里的settings.py的文件,添加创建的数据库和用户信息。

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',   # 数据库引擎
        'NAME': 'mydb',         # 数据库名,Django不会帮你创建,需要自己进入数据库创建。
        'USER': 'myuser',     # 设置的数据库用户名
        'PASSWORD': 'mypass',     # 设置的密码
        'HOST': 'localhost',    # 本地主机或数据库服务器的ip
        'PORT': '',         # 数据库使用的端口
    }
}

设置好后,连续使用如下命令如果没有出现错误,那么恭喜你已经在Django项目中使用PostgreSQL数据库啦。

python manage.py makemigrations                                                              
python manage.py migrate

Django项目中Cookie和Session应用场景及案例

什么是cookie,cookie的应用场景及缺点

cookie是一种数据存储技术, 它是将一段文本保存在客户端(浏览器或本地电脑)的一种技术,并且可以长时间的保存。当用户首次通过客户端访问服务器时,web服务器会发送给客户端的一小段信息。客户端浏览器会将这段信息以cookie形式保存在本地某个目录下的文件内。当客户端下次再发送请求时会自动将cookie也发送到服务器端,这样服务器端通过查验cookie内容就知道该客户端之前访问过了。

cookie的常见应用场景包括:

  • 判断用户是否已经登录
  • 记录用户登录信息(比如用户名,上次登录时间)
  • 记录用户搜索关键词

cookie的缺点在于其并不可靠和不安全,主要原因如下:

  • 浏览器不一定会保存服务器发来的cookie,用户可以通过设置选择是否禁用cookie。
  • cookie是有生命周期的(通过Expire设置),如果超过周期,cookie就会被清除。
  • HTTP数据通过明文发送,容易受到攻击,因此不能在cookie中存放敏感信息(比如信用卡号,密码等)。
  • cookie以文件形式存储在客户端,用户可以随意修改的。

Django中如何使用cookie

第一步:提供响应数据时设置cookie(保存到客户端)

response.set_cookie(cookie_name, value, max_age = None, expires = None) 

# key : cookie的名称
# value : 保存的cookie的值
# max_age: 保存的时间,以秒为单位
# expires: 过期时间,为datetime对象或时间字符串

例子: response.set_cookie('username','John',600)

注意:Django的视图默认返回的response是不包含cookie的,需手动调用set_cookie方法。

下面是3个设置cookie的例子:

# 例子1:不使用模板
response = HttpResponse("hello world")
response.set_cookie(key,value,max_age)
return response

# 例子2: 使用模板
response = render(request,'xxx.html', context)
response.set_cookie(key,value,max_age)
return response

# 例子3: 重定向
response = HttpResponseRedirect('/login/')
response.set_cookie(key,value,max_age)
return response

第二步: 获取COOKIES中的数据, 进行处理验证

# 方法一
request.COOKIES['username']

# 方法二
request.COOKIES.get('username','')

客户端再次发送请求时,request会携带本地存储的cookie信息,视图中你可以通过request.COOKIES获取。

为了防止获取不能存在的Key报错,你可以通过如下方式检查一个cookie是否已存在。

request.COOKIES.has_key('cookie_name')

如果你希望删除某个cookie,你可以使用如下方法:

response.delete_cookie('username')

Cookie使用示例

下面是django中使用cookie验证用户是否已登录的一个示例。用户首次登录时设置cookie,再次请求时验证请求携带的cookie。

# 如果登录成功,设置cookie
def login(request):
    if request.method == 'POST':
        form = LoginForm(request.POST)
        
        if form.is_valid():
            username = form.cleaned_data['username']
            password = form.cleaned_data['password']
            user = User.objects.filter(username__exact=username, password__exact=password)

            if user:
                response = HttpResponseRedirect('/index/')
                # 将username写入浏览器cookie,有效时间为360秒
                response.set_cookie('username', username, 360)
                return response
            else:
                return HttpResponseRedirect('/login/')
                                                           
    else:
        form = LoginForm()

    return render(request, 'users/login.html', {'form': form})


# 通过cookie判断用户是否已登录
def index(request):
    # 读取客户端请求携带的cookie,如果不为空,表示为已登录帐号
    username = request.COOKIES.get('username', '')
    if not username:
        return HttpResponseRedirect('/login/')
    return render(request, 'index.html', {'username': username})

什么是session及session的工作原理

session又名会话,其功能与应用场景与cookie类似,用来存储少量的数据或信息。但由于数据存储在服务器上,而不是客户端上,所以比cookie更安全。不过当用户量非常大时,所有的会话信息都存储于服务器会对服务器造成一定的压力。

Django中如何使用会话session

第一步:检查基本设置

Django中使用session首选需要确保settings.py中已开启了SessionMiddleware中间件。

'django.contrib.sessions.middleware.SessionMiddleware',

Django默认使用数据库存储每个session的sessionid, 所以你还需确保INSTALLED_APPS 是包含如下app:

'django.contrib.sessions',

当然你还可以使用更快的文件或缓存来存储会话信息,可以通过SESSION_ENGINE设置就行。

第二步:使用session

request.session是一个字典,你可以在视图和模板中直接使用它。

# 设置session的值
request.session['key'] = value
request.session.set_expiry(time): 设置过期时间,0表示浏览器关闭则失效

# 获取session的值
request.session.get('key',None)

# 删除session的值, 如果key不存在会报错
del request.session['key']

# 判断一个key是否在session里
'fav_color' in request.session

# 获取所有session的key和value
request.session.keys()
request.session.values()
request.session.items()

另外,settings.py 还有两项有关session比较重要的设置: 1、SESSION_COOKIE_AGE:以秒为单位,session的有效时间,可以通过set_expiry 方法覆盖。 2、SESSION_EXPIRE_AT_BROWSER_CLOSE:默认为Flase,是否设置为浏览器关闭,会话自动失效。

Session使用示例

下面是django中使用session进行用户登录和登出的一个示例。用户首次登录时设置session,退出登录时删除session。

# 如果登录成功,设置session
def login(request):
    if request.method == 'POST':
        form = LoginForm(request.POST)

        if form.is_valid():
            username = form.cleaned_data['username']
            password = form.cleaned_data['password']
            user = User.objects.filter(username__exact=username, password__exact=password)
            if user:
                # 将username写入session,存入服务器
                request.session['username'] = username
                return HttpResponseRedirect('/index/')
            else:
                return HttpResponseRedirect('/login/')
    else:
        form = LoginForm()

    return render(request, 'users/login.html', {'form': form})


# 通过session判断用户是否已登录
def index(request):
    # 获取session中username
    username = request.session.get('username', '')
    if not username:
        return HttpResponseRedirect('/login/')
    return render(request, 'index.html', {'username': username})

# 退出登录
def logout(request):
    try:
        del request.session['username']
    except KeyError:
        pass
    return HttpResponse("You're logged out.")

下面是通过session控制不让用户连续评论两次的例子。实际应用中我们还可以通过session来控制用户登录时间,记录访问历史,记录购物车信息等等。

from django.http import HttpResponse

def post_comment(request, new_comment):
    if request.session.get('has_commented', False):
        return HttpResponse("You've already commented.")
    c = comments.Comment(comment=new_comment)
    c.save()
    request.session['has_commented'] = True
    return HttpResponse('Thanks for your comment!')

Django项目中如何使用分页及通用模板

为什么要分页?

当你的数据库数据量非常大时,如果一次将这些数据查询出来, 必然加大了服务器内存的负载,降低系统的运行速度。一种更好的方式是将数据分段展示给用户,这就是分页(pagination)的作用。

函数视图中使用分页

以博客为例,在Django视图函数中使用Paginator类对首页文章列表进行分页。它会向模板传递2个重要参数:

  1. page_obj: 分页后的对象列表,在模板中使用for循环遍历即可;
  2. is_paginated: 可选参数。当总页数不超过1页,值为False,此时模板不显示任何分页链接 。
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from .models import Article
from django.shortcuts import render

def article_list(request):
    queryset = Article.objects.filter(status='p').order_by('-pub_date')
    paginator = Paginator(queryset, 10)  # 实例化一个分页对象, 每页显示10个
    page = request.GET.get('page')  # 从URL通过get页码,如?page=3
    try:
        page_obj = paginator.page(page)
    except PageNotAnInteger:
        page_obj = paginator.page(1) # 如果传入page参数不是整数,默认第一页
    except EmptyPage:
        page_obj = paginator.page(paginator.num_pages)
    is_paginated = True if paginator.num_pages > 1 else False # 如果页数小于1不使用分页
    context = {'page_obj': page_obj, 'is_paginated': is_paginated}
    return render(request, 'blog/article_list.html', context)

基于类的视图中使用分页

在基于类的视图ListView中使用分页,只需设置paginate_by这个参数即可。它同样会向模板传递page_objis_paginated这2个参数。

from django.views.generic import ListView
from .models import Article

class ArticleListView(ListView):
    queryset = Article.objects.filter(status='p').order_by('-pub_date')
    template_name = "blog/article_list.html"
    paginate_by = 10 # 每页10条

展示分页链接的通用模板

这里提供了两种展示分页链接的通用模板,对基于函数的视图和类视图都是适用的。当is_paginated=True时展示分页链接。

方式1: 上一页, Page 1 of 3, 下一页

 
    {% for article in page_obj %}
  • {{ article.title }}
  • {% endfor %}
{% if is_paginated %} {% endif %}

方式2: 上一页, 1, 2, 3, 4, 5, 6, 7, 8, … 下一页。本例加入了Bootstrap 4的样式美化(推荐)。

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
 
{% if page_obj %}
    <ul> 
    {% for article in page_obj %}
       <li> {{ article.title }</li>
     {% endfor %}
   </ul>

   {# 分页链接 #}
   {% if is_paginated %}
     <ul class="pagination">
    {% if page_obj.has_previous %}
      <li class="page-item"><a class="page-link" href="?page={{ page_obj.previous_page_number }}">Previous</a></li>
    {% else %}
      <li class="page-item disabled"><span class="page-link">Previous</span></li>
    {% endif %}
 
    {% for i in page_obj.paginator.page_range %}
        {% if page_obj.number == i %}
      <li class="page-item active"><span class="page-link"> {{ i }} <span class="sr-only">(current)</span></span></li>
       {% else %}
        <li class="page-item"><a class="page-link" href="?page={{ i }}">{{ i }}</a></li>
       {% endif %}
    {% endfor %}
 
      {% if page_obj.has_next %}
      <li class="page-item"><a class="page-link" href="?page={{ page_obj.next_page_number }}">Next</a></li>
    {% else %}
      <li class="page-item disabled"><span class="page-link">Next</span></li>
    {% endif %}
    </ul>
    {% endif %}
 
{% else %}
{# 注释: 这里可以写自己的句子 #}
{% endif %}

Django上传文件

Django文件上传需要考虑的重要事项

文件或图片一般通过表单进行。用户在前端点击文件上传,然后以POST方式将数据和文件提交到服务器。服务器在接收到POST请求后需要将其存储在服务器上的某个地方。Django默认的存储地址是相对于根目录的/media/文件夹,存储的默认文件名就是文件本来的名字。上传的文件如果不大于2.5MB,会先存入服务器内存中,然后再写入磁盘。如果上传的文件很大,Django会把文件先存入临时文件,再写入磁盘。

Django默认处理方式会出现一个问题,所有文件都存储在一个文件夹里。不同用户上传的有相同名字的文件可能会相互覆盖。另外用户还可能上传一些不安全的文件如js和exe文件,我们必需对允许上传文件的类型进行限制。因此我们在利用Django处理文件上传时必需考虑如下3个因素:

  • 设置存储上传文件的文件夹地址
  • 对上传文件进行重命名
  • 对可接受的文件类型进行限制(表单验证)

注意:以上事项对于上传图片是同样适用的。

Django文件上传的3种常见方式

Django文件上传一般有3种方式(如下所示)。我们会针对3种方式分别提供代码示范。

  • 使用一般的自定义表单上传,在视图中手动编写代码处理上传的文件
  • 使用由模型创建的表单(ModelForm)上传,使用form.save()方法自动存储
  • 使用Ajax实现文件异步上传,上传页面无需刷新即可显示新上传的文件

Ajax文件上传部分见Django与Ajax交互篇。

项目创建与设置

我们先使用django-admin startproject命令创建一个叫file_project的项目,然后cd进入file_project, 使用python manage.py startapp创建一个叫file_upload的app。

我们首先需要将file_upload这个app加入到我们项目里,然后设置/media/和/STATIC_URL/文件夹。我们上传的文件都会放在/media/文件夹里。我们还需要使用css和js这些静态文件,所以需要设置STATIC_URL。

#file_project/settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'file_upload',# 新增
]

STATIC_URL = '/static/'
STATICFILES_DIRS = [os.path.join(BASE_DIR, "static"), ]

MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'

#file_project/urls.py
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    path('admin/', admin.site.urls),
    path('file/', include("file_upload.urls")),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

创建模型

使用Django上传文件创建模型不是必需,然而如果我们需要对上传文件进行系统化管理,模型还是很重要的。我们的File模型包括fileupload_method两个字段。我们通过upload_to选项指定了文件上传后存储的地址,并对上传的文件名进行了重命名。

#file_upload/models.py
from django.db import models
import os
import uuid

# Create your models here.
# Define user directory path
def user_directory_path(instance, filename):
    ext = filename.split('.')[-1]
    filename = '{}.{}'.format(uuid.uuid4().hex[:10], ext)
    return os.path.join("files", filename)

class File(models.Model):
    file = models.FileField(upload_to=user_directory_path, null=True)
    upload_method = models.CharField(max_length=20, verbose_name="Upload Method")

注意:如果你不使用ModelForm,你还需要手动编写代码存储上传文件。

URLConf配置

本项目一共包括3个urls, 分别对应普通表单上传,ModelForm上传和显示文件清单。

#file_upload/urls.py
from django.urls import re_path, path
from . import views

# namespace
app_name = "file_upload"

urlpatterns = [
    # Upload File Without Using Model Form
    re_path(r'^upload1/$', views.file_upload, name='file_upload'),

    # Upload Files Using Model Form
    re_path(r'^upload2/$', views.model_form_upload, name='model_form_upload'),

    # View File List
    path('file/', views.file_list, name='file_list'),

]

使用一般表单上传文件

我们先定义一个一般表单FileUploadForm,并通过clean方法对用户上传的文件进行验证,如果上传的文件名不以jpg, pdf或xlsx结尾,将显示表单验证错误信息。关于表单的自定义和验证更多内容见Django基础: 表单forms的设计与使用。

#file_upload/forms.py

from django import forms
from .models import File

# Regular form
class FileUploadForm(forms.Form):
    file = forms.FileField(widget=forms.ClearableFileInput(attrs={'class': 'form-control'}))
    upload_method = forms.CharField(label="Upload Method", max_length=20,
                                   widget=forms.TextInput(attrs={'class': 'form-control'}))
    def clean_file(self):
        file = self.cleaned_data['file']
        ext = file.name.split('.')[-1].lower()
        if ext not in ["jpg", "pdf", "xlsx"]:
            raise forms.ValidationError("Only jpg, pdf and xlsx files are allowed.")
        # return cleaned data is very important.
        return file

注意: 使用clean方法对表单字段进行验证时,别忘了return验证过的数据,即cleaned_data。只有返回了cleaned_data, 视图中才可以使用form.cleaned_data.get(‘xxx’)获取验证过的数据。

对应一般文件上传的视图file_upload方法如下所示。当用户的请求方法为POST时,我们通过form.cleaned_data.get('file')获取通过验证的文件,并调用自定义的handle_uploaded_file方法来对文件进行重命名,写入文件。如果用户的请求方法不为POST,则渲染一个空的FileUploadFormupload_form.html里。我们还定义了一个file_list方法来显示文件清单。

#file_upload/views.py

from django.shortcuts import render, redirect
from .models import File
from .forms import FileUploadForm, FileUploadModelForm
import os
import uuid
from django.http import JsonResponse
from django.template.defaultfilters import filesizeformat

# Create your views here.


# Show file list
def file_list(request):
    files = File.objects.all().order_by("-id")
    return render(request, 'file_upload/file_list.html', {'files': files})

# Regular file upload without using ModelForm
def file_upload(request):
    if request.method == "POST":
        form = FileUploadForm(request.POST, request.FILES)
        if form.is_valid():
            # get cleaned data
            upload_method = form.cleaned_data.get("upload_method")
            raw_file = form.cleaned_data.get("file")
            new_file = File()
            new_file.file = handle_uploaded_file(raw_file)
            new_file.upload_method = upload_method
            new_file.save()
            return redirect("/file/")
    else:
        form = FileUploadForm()

    return render(request, 'file_upload/upload_form.html', 
                  {'form': form, 'heading': 'Upload files with Regular Form'}
                 )

def handle_uploaded_file(file):
    ext = file.name.split('.')[-1]
    file_name = '{}.{}'.format(uuid.uuid4().hex[:10], ext)

    # file path relative to 'media' folder
    file_path = os.path.join('files', file_name)
    absolute_file_path = os.path.join('media', 'files', file_name)

    directory = os.path.dirname(absolute_file_path)
    if not os.path.exists(directory):
        os.makedirs(directory)

    with open(absolute_file_path, 'wb+') as destination:
        for chunk in file.chunks():
            destination.write(chunk)

    return file_path

注意:

  • handle_uploaded_file方法里文件写入地址必需是包含/media/的绝对路径,如果/media/files/xxxx.jpg,而该方法返回的地址是相对于/media/文件夹的地址,如/files/xxx.jpg。存在数据中字段的是相对地址,而不是绝对地址。
  • 构建文件写入绝对路径时请用os.path.join方法,因为不同系统文件夹分隔符不一样。写入文件前一个良好的习惯是使用os.path.exists检查目标文件夹是否存在,如果不存在先创建文件夹,再写入。

上传表单模板upload_form.html代码如下:

#file_upload/templates/upload_form.html
{% extends "file_upload/base.html" %}
{% block content %}
{% if heading %}

{{ heading }}

{% endif %}
{% csrf_token %} {{ form.as_p }}
{% endblock %}

显示文件清单模板file_list.html代码如下所示:

# file_upload/templates/file_list.html
{% extends "file_upload/base.html" %}

{% block content %}

File List

RegularFormUpload | ModelFormUpload | AjaxUpload

{% if files %} {% for file in files %} {% endfor %}
Filename & URL Filesize Upload Method
{{ file.file.url }} {{ file.file.size | filesizeformat }} {{ file.upload_method }}
{% else %}

No files uploaded yet. Please click here to upload files.

{% endif %} {% endblock %}

注意:

  • 对于上传的文件我们可以调用file.url, file.namefile.size来查看上传文件的链接,地址和大小。
  • 上传文件的大小默认是以B显示的,数字非常大。使用Django模板过滤器filesizeformat可以将文件大小显示为人们可读的方式,如MB,KB。

使用ModelForm上传文件

使用ModelForm上传是小编我推荐的上传方式,前提是你已经在模型中通过upload_to选项自定义了用户上传文件存储地址,并对文件进行了重命名。我们首先要自定义自己的FileUploadModelForm,由File模型重建的。代码如下所示:

#file_upload/forms.py
from django import forms
from .models import File

# Model form
class FileUploadModelForm(forms.ModelForm):
    class Meta:
        model = File
        fields = ('file', 'upload_method',)
        widgets = {
            'upload_method': forms.TextInput(attrs={'class': 'form-control'}),
            'file': forms.ClearableFileInput(attrs={'class': 'form-control'}),
        }

    def clean_file(self):
        file = self.cleaned_data['file']
        ext = file.name.split('.')[-1].lower()
        if ext not in ["jpg", "pdf", "xlsx"]:
            raise forms.ValidationError("Only jpg, pdf and xlsx files are allowed.")
        # return cleaned data is very important.
        return file

使用ModelForm处理文件上传的视图model_form_upload方法非常简单,只需调用form.save()即可,无需再手动编写代码写入文件。

#file_upload/views.py

from django.shortcuts import render, redirect
from .models import File
from .forms import FileUploadForm, FileUploadModelForm
import os
import uuid
from django.http import JsonResponse
from django.template.defaultfilters import filesizeformat

# Create your views here.
# Upload File with ModelForm

def model_form_upload(request):
    if request.method == "POST":
        form = FileUploadModelForm(request.POST, request.FILES)
        if form.is_valid():
            form.save() # 一句话足以
            return redirect("/file/")
    else:
        form = FileUploadModelForm()

    return render(request, 'file_upload/upload_form.html', 
                  {'form': form,'heading': 'Upload files with ModelForm'}
                 )

模板跟前面一样,这里就不展示了。

GitHub源码地址

  • https://github.com/shiyunbo/django-file-upload-download

Django与Ajax交互

  1. 前后端传输数据的编码格式
  2. Ajax提交urlencoded格式数据
  3. Ajax通过FormData上传文件
  4. Ajax提交Json格式数据
  5. Ajax发送POST请求时如何通过CSRF认证
  6. Django Ajax案例1:联动下例菜单
  7. Django Ajax案例2:Ajax上传文件https://pythondjango.cn/django/basics/17-settings/#小结)

前后端传输数据的编码格式

前后端传输数据的编码格式主要有三种, 本文接下来将详细演示。

urlencoded
formdata
json

Ajax提交urlencoded格式数据

Ajax给后台发送数据的默认编码格式是urlencoded,比如username=abcde&password=123456的形式。Django后端拿到符合urlencoded编码格式的数据都会自动帮你解析分装到request.POST中,与form表单提交的数据相同。

下面两种方式是等同的。

//手动构造数据data
$("#btnSubmit").click(function () {
    $.ajax({
        url: '/login/', //也可以反向解析{% url 'login' %}
        type: 'post',
        data: {
            'username': $("#id_username").val(),
            'password': $('#id_password').val()
        },
        // 上面data为提交数据,下面data形参指代的就是异步提交的返回结果data
        success: function (data){
            
        }
    });
};
                    
// .serialize() 方法可将,