Переопределить сохранение в Django InlineModelAdmin

Этот вопрос может выглядеть похожим на этот, но это не так...

У меня есть структура модели, как:

class Customer(models.Model):
    ....

class CustomerCompany(models.Model):
    customer = models.ForeignKey(Customer)
    type = models.SmallIntegerField(....)

я использую InlineModelsи имеют два типа CustomerCampany.type, Так что я определяю два различных встроенных для CustomerCompany и переопределить InlineModelAdmin.queryset

class CustomerAdmin(admin.ModelAdmin):
    inlines=[CustomerCompanyType1Inline, CustomerCompanyType2Inline]


class CustomerCompanyType1Inline(admin.TabularInline):
    model = CustomerCompany
    def queryset(self, request):
        return super(CustomerCompanyType1Inline, self).queryset(request).filter(type=1)

class CustomerCompanyType2Inline(admin.TabularInline):
    model = CustomerCompany
    def queryset(self, request):
        return super(CustomerCompanyType2Inline, self).queryset(request).filter(type=2)

Все хорошо и хорошо до здесь, но для добавления новых записей для InlineModelAdminмне все еще нужно отобразить type поле CustomerCompany на AdminForm, так как я не могу переопределить save метод InlineModelAdmin лайк:

class CustomerCompanyType2Inline(admin.TabularInline):
    model = CustomerCompany
    def queryset(self, request):
        return super(CustomerCompanyType2Inline, self).queryset(request).filter(type=2)
    #Following override do not work
    def save_model(self, request, obj, form, change):
        obj.type=2
        obj.save()

Использование сигнала также не является решением, так как мой сигнал sender будет таким же Modelтак что я не могу определить какой InlineModelAdmin отправь и что type должно быть...

Есть ли способ, который позволит мне установить type поле перед сохранением?

5 ответов

Решение

Ответ Alasdair не является неправильным, но у него есть несколько болезненных моментов, которые могут вызвать проблемы. Во-первых, перебирая набор форм с помощью form в качестве имени переменной вы фактически переопределяете значение, переданное в метод для form, Это не большое дело, но, поскольку вы можете делать сохранение без фиксации прямо из набора форм, лучше сделать это таким образом. Во-вторых, все важные formset.save_m2m() был оставлен вне ответа. Фактические документы Django рекомендуют следующее:

def save_formset(self, request, form, formset, change):
    instances = formset.save(commit=False)
    for instance in instances:
        # Do something with `instance`
        instance.save()
    formset.save_m2m()

Проблема, с которой вы столкнетесь, заключается в том, что save_formset метод должен идти на родителя ModelAdmin а не inlines, и оттуда, нет никакого способа узнать, какой inline фактически используется. Если у вас есть объект с двумя "типами" и все поля одинаковы, то вы должны использовать прокси-модели, и вы можете переопределить метод save каждого для автоматической установки соответствующего типа.

class CustomerCompanyType1(CustomerCompany):
    class Meta:
       proxy = True

    def save(self, *args, **kwargs):
        self.type = 1
        super(CustomerCompanyType1, self).save(*args, **kwargs)

class CustomerCompanyType2(CustomerCompany):
    class Meta:
       proxy = True

    def save(self, *args, **kwargs):
        self.type = 2
        super(CustomerCompanyType2, self).save(*args, **kwargs)

Тогда вам не нужно делать ничего особенного со своими строками. Просто измените свои существующие классы встроенного администратора, чтобы использовать их соответствующую модель прокси, и все само уладится.

Есть save_formset метод, который вы могли бы переопределить. Вы должны решить, какие formset представляет как-то.

def save_formset(self, request, form, formset, change):
    instances = formset.save(commit=False)
    for instance in instances:
        # Do something with `instance`
        instance.save()
    formset.save_m2m()

Другие ответы правильны, когда дело доходит до использования save_formset. Им не хватает способа проверить, какая модель в данный момент сохранена. Для этого вы можете просто:

if formset.model == CustomerCompany:
    # actions for specific model

Что заставило бы функцию save_formset выглядеть следующим образом: (при условии, что вы просто хотите переопределить сохранение для конкретной модели (ей))

def save_formset(self, request, form, formset, change):

    # if it's not the model we want to change
    # just call the default function
    if formset.model != CustomerCompany:
        return super(CustomerAdmin, self).save_formset(request, form, formset, change)

    # if it is, do our custom stuff
    instances = formset.save(commit=False)
    for instance in instances:
        instance.type = 2
        instance.save()
    formset.save_m2m()

В тех случаях, когда вам нужно выполнить действие, если реестр новый, вам нужно сделать это перед сохранением набора форм.

          def save_formset(self, request, form, formset, change):
        for form in formset:
            model = type(form.instance)
            if not form["id"].initial and hasattr(model, "created_by"):
                # craeted_by will not appear in the form dictionary because
                # is read_only, but we can anyway set it directly at the yet-
                # to-be-saved instance.
                form.instance.created_by = request.user
        super().save_formset(request, form, formset, change)

В этом случае я также применяю его, когда модель содержит поле «created_by» (потому что это для примеси, которую я использую во многих местах, а не для конкретной модели).

Просто удалите and hasattr(model, "created_by")часть, если она вам не нужна.

Некоторые другие свойства, которые могут быть вам интересны при работе с наборами форм:

              """
        The interesting fields to play with are:
        for form in formset:
            print("Instance str representation:", form.instance)
            print("Instance dict:", form.instance.__dict__)
            print("Initial for ID field:", form["id"].initial)
            print("Has changed:", form.has_changed())

        form["id"].initial will be None if it's a new entry.
        """

Я надеюсь, что мои копания помогут кому-то еще! ^^

Вы можете переопределить набор форм , который InlineModelAdmin использует для сохранения новых моделей:

      def formset_cls(type):
    class CustomerCompanyInlineFormset(BaseInlineFormSet):
        def save_new(self, form, commit=True):  # override
            form.instance.type = type
            return super().save_new(form, commit=commit)
    return CustomerCompanyInlineFormset

class CustomerCompanyType1Inline(admin.TabularInline):
    model = CustomerCompany
    formset = formset_cls(type=1)
    def queryset(self, request):
        return super(CustomerCompanyType1Inline, self).queryset(request).filter(type=1)
Другие вопросы по тегам