I am integrating CKeditor to Flask to add rich text support. But when file upload feature is enabled, the post request is always failed. It should be the csrf problem. Added {{csrf_token()} directly doesn't work. Is there anyplace in CKeditor should be changed to add csrf?
{% block content %}
<h1>Add articile:</h1>
<form>
<input type="hidden" name="csrf_token" value="{{csrf_token()}}" />
<textarea name="editor1" id="editor1" rows="20" cols="80">
This is my textarea to be replaced with CKEditor.
</textarea>
<script type="text/javascript">
CKEDITOR.replace('editor1', {
filebrowserUploadUrl: '/ckupload',
});
</script>
</form>
{% endblock %}
To handle file upload,
def gen_rnd_filename():
filename_prefix = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
return '%s%s' % (filename_prefix, str(random.randrange(1000, 10000)))
#app.route('/ckupload', methods=['POST', 'OPTIONS'])
def ckupload():
"""CKEditor file upload"""
error = ''
url = ''
callback = request.args.get("CKEditorFuncNum")
print callback
print request.method
if request.method == 'POST' and 'upload' in request.files:
fileobj = request.files['upload']
fname, fext = os.path.splitext(fileobj.filename)
rnd_name = '%s%s' % (gen_rnd_filename(), fext)
filepath = os.path.join(app.static_folder, 'upload', rnd_name)
dirname = os.path.dirname(filepath)
if not os.path.exists(dirname):
try:
os.makedirs(dirname)
except:
error = 'ERROR_CREATE_DIR'
elif not os.access(dirname, os.W_OK):
error = 'ERROR_DIR_NOT_WRITEABLE'
if not error:
fileobj.save(filepath)
url = url_for('static', filename='%s/%s' % ('upload', rnd_name))
else:
error = 'post error'
res = """<script type="text/javascript">
window.parent.CKEDITOR.tools.callFunction(%s, '%s', '%s');
</script>""" % (callback, url, error)
response = make_response(res)
response.headers["Content-Type"] = "text/html"
return response
Current my workaround is to add csrf exception to this url.
#csrf.exempt
CKEditor use AJAX to send uploads, so you can add CSRF support in this way:
<script type="text/javascript">
CKEDITOR.replace( "textarea-name", {
fileTools_requestHeaders: {
'X-CSRFToken': '{{ csrf_token() }}',
},
});
</script>
BTW, I recommend use Flask-CKEditor to integrate CKEditor with Flask-WTF, it makes this usage easier:
from flask_wtf import CSRFProtect
app = Flask(__name__)
# the secret key used to generate CSRF token
app.config['SECRET_KEY'] = 'dev key'
# enable CSRF protection
app.config['CKEDITOR_ENABLE_CSRF'] = True
csrf = CSRFProtect(app)
Related
this is my code:
$("select").change(function(){
$.post("/sort", {sort:$(this).val()}, function(table_data)
{
for (let i in table_data)
{
var tr = $("<tr/>");
var filename = table_data[i].filename;
var size = table_data[i].size;
var uploaded = table_data[i].upload_time;
tr.append("<td>"+filename+"</td>");
tr.append("<td>"+size+"</td>");
tr.append("<td>"+uploaded+"</td>");
**tr.append("<td>"+"<a href='{{url_for('.download', filename=***filename***)}}'>"+'Download'+"</a>"+"</td>")**;
tr.appendTo(table);
}
Interestingly the jinja statement inside the js statement works, the browser directs to that path, but the filename remains None, because the server, which is using python flask, cannot resolve the value sent from here which is a js variable. Table_data is a json that was returned from the server using jsonify in response to an ajax call. My question is if there's any way to use that 'filename' js variable inside that jinja statement, or perhaps convert it to a jinja variable. Thanks.
As I wrote in my comment, it is only possible to pass variables from Jinja to JavaScript when the page is served. This time has already passed during the AJAX request.
I think the best solution is to construct the url with url_for on the server and transfer it via JSON with the record. So your requirements should be met.
I wrote you a small example, which is based on your question.
All folders in the application's static folder are listed and made available to the user for selection. If he selects a directory, file information and the download URL are queried via Ajax. The information is then displayed in a table.
Flask (app.py)
import os
from datetime import datetime
from flask import Flask
from flask import (
jsonify,
render_template,
request,
send_from_directory,
url_for
)
app = Flask(__name__)
#app.route('/')
def index():
# List all folders in the static folder.
folders = [file \
for file in os.listdir(app.static_folder) \
if os.path.isdir(os.path.join(app.static_folder, file))
]
return render_template('index.html', **locals())
#app.route('/stats', methods=['POST'])
def stats():
# Return stats of all files in the selected folder.
if 'target' in request.form:
data = []
target = request.form['target']
try:
for filename in os.listdir(os.path.join(app.static_folder, target)):
filepath = os.path.join(app.static_folder, target, filename)
data.append(
{
'filename': filename,
'filesize': os.path.getsize(filepath),
'filetime': datetime.fromtimestamp(os.path.getmtime(filepath)).isoformat(),
'filedest': url_for('.download', filename=os.path.join(target, filename))
}
)
return jsonify(data)
except OSError: pass
return '', 400
#app.route('/download/<path:filename>')
def download(filename):
return send_from_directory(app.static_folder, filename)
HTML (templates/index.html)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Index</title>
</head>
<body>
<select name="target">
<option disabled selected value>-- select an option --</option>
{% for folder in folders -%}
<option value="{{folder}}">{{folder}}</option>
{% endfor -%}
</select>
<table id="my-table" style="width: 100%"></table>
<script
src="https://code.jquery.com/jquery-3.6.0.min.js"
integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4="
crossorigin="anonymous"></script>
<script type="text/javascript">
((uri) => {
$(document).ready(function() {
$('select[name="target"]').change(function(evt) {
$('table#my-table').empty();
$.post(uri, { target: $(this).val() })
.done(function(data) {
data.forEach(info => {
const { filename, filesize, filetime, filedest } = info;
const row = $('<tr/>');
row.html(`
<td>${filename}</td>
<td>${filesize}</td>
<td>${filetime}</td>
<td>Download</td>
`)
row.appendTo($('table#my-table'));
});
});
})
});
})({{ url_for('.stats') | tojson }});
</script>
</body>
</html>
My code is working fine, but I can't figure out how to catch a file arrives message in JavaScript.
I'm working with Django 2.1.
Template:
...
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<div class="form-group">
...
<input type="file" id="prd" name="prd" required>
</div>
...
<input id="btn_ini" type="submit" name="submit" value="Init process">
</form>
...
<script type="text/javascript">
document.addEventListener('???', function(){
// I wish indicate to the user that the process is runnning and don't close the page.
// at the moment the logic is working as spected but I can't achieve how to check in javascript when file arrives.
})
</script>
</body>
</html>
Views:
def add_destination(request):
...
if request.method == 'POST':
...
return handle_uploaded_file(prd)
return render(request, 'add-destination.html')
def handle_uploaded_file(f)
...
file = # processing file
...
with open(file, 'rb') as f:
file_data = f.read()
response = HttpResponse(
file_data, content_type='application/force-dowload')
response['Content-Disposition'] = 'attachment; filename="proc_{}"'.format(incoming_file_name)
return response
return None
After read some books and many test, I found a solution
All backend code has no changes and only I improved the javascript code
<script type="text/javascript">
const form = document.querySelector('form');
const url = '{% url "app:index" %}';
const loading = document.querySelector('.loading');
let objUrl = null;
form.submit = async e => {
e.preventDefault()
// ...
const formData = new FormData(e.target);
// fetch file from server
const result = await fetch(url,{
method:'POST',
data:formData
})
// convert it to blob
const blob = await result.blob()
// Create obj locally
objUrl = URL.createObjectURL(blob)
//revoke url
URL.revokeObjectURL(blob)
// ....
}
When client submit Form, everythings works as expected.
The browser open it dialog automatically when file arrive after my processes done at server side.
I would like to pass some data from my Python view function to a JS script using HTML. That's my view function
def home(request):
if request.method == 'POST':
params = GameOfLifeForm(request.POST)
if params.is_valid():
starting_grid = get_starting_grid(params.cleaned_data)
to_html = {
'animation': True,
'gameoflifeform': params,
'start_grid': starting_grid,
}
else:
to_html = {
'animation': False,
'warning_msg': 'Something went wrong. Try once again.'
}
return render(request, 'get_animations/home.html', to_html)
else:
form = GameOfLifeForm()
return render(request, 'get_animations/home.html', {'gameoflifeform': form})
My form contains four parameters, one of them is called iterations and that is the one I would like to pass to JS script. Moreover I would like to pass start_grid.
I tried to do it in the following way in my HTML file
{{ start_grid | json_script:"start-grid" }}
<script
type="text/javascript"
src="{% static 'js/runGameOfLife.js' %}"
></script>
Then in my JS script I wrote
var startGrid = JSON.parse(document.getElementById("start-grid").textContent);
console.log(startGrid);
Worked perfectly, I got the grid printed out in my console. Similar I could grab iterations from HTML
{{ gameoflifeform.iterations.value | json_script:"iterations"}}
<script
type="text/javascript"
src="{% static 'js/runGameOfLife.js' %}"
></script>
When I tried to add both variables into my JS script it didn't work.
{{ gameoflifeform.iterations.value | json_script:"iterations"}}
{{ start_grid | json_script:"start-grid" }}
<script
type="text/javascript"
src="{% static 'js/runGameOfLife.js' %}"
></script>
How can I pass several variables into my JS script? What would be the best way of doing it?
Best to use some combination of ajax and view functions, if you learn this pattern you can accomplish a lot:
views.py
def my_view_function(request):
''' this method accepts data from the front end,
processes it, and returns a response '''
# unpack post request:
first_value = request.POST.get('first_key')
# do some logic:
...
# pack response
response = {
"second_key" : "second_value"
}
# return a json response:
return JsonResponse(response)
scripts.js
function post_request() {
/* set an event to trigger this method
this method will then send data to the backend
and process the response */
// send an ajax post request:
$.ajax({
type : 'POST',
url : 'the_url',
data : {
first_key : 'first_value'
},
success : function(response) {
// unpack response
second_value = response.second_key
// do some logic
...
}
});
}
Once you understand this, you will be able to seamlessly pass data back and forth between the frontend and backend. Let me know if you need more details.
Solution:
My mistake was that the url attribute doesn't just add the string given to 127.0.0.1 but to the current url, and so the url for the Like view was supposed to be 127.0.0.1/article/article_id/like-article-commm
I wrote a django app that has articles and im trying to add a like functionality, I added the code in the bottom and nothing happens. No error just nothing in the database or the html code changes. Any idea what is the problem?
The relevent part of the html/javascript code:
<head>
<script src="https://code.jquery.com/jquery-3.4.1.js" integrity="sha256-WpOohJOqMqqyKL9FccASB9O0KwACQJpFTUBLTYOVvVU=" crossorigin="anonymous"></script>
</head>
<button id='like-button' color = 'black'> Like </button>
<script type="text/javascript">
$('#like-button').click(function(){
var article_id = '{{ article.id }}';
var user_id = '{{ user.id }}';
var like_dislike = True;
$.ajax(
{
type:"GET",
url: "like-article-commm",
data:{
article_id: article_id,
user_id: user_id,
like_dislike: like_dislike
},
success: function( data )
{
$('#like-button').css('color', 'blue'); } }) });
</script>
The like-article-comm View:
def Like_Article_View(request):
if request.method == 'GET':
article_id = int(request.GET['article_id'])
likedarticle = Article.objects.get(id = article_id)
user_liked_id = int(request.GET['user_id'])
userliked = User.objects.get(id = user_liked_id)
like_dislike_0 = request.GET['like_dislike']
like_object_list = Like_Article.objects.filter(article_liked = likedarticle, user_liked = userliked)
if like_object_list.count() > 0:
existing_like = like_object_list.filter()
if existing_like.like_dislike == like_dislike_0:
return HttpResponse('success')
existing_like.like_dislike = like_dislike_0
existing_like.save()
like_new_object= Like_Article(article_liked=likedarticle, user_liked=userliked, like_dislike=like_dislike_0)
like_new_object.save()
return HttpResponse('success')
else:
return HttpResponse("unsuccesful")
urls.py file:
from django.urls import path
from . import views
from .views import ArticleListView, ArticleDetailView, ArticleCreateView, ArticleUpdateView, ArticleDeleteView
urlpatterns = [
path('', ArticleListView.as_view(), name="home-comm"),
path('article/<int:pk>/', ArticleDetailView.as_view(), name="article-comm"),
path('like_article/', views.Like_Article_View, name='like-article-commm'),
]
I can add like objects to the database manually.
--update--
After some discussion, we figure out that frontend ajax is not communicating with the backend properly. With chrome devtools we managed to pin point where the issue lies.
have you enabled CORS cross site resource sharing?
I'm trying to upload a dicom file to a flask server that runs a deep learning model and get the predicted values from the server as in JSON format!
so the problem is in the javascript code below. is there a way to do both sending and getting values at the same time? please Help!!
HTML:
<body>
<input id="image-selector" type="file">
<button id="predict-button">Predict</button>
<p><h1>PREDICTIONS</h1></p>
<span id="predicted-value_1">
<span id="predicted-value_2">
<span id="predicted-value_3">
</body>
JavaScript
$("#predict-button").click(function(){
var form_data = new FormData();
var ins = document.getElementById('image-selector').files.length;
if(ins == 0) {
$('#msg').html('<span style="color:red">Select one file</span>');
return;
}
else{
form_data = document.getElementById('image-selector').files[0]
}
let message = {
"file": form_data
}
console.log(message);
$.post("http://127.0.0.1:5000/predict", JSON.stringify(message), function(response){
$("#predicted-value_1").text(response.prediction.value1);
$("#predicted-value_1").text(response.prediction.value2);
$("#predicted-value_1").text(response.prediction.value3);
console.log(response);
});
});
Python
#app.route("/predict", methods=['GET', 'POST'])
def predict():
if request.method == 'POST':
# check if the post request has the file part
if 'file' not in request.files:
flash('No file part')
return redirect(request.url)
file = request.files['file']
# if user does not select file, browser also
# submit an empty part without filename
if file.filename == '':
flash('No selected file')
return redirect(request.url)
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
#Rest of the code! I can take it from here!!
I did something like this in my flask demo. You can have a look and give a try.
HTML
<h4>Welcome to ImageClassifier Demo</h4>
<input type=text size=5 name=url>
<button id="prediction" type="submit">
<span>Predict</span>
</button>
<span id=result>
Result:
</span>
js
<script type="text/javascript">
$(document).ready(function(){
$('#prediction').bind('click', function() {
$.getJSON('/predict', {
a: $('input[name="url"]').val()
}, function(data) {
console.log("Result Received")
$("#result").text("Result: " + data.result);
});
return false;
});
});
</script>