Django render template variable - django

I have a field called "GOALS" that is rendered as a template variable and stored in a django model. That field is input via the admin interface and contains raw html, so in the django template there is
<html>
...
<P>{{ GOALS|safe }}</P}
...
</html>
Now I want to render this variable before it gets placed into context with my own template tag. This will allow staff to enter tags that will query the database rather than hard code variables such as the title of a project that is identified by a slug etc.
I thought of allowing them to enter template tags such as
GOALS = "... {{ "slug"|gettitle }} ..."
and rendering GOALS before it entered the context with
from django.template import Template, Context
html2render = "{% load episode_tags %}" + GOALS
miniTemplate = Template(html2render)
context = Context({})
GOALS = miniTemplate.render(context)
but loading my template tags did not work using Template this way, and this crashed with TemplateSyntaxError. So I figured I could use ElementTree and XML, but before I go ahead and do that (which is a little tedious) I wanted to ask the wider community if there was some better way to do this.

As it turned out, the solution I proposed was correct (thanks #GwynBleidD). The editor in django admin that allowed entry of the fields used widget CKEditorWidget that automatically renders the html. It was turning quotes " into ". I started subclassing the widget to replace the html safe quotes, but I just don't have the time. I told the user to expect to see the " in the source and used the following.
# The GOALS field is rendered by the CKEditorWidget before it is stored.
html2render = "{% load episode_tags %}" + GOALS.replace(""", '"')
try:
miniTemplate = Template(html2render)
context = Context({})
GOALS = miniTemplate.render(context)
except TemplateSyntaxError:
pass

Related

How to process django url tags in context variables

I'm creating a django-based website where page content is stored in a model textfield with a detail view of the model displaying it. How can I put {% url %} tags in the content to link to other pages without hard coding links?
Putting the tag into the textfield will just print the tag as plain text. (or if inside an href create a broken link)
Other options are
parsing the content manually inside the view and replacing the tags with the correct url
using some django supplied string parser to create the links before sending to the template
maybe there is a way to do it in the template with a filter or tag around the variable.
I could probably do the manual way but I am hoping there is a django way that I am missing.
class Page(models.Model):
content = models.TextField(blank=True)
class PageView(DetailView):
model=Page
context_object_name='page'
{% extends "base.html" %}
{% block content %}
{{ page.content|safe }}
{% endblock %}
Edit for clarity:
Content in admin
What it renders
What it should render
Edit 2:
I was able to find a solution to the second option thanks to this answer on another question. Using that template_from_string function I rendered the page content as a template then replaced the content in the context with the rendered result.
class PageView(DetailView):
model=Page
context_object_name='page'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Add to context here
context['page'].content = template_from_string(context['page'].content).render(context)
return context
your url's will have a name as shown below-
path('page//',PageView.as_view, name="page-detail")
and in your Page Detail
hope this is what your asking for
Let see if I understood you well, do you want django to show you a url as such and not as plain text?
If you want to avoid django to show you the data as plain text, you can use the escape filter as follows: {{string | escape}}
Check this page for more details about django filters and tags

How do I get reverse() to work in a function based view

I am new to Django, and I would like to try a FBV for an activity that doesn't require a Model. This will eventually implement a search with user-defined parameters and show the results in the template, but for now my template and views are essentially empty to show this problem.
I'm using python 3.6 and Django 2.1.3.
The tutorials go straight to CBV and I'm having a hard time getting good info on the FBV way.
File: 'positivepets/picture_search.html':
-------
{% extends 'positivepets/base.html' %}
{% block body %}
<p> You have reached the picture search page </p>
{% endblock %}
File: urls.py
--------
app_name = 'positivepets'
urlpatterns = [...
url(r'^picture_search/$', views.misc_views.picture_search, name='picture_search'),
...]
File: misc_views.py
--------
def picture_search(request):
return render(request, 'positivepets/picture_search.html')
Problem
This all works fine and renders the template picture_search.html.
My problem is that I want to avoid hardcoding the template name. I thought this would work:
def picture_search(request):
return HttpResponseRedirect(reverse('positivepets:picture_search'))
This takes the user to http://127.0.0.1:8000/positivepets/picture_search/
but produces a "too many redirects" error in chrome. I think I am just telling it to redirect to itself over and over.
Question
Where do I specify that picture_search.html is the template that I want to render without hardcoding it in the view?
It seems that the answer should lie in reverse, but I can only find CBV examples of reverse(), and I think it works with CBV because there is a template_name attribute set in the class definition. FBV doesn't have that, of course.
I'm hoping this is an easy one for someone with some FBV experience.
I don't think there is any benefit in doing what you're trying to do. You have to tell django what template you want by name somewhere. If you don't do it in your return, but somewhere else in your code you've only added a layer of abstraction with no net benefit.
i.e
def picture_search(request):
return render(request, reverse('some_pointer'))
some_pointer = 'picture_search' - you still have to hard code it
= No benefit
the reverse function is for getting the url from the view, or the label.
At the end of that url is some function to render a template, with the template name.

NoReverseMatch encountered when importing one Django template into another

In a Django project, I have a mini navbar that is common in ~30% of my templates. Instead of including it in my global base.html, I decided to take a different route.
I first wrote a separate view for this:
from django.template.loader import render_to_string
def navbar(origin=None):
if origin == '1':
locations = get_approved_loc(withscores=True)
else:
locations = get_approved_loc()
obj_count = get_all_obj_count()
return render_to_string("mini_navbar.html",{'top_3_locs':locations[:3],\
'other_cities':len(locations[3:]),'obj_count':obj_count})
I next added it in the templates it needed to be in via:
{% include "mini_navbar.html" with origin='1' %}
When I run this code, I get a NoReverseMatch error. It seems the view function navbar never runs. So the context variables it was sending in (e.g. top_3_locs or other_cities etc) are never populated. Hence NoReverseMatch.
What's wrong with this pattern, and what's the fix for it? An illustrative example would do the trick.
Rather than including a template directly, you should write a custom template tag - specifically, an inclusion tag that renders the template with the custom context. The code that you have put in that separate view goes in that template tag instead.
Here's an illustrative example of Daniel's suggestion:
I created an 'inclusion' template tag like so:
from django import template
from redis_modules import get_approved_loc, get_all_obj_coun
register = template.Library()
#register.inclusion_tag(file_name='mini_navbar.html')
def mini_navbar(origin=None):
locations = get_approved_loc(withscores=True)
obj_count = get_all_obj_count()
return {'top_3_locs':locations[:3],'origin':origin,'other_cities':len(locations[3:]),'obj_count':obj_count}
Next I included this in the relevants templates like so:
{% load get_mini_navbar %} <!-- name of the module -->
And finally, I called it within the template like so:
{% mini_navbar origin %}
Where origin is the parameter passed into the tag.

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 }}

Django - get an object back from template form to view (from a search result)

In my Django app, the user is presented a simple form, on validation the view searches the data (some web scraping) and returns a template in the context of which we added a list of the results, we present them to the user and he/she chooses one and (this is my problem) I want to get that choice back to another view (now we want to add the selected object to the database).
I identified my problem, but I really think I'm missing something and not using Django the right way. For now this is how I display the data and put it back in a form: (jade template. Of course I do this with many more attributes so the template is quite big and I have to add an hidden input for each value I want back)
for b in result_list
h4
for aut in b.authors
div {{ aut }}
form(action='/add/', method='post') {% csrf_token %}
{% csrf_token %}
input(type='hidden', value="{{ b.authors }}", name='authors')
edit2: there is one button per book. I am just trying to return one book.
It works fine when authors has a single element, but with more than once the template doesn't return a valid json. I could give you the structure but I tend to think this is not the point.
What am I missing ? How to display search results and get the selected one to another view ?
Do I miss something about forms ? Shall I use a session variable, a context processor with middleware ? I guess the hidden input is wrong: how can I do differently ?
many thanks
edit: below I show where my problem is. However, maybe I shouldn't fix that problem but use Django the right way instead.
1- my 1st view renders a template and feeds it with the search results:
return render(request, "search/search_result.jade", {
"return_list": book_list,
})
book_list is a list of dicts where authors is a list of unicode str:
retlist[0]["authors"]
>>> [u'Ayroles, Alain']
2- my template is above. It displays a form and a hidden input. The goal is to send back a book: a dictionnary with the title, the editor… and a list of authors.
3- now the users clicks "Add this book", we are in the "add" view. Let's inspect the authors:
req = request.POST.copy()
authors = req["authors"]
>>> u"[u'Ayroles']" # note the u after the [
# this is not a valid json, I can't get a list out of it
import json
json.loads(req["authors"])
>>> *** ValueError: No JSON object could be decoded
So I passed a list from the first view to the template, I parsed it and displayed it, but the template doesn't return a valid list/json to the second view in the POST parameters and I can't extract my authors. I don't know what to think:
else what I want to do is fine and I just have to better handle my data structure
else I shouldn't be doing like that and use more of Django: use more forms ? sessions ?
This is how I see it. It is not really an answer, but it's already too much code to put into comments
Template:
for b in result_list
h4
for aut in b.authors
div {{ aut }}
form(action='/add/', method='post')
{% csrf_token %}
input(type='hidden', value="{{ b.authors }}", name='authors')
input(type='submit', value="Add book")
View:
if request.method == 'POST':
authors = request.POST.get['authors']
#do something with authors, e.g. create a JSON string:
authors_JSON = json.dumps(authors)
Same logic, using Django forms:
View:
if request.method == 'POST':
book = BookForm(request.POST)
#do something with authors, e.g. create a JSON string:
authors_JSON = json.dumps(book.authors)
Forms.py:
class ContactForm(forms.Form):
authors = forms.CharField(max_length=100)
#etc, possibly including custom __init__ logic
That 'u' thing, it happens because you do json.loads() on an object. If you want to serialize, it should be json.dumps(), otherwise array converted to string and then treated as JSON, that is why Python unicode mark got there (i.e. not a template bug)
I finally got it: we can easily share data between views using the session.
The session is activated in the default Django install. We just have to choose a session engine: in a temporary file, in memory,…
SESSION_ENGINE = 'django.contrib.sessions.backends.file'
Now in my search/ url I can add a variable in request.session, a dictionnary-like object:
search_results = compute_and_get_data()
request = search_results()
I feed my template with the search_results list. In my template, I iterate on it and I use a single hidden input field to return the counter of the for loop.
for b in search_results
table
tr
td
h4 {{b.authors}}
form(action='/add/', method='post') {% csrf_token %}
{% csrf_token %}
input(type='hidden', value="{{ forloop.counter0 }}", name='forloop_counter0')
input.btn.btn-primary(type='submit', value='add book', title="add that book to my collection")
On submit, we enter the add/ view. Here I can get the book the user selected:
forloop_counter0 = int(request.POST["forloop_counter0"])
book = request.session["search_result"][forloop_counter0]
Now my book is a dictionnary with a lot of information. I didn't have to handle json formatting and the like.
This question helped me:
Django Passing data between views
How do you pass or share variables between django views?