Package: django-visitors
Django VisitorsDjango app for managing temporary session-based users.BackgroundThis package has been extracted out ofdjango-request-tokenas a specific use
case - that of temporary site "visitors". It enables a type of ephemeral user
who is neither anonymous nor authenticated, but somewhere in between - known for
the duration of their session.MotivationWe've been usingdjango-request-tokenfor a while, and have issued over
100,000 tokens. A recent analysis showed two main use cases - single-use "magic
links" for logging people in, and a more involved case where we invite
unregistered users on to the platform to perform some action - providing a
reference perhaps, or collaborating on something with (registered) users. The
former we have extracted out intodjango-magic-links- and this package
addresses the latter.What is a "visitor"?In the standard Django model you have the concept of anAnonymousUser, and an
authenticatedUser- someone who has logged in. We have a third, intermediate,
type of user - which we have historically referred to as a "Temp User", which is
someone we knowof, but who has not yet registered.The canonical example of this is leaving a reference: user A on the site invites
user B to leave a reference for them. They (A) give us B's name and email, we
invite them to click on a link and fill out a form. That's it. We store their
name and email so that we can contact them, but it's ephemeral - we don't need
it, and we don't use it. Storing this data in the User table made sense (as it
has name and email fields), but it led to a lot ofuser_type=TEMPmunging to
determine who is a 'real' user on the site.What we really want is to 'stash' this information somewhere outside of the auth
system, and to enable these temp users to have restricted access to specific
areas of the application, for a limited period, after which we can forget about
them and clear out the data.We call these users "visitors".Use Case - request a referenceFred is a registered user on the site, and would like a reference from Ginger,
his dance partner.Fred fills out the reference request form:Name: Ginger
   Email: ginger@[...].com
   Message: ...
   Scope: REFERENCE_REQUEST [hidden field]We save this information, and generate a unique link which we send to Ginger,
along with the message.Ginger clicks on the link, at which point we recognise that this is someone
we know about - a "visitor" - but who is in all other respects anAnonymousUser.We stash the visitor info in the standard session object - so that even
though Ginger is not authenticated, we know who she is, and more importantly
we know why she's here (REFERENCE_REQUEST).Ginger submits the reference - which may be a multi-step process, involving
GETs and POSTs, all of which are guarded by a decorator that restricts access
to visitors with the appropriate Scope (just likedjango-request-token).At the final step we can (optionally) choose to clear the session info
immediately, effectively removing all further access.ImplementationThis code has been extracted out ofdjango-request-tokenand simplified. It
stores the visitor data in theVisitormodel, and on each successful first
request (where the token is 'processed' and the session filled) we record aVisitorLogrecord. This includes HTTP request info (session_key, referer,
client IP, user-agent). This information is for analytics only - for instance
determining whether links are being shared.ConfigurationDjango SettingsAddvisitorstoINSTALLED_APPSAddvisitors.middleware.VisitorRequestMiddlewaretoMIDDLEWAREAddvisitors.middleware.VisitorSessionMiddlewaretoMIDDLEWAREEnvironment SettingsVISITOR_SESSION_KEY: session key used to stash visitor info
("visitor:session")VISITOR_QUERYSTRING_KEY: querystring param used on tokenised links ("vid")UsageOnce you have the package configured, you should use theallow_visitordecorator to protect views that you want to restricted to visitors only. The
decorator requires ascopekwarg, which must match theVisitor.scopevalue
set when theVisitorobject is created.@allow_visitor(scope="foo")defprotected_view(request):passBy default the decorator will allow visitors with the correct scope, deny
anonymous users, and also allow authenticated users. If you want more control
over the access, you can pass a callable as thebypassparam:# prevent authenticated users from bypassing the check@allow_visitor(scope="foo",bypass=lambdau:False)defdeny_authenticated_users(request):passIf you don't care about the scope (you should), you can use"*"to allow all
visitors access:@allow_visitor(scope="*")defallow_all_scopes(request):pass