Поле моделі Django за замовчуванням базується на іншому полі тієї ж моделі


92

У мене є модель, в якій я хотів би містити ім’я суб’єктів та їхні ініціали (його дані дещо анонімізуються та відстежуються ініціалами).

Зараз я писав

class Subject(models.Model):

    name = models.CharField("Name", max_length=30)
    def subject_initials(self):
        return ''.join(map(lambda x: '' if len(x)==0 else x[0],
                           self.name.split(' ')))
    # Next line is what I want to do (or something equivalent), but doesn't work with
    # NameError: name 'self' is not defined
    subject_init = models.CharField("Subject Initials", max_length=5, default=self.subject_initials)

Як вказує останній рядок, я вважаю за краще мати можливість, щоб ініціали фактично зберігались у базі даних як поле (незалежно від імені), але це ініціалізується значенням за замовчуванням на основі поля імені. Однак у мене виникають проблеми, оскільки моделі django, здається, не мають "себе".

Якщо я змінив рядок на subject_init = models.CharField("Subject initials", max_length=2, default=subject_initials), я можу виконати syncdb, але не можу створити нові теми.

Чи можливо це в Django, якщо функція, що викликається, дає за замовчуванням деяке поле на основі значення іншого поля?

(З цікавого, я хочу розділити свої ініціали магазину окремо в рідкісних випадках, коли дивні прізвища можуть відрізнятися від тих, які я відстежую. Наприклад, хтось вирішив, що ініціалами Теми 1, що називається "Джон О'Меллорі", є "JM", а не "JO", і хоче виправити редагування як адміністратор.)

Відповіді:


89

Моделі, безумовно, мають "я"! Просто ви намагаєтесь визначити атрибут класу моделі як залежний від екземпляра моделі; це неможливо, оскільки екземпляр не існує (і не може) існувати до того, як ви визначите клас та його атрибути.

Щоб отримати бажаний ефект, перевизначте метод save () класу моделі. Внесіть будь-які необхідні зміни в екземпляр, а потім зателефонуйте методу суперкласу, щоб здійснити фактичне збереження. Ось короткий приклад.

def save(self, *args, **kwargs):
    if not self.subject_init:
        self.subject_init = self.subject_initials()
    super(Subject, self).save(*args, **kwargs)

Це висвітлено в Замінні методи моделі у документації.


5
Зверніть увагу, що в Python 3 ви можете просто зателефонувати super().save(*args, **kwargs)(без Subject, selfаргументів), як у прикладі в документації, на яку посилаються.
Курт Пік,

1
Шкода, що це не дозволяє визначити значення атрибута моделі за замовчуванням на основі іншого, що особливо корисно на стороні адміністратора (наприклад, для автоінкрементованого поля), оскільки воно обробляє його при збереженні. Або я це неправильно розумію?
Vadorequest

18

Я не знаю, чи є кращий спосіб зробити це, але ви можете використовувати обробник сигналу для по pre_saveсигналу :

from django.db.models.signals import pre_save

def default_subject(sender, instance, using):
    if not instance.subject_init:
        instance.subject_init = instance.subject_initials()

pre_save.connect(default_subject, sender=Subject)

1
Ви імпортували post_saveзамість pre_save.
Алі Расім Кокал

1
@arkocal: дякую за голови, щойно виправили. У таких випадках ви можете запропонувати редагування самостійно, це допомагає виправити подібні речі :)
Габі Пуркару

1
Здається, у цій реалізації відсутній **kwargsаргумент, який повинні мати всі функції одержувача згідно docs.djangoproject.com/en/2.0/topics/signals/… ?
Курт Пік,

Документація рекомендує проти сигналів для контрольованих вами шляхів коду . Прийнята відповідь заміщення save(), можливо, змінена на заміщення __init__, є кращою, оскільки вона є більш явною.
Адам Джонсон,

7

Використовуючи сигнали Django , це може бути зроблено досить рано, отримавши на post_initсигналі від моделі.

from django.db import models
import django.dispatch

class LoremIpsum(models.Model):
    name = models.CharField(
        "Name",
        max_length=30,
    )
    subject_initials = models.CharField(
        "Subject Initials",
        max_length=5,
    )

@django.dispatch.receiver(models.signals.post_init, sender=LoremIpsum)
def set_default_loremipsum_initials(sender, instance, *args, **kwargs):
    """
    Set the default value for `subject_initials` on the `instance`.

    :param sender: The `LoremIpsum` class that sent the signal.
    :param instance: The `LoremIpsum` instance that is being
        initialised.
    :return: None.
    """
    if not instance.subject_initials:
        instance.subject_initials = "".join(map(
                (lambda x: x[0] if x else ""),
                instance.name.split(" ")))

post_initСигнал посилається по класу , як тільки він зробив ініціалізації на екземплярі. Таким чином, екземпляр отримує значення nameперед тестуванням, чи встановлені його ненульовані поля.


Документація рекомендує проти сигналів для контрольованих вами шляхів коду . Прийнята відповідь заміщення save(), можливо, змінена на заміщення __init__, є кращою, оскільки вона є більш явною.
Адам Джонсон,

2

Як альтернативну реалізацію відповіді Габі Пуркару , ви також можете підключитися до pre_saveсигналу за допомогою receiverдекоратора :

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


@receiver(pre_save, sender=Subject)
def default_subject(sender, instance, **kwargs):
    if not instance.subject_init:
        instance.subject_init = instance.subject_initials()

Ця функція приймача також бере **kwargsпідстановочні аргументи ключового слова, які повинні обробляти всі обробники сигналів відповідно до https://docs.djangoproject.com/en/2.0/topics/signals/#receiver-functions .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.