Django:帮助覆盖模型的save()方法,以基于对象创建函数自动填充CharField

时间:2019-12-22 06:09:05

标签: python django django-models

我有一个模型(膳食),其中包含多个字段(蛋白质,碳水化合物和脂肪),最近我在其中添加了一个“名称” CharField。我想允许用户输入一餐的名称,但是如果他们不输入名称,我希望根据我在模型中具有的函数定义(该函数定义只是将食物的名称串联在一起)自动填充该名称。一弦。我正在尝试遵循此guide

现在,如果该餐已经存在,那么我所拥有的实际上可以正常工作。但是,如果不存在,则每个项目的food_name似乎尚未保存,因为它们为空。我将super(Meal,self).save()语句放在我的if not self.name:语句之前,希望这会将对象保存到数据库中,以便随后可以检索food_names,但它不起作用,而是在保存时名称另存为“(0)”。例如,我正在寻找要通过__str__函数填充的名称,例如“猪里脊肉,菠菜(蒸/煮),土豆(红,中)(3)”。

此外,如果我没有在if语句之前调用super(Meal,self).save(),则会收到“超出最大递归深度”错误。

有人能告诉我是否有一种方法,可以像我描述的那样根据我对对象创建的函数定义来自动填充此名称字段?

我是Django的新手,并且对Python的经验有限,所以非常感谢您能提供的任何帮助。

这是我的模特:

class Meal(models.Model):
    class Meta:
        verbose_name_plural = 'Meal Plan Meals'

    name = models.CharField(max_length=255,blank=True,null=True)
    proteins = models.ManyToManyField(to="Food", limit_choices_to={'food_type': 'P'},blank=True,related_name='food_proteins')
    carbohydrates = models.ManyToManyField(to="Food", limit_choices_to={'food_type': 'C'}, blank=True, related_name='food_carbohydrates')
    fats = models.ManyToManyField(to="Food",  limit_choices_to={'food_type': 'F'}, blank=True, related_name='food_fats')

    def all_foods(self):
       return list(self.proteins.all())+list(self.carbohydrates.all())+list(self.fats.all())

    def __str__(self):
        return ', '.join(map(lambda x: x.food_name, self.all_foods()))+f' ({len(self.all_foods())})'

    def save(self):
        super(Meal,self).save()
        if not self.name:
            self.name = self.__str__()
            self.save()

编辑:

我尝试执行此操作的主要原因是因为我需要能够对Meal模型中__str__方法返回的字符串进行排序,但是在堆栈溢出{{3} }我发现我相信这是不可能的。看来您只能对模型中的字段进行排序,因此我选择添加一个名称字段(在此我另外决定,如果允许的话,我可以允许用户为餐点命名,而不是让餐点自动填充) 。当前,当有很多餐时,找不到任何一餐,因为订购是基于pk的,pk的名称完全随机,几乎无法使用。这是供参考的图片:

here

当前,我仅通过django admin ui创建餐对象。这是admin.py中我的MealAdmin的代码:

class MealAdmin(admin.ModelAdmin):
    model = Meal
    save_as = True
    #search bar - search by food name
    search_fields = ['name','proteins__food_name','carbohydrates__food_name','fats__food_name',]
    fieldsets = (
        (None, {
            'fields': ('name', 'proteins', 'carbohydrates', 'fats',),
            'description': "Note: If you do not choose a name for your meal the meal will be named according to all of the foods it contains. Ex: 'Chicken Breast,Rice (white) (cooked),Avocado'"
        }),
    )

和图片供参考:

meal list

因此,如果有人有任何想法如何基于我的__str__函数在创建时使save函数自动填充名称字段-或其他解决方法,将不胜感激!

3 个答案:

答案 0 :(得分:1)

如果您希望通过admin界面创建新实例时发生某些事情,则该方法不同于仅覆盖模型save方法:您必须覆盖save_model admin声明中的方法。

您可以尝试以下操作:

# admin.py
class MealAdmin(admin.ModelAdmin):

    def save_model(self, request, obj, form, change):        
        obj.save()
        form.save_m2m()
        # your custom stuff goes here
        if not obj.name:
            obj.name = obj.__str__()
            obj.save()

答案 1 :(得分:1)

在创建对象时,您可以尝试使用save信号执行自定义操作。例如:

from django.db.models.signals import post_save

class Meal(models.Model):

    # [...]

    @classmethod
    def update_name(cls, *args, **kwargs):
        if not cls.name:
            cls.name = self.__str__()
            cls.save()

post_save.connect(Meal.update_name, sender=Meal)

但是,无论何时调用save都将调用它,不仅在创建时(因为没有post_create信号)。这不是一个大问题,但不是100%令人满意。希望这个可以工作!


编辑

再次尝试使用信号,但是这次使用m2m_changed。每当update_name字段之一被更新时,我们都会尝试调用m2m,因为问题似乎是那些字段是独立于Meal模型保存的,所有内容都是异步的,因此进行了更新这些字段的数据在需要时不可用。

from django.db.models.signals import m2m_changed

class Meal(models.Model):

    # [...]

    @classmethod
    def update_name(cls, *args, **kwargs):
        if not cls.name:
            cls.name = self.__str__()
            cls.save()

m2m_changed.connect(Meal.update_name, sender=Meal.proteins.through)
m2m_changed.connect(Meal.update_name, sender=Meal.carbohydrates.through)
m2m_changed.connect(Meal.update_name, sender=Meal.fats.through)

使用此解决方案,每次m2m字段之一进行更新时,都将使用update_name方法来更新名称。

来源:https://docs.djangoproject.com/en/3.0/ref/signals/#m2m-changed

答案 2 :(得分:0)

实际上,如果对象尚不存在,则无法分配m2m字段(因为在尚未与实例相关联的主键之前)。您首先需要保存对象,然后为m2m字段设置一个值。然后,您可以保存对象并自定义name字段。这就是为什么您有空值的原因。

一个问题,为什么不自定义__str__函数呢? (当然,我认为!)比覆盖save基本函数更容易维护和使用。像这样:

def __str__(self):
    if self.name: return self.name
    return ', '.join(map(lambda x: x.food_name, self.all_foods()))+f' ({len(self.all_foods())})'

因此,如果用户设置了自定义的name值,可以使用它;如果没有,您将退还全部。否则,我实在不愿意覆盖默认的save函数!