Wicket-artiges komponentenbasiertes Rahmenwerk: Sinnvoll?

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

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.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

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
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

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).
Dauerbaustelle
User
Beiträge: 996
Registriert: Mittwoch 9. Januar 2008, 13:48

Wo ist denn der Code?
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

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
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

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?

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

@jens Es geht hier nicht darum ein CMS zu haben sondern ein Webframework und dabei ist der PyLucid weg nicht sonderlich brauchbar.
Benutzeravatar
cryzed
User
Beiträge: 82
Registriert: Samstag 28. März 2009, 15:53

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.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ich hoffe ja mal, dass "Web" im Sinne von "weben" gemeint ist. ;P
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

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.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

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
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

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.

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

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*
derdon
User
Beiträge: 1316
Registriert: Freitag 24. Oktober 2008, 14:32

Darii hat geschrieben:HTML verlangt sowieso nur die Existenz eines doctypes und <title>, mehr ist überflüssig ;) *scnr*
Wo ist der Witz?
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

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 :-)
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

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 ;)
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

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
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

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.
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

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.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

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
Antworten