Wie ein HTML-Menü mit Django darstellen?

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

Würde gerne ein Menü in einer Django-Anwendung auf diversen HTML-Seiten darstellen. Das HTML sieht so aus:

Code: Alles auswählen

<ul class="tabbed">
  <li class="selected">Detail 1</li>
  <li><a href="...">Detail 2</a></li>
</ul>
Wie würdet ihr vorgehen? Namen, URLs und Index im View erzeugen (und dann auch dort ggf. internationalisieren) und im Kontext herausreichen? Eigenes Tag auf jeder Seite? Copy&Paste?

In urls.py kann ich's mir so vorstellen:

Code: Alles auswählen

url(r'/path/detail1', 'view', {'detail': 1}, name='path_detail1'),
url(r'/path/detail2', 'view', {'detail': 2}, name='path_detail2'),
Der View schleift dann `detail` zum Template durch. Dort müsste dann das Menü dargestellt werden. Zur Zeit benutze ich ein `ifequal` für jede Zeile, was extrem häßlich ist:

Code: Alles auswählen

{% ifequal detail 1 %}<li class="selected">Detail 1</li>
{% else %}<li><a href="{% url path_detail1 %}">Detail 1</a></li>
Gibt's da schon was von Ratiopharm?

Stefan
Dauerbaustelle
User
Beiträge: 996
Registriert: Mittwoch 9. Januar 2008, 13:48

Django ist nicht dafür geschrieben, seine Du-sollst-wenig-Code-Schreiben-Philosophie zu umgehen :D

Nimm ein Template als Basis für deine HTML-Seite.

Code: Alles auswählen

...bla...
<ul class="tabbed">
    {% for link in inks %}
        {% ifequal link.url current %}
            <li class="selected">{{ link.name }}</li>
        {% else %}
            <li><a href="{% url detailview link.url %}">{{ link.name }}</a></li>
        {% endifequal %}
    {% endfor %}
</ul>
...bla...
Hier musst du zwei Dinge übermitteln:
Einmal die Links, am Besten in einem Dict, welches sich in einer Liste befindet
und die Angabe für das 'current' Item, also das aktive, welches ja vorgehoben werden soll (?):

Code: Alles auswählen

def detail(detail):
    ...bla... (Detail-Ansicht mit der ID aus `detail` holen)
    links = [
        {'url' : 'detail1', 'name' : 'Menüeintrag eins'}
        {'url' : 'detail2', 'name' : 'Menüeintrag zwei'}
        {'url' : 'detail3', 'name' : 'Menüeintrag drei'}
    ]
    current = detail
    ...bla...
    return render_to_response(deintemplate, links=links, current=current)
urls.py sähe dann in etwa so aus:

Code: Alles auswählen

    url(r'/path/detail/(?P<detail>[A-Za-z0-9])', 'detail', name='detailview')
Als Tipp noch: http://djangoproject.org hat nen gutes Tutorial :)
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Dauerbaustelle, danke für deine Antwort. Mein ästhetisches Empfinden, wo welcher Teil des Codes wie oft stehen sollte, befriedigt das leider auch nicht.

Ich möchte nicht auf jeder HTML-Seite, die Teil des Menüs ist, den Code für das Menü wiederholen. Ich könnte `include` benutzen, doch das ist mir irgendwie suspekt, weil es keinen eigenen Kontext hat, sondern den er einbettenden Seite mit benutzt, was die Wiederverwendbarkeit über ein spezielles Menü hinaus einschränkt.

Ich betrachte das Menü als ein Implementationsdetail des Webdesigners, der sich überlegt hat, das Tabs schicker sind als einfach eine andere Liste von Hyperlinks. Ich möchte dies daher nicht im View vorsehen und dort die Titel definieren.

Ich könnte mir vorstellen, sowas zu machen:

Code: Alles auswählen

{% load menu %}
{% menu %}
    {% item "Detail 1" as detail, 1 %}
    {% item "Detail 2" as detail, 2 %}
{% endmenu %}
Doch dafür muss man sich eben ein eigenes Tag bauen, welches auch noch 90% des Codes von {% url %} kopieren muss, weil das leider nicht besser im Django-Code faktorisiert ist. Das erste Argument sei der anzuzeigende Titel (optional könnte man dort auch `_("...")` schreiben) und nach dem Schlüsselwort `as` kommen dann die selben Argumente wie bei {% url %}, aus denen dann eine URL berechnet wird. Ist dies die aktuelle URL (die ich aus einem dann dem Template zu übergebenen request-Parameter auslesen muss) stelle ich den Reiter als aktuell dar, ansonsten als Link. Dem Menü könnte man noch zwei Templates zuordnen, in dem der Designer Details der Darstellung der Reiter einstellen kann.

Übrigens: Das man ähnliche URLs über reguläre Ausdrücke zusammenfassen kann, ist mir schon klar, ist in diesem Beispiel aber eher zufällig. Allgemein soll es natürlich für beliebige URLs funktionieren. Das hätte ich deutlicher schreiben können.

