【问题标题】:How to exclude django model fields during a save?如何在保存期间排除 django 模型字段?
【发布时间】:2015-10-19 22:27:38
【问题描述】:

我有一个相当复杂的 Django 模型,其中包含一些只能在特定情况下保存的字段。举个简单的例子,

from django.db import models

class MyModel(models.Model):
    name = models.CharField(max_length=200)
    counter = models.IntegerField(default=0)

    def increment_counter(self):
        self.counter = models.F('counter') + 1
        self.save(update_fields=['counter'])

这里我使用F expressions 来避免在递增计数器时出现竞争条件。我通常不会希望将 counter 的值保存在 increment_counter 函数之外,因为这可能会撤消从另一个线程或进程调用的增量。

所以问题是,在模型的保存功能中默认排除某些字段的最佳方法是什么?我已经尝试了以下

def save(self, **kwargs):
    if update_fields not in kwargs:
        update_fields = set(self._meta.get_all_field_names())
        update_fields.difference_update({
            'counter',
        })
        kwargs['update_fields'] = tuple(update_fields)
    super().save(**kwargs)

但这会导致ValueError: The following fields do not exist in this model or are m2m fields: id。我当然可以在差异更新中添加 id 和任何 m2m 字段,但这开始看起来像一个无法维护的混乱,特别是一旦其他模型开始引用这个,这将在 self._meta.get_all_field_names() 中添加其他需要的名称从update_fields 中排除。

对于它的价值,我主要需要这个功能来与 django 管理站点交互;代码中的所有其他位置都相对可以轻松地使用正确的update_fields 调用model_obj.save()

【问题讨论】:

  • 为什么将id 添加到difference_update 会一团糟?对于 m2m 字段,您始终可以遍历所有 get_fields() 并排除它们。
  • @Ivan 当然,这适用于简单的情况,但是一旦我开始在其他模型中将此模型引用为ForeignKey,那么我需要将所有这些字段名称添加到difference_update 也是。这很快就违反了 DRY。
  • 我的意思是一般地遍历所有字段,看看它们是否是反向关系。据我所知,我已经这样做了,我会检查一下。
  • 您可以使用hasattr(field,'column')。如果字段是反向关系,它将是False。我自己实际上使用了auto_created 属性,但目标有点不同。
  • 看起来使用 _meta.get_concrete_fields_with_model() 而不是 _meta.get_all_field_names() 可能是要走的路,尽管它在 Django 1.8 中已被弃用。他们有一个migration guide,不过可以帮助替换它。

标签: django django-models


【解决方案1】:

我最终使用了以下内容:

from django.db import models

class MyModel(models.Model):
    name = models.CharField(max_length=200)
    counter = models.IntegerField(default=0)

    default_save_fields = None

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if self.default_save_fields is None:
            # This block should only get called for the first object loaded
            default_save_fields = {
                f.name for f in self._meta.get_fields()
                if f.concrete and not f.many_to_many and not f.auto_created
            }
            default_save_fields.difference_update({
                'counter',
            })
            self.__class__.default_save_fields = tuple(default_save_fields)

    def increment_counter(self):
        self.counter = models.F('counter') + 1
        self.save(update_fields=['counter'])    

    def save(self, **kwargs):
        if self.id is not None and 'update_fields' not in kwargs:
            # If self.id is None (meaning the object has yet to be saved)
            # then do a normal update with all fields.
            # Otherwise, make sure `update_fields` is in kwargs.
            kwargs['update_fields'] = self.default_save_fields
        super().save(**kwargs)

这似乎适用于我在其他模型中作为 ForeignKey 引用的更复杂的模型,尽管它可能没有涵盖一些边缘情况。

【讨论】:

  • 如果这已经解决了问题,请将此答案标记为已接受。谢谢!
【解决方案2】:

clwainwright's answer 的启发,我创建了一个 mixin 类,以便轻松添加到模型中。虽然它使用第二个 mixin 类来跟踪哪些字段已被更改,但受 this 答案的启发。

https://gitlab.com/snippets/1746711

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-05-18
    • 2020-05-15
    • 2011-06-11
    • 1970-01-01
    • 1970-01-01
    • 2020-11-20
    • 1970-01-01
    相关资源
    最近更新 更多