Campo modello Django predefinito basato su un altro campo nello stesso modello


91

Ho un modello che vorrei contenere un nome di soggetti e le loro iniziali (i dati sono in qualche modo resi anonimi e tracciati dalle iniziali).

In questo momento, ho scritto

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)

Come indicato dall'ultima riga, preferirei che le iniziali vengano effettivamente memorizzate nel database come un campo (indipendentemente dal nome), ma che viene inizializzato con un valore predefinito basato sul campo del nome. Tuttavia, ho problemi perché i modelli django non sembrano avere un "sé".

Se cambio la linea in subject_init = models.CharField("Subject initials", max_length=2, default=subject_initials), posso fare il syncdb, ma non posso creare nuovi soggetti.

È possibile in Django, avendo una funzione richiamabile che assegna un valore predefinito a un campo in base al valore di un altro campo?

(Per i curiosi, il motivo per cui desidero separare le iniziali del mio negozio separatamente è in rari casi in cui cognomi strani possono avere diversi da quelli che sto monitorando. Ad esempio, qualcun altro ha deciso che le iniziali del Soggetto 1 denominato "John O'Mallory" sono "JM" anziché "JO" e desidera correggerlo modificandolo come amministratore.)

Risposte:


88

I modelli hanno certamente un "sé"! È solo che stai cercando di definire un attributo di una classe del modello come dipendente da un'istanza del modello; ciò non è possibile, poiché l'istanza non esiste (e non può) esistere prima di definire la classe e i suoi attributi.

Per ottenere l'effetto desiderato, sovrascrivi il metodo save () della classe del modello. Apporta le modifiche che desideri all'istanza necessarie, quindi chiama il metodo della superclasse per eseguire il salvataggio effettivo. Ecco un rapido esempio.

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

Questo è trattato in Override dei metodi del modello nella documentazione.


4
Nota che in Python 3 puoi semplicemente chiamare super().save(*args, **kwargs)(senza gli Subject, selfargomenti) come nell'esempio nella documentazione di riferimento.
Kurt Peek

1
Peccato che questo non permetta di definire un valore di attributo del modello predefinito basato su un altro, il che è particolarmente utile nel lato Admin (ad esempio, per un campo autoincrementato), poiché lo gestisce durante il salvataggio. O sto fraintendendo?
Vadorequest

18

Non so se esiste un modo migliore per farlo, ma puoi usare un gestore di segnale per il pre_savesegnale :

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
Hai importato post_saveinvece di pre_save.
Ali Rasim Kocal

1
@arkocal: grazie per l'avviso, l'ho appena risolto. Puoi suggerire le modifiche da solo in questi casi, aiuta a risolvere cose come questa :)
Gabi Purcaru

1
Sembra che a questa implementazione manchi l' **kwargsargomento che tutte le funzioni del ricevitore dovrebbero avere secondo docs.djangoproject.com/en/2.0/topics/signals/… ?
Kurt Peek

La documentazione sconsiglia i segnali per i percorsi di codice che controlli . La risposta accettata di override save(), forse cambiata in override __init__, è migliore in quanto più esplicita.
Adam Johnson

7

Usando i segnali Django , questo può essere fatto abbastanza presto, ricevendo il post_initsegnale dal modello.

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

Il post_initsegnale viene inviato dalla classe una volta completata l'inizializzazione sull'istanza. In questo modo, l'istanza ottiene un valore per nameprima di verificare se i suoi campi non annullabili sono impostati.


La documentazione sconsiglia i segnali per i percorsi di codice che controlli . La risposta accettata di override save(), forse cambiata in override __init__, è migliore in quanto più esplicita.
Adam Johnson

2

Come implementazione alternativa della risposta di Gabi Purcaru , puoi anche connetterti al pre_savesegnale utilizzando il receiverdecoratore :

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

Questa funzione del ricevitore accetta anche gli **kwargsargomenti delle parole chiave con caratteri jolly che tutti i gestori di segnali devono accettare secondo https://docs.djangoproject.com/en/2.0/topics/signals/#receiver-functions .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.