is it possible to show HTML code like tables with css style instead of json/csv/text/whatever?
I tried to send html as string but it just inserts html like raw text
Thanks in advance!
This is an old question, but I just went through this and wanted to share my solution in case anyone else is going through it as well.
What I ended up having to do is shim the rest_framework.compat.apply_markdown function to enable the tables extension. At the end of my settings.py, I added the following:
# Monkey patch rest_framework's markdown rendering function, to enable the
# tables extension.
import markdown
import rest_framework.compat
def apply_markdown(text):
"""
Simple wrapper around :func:`markdown.markdown` to set the base level
of '#' style headers to <h2>.
"""
extensions = ['markdown.extensions.toc', 'markdown.extensions.tables']
extension_configs = {
'markdown.extensions.toc': {
'baselevel': '2'
}
}
md = markdown.Markdown(
extensions=extensions, extension_configs=extension_configs
)
return md.convert(text)
rest_framework.compat.apply_markdown = apply_markdown
In this case I'm using DRF 3.6.4 and markdown 2.6.9. In the original rest_framework.compat.apply_markdown function there's some code that sets different options based on the version of markdown, but I omitted that in the shim.
Also note that the default tables extension may not give you tables styled the way you want. I ended up copying markdown/extensions/tables.py into a new module and adding class="table" to the table element. The source for that change is in a gist. For more information about the limited table syntax in markdown see this thread.
For djangorestframework>=3.7 update the default view description function.
https://www.django-rest-framework.org/api-guide/settings/#view_description_function
REST_FRAMEWORK = {
# Module path to a callable which should have a signature (self, html=False)
'VIEW_DESCRIPTION_FUNCTION': 'app_name.view_description.get_view_description',
}
view_description.py
import markdown
from django.utils.encoding import smart_text
from django.utils.html import escape
from django.utils.safestring import mark_safe
from rest_framework.compat import (
HEADERID_EXT_PATH, LEVEL_PARAM, md_filter_add_syntax_highlight
)
from rest_framework.utils import formatting
TABLE_EXTENSION_PATH = 'markdown.extensions.tables'
def _apply_markdown(text):
extensions = [HEADERID_EXT_PATH, TABLE_EXTENSION_PATH]
extension_configs = {
HEADERID_EXT_PATH: {
LEVEL_PARAM: '2'
}
}
md = markdown.Markdown(
extensions=extensions, extension_configs=extension_configs
)
md_filter_add_syntax_highlight(md)
return md.convert(text)
def get_view_description(view_cls, html=False):
description = view_cls.__doc__ or ''
description = formatting.dedent(smart_text(description))
if html:
return mark_safe(_apply_markdown(description))
return description
in your settings.py use renderer classes.
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.AdminRenderer',
],
}
Have you installed the markdown and django-filter packages? These were required to get our HTML browsing capability working on a recent project.
sudo pip install markdown
sudo pip install django-filter
Related
I'm stuck with following problem while creating my Flask based blog.
Firstly, I used CKeditor 4 but than upgraded to 5 version.
I can't understand how to handle image upload on server side now, with adapters etc. As for 4 version, I used flask-ckeditor extension and Flask documentation to handle image uploading.
I didn't find any examples for this combination. I understand that I lack knowledge and I'm looking for advice in which direction may I advance and which concepts should I know to approach such subject.
Thanks in advance.
My takes on this so far:
According to https://ckeditor.com/docs/ckeditor5/latest/features/image-upload/simple-upload-adapter.html
(official guide on simplest adapters).
config.simpleUpload.uploadUrl should be like /upload route that was used in cke4. Object with URL property needed by cke5 is cke4's upload_successful which was returned by /upload route.
So I figured it out.
As for cke 4:
/upload route handled uploading process by returning upload_successful() from flask-ckeditor extension.
upload_successful() itself is a jsonify-ing function, which in turn modify arguments to fit json format.
As for cke 5:
There were some things aside upload handling, which caused problems.
Plugin used: "Simple upload adapter"
I integrated cke5 by downloading from Online-builder and then reinstalling and rebuilding it by myself. (for this on Ubuntu 20.04 I installed nodejs and npm by sudo apt install.) Plugin is installed by executing from /static/ckeditor folder:
npm install
npm install --save #ckeditor/ckeditor5-upload
npm run build (need to wait here for a little)
Different adapters may conflict and not allow Editor to load, so I removed CKFinder adapter from src/ckeditor.js in import and .builtinPlugins sections, replacing them by import SimpleUploadAdapter from '#ckeditor/ckeditor5-upload/src/adapters/simpleuploadadapter.js'; and SimpleUploadAdapter correspondingly.
.html, where CKEditor instance is created. body here is name of flask_wtf text-field:
<script>
ClassicEditor
.create( document.querySelector( '#body' ), {
extraPlugins: ['SimpleUploadAdapter'],
simpleUpload: {
uploadUrl: '/upload',
},
mediaEmbed: {previewsInData: true}
} )
.catch( error => {
console.error( error.stack );
} );
</script>
Things to notice:
In official guide plugins are recommended to enable as following:
.create( document.querySelector( '#editor' ), {
plugins: [ Essentials, Paragraph, Bold, Italic, Alignment ],
For me it is not working: Editor would not load with such syntaxis. What worked is this (from docs):
.create( document.querySelector( '#body' ), {
extraPlugins: ['SimpleUploadAdapter'],
So, plugins -> extraPlugins and PluginName -> 'PluginName'.
/upload route itself:
#main.route('/files/<path:filename>')
def uploaded_files(filename):
app = current_app._get_current_object()
path = app.config['UPLOADED_PATH']
return send_from_directory(path, filename)
#main.route('/upload', methods=['POST'])
def upload():
app = current_app._get_current_object()
f = request.files.get('upload')
# Add more validations here
extension = f.filename.split('.')[-1].lower()
if extension not in ['jpg', 'gif', 'png', 'jpeg']:
return upload_fail(message='Image only!')
f.save(os.path.join(app.config['UPLOADED_PATH'], f.filename))
url = url_for('main.uploaded_files', filename=f.filename)
return jsonify(url=url)
I will edit this answer as I advance in this subject.
I have a simple form like this:
class RecordForm(Form):
notes = TextAreaField('Notes')
I record the data in three paragraphs like this:
para1
para2
para3
In the template I would like to see the content of that record in read-only. (Not editable form)
record is this case the model containing the data:
<td>{{ record.notes }}</td>
-->
<td>para1 para2 para3</td>
What can I do to make it to show the multi-lines?
All whitespace, including newlines, is turned into a single space in HTML.
Your options, from best to worst:
Put white-space: pre-wrap; on the containing element. This tells HTML to show all whitespace exactly as it appears in the source, including newlines. (You could also use a <pre> tag, but that will also disable word-wrapping, which you probably don't want.)
Treat the plain text as Markdown and throw a Markdown processor at it—one of the things Markdown does is wrap paragraphs in <p>.
In Python-land, do .replace('\n', '<br>'). But this leaves you vulnerable to XSS because there might be other HTML-like junk in the string, and fixing that is a bit of a pain.
As suggested by Martijn Pieters (by linking Flask snippet 28), there is also the possibility to add a custom filter for that. The link is outdated, because Flask Snippets are no longer provided.
So I will provide the snippet from web archive here:
nl2br filter
Posted by Dan Jacob on 2010-06-17 # 05:03 and filed in Template Tricks
This is a nl2br (newline to <BR>) filter, adapted from the Jinja2 example here:
http://jinja.pocoo.org/2/documentation/api#custom-filters
import re
from jinja2 import evalcontextfilter, Markup, escape
_paragraph_re = re.compile(r'(?:\r\n|\r|\n){2,}')
app = Flask(__name__)
#app.template_filter()
#evalcontextfilter
def nl2br(eval_ctx, value):
result = u'\n\n'.join(u'<p>%s</p>' % p.replace('\n', '<br>\n') \
for p in _paragraph_re.split(escape(value)))
if eval_ctx.autoescape:
result = Markup(result)
return result
The link above about custom-filters seems to be outdated, too. Here is a similar link from the current stable version 1.1: https://flask.palletsprojects.com/en/1.1.x/templating/#registering-filters
I'm not really sure why he uses such a complicated result computation. For me the following code worked and it's much simpler. Maybe, the variant above is better if you don't use autoescape (which I do not want to disable)?! Anyway, now both variants are available:
# custom_template_filters.py
from flask import Blueprint
from jinja2 import evalcontextfilter, Markup, escape
blueprint = Blueprint('custom_template_filters', __name__)
#evalcontextfilter
#blueprint.app_template_filter()
def newline_to_br(context, value: str) -> str:
result = "<br />".join(re.split(r'(?:\r\n|\r|\n){2,}', escape(value)))
if context.autoescape:
result = Markup(result)
return result
It is worth mentioning that the code snippet from Dan Jacob uses Python2, and I get the template filters running via Blueprint. For the sake of completeness I also provide the app.py using a factory method:
# app.py
from flask import Flask
def create_app() -> Flask:
app = Flask(__name__)
...
register_template_filters(flask_app=app)
return app
def register_template_filters(flask_app: Flask) -> None:
from . import custom_template_filters
flask_app.register_blueprint(custom_template_filters.blueprint)
return None
It is more or less an implementation detail how you will get the context filter working. The main idea is inside the function nlbr or newline_to_br itself. If you get the custom filter working, it is available in all your Jinja templates and you can use it like this:
{{ anystring | newline_to_br }}
I've modified nl2br filter from documentation according to this https://stackoverflow.com/a/60572523/12851576 answer:
import re
from jinja2 import evalcontextfilter
from markupsafe import Markup, escape
#evalcontextfilter
def nl2br(eval_ctx, value):
"""Converts newlines in text to HTML-tags"""
result = "<br>".join(re.split(r'(?:\r\n|\r|\n)', escape(value)))
if eval_ctx.autoescape:
result = Markup(result)
return result
It works for me.
Are there any drawbacks?
I would like to know how I can modify the URL to the welcome page.
Currently it is /superset/welcome.
It is run into superset/views/core.py in a #expose('/welcome').
I know I can modify the code inside this #expose, but I want to redirect to another url.
So I want to find the line where there is:
welcome_page = /superset/welcome
As of Superset 1.3, you can change the default landing page by adding this code to your Superset config:
from flask import Flask, redirect
from flask_appbuilder import expose, IndexView
from superset.typing import FlaskResponse
class SupersetDashboardIndexView(IndexView):
#expose("/")
def index(self) -> FlaskResponse:
return redirect("/dashboard/list/")
FAB_INDEX_VIEW = f"{SupersetDashboardIndexView.__module__}.{SupersetDashboardIndexView.__name__}"
In the above example, I am using /dashboard/list/ instead of the default /superset/welcome/.
The code above is Unlicensed and thus is free and unencumbered software released into the public domain.
In superset's file structure, navigate to:
superset/app.py
There you will find
class SupersetIndexView(IndexView):
#expose("/")
def index(self) -> FlaskResponse:
return redirect("/superset/welcome")
Modify this to path where you want to redirect.
I would like to add Yogaglo support for youtube-dl.
I've followed guidance on Github .
And have drafted the following:
# coding: utf-8
from __future__ import unicode_literals
from .common import InfoExtractor
class YogagloIE(InfoExtractor):
_SIGNIN_URL = 'https://www.yogaglo.com/login'
_PASSWORD_URL = 'https://www.yogaglo.com/login/password'
_USER_URL = 'https://www.yogaglo.com/login/user'
_ACCOUNT_CREDENTIALS_HINT = 'Use --username and --password options to provide yogaglo.com account credentials.'
_NETRC_MACHINE = 'yogaglo'
def _real_initialize(self):
self._login()
_VALID_URL = r'https?://(?:www\.)?yogaglo\.com/class/(?P<id>[0-9]+)'
_TEST = {
'url': 'https://www.yogaglo.com/class/7206',
'md5': 'TODO: md5 sum of the first 10241 bytes of the video file (use --test)',
'info_dict': {
'id': '7206',
'ext': 'mp4',
'title': 'Have a Great Day!'
# TODO more properties, either as:
# * A value
# * MD5 checksum; start the string with md5:
# * A regular expression; start the string with re:
# * Any Python type (for example int or float)
}
}
def _real_extract(self, url):
video_id = self._match_id(url)
webpage = self._download_webpage(url, video_id)
title = self._html_search_regex(r'<h1>(.+?)</h1>', webpage, 'title')
return {
'id': video_id,
'title': title,
'description': self._og_search_description(webpage),
'uploader': self._search_regex(r'<div[^>]+id="uploader"[^>]*>([^<]+)<', webpage, 'uploader', fatal=False),
# TODO more properties (see youtube_dl/extractor/common.py)
}
I've added yogagloIE to the list of extractors and when I run it I get an error that the URL is not supported. This is really a first draft and any guidance on hoe to improve it and make it work is recommended.
In Python, indentation is significant, so make sure you indent your class correctly.
After that, you must define a _login method or simply leave _real_initialize empty.
With that being implemented, the extractor will be called (although of course it's not functional yet):
$ youtube-dl test:yogaglo
[TestURL] Test URL: https://www.yogaglo.com/class/7206
[Yogaglo] 7206: Downloading webpage
ERROR: Unable to extract title; please report this issue on https://yt-dl.org/bug . Make sure you are using the latest version; see https://yt-dl.org/update on how to update. Be sure to call youtube-dl with the --verbose flag and include its complete output.
You can pull this working state with the following command (maybe git stash everything beforehand, and rename your old yogaglo.py file to something else):
git pull https://github.com/phihag/youtube-dl.git yogaglo
I have a simple form like this:
class RecordForm(Form):
notes = TextAreaField('Notes')
I record the data in three paragraphs like this:
para1
para2
para3
In the template I would like to see the content of that record in read-only. (Not editable form)
record is this case the model containing the data:
<td>{{ record.notes }}</td>
-->
<td>para1 para2 para3</td>
What can I do to make it to show the multi-lines?
All whitespace, including newlines, is turned into a single space in HTML.
Your options, from best to worst:
Put white-space: pre-wrap; on the containing element. This tells HTML to show all whitespace exactly as it appears in the source, including newlines. (You could also use a <pre> tag, but that will also disable word-wrapping, which you probably don't want.)
Treat the plain text as Markdown and throw a Markdown processor at it—one of the things Markdown does is wrap paragraphs in <p>.
In Python-land, do .replace('\n', '<br>'). But this leaves you vulnerable to XSS because there might be other HTML-like junk in the string, and fixing that is a bit of a pain.
As suggested by Martijn Pieters (by linking Flask snippet 28), there is also the possibility to add a custom filter for that. The link is outdated, because Flask Snippets are no longer provided.
So I will provide the snippet from web archive here:
nl2br filter
Posted by Dan Jacob on 2010-06-17 # 05:03 and filed in Template Tricks
This is a nl2br (newline to <BR>) filter, adapted from the Jinja2 example here:
http://jinja.pocoo.org/2/documentation/api#custom-filters
import re
from jinja2 import evalcontextfilter, Markup, escape
_paragraph_re = re.compile(r'(?:\r\n|\r|\n){2,}')
app = Flask(__name__)
#app.template_filter()
#evalcontextfilter
def nl2br(eval_ctx, value):
result = u'\n\n'.join(u'<p>%s</p>' % p.replace('\n', '<br>\n') \
for p in _paragraph_re.split(escape(value)))
if eval_ctx.autoescape:
result = Markup(result)
return result
The link above about custom-filters seems to be outdated, too. Here is a similar link from the current stable version 1.1: https://flask.palletsprojects.com/en/1.1.x/templating/#registering-filters
I'm not really sure why he uses such a complicated result computation. For me the following code worked and it's much simpler. Maybe, the variant above is better if you don't use autoescape (which I do not want to disable)?! Anyway, now both variants are available:
# custom_template_filters.py
from flask import Blueprint
from jinja2 import evalcontextfilter, Markup, escape
blueprint = Blueprint('custom_template_filters', __name__)
#evalcontextfilter
#blueprint.app_template_filter()
def newline_to_br(context, value: str) -> str:
result = "<br />".join(re.split(r'(?:\r\n|\r|\n){2,}', escape(value)))
if context.autoescape:
result = Markup(result)
return result
It is worth mentioning that the code snippet from Dan Jacob uses Python2, and I get the template filters running via Blueprint. For the sake of completeness I also provide the app.py using a factory method:
# app.py
from flask import Flask
def create_app() -> Flask:
app = Flask(__name__)
...
register_template_filters(flask_app=app)
return app
def register_template_filters(flask_app: Flask) -> None:
from . import custom_template_filters
flask_app.register_blueprint(custom_template_filters.blueprint)
return None
It is more or less an implementation detail how you will get the context filter working. The main idea is inside the function nlbr or newline_to_br itself. If you get the custom filter working, it is available in all your Jinja templates and you can use it like this:
{{ anystring | newline_to_br }}
I've modified nl2br filter from documentation according to this https://stackoverflow.com/a/60572523/12851576 answer:
import re
from jinja2 import evalcontextfilter
from markupsafe import Markup, escape
#evalcontextfilter
def nl2br(eval_ctx, value):
"""Converts newlines in text to HTML-tags"""
result = "<br>".join(re.split(r'(?:\r\n|\r|\n)', escape(value)))
if eval_ctx.autoescape:
result = Markup(result)
return result
It works for me.
Are there any drawbacks?