Переопределить save_model в Django InlineModelAdmin
У меня есть модель, которая имеет user
поле, которое необходимо заполнить автоматически вошедшему в систему пользователю. Я могу заставить его работать как указано здесь, если user
поле находится в стандартном ModalAdmin, но если модель, с которой я работаю, находится в InlineModelAdmin
и быть сохраненным из записи другой модели внутри Admin, это не займет.
6 ответов
Вот то, что я считаю лучшим решением. Мне понадобилось время, чтобы найти его... этот ответ дал мне подсказки: /questions/11987343/django-admin-inline-formiruet-ishodnyie-dannyie-dlya-kazhdogo-ekzemplyara/11987359#11987359
На вашем admin.py:
class YourInline(admin.TabularInline):
model = YourInlineModel
formset = YourInlineFormset
def get_formset(self, request, obj=None, **kwargs):
formset = super(YourInline, self).get_formset(request, obj, **kwargs)
formset.request = request
return formset
На вашей форме.
class YourInlineFormset(forms.models.BaseInlineFormSet):
def save_new(self, form, commit=True):
obj = super(YourInlineFormset, self).save_new(form, commit=False)
# here you can add anything you need from the request
obj.user = self.request.user
if commit:
obj.save()
return obj
Я знаю, что опаздываю на вечеринку, но вот моя ситуация и то, что я придумал, что может пригодиться кому-то еще в будущем.
У меня есть 4 встроенных модели, которым нужен зарегистрированный пользователь.
- 2 как
created_by
поле типа. (устанавливается один раз при создании) - и 2 других как
closed_by
поле типа. (устанавливается только при условии)
Я использовал ответ, предоставленный rafadev, и превратил его в простой миксин, который позволяет мне указывать имя пользовательского поля в другом месте.
Универсальный набор форм в forms.py
from django.forms.models import BaseInlineFormSet
class SetCurrentUserFormset(forms.models.BaseInlineFormSet):
"""
This assume you're setting the 'request' and 'user_field' properties
before using this formset.
"""
def save_new(self, form, commit=True):
"""
This is called when a new instance is being created.
"""
obj = super(SetCurrentUserFormset, self).save_new(form, commit=False)
setattr(obj, self.user_field, self.request.user)
if commit:
obj.save()
return obj
def save_existing(self, form, instance, commit=True):
"""
This is called when updating an instance.
"""
obj = super(SetCurrentUserFormset, self).save_existing(form, instance, commit=False)
setattr(obj, self.user_field, self.request.user)
if commit:
obj.save()
return obj
Mixin класс в вашем admin.py
class SetCurrentUserFormsetMixin(object):
"""
Use a generic formset which populates the 'user_field' model field
with the currently logged in user.
"""
formset = SetCurrentUserFormset
user_field = "user" # default user field name, override this to fit your model
def get_formset(self, request, obj=None, **kwargs):
formset = super(SetCurrentUserFormsetMixin, self).get_formset(request, obj, **kwargs)
formset.request = request
formset.user_field = self.user_field
return formset
Как это использовать
class YourModelInline(SetCurrentUserFormsetMixin, admin.TabularInline):
model = YourModel
fields = ['description', 'closing_user', 'closing_date']
readonly_fields = ('closing_user', 'closing_date')
user_field = 'closing_user' # overriding only if necessary
Быть осторожен...
... так как этот смешанный код будет каждый раз устанавливать пользователя, вошедшего в систему. Если вам нужно заполнить поле только при создании или при конкретном обновлении, вам нужно решить эту проблему в методе сохранения модели. Вот некоторые примеры:
class UserOnlyOnCreationExampleModel(models.Model):
# your fields
created_by = # user field...
comment = ...
def save(self, *args, **kwargs):
if not self.id:
# on creation, let the user field populate
self.date = datetime.today().date()
super(UserOnlyOnCreationExampleModel, self).save(*args, **kwargs)
else:
# on update, remove the user field from the list
super(UserOnlyOnCreationExampleModel, self).save(update_fields=['comment',], *args, **kwargs)
Или если вам нужен только пользователь, если определенное поле установлено (например, логическое поле closed
):
def save(self, *args, **kwargs):
if self.closed and self.closing_date is None:
self.closing_date = datetime.today().date()
# let the closing_user field set
elif not self.closed :
self.closing_date = None
self.closing_user = None # unset it otherwise
super(YourOtherModel, self).save(*args, **kwargs) # Call the "real" save() method.
Этот код, вероятно, можно сделать более общим, так как я довольно плохо знаком с Python, но это то, что будет в моем проекте на данный момент.
Только save_model
для модели, которую вы редактируете, вместо этого вам нужно будет использовать post_save
сигнал для обновления встроенных данных.
(Не совсем дубликат, но по сути тот же вопрос, на который дан ответ в формах встроенной модели, испускающих сигналы post_save? (Django))
У меня была похожая проблема с пользовательским полем, которое я пытался заполнить во встроенной модели. В моем случае в родительской модели также было определено пользовательское поле, поэтому я переопределил save
на детской модели следующим образом:
class inline_model:
parent = models.ForeignKey(parent_model)
modified_by = models.ForeignKey(User,editable=False)
def save(self,*args,**kwargs):
self.modified_by = self.parent.modified_by
super(inline_model,self).save(*args,**kwargs)
Пользовательское поле было первоначально автоматически заполнено в родительской модели путем переопределения save_model в ModelAdmin для родительской модели и назначения
obj.modified_by = request.user
Имейте в виду, что если у вас также есть отдельная страница администратора для дочерней модели, вам понадобится какой-то другой механизм для синхронизации родительского и дочернего полей ified_by (например, вы можете переопределить save_model
на дочернем ModelAdmin и обновите / сохраните поле ified_by на родительском объекте перед вызовом save
на ребенка).
Я не нашел хороший способ справиться с этим, если пользователь не в родительской модели. Я не знаю как получить request.user
используя сигналы (например, post_save
), но, возможно, кто-то еще может дать более подробную информацию об этом.
Сохраняет ли другая модель пользователя? В этом случае вы можете использовать post_save
сигнал для добавления этой информации в набор встроенной модели.
Вы пытались реализовать пользовательскую проверку в админке, как описано в документации? Переопределение функции clean_user() в форме модели может помочь вам.
На ум приходит еще один, более сложный вариант. Вы можете переопределить шаблон администратора, который отображает форму изменения. Переопределение формы изменения позволит вам создать пользовательский тег шаблона, который передает зарегистрированного пользователя в ModelForm. Затем вы можете написать пользовательскую функцию инициализации в форме модели, которая автоматически устанавливает пользователя. Этот ответ является хорошим примером того, как это сделать, как и ссылка на b-list, на которую вы ссылаетесь в вопросе.