Kategoriebaum aus DB einlesen

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Benutzeravatar
Whitie
User
Beiträge: 216
Registriert: Sonntag 4. Juni 2006, 12:39
Wohnort: Schulzendorf

Ahhh,
jetzt hab ich wahrscheinlich begriffen.

Ich kann jetzt gerade nicht testen, aber mit

Code: Alles auswählen

def parse_categories(category_id, db_cursor, list_of_cat_ids):
und dem Aufruf

Code: Alles auswählen

cat_list = parse_categories(4, cursor, list())
müsste es doch dann gehen, oder ?
Liegt da jetzt mein nächster Denkfehler ?

Vielen Dank auf jeden Fall.

Gruß, Whitie
BlackJack

Jup, das funktioniert dann. Dann wird bei jedem Aufruf von "oberster Ebene" mit einer neuen, frischen, leeren Liste begonnen.
tabellar
User
Beiträge: 186
Registriert: Mittwoch 4. September 2002, 15:28

Ich hätte da auch noch ne Anregung. Solche DB-Bäume, Graphen oder Zeiger-Strukturen
kann man super in Dicts abbilden. Je nach Verwendungs-
zweck wird das tree_model dann entsprechend angepasst.

Als Grundprinzip habe ich immer zwei Schritte bei solchen DB-Trees:
1. Die DB-Tree-Daten durch EINE Select-Abfrage holen -> db_tree_list = cur.fetchall()
2. Die db_tree_list dann in ein entsprechendes dict_model wandeln.

Vorteil ist hier, dass man sich ganz auf die Baum-Datenstruktur konzentrieren
und mit einer Python-Liste testen kann. Passt das Wandeln der Liste in
das Dict-Model, kann man die echten Werte von der DB holen...

Einfaches Beispiel mit einfachem view:

Code: Alles auswählen

#database select -> cursor.execute(sql)
#db_category_tree = cursor.fetchall()
db_category_tree = [
[10,   0, 'Dach',             'container', 1],
[20,   0, 'Heizung',          'container', 2],
[101, 10, 'Firstpfette',      'task',      3],
[102, 10, 'Innenverkleidung', 'task',      4],
[201, 20, 'Aussparungen OG',  'task',      5],
[202, 20, 'Radiatoren EG',    'task',      6]
]

#create tree_model
tree = {}
tree[0]= {'name': 'root', 'childs': []}
for row in db_category_tree:
    tree[row[0]]= {'name': row[2], 'type': row[3], 'childs': []}
    tree[row[1]]['childs'].append(row[0])

#simple tree_view
for child in tree[0]['childs']:
    print tree[child]['name']
    for subchild in tree[child]['childs']:
        print '-'*1 + tree[subchild]['name']

Dach
-Firstpfette
-Innenverkleidung
Heizung
-Aussparungen OG
-Radiatoren EG

Das Beispiel hat pro Knoten als childs-Objekt eine Liste, damit die Reihenfolge des Baumes beigehalten werden kann. Ist die Reihenfolge
des Baumes egal, kann das childs-Objekt auch ein Dict sein: 'childs': {10:None, 20:None}

>>> tree[0]['childs']
[10, 20]



Tabellar
Benutzeravatar
Whitie
User
Beiträge: 216
Registriert: Sonntag 4. Juni 2006, 12:39
Wohnort: Schulzendorf

@ tabellar
Danke, werde ich auch mal ausprobieren.

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

Siehe da, ich hab im Grunde das selbe Problem in PyLucid: http://pylucid.htfx.eu/phpBB2/viewtopic.php?t=46 ;)

z.Z. hab ich allerdings schon eine Lösung dafür, denn sonst hätte ich kein Hauptmenü oder Sitemap:
main_menu.py SiteMap.py
:)

Allerdings möchte ich das Hauptmenü erweitern, sodas ich auch TABs benutzten kann. Generell soll es dann diese Möglichkeiten geben:
<lucidTag:main_menu/> - Generiert das bisherige "normale" Baum-Menü

<lucidFunction:main_menu>[0]</lucidFunction> - Generiert eine TAB-Liste der ersten Menü-Ebene

<lucidFunction:main_menu>[1:]</lucidFunction> - Generiert ein Baum-Menü ohne die erste Ebene


@tabellar: Ich hab erstmal deine Version erweitert bzw. geändert. Ich hab z.B. keine Kategorien und keine "deep" Angabe. Außerdem funktioniert deine Variante nur mit zwei Ebenen. In PyLucid hab ich aber keine feste tiefe. Somit hab ich es mal rekursiv gemacht:

Code: Alles auswählen

#!/usr/bin/python
# -*- coding: UTF-8 -*-

import pprint

