from django.conf import settings from django.contrib.auth.models import User, SiteProfileNotAvailable from django.core.exceptions import ImproperlyConfigured from django.db.backends.signals import connection_created from django.db.models import DateField, DateTimeField, get_model from django.db.models.signals import post_save from django.utils.functional import curry __all__ = ['inject_user_profile'] def inject_user_profile(include=None, exclude=None): '''Allow users to access transparently their profile information. The ``User`` model is monkeypatched so that the fields of the profile model (specified in ``settings.AUTH_PROFILE_MODULE``) can be accessed (both get and set) transparently from a user instance. The associated profile is loaded lazily the first time a field is accessed; therefore there is no overhead if the profile information is never accessed. Once the profile has been fetched, saving the user instance also saves the profile. By default, all profile fields are added as ``User`` properties, with the exception of ``user`` and those fields having the same name with an existing User attribute. The optional ``include`` or ``exclude`` parameter can be used to limit further the exposed profile fields. For consistency with regular models, ``User`` is also patched with the ``get_{FIELD}_display`` method for every ``FIELD`` that has ``choices`` and with the ``get_next_by_{FIELD}``/``get_previous_by_{FIELD}`` methods for every ``FIELD`` that is ``DateField`` or ``DateTimeField`` and has ``null=False``. The monkeypatching is delayed until the first database connection is initiated. Therefore it is not necessary for the ``settings.AUTH_PROFILE_MODULE`` model to be known when this function is called; it can be called from almost anywhere, e.g. in the project's ``__init__.py``. :param include: If not ``None``, an iterable of profile field names to add as ``User`` properties (``exclude`` must be ``None``). :param exclude: If not ``None``, an iterable of profile field names to not add as ``User`` properties (``include`` must be ``None``). ''' def monkeypatch_user(sender, **kwargs): profile_model = get_profile_model() exclude_names = set(field.name for field in User._meta.fields) exclude_names.add('user') if include is not None: if exclude is not None: raise ValueError('Cannot pass both "include" and "exclude"') include_names = set(include) - exclude_names iterfields = (field for field in profile_model._meta.fields if field.name in include_names) else: if exclude: exclude_names.update(exclude) iterfields = (field for field in profile_model._meta.fields if field.name not in exclude_names) for field in iterfields: name = field.name setattr_if_unset(User, name, _make_profile_property(name)) if field.choices: setattr_if_unset(User, 'get_%s_display' % name, curry(User._get_FIELD_display, field=field)) if isinstance(field, (DateField, DateTimeField)) and not field.null: setattr_if_unset(User, 'get_next_by_%s' % name, curry(_get_next_or_previous_by_profile_FIELD, field=field, is_next=True)) setattr_if_unset(User, 'get_previous_by_%s' % name, curry(_get_next_or_previous_by_profile_FIELD, field=field, is_next=False)) post_save.connect(_save_profile_listener, sender=User) connection_created.disconnect(monkeypatch_user) connection_created.connect(monkeypatch_user, weak=False) def get_profile_model(): if not getattr(settings, 'AUTH_PROFILE_MODULE', False): raise SiteProfileNotAvailable('You need to set AUTH_PROFILE_MODULE in ' 'your project settings') try: app_label, model_name = settings.AUTH_PROFILE_MODULE.split('.') except ValueError: raise SiteProfileNotAvailable('app_label and model_name should' ' be separated by a dot in the AUTH_PROFILE_MODULE setting') try: model = get_model(app_label, model_name) if model is None: raise SiteProfileNotAvailable('Unable to load the profile model, ' 'check AUTH_PROFILE_MODULE in your project settings') return model except (ImportError, ImproperlyConfigured): raise SiteProfileNotAvailable def setattr_if_unset(obj, name, value): if not hasattr(obj, name): setattr(obj, name, value) def _make_profile_property(name): return property(lambda self: getattr(self.get_profile(), name), lambda self, value: setattr(self.get_profile(), name, value)) def _get_next_or_previous_by_profile_FIELD(self, field, is_next): return self.get_profile()._get_next_or_previous_by_FIELD(field, is_next).user def _save_profile_listener(sender, instance, created, **kwargs): if hasattr(instance, '_profile_cache'): instance._profile_cache.save()