How to add a calculated field to a Django model

后端 未结 5 1345
情书的邮戳
情书的邮戳 2020-11-28 23:59

I have a simple Employee model that includes firstname, lastname and middlename fields.

On the admin side and li

相关标签:
5条回答
  • 2020-11-29 00:07

    I recently worked on a library that may solve the problem you're having quite easily.

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

    Install that, add to INSTALLED_APPS and then

    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')
    
    0 讨论(0)
  • 2020-11-29 00:10

    Ok... Daniel Roseman's answer seemed like it should have worked. As is always the case, you find what you're looking for after you post the question.

    From the Django 1.5 docs I found this example that worked right out of the box. Thanks to all for your help.

    Here is the code that worked:

    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)
    
    0 讨论(0)
  • 2020-11-29 00:20

    Daniel Roseman's solution makes a calculated field an attribute of a Model, however it does not make it accessible via QuerySet methods (eg. .all(), .values()). This is because QuerySet methods call the database directly, circumventing the django Model.

    Since QuerySets access the database directly, the solution is to override the Manager's .get_queryset() method by appending your calculated field. The calculated field is created using .annotate(). Finally, you set the objects Manager in your Model to your new Manager.

    Here is some code demonstrating this:

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

    Now, you will be able to call .values() or .all() and access the newly calculated link attribute as declared in the Manager.

    It would have also been possible to use other functions in .annotate(), such as F().

    I believe the attribute would still not be available in object._meta.get_fields(). I believe you can add it here, but I haven't explored how - any edits/comments would be helpful.

    0 讨论(0)
  • 2020-11-29 00:22

    In this case if you are only going to use the field for representation in admin site and such issues, you might better to consider overriding str() or unicode() method of the class as it is mentioned in django documentation here:

    class Employee(models.Model):
        # fields definitions
        def __str__(self):
            return self.lastname + ' ,' + self.firstname + ' ' + self.middlename
    
    0 讨论(0)
  • 2020-11-29 00:26

    That's not something you do as a field. Even if that syntax worked, it would only give the value when the class was defined, not at the time you access it. You should do this as a method, and you can use the @property decorator to make it look like a normal attribute.

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

    self.lastname etc appear as just their values, so no need to call any other method to convert them.

    0 讨论(0)
提交回复
热议问题