What is the right way to pass global template variables in Django? - django

Suppose this is what we want in every page, we'd create "base_template.html"
<title>{% block title %}{{ page_title }}{% endblock %}</title>
{{ page_title }}{% endblock %}
Instead of passing page_title, domain, current_path from every view function such as:
def display_meta(request):
user_meta = request.META.items()
sorted_meta = sorted(user_meta) # a list of tuples
return render_to_response('meta.html', {'sorted_meta': sorted_meta,
'current_path': request.get_full_path(),
'domain': request.get_host(),
'page_title': display_meta.__name__})
# and repeat the dictionary same manner for other views....
#urls.py
('^book_list/$', 'object_get_list', {'model': models.Book}),
A different approach is wrapping view functions
# urls.py
('^book_list/$', views.get_template(views.object_get_list),{'model': models.Book}),
# views.py
def get_template(view, **extrakw):
def wrapview(request, **extrakw):
template_dict = {'current_path': request.get_full_path(), 'domain': request.get_host()}
extrakw['template_dict'] = template_dict
return view(request, **extrakw)
return wrapview
def object_get_list(request, **kwargs):
model = kwargs.pop('model', None)
model_name = model.__name__.lower()
template_dict = kwargs.pop('template_dict', None)
template_dict[model_name] = model.objects.all()
template_dict['page_title'] = model_name +" list"
template_name = '%s.html' %(model_name)
return render_to_response(template_name, template_dict)
Pro: Besides editing htmls, now modification is done in just one view, instead of every view.
Cons: Ugly URLConf and probably error propne too
Attempt 3:
Create a global dictionary just like template_dict I created.
template_dict = {/..../}
def view1()
def view2() ...
Problem: I can't use request.path (or anything has to do with request). This falls back to the previous attempt (wrapper).
But there must be an easier way. What is the proper way of passing global template variables throughout a django site so each view function is now indepenednt of gloabl templat variables?
Thank you for you time.

Use a context processor.
Add the name of your function to TEMPLATE_CONTEXT_PROCESSORS in settings.py.
A simple context processor I use is:
def common_request_parameters(request):
return {'home_login_form': AuthenticationForm(request)}

Related

using CKEditor with Django and an inlineformset_factory with empty_form

