How to customize third-party package in django - django

I couldn't find anywhere an explicit guide that will show what is the correct way. For example, I am using a package django-two-factor-auth for my django website. I wanted to add a context variable to the template and display it. I created a folder two_factor inside of my templates folder and the template file with the same name. That part is easy. But I also needed to inherit generic view to add my context (of course, I don't want to change source code of the third-party package). For this I created a new app inside my project and called it two_factor_custom and added following code to views.py:
from binascii import unhexlify
from base64 import b32encode
from two_factor.views.core import SetupView
class SetupViewCustom(SetupView):
def get_context_data(self, form, **kwargs):
context = super(SetupViewCustom, self).get_context_data(form, **kwargs)
if self.steps.current == 'generator':
key = unhexlify(self.get_key('generator').encode('ascii'))
context.update({
'secret': b32encode(key).decode('ascii')
})
return context
I would appreciate if you could say that this is the correct way to extend or override some of the behaviour of third-party packages. If not what I am doing wrong?

You can extend the template context by adding your own template context processor. See also the Django documentation.
The TEMPLATE_CONTEXT_PROCESSORS setting is a tuple of callables – called context processors – that take a request object as their argument and return a dictionary of items to be merged into the context.

Related

Customizing Longclaw ProductIndex page

Longclaw/Wagtail newbie here. Wagtail CMS provides an overridable get_context method that makes it possible to pass dictionary values into the template. From the documentation:
class BlogIndexPage(Page):
...
def get_context(self, request):
context = super(BlogIndexPage, self).get_context(request)
# Add extra variables and return the updated context
context['blog_entries'] = BlogPage.objects.child_of(self).live()
return context
Longclaw is an eCommerce project built on top of Wagtail. Longclaw has an inbuilt Page model called ProductIndex. Is there any way to pass variables into the ProductIndex template, like I can with get_context?
Great question. It does not currently support this, but it would be great if it could. I recommend raising an issue on GitHub so we can't get this into development (https://github.com/JamesRamm/longclaw)
It may be possible to dynamically add the function;
def get_context(...):
....
ProductIndex.get_context = get_context
Ive not tried it so can't say for certain that it will work!

Add extra context variables into django oscar emails

Trying to add extra context variables for all django oscar email templates. The only way I've got it to work is overriding specific views like ProfileUpdateView. This method seems very messy, I'd have to override a lot of files. Is there a better way of doing this?
From checking the source code, the ProfileUpdateView uses Django's Class Based View FormView, which in turn implements the get_context_data method allowing the injection of extra data in the view.
You can simply create a view inehiriting ProfileUpdateView and override the get_context_data:
class MyProfileUpdateView(ProfileUpdateView):
...
def get_context_data(self, **kwargs):
context = super(MyProfileUpdateView, self).get_context_data(**kwargs)
context['somevar'] = SomeQueryMaybe.objects.all()
return context
Ended up making a custom management command to do it manually since the need to change the emails templates would be really rare.

Passing request to custom Django template loader

I want to write custom template loader for my Django app which looks for a specific folder based on a key that is part of the request.
Let me get into more details to be clear. Assume that I will be getting a key on every request(which I populate using a middleware).
Example: request.key could be 'india' or 'usa' or 'uk'.
I want my template loader to look for the template "templates/<key>/<template.html>". So when I say {% include "home.html" %}, I want the template loader to load "templates/india/home.html" or "templates/usa/home.html" or "templates/uk/home.html" based on the request.
Is there a way to pass the request object to a custom template loader?
I've been searching for the same solution and, after a couple days of searching, decided to use threading.local(). Simply make the request object global for the duration of the HTTP request processing! Commence rotten tomato throwing from the gallery.
Let me explain:
As of Django 1.8 (according to the development version docs) the "dirs" argument for all template finding functions will be deprecated. (ref)
This means that there are no arguments passed into a custom template loader other than the template name being requested and the list of template directories. If you want to access paramters in the request URL (or even the session information) you'll have to "reach out" into some other storage mechanism.
import threading
_local = threading.local()
class CustomMiddleware:
def process_request(self, request):
_local.request = request
def load_template_source(template_name, template_dirs=None):
if _local.request:
# Get the request URL and work your magic here!
pass
In my case it wasn't the request object (directly) I was after but rather what site (I'm developing a SaaS solution) the template should be rendered for.
To find the template to render Django uses the get_template method which only gets the template_name and optional dirs argument. So you cannot really pass the request there.
However, if you customize your render_to_response function to pass along a dirs argument you should be able to do it.
For example (assuming you are using a RequestContext as most people would):
from django import shortcuts
from django.conf import settings
def render_to_response(template_name, dictionary=None, context_instance=None, content_type=None, dirs):
assert context_instance, 'This method requires a `RequestContext` instance to function'
if not dirs:
dirs = []
dirs.append(os.path.join(settings.BASE_TEMPLATE_DIR, context_instance['request'].key)
return shortcuts.render_to_response(template_name, dictionary, context_instance, content_type, dirs)

Django - Extra context in django-registration activation email

I'm using django-registration for a project of mine.
I'd like to add some extra contextual data to the template used for email activation.
Looking into the register view source, I cannot figure out how to do it.
Any idea ?
From what I remember, you need to write your own registration backend object (easier then is sounds) as well as your own profile model that inherits from RegistrationProfile and make the backend use your custom RegistrationProfile instead (This model is where the email templates are rendered and there is no way to extend the context, so they need to be overwritten)
A simple solution is to rewrite the send_activation_email
So instead of
registration_profile.send_activation_email(site)
I wrote this in my Users model
def send_activation_email(self, registration_profile):
ctx_dict = {
'activation_key': registration_profile.activation_key,
'expiration_days': settings.ACCOUNT_ACTIVATION_DAYS,
'OTHER_CONTEXT': 'your own context'
}
subject = render_to_string('registration/activation_email_subject.txt',
ctx_dict)
subject = ''.join(subject.splitlines())
message = render_to_string('registration/activation_email.txt',
ctx_dict)
self.email_user(subject, message, settings.DEFAULT_FROM_EMAIL)
And I call it like this
user.send_activation_email(registration_profile)
I don't get what it is your problem but the parameter is just in the code you link (the last one):
def register(request, backend, success_url=None, form_class=None,
disallowed_url='registration_disallowed',
template_name='registration/registration_form.html',
extra_context=None)
That means you can do it from wherever you are calling the method. Let's say your urls.py:
from registration.views import register
(...)
url(r'/registration/^$', register(extra_context={'value-1':'foo', 'value-2':'boo'})), name='registration_access')
That's in urls.py, where usually people ask more, but, of course, it could be from any other file you are calling the method.

how to pass variables to templates only for some applications

I have a django project, with a lot of applications. Now i need to add some variables to the context dictionary only for some applications.Does django provide any option to achieve this?
I know decorator can be helpful.Is there any other way,like a middleware / context processor that runs automatically but only for specific applications.
A context processor like this can do what you need:
from django.core.urlresolvers import resolve
def app_var(request):
if resolve(request.path).app_name == 'YOUR_APP_NAME':
return {'CUSTOM_VAR': 'VALUE'}
return {}
to install the context processor, put the code in any file you like, and add a entry referencing (e.g. 'folder.file.app_var') it in the CONTEXT_PROCESSORS in your settings.py.