【问题标题】:Customizing the entry uniqueness in Django自定义 Django 中的条目唯一性
【发布时间】:2016-06-17 11:35:02
【问题描述】:

我有一个包含成分列表的数据库。我想避免此表中的重复条目。我不想使用unique 关键字有两个原因:

  • 我的唯一性约束比单纯的= 更复杂一点
  • 我不想在创建预先存在的成分模型时引发异常,而是只想返回该模型,以便我可以写 Ingredient(ingredient_name='tomato') 并继续我的一天,而不是封装所有在try 子句中。这将使我能够轻松地在我的食谱表中快速添加配料。

一个解决方案是简单地拥有一个像create_ingredient 这样的包装器函数,但我不觉得它特别优雅,更具体地说,它对于其他一些开发人员来说并不健壮,只是忘记使用包装器。因此,我正在玩弄 pre_init 和 post_init 信号。

这是我目前所拥有的:

class Ingredient(models.Model):
    ingredient_name = models.CharField(max_length=200)
    recipes = models.ManyToManyField(Recipe,related_name='ingredients')

    def __str__(self):
        return self.ingredient_name

class Name(models.Model):
    main_name = models.CharField(max_length=200, default=None)
    equivalent_name = models.CharField(max_length=200, primary_key=True, default=None)

def _add_ingredient(sender, args, **kwargs):
    if 'ingredient_name' not in kwargs['kwargs'] :
        return
    kwargs['kwargs']['ingredient_name'] = kwargs['kwargs']['ingredient_name'].lower()
    # check if equivalent name exists, make this one the main one otherwise
    try:
        kwargs['kwargs']['ingredient_name'] = Name.objects.filter(
            equivalent_name=kwargs['kwargs']['ingredient_name']
        )[0].main_name
    except IndexError:
        name = Name(main_name=kwargs['kwargs']['ingredient_name'],
                equivalent_name=kwargs['kwargs']['ingredient_name'])
        name.save()

pre_init.connect(_add_ingredient, Ingredient)

到目前为止一切顺利。这实际上有效,并将在模型初始化之前在需要时替换 ingredient_name。现在我想要检查有问题的成分是否已经存在,如果存在则让初始化程序返回它。我想我需要使用post_init 来执行此操作,但我不知道如何修改正在创建的特定实例。这就是我的意思:

def _finalize_ingredient(sender, instance, **kwargs):
    try:
        # doesn't work because of python's "pass arguments in python's super unique way of doing things" thing
        instance = Ingredient.objects.filter(ingredient_name=instance.ingredient_name)[0]
    except IndexError:
        pass

post_init.connect(_finalize_ingredient, Ingredient)

正如我所评论的,我不希望这会起作用,因为 instance = ... 实际上并没有修改 instance,它只是重新分配变量名(顺便说一下,如果你尝试运行它,会发生各种可怕的事情我不想理解,因为我知道这是完全错误的)。那么我该怎么做呢?我真的希望包装函数不是这里最干净的选择。我是 OOP 的忠实拥护者,天哪,我想要一个 OOP 解决方案来解决这个问题(正如我所说,我认为从长远来看,它会比包装器更健壮、更安全)。

我当然意识到我可以向 Recipe 添加一个 add_ingredient 方法,它会为我完成所有这些,但我真的很喜欢将所有这些包含在我的 Ingredient 类中的想法,因为它可以保证正确任何情况下的数据库行为。我也很想知道post_init 方法是否/如何用于在给定情况下完全覆盖创建的对象。

顺便说一句,你们中的一些人可能想知道为什么我的Name 类中没有ForeignKey 条目可以将Name 表连接到Ingredient 表。毕竟,这不是我的检查在我的_add_ingredient 方法中基本上完成的吗?原因之一是,如果我这样做,那么我最终会遇到我试图在这里解决的相同问题:如果我想动态创建一种成分以将其添加到我的食谱中,我可以简单地创建一个 @987654340 @object 创建Ingredient 对象时,但如果它对应于已在使用中的main_name(而不是简单地返回我需要的对象),则会引发异常。

【问题讨论】:

    标签: django parameter-passing unique-index


    【解决方案1】:

    相信你在找get_or_create(),它已经是Django内置的了。

    你提到:

    一种解决方案是简单地使用像 create_ingredient 这样的包装器函数,但我不觉得它特别优雅,更具体地说,它对于其他一些开发人员来说并不健壮,只是忘记使用包装器。

    好吧,反过来看。如果您确实需要创建“重复”成分怎么办?那么有这个机会就好了。

    【讨论】:

      【解决方案2】:

      我想出了一些既优雅又强大的东西,我认为它可以得到我所追求的东西。我仍然必须定义一个add_ingredient 方法,但我仍然具有我需要的稳健性。我已经做到了,它可以推广到任何具有主键的类,Name 表将包含定义任何表的名称唯一性的信息:

      class Name(models.Model):
          main_name = models.CharField(max_length=200, default=None)
          equivalent_name = models.CharField(max_length=200, primary_key=True, default=None)
      
      def _pre_init_unique_fetcher(sender, args, **kwargs):
          pk_name = sender._meta.pk.name
          if pk_name not in kwargs['kwargs'] :
              return
          kwargs['kwargs'][pk_name] = kwargs['kwargs'][pk_name].lower()
          # check if equivalent name exists, make this one the main one otherwise
          try:
              kwargs['kwargs'][pk_name] = Name.objects.filter(
                  equivalent_name=kwargs['kwargs'][pk_name]
              )[0].main_name
          except IndexError:
              name = Name(main_name=kwargs['kwargs'][pk_name],
                          equivalent_name=kwargs['kwargs'][pk_name])
              name.save()
          sender._input_dict = kwargs['kwargs']
      
      def _post_init_unique_fetcher(sender, instance, **kwargs):
          pk_name = sender._meta.pk.name
          pk_instance = instance.__dict__[pk_name]
          filter_dict = {}
          filter_dict[pk_name] = pk_instance
          try:
              post_init.disconnect(_post_init_unique_fetcher,sender)
              instance.__dict__ = sender.objects.filter(**filter_dict)[0].__dict__
              post_init.connect(_post_init_unique_fetcher, sender)
              for key in sender._input_dict:
                  instance.__dict__[key] = sender._input_dict[key]
          del sender._input_dict
          except IndexError:
              post_init.connect(_post_init_unique_fetcher, sender)
          except:
              post_init.connect(_post_init_unique_fetcher, sender)
              raise
      
      unique_fetch_models = [Ingredient, Recipe, WeekPlan]
      for unique_fetch_model in unique_fetch_models :
          pre_init.connect(_pre_init_unique_fetcher, unique_fetch_model)
          post_init.connect(_post_init_unique_fetcher, unique_fetch_model)
      

      现在,如果存在具有相同名称的模型,它将使用先前模型的预先存在的数据(而不是默认值)加载任何新模型。我在Recipe 类中仍然需要add_ingredient 方法的原因是,尽管我可以创建模型并立即保存它,但我不能在不引发异常的情况下为预先存在的成分调用Ingredient.objects.create()。这与 Django 如何处理 primary_key 名称有关:如果您创建模型然后 save 它,它假定您只是在更新条目(如果它已经使用该键存在),但是如果您 create 它,它尝试添加另一个条目,并且与 primary_key 指定冲突。所以现在我可以做recipe.add_ingredient(Ingredient(ingredient_name='tomato', vegetarian=True))之类的事情了。

      【讨论】:

        猜你喜欢
        • 2016-09-26
        • 2017-03-02
        • 1970-01-01
        • 2020-12-06
        • 2023-03-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多