= pyMFrame :: Ein minimalistisches Webframework in Python = == Hintergrund == Der Grund zur Entwicklung von pyMFrmae waren Überlegungen zum Thema [[MinimalesFramework|Architektur eines minimalen Webframeworks]]. Die Ideen hinter diesen Überlegungen wurden mittels der Sprache Python umgesetzt. {{{#!wiki lightgrey/solid '''Hinweis''' Diese Software dient lediglicht zu Test und Schulungszwecken. Von einer Benutzung im Produktionsbetrieb wird abgeraten. Der Autor übernimmt keinerlei Verantwortung für das korrekte Funktionieren des Programms. }}} ||<#F7F7F7>'''Ressourcen'''|| ||[[attachment:pymframe.zip|Download Sourcecode (leere Anwendung)]]|| ||[[https://docs.google.com/folder/d/0B2yfXLAZOsHXcUhvdWxnVFBDNzQ/edit|Dokumentation auf Google Drive|target="_blank"]]|| ||[[http://www.noe.pfadfinder.at/~nagyw/northwind|Demo (Zugriff admin/admin)|target="_blank"]]|| ||[[WilhelmNagy|Kontakt zum Entwickler]]|| ||<#F7F7F7>'''Ziel'''||Entwickung eines Webframeworks mittels der Sprache Python als Übungs Projekt. Einsatz bei einfachen Projekten auf 'Standardservern' Um eine möglichst einfache Entwicklung zu ermöglichen, sind keine über den Python Standard hinausgehenden Klassen (mit Ausnahme ggf. notwendiger Datenbanktreibern) verwendet worden. Es wurde weitesgehend auf die Verwendung von Metasprachen verzichtet. {{attachment:Uebersicht.jpg}}|| ||<#F7F7F7>'''Umsetzung'''||Entwickeln von Klassen und Hilfsroutinen um mit möglichst geringen Aufwand eine Datenbankgestütze Anwendung zu Entwickeln (rapid prototyping). Es wird das MVC Modell unterstützt. Der Zugriff auf Datenbanktabellen wird gekapselt, um einen möglichst hohe Unabhängikeit der Datenbak zu ermöglichen. Die Verwaltung von Datenbankinhalten soll weitestgehende automatisiert erfolgen. Keine expliziten schreibe und Leseroutinen.|| ||<#F7F7F7>'''Basis'''||Python 2.7, Datenbanktreiber (sqlite, mySql, Oracle)|| == Templates == Es existieren zwei Arten von Templates. * Container * Viewer === Container === Container bilden den Rahmen fuer das rendering einer Ausgabe. Diese sind im Grunde eine HTML Seite und enhalten keine dynamischen Elemente sondern nur Platzhalter. '''Auswahl von Platzhalten''' ||<#F7F7F7>Platzhaler||<#F7F7F7>Inhalt|| ||$body||Enhält das Ergebnis eines Controllers|| ||$menu||Menü|| ||$flash||Benutzungshinweise, Meldungen|| === Viewer === Viewer enhalte eingebetetten Pythoncode. Der Code basiert auf - Tomer Filiba http://code.activestate.com/recipes/496702/ und wurde vom Autor erweitert. '''Beispiel ''' {{{
Zur Gruppenregistrierung auf Gruppenregistrierung klicken
') else: out('Verwaltung der Anmeldedaten
') %> }}} == Controller == Controller sind Python Klassen. Diese werden im Navigationsystem registriert und über eine CGI Variable (path) angesprochen. Siehe dazu Module->Navigationssystem. Vor dem Aufruf werden die Rechte überprüft und die Ausführung ggf. verhindert. '''Beispiel eines einfachen Controllers''' {{{ # -*- coding: iso-8859-15 -*- from controller import Controller class RootController(Controller): def get(self): # Ermittle Rechte rights = self.main.authen.getRights() self.view('view.tpl', param={ 'rights':rights }) }}} Im o.A. Beispiel werden dem Viewer Paramter mitgegeben. == Navigationssystem == Das Framework stellt ein Navigatissystem (Menüs) zur Verfügung. Im Deklarationsteil werden der Anzeigename, der Controllername (optionanl) die Sichtbarkeit (optional) und die Rechte notiert. Die Parameter eines Menüeintrags können programmatisch im Controller beinflusst werden. {{{ if self.main.getAttribute('grpKennung') == None: for path in [ '/root/gruppenregistrierung/gruppenstammdaten', '/root/gruppenregistrierung/teilnehmer', '/root/gruppenregistrierung/report' ]: self.main.setEntryDisplay(path,False) }}} === Beispiel eines Menüeintrags === {{{ { 'path':'/root/gruppe', 'controller':'GruppeController', 'text':'Gruppe', 'display':True, 'rights':['admin'], } }}} == Datenkbankzugriff == Der Datenbankzugriff ist gekapselt im Modul '''dbaccess'''. Ziel ist es, dass der Nutzcode keine nativen SQL Anweisungen enhält. Durch die Angabe des Datentyps wird beim Befüllen eines Feldes der Type überprüft. {{attachment:Domain.jpg}} '''Beispiel einer Domain''' {{{ # -*- coding: iso-8859-15 -*- from dbaccess.core import * class BuecherDomain(Domain): bueID = None bueTitel = None bueISBN = None autID = None bueErscheinungsdatum = None meta = { 'tablename':'buecher', 'primarykey':'bueID', 'fields':{ 'bueID' : {'dbfield':'bueID', 'type':'Integer'}, 'bueTitel' : {'dbfield':'bueTitel', 'type':'String'}, 'bueISBN' : {'dbfield':'bueISBN', 'type':'String'}, 'autID' : {'dbfield':'autID', 'type':'Integer'}, 'bueErscheinungsdatum' : {'dbfield':'bueErscheinungsdatum','type':'AnsiDate'} } } def getDatasource(self,selected=None): """ Erzeugt eine Datasource fuer die Verwendung in taglib.promptinput """ retval = [] for domain in self.eachDomain(where=None,orderby=None): retval.append([domain.bueID,domain.bueID]) return retval # ### HANDLER # # HINT: # Handler liefern [True|False] Zurueck. # Bei False wird die Datenbankaktion abgebrochen # # Diese Methoden koennen, wenn sie nicht benoetigt wereden # aus dieser Datei entfernt werden. # # Fehlermeldungen koennen mit self.addError("Meldung") # angegben werden. # def checkISBN(self,value): """ Pruefe die Laenge von ISBN @param value ISBN @return True|False """ if len(value) != 17: self.addError('Laengenfehler bei ISBN Nummer') return False return True def onCgiField(self,fieldname,value): """ Wenn die Domain ueber das CGI befuellt wir wird bei jedem Feld dieser Handler aufgerufen. @param fieldname Feldname @param value Inhalt aus dem CGI @param [True|False] wird False uebergeben so bricht das Laden ab """ if fieldname=='bueISBN': return self.checkISBN(value) return True def onWrite(self,mode=None): """ Wird vor jeder schreibenden Operation aufgerfuen. @param mode Enthaelt insert/update/delete @return [True|False] """ if mode in (self.INSERT,self.UPDATE): return self.checkISBN(self.bueISBN) return True # # ################# END OF HANDLER }}} == Die Klasse viewhandler == Um alle lesenden und Schreibenden Vorgänge in der Datenbank zu vereinfachen wurde diese Klasse entwickelt. Sie wird im Controller deklariert und liefert in Zusammenarbeit mit den Viewern die Bearbeitungsmasken. {{attachment:Viewhandler.jpg}} '''Beispiel der Verwendung''' {{{ # -*- coding: iso-8859-15 -*- """ Dieser Controller wird vom Framework aufgerufen. Er ist fuer ein GRID Layout optimiert. """ from controller import Controller from viewhandler import Viewhandler from helper.utility import Utility from conf.config import Config from domain.persondomain import PersonDomain from domain.gruppedomain import GruppeDomain class TeilnehmerController(Controller): viewhandler = None def get(self): gruppe = GruppeDomain(db=self.db) grpKennung = self.main.getAttribute('grpKennung') gruppe.getOverKennung(grpKennung) config = Config() where = "grpID = {0} and perArt in ('{1}','{2}')".format(gruppe.grpID,config.TN,config.FLI) self.viewhandler = Viewhandler( controller = self, layout = Viewhandler.GRID_LAYOUT, domain = PersonDomain(db=self.db), where = where, orderby = 'perID', listparam = { 'db':self.db, 'gruppe':gruppe }, gridlist = None, filter=Utility.normalizeFilter(self.cgiparam('_filter')) ) # aufrufen des Handlers self.viewhandler.run() }}} Dieser Controller liefert eine vollständige Grid-Editiermaske: {{attachment:BeispielGrid.jpg}} Es stehen 3 Layouts zur Verfügung * Grid Layout: die Daten werden in Tabellenform angezeigt und können dort Bearbeitet werden. * Listauswahl Edit Layout: Es wird eine Liste von Einträgen angezeigt. Durch Auswahl aus dieser List wird eine Editiermaske angezeigt und die Daten können dort Bearbeitet werden. Es werden zwei unterschiedliche Viewer verwendet. * List-Edit Es wird sowohl die Listen Auswahl als auch die Detailedirmaske in einem Viewer notiert. (Bequemlichkeitfeature)