├── test_project ├── __init__.py ├── test_app │ ├── __init__.py │ ├── models.py │ ├── urls.py │ ├── signals.py │ ├── views.py │ └── tests.py ├── urls.py ├── templates │ └── admin │ │ └── base_site.html └── settings.py ├── setup.py ├── MANIFEST.in ├── .gitignore ├── requirements ├── tox.ini ├── dev.in ├── dev38.txt ├── dev39.txt ├── dev35.txt ├── dev37.txt └── dev36.txt ├── .coveragerc ├── .github └── workflows │ └── test.yml ├── manage.py ├── tox.ini ├── LICENSE ├── crum ├── signals.py └── __init__.py ├── README.rst ├── Makefile ├── setup.cfg └── docs ├── index.rst ├── Makefile ├── make.bat └── conf.py /test_project/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test_project/test_app/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test_project/test_app/models.py: -------------------------------------------------------------------------------- 1 | from . import signals # noqa 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Setuptools 4 | from setuptools import setup 5 | 6 | 7 | setup() 8 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include crum *.py 2 | include LICENSE 3 | include README.rst 4 | recursive-exclude test_project * 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | build 3 | dist 4 | reports 5 | *.egg-info 6 | *.pyc 7 | *.pyo 8 | .coverage* 9 | .pytest_cache 10 | .tox 11 | *.sqlite3 12 | -------------------------------------------------------------------------------- /requirements/tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py3{5,6,7,8,9} 3 | skipsdist = true 4 | 5 | [testenv] 6 | commands = 7 | make -C .. update-requirements requirements 8 | whitelist_externals = make 9 | basepython = 10 | py35: python3.5 11 | py36: python3.6 12 | py37: python3.7 13 | py38: python3.8 14 | py39: python3.9 15 | -------------------------------------------------------------------------------- /test_project/test_app/urls.py: -------------------------------------------------------------------------------- 1 | # Django 2 | try: 3 | from django.urls import re_path 4 | except ImportError: 5 | from django.conf.urls import url as re_path 6 | 7 | # Test App 8 | from test_project.test_app.views import index, api_index 9 | 10 | 11 | app_name = 'test_app' 12 | 13 | urlpatterns = [ 14 | re_path(r'^$', index, name='index'), 15 | re_path(r'^api/$', api_index, name='api_index'), 16 | ] 17 | -------------------------------------------------------------------------------- /requirements/dev.in: -------------------------------------------------------------------------------- 1 | # Development/test environment uses Python 3.5 - 3.8 (separate requirements files generated for each). 2 | 3 | bumpversion 4 | django 5 | django-debug-toolbar 6 | django-extensions 7 | djangorestframework 8 | flake8 9 | ipython 10 | pycodestyle 11 | pip-tools 12 | pytest 13 | pytest-cov 14 | pytest-django 15 | pytest-flake8 16 | pytest-runner 17 | setuptools-twine 18 | sphinx 19 | tox 20 | tox-gh-actions 21 | twine 22 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | omit = 4 | parallel = True 5 | data_file = .coverage${TOX_ENV_NAME} 6 | 7 | [report] 8 | # Regexes for lines to exclude from consideration 9 | exclude_lines = 10 | # Have to re-enable the standard pragma 11 | pragma: no cover 12 | 13 | # Don't complain about missing debug-only code: 14 | def __repr__ 15 | if self\.debug 16 | 17 | # Don't complain if tests don't hit defensive assertion code: 18 | raise AssertionError 19 | raise NotImplementedError 20 | 21 | # Don't complain if non-runnable code isn't run: 22 | if 0: 23 | if __name__ == .__main__.: 24 | 25 | ignore_errors = True 26 | 27 | -------------------------------------------------------------------------------- /test_project/urls.py: -------------------------------------------------------------------------------- 1 | # Django 2 | from django.conf import settings 3 | try: 4 | from django.urls import include, re_path 5 | except ImportError: 6 | from django.conf.urls import include, url as re_path 7 | 8 | include_kwargs = dict(namespace='test_app') 9 | 10 | urlpatterns = [ 11 | re_path(r'^test_app/', include('test_project.test_app.urls', **include_kwargs)), 12 | ] 13 | 14 | if 'django.contrib.admin' in settings.INSTALLED_APPS: 15 | from django.contrib import admin 16 | admin.autodiscover() 17 | urlpatterns += [ 18 | re_path(r'', admin.site.urls), 19 | ] 20 | 21 | if 'django.contrib.staticfiles' in settings.INSTALLED_APPS and settings.DEBUG: 22 | from django.contrib.staticfiles.urls import staticfiles_urlpatterns 23 | urlpatterns += staticfiles_urlpatterns() 24 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: test 4 | 5 | on: 6 | push: 7 | pull_request: 8 | schedule: 9 | - cron: '0 9 9 * *' 10 | 11 | defaults: 12 | run: 13 | shell: bash 14 | 15 | jobs: 16 | test-tox: 17 | runs-on: ubuntu-latest 18 | strategy: 19 | matrix: 20 | python-version: [3.5, 3.6, 3.7, 3.8, 3.9] 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v2 24 | - name: Setup Python ${{ matrix.python-version }} 25 | uses: actions/setup-python@v2 26 | with: 27 | python-version: ${{ matrix.python-version }} 28 | - name: Install requirements 29 | run: | 30 | python -m pip install -U pip 31 | python -m pip install -U setuptools 32 | make requirements 33 | - name: Test with Tox 34 | run: tox 35 | -------------------------------------------------------------------------------- /test_project/templates/admin/base_site.html: -------------------------------------------------------------------------------- 1 | {# Admin site customizations. #} 2 | {% extends "admin/base.html" %} 3 | {% load i18n %} 4 | 5 | {% block title %}{{ title }} | {% trans 'Django CRUM Admin' %}{% endblock %} 6 | 7 | {% block extrastyle %} 8 | {{ block.super }} 9 | {% endblock %} 10 | 11 | {% block extrahead %} 12 | {{ block.super }} 13 | {% endblock %} 14 | 15 | {% block blockbots %} 16 | {{ block.super }} 17 | {% endblock %} 18 | 19 | {% block branding %} 20 |

{% trans 'Django CRUM Admin' %}

