Days
Hours
Minutes
Seconds
x

Froala Editor v4.2.0 is Here LEARN MORE

Skip to content

Pyramid

File Upload

The following sections describe how to handle file uploads on your server using pyramid as a server-side language. For information on the upload workflow refer to the file upload documentation.

Frontend

Setting up the index page.

  1. On the head section include the Editor style.

  2. <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    
    <link href="https://cdn.jsdelivr.net/npm/froala-editor@latest/css/froala_editor.pkgd.min.css" rel="stylesheet" type="text/css" />
    </head>
  3. On the body section include the Editor JS files and define the area for the editor.

  4. <body>  
    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/froala-editor@latest/js/froala_editor.pkgd.min.js"></script>
    
    <div class="sample">
    <h2>File upload example.</h2>
    <form>
    <textarea id="edit" name="content"></textarea>
    </form>
    </div>
    
  5. Initialize the editor and set the file upload URL

  6. <script>
    new FroalaEditor('#edit', {
    // Set the file upload URL.
    fileUploadURL: '/UploadFiles',
    
    fileUploadParams: {
    id: 'my_editor'
    }
    })
    </script>
    </body>
    </html>

The full code should look like this:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link href="https://cdn.jsdelivr.net/npm/froala-editor@latest/css/froala_editor.pkgd.min.css" rel="stylesheet" type="text/css" />
</head>

<body>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/froala-editor@latest/js/froala_editor.pkgd.min.js"></script>

<div class="sample">
<h2>File upload example.</h2>
<form>
<textarea id="edit" name="content"></textarea>
</form>
</div>


<script>
new FroalaEditor('#edit', {

fileUploadURL: '/UploadFiles',

fileUploadParams: {
id: 'my_editor'
}
})
</script>
</body>
</html>

Backend

server.py handles the server part for pyramid

In server.py, the File class handles file uploads

from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
from pyramid.response import FileResponse
import os.path
import base64
import datetime
from os.path import isfile, join
from mimetypes import MimeTypes
from os import listdir
import wand
from wand.image import Image
import hashlib
import shutil
import json
import time
import hmac
import copy
import sys
import os


# Create public directory at startup.
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
publicDirectory = os.path.join(BASE_DIR, "public")
if not os.path.exists(publicDirectory):
os.makedirs(publicDirectory)


def get_main_html(request):
return FileResponse("index.html")


def upload_file(request):
options = {
"validation": None
}
try:
response = File.upload(PyramidAdapter(request), "/public/", options)
except Exception:
response = {"error": str(sys.exc_info()[1])}
return Response(json.dumps(response))


def upload_file_validation(request):

def validation(filePath, mimetype):
size = os.path.getsize(filePath)
if size > 10 * 1024 * 1024:
 return False
return True

options = {
"fieldname": "myFile",
"validation": validation
}

try:
response = File.upload(PyramidAdapter(request), "/public/", options)
except Exception:
response = {"error": str(sys.exc_info()[1])}
return Response(json.dumps(response))


class File(object):

defaultUploadOptions = {
"fieldname": "file",
"validation": {
 "allowedExts": ["txt", "pdf", "doc"],
 "allowedMimeTypes": ["text/plain", "application/msword", "application/x-pdf", "application/pdf"]
}
}

   @staticmethod
def upload(req, fileRoute, options = None):
"""
File upload to disk.
Parameters:
 req: framework adapter to http request. See BaseAdapter.
 fileRoute: string
 options: dict optional, see defaultUploadOptions attribute
Return:
 dict: {link: "linkPath"}
"""

if options is None:
 options = File.defaultUploadOptions
else:
 options = Utils.merge_dicts(File.defaultUploadOptions, options)

# Get extension.
filename = req.getFilename(options["fieldname"]);
extension = Utils.getExtension(filename)
extension = "." + extension if extension else ""

# Generate new random name.

# python 2-3 compatible:
try:
 sha1 = hashlib.sha1(str(time.time()).encode("utf-8")).hexdigest() #  v3
except Exception:
 sha1 = hashlib.sha1(str(time.time())).hexdigest() # v2
routeFilename = fileRoute + sha1 + extension

fullNamePath = Utils.getServerPath() + routeFilename

req.saveFile(options["fieldname"], fullNamePath)

# Check validation.
if "validation" in options:
 if not Utils.isValid(options["validation"], fullNamePath, req.getMimetype(options["fieldname"])):
     File.delete(routeFilename)
     raise Exception("File does not meet the validation.")

# Check image resize.
if "resize" in options and options["resize"] is not None:
 with Image(filename = fullNamePath) as img:
     img.transform(resize = options["resize"])
     img.save(filename = fullNamePath)

# build and send response.
response = {}
response["link"] = routeFilename
return response

   @staticmethod
def delete(src):
"""
Delete file from disk.
Parameters:
 src: string
"""

filePath = Utils.getServerPath() + src
try:
 os.remove(filePath)
except OSError:
 pass


class Utils(object):
"""
Utils static class.
"""

   @staticmethod
