django自定义field类型

介绍

有两种实现方式:

1,编写一个复杂的 Python 对象,让它以某种方式将数据序列化,以适应某个数据库的列类型;

2,创建一个Field子类,从而让你可以使用 model 中的对象。

        这里,我们演示第二种方法。

编写一个 field 的子类

1.编写一个继承自Field类的子类

from django.db import models  
class HandField(models.Field):  
   def __init__(self, *args, **kwargs):  
       kwargs['max_length'] = 104  
       super(HandField, self).__init__(*args, **kwargs) 

我们的 HandField接受大多数标准的字段选项(见下面的列表),但是我们要确保它的长度固定,这是因为它需要容纳 52 张纸牌的数据,一共是104个字符。

上面这些加注解的选项,在自定义字段中所起的作用和在普通字段中一样。详见 field documentation

SubfieldBase 元类

确保你的字段子类使用了一个特别的元类(metaclass):

class django.db.models.SubfieldBase

例如:

class HandField(models.Field):  
    __metaclass__ = models.SubfieldBase  
    def __init__(self, *args, **kwargs):  
        # ... 

这段代码保证 to_python()方法(会在接下来提到),会在初始化属性时被调用。

有用的方法(Useful methods)

一理你创建了 Field子类并且编写了__metaclass__时,你可能会根据你的字段行为,考虑去重写某些标准方法。下面的方法按重要性依次递减,所以让我们从头开始介绍。

定制数据库类型

db_type(self)

根据 DATABASE_ENGINE中定义的数据库设置,返回Field对应的数据库列类型。

假设你已经创建了一个 PostgreSQL 自定义类型,称为 mytype。你就可以通过继承Field和实现db_type()方法,在 Django 中使用这个字段:

 from django.db import models  
 class MytypeField(models.Field):  
     def db_type(self): 
         return 'mytype' 

一旦你有了 MytypeField,你就可以象使用其他Field类型一样,在 model 中使用它:

 class Person(models.Model):  
     name = models.CharField(max_length=80)  
     gender = models.CharField(max_length=1)  
     something_else = MytypeField() 

如果你的目的建立一个数据库通用的应用,你就应该考虑不同数据库中列类型的差异。例如,PostgreSQL 中的日期/时间列类型被称为timestamp,而同样的列在 MySQL 中被称为 datetime。处理这个问题的最简单方法就是在db_type()方法中导入 Django 的设置模块,并检查DATABASE_ENGINE设置。例如:

 class MyDateField(models.Field):  
     def db_type(self):  
         from django.conf import settings  
         if settings.DATABASE_ENGINE == 'mysql':  
             return 'datetime'  
         else:  
            return 'timestamp'

db_type()方法仅仅被 Django 调用一次,就是在框架为你的应用生成 CREATETABLE语句的时候,这就是说,是在你第一次创建你的数据库这个时候,才会调用 db_type()。其他任何时候都不会调用这个方法,所以我们可以在它里面写一些很复杂的方法,比如上面的例子做DATABASE_ENGINE检测。

某些数据库列类型是允许有参数的,比如 CHAR(25)25表示列数据最大的长度。在 model 中定义参数值要比在 db_type()方法硬编码灵活的多。例如,做一个CharMaxlength25Field字段并没有多大意义:

 # This is a silly example of hard-coded parameters.  
 class CharMaxlength25Field(models.Field):  
     def db_type(self):  
         return 'char(25)'  
 # In the model:  
 class MyModel(models.Model):  
     # ...  
     my_field = CharMaxlength25Field() 

更好的方式是在运行时指定参数值-例如,在类实例化之时。在做到这一点,只要实现 django.db.models.Field.__init__()方法即可,例如:

 # This is a much more flexible example.  
 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):  
           return 'char(%s)' % self.max_length  
 # In the model:  
 class MyModel(models.Model):  
     # ...  
     my_field = BetterCharField(25) 

最后,如果你的列在生成时需要很复杂的 SQL ,那就在 db_type()中返回None。这会在 Django 在生成创建数据库代码时跳过这个字段。你需要以别的某种方式来创建这个字段对应的列,当然,这也告诉了你如何不使用 db_type() 这种方式来创建字段。

将数据库内容转换成 PYTHON 对象

to_python(self,value)

将从数据库/序列器中取得的值转换成 Python 对象。

对于大我数应用来说,后台数据库返回格式正确的数据(比如字符串),默认的实现只返回 value

如果你的自定义 Field类要处理比字符串,时间,整数或是浮点数更复杂的数据结构,你就需要重写这个方法。做为一般规则,该方法可以优雅地处理下列参数:

  • 一个合适的类型的实例 (例如,在我们的例子中就是 Hand)。
  • 一个字符串(例如,从反序列化中得到).
  • 数据根据你使用的列类型所返回的一切数据。

在我们的 HandField类当中,我们将数据保存为数据库中的 VARCHAR 字段,所以我们需要在to_python()中处理字符串和Hand实例:


要注意该方法总是返回一个 Hand实例。这就是我们想保存在 model 属性中的 Python 对象。

牢记:如果你的自定义字段需要to_python()方法在被创建时被调用,你应当使用早先提到的The SubfieldBase metaclass,否则to_python()将不会被自动调用。

将 PYTHON 对象转换成数据库的值(CONVERTING PYTHON OBJECTS TO DATABASE VALUES)

