Simple JSON AJAX WSGI MVC App

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Antworten
tabellar
User
Beiträge: 186
Registriert: Mittwoch 4. September 2002, 15:28

Ich hab mal die BUZZ Hypes JSON AJAX WSGI MVC in einer Mini Anwendung exemplarisch zusammengebaut :) . Das ganze baut auf dem Simple AJAX JSON Beispiel auf. Es wird eine Tabelle erzeugt und auf Knopfdruck via AJAX (JSON) neue RANDOM Tabellenwerte vom MVC System geholt. Mittels JavaScript und DOM wird die Tabelle dann aktualisiert.

Es sind zwei Dateien: ajaxSrv.py und ajax.html. Beide Dateien in einem Verzeichnis ablegen und ajaxSrv.py starten. Anschliessend sollte via http://localhost:8081 im Browser die Tabelle angezeigt werden.

Tabellar

edit: ist nun WSGI kompatibel + div. Änderungen

ajaxSrv.py

Code: Alles auswählen

# -*- coding: utf-8 -*-
"""
   Simple JSON-AJAX-WSGI-MVC Application
   =====================================
   Based on simple JSON-AJAX example from
   http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/440637

   StartUrl: http://localhost:8081
 """

from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
import random

class AjaxApp(object):
    def __init__(self,controller):
        self.controller=controller
        self.view=View(self)
        
    def __call__(self,environ,start_response):
        self.start=start_response
        urlpath=environ['PATH_INFO'].strip('/') 

        if urlpath   == "" :
            return self.view.ajax_table()
        elif urlpath == "update":
            return self.view.ajax_table_update()
        else:
            return self.view.ajax_table() 


class View(object):
    def __init__(self,app):
        self.app=app

    def ajax_table(self):
        status = "200 OK"
        response_headers = [('Content-type','text/html')]
        self.app.start(status, response_headers)
        return self.app.controller.getAjaxTable()

    def ajax_table_update(self):
        status = "200 OK"
        response_headers = [('Content-type','text/plain')]
        self.app.start(status, response_headers)
        return self.app.controller.getRandomTableData()


class Controller(object):
    def __init__(self):
        self.model=Model()
    def getAjaxTable(self):
        return self.model.getAjaxTable()
    def getRandomTableData(self):
        return self.model.getRandomTableData()        


class Model(object):
    def getAjaxTable(self):
        file=open("ajax.html","r")
        file_raw=file.read()
        file.close()
        return [file_raw]

    def getRandomTableData(self):
        jsondata =[{"one":'Hello world',"two":12345678L,"three":3.1415926},\
                   ['to',"be","or",'not',"to",'be'],\
                   {'title':"that's the question",'random':random.randrange(0,1000000)}\
                  ] 
        return [jsondata]


class WSGIHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        """
           HTTP-GET-Anfragen
        """
        self.process_request()
    
    def do_POST(self):
        """
          HTTP-POST-Anfragen
        """
            
    def process_request(self):
        environ = {'PATH_INFO': self.path}
        headers_set = []
        headers_sent = []

        def write(data):
            if not headers_set:
                raise AssertionError("write() before start_response()")

            elif not headers_sent:
                # Before the first output, send the stored headers
                status, response_headers = headers_sent[:] = headers_set
                code, msg = status.split(' ',1)
                self.send_response(int(code), msg)

                for header in response_headers:
                    self.send_header(*header)
                self.end_headers()

            self.wfile.write(data)

        def start_response(status,response_headers,exc_info=None):
            if exc_info:
                try:
                    if headers_sent:
                        # Re-raise original exception if headers sent
                        raise exc_info[0], exc_info[1], exc_info[2]
                finally:
                    exc_info = None     # avoid dangling circular ref
            elif headers_set:
                raise AssertionError("Headers already set!")

            headers_set[:] = [status,response_headers]
            return write

        result = self.server.app(environ, start_response)
        try:
            for data in result:
                if data:    # don't send headers until body appears
                    write(data)
            if not headers_sent:
                write('')   # send headers now if body was empty
        finally:
            if hasattr(result,'close'):
                result.close()
      

        
