Seite 1 von 2

Wicket-artiges komponentenbasiertes Rahmenwerk: Sinnvoll?

Verfasst: Donnerstag 30. April 2009, 20:08
von sma
Habe eben ein bisschen gespielt und kann jetzt dieses Beispiel tatsächlich als WSGI-Anwendung laufen lassen:

Code: Alles auswählen

import web

class HelloWorld(web.Page):
    def create(self):
        self.add(web.Label("message", "Hello World"))
    
    html = """<span web:id="message">Message goes here</span>"""

web.start(HelloWorld)
Völlig fantasielos heißt mein Rahmenwerk "web". Es baut eine Seite mit einem Label, das "Hello World" ausgibt. Das HTML-Fragment für meine Seite packe ich direkt in die Klasse. Dort sieht man, wo die Nachricht ersetzt wird.

Das Rahmenwerk hat ~200 Zeilen. Die eine Hälfte ist ein Template-Parser, die andere Hälfe definiert web.Page und web.Label und ein paar andere Komponenten.

An diesem Beispiel arbeite ich gerade:

Code: Alles auswählen

import web

class Echo(web.Page):
    def create(self):
        self.message = "[type your message here]"
        
        message_model = web.PropertyModel(self, 'message')
        
        self.add(web.Label("message", message_model))
        
        self.add(web.Form("form").add(web.TextField("input", message_model)))
    
    html = """
    <form web:id="form">
      <input type="text" web:id="input"/>
      <input type="submit" value="Set"/>
    </form>
    <span web:id="message">Message goes here<span>
    """

web.start(Echo)
Da ich 0 Rahmenwerk habe (*), muss ich selbst den WGSI-Request für das Formular parsen. Das nervt. Der Rest ist dafür einfach. Interessant ist hier, dass sich Label und TextField ein Modell teilen.

Den Code habe ich 1:1 von Wicket-Beispielen abgeschaut. In diesem dritten Beispiel sieht man, dass die inneren Klassen, die bei Wicket allgegenwärtig sind, in Python nicht so elegant wirken.

Ist der Ansatz interessant genug, um ihn weiter zu verfolgen?

Stefan

(*) Ich könnte es dadurch unter Python 3.0 laufen lassen, auch wenn's ich gerade mit Python 2.6 entwickelt habe und nicht auf 3.0 spezifisches geachtet habe.

Verfasst: Freitag 1. Mai 2009, 22:29
von sma
Update: Mein Rahmenwerk ist auf 250 Zeilen gewachsen und kann jetzt auch dieses Beispiel ausführen:

Code: Alles auswählen

import web

class Page1(web.Page):
    def create(self):
        self.add(web.PageLink("link", Page2))

    html = """<h1>Page 1</h1><a web:id="link">Other page</a>"""

class Page2(web.Page):
    def create(self):
        self.add(web.PageLink("link", Page1))

    html = """<h1>Page 2</h1><a web:id="link">Other page</a>"""

web.start(Page1)
Nachteil: Wicket hält den gesamten Programmzustand in einer Session. Dafür habe ich noch gar kein Konzept. Somit funktionieren bei mir nur zustandslose Anwendungen.

Stefan

Verfasst: Samstag 2. Mai 2009, 10:38
von Darii
Sieht ganz nett aus. Verstehe ich das richtig, dass dann die Widgets bei bedarf die Attribute/Inhalte der Elemente überschreiben und den Rest unberührt lassen?

Code: Alles auswählen

<a web:id="foo" style="font-weight: bold">Klick mich!</a>
<!-- Wird z.B. zu -->
<a class="internal_link" style="font-weight: bold">Besuche Foo!</a>
Ansonsten würde ich das vielleicht etwas „Pythonischer“ gestalten. Statt einem web.PropertyModel würde sich vielleicht sowas wie KVO anbieten (edit: wenn ich es mir recht überlege wäre das wohl mit Kanonen auf Spatzen geschossen, die Widgets können sich die Eigenschaften ja auch beim rendern holen, da ist eigentlich keine Benachrichtigung nötig).

