Переопределить 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, на которую вы ссылаетесь в вопросе.

Другие вопросы по тегам