【问题标题】:Setting default value for Foreign Key attribute设置外键属性的默认值
【发布时间】:2012-03-07 21:26:27
【问题描述】:

为模型中的外键字段设置默认值的最佳方法是什么?假设我有两个模型,StudentExam,学生有exam_taken 作为外键。理想情况下,我将如何为其设置默认值?这是我的努力记录

class Student(models.Model):
   ....
   .....
   exam_taken = models.ForeignKey("Exam", default=1)

有效,但预感有更好的方法。

def get_exam():
    return Exam.objects.get(id=1)

class Student(models.Model):
    ....
    .....
    exam_taken = models.ForeignKey("Exam", default=get_exam)

但这会失败,因为同步时表不存在错误。

任何帮助将不胜感激。

【问题讨论】:

  • @NitzanTomer 它用于 AdminModelField。以前看过。
  • @TomIngram: default=get_exam() 将立即调用get_exam 并永久存储该值,而default=get_exam 存储稍后将在每次使用default 属性时调用的方法,以获取那一刻的价值。它通常与日期时间一起使用,即default=datetime.nownot default=datetime.now()
  • @TomIngram:我不是在争论一种方法相对于另一种方法的优点。我的意思只是它是有效的,而且作者似乎想要那样。
  • @TomIngram:不同的是,当你添加括号时,它不再是“可调用的”;它是一个静态值,与只放一个整数没有什么不同。

标签: django foreign-keys default


【解决方案1】:

大多数这些方法的问题是它们在模型中使用 HARD CODED 值或 lambda 方法,自 Django 版本 1.7 起不再支持这些方法。

在我看来,这里最好的方法是使用sentinel 方法,该方法也可用于on_delete 参数。

所以,在你的情况下,我会这样做

# Create or retrieve a placeholder
def get_sentinel_exam():
    return Exam.objects.get_or_create(name="deleted",grade="N/A")[0]

# Create an additional method to return only the id - default expects an id and not a Model object
def get_sentinel_exam_id():
    return get_sentinel_exam().id

class Exam(models.Model):
    ....
    # Making some madeup values
    name=models.CharField(max_length=200) # "English", "Chemistry",...
    year=models.CharField(max_length=200) # "2012", "2022",...

class Student(models.Model):
    ....
    .....
    exam_taken = models.ForeignKey("Exam",    
                       on_delete=models.SET(get_sentinel_exam),
                       default=get_sentinel_exam_id
                 )

现在,当您刚刚添加 exam_taken 字段时,会使用有保证的现有值,同时,在删除考试时,学生本身不会被删除,并且具有 deleted 值的外键。

