11

I have a model that holds user address. This model has to have first_name and last_name fields since one would like to set address to a recipient (like his company, etc.). What I'm trying to achieve is:

  • If the first_name/last_name field in the address is filled - return simply that field
  • If the first_name/last_name field in the address is empty - fetch the corrresponding field data from a foreignkey pointing to a proper django.auth.models.User
  • I'd like this to be treated as normal Django field that would be present in fields lookup
  • I don't want to create a method, since it's a refactoring and Address.first_name/last_name are used in various places in the application (also in model forms, etc.), so I need this to me as smooth as possible, or else, I will have to tinker around in a lot of places.
David Buck
  • 3,752
  • 35
  • 31
  • 35
pielgrzym
  • 1,647
  • 3
  • 19
  • 28

2 Answers2

13

There are two options here. The first is to create a method to look it up dynamically, but use the property decorator so that other code can still use straight attribute access.

class MyModel(models.Model):
    _first_name = models.CharField(max_length=100, db_column='first_name')

    @property
    def first_name(self):
        return self._first_name or self.user.first_name

    @first_name.setter
    def first_name(self, value):
       self._first_name = value

This will always refer to the latest value of first_name, even if the related User is changed. You can get/set the property exactly as you would an attribute: myinstance.first_name = 'daniel'

The other option is to override the model's save() method so that it does the lookup when you save:

def save(self, *args, **kwargs):
    if not self.first_name:
        self.first_name = self.user.first_name
    # now call the default save() method
    super(MyModel, self).save(*args, **kwargs)

This way you don't have to change your db, but it is only refreshed on save - so if the related User object is changed but this object isn't, it will refer to the old User value.

Daniel Roseman
  • 588,541
  • 66
  • 880
  • 895
  • you can specify db_column='first_name' to prevent a schema change: http://docs.djangoproject.com/en/dev/ref/models/fields/#db-column – Benjamin Wohlwend Aug 27 '09 at 18:11
  • so after the @first_name.setter thingy just add first_name.db_column="first_name" ? Will I be able to do: address = Address.objects.get(first_name="blah") ? – pielgrzym Aug 27 '09 at 18:18
  • I've updated the code and explanation in response to piquadrat's comment. – Daniel Roseman Aug 27 '09 at 18:31
  • Thanks for help :) it works flawlessly (the property method), only thing I'm concerned - I've got a model form and it doesn't fill empty fields with the self.user.first_name. Setting the attribute and getting is works flawlessly. – pielgrzym Aug 27 '09 at 18:42
  • There is one more tiny problem - when doing lookups django orm can't resolve the property info field it requires the original _first_name field. – pielgrzym Aug 27 '09 at 19:23
2

Although this does not meet all the OP's requirements, in some use-cases where the fallback is needed in queryset filters (on the database level) it may be possible to use Django's Coalesce() class (docs).

The simplest solution in such a case might be queryset annotation (using the example from @daniel-roseman's answer):

queryset = MyModel.objects.annotate(first_name=Coalesce('_first_name', 'user__first_name'))

Each item obtained from the queryset will then get a first_name attribute, which has the value of _first_name with a fallback (in case the value is null) to user.first_name.

That is, assuming your model has a user relation.

This can be implemented e.g. in a custom manager and could also be used in conjunction with a property approach.

A detailed example for a similar case (override instead of fallback) is provided in this SO answer.

djvg
  • 11,722
  • 5
  • 72
  • 103