How to add Wagtail 'admin' menu to custom templates? - django

On templates that derive from the Wagtail Page model, there is a small Wagtail icon/menu in the lower right corner. This provides a quick way to edit the page and/or jump to the Wagtail Admin. However, this menu does not appear on custom view templates that are not derived from Wagtail Page model.
How can I tell Wagtail to display the small menu on my frontend templates, so the pages have consistent navigation?

The standard Wagtail user bar is rendered by placing in your template:
{% load wagtailuserbar %}
{% wagtailuserbar 'top-left' %}
I typically just put the above in base.html. The 'top-left' designation, of course, tells the template tag where to render the user bar. Reference
However, the wagtailuserbar is only rendered for Wagtail pages. What you want to do is render the user bar with only the Go to Wagtail Admin option (because no other options would be relevant). Therefore, you could create your own template tag and place it in base.html beside the wagtailuserbar. You would set it up so that it renders if there IS NO page in the context (the wagtailuserbar template tag checks to make sure there IS a page in the context). To create your tag, just start with the code from wagtailuserbar.py and modify it to create a template tag called wagtailuserbar_admin_only (untested):
from django import template
from django.template.loader import render_to_string
from wagtail.admin.templatetags.wagtailuserbar import get_page_instance
from wagtail.admin.userbar import (AdminItem)
#register.simple_tag(takes_context=True)
def wagtailuserbar_admin_only(context, position='bottom-right'):
# Find request object
try:
request = context['request']
except KeyError:
return ''
# Don't render without a user because we can't check their permissions
try:
user = request.user
except AttributeError:
return ''
# Don't render if user doesn't have permission to access the admin area
if not user.has_perm('wagtailadmin.access_admin'):
return ''
# Only render if the context does NOT contain a variable referencing a saved page
page = get_page_instance(context)
if page:
return ''
# Render the items
rendered_items = [AdminItem()]
# Render the userbar items
return render_to_string('wagtailadmin/userbar/base.html', {
'request': request,
'items': rendered_items,
'position': position,
})
Then, to use in templates, place in base.html:
{% load wagtailuserbar_admin_only %}
{% wagtailuserbar_admin_only 'top-left' %}

Related

What is the optimal way to write function-based views in Django?

What is the recommended way to write views (as functions) in Django?
I am asking in terms of readability, etc.
For example: define the template first, then do the translations, then define models and lastly define context.
Here is some example code:
def index(request): # Landing page, translated to the browser's language (default is english)
template = loader.get_template("koncerti/index.html")
# Translators: This is the text on the homepage buttons
concerts = gettext("Koncerti")
band = gettext("Band")
# Translators: This is the option in the language-switch box
foreignLanguage = gettext("eng")
koncertiUrl = '/koncerti/international' # The URL slug leading to 'koncerti' page
bandUrl = '/band/international' # The URL slug leading to 'band' page
translationMode = '' # The URL slug that is leading to slovenian translation mode
context = {
'Concerts' : concerts,
'Band' : band,
'foreignLanguage' : foreignLanguage,
'koncertiUrl' : koncertiUrl,
'bandUrl' : bandUrl,
'translationMode' : translationMode
}
return HttpResponse(template.render(context, request))
I think you are doing too much in the view. A view is supposed to make the proper querysets, and make changes to the database. It's main task is thus to decide what to render, and what to update. Not how to update it.
Deciding about translations, url resolution, etc. is normally a task of the template. The template has template tags for that such as {% trans … %} [Django-doc] and {% url … %} [Django-doc].
Using the {% url … %} over raw URLs you construct yourself is strongly advisable. It makes the odds of making a mistake smaller, and furthermore it is clear to what view you are referring.
You furthermore might want to use render(..) [Django-doc]. This function combines looking for a template, rendering that template with the template render engine, and returning a HTTP response.
from django.shortcuts import render
def index(request):
return render(request, 'koncerti/index.html', {})
In the template, you can then render this with:
{% load i18n %}
{% trans 'Koncerti' %}

How can i render a specific Django admin page from a view?

I know that can reverse to admin pages from a template by doing this:
{% url 'admin:index' %}
or
{% url 'admin:app_model_add' %}
But how can i render an admin page using the render function in my view?
When i do
render(request, template_name='admin/index.html')
I get a blank looking admin page with the message "You do not have permission to view or edit anything"
Furthermore, I am not sure what the syntax would be to render a specific admin template, i.e. the equivalent of using this reverse in a template:
admin:app_model_add
is not something like this in a view:
render(request, template_name='admin/app_model_add.html')
In fact i shouldn't to try to render an admin page, i should redirect to it.
so in my view I should be using:
redirect(to=reverse('admin:index'))
or
redirect(to=reverse('admin:app_model_add'))

django dynamic inclusion tag

I'm trying to add some image gallery to the template but position of the gallery on the page need to be defined by user. Sometimes it will be at the top of the text, sometimes at the end and sometimes at the middle of the text on the page. In the text field user will add this line #foto and based on the position of the that line I need to render gallery. For example:
some paragraph written by user in the text field...
#foto
some another paragraph written by user ...
Now the easiest way will be to replace #foto with include tag in the view. Something as :
foto = "{% include 'includes/gallery.html' %}"
xx = "#foto"
post.text = post.text.replace(xx, foto)
But this example doesn't work because include tag can't be call on that way. It is rendered as pure text {% include 'includes/gallery.html' %}. What can be done to render include tag as expected on the position of the #foto text.
assign foto the rendered template, inside your view:
...
from django.template.loader import render_to_string
"""
Get the items from the model, define your query
"""
gallery = GalleryModel.objects.all()
"""
Render gallery template and assign to foto, which then you
can inject in the basic template
"""
foto = render_to_string('includes/gallery.html', {'gallery': gallery})
...
then in your template:
{{ foto }}

How to put multiple models on the same page?

I'm doing a website in html and base (where all pages extend) I want
to put a session of social network icons. As this session is base on
html it should be displayed on all pages of the website.
I do not want
to put this session in a static html, I want to do in django using
models. This is already done.
Question: Do I have to put the session of social network icons on each view, or can I make a separate view and all others extend this view?
How can I do this?
Try using an inclusion tag. You can create a function for doing all of the work to create the sessions and then associate that with a particular block of HTML.
templatetags/session.py
#register.inclusion_tag('includes/session_box.html')
def output_session_box(...):
...
return { .. }
The associated template file, includes/session_box.html, would have the HTML like any template.
And then your base.html would have:
{% load session %}
{% output_session_box ... %}
Use RequestContext and a context_processor to inject template variables into every view using RequestContext.
It's as simple as a python function accepting request as an arg, and returning a dictionary to be passed into your template.
https://docs.djangoproject.com/en/dev/ref/templates/api/#django.template.RequestContext
def my_processor(request):
return {'foo': 'bar'}
TEMPLATE_CONTEXT_PROCESSORS = (
# add path to your context processor here.
)
I generally have a per-project processor for the basics... It's exactly how django adds {{ user }} or {{ STATIC_URL }} to every template.

Embed an optional Django application in another page, if that app is present

We have a Django project installed at multiple sites. At a couple of them, there will also be an app which produces a status box which should show on the front page, say. What's the right way to have it show up, if the app happens to be installed. If the app is not present, then nothing should display.
I think I could do it by having the status app extend the main index page:
{% extends "mainproject/index.html" %}
{% block statusboxplaceholder %}
<div> status here </div>
{% endblock %}
Is that the right, idiomatic approach? It seems slightly counterintuitive to extend the entire front page just to add a little bit of content.
EDIT: Also, how do I manage the fact that my app will want to define its own "index" page, that should be shown in preference to the project-wide "index" page? I obviously don't want to hard-code a reference to it in the project's urls.py. Do I create a deployment-specific urls.py which refers to specific apps that are installed at that deployment? And if so, isn't that repeating the information in INSTALLED_APPS, and hence violating DRY?
Although I don't see a problem with your approach, but I think a generic template tag would provide the most flexibilty, especially if you want to extend this ability to other applications that you might install later.
Your base template loads a generic "boxes" tag. In the tag's source, you can then render whatever you want based on the installed apps for that particular instance. So you can have a set of default apps to render boxes for, or the end user can customize which apps should have boxes rendered.
In your settings, configuration or even in the tag itself, you can identify the template to render for the block per app.
Assuming that each app has its templates in app/templates directory - this psuedo should get you going (this is untested):
from django.conf import settings
from django import template
register = template.Library()
class GenericBox(template.Node):
def __init__(self, app):
self.app = app
def render(self, context):
if self.app not in settings.INSTALLED_APPS:
return '' # if the app is not installed
# Here you would probably do a lookup against
# django.settings or some other lookup to find out which
# template to load based on the app.
t = template.loader.get_template('some_template.html') # (or load from var)
c = template.RequestContext(context,{'var':'value'}) # optional
return t.render(c)
#register.tag(name='custom_box', takes_context=True, context_class=RequestContext)
def box(parser, token):
parts = token.split_contents()
if len(parts) != 2:
parts[1] = 'default_app' # is the default app that will be rendered.
return GenericBox(parts[1])