Image uploading in Flask blog with CKeditor 5 - python

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.

Related

Flask frontend is working properly, however the svelte app inside is not, what am I missing

I have been strugling with this example https://github.com/cabreraalex/svelte-flask-example. I created a simpler example here
When I run the server.py, I get the template, in the source code I even see the main.js. When I go to /rand I see the randomly generated number, however I am missing something, as I can not get the App.svelte to work.
This is what the server.py contains:
#app.route('/home')
def base():
return send_from_directory('templates/', 'index.html' )
#app.route('/static/<path:path>')
def home(path):
return send_from_directory('', path)
#app.route('/rand')
def rand():
return str(randint(0,100))
The index.html has the <script src="../static/main.js" defer></script>
The main JS import the svelte App
import App from './App.svelte'
const app = new App({
target: document.querySelector('#svelte-app')
})
The Svelte app itself:
<script>
let rand = -1
function getRand(){
fetch('./rand')
.then(d => t.text())
.then(d => (rand = d));
}
</script>
<h1> your number is: {rand}</h1>
<button on:click={getRand}>Get a random number</button>
I am brand new to the combination of flask and JS, so I am sorry in advance.
I am assuming that you installed Node.js when creating your Svelte project and used the instructions on the Svelte website.
With Svelte it is not possible to import your source code directly into an HTML page. It is necessary to build the project first with npm run build. This will generate the final code, which can be found in the "public/build" folder. It also happens regularly when you start the development server with npm run dev and modify one of the files.
If you have followed this step, you can serve all files from the "public" directory and its subdirectories with Flask. Because that's where the final product is for release.
In order to also forward requests to your Flask server with the development server, it is necessary to install a plugin and define a proxy. I used the "rollup-plugin-dev" plugin and made the simple modification below to the "rollup.config.js" file.
import localdev from 'rollup-plugin-dev';
// ...
plugins: [
// ...
// This is the proxy definition
!production && localdev({
dirs: ['public'],
host: 'localhost',
port: 8080,
proxy: [
{
from : '/rand',
to: 'http://localhost:5000/rand'
}
],
}),
// This line is now no longer needed.
// In dev mode, call `npm run start` once
// the bundle has been generated
// !production && serve(),
// ...
],
// ...
You can then simplify your Flask application a bit. Here the argument static_folder should point to the "public" folder of your svelte application.
from flask import Flask
from random import randint
app = Flask(__name__,
static_url_path = '',
static_folder='../client/public/'
)
#app.route('/')
def index():
return app.send_static_file('index.html')
#app.route('/rand')
def rand():
return str(randint(0,100))
As an aside, I have to tell you that there is a small typo within a then block of your fetch call. The following call should not throw an error.
fetch('./rand')
.then(r => r.text())
.then(d => (rand = d));
I wish you fun and success in implementing your project.

How to override aws_cdk.core.LegacyStackSynthesizer.add_docker_image_asset in AWS CDK for Python

