Django自定义非主键自增字段实现与外键关系详解

桃夭
桃夭
发布时间:2026-10-14| 6人浏览过
原创

本文详解在Django中实现非主键自增字段的方法,通过继承Field类创建AutoIncreField,并解决save后字段未刷新的Bug。同时补充Django模型中一对多、多对多及一对一外键关系的定义、应用场景及跨表查询操作,包括values双下划线用法。

Django自定义非主键自增字段实现

在Django开发中,有时需要为模型添加非主键的自增字段。本文将介绍如何自定义字段类型来实现这一功能,确保每个实例拥有唯一的递增编号。


# -*- encoding: utf-8 -*-

from django.db.models.fields import Field, IntegerField
from django.core import checks, exceptions
from django.utils.translation import ugettext_lazy as _


class AutoIncreField(Field):
 description = _("Integer")

 empty_strings_allowed = False
 default_error_messages = {
 'invalid': _("'%(value)s' value must be an integer."),
 }

 def __init__(self, *args, **kwargs):
 kwargs['blank'] = True
 super(AutoIncreField, self).__init__(*args, **kwargs)

 def check(self, **kwargs):
 errors = super(AutoIncreField, self).check(**kwargs)
 # 每张表只能设置一个字段为自增长字段,这个字段可以是主键,也可以不是主键,如果不是主键,则必须设置为一种“键(key)”
 # (primary key)也是键(key)的一种,key还包括外键(foreign key)、唯一键(unique key)
 errors.extend(self._check_key())
 return errors

 def _check_key(self):
 if not self.unique:
  return [
  checks.Error(
   'AutoIncreFields must set key(unique=True).',
   obj=self,
   id='fields.E100',
  ),
  ]
 else:
  return []

 def deconstruct(self):
 name, path, args, kwargs = super(AutoIncreField, self).deconstruct()
 del kwargs['blank']
 kwargs['unique'] = True
 return name, path, args, kwargs

 def get_internal_type(self):
 return "AutoIncreField"

 def to_python(self, value):
 if value is None:
  return value
 try:
  return int(value)
 except (TypeError, ValueError):
  raise exceptions.ValidationError(
  self.error_messages['invalid'],
  code='invalid',
  params={'value': value},
  )

 def db_type(self, connection):
 return 'bigint AUTO_INCREMENT'

 def rel_db_type(self, connection):
 return IntegerField().db_type(connection=connection)

 def validate(self, value, model_instance):
 pass

 def get_db_prep_value(self, value, connection, prepared=False):
 if not prepared:
  value = self.get_prep_value(value)
  value = connection.ops.validate_autopk_value(value)
 return value

 def get_prep_value(self, value):
 value = super(AutoIncreField, self).get_prep_value(value)
 if value is None:
  return None
 return int(value)

 def contribute_to_class(self, cls, name, **kwargs):
 assert not cls._meta.auto_field, "A model can't have more than one AutoIncreField."
 super(AutoIncreField, self).contribute_to_class(cls, name, **kwargs)
 cls._meta.auto_field = self

 def formfield(self, **kwargs):
 return None

接下来展示具体的使用方式,包括模型定义和字段配置。


class Test(models.Model):

 id = models.UUIDField(primary_key=True, default=uuid4)
 numbering = AutoIncreField(_(u'numbering'), unique=True)
 name = models.CharField(_(u'name'), max_length=32, blank=True, null=True)

在实际使用过程中,可能会遇到一些Bug,需要特别注意。

主要问题在于调用save()方法后,实例中的自增字段值并未立即刷新,导致numbering字段可能为空。因此,需要在保存后重新获取一次对象以获取正确的编号值。

如果您有其他的修复方案或优化建议,欢迎留言回复,谢谢分享。

针对上述Bug,我们提供了一种修复思路。

虽然这种方法略显不优雅,但通过重写模型的save方法,在保存完成后重新从数据库获取对象(get),并将自增字段的值同步到当前实例,从而解决了数值为空的问题。


class AutoIncreFieldFixMinxin(object):
 def save(self, *args, **kwargs):
 super(AutoIncreFieldFixMinxin, self).save(*args, **kwargs)
 auto_field = self._meta.auto_field.name
 new_obj = self.__class__.objects.get(pk=self.pk)
 setattr(self, auto_field, int(getattr(new_obj, auto_field)))


class Test(AutoIncreFieldFixMinxin, models.Model):
 id = models.UUIDField(primary_key=True, default=uuid4)
 sequence = AutoIncreField(_(u'sequence'), unique=True)
 name = models.CharField(_(u'name'), max_length=100)

Django模型外键关系与跨表操作

此外,了解Django模型中表与表之间的关系对于数据库设计至关重要。

一对多关系通常使用models.ForeignKey指向其他表来实现。

多对多关系则通过models.ManyToManyField关联其他表。

一对一关系可以使用models.OneToOneField来定义。

以下是这些关系的具体应用场景分析。

一对多关系适用于当创建一条数据时,需要从一个可重复选择的单选下拉框中选取一个选项的场景。

例如,在创建用户信息时,需要为用户指定一个类型,如【普通用户】、【金牌用户】或【铂金用户】等,这就是一对多关系的典型应用。

多对多关系适用于在创建数据时,需要从多选下拉框中选择多个选项的场景。

例如,创建用户信息时,需要为用户指定多个爱好,这就涉及多对多关系。

一对一关系适用于在创建数据时,需要从一个单选下拉框中选择且该选项一旦被使用即消失的场景。

例如,原有包含10列数据的表保存了相关信息,随着时间推移,10列数据无法满足需求,需要为原表额外添加5列数据,此时可能需要一对一关系来扩展或关联新表。


ForeignKey(ForeignObject) # ForeignObject(RelatedField)
 to,    # 要进行关联的表名
 to_field=None,  # 要关联的表中的字段名称
 on_delete=None,  # 当删除关联表中的数据时,当前表与其关联的行的行为
     - models.CASCADE,删除关联数据,与之关联也删除
     - models.DO_NOTHING,删除关联数据,引发错误IntegrityError
     - models.PROTECT,删除关联数据,引发错误ProtectedError
     - models.SET_NULL,删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空)
     - models.SET_DEFAULT,删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值)
     - models.SET,删除关联数据,
       a. 与之关联的值设置为指定值,设置:models.SET(值)
       b. 与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象)

       def func():
        return 10

       class MyModel(models.Model):
        user = models.ForeignKey(
        to="User",
        to_field="id"
        on_delete=models.SET(func),)
 related_name=None,  # 反向操作时,使用的字段名,用于代替 【表名_set】 如: obj.表名_set.all()
 related_query_name=None, # 反向操作时,使用的连接前缀,用于替换【表名】 如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名')
 limit_choices_to=None, # 在Admin或ModelForm中显示关联数据时,提供的条件:
     # 如:
      - limit_choices_to={'nid__gt': 5}
      - limit_choices_to=lambda : {'nid__gt': 5}

      from django.db.models import Q
      - limit_choices_to=Q(nid__gt=10)
      - limit_choices_to=Q(nid=8) | Q(nid__gt=10)
      - limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root')
 db_constraint=True  # 是否在数据库中创建外键约束
 parent_link=False  # 在Admin中是否显示关联数据


 OneToOneField(ForeignKey)
 to,    # 要进行关联的表名
 to_field=None  # 要关联的表中的字段名称
 on_delete=None,  # 当删除关联表中的数据时,当前表与其关联的行的行为

     ###### 对于一对一 ######
     # 1. 一对一其实就是 一对多 + 唯一索引
     # 2.当两个类之间有继承关系时,默认会创建一个一对一字段
     # 如下会在A表中额外增加一个c_ptr_id列且唯一:
      class C(models.Model):
      nid = models.AutoField(primary_key=True)
      part = models.CharField(max_length=12)

      class A(C):
      id = models.AutoField(primary_key=True)
      code = models.CharField(max_length=1)

 ManyToManyField(RelatedField)
 to,    # 要进行关联的表名
 related_name=None,  # 反向操作时,使用的字段名,用于代替 【表名_set】 如: obj.表名_set.all()
 related_query_name=None, # 反向操作时,使用的连接前缀,用于替换【表名】 如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名')
 limit_choices_to=None, # 在Admin或ModelForm中显示关联数据时,提供的条件:
     # 如:
      - limit_choices_to={'nid__gt': 5}
      - limit_choices_to=lambda : {'nid__gt': 5}

      from django.db.models import Q
      - limit_choices_to=Q(nid__gt=10)
      - limit_choices_to=Q(nid=8) | Q(nid__gt=10)
      - limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root')
 symmetrical=None,  # 仅用于多对多自关联时,symmetrical用于指定内部是否创建反向操作的字段
     # 做如下操作时,不同的symmetrical会有不同的可选字段
     models.BB.objects.filter(...)

     # 可选字段有:code, id, m1
      class BB(models.Model):

      code = models.CharField(max_length=12)
      m1 = models.ManyToManyField('self',symmetrical=True)

     # 可选字段有: bb, code, id, m1
      class BB(models.Model):

      code = models.CharField(max_length=12)
      m1 = models.ManyToManyField('self',symmetrical=False)

 through=None,  # 自定义第三张表时,使用字段用于指定关系表
 through_fields=None, # 自定义第三张表时,使用字段用于指定关系表中那些字段做多对多关系表
     from django.db import models

     class Person(models.Model):
      name = models.CharField(max_length=50)

     class Group(models.Model):
      name = models.CharField(max_length=128)
      members = models.ManyToManyField(
      Person,
      through='Membership',
      through_fields=('group', 'person'),
      )

     class Membership(models.Model):
      group = models.ForeignKey(Group, on_delete=models.CASCADE)
      person = models.ForeignKey(Person, on_delete=models.CASCADE)
      inviter = models.ForeignKey(
      Person,
      on_delete=models.CASCADE,
      related_name="membership_invites",
      )
      invite_reason = models.CharField(max_length=64)
 db_constraint=True,  # 是否在数据库中创建外键约束
 db_table=None,  # 默认创建第三张表时,数据库中表的名称

ForeignKey(或ForeignObject)用于实现跨表操作,其关键参数包括:

跨表操作的第一种方式是通过对象属性直接访问。

v = models.Host.objects.filter(nid__gt=0)

v[0].b.caption #通过.进行跨表操作,在对象中去做跨表操作用.

跨表操作的第二种方式是利用values()方法配合双下划线进行查询。

v = models.Host.objects.filter(nid__gt=0).values('nid','hostname','b_id','b__caption') #使用values()取值时可以用双下划线做跨表操作

for row in v: print(row['nid'],row['hostname'],row['b_id'],row['b__caption'])

在前端模板中展示这些数据。

<td>{{ row.b__caption }}</td> # 用双下划线做跨表操作

以上这篇django自定义非主键自增字段类型详解(auto increment field)就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持本站。

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。