Code verbessern: "Parent Daten Modell und tree erstelle

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Antworten
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Wie ich bei http://www.python-forum.de/topic-10824.html festgestellt habe nutzte ich in PyLucid z.Z. das sog. "Partent Daten Modell"...

Unter http://www.python-forum.de/topic-9698.html habe ich schon einmal nachgefragt, wie man aus den DB Daten einen tree erzeugen kann.
Die Daten aus der DB sehen z.B. so aus:

Code: Alles auswählen

    data = [
        {'id': 1, 'parent': None, 'name': '1. AAA'},
        {'id': 2, 'parent': 1,    'name': '1.1. BBB'},
        {'id': 3, 'parent': 1,    'name': '1.2. BBB'},
        {'id': 4, 'parent': 2,    'name': '1.2.1. CCC'},
        {'id': 5, 'parent': 2,    'name': '1.2.2. CCC'},
        {'id': 6, 'parent': None, 'name': '2. AAA'},
        {'id': 7, 'parent': 6,    'name': '2.1. BBB'},
    ]
Ziel ist es, einmal für das Hauptmenü und einmal für das SiteMap die Daten zu reoganisieren.

Dabei unterscheiden sich die beiden Varianten so: Im Hauptmenü werden nur die relevanten Zweige dargestellt , während im SiteMap alle CMS Seiten als Baum aufgelistet werden. Allerdings werden bei beiden Varianten erstmal alle CMS Seiten aus der DB geholt. Das muß man ja leider beim Parent-Modell tun.

Das Hauptmenü soll so funktionieren, wie es z.B. auf http://www.pylucid.org funktioniert. (Dort läuft ja noch die alte PyLucid Version)

Bisher hatte ich nur einen tree-Generator geschrieben, der die SiteMap Daten aufbaut. Gestern habe ich den Teil fertig bekommen, der für das Hauptmenü zuständig ist.

Irgendwie erscheint mir meine Lösung nicht so super effektiv. Vielleicht hat der ein oder andere ja ein paar optimierungs Vorschläge.
Im Grunde wäre es vielleicht nicht schlecht das Parent-Modell mit einer "Nested Sets" Implementierung zu tauschen. Aber das möchte ich z.Z. nicht.

Der Sourcecode ist seperat und kann somit losgelöst getestet werden. Ich hab auf extra unten testdaten eingefügt. z.Z. macht es genau das was es soll.

Sourcecode via Subversion:
http://pylucid.net/trac/browser/branche ... nerator.py
Soucecode im paste:
http://paste.pocoo.org/show/1546/

Wenn ich Verbesserungen im Code machen solltet, wäre es das beste, ihr postet das im paste Dienst, hier der "Replay" Link: http://paste.pocoo.org/reply/1546/ (Dann kann man die DIFF Darstellung benutzten)
Ansonsten: Allgemeine Kommentare sehr erwünscht.

Kleine Erklärung zum Code:

Der erste Aufruf mit .get_complete_tree() erzeugt die SiteMap Daten.
Der zweite und dritte Aufruf mit .get_tree_menu() erzeugt eine "Hauptmenü-Ansicht".

Ich nutzte unten beim Test deepcopy, weil ich im Code später mit .pop() Datensätze aus dem Dict herauslöse. Ich erhoffe mir davon etwas Zeiteinsparung, denn in einem späteren Schritt werden diese Teile eh nicht mehr verwendet. Somit werden die Grunddaten nach und nach weniger.
Ob das wirklich eine gute Idee ist, weiß ich noch nicht so genau...

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
BlackJack

Das sieht nach Java aus -> Eine überflüssige Klasse `TreeGenerator`. Wenn man `start_level` als Argument der entsprechenden Methoden setzen würde, hat man einfach nur ein paar Funktionen, die ohne Grund in einer Klasse stecken. Die ersten drei Methoden nach `__init__()` könnten jetzt schon Funktionen sein.

Etwas kompliziert sieht's IMHO auch aus.

Ich würde das mit "richtigen" Klassen lösen. Und zwar Knoten-Objekte und eine Menübaum-Klasse. `MenuTree`-Objekte werden aus den "flachen" Daten erstellt. Dann kann man entweder einen bestimmten Menüpunkt mit einer Methode aktivieren, oder alle für die Sitemap und sich mit `to_dict()` eine Darstellung holen, wie sie Dein Code auch erzeugt.

`MenuNode`-Objekte haben eine ID, einen Namen, eine Referenz auf den Elternknoten, eine Liste mit Unterknoten und ein Flag, ob der Knoten sichtbar ist, oder nicht. Das heisst man kann ein und den selben Baum mit der gleichen `to_dict()`-Methode verschieden darstellen, je nach Sichtbarkeit der Knoten. Wenn man die Sitemap will, dann macht man alle sichtbar.

Wenn nur ein bestimmter Knoten aktiviert werden soll, dann muss der Knoten selbst, und seine direkten Kinder sichtbar sein. Ausserdem muss das gleiche für all seinen Elternknoten und dessen Elternknoten und… gelten. Also muss man die Methode nur rekursiv auf dem Elternknoten aufrufen.

Die `to_dict()`-Methode muss einfach nur rekursiv alle sichtbaren Knoten in ein verschachteltes Dictionary umwandeln.

http://www.ubuntuusers.de/paste/11331/

(paste.pocoo.org hat mir schon wieder mal mit einem Internal Server Error geantwortet)
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Vielen dank für deine Mühe!

