Re-adding README.rst. Forgot it's required by pypi. Doh!
3 files changed, 510 insertions(+), 2 deletions(-)

M README.md
A => README.rst
M setup.py
M README.md +1 -1
@@ 3,7 3,7 @@ 
 
 Simple application to allow superusers to "impersonate" other non-superuser accounts.
 
-**Version:** 1.5
+**Version:** 1.5.1
 
 **Author:** Peter Sanchez (<http://www.petersanchez.com>)
 

          
A => README.rst +508 -0
@@ 0,0 1,508 @@ 
+django-impersonate |nlshield|
+=============================
+
+:Info: Simple application to allow superusers to "impersonate" other non-superuser accounts.
+:Version: 1.5.1
+:Author: Peter Sanchez (http://www.petersanchez.com)
+
+Python / Django Support
+=======================
+
+-  Python 3.6+ for Django versions 1.11, 2.2 and 3.0
+
+**Note:** As of version 1.4 we are only officially supporting Python
+3.6+ and following the Django support schedule. Meaning we can only
+guarantee support for the Django versions actively supported by the
+Django project. However, older versions of Python and Django should
+continue to work. We just will not guarantee it.
+
+Dependencies
+============
+
+-  Depends on your project using the django.contrib.session framework.
+
+**NOTE:**
+
+-  **Version 1.5 is now only officially supporting Django's 1.11, 2.2,
+   and 3.0**
+-  **Version 1.4 is now officially supporting Python 3.6+ and Django
+   1.11+**
+-  **Version 1.3 adds a settings format change.** Please see the
+   Settings section for details.
+-  **Version 1.0 adds new functionality by default.** Please see the
+   ``DISABLE_LOGGING`` settings option.
+-  If you need to use this with Django older than 1.8, please use
+   version django-impersonate == 1.0.1
+-  If you need to use this with Django older than 1.7, please use
+   version django-impersonate == 0.9.2
+-  **Version 0.9.2 partially reverts work completed in version 0.9.1.**
+   This is because work done to address a request in `Issue
+   #17 <https://bitbucket.org/petersanchez/django-impersonate/issues/17/remember-where-to-return-to-after>`__
+   broke default behavior for all previous versions. `Issue
+   #24 <https://bitbucket.org/petersanchez/django-impersonate/issues/24/impersonate_redirect_url-no-longer-works>`__
+   was opened and the fix was released in 0.9.2 to address it. Please
+   see the new ``USE_HTTP_REFERER`` settings option.
+-  If you need to use this with Django older than 1.4, please use
+   version django-impersonate == 0.5.3
+
+Installation
+============
+
+PIP:
+
+::
+
+    pip install django-impersonate
+
+Basic Manual Install:
+
+::
+
+    $ python setup.py build
+    $ sudo python setup.py install
+
+Alternative Install (Manually):
+
+Place impersonate directory in your Python path. Either in your Python
+installs site-packages directory or set your $PYTHONPATH environment
+variable to include a directory where the impersonate directory lives.
+
+Use
+===
+
+1. Add ``impersonate`` to your INSTALLED\_APPS
+2. Add ``impersonate.middleware.ImpersonateMiddleware`` to your
+   ``MIDDLEWARE`` setting.
+3. Add ``impersonate.urls`` somewhere in your url structure. Example:
+
+   ::
+
+       urlpatterns = patterns('',
+           url(r'^admin/', include(admin.site.urls)),
+           url(r'^impersonate/', include('impersonate.urls')),
+           ... (all your other urls here) ...
+       )
+
+**Note:** The ``ImpersonationMiddleware`` class should be placed AFTER
+the ``django.contrib.auth.*`` middleware classes
+
+Functionality
+=============
+
+**You can now impersonate another user by hitting the following path:**
+
+::
+
+    /impersonate/<user-id>/
+
+Replace <user-id> with the user id of the user you want to impersonate.
+
+While in impersonation "mode" the ``request.user`` object will have an
+``is_impersonate`` attribute set to ``True``. So if you wanted to check
+in your templates or view, you just do something like...:
+
+::
+
+    {% if user.is_impersonate %} .... {% endif %}
+
+The original user is available as ``request.impersonator``:
+
+::
+
+    {{ request.user }} ({{ request.impersonator }})
+
+The real user is available as ``request.real_user`` - this is equivalent
+to calling ``getattr(request, 'impersonator', request.user)``:
+
+::
+
+    assert request.real_user == getattr(request, 'impersonator', request.user)
+
+You can reference this URL with ``reverse`` or the ``{% url %}``
+template tag as ``impersonate-start`` and expects the argument of the
+user ID. Example:
+
+::
+
+    reverse('impersonate-start', args=[user.id])
+    reverse('impersonate-start', uid=user.id)
+
+**To remove the impersonation, hit the following path:**
+
+::
+
+    /impersonate/stop/
+
+You can reference this URL with ``reverse`` or the ``{% url %}``
+template tag as ``impersonate-stop``. When you call this URL, you will
+be redirected to the page that you used to start impersonating a user
+(eg, some search results or the user list).
+
+**To list all users you can go to:**
+
+::
+
+    /impersonate/list/
+
+This will render the template ``impersonate/list_users.html`` and will
+pass the following in the context:
+
+-  ``users`` - queryset of all users
+-  ``paginator`` - Django Paginator instance
+-  ``page`` - Current page of objects (from Paginator)
+-  ``page_number`` - Current page number, defaults to 1
+
+You can reference this URL with ``reverse`` or the ``{% url %}``
+template tag as ``impersonate-list``.
+
+**To search all users you can go to:**
+
+::
+
+    /impersonate/search/
+
+This will render the template 'impersonate/search\_users.html' and will
+pass the following in the context:
+
+-  ``users`` - queryset of all users
+-  ``paginator`` - Django Paginator instance
+-  ``page`` - Current page of objects (from Paginator)
+-  ``page_number`` - Current page number, defaults to 1
+-  ``query`` - The search query that was entered
+
+The view will expect a GET request and look for the ``q`` variable being
+passed. If present, it will search the user entries with the value of
+``q``. The default fields searched are:
+
+``User.username``, ``User.first_name``, ``User.last_name``,
+``User.email``
+
+You can reference this URL with ``reverse`` or the ``{% url %}``
+template tag as ``impersonate-search``.
+
+**To allow some users to impersonate other users**
+
+You can optionally allow only some non-superuser and non-staff users to
+impersonate by adding a ``CUSTOM_ALLOW`` setting option. Create a
+function that takes a request object, and based on your rules, returns
+``True`` if the user is allowed to impersonate or not.
+
+**To limit what users a user can impersonate**
+
+By, optionally, setting the ``CUSTOM_USER_QUERYSET`` option you can
+control what users can be impersonated. It takes a request object of the
+user, and returns a QuerySet of users. This is used when searching for
+users to impersonate, when listing what users to impersonate, and when
+trying to start impersonation.
+
+Signals
+=======
+
+If you wish to hook into the impersonation session (for instance, in
+order to audit access), there are two signals that are fired by
+django-impersonate, at the beginning and end of a session:
+
+-  ``session_begin`` - sent when calling the ``impersonate`` view
+-  ``session_end`` - sent when calling the ``stop_impersonate`` view
+
+Both of these signals send the same arguments:
+
+-  ``sender`` - this is a Django signal requirement, and is always set
+   to None
+-  ``impersonator`` - a reference to the User object of the person doing
+   the impersonation
+-  ``impersonating`` - a reference to the User object of the person
+   being impersonated
+-  ``request`` - the Django HttpRequest object from which the
+   impersonation was invoked
+
+The request object is included as it contains pertinent information that
+you may wish to audit - such as client IP address, user-agent string,
+etc.
+
+For an example of how to hook up the signals, see the relevant test -
+``test_successful_impersonation_signals``.
+
+The ``session_end`` signal will only be fired if the impersonator
+explicitly ends the session.
+
+Settings
+========
+
+The following settings are available for django-impersonate. All
+settings should be set as variables in a dictionary assigned to the
+attribute named ``IMPERSONATE``.
+
+For example:
+
+::
+
+    IMPERSONATE = {
+        'REDIRECT_URL': '/some-path/',
+        'PAGINATE_COUNT': 10,
+    }
+
+Here are the options available...
+
+::
+
+    REDIRECT_URL
+
+This is the URL you want to be redirected to *after* you have chosen to
+impersonate another user. If this is not present it will check for the
+LOGIN\_REDIRECT\_URL setting and fall back to '/' if neither is present.
+Value should be a string containing the redirect path.
+
+::
+
+    USE_HTTP_REFERER
+
+If this is set to ``True``, then the app will attempt to be redirect you
+to the URL you were at when the impersonation began once you have
+*stopped* the impersonation. For example, if you were at the url
+'/foo/bar/' when you began impersonating a user, once you end the
+impersonation, you will be redirected back to '/foo/bar/' instead of the
+value in ``REDIRECT_URL``.
+
+Value should be a boolean, defaults to ``False``
+
+::
+
+    PAGINATE_COUNT
+
+This is the number of users to paginate by when using the list or search
+views. This defaults to 20. Value should be an integer.
+
+::
+
+    REQUIRE_SUPERUSER
+
+If this is set to ``True``, then only users who have ``is_superuser``
+set to ``True`` will be allowed to impersonate other users. Default is
+``False``. If ``False``, then any ``is_staff`` user will be able to
+impersonate other users.
+
+**Note:** Regardless of this setting, a ``is_staff`` user will **not**
+be allowed to impersonate a ``is_superuser`` user.
+
+Value should be a boolean
+
+If the ``CUSTOM_ALLOW`` is set, then that custom function is used, and
+this setting is ignored.
+
+::
+
+    ALLOW_SUPERUSER
+
+By default, superusers cannot be impersonated; this setting allows for
+that.
+
+**Note:** Even when this is true, only superusers can impersonate other
+superusers, regardless of the value of REQUIRE\_SUPERUSER.
+
+Value should be a boolean and the default is ``False``.
+
+::
+
+    URI_EXCLUSIONS
+
+Set to a list/tuple of url patterns that, if matched, user impersonation
+is not completed. It defaults to:
+
+::
+
+    (r'^admin/',)
+
+If you do not want to use even the default exclusions then set the
+setting to an empty list/tuple.
+
+::
+
+    CUSTOM_USER_QUERYSET
+
+A string that represents a function (e.g.
+``module.submodule.mod.function_name``) that allows more fine grained
+control over what users a user can impersonate. It takes one argument,
+the request object, and should return a QuerySet. Only the users in this
+queryset can be impersonated.
+
+This function will not be called when the request has an unauthorised
+users, and will only be called when the user is allowed to impersonate
+(cf. ``REQUIRE_SUPERUSER`` and ``CUSTOM_ALLOW``).
+
+Regardless of what this function returns, a user cannot impersonate a
+superuser, even if there are superusers in the returned QuerySet.
+
+It is optional, and if it is not present, the user can impersonate any
+user (i.e. the default is ``User.objects.all()``).
+
+::
+
+    CUSTOM_ALLOW
+
+A string that represents a function (e.g.
+``module.submodule.mod.function_name``) that allows more fine grained
+control over who can use the impersonation. It takes one argument, the
+request object, and should return True to allow impersonation.
+Regardless of this setting, the user must be logged in to impersonate.
+If this setting is used, ``REQUIRE_SUPERUSER`` is ignored.
+
+It is optional, and if it is not present, the previous rules about
+superuser and ``REQUIRE_SUPERUSER`` apply.
+
+::
+
+    REDIRECT_FIELD_NAME
+
+A string that represents the name of a request (GET) parameter which
+contains the URL to redirect to after impersonating a user. This can be
+used to redirect to a custom page after impersonating a user. Example:
+
+::
+
+    # in settings.py
+    IMPERSONATE = {'REDIRECT_FIELD_NAME': 'next'}
+
+    # in your template
+    <a href="{% url 'impersonate-list' %}?next=/some/url/">switch user</a>
+
+To return always to the current page after impersonating a user, use
+request.path:
+
+::
+
+    `<a href="{% url 'impersonate-list' %}?next={{request.path}}">switch user</a>`
+
+Each use case is different so obviously set the next value to whatever
+your case requires.
+
+::
+
+    SEARCH_FIELDS
+
+Array of user model fields used for building searching query. Default
+value is [``User.USERNAME_FIELD``, ``first_name``, ``last_name``,
+``email``]. If the User model doesn't have the ``USERNAME_FIELD``
+attribute, it falls back to 'username' (< Django 1.5).
+
+::
+
+    LOOKUP_TYPE
+
+A string that represents SQL lookup type for searching users by query on
+fields above. It is ``icontains`` by default.
+
+::
+
+    DISABLE_LOGGING
+
+A boolean that can be used to disable the logging of impersonation
+sessions. By default each impersonation ``session_begin`` signal will
+create a new ``ImpersonationLog`` object, which is closed out (duration
+calculated) at the corresponding ``session_end`` signal.
+
+It is optional, and defaults to False (i.e. logging is enabled).
+
+::
+
+    MAX_FILTER_SIZE
+
+The max number of items acceptable in the admin list filters. If the
+number of items exceeds this, then the filter is removed (just shows
+all). This is used by the "Filter by impersonator" filter.
+
+It is optional, and defaults to 100.
+
+Admin
+=====
+
+As of version 1.3 django-impersonate now includes a helper admin mixin,
+located at ``impersonate.admin.UserAdminImpersonateMixin``, to include
+in your User model's ModelAdmin. This provides a direct link to
+impersonate users from your user model's Django admin list view. Using
+it is very simple, however if you're using the default
+``django.contrib.auth.models.User`` model you will need to unregister
+the old ModelAdmin before registering your own.
+
+The ``UserAdminImpersonateMixin`` has a attribute named
+``open_new_window`` that **defaults to ``False``**. If this is set to
+True a new window will be opened to start the new impersonation session
+when clicking the impersonate link directly in the admin.
+
+Here's an example:
+
+::
+
+    # yourapp/admin.py
+    from django.contrib import admin
+    from django.contrib.auth.models import User
+    from django.contrib.auth.admin import UserAdmin
+    from impersonate.admin import UserAdminImpersonateMixin
+
+
+    class NewUserAdmin(UserAdminImpersonateMixin, UserAdmin):
+        open_new_window = True
+        pass
+
+    admin.site.unregister(User)
+    admin.site.register(User, NewUserAdmin)
+
+Testing
+=======
+
+From the repo checkout, ensure you have Django in your ``PYTHONPATH``
+and run:
+
+::
+
+    $ python runtests.py
+
+To get test coverage, use:
+
+::
+
+    $ coverage run --branch runtests.py
+    $ coverage html  <- Pretty HTML files for you
+    $ coverage report -m  <- Ascii report
+
+If you're bored and want to test all the supported environments, you'll
+need tox.:
+
+::
+
+    $ pip install tox
+    $ tox
+
+And you should see:
+
+::
+
+    py36-django1.11: commands succeeded
+    py36-django2.2: commands succeeded
+    py36-django3.0: commands succeeded
+    py37-django1.11: commands succeeded
+    py37-django2.2: commands succeeded
+    py37-django3.0: commands succeeded
+    py38-django1.11: commands succeeded
+    py38-django2.2: commands succeeded
+    py38-django3.0: commands succeeded
+    congratulations :)
+
+Copyright & Warranty
+====================
+
+All documentation, libraries, and sample code are Copyright 2011 Peter
+Sanchez <petersanchez@gmail.com>. The library and sample code are made
+available to you under the terms of the BSD license which is contained
+in the included file, BSD-LICENSE.
+
+Commercial Support
+------------------
+
+This software, and lots of other software like it, has been built in
+support of many of Netlandish's own projects, and the projects of our
+clients. We would love to help you on your next project so get in touch
+by dropping us a note at hello@netlandish.com.
+
+.. |nlshield| image:: https://img.shields.io/badge/100%25-Netlandish-blue.svg?style=square-flat
+   :target: http://www.netlandish.com

          
M setup.py +1 -1
@@ 2,7 2,7 @@ import os
 from distutils.core import setup
 
 project_name = 'impersonate'
-long_description = open('README.md').read()
+long_description = open('README.rst').read()
 
 # Idea from django-registration setup.py
 packages, data_files = [], []