问题
I have a model (Meal) with several many to many fields (proteins, carbohydrates and fats), which I recently added a 'name' CharField to. I wanted to allow the user to enter a name for a meal, but if they do not enter a name, I want the name to be populated automatically based on the function definitions I have in the model which just concatenate the names of the foods in one string. I was trying to follow this guide.
Now, if the Meal already exists, what I have actually works fine. However, if it does not exist, the food_name for each item appears not to have saved yet because they are empty. I put the super(Meal,self).save()
statement before my if not self.name:
statement in the hopes that this would save the object to the database so that the food_names could then be retrieved, but it does not work and instead when I do save the name is saved as '(0)'. What I am looking for the name to populate via the __str__
function as 'Pork Tenderloin, Spinach (steamed/boiled), Potato (Red, medium) (3)', for example.
Also, if I don't call super(Meal,self).save()
before the if statement, I actually get a 'maximum recursion depth exceeded' error.
Can anyone tell me if there is a way to auto populate this name field based on my function definition on object creation as I've described?
I am new to Django, and have limited experience with Python, so thank you very much for any help you can provide.
Here is my model:
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()
Edit:
The main reason I am trying to do this is because I need to be able to sort on the string returned by the __str__
method in my Meal model, but after posting another question on stack overflow here I found out that I believe this is not possible. It seems you can only sort on fields in your model, and so I chose instead to add a name field (where I additionally decided that I could allow the user to name the meal something if they wanted to instead of letting it be auto populated). Currently when there are many meals it is impossible to find any single one because the ordering is based on pk's which appears totally random in terms of the names of the items and makes it virtually unusable. Here is a picture for reference:
Currently, I create meal objects through the django admin ui only. Here is the code for my MealAdmin in admin.py:
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'"
}),
)
and a picture for reference:
So, if anyone has any idea how to cause the save function to auto-populate the name field on creation based on my __str__
function - or any other work around, it would be greatly appreciated!
回答1:
If you want something to happen when creating a new instance through admin
interface, the way to do is different than just overriding your model save
method: you have to override the save_model
method in your admin
declaration.
You could try something like below:
# 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()
回答2:
You can try using save
signal to do your custom stuff when creating your object. For instance:
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)
However, this will be called whenever save
is being called, not only at creation time (since there is not a post_create
signal). Not a big issue, but not 100% satisfying. Hope this one will work!
EDIT
Another try with signals, but with m2m_changed
this time. We will try to call update_name
each time one of the m2m
fields has been updated, because the issue seems to be that those fields being saved independantly from Meal
model, everything is asynchronous thus updated data of those fields are not available when needed.
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)
With this solution, each time one of those m2m
field is updated, name will be updated too thanks to update_name
method.
Source : https://docs.djangoproject.com/en/3.0/ref/signals/#m2m-changed
回答3:
Actually you can not assign m2m
fields if the object does not exist already (because before there is no primary key associated to your instance yet). You first need to save the object, then set a value for m2m fields. Then you can save your object and customize your name
field. That's why you have got empty values.
Just a question, why don't you customize your __str__
function instead? It would be (in my opinion of course!) simpler to maintain and use than override save
base function. Something like:
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())})'
So if there is a custom name
value set by user, fine you will use that; if not, you will return the whole thing. I am never comfortable in overriding default save
functions actually when you can do otherwise!
来源:https://stackoverflow.com/questions/59442018/django-help-overwriting-a-models-save-method-to-auto-populate-a-charfield-ba