When I render the empty form it is not attaching any media to it. i.e. the CKEditor is not displayed. The element looks like it is missing the css/js - it's like it doesn't get set up properly.
Note : the other sections are displayed correctly.
Where to start? Problem with Django's Empty Form method? Problem with CKEditor? Me :)
<div class="container">
<button type ="button" class="btn-info btn-lg" id="add_section">Add Section</button>
{{form.media }}
{{form|crispy }}
{{sections.media}}
<div>
{{sections.empty_form}}
</div>
<div id = 'section_management'> {{ sections.management_form }} </div>
{% for section in sections %}
{{ section|crispy }}
{% endfor %}
<button class="btn btn-info ml-2" type="submit">Update</button>
Cancel
</div>
Here's my Forms
class SectionForm(forms.ModelForm):
content = RichTextFormField()
class Meta:
model = Section
fields = ('content',)
empty_permitted=True
def __init__(self, *args, **kwargs):
print('section form called')
super().__init__(*args, **kwargs)
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = ('title','category','span')
def __init__(self, *args, **kwargs):
self.is_superuser = kwargs.pop('is_superuser', None)
super().__init__(*args, **kwargs)
if self.is_superuser == False:
self.fields.pop("span")
view code
class ArticleUpdateView(LoginRequiredMixin,UserPassesTestMixin,UpdateView):
template_name = 'articles/ArticleUpdate.html'
form_class = ArticleForm
model = Article
SectionFormSet = inlineformset_factory(Article, Section, form=SectionForm, extra=0, can_delete=False, fields=('content',))
#if i always pass back at least 1 extra section form, I can grab the html for it in Jquery */
#if i do not pass back extra=0 how would i get the html in jquery for the extra form?
def test_func(self):
article = self.get_object()
if self.request.user == article.author or self.request.user.is_superuser :
return True
else:
return False
def get_context_data(self, **kwargs):
print('get context data called update view')
'''
section_form
'''
context = super().get_context_data(**kwargs)
if self.request.POST:
context['sections'] = self.SectionFormSet(self.request.POST,instance=self.object)
else:
context['sections'] = self.SectionFormSet(instance=self.object)
return context
def get_section_form(self): #we know we can access this in the template
return SectionForm()
def save_sections(self):
print('save sections called update view')
try:
context = self.get_context_data()
section_form = context['sections']
if section_form.is_valid():
# section_form.instance = self.object #if im passing instance in the factory, do I need it here to?
section_form.save()
except Exception as e:
print('failed to save section: ' + str(e))
def form_valid(self, form):
print('form valid called update view')
form.instance.author = self.request.user
response = super().form_valid(form) #save article form
self.save_sections()
return response
def get_success_url(self):
return reverse_lazy('index')
Basically, what I've done so far to overcome this problem is by accessing the form directly from the template, bypassing the inlineFormSet to get an empty form....(hope that makes sense).
I go directly to the view :
{{view.get_section_form}}
with this method in the view
def get_section_form(self): #we know we can access this in the template
return SectionForm()
I have subsequently found out I can do this in the template as well :
{{sections.media}}
{{sections.form}}
The above also passes an empty form - with the media filled in- as long as you pass the model form into the factory to start of with.
These are work-arounds for me currently, but would appreciate a proper answer as to why empty_form doesn't work properly.
My further investigation into this was basically comparing what is returned via accessing the formset to return an empty form, or using the modelForm directly.
Django docs :
empty_formĀ¶
BaseFormSet provides an additional attribute empty_form which returns a form instance with a prefix of __prefix__ for easier use in dynamic forms with JavaScript.
If you replace prefix on the generated html -- everything works. No idea why. You can replace it with anything, i.e. prefix1
at which point CKEditor starts to display the formset correctly.

Django Admin Actions on single object