I would like to upload a docker image from local disc to a repository that I created with the AWS CDK.
When i use aws_ecr_assets.DockerImageAsset to add a Docker image to a repository (which works fine except that I am not able to set permissions on its repository via CDK), I get the following deprecation warning:
DockerImageAsset.repositoryName is deprecated. Override "core.Stack.addDockerImageAsset" to control asset locations
When looking into core.Stack.addDockerImageAsset, I get a hint that I should override stack.synthesizer.addDockerImageAsset().
My simplified stack with a custom synthesizer looks like this:
class CustomSynthesizer(core.LegacyStackSynthesizer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def add_docker_image_asset(
self,
*,
directory_name,
source_hash,
docker_build_args=None,
docker_build_target=None,
docker_file=None,
repository_name=None,
):
# What should I put in this function to upload the docker image into my repository?
class ContainerStack(core.Stack):
def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
super().__init__(scope, id, synthesizer=CustomSynthesizer(), **kwargs)
repo = ecr.Repository(self, "TestRepo", repository_name="test_repo")
repo.grant_pull(iam.AccountPrincipal("123456789123"))
image_location = self.synthesizer.add_docker_image_asset(
directory_name="path/to/dir/with/Dockerfile",
source_hash="latest",
repository_name=repo.repository_name,
)
Another thing that I tried is to use the standard stack synthesizer and calling add_docker_image_asset on it. Unfortunately, I get the following error message and the stack fails to deploy:
test-stack: deploying...
[0%] start: Publishing latest:current
[25%] fail: Unable to execute 'docker' in order to build a container asset. Please install 'docker' and try again.
...
❌ test-stack failed: Error: Failed to publish one or more assets. See the error messages above for more information.
at Object.publishAssets (/home/user/.npm-global/lib/node_modules/aws-cdk/lib/util/asset-publishing.ts:25:11)
at processTicksAndRejections (internal/process/task_queues.js:97:5)
at Object.deployStack (/home/user/.npm-global/lib/node_modules/aws-cdk/lib/api/deploy-stack.ts:216:3)
at CdkToolkit.deploy (/home/user/.npm-global/lib/node_modules/aws-cdk/lib/cdk-toolkit.ts:181:24)
at main (/home/user/.npm-global/lib/node_modules/aws-cdk/bin/cdk.ts:268:16)
at initCommandLine (/home/user/.npm-global/lib/node_modules/aws-cdk/bin/cdk.ts:188:9)
Failed to publish one or more assets. See the error messages above for more information.
I'm flat on my back as to how to solve this problem and any help is much appreciated!
DockerImageAsset is a managed construct, which handles the repository and versioning by itself. By default it creates a repository for your stack and uploads your docker images to it (tagging them by their hash).
You do not need to create this repository yourself. However, if you are like me and want to have a legible name for the repository, you can name the repo using cdk.json config file and its context section:
// cdk.json
{
"app": "python3 your_app",
"context": {
"#aws-cdk/core:enableStackNameDuplicates": "true",
"aws-cdk:enableDiffNoFail": "true",
"assets-ecr-repository-name": "<REPO NAME GOES HERE>"
}
}
If you want to further alter the repo (I like to leave it fully managed, but hey), you can load the repo into the CDK stack by using one of the static methods on the aws_ecr.Repository construct.
https://docs.aws.amazon.com/cdk/api/latest/docs/#aws-cdk_aws-ecr.Repository.html#static-from-wbr-repository-wbr-namescope-id-repositoryname
Hope this helps a little :-)

Google Cloud Function Python installing dependency on each request

requirements.txt:
pymystem3==0.2.0
main.py:
from pymystem3 import Mystem
import json
def hello_world(request):
request_json = request.get_json()
if request_json and 'text' in request_json:
text = request_json['text']
m = Mystem()
lemmas = m.lemmatize(text)
return json.dumps(m.analyze(text), ensure_ascii=False, encoding='utf8', sort_keys=True, indent=2)
else:
return 'No text provided'
Logs in google cloud:
hello_world
032ljsdfawo
Function execution started
hello_world
032ljsdfawo
Installing mystem to /tmp/.local/bin/mystem from http://download.cdn.yandex.net/mystem/mystem-3.1-linux-64bit.tar.gz
Expand all | Collapse all {
insertId: "000000-a9eaa07b-d936-45d8-a186-927cc94246d6"
labels: {…}
logName: "projects/project_name/logs/cloudfunctions.googleapis.com%2Fcloud-functions"
receiveTimestamp: "2018-09-26T09:41:31.070927654Z"
resource: {…}
severity: "ERROR"
textPayload: "Installing mystem to /tmp/.local/bin/mystem from http://download.cdn.yandex.net/mystem/mystem-3.1-linux-64bit.tar.gz"
timestamp: "2018-09-26T09:41:19.595Z"
}
I'm new to python, so it's probably a very simple mistake. Maybe dependencies suppose to be installed before deploying the function somehow and compiled with main.py?
This is due to unfortunate behavior by the pymsystem library: instead of including everything it needs in it's Python package, it's attempting to download additional things at runtime, specifically every time you import it.
You can see that the message in your logs is coming from the library itself.
It seems like the library is attempting to determine if it has already been installed or not, but this might not be working correctly. I'd recommend filing an issue on the project's issue tracker.

