Django框架—模型层之单表查询
一、模型层之ORM
1.什么是ORM
MVC或者MTV框架中包括一个重要的部分,就是ORM,它实现了数据模型与数据库的解耦,即数据模型的设计不需要依赖于特定的数据库,通过简单的配置就可以轻松更换数据库,这极大的减轻了开发人员的工作量,不需要面对因数据库变更而导致的无效劳动
ORM是“对象-关系-映射”的简称;(Object Relational Mapping,简称ORM),ORM是Django中的一个模型,能够独立出来使用的有另一个模块sqlalchemy。
ORM本质就是将类对象的语法翻译成sql语句的一个引擎,我们需要掌握的就是如何使用ORM,写类对象关系语句。
-
类对象-->sql-->pymysql-->mysql服务端-->磁盘
2.ORM和原生sql语句对比
mysql中的原生sql语句写法
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
sql中的表 -- 创建表: CREATE TABLE employee( id INT PRIMARY KEY auto_increment , name VARCHAR (20), gender BIT default 1, birthday DATA , department VARCHAR (20), salary DECIMAL (8,2) unsigned, ); -- sql中的表纪录 -- 添加一条表纪录: INSERT employee (name,gender,birthday,salary,department) VALUES("alex",1,"1985-12-12",8000,"保洁部"); -- 查询一条表纪录: SELECT * FROM employee WHERE age=24; -- 更新一条表纪录: UPDATE employee SET birthday="1989-10-24" WHERE id=1; -- 删除一条表纪录: DELETE FROM employee WHERE name="alex"
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from django.db import models # python的类 class Employee(models.Model): id=models.AutoField(primary_key=True) name=models.CharField(max_length=32) gender=models.BooleanField() birthday=models.DateField() department=models.CharField(max_length=32) salary=models.DecimalField(max_digits=8,decimal_places=2) # python的类对象 # 添加一条表纪录: emp=Employee(name="alex",gender=True,birthday="1985-12-12",epartment="保洁部") emp.save() # 查询一条表纪录: Employee.objects.filter(age=24) # 更新一条表纪录: Employee.objects.filter(id=1).update(birthday="1989-10-24") # 删除一条表纪录: Employee.objects.filter(name="alex").delete()
具体配置参考如下;
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', # 指定存储数据库 'NAME': 'day057_single_table_lesson', # 指定连接的数据库名 'USER': 'root', # 用户名 'PASSWORD': '123', # 密码 'HOST': 'localhost', # 数据库主机 'PORT': 3306, # 数据库端口 } }
修改数据库驱动
在Django中的默认是使用的是自带的db.sqlite3数据库,用户名和密码也是自动创建的,如果我们需要使用mysql数据库,需要先创建mysql数据库,并激活。
然后启动项目,发现会报错no module named MySQLdb 。这是因为django默认你导入的驱动是MySQLdb,可是MySQLdb 对于py3有很大问题,所以我们需要的驱动是PyMySQL,只需要在项目名文件下的__init__总写入:
import pymysql pymysql.install_as_MySQLdb()
可视化插件
二、单表操作
1.创建模型
在项目下创建一个名为app01的应用;
然后在app01下的models.py中创建模型;
from django.db import models # Create your models here. class Book(models.Model): # 创建一个book类 id = models.AutoField(primary_key=True) # 如果表里面没有写主键,表里面会自动生成一个自增主键字段,叫做id字段,orm要求每个表里面必须要写一个主键 title = models.CharField(max_length=32) # 和varchar(32)是一样的,32个字符 pub_date = models.DateField() # 必须存这种格式"2018-12-12" price = models.DecimalField(max_digits=8, decimal_places=2) # max_digits最大位数,decimal_places小数部分占多少位 author = models.CharField(max_length=20,null=True)
2.ORM中的字段和参数
ORM中的字段和mysql中是对应的,每个字段有一些特有的参数,参数使用来限制字段,常用的字段和参数
常用简单字段
-
charField:字符串字段, 用于较短的字符串.
-
CharField 要求必须有一个参数 maxlength, 用于从数据库层和Django校验层限制该字段所允许的最大字符数.
-
-
IntegerField:用于保存一个整数.
-
FloatField:一个浮点数.
-
必须提供两个参数:max_digits:总位数(不包括小数点和符号);decimal_places:小数位数;
-
-
AutoField:一个 IntegerField, 添加记录时它会自动增长. 你通常不需要直接使用这个字段;
-
booleanField:一个表示真或者假的字段
-
TextField:一个带有检查Email合法性的 CharField,不接受 maxlength 参数.
-
DateField:一个日期字段. 共有下列额外的可选参数:
-
参数:Argument:描述;auto_now:当对象被保存时,自动将该字段的值设置为当前时间.通常用于表示 "last-modified" 时间戳;
-
-
DateTimeField:一个日期时间字段. 类似 DateField 支持同样的附加选项.
-
ImageField:类似 FileField, 不过要校验上传对象是否是一个合法图片。
-
-
FileField:一个文件上传字段. 要求一个必须有的参数: upload_to, 一个用于保存上载文件的本地文件系统路径。
注意:在一个 model 中使用 FileField 或 ImageField 需要以下步骤: 1.在你的settings 文件中, 定义一个完整路径给 MEDIA_ROOT 以便让 Django在此处保存上传文件.(出于性能考虑,这些文件并不保存到数据库.) 定义MEDIA_URL 作为该目录的公共 URL. 要确保该目录对WEB服务器用户帐号是可写的. 2.在你的 model 中添加 FileField 或 ImageField, 并确保定义了 upload_to 选项,以告诉 Django使用 MEDIA_ROOT 的哪个子目录保存上传文件.你的数据库中要保存的只是文件的路径(相对于 MEDIA_ROOT).出于习惯你一定很想使用 Django 提供的 get_<#fieldname>_url 函数.举例来说,如果你的 ImageField叫作 mug_shot, 你就可以在模板中以 {{object.#get_mug_shot_url }} 这样的方式得到图像的绝对路径.
-
URLField:用于保存 URL. 若 verify_exists 参数为 True (默认), 给定的 URL 会预先检查是否存在( 即URL是否被有效装入且没有返回404响应)。
-
NullBooleanField: 类似 BooleanField, 不过允许 NULL 作为其中一个选项.。推荐使用这个字段而不要用 BooleanField 加 null=True 选项。
更多约束参数
-
null:指定字段是否可为空,默认是False
-
blank:如果为True,该字段允许不填。默认为False。注意与null的区别,两者的范畴不一致,null是数据库范畴的,而blank是orm中数据验证范畴的。
-
default:字段的默认值。可以是一个值或者可调用对象。如果可调用 ,每有新对象被创建它都会被调用,如果你的字段没有设置可以为空,那么将来如果我们后添加一个字段,这个字段就要给一个default值。
-
primary_key:设置主键,如果为True,那么这个字段就是模型的主键。
-
如果你没有指定任何一个字段的primary_key=True,Django 就会自动添加一个IntegerField字段做为主键,所以除非你想覆盖默认的主键行为,否则没必要设置任何一个字段的primary_key=True。
-
unique:如果该值设置为 True, 这个数据字段的值在整张表中必须是唯一的
-
choices:由二元组组成的一个可迭代对象(例如,列表或元组),用来给字段提供选择项。
如果设置了choices ,默认的表单将是一个选择框而不是标准的文本框,而且这个选择框的选项就是choices 中的选项。
-
db_index:如果db_index=True 则代表着为此字段设置数据库索引。
choices字段约束补充
当我们使用choices字段时,是通过元组的形式来指定选择框的提交值和展示值。
如models中定义一个类
class Student(models.Model): name = models.CharField(max_length=12,null=True) sex_choice = ((1,'男'),(2,'女'),) # 1,2是提交后台的值,男,女是展示给用户看的值 sex = models.IntegerField( choices=sex_choice, )
在视图函数中使用
def test(request): stu_obj = models.Student.objects.get(name='ryxiong') # 获取一个学生对象 print(stu_obj .sex) # 1,直接查看数据库中学生的性别是1 print(stu_obj .get_sex_display()) # 男,通过这个方法可以查到对应的显示文本,方便前端展示数据 print(stu_obj .name) # ryxiong return render(request,'testchoice.html',{'stu_obj ':stu_obj })
模板文件中显示
<p>性别:{{ new_obj.get_sex_display }} </p> <!--没有参数的方法,可以使用万能的句点号调用,展示学生的性别-->
时间字段的独有约束
对于DatetimeField、DateField、TimeField这个三个时间字段,都可以设置如下属性
-
auto_now_add:配置auto_now_add=True,创建数据记录的时候会把当前时间添加到数据库。
-
auto_now:配置上auto_now=True,每次更新数据记录的时候会更新该字段,标识这条记录最后一次的修改时间。
3.同步指令
写完models中的代码后,需要通过两条数据库迁移指令在指定的数据库中创建表。
makemigrations命令
python manage.py makemigrations # 生成记录,每次修改了models里面的内容或者添加了新的app,新的app里面写了models里面的内容,都要执行这两条
makemigrations命令用来将models中的创建代码语句提交到migrations文件中生成对应的脚本文件,第一次提交会生成一个名为0001_initial.py的初始化文件,之后的每一次更新执行makemigrations命令都会将更新的操作更新到一个新的文件中,如0002_book_author.py。
migrate命令
python manage.py migrate #执行上面这个语句的记录来创建表,生成的表名字前面会自带应用的名字,例如:你的book表在mysql里面叫做app01_book表
migrate命令会翻译migrations中文件的语句,然后连接到数据库,执行对应的sql语句创建表;
注意:每一次执行migrations中的文件时,都会在django_migrations表中记录,当我们再次执行migrate命令,Django会查看这个记录表,如果发现执行的命令在记录中存在,说明该命令已经被执行过了,不会再被执行。
所以当我们需要重新初始化数据库中的表结构,不仅仅需要删除表名,还需要删除django_migrations中对应的执行记录。
另外我们可以通过pycharm的功能来执行manage.py相关指令。
几个注意事项
确保配置文件中的INSTALLED_APPS中写入我们创建的app名称
INSTALLED_APPS = [ 'django.contrib.admin', # 这是django提供的一些特殊功能的配置(应用,只是咱们看不到),执行同步指令,django会到这里来找到应用的models创建各个应用的表 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', "book" # 直接写app的名字也行,写'app01.apps.App01Config'也行 ]
注意点2:mysql版本兼容问题
如果报如下错误
django.core.exceptions.ImproperlyConfigured: mysqlclient 1.3.3 or newer is required; you have 0.7.11.None
MySQLclient目前只支持到python3.4,因此如果使用的更高版本的python,需要修改如下:
通过查找路径C:\Programs\Python\Python36-32\Lib\site-packages\Django-2.0-py3.6.egg\django\db\backends\mysql,这个路径里的文件把下面代码注释掉就OK了。
if version < (1, 3, 3): raise ImproperlyConfigured("mysqlclient 1.3.3 or newer is required; you have %s" % Database.__version__)
注意点3:设置显示对应sql语句
如果想打印orm转换过程中执行的对应sql语句,需要在settings中进行如下配置:
LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'console':{ 'level':'DEBUG', 'class':'logging.StreamHandler', }, }, 'loggers': { 'django.db.backends': { 'handlers': ['console'], 'propagate': True, 'level':'DEBUG', }, } }
另一种查看orm代码对应的sql语句方式
from app01 import models def add_book(request): ''' 添加表记录 :param request: http请求信息 :return: ''' book_obj = models.Book(title='python',price=123,pub_date='2012-12-12',publish='人民出版社') book_obj.save() from django.db import connection # 通过这种方式也能查看执行的sql语句 print(connection.queries) return HttpResponse('ok')
注意点4:mysql严格模式设置
mysql严格模式可以严格控制输入数据库数据的安全性,我们知道在mysql客户端设置严格模式只需要在mysql环境中执行
select @@sql_mode; --查看当前mysql的模式
set sql_mode="STRICT_TRANS_TABLES" --设置mysql为严格模式,暂时性的。
那么在Django中如何设置mysql的严格模式呢,其实也很简单,只需要在settings配置数据库为mysql的配置中加上一条选项数据就可以了。
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': "book_manage1", "HOST":"127.0.0.1", "PORT":3306, "USER":"root", "PASSWORD":"123", "OPTIONS":{ "init_command":"set sql_mode='STRICT_TRANS_TABLES'", } # 设置mysql为严格模式 } }
三、单表操作值添加表记录
在python中orm的对应关系有三种:
-
类 ---------->表
-
类对象 ---------->行(记录)
-
类属性 ---------->表的字段(重点)
首先想操作表的增删改查,你需要导入这个表,定义对应函数,通过url访问执行这个函数,如这个例子中我们定义operate函数
# urls.py文件 urlpatterns = [ url(r'^operate/', views.operate), ] # views.py文件 def operate(request): """这里写相关对表的操作""" # 如添加、删除、修改、查询代码
1.添加单条表记录
方式一:用的少
book_boj = models.Book(title="红楼梦",pub_date="2012-12-12",price=500,author="曹雪芹") # pub_date=datetime.date.today()这种方式设置时间数据也可以。 # 实例化一个对象表示一行记录 book_boj.save() # 这个命令就是执行pymysql中的commit命令
方式二:用的多
book_obj = models.Book.objects.create(title="蝶恋花",pub_date="1856-12-6",price=200,author="晏殊") # create方法的返回值book_obj就是插入book表中的蝶恋花这本书籍纪录对象 # Book.objects就像book表的管理器,提供了增删改查的所有方法
2.批量添加表记录
使用bulk_create方法批量添加列表中的对象
import random # 方式3:批量插入 book_list = [] for i in range(10): bk_obj = models.Book( title='枉凝眉%s'%i, pub_date='1846-7-2%s'%(random.randint(0,9)), price=random.randint(80,200), author="一只熊%s"%i ) book_list.append(bk_obj) models.Book.objects.bulk_create(book_list) # 使用bulk_create方法批量添加数据,效率比for循环添加高
3.update_or_create方法
update_or_create方法,有就更新,没有就创建
obj, created = models.Book.objects.update_or_create( id=3, # 查找筛选条件 defaults={ # 更新或添加的数据,有就更新,没有就添加 "title": "红楼梦(下)", "pub_date": "1826-10-8", "price": 250, } )
执行所有插入语句后的表结构如下
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
mysql> select * from app01_book; +----+--------------------+------------+--------+------------+ | id | title | pub_date | price | author | +----+--------------------+------------+--------+------------+ | 1 | 红楼梦 | 2012-12-12 | 500.00 | 曹雪芹 | | 2 | 蝶恋花 | 1856-12-06 | 200.00 | 晏殊 | | 3 | 红楼梦(下) | 1826-10-08 | 250.00 | 曹雪芹 | | 4 | 蝶恋花 | 1856-12-06 | 200.00 | 晏殊 | | 5 | 枉凝眉0 | 1846-07-29 | 124.00 | 一只熊0 | | 6 | 枉凝眉1 | 1846-07-21 | 147.00 | 一只熊1 | | 7 | 枉凝眉2 | 1846-07-23 | 127.00 | 一只熊2 | | 8 | 枉凝眉3 | 1846-07-29 | 92.00 | 一只熊3 | | 9 | 枉凝眉4 | 1846-07-22 | 187.00 | 一只熊4 | | 10 | 枉凝眉5 | 1846-07-29 | 142.00 | 一只熊5 | | 11 | 枉凝眉6 | 1846-07-21 | 132.00 | 一只熊6 | | 12 | 枉凝眉7 | 1846-07-25 | 191.00 | 一只熊7 | | 13 | 枉凝眉8 | 1846-07-28 | 188.00 | 一只熊8 | | 14 | 枉凝眉9 | 1846-07-22 | 141.00 | 一只熊9 | +----+--------------------+------------+--------+------------+ 14 rows in set (0.00 sec)
四、单表操作之查询表(重点)
model中的表类有一个objects属性,这个objects就好像一个管理器,提供了所有的增删改查方法,下面是常用的查询方法。
注意:model对象和queryset对象区别
-
model对象就是表中某一行数据对应的数据模型
-
queryset类型是django中的数据类型,里面存放了model对象,可以为空
1.常用查询方法
all()
查询所有的对象结果,返回一个queryset类型,包含所有的model对象
bk_obj = models.Book.objects.all() # 查询所有结果 print(bk_obj) # <QuerySet [<Book: Book object>, <Book: Book object>, <Book: Book object>, <Book: Book object>, <Book: Book object>, <Book: Book object>, <Book: Book object>, <Book: Book object>, <Book: Book object>, <Book: Book object>, <Book: Book object>, <Book: Book object>, <Book: Book object>, <Book: Book object>]>
注意:如果想显示时,显示特定的特征字段,便于查看,可以在book类中定义__str__
方法。
def __str__(self): return str(self.id) +"." + self.title
filter(**kwargs)
查询满足筛选条件的model对象结果,返回一个queryset类型。
有多个条件使用逗号分隔,条件之间是and的关系,这里无法实现or的关系
bk_obj = models.Book.objects.filter(title="红楼梦",price=500) print(bk_obj) # <QuerySet [<Book: 1.红楼梦>]>
get(**keargs)
查询满足筛选条件的model对象,返回model对像,也就是行记录
注意:
-
get的查询结果有且只有一个。
-
多余一个报错:get() returned more than one Book -- it returned 3!
-
没有也报错:Book matching query does not exist.
bk_obj = models.Book.objects.get(title="枉凝眉1") print(bk_obj) # 6.枉凝眉1,这里返回的是model对象,行记录
exclude(**kwargs)
查询排除了满足筛选条件的所有结果对象,返回一个queryset类型
参数可以为多个,排除满足任意筛选条件的对象
bk_obj = models.Book.objects.exclude(title="蝶恋花",price=127) print(bk_obj) # <QuerySet [<Book: 1.红楼梦>, <Book: 2.蝶恋花>, <Book: 3.红楼梦(下)>, <Book: 4.蝶恋花>, <Book: 5.枉凝眉0>, <Book: 6.枉凝眉1>, <Book: 7.枉凝眉2>, <Book: 8.枉凝眉3>, <Book: 9.枉凝眉4>, <Book: 10.枉凝眉5>, <Book: 11.枉凝眉6>, <Book: 12.枉凝眉7>, <Book: 13.枉凝眉8>, <Book: 14.枉凝眉9>]>
order_by(*field)
由queryset类型的对象来调用,对查询的结果排序,默认按照id的升序,返回仍是queryset类型。
排序默认是升序,如果需要降序,在字段前面添加"-"即可。
bk_obj = models.Book.objects.filter(author="曹雪芹").order_by("-id","price") # 查询作者为“曹雪芹”的书籍,按照id的降序,价格的升序排列 print(bk_obj) # <QuerySet [<Book: 3.红楼梦(下)>, <Book: 1.红楼梦>]>
reverse()
只能有order_by查询返回的queryset类型对象调用,对查询的结果反转。
bk_obj = models.Book.objects.filter(author="曹雪芹").order_by("-id","price").reverse() print(bk_obj) # <QuerySet [<Book: 1.红楼梦>, <Book: 3.红楼梦(下)>]>
count()
由queryset类型的数据来调用,返回数据库中匹配查询(QuerySet)的对象数量。
bk_obj = models.Book.objects.filter(author="曹雪芹").count() print(bk_obj) # 2
first()
由queryset类型的数据来调用,返回第一个model对象,也就是第一条记录。等效于索引取值[0]
bk_obj = models.Book.objects.filter(author="曹雪芹").first() print(bk_obj) # 1.红楼梦 等效于取索引[0]
last()
由queryset类型的数据来调用,返回最后一条记录
exists()
由queryset类型的数据来调用,如果QuerySet包含数据,就返回True,否则返回False
bk_obj = models.Book.objects.filter(author="曹雪芹").exists() print(bk_obj) # True
注意exists与直接对结果判断的区别
-
exists是取查询结果的第一条来判断是否有值,效率高
-
直接判断结果,会遍历所有查询结果,效率低
values(*field) ***
较常用,由queryset类型的对象来调用,返回一个ValueQuerySet对象,一个特殊的queryset,结果是一个可迭代的列表嵌套字典序列
只要是返回queryset类型的对象,就可以继续链式调用queryset对象的其他查找方法。
bk_obj = models.Book.objects.filter(author="曹雪芹").values("id","title") print(bk_obj) # <QuerySet [{'id': 1, 'title': '红楼梦'}, {'id': 3, 'title': '红楼梦(下)'}]>
values_list(*field)
与values()非常相似,只不过values_list返回的是一个列表嵌套元组序列,values返回列表嵌套字典序列
bk_obj = models.Book.objects.filter(author="曹雪芹").values_list("id", "title") print(bk_obj) # <QuerySet [(1, '红楼梦'), (3, '红楼梦(下)')]>
distinct()
对values()和values_list()方法得到的queryset类型数据去重。
注意:
如果表中没有主键,django也会默认生成id为主键,所以我们直接对所有查询结果去重是毫无意义的。
all_books = models.Book.objects.all().distinct() # 不会有重复项
使用方法
bk_obj = models.Book.objects.all().values_list("title","price").distinct() # title和price两个同时重复才算一条重复的记录 print(bk_obj) # 结果中蝶恋花被去重了
小结
只有get、first、last方法拿到的是一个model对象,其他方法拿到的都是queryset类型。
2.基于双下滑线的模糊查询
filter条件中的模糊查询
使用双下划线对条件中的字段进行条件约束
语法:字段名__模糊条件
models.Book.objects.filter(price__in=[250,500]) # price值等于这三个里面的任意一个的对象 models.Book.objects.filter(price__gt=100) # bigthan,大于,大于等于是price__gte=100,别写price>100,这种参数不支持 models.Book.objects.filter(price__lt=100) # smallthan,小于 models.Book.objects.filter(price__range=[100,200]) # sql的between and,大于等于100,小于等于200 models.Book.objects.filter(title__contains="红楼") # title值中包含“红楼”字样 models.Book.objects.filter(title__icontains="python") # 不区分大小写 models.Book.objects.filter(title__startswith="红") # 以什么开头;istartswith,不区分大小写 models.Book.objects.filter(pub_date__year=2012) # 日期数据年份是2012的
关于日期查询
models.Book.objects.filter(pub_date__year=2012) # 年份为2012 models.Book.objects.filter(pub_date__year__gt=2012) # 年份大于2012 models.Book.objects.filter(pub_date__year=2019, pub_date__month=2) # 年份为2019,月份为2月
五、单表操作之删除记录
python中删除一条记录的方法是delete(),而删除记录的调用者可以是一个model对象,也可以是一个queryset集合。
delete运行时会立即删除对象,没有任何返回值。
1.两种对象调用delete
1.model对象调用删除方法,会删除单个model对象
bk_obj = models.Book.objects.get(id=14) # 找到model对象 bk_obj.delete() print(bk_obj) # <QuerySet []>
2.queryset类型调用删除方法,可以一次删除queryset中的所有对象
bk_obj = models.Book.objects.filter(id__in=[13,14]) bk_obj.delete() print(bk_obj) # <QuerySet []>
2.关于联级删除
如果涉及到外键,django中的外键会默认设置联级关系,on delete cascade;也就是说删除一个对象会删除与它相关的外键对象。
obj = models.Book.objects.get(pk=1) # This will delete the Blog and all of its Entry objects. obj.delete()
delete() 方法是 QuerySet 上的方法,但并不适用于 Manager 本身。这是一种保护机制,是为了避免意外地调用 Entry.objects.delete() 方法导致 所有的 记录被误删除。
如果你确认要删除所有的对象,那么你必须显式地调用;
Entry.objects.all().delete()
如果不想联级删除,可以在settings中设置
pubHouse = models.ForeignKey(to='Publisher', on_delete=models.SET_NULL, blank=True, null=True)
六、单表操作之修改记录
update()
models中修改表记录使用update方法;
update方法只能由queryset类型的对象调用,意思是你可以同时更新多条记录,update方法会返回一个整型数值,表示收到影响的记录条数。
bk_obj= models.Book.objects.filter(title__startswith="蝶恋").update(price=150) # 更新所有名字以“蝶恋”开头的数据价格为150 print(bk_obj) # 2
注意
type = "date" 的input标签,value的值必须是'Y-m-d'的格式,这个标签才能认识并被赋值,所以,要通过date过滤给它改变格式。
<input type="date" class="form-control" id="book_pub_date" placeholder="出版日期" name="book_pub_date" value="{{ edit_obj.pub_date|date:'Y-m-d' }}">