Задать вопрос
ri_gilfanov
@ri_gilfanov
Web- and desktop-developer

Как переопределить возможные значения ManyToManyField в InlineModelAdmin с учётом значения другого поля инлайн-формы?

1. Товары относятся к категориями
2. Категории имеют фильтры с возможными значениями
3. Товары имеют атрибуты с теми же возможными значениями

Как переопределить инлайн-поля ManyToManyField в редактировании товара так, чтобы выбор значений атрибута соответствовал набору значений фильтра?

Метод formfield_for_manytomany класса admin.TabularInline где-то вызывается в цикле и получает как аргумент только отдельное поле (db_field) отдельной инлайн-формы.

Мне же для переопределения выборки нужно в этом методе иметь доступ к соседнему полю (filter) инлайн-формы и его набору значений (filter.variant_set).

Как можно пробросить в этот метод значения соседних полей той же инлайн-формы? Какой метод переопределить?

1. models.py (в сокращении)
class Category(MPTTModel):  # Категория
    name = models.CharField('название', max_length=64)


class Product(models.Model):  # Товар
    category = TreeForeignKey(Category, models.CASCADE, verbose_name='категория')

    def save(self, *args, **kwargs):
        # тут кастомная логика с авто-удалением/добавлением атрибутов товарам


class VariantFilter(models.Model):  # Фильтр категории
    name = models.CharField('имя фильтра', max_length=64)
    category = TreeForeignKey(Category, models.CASCADE, verbose_name='категория')

    def save(self, *args, **kwargs):
        # тут кастомная логика с автодобавлением атрибутов товарам


class Variant(models.Model):  # Значения атрибутов товаров
    value = models.CharField('значение', max_length=64)
    filter = models.ForeignKey(VariantFilter, models.CASCADE)


class VariantAttribute(models.Model): #  Атрибут товара
    product = models.ForeignKey(Product, models.CASCADE, verbose_name='товар')
    filter = models.ForeignKey(VariantFilter, models.CASCADE, verbose_name='атрибут')
    values = models.ManyToManyField(Variant, blank=True, verbose_name='значения')

    class Meta:
        unique_together = (('product', 'filter'),)


2. admin.py (в сокращении)
# Инлайн-поля со значениями фильтров
class VariantInline(admin.TabularInline):
    model = Variant
    extra = 1


# Редактирование фильтра
@admin.register(VariantFilter)
class VariantFilterAdmin(admin.ModelAdmin):
    inlines = [VariantInline]


# Инлайн-поля с атрибутами товаров
class VariantAttributeInline(admin.TabularInline):
    model = VariantAttribute
    max_num = 0
    fields = ('filter', 'values',)
    readonly_fields = ('filter',)

    def has_delete_permission(self, request, obj=None):
        return False

    def formfield_for_manytomany(self, db_field, request, **kwargs):
        # тут нужно ограничить выбор значений атрибута товара
        # для этого сюда НУЖНО ПРОКИНУТЬ соседнее поле filter,
        # чтобы достать его набор значений (variant_set)
        field = super(VariantAttributeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
        if db_field.name == 'values':
            print(field)
        return field


# Редактирование товара
@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    model = Product
    inlines = [VariantAttributeInline]


Спасибо, что обратили внимание на вопрос.
Буду рад любым предложенным решениям.
  • Вопрос задан
  • 646 просмотров
Подписаться 2 Средний Комментировать
Решения вопроса 2
Поле filter не является редактируемым. Я думаю, что вам нужно получать доступ не к полю, а к объекту модели VariantAttribute. Верно?

Если так, то вам должен подойти вот этот вариант: https://stackoverflow.com/a/22003809/1245471 - здесь предлагают создать отдельный класс для формы и провести фильтрацию в нём, а потом задать этот класс для VariantAttributeInline (атрибут form, если мне не изменяет память).
Ответ написан
ri_gilfanov
@ri_gilfanov Автор вопроса
Web- and desktop-developer
Спасибо Anatoly Scherbakov, что натолкнул на решение.

class VariantAttributeForm(ModelForm):
    def __init__(self, *args, **kwargs):
        super(VariantAttributeForm, self).__init__(*args, **kwargs)
        if 'instance' in kwargs:
            self.fields['values'].queryset = Variant.objects.filter(filter=kwargs['instance'].filter)

    class Meta:
        model = VariantAttribute
        fields = '__all__'

class VariantAttributeInline(admin.TabularInline):
    form = VariantAttributeForm
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы