Django : rendering a TemplateView in another - django

Using Django, I have multiple template files (A, B and C) that could be rendered in the same TemplateView called GenericView.
A, B and C uses the same View (let's call it DynamicView), therefore I need to call the rendering method of this DynamicView from GenericView's get_context_data`.
Is there a way I can render easily DynamicView's template within GenericView's template?
EDIT : I am using class based view coding
EDIT : Added some code to make my question clearer :
Here is my GenericView :
class GenericView(DetailView):
model = SimpleModel
template_name = "template.html"
def get_context_data(self, **kwargs):
context = super(GenericView, self).get_context_data(**kwargs)
tests = DynamicModel.objects.filter(test=context['object'].pk)
context['tests'] = tests
print("tests : ", tests[0]) # each test contains a field called "template_path", I would like to instanciate a DynamicView so that I can include the rendered page in context
return context
class DynamicView(TemplateView):
template_name = "dummy.html"
model = DynamicModel
def render_to_response(self, context, **kwargs):
absolute_path = get_object_or_404(DynamicModel, pk=self.kwargs['pk'])
page = render(self.request, absolute_path, context, content_type=None, status=None, using=None) # here the page is rendered

You could override DynamicView's get_template_names method.
Before doing that though, what is being considered in making the decision on what template to render? It might be better to make 3 different views, use inheritance to let them share code and not rework any of that view routing.

Yes, it can be done with the Django template tag. You need to call render function inside of this tag and put it inside of needed template.
EDIT after code updates.
from django import template
from django.shortcuts import get_object_or_404
from myapp.models import DynamicModel
register = template.Library()
#register.inclusion_tag('dummy.html', takes_context=True)
def render_dynamic_model(context, **kwargs):
absolute_path = get_object_or_404(DynamicModel, pk=kwargs['pk'])
return {
'absolute_path': absolute_path,
}
And put it into template template.html
{% render_dynamic_model pk %}
This code like a general idea, if you need some variables from request, you can pass it in the template and update template tag code respectively.
{% render_dynamic_model pk request.user %}

Related

Django templates not updating after database changes via admin

I am using Django v2.2 admin to change the information on my database but after I change it and refresh the page, the new data is not there, only the old data.
A fix for this if I restart the server, the templates can now fetch the new data that I input.
views.py
# template with context
class Home(TemplateView):
template = 'home.html'
context = { 'bar': Baby.objects.all() }
def get(self, request):
return render(request, self.template, self.context)
home.html
{% for foo in bar %}
{{ foo.name }}
{{ foo.cost }}
{% endfor %}
How I can get the new data by refreshing the page and not restarting the server?
As others mentioned, use get_context_data() method is good idea, because ContextMixin is parent class (not base class, but part of TemplateView's __mro__ Method Resolution Order) of TemplateView which is responsible to pass data from view to template. But, if you want to render template manually using get() method, You should hit on database on every GET request (in your case).
class Home(TemplateView):
template = 'home.html'
def get(self, request):
self.context = {'bar': Baby.objects.all()}
return render(request, self.template, self.context)
Your code does not work, because static variables are initialized only once. In your case context was static variable.
Hope, it helps you.
Can you please try this?
class Home(TemplateView):
template_name = 'home.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['bar'] = Baby.objects.all()
return context

Adding a button to Wagtail Dashboard

Is it possible to add a additional button(buttons) on the top panel as shown in the picture?
I did't find anything in Google and here.
It is not clear on the screenshot whether you use modeladmin or the snippets to expose this model to the user but I'll assume the former.
I don't know about a hook which allows you to add buttons directly to the header, however the template uses blocks which should allow us to overwrite just this part.
We can take advantage of the resolution order of the templates and create templates/modeladmin/app-name/model-name/index.html which will take precedence over /modeladmin/index.html. So given with your app called feedler and a model called Entry, create /modeladmin/feedler/entry/index.html with the following content:
{% extends "modeladmin/index.html" %}
{% block header_extra %}
My New Button
{{ block.super }}{% comment %}Display the original buttons {% endcomment %}
{% endblock %}
Right now your button doesn't do much. To create an action which will interact with that model admin, you'll need to create some button/url/permission helpers and a view.
Let's say the action is exporting the objects to a CSV file. Brace yourself, there's quite a bit of code ahead.
In /feedler/admin.py, create the button/url/permission helpers and view:
from django.contrib.auth.decorators import login_required
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.utils.functional import cached_property
from django.utils.translation import ugettext as _
from wagtail.contrib.modeladmin.helpers import AdminURLHelper, ButtonHelper
from wagtail.contrib.modeladmin.views import IndexView
class ExportButtonHelper(ButtonHelper):
"""
This helper constructs all the necessary attributes to create a button.
There is a lot of boilerplate just for the classnames to be right :(
"""
export_button_classnames = ['icon', 'icon-download']
def export_button(self, classnames_add=None, classnames_exclude=None):
if classnames_add is None:
classnames_add = []
if classnames_exclude is None:
classnames_exclude = []
classnames = self.export_button_classnames + classnames_add
cn = self.finalise_classname(classnames, classnames_exclude)
text = _('Export {}'.format(self.verbose_name_plural.title()))
return {
'url': self.url_helper.get_action_url('export', query_params=self.request.GET),
'label': text,
'classname': cn,
'title': text,
}
class ExportAdminURLHelper(AdminURLHelper):
"""
This helper constructs the different urls.
This is mostly just to overwrite the default behaviour
which consider any action other than 'create', 'choose_parent' and 'index'
as `object specific` and will try to add the object PK to the url
which is not what we want for the `export` option.
In addition, it appends the filters to the action.
"""
non_object_specific_actions = ('create', 'choose_parent', 'index', 'export')
def get_action_url(self, action, *args, **kwargs):
query_params = kwargs.pop('query_params', None)
url_name = self.get_action_url_name(action)
if action in self.non_object_specific_actions:
url = reverse(url_name)
else:
url = reverse(url_name, args=args, kwargs=kwargs)
if query_params:
url += '?{params}'.format(params=query_params.urlencode())
return url
def get_action_url_pattern(self, action):
if action in self.non_object_specific_actions:
return self._get_action_url_pattern(action)
return self._get_object_specific_action_url_pattern(action)
class ExportView(IndexView):
"""
A Class Based View which will generate
"""
def export_csv(self):
data = self.queryset.all()
response = ...
return response
#method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
super().dispatch(request, *args, **kwargs)
return self.export_csv()
class ExportModelAdminMixin(object):
"""
A mixin to add to your model admin which hooks the different helpers, the view
and register the new urls.
"""
button_helper_class = ExportButtonHelper
url_helper_class = ExportAdminURLHelper
export_view_class = ExportView
def get_admin_urls_for_registration(self):
urls = super().get_admin_urls_for_registration()
urls += (
url(
self.url_helper.get_action_url_pattern('export'),
self.export_view,
name=self.url_helper.get_action_url_name('export')
),
)
return urls
def export_view(self, request):
kwargs = {'model_admin': self}
view_class = self.export_view_class
return view_class.as_view(**kwargs)(request)
In /feedler/wagtail_hooks.py, create and register the ModelAdmin:
from wagtail.contrib.modeladmin.options import ModelAdmin, modeladmin_register
from .admin import ExportModelAdminMixin
from .models import Entry
class EntryModelAdmin(ExportModelAdminMixin, ModelAdmin):
model = Entry
# ...
modeladmin_register(EntryModelAdmin)
With all that setup, you should be able to use {% include 'modeladmin/includes/button.html' with button=view.button_helper.export_button %} in the template created above.
Watch out, if you are listing Pages you will need to subclass from PageAdminURLHelper, not from AdminURLHelper (FilterableAdminURLHelper doesn't seem to be a standard implementation).
Otherwise, you will have some strange redirections taking place.

Can Function based views be reused in several apps without duplicating the code?

I have bunch of function based views that have similar functionality
For example create, list, edit view and search companies and contacts of: Customers , Vendors and Manufactures.
I can reuse the same model with few boolean flags for all 3 of them.
But I was wondering how to deal with view and templates in a best way to avoid code duplications. (currently I am thinking but massive usage of control-H but it doesn't feel right)
(I regret now not using the Class based views but it is too late)
There is a lot you can do by passing in url parameters to achieve reuse. Your imagination and consistency will help a lot here:
For a simple example:
urlpatterns = [
url(r'^add-customer/$', create, {'template':'customer.html'}, name='create_customer'),
url(r'^add-vendor/$', create, {'template':'vendor.html'}, name='create_vendor'),
url(r'^add-manufacturer/$', create, {'template':'manufacturer.html'}, name='create_manufacturer'),
...
and the view:
def create(request, template):
#use template as you like, coming from url conf.
return render(request, template)
...
That's probably normal code when you think about it. A little more interesting example:
from someapp.forms import *
from someapp.models import *
urlpatterns = [
url(r'^customer/$', create, {'template':'customer.html', 'form_class':customer_form}, name='create_customer'),
url(r'^vendor/$', create, {'template':'vendor.html', 'form_class':vendor_form}, name='create_vendor'),
url(r'^manufacturer/$', create, {'template':'manufacturer.html', 'form_class':manufacturer_form}, name='create_manufacturer'),
...
and the view:
def create(request, template, form_class):
#use template as you like, coming from url conf.
form = form_class()#
return render(request, template)
...
Still pretty much normal code. You can get funky and generate the urls dynamically:
In your urls.py:
for form_class in (CustomerForm, VendorForm, ManufacturerForm): #model forms
model = form_class._meta.model
model_name = model._meta.module_name
urlpatterns += url(r'^create/$', create, {'template':'%s.html' % model_name, 'form_class':form_class, 'model':model}, name='create_%s' % model_name),
and in the view:
def create(request, template, form_class, model):
#use template as you like, coming from url conf.
form = form_class()#
return render(request, template)
You can pass in pks to models and write codes like:
def create(request, form_class, model, id=None):
instance = get_object_or_404(model, pk=id) if id else None
edited = True if instance else False
if request.method == 'POST':
form = form_class(data=request.POST, files=request.FILES, instance=instance)
if form.is_valid():
instance = form.save()
...
else:
form = form_class(instance=instance)
return render(request, template_name, {'form': form, 'instance': instance})
Hopefully you have the stomach for it.
The answer, of course, depends on what actually is different between the models. But if you can abstract your view functions "with a few boolean flags" I would suggest to put them in a central place, probably a common file in your app or project, e.g. abstract_views.py.
Then you will only need to dispatch the view functions in a meaningful way:
import my_app.abstract_views
from .models import Model1, Model2
def model1_create_view(request):
abstract_views.create(request, model=Model1)
def model2_create_view(request):
abstract_views.create(request, model=Model2)
In this hypthetical example create would be the abstract view function for creating an instance and it would require a parameter model with the actual model to operate on.
ADDED:
Alternatively, use boolean flags, as requested:
import my_app.abstract_views
def model1_create_view(request):
abstract_views.create(request, is_customer=True)
def model2_create_view(request):
abstract_views.create(request, is_vendor=True)
When using boolean flags, remember to define what happens if someone is both, a customer and a vendor...
The definition of the abstract view then reads something like:
def abstract_view(request, is_customer=False, is_vendor=False):
context = do_something (is_customer, is_vendor)
return(render(request, 'app/template.html', context))
Hope that helps.

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 How to FormView rename context object?

I have a class view which uses FormView. I need to change the name of the form i.e. this is what it used to be in my old function view:
upload_form = ContactUploadForm(request.user)
context = {'upload': upload_form,}
With my new view I'm assuming I can rename using the get_context_data method but unsure how.
How can I rename this form to upload instead of form as my templates uses {{ upload }} not {{ form }}? Thanks.
Current Class View:
class ImportFromFile(FormView):
template_name = 'contacts/import_file.html'
form_class = ContactUploadForm
def get_context_data(self, **kwargs):
"""
Get the context for this view.
"""
# Call the base implementation first to get a context.
context = super(ImportFromFile, self).get_context_data(**kwargs)
return context
Try this:
class ImportFromFile(FormView):
template_name = 'contacts/import_file.html'
form_class = ContactUploadForm
def get_context_data(self, **kwargs):
"""
Get the context for this view.
"""
kwargs['upload'] = kwargs.pop('form')
return super(ImportFromFile, self).get_context_data(**kwargs)
Django 2.0+ provides support for changing context object names. See: Built-in class-based generic views
Making “friendly” template contexts
You might have noticed that our sample publisher list template stores all the publishers in a variable named object_list. While this works just fine, it isn’t all that “friendly” to template authors: they have to “just know” that they’re dealing with publishers here.
Well, if you’re dealing with a model object, this is already done for you. When you are dealing with an object or queryset, Django is able to populate the context using the lowercased version of the model class’ name. This is provided in addition to the default object_list entry, but contains exactly the same data, i.e. publisher_list.
If this still isn’t a good match, you can manually set the name of the context variable. The context_object_name attribute on a generic view specifies the context variable to use:
class PublisherList(ListView):
model = Publisher
context_object_name = 'choose the name you want here'