#database select -> cursor.execute(sql)
#db_category_tree = cursor.fetchall()
db_category_tree = [
    [1,   0, '1'],
    [2,   0, '2'],
    [3,   1, '1.2'],
    [4,   1, '1.3'],
    [5,   2, '2.1'],
    [6,   2, '2.2'],
    [7,   5, '2.1.1'],
    [8,   5, '2.1.2'],
    [9,   0, '3'],
]

#create tree_model
tree = {}
tree[0]= {'name': 'root', 'childs': []}
for row in db_category_tree:
    tree[row[0]]= {'name': row[2], 'childs': []}
    tree[row[1]]['childs'].append(row[0])

print pprint.pformat(tree)
print

#simple tree_view
def tree_view(tree, parent=0):
    for child in tree[parent]['childs']:
        print tree[child]['name']

        if tree[child]['childs'] != []:
            tree_view(tree, child)

print "Tree view:"
tree_view(tree)
Ausgabe:
{0: {'childs': [1, 2, 9], 'name': 'root'},
1: {'childs': [3, 4], 'name': '1'},
2: {'childs': [5, 6], 'name': '2'},
3: {'childs': [], 'name': '1.2'},
4: {'childs': [], 'name': '1.3'},
5: {'childs': [7, 8], 'name': '2.1'},
6: {'childs': [], 'name': '2.2'},
7: {'childs': [], 'name': '2.1.1'},
8: {'childs': [], 'name': '2.1.2'},
9: {'childs': [], 'name': '3'}}

Tree view:
1
1.2
1.3
2
2.1
2.1.1
2.1.2
2.2
3
Nun muß ich das ganze in drei Verschiedene Varianten ausbauen:
SiteMap:
Das ist quasi fertig, die Lösung von oben.

normales Menü:
Das verhalten des Hauptmenü's, wie auf http://pylucid.org

TAB Menü:
Anzeige nur der ersten Ebene (Wohl kein Problem). Anzeige der Unterpunkte nur ab Level 1. Also wenn man z.B. gerade bei Punkt 2.1 ist, sollte das angezeigt werden:

Code: Alles auswählen

2.1
2.1.1
2.1.2
2.2

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

Nur ganz kurz, das tree_model ist vollständig nutzbar, ev. müsste man noch ein Feld parent_id einfügen. Dann ist das ganze flexibler. Der einfache View ist eben statisch gebaut. Nur zwei Ebenen. Für beliebige Tiefe muss man dann eben eine rekursive view_funktion bauen...

Tabellar
tabellar
User
Beiträge: 186
Registriert: Mittwoch 4. September 2002, 15:28

Hab den Baum mal mit einem automatischen Numerierungs View und der Angabe
der Tiefe gebastelt :wink: (Anmerkung: die austeigende Laufzahl
in der Datenliste ist keine Tiefenangabe, sondern ein Hilfsmittel zur Sortierung
in der Datenbank. Nur so ist die Reihenfolge beim Select eindeutig gegeben)

Code: Alles auswählen

#database select
#db_category_tree = cursor.fetchall()
db_category_tree = [
[10,   0, 'Dach',             'container', 1],
[20,   0, 'Heizung',          'container', 2],
[101, 10, 'Firstpfette',      'task',      3],
[102, 10, 'Innenverkleidung', 'task',      4],
[201, 20, 'Aussparungen OG',  'task',      5],
[202, 20, 'Radiatoren EG',    'task',      6],
[301, 202, 'Schlafen',        'task',      7],
[302, 202, 'Wohnen',          'task',      8]
]

#create tree_model
tree = {}
tree[0]= {'parent_id': 0, 'name': 'root', 'childs': []}
for row in db_category_tree:
    tree[row[0]]= {'parent_id': row[1], 'name': row[2], 'type': row[3], 'childs': []}
    tree[row[1]]['childs'].append(row[0])

#create simple_depth_view
def tree_view(tree, n=2):
    def recursion(tree,node,depth, index):
        if depth +1 <= n:
            j = 1
            for child in tree[node]['childs']:
                tree[child]['depth'] = depth +1
                tree[child]['index'] = str(index) + '.' + str(j)
                j += 1
            for child in tree[node]['childs']:
                if tree[child]['depth'] == 1:
                    print str(tree[child]['index'])[2:], tree[child]['name']
                else:
                    print (tree[child]['depth'] - 1) * ' ' + str(tree[child]['index'])[2:], tree[child]['name']
                if tree[child]['childs'] != []:
                   recursion(tree,child,tree[child]['depth'], tree[child]['index'])
    recursion(tree,0,0,0)    
Ergebnis:

Code: Alles auswählen

>>> tree_view(tree,1)
1 Dach
2 Heizung

>>> tree_view(tree,2)
1 Dach
 1.1 Firstpfette
 1.2 Innenverkleidung
