Как я могу изменить поле на основе другого поля m2m?

Итак, я пытаюсь сделать следующее: установить статус объекта на основе длины поля m2m. Вот как это выглядит

from django.db import models


class Dependency(models.Model):
    dependency = models.SlugField('Шаблон')


class Seo(models.Model):
    statuses = (
        (1, 'Дефолтный'),
        (2, 'Дополнительный')
    )

    dependencies = models.ManyToManyField(
        Dependency,
        verbose_name='Зависимости',
        blank=True,
        help_text='Оставьте пустым, если это дефолтный шаблон'
    )
    h1 = models.CharField('Заголовок(h1)', max_length=200)
    title = models.CharField('Заголовок(title)', max_length=200)
    description = models.CharField('Описание', max_length=200)
    keywords = models.TextField('Ключевые слова')
    status = models.IntegerField('Статус', choices=statuses, blank=True, editable=False)

    def save(self, *args, **kwargs):
        if len(self.dependencies) == 0:
            self.status = 1
        else:
            self.status = 2

        # self.status = 1
        #
        # print(len(self.dependencies))

        super().save(*args, **kwargs)


class Page(models.Model):
    pass

Но он выдает мне ошибку, которая выглядит так

ValueError: "<Seo: Seo object (None)>" needs to have a value for field "id" before this many-to-many relationship can be used.

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

Думаю, вы можете использовать транзакции базы данных Django.

from django.db import transaction

class Seo(models.Model):
    ...

    def save(self, *args, **kwargs):
        instance = super(Seo, self).save(*args, **kwargs)
        if self.id = None:
            transaction.on_commit(self.update_status)       
        return instance

    def update_status(self):
        if len(self.dependencies) == 0:
            self.status = 1
        else:
            self.status = 2
        self.save()

Надеюсь, это может помочь.

Итак, после нескольких часов гугления и просто плавления функций у меня возникла мысль, которая просто всплыла в моем мозгу, что если я погуглю что-то вроде "django m2m validation" и я получил это. После чтения документации django и этого Django ManyToMany model validation мне удалось заставить его работать.

но все же для меня это действительно безумие, почему в django нет m2m сохранения и валидации по умолчанию.

Вот код.

admin.py
@admin.register(Seo)
class SeoAdmin(admin.ModelAdmin):
    form = SEOForm
    list_display = [
        field.name for field in Seo._meta.get_fields() if field.name not in ['dependencies']
    ] + ['get_dependencies']
    list_display_links = ['id']
    search_fields = ['id', 'h1', 'title']

    def get_dependencies(self, obj):
        if obj:
            return ', '.join(d.dependency for d in obj.dependencies.all())

    get_dependencies.short_description = 'Зависимости'

forms.py

from django import forms
from django.core.exceptions import ValidationError
from .models import Seo
from .services import SEOService


_seo_service = SEOService()


class SEOForm(forms.ModelForm):
    """
    I just wonder why Django doesn't have validation m2m mechanism.
    """
    class Meta:
        model = Seo
        fields = [field.name for field in Seo._meta.get_fields()]

    _delimiter = _seo_service.delimiter

    def clean(self):
        dependencies = self.cleaned_data.get('dependencies')

        if dependencies:
            fields = [
                self.cleaned_data.get('h1'),
                self.cleaned_data.get('title'),
                self.cleaned_data.get('keywords'),
                self.cleaned_data.get('description')
            ]

            dependencies_list = [dep.dependency for dep in dependencies]

            for field in fields:
                if not _seo_service.is_value_valid(field, dependencies_list, delimiter=self._delimiter):
                    raise ValidationError(
                        f'The amount of {self._delimiter} signs is the same as dependencies. '
                        f'Amount of {self._delimiter}: {field.count(self._delimiter)}. Dependencies: {len(dependencies_list)}. '
                        f'Field content: {field}.'
                    )

        return self.cleaned_data

    def save(self, commit=True):
        m = super(SEOForm, self).save(commit=False)

        m.save()

        self.save_m2m()

        if not m.dependencies.all():
            m.status = 1
            return m

        m.status = 2

        dependencies_list = [dep.dependency for dep in m.dependencies.all()]

        m.h1 = _seo_service.replace_by_delimiter(m.h1, dependencies_list, self._delimiter)
        m.title = _seo_service.replace_by_delimiter(m.title, dependencies_list, self._delimiter)
        m.description = _seo_service.replace_by_delimiter(m.description, dependencies_list, self._delimiter)
        m.keywords = _seo_service.replace_by_delimiter(m.keywords, dependencies_list, self._delimiter)

        return m

models.py

from django.db import models
from .services import SEOService


class Dependency(models.Model):
    dependency = models.SlugField(
        'Зависимость', unique=True,
        help_text='Перечислите зависимости через нижнее подчеркивание. Пример: brand_model'
    )

    def __str__(self) -> str:
        return f'Зависимость {self.dependency}'

    class Meta:
        verbose_name = 'Зависимость'
        verbose_name_plural = 'Зависимости'

class Seo(models.Model):
    statuses = (
        (1, 'Дефолтная'),
        (2, 'Дополнительная')
    )

    _delimiter = SEOService().delimiter

    dependencies = models.ManyToManyField(
        Dependency,
        verbose_name='Зависимости',
        blank=True,
        help_text='Оставьте пустым, если это дефолтный шаблон.'
    )
    h1 = models.CharField(
        'Заголовок(h1)', max_length=200,
        help_text=f'Если вы ввели Купить {_delimiter}, а зависимость - car,'
        f' то после сохранения получится Купить car машину. '
        f'Все {_delimiter} заменяются на соотв. им зависимости.'
    )
    title = models.CharField(
        'Заголовок(title)', max_length=200,
        help_text=f'Если вы ввели Купить {_delimiter}, а зависимость - car,'
        f' то после сохранения получится Купить car машину. '
        f'Все {_delimiter} заменяются на соотв. им зависимости.'
    )
    description = models.CharField(
        'Описание', max_length=200,
        help_text=f'Если вы ввели Купить {_delimiter}, а зависимость - car,'
        f' то после сохранения получится Купить car машину. '
        f'Все {_delimiter} заменяются на соотв. им зависимости.'
    )
    keywords = models.TextField(
        'Ключевые слова',
        help_text=f'Если вы ввели Купить {_delimiter}, а зависимость - car,'
        f' то после сохранения получится Купить car машину. '
        f'Все {_delimiter} заменяются на соотв. им зависимости.'
    )
    status = models.IntegerField('Статус', blank=True, choices=statuses, help_text='Не трогать руками', null=True)

    def __str__(self) -> str:
        return f'Настройка сео'

    class Meta:
        verbose_name = 'Настройка'
        verbose_name_plural = 'Настройки'
Вернуться на верх