【讨论】:

    【解决方案2】:

    我知道的最好的方法是使用 lambdas

    class TblSearchCase(models.Model):
        weights = models.ForeignKey('TblSearchWeights', models.DO_NOTHING, default=lambda: TblSearchWeights.objects.get(weight_name='value_you_want'))
    

    所以你可以指定默认行..

    default=lambda: TblSearchWeights.objects.get(weight_name='value_you_want')
    

    【讨论】:

      【解决方案3】:

      正如@gareth 的answer 中已经暗示的那样,硬编码默认的id 值可能并不总是最好的主意:

      如果数据库中不存在id 值,那么您就有麻烦了。即使那个特定的id 值确实存在,相应的对象也可能会改变。在任何情况下,当使用硬编码的id 值时,您都必须求助于数据迁移或手动编辑现有数据库内容。

      为防止这种情况,您可以将get_or_create()unique 字段结合使用(id 除外)。

      我会这样做:

      from django.db import models
      
      class Exam(models.Model):
          title = models.CharField(max_length=255, unique=True)
          description = models.CharField(max_length=255)
      
          @classmethod
          def get_default_pk(cls):
              exam, created = cls.objects.get_or_create(
                  title='default exam', defaults=dict(description='this is not an exam'))
              return exam.pk
      
      
      class Student(models.Model):
          exam_taken = models.ForeignKey(to=Exam, on_delete=models.CASCADE,
                                         default=Exam.get_default_pk)
      

      这里Exam.title 字段用于获取唯一对象,Exam.description 字段说明了我们如何使用defaults 参数(用于get_or_create)来完全指定默认的Exam 对象。

      请注意,我们返回 pk,正如 docs 所建议的那样:

      对于像 ForeignKey 这样映射到模型实例的字段,默认值应该是它们引用的字段的值(pk,除非设置了 to_field)而不是模型实例。

      另请注意,default 可调用对象在 Model.__init__() (source) 中进行评估。因此,如果您的默认值取决于同一模型的另一个字段,或者取决于请求上下文,或者取决于客户端表单的状态,您可能应该查看elsewhere

      【讨论】:

      • 如果你还没有对我来说任何迁移,它就不起作用。我为用户使用字段角色,我使用get_default_user_role 回调作为默认参数,就像在你的示例中一样。因此,如果我没有迁移,我会在 python manage.py makemigration 上获得 no such table: users_role。但是,如果我在 User 模型和 run makemigrations 中评论 role field - 没问题。在我再次取消注释role fieldrun migrations 之后,没有任何问题。也许你可以阻止我?
      • @parfeniukink:我很乐意提供帮助,但是如果没有看到您的实际代码,就很难判断出了什么问题。也许您可以创建一个包含更多详细信息的新问题?
      • 哦,非常感谢,但我已经完成了这个任务)))
      【解决方案4】:

      我在 Django Admin 中寻找解决方案,然后我发现了这个:

      class YourAdmin(admin.ModelAdmin)
      
          def get_changeform_initial_data(self, request):
              return {'owner': request.user}
      

      这也允许我使用当前用户。

      see django docs

      【讨论】:

        【解决方案5】:

        我会稍微修改上面@vault 的答案(这可能是一个新功能)。绝对希望通过自然名称来引用该字段。但是,我不会覆盖Manager,而是简单地使用ForeignKeyto_field 参数:

        class Country(models.Model):
            sigla   = models.CharField(max_length=5, unique=True)
        
            def __unicode__(self):
                return u'%s' % self.sigla
        
        class City(models.Model):
            nome   = models.CharField(max_length=64, unique=True)
            nation = models.ForeignKey(Country, to_field='sigla', default='IT')
        

        【讨论】:

          【解决方案6】:

          我使用自然键来采用更自然的方法:

          <app>/models.py

          from django.db import models
          
          class CountryManager(models.Manager):
              """Enable fixtures using self.sigla instead of `id`"""
          
              def get_by_natural_key(self, sigla):
                  return self.get(sigla=sigla)
          
          class Country(models.Model):
              objects = CountryManager()
              sigla   = models.CharField(max_length=5, unique=True)
          
              def __unicode__(self):
                  return u'%s' % self.sigla
          
          class City(models.Model):
              nome   = models.CharField(max_length=64, unique=True)
              nation = models.ForeignKey(Country, default='IT')
          

          【讨论】:

          • 我在 Django 1.6 中尝试过这个,但我收到错误消息,“Invalid literal for int() with base 10: 'IT'。(我的字符串不同,但你明白了。)跨度>
          • 这很好用,而且看起来更像 Pythonic:default=lambda: Country.objects.filter(sigla='IT').first()
          • 奇怪,我记得用 django 1.6 测试过这个。谷歌搜索我发现这是一个元组问题,尝试:def get_by_natural_key(self, sigla): return (self.get(sigla=sigla),)default=('IT',)。我只是猜测;)
          • “自然”在这里是一个有问题的概念。您似乎在说您是在强迫用户为每件商品选择一个由五个字符组成的 ID,但这本质上并不比序列号更自然。
          【解决方案7】:

          就我而言,我想将默认设置为相关模型的任何现有实例。因为有可能ID为1Exam已经被删除了,所以我做了以下操作:

          class Student(models.Model):
              exam_taken = models.ForeignKey("Exam", blank=True)
          
              def save(self, *args, **kwargs):
                  try:
                      self.exam_taken
                  except:
                      self.exam_taken = Exam.objects.first()
                  super().save(*args, **kwargs)
          

          如果exam_taken 不存在,django.db.models.fields.related_descriptors.RelatedObjectDoesNotExist 将在尝试访问它时引发。

          【讨论】:

            【解决方案8】:

            你可以使用这个模式:

            class Other(models.Model):
                DEFAULT_PK=1
                name=models.CharField(max_length=1024)
            
            class FooModel(models.Model):
                other=models.ForeignKey(Other, default=Other.DEFAULT_PK)
            

            当然你需要确保Other的表中有一行。您应该使用数据迁移来确保它存在。

            【讨论】:

              【解决方案9】:

              在您的两个示例中,您都在硬编码默认实例的 id。如果这是不可避免的,我会设置一个常数。

              DEFAULT_EXAM_ID = 1
              class Student(models.Model):
                  ...
                  exam_taken = models.ForeignKey("Exam", default=DEFAULT_EXAM_ID)
              

              代码更少,并且命名常量使其更具可读性。

              【讨论】:

              • 我收到以下错误:TypeError: Additional arguments should be named <dialectname>_<argument>, got 'default' 代码:category_id = db.Column(db.Integer, db.ForeignKey("category.id", default=1), primary_key=True) 你能帮忙吗?
              • 顺便说一句,有两个 PK,所以密钥仍然是唯一的。
              • 嗨。好问题!但是我如何在默认情况下没有“ID”的情况下对其进行编码。我使用角色模型类,并将“get_or_create 默认角色”作为默认角色字段。我应该返回 ID,但想返回对象
              猜你喜欢
              • 2015-01-10
              • 1970-01-01
              • 2018-08-30
              • 1970-01-01
              • 1970-01-01
              • 2012-04-07
              • 1970-01-01
              • 2010-10-16
              • 2011-09-16
              相关资源
              最近更新 更多