Mein Problem ist eigentlich, dass ich nicht so recht weiß, was ich will - nur, was ich nicht will :) Dies sagt mir nicht zu, weil ich nicht nur ein Menü und dieses nicht in den Settings definieren will. Da hat ein Designer schon mal gar nichts zu ändern. Hier hatte jemand ein ähnliches Problem zu lösen versucht und war auch nicht wirklich zufrieden :) Seine Lösung, JavaScript zu benutzen, finde ich aber noch schlechter. Hier ist eine extrem kompliziert aussehende Lösung, die auch die aktuelle Seite erkennen will und ein `ifactive`-Tag zur Verfügung stellt. Der Autor ist hier wie von mir oben als notwendig erwähnten Weg des Kopierens von {% url %}-Code gegangen... ach ja...

Wollte man das menu-Tag implementieren, ist dies eine Vorlage, wie man das `item`-Kindelement finden und verarbeiten kann.

Stefan
Y0Gi
User
Beiträge: 1454
Registriert: Freitag 22. September 2006, 23:05
Wohnort: ja

sma hat geschrieben:Ich betrachte das Menü als ein Implementationsdetail des Webdesigners, der sich überlegt hat, das Tabs schicker sind als einfach eine andere Liste von Hyperlinks. Ich möchte dies daher nicht im View vorsehen und dort die Titel definieren.
Vielleicht habe ich dich nicht richtig verstanden, aber die *Darstellung* eines Menüs - das nach allgemeiner Web-Weisheit aktuell wohl als sortierte Liste von Links die meiste semantische Korrektheit hat - lässt sich weitgehend mit CSS vornehmen, auch als Tabs dargestellt. Oder meinst du da was verschachteltes?
Dauerbaustelle
User
Beiträge: 996
Registriert: Mittwoch 9. Januar 2008, 13:48

Hallo,

wenn du aber

Code: Alles auswählen

{% load menu %}
{% menu %}
    {% item "Detail 1" as detail, 1 %}
    {% item "Detail 2" as detail, 2 %}
{% endmenu %} 
benutzt, dann musst du das ja auf jeder Seite wieder einbinden. Dann kannst du auch gleich raw-HTML schreiben.

Ich verstehe nicht genau, was du willst? Warum machst du nicht sowas:

base.html

Code: Alles auswählen

...bla...
{% bock menu %}
<ul class="tabbed">
    {% for link in inks %}
        {% ifequal link.url current %}
            <li class="selected">{{ link.name }}</li>
        {% else %}
            <li><a href="{% url detailview link.url %}">{{ link.name }}</a></li>
        {% endifequal %}
    {% endfor %}
</ul>
{% endblock %}
...bla...
deintemplate.html

Code: Alles auswählen

{% extends 'base.html' %}
...bla..
Oder stört dich das Definieren der Menüeinträge in den views? Mich auch. Leider kann Django auch kein

Code: Alles auswählen

{% for url,name in (("detail1", "About us"),("detail2","Products")) %}
oder so.

Aber die Idee mit dem {% menu %}-Tag ist vielleicht garnicht so schlecht.

Gruß
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Dauerbaustelle hat geschrieben:...dann musst du das ja auf jeder Seite wieder einbinden. Dann kannst du auch gleich raw-HTML schreiben.
"item" ist eine Zeile, das andere wären 5 Zeilen pro Menüeintrag. Sie wiederholen zudem immer und immer wieder den Algorithmus, wie man zwischen aktiv und anklickbar unterscheidet und man müsste alle diese Zeilen ändern, wenn man hier etwas anpassen will. Ich sehe da schon einen Unterschied.
Dauerbaustelle hat geschrieben:Oder stört dich das Definieren der Menüeinträge in den views? Mich auch.
Ja, das tut es. Für eine ausdrucksstärkere Template-Sprache hatte ich mir schon mal Jinja2 als Alternative angeschaut, das Projekt ist aber leider zu weit fortgeschritten, als das wir uns dabei wohlfühlen, jetzt noch die Template-Sprache auszutauschen - selbst wenn Jinja2 sehr ähnlich ist.

Ich habe mich jetzt für folgendes entschieden:

Code: Alles auswählen

class MenuNode(template.Node):
    def __init__(self, nodelist):
        self.nodelist = nodelist
    
    def render(self, context):
        output = self.nodelist.render(context)
        result = []
        for line in (line.strip() for line in output.splitlines()):
            if line:
                title, url = map(unicode.strip, line.split(" -- "))
                # hier das "if"
                result.append('<li><a href="%s">%s</a></li>' % (url, title))
        return "<ul>%s</ul>" % "".join(result)

@register.tag
def menu(parser, token):
    nodelist = parser.parse(('endmenu',))
    parser.delete_first_token()
    return MenuNode(nodelist)
Dann kann ich's so schreiben:

Code: Alles auswählen

{% menu %}
    About -- {% url about %}
    Products -- {% url prod %}
{% endmenu %}
Ich muss nicht den Code für `url` nachbauen und ich kann besser mein eigenes Format parsen als mich mit Django's krankem Parser zu quälen.

Wo der Kommentar steht, kann ich jetzt meine Logik einbauen. Welches die aktuelle Seite ist, erkenne ich an der URL, die ich mir aus dem `context` hole. Als Parameter kann ich außerdem die Klasse(n) für das <ul> übergeben.

Stefan
Antworten