Comment ajouter un champ calculé à un modèle Django

j'ai un modèle simple Employee qui inclut les champs firstname , lastname et middlename .

du côté administratif et probablement ailleurs, je voudrais l'afficher comme:

lastname, firstname middlename

pour moi l'endroit logique pour faire ceci est dans le modèle en créant un champ calculé comme tel:

from django.db import models
from django.contrib import admin

class Employee(models.Model):
    lastname = models.CharField("Last", max_length=64)
    firstname = models.CharField("First", max_length=64)
    middlename = models.CharField("Middle", max_length=64)
    clocknumber = models.CharField(max_length=16)
    name = ''.join(
        [lastname.value_to_string(),
        ',',
         firstname.value_to_string(),
        ' ',
         middlename.value_to_string()])

    class Meta:
        ordering = ['lastname','firstname', 'middlename']

class EmployeeAdmin(admin.ModelAdmin):
    list_display = ('clocknumber','name')
    fieldsets = [("Name", {"fields":(("lastname", "firstname", "middlename"), "clocknumber")}),
        ]

admin.site.register(Employee, EmployeeAdmin)

en fin de compte, je pense que ce dont j'ai besoin est d'obtenir la valeur des champs de nom comme chaînes. L'erreur que j'ai obtiens est value_to_string() takes exactly 2 arguments (1 given) . Valeur de chaîne veut self, obj . Je ne sais pas ce que obj signifie.

Il doit y avoir un moyen facile de faire cela, je suis sûr que je ne suis pas le premier à vouloir le faire.

Edit: C'est mon code modifié à la réponse de Daniel. L'erreur que j'obtiens est:

django.core.exceptions.ImproperlyConfigured: 
    EmployeeAdmin.list_display[1], 'name' is not a callable or an 
    attribute of 'EmployeeAdmin' of found in the model 'Employee'.


from django.db import models
from django.contrib import admin

class Employee(models.Model):
    lastname = models.CharField("Last", max_length=64)
    firstname = models.CharField("First", max_length=64)
    middlename = models.CharField("Middle", max_length=64)
    clocknumber = models.CharField(max_length=16)

    @property
    def name(self):
        return ''.join(
            [self.lastname,' ,', self.firstname, ' ', self.middlename])

    class Meta:
        ordering = ['lastname','firstname', 'middlename']

class EmployeeAdmin(admin.ModelAdmin):
    list_display = ('clocknumber','name')
    fieldsets = [("Name", {"fields":(("lastname", "firstname", "middlename"), "clocknumber")}),
]

admin.site.register(Employee, EmployeeAdmin)
31
demandé sur cstrutton 2013-07-16 20:54:36

5 réponses

ce n'est pas quelque chose que tu fais comme un champ. Même si cette syntaxe fonctionnait, elle ne donnerait la valeur que lorsque la classe était définie, pas au moment où vous y accédez. Vous devez faire cela comme une méthode, et vous pouvez utiliser le @property décorateur pour le faire ressembler à un attribut normal.

@property
def name(self):
    return ''.join(
        [self.lastname,' ,', self.firstname, ' ', self.middlename])

self.lastname etc apparaissent comme juste leurs valeurs, donc pas besoin d'appeler une autre méthode pour les convertir.

54
répondu Daniel Roseman 2013-07-16 17:01:12

Ok... La réponse de Daniel Roseman semblait devoir marcher. Comme c'est toujours le cas, vous trouverez ce que vous recherchez après avoir posté la question .

du django 1.5 docs j'ai trouvé cet exemple qui a fonctionné directement de la boîte. Merci à tous pour votre aide.

voici le code qui a fonctionné:

from django.db import models
from django.contrib import admin

class Employee(models.Model):
    lastname = models.CharField("Last", max_length=64)
    firstname = models.CharField("First", max_length=64)
    middlename = models.CharField("Middle", max_length=64)
    clocknumber = models.CharField(max_length=16)

    def _get_full_name(self):
        "Returns the person's full name."
        return '%s, %s %s' % (self.lastname, self.firstname, self.middlename)
    full_name = property(_get_full_name)


    class Meta:
        ordering = ['lastname','firstname', 'middlename']

class EmployeeAdmin(admin.ModelAdmin):
    list_display = ('clocknumber','full_name')
    fieldsets = [("Name", {"fields":(("lastname", "firstname", "middlename"), "clocknumber")}),
]

admin.site.register(Employee, EmployeeAdmin)
30
répondu cstrutton 2013-07-16 18:07:37

Daniel Roseman, la solution d'un champ calculé un attribut d'un Model , mais il ne pas le rendre accessible par QuerySet méthodes (eg. . all() , .values() ). C'est parce que les méthodes QuerySet appellent la base de données directement , en contournant le django Model .

étant donné que les QuerySets accèdent directement à la base de données, la solution consiste à outrepasser la méthode Manager 's .get_queryset() en ajoutant votre champ calculé. Le champ calculé est créé en utilisant .annotate() . Enfin, vous avez placé le objects Manager dans votre Model sur votre nouveau Manager .

voici un code qui démontre ceci:

models.py

from django.db.models.functions import Value, Concat
from django.db import Model

class InvoiceManager(models.Manager):
    """QuerySet manager for Invoice class to add non-database fields.

    A @property in the model cannot be used because QuerySets (eg. return
    value from .all()) are directly tied to the database Fields -
    this does not include @property attributes."""

    def get_queryset(self):
        """Overrides the models.Manager method"""
        qs = super(InvoiceManager, self).get_queryset().annotate(link=Concat(Value("<a href='#'>"), 'id', Value('</a>')))
        return qs

class Invoice(models.Model):
    # fields

    # Overridden objects manager
    objects = InvoiceManager()

Maintenant, vous pourrez appeler .values() ou .all() et accéder à l'attribut nouvellement calculé link comme déclaré dans le Manager .

Il aurait également été possible d'utiliser autres fonctions dans .annotate() , comme F() .

je crois que l'attribut ne serait toujours pas disponible dans object._meta.get_fields() . Je crois que vous pouvez l'ajouter ici, mais je n'ai pas exploré comment les modifications/commentaires seraient utiles.

10
répondu Alex Petralia 2017-05-23 12:09:59

dans ce cas si vous allez utiliser le champ pour la représentation dans le site d'administration et de telles questions, vous pourriez mieux envisager de passer outre str () ou unicode () méthode de la classe comme il est mentionné dans la documentation de django ici :

class Employee(models.Model):
    # fields definitions
    def __str__(self):
        return self.lastname + ' ,' + self.firstname + ' ' + self.middlename
1
répondu Farzad Vertigo 2017-05-03 20:29:37

j'ai récemment travaillé sur une bibliothèque qui peut résoudre le problème assez facilement.

https://github.com/brechin/django-computed-property

installez cela, ajoutez à INSTALLED_APPS et ensuite

class Employee(models.Model):
    ...
    name = computed_property.ComputedCharField(max_length=3 * 64, compute_from='full_name')

    @property
    def full_name(self):
        return '{LAST}, {FIRST} {MIDDLE}'.format(LAST=self.lastname, FIRST=self.firstname, MIDDLE=self.middlename')
1
répondu brechin 2018-02-12 15:38:44