Verfasst: Samstag 2. Mai 2009, 16:50
von Dauerbaustelle
Wo ist denn der Code?

Verfasst: Sonntag 3. Mai 2009, 18:32
von sma
Es funktioniert, wie Darii vermutet: Ein Label namens xyz sucht sich aus dem HTML-Dokument ein Element mit dem web-id-Attributwert xyz heraus und manipuliert es. Die Kindelemente werden durch den Wert des Modells des Labels ersetzt. Konkret sieht das so aus:

Code: Alles auswählen

class Label(Component):
    def apply(self, html):
        self.element(html).replace_content(self.model.object())
Observer halte ich in diesem Zusammenhang für unnötig, da sich während der Verarbeitung eines HTTP-Requests nichts ändert, sondern an Ende immer das angepasste HTML-Dokument ausgegeben wird. Zu einem Request wird (theoretisch) zunächst die Session bestimmt. Darin finde ich das aktuelle Page-Objekt. Dieses initialisiere ich mit den Request-Parametern (`setup`). Dann rufe ich die passende Aktion (`on_click`, `on_submit`) auf. Danach lasse ich die Seite ihr HTML-Dokument erzeugen (`apply`) und mache daraus die Response.

Über weitere Tipps, wie das ganze "pythonischer" aussehen könnte, freue ich mich aber.

Übrigens, soll ein Link immer `class="internal"` bekommen, kann man bei Wicket ein "Behavior", speziell einen "AttributeAppender" verwenden. So etwas habe ich nicht implementiert, aber es könnte (vereinfacht) so aussehen:

Code: Alles auswählen

    # Application
    link = web.Link("link", ...).add(web.AttributeAppender("class", "internal"))

# Implementation
class Behavior(Component):
    def apply(self, html):
        self.on_component(self.parent, html)
    
    def on_component(self, component, html):
        pass

class AttributeAppender(Behavior):
    def on_component(self, component, html):
        component.element(html).replace_attr(self.name, self.model.object())
Dauerbaustelle, der Code befindet sich auf der Festplatte meines Mac ;) Eigentlich wollte ich nur genug Code schreiben, damit mein ursprüngliches Beispiel kompiliert, dann habe ich gefallen an der Art und Weise gefunden und weiter gemacht. Ich habe jetzt eben einen Session-WSGI-Middleware gebaut und angefangen, alles noch mal neu zu schreiben. Siehe http://gist.github.com/106070 für den aktuellen Stand.

Stefan

Verfasst: Montag 4. Mai 2009, 07:12
von jens
Es gibt da parallelen zu PyLucid. Allerdings läuft es da anders herrum. Es wird nach Tags im html code (dem Template) "gesucht". Alle gefunden Tags "rufen" dann die passenden views auf und der Tag wird mit dem Rückgabewert des views ersetzt.

Wenn ich sma richtig verstanden hab, geht es hier von der code methode zum ID im html code. Ich frage mich allerdings, wie man dabei eine komplette html Seite zusammen bauen kann. Man müßte das also im Python code machen?
Macht die PyLucid Variante nicht mehr Sinn? Der User kann sich ein Template (eine komplette html Seite) mit den ganzen widget Platzhaltern zusammen bauen und diese wird dann gerendert?

Verfasst: Montag 4. Mai 2009, 13:34
von DasIch
@jens Es geht hier nicht darum ein CMS zu haben sondern ein Webframework und dabei ist der PyLucid weg nicht sonderlich brauchbar.

Verfasst: Montag 4. Mai 2009, 20:13
von cryzed
Völlig irrelevant zur Diskussion, aber kann man sowas wirklich "Webrahmenwerk" nennen? Es heißt Webframework, Ich glaube ehrlich gesagt nicht das es dafür eine gute gültige deutsche Übersetzung gibt. Wikpedia und dict.cc stimmen mir zu.

Verfasst: Dienstag 5. Mai 2009, 10:05
von snafu
Ich hoffe ja mal, dass "Web" im Sinne von "weben" gemeint ist. ;P

