django “如何”系列3:如何编写模型域(model filed)
django自带很多的域类--CharField,DateField等等--,如果django的这些域都不能满足你精确的要求,那么你可以编写自己的模型域。
django自带的域没有和数据库列类型一一对应的,只有简单的VARCHAR,INTEGER等类型,为了使用更复杂的类型,例如多边形,你可以定义你的域子类;或者,你可能有一个很复杂的对象,这个对象可以通过某些方法序列化标准的数据库列类型,那么你可以定义自己的域子类。
示例对象
创建自定义的域需要注意一些特别的问题,为了使问题更简单的得以阐述,我们使用一个例子来说明吧。
我们使用一副扑克牌作为例子,类Hand代表一手牌,一手牌有52张,平均的发给四个玩家东南西北
class Hand(object):
"""A hand of cards (bridge style)""" def __init__(self, north, east, south, west):
# Input parameters are lists of cards ('Ah', '9s', etc)
self.north = north
self.east = east
self.south = south
self.west = west # ... (other possibly useful methods omitted) ...
这只是一个正常的python类,没有任何的django特性,我们假设hand属性在我们的模型中是一个Hand实体,我们可以做类似下面的事情
example = MyModel.objects.get(pk=1)
print example.hand.north new_hand = Hand(north, east, south, west)
example.hand = new_hand
example.save()
我们从我们的模型中赋值和检索hand属性,这和其他的python类没什么区别,问题在于,django是如何处理保存和加载这样一个对象的。
背景理论
数据库存储
模型的域无论如何都应该被转换成已经存在的数据库列类型中合适的一个。尽管不同的数据库提供了不同的列类型的集合,但规则都是一样的:你只能使用提供的那些列类型,无论你想存储什么。所以,你只能去迎合某个数据库列类型或者有一个直接的方法可以把你的数据转换成一个字符串。
据我们的Hand例子,我们可以把扑克牌数据转换成一个104的字符串(使用事先约定的顺序编号,如北东南西),所以Hand对象可以以文本或者字符的类型存储在数据库中。
一个域类是做什么的
我们这节里面说的域类,如果没有特别说明都是指模型域,而不是表单域。
django所有的域都是django.db.models.Field的子类。意识到django的域类并不似存储在你的模型的属性里面这一点非常重要,模型属性包含正常的python对象,当一个模型被创建的时候,你在一个模型里面定义的域类是被存储在Meta类的,这是因为当你仅仅是创建和修改属性的时候,域类并不是必需的,相反,域类只是提供了属性值和 数据库存储或者序列化 时候需要的转换机制而已。
当创建自己的域时,请记住上面这一点。记住,当你需要自定义一个域的时候,你只需要创建下面这两个类:
- 第一个类是用户可以操纵的python对象
- 第二个类是Field的子类
下面我们通过例子来讲述吧
编写一个field子类
当设计你的filed子类的时候,你首先要考虑的是有没有那双鞋已经存在的域类是比较接近你的需求的,如果有,请继承那个域类,否则,你只能继承Field这个比较底层的类了
初始化你的域类其实是一件:把你传进来的具体参数分离开来然后传给Filed(或者某个父类)的__init__()方法
在我们的例子中,我们把我们的域称为HandField,由于没有存在其他的域类接近我们的需求,我们直接继承Field好了
from django.db import models class HandField(models.Field): description = "A hand of cards (bridge style)" def __init__(self, *args, **kwargs):
kwargs['max_length'] = 104
super(HandField, self).__init__(*args, **kwargs)
在这个例子中,我们的HandField接受大部分的域的可选参数,下面我们会介绍,同时我们确保了它有一个精确的长度max_length,Field.__inti__()接受的参数如下:
- verbose_name
- name
- primary_key
- max_length
- unique
- blank
- null
- db_index
- rel: 用于相关域,想ForeignKey。仅供高级使用
- default
- editable
- serialize: 默认为真,如果是False,当该域传递给序列器的时候不会被序列化
- unique_for_date
- unique_for_month
- unique_for_year
- choices
- help_text
- db_column
- db_tablespace: 仅供索引创建,如果后端支持tablespaces的话,一般你可以忽略这个参数
- auto_created: 仅供高级使用
没特别说明,这些参数的意义和django默认的意义一致
SubfieldBase元类
处于两个原因,我们使用域的子类:利用通用的数据库列类型或处理复杂的python类型。显然,两者合二为一是有可能的,不够一般情况下你是用不到的。
如果你在处理自定义的python类型,比如我们的Hand类,我们需要确保,当django初始化一个我们模型的实例和给我们的自定义的域属性赋予数据库值的时候,我们可以把该值转换成适合的python对象。这个过程内部的实现有点复杂,但我们要写的代码是比较简单的:确保你使用了一个特定的元类。如下:
lass HandField(models.Field): description = "A hand of cards (bridge style)" __metaclass__ = models.SubfieldBase def __init__(self, *args, **kwargs):
# ...
这保证了当属性被初始化的时候,to_python()方法会被调用
ModelForms和自定义域
如果你是使用SubfieldBase,to_python()将在每次域的实例被赋值的时候被调用,这意味着无论在哪里什么时候发生赋值,你必须保证这是一个正确的数据类型或者你有异常处理。
当你使用ModelForms的时候这点尤为重要,当保存一个ModelForm的时候,django将使用表单值去实例化模型实例。然而,如果清理后的表单数据不能作为有效的输入时,正常的表单验证过程将会被中断。
因此,你必须保证,用来表示你的自定义的域的表单域,无论是在执行输入验证还是数据清理,都需要把用户提供的表单输入转化成一个to_python()可以兼容的模型域值。这可能要求你编写一个自定义的表单域或者在你的域中实现formfield()方法以返回一个表达域类,该类的to_python()返回一个正确的数据类型。
文档你的自定义域
field.description
一如既往的,你应该为你的域类型编写文档,一遍让你的用户知道这是什么。使用field.description为你的域编写注释吧,这个注释可以被admindocs使用 。
有用方法
一旦你创建了你的Field子类和设置了__metaclass__,你可能考虑覆盖一些标准的方法,下面的方法按重要性排序
- Field.db_type(self,connection):考虑connection对象以及相关的配置,返回Field的数据库列数据类型。例如你已经创建了一个PostgreSQL自定义类型mytype,你可以通过继承Field然后实现db_type()方法来使用这个类型,例如:
from django.db import models class MytypeField(models.Field):
def db_type(self, connection):
return 'mytype'一旦你有了MytypeField,你可以在任何一个模型中使用
class Person(models.Model):
name = models.CharField(max_length=80)
something_else = MytypeField()
如果你致力于创建一个数据库无关的应用,那么你应该注意不同数据库列类型中的不同之处,比如:日期类型在PostgreSQL叫timestamp,在MySQL中叫datetime,那么你可以这样写:
class MyDateField(models.Field): def db_type(self, connection):#检查connection.settings_dict['ENGINE']属性 if connection.settings_dict['ENGINE'] == 'django.db.backends.mysql': return 'datetime' else: return 'timestamp'
db_type()方法仅当第一次创建表的时候被django调用,而不会在其他的任何时候调用,因此它可以运行相当复杂的代码,比如检查connection.settings_dict['ENGINE']。
一些数据库列属性接受参数,比如CHAR(25),25代表最大列长,在这里你可以这样写,虽然有点别扭和牵强。
# This is a silly example of hard-coded parameters.
class CharMaxlength25Field(models.Field):
def db_type(self, connection):
return 'char(25)' # In the model:
class MyModel(models.Model):
# ...
my_field = CharMaxlength25Field()
一个更好的方法是动态传递参数,你可以通过实现__init__()来达到动态传递参数的效果
#有点复杂的例子
class BetterCharField(models.Field):
def __init__(self, max_length, *args, **kwargs):
self.max_length = max_length
super(BetterCharField, self).__init__(*args, **kwargs) def db_type(self, connection):
return 'char(%s)' % self.max_length # In the model:
class MyModel(models.Model):
# ...
my_field = BetterCharField(25)
最后,如果你的列需要相当复杂的SQL设置,db_type()返回None,这会引起django的SQL创建代码去跳过这个域,你应该通过其他的方法在对的表里面创建这个列。
- Field.to_python(self,value):把数据库或者序列器返回的值转换成一个python对象。默认的实现只是简单的返回一个值,因为通常情况下数据库后端会以正确的python格式返回值。也因此,如果你的返回值是比python默认数据类型更复杂的话,你需要覆盖这个方法,按照一般的规则,这个方法应该优雅的处理一下的参数:
- 正确类型的一个实例
- 一个字符串
- 数据库返回的任何你正在使用的列类型
import re class HandField(models.Field):
def to_python(self, value):
if isinstance(value, Hand):
return value # The string case.
p1 = re.compile('.{26}')
p2 = re.compile('..')
args = [p2.findall(x) for x in p1.findall(value)]
return Hand(*args)
- Field.get_prep_value(self,value):和to_python()相反(当与数据库后端配合的时候).
django “如何”系列3:如何编写模型域(model filed)的更多相关文章
- Django学习系列之重写User模型和登录验证
重写User模型 Django内置的User模型可能不适合某些项目,我们可能要基于内置的添加一些字段 创建users app startapp users 修改settings.py配置文件,覆盖默认 ...
- Django学习系列4:编写第一个简单的应用代码
首页视图编写 lists/tests.py from django.test import TestCasefrom django.urls import resolvefrom lists.view ...
- Python+Django+SAE系列教程17-----authauth (认证与授权)系统1
通过session,我们能够在多次浏览器请求中保持数据,接下来的部分就是用session来处理用户登录了. 当然,不能仅凭用户的一面之词,我们就相信,所以我们须要认证. 当然了,Django 也提供了 ...
- Django 1.10中文文档-模型参考
模型字段 本文档包含了Django提供的全部模型 Field 包括 字段选项 和 字段类型 的API参考. 参见 如果内建的字段不能满足你的需求, 你可以蚕食 django-localflavor ( ...
- Django 模型系统(model)&ORM--进阶
QuerySet 可切片 使用Python 的切片语法来限制查询集记录的数目 .它等同于SQL 的LIMIT 和OFFSET 子句. >>> Entry.objects.all()[ ...
- Django 初识之安装下载以及模型目录简介
Django 一.web应用 web应用什么 Web应用程序是一种可以通过Web访问的应用程序,程序的最大好处是用户很容易访问应用程序,用户只需要有浏览器即可,不需要再安装其他软件 应用程序有两种模式 ...
- Django模型(model)系统
Object Relational Mapping(ORM) ORM介绍 ORM概念 对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据 ...
- 创建app子应用,配置数据库,编写模型,进行数据迁移
文章目录 web开发django模型 1.创建app子应用 2.配置子应用 3.使用 4.配置子应用管理自已的路由 django数据库开发思维与ORM 1.创建数据库 2.配置数据库 3.安装pymy ...
- [Django]下拉表单与模型查询
前言:本文主要针对自定义下拉表单制作,下拉表单的内容是取至于数据库,即动态实现下拉表单 正文: 动态实现下拉表单有两种方法: 一.自己手动写 html 模板中的 <form ...> &l ...
随机推荐
- POJ1066:Treasure Hunt——题解
http://poj.org/problem?id=1066 题目大意:给一个由墙围成的正方形,里面有若干墙,每次破墙只能从(当前看到的)墙的中点破,求最少破多少墙才能看到宝藏. —————————— ...
- BZOJ5343 & 洛谷4602 & LOJ2555:[CTSC2018]混合果汁——题解
https://www.luogu.org/problemnew/show/P4602 https://loj.ac/problem/2555 https://www.lydsy.com/JudgeO ...
- BZOJ1492:[NOI2007]货币兑换——题解
http://www.lydsy.com/JudgeOnline/problem.php?id=1492 (题目描述太长了不粘贴了……) ……………………………………………………… 我 是自己做的 抄 ...
- manacher 板子
这个东西不难,不知道回文自动机咋样,掌握一种简单的写法就好了. 洛谷板子,欢迎来hack #include <cstdio> #include <cstring> int mi ...
- 【HASH】【UVA 10125】 Sumset
传送门 Description 给定一个整数集合S,求一个最大的d,满足a+b+c=d,其中a,b,c,d∈S Input 多组数据,每组数据包括: 第一行一个整数n,代表元素个数 下面n行每行一个整 ...
- hibernate、mybatis、beetsql的学习
先推荐两篇文章吧: https://my.oschina.net/xiandafu/blog/617542 http://blog.csdn.net/xiandafu/article/details/ ...
- HDU 4258 斜率优化dp
Covered Walkway Time Limit: 30000/10000 MS (Java/Others) Memory Limit: 131072/131072 K (Java/Othe ...
- 清除windows系统垃圾文件简易脚本(bat)
@echo off echo 正在清除系统垃圾文件,请稍等...... del /f /s /q %systemdrive%\*.tmp del /f /s /q %sy ...
- [解决]java.io.IOException: Cannot obtain block length for LocatedBlock
在hadoop测试集群运行job的过程中发现部分运行失败,有Cannot obtain block length for LocatedBlock,使用hdfs dfs -cat ${文件}的时候也报 ...
- Java nio和io
当学习了Java NIO和IO的API后,一个问题马上涌入脑海: 我应该何时使用IO,何时使用NIO呢?在本文中,我会尽量清晰地解析Java NIO和IO的差异.它们的使用场景,以及它们如何影响您的代 ...