class WSGIServer(HTTPServer):
    def __init__(self, hostname='', port=8081,application=''):
        HTTPServer.__init__(self, (hostname, port), WSGIHandler)
        self.app = application
        
    def run(self):
        print "SIMPLE-WSGI-AJAX-MVC-APP..."
        while True:
            try:
                self.handle_request()
            except KeyboardInterrupt:
                break


if __name__ == '__main__':
    app=AjaxApp(Controller()) #Application instance
    WSGIServer(application = app).run()



ajax.html

Code: Alles auswählen

<html>
<head>
<title>simple json-ajax-wsgi-mvc example</title>
<script language="JavaScript">
    //json parser
    //from json.org with small modification
    var cur_str_chr;
    function json_parse(text) {
        var at = 0;
        var ch = ' ';

        function error(m) {
            throw {
                name: 'JSONError',
                message: m,
                at: at - 1,
                text: text
            };
        }

        function next() {
            ch = text.charAt(at);
            at += 1;
            return ch;
        }

        function white() {
            while (ch !== '' && ch <= ' ') {
                next();
            }
        }

        function str() {
            var i, s = '', t, u;

            if (ch == '\'' || ch == '"') { //change " to ' for python
                cur_str_chr = ch;
outer:          while (next()) {
                    if (ch == cur_str_chr) {
                        next();
                        return s;
                    } else if (ch == '\\') {
                        switch (next()) {
                        case 'b':
                            s += '\b';
                            break;
                        case 'f':
                            s += '\f';
                            break;
                        case 'n':
                            s += '\n';
                            break;
                        case 'r':
                            s += '\r';
                            break;
                        case 't':
                            s += '\t';
                            break;
                        case 'u':
                            u = 0;
                            for (i = 0; i < 4; i += 1) {
                                t = parseInt(next(), 16);
                                if (!isFinite(t)) {
                                    break outer;
                                }
                                u = u * 16 + t;
                            }
                            s += String.fromCharCode(u);
                            break;
                        default:
                            s += ch;
                        }
                    } else {
                        s += ch;
                    }
                }
            }
            error("Bad string");
        }

        function arr() {
            var a = [];

            if (ch == '[') {
                next();
                white();
                if (ch == ']') {
                    next();
                    return a;
                }
                while (ch) {
                    a.push(val());
                    white();
                    if (ch == ']') {
                        next();
                        return a;
                    } else if (ch != ',') {
                        break;
                    }
                    next();
                    white();
                }
            }
            error("Bad array");
        }

        function obj() {
            var k, o = {};

            if (ch == '{') {
                next();
                white();
                if (ch == '}') {
                    next();
                    return o;
                }
                while (ch) {
                    k = str();
                    white();
                    if (ch != ':') {
                        break;
                    }
                    next();
                    o[k] = val();
                    white();
                    if (ch == '}') {
                        next();
                        return o;
                    } else if (ch != ',') {
                        break;
                    }
                    next();
                    white();
                }
            }
            error("Bad object");
        }

        function num() {
            var n = '', v;
            if (ch == '-') {
                n = '-';
                next();
            }
            while (ch >= '0' && ch <= '9') {
                n += ch;
                next();
            }
            if (ch == '.') {
                n += '.';
                while (next() && ch >= '0' && ch <= '9') {
                    n += ch;
                }
            }
            if (ch == 'e' || ch == 'E') {
                n += 'e';
                next();
                if (ch == '-' || ch == '+') {
                    n += ch;
                    next();
                }
                while (ch >= '0' && ch <= '9') {
                    n += ch;
                    next();
                }
            }
            if (ch == 'L')next();//for python long
            v = +n;
            if (!isFinite(v)) {
                error("Bad number");
            } else {
                return v;
            }
        }

        function word() {
            switch (ch) {
                case 't':
                    if (next() == 'r' && next() == 'u' && next() == 'e') {
                        next();
                        return true;
                    }
                    break;
                case 'f':
                    if (next() == 'a' && next() == 'l' && next() == 's' &&
                            next() == 'e') {
                        next();
                        return false;
                    }
                    break;
                case 'n':
                    if (next() == 'u' && next() == 'l' && next() == 'l') {
                        next();
                        return null;
                    }
                    break;
            }
            error("Syntax error");
        }

        function val() {
            white();
            switch (ch) {
                case '{':
                    return obj();
                case '[':
                    return arr();
                case '\'':
                case '"':
                    return str();
                case '-':
                    return num();
                default:
                    return ch >= '0' && ch <= '9' ? num() : word();
            }
        }

        return val();
    }
    //end json parser

