Search code examples
djangodjango-viewsdjango-sessions

Django: How to exclude a View from session creation?


My Django Project heavily relies on sessions, and the Framework does a very good job with this. It manages Session Cookies, the Session Database and so on, mostly done through the SessionMiddleware.

But there a some Views that explicitly do not require a session. And those Views often appear as an entry to the page.

Can I exclude these Views from the SessionMiddleware creating new sessions? If a new (anonymous) user visits such a View and leaves, there is no need for setting a Cookie or creating a database record in the sessions table.


Solution

  • I've gone the "Adapt the Middleware" way, as it looks like a session is created only on an access to the session or - in case of totally new sessions - during handling the response in the SessionMiddleware.

    So I created this Middleware, that extends the SessionMiddleware:

    from django.contrib.sessions.middleware import SessionMiddleware
    from django.http import HttpRequest
    
    
    class ConditionalSessionMiddleware(SessionMiddleware):
    
        def process_request(self, request: HttpRequest):
            # Default: We need a Session
            setattr(request, '_conditional_session_middleware_session_required', True)
            return super(ConditionalSessionMiddleware, self).process_request(request)
    
        def process_response(self, request: HttpRequest, response):
            # Is still a Session required?
            required = getattr(request, '_conditional_session_middleware_session_required', True)
            # or is the user not anonymous?
            if required or not request.user.is_anonymous:
                return super(ConditionalSessionMiddleware, self).process_response(request, response)
            else:
                return response
    

    To use it, I created a simple function decorator:

    from types import MethodType
    
    
    class NoSessionUsed:
    
        def __init__(self, f):
            self.__func = f
    
        def __call__(self, *args, **kwargs):
            for a in args:
                if isinstance(a, HttpRequest):
                    setattr(a, '_conditional_session_middleware_session_required', False)
                    break
            return self.__func(*args, **kwargs)
    
        def __get__(self, instance, owner):
            if instance:
                return MethodType(self, instance)
            else:
                return self
    

    In the class-based views, I use the decorator on the dispatch method:

    @NoSessionUsed
    def dispatch(self, request, *args, **kwargs):
        return super(MyClassBasedView, self).dispatch(request, *args, **kwargs)
    

    You can also use it in urls.py, e.g. for Sitemaps and Feeds, where you have no direct access to the Views:

    urlpatterns = [
        path('rss.xml', NoSessionUsed(RssFeed()), name='rss'),
        path('atom.xml', NoSessionUsed(AtomFeed()), name='atom'),
        path('sitemap.xml', NoSessionUsed(views.index), {
            'sitemaps': SITEMAPS,
            'sitemap_url_name': 'sitemaps:sitemap.section'
        }, name='sitemap'),
        path('sitemaps/sitemap-<section>.xml', NoSessionUsed(views.sitemap), {
            'sitemaps': SITEMAPS
        }, name='sitemap.section'),
    ]
    

    To activate the new Middleware, I replaced the SessionMiddleware in the settings.py MIDDLEWARE section with the new ConditionalSessionMiddleware.

    Of course there are downsides:

    • When I use sessions in the decorated view, changes will most likely not be saved
    • When I use new sessions in the decorated view, the user will not get a Session Cookie

    This might not be the perfect solution, but it's quite simple and works for me.