Django学习笔记
Django是一个Python的Web开发框架,它是一个WSGI规范的框架端实现。使用该框架可以把你从Web开发的很多重复劳动中解放出来而专注于业务逻辑。
Django运行速度非常快、并且是可扩容的,安全性方面也做的很好,可以避免一些常见的安全性错误。
最新的1.9版本,要求Python版本为2.7、3.4或者3.5。具体安装步骤本文不赘述,可以参考Python知识集锦。
Django内置了一个简单的开发用Web服务器,可以用于开发和测试,要启动此服务器,只需要:
1 2 3 4 |
# 先进入工程根目录 cd librarymgr # 启动开发服务器 python manage.py runserver |
这个开发服务器会自动重新载入最新的Python代码,便于开发调试。
在生产环境下你应当使用可靠的Web服务器,例如Apache、Nginx等。
这个组合可以作为Django的Web容器。Apache是广泛使用的、模块化的Web服务器;而mod_wsgi模块则是一个Apache模块,同时也是WSGI规范的服务器端实现,安装此模块后Apache可以和任何支持WSGI的Python Web应用通信,例如基于Django的应用。
mod_wsgi有两种运行模式:
- 嵌入式模式:类似于mod_perl,Python被嵌入在Apache中,Python代码在Apache启动时加载到Apache进程空间并驻留直到Apache进程结束。这种方式具有较高的性能
- 守护模式:mod_wsgi产生独立进程,由该进程来处理请求。此进程的运行用户可以不同于Apache进程。此进程可以独立于Apache重启,因而在开发期间可以更无缝的更新代码
手工构建mod_wsgi的步骤如下:
1 2 3 4 5 6 7 8 9 10 11 |
wget https://github.com/GrahamDumpleton/mod_wsgi/archive/4.5.3.tar.gz tar xzf 4.5.3.tar.gz cd mod_wsgi-4.5.3/ ./configure # 在Linux上,Apache2默认使用的MPM(多处理模块)是prefork,下面安装开发支持 # 如果是CentOS,安装 yum install httpd-devel sudo apt-get install apache2-prefork-dev make && sudo make install # 构建好的模块默认被安装到: # Ubuntu 14 /usr/lib/apache2/modules/mod_wsgi.so # CentOS 7 /usr/lib64/httpd/modules/mod_wsgi.so |
然后,你需要让Apache加载mod_wsgi模块。你可以使用a2enmod命令或者修改配置文件:
1 |
LoadModule wsgi_module /usr/lib/apache2/modules/mod_wsgi.so |
如果要使用Django的数据库API,你需要安装数据库服务器。Django支持多种数据库后端,官方支持包括:PostgreSQL、MySQL、Oracle和SQLite,其它主流数据库有第三方支持。开发一个简单的项目,不需要在生成环境部署时,你可以使用SQLite,它不需要独立的服务器,也不需要安装第三方Python模块。
除了后端以外,你还需要安装对应的Python database bindings:
1 2 3 4 |
# MySQL sudo pip install mysqlclient # PostgreSQL sudo pip install psycopg2 |
如果你想用Django的 manage.py migrate 命令为工程模型自动创建表结构,你需要目标数据库用户授予对应的权限。
你可以通过pip安装:
1 2 3 4 5 |
# 安装Release sudo pip install django # 安装开发版本 git clone git://github.com/django/django.git sudo pip install -e django/ |
本章通过一个简单的图书馆管理系统,来了解Django的开发流程和基本的API。
开始编程之前,你需要创建一个Django工程:
1 2 |
cd ~/Python/projects/pycharm django-admin startproject librarymgr |
上面的命令会在当前目录下创建一个librarymgr目录,这是新工程的根目录,其结构如下:
1 2 3 4 5 6 7 |
└── librarymgr # 工程容器目录 ├── manage.py # 一个命令行工具,通过它你可以和Django工程互动 └── librarymgr # 当前工程对应的Python包 ├── __init__.py # 空白文件,仅仅用于识别当前目录为一个包 ├── settings.py # 当前Django工程的设置/配置 ├── urls.py # 当前工程的URL映射声明 └── wsgi.py # WSGI兼容的Web服务器的入口点,部署工程时用到此文件 |
一个Django实例的全部设置: 数据库配置、Django选项、应用程序选项,都存放在工程目录下。
你可以直接把工程容器目录作为PyCharm工程打开,PyCharm会自动识别并启用Django支持。
执行下面的命令,可以启动开发服务器:
1 2 3 4 |
# 默认监听127.0.0.1:8000 python manage.py runserver # 监听所有网络接口 python manage.py runserver 0.0.0.0:8000 |
注意此服务器会自动Reload最新的Python代码,因而你不需要在修改代码后重启服务器。但是某些时候,例如添加了新文件,可能不会自动载入,这时你需要手工重启。
使用PyCharm时,用于启动开发服务器的Run Configuration会自动创建,点击工具栏按钮即可启动。
到目前为止,开发环境已经搭建完毕,可以实现功能了。
在一个工程中,可以包含多个Django应用,有时你可能觉得称之为模块更合适。而每个应用也可以归属于多个工程。工程中的应用共享工程的配置——例如数据库连接。每个Django应用由一个Python包组成,这个包包含一些约定俗成的内容。
你可以把Django应用放置在PYTHONPATH中的任意目录,在这里,我们把它们放置在工程根目录下。执行下面的命令,创建一个应用:
1 |
python manage.py startapp bookmgr |
命令执行完毕后,工程结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
├── bookmgr │ ├── admin.py │ ├── apps.py # 包含当前应用的定义及配置 │ ├── __init__.py │ ├── migrations │ │ └── __init__.py │ ├── models.py # 在这里定义模型类 │ ├── tests.py | ├── urls.py # 在这里定义URLconf,即URL和视图的映射关系 │ └── views.py # 在这里创建新的视图 ├── librarymgr │ ├── __init__.py │ ├── __init__.pyc │ ├── settings.py │ ├── settings.pyc │ ├── urls.py │ └── wsgi.py └── manage.py |
我们为图书管理模块添加一个最简单的视图,在响应中简单的输出欢迎文字:
1 2 3 4 |
from django.http.response import HttpResponse def index(request): return HttpResponse("Welcome to Book Management module.") |
要调用视图,必须将其映射到某个URL,因而我们需要 URLconf ,要为bookmgr创建URLconf,只需要新建一个 urls.py 文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
from django.conf.urls import url from . import views # 该固定名称的变量定义一系列的URL映射规则 urlpatterns = [ # url函数定义URL到视图的映射,注意URL的base部分可能已经被父URLconf脱去。该函数接受4个参数 # regex 匹配URL的正则式,注意此正则式不搜索GET/POST参数或者网址的域名部分,例如对于 # https://librarymgr.gmem.cc/bookmgr/?id=1,进行匹配判断的仅仅时bookmgr/ # view 匹配后,需要调用的视图函数,并把HttpRequest作为第一个入参,正则式中捕获的分组依次作为后续位置参数 # 如果使用正则式的命名分组,则捕获的分组作为关键字参数传入 # kwargs 所谓关键字参数 # name 为URL命名,以便从Django的其它地方——例如模板中——无歧义的引用之 url(r'^$', views.index, name = 'index') ] |
完成URL到视图的映射后,必须把URLconf包含到工程的根URLconf中去:
1 2 3 4 5 6 7 8 9 |
from django.conf.urls import url, include from django.contrib import admin urlpatterns = [ url(r'^admin/', admin.site.urls), # 使用include函数引用其它URLconf # 在处理请求时,前缀匹配部分bookmgr/会自动去除,把剩余的部分传递给子URLconf,进行匹配 url(r'^bookmgr/', include('bookmgr.urls')) ] |
现在启动开发服务器,输入网址http://127.0.0.1:8000/bookmgr/,你应该可以看到之前编写的欢迎文字。
传统的增删改查应用是离不开数据库的,我们可以为工程中所有模块配置共享的数据源。
librarymgr/settings.py 是一个用作配置文件的Python模块,我们可以在其中配置很多代表了Django设置的模块变量,例如数据库连接信息。
默认情况下,配置文件包含了SQLite的配置:
1 2 3 4 5 6 7 8 9 10 11 |
# 工程使用的所有数据源 DATABASES = { # 单个数据源 'default': { # 引擎(数据库后端),其它可选引擎包括: # django.db.backends.postgresql、django.db.backends.mysql、django.db.backends.oracle等 'ENGINE': 'django.db.backends.sqlite3', # 数据库的名称,如果使用SQLite,则指定数据库文件的绝对路径 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } } |
不使用SQLite时,你需要提供额外的配置项。本章使用MySQL数据库:
1 2 3 4 5 6 7 8 9 10 |
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'test', 'USER': 'root', 'PASSWORD': 'root', 'HOST': '127.0.0.1', 'PORT': '3306', } } |
由于默认激活的那些应用程序必须使用表,因此必须初始化数据库,即创建表结构、初始化表数据,执行下面的命令:
1 2 |
# 根据DATABASES设置,以及应用的INSTALLED_APPS的database migrations,完成表创建和数据插入 python manage.py migrate |
配置完数据库后,顺便设置一下时区:
1 |
TIME_ZONE = 'Asia/Shanghai' |
包含该实例激活的Django应用程序的名称,默认激活以下通用应用:
应用程序 | 说明 |
django.contrib.admin | 管理站点(Admin site) |
django.contrib.auth | 身份验证系统 |
django.contrib.contenttypes | 内容类型框架 |
django.contrib.sessions | 会话支持 |
django.contrib.messages | 一个消息框架 |
django.contrib.staticfiles | 处理静态文件的框架 |
图书管理模块涉及的模型包括书籍类目、书籍、书籍实例等,我们需要将它们建模为Python类。
每个模型类都应该是django.db.models.Model的子类型,这是Django提供的充血模型基类。我们创建以下三个类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
from django.db import models from django.utils.encoding import python_2_unicode_compatible @python_2_unicode_compatible # 如果需要支持Python2 class Category(models.Model): # 每个模型会定义若干个Field子类型的变量,这些变量对应数据库的字段,二者默认名字一致 # 字符串类型的字段 # 第一个可选参数是人类友好的字段名称 name = models.CharField('Category name', max_length = 128) # 对其它模型的引用,自引用必须用引号包围的类名或者'self' # Django支持o2m、m2o、m2m等常见数据库关系 # blank=True表示表单验证时允许为空,null=True表示数据库字段可以为空 parent_category = models.ForeignKey('self', blank=True, null=True ) # 下面这个函数定义模型的字符串展示 def __str__(self): return self.name class Book(models.Model): name = models.CharField(max_length = 128) category = models.ForeignKey(Category) isbn = models.BigIntegerField() pub_date = models.DateTimeField() class BookInstance(models.Model): no = models.IntegerField() book = models.ForeignKey(Book) # 枚举值和枚举字段 STATUS_LEND_OUT = 1 STATUS_RETURNED = 0 status = models.SmallIntegerField( choices = ((STATUS_LEND_OUT, 'Lend out'), (STATUS_RETURNED, 'Returned')), # 字段可以提供默认值 default = STATUS_RETURNED ) |
Django根据上面一小片模型代码,可以自动生成Schema(表结构)。首先,你需要安装bookmgr这个应用:
1 2 3 4 |
INSTALLED_APPS = [ # 在开始处添加下面的内容,BookmgrConfig类以及自动在apps.py中定义 'bookmgr.apps.BookmgrConfig' ] |
然后执行下面的命令,生成一个“迁移”:
1 2 3 4 5 6 7 8 9 10 |
# 运行makemigrations命令,告知Django你对模型执行了修改(这里是添加了新模型) # 并且你想把这个改变保存到一个迁移(migration)中 python manage.py makemigrations bookmgr # 输出如下: # Migrations for 'bookmgr': # 0001_initial.py: # - Create model Book # - Create model BookInstance # - Create model Category # - Add field category to book |
所谓迁移,只是磁盘上的文件,Django用它来记录模型(以及对应的数据库Schema)的变化。
你可以到应用的migrations目录查看新生成的迁移0001_initial.py,它同样是一段标准的Python代码。
使用命令 python manage.py sqlmigrate bookmgr 0001 ,可以将上面的0001号迁移文件转换为SQL脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
BEGIN; -- -- Create model Book -- CREATE TABLE `bookmgr_book` ( `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `name` varchar(128) NOT NULL, `isbn` bigint NOT NULL, `pub_date` datetime NOT NULL ); -- -- Create model BookInstance -- CREATE TABLE `bookmgr_bookinstance` ( `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `no` integer NOT NULL, `status` smallint NOT NULL, `book_id` integer NOT NULL) ; -- -- Create model Category -- CREATE TABLE `bookmgr_category` ( `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `name` varchar(128) NOT NULL, `parent_category_id` integer NOT NULL ); -- -- Add field category to book -- ALTER TABLE `bookmgr_book` ADD COLUMN `category_id` integer NOT NULL; ALTER TABLE `bookmgr_book` ALTER COLUMN `category_id` DROP DEFAULT; ALTER TABLE `bookmgr_bookinstance` ADD CONSTRAINT `bookmgr_bookinstance_book_id_73e96047_fk_bookmgr_book_id` FOREIGN KEY (`book_id`) REFERENCES `bookmgr_book` (`id`); ALTER TABLE `bookmgr_category` ADD CONSTRAINT `bookmgr_categ_parent_category_id_736b2e01_fk_bookmgr_category_id` FOREIGN KEY (`parent_category_id`) REFERENCES `bookmgr_category` (`id`); CREATE INDEX `bookmgr_book_b583a629` ON `bookmgr_book` (`category_id`); ALTER TABLE `bookmgr_book` ADD CONSTRAINT `bookmgr_book_category_id_e3c50876_fk_bookmgr_category_id` FOREIGN KEY (`category_id`) REFERENCES `bookmgr_category` (`id`); COMMIT; |
关于上述脚本,你需要了解:
- 我们没有定义主键字段id,但是父类Model定义了,因此继承而来。你也可以覆盖默认定义
- 主键的生成规则依据数据库的不同,自动设置
- 表名称默认为:应用名称_模型类名,模型类名自动转为小写
- 外键字段的名称为:引用字段名称_id
sqlmigrate命令只是打印SQL,并不会执行数据库迁移。后者通过下面的命令完成:
1 2 |
# 把所有尚未执行的迁移应用到数据库中 python manage.py migrate |
注意,你可以在执行迁移之前执行check命令,确保工程中没有错误:
1 |
python manage.py check |
Django的迁移功能很强大,它在django_migrations表中记录数据库的当前状态,因而可以跟踪迁移的进度。每次执行migrate命令时,Django总是把尚未执行的迁移应用到数据库中。迁移功能还能在升级数据库的同时避免数据丢失。
当你修改模型后,记住以下执行三个步骤:
- 在models.py中修改模型类
- 创建迁移文件: python manage.py makemigrations
- 执行数据库迁移: python manage.py migrate
本节我们尝试通过交互式的Python控制台来试验Django的API,首先执行下面的命令启动控制台:
1 |
python manage.py shell |
现在可以可以编写代码试验功能了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
# 导入模型类 from bookmgr.models import Category, Book, BookInstance # 查询所有类目 Category.objects.all() # 输出:[] 因为当前是空表 # 新建一个类目,你可以使用关键字参数传递各字段的值,这是Model类的行为 c = Category(name='social science') # 必须调用save(),才能持久化到数据库中 c.save() # 根据主键get,亦可入参id=1 c_sc = Category.objects.get(pk=1) # 外键字段赋值,使用模型对象,而不是其ID c = Category(name='economics',parent_category=c_sc) c.save() c = Category(name='politics',parent_category=c_sc) c.save() # 自动产生的双向关联,使用对方模型名称_set访问 c_sc.category_set.all() # [<Category: Category object>, <Category: Category object>] # 通过双向关联创建新对象 c_sc.category_set.create(name='law') c_sc.category_set.create(name='pedagogy') |
执行下面的命令,来创建超级用户
1 |
python manage.py createsuperuser |
现在通过http://127.0.0.1:8000/admin登陆,默认情况下你可以管理用户和组。
要允许Admin应用管理模型对象,你需要进行注册:
1 2 3 4 5 6 7 |
from django.contrib import admin from .models import Category, Book, BookInstance admin.site.register(Category) admin.site.register(Book) admin.site.register(BookInstance) |
重启服务后,你就可以在Web界面中添加、修改、删除模型了。 依据模型字段类型的不同,Admin应用会呈现不同的表单元素。
本节我们开发几个视图,用来展示Book模型。
为了可维护性的考虑,不应当把HTML片段硬编码在视图函数中,我们可以使用Django提供的模板子系统来分离Python代码和HTML代码。依据Django的默认设置:
1 2 3 4 5 6 7 8 |
TEMPLATES = [ { # 使用何种模板引擎 'BACKEND': 'django.template.backends.django.DjangoTemplates', # 在应用的根目录下寻找templates目录 'APP_DIRS': True, }, ] |
你需要在应用(bookmgr)的根目录下创建一个 templates子目录,Django会在此目录中寻找需要的模板。
任何网站都需要图片、脚本、样式表等静态资源。在Django框架中应用 django.contrib.staticfiles 专门负责处理静态文件,此应用默认已经加载。
你需要设置静态文件的URL前缀以及静态文件搜索目录:
1 2 3 4 5 6 |
# URL前缀 STATIC_URL = '/static/' # 静态文件搜索目录,这里工程根目录下的static目录 STATICFILES_DIRS = ( os.path.join(BASE_DIR, 'static'), ) |
在Django模板中,你可以类似 /static/css/default.css 这样的硬编码来引用静态文件,或者使用static这个模板标签(tag):
1 2 |
{% load static from staticfiles %} <link rel="stylesheet" href="{% static "css/default.css" %}"> |
要能够正常访问default.css,你必须将其存放到 librarymgr/static/css/default.css
现在我们编写Book模块的首页,它展示欢迎信息和最新出版的10本图书。视图函数如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
def book_index(request): # 上下文对象 context = { 'books': Book.objects.order_by('-pub_date')[:10] } # 加载模板 template = loader.get_template('book/index.html') # 调用模板的render()方法,可以把上下文变量合并入模板,并生成字符串 return HttpResponse(template.render(context, request)) # 亦可使用下面的捷径: from django.shortcuts import render return render(request, 'book/index.html', context) |
URL映射定义如下:
1 |
url(r'^book/index', views.book_index, name = 'book_index') |
模板代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Book Index</title> {% load static from staticfiles %} <link rel="stylesheet" href="{% static 'css/default.css' %}"> </head> <body> {% if books %} <div><span>Recently 10 published books:</span></div> <table> <thead> <tr> <th>Book name</th> <th>Publish date</th> <th>Action</th> </tr> </thead> <tbody> {% for book in books %} <tr> <td><a href="{% url 'book_view' book.id %}">{{ book.name }}</a></td> <td>{{ book.pub_date }}</td> <td><a href="{% url 'book_edit' book.id %}"><img src="{% static 'images/edit.png' %}"/></a></td> </tr> {% endfor %} </tbody> </table> {% else %} No book available. {% endif %} </body> </html> |
这里可以大概了解一下Django模板的语法风格:
- 使用 {% %} 语法声明流程控制指令
- 使用 {{ }} 语法声明占位符
- 使用点号导航语法来访问对象属性
打开:http://127.0.0.1:8000/bookmgr/book/index,可以看到如下页面:
在上一节的模板中,我们有如下一段代码:
1 |
<td><a href="/bookmgr/book/{{ book.id }}">{{ book.name }}</a></td> |
上面的href属性中,硬编码了工程名称和应用名称,这会导致部署时修改URL困难。你随时可以url标签避免硬编码:
1 |
<td><a href="{% url 'book_view' book.id %}">{{ book.name }}</a></td> |
其中book_view就是URLConf中映射条目的name字段:
1 2 3 |
urlpatterns = [ url(r'^book/(?P<book_id>[0-9]+)', views.book_view, name = 'book_view') ] |
上一节我们开发的视图展示了最近10条书籍信息,其中书籍名称具有链接,很明星,此链接点击后应该显示单本书籍的详细信息。
注意详细信息视图的URL设计:http://127.0.0.1:8000/bookmgr/book/view/4,仍然是RESTful风格的URL,最后的数字是书籍的主键。 我们可以这样映射URL:
1 |
url(r'^book/view/(?P<book_id>[0-9]+)', views.book_view, name = 'book_view') |
其中 (?P<group_name>...) 是正则式中的命名分组,使用命名分组后,可以把分组名作为视图函数的入参,其运行时值等于该命名分组的捕获。
视图函数如下:
1 2 3 4 5 6 7 8 9 10 11 12 |
# book_id这个入参名称必须和分组的命名一致 def book_view(request, book_id): try: book = Book.objects.get(pk = book_id) except Book.DoesNotExist: # 触发异常,Django会自动将其转换为404响应 raise Http404('No such book:%i' % book_id) # 上面的try-except块也可以使用快捷方式代替: book = get_object_or_404(Book, pk = book_id) return render(request, 'book/view.html', {'book': book}) |
模板代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>View Book</title> {% load static from staticfiles %} <link rel="stylesheet" href="{% static 'css/default.css' %}"> </head> <body> <div><span>Detail information of: {{ book.name }}</span></div> <table> <tr> <th>Book name:</th> <td>{{ book.name }}</td> </tr> <tr> <th>Category:</th> <td>{{ book.category.name }}</td> </tr> <tr> <th>ISBN:</th> <td>{{ book.isbn }}</td> </tr> <tr> <th>Publish date:</th> <td>{{ book.pub_date }}</td> </tr> </table> </body> </html> |
打开:http://127.0.0.1:8000/bookmgr/book/view/1,可以看到如下页面:
本节我们了解一下Django中的表单处理。
添加视图函数:
1 2 3 4 5 6 7 |
def book_edit(request, book_id, messages=[]): categories = Category.objects.all() try: book = Book.objects.get(pk=book_id) except Book.DoesNotExist: raise Http404('No such book:%i' % book_id) return render(request, 'book/edit.html', {'book': book, 'categories': categories, 'messages': messages}) |
URL映射如下:
1 |
url(r'^book/edit/(?P<book_id>[0-9]+)', views.book_edit, name='book_edit') |
模板代码如下,注意其中的for和if控制结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Edit Book</title> {% load static from staticfiles %} <link rel="stylesheet" href="{% static "css/default.css" %}"> </head> <body> <div><span>Edit: {{ book.name }}</span></div> {% if messages %} {% for msg in messages %} <div class="{% if msg.ok %}ok{% else %}error{% endif %}">{{ msg.message }}</div> {% endfor %} {% endif %} <form action="{% url 'book_commit' %}" method="post"> <table> <tr> <th>Book name:</th> <td> {% csrf_token %} {% comment %} 必须,否则报错:CSRF verification failed. Request aborted. {% endcomment %} <input type="hidden" name="id" value="{{ book.id }}"/> <input type="text" name="name" value="{{ book.name }}"/></td> </tr> <tr> <th>Category:</th> <td> <select name="category"> {% for c in categories %} <option value="{{ c.id }}" {% if c.id == book.category.id %}selected="selected"{% endif %}>{{ c.name }}</option> {% endfor %} </select> </td> </tr> <tr> <th>ISBN:</th> <td> <input type="number" name="isbn" value="{{ book.isbn }}"/> </td> </tr> <tr> <th>Publish date:</th> <td> <input type="date" name="pub_date" value="{{ book.pub_date.isoformat }}"/> </td> </tr> </table> <input class="submit-btn" type="submit" value="Submit"/> </form> </body> </html> |
点击首页的Action列的按钮 ,即可看到此编辑Book的页面:
点击上面的Submit按钮后,需要有服务器程序来处理表单数据,并返回处理结果。这些工作由book_commit视图函数完成:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
def book_commit(request): # 类似于字典的、存放所有POST请求参数的对象。类似的 request.GET 用于访问GET请求参数 data = request.POST messages = [] categories = Category.objects.all() try: book_id = int(data['id']) category_id = int(data['category']) book = Book.objects.get(pk=book_id) category = Category.objects.get(pk=category_id) book.name = data['name'] book.category = category book.isbn = int(data['isbn']) from dateutil.parser import parse book.pub_date = parse(data['pub_date']) messages.append({'ok': True, 'message': 'Book updated: %i' % book_id}) except Book.DoesNotExist: messages.append({'ok': False, 'message': 'No such book: %i' % book_id}) except Category.DoesNotExist: messages.append({'ok': False, 'message': 'No such category: %i' % category_id}) except BaseException as e: print e.message messages.append({'ok': False, 'message': 'Failed to update book, %s' % e.message}) return book_edit(request, book_id, messages) |
注意最后的return语句调用了其它视图函数,这类似于Java Servlet中的请求转发。另外一种处理方式是301重定向:
1 2 |
# reverse用于避免硬编码URL return HttpResponseRedirect(reverse('book_edit', args=(book.id,))) |
这样也可以把用户带回到编辑页面。区别有两点:
- 重定向方式下,不能方便的传递模板上下文变量。例如messages变量
- 重定向方式下,实际上就是让浏览器重新定位到book_edit这个URL,因此浏览器地址是/bookmgr/book/edit/3。前一种方式的浏览器地址是/bookmgr/book/commit
完成开发后,我们需要把工程部署到生产环境的服务器上。
最简单的配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# /librarymgr/为URL前缀。匹配的URL将由wsgi.py处理 WSGIScriptAlias /librarymgr/ /home/alex/Python/projects/pycharm/librarymgr/librarymgr/wsgi.py # PYTHONPATH,指定为工程根目录 WSGIPythonPath /home/alex/Python/projects/pycharm/librarymgr <Directory /home/alex/Python/projects/pycharm/librarymgr/librarymgr> <Files wsgi.py> Require all granted </Files> </Directory> Alias /static/ /home/alex/Python/projects/pycharm/librarymgr/static/ <Directory /home/alex/Python/projects/pycharm/librarymgr/static/> Require all granted </Directory> |
虚拟主机方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
WSGIPythonPath /home/alex/Python/projects/pycharm/librarymgr <VirtualHost 127.0.0.1:80> WSGIScriptAlias / /home/alex/Python/projects/pycharm/librarymgr/librarymgr/wsgi.py <Directory /home/alex/Python/projects/pycharm/librarymgr/librarymgr> <Files wsgi.py> Require all granted </Files> </Directory> Alias /static/ /home/alex/Python/projects/pycharm/librarymgr/static/ <Directory /home/alex/Python/projects/pycharm/librarymgr/static/> Require all granted </Directory> </VirtualHost> |
Leave a Reply