function loadurl(dest) { 
    xmlhttp = window.XMLHttpRequest?new XMLHttpRequest(): new ActiveXObject("Microsoft.XMLHTTP");
    xmlhttp.onreadystatechange = pop_table;
    xmlhttp.open("GET", dest);
    xmlhttp.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT");
    xmlhttp.send(null);
}
function pop_table() {
    if ((xmlhttp.readyState == 4) && (xmlhttp.status == 200)) {
        var json_data = json_parse(xmlhttp.responseText);
	var rows = document.getElementById("testtable").getElementsByTagName("tr");

	rows[0].childNodes[0].innerHTML = json_data[0]['one']
	rows[0].childNodes[1].innerHTML = json_data[0]['two']
	rows[0].childNodes[2].innerHTML = json_data[0]['three']
	for(i=0;i<rows[1].childNodes.length;i++)
		rows[1].childNodes[i].innerHTML = json_data[1][i]
	rows[2].childNodes[0].innerHTML = json_data[2]['title']
	rows[2].childNodes[2].innerHTML = json_data[2]['random']
    }
}
</script>
<style type="text/css">
div,td {
    width: 10em;
    font-family: sans-serif;
    padding: 0.2em;
    border: 1px solid #ccc;
    margin: 0.5em;
    background-color: #eee;
}
</style>
</head>
<body>
<table id="testtable" border=1>
<tr><td>11</td><td>12</td><td>13</td></tr>
<tr><td>21</td><td>22</td><td>23</td></tr>
<tr><td>31</td><td>32</td><td>33</td></tr>
</table>
<div id="clickhere" onclick="loadurl('http://localhost:8081/update')">click here</div>
</body>
</html>
Zuletzt geändert von tabellar am Donnerstag 23. März 2006, 23:53, insgesamt 1-mal geändert.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Irgendwas stimmt mit deinem einrücken nicht... Kann es sein, das du Leerzeichen und TABs gemischt hast?

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
tabellar
User
Beiträge: 186
Registriert: Mittwoch 4. September 2002, 15:28

jens hat geschrieben:Irgendwas stimmt mit deinem einrücken nicht... Kann es sein, das du Leerzeichen und TABs gemischt hast?
Eigentlich nicht :roll:, ich verwende keine Tabs . Bei mir funktioniert das genau
so. Läuft es bei Dir nicht? Den Python Code habe ich direkt aus PythonWin
übernommen. Den html Code auch. Allerdings ist der aus dem ASPN Beispiel
via Browser rauskopiert. Was ich allerdings festgestellt habe ist, dass ich das
JavaScript nicht separat aufrufen kann. Im Sinne eines Imports oder bei einer
html Template Variante. Ich will das noch mal genauer anschauen. Ziel ist es,
das JSON Parser JavaScript in Templates einzubauen in Form von %(jsonparser)s...

Tabellar
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Probiert hab ich es nicht. Man sieht nur hier im Forum, das zwei unterschiedlich große Einrückung in den Klassen verwendet wurde...
Es wird sicherlich so aber auch laufen, der Python-Interpreter ist ja recht tolerant... Es kommt wohl eh nur darauf an, das ein Block gleich eingerückt ist, untereinander können aber Blöcke unterschiedlich sein, IMHO...

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
mitsuhiko
User
Beiträge: 1790
Registriert: Donnerstag 28. Oktober 2004, 16:33
Wohnort: Graz, Steiermark - Österreich
Kontaktdaten:

Also WSGI ist das nicht.
TUFKAB – the user formerly known as blackbird
tabellar
User
Beiträge: 186
Registriert: Mittwoch 4. September 2002, 15:28

blackbird hat geschrieben:Also WSGI ist das nicht.
Das ist sicherlich kein "ganz echtes" WSGI (keine Iteratoren, kein vollständiges
environment, etc.). Ich habe eben versucht, das Prinzip darzustellen und das
ganze sehr zu vereinfachen, zum besseren Verständnis (hoffe ich wenigstens :oops: ).