The admin actions seem to work on several items selected in the list view of django admin interface:
In my case I would like to have a simple action button on the change (one item) view.
Is there a way to make the django admin actions available there?
I know that I can walk around this problem by going to the list view, and select one item there. But it would be more nice to have it directly available.
Create a template for your model in your app.
templates/admin/<yourapp>/<yourmodel>/change_form.html
With this example content to add a button when changing an existing object.
{% extends "admin/change_form.html" %}
{% block submit_buttons_bottom %}
{{ block.super }}
{% if original %} {# Only show if changing #}
<div class="submit-row">
<a href="{% url 'custom-model-action' original.pk %}">
Another action
</a>
</div>
{% endif %}
{% endblock %}
Link that action to any url and redirect back to your model change object view. More information about extending admin templates.
Update: Added complete common use case for custom action on existing object
urls.py
urlpatterns = [
url(r'^custom_model_action/(?P<object_pk>\d+)/$',
core_views.custom_model_action, name='custom-model-action')
]
views.py
from django.urls import reverse
from django.contrib import messages
from django.http import HttpResponse, HttpResponseRedirect
def custom_model_action(request, object_pk):
messages.info(request, 'Performed custom action!')
return HttpResponseRedirect(
reverse('admin:<yourapp>_<yourmodel>_change', args=[object_pk])
)
If you realy need per-single object, I suggest you to use this solution, eg:
class Gallery(TimeStampedModel):
title = models.CharField(max_length=200)
attachment = models.FileField(upload_to='gallery/attachment/%Y/%m/%d')
def __str__(self):
return self.title
def process_button(self):
return ('<button id="%(id)s class="btn btn-default process_btn" '
'data-value="%(value)s>Process</button>' % {'id': self.pk, 'value': self.attachment.url})
process_button.short_description = 'Action'
process_button.allow_tags = True
In your admin.py, insert process_button into list_display;
class GalleryAdmin(admin.ModelAdmin):
list_display = ['title', 'process_button', 'created']
search_fields = ['title', 'pk']
....
class Media:
js = ('path/to/yourfile.js', )
Then, inside yourfile.js, you can also process it..
$('.process_btn').click(function(){
var id = $(this).attr('id'); // single object id
var value = $(this).data('value'); // single object value
...
});
Hope it helpful..
Not the same as the topic starter asked, but this snippet allows to have Single Object action from on the list page with minimum amount of code
BaseAction code
class AdminActionError(Exception):
pass
class AdminObjectAction:
"""Base class for Django Admin actions for single object"""
short_description = None
exp_obj_state = {}
def __init__(self, modeladmin, request, queryset):
self.admin = modeladmin
self.request = request
self.queryset = queryset
self.__call__()
def validate_qs(self):
count = self.queryset.count()
if count != 1:
self.error("You must select one object for this action.")
if self.exp_obj_state:
if self.queryset.filter(**self.exp_obj_state).count() != 1:
self.error(f'Selected object does not meet the requirements: {self.exp_obj_state}')
def error(self, msg):
raise AdminActionError(msg)
def get_object(self):
return self.queryset.get()
def process_object_action(self, obj):
pass
def validate_obj(self, obj):
pass
def __call__(self, *args, **kwargs):
try:
self.validate_qs()
obj = self.get_object()
self.validate_obj(obj)
except AdminActionError as e:
self.admin.message_user(self.request, f"Failed: {e}", level=messages.ERROR)
else:
with transaction.atomic():
result = self.process_object_action(obj)
self.admin.message_user(self.request, f"Success: {self.short_description}, {result}")
Custom Action [minimum amount of code]
class RenewSubscriptionAction(AdminObjectAction):
short_description = 'Renew subscription'
exp_obj_state = {
'child': None,
'active_status': True,
}
def process_object_action(self, obj):
manager = RenewManager(user=obj.user, subscription=obj)
return manager.process()
AdminClass
class SomeAdmin(admin.ModelAdmin):
actions = [RenewSubscriptionAction]
The built-in admin actions operate on a queryset.
You can use a calable for the action you whant or to show something else:
class ProductAdmin(admin.ModelAdmin):
list_display ('name' )
readonly_fields('detail_url)
def detail_url(self, instance):
url = reverse('product_detail', kwargs={'pk': instance.slug})
response = format_html("""{0}""", product_detail)
return response
or using forms
class ProductForm(forms.Form):
name = forms.Charfield()
def form_action(self, product, user):
return Product.value(
id=product.pk,
user= user,
.....
)
#admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
# render buttons and links to
def product_actions(self, obj):
return format_html(
'<a class="button" href="{}">Action1</a> '
'<a class="button" href="{}">Action 2</a>',
reverse('admin:product-action-1', args=[obj.pk]),
reverse('admin:aproduct-action-3', args=[obj.pk]),
)
for more details about using forms

What's the best way to render multiple views in the same template?

Scenario
I need to render 2 separate views from a third party app, in the same View. The views in question are for login and signup.
The template for each view then simply includes an inclusion tag to render a generic form.
Solution
The solution i've come up with is to register a tag for each view that creates a template.Node to render each one.
from django import template
from third_party_app import LoginView, SignupView
register = template.Library()
#register.tag
def login_form(parser, token):
return ViewNode(LoginView, template_name=get_template_name(token))
#register.tag
def signup_form(parser, token):
return ViewNode(SignupView, template_name=get_template_name(token))
class ViewNode(template.Node):
def __init__(self, view_class, **kwargs):
self.view_class = view_class
self.kwargs = kwargs
def render(self, context):
request = context['request']
self.kwargs['request'] = request
view = self.view_class(**self.kwargs)
response = view.get(request)
response.render()
return response.content
def get_template_name(token):
tag_name, template = token.split_contents()
return str(template[1:-1])
And the template for the main View looks like this:
<div>
{% login_form 'account/login.html' %}
</div>
... some other html ...
<div>
{% signup_form 'account/signup.html' %}
</div>
The template for each of the individual login and signup views only contains an inclusion tag to render another template for a generic form.
So accounts/login.html is simply this:
{% render_login_form %}
and the inclusion tag looks like this
#register.inclusion_tag('account/snippets/form.html', takes_context=True)
def render_login_form(context):
return {'form': context['form'],
'primary_btn_label': 'Sign In',
'secondary_btn_label': 'Forgot Password?',
'tertiary_btn_label': 'Sign Up?',
'col_offset': '3',
'col_width': '9'}
Question
It works, but i'm wondering 2 things.
Is this the best way to render 2 Views in the same View?
It feels like there are two many steps to achieve this. Is there a simpler way to solve this problem?
I have does something similar this way:
First two templates login.html and signup.html. In these templates I access context variables like form, primary_btn_label and other stuff you have in your render_login_form inclusion tag.
But then I have a custom context processor (See the Django documentation on context processors) that initializes these variables (like form and so on)
The context processor (save in myapp/context_processors.py) looks something like this:
from .forms import LoginForm, RegisterForm
def login_register_form(request):
login_popup_form = LoginFormForPopup()
register_popup_form = RegisterFormForPopup()
context = {
'login_popup_form': login_popup_form,
'register_popup_form': register_popup_form,
}
return context
The custom context processor is enabled in your Django settings file by adding it to the TEMPLATE_CONTEXT_PROCESSORS variable:
TEMPLATE_CONTEXT_PROCESSORS = (
...
'myapp.context_processors.login_register_form',
)
Hope this helps!

Django: Can class-based views accept two forms at a time?

If I have two forms:
class ContactForm(forms.Form):
name = forms.CharField()
message = forms.CharField(widget=forms.Textarea)
class SocialForm(forms.Form):
name = forms.CharField()
message = forms.CharField(widget=forms.Textarea)
and wanted to use a class based view, and send both forms to the template, is that even possible?
class TestView(FormView):
template_name = 'contact.html'
form_class = ContactForm
It seems the FormView can only accept one form at a time.
In function based view though I can easily send two forms to my template and retrieve the content of both within the request.POST back.
variables = {'contact_form':contact_form, 'social_form':social_form }
return render(request, 'discussion.html', variables)
Is this a limitation of using class based view (generic views)?
Many Thanks
Here's a scaleable solution. My starting point was this gist,
https://gist.github.com/michelts/1029336
i've enhanced that solution so that multiple forms can be displayed, but either all or an individual can be submitted
https://gist.github.com/jamesbrobb/748c47f46b9bd224b07f
and this is an example usage
class SignupLoginView(MultiFormsView):
template_name = 'public/my_login_signup_template.html'
form_classes = {'login': LoginForm,
'signup': SignupForm}
success_url = 'my/success/url'
def get_login_initial(self):
return {'email':'dave#dave.com'}
def get_signup_initial(self):
return {'email':'dave#dave.com'}
def get_context_data(self, **kwargs):
context = super(SignupLoginView, self).get_context_data(**kwargs)
context.update({"some_context_value": 'blah blah blah',
"some_other_context_value": 'blah'})
return context
def login_form_valid(self, form):
return form.login(self.request, redirect_url=self.get_success_url())
def signup_form_valid(self, form):
user = form.save(self.request)
return form.signup(self.request, user, self.get_success_url())
and the template looks like this
<form class="login" method="POST" action="{% url 'my_view' %}">
{% csrf_token %}
{{ forms.login.as_p }}
<button name='action' value='login' type="submit">Sign in</button>
</form>
<form class="signup" method="POST" action="{% url 'my_view' %}">
{% csrf_token %}
{{ forms.signup.as_p }}
<button name='action' value='signup' type="submit">Sign up</button>
</form>
An important thing to note on the template are the submit buttons. They have to have their 'name' attribute set to 'action' and their 'value' attribute must match the name given to the form in the 'form_classes' dict. This is used to determine which individual form has been submitted.
By default, class-based views only support a single form per view. But there are other ways to accomplish what you need. But again, this cannot handle both forms at the same time. This will also work with most of the class-based views as well as regular forms.
views.py
class MyClassView(UpdateView):
template_name = 'page.html'
form_class = myform1
second_form_class = myform2
success_url = '/'
def get_context_data(self, **kwargs):
context = super(MyClassView, self).get_context_data(**kwargs)
if 'form' not in context:
context['form'] = self.form_class(request=self.request)
if 'form2' not in context:
context['form2'] = self.second_form_class(request=self.request)
return context
def get_object(self):
return get_object_or_404(Model, pk=self.request.session['value_here'])
def form_invalid(self, **kwargs):
return self.render_to_response(self.get_context_data(**kwargs))
def post(self, request, *args, **kwargs):
self.object = self.get_object()
if 'form' in request.POST:
form_class = self.get_form_class()
form_name = 'form'
else:
form_class = self.second_form_class
form_name = 'form2'
form = self.get_form(form_class)
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(**{form_name: form})
template
<form method="post">
{% csrf_token %}
.........
<input type="submit" name="form" value="Submit" />
</form>
<form method="post">
{% csrf_token %}
.........
<input type="submit" name="form2" value="Submit" />
</form>
Its is possible for one class-based view to accept two forms at a time.
view.py
class TestView(FormView):
template_name = 'contact.html'
def get(self, request, *args, **kwargs):
contact_form = ContactForm()
contact_form.prefix = 'contact_form'
social_form = SocialForm()
social_form.prefix = 'social_form'
# Use RequestContext instead of render_to_response from 3.0
return self.render_to_response(self.get_context_data({'contact_form': contact_form, 'social_form': social_form}))
def post(self, request, *args, **kwargs):
contact_form = ContactForm(self.request.POST, prefix='contact_form')
social_form = SocialForm(self.request.POST, prefix='social_form ')
if contact_form.is_valid() and social_form.is_valid():
### do something
return HttpResponseRedirect(>>> redirect url <<<)
else:
return self.form_invalid(contact_form,social_form , **kwargs)
def form_invalid(self, contact_form, social_form, **kwargs):
contact_form.prefix='contact_form'
social_form.prefix='social_form'
return self.render_to_response(self.get_context_data({'contact_form': contact_form, 'social_form': social_form}))
forms.py
from django import forms
from models import Social, Contact
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit, Button, Layout, Field, Div
from crispy_forms.bootstrap import (FormActions)
class ContactForm(forms.ModelForm):
class Meta:
model = Contact
helper = FormHelper()
helper.form_tag = False
class SocialForm(forms.Form):
class Meta:
model = Social
helper = FormHelper()
helper.form_tag = False
HTML
Take one outer form class and set action as TestView Url
{% load crispy_forms_tags %}
<form action="/testview/" method="post">
<!----- render your forms here -->
{% crispy contact_form %}
{% crispy social_form%}
<input type='submit' value="Save" />
</form>
Good Luck
I have used a following generic view based on TemplateView:
def merge_dicts(x, y):
"""
Given two dicts, merge them into a new dict as a shallow copy.
"""
z = x.copy()
z.update(y)
return z
class MultipleFormView(TemplateView):
"""
View mixin that handles multiple forms / formsets.
After the successful data is inserted ``self.process_forms`` is called.
"""
form_classes = {}
def get_context_data(self, **kwargs):
context = super(MultipleFormView, self).get_context_data(**kwargs)
forms_initialized = {name: form(prefix=name)
for name, form in self.form_classes.items()}
return merge_dicts(context, forms_initialized)
def post(self, request):
forms_initialized = {
name: form(prefix=name, data=request.POST)
for name, form in self.form_classes.items()}
valid = all([form_class.is_valid()
for form_class in forms_initialized.values()])
if valid:
return self.process_forms(forms_initialized)
else:
context = merge_dicts(self.get_context_data(), forms_initialized)
return self.render_to_response(context)
def process_forms(self, form_instances):
raise NotImplemented
This has the advantage that it is reusable and all the validation is done on the forms themselves.
It is then used as follows:
class AddSource(MultipleFormView):
"""
Custom view for processing source form and seed formset
"""
template_name = 'add_source.html'
form_classes = {
'source_form': forms.SourceForm,
'seed_formset': forms.SeedFormset,
}
def process_forms(self, form_instances):
pass # saving forms etc
It is not a limitation of class-based views. Generic FormView just is not designed to accept two forms (well, it's generic). You can subclass it or write your own class-based view to accept two forms.
Use django-superform
This is a pretty neat way to thread a composed form as a single object to outside callers, such as the Django class based views.
from django_superform import FormField, SuperForm
class MyClassForm(SuperForm):
form1 = FormField(FormClass1)
form2 = FormField(FormClass2)
In the view, you can use form_class = MyClassForm
In the form __init__() method, you can access the forms using: self.forms['form1']
There is also a SuperModelForm and ModelFormField for model-forms.
In the template, you can access the form fields using: {{ form.form1.field }}. I would recommend aliasing the form using {% with form1=form.form1 %} to avoid rereading/reconstructing the form all the time.
Resembles #james answer (I had a similar starting point), but it doesn't need to receive a form name via POST data. Instead, it uses autogenerated prefixes to determine which form(s) received POST data, assign the data, validate these forms, and finally send them to the appropriate form_valid method. If there is only 1 bound form it sends that single form, else it sends a {"name": bound_form_instance} dictionary.
It is compatible with forms.Form or other "form behaving" classes that can be assigned a prefix (ex. django formsets), but haven't made a ModelForm variant yet, tho you could use a model form with this View (see edit below). It can handle forms in different tags, multiple forms in one tag, or a combination of both.
The code is hosted on github (https://github.com/AlexECX/django_MultiFormView). There are some usage guidelines and a little demo covering some use cases. The goal was to have a class that feels as close as possible like the FormView.
Here is an example with a simple use case:
views.py
class MultipleFormsDemoView(MultiFormView):
template_name = "app_name/demo.html"
initials = {
"contactform": {"message": "some initial data"}
}
form_classes = [
ContactForm,
("better_name", SubscriptionForm),
]
# The order is important! and you need to provide an
# url for every form_class.
success_urls = [
reverse_lazy("app_name:contact_view"),
reverse_lazy("app_name:subcribe_view"),
]
# Or, if it is the same url:
#success_url = reverse_lazy("app_name:some_view")
def get_contactform_initial(self, form_name):
initial = super().get_initial(form_name)
# Some logic here? I just wanted to show it could be done,
# initial data is assigned automatically from self.initials anyway
return initial
def contactform_form_valid(self, form):
title = form.cleaned_data.get('title')
print(title)
return super().form_valid(form)
def better_name_form_valid(self, form):
email = form.cleaned_data.get('email')
print(email)
if "Somebody once told me the world" is "gonna roll me":
return super().form_valid(form)
else:
return HttpResponse("Somebody once told me the world is gonna roll me")
template.html
{% extends "base.html" %}
{% block content %}
<form method="post">
{% csrf_token %}
{{ forms.better_name }}
<input type="submit" value="Subscribe">
</form>
<form method="post">
{% csrf_token %}
{{ forms.contactform }}
<input type="submit" value="Send">
</form>
{% endblock content %}
EDIT - about ModelForms
Welp, after looking into ModelFormView I realised it wouldn't be that easy to create a MultiModelFormView, I would probably need to rewrite SingleObjectMixin as well. In the mean time, you can use a ModelForm as long as you add an 'instance' keyword argument with a model instance.
def get_bookform_form_kwargs(self, form_name):
kwargs = super().get_form_kwargs(form_name)
kwargs['instance'] = Book.objects.get(title="I'm Batman")
return kwargs

Cannot get MEDIA_URL from Django widget's template

I am a new Djangoer, and figuring out how to build custom widget, my problem is cannot get the MEDIA_URL in my widget's template, while the form use MySelectWidget able to get the MEDIA_URL itself.
#
#plus_sign.html
#
<a href="" class="" id="id_{{ field }}">
<img src="{{ MEDIA_URL }}images/plus_sign.gif" width="10" height="10" alt="Add"/>
</a>
^ cannot load the {{ MEDIA_URL}} to this widget's template, and therefore I can't load the .gif image properly. :(
#
#custom_widgets.py
#
from django import forms
class MySelectMultiple(forms.SelectMultiple):
def render(self, name, *args, **kwargs):
html = super(MySelectMultiple, self).render(name, *args, **kwargs)
plus = render_to_string("plus_sign.html", {'field': name})
return html+plus
#
#forms.py
#
from django import forms
from myapp.custom_widgets.py import MySelectMultiple
class MyForm(forms.ModelForm):
contacts = forms.ModelMultipleChoiceField(Contact.objects, required=False, widget=MySelectMultiple)
#
#views.py
#
def AddContacts(request):
if request.method == 'POST':
form = MyForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
new = form.save()
return HttpResponseRedirect('/addedContact/')
else:
form = MyForm()
return render_to_response('shop/my_form.html', {'form': form}, context_instance=RequestContext(request))
#
#my_form.html
#
{% extends "base.html" %}
{% block content %}
{{ form.contacts }}
{% endblock %}
Please let me know how can I load the widget's image properly. Thank you so much for all responses.
Context processors only get applied when you use a RequestContext.
Your render method should be something like:
from django.template import RequestContext
def render(self, name, *args, **kwargs):
html = super(MySelectMultiple, self).render(name, *args, **kwargs)
context = RequestContext({'field': name})
plus = render_to_string("plus_sign.html", context)
return html + plus
And, as was mentioned by #czarchaic, make sure the media context processor is in TEMPLATE_CONTEXT_PROCESSORS (it should be by default).
Docs link.
Actually the correct way to do this is using Widget Media.
When defining your widget, you should define a Media inner class in which you should include a CSS file in order to style your widget. In this case make the <a> tag not to display text and have a plus sign background image.
class MyWidget(TexInput):
...
class Media:
css = {
'all': ('my_widget.css',)
}
If you really need to include the MEDIA_URL inside your rendered widget, I'd recommmend to import it directly from django.conf.settings and include settings.MEDIA_URL in your rendering context.
from django.conf import settings
class MyWidget(TextInput):
...
def render(self):
return render_to_string('my_widget.html', {
'MEDIA_URL': settings.MEDIA_URL,
...
})
Make sure the context processor is being loaded in settings.py
TEMPLATE_CONTEXT_PROCESSORS=(
...other processors,
"django.core.context_processors.media",
)
It is loaded by default if you don't specify TEMPLATE_CONTEXT_PROCESSORS, but if specified, the above processor must also be included.
http://docs.djangoproject.com/en/dev/ref/settings/#template-context-processors
I think we can do in this way, to pass the RequestContext, in order to access the MEDIA_URL without making another variable, and passing other variables at the 2nd parameter of the render_to_string method.
If we use:
context = RequestContext({'field': name})
The {{ field }} in the widget's template is empty and not able to access.
Here is the block which can access the MEDIA_URL as well as the {{ field }}.
However, I agree using the inner Media class for complex javascript and CSS setting. However, for a simple image src path, I think this will do.
def render(self, name, *args, **kwargs):
html = super(SelectMultipleWithModalDialog, self).render(name, *args, **kwargs)
**context = RequestContext({})
popup_plus = render_to_string("widgets/modal_dialog_plus_sign.html", {'field': name}, context_instance=context)**
return html + popup_plus
Please correct me if this is not the good way of doing it. Thanks for all participants of this thread.