Alternative to User.get_profile()

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
from django.conf import settings
from django.contrib.auth.models import User
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
from django.db import models
from django.db.models.signals import post_init
from django.dispatch import dispatcher
from django.utils.functional import curry


try:
    getattr(settings, 'AUTH_PROFILE_MODULE')
except AttributeError:
    raise ImproperlyConfigured(
        "The 'AUTH_PROFILE_MODULE' setting isn't defined."
    )


def _get_next_or_previous_by_profile_FIELD(self, field, is_next):
    profile = self.get_profile()
    next_or_previous = profile._get_next_or_previous_by_FIELD(field, is_next)
    return next_or_previous.user


def inject_profile(sender, instance):
    """Attach fields from a ``Profile`` to a ``User`` instance.

    Provide an alternative to ``User.get_profile`` by automatically attaching
    the profile's fields to the ``User`` when it is instantiated.  The special
    methods for ``DateField``, ``FileField``, ``ImageField`` and fields with
    choices are also created.  ``_get_next_or_previous_by_profile_FIELD`` is
    used instead of ``Model._get_next_or_previous_by_FIELD`` when currying the
    ``get_next_by_FIELD`` and ``get_previous_by_FIELD`` methods.

    If the profile doesn't exist -- ``AUTH_PROFILE_MODULE`` isn't set or a
    profile instance hasn't been created for the ``User`` -- nothing happens.

    """
    try:
        profile = instance.get_profile()
    except ObjectDoesNotExist:
        return None

    # Don't overwrite existing fields on User model.
    blacklist = [f.name for f in instance._meta.fields] + ['id', 'user']

    for field in [f for f in profile._meta.fields if f.name not in blacklist]:
        value = getattr(profile, field.name)
        setattr(instance, field.name, value)

        if field.choices:
            setattr(
                sender,
                'get_%s_display' % field.name,
                curry(sender._get_FIELD_display, field=field)
            )

        if isinstance(field, models.DateField) and not field.null:
            setattr(
                sender,
                'get_next_by_%s' % field.name,
                curry(
                    _get_next_or_previous_by_profile_FIELD,
                    field=field,
                    is_next=True
                )
            )
            setattr(
                sender,
                'get_previous_by_%s' % field.name,
                curry(
                    _get_next_or_previous_by_profile_FIELD,
                    field=field,
                    is_next=False
                )
            )

        if isinstance(field, models.FileField):
            setattr(
                sender,
                'get_%s_filename' % field.name,
                curry(sender._get_FIELD_filename, field=field)
            )
            setattr(
                sender,
                'get_%s_url' % field.name,
                curry(sender._get_FIELD_url, field=field)
            )
            setattr(
                sender,
                'get_%s_size' % field.name,
                curry(sender._get_FIELD_size, field=field)
            )

        if isinstance(field, models.ImageField):
            setattr(
                sender,
                'get_%s_height' % field.name,
                curry(sender._get_FIELD_height, field=field)
            )
            setattr(
                sender,
                'get_%s_width' % field.name,
                curry(sender._get_FIELD_width, field=field)
            )


dispatcher.connect(inject_profile, sender=User, signal=post_init)

More like this

  1. filter dates to user profile's timezone by Scanner 7 years ago
  2. User post_save signal to auto create 'admin' profile by marinho 6 years, 4 months ago
  3. Export Related as JSON Admin Action by johnboxall 4 years, 9 months ago
  4. Adding buttons to submit line in a Admin page by marinho 5 years, 11 months ago
  5. CodeLookupField by girasquid 4 years, 10 months ago

Comments

daevaorn (on March 19, 2008):

And what is the sense?

#

jpwatts (on March 20, 2008):

It's not the only benefit, but a big one is not having to explain to your designer why they can do {{ user.first_name }} but not {{ user.gender }}.

{{ user.get_profile.gender }} exposes implementation details of the authentication and profile systems that a designer shouldn't have to know or care about.

#

daevaorn (on March 20, 2008):

The better way is to pass profile instance explicitly into context. Profile may have proxy properties to underlying user attributes: username,email,first_name,second_name. And there is no need in any magic. It is my opinion.

#

jpwatts (on March 20, 2008):

Passing the profile instance into context doesn't fix the problem that the separation between user and profile objects is an implementation detail.

Why should anyone but me care that I used a generic auth system and then augmented it with a site-specific profile?

If you'd prefer to take the opposite approach and add the user attributes to the profile instance, that's fine -- but I don't see how that's fundamentally any different from what I do here.

#

(Forgotten your password?)