2 Heizung
 2.1 Aussparungen OG
 2.2 Radiatoren EG

>>> tree_view(tree,3)
1 Dach
 1.1 Firstpfette
 1.2 Innenverkleidung
2 Heizung
 2.1 Aussparungen OG
 2.2 Radiatoren EG
  2.2.1 Schlafen
  2.2.2 Wohnen
Benutzeravatar
Whitie
User
Beiträge: 216
Registriert: Sonntag 4. Juni 2006, 12:39
Wohnort: Schulzendorf

Hi Leute,
ich hab die letzten Tage mal wieder rumprobiert und habe jetzt eine für mich ideale Lösung gefunden. Inspiriert von einem anderen Thread, indem es um saubere Web-Programmierung geht, habe ich jetzt Django gewählt, um das Projekt auf die Beine zu stellen. Nach einigen Tutorials und durchstöbern der Dokumentation ist folgendes Django-Model rausgekommen:

Code: Alles auswählen

class Category(models.Model):
    parent = models.ForeignKey('self', blank = True, null = True,
                               verbose_name = _('Oberkategorie'),
                               help_text = _('So lassen für neue '
                                             'Oberkategorie.'))
    name = models.CharField(_('Name'), maxlength = 50, unique = True,
                            core = True)
    cat_opening = models.TextField(_('Einführung'), null = True)
    example = models.CharField(_('Beispiele'), maxlength = 3, blank = True,
        help_text = _('Für welche Art Fragen soll zu Beginn der Kategorie '
        'ein Beispiel angezeigt werden ? s = Single Choice, m = Multiple '
        'Choice, f = Freier Text (Einfach die Buchstaben hintereinander '
        'schreiben, z. B. smf für alle und leer für keine)'), default = 'sm')
    active = models.BooleanField(_('Aktiviert'), default = True)
    cat_time = models.IntegerField(_('Zeit'), default = 40,
        help_text = _('Zeit zur Beantwortung aller Fragen in dieser '
        'Kategorie in Minuten.'))
    use_item_time = models.BooleanField(_('Zeit von Fragen benutzen'),
        default = False, help_text = _('Wenn aktiviert, werden '
        'die Fragen einzeln angezeigt und jede Frage kann ein eigenes '
        'Zeitlimit haben.'))
    
    def __str__(self):
        p_list = self._recurse_for_parents(self)
        p_list.append(self.name)
        return self.get_separator().join(p_list)
    
    def _recurse_for_parents(self, cat_obj):
        p_list = []
        if cat_obj.parent_id:
            p = cat_obj.parent
            p_list.append(p.name)
            more = self._recurse_for_parents(p)
            p_list.extend(more)
        if cat_obj == self and p_list:
            p_list.reverse()
        return p_list
                
    def get_separator(self):
        return ' -> '
        
    def _parents_repr(self):
        p_list = self._recurse_for_parents(self)
        return self.get_separator().join(p_list)
    _parents_repr.short_description = _('Oberkategorie(n)')
    
    def _recurse_for_children(self, cat_obj, c_list):
        c_list.append(int(cat_obj.id))
        children = Category.objects.filter(parent = cat_obj.id,
                                           active = True)
        if not children:
            return c_list
        else:
            for child in children:
                c_list = self._recurse_for_children(child, c_list)
            return c_list
    
    def get_children_by_name(self):
        c_list_id = self.get_children_by_id()
        c_list_name = []
        for c_id in c_list_id:
            child = Category.objects.get(pk = c_id)
            c_list_name.append(child.name)
        return ', '.join(c_list_name)
    
    def get_children_by_id(self):
        return self._recurse_for_children(self, list())

    def save(self):
        p_list = self._recurse_for_parents(self)
        if self.name in p_list:
            raise validators.ValidationError(_('Sie können die Kategorie '
                                               'nicht in sich selbst '
                                               'speichern'))
        super(Category, self).save()
    
    class Admin:
        list_display = ['id', '_parents_repr',
                        'name', 'active', 'cat_time']
        list_display_links = ['id', 'name']
        list_filter = ['active']
        search_fields = ['name']
    
    class Meta:
        verbose_name = _('Kategorie')
        verbose_name_plural = _('Kategorien')
        ordering = ['id']
Das Kategoriemodell (entspricht ja meiner Idee) ist dabei aus einem der Codebeispiele von der Django Seite. Auch wenn es nicht jeder ausprobieren kann, da eine Django-Installation nötig ist, wollte ich es Euch nicht vorenthalten.

Gruß, Whitie

Edit: Das Rausfinden der Unterkategorien hatte gefehlt.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Und ich hab hier was neues gemacht: http://www.python-forum.de/topic-9698.html

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