DJANGO template tags in plain text not displaying - html

I am making an app that displays questions. The question model has a text field and an image field. Each question has a template that is stored in my database in the text field. My problem is when I want to access images from the model, template tags are displayed as text and not rendering. My code:
# question model
class Question(models.Model):
question_text = models.TextField()
question_image = models.FileField(upload_to='static/images/questions', blank=true)
# question view
def question(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'questiontemplate.html', {'question': question})
# template
{% extends 'base.html %}
{% load static %}
{% autoscape off %}
{{ question.question_text }}
{% endautoscape %}
# in my database:
question.question_text = '<p> some html
{{ question.question_image.url }}
some html </p>'
question.question_image = 'image.png'
This works fine and renders the html perfectly except the template tag is not rendered and does not not give the image url
I want this to be the output:
Some html
static/images/questions/image.png
some html
But instead this is the output:
some html
{{ question.question_image.url }}
some html
Any suggestions to how the template tags could be render from the database text would be much appreciated.
Thanks for reading

Django doesn't know that the content in your model field is itself a model. The template can't know that. The only way to make this work is to treat that field itself as a template, and render it manually.
You could do that with a method on the model:
from django.template import Template, Context
class Question(models.Model):
...
def render_question(self):
template = Template(self.question_text)
context = Context({'question': self})
rendered = template.render(context)
return mark_safe(rendered)
Now you can call it in your template:
{{ question.render_question }}

Related

How to save HTML tags + data into sqlalchemy?

I am creating a personal blog website with Flask and sqlalchemy.
While posting my blogs, I want the blog to be published with well formatted html.
Here is my model for Blogs:
class Blog(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(), index=True)
description = db.Column(db.Text(), index=True)
content = db.Column(db.Text(), index=True)
timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
likes = db.Column(db.Integer, default=0)
dislikes = db.Column(db.Integer, default=0)
comments = db.relationship('Comment', backref='commented_by', lazy='dynamic')
def __repr__(self):
return 'Title <>'.format(self.title)
And here is my form for adding blog:
{% extends 'base.html' %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12 col-xl-12">
<h1 class="code-line text-center" data-line-start="14" data-line-end="15">Add Blog</h1>
<br>
</div>
</div>
<form action="" method="POST" novalidate>
{{ form.hidden_tag() }}
<p>
{{ form.title.label }}<br>
{{ form.title(size=30) }}<br>
</p>
<p>
{{ form.description.label }}<br>
{{ form.description(size=30) }}<br>
</p>
<p>
{{ form.content.label }}<br>
{{ form.content() }}<br>
</p>
<p>
{{ form.submit() }}
</p>
</form>
{{ ckeditor.load() }}
{{ ckeditor.config(name='content') }}
{% endblock %}
This is how I am rendering my blog:
{% extends 'base.html' %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12 col-xl-12">
<h1 class="code-line text-center" data-line-start="14" data-line-end="15">{{ blog.title }}</h1>
<br>
{{ blog.content }}
</div>
</div>
</div>
{% endblock %}
While adding blog, I am using a text editor
But once it has been posted and I render it on view blog page, no html content is being rendered not even linebreaks
How can I save html content and tags in my sql database and then render it using jinja template?
first, what is wrong:
the text you get from the text field in the form is not the same thing as HTML that renders it, what you are getting is the text.
in case you want to get the HTML generated inthat form, you should integrate a rich text editor, like quilljs.com, or tiny.cloud in your template, that will have a field that you can use, to grab the HTML it generated, and it will also allow you to create nice looking blog articles.
if you do not want this either, to get html from that form, writing HTML directly in that form will give you what you want.
In the context of markdown, it is actually possible to apply the same format to your database-saved content. You can use a few packages to help you work with HTML in your database.
To begin, let me suggest Stackoverflow QA forms. Notice how it has enabled markdown editing and a nice little preview of the text being written. To enable the preview, you can install the flask-pagedown package in your virtual environment.
(venv)$ pip3 install flask-pagedown
Initialize a pagedown object in your application's instance, say in __init__.py file, or whatever other file you are using.
# __init__.py
from flask import Flask
from flask_pagedown import PageDown
app = Flask(__name__)
pagedown = PageDown(app)
Within your head tags in HTML, add this CDN call whose files you do not need to have in your application.
<!-- base.html -->
{% block head %}
{{ pagedown.html_head() }}
{% endblock %}
Or this:
<head>
{{ pagedown.html_head() }}
</head>
If you prefer to use your own JavaScript source files, you can simply include your Converter and Sanitizer files directly in the HTML page instead of calling pagedown.html_head():
<head>
<script type="text/javascript" src="https://mycdn/path/to/converter.min.js"></script>
<script type="text/javascript" src="https://mycdn/path/to/sanitizer.min.js"></script>
</head>
Now, simply update your forms to use PageDownField:
# forms.py
from flask_pagedown.fields import PageDownField
class Post(FlaskForm):
post = PageDownField('Comment', validators=[DataRequired()])
Or this:
<form method="POST">
{{ form.pagedown(rows=10) }}
</form>
That's it! You should be able to have a client-side post preview right below the form.
Handling Rich Text in the Server
When the post request is made, then only raw markdown will be sent to the database and the preview will be discarded. It is a security risk to send HTML content to your database. An attacker can easily construct HTML sequences which don't match the markdown source and submit them, hence the reason why only markdown text is submitted. Once in the server, that text can be converted back to HTML using a Python markdown-to-html convertor. There are two packages that you can make use of. Install then in your virtual environment as seen below:
(venv)$ pip3 install markdown bleach
bleach is used to sanitize the HTML you want converted to allow for a set of tags.
At this point, the next logical step would be to cache your content field content while in the database. This is done by adding a new field, let us say content_html, in your database specifically for this cached content. It is best to leave your content field as it is in case you would want to use it.
# models.py
class Blog(db.Model):
content = db.Column(db.String(140))
content_html = db.Column(db.String(140))
#staticmethod
def on_changed_body(target, value, oldvalue, initiator):
allowed_tags = ['a', 'abbr', 'acronym', 'b', 'blockquote', 'code',
'em', 'i', 'li', 'ol', 'pre', 'strong', 'ul',
'h1', 'h2', 'h3', 'p']
target.content_html = bleach.linkify(bleach.clean(
markdown(value, output_format='html'),
tags=allowed_tags, strip=True))
def __repr__(self):
return f'Title {self.title}'
db.event.listen(Blog.content, 'set', Blog.on_changed_body)
The on_changed_body() function is registered as a listener of SQLAlchemy’s “set” event for body , which means that it will be automatically invoked whenever the body field is set to a new value. The handler function renders the HTML version of the content and stores it in content_html , effectively making the conversion of the Markdown text to HTML fully automatic.
The actual conversion is done in 3 steps:
markdown() function does an initial conversion to HTML. The result is passed to clean() function with a list of approved HTML tags
clean() function removes any tags that are not in the whitelist.
linkify() function from bleach converts any URLs written in plain text into proper <a> links. Automatic link generation is not officially in the Markdown specification, but is a very convenient feature. On the client side, PageDown supports this feature as an optional extension, so linkify() matches that functionality on the server.
In your template, where you want to post your content you can add a condition such as:
{% for blog in blogs %}
{% if blog.content_html %}
{{ blog.content_html | safe }}
{% else %}
{{ blog.content }}
{% endif %}
{% endfor %}
The | safe suffix when rendering the HTML body is there to tell Jinja2 not to escape the HTML elements. Jinja2 escapes all template variables by default as a security measure, but the Markdown-generated HTML was generated by the server, so it is safe to render directly as HTML.

Django,html template,for loop not working as expected

I am trying to apply a for loop to the following html (in a Django project) such that the 'Name' and the 'Comments' field are caused to repeat on the html view.
When I insert the templating code, that is:
{% for c in comments %}
{% endfor %}
on either side of the content i want to repeat, it simply makes the name and comments disappear altogether and does not have the desired result.
The relevant parts of the file are below:
index.html (the main html page)
{% load static %}
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="{% static 'guestbook/styles.css' %}">
</head>
<body>
<h1>The world's guestbook</h1>
<p>Sign the guestbook</p>
{% for c in comments %}
<h2>Name</h2>
<p>This the message that the user leaves.</p>
{% endfor %}
</body>
</html>
views.py (in the guestbook app)
from django.shortcuts import render
from .models import Comment
# Create your views here.
def index(request):
comments = Comment.objects.order_by('-date_added')
context ={'comments': comments}
#name=Name.objects.order_by('-date_added')
return render(request,'guestbook/index.html')
def sign(request):
return render(request,'guestbook/sign.html')
models.py file
from django.db import models
from django.utils import timezone
# Create your models here.
class Comment(models.Model):
name=models.CharField(max_length=20)
comment=models.TextField()
date_added=models.DateTimeField(default=timezone.now)
def __str__(self):
return self.name
I am working off a tutorial in which this is the recommended code and the desired result is as expected - I notice my html template does not have div tags and wonder if that could be an issue? If so, how can it be resolved?
You need to pass that context:
def index(request):
comments = Comment.objects.order_by('-date_added')
context ={'comments': comments}
return render(request,'guestbook/index.html', context=context)
^^^^^^^^^^^^^^^
From documentation of render:
Context: A dictionary of values to add to the template context. By default, this is an empty dictionary. If a value in the dictionary is
callable, the view will call it just before rendering the template.
Meaning, the values inside dictionary which is being used with known argument context of render function, these values will be sent to the template. Then you can access those values through {{ key }} of the dictionary(which is sent as context) in html template, or your case {{ comments }}. More information can be found regarding context in this SO Answer.

Django 1.11 How to display template variable from within HTML saved in model for CMS Site

Within a template, I need to render a {{ variable }} from HTML content saved in a model instance.
Below are the stripped down parts of the code.
page.html
{% load static %}
<html>
<head>
<styles, links, etc.>
<title>{{ object.title }}</title>
</head>
<body>
<div>{{ object.html_content }}</div>
</body>
</html>
Model
class Page(models.Model):
title = models.CharField(max_length=30)
html_content = models.TextField()
GlobalMixin
# Used site wide for Global information.
class GlobalMixin(object):
def get_context_data(self, *args, **kwargs):
context = super(GlobalMixin, self).get_context_data(*args, **kwargs)
context['variable'] = "A Global piece of information"
return context
View
from .mixins import GlobalMixin
from .models import Page
PageView(GlobalMixin, generic.DetailView):
model = Page
template_name = "my_app/page.html"
def get_context_data(self, *args, **kwargs):
context = super(PageView, self).get_context_data(*args, **kwargs)
return context
Admin & HTML Content Field
I then enter admin, add new Page, enter my HTML content into html_content Field "Html Content" as per the following example.
<p>This is {{ variable }} that I need to display within my rendered page!</p>
Then SAVE.
BROWSER RESULTS
This is {{ variable }} that I need to display within my loaded page!
I know there is Django Flat Pages, but it doesn't look like it works for me as I require Global variables in my templates that flat pages doesn't offer.
The template is being rendered directly with the models content without looking at it.
I think I need to process the html_content field in the view and then add the required context variables to the context returned or save a temporary template file, append the html_content into the file, then render it.
How do I do make this work?
Is there a Django packaged interface that I can use to process the template from within my view?
I worked it out. Here's the code if anyone else comes across this or has a better way, please share.
Change View type to TemplateView, use slug in url to get model instance, use django.template.engines to convert the string to a template object then render the template object and return it in the context.
page.html
{% load static %}
<html>
<head>
<styles, links, etc.>
<title>{{ object.title }}</title>
</head>
<body>
<!-- <div>{{ object.html_content }}</div> -->
<div>{{ html_content }}</div>
</body>
</html>
views.py
from django.views.generic.base import TemplateView
from .mixins import GlobalMixin
from .models import Page
# NEW
from django.shortcuts import get_object_or_404
from django.template import engines
PageView(GlobalMixin, TemplateView): # generic.DetailView
# model = Page
template_name = "my_app/page.html"
def get_context_data(self, *args, **kwargs):
context = super(PageView, self).get_context_data(*args, **kwargs)
# NEW
context['object'] = get_object_or_404(Page, slug=self.kwargs['slug'])
t = engines['django'].from_string(context['object'].html_content)
context['html_content'] = t.render(context=context, request=None)
return context
I thought you could use custom filter like below.
file path
yourapp/templatetags/__init__.py
yourapp/templatetags/filters.py
filters.py
from django import template
import re
register = template.Library()
#register.filter(name="change_global_variable")
def change_global_variable(value):
return re.sub(r'\{\{(.*)\}\}', 'A global piece', value)
template
{% load static %}
{% load filters %} //filters.py//
<html>
<head>
<styles, links, etc.>
<title>{{ object.title }}</title>
</head>
<body>
<div>{{ object.html_content | change_global_variable}}</div>
</body>
</html>

Unable to link blog post to its content page in Wagtail

I'm having a problem creating a link of a Blog Post to its own content page in wagtail. In my models I have two page classes, BlogPage and IndexPage. My BlogPage class is used to create the blog post, and IndexPage class is used to display a list of blog posts.
Please see models below:
from django.db import models
from modelcluster.fields import ParentalKey
from wagtail.wagtailcore.models import Page, Orderable
from wagtail.wagtailcore.fields import RichTextField
from wagtail.wagtailadmin.edit_handlers import FieldPanel, MultiFieldPanel, InlinePanel
from wagtail.wagtailimages.edit_handlers import ImageChooserPanel
from wagtail.wagtailsearch import index
class IndexPage(Page):
intro = RichTextField(blank=True)
def child_pages(self):
return BlogPage.objects.live()
content_panels = Page.content_panels + [
FieldPanel('intro', classname='full'),
]
subpage_types = ['blog.BlogPage']
class BlogPage(Page):
date = models.DateField("Post date")
intro = models.CharField(max_length=250)
body = RichTextField(blank=True)
search_fields = Page.search_fields + (
index.SearchField('intro'),
index.SearchField('body'),
)
content_panels = Page.content_panels + [
FieldPanel('date'),
FieldPanel('intro'),
FieldPanel('body', classname="full")
]
My challenge is that I can't figure out how to link the blog post on the Index Page to its own page. Do I need to create a separate page model and html template to achieve this? or what could be the best approach to solve this problem?
You can create an include template (it doesn't need a model) - let's name it truncated_blog_post.html - which you can then invoke in your index_page.html template. This would be the recommended approach because using a include template for a post gives the possibility to use it anywhere you need to display a list of (truncated usually) posts: when you want the posts under a certain tag, for example.
truncated_blog_post.html
{% load wagtailcore_tags %}
<article>
<h2>{{ blog.title }}</h2>
<p>{{ blog.date }}</p>
<p>{{ blog.body|truncatewords:40 }}</p>
</article>
Using the pageurl tag from wagtailcore_tags you get the relative URL of that blog post. Obviously, if you don't want to create a include template for a truncated post, you can put the article code from blog_post.html directly in the for loop in the index_page.html template.
And your index_page.html template:
....
{% for blog in blogs %}
{% include "path/to/includes/truncated_blog_post.html" %}
{% empty %}
No posts found
{% endfor %}
....
For this to work you have to modify the IndexPage model:
class IndexPage(Page):
intro = RichTextField(blank=True)
#property
def blogs(self):
blogs = BlogPage.objects.live()
return blogs
def get_context(self, request):
# Get blogs
blogs = self.blogs
# Update template context
context = super(IndexPage, self).get_context(request)
context['blogs'] = blogs
return context
content_panels = Page.content_panels + [
FieldPanel('intro', classname='full'),
]
subpage_types = ['blog.BlogPage']

Working with links in Django

I am working in a small blog application using Django. Sorry if the question is obvious, but I am a newbie. Actually it is my third since I started an online course. I have the following Queryset:
def all(request):
allTiles = Post.objects.values('title')
allPosts = Post.objects.all()[:3]
context = {'Posts': allPosts,"Titles":allTiles}
template = "home.html"
return render(request, template, context)
and the follwing html code:
<ol class="list-unstyled">
{% for singleTile in Titles %}
<li>{{singleTile.title}}</li>
{% endfor %}
</ol>
As you can see every title creates an link. Lets assume a person decides to read one of the posts. How can I use the title name and send a request back to the database to get the content of the post.
It is better to use the id or slug field for such task.
But if you surely want to use the title as the GET parameter then apply the urlencode filter to the field's value:
<a href="{% url 'post_detail' %}?title={{ singleTile.title|urlencode }}">
{{ singleTile.title }}
</a>
And the view will be something like this:
def post_detail(request):
post = get_object_or_404(Post, title=request.GET.get('title'))
return render(request, 'post_detail.html', {'post': post})
UPDATE: If you decide to go with the id/slug option then you can use the generic DetailView:
<a href="{% url 'post_detail' singleTile.id %}">
{{ singleTile.title }}
</a
urls.py:
from django.views.generic.detail import DetailView
from app.models import Post
url(r'^post/(?P<pk>\d+)/$', DetailView.as_view(model=Post),
name='post_detail')
You have to configure url first like
{% url 'app.views.post_id' singleTile.id %}</li>
In your urls
url(r'^post/(?P<post_id>\d+)/$', views.by_id, name='post_id'),
And in your views
def post_id(request, post_id):
allTiles = Post.objects.get(id=post_id)
return render(request, template, context)