get_db_prep_value(self,value)

这是 to_python()的反方法,用来将 Python 对象转换成数据库中的值或是序列化的值。 value参数就是当前 model 的属性值。(字段中没有包含它的 modela 引用,所以它并不能获取自己的职。),该方法返回某种形式的数据,用来做为后台数据库查询中参数。

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) 
get_db_prep_save(self,value)

和上面的方法一样,唯一的不同是该方法只在字段值必须被保存到数据库的时候才被调用。就象它的函式名get_db_prep_value所反映的那样,只有当字段值在保存时所做的转换与查询时有所不同的情况下,才使用该方法。

class HandField(models.Field):  
   # ...  
   def get_db_prep_value(self, value):  
       return ''.join([''.join(l) for l in (value.north,  
         value.east, value.south, value.west)]) 

存储前对字段进行预处理

pre_save(self,model_instance,add)

在运行 get_db_prep_save()之前,就会调用该方法,然后从model_instance为该字段返回相应的属性。属性名称在self.attname(在Field中)。如果 model 第一次保存在数据库中,add参数值就是True,其他时候都是False

如果你仅仅想在保存之存对数据库某种方式的预处理,只要重写该方法即可。例如,Django 的 DateTimeField字段就利用该方法来正确的使用auto_now还是auto_now_add

如果你要重写该方法,就必须在最后返回该属性的值。如果你改动了字段值,你也应该更新字段属性。所以对该 model 的引用始终都返回正确的值。

为在数据库筛选而做准备工作

get_db_prep_lookup(self,lookup_type,value)

如果 value要在数据库筛选条件中使用 ( SQL 中的WHERE从句),就要提前进行准备工作。lookup_type得是可用的 Django 过滤器的筛选条件之一:exact,iexact,contains,icontains,gt,gte,lt,lte,in,startswith,istartswith,endswith,iendswith,range,year,month,day,isnull,search,regex, 和iregex.

你的方法必须要为处理这些 lookup_type值而做好准备,而且如果value是一个错误的类型(比如应该是一个对象,却用了一个列表),那就要抛出ValueError异常;或者该字段并不支持这种查询类型,就会抛出TypeError导常。对很多字段来说,你可以单独处理那些特殊的筛选条件,而将其他的交给父类的get_db_prep_lookup()方来来处理。

如果你要实现 get_db_prep_save(),通常你要先实现get_db_prep_lookup()。如果你不这么做,get_db_prep_value就会调用默认地实现来管理exact,gt,gte,lt,lte,inrange筛选条件。

你也会想实现该方法来限制在自定义字段类型中所用到的筛选条件。

要注意,对 rangein筛选而言,get_db_prep_lookup将接收一个对象列表(可能是正确的类型) 并且需要将他们转换成适应数据库的列表。大多数情况下,你可以重用get_db_prep_value(),或是至少重用一部分。

例如,下面的代码实现了 get_db_prep_lookup来限制两种条件,而只使用exactin:

 class HandField(models.Field):  
   # ...  
   def get_db_prep_lookup(self, lookup_type, value):  
   # We only handle 'exact' and 'in'. All others are errors.  
   if lookup_type == 'exact':  
        return [self.get_db_prep_value(value)]  
   elif lookup_type == 'in':  
         return [self.get_db_prep_value(v) for v in value]  
   else:  
      raise TypeError('Lookup type %r not supported.' % lookup_type) 

为 MODEL 字段指定表单字段

formfield(self,form_class=forms.CharField,**kwargs)

在某字段在 model 中显示时,该方法返回字段默认的表单字段。我们通过 ModelForm调用该方法。

所有的 kwargs字典被直接传递给表单字段的Field__init__()方法。正常情况下,你所要做就是为form_class参数设置一个默认值,然后进一步委托给父类来处理。这可能要求你编写一个自定义字段(甚至可能是一个部件 widget)。 详见表单文档(forms documentation),再参考一下django.contrib.localflavor中展示自定义部件的代码。

继续回到我们的例子中,我们可以编写一个 formfield()方法:

 class HandField(models.Field):  
   # ...  
   def formfield(self, **kwargs):  
       # This is a fairly standard way to set up some defaults 
       # while letting the caller override them.  
       defaults = {'form_class': MyFormField}  
       defaults.update(kwargs)  
       return super(HandField, self).formfield(**defaults) 

这段代码假设我们已引入了一个 MyFormField字段类 (它有默认的部件)。 这个文档并不涉及编写编写自定义表单字段的细节。

自己写一个简单的demo:

class extraInfoDictField(models.TextField):
    __metaclass__ = models.SubfieldBase
    def to_python(self,value):
        if value is None: 
            return {}
        if  isinstance(value,dict):
            return value
        return eval(value)

    def get_db_prep_save(self,value):   #django从1.2起为这个方法添加了一个参数
    '''  1.2以上为get_db_prep_save(self, value, connection) '''
        if value is None:return str({})
        return str(value)
#业务
class Business(models.Model):
    .......
    extra_info = extraInfoDictField('额外信息',null=True)
    addtime =  models.DateTimeField(null=True)
    changetime =  models.DateTimeField(null=True)


 

 

posted @ 2013-05-14 16:15  jianhong  阅读(190)  评论(0编辑  收藏  举报