def hmac(key, string, hex = False):
"""
Calculate hmac.
Parameters:
 key: string
 string: string
 hex: boolean optional, return in hex, else return in binary
Return:
 string: hmax in hex or binary
"""

# python 2-3 compatible:
try:
 hmac256 = hmac.new(key.encode() if isinstance(key, str) else key, msg = string.encode("utf-8") if isinstance(string, str) else string, digestmod = hashlib.sha256) # v3
except Exception:
 hmac256 = hmac.new(key, msg = string, digestmod = hashlib.sha256) # v2

return hmac256.hexdigest() if hex else hmac256.digest()

   @staticmethod
def merge_dicts(a, b, path = None):
"""
Deep merge two dicts without modifying them. Source: http://stackoverflow.com/questions/7204805/dictionaries-of-dictionaries-merge/7205107#7205107
Parameters:
 a: dict
 b: dict
 path: list
Return:
 dict: Deep merged dict.
"""

aClone = copy.deepcopy(a);
# Returns deep b into a without affecting the sources.
if path is None: path = []
for key in b:
 if key in a:
     if isinstance(a[key], dict) and isinstance(b[key], dict):
         aClone[key] = Utils.merge_dicts(a[key], b[key], path + [str(key)])
     else:
         aClone[key] = b[key]
 else:
     aClone[key] = b[key]
return aClone

   @staticmethod
def getExtension(filename):
"""
Get filename extension.
Parameters:
 filename: string
Return:
 string: The extension without the dot.
"""
return os.path.splitext(filename)[1][1:]

   @staticmethod
def getServerPath():
"""
Get the path where the server has started.
Return:
 string: serverPath
"""
return os.path.abspath(os.path.dirname(sys.argv[0]))

   @staticmethod
def isFileValid(filename, mimetype, allowedExts, allowedMimeTypes):
"""
Test if a file is valid based on its extension and mime type.
Parameters:
 filename string
 mimeType string
 allowedExts list
 allowedMimeTypes list
Return:
 boolean
"""

# Skip if the allowed extensions or mime types are missing.
if not allowedExts or not allowedMimeTypes:
 return False

extension = Utils.getExtension(filename)
return extension.lower() in allowedExts and mimetype in allowedMimeTypes

   @staticmethod
def isValid(validation, filePath, mimetype):
"""
Generic file validation.
Parameters:
 validation: dict or function
 filePath: string
 mimetype: string
"""

# No validation means you dont want to validate, so return affirmative.
if not validation:
 return True

# Validation is a function provided by the user.
if callable(validation):
 return validation(filePath, mimetype)

if isinstance(validation, dict):
 return Utils.isFileValid(filePath, mimetype, validation["allowedExts"], validation["allowedMimeTypes"])

# Else: no specific validating behaviour found.
return False


class BaseAdapter(object):
"""
Interface. Inherit this class to use the lib in your framework.
"""

def __init__(self, request):
"""
Constructor.
Parameters:
 request: http request object from some framework.
"""
self.request = request

def riseError(self):
"""
Use this when you want to make an abstract method.
"""
raise NotImplementedError( "Should have implemented this method." )

def getFilename(self, fieldname):
"""
Get upload filename based on the fieldname.
Parameters:
 fieldname: string
Return:
 string: filename
"""
self.riseError()

def getMimetype(self, fieldname):
"""
Get upload file mime type based on the fieldname.
Parameters:
 fieldname: string
Return:
 string: mimetype
"""
self.riseError()

def saveFile(self, fieldname, fullNamePath):
"""
Save the upload file based on the fieldname on the fullNamePath location.
Parameters:
 fieldname: string
 fullNamePath: string
"""
self.riseError()


class PyramidAdapter(BaseAdapter):
"""
Pyramid Adapter: Check BaseAdapter to see what methods description.
"""

def checkFile(self, fieldname):
if fieldname not in self.request.POST:
 raise Exception("File does not exist.")

def getFilename(self, fieldname):
self.checkFile(fieldname)
return self.request.POST[fieldname].filename

def getMimetype(self, fieldname):
self.checkFile(fieldname)
return self.request.POST[fieldname].type

def saveFile(self, fieldname, fullNamePath):
self.checkFile(fieldname)
file = self.request.POST[fieldname].file

file.seek(0)
with open(fullNamePath, "wb") as output_file:
 shutil.copyfileobj(file, output_file)


if __name__ == "__main__":

config = Configurator()

config.add_route("get_main_html", "/")
config.add_view(get_main_html, route_name = "get_main_html")

config.add_route("upload_file", "/upload_file")
config.add_view(upload_file, route_name = "upload_file")

config.add_route("upload_file_validation", "/upload_file_validation")
config.add_view(upload_file_validation, route_name = "upload_file_validation")

config.add_static_view("public", path = "public/")
config.add_static_view("static", path = "/")

app = config.make_wsgi_app()
server = make_server("0.0.0.0", 7000, app)
print("* Running on http://127.0.0.1:7000/ (Press CTRL+C to quit)")
server.serve_forever()

Do you think we can improve this article? Let us know.