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()相反(当与数据库后端配合的时候).
时间: 2024-10-08 23:29:04

django “如何”系列3:如何编写模型域(model filed)的相关文章

Django学习系列之基础

Django介绍 Django简介 Django是一个基于MVC构造的框架.但是在Django中,控制器接受用户输入的部分由框架自行处理,所以 Django 里更关注的是模型(Model).模板(Template)和视图(Views),称为 MTV模式,它们各自的职责如下: 模型(Model),即数据存取层 处理与数据相关的所有事务: 如何存取.如何验证有效性.包含哪些行为以及数据之间的关系等 视图(View),即表现层 处理与表现相关的决定: 如何在页面或其他类型文档中进行显示;模型与模板的桥

Python+Django+SAE系列教程16-----cookie&session

本章我们来讲解cookie和session ,这两个东西相信大家一定不陌生,概念就不多讲了,我们直接来看其用法,首先是cookie,我们在view中添加三个视图,一个是显示cookie的,一个是设置cookie的,如下: def show_cookie(request): if "MyTestCookie" in request.COOKIES: return HttpResponse("Cookie[MyTestCookie]的内容是: %s" % request

Python+Django+SAE系列教程17-----authauth (认证与授权)系统1

通过session,我们可以在多次浏览器请求中保持数据,接下来的部分就是用session来处理用户登录了. 当然,不能仅凭用户的一面之词,我们就相信,所以我们需要认证. 当然了,Django 也提供了工具来处理这样的常见任务(就像其他常见任务一样). Django 用户认证系统处理用户帐号,组,权限以及基于cookie的用户会话.这个系统一般被称为 auth/auth (认证与授权)系统. 这个系统的名称同时也表明了用户常见的两步处理. 我们需要: 1.     验证 (认证) 用户是否是他所宣

Python+Django+SAE系列教程12-----配置MySQL数据库

因为SAE上支持的是Mysql,首先我们要在本地配置一个Mysql的环境 ,我在网上找到MySQL-python-1.2.4b4.win32-py2.7.exe,并双击 安装 选择典型安装 安装结束后,会弹出配置数据库的界面 : 然后输数据管理员密码: 最后是运行服务. 这个过程并不复杂,安装完成Mysql以后,系统会启动数据库服务,由于Mysql是用命令行控制的,想我这样懒的 人还是需要借助一个可视化 工具来管理,我选择MySQL-Front. 在网上找到MySQL_Front_Setup.1

Django 模型系统(model)&ORM--进阶

QuerySet 可切片 使用Python 的切片语法来限制查询集记录的数目 .它等同于SQL 的LIMIT 和OFFSET 子句. >>> Entry.objects.all()[:5] # (LIMIT 5) >>> Entry.objects.all()[5:10] # (OFFSET 5 LIMIT 5) 不支持负的索引(例如Entry.objects.all()[-1]).通常,查询集 的切片返回一个新的查询集 —— 它不会执行查询. 可迭代 articleL

Django - 模型(model)-- ORM

一.ORM介绍 ORM概念 对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术. 简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中. ORM在业务逻辑层和数据库层之间充当了桥梁的作用 ORM由来 让我们从O/R开始.字母O起源于"对象"(Object),而R则来自于"关系"(Relational). 几乎所有的软件开发过程中都

【iOS与EV3混合机器人编程系列之三】编写EV3 Port Viewer 应用监测EV3端口数据

在前两篇文章中,我们对iOS与EV3混合机器人编程做了一个基本的设想,并且介绍了要完成项目所需的软硬件准备和知识准备. 那么在今天这一篇文章中,我们将直接真正开始项目实践. ==第一个项目: EV3 Port Viewer== 项目目的:在iOS设备上通过WiFi连接EV3并且读取EV3每个端口的数据. 大家可以一周之后在App Store上搜索EV3 Port Viewer,那么我已经做了一个范例App发布了,正在审核中 应用的基本使用要求:将EV3和iPhone同时连接到同一个WiFi网络中

Django学习笔记(三)—— 模型 model

疯狂的暑假学习之 Django学习笔记(三)-- 模型 model 参考:<The Django Book> 第5章 1.setting.py 配置 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.', # 用什么数据库管理系统 'NAME': '', # 数据库名称,如果用sqlite,要写完整路径 'USER': '', # 如果用sqlite,这个不用写 'PASSWORD': '', # 如果用sqlite,这个不用写

我给女朋友讲编程CSS系列(4) CSS盒子模型

什么是CSS盒子模型?如何学习CSS的盒子模型? 这篇文章,以 [分享 + 结论]  的方式来写. 1,  看w3school的[CSS 框模型概述] 网址为: http://www.w3school.com.cn/css/css_boxmodel.asp 接着把[CSS内边距],[CSS外边距],[CSS外边距合并]看看. 小结: (1)    一般,在样式表中,都会先把所有元素的外边距和内边距设置为0 * {   margin: 0;  padding: 0;  } * 是通配符,就是一个符