skip to Main Content

So, what i’m tryna do here is set the status of an object based on the length of m2m field.
Here’s how it looks

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

But it throws me an error that goes like

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

And what it want to achieve is whenever the dependency field is empty then status should be 1 and otherwise it should be 2. But i couldn’t find a way to do it.

2

Answers


  1. Chosen as BEST ANSWER

    So, after several hours googling and just melt-functioning i had a thought that just popped right into my brain, what if i google something like "django m2m validation" and i got it. after reading django docs and this Django ManyToMany model validation i managed to get it working.

    but still for me it is indeed crazy why django has no m2m sort of saving and validation kinda thing by default.

    Here goes the code.

    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 = 'Настройки'
    

  2. I think you can use Django database transactions.

    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()
    

    Hope it could help.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search