Die Hauptpunkte, die ich darstellen wollte sind wie folgt:
- WSGI Server wird mit RequestHandler und App initalisiert
- Client setzt einen Request an den http Server ab (GET,POST)
- RequestHandler ruft die App mit den Argumenten (environment, start_response) auf
- App gibt einen Response zurück
- Request Handler sendet den AppResponse an den Client zurück

Mir ging es mehr um das Prinzip, nicht um den exakten Standard. Wenn
das zu weit von WSGI entfernt ist, dann nehm ich das Wort WSGI halt wieder
raus :wink: .

Tabellar
tabellar
User
Beiträge: 186
Registriert: Mittwoch 4. September 2002, 15:28

blackbird hat geschrieben:Also WSGI ist das nicht.
Es sollte jetzt passen ... :wink:

Tabellar
tabellar
User
Beiträge: 186
Registriert: Mittwoch 4. September 2002, 15:28

Ich hab das ganze jetzt nochmals umgebaut. Den JSON Parser hab ich mal
rausgeworfen und mache das ganze jetzt vereinfacht mit eval(). Allerdings
ohne Serialisierung. Dafür bräuchte man dann zusätzliche JavaScript Funktionalität :wink:.
Für die Requests gibt es jetzt einen Html- und einen JsonHandler. Das MVC System
kann jetzt auch Views speichern und bei Modelländerungen updates veranlassen.
Die Applikation ist vollständig in einem Modul zusammengefasst, muss
also in dem obigen einfachen WSGI Server importiert werden.


Tabellar

Getestet auch unter unter James :), es muss allerdings
noch die 'PATH_INFO': self.path Environment Variable gesetzt werden.

Code: Alles auswählen

from ajaxapp import API,Controller
from james import WSGIServer
app=API(Controller())
WSGIServer(applications={'/': app}).run()

Code: Alles auswählen

# -*- coding: utf-8 -*-
"""
   Simple JSON-AJAX-WSGI-MVC Application
   =====================================
   Based on simple JSON-AJAX example from
   http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/440637

   !!! *** ONLY FOR TESTING *** !!!

   RequestHandler:
    - HtmlHandler, JsonHandler
   MVC-System:
    - View instances with render(), update() methods
    - View caching
    - Automatic controller view updates

"""

from urllib import quote,unquote
import random

class API(object):
    def __init__(self,controller):
        self.controller = controller
        self.html       = HtmlHandler(self)
        self.json       = JsonHandler(self)
        
    def __call__(self,environ,start_response):
        self.start=start_response
        urlpath=environ['PATH_INFO'].strip('/') 

        if urlpath   == "" :
            return self.html.view_table()
        elif urlpath[:8] == "!jsonrpc":
            return self.json.switch(unquote(urlpath[9:]))
        else:
            return self.html.view_table() 

class HtmlHandler(object):
    def __init__(self, app):
        self.app=app

    def view_table(self):
        status = "200 OK"
        response_headers = [('Content-type','text/html')]
        self.app.start(status, response_headers)
        try:
            return [self.app.controller.views['anonymous'].render()]
        except KeyError :
            self.app.controller.views['anonymous']=View(self.app.controller)
            return [self.app.controller.views['anonymous'].render()]

class JsonHandler(object):
    def __init__(self, app):
        self.app = app
    
    def switch(self,raw_json):
        json_request = eval(raw_json)
        if json_request['method'] == 'get_random_table_data':
            return self.get_random_table_data(json_request)
        elif json_request['method'] == 'modify_table_data':      
            return self.modify_table_data(json_request)
        
    def get_random_table_data(self,json_request):
        status = "200 OK"
        response_headers = [('Content-type','text/plain')]
        self.app.start(status, response_headers)
        tabledata = self.app.controller.get_random_table_data()
        json_response = {'result': tabledata,
                         'error:': 'null',
                         'id'    : json_request['id']
                         }
        #return only the JSON result statement,
        #because there is no serializing in the JavaScript atm
        return [repr(json_response['result'])]

    def modify_table_data(self,json_request):
        status = "200 OK"
        response_headers = [('Content-type','text/plain')]
        self.app.start(status, response_headers)
        return self.app.controller.modify_table_data()

