Django-Modellfeld Standard basierend auf einem anderen Feld im selben Modell

91

Ich habe ein Modell, das einen Betreffnamen und deren Initialen enthalten soll (die Daten sind etwas anonymisiert und werden durch Initialen verfolgt).

Im Moment habe ich geschrieben

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)

Wie in der letzten Zeile angegeben, würde ich es vorziehen, wenn die Initialen tatsächlich als Feld (unabhängig vom Namen) in der Datenbank gespeichert werden, dies wird jedoch mit einem Standardwert initialisiert, der auf dem Namensfeld basiert. Ich habe jedoch Probleme, da Django-Modelle kein "Selbst" zu haben scheinen.

Wenn ich die Zeile in subject_init = models.CharField("Subject initials", max_length=2, default=subject_initials)ändere, kann ich die Synchronisierung durchführen, aber keine neuen Themen erstellen.

Ist dies in Django möglich, wenn eine aufrufbare Funktion einem Feld einen Standardwert gibt, der auf dem Wert eines anderen Felds basiert?

(Für Neugierige ist der Grund, warum ich meine Geschäftsinitialen separat trennen möchte, in seltenen Fällen, in denen seltsame Nachnamen andere als die von mir verfolgten haben. ZB hat jemand anderes entschieden, dass die Initialen von Betreff 1 mit dem Namen "John O'Mallory" lauten "JM" statt "JO" und möchte es als Administrator bearbeiten.)

Dr. Jimbob
quelle

Antworten:

88

Models haben sicherlich ein "Selbst"! Sie versuchen lediglich, ein Attribut einer Modellklasse als abhängig von einer Modellinstanz zu definieren. Dies ist nicht möglich, da die Instanz nicht existiert (und nicht existieren kann), bevor Sie die Klasse und ihre Attribute definieren.

Überschreiben Sie die save () -Methode der Modellklasse, um den gewünschten Effekt zu erzielen. Nehmen Sie die gewünschten Änderungen an der Instanz vor und rufen Sie dann die Methode der Oberklasse auf, um das eigentliche Speichern durchzuführen. Hier ist ein kurzes Beispiel.

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

Dies wird in der Dokumentation unter Überschreiben von Modellmethoden behandelt .

Elf Sternberg
quelle
4
Beachten Sie, dass Sie in Python 3 einfach super().save(*args, **kwargs)(ohne die Subject, selfArgumente) wie im Beispiel in der Dokumentation aufrufen können .
Kurt Peek
Schade, dass dies nicht erlaubt, einen Standardmodellattributwert basierend auf einem anderen zu definieren, was auf der Admin-Seite besonders nützlich ist (zum Beispiel für ein automatisch inkrementiertes Feld), da es beim Speichern behandelt wird. Oder verstehe ich das falsch?
Vadorequest
18

Ich weiß nicht, ob es einen besseren Weg gibt, aber Sie können einen Signalhandler für das pre_saveSignal verwenden :

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)
Gabi Purcaru
quelle
1
Sie importiert post_savestatt pre_save.
Ali Rasim Kocal
1
@arkocal: danke für das Heads-up, habe es einfach behoben. Sie können vorschlagen, Änderungen in solchen Fällen selbst
vorzunehmen
1
Es scheint, dass dieser Implementierung das **kwargsArgument fehlt , das alle Empfängerfunktionen laut docs.djangoproject.com/de/2.0/topics/signals/… haben sollten .
Kurt Peek
In der Dokumentation wird empfohlen, keine Signale für Codepfade zu verwenden, die Sie steuern . Die akzeptierte Antwort des Überschreibens save(), die möglicherweise in Überschreiben geändert wurde __init__, ist besser, da sie expliziter ist.
Adam Johnson
7

Mit Django-Signalen kann dies ziemlich früh erfolgen, indem das post_initSignal vom Modell empfangen wird .

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

Das post_initSignal wird von der Klasse gesendet, sobald die Instanz initialisiert wurde. Auf diese Weise erhält die Instanz einen Wert für, namebevor getestet wird, ob ihre nicht nullbaren Felder festgelegt sind.

große Nase
quelle
In der Dokumentation wird empfohlen, keine Signale für Codepfade zu verwenden, die Sie steuern . Die akzeptierte Antwort des Überschreibens save(), die möglicherweise in Überschreiben geändert wurde __init__, ist besser, da sie expliziter ist.
Adam Johnson
2

Als alternative Implementierung der Antwort von Gabi Purcaru können Sie auch pre_saveüber den receiverDekorateur eine Verbindung zum Signal herstellen :

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

Diese Empfängerfunktion verwendet auch die **kwargsPlatzhalter-Schlüsselwortargumente, die alle Signalhandler gemäß https://docs.djangoproject.com/de/2.0/topics/signals/#receiver-functions verwenden müssen .

Kurt Peek
quelle