21 | {% endblock %} 22 | 23 | {% block userlinks %} 24 | {{ block.super }} 25 | {% endblock %} 26 | 27 | {% block nav-global %} 28 | {% if user.is_active and user.is_staff and not is_popup %} 29 | {# Placeholder for version/hostname info. #} 30 | {% endif %} 31 | {{ block.super }} 32 | {% endblock %} 33 | 34 | {% block content_title %} 35 | {{ block.super }} 36 | {% endblock %} 37 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Python 4 | import os 5 | import sys 6 | 7 | if __name__ == '__main__': 8 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'test_project.settings') 9 | import django 10 | django.setup() 11 | 12 | # Use the runserver addr/port defined in settings. 13 | from django.conf import settings 14 | default_addr = getattr(settings, 'RUNSERVER_DEFAULT_ADDR', '127.0.0.1') 15 | default_port = getattr(settings, 'RUNSERVER_DEFAULT_PORT', 8000) 16 | from django.core.management.commands import runserver as core_runserver 17 | original_handle = core_runserver.Command.handle 18 | 19 | def handle(self, *args, **options): 20 | if not options.get('addrport'): 21 | options['addrport'] = '%s:%d' % (default_addr, int(default_port)) 22 | elif options.get('addrport').isdigit(): 23 | options['addrport'] = '%s:%d' % (default_addr, int(options['addrport'])) 24 | return original_handle(self, *args, **options) 25 | 26 | core_runserver.Command.handle = handle 27 | 28 | from django.core.management import execute_from_command_line 29 | execute_from_command_line(sys.argv) 30 | -------------------------------------------------------------------------------- /test_project/test_app/signals.py: -------------------------------------------------------------------------------- 1 | # Django 2 | from django.dispatch import receiver 3 | 4 | # Django-CRUM 5 | from crum import get_current_request 6 | from crum.signals import current_user_getter, current_user_setter 7 | 8 | 9 | @receiver(current_user_getter) 10 | def _get_current_user_always_raises_exception(sender, **kwargs): 11 | raise RuntimeError('this receiver always raises an exception') 12 | 13 | 14 | @receiver(current_user_getter) 15 | def _get_current_user_always_returns_invalid(sender, **kwargs): 16 | return [] 17 | 18 | 19 | @receiver(current_user_getter) 20 | def _get_current_user_always_returns_false(sender, **kwargs): 21 | return False 22 | 23 | 24 | @receiver(current_user_getter) 25 | def _get_current_user_always_returns_list_with_false(sender, **kwargs): 26 | return [False] 27 | 28 | 29 | @receiver(current_user_setter) 30 | def _set_current_user_always_raises_exception(sender, **kwargs): 31 | raise RuntimeError('this receiver always raises an exception') 32 | 33 | 34 | @receiver(current_user_getter) 35 | def _get_current_user_from_drf_request(sender, **kwargs): 36 | request = get_current_request() 37 | drf_request = getattr(request, 'drf_request', None) 38 | return (getattr(drf_request, 'user', False), 0) 39 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | py35-dj{111,20,21,22}, 4 | py36-dj{20,21,22,30,31,32}, 5 | py37-dj{20,21,22,30,31,32}, 6 | py38-dj{22,30,31,32,main} 7 | py39-dj{22,30,31,32,main} 8 | 9 | [testenv] 10 | commands = 11 | coverage erase 12 | py.test {posargs} 13 | basepython = 14 | py35: python3.5 15 | py36: python3.6 16 | py37: python3.7 17 | py38: python3.8 18 | py39: python3.9 19 | deps = 20 | dj111: Django~=1.11.0 21 | dj111: djangorestframework~=3.11.0 22 | dj20: Django~=2.0.0 23 | dj20: djangorestframework 24 | dj21: Django~=2.1.0 25 | dj21: djangorestframework 26 | dj22: Django~=2.2.0 27 | dj22: djangorestframework 28 | dj30: Django~=3.0.0 29 | dj30: djangorestframework 30 | dj31: Django~=3.1.0 31 | dj31: djangorestframework 32 | dj32: Django>=3.2.0a0,<3.3 33 | dj32: djangorestframework 34 | djmain: https://github.com/django/django/zipball/main#egg=Django 35 | djmain: https://github.com/encode/django-rest-framework/zipball/master#egg=djangorestframework 36 | pytest 37 | pytest-cov 38 | pytest-django 39 | pytest-flake8 40 | pytest-runner 41 | setenv = 42 | DJANGO_SETTINGS_MODULE = test_project.settings 43 | PYTHONDONTWRITEBYTECODE = 1 44 | install_command = pip install --pre {opts} {packages} 45 | 46 | [testenv:py3{8,9}-djmain] 47 | ignore_outcome = true 48 | 49 | [gh-actions] 50 | python = 51 | 3.5: py35 52 | 3.6: py36 53 | 3.7: py37 54 | 3.8: py38 55 | 3.9: py39 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Nine More Minutes, Inc. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of Nine More Minutes, Inc. nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY EXPRESS OR 19 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 20 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 21 | EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 25 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 26 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 27 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /crum/signals.py: -------------------------------------------------------------------------------- 1 | # Django 2 | from django.dispatch import Signal, receiver 3 | 4 | __all__ = ['current_user_getter'] 5 | 6 | 7 | # Signal used when getting current user. Receivers should return a tuple of 8 | # (user, priority). 9 | current_user_getter = Signal() 10 | 11 | 12 | # Signal used when setting current user. Takes one keyword argument: 'user'. 13 | # Receivers should store the current user as needed. Return values are ignored. 14 | current_user_setter = Signal() 15 | 16 | 17 | @receiver(current_user_getter) 18 | def _get_current_user_from_request(sender, **kwargs): 19 | """ 20 | Signal handler to retrieve current user from request. 21 | """ 22 | from crum import get_current_request 23 | return (getattr(get_current_request(), 'user', False), -10) 24 | 25 | 26 | @receiver(current_user_getter) 27 | def _get_current_user_from_thread_locals(sender, **kwargs): 28 | """ 29 | Signal handler to retrieve current user from thread locals. 30 | """ 31 | from crum import _thread_locals 32 | return (getattr(_thread_locals, 'user', False), 10) 33 | 34 | 35 | @receiver(current_user_setter) 36 | def _set_current_user_on_request(sender, **kwargs): 37 | """ 38 | Signal handler to store current user to request. 39 | """ 40 | from crum import get_current_request 41 | request = get_current_request() 42 | if request: 43 | request.user = kwargs['user'] 44 | 45 | 46 | @receiver(current_user_setter) 47 | def _set_current_user_on_thread_locals(sender, **kwargs): 48 | """ 49 | Signal handler to store current user on thread locals. 50 | """ 51 | from crum import _thread_locals 52 | _thread_locals.user = kwargs['user'] 53 | -------------------------------------------------------------------------------- /test_project/test_app/views.py: -------------------------------------------------------------------------------- 1 | # Python 2 | from __future__ import with_statement 3 | from __future__ import unicode_literals 4 | 5 | # Django 6 | from django.http import HttpResponse 7 | from django.views.generic.base import View 8 | try: 9 | from django.utils.six import text_type 10 | except ImportError: 11 | text_type = str 12 | 13 | # Django-REST-Framework 14 | from rest_framework.response import Response 15 | from rest_framework.views import APIView 16 | 17 | # Django-CRUM 18 | from crum import get_current_user, impersonate 19 | 20 | 21 | class IndexView(View): 22 | 23 | def get(self, request): 24 | if request.GET.get('raise', ''): 25 | raise RuntimeError() 26 | if request.GET.get('impersonate', ''): 27 | with impersonate(None): 28 | current_user = text_type(get_current_user()) 29 | else: 30 | current_user = text_type(get_current_user()) 31 | return HttpResponse(current_user, content_type='text/plain') 32 | 33 | 34 | index = IndexView.as_view() 35 | 36 | 37 | class ApiIndexView(APIView): 38 | 39 | def initialize_request(self, request, *args, **kwargs): 40 | """Store the REST Framework request on the Django request.""" 41 | req = super(ApiIndexView, self).initialize_request(request, *args, 42 | **kwargs) 43 | request.drf_request = req 44 | return req 45 | 46 | def get(self, request, format=None): 47 | if request.query_params.get('raise', ''): 48 | raise RuntimeError() 49 | if request.query_params.get('impersonate', ''): 50 | with impersonate(None): 51 | current_user = text_type(get_current_user()) 52 | else: 53 | current_user = text_type(get_current_user()) 54 | return Response(current_user) 55 | 56 | 57 | api_index = ApiIndexView.as_view() 58 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | |Build Status| |PyPI Version| |PyPI License| |Python Versions| |Django Versions| |Read the Docs| 2 | 3 | Django-CRUM 4 | =========== 5 | 6 | Django-CRUM (Current Request User Middleware) captures the current request and 7 | user in thread local storage. 8 | 9 | It enables apps to check permissions, capture audit trails or otherwise access 10 | the current request and user without requiring the request object to be passed 11 | directly. It also offers a context manager to allow for temporarily 12 | impersonating another user. 13 | 14 | It provides a signal to extend the built-in function for getting the current 15 | user, which could be helpful when using custom authentication methods or user 16 | models. 17 | 18 | Documentation can be found at `RTFD `_. 19 | 20 | It is tested against: 21 | * Django 1.11 (Python 3.5 and 3.6) 22 | * Django 2.0 (Python 3.5, 3.6 and 3.7) 23 | * Django 2.1 (Python 3.5, 3.6 and 3.7) 24 | * Django 2.2 (Python 3.5, 3.6, 3.7, 3.8 and 3.9) 25 | * Django 3.0 (Python 3.6, 3.7, 3.8 and 3.9) 26 | * Django 3.1 (Python 3.6, 3.7, 3.8 and 3.9) 27 | * Django 3.2 pre-release (Python 3.6, 3.7, 3.8 and 3.9) 28 | * Django main/4.0 (Python 3.8 and 3.9) 29 | 30 | .. |Build Status| image:: https://img.shields.io/github/workflow/status/ninemoreminutes/django-crum/test 31 | :target: https://github.com/ninemoreminutes/django-crum/actions?query=workflow%3Atest 32 | .. |PyPI Version| image:: https://img.shields.io/pypi/v/django-crum.svg 33 | :target: https://pypi.python.org/pypi/django-crum/ 34 | .. |PyPI License| image:: https://img.shields.io/pypi/l/django-crum.svg 35 | :target: https://pypi.python.org/pypi/django-crum/ 36 | .. |Python Versions| image:: https://img.shields.io/pypi/pyversions/django-crum.svg 37 | :target: https://pypi.python.org/pypi/django-crum/ 38 | .. |Django Versions| image:: https://img.shields.io/pypi/djversions/django-crum.svg 39 | :target: https://pypi.org/project/django-crum/ 40 | .. |Read the Docs| image:: https://img.shields.io/readthedocs/django-crum.svg 41 | :target: http://django-crum.readthedocs.io/ 42 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PYTHON_MAJOR_MINOR := $(shell python -c "import sys; print('{0}{1}'.format(*sys.version_info))") 2 | REQUIREMENTS_TXT = requirements/dev$(PYTHON_MAJOR_MINOR).txt 3 | 4 | .PHONY: core-requirements 5 | core-requirements: 6 | pip install pip setuptools pip-tools 7 | 8 | .PHONY: update-requirements 9 | update-requirements: core-requirements 10 | pip install -U pip setuptools pip-tools 11 | pip-compile -U requirements/dev.in -o $(REQUIREMENTS_TXT) 12 | 13 | .PHONY: requirements 14 | requirements: core-requirements 15 | pip-sync $(REQUIREMENTS_TXT) 16 | 17 | .PHONY: clean-pyc 18 | clean-pyc: requirements 19 | find . -iname "*.pyc" -delete 20 | find . -iname __pycache__ | xargs rm -rf 21 | 22 | .PHONY: tox-update-requirements 23 | tox-update-requirements: clean-pyc 24 | tox -c requirements/tox.ini 25 | 26 | .PHONY: develop 27 | develop: clean-pyc 28 | python setup.py develop 29 | 30 | .PHONY: check 31 | check: develop 32 | python manage.py check 33 | 34 | .PHONY: migrate 35 | migrate: check 36 | python manage.py migrate --noinput 37 | 38 | .PHONY: runserver 39 | runserver: migrate 40 | python manage.py runserver 41 | 42 | reports: 43 | mkdir -p $@ 44 | 45 | .PHONY: pycodestyle 46 | pycodestyle: reports requirements 47 | set -o pipefail && $@ | tee reports/$@.report 48 | 49 | .PHONY: flake8 50 | flake8: reports requirements 51 | set -o pipefail && $@ | tee reports/$@.report 52 | 53 | .PHONY: check8 54 | check8: pycodestyle flake8 55 | 56 | .PHONY: test 57 | test: clean-pyc 58 | python setup.py test 59 | 60 | .PHONY: clean-tox 61 | clean-tox: 62 | rm -rf .tox 63 | rm -rf .coveragepy* 64 | 65 | .PHONY: tox 66 | tox: clean-pyc 67 | tox 68 | 69 | .PHONY: tox-parallel 70 | tox-parallel: clean-pyc 71 | tox -p auto 72 | 73 | .PHONY: clean-all 74 | clean-all: clean-pyc clean-tox 75 | rm -rf *.egg-info .eggs .cache .coverage build reports 76 | 77 | .PHONY: bump-major 78 | bump-major: requirements 79 | bumpversion major 80 | 81 | .PHONY: bump-minor 82 | bump-minor: requirements 83 | bumpversion minor 84 | 85 | .PHONY: bump-patch 86 | bump-patch: requirements 87 | bumpversion patch 88 | 89 | .PHONY: docs 90 | docs: requirements 91 | python setup.py build_sphinx 92 | 93 | .PHONY: ship-it 94 | ship-it: requirements clean-pyc 95 | python setup.py ship_it 96 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | commit = True 3 | current_version = 0.7.9 4 | tag = True 5 | tag_name = {new_version} 6 | 7 | [metadata] 8 | name = django-crum 9 | version = attr: crum.__version__ 10 | author = Nine More Minutes, Inc. 11 | author_email = support@ninemoreminutes.com 12 | description = Django middleware to capture current request and user. 13 | long_description = file: README.rst 14 | long_description_content_type = text/x-rst 15 | keywords = django, request, user, middleware, thread, local 16 | license = BSD 17 | url = https://github.com/ninemoreminutes/django-crum/ 18 | project_urls = 19 | Documentation = https://django-crum.rtfd.org/ 20 | Source = https://github.com/ninemoreminutes/django-crum/ 21 | Tracker = https://github.com/ninemoreminutes/django-crum/issues 22 | classifiers = 23 | Development Status :: 4 - Beta 24 | Environment :: Web Environment 25 | Framework :: Django 26 | Framework :: Django :: 1.11 27 | Framework :: Django :: 2.0 28 | Framework :: Django :: 2.1 29 | Framework :: Django :: 2.2 30 | Framework :: Django :: 3.0 31 | Framework :: Django :: 3.1 32 | Framework :: Django :: 3.2 33 | Intended Audience :: Developers 34 | License :: OSI Approved :: BSD License 35 | Operating System :: OS Independent 36 | Programming Language :: Python 37 | Programming Language :: Python :: 3 38 | Programming Language :: Python :: 3.4 39 | Programming Language :: Python :: 3.5 40 | Programming Language :: Python :: 3.6 41 | Programming Language :: Python :: 3.7 42 | Programming Language :: Python :: 3.8 43 | Programming Language :: Python :: 3.9 44 | Topic :: Internet :: WWW/HTTP 45 | Topic :: Software Development :: Libraries 46 | Topic :: Software Development :: Libraries :: Python Modules 47 | 48 | [options] 49 | zip_safe = False 50 | packages = crum 51 | include_package_data = True 52 | setup_requires = 53 | pytest-runner 54 | setuptools-twine 55 | tests_require = 56 | django>=1.8 57 | djangorestframework 58 | pytest 59 | pytest-cov 60 | pytest-django 61 | pytest-flake8 62 | install_requires = 63 | django>=1.8 64 | 65 | [check] 66 | metadata = True 67 | restructuredtext = True 68 | strict = True 69 | 70 | [clean] 71 | all = True 72 | 73 | [egg_info] 74 | tag_build = .dev 75 | 76 | [build_sphinx] 77 | source_dir = docs 78 | build_dir = docs/_build 79 | all_files = True 80 | version = attr: crum.__version__ 81 | release = attr: crum.__version__ 82 | 83 | [upload_sphinx] 84 | upload_dir = docs/_build/html 85 | 86 | [upload_docs] 87 | upload_dir = docs/_build/html 88 | 89 | [bdist_wheel] 90 | universal = 1 91 | 92 | [aliases] 93 | dev_build = clean test egg_info sdist bdist_wheel twine_check build_sphinx 94 | release_build = clean test egg_info -b "" sdist bdist_wheel twine_check build_sphinx 95 | test = pytest 96 | ship_it = release_build twine_upload 97 | 98 | [bumpversion:file:crum/__init__.py] 99 | 100 | [bumpversion:file:docs/conf.py] 101 | 102 | [pycodestyle] 103 | ignore = E501 104 | exclude = .git,.tox,build,dist,docs 105 | 106 | [flake8] 107 | ignore = E501 108 | exclude = .git,.tox,build,dist,docs 109 | 110 | [tool:pytest] 111 | DJANGO_SETTINGS_MODULE = test_project.settings 112 | python_files = test*.py 113 | testpaths = crum test_project 114 | norecursedirs = .git .tox build dist docs 115 | flake8-ignore = E501 116 | addopts = --reuse-db --nomigrations --cache-clear --flake8 --cov crum --cov-append --cov-report term-missing 117 | -------------------------------------------------------------------------------- /crum/__init__.py: -------------------------------------------------------------------------------- 1 | # Python 2 | import contextlib 3 | import logging 4 | import threading 5 | 6 | _thread_locals = threading.local() 7 | 8 | _logger = logging.getLogger('crum') 9 | 10 | __version__ = '0.7.9' 11 | 12 | __all__ = ['get_current_request', 'get_current_user', 'impersonate'] 13 | 14 | 15 | @contextlib.contextmanager 16 | def impersonate(user=None): 17 | """ 18 | Temporarily impersonate the given user for audit trails. 19 | """ 20 | try: 21 | current_user = get_current_user(_return_false=True) 22 | set_current_user(user) 23 | yield user 24 | finally: 25 | set_current_user(current_user) 26 | 27 | 28 | def get_current_request(): 29 | """ 30 | Return the request associated with the current thread. 31 | """ 32 | return getattr(_thread_locals, 'request', None) 33 | 34 | 35 | def set_current_request(request=None): 36 | """ 37 | Update the request associated with the current thread. 38 | """ 39 | _thread_locals.request = request 40 | # Clear the current user if also clearing the request. 41 | if not request: 42 | set_current_user(False) 43 | 44 | 45 | def get_current_user(_return_false=False): 46 | """ 47 | Return the user associated with the current request thread. 48 | """ 49 | from crum.signals import current_user_getter 50 | top_priority = -9999 51 | top_user = False if _return_false else None 52 | results = current_user_getter.send_robust(get_current_user) 53 | for receiver, response in results: 54 | priority = 0 55 | if isinstance(response, Exception): 56 | _logger.exception('%r raised exception: %s', receiver, response) 57 | continue 58 | elif isinstance(response, (tuple, list)) and response: 59 | user = response[0] 60 | if len(response) > 1: 61 | priority = response[1] 62 | elif response or response in (None, False): 63 | user = response 64 | else: 65 | _logger.error('%r returned invalid response: %r', receiver, 66 | response) 67 | continue 68 | if user is not False: 69 | if priority > top_priority: 70 | top_priority = priority 71 | top_user = user 72 | return top_user 73 | 74 | 75 | def set_current_user(user=None): 76 | """ 77 | Update the user associated with the current request thread. 78 | """ 79 | from crum.signals import current_user_setter 80 | results = current_user_setter.send_robust(set_current_user, user=user) 81 | for receiver, response in results: 82 | if isinstance(response, Exception): 83 | _logger.exception('%r raised exception: %s', receiver, response) 84 | 85 | 86 | class CurrentRequestUserMiddleware(object): 87 | """ 88 | Middleware to capture the request and user from the current thread. 89 | """ 90 | 91 | def __init__(self, get_response=None): 92 | self.get_response = get_response 93 | 94 | def __call__(self, request): # pragma: no cover 95 | response = self.process_request(request) 96 | try: 97 | response = self.get_response(request) 98 | except Exception as e: 99 | self.process_exception(request, e) 100 | raise 101 | return self.process_response(request, response) 102 | 103 | def process_request(self, request): 104 | set_current_request(request) 105 | 106 | def process_response(self, request, response): 107 | set_current_request(None) 108 | return response 109 | 110 | def process_exception(self, request, exception): 111 | set_current_request(None) 112 | -------------------------------------------------------------------------------- /test_project/settings.py: -------------------------------------------------------------------------------- 1 | # Python 2 | import os 3 | 4 | 5 | # Absolute path to the directory containing this Django project. 6 | BASE_DIR = os.path.abspath(os.path.dirname(__file__)) 7 | 8 | DEBUG = True 9 | TEMPLATE_DEBUG = DEBUG 10 | 11 | DATABASES = { 12 | 'default': { 13 | 'ENGINE': 'django.db.backends.sqlite3', 14 | 'NAME': os.path.join(BASE_DIR, 'test_project.sqlite3'), 15 | } 16 | } 17 | 18 | TIME_ZONE = 'America/New_York' 19 | 20 | USE_TZ = True 21 | 22 | SITE_ID = 1 23 | 24 | SECRET_KEY = '347e28aa55547c3195e028fdead3448817e74fe2' 25 | 26 | STATICFILES_DIRS = () 27 | 28 | STATIC_URL = '/static/' 29 | 30 | STATIC_ROOT = os.path.join(BASE_DIR, 'public', 'static') 31 | 32 | MEDIA_URL = '/media/' 33 | 34 | MEDIA_ROOT = os.path.join(BASE_DIR, 'public', 'media') 35 | 36 | MIDDLEWARE = [ 37 | 'django.middleware.security.SecurityMiddleware', 38 | 'django.contrib.sessions.middleware.SessionMiddleware', 39 | 'django.middleware.common.CommonMiddleware', 40 | 'django.middleware.csrf.CsrfViewMiddleware', 41 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 42 | 'django.contrib.messages.middleware.MessageMiddleware', 43 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 44 | 'crum.CurrentRequestUserMiddleware', 45 | ] 46 | 47 | TEMPLATES = [ 48 | { 49 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 50 | 'DIRS': [ 51 | os.path.join(BASE_DIR, 'templates'), 52 | ], 53 | 'APP_DIRS': True, 54 | 'OPTIONS': {}, 55 | }, 56 | ] 57 | 58 | TEMPLATE_DIRS = ( 59 | os.path.join(BASE_DIR, 'templates'), 60 | ) 61 | 62 | ROOT_URLCONF = 'test_project.urls' 63 | 64 | INSTALLED_APPS = ( 65 | 'django.contrib.auth', 66 | 'django.contrib.contenttypes', 67 | 'django.contrib.sessions', 68 | 'django.contrib.sites', 69 | 'django.contrib.messages', 70 | 'django.contrib.staticfiles', 71 | 'django.contrib.admin', 72 | 'rest_framework', 73 | 'test_project.test_app', 74 | ) 75 | 76 | try: 77 | import django_extensions # noqa 78 | INSTALLED_APPS += ('django_extensions',) 79 | except ImportError: 80 | pass 81 | 82 | try: 83 | import debug_toolbar # noqa 84 | INSTALLED_APPS += ('debug_toolbar',) 85 | MIDDLEWARE += ('debug_toolbar.middleware.DebugToolbarMiddleware',) 86 | DEBUG_TOOLBAR_CONFIG = { 87 | 'INTERCEPT_REDIRECTS': False, 88 | } 89 | except ImportError: 90 | pass 91 | 92 | RUNSERVER_DEFAULT_ADDR = '127.0.0.1' 93 | RUNSERVER_DEFAULT_PORT = '8034' 94 | 95 | INTERNAL_IPS = ('127.0.0.1',) 96 | 97 | LOGGING = { 98 | 'version': 1, 99 | 'disable_existing_loggers': False, 100 | 'filters': { 101 | 'require_debug_false': { 102 | '()': 'django.utils.log.RequireDebugFalse', 103 | }, 104 | 'require_debug_true': { 105 | '()': 'django.utils.log.RequireDebugTrue', 106 | }, 107 | }, 108 | 'handlers': { 109 | 'console': { 110 | 'level': 'INFO', 111 | 'filters': ['require_debug_true'], 112 | 'class': 'logging.StreamHandler', 113 | }, 114 | 'null': { 115 | 'class': 'logging.NullHandler', 116 | }, 117 | 'mail_admins': { 118 | 'level': 'ERROR', 119 | 'filters': ['require_debug_false'], 120 | 'class': 'django.utils.log.AdminEmailHandler' 121 | } 122 | }, 123 | 'loggers': { 124 | 'django': { 125 | 'handlers': ['console'], 126 | }, 127 | 'django.request': { 128 | 'handlers': ['mail_admins'], 129 | 'level': 'ERROR', 130 | 'propagate': False, 131 | }, 132 | 'django.security': { 133 | 'handlers': ['mail_admins'], 134 | 'level': 'ERROR', 135 | 'propagate': False, 136 | }, 137 | 'py.warnings': { 138 | 'handlers': ['console'], 139 | }, 140 | 'crum': { 141 | 'handlers': ['null'], 142 | }, 143 | }, 144 | } 145 | -------------------------------------------------------------------------------- /requirements/dev38.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile 3 | # To update, run: 4 | # 5 | # pip-compile --output-file=requirements/dev38.txt requirements/dev.in 6 | # 7 | alabaster==0.7.12 8 | # via sphinx 9 | appdirs==1.4.4 10 | # via virtualenv 11 | appnope==0.1.2 12 | # via ipython 13 | asgiref==3.3.4 14 | # via django 15 | attrs==20.3.0 16 | # via pytest 17 | babel==2.9.0 18 | # via sphinx 19 | backcall==0.2.0 20 | # via ipython 21 | bleach==3.3.0 22 | # via readme-renderer 23 | bump2version==1.0.1 24 | # via bumpversion 25 | bumpversion==0.6.0 26 | # via -r requirements/dev.in 27 | certifi==2020.12.5 28 | # via requests 29 | chardet==4.0.0 30 | # via requests 31 | click==7.1.2 32 | # via pip-tools 33 | colorama==0.4.4 34 | # via twine 35 | coverage==5.5 36 | # via pytest-cov 37 | decorator==5.0.5 38 | # via ipython 39 | distlib==0.3.1 40 | # via virtualenv 41 | django-debug-toolbar==3.2 42 | # via -r requirements/dev.in 43 | django-extensions==3.1.2 44 | # via -r requirements/dev.in 45 | django==3.2 46 | # via 47 | # -r requirements/dev.in 48 | # django-debug-toolbar 49 | # django-extensions 50 | # djangorestframework 51 | djangorestframework==3.12.4 52 | # via -r requirements/dev.in 53 | docutils==0.17 54 | # via 55 | # readme-renderer 56 | # sphinx 57 | filelock==3.0.12 58 | # via 59 | # tox 60 | # virtualenv 61 | flake8==3.9.0 62 | # via 63 | # -r requirements/dev.in 64 | # pytest-flake8 65 | idna==2.10 66 | # via requests 67 | imagesize==1.2.0 68 | # via sphinx 69 | importlib-metadata==3.10.0 70 | # via 71 | # keyring 72 | # twine 73 | iniconfig==1.1.1 74 | # via pytest 75 | ipython-genutils==0.2.0 76 | # via traitlets 77 | ipython==7.22.0 78 | # via -r requirements/dev.in 79 | jedi==0.18.0 80 | # via ipython 81 | jinja2==2.11.3 82 | # via sphinx 83 | keyring==23.0.1 84 | # via twine 85 | markupsafe==1.1.1 86 | # via jinja2 87 | mccabe==0.6.1 88 | # via flake8 89 | packaging==20.9 90 | # via 91 | # bleach 92 | # pytest 93 | # sphinx 94 | # tox 95 | parso==0.8.2 96 | # via jedi 97 | pep517==0.10.0 98 | # via pip-tools 99 | pexpect==4.8.0 100 | # via ipython 101 | pickleshare==0.7.5 102 | # via ipython 103 | pip-tools==6.0.1 104 | # via -r requirements/dev.in 105 | pkginfo==1.7.0 106 | # via twine 107 | pluggy==0.13.1 108 | # via 109 | # pytest 110 | # tox 111 | prompt-toolkit==3.0.18 112 | # via ipython 113 | ptyprocess==0.7.0 114 | # via pexpect 115 | py==1.10.0 116 | # via 117 | # pytest 118 | # tox 119 | pycodestyle==2.7.0 120 | # via 121 | # -r requirements/dev.in 122 | # flake8 123 | pyflakes==2.3.1 124 | # via flake8 125 | pygments==2.8.1 126 | # via 127 | # ipython 128 | # readme-renderer 129 | # sphinx 130 | pyparsing==2.4.7 131 | # via packaging 132 | pytest-cov==2.11.1 133 | # via -r requirements/dev.in 134 | pytest-django==4.1.0 135 | # via -r requirements/dev.in 136 | pytest-flake8==1.0.7 137 | # via -r requirements/dev.in 138 | pytest-runner==5.3.0 139 | # via -r requirements/dev.in 140 | pytest==6.2.3 141 | # via 142 | # -r requirements/dev.in 143 | # pytest-cov 144 | # pytest-django 145 | # pytest-flake8 146 | pytz==2021.1 147 | # via 148 | # babel 149 | # django 150 | readme-renderer==29.0 151 | # via twine 152 | requests-toolbelt==0.9.1 153 | # via twine 154 | requests==2.25.1 155 | # via 156 | # requests-toolbelt 157 | # sphinx 158 | # twine 159 | rfc3986==1.4.0 160 | # via twine 161 | setuptools-twine==0.1.3 162 | # via -r requirements/dev.in 163 | six==1.15.0 164 | # via 165 | # bleach 166 | # readme-renderer 167 | # tox 168 | # virtualenv 169 | snowballstemmer==2.1.0 170 | # via sphinx 171 | sphinx==3.5.3 172 | # via -r requirements/dev.in 173 | sphinxcontrib-applehelp==1.0.2 174 | # via sphinx 175 | sphinxcontrib-devhelp==1.0.2 176 | # via sphinx 177 | sphinxcontrib-htmlhelp==1.0.3 178 | # via sphinx 179 | sphinxcontrib-jsmath==1.0.1 180 | # via sphinx 181 | sphinxcontrib-qthelp==1.0.3 182 | # via sphinx 183 | sphinxcontrib-serializinghtml==1.1.4 184 | # via sphinx 185 | sqlparse==0.4.1 186 | # via 187 | # django 188 | # django-debug-toolbar 189 | toml==0.10.2 190 | # via 191 | # pep517 192 | # pytest 193 | # tox 194 | tox-gh-actions==2.4.0 195 | # via -r requirements/dev.in 196 | tox==3.23.0 197 | # via 198 | # -r requirements/dev.in 199 | # tox-gh-actions 200 | tqdm==4.60.0 201 | # via twine 202 | traitlets==5.0.5 203 | # via ipython 204 | twine==3.4.1 205 | # via 206 | # -r requirements/dev.in 207 | # setuptools-twine 208 | urllib3==1.26.4 209 | # via requests 210 | virtualenv==20.4.3 211 | # via tox 212 | wcwidth==0.2.5 213 | # via prompt-toolkit 214 | webencodings==0.5.1 215 | # via bleach 216 | zipp==3.4.1 217 | # via importlib-metadata 218 | 219 | # The following packages are considered to be unsafe in a requirements file: 220 | # pip 221 | # setuptools 222 | -------------------------------------------------------------------------------- /requirements/dev39.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile 3 | # To update, run: 4 | # 5 | # pip-compile --output-file=requirements/dev39.txt requirements/dev.in 6 | # 7 | alabaster==0.7.12 8 | # via sphinx 9 | appdirs==1.4.4 10 | # via virtualenv 11 | appnope==0.1.2 12 | # via ipython 13 | asgiref==3.3.4 14 | # via django 15 | attrs==20.3.0 16 | # via pytest 17 | babel==2.9.0 18 | # via sphinx 19 | backcall==0.2.0 20 | # via ipython 21 | bleach==3.3.0 22 | # via readme-renderer 23 | bump2version==1.0.1 24 | # via bumpversion 25 | bumpversion==0.6.0 26 | # via -r requirements/dev.in 27 | certifi==2020.12.5 28 | # via requests 29 | chardet==4.0.0 30 | # via requests 31 | click==7.1.2 32 | # via pip-tools 33 | colorama==0.4.4 34 | # via twine 35 | coverage==5.5 36 | # via pytest-cov 37 | decorator==5.0.5 38 | # via ipython 39 | distlib==0.3.1 40 | # via virtualenv 41 | django-debug-toolbar==3.2 42 | # via -r requirements/dev.in 43 | django-extensions==3.1.2 44 | # via -r requirements/dev.in 45 | django==3.2 46 | # via 47 | # -r requirements/dev.in 48 | # django-debug-toolbar 49 | # django-extensions 50 | # djangorestframework 51 | djangorestframework==3.12.4 52 | # via -r requirements/dev.in 53 | docutils==0.17 54 | # via 55 | # readme-renderer 56 | # sphinx 57 | filelock==3.0.12 58 | # via 59 | # tox 60 | # virtualenv 61 | flake8==3.9.0 62 | # via 63 | # -r requirements/dev.in 64 | # pytest-flake8 65 | idna==2.10 66 | # via requests 67 | imagesize==1.2.0 68 | # via sphinx 69 | importlib-metadata==3.10.0 70 | # via 71 | # keyring 72 | # twine 73 | iniconfig==1.1.1 74 | # via pytest 75 | ipython-genutils==0.2.0 76 | # via traitlets 77 | ipython==7.22.0 78 | # via -r requirements/dev.in 79 | jedi==0.18.0 80 | # via ipython 81 | jinja2==2.11.3 82 | # via sphinx 83 | keyring==23.0.1 84 | # via twine 85 | markupsafe==1.1.1 86 | # via jinja2 87 | mccabe==0.6.1 88 | # via flake8 89 | packaging==20.9 90 | # via 91 | # bleach 92 | # pytest 93 | # sphinx 94 | # tox 95 | parso==0.8.2 96 | # via jedi 97 | pep517==0.10.0 98 | # via pip-tools 99 | pexpect==4.8.0 100 | # via ipython 101 | pickleshare==0.7.5 102 | # via ipython 103 | pip-tools==6.0.1 104 | # via -r requirements/dev.in 105 | pkginfo==1.7.0 106 | # via twine 107 | pluggy==0.13.1 108 | # via 109 | # pytest 110 | # tox 111 | prompt-toolkit==3.0.18 112 | # via ipython 113 | ptyprocess==0.7.0 114 | # via pexpect 115 | py==1.10.0 116 | # via 117 | # pytest 118 | # tox 119 | pycodestyle==2.7.0 120 | # via 121 | # -r requirements/dev.in 122 | # flake8 123 | pyflakes==2.3.1 124 | # via flake8 125 | pygments==2.8.1 126 | # via 127 | # ipython 128 | # readme-renderer 129 | # sphinx 130 | pyparsing==2.4.7 131 | # via packaging 132 | pytest-cov==2.11.1 133 | # via -r requirements/dev.in 134 | pytest-django==4.1.0 135 | # via -r requirements/dev.in 136 | pytest-flake8==1.0.7 137 | # via -r requirements/dev.in 138 | pytest-runner==5.3.0 139 | # via -r requirements/dev.in 140 | pytest==6.2.3 141 | # via 142 | # -r requirements/dev.in 143 | # pytest-cov 144 | # pytest-django 145 | # pytest-flake8 146 | pytz==2021.1 147 | # via 148 | # babel 149 | # django 150 | readme-renderer==29.0 151 | # via twine 152 | requests-toolbelt==0.9.1 153 | # via twine 154 | requests==2.25.1 155 | # via 156 | # requests-toolbelt 157 | # sphinx 158 | # twine 159 | rfc3986==1.4.0 160 | # via twine 161 | setuptools-twine==0.1.3 162 | # via -r requirements/dev.in 163 | six==1.15.0 164 | # via 165 | # bleach 166 | # readme-renderer 167 | # tox 168 | # virtualenv 169 | snowballstemmer==2.1.0 170 | # via sphinx 171 | sphinx==3.5.3 172 | # via -r requirements/dev.in 173 | sphinxcontrib-applehelp==1.0.2 174 | # via sphinx 175 | sphinxcontrib-devhelp==1.0.2 176 | # via sphinx 177 | sphinxcontrib-htmlhelp==1.0.3 178 | # via sphinx 179 | sphinxcontrib-jsmath==1.0.1 180 | # via sphinx 181 | sphinxcontrib-qthelp==1.0.3 182 | # via sphinx 183 | sphinxcontrib-serializinghtml==1.1.4 184 | # via sphinx 185 | sqlparse==0.4.1 186 | # via 187 | # django 188 | # django-debug-toolbar 189 | toml==0.10.2 190 | # via 191 | # pep517 192 | # pytest 193 | # tox 194 | tox-gh-actions==2.4.0 195 | # via -r requirements/dev.in 196 | tox==3.23.0 197 | # via 198 | # -r requirements/dev.in 199 | # tox-gh-actions 200 | tqdm==4.60.0 201 | # via twine 202 | traitlets==5.0.5 203 | # via ipython 204 | twine==3.4.1 205 | # via 206 | # -r requirements/dev.in 207 | # setuptools-twine 208 | urllib3==1.26.4 209 | # via requests 210 | virtualenv==20.4.3 211 | # via tox 212 | wcwidth==0.2.5 213 | # via prompt-toolkit 214 | webencodings==0.5.1 215 | # via bleach 216 | zipp==3.4.1 217 | # via importlib-metadata 218 | 219 | # The following packages are considered to be unsafe in a requirements file: 220 | # pip 221 | # setuptools 222 | -------------------------------------------------------------------------------- /requirements/dev35.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile 3 | # To update, run: 4 | # 5 | # pip-compile --output-file=requirements/dev35.txt requirements/dev.in 6 | # 7 | alabaster==0.7.12 8 | # via sphinx 9 | appdirs==1.4.4 10 | # via virtualenv 11 | appnope==0.1.2 12 | # via ipython 13 | attrs==20.3.0 14 | # via pytest 15 | babel==2.9.0 16 | # via sphinx 17 | backcall==0.2.0 18 | # via ipython 19 | bleach==3.3.0 20 | # via readme-renderer 21 | bump2version==1.0.1 22 | # via bumpversion 23 | bumpversion==0.6.0 24 | # via -r requirements/dev.in 25 | certifi==2020.12.5 26 | # via requests 27 | chardet==4.0.0 28 | # via requests 29 | click==7.1.2 30 | # via pip-tools 31 | coverage==5.5 32 | # via pytest-cov 33 | decorator==5.0.5 34 | # via 35 | # ipython 36 | # traitlets 37 | distlib==0.3.1 38 | # via virtualenv 39 | django-debug-toolbar==3.1.1 40 | # via -r requirements/dev.in 41 | django-extensions==3.1.0 42 | # via -r requirements/dev.in 43 | django==2.2.20 44 | # via 45 | # -r requirements/dev.in 46 | # django-debug-toolbar 47 | # djangorestframework 48 | djangorestframework==3.12.4 49 | # via -r requirements/dev.in 50 | docutils==0.17 51 | # via 52 | # readme-renderer 53 | # sphinx 54 | filelock==3.0.12 55 | # via 56 | # tox 57 | # virtualenv 58 | flake8==3.9.0 59 | # via 60 | # -r requirements/dev.in 61 | # pytest-flake8 62 | idna==2.10 63 | # via requests 64 | imagesize==1.2.0 65 | # via sphinx 66 | importlib-metadata==2.1.1 67 | # via 68 | # flake8 69 | # pluggy 70 | # pytest 71 | # tox 72 | # virtualenv 73 | importlib-resources==3.2.1 74 | # via virtualenv 75 | iniconfig==1.1.1 76 | # via pytest 77 | ipython-genutils==0.2.0 78 | # via traitlets 79 | ipython==7.9.0 80 | # via -r requirements/dev.in 81 | jedi==0.17.2 82 | # via ipython 83 | jinja2==2.11.3 84 | # via sphinx 85 | markupsafe==1.1.1 86 | # via jinja2 87 | mccabe==0.6.1 88 | # via flake8 89 | packaging==20.9 90 | # via 91 | # bleach 92 | # pytest 93 | # sphinx 94 | # tox 95 | parso==0.7.1 96 | # via jedi 97 | pathlib2==2.3.5 98 | # via pytest 99 | pexpect==4.8.0 100 | # via ipython 101 | pickleshare==0.7.5 102 | # via ipython 103 | pip-tools==5.5.0 104 | # via -r requirements/dev.in 105 | pkginfo==1.7.0 106 | # via twine 107 | pluggy==0.13.1 108 | # via 109 | # pytest 110 | # tox 111 | prompt-toolkit==2.0.10 112 | # via ipython 113 | ptyprocess==0.7.0 114 | # via pexpect 115 | py==1.10.0 116 | # via 117 | # pytest 118 | # tox 119 | pycodestyle==2.7.0 120 | # via 121 | # -r requirements/dev.in 122 | # flake8 123 | pyflakes==2.3.1 124 | # via flake8 125 | pygments==2.8.1 126 | # via 127 | # ipython 128 | # readme-renderer 129 | # sphinx 130 | pyparsing==2.4.7 131 | # via packaging 132 | pytest-cov==2.11.1 133 | # via -r requirements/dev.in 134 | pytest-django==4.1.0 135 | # via -r requirements/dev.in 136 | pytest-flake8==1.0.7 137 | # via -r requirements/dev.in 138 | pytest-runner==5.2 139 | # via -r requirements/dev.in 140 | pytest==6.1.2 141 | # via 142 | # -r requirements/dev.in 143 | # pytest-cov 144 | # pytest-django 145 | # pytest-flake8 146 | pytz==2021.1 147 | # via 148 | # babel 149 | # django 150 | readme-renderer==29.0 151 | # via twine 152 | requests-toolbelt==0.9.1 153 | # via twine 154 | requests==2.25.1 155 | # via 156 | # requests-toolbelt 157 | # sphinx 158 | # twine 159 | setuptools-twine==0.1.3 160 | # via -r requirements/dev.in 161 | six==1.15.0 162 | # via 163 | # bleach 164 | # pathlib2 165 | # prompt-toolkit 166 | # readme-renderer 167 | # tox 168 | # traitlets 169 | # virtualenv 170 | snowballstemmer==2.1.0 171 | # via sphinx 172 | sphinx==3.5.3 173 | # via -r requirements/dev.in 174 | sphinxcontrib-applehelp==1.0.2 175 | # via sphinx 176 | sphinxcontrib-devhelp==1.0.2 177 | # via sphinx 178 | sphinxcontrib-htmlhelp==1.0.3 179 | # via sphinx 180 | sphinxcontrib-jsmath==1.0.1 181 | # via sphinx 182 | sphinxcontrib-qthelp==1.0.3 183 | # via sphinx 184 | sphinxcontrib-serializinghtml==1.1.4 185 | # via sphinx 186 | sqlparse==0.4.1 187 | # via 188 | # django 189 | # django-debug-toolbar 190 | toml==0.10.2 191 | # via 192 | # pytest 193 | # tox 194 | tox-gh-actions==2.4.0 195 | # via -r requirements/dev.in 196 | tox==3.23.0 197 | # via 198 | # -r requirements/dev.in 199 | # tox-gh-actions 200 | tqdm==4.60.0 201 | # via twine 202 | traitlets==4.3.3 203 | # via ipython 204 | twine==1.15.0 205 | # via 206 | # -r requirements/dev.in 207 | # setuptools-twine 208 | urllib3==1.26.4 209 | # via requests 210 | virtualenv==20.4.3 211 | # via tox 212 | wcwidth==0.2.5 213 | # via prompt-toolkit 214 | webencodings==0.5.1 215 | # via bleach 216 | zipp==1.2.0 217 | # via 218 | # importlib-metadata 219 | # importlib-resources 220 | 221 | # The following packages are considered to be unsafe in a requirements file: 222 | # pip 223 | # setuptools 224 | -------------------------------------------------------------------------------- /requirements/dev37.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile 3 | # To update, run: 4 | # 5 | # pip-compile --output-file=requirements/dev37.txt requirements/dev.in 6 | # 7 | alabaster==0.7.12 8 | # via sphinx 9 | appdirs==1.4.4 10 | # via virtualenv 11 | appnope==0.1.2 12 | # via ipython 13 | asgiref==3.3.4 14 | # via django 15 | attrs==20.3.0 16 | # via pytest 17 | babel==2.9.0 18 | # via sphinx 19 | backcall==0.2.0 20 | # via ipython 21 | bleach==3.3.0 22 | # via readme-renderer 23 | bump2version==1.0.1 24 | # via bumpversion 25 | bumpversion==0.6.0 26 | # via -r requirements/dev.in 27 | certifi==2020.12.5 28 | # via requests 29 | chardet==4.0.0 30 | # via requests 31 | click==7.1.2 32 | # via pip-tools 33 | colorama==0.4.4 34 | # via twine 35 | coverage==5.5 36 | # via pytest-cov 37 | decorator==5.0.5 38 | # via ipython 39 | distlib==0.3.1 40 | # via virtualenv 41 | django-debug-toolbar==3.2 42 | # via -r requirements/dev.in 43 | django-extensions==3.1.2 44 | # via -r requirements/dev.in 45 | django==3.2 46 | # via 47 | # -r requirements/dev.in 48 | # django-debug-toolbar 49 | # django-extensions 50 | # djangorestframework 51 | djangorestframework==3.12.4 52 | # via -r requirements/dev.in 53 | docutils==0.17 54 | # via 55 | # readme-renderer 56 | # sphinx 57 | filelock==3.0.12 58 | # via 59 | # tox 60 | # virtualenv 61 | flake8==3.9.0 62 | # via 63 | # -r requirements/dev.in 64 | # pytest-flake8 65 | idna==2.10 66 | # via requests 67 | imagesize==1.2.0 68 | # via sphinx 69 | importlib-metadata==3.10.0 70 | # via 71 | # flake8 72 | # keyring 73 | # pep517 74 | # pluggy 75 | # pytest 76 | # tox 77 | # twine 78 | # virtualenv 79 | iniconfig==1.1.1 80 | # via pytest 81 | ipython-genutils==0.2.0 82 | # via traitlets 83 | ipython==7.22.0 84 | # via -r requirements/dev.in 85 | jedi==0.18.0 86 | # via ipython 87 | jinja2==2.11.3 88 | # via sphinx 89 | keyring==23.0.1 90 | # via twine 91 | markupsafe==1.1.1 92 | # via jinja2 93 | mccabe==0.6.1 94 | # via flake8 95 | packaging==20.9 96 | # via 97 | # bleach 98 | # pytest 99 | # sphinx 100 | # tox 101 | parso==0.8.2 102 | # via jedi 103 | pep517==0.10.0 104 | # via pip-tools 105 | pexpect==4.8.0 106 | # via ipython 107 | pickleshare==0.7.5 108 | # via ipython 109 | pip-tools==6.0.1 110 | # via -r requirements/dev.in 111 | pkginfo==1.7.0 112 | # via twine 113 | pluggy==0.13.1 114 | # via 115 | # pytest 116 | # tox 117 | prompt-toolkit==3.0.18 118 | # via ipython 119 | ptyprocess==0.7.0 120 | # via pexpect 121 | py==1.10.0 122 | # via 123 | # pytest 124 | # tox 125 | pycodestyle==2.7.0 126 | # via 127 | # -r requirements/dev.in 128 | # flake8 129 | pyflakes==2.3.1 130 | # via flake8 131 | pygments==2.8.1 132 | # via 133 | # ipython 134 | # readme-renderer 135 | # sphinx 136 | pyparsing==2.4.7 137 | # via packaging 138 | pytest-cov==2.11.1 139 | # via -r requirements/dev.in 140 | pytest-django==4.1.0 141 | # via -r requirements/dev.in 142 | pytest-flake8==1.0.7 143 | # via -r requirements/dev.in 144 | pytest-runner==5.3.0 145 | # via -r requirements/dev.in 146 | pytest==6.2.3 147 | # via 148 | # -r requirements/dev.in 149 | # pytest-cov 150 | # pytest-django 151 | # pytest-flake8 152 | pytz==2021.1 153 | # via 154 | # babel 155 | # django 156 | readme-renderer==29.0 157 | # via twine 158 | requests-toolbelt==0.9.1 159 | # via twine 160 | requests==2.25.1 161 | # via 162 | # requests-toolbelt 163 | # sphinx 164 | # twine 165 | rfc3986==1.4.0 166 | # via twine 167 | setuptools-twine==0.1.3 168 | # via -r requirements/dev.in 169 | six==1.15.0 170 | # via 171 | # bleach 172 | # readme-renderer 173 | # tox 174 | # virtualenv 175 | snowballstemmer==2.1.0 176 | # via sphinx 177 | sphinx==3.5.3 178 | # via -r requirements/dev.in 179 | sphinxcontrib-applehelp==1.0.2 180 | # via sphinx 181 | sphinxcontrib-devhelp==1.0.2 182 | # via sphinx 183 | sphinxcontrib-htmlhelp==1.0.3 184 | # via sphinx 185 | sphinxcontrib-jsmath==1.0.1 186 | # via sphinx 187 | sphinxcontrib-qthelp==1.0.3 188 | # via sphinx 189 | sphinxcontrib-serializinghtml==1.1.4 190 | # via sphinx 191 | sqlparse==0.4.1 192 | # via 193 | # django 194 | # django-debug-toolbar 195 | toml==0.10.2 196 | # via 197 | # pep517 198 | # pytest 199 | # tox 200 | tox-gh-actions==2.4.0 201 | # via -r requirements/dev.in 202 | tox==3.23.0 203 | # via 204 | # -r requirements/dev.in 205 | # tox-gh-actions 206 | tqdm==4.60.0 207 | # via twine 208 | traitlets==5.0.5 209 | # via ipython 210 | twine==3.4.1 211 | # via 212 | # -r requirements/dev.in 213 | # setuptools-twine 214 | typing-extensions==3.7.4.3 215 | # via 216 | # asgiref 217 | # importlib-metadata 218 | urllib3==1.26.4 219 | # via requests 220 | virtualenv==20.4.3 221 | # via tox 222 | wcwidth==0.2.5 223 | # via prompt-toolkit 224 | webencodings==0.5.1 225 | # via bleach 226 | zipp==3.4.1 227 | # via 228 | # importlib-metadata 229 | # pep517 230 | 231 | # The following packages are considered to be unsafe in a requirements file: 232 | # pip 233 | # setuptools 234 | -------------------------------------------------------------------------------- /test_project/test_app/tests.py: -------------------------------------------------------------------------------- 1 | # Python 2 | from __future__ import with_statement 3 | from __future__ import unicode_literals 4 | import base64 5 | try: 6 | from importlib import reload as reload_module 7 | except ImportError: 8 | from imp import reload as reload_module 9 | import json 10 | 11 | # Django 12 | from django.test import TestCase 13 | from django.test.client import Client 14 | try: 15 | from django.urls import reverse 16 | except ImportError: 17 | from django.core.urlresolvers import reverse 18 | from django.contrib.auth.models import User 19 | try: 20 | from django.utils.six import binary_type, text_type 21 | except ImportError: 22 | binary_type, text_type = bytes, str 23 | 24 | # Django-CRUM 25 | from crum import get_current_user, impersonate 26 | 27 | 28 | class TestCRUM(TestCase): 29 | """Test cases for the CRUM app.""" 30 | 31 | def setUp(self): 32 | super(TestCRUM, self).setUp() 33 | self.user_password = User.objects.make_random_password() 34 | self.user = User.objects.create_user('user', 'user@example.com', 35 | self.user_password) 36 | 37 | def test_middleware(self): 38 | # For test coverage. 39 | import crum 40 | reload_module(crum) 41 | # Test anonymous user. 42 | self.assertEqual(get_current_user(), None) 43 | url = reverse('test_app:index') 44 | response = self.client.get(url) 45 | response_content = response.content.decode('utf-8') 46 | self.assertEqual(response.status_code, 200) 47 | self.assertEqual(response_content, 'AnonymousUser') 48 | self.assertEqual(get_current_user(), None) 49 | # Test logged in user. 50 | self.client.login(username=self.user.username, 51 | password=self.user_password) 52 | response = self.client.get(url) 53 | response_content = response.content.decode('utf-8') 54 | self.assertEqual(response.status_code, 200) 55 | self.assertEqual(response_content, text_type(self.user)) 56 | self.assertEqual(get_current_user(), None) 57 | # Test impersonate context manager. 58 | with impersonate(self.user): 59 | self.assertEqual(get_current_user(), self.user) 60 | self.assertEqual(get_current_user(), None) 61 | # Test impersonate(None) within view requested by logged in user. 62 | self.client.login(username=self.user.username, 63 | password=self.user_password) 64 | response = self.client.get(url + '?impersonate=1') 65 | response_content = response.content.decode('utf-8') 66 | self.assertEqual(response.status_code, 200) 67 | self.assertEqual(response_content, text_type(None)) 68 | self.assertEqual(get_current_user(), None) 69 | # Test when request raises exception. 70 | try: 71 | response = self.client.get(url + '?raise=1') 72 | except RuntimeError: 73 | response = None 74 | self.assertEqual(response, None) 75 | self.assertEqual(get_current_user(), None) 76 | 77 | def test_middleware_with_rest_framework(self): 78 | # Test anonymous user. 79 | self.assertEqual(get_current_user(), None) 80 | url = reverse('test_app:api_index') 81 | response = self.client.get(url) 82 | response_content = json.loads(response.content.decode('utf-8')) 83 | self.assertEqual(response.status_code, 200) 84 | self.assertEqual(response_content, text_type('AnonymousUser')) 85 | self.assertEqual(get_current_user(), None) 86 | # Test logged in user (session auth). 87 | self.client.login(username=self.user.username, 88 | password=self.user_password) 89 | response = self.client.get(url) 90 | response_content = json.loads(response.content.decode('utf-8')) 91 | self.assertEqual(response.status_code, 200) 92 | self.assertEqual(response_content, text_type(self.user)) 93 | self.assertEqual(get_current_user(), None) 94 | # Test logged in user (basic auth). 95 | basic_auth = '{0}:{1}'.format(self.user.username, self.user_password) 96 | basic_auth = binary_type(basic_auth.encode('utf-8')) 97 | basic_auth = base64.b64encode(basic_auth).decode('ascii') 98 | client_kwargs = {'HTTP_AUTHORIZATION': 'Basic %s' % basic_auth} 99 | client = Client(**client_kwargs) 100 | response = client.get(url) 101 | response_content = json.loads(response.content.decode('utf-8')) 102 | self.assertEqual(response.status_code, 200) 103 | self.assertEqual(response_content, text_type(self.user)) 104 | self.assertEqual(get_current_user(), None) 105 | # Test impersonate(None) within view requested by logged in user. 106 | self.client.login(username=self.user.username, 107 | password=self.user_password) 108 | response = self.client.get(url + '?impersonate=1') 109 | response_content = json.loads(response.content.decode('utf-8')) 110 | self.assertEqual(response.status_code, 200) 111 | self.assertEqual(response_content, text_type(None)) 112 | self.assertEqual(get_current_user(), None) 113 | # Test when request raises exception. 114 | try: 115 | response = self.client.get(url + '?raise=1') 116 | except RuntimeError: 117 | response = None 118 | self.assertEqual(response, None) 119 | self.assertEqual(get_current_user(), None) 120 | -------------------------------------------------------------------------------- /requirements/dev36.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile 3 | # To update, run: 4 | # 5 | # pip-compile --output-file=requirements/dev36.txt requirements/dev.in 6 | # 7 | alabaster==0.7.12 8 | # via sphinx 9 | appdirs==1.4.4 10 | # via virtualenv 11 | appnope==0.1.2 12 | # via ipython 13 | asgiref==3.3.4 14 | # via django 15 | attrs==20.3.0 16 | # via pytest 17 | babel==2.9.0 18 | # via sphinx 19 | backcall==0.2.0 20 | # via ipython 21 | bleach==3.3.0 22 | # via readme-renderer 23 | bump2version==1.0.1 24 | # via bumpversion 25 | bumpversion==0.6.0 26 | # via -r requirements/dev.in 27 | certifi==2020.12.5 28 | # via requests 29 | chardet==4.0.0 30 | # via requests 31 | click==7.1.2 32 | # via pip-tools 33 | colorama==0.4.4 34 | # via twine 35 | coverage==5.5 36 | # via pytest-cov 37 | decorator==5.0.5 38 | # via 39 | # ipython 40 | # traitlets 41 | distlib==0.3.1 42 | # via virtualenv 43 | django-debug-toolbar==3.2 44 | # via -r requirements/dev.in 45 | django-extensions==3.1.2 46 | # via -r requirements/dev.in 47 | django==3.2 48 | # via 49 | # -r requirements/dev.in 50 | # django-debug-toolbar 51 | # django-extensions 52 | # djangorestframework 53 | djangorestframework==3.12.4 54 | # via -r requirements/dev.in 55 | docutils==0.17 56 | # via 57 | # readme-renderer 58 | # sphinx 59 | filelock==3.0.12 60 | # via 61 | # tox 62 | # virtualenv 63 | flake8==3.9.0 64 | # via 65 | # -r requirements/dev.in 66 | # pytest-flake8 67 | idna==2.10 68 | # via requests 69 | imagesize==1.2.0 70 | # via sphinx 71 | importlib-metadata==3.10.0 72 | # via 73 | # flake8 74 | # keyring 75 | # pep517 76 | # pluggy 77 | # pytest 78 | # tox 79 | # twine 80 | # virtualenv 81 | importlib-resources==5.1.2 82 | # via virtualenv 83 | iniconfig==1.1.1 84 | # via pytest 85 | ipython-genutils==0.2.0 86 | # via traitlets 87 | ipython==7.16.1 88 | # via -r requirements/dev.in 89 | jedi==0.18.0 90 | # via ipython 91 | jinja2==2.11.3 92 | # via sphinx 93 | keyring==23.0.1 94 | # via twine 95 | markupsafe==1.1.1 96 | # via jinja2 97 | mccabe==0.6.1 98 | # via flake8 99 | packaging==20.9 100 | # via 101 | # bleach 102 | # pytest 103 | # sphinx 104 | # tox 105 | parso==0.8.2 106 | # via jedi 107 | pep517==0.10.0 108 | # via pip-tools 109 | pexpect==4.8.0 110 | # via ipython 111 | pickleshare==0.7.5 112 | # via ipython 113 | pip-tools==6.0.1 114 | # via -r requirements/dev.in 115 | pkginfo==1.7.0 116 | # via twine 117 | pluggy==0.13.1 118 | # via 119 | # pytest 120 | # tox 121 | prompt-toolkit==3.0.18 122 | # via ipython 123 | ptyprocess==0.7.0 124 | # via pexpect 125 | py==1.10.0 126 | # via 127 | # pytest 128 | # tox 129 | pycodestyle==2.7.0 130 | # via 131 | # -r requirements/dev.in 132 | # flake8 133 | pyflakes==2.3.1 134 | # via flake8 135 | pygments==2.8.1 136 | # via 137 | # ipython 138 | # readme-renderer 139 | # sphinx 140 | pyparsing==2.4.7 141 | # via packaging 142 | pytest-cov==2.11.1 143 | # via -r requirements/dev.in 144 | pytest-django==4.1.0 145 | # via -r requirements/dev.in 146 | pytest-flake8==1.0.7 147 | # via -r requirements/dev.in 148 | pytest-runner==5.3.0 149 | # via -r requirements/dev.in 150 | pytest==6.2.3 151 | # via 152 | # -r requirements/dev.in 153 | # pytest-cov 154 | # pytest-django 155 | # pytest-flake8 156 | pytz==2021.1 157 | # via 158 | # babel 159 | # django 160 | readme-renderer==29.0 161 | # via twine 162 | requests-toolbelt==0.9.1 163 | # via twine 164 | requests==2.25.1 165 | # via 166 | # requests-toolbelt 167 | # sphinx 168 | # twine 169 | rfc3986==1.4.0 170 | # via twine 171 | setuptools-twine==0.1.3 172 | # via -r requirements/dev.in 173 | six==1.15.0 174 | # via 175 | # bleach 176 | # readme-renderer 177 | # tox 178 | # traitlets 179 | # virtualenv 180 | snowballstemmer==2.1.0 181 | # via sphinx 182 | sphinx==3.5.3 183 | # via -r requirements/dev.in 184 | sphinxcontrib-applehelp==1.0.2 185 | # via sphinx 186 | sphinxcontrib-devhelp==1.0.2 187 | # via sphinx 188 | sphinxcontrib-htmlhelp==1.0.3 189 | # via sphinx 190 | sphinxcontrib-jsmath==1.0.1 191 | # via sphinx 192 | sphinxcontrib-qthelp==1.0.3 193 | # via sphinx 194 | sphinxcontrib-serializinghtml==1.1.4 195 | # via sphinx 196 | sqlparse==0.4.1 197 | # via 198 | # django 199 | # django-debug-toolbar 200 | toml==0.10.2 201 | # via 202 | # pep517 203 | # pytest 204 | # tox 205 | tox-gh-actions==2.4.0 206 | # via -r requirements/dev.in 207 | tox==3.23.0 208 | # via 209 | # -r requirements/dev.in 210 | # tox-gh-actions 211 | tqdm==4.60.0 212 | # via twine 213 | traitlets==4.3.3 214 | # via ipython 215 | twine==3.4.1 216 | # via 217 | # -r requirements/dev.in 218 | # setuptools-twine 219 | typing-extensions==3.7.4.3 220 | # via 221 | # asgiref 222 | # importlib-metadata 223 | urllib3==1.26.4 224 | # via requests 225 | virtualenv==20.4.3 226 | # via tox 227 | wcwidth==0.2.5 228 | # via prompt-toolkit 229 | webencodings==0.5.1 230 | # via bleach 231 | zipp==3.4.1 232 | # via 233 | # importlib-metadata 234 | # importlib-resources 235 | # pep517 236 | 237 | # The following packages are considered to be unsafe in a requirements file: 238 | # pip 239 | # setuptools 240 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. Django-CRUM documentation master file, created by 2 | sphinx-quickstart on Sat Jul 6 00:44:15 2013. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Django-CRUM 7 | =========== 8 | 9 | **Django-CRUM (Current Request User Middleware)** captures the current request 10 | and user in thread local storage. 11 | 12 | It enables apps to check permissions, capture audit trails or otherwise access 13 | the current request and user without requiring the request object to be passed 14 | directly. It also offers a context manager to allow for temporarily 15 | impersonating another user. 16 | 17 | It provides a signal to extend the built-in function for getting the current 18 | user, which could be helpful when using custom authentication methods or user 19 | models. 20 | 21 | It is tested against: 22 | * Django 1.11 (Python 3.5 and 3.6) 23 | * Django 2.0 (Python 3.5, 3.6 and 3.7) 24 | * Django 2.1 (Python 3.5, 3.6 and 3.7) 25 | * Django 2.2 (Python 3.5, 3.6, 3.7, 3.8 and 3.9) 26 | * Django 3.0 (Python 3.6, 3.7, 3.8 and 3.9) 27 | * Django 3.1 (Python 3.6, 3.7, 3.8 and 3.9) 28 | * Django 3.2 pre-release (Python 3.6, 3.7, 3.8 and 3.9) 29 | * Django main/4.0 (Python 3.8 and 3.9) 30 | 31 | Installation 32 | ------------ 33 | 34 | Install the application from PYPI:: 35 | 36 | pip install django-crum 37 | 38 | Add ``CurrentRequestUserMiddleware`` to your 39 | ``MIDDLEWARE`` setting:: 40 | 41 | MIDDLEWARE += ('crum.CurrentRequestUserMiddleware',) 42 | 43 | *That's it!* 44 | 45 | Usage 46 | ----- 47 | 48 | The `crum` package exports three functions as its public API. 49 | 50 | get_current_request() 51 | ~~~~~~~~~~~~~~~~~~~~~ 52 | 53 | ``get_current_request`` returns the current request instance, or ``None`` if 54 | called outside the scope of a request. 55 | 56 | For example, the ``Comment`` model below overrides its ``save`` method to track 57 | the IP address of each commenter:: 58 | 59 | from django.db import models 60 | from crum import get_current_request 61 | 62 | class Comment(models.Model): 63 | created = models.DateTimeField(auto_now_add=True) 64 | comment = models.TextField() 65 | remote_addr = models.CharField(blank=True, default='') 66 | 67 | def save(self, *args, **kwargs): 68 | request = get_current_request() 69 | if request and not self.remote_addr: 70 | self.remote_addr = request.META['REMOTE_ADDR'] 71 | super(Comment, self).save(*args, **kwargs) 72 | 73 | get_current_user() 74 | ~~~~~~~~~~~~~~~~~~ 75 | 76 | ``get_current_user`` returns the user associated with the current request, or 77 | ``None`` if no user is available. 78 | 79 | If using the built-in ``User`` model from ``django.contrib.auth``, the returned 80 | value may be the special ``AnonymousUser``, which won't have a primary key. 81 | 82 | For example, the ``Thing`` model below records the user who created it as well 83 | as the last user who modified it:: 84 | 85 | from django.db import models 86 | from crum import get_current_user 87 | 88 | class Thing(models.Model): 89 | created = models.DateTimeField(auto_now_add=True) 90 | created_by = models.ForeignKey('auth.User', blank=True, null=True, 91 | default=None) 92 | modified = models.DateTimeField(auto_now=True) 93 | modified_by = models.ForeignKey('auth.User', blank=True, null=True, 94 | default=None) 95 | 96 | def save(self, *args, **kwargs): 97 | user = get_current_user() 98 | if user and not user.pk: 99 | user = None 100 | if not self.pk: 101 | self.created_by = user 102 | self.modified_by = user 103 | super(Thing, self).save(*args, **kwargs) 104 | 105 | impersonate(user=None) 106 | ~~~~~~~~~~~~~~~~~~~~~~ 107 | 108 | ``impersonate`` is a context manager used to temporarily change the current 109 | user as returned by ``get_current_user``. It is typically used to perform an 110 | action on behalf of a user or disable the default behavior of 111 | ``get_current_user``. 112 | 113 | For example, a background task may need to create or update ``Thing`` objects 114 | when there is no active request or user (such as from a management command):: 115 | 116 | from crum import impersonate 117 | 118 | def create_thing_for_user(user): 119 | with impersonate(user): 120 | # This Thing will indicated it was created by the given user. 121 | user_thing = Thing.objects.create() 122 | # But this Thing won't have a created_by user. 123 | other_thing = Thing.objects.create() 124 | 125 | When running from within a view, ``impersonate`` may be used to prevent certain 126 | actions from being attributed to the requesting user:: 127 | 128 | from django.template.response import TemplateResponse 129 | from crum import impersonate 130 | 131 | def get_my_things(request): 132 | # Whenever this view is accessed, trigger some cleanup of Things. 133 | with impersonate(None): 134 | Thing.objects.cleanup() 135 | my_things = Thing.objects.filter(created_by=request.user) 136 | return TemplateResponse(request, 'my_things.html', 137 | {'things': my_things}) 138 | 139 | Signals 140 | ------- 141 | 142 | (New in 0.6.0) The `crum` package provides a signal to extend the capabilities 143 | of the `get_current_user()` function. 144 | 145 | current_user_getter 146 | ~~~~~~~~~~~~~~~~~~~ 147 | 148 | The ``current_user_getter`` signal is dispatched for each call to 149 | ``get_current_user()``. Receivers for this signal should return a tuple of 150 | ``(user, priority)``. Receivers should return ``None`` for the user when there 151 | is no current user set, or ``False`` when they can not determine the current 152 | user. 153 | 154 | The priority value which will be used to determine which response contains the 155 | current user. The response with the highest priority will be used as long as 156 | the user returned is not ``False``, otherwise lower-priority responses will 157 | be used in order of next-highest priority. Built-in receivers for this signal 158 | use priorities of -10 (current request) and +10 (thread locals); any custom 159 | receivers should usually use -10 < priority < 10. 160 | 161 | The following example demonstrates how a custom receiver could be implemented 162 | to determine the current user from an auth token passed via an HTTP header:: 163 | 164 | from django.dispatch import receiver 165 | from crum import get_current_request 166 | from crum.signals import current_user_getter 167 | 168 | @receiver(current_user_getter) 169 | def (sender, **kwargs): 170 | request = get_current_request() 171 | if request: 172 | token = request.META.get('HTTP_AUTH_TOKEN', None) 173 | try: 174 | auth_token = AuthToken.objects.get(token=token) 175 | return (auth_token.user, 0) 176 | except AuthToken.DoesNotExist: 177 | return (None, 0) 178 | return (False, 0) 179 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Django-CRUM.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Django-CRUM.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Django-CRUM" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Django-CRUM" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | goto end 41 | ) 42 | 43 | if "%1" == "clean" ( 44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 45 | del /q /s %BUILDDIR%\* 46 | goto end 47 | ) 48 | 49 | 50 | %SPHINXBUILD% 2> nul 51 | if errorlevel 9009 ( 52 | echo. 53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 54 | echo.installed, then set the SPHINXBUILD environment variable to point 55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 56 | echo.may add the Sphinx directory to PATH. 57 | echo. 58 | echo.If you don't have Sphinx installed, grab it from 59 | echo.http://sphinx-doc.org/ 60 | exit /b 1 61 | ) 62 | 63 | if "%1" == "html" ( 64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 68 | goto end 69 | ) 70 | 71 | if "%1" == "dirhtml" ( 72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 76 | goto end 77 | ) 78 | 79 | if "%1" == "singlehtml" ( 80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 84 | goto end 85 | ) 86 | 87 | if "%1" == "pickle" ( 88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can process the pickle files. 92 | goto end 93 | ) 94 | 95 | if "%1" == "json" ( 96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 97 | if errorlevel 1 exit /b 1 98 | echo. 99 | echo.Build finished; now you can process the JSON files. 100 | goto end 101 | ) 102 | 103 | if "%1" == "htmlhelp" ( 104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 105 | if errorlevel 1 exit /b 1 106 | echo. 107 | echo.Build finished; now you can run HTML Help Workshop with the ^ 108 | .hhp project file in %BUILDDIR%/htmlhelp. 109 | goto end 110 | ) 111 | 112 | if "%1" == "qthelp" ( 113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 117 | .qhcp project file in %BUILDDIR%/qthelp, like this: 118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Django-CRUM.qhcp 119 | echo.To view the help file: 120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Django-CRUM.ghc 121 | goto end 122 | ) 123 | 124 | if "%1" == "devhelp" ( 125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished. 129 | goto end 130 | ) 131 | 132 | if "%1" == "epub" ( 133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 137 | goto end 138 | ) 139 | 140 | if "%1" == "latex" ( 141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 145 | goto end 146 | ) 147 | 148 | if "%1" == "latexpdf" ( 149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 150 | cd %BUILDDIR%/latex 151 | make all-pdf 152 | cd %BUILDDIR%/.. 153 | echo. 154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 155 | goto end 156 | ) 157 | 158 | if "%1" == "latexpdfja" ( 159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 160 | cd %BUILDDIR%/latex 161 | make all-pdf-ja 162 | cd %BUILDDIR%/.. 163 | echo. 164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 165 | goto end 166 | ) 167 | 168 | if "%1" == "text" ( 169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 170 | if errorlevel 1 exit /b 1 171 | echo. 172 | echo.Build finished. The text files are in %BUILDDIR%/text. 173 | goto end 174 | ) 175 | 176 | if "%1" == "man" ( 177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 178 | if errorlevel 1 exit /b 1 179 | echo. 180 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 181 | goto end 182 | ) 183 | 184 | if "%1" == "texinfo" ( 185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 186 | if errorlevel 1 exit /b 1 187 | echo. 188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 189 | goto end 190 | ) 191 | 192 | if "%1" == "gettext" ( 193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 194 | if errorlevel 1 exit /b 1 195 | echo. 196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 197 | goto end 198 | ) 199 | 200 | if "%1" == "changes" ( 201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 202 | if errorlevel 1 exit /b 1 203 | echo. 204 | echo.The overview file is in %BUILDDIR%/changes. 205 | goto end 206 | ) 207 | 208 | if "%1" == "linkcheck" ( 209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 210 | if errorlevel 1 exit /b 1 211 | echo. 212 | echo.Link check complete; look for any errors in the above output ^ 213 | or in %BUILDDIR%/linkcheck/output.txt. 214 | goto end 215 | ) 216 | 217 | if "%1" == "doctest" ( 218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 219 | if errorlevel 1 exit /b 1 220 | echo. 221 | echo.Testing of doctests in the sources finished, look at the ^ 222 | results in %BUILDDIR%/doctest/output.txt. 223 | goto end 224 | ) 225 | 226 | if "%1" == "xml" ( 227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 228 | if errorlevel 1 exit /b 1 229 | echo. 230 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 231 | goto end 232 | ) 233 | 234 | if "%1" == "pseudoxml" ( 235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 236 | if errorlevel 1 exit /b 1 237 | echo. 238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 239 | goto end 240 | ) 241 | 242 | :end 243 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Django-CRUM documentation build configuration file, created by 4 | # sphinx-quickstart on Sat Jul 6 00:44:15 2013. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | #sys.path.insert(0, os.path.abspath('.')) 20 | 21 | # -- General configuration ----------------------------------------------------- 22 | 23 | # If your documentation needs a minimal Sphinx version, state it here. 24 | #needs_sphinx = '1.0' 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be extensions 27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 28 | extensions = [] 29 | 30 | # Add any paths that contain templates here, relative to this directory. 31 | templates_path = ['_templates'] 32 | 33 | # The suffix of source filenames. 34 | source_suffix = '.rst' 35 | 36 | # The encoding of source files. 37 | #source_encoding = 'utf-8-sig' 38 | 39 | # The master toctree document. 40 | master_doc = 'index' 41 | 42 | # General information about the project. 43 | project = u'Django-CRUM' 44 | copyright = u'2018, Nine More Minutes, Inc.' 45 | 46 | # The version info for the project you're documenting, acts as replacement for 47 | # |version| and |release|, also used in various other places throughout the 48 | # built documents. 49 | # 50 | # The short X.Y version. 51 | version = '0.7.9' 52 | # The full version, including alpha/beta/rc tags. 53 | release = '0.7.9' 54 | 55 | # The language for content autogenerated by Sphinx. Refer to documentation 56 | # for a list of supported languages. 57 | #language = None 58 | 59 | # There are two options for replacing |today|: either, you set today to some 60 | # non-false value, then it is used: 61 | #today = '' 62 | # Else, today_fmt is used as the format for a strftime call. 63 | #today_fmt = '%B %d, %Y' 64 | 65 | # List of patterns, relative to source directory, that match files and 66 | # directories to ignore when looking for source files. 67 | exclude_patterns = ['_build'] 68 | 69 | # The reST default role (used for this markup: `text`) to use for all documents. 70 | #default_role = None 71 | 72 | # If true, '()' will be appended to :func: etc. cross-reference text. 73 | #add_function_parentheses = True 74 | 75 | # If true, the current module name will be prepended to all description 76 | # unit titles (such as .. function::). 77 | #add_module_names = True 78 | 79 | # If true, sectionauthor and moduleauthor directives will be shown in the 80 | # output. They are ignored by default. 81 | #show_authors = False 82 | 83 | # The name of the Pygments (syntax highlighting) style to use. 84 | pygments_style = 'sphinx' 85 | 86 | # A list of ignored prefixes for module index sorting. 87 | #modindex_common_prefix = [] 88 | 89 | # If true, keep warnings as "system message" paragraphs in the built documents. 90 | #keep_warnings = False 91 | 92 | 93 | # -- Options for HTML output --------------------------------------------------- 94 | 95 | # The theme to use for HTML and HTML Help pages. See the documentation for 96 | # a list of builtin themes. 97 | html_theme = 'alabaster' 98 | 99 | # Theme options are theme-specific and customize the look and feel of a theme 100 | # further. For a list of options available for each theme, see the 101 | # documentation. 102 | #html_theme_options = {} 103 | 104 | # Add any paths that contain custom themes here, relative to this directory. 105 | #html_theme_path = [] 106 | 107 | # The name for this set of Sphinx documents. If None, it defaults to 108 | # " v documentation". 109 | #html_title = None 110 | 111 | # A shorter title for the navigation bar. Default is the same as html_title. 112 | #html_short_title = None 113 | 114 | # The name of an image file (relative to this directory) to place at the top 115 | # of the sidebar. 116 | #html_logo = None 117 | 118 | # The name of an image file (within the static path) to use as favicon of the 119 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 120 | # pixels large. 121 | #html_favicon = None 122 | 123 | # Add any paths that contain custom static files (such as style sheets) here, 124 | # relative to this directory. They are copied after the builtin static files, 125 | # so a file named "default.css" will overwrite the builtin "default.css". 126 | html_static_path = ['_static'] 127 | 128 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 129 | # using the given strftime format. 130 | #html_last_updated_fmt = '%b %d, %Y' 131 | 132 | # If true, SmartyPants will be used to convert quotes and dashes to 133 | # typographically correct entities. 134 | #html_use_smartypants = True 135 | 136 | # Custom sidebar templates, maps document names to template names. 137 | #html_sidebars = {} 138 | 139 | # Additional templates that should be rendered to pages, maps page names to 140 | # template names. 141 | #html_additional_pages = {} 142 | 143 | # If false, no module index is generated. 144 | #html_domain_indices = True 145 | 146 | # If false, no index is generated. 147 | #html_use_index = True 148 | 149 | # If true, the index is split into individual pages for each letter. 150 | #html_split_index = False 151 | 152 | # If true, links to the reST sources are added to the pages. 153 | #html_show_sourcelink = True 154 | 155 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 156 | #html_show_sphinx = True 157 | 158 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 159 | #html_show_copyright = True 160 | 161 | # If true, an OpenSearch description file will be output, and all pages will 162 | # contain a tag referring to it. The value of this option must be the 163 | # base URL from which the finished HTML is served. 164 | #html_use_opensearch = '' 165 | 166 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 167 | #html_file_suffix = None 168 | 169 | # Output file base name for HTML help builder. 170 | htmlhelp_basename = 'Django-CRUMdoc' 171 | 172 | 173 | # -- Options for LaTeX output -------------------------------------------------- 174 | 175 | latex_elements = { 176 | # The paper size ('letterpaper' or 'a4paper'). 177 | #'papersize': 'letterpaper', 178 | 179 | # The font size ('10pt', '11pt' or '12pt'). 180 | #'pointsize': '10pt', 181 | 182 | # Additional stuff for the LaTeX preamble. 183 | #'preamble': '', 184 | } 185 | 186 | # Grouping the document tree into LaTeX files. List of tuples 187 | # (source start file, target name, title, author, documentclass [howto/manual]). 188 | latex_documents = [ 189 | ('index', 'Django-CRUM.tex', u'Django-CRUM Documentation', 190 | u'Nine More Minutes, Inc.', 'manual'), 191 | ] 192 | 193 | # The name of an image file (relative to this directory) to place at the top of 194 | # the title page. 195 | #latex_logo = None 196 | 197 | # For "manual" documents, if this is true, then toplevel headings are parts, 198 | # not chapters. 199 | #latex_use_parts = False 200 | 201 | # If true, show page references after internal links. 202 | #latex_show_pagerefs = False 203 | 204 | # If true, show URL addresses after external links. 205 | #latex_show_urls = False 206 | 207 | # Documents to append as an appendix to all manuals. 208 | #latex_appendices = [] 209 | 210 | # If false, no module index is generated. 211 | #latex_domain_indices = True 212 | 213 | 214 | # -- Options for manual page output -------------------------------------------- 215 | 216 | # One entry per manual page. List of tuples 217 | # (source start file, name, description, authors, manual section). 218 | man_pages = [ 219 | ('index', 'django-crum', u'Django-CRUM Documentation', 220 | [u'Nine More Minutes, Inc.'], 1) 221 | ] 222 | 223 | # If true, show URL addresses after external links. 224 | #man_show_urls = False 225 | 226 | 227 | # -- Options for Texinfo output ------------------------------------------------ 228 | 229 | # Grouping the document tree into Texinfo files. List of tuples 230 | # (source start file, target name, title, author, 231 | # dir menu entry, description, category) 232 | texinfo_documents = [ 233 | ('index', 'Django-CRUM', u'Django-CRUM Documentation', 234 | u'Nine More Minutes, Inc.', 'Django-CRUM', 'One line description of project.', 235 | 'Miscellaneous'), 236 | ] 237 | 238 | # Documents to append as an appendix to all manuals. 239 | #texinfo_appendices = [] 240 | 241 | # If false, no module index is generated. 242 | #texinfo_domain_indices = True 243 | 244 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 245 | #texinfo_show_urls = 'footnote' 246 | 247 | # If true, do not generate a @detailmenu in the "Top" node's menu. 248 | #texinfo_no_detailmenu = False 249 | --------------------------------------------------------------------------------