class Controller(object):
    def __init__(self):
        self.model=Model()
        #stored views
        self.views={}
    def get_raw_table_data(self):
        return self.model.get_raw_table_data()
    def get_random_table_data(self):
        return self.model.get_random_table_data()
    def modify_table_data(self):
        self.model.modify_table_data()
        #modify views
        for session, view in self.views.iteritems():
            view.update()
        return []

class Model(object):
    def __init__(self):
        self.table_rows = [
            [11,12,13],
            [21,22,23],
            [31,32,33],
            ]

    def get_raw_table_data(self):
        return self.table_rows

    def get_random_table_data(self):
        tabledata =[{"one":'Hello world',"two": "12345678L","three":3.1415926},\
                    ['to',"be","or",'not',"to",'be'],\
                    {'title' :"that's the question",'random' :random.randrange(0,1000000)},\
                   ]
        return tabledata

    def modify_table_data(self):
        self.table_rows[0][0]=random.randrange(0,1000)
        self.table_rows[1][1]=random.randrange(0,1000)
        self.table_rows[2][2]=random.randrange(0,1000)

class View(object):
    def __init__(self,controller):
        self.controller = controller
        self.update()

    def update(self):
        self.rows = self.controller.get_raw_table_data()

    def render(self):
        table_body = ""
        for row in self.rows:
            table_body += "<tr>"
            for value in row:
                table_body +="<td>%s</td>" % str(value)
            table_body += "</tr>"
            
        self.content=""
        self.content += html_head  % {'script'     : html_script, 'style': html_style}
        self.content += html_table % {'table_body' : table_body}
        self.content += html_footer
        return self.content


html_head = """\
<html>
<head>
<title>simple json-ajax-wsgi-mvc example</title>
%(script)s
%(style)s
</body>"""

html_style = """\
<style type="text/css">
div,td {
    width: 10em;
    font-family: sans-serif;
    padding: 0.2em;
    border: 1px solid #ccc;
    margin: 0.5em;
    background-color: #eee;
}
</style>"""

html_script = """\
<script language="JavaScript">
function dom_update() { 
    var json_request = "{'method': 'get_random_table_data', 'params': [], 'id' : 1, }";
    var url = "!jsonrpc=" + json_request;
    jsonrpc(url,pop_table)
}

function mvc_update() { 
    var json_request = "{'method': 'modify_table_data', 'params': [], 'id' : 0, }";
    var url = "!jsonrpc=" + json_request;
    jsonrpc(url,null)
}

function jsonrpc(url,fct){
    //var url = "!jsonrpc={'method': 'get_random_table_data', 'params': [], 'id' : 1, }"
    xmlhttp = window.XMLHttpRequest?new XMLHttpRequest(): new ActiveXObject("Microsoft.XMLHTTP");
    xmlhttp.onreadystatechange = fct;
    xmlhttp.open("GET", url);
    xmlhttp.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT");
    xmlhttp.send(null);
}

function pop_table() {
    if ((xmlhttp.readyState == 4) && (xmlhttp.status == 200)) {
        var json_response = eval(xmlhttp.responseText);
        
    var rows = document.getElementById("ajaxtable").getElementsByTagName("tr");
    rows[0].childNodes[0].innerHTML = json_response[0]['one']
    rows[0].childNodes[1].innerHTML = json_response[0]['two']
    rows[0].childNodes[2].innerHTML = json_response[0]['three']
    for(i=0;i<rows[1].childNodes.length;i++)
        rows[1].childNodes[i].innerHTML = json_response[1][i]
    rows[2].childNodes[0].innerHTML = json_response[2]['title']
    rows[2].childNodes[2].innerHTML = json_response[2]['random']
    }
}
</script>"""

html_table = """\
<table id="ajaxtable" border=1>
%(table_body)s
</table>
<div id="clickhere" onclick="dom_update()">AjaxDOM update</div>
<div id="clickhere" onclick="mvc_update()">AjaxMVC modify</div>
<div><a href="/">Refresh</a></div>"""

html_footer = """\
</body></html>"""
Antworten