Django Admin:根据相关的许多模型的串联字符字段进行自定义排序

时间:2019-11-20 21:38:20

标签: python django django-admin sql-order-by django-queryset

这是我的简化模型:

class MasterFood(models.Model):
    class Meta:
        verbose_name_plural = 'Master Foods'

    FOOD_TYPE_CHOICES = (
        ('F', 'Fats'),
        ('C', 'Carbohydrates'),
        ('P', 'Proteins'),
        ('O', 'Others'),
    )

    food_name = models.CharField(max_length=255)
    food_type = models.CharField(max_length=20,choices=FOOD_TYPE_CHOICES)
    def __str__(self):
        return self.food_name

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

    proteins = models.ManyToManyField(to="MasterFood", limit_choices_to={'food_type': 'P'},blank=True,related_name='food_proteins')
    carbohydrates = models.ManyToManyField(to="MasterFood", limit_choices_to={'food_type': 'C'}, blank=True, related_name='food_carbohydrates')
    fats = models.ManyToManyField(to="MasterFood",  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())})'

和admin.py中的我的modelAdmin对象:

class MealAdmin(admin.ModelAdmin):
    model = MasterMeal
    save_as = True

    ordering = ('proteins__food_name','carbohydrates__food_name','fats__food_name',)

我想在Django管理员页面中使用MasterMeals,是让每个对象都以我的MasterMeal模型中给出的__str__方法命名,但是顺序与默认顺序不同。  具体来说,我希望根据__str__方法定义按字母顺序对对象进行排序,并且如果可能的话,我不想修改MasterMeal模型来添加另一个字段来实现此目的。我已经尝试了多种方法,例如MealAdmin对象中的ordering = ('proteins__food_name','carbohydrates__food_name','fats__food_name',)以及ordering = ('proteins','carbohydrates','fats',)。我还尝试使用带注释功能的重写ModelAdmin的queryset:

class MealAdmin(admin.ModelAdmin):
    model = MasterMeal
    save_as = True

def get_queryset(self,request):
        qs = super(MealAdmin,self).get_queryset(request) 

        return qs.distinct() \
        .annotate(protein_order=F('proteins__food_name'),carb_order = F('carbohydrates__food_name'),fat_order = F('fats__food_name')) \
        .order_by('protein_order', 'carb_order','fat_order')

但是我显然不是按照获得我想要的顺序所需的方式来注释对象。所有这三个示例都产生完全相同的顺序:

Incorrect Ordering

您可以看到名称完全相同的对象之间还有其他几个条目。对象不是根据__str__方法按字母顺序排序的,而是(我相信?)是根据针对蛋白质/碳水化合物/脂肪的每一组生成的键排序的(我相信吗?)。

我确实阅读了Django文档中关于ModelAdmin的here。 “为确保结果的确定性排序,如果更改列表找不到提供整体排序的单个或唯一的字段集,则更改列表会将pk添加到排序中。”但是我不知道如何修改我的代码以使其按我想要的顺序对这些对象进行排序。

我相信我可以在我的MasterMeal模型中添加一个不可编辑的字段,并覆盖here所示的模型的save()方法,以便使用所需的字符串自动填充该字段,我相信我可以继续订购,但是我不想更改模型并不必更新所有服务器上的所有对象来添加此字段。

有人可以告诉我是否有一种方法可以实现这种订购?

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

2 个答案:

答案 0 :(得分:0)

您可以执行以下操作:

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

    proteins = models.ManyToManyField(to="MasterFood", limit_choices_to={'food_type': 'P'},blank=True,related_name='food_proteins')
    carbohydrates = models.ManyToManyField(to="MasterFood", limit_choices_to={'food_type': 'C'}, blank=True, related_name='food_carbohydrates')
    fats = models.ManyToManyField(to="MasterFood",  limit_choices_to={'food_type': 'F'}, blank=True, related_name='food_fats')

    def all_foods_string(self):
        proteins = self.proteins.all().values_list('food_name', flat=True)
        carbs = self.carbohydrates.all().values_list('food_name', flat=True)
        fats = self.fats.all().values_list('food_name', flat=True)

        return sorted(proteins + carbs + fats)

    def __str__(self):
        all_foods = self.all_foods_string()
        return ', '.join(all_foods)) + f' ({len(all_foods)})'

我更改了对每种食物的查询,以仅返回food_name字段的字符串值,以避免在只需要一个字段的情况下对对象进行完整查询(这非常有用)。

答案 1 :(得分:0)

我发现无法执行我想做的事情,因为我没有要订购的字段,而且我无法使用查询表达式来完成我需要做的事情。我改为选择在模型name中创建一个新的CharField,将其留空时自动使用__str__方法填充该模型。请参阅解决方案here