Verfasst: Dienstag 5. Mai 2009, 10:40
von sma
Also ich werde ein Rahmenwerk "Rahmenwerk" nennen, weil ich das schon seit mehr als 15 Jahren so mache. Und wenn ich will, werde ich auch ein "Web" davor setzen :) In der Tat liefert eine Google-Suche viele meiner Beiträge zu diesem Thema. Daher lade ich alle ein, in diesem Fall ebenfalls unnötige Anglizismen zu verwenden. Framework ist nicht ein Deut genauer als Rahmenwerk.

Zu den Templates: Meine Beispiele zeigen alle nicht den Vorteil von Komponenten, wieder aus Komponenten zu bestehen. Daher sieht man kaum einen Unterschied zu dem von jens erwähnten Verfahren, die Ausgabe über die Templates zu treiben. Es ist aber ein großer Unterschied, denn ein Template ist flach, während Komponenten, nun, komponierbar sind. Bei Wicket scheint es aber eine Gradwanderung zu sein, denn man will auch nicht den Vorteil verlieren, mit "echten" HTML-Seiten, die von "echten" HTML-Entwicklern erstellt werden können, zu arbeiten.

Eine komplexere Anwendung würde so aufgebaut sein:

Code: Alles auswählen

class Header(web.Panel):
    def create(self):
        self.add(web.Link("login", ...))
    
    html = "<div>....</div>"

class Site(web.Page):
    def create(self):
        self.add(Header())
        self.add(Sidebar())
        ...
Das sollte so gebaut werden, dass man `Header` getrennt entwickeln und testen und dann in mehr als einer Anwendung verwenden kann. Wicket erlaubt z.B. dass das Template (was ja eine HTML-Datei ist) von Panel ebenfalls eine komplette HTML-Seite ist, in der der Teil markiert wird, den sich das Panel herauspickt. Dadurch wird das Template wieder direkt mit Dreamweaver oder was weiß ich bearbeitbar.

Ja, die Seite wird "in Python" zusammengebaut. Meine HTML-Fragmente waren immer so klein, wie eben möglich, damit ein gnädiger Browser noch etwas anzeigt. Auf `<html><title><body>` usw. hatte ich verzichtet.

Eigentlich ist es nur konsequent MVC.

Und der Übergang zwischen Webrahmenwerk und CMS ist IMHO fließend, schon daher, weil nicht genau definiert ist, was eigentlich ein CMS ist. Es verarbeitet halt "Content" - na toll.

Verfasst: Dienstag 5. Mai 2009, 10:41
von sma
snafu hat geschrieben:Ich hoffe ja mal, dass "Web" im Sinne von "weben" gemeint ist. ;P
Nein, so etwas würde ich Webstuhl nennen :)

Stefan

Verfasst: Dienstag 5. Mai 2009, 10:55
von jens
sma hat geschrieben:Ja, die Seite wird "in Python" zusammengebaut. Meine HTML-Fragmente waren immer so klein, wie eben möglich, damit ein gnädiger Browser noch etwas anzeigt.
Also in PyLucid haben kleine Plugins auch nur recht kleine HTML-Fragmente. Dennoch werden dafür extra eigene Template.html Dateien erstellt. html code im python code sollte IMHO immer vermieden werden.

Verfasst: Dienstag 5. Mai 2009, 16:56
von Darii
sma hat geschrieben:Bei Wicket scheint es aber eine Gradwanderung zu sein, denn man will auch nicht den Vorteil verlieren, mit "echten" HTML-Seiten, die von "echten" HTML-Entwicklern erstellt werden können, zu arbeiten.
Das ist schon ein großer Vorteil.

Ich frage mich, ob man das ganze nicht auch besser cachen kann wenn man die Seite aus generischen Komponenten zusammensetzt.
Ja, die Seite wird "in Python" zusammengebaut. Meine HTML-Fragmente waren immer so klein, wie eben möglich, damit ein gnädiger Browser noch etwas anzeigt. Auf `<html><title><body>` usw. hatte ich verzichtet.
HTML verlangt sowieso nur die Existenz eines doctypes und <title>, mehr ist überflüssig ;) *scnr*

