# Django-CRUM

Django-CRUM (Current Request User Middleware) captures the current request and user in thread local storage.

It enables apps to check permissions, capture audit trails or otherwise access the current request and user without requiring the request object to be passed directly. It also offers a context manager to allow for temporarily impersonating another user.

It provides a signal to extend the built-in function for getting the current user, which could be helpful when using custom authentication methods or user models.

It is tested against:

  - Django 1.11 (Python 3.5 and 3.6)

  - Django 2.0 (Python 3.5, 3.6 and 3.7)

  - Django 2.1 (Python 3.5, 3.6 and 3.7)

  - Django 2.2 (Python 3.5, 3.6, 3.7, 3.8 and 3.9)

  - Django 3.0 (Python 3.6, 3.7, 3.8 and 3.9)

  - Django 3.1 (Python 3.6, 3.7, 3.8 and 3.9)

  - Django master/3.2 (Python 3.6, 3.7, 3.8 and 3.9)

## Installation

Install the application from PYPI:

    pip install django-crum

Add `CurrentRequestUserMiddleware` to your `MIDDLEWARE` setting:

    MIDDLEWARE += ('crum.CurrentRequestUserMiddleware',)

That’s it!

## Usage

The crum package exports three functions as its public API.

### get_current_request()

`get_current_request` returns the current request instance, or `None` if called outside the scope of a request.

For example, the `Comment` model below overrides its `save` method to track the IP address of each commenter:

    from django.db import models
    from crum import get_current_request

    class Comment(models.Model):
        created = models.DateTimeField(auto_now_add=True)
        comment = models.TextField()
        remote_addr = models.CharField(blank=True, default='')

        def save(self, *args, **kwargs):
            request = get_current_request()
            if request and not self.remote_addr:
                self.remote_addr = request.META['REMOTE_ADDR']
            super(Comment, self).save(*args, **kwargs)

### get_current_user()

`get_current_user` returns the user associated with the current request, or `None` if no user is available.

If using the built-in `User` model from `django.contrib.auth`, the returned value may be the special `AnonymousUser`, which won’t have a primary key.

For example, the `Thing` model below records the user who created it as well as the last user who modified it:

    from django.db import models
    from crum import get_current_user

    class Thing(models.Model):
        created = models.DateTimeField(auto_now_add=True)
        created_by = models.ForeignKey('auth.User', blank=True, null=True,
                                       default=None)
        modified = models.DateTimeField(auto_now=True)
        modified_by = models.ForeignKey('auth.User', blank=True, null=True,
                                        default=None)

        def save(self, *args, **kwargs):
            user = get_current_user()
            if user and not user.pk:
                user = None
            if not self.pk:
                self.created_by = user
            self.modified_by = user
            super(Thing, self).save(*args, **kwargs)

### impersonate(user=None)

`impersonate` is a context manager used to temporarily change the current user as returned by `get_current_user`. It is typically used to perform an action on behalf of a user or disable the default behavior of `get_current_user`.

For example, a background task may need to create or update `Thing` objects when there is no active request or user (such as from a management command):

    from crum import impersonate

    def create_thing_for_user(user):
        with impersonate(user):
            # This Thing will indicated it was created by the given user.
            user_thing = Thing.objects.create()
        # But this Thing won't have a created_by user.
        other_thing = Thing.objects.create()

When running from within a view, `impersonate` may be used to prevent certain actions from being attributed to the requesting user:

    from django.template.response import TemplateResponse
    from crum import impersonate

    def get_my_things(request):
        # Whenever this view is accessed, trigger some cleanup of Things.
        with impersonate(None):
            Thing.objects.cleanup()
        my_things = Thing.objects.filter(created_by=request.user)
        return TemplateResponse(request, 'my_things.html',
                                {'things': my_things})

## Signals

(New in 0.6.0) The crum package provides a signal to extend the capabilities of the get_current_user() function.

### current_user_getter

The `current_user_getter` signal is dispatched for each call to `get_current_user()`. Receivers for this signal should return a tuple of `(user, priority)`. Receivers should return `None` for the user when there is no current user set, or `False` when they can not determine the current user.

The priority value which will be used to determine which response contains the current user. The response with the highest priority will be used as long as the user returned is not `False`, otherwise lower-priority responses will be used in order of next-highest priority. Built-in receivers for this signal use priorities of -10 (current request) and +10 (thread locals); any custom receivers should usually use -10 < priority < 10.

The following example demonstrates how a custom receiver could be implemented to determine the current user from an auth token passed via an HTTP header:

    from django.dispatch import receiver
    from crum import get_current_request
    from crum.signals import current_user_getter

    @receiver(current_user_getter)
    def (sender, **kwargs):
        request = get_current_request()
        if request:
            token = request.META.get('HTTP_AUTH_TOKEN', None)
            try:
                auth_token = AuthToken.objects.get(token=token)
                return (auth_token.user, 0)
            except AuthToken.DoesNotExist:
                return (None, 0)
        return (False, 0)