diff --git a/coverage.rc b/.coveragerc similarity index 100% rename from coverage.rc rename to .coveragerc diff --git a/.travis.yml b/.travis.yml index 2b81084..d377f9f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,32 +8,32 @@ python: env: - - DJANGO=django==1.4.15 - - DJANGO=django==1.5.10 - - DJANGO=django==1.6.7 - - DJANGO=django==1.7 + - DJANGO=django==1.4.18 + - DJANGO=django==1.5.12 + - DJANGO=django==1.6.10 + - DJANGO=django==1.7.3 matrix: exclude: - python: "2.6" - env: DJANGO=django==1.7 + env: DJANGO=django==1.7.3 - python: "3.3" - env: DJANGO=django==1.4.15 + env: DJANGO=django==1.4.18 - python: "3.4" - env: DJANGO=django==1.4.15 + env: DJANGO=django==1.4.18 install: - pip install -q $DJANGO - - pip install -q flake8 + - pip install -q flake8 coverage coveralls - python setup.py install before_script: - make flake8 script: - - make test + - make coverage after_success: - make coveralls diff --git a/CHANGELOG.rst b/CHANGELOG.rst new file mode 100644 index 0000000..d961509 --- /dev/null +++ b/CHANGELOG.rst @@ -0,0 +1,20 @@ +Changes +======= + +0.1 +--- + +* Permissions for disguise now linked with User model; +* Using django system check framework in newest versions; +* Disguise widget become a template tag; earlier it added into page with middleware; +* Migrated to CBV views; +* Code imporvements (pep8); +* Added coverage support; +* Added signals; +* Removed the ``update_user_login`` feature prior to custom signal handling; + + +0.0.3 +----- + +* Travis CI integration diff --git a/Makefile b/Makefile index 49bfac5..0b2a70f 100644 --- a/Makefile +++ b/Makefile @@ -1,23 +1,26 @@ test: python setup.py test + release: python setup.py sdist --format=zip,bztar,gztar register upload + python setup.py bdist_wheel register upload flake8: - flake8 --ignore=E501 disguise - flake8 --ignore=E501 tests.py - flake8 --ignore=E501 setup.py + flake8 \ + disguise \ + tests.py \ + setup.py coverage: - coverage run --include=disguise/*.py setup.py test + coverage run --rcfile=.coveragerc --include=disguise/*.py setup.py test coverage html coveralls: - coveralls --config_file=coverage.rc + coveralls clean: rm -rf *.egg *.egg-info diff --git a/disguise/__init__.py b/disguise/__init__.py index e69de29..1f6693a 100644 --- a/disguise/__init__.py +++ b/disguise/__init__.py @@ -0,0 +1,6 @@ +# coding: utf-8 + +from __future__ import unicode_literals +from __future__ import print_function +from __future__ import absolute_import +from __future__ import division diff --git a/disguise/compat.py b/disguise/compat.py new file mode 100644 index 0000000..df7ac9c --- /dev/null +++ b/disguise/compat.py @@ -0,0 +1,25 @@ +# coding: utf-8 + +from __future__ import unicode_literals +from __future__ import print_function +from __future__ import absolute_import +from __future__ import division + + +try: + from django.contrib.auth import get_user_model +except ImportError: + from django.contrib.auth.models import User + get_user_model = lambda: User + +try: + from django.core import checks + assert hasattr(checks, 'Error') +except (ImportError, AssertionError): + from django.core.exceptions import ImproperlyConfigured + + class Checks(object): + class Error(object): + def __init__(self, message, hint='', error='', id=''): + raise ImproperlyConfigured(message, id) + checks = Checks() diff --git a/disguise/const.py b/disguise/const.py new file mode 100644 index 0000000..27a7c8d --- /dev/null +++ b/disguise/const.py @@ -0,0 +1,14 @@ +# coding: utf-8 + +from __future__ import unicode_literals +from __future__ import print_function +from __future__ import absolute_import +from __future__ import division +from django.contrib.auth import SESSION_KEY, BACKEND_SESSION_KEY + +KEYNAME = 'django_disguise:original_user' +TAGNAME = 'body' +BACKEND = 'django.contrib.auth.backends.ModelBackend' + +__all__ = ['KEYNAME', 'TAGNAME', 'SESSION_KEY', 'BACKEND_SESSION_KEY', + 'BACKEND'] diff --git a/disguise/forms.py b/disguise/forms.py index a3d2afe..8adec72 100644 --- a/disguise/forms.py +++ b/disguise/forms.py @@ -1,36 +1,37 @@ +# coding: utf-8 + +from __future__ import unicode_literals +from __future__ import print_function +from __future__ import absolute_import +from __future__ import division from django import forms from django.utils.translation import ugettext_lazy as _ +from disguise.compat import get_user_model + -try: - from django.contrib.auth import get_user_model - User = get_user_model() -except ImportError: - from django.contrib.auth.models import User +User = get_user_model() class DisguiseForm(forms.Form): """ Disguise form """ - username = forms.CharField(label=_('User name'), - required=False) - user_id = forms.IntegerField(label=_('User ID'), - required=False) - update_last_login = forms.BooleanField(label=_('Update last login'), - required=False) + username = forms.CharField(label=_('User name'), required=False) + user_id = forms.IntegerField(label=_('User ID'), required=False) def clean_username(self): """ Cleans username field """ username = self.cleaned_data.get('username') + if not username: return None qset = User.objects.filter(username=username) - if len(qset) == 1: - return qset[0] + if qset.exists(): + return qset.get() raise forms.ValidationError(_('No such username')) def clean_user_id(self): @@ -43,16 +44,21 @@ def clean_user_id(self): return None qset = User.objects.filter(pk=user_id) - if len(qset) == 1: - return qset[0] + if qset.exists(): + return qset.get() raise forms.ValidationError(_('No such user id')) def clean(self): """ Clears whole form totally """ - if not getattr(self, 'cleaned_data'): - raise forms.ValidationError(_('No such username or user id')) + cleaned_data = getattr(self, 'cleaned_data', {}) + + if not cleaned_data.get('user_id') and \ + not cleaned_data.get('username'): + raise forms.ValidationError( + _('Please enter either username or user id') + ) return self.cleaned_data def get_user(self): @@ -67,4 +73,3 @@ def get_user(self): if not isinstance(user, User): continue return user - raise ValueError('Cannot retrieve user') diff --git a/disguise/middleware.py b/disguise/middleware.py index eab7596..a65f49c 100644 --- a/disguise/middleware.py +++ b/disguise/middleware.py @@ -1,39 +1,12 @@ # coding: utf-8 -from django.template import RequestContext -from django.template.loader import render_to_string -from disguise.forms import DisguiseForm -import warnings - - -try: - from django.utils.encoding import smart_unicode as smart_text -except ImportError: - from django.utils.encoding import smart_text - - -try: - from django.contrib.auth import get_user_model -except ImportError: - from django.contrib.auth.models import User - get_user_model = lambda: User - - -KEYNAME = 'django_disguise:original_user' -TAGNAME = '' - - -def replace_insensitive(string, target, replacement): - """ - Similar to string.replace() but is case insensitive - Code borrowed from: http://forums.devshed.com/python-programming-11/case-insensitive-string-replace-490921.html - """ - no_case = string.lower() - index = no_case.rfind(target.lower()) - if index >= 0: - return string[:index] + replacement + string[index + len(target):] - else: # no results so return the original string - return string +from __future__ import unicode_literals +from __future__ import print_function +from __future__ import absolute_import +from __future__ import division +from disguise.compat import get_user_model +from disguise.utils import can_disguise +from disguise.const import KEYNAME class DisguiseMiddleware(object): @@ -41,18 +14,6 @@ class DisguiseMiddleware(object): Disguise user middleware """ - def test_disguise(self, request): - if KEYNAME in request.session: - return True - - if hasattr(request, 'original_user'): - return True - - if request.user.has_perm('disguire.can_disguise'): - return True - - return False - def get_original_user(self, request): if KEYNAME in request.session: return get_user_model().objects.get(pk=request.session[KEYNAME]) @@ -60,51 +21,10 @@ def get_original_user(self, request): def process_request(self, request): """ - Runs during each request + Injects the `original_user` attribute into HttpRequest object """ - if not hasattr(request, 'user'): - warnings.warn("DisguiseMiddleware must be installed after " - "django.contrib.auth.middleware.AuthenticationMiddleware") - return - - if not hasattr(request, 'session'): - warnings.warn("DisguiseMiddleware must be installed after " - "django.contrib.sessions.middleware.SessionMiddleware") - return - if not request.user.is_authenticated(): return - - if not self.test_disguise(request): + if not can_disguise(request): return - request.original_user = self.get_original_user(request) - - def process_response(self, request, response): - """ - Runs during responding - """ - if not request.user.is_authenticated(): - return response - - if not response.status_code == 200: - return response - - if not response.get('content-type', '').startswith('text/html'): - return response - - if self.test_disguise(request): - # Render HTML code that helps to select disguise - html = render_to_string('disguise/form.html', { - 'form': DisguiseForm(), - 'original_user': getattr(request, 'original_user', None), - 'disguise_user': getattr(request, 'user'), - }, RequestContext(request)) - - # Insert this code before - response.content = replace_insensitive( - smart_text(response.content), # Subject - TAGNAME, # Search - smart_text(html + TAGNAME) # Replace - ) - return response diff --git a/disguise/models.py b/disguise/models.py index 884fd8d..fb79cea 100644 --- a/disguise/models.py +++ b/disguise/models.py @@ -1,26 +1,31 @@ +# coding: utf-8 + +from __future__ import unicode_literals +from __future__ import print_function +from __future__ import absolute_import +from __future__ import division from django.contrib.auth.models import Permission from django.contrib.contenttypes.models import ContentType from django.db.models.signals import post_save, post_syncdb +from django.utils.translation import ugettext_lazy as _ +from disguise.compat import get_user_model def create_perms(sender, **kwargs): perms = ( - ('can_disguise', 'Can disguise'), + ('can_disguise', _('Can disguise')), ) - # create a content type for the app if it doesn't already exist - content_type, created = ContentType.objects.get_or_create( - model='', - app_label='disguise', - defaults={'name': 'disguise'}) + content_type = ContentType.objects.get_for_model(get_user_model()) + for codename, title in perms: - # create a permission if it doesn't already exist - Permission.objects.get_or_create(codename=codename, - content_type__pk=content_type.id, - defaults={ - 'name': title, - 'content_type': content_type - }) + Permission.objects.get_or_create( + codename=codename, + content_type=content_type, + defaults={ + 'name': title, + }) + post_save.connect(create_perms, Permission) post_syncdb.connect(create_perms, sender=__import__('disguise')) diff --git a/disguise/signals.py b/disguise/signals.py new file mode 100644 index 0000000..6daf5e2 --- /dev/null +++ b/disguise/signals.py @@ -0,0 +1,10 @@ +# coding: utf-8 + +from __future__ import unicode_literals +from __future__ import print_function +from __future__ import absolute_import +from __future__ import division +from django.core.signals import Signal + +disguise_applied = Signal(providing_args=['original_user', 'new_user']) +disguise_disapplied = Signal(providing_args=['original_user', 'old_user']) diff --git a/disguise/static/disguise/js/disguise-angular.js b/disguise/static/disguise/js/disguise-angular.js new file mode 100644 index 0000000..0707895 --- /dev/null +++ b/disguise/static/disguise/js/disguise-angular.js @@ -0,0 +1,11 @@ +(function(angular, undefined){ + "use strict"; + angular + .module("ngDjangoDisguise", []) + .directive("disguise", { + restrict: "A", + scope: true, + function($scope, element, attrs, ctrl) { + } + }) +})(angular) diff --git a/disguise/static/disguise/js/disguise-jquery.js b/disguise/static/disguise/js/disguise-jquery.js new file mode 100644 index 0000000..ee1ce49 --- /dev/null +++ b/disguise/static/disguise/js/disguise-jquery.js @@ -0,0 +1,5 @@ +(function($, undefined){ + "use strict"; + $(document).on('#disguise', 'submit', function(e){ + }); +})((django ? django.jQuery : false) || jQuery); diff --git a/disguise/templates/disguise/form.html b/disguise/templates/disguise/form.html index 061cff5..5bc2eb3 100644 --- a/disguise/templates/disguise/form.html +++ b/disguise/templates/disguise/form.html @@ -1,38 +1,46 @@ {% load i18n %} -{% load url from future %} -