Altering model after field validation

I'm implementing a tagging system. Currently, the models look like this:

class Tag(models.Model):
    label = models.CharField(max_length=MAX_TAG_LENGTH)

class TagManager(models.Model):
    tags = models.ManyToManyField(Tag, related_name="referrers")

    def associate_tag(self, tag_label: str):
        . . .

And I have a custom field that cuts its input on commas so the user can enter tags as a comma-separated list:

class TagsField(forms.CharField):
    def to_python(self, value):
        if not value:
            return []
        return [tag.strip() for tag in value.split(',')]

Finally, I have the model and form where these are used:

class Thing(models.Model):
    tags = models.OneToOneField(TagManager, on_delete=models.SET_NULL, null=True)

class ThingForm(forms.ModelForm):
    tags = TagsField(widget=forms.TextInput(attrs={"placeholder": "Tags", "required": False}))

    class Meta:
        model = Thing
        fields = ["tags"]


My issue is that if I populate and validate the form:

form = ThingForm(data={"tags": ["One", "Two"]})

I get errors:

{'tags': ["“["One", "Two"]” value must be an integer."]}

Which I'm guessing is because it's trying to place the stringified list in the OneToOneField, which isn't going to work.

What I really need to do is after validating the field, I need to iterate the results of to_python, and call thing_instance.tags.associate_tag on each of the validated tag strings.

Is there a "hook" method on forms that will allow me to do this cleanly? I've read over the docs and Form source and can't find any obvious candidates.

I realized as I was writing this that it's a clean_* method that I need. This didn't strike me as "cleaning" on first brush, so I ignored it.

The solution was to add a clean_tags method to the ThingForm class:

def clean_tags(self):
    tags = self.cleaned_data["tags"]
    for tag in tags:

It associates the cleaned tags, and then returns the PK of the TagManager they were added to.


Константы Python: Улучшение управляемости вашего кода

Современный Python: начинаем проект с pyenv и poetry

Настройка проекта Python — виртуальные среды и управление пакетами

Использование requests в Python — тайм-ауты, повторы, хуки

Понимание декораторов в Python

ProcessPoolExecutor в Python: полное руководство

map() против submit() с ProcessPoolExecutor в Python

Понимание атрибутов, словарей и слотов в Python

Полное руководство по slice в Python

Выпуск Django 4.0

Безопасное развертывание приложения Django с помощью Gunicorn, Nginx и HTTPS

Автоматический повтор невыполненных задач Celery

Django REST Framework и Elasticsearch

Докеризация Django с помощью Postgres, Gunicorn и Nginx

Асинхронные задачи с Django и Celery

Релизы безопасности Django: 3.2.4, 3.1.12 и 2.2.24

Выпуски исправлений ошибок Django: 3.2.3, 3.1.11 и 2.2.23

Эффективное использование сериализаторов Django REST Framework

Выпуски безопасности Django: 3.2.2, 3.1.10 и 2.2.22

Выпущенные релизы безопасности Django: 3.2.1, 3.1.9 и 2.2.21

View all tutorials →