问题
Here are my simplified models:
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())})'
and my modelAdmin object in admin.py:
class MealAdmin(admin.ModelAdmin):
model = MasterMeal
save_as = True
ordering = ('proteins__food_name','carbohydrates__food_name','fats__food_name',)
What I would like is in the Django admin page for MasterMeals is to have each object named after the __str__
method given in my MasterMeal model, but in a different ordering than the default.
Specifically, I would like the objects to be sorted in alphabetical order according to the __str__
method definition, and if possible, I don't want to modify my MasterMeal model to add another field to achieve this. I have tried several things such as the ordering = ('proteins__food_name','carbohydrates__food_name','fats__food_name',)
in my MealAdmin object as well as ordering = ('proteins','carbohydrates','fats',)
. I have also tried overwriting the queryset of the ModelAdmin with an annotate function like:
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')
but I am clearly not annotating the objects in the way that they need to be in order to get the ordering that I am after. All 3 of these examples produce this exact same ordering:
Incorrect Ordering,
You can see an object with the exact same name has several other entries in between it. The objects are not sorted in alphabetical order according to the __str__
method, and are instead, (I believe?) being sorted on keys that have been generated for each of the groups of proteins/carbs/fats.
I did read here in the Django docs in regard to ModelAdmin.ordering that: 'To ensure a deterministic ordering of results, the changelist adds pk to the ordering if it can’t find a single or unique together set of fields that provide total ordering.' But I do not know how to modify my code to get it to order these objects in the order I DO want.
I believe I could add a non-editable field to my MasterMeal model, and overwrite the save() method for the model as shown here, in order to auto-populate the field with my desired string, which I believe I could then order on, but I don't want to have to change my model and have to update all of my objects on all of my servers to add this field.
Can anyone tell me if there is a way I can achieve this ordering?
I am very new to Django, and have limited experience with Python, so thank you very much for any help you can provide.
回答1:
You can do something like this:
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)})'
I changed the query for each food to only return the string value of the food_name
field to avoid making full query of the object if we only need one field (this is very useful).
回答2:
I found that it wasn't possible to do what I wanted since I did not have a field to order on and I was not able to use a query expression to do what I needed. I chose instead to create a new CharField in my model, name
, which I auto populated with my __str__
method when left blank. See the solution here.
来源:https://stackoverflow.com/questions/58963778/django-admin-custom-ordering-according-to-concatenated-charfields-of-a-related