Thursday, May 5, 2011

How do you use FCKEditor's image upload and browser with mod-wsgi?

I am using FCKEditor within a Django app served by Apache/mod-wsgi. I don't want to install php just for FCKEditor andI see FCKEditor offers image uploading and image browsing through Python. I just haven't found good instructions on how to set this all up.

So currently Django is running through a wsgi interface using this setup:

import os, sys

DIRNAME = os.sep.join(os.path.abspath(__file__).split(os.sep)[:-3])
sys.path.append(DIRNAME)
os.environ['DJANGO_SETTINGS_MODULE'] = 'myapp.settings'
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()

In fckeditor in the editor->filemanager->connectors->py directory there is a file called wsgi.py:

from connector import FCKeditorConnector
from upload import FCKeditorQuickUpload

import cgitb
from cStringIO import StringIO

# Running from WSGI capable server (recomended)
def App(environ, start_response):
    "WSGI entry point. Run the connector"
    if environ['SCRIPT_NAME'].endswith("connector.py"):
     conn = FCKeditorConnector(environ)
    elif environ['SCRIPT_NAME'].endswith("upload.py"):
     conn = FCKeditorQuickUpload(environ)
    else:
     start_response ("200 Ok", [('Content-Type','text/html')])
     yield "Unknown page requested: "
     yield environ['SCRIPT_NAME']
     return
    try:
     # run the connector
     data = conn.doResponse()
     # Start WSGI response:
     start_response ("200 Ok", conn.headers)
     # Send response text
     yield data
    except:
     start_response("500 Internal Server Error",[("Content-type","text/html")])
     file = StringIO()
     cgitb.Hook(file = file).handle()
 yield file.getvalue()

I need these two things two work together by means of modifying my django wsgi file to serve the fckeditor parts correctly or make apache serve both django and fckeditor correctly on a single domain.

