webentwicklung-frage-antwort-db.com.de

Django-Modellfeld Standardmäßig aus einem anderen Feld im selben Modell

Ich habe ein Modell, das ich gerne einen Betreffnamen und seine Initialen enthalten würde. (Die Daten sind etwas anonymisiert und werden von Initialen nachverfolgt.) 

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, die Initialen tatsächlich als Feld (unabhängig vom Namen) in der Datenbank speichern zu lassen, 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 syncdb machen, aber keine neuen Betreffs erstellen.

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

(Für Neugierige ist der Grund, weshalb ich meine Geschäftsinitialen separat trennen möchte, in seltenen Fällen, in denen merkwürdige Nachnamen andere Namen haben als die, die ich aufspüre. Ein anderer Benutzer hat beispielsweise entschieden, dass die Initialen des Subjekts 1 "John O'Mallory" heißen "JM" anstelle von "JO" und möchte es als Administrator bearbeiten.)

70
dr jimbob

Modelle haben sicherlich ein "Selbst"! Es ist nur so, dass Sie versuchen, ein Attribut einer Modellklasse als abhängig von einer Modellinstanz zu definieren. Das ist nicht möglich, da die Instanz nicht existiert (und auch nicht existieren kann), bevor Sie die Klasse und ihre Attribute definieren.

Um den gewünschten Effekt zu erzielen, überschreiben Sie die save () -Methode der Modellklasse. Nehmen Sie die gewünschten Änderungen an der Instanz vor, und rufen Sie dann die Methode der Superklasse auf, um die tatsächliche Speicherung 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 Überschreiben von Modellmethoden in der Dokumentation behandelt.

70
Elf Sternberg

Ich weiß nicht, ob es einen besseren Weg gibt, aber Sie können einen Signal-Handler verwenden für das pre_save-Signal :

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)
14
Gabi Purcaru

Mit Django-Signalen kann dies recht früh erfolgen, indem das post_init-Signal 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_init-Signal wird von der Klasse gesendet, sobald die Instanz für die Instanz initialisiert wurde. Auf diese Weise erhält die Instanz einen Wert für name, bevor geprüft wird, ob die nicht-zulässigen Felder festgelegt sind.

4
bignose

Als alternative Implementierung der Antwort von Gabi Purcaru können Sie auch eine Verbindung zum pre_save-Signal unter Verwendung des receiver-Dekorators 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 übernimmt auch die **kwargs-Platzhalter-Schlüsselwortargumente, die alle Signalhandler gemäß https://docs.djangoproject.com/en/2.0/topics/signals/#receiver-functions annehmen müssen. 

1
Kurt Peek