I'm currently working with Django's admin interface and encountering some unexpected behavior related to form saving with the commit parameter. Here's what I'm trying to understand:
Context: I'm using a custom form (DeckCreateForm) with a save method overridden to handle saving deck data. Within this method, I've set the commit parameter to False to prevent immediate database commits.
Observation: Despite setting commit to False, when saving a form instance through the admin interface, it appears that the form data is being committed to the database.
Understanding Needed: I'm seeking clarification on why the form data is being committed to the database even when commit is explicitly set to False. Additionally, I want to understand the factors that could influence the behavior of the commit parameter within the context of Django's admin interface.
Efforts Made: I've reviewed the Django documentation regarding form saving and the admin interface but haven't found a clear explanation for this behavior. I've also examined my code and haven't identified any obvious bugs or misconfigurations.
Request: I'm looking for insights or explanations from the Django community on how Django's admin interface handles form saving and the factors that might affect the behavior of the commit parameter. Additionally, any suggestions for troubleshooting or further exploration would be greatly appreciated.
My Code:
Custom save function (forms.py):
def save(self, commit=False):
deck = super().save(commit=False)
if commit:
deck.save()
self.save_m2m()
cleaned_data = self.cleaned_data
word_items = cleaned_data.get('word_items')
with transaction.atomic():
try:
for rank, word_item in enumerate(word_items, start=1):
deck_entry, created = DeckWord.objects.get_or_create(
deck=deck,
word_item=word_item,
defaults={'rank':1}
)
except IntegrityError as e:
raise
return deck
Admin.py
@admin.register(Deck)
class DeckAdmin(admin.ModelAdmin):
list_display = ['id', 'name', 'description', 'language', 'is_ranked', 'created_by', 'visibility']
form = DeckCreateForm
Any guidance or insights into this matter would be invaluable in helping me better understand Django's admin interface behavior. Thank you in advance for your assistance!
Yes well, that can be a bit confusing. You'll find your answers if you delve into the logic of the view that handles the form/object saving for the admin panel. Your DeckAdmin
inherits from ModelAdmin
. So the code for the change-form-logic is found here:
django>contrib>admin>options.py ModelAdmin._changeform_view
# an extraction from mentioned function
if form_validated:
new_object = self.save_form(request, form, change=not add)
else:
new_object = form.instance
if all_valid(formsets) and form_validated:
self.save_model(request, new_object, form, not add)
As you see there are ModelAdmin.save_form
and ModelAdmin.save_model
called.
def save_form(self, request, form, change):
"""
Given a ModelForm return an unsaved instance. ``change`` is True if
the object is being changed, and False if it's being added.
"""
return form.save(commit=False)
def save_model(self, request, obj, form, change):
"""
Given a model instance save it to the database.
"""
obj.save()
As you can see, there is no database action when the .save_form()
method is called. It calls your custom form.save(commit=False)
method and your code is respected. But when afterwards the ._changeform_view
method calls the .save_model()
method, the obj.save()
method is issued. And the obj.save()
method does not respect the code that you write in your custom form but respects the save logic that you potentially write in your models.py.
TLDR: Your form.save
logic runs, but afterwards the obj.save()
method is what triggers the database interaction regardless from your form.save()
logic.
You have two options:
A) As a logical consequence you can configure the .save()
method of your model from within the models.py file. I don't know why you would do that, but for learning purposes you can overwrite that method to not commit to the database.
B) You can add a save_model()
method to your DeckAdmin
.
def save_model(self, request, obj, form, change):
# Call the form's save method with commit=False
deck = form.save(commit=False)
# Perform any additional logic here
cleaned_data = form.cleaned_data
word_items = cleaned_data.get('word_items')
with transaction.atomic():
try:
for rank, word_item in enumerate(word_items, start=1):
deck_entry, created = DeckWord.objects.get_or_create(
deck=deck,
word_item=word_item,
defaults={'rank': 1}
)
except IntegrityError as e:
raise
# Save the deck instance
deck.save()
form.save_m2m()