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 自动创建数据库表
# 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)
简洁优雅的 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 还有更多实用的特性:
一个让人们查看和投票的公共站点。
一个让你能添加、修改和删除投票的管理站点。
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()
具有四个参数,两个必须参数:route
和 view
,两个可选参数:kwargs
和 name
。现在,是时候来研究这些参数的含义了。
route
是一个匹配 URL 的准则(类似正则表达式)。当 Django 响应一个请求时,它会从 urlpatterns
的第一项开始,按顺序依次匹配列表中的项,直到找到匹配的项。
这些准则不会匹配 GET 和 POST 参数或域名。例如,URLconf 在处理请求 https://www.example.com/myapp/
时,它会尝试匹配 myapp/
。处理请求 https://www.example.com/myapp/?page=3
时,也只会尝试匹配 myapp/
。
当 Django 找到了一个匹配的准则,就会调用这个特定的视图函数,并传入一个 HttpRequest
对象作为第一个参数,被“捕获”的参数以关键字参数的形式传入。稍后,我们会给出一个例子。
任意个关键字参数可以作为一个字典传递给目标视图函数。
为你的 URL 取名能使你在 Django 的任意地方唯一地引用它,尤其是在模板中。这个有用的特性允许你只改一个文件就能全局地修改某个 URL 模式当你了解了基本的请求和响应流程后,请阅读 教程的第 2 部分 开始使用数据库.
打开 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
来应用数据库迁移。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)>]>
MVC即 Model-View-Controller(模型-视图-控制器) ,是经典的软件开发设计模式。
如果把MVC比喻成一个粽子,那么View就是最外面一层的绿色玉米叶,是吃货们可以直接看到的。Controller就是中间那层熟糯米,而粽子的核心自然是最里面那一层的肉馅Model模型了。现在大家知道中学和大学数学建模的重要性了吧?
MVC最大的优点是实现了软件或网络应用开发过程中数据、业务逻辑和用户界面的分离,使软件开发更清晰,也是维护变得更容易。这与静态网页设计中使用html和css实现了内容和样式的分离是同一个道理。
Django的MVT设计模式由Model(模型), View(视图) 和Template(模板)三部分组成,分别对应单个app目录下的models.py, views.py和templates文件夹。它们看似与MVC设计模式不太一致,其实本质是相同的。Django的MVT设计模式与经典的MVC对应关系如下。
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三部分是如何工作的。
假如你有一个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'))
]
编辑tasks目录下models.py
创建Task模型, Task模型包含里名称name和状态status两个字段。当你使用python manage.py makemigrations
和python 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
接下来我们要编辑视图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
视图函数。这个视图函数将同时与数据库和模板进行交互。
最后我们要创建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
视图实现。
本例中我们只讲述核心逻辑,不浪费时间在前端样式上。文末有GitHub源码地址,里面同时包含了函数视图和基于类的视图, 具体演示效果如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rtf1nIEg-1655293295135)(https://pythondjango.cn/django/basics/2-installation-use.assets/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X2dpZi9idWFGTEZLaWNSb0RUQzlhMzBnRHNJamhyaDZVZW81UUFtdlQ0dWxKU2liczBxenFyV0I4dWhDZUlsdTJWTHdDQjRzRHVLY0VHOUF6Q0RHNmhWcVFpYU1LZy82NDA)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nsVua8Js-1655293295135)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]
本例假设你已经有了一个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模型非常简单,仅包含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__"
我们需要创建5个urls, 对应5个函数视图。这是因为对于Retrieve操作,我们需要编写两个函数视图,一个用户获取任务列表,一个用于获取任务详情。对于task_detail
, task_update
和task_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.html
和task_form.html。
最后一个模板由task_create
和task_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
本项目源码地址,里面同时包含了函数视图和基于类的视图。
https://github.com/shiyunbo/django-crud-example
每个Django的模型(model)实际上是个类,继承了models.Model
。每个Model应该包括属性(字段),关系(比如单对单,单对多和多对多)和方法。当你定义好Model模型后,Django的接口会自动帮你在数据库生成相应的数据表(table)。这样你就不用自己用SQL语言创建表格或在数据库里操作创建表格了,是不是很省心?
假设你要开发一个名叫bookstore
的应用,专门来管理书店里的书籍。我们首先要为书本和出版社创建模型。出版社有名字和地址。书有名字,描述和添加日期。我们还需要利用ForeignKey定义了出版社与书本之间单对多的关系,因为一个出版社可以出版很多书,每本书都有对应的出版社。我们定义了Publisher
和Book
模型,它们都继承了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件事:
CharField
的max_length
和ForeignKey
的on_delete
选项是必须要设置的。下面是订正错误后的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 makemigrations
和python manage.py migrate
这两个命令,前者检查模型有无变化,后者将变化迁移至数据表。如果一切顺利,Django会在数据库(默认sqlite)中生成或变更由appname_modelname
组成的数据表,本例两张数据表分别为bookstore_publisher
和bookstore_book
。
一个标准的Django模型分别由模型字段、META选项和方法三部分组成。我们接下来对各部分进行详细介绍。Django官方编码规范建议按如下方式排列:
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选项设置默认日期和时间。
from django.utils import timezone
auto_now=True
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) - 文件字段 **
**ImageField (upload_to=None, max_length=100,)- 图片字段 **
OneToOneField(to, on_delete=xxx, options) - 单对单关系
on_delete
选项(删除选项): i.e, “on_delete = models.CASCADE
” or “on_delete = models.SET_NULL
” .related_name = xxx
” 便于反向查询。ForeignKey(to, on_delete=xxx, options) - 单对多关系
on_delete
选项(删除选项): i.e, “on_delete = models.CASCADE
” or “on_delete = models.SET_NULL
” .limit_choices_to =
“,related_name = xxx
” 便于反向查询。ManyToManyField(to, options) - 多对多关系
symmetrical = False
“ 表示多对多关系不是对称的,比如A关注B不代表B关注Athrough = '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)
对于OneToOneField
和ForeignKey
, on_delete
选项和related_name
是两个非常重要的设置,前者决定了了关联外键删除方式,后者决定了模型反向查询的名字。
Django提供了如下几种关联外键删除选项, 可以根据实际需求使用。
CASCADE
:级联删除。当你删除publisher记录时,与之关联的所有 book 都会被删除。PROTECT
: 保护模式。如果有外键关联,就不允许删除,删除的时候会抛出ProtectedError错误,除非先把关联了外键的记录删除掉。例如想要删除publisher,那你要把所有关联了该publisher的book全部删除才可能删publisher。SET_NULL
: 置空模式。删除的时候,外键字段会被设置为空。删除publisher后,book 记录里面的publisher_id 就置为null了。SET_DEFAULT
: 置默认值,删除的时候,外键字段设置为默认值。SET()
: 自定义一个值。DO_NOTHING
:什么也不做。删除不报任何错,外键值依然保留,但是无法用这个外键去做查询。related_name
用于设置模型反向查询的名字,非常有用。在文初的Publisher
和Book
模型里,我们可以通过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查询其出版的所有书籍,你觉得哪个更好呢?
related_name
前:publisher.book_set.all
related_name
后:publisher.books.all
abstract=True
: 指定该模型为抽象模型proxy=True
: 指定该模型为代理模型verbose_name=xxx
和verbose_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'])
# 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高级模型结构如下所示,可以满足绝大部分应用场景,希望对你有所帮助。
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提供了两种方法,save()
和create()
方法。
from .models import Article
article = Article(title="My first article", body="My first article body")
article.save()
注意: 该方法如果不主动选择save(), 创建的对象实例只会存于内存之中,不会保存到数据库中去。正因为如此,Django还提供了更便捷的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')
在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方法更合适。
article = Article.objects.get(id=1)
article.title = "New article title"
article.save()
article = Article.objects.get(id=1).update(title='new title')
# 更新所有文章标题
article = Article.objects.filter(title__icontains='python').update(title='Django')
与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()
有时候我们需要执行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()
方法可以实现基于自身字段值来过滤一组对象,它还支持加、减、乘、除、取模和幂运算等算术操作。
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)
假如我们有一个blog
的博客应用,你需要编写两个视图函数,一个用于展示文章列表,一个用于展示文章详情,你的urls.py
和views.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')),
...
]
写个URL很简单,但如何通过URL把参数传递给给视图view是个技术活。Django提供了两种设计URL的方法: path
和re_path
,它们均支持向视图函数或类传递参数。path
是正常参数传递,re_path
是采用正则表达式regex匹配。path
和re_path
传递参数方式如下:
path
方法:采用双尖括号<变量类型:变量名>
或<变量名>
传递,例如
,
或
。re_path
方法: 采用命名组(?P<变量名>表达式)
的方式传递参数。下例中,我们分别以path
和re_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):
# 展示某篇文章
在使用path
和re_path
方法设计urls需注意:
/
,但建议以斜杠结尾;re_path
时不一定总是以$
结尾,有时不能加。比如下例中把blog.urls
通过re_path
加入到项目urls中时就不能以$
结尾,因为这里的blog/
并不是完整的url,只是一个开头而已。from django.urls import include, re_path
urlpatterns = [
re_path(r'^blog/', include('blog.urls')),
...
]
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取了一个名字,比如 article_list
和article_create
。这个名字大有用处,相当于给每个URL取了个全局变量的名字。它可以让你能够在Django的任意处,尤其是模板内显式地引用它。假设你需要在模板中通过链接指向一篇具体文章,下面那种方式更好?
{% for article in articles %}
{{ article.title }}
{% endfor %}
url
是个模板标签,其作用是对命名的url进行方向解析,动态生成链接。
注意:命名的url里有几个参数,使用url
模板标签反向生成动态链接时,就需要向它传递几个参数。比如我们的命名urlarticle_detail
里有整数型id
这个参数,我们在模板中还需要传递article.id
。
{% 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])
目前path
和re_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时,你还可以通过字典的形式传递额外的参数给视图, 而不用把这个参数写在链接里。如下面案例所示:
# 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的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.py
和views.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
函数。。latest_articles
, 然后通过render
方法传递给模板blog/article_list.html
.。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_create
和article_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
实际上是非常简单的,仅包含了title
和body
两个字段。
#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)。前面案例中的index
,article_detail
和article_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()),
]
在实际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
用来展示一个对象的列表。它只需要一个参数模型名称即可。比如我们希望展示所有文章列表,我们的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
.object_list
你或许已经注意到了2个问题:需要显示的文章对象列表并没有按发布时间逆序排列,默认内容对象名称object_list
也不友好。或许你也不喜欢默认的模板名字,还希望通过这个视图给模板传递额外的内容(比如现在的时间)。你可以轻易地通过重写queryset
, template_name
和context_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
用来展示一个具体对象的详细信息。它需要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_name
和context_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可以写成:
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
如果你不想使用默认的模板和默认的表单,你可以通过重写template_name
和form_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一般通过某个表单更新现有对象的信息,更新完成后会转到对象详细信息页面。它需要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
, 因此它们可以共用一个模板。但是区别有两点:
你可以通过重写template_name
和form_class
来完成UpdateView的自定义。
article_form.html
, 你可以改为article_update_form.html
。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一般需要定义template_name
, form_class
和success_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一般用来删除某个具体对象。它要求用户点击确认后再删除一个对象。使用这个通用视图,你需要定义模型的名称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>
模板中的变量一般使用双括号``包围。在模板中你可以使用.
获取一个变量(字典、对象和列表)的属性,如:
{{ my_dict.key }}
{{ my_object.attribute }}
{{ my_list.0 }}
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" %}
在模板中你可以使用过滤器(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提供了两种自定义表单的方式:继承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.",
},
}
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 %}
{% 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"
属性。
我们来看下RegistrationForm
是怎么工作的:
RegistrationForm
结合,然后验证表单。create_user
方法创建user对象。HttpResponseRedirect
方法转到登陆页面。** 提示 **:本例的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'
有的时候用户需要在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,一行代码即可增加对一个模型(数据表)的增删查改。试想如果你要自己手动编写后台对一个模型进行增删查改,你一般需要4个urls, 4个视图函数或通用视图和4个模板。当一个项目比较大包含多个app时,而每个app又包含多个模型(数据表)时, 那么编写和维护整个项目管理后台的工作量可想而知。如果你的管理后台主要是内部人员使用,你完全不需要太过在意它的外观。
使用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_hierarchy
和ordering
选项可以轻松帮我们做到。
要自定义数据表显示字段,我们只需对上述代码做出如下改进。我们先定义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)]
目前文章列表中的文章创建日期是英文字段,不是我们想要的。我们可以自定义一个时间格式(比如以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)]
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_horizontal
或filter_vertical
设置,其展示效果为双向穿梭选择器,如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xgCyVdOB-1655293295138)(https://pythondjango.cn/django/basics/11-admin.assets/9.png)]
本例中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)]
SQLite是一个轻量级的开源免费的数据库。它是一种嵌入式数据库,只是一个.db格式的文件,无需安装,配置和启动。SQLite试图为单独的应用程序和设备提供本地的数据存储。
SQLite常见应用场景包括中小型网站,嵌入式设备和应用软件(如android),文件档案管理和桌面程序(exe)文件数据库。SQLite支持多种编程语言(如python)和操作系统(windows, iOS, unix, linux),移植性非常好。
如果你需要开发一个高流量或高并发的网站,SQLite将不能满足你的需求。同时如果你要开发一个Web APP, 不同用户通过网络对数据库进行读写操作,那么SQLite也将不能胜任(比如分布式数据库)。这时我们需要考虑企业级的专业数据库了,比如MySQL和Postgres。
MySQL是最流行的开源免费的关系型数据库,可作为客户端/服务器数据库提供企业级的数据库服务。Django项目中配置使用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'
Django项目中操作MySQL,官方推荐mysqlclient
这个库。
pip install mysqlclient
修改项目文件夹里的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在全球是仅次于MySQL的开源免费的关系型数据库,功能更加强大,是Django首选的关系型数据库。Django项目中配置使用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';
Django项目中操作MySQL,官方推荐psycopg2 这个库。
pip install psycopg2
修改项目文件夹里的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
cookie是一种数据存储技术, 它是将一段文本保存在客户端(浏览器或本地电脑)的一种技术,并且可以长时间的保存。当用户首次通过客户端访问服务器时,web服务器会发送给客户端的一小段信息。客户端浏览器会将这段信息以cookie形式保存在本地某个目录下的文件内。当客户端下次再发送请求时会自动将cookie也发送到服务器端,这样服务器端通过查验cookie内容就知道该客户端之前访问过了。
cookie的常见应用场景包括:
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')
下面是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又名会话,其功能与应用场景与cookie类似,用来存储少量的数据或信息。但由于数据存储在服务器上,而不是客户端上,所以比cookie更安全。不过当用户量非常大时,所有的会话信息都存储于服务器会对服务器造成一定的压力。
第一步:检查基本设置
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,是否设置为浏览器关闭,会话自动失效。
下面是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!')
当你的数据库数据量非常大时,如果一次将这些数据查询出来, 必然加大了服务器内存的负载,降低系统的运行速度。一种更好的方式是将数据分段展示给用户,这就是分页(pagination)的作用。
以博客为例,在Django视图函数中使用Paginator类对首页文章列表进行分页。它会向模板传递2个重要参数:
page_obj
: 分页后的对象列表,在模板中使用for循环遍历即可;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_obj
和is_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 %}
文件或图片一般通过表单进行。用户在前端点击文件上传,然后以POST方式将数据和文件提交到服务器。服务器在接收到POST请求后需要将其存储在服务器上的某个地方。Django默认的存储地址是相对于根目录的/media/文件夹,存储的默认文件名就是文件本来的名字。上传的文件如果不大于2.5MB,会先存入服务器内存中,然后再写入磁盘。如果上传的文件很大,Django会把文件先存入临时文件,再写入磁盘。
Django默认处理方式会出现一个问题,所有文件都存储在一个文件夹里。不同用户上传的有相同名字的文件可能会相互覆盖。另外用户还可能上传一些不安全的文件如js和exe文件,我们必需对允许上传文件的类型进行限制。因此我们在利用Django处理文件上传时必需考虑如下3个因素:
注意:以上事项对于上传图片是同样适用的。
Django文件上传一般有3种方式(如下所示)。我们会针对3种方式分别提供代码示范。
form.save()
方法自动存储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模型包括file
和upload_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
,你还需要手动编写代码存储上传文件。
本项目一共包括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,则渲染一个空的FileUploadForm
在upload_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 %}
{% endblock %}
显示文件清单模板file_list.html
代码如下所示:
# file_upload/templates/file_list.html
{% extends "file_upload/base.html" %}
{% block content %}
File List
RegularFormUpload | ModelFormUpload
| AjaxUpload
{% if files %}
Filename & URL
Filesize
Upload Method
{% for file in files %}
{{ file.file.url }}
{{ file.file.size | filesizeformat }}
{{ file.upload_method }}
{% endfor %}
{% else %}
No files uploaded yet. Please click here
to upload files.
{% endif %}
{% endblock %}
注意:
file.url
, file.name
和file.size
来查看上传文件的链接,地址和大小。filesizeformat
可以将文件大小显示为人们可读的方式,如MB,KB。使用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'}
)
模板跟前面一样,这里就不展示了。
前后端传输数据的编码格式主要有三种, 本文接下来将详细演示。
urlencoded
formdata
json
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() 方法可将,
Ajax上传文件需要借助于js内置对象FormData
,另外上传文件时表单千万别忘了加enctype="multipart/form-data"
属性。
//案例1,点击submi上传文件
$("#submitFile").click(function () {
let formData = new FormData($("#upload-form"));
$.ajax({
url:"/upload/",//也可以写{% url 'upload' %}
type:"post",
data:formData,
//这两个要必须写
processData:false, //不预处理数据 因为FormData 已经做了
contentType:false, //不指定编码了 因为FormData 已经做了
success:function(data){
console.log(data);
}
});
});
//案例2,同时上传文件并提交其它数据
$("#submitFile").click(function () {
//js取到文件,一定要加0
let myfile = $("#id_file").files[0];
//生成一个FormData对象
let formdata = new FormData();
//加其它值
formdata.append('name', $("#id_name").val());
//加文件
formdata.append('myfile', myfile);
$.ajax({
url: '/upload/', //url别忘了加/杠
type: 'post',
//这两个要必须写
processData:false, //不预处理数据 因为FormData 已经做了
contentType:false, //不指定编码了 因为FormData 已经做了
data: formdata,
success: function (data) {
console.log(data);
}
});
});
前后端传输数据的时候一定要确保声明的编码格式跟数据真正的格式是一致的。如果你通过Ajax发送Json格式数据给Django后端,请一定注意以下三点:
contentType
参数指定成application/json
;request.body
获取并处理。$("#submitBtn").click(function () {
var data_obj={'name':'abcdef','password':123456};//Javascript对象
$.ajax({
url:'',
type:'post',
contentType:'application/json', //一定要指定格式 contentType
data:JSON.stringify(data_obj), //转换成json字符串格式
success:function (data) {
console.log(data)
}
});
});
// 第一种方式直接在发送数据中加入csrfmiddlewaretoken
$("#btn").on("click",function () {
$.ajax({
url:"/some_url/",
type:"POST",
data:{
//写在模板中,才会被渲染
'csrfmiddlewaretoken': {{ csrf_token }},
//其它数据
'username': $("#id_username").val(),
'password': $('#id_password').val()
},
success:function (data) {
}
});
});
//通过jquery选择器获取csrfmiddlewaretoken
$("#btn").on("click",function () {
$.ajax({
url:"/some_url/",
type:"POST",
data:{
'csrfmiddlewaretoken':$('[name="csrfmiddlewaretoken"]').val(),
'username': $("#id_username").val(),
'password': $('#id_password').val()
},
success:function (data) {
}
});
});
//使用jquery.cookie.js调用请求头cookie中的csrftoken
联动下拉菜单是Web开发中一个被经常使用的应用。比如当你从一个列表从选择一个国家的时候,联动下拉菜单会同步显示属于该国家所有城市列表供用户选择。今天我们就教你如何使用Django+Ajax生成联动下拉菜单。
我们的模型如下所示:
class Country(models.Model):
name = models.CharField(verbose_name="国家", max_length=50)
def __str__(self):
return self.name
class City(models.Model):
name = models.CharField(verbose_name="城市", max_length=50)
country = models.ForeignKey(Country, on_delete=models.CASCADE, verbose_name="国家",)
def __str__(self):
return self.name
我们的模板如下所示,表单中对应国家和城市下拉菜单的DOM元素id分别为id_country
和id_city
。当用户选择国家后,ajax会携带国家的id值向后台发送请求获得当前国家的所有城市清单,并在前端渲染显示。
{% block content %}
创建用户 - 联动下拉菜单
{% endblock %}
Django负责处理视图Ajax请求的视图函数如下所示:
def ajax_load_cities(request):
if request.method == 'GET':
country_id = request.GET.get('country_id', None)
if country_id:
data = list(City.objects.filter(country_id=country_id).values("id", "name"))
return JsonResponse(data, safe=False)
前端模板及js文件如下所示, 请注意我们是如何在表单中加入了enctype
属性,如何使用FormData
上传文件,并解决了csrftoken
问题的。
{% block content %}
{% endblock %}
{% block js %}
{% endblock %}
Django负责处理视图Ajax请求的视图函数如下所示:
# handling AJAX requests
def ajax_upload(request):
if request.method == "POST":
form = FileUploadModelForm(data=request.POST, files=request.FILES)
if form.is_valid():
form.save()
# Obtain the latest file list
files = File.objects.all().order_by('-id')
data = []
for file in files:
data.append({
"url": file.file.url,
"size": filesizeformat(file.file.size),
})
return JsonResponse(data, safe=False)
else:
data = {'error_msg': "Only jpg, pdf and xlsx files are allowed."}
return JsonResponse(data)
return JsonResponse({'error_msg': 'only POST method accpeted.'})
Django设置文件settings.py包含的选项非常多,但好消息是大部分不需要我们手动去设置。本章就来看下一些常用设置选项及它们背后的含义。
默认值BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
。这个是Django项目文件夹所在目录得绝对路径,一般不要修改。
默认值是True
。在本地开发测试环境下设置DEBUG=True可以显示bug信息,便于开发者找出代码错误所在。当你在部署项目在生产环境时,请切记设置DEBUG=False。这是因为生产环境下打开Debug一旦发生错误或异常会暴露很多敏感设置信息。
注意: 当你设置DEBUG=False
, 你一定要设置ALLOWED_HOSTS
选项, 否则会抛出异常。
默认值为空[]
。设置ALLOWED_HOSTS是为了限定用户请求中的host值,以防止黑客构造包来进行头部攻击。该选项正确设置方式如下:
当你关闭DEBUG时,HOST一般为服务器公网IP或者注册域名。 当你还需要使用子域名时,你可以用.bat.com
。它将匹配bat.com
, www.bat.com
和news.bat.com
。在正式部署项目时,请尽量不要设置ALLOWED_HOSTS=['*']
。
SECRET_KEY是Django根据自己算法生成的一大串随机数,本质是个加密盐,用于防止CSRF(Cross-site request forgery)跨站请求伪造攻击。当部署Django项目到生产环境中时,Django文档建议不直接在settings.py
里输入字符串,而是采取下面几种方法读取SECRET_KEY。
import os
SECRET_KEY = os.environ['SECRET_KEY']
with open('/etc/secret_key.txt') as f:
SECRET_KEY = f.read().strip()
这个设置比较简单,也比较常用,用于增删一个项目(Project)所包含的应用(APP)。只有对列入此项的APP, Django才会生成相应的数据表。
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'polls', # 自定义的APP
]
默认为auth.user
。也可以为自定义用户模型, 如users.user
。
这两个选项是关于静态文件(如CSS, JS,和图片)的最重要的设置,一般设置如下。STATIC_URL是静态文件URL,设置后可以通过使用{% static 'assets/imges/xxx.jpg' %}
方式直接访问/static/文件夹里的静态文件。如果你设置了STATIC_ROOT, 当你运行python manage.py collectstatic
命令的时候,Django会将各app下所有名为static的文件夹及其子目录复制收集到STATIC_ROOT。把静态文件集中一起的目的是为了更方便地通过Apache或Nginx部署。
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
一般情况下我们会尽量把静态文件只放在static文件夹或它的子目录下,所以上述两个设置对于一般项目是够的。那么问题来了,如果你还有一些文件夹中也有静态文件,可是文件夹并不是以static命名也不在static子目录里,此时你也希望搜集使用那些静态文件,你该怎么办呢?这时我们就要设置静态文件目录STATICFILES_DIRS
值了。
默认值为空。当你设置该选项后,python manage.py collectstatic
命令会把static文件夹及静态文件目录STATICFILES_DIRS
里的静态文件都复制到一份到STATIC_ROOT
。比如下例中Django会将下面两个文件夹内容也复制到STATIC_ROOT
。注意里面的路径必需是绝对路径哦。
STATICFILES_DIRS = [
"/home/user/pictures",
"/opt/webfiles/myfiles",
]
media文件价一般用于放置用户上传的文件。对于此文件夹的权限设置异常重要,因为用户可能会上传可执行的文件,影响网站和服务器的安全。对于此文件夹权限,建议使用sudo chmod 755 media
命令设置成755,而不要使用777(可读、可写、可执行)。
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
USE_TZ = True # 默认值True。
TIME_ZONE = 'Asia/Shanghai' # 设置时区
USE_I18N = True # 默认为True,是否启用自动翻译系统
USE_L10N = True # 默认False,以本地化格式显示数字和时间
EMAIL_HOST = 'smtp.qq.com' # 发送者邮箱服务器
EMAIL_PORT = 25 # 端口
EMAIL_HOST_USER = '' # 发送者用户名(邮箱地址)
EMAIL_HOST_PASSWORD = '' # 发送者密码
EMAIL_USE_SSL = True
DEFAULT_FROM_EMAIL = 'xxx@qq.com'
如果你在根目录下建立了一个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',
],
},
},
]
如果你希望在Django项目中使用自定义的中间件,你需要在 MIDDLEWARE
选项里注册。注意:中间件的添加顺序很重要。
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'myapp.middleware.CustomMiddleware1', # 新增自定义中间件。
]
Django默认使用轻量级的SQLite数据库,无需进行任何设置开箱即用。如果你希望使用MySQL或则postgreSQL,你需要先在本地或服务器上安装MySQL或postgreSQL,创建数据库和用户并授权,然后再修改配置文件。
当然小编我并不建议在settings.py
直接写入数据库密码,而是采取读取外部配置文件的方式,更安全。下面以MYSQL为例介绍了基本配置方式。
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # 数据库引擎
'NAME': 'mydb', # 你要存储数据的库名,事先要创建之
'USER': 'xxs', # 数据库用户名
'PASSWORD': 'xxxx', # 密码
'HOST': 'localhost', # 主机
'PORT': '3306', # 数据库使用的端口
}
}
Django中提供了多种缓存方式,如果要使用缓存,需要先在settings.py
中进行配置,然后应用。根据缓存介质的不同,你需要设置不同的缓存后台Backend。
比如如果你希望使用redis做缓存后台,你需要先安装redis
和django_redis
, 然后再修改settings.py
中的缓存配置。
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': 'redis://your_host_ip:6379', # redis所在服务器或容器ip地址
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"PASSWORD": "your_pwd", # 你设置的密码
},
},
}
SESSION_ENGINE = 'django.contrib.sessions.backends.db' # 引擎(默认)
SESSION_COOKIE_NAME = "sessionid" # Session的cookie保存在浏览器上时的key,
SESSION_COOKIE_PATH = "/" # Session的cookie保存的路径(默认)
SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名(默认)
SESSION_COOKIE_SECURE = False # 是否Https传输cookie(默认)
SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http传输(默认)
SESSION_COOKIE_AGE = 60 * 30 # Session的cookie失效日期(30min)(默认)
SESSION_EXPIRE_AT_BROWSER_CLOSE = True # 是否关闭浏览器使得Session过期(默认)
SESSION_SAVE_EVERY_REQUEST = True # 是否每次请求都保存Session,默认修改之后才保存
ntrib.staticfiles’,
‘polls’, # 自定义的APP
]
## AUTH_USER_MODEL
默认为`auth.user`。也可以为自定义用户模型, 如`users.user`。
## STATIC_ROOT和STATIC_URL
这两个选项是关于静态文件(如CSS, JS,和图片)的最重要的设置,一般设置如下。STATIC_URL是静态文件URL,设置后可以通过使用`{% static 'assets/imges/xxx.jpg' %}`方式直接访问/static/文件夹里的静态文件。如果你设置了STATIC_ROOT, 当你运行`python manage.py collectstatic`命令的时候,Django会将各app下所有名为static的文件夹及其子目录复制收集到STATIC_ROOT。把静态文件集中一起的目的是为了更方便地通过Apache或Nginx部署。
STATIC_URL = ‘/static/’
STATIC_ROOT = os.path.join(BASE_DIR, ‘static’)
一般情况下我们会尽量把静态文件只放在static文件夹或它的子目录下,所以上述两个设置对于一般项目是够的。那么问题来了,如果你还有一些文件夹中也有静态文件,可是文件夹并不是以static命名也不在static子目录里,此时你也希望搜集使用那些静态文件,你该怎么办呢?这时我们就要设置静态文件目录`STATICFILES_DIRS`值了。
## STATICFILES_DIRS
默认值为空。当你设置该选项后,`python manage.py collectstatic`命令会把static文件夹及静态文件目录`STATICFILES_DIRS`里的静态文件都复制到一份到`STATIC_ROOT`。比如下例中Django会将下面两个文件夹内容也复制到`STATIC_ROOT`。注意里面的路径必需是绝对路径哦。
STATICFILES_DIRS = [
“/home/user/pictures”,
“/opt/webfiles/myfiles”,
]
## MEDIA_ROOT和MEDIA_URL
media文件价一般用于放置用户上传的文件。对于此文件夹的权限设置异常重要,因为用户可能会上传可执行的文件,影响网站和服务器的安全。对于此文件夹权限,建议使用`sudo chmod 755 media`命令设置成755,而不要使用777(可读、可写、可执行)。
MEDIA_URL = ‘/media/’
MEDIA_ROOT = os.path.join(BASE_DIR, ‘media’)
## 国际化(语言与时间)
USE_TZ = True # 默认值True。
TIME_ZONE = ‘Asia/Shanghai’ # 设置时区
USE_I18N = True # 默认为True,是否启用自动翻译系统
USE_L10N = True # 默认False,以本地化格式显示数字和时间
## 邮箱服务配置
EMAIL_HOST = ‘smtp.qq.com’ # 发送者邮箱服务器
EMAIL_PORT = 25 # 端口
EMAIL_HOST_USER = ‘’ # 发送者用户名(邮箱地址)
EMAIL_HOST_PASSWORD = ‘’ # 发送者密码
EMAIL_USE_SSL = True
DEFAULT_FROM_EMAIL = ‘xxx@qq.com’
## 模板设置
如果你在根目录下建立了一个`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’,
],
},
},
]
## 中间件设置
如果你希望在Django项目中使用自定义的中间件,你需要在 `MIDDLEWARE`选项里注册。注意:中间件的添加顺序很重要。
MIDDLEWARE = [
‘django.middleware.security.SecurityMiddleware’,
‘django.contrib.sessions.middleware.SessionMiddleware’,
‘django.middleware.common.CommonMiddleware’,
‘django.middleware.csrf.CsrfViewMiddleware’,
‘django.contrib.auth.middleware.AuthenticationMiddleware’,
‘django.contrib.messages.middleware.MessageMiddleware’,
‘django.middleware.clickjacking.XFrameOptionsMiddleware’,
‘myapp.middleware.CustomMiddleware1’, # 新增自定义中间件。
]
## 数据库设置
Django默认使用轻量级的SQLite数据库,无需进行任何设置开箱即用。如果你希望使用MySQL或则postgreSQL,你需要先在本地或服务器上安装MySQL或postgreSQL,创建数据库和用户并授权,然后再修改配置文件。
当然小编我并不建议在`settings.py`直接写入数据库密码,而是采取读取外部配置文件的方式,更安全。下面以MYSQL为例介绍了基本配置方式。
DATABASES = {
‘default’: {
‘ENGINE’: ‘django.db.backends.mysql’, # 数据库引擎
‘NAME’: ‘mydb’, # 你要存储数据的库名,事先要创建之
‘USER’: ‘xxs’, # 数据库用户名
‘PASSWORD’: ‘xxxx’, # 密码
‘HOST’: ‘localhost’, # 主机
‘PORT’: ‘3306’, # 数据库使用的端口
}
}
## 缓存设置
Django中提供了多种缓存方式,如果要使用缓存,需要先在`settings.py`中进行配置,然后应用。根据缓存介质的不同,你需要设置不同的缓存后台Backend。
比如如果你希望使用redis做缓存后台,你需要先安装`redis`和`django_redis`, 然后再修改`settings.py`中的缓存配置。
CACHES = {
‘default’: {
‘BACKEND’: ‘django_redis.cache.RedisCache’,
‘LOCATION’: ‘redis://your_host_ip:6379’, # redis所在服务器或容器ip地址
“OPTIONS”: {
“CLIENT_CLASS”: “django_redis.client.DefaultClient”,
“PASSWORD”: “your_pwd”, # 你设置的密码
},
},
}
## Session相关设置
SESSION_ENGINE = ‘django.contrib.sessions.backends.db’ # 引擎(默认)
SESSION_COOKIE_NAME = “sessionid” # Session的cookie保存在浏览器上时的key,
SESSION_COOKIE_PATH = “/” # Session的cookie保存的路径(默认)
SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名(默认)
SESSION_COOKIE_SECURE = False # 是否Https传输cookie(默认)
SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http传输(默认)
SESSION_COOKIE_AGE = 60 * 30 # Session的cookie失效日期(30min)(默认)
SESSION_EXPIRE_AT_BROWSER_CLOSE = True # 是否关闭浏览器使得Session过期(默认)
SESSION_SAVE_EVERY_REQUEST = True # 是否每次请求都保存Session,默认修改之后才保存