From stackoverflow
  • This describes how to embed the FCK editor and enable image uploading.

    First you need to edit fckconfig.js to change the image upload URL to point to some URL inside your server.

    FCKConfig.ImageUploadURL = "/myapp/root/imageUploader";
    

    This will point to the server relative URL to receive the upload. FCK will send the uploaded file to that handler using the CGI variable name "NewFile" encoded using multipart/form-data. Unfortunately you will have to implement /myapp/root/imageUploader, because I don't think the FCK distribution stuff can be easily adapted to other frameworks.

    The imageUploader should extract the NewFile and store it somewhere on the server. The response generated by /myapp/root/imageUploader should emulate the HTML constructed in /editor/.../fckoutput.py. Something like this (whiff template format)

    {{env
        whiff.content_type: "text/html",
        whiff.headers: [
            ["Expires","Mon, 26 Jul 1997 05:00:00 GMT"],
            ["Cache-Control","no-store, no-cache, must-revalidate"],
            ["Cache-Control","post-check=0, pre-check=0"],
            ["Pragma","no-cache"]
            ]
    /}}
    
    <script>
    //alert("!! RESPONSE RECIEVED");
    errorNumber = 0;
    fileUrl = "fileurl.png";
    fileName = "filename.png";
    customMsg = "";
    window.parent.OnUploadCompleted(errorNumber, fileUrl, fileName, customMsg);
    </script>
    

    The {{env ...}} stuff at the top indicate the content type and recommended HTTP headers to send. The fileUrl should be the Url to use to find the image on the server.

    Here are the basic steps to get the html fragment which generates the FCK editor widget. The only tricky part is you have to put the right client indentification into the os.environ -- it's ugly but that's the way the FCK library works right now (I filed a bug report).

    import fckeditor # you must have the fck editor python support installed to use this module
    import os
    
    inputName = "myInputName" # the name to use for the input element in the form
    basePath = "/server/relative/path/to/fck/installation/" # the location of FCK static files
    if basePath[-1:]!="/":
            basePath+="/" # basepath must end in slash
    oFCKeditor = fckeditor.FCKeditor(inputName)
    oFCKeditor.BasePath = basePath
    oFCKeditor.Height = 300 # the height in pixels of the editor
    oFCKeditor.Value = "<h1>initial html to be editted</h1>"
    os.environ["HTTP_USER_AGENT"] = "Mozilla/5.0 (Macintosh; U;..." # or whatever
    # there must be some way to figure out the user agent in Django right?
    htmlOut = oFCKeditor.Create()
    # insert htmlOut into your page where you want the editor to appear
    return htmlOut
    

    The above is untested, but it's based on the below which is tested.

    Here is how to use FCK editor using mod-wsgi: Technically it uses a couple features of WHIFF (see WHIFF.sourceforge.net), -- in fact it is part of the WHIFF distribution -- but the WHIFF features are easily removed.

    I don't know how to install it in Django, but if Django allows wsgi apps to be installed easily, you should be able to do it.

    NOTE: FCK allows the client to inject pretty much anything into HTML pages -- you will want to filter the returned value for evil attacks. (eg: see whiff.middleware.TestSafeHTML middleware for an example of how to do this).

        
    """
    Introduce an FCK editor input element. (requires FCKeditor http://www.fckeditor.net/).
    
    Note: this implementation can generate values containing code injection attacks if you
      don't filter the output generated for evil tags and values.
    """
    
    import fckeditor # you must have the fck editor python support installed to use this module
    from whiff.middleware import misc
    import os
    
    class FCKInput(misc.utility):
        def __init__(self,
                     inputName, # name for input element
                     basePath, # server relative URL root for FCK HTTP install
                     value = ""):  # initial value for input
            self.inputName = inputName
            self.basePath = basePath
            self.value = value
        def __call__(self, env, start_response):
            inputName = self.param_value(self.inputName, env).strip()
            basePath = self.param_value(self.basePath, env).strip()
            if basePath[-1:]!="/":
                basePath+="/"
            value = self.param_value(self.value, env)
            oFCKeditor = fckeditor.FCKeditor(inputName)
            oFCKeditor.BasePath = basePath
            oFCKeditor.Height = 300 # this should be a require!
            oFCKeditor.Value = value
            # hack around a bug in fck python library: need to put the user agent in os.environ
            # XXX this hack is not safe for multi threaded servers (theoretically)... need to lock on os.env
            os_environ = os.environ
            new_os_env = os_environ.copy()
            new_os_env.update(env)
            try:
                os.environ = new_os_env
                htmlOut = oFCKeditor.Create()
            finally:
                # restore the old os.environ
                os.environ = os_environ
            start_response("200 OK", [('Content-Type', 'text/html')])
            return [htmlOut]
    
    __middleware__ = FCKInput
    
    def test():
        env = {
            "HTTP_USER_AGENT":
            "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.14) Gecko/20080404 Firefox/2.0.0.14"
            }
        f = FCKInput("INPUTNAME", "/MY/BASE/PATH", "THE HTML VALUE TO START WITH")
        r = f(env, misc.ignore)
        print "test result"
        print "".join(list(r))
    
    if __name__=="__main__":
        test()
    

    See this working, for example, at http://aaron.oirt.rutgers.edu/myapp/docs/W1500.whyIsWhiffCool.

    btw: thanks. I needed to look into this anyway.

  • Edit: Ultimately I was unhappy with this solution also so I made a Django app that takes care of the file uploads and browsing.

    This is the solution I finally hacked together after reading the fckeditor code:

    import os, sys
    
    def fck_handler(environ, start_response):
        path = environ['PATH_INFO']
        if path.endswith(('upload.py', 'connector.py')):
            sys.path.append('/#correct_path_to#/fckeditor/editor/filemanager/connectors/py/')
            if path.endswith('upload.py'):
                from upload import FCKeditorQuickUpload
                conn = FCKeditorQuickUpload(environ)
            else:
                from connector import FCKeditorConnector
                conn = FCKeditorConnector(environ)
            try:
                data = conn.doResponse()
                start_response('200 Ok', conn.headers)
                return data
            except:
                start_response("500 Internal Server Error",[("Content-type","text/html")])
                return "There was an error"
        else:
            sys.path.append('/path_to_your_django_site/')
            os.environ['DJANGO_SETTINGS_MODULE'] = 'your_django_site.settings'
            import django.core.handlers.wsgi
            handler = django.core.handlers.wsgi.WSGIHandler()
            return handler(environ, start_response)
    
    application = fck_handler
    

0 comments:

Post a Comment