Search code examples
djangodjango-templatesdjango-allauthbidi

django-allauth change templates dynamically (BIDI support)


I'm trying to add support for arabic languages to a site I'm working on, so currently my templates are like

/templates
    /accounts/
    /includes/
    /rtl
        /accounts/
        /includes/
        ...
    ...

django-allauth will try to load the templates from /templates/accounts no matter what (it's hardcoded in their views) but I want to load the RTL (Right To Left) version based on a context variable when necessary so I thought about four solutions, but none of them seem to be good enough to me (I'm not saying that I won't do them, I'm saying that I can't work out a better way, if there is one)

  1. Set a conditional in my overrides to load the LTR or RTL versions (that will require the conditional in /templates/accounts, a LTR version with the template somewhere else and the RTL version in /templates/rtl/accounts.

  2. Create a template tag which has a parameter with the template name and loads the template dynamically, this looks like a waste of resources

  3. Create a mess in the main templates with tons of logic so it switches between LTR and RTL when needed (that will need tons of logic in the template, which is not good)

  4. Fork allauth into my project and add the logic to the views. I really don't want to do this, because it will make maintenance a hell in the future.

I'm not using the standard django i18n, so I can't use the BIDI settings.

Anyone has a better approach to this?


Solution

  • Solved it, in the end I had to use a custom template loader to change the template directoy on the fly at the moment of the request. I followed a really useful tutorial from the guys at the washington times: http://opensource.washingtontimes.com/blog/2010/feb/17/loading-templates-based-request-headers-django/

    I don't find really fancy the idea of creating a local thread to store the request (in my case a context value) but it seems that's the only way to actually pass data to the template loader.

    My code:

    < projectname >/settings/defaults.py

    We load our template loader FIRST, so we can process the data before django loads anything. If something fails in our code, it will fallback to the default django loaders.

    TEMPLATE_LOADERS = (
        'projectname.templateloaders.arabic.load_template_source',
        'django.template.loaders.filesystem.Loader',
        'django.template.loaders.app_directories.Loader',
        #'django.template.loaders.eggs.Loader',
    )
    

    < projectname >/middleware/templaterequest.py

    # -*- coding: utf-8 -*-
    
    try:
        from threading import local
    except ImportError:
        from django.utils._threading_local import local
    
    from apps.locales.models import Locale
    from apps.markets.models import Market
    
    # for more info:
    # http://opensource.washingtontimes.com/blog/2010/feb/17/loading-templates-based-request-headers-django/
    
    _thread_locals = local()
    
    
    def get_current_request():
        return getattr(_thread_locals, 'arabic', None)
    
    
    class RequestMiddleware(object):
    
        """
        This middleware will store the market.rtl value at each request that is made.
        Basically here you can do anything that you can get from the request.
    
        In this case we get the locale and the market settings and extract the RTL
        value, which we store in a local thread in memory an will be retrieved
        later by the template loader when calling for get_current_request()
        """
        def process_request(self, request):
            site = request.META['HTTP_HOST']
            locale = Locale.objects.get(site=site)
            market = Market.objects.get(locale=locale)
    
            _thread_locals.arabic = market.rtl
    

    < projectname >/templateloaders/arabic.py

    # -*- coding: utf-8 -*-
    
    from django.conf import settings
    from django.template.loader import BaseLoader, TemplateDoesNotExist
    from django.utils._os import safe_join
    
    from tipx.middleware.templaterequest import get_current_request
    
    def get_template_sources(template_name, template_dirs=None):
    
        """
        This class will modify the template directory in case the market has
        RTL activated in the market settings. If RTL if False it will pass and
        let the standard django template loaders to work.
    
        Explanation of how it behaves (it's weird...) the request comes trough and
        hits first our code, tries to determine if the market is arabic or not.
        It it's arabic it changes the template directory to /rtl/, but for example
        third party templates are not there (except for the overrides), so it will
        continue processing through the template loaders until it finds the right
        template. This guarantees that no matter how twisted our template locations
        are, it will always load the right templates in the right moment even
        when the templates have includes from RTL to english.
        """
        arabic = get_current_request()
        if arabic:
            # Loop through the template dirs
            for directory in settings.TEMPLATE_DIRS:
                new_directory = directory + '/rtl/'
                yield safe_join(new_directory, template_name)
    
    
    def load_template_source(template_name, template_dirs=None):
        for filepath in get_template_sources(template_name, template_dirs):
            try:
                file = open(filepath)
                try:
                    return (file.read().decode(settings.FILE_CHARSET), filepath)
                finally:
                    file.close()
            except IOError:
                pass
        raise TemplateDoesNotExist(template_name)
    load_template_source.is_usable = True
    

    The last thing is to have our RTL templates folder. If any of your templates have includes, you will need to append the foldername that you have for your arabic version, example:

    Original include:

    {% include 'whatever/template.html' %}
    

    RTL include:

    {% include 'rtl/whatever/template' %}
    

    If someone finds this answer incomplete just tell me! :)