Python & gdata within Django app: "POST method does not support concurrency"

I am trying to use gdata within a Django app to create a directory in my google drive account. This is the code written within my Django view:
def root(request):
from req_info import email, password
from gdata.docs.service import DocsService
print "Creating folder........"
folder_name = '2015-Q1'
service_client = DocsService(source='spreadsheet create')
service_client.ClientLogin(email, password)
folder = service_client.CreateFolder(folder_name)
Authentication occurs without issue, but that last line of code triggers the following error:
Request Method: GET
Request URL: http://127.0.0.1:8000/
Django Version: 1.7.7
Exception Type: RequestError
Exception Value: {'status': 501, 'body': 'POST method does not support concurrency', 'reason': 'Not Implemented'}
I am using the following software:
Python 2.7.8
Django 1.7.7
PyCharm 4.0.5
gdata 2.0.18
google-api-python-client 1.4.0 (not sure if relevant)
[many other packages that I'm not sure are relevant]
What's frustrating is that the exact same code (see below) functions perfectly when I run it in its own, standalone file (not within a Django view).
from req_info import email, password
from gdata.docs.service import DocsService
print "Creating folder........"
folder_name = '2015-Q1'
service_client = DocsService(source='spreadsheet create')
service_client.ClientLogin(email, password)
folder = service_client.CreateFolder(folder_name)
I run this working code in the same virtual environment and the same PyCharm project as the code that produced the error. I have tried putting the code within a function in a separate file, and then having the Django view call that function, but the error persists.
I would like to get this code working within my Django app.
I don't recall if I got this to work within a Django view, but because Google has since required the use of Oauth 2.0, I had to rework this code anyways. I think the error had something to do with my simultaneous use of two different packages/clients to access Google Drive.
Here is how I ended up creating the folder using the google-api-python-client package:
from google_api import get_drive_service_obj, get_file_key_if_exists, insert_folder
def create_ss():
drive_client, credentials = get_drive_service_obj()
# creating folder if it does not exist
folder = get_file_key_if_exists(drive_client, 'foldername')
if folder: # if folder exists
print 'Folder "' + folder_name + '" already exists.'
else: # if folder doesn't exist
print 'Creating folder........"' + folder_name + '".'
folder = insert_folder(drive_client, folder_name)
After this code, I used a forked version (currently beta) of sheetsync to copy my template spreadsheet and populate the new file with my data. I then had to import sheetsync after the code above to avoid the "concurrency" error. (I could post the code involving sheetsync here too if folks want, but for now, I don't want to get too far off topic.)

Problems with SSL(django-sslserver) on Django project

I am using Django 1.6.2 in virtualenv, Ubuntu 12.04 LTS. As I wanted to shift my project to https, I installed django-sslserver. The project needs self signing, and works fine for Home Page. However, apps in my django project encounter problems. Not all pages are redirected to https, and hence causes 404 error (works only if explicitly prefixed as https). Also, the overall template (appearance i.e. static files?) is lost.
What exactly is happening here? How to make sure that all pages are redirected to https and works the same way as in http?
Edited: My pull request has been merged. Static resources are served normally now.
The problem is that the runsslserver command is not implemented to serve static resources. A way to fix is to override get_handler in PATH_TO_PYTHON_SITE_PACKAGE/sslserver/management/commands/runsslserver.py like so:
# ...
from django.contrib.staticfiles.handlers import StaticFilesHandler
from django import get_version
# ...
class Command(runserver.Command):
# ...
help = "Run a Django development server over HTTPS"
def get_handler(self, *args, **options):
"""
Returns the static files serving handler wrapping the default handler,
if static files should be served. Otherwise just returns the default
handler.
"""
handler = super(Command, self).get_handler(*args, **options)
use_static_handler = options.get('use_static_handler', True)
insecure_serving = options.get('insecure_serving', False)
if use_static_handler:
return StaticFilesHandler(handler)
return handler
# ...
You might want to get your site package path with
python -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())"
I've also submitted a pull request in case you want to branch, merge, and reinstall it as a package on your own.
Cheers

Categories