Предварительное сохранение Джанго

Я работаю над своим первым проектом с Django, это личный блог.

Модель истории имеет поле «начало», то есть первые 100 символов самой истории

Я хочу создать начало с pre_save, но всегда получаю ошибку: в разделе администратора, когда я добавляю историю и оставляю начало пустым, Джанго говорит: «Это поле обязательно для заполнения»!!

Это очень хорошо работает в другом файле

Вот код:

from django.db import models
from django.db.models.signals import pre_save

from my_blog_tags.models import Tag


# Create your models here.

class Story(models.Model):
    title = models.CharField(max_length=75)
    story = models.TextField()
    beginning = models.CharField(max_length=100)
    tags = models.ManyToManyField(Tag, blank=True)
    active = models.BooleanField(default=True)
    views = models.IntegerField(default=0)

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = "story"
        verbose_name_plural = "stories"


def story_pre_save(sender, instance: Story, *args, **kwargs):
    if not instance.beginning:
        story = str(instance.story)
        instance.beginning = story[:100]


pre_save.connect(story_pre_save, sender=Story)

Скрин из админки:

Пожалуйста, покажите полную трассировку. Я бы настоятельно не советовал использовать сигналы. Сигналы — это антипаттерн: lincolnloop.com/blog/django-anti-patterns-signals специально для этого. Вам здесь не нужно поле beginning, во-первых, я думаю, это просто свойство.

Willem Van Onsem 20.12.2020 17:51

@WillemVanOnsem Имеет значение как собственное поле, поскольку его можно вручную установить на что-то другое. Я согласен с тем, что вы не должны использовать сигналы, если вам не нужно подключаться к поведению сторонних приложений.

user2390182 20.12.2020 17:53

@schwobaseggl: да, я согласен, что мы можем использовать это, если пользователь может сам определить начало, но если это всегда «нарезанная» версия .story, то это не должно храниться в базе данных. Но вы правы, что пользователь может определить другое начало.

Willem Van Onsem 20.12.2020 17:55

Опять же, метод get_beginning а-ля return self.beginning or self.story[:100] был бы проще, чем рисковать в этой глуши :-), особенно если вы когда-нибудь захотите изменить начальную логику по умолчанию.

user2390182 20.12.2020 17:57

Вы имеете в виду, что лучше добавить метод в ModelManger для возврата первых 100 символов поля истории?

Mohammad javad 20.12.2020 18:01

@Мохаммадджавад: да. Сигналы не очень надежны: некоторые вызовы ORM, такие как .bulk_create(..) и .update(..), обходят сигналы, а это означает, что есть способы создавать/обновлять истории без соответствующего обновления .beginning. Даже если это сработает, становится неясно, что именно происходит, когда вы сохраняете объект модели. Только в редких случаях сигналы полезны.

Willem Van Onsem 20.12.2020 18:04

@WillemVanOnsem, если это не очень хорошо, как я могу сделать это по-другому: модель тега имеет поля title и in_url, in_url' такая же, как заголовок, но с replace(' ','-') в функции pre_save

Mohammad javad 20.12.2020 18:09

@Mohammadjavad: похоже, вы делаете свой собственный slugify с частью replace (docs.djangoproject.com/en/3.1/ref/utils/… ). Вместо этого я бы посоветовал использовать slugify. Вам лучше переопределить метод def save() и слагифицировать там, или вы установите его в представлении. Вы также можете использовать AutoSlugField из django-autoslug, чтобы просто указать, что должно быть слагифицировано, и тогда вам не нужно заботиться об этом самостоятельно.

Willem Van Onsem 20.12.2020 18:11
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python.
Некоторые методы, о которых вы не знали, что они существуют в Python.
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
8
1 450
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Ошибка, которую вы получаете, связана с тем, что beginning является обязательным полем. Вы можете разрешить элементу формы быть пустым с помощью пустого = True в соответствующем поле модели:

class Story(models.Model):
    # …
    beginning = models.CharField(max_length=100, blank=True)

Я бы также посоветовал не использовать сигналы для установки поля beginning, если оно оставлено пустым. Сигналы — это антипаттерн. Они полезны, если у вас есть модели в другом приложении, но если у вас есть контроль над моделью, работать с сигналами не рекомендуется.

Более элегантный способ решить эту проблему может быть предложен @schwobaseggl, где у вас есть свойство, которое либо берет начало, либо нарезает историю, поэтому:

class Story(models.Model):
    
    def get_beginning(self):
        return self.beginning or self.story[:100]

Вы можете использовать это в шаблоне с {{ mystory.get_beginning }}. Если вы не укажете .beginning самостоятельно, а позже обновите .story, то get_beginning автоматически вернет обновленную нарезанную версию.

Он хочет сохранить первые 100 символов поля истории в начальном поле. переопределение метода сохранения должно быть хорошим

Reza GH 20.12.2020 18:17

@RezaGH: проблема в том, что если вы позже измените story, то он больше не будет «обновляться». Это также сделало бы базу данных больше, так как теперь она в основном копирует первые 100 символов в beginning, это также замедляет запрос, потому что для возврата данных из базы данных требуется больше «пропускной способности». Так что это дублирующийся data-antipattern

Willem Van Onsem 20.12.2020 18:18

Так что, само начальное поле нам больше не нужно?

Reza GH 20.12.2020 18:21

@RezaGH: нет. У вас есть поле beginning, где вы можете ввести другое начало, если это необходимо. Дело в том, что у нас есть оба поля, но мы выполняем логику выбора либо self.beginning, либо self.story[:100] во время выполнения. Это означает, что если вы позже измените self.beginning на другое начало, self.get_beginning() вернет это начало, а не нарезанный вариант истории.

Willem Van Onsem 20.12.2020 18:23

@RezaGH: это позволяет, как говорит schwobaseggl, автоматически использовать нарезанную историю или указать собственное начало.

Willem Van Onsem 20.12.2020 18:24

Спасибо за помощь и объяснение <3

Mohammad javad 20.12.2020 18:33

Нет необходимости использовать синглы, вы можете переопределить метод save. Чтобы сделать это для вас. вам просто нужно сделать что-то вроде этого

    class Story(models.Model):
        beginning = models.CharField(max_length=100, blank=True, null=True)

    def save(self, *args, **kwargs):
        self.beginning = self.story[0:100]
        return super().save(*args, **kwargs)

Он автоматически сохранит первые 100 символов вашей истории.

Другие вопросы по теме