Home > Enterprise >  How can i change a field based on another m2m field?
How can i change a field based on another m2m field?

Time:05-17

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.

CodePudding user response:

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.

CodePudding user response:

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