web-dev-qa-db-fra.com

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

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

Du côté administrateur et probablement ailleurs, je voudrais afficher cela comme:

lastname, firstname middlename

Pour moi, l'endroit logique pour le faire est dans le modèle en créant un champ calculé en tant que 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, ce dont je pense avoir besoin, c'est d'obtenir la valeur des champs de nom sous forme de chaînes. L'erreur que j'obtiens est value_to_string() takes exactly 2 arguments (1 given). La valeur de la chaîne veut self, obj. Je ne sais pas ce que signifie obj.

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

Edit: Ceci est mon code modifié pour 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)
44
cstrutton

D'accord ... La réponse de Daniel Roseman semblait avoir dû fonctionner. Comme c'est toujours le cas, vous trouvez ce que vous cherchez après avoir posté la question .

À partir de la Django 1.5 docs j'ai trouvé cet exemple qui a fonctionné dès la sortie de la boîte. Merci a 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)
38
cstrutton

Ce n'est pas quelque chose que vous faites en tant que champ. Même si cette syntaxe fonctionnait, elle ne donnerait la valeur que lorsque la classe a été définie, pas au moment où vous y accédez. Vous devez le faire comme 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 uniquement comme leurs valeurs, donc pas besoin d'appeler une autre méthode pour les convertir.

64
Daniel Roseman

La solution de Daniel Roseman fait d'un champ calculé un attribut d'un Model, mais il ne le rend pas accessible via les méthodes QuerySet (par exemple .all(), .values()). En effet, les méthodes QuerySet appeler directement la base de données , contournant le Django Model.

Étant donné que les QuerySets accèdent directement à la base de données, la solution consiste à remplacer la méthode .get_queryset() de Manager en ajoutant votre champ calculé. Le champ calculé est créé à l'aide de .annotate(). Enfin, vous définissez le gestionnaire objects dans votre Model sur votre nouveau Manager.

Voici un code qui le démontre:

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 link nouvellement calculé comme déclaré dans 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 pense que vous pouvez l'ajouter ici, mais je n'ai pas exploré comment - des modifications/commentaires seraient utiles.

20
Alex Petralia

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

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

Installez-le, ajoutez-le à INSTALLED_APPS, puis

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')
6
brechin

Dans ce cas, si vous ne souhaitez utiliser le champ que pour la représentation dans le site d'administration et de tels problèmes, vous pourriez mieux envisager de remplacer str () ou méthode unicode () de la classe telle qu'elle est mentionnée dans Django ici :

class Employee(models.Model):
    # fields definitions
    def __str__(self):
        return self.lastname + ' ,' + self.firstname + ' ' + self.middlename
1
Farzad Vertigo