Verfasst: Dienstag 5. Mai 2009, 19:05
von derdon
Darii hat geschrieben:HTML verlangt sowieso nur die Existenz eines doctypes und <title>, mehr ist überflüssig ;) *scnr*
Wo ist der Witz?

Verfasst: Dienstag 5. Mai 2009, 19:39
von Hyperion
Also ich halte das für einen sehr interessanten Ansatz. Allerdings würde ich auch eher für einen Template-Aufsatz plädieren. Oder "schadet" der irgendwo? Wenn nein, ist das doch wesentlich flexibler.

Fänds aber mal interessant ein Ergebnis davon zu sehen :-)

Verfasst: Dienstag 5. Mai 2009, 19:51
von Darii
derdon hat geschrieben:
Darii hat geschrieben:HTML verlangt sowieso nur die Existenz eines doctypes und <title>, mehr ist überflüssig ;) *scnr*
Wo ist der Witz?
Dass das wirklich so ist, sma hatte das ja mehr Scherzhaft gemeint und dann gibts ja noch die shorttags, damit lassen sich recht krude, aber valide Dokumente basteln. Also am besten einfach vergessen, was du über HTML gelernt hast – es stimmt nicht ;)

Verfasst: Dienstag 5. Mai 2009, 22:20
von sma
Das wird mir hier alles zu unübersichtlich. Daher ein Beispiel, um zu zeigen, dass man das HTML für ein Page-Objekt natürlich auch extern als Datei verwalten kann:

Code: Alles auswählen

# example/pages.py
class TemplatePage(web.Page):
    @property
    def html(self):
        d = os.path.splitext(sys.modules[self.__class__.__module__].__file__)[0]
        with os.path.join(d, self.__class__.__name__ + ".html") as f:
            return f.read()

class Welcome(TemplatePage):
    def create(self):
        self.add(web.Label("message", "Hello, World!"))

Code: Alles auswählen

<!-- example/pages/Welcome.html -->
<html>
  <head>
    <title>Example</title>
    <style type="text/css">
      ...
    </style>
  </head>
  <body>
    <h1 web:id="message">greeting...</h1>
  </body>
</html>
Alternativ kann ich Layouts einführen, die noch mal Gemeinsamkeiten verschiedener Seiten zusammenfassen. Ich verweise da auf die Wicket-Dokumentation.

Stefan

Verfasst: Dienstag 5. Mai 2009, 22:30
von Hyperion
Gibts einen Grund, wieso Du das Templating selber implementierst? Evtl. beantwortet das die Frage, wieso z.B. jinja2 da keine Option für eine Trennung von Code und HTML ist.

Verfasst: Dienstag 5. Mai 2009, 22:34
von Darii
Um das ganze „pythonischer“ zu machen könnte man da auch Mixins über Mehrfachvererbung nutzen.

Code: Alles auswählen

class Welcome(web.Page, AutoTemplate): pass
ich fänds zumindest von der Objekthierarchie schöner.

Verfasst: Donnerstag 7. Mai 2009, 10:26
von sma
Mixins waren tatsächlich ebenfalls meine erste Idee, ich fand's für die Diskussion aber mit einer einfachen Unterklasse verständlicher.

Hyperion, Jinja verhält sich nicht wie Wicket und genau das habe ich ja nachbauen wollen. Daher kann ich Jinja auch nicht einsetzen.

Schließlich fing ich an, Gefallen an der Idee zu finden, auf diese Weise ein Web-Rahmenwerk für Python 3 (ich fragte vor einiger Zeit ja mal danach) zu bekommen, welches möglichst keine externen Abhängigkeiten haben sollte, damit ich das mit meinem eigenen Python-Interpreter (wenn ich ihn dann weitergebaut habe) laufen lassen kann. Steht aber alles in meinem "Wicket in Python"-Artikel am Ende von http://gist.github.com/106070

Stefan