Ich schlage mich seid dem Wochendende mit Varizellen rum :( Als Erwachsener macht das nicht so richtig Spaß (Wobei als Kind bestimmt auch nicht).

Deswegen hab ich mir deine Variante noch nicht näher anschauen können... Werde ich aber auf alle Fälle noch nachholen...

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
N317V
User
Beiträge: 504
Registriert: Freitag 8. April 2005, 13:23
Wohnort: München

Hey, gute Besserung, jens! Kurier Dich gut aus.
Es gibt für alles eine rationale Erklärung.
Außerdem gibt es eine irrationale.

Wie man Fragen richtig stellt
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

@N317V: Dank dir ;)

@BlackJack: Ich hab mal einen Benchmark gemacht. Deine Variante ist langsamer. Allerdings nur dann, wenn nach der Initialisierung nur eine "Abfrage" stattfindet. Oder anders ausgedrückt: Sie ist langsamer, wenn die Klasse für jede Abfrage neu initialisiert wird.

Wenn allerdings nach dem Initialisieren mehrere Abfragen stattfinden, ist deine Variante deutlich schneller. Das ist für eine Web Anwendung aber der bessere Ausgangspunkt (wenn mehr als CGI im Spiel ist)

Ich habe mal versucht meine Variante so umzustricken, das kein .pop() mehr benutzt wird und bei der Initialisierung die Index-Dicts aufgebaut werden. Aber ich hab es nicht zum laufen gebracht.

Da dein Code eh viel schöner ist, werde ich diesen verwenden, wenn du nichts dagegen hast ;)

EDIT: Sehr dumm... Ich kann die einmal Initialisierte Klasse nicht für den nächsten Gebrauch "aufbewahren". Wüsste einfach nicht, wie ich das tun sollte... :(

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
BlackJack

Klar kannst Du die gerne verwenden.

Zum speichern: Picklen!? Falls dass dann nicht wieder zu langsam wird.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

BlackJack hat geschrieben:Zum speichern: Picklen!? Falls dass dann nicht wieder zu langsam wird.
Im alten PyLucid hatte ich dazu einen "object cache" der genau das gemacht hab...

Was mir gerade einfällt: Die Sache mit dem Caching ist eh nicht so einfach... Denn es gibt in PyLucid auch Seiten, die nur eingeloggte User sehen dürfen... Ich Filter das im Vorhinein aus der DB Abfrage raus.

Ich sehe da drei Möglichkeiten:
1. Nicht vorher filtern, sondern das im Nachhinein mit dem tree Generator machen.
2. Für jede Usergruppe die Daten cachen
3. So lassen wie es jetzt ist und bei jedem Request die Daten neu aufbauen.

Die erste Variante ist eigentlich die schönste. Aber auch Aufwendig.
Die zweite Variante ist wohl die dümmste ;)
Die dritte die einfachste und das werde ich also erstmal so machen und mich später darum kümmern...

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

So... Nochmal die überarbeitete Version von mir:
http://paste.pocoo.org/show/1566/

Ich hab nicht sooo viel geändert. Nur: Im live system kommen ein paar andere Angaben (keys) im source dict vor. Nun muß nur "id" und "parent" enthalten sein. Alle anderen key-values werden aber in MenuNode.data gespeichert und auch wieder ausgegeben... So ist es ein wenig flexibler...

Außerdem habe ich die "High-Level" Methoden get_menu_tree() und get_sitemap_tree() eingefügt, sowie einen einfachen Test...

Das ganze steckt nun in PyLucid: http://pylucid.net/trac/changeset/1031 *freu*

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

jens hat geschrieben:...Ziel ist es, einmal für das Hauptmenü und einmal für das SiteMap die Daten zu reoganisieren.

Dabei unterscheiden sich die beiden Varianten so: Im Hauptmenü werden nur die relevanten Zweige dargestellt , während im SiteMap alle CMS Seiten als Baum aufgelistet werden. Allerdings werden bei beiden Varianten erstmal alle CMS Seiten aus der DB geholt. Das muß man ja leider beim Parent-Modell tun...
Hi Jens,

Dein neuer Code ist ja schon fertig und sehr schön. Falls Du noch
Interesse hättest, hätte ich da vielleicht noch die ein oder andere Idee.
Dazu hätte ich noch ein paar Fragen. Wie wichtig sind Dir sortierte
Baumknoten? Ist eine automatische Nummerierung wichtig
(1. - 1.1 - 1.2 - 2.)? Brauchst Du eine individuelle Usersteuerung für
das Menü, die ev. auch noch gespeichert wird? Ich teste da gerade eine
Datenstruktur, die Dir auch vielleicht was bringen könnte, brauch aber
noch etwas ...

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

Die Sortierung erledige ich schon bei der DB Abfrage:

Code: Alles auswählen

    page_data = Page.objects.values(
        "id", "parent", "name", "title", "shortcut"
    ).order_by("position")
Die Sortierung kommt also von .order_by("position")... Dabei ist position nur eine Gewichtungszahl.
Da die DB Daten schön der Reihe nach verarbeitet werden, funktioniert das so ganz gut...

Über eine Nummerierung hab ich mir bisher keine Gedanken gemacht ;) Wäre aber natürlich auch nett, wenn man es optional Einschalten könnte...

Das verwendete Parent-Modell hat aber andere dumme Nachteile. Es müssen alle Einträge verarbeitet werden, auch wenn man nur einen Ausschnitt braucht. Aber naja...

Meld dich mal, wenn du mit deinem Daten-Modell weiter bist ;)

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Antworten