gui - datenbank (tabelle)

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
BlackJack

Wie es aussieht hast Du einen SQL-Ausdruck für jede Datenbank? Aber gerade SQL sollte doch für jede (SQL-)Datenbank gleich sein?

Oder habe ich da jetzt etwas missverstanden?
Gast

hi blackjack,

sollte, ist es aber nicht immer ganz, allerdings muss ich sagen, dass ich ausser mit mysql und gadfly keine erfahrung mit datenbanken habe.
gadfly gibts eigentlich nur in zope, hmm...

hier mal ein link, der unterschiede zwischen mysql und postgresql beschreibt, vom ersten lesen her, sollte es möglich sein mit einer api auszukommen.


http://www.pronix.de/pronix-371.html


das zitat von einer webseite (http://www.linux-magazin.de/Artikel/aus ... lebri.html)
spricht wieder nicht so dafür,
gut argumentiert, allerdings traue ich den entwicklern von mysql schon zu sich etwas bei "auto_increment" gedacht zu haben.
11/02, S. 104: Im Kasten "Unterschiede bei PostgreSQL" ist zu lesen, dass PostgreSQL keine Eigenschaft »auto_increment« kennt und man daher umständlich ein »CREATE SEQUENCE« machen muss. Viel einfacher ist es aber, den Datentyp »SERIAL« zu verwenden, der wird automatisch zu einem Ganzzahltyp mit angeschlossener Sequence.

So etwas passiert oft, wenn man sich zunächst auf MySQL bezieht und dann die Unterschiede von PostgreSQL dazu darstellt. Da PostgreSQL erstens im Gegensatz zu MySQL eine richtige Datenbank ist (ACID-Kriterien) und außerdem dem SQL-Standard weit näher ist (auch näher als Oracle), sollte jeder, der sich auch nur ein bisschen mit Datenbanken auskennt, sich eher auf PostgreSQL beziehen und dann die (vielen) Abweichungen von MySQL darstellen.
da denke ich mir dann eher wieder:wenn es dann doch wieder unterschiede gibt und wenn man auch berücksichtigt, dass datumsfunktionen anders funzen, dann entwerfe ich doch besser etwas das verschiedene apis hat, deshalb auch das skizzierte dictionary usw.

naja, und wenn der user dann erst wieder lesen muss, welche dinge er berücksichtigen muss, wenn er seine tabellen, datenbanken mit meiner gui verwenden will, ist es eigentlich nicht wirklich userfreundlich, allein die ansage: auto_increment kann nicht verwendet werden, weil.....usw. klingt für jmd. der mysql verwendet wahrscheinlich nicht verlockend.


freilich die tatsächlichen sqlanweisungen sind gleich (behaupte ich mal), aber das drumherum macht es eben schwierig.

sorry, wenn ich so viele tippe, aber ich denke so vor mich hin und tippe und denke, warum ich immer so viel denke und tippe, wenn ich schon ne flasche wein geleert habe :D



bis bald

rolgal
Gast

Hallo zusammen,

es ist schon etwas spät, aber trotzdem noch ein kleiner comment.

Das Design für den Zugriff auf eine Datenbank ist ein Endlosthema. Zu unterschiedlich sind die individuellen Anforderungen. Einmal ist die Geschwindigkeit das Hauptthema, einmal die Datenbankunabhängigkeit, einmal Zugriff über ein Webinterface etc.

Ziel der Anwendung von Rolgal ist doch die einfache Pflege von Tabellen (Anzeigen, Suchen, Löschen, Editieren, Einfügen) ohne das Erstellen von tabellenspezifischen Formularen(Fenstern). EINE Anwendung für beliebige Tabellen. Also müssen wir auf das Erzeugen der individuellen Datenbanktabellen (MySQL, PostgreSQL, Gadfly, etc. mit autoincrement, serial usw.) hier gar nicht eingehen.

Das GUI braucht doch lediglich Tabellenangaben in der Form table.getColumns, table.getItems, table.deleteItem(4711) etc.
Diese Funktionen werden von einem Datenbankmanager oder -wrapper oder einer speziellen Tabellenklasse bereitgestellt. Eben diese "client neutrale" Funktionsbibliothek muss dann durch die individuellen DBApis erweitert werden:

(GUI)client->DBManager/TableClass->DbAPI->DB

Man kann das ganze dann auch noch die Spitze treiben, in dem man den DBWrapper so schreibt, als ob man Tabellen wie Python Listen behandelt.
Bsp.:
books=Table(books,db)
for i in books:
print i

Schaut Euch hierzu mal folgenden Artikel an:
http://www.devx.com/dbzone/Article/22093/1954?pf=true

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

Dank Piddon habe ich meinen Zugang zum Forum wieder :P Danke!

Ich kämpfe gerade mit den einfachsten Dingen herum. Ich habe einen MySQL Server, auf den ich via VPN und MySQLdb nicht connecten kann (Zugriffsrechte???) und bei Postgres finde ich das MySQL Pendant "Show columns from table;" nicht.

Ich würde gerne mal die TKinter Fenster von rolgal sehen, damit ich einen Überblick über das Programm bekomme. Vielleicht gibt es ja ein paar Screenshots???

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

Also, ich hab jetzt eine "TableWrapper" Klasse, die die benötigten Funktionen für die Hauptfensterklasse bereitstellt. SQL oder sonstige Dinge sind grösstenteils schon aus der "HauptFenster" Klasse eliminiert. Mit Tk habe ich bisher noch nicht viel gemacht, wenn GUI, dann eher im CGI Bereich...

Bei der Trennung der Klassen "Hauptfenster" und "TableWrapper" lasse ich die Datenbankverbindung im Moment weg und füttere die "Hauptfenster" Klasse lediglich mit "Datenlisten", wie sie nachher bei der Datenbankabfrage via SQL erzeugt werden.

Ziel ist nachher der Zugriff auf MySQL und PostgreSQL Datenbankserver sowie auf ein XML File mit einer "datenbankähnlichen" Struktur (s.u.). Vorteil wäre, dass Interessierte das Programm verwenden können, ohne die entsprechenden DB-Server installiert zu haben ;-) .

Code: Alles auswählen

<xmldb>

 <table name="tn1">
   <row>...
   <row>...
 </table>

 <table name="tn2">
   <row>...
   <row>...
 </table>

</xmldb>
Das ganze soll die 3-Schicht Architektur verdeutlichen. Eine CGI Applikation wäre so auch schnell möglich zu erstellen.

Tabellar
Gast

hallo tabellar,

Also müssen wir auf das Erzeugen der individuellen Datenbanktabellen (MySQL, PostgreSQL, Gadfly, etc. mit autoincrement, serial usw.) hier gar nicht eingehen.
die sache mit auto_increment ist nicht nur für das erstellen von tabellen wichtig:

in meinem code gibt es eine regel (noch als einfach if-anweisung):
datenfelder die eben auto_increment aufweisen werden ignoriert beim anzeigen, beim erstellen der eingabefelder, der radiobuttons.
natürlich nicht beim schrieben von daten(!)

da postgresql das nicht kennt und ich in einer allfälligen api für versch. sqldbs mich auf einen gemeinsamen nenner einigen müsste, müsste der user drauf aufmerksam gemacht werden, dass er mysql, die auto_increment verwendet, vergessen kann.

freilich liessen sich alle spezfika einer db berücksichtigen, aber irgendwie riecht mir das nach frickeln. im sinne von elendslangen if anweisungen usw.

und wenn man da loslegt, finden sich sicherlich noch viele "auto_increments", deshalb war meine idee gleich für jede db eine eigene api, auch wenn sich viele sqlanweisungen möglicherweise wiederholen.

ich habe deinen thread in allgemeine fragen gelesen bez. "show columns..."
da ist es also schon das zweite auto_increment:d

mfg

rolgal



[/quote]
tabellar
User
Beiträge: 186
Registriert: Mittwoch 4. September 2002, 15:28

Hi Rolgal,
...in meinem code gibt es eine regel (noch als einfach if-anweisung):
datenfelder die eben auto_increment aufweisen werden ignoriert beim anzeigen, beim erstellen der eingabefelder, der radiobuttons.
natürlich nicht beim schrieben von daten(!)...
Das habe ich schon erkannt ;-) , letztendlich braucht Deine Hauptfensterklasse (HF-Klasse) ja nur die Zuweisung von "self.cols_names" bei der Instantiierung durch die Angabe einer "ColumnsList".

Diese ColumnsList erhält die HF-Klasse durch eine Anfrage bei der TableWrapper Class. Z.B. so:

>>> self.col_names=self.tablewrapper.getColumnsAsList(tablename)

Die TableWrapper Class agiert sozusagen dann als API oder "Gate" Klasse, die wiederum die individuellen DB APIs beinhaltet.
Solche APIs wären dann zb. MySQL.py, PgSQL.py, XMLdb.py, die alle die Methode "getColumnsAsList" via "import" zur Verfügung stellen mit Übergabe der entsprechenden ColumnsList, OHNE Primärschlüssel (autoincrement, serial, etc.).

Super wäre also so etwas in der Art:

HF-Class
TableWrapper Class
MySQL.py API
PgSQL.py API
XMLdb.py API

Ich werde mich im nächsten Schritt mal um die PgSQL und XML Geschichte kümmern.

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

Ich hab hier jetzt mal einen ersten Entwurf für den GUI Datenbank Client von rolgal. Das ganze ist natürlich noch nicht fertig, aber die Trennung von GUI und der Datenbank ist erfolgt. Im Moment kann ich auf PostgreSQL und XML Datenbanken (db.xml) zugreifen (Daten anzeigen und Daten neu einfügen) .

Das db-GUI greift über das TableWrapper Modul und den entsprechenden DB-DAOs Modulen (pgsqlDAO, xmlDAO - DAO=Data Access Objects) auf die Daten zu. Auf die XML Datenbankdatei wird über das interne Python Modul "MiniDOM" zugegriffen. Der dbGUI Client kann somit allein mit Python Bordmitteln genutzt werden. Ein DB-Server muss nicht installiert sein.

Durch die Trennung des GUIs von dem DAO Modul, kann jetzt die Funktionalität des GUIs einfacher verbessert werden.

Mögliche weitere Schritte sind:

GUI:
- Datenbankauswahl via Dialogfenster
- Tabellenauswahl
- Schönere Datenansicht

DBDAO:
- Implementation des mySQL DAO Moduls (ich kann nicht auf meinen Gentoo MySQL Server von extern zugreifen...)
- Implementation sqliteDAO
- Bearbeiten und Löschen Funktion

Viel Spass
Tabellar


Bild

Db GUI Client

Code: Alles auswählen

#!/usr/bin/env python
#Modul uniDbGUI - Universaler Datenbank GUI Client

from Tkinter import *
import sys
import tkMessageBox
import tableWrapper

class Fenster:


    #Abstand der Elemente
    a_x=5 #Abstand horizontal
    a_y=5 #Abstand vertikal

    #Die Buttons bekommen alle dieselbe Breite
    b=20

    #Farbe wird auch von la verwendet
    f="#00ff00"

    lt=""

   
    def __init__(self):

        raise NotImplementedError("Abstract class")

   

    def prepare(self):

        self.root.resizable(0,0)

        self.root.protocol("WM_DELETE_WINDOW",self.mainexit)

        la=Label(self.root,bg=self.f,text="///GARO_GUI")
        la.grid(row=0,column=0,           
                     columnspan=2,
                     padx=self.a_x,pady=self.a_y)
     
        la_1=Label(self.root,bg=self.f,text=self.lt)
        la_1.grid(row=1,column=0,
                       columnspan=2,
                       padx=self.a_x,pady=self.a_y)
       
        button=Button(self.root,
                           text="Prog. Beenden",
                           width=10,command=self.ende)
        button.grid(row=18,column=0,
                         columnspan=2,
                         padx=10,pady=10)



    def ende(self):

        antwort=tkMessageBox.askyesno\
                 ("Warnung","Sicher beenden?")
        if antwort==1:
            sys.exit(0)


    def mainexit(self):
       
        self.root.withdraw()


class HauptFenster(Fenster):

    lt="Ausgabe"

       
    def __init__(self):
        """,host,user,db,db_table,passwd=''"""
        self.root=Tk()
        self.root.wm_geometry('+20+20')
        self.prepare()
        self.root.title("Hauptfenster")
         
        #instantiierung DbWrapper... 
        self.tablewrapper=tableWrapper.TableWrapper()

        # Manuelle Datenbankauswahl via Angabe 
        # des Datenbanktyps: (später durch Dialogauswahl...)
        # PgSQL 
        # MySQL
        # XML
        self.tablewrapper.connect('XML','host','dbname','user')


        self.cols_names=self.tablewrapper.getTableColumnsList() 
        li_1width=len(self.cols_names)*12
        self.s_cols_names=", ".join(self.cols_names)

        scb_v=Scrollbar(self.root, orient="vertical")
        scb_h=Scrollbar(self.root, orient="horizontal")

        self.li_1=Listbox(self.root,
                          width=li_1width,height=8,
                          yscrollcommand=scb_v.set,
                          xscrollcommand=scb_h.set)
       
        scb_v["command"]=self.li_1.yview
        scb_h["command"]=self.li_1.xview
       
        self.li_1.grid(row=2,column=0,
                       columnspan=2,
                       padx=self.a_x,pady=self.a_y)
       
        scb_v.grid(row=2,column=2,
                      padx=self.a_x,pady=self.a_y)
       
        scb_h.grid(row=3,column=0,
                        columnspan=2,
                        padx=self.a_x,pady=self.a_y)
                       

        b_1=Button(self.root,width=self.b,text="Alle Daten",
                        command=lambda:
                        self.list_data(self.tablewrapper.getTableColumnsData()))
                   
        b_1.grid(row=4,column=0,
                      padx=self.a_x,pady=self.a_y)
       

        b_2=Button(self.root,width=self.b,
                        text="Loeschen",command=self.ask)
        b_2.grid(row=4,column=1,
                      padx=self.a_x,pady=self.a_y)
       
       
        b_3=Button(self.root,width=self.b,text="Sortieren",
                        command=lambda:
                        self.list_data
                        (result=self.database.get_data
                        ("select %s from %s order by %s"%(self.s_cols_names,self.db_table,choice.get()))))
        b_3.grid(row=5,column=0,
                      padx=self.a_x,pady=self.a_y)
       

        b_4=Button(self.root,width=self.b,text="Eingabe",
                        command=lambda:
                        EingabeFenster
                        (self.root,self.tablewrapper,
                         self.root.winfo_width()))

       
        b_4.grid(row=5,column=1,
                      padx=self.a_x,pady=self.a_y)


        b_5=Button(self.root,width=self.b,text="Suchen",
                        command=lambda:
                        SuchFenster
                        (self,self.root,self.database,self.db_table))
        b_5.grid(row=6,column=0,
                      padx=self.a_x,pady=self.a_y)


        b_6=Button(self.root,width=self.b,text="Bearbeiten",
                        command=lambda:
                        BearbeitungsFenster
                        (self.root,self.database,
                         self.db_table,self.li_1.get("active"),
                         self.root.winfo_width()))
        b_6.grid(row=6,column=1,padx=self.a_x,pady=self.a_y)

        
    def list_data(self,result):
       
        self.li_1.delete(0,END)
        result=[list(element) for element in result]

        for element in result:
            for i in range(len(element)):
                element[i]=str(element[i])
               
        result=["   ".join(element) for element in result]

        for elements in result:
                self.li_1.insert("end",elements)
        self.root.title("Hauptfenster-Connected...")

        #radio buttons...
        choice=StringVar()
        for i in range(len(self.cols_names)):

            rb=Radiobutton(self.root,text=self.cols_names[i],
                        value=self.cols_names[i],
                        variable=choice)
            rb.grid(row=i+7,column=1,
                         padx=self.a_x,pady=self.a_y)

            if i==0:
                rb.select()



    def get_selected(self,action):
        """muss noch gemacht werden"""

   

    def ask(self):
        """muss noch gemacht werden"""


class EingabeFenster(Fenster):

    lt="Eingabe/Bearbeitung"

   
    def __init__(self,root,tablewrapper,hfwidth):

        self.root=Toplevel(root)
        self.root.wm_geometry('%s+40'%('+'+str(hfwidth+80)))
        self.root.title("Eingabefenster")
        self.prepare()
        self.tablewrapper=tablewrapper
        self.cols_names=self.tablewrapper.getTableColumnsList() 
        self.s_cols_names=", ".join(self.cols_names)
        self.entrylist=[]

        for i in range(len(self.cols_names)):
            label=Label(self.root,text=self.cols_names[i])
            label.grid(row=i+2,column=0)

            entry=Entry(self.root)
            entry.grid(row=i+2,column=1,padx=self.a_x,pady=self.a_y)
            self.entrylist.append(entry)

        b_1=Button(self.root,text="Eintragen",command=self.printEntries)
        b_1.grid(row=(len(self.cols_names)+2),column=2,padx=self.a_x,pady=self.a_y)
        
    def printEntries(self):
        """---"""
        entryTextList=[]
        for element in self.entrylist:
            entryTextList.append(element.get())

        for element in self.entrylist:
            element.delete(0,"end")

        print self.cols_names
        print entryTextList
        self.tablewrapper.insertTableColumnsData(self.cols_names,entryTextList)

        
if __name__=="__main__":
   hf = HauptFenster()
   mainloop() 
tableWrapper Modul:

Code: Alles auswählen

#!/usr/bin/env python
#Modul tableWrapper

import pgsqlDAO
import mysqlDAO
import xmlDAO


class TableWrapper:
    """dbwrapper"""
    def __init__(self):
        """init"""

    def connect(self,DbType,Host,Db,User):

        if DbType=='PgSQL':
           self.dbWrapper=pgsqlDAO.PgSqlDAO()
           self.dbWrapper.connect(Host,Db,User)

        if DbType=='MySQL':
           self.dbWrapper=mysqlDAO.MySqlDAO()
           self.dbWrapper.connect(Host,Db,User)

        if DbType=='XML':
           self.dbWrapper=xmlDAO.XmlDAO()
           
    def getTableNameList(self):
        return self.dbWrapper.getTableNameList()

    def getTableColumnsList(self):
        return self.dbWrapper.getTableColumnsList()
    
    def getTableColumnsData(self):
        return self.dbWrapper.getTableColumnsData()

    def insertTableColumnsData(self,colList,valueList):
        self.dbWrapper.insertTableColumnsData(colList,valueList)
        
    def close(self):
        self.dbWrapper.close()
pgsqlDAO Modul:

Code: Alles auswählen

#!/usr/bin/env python
#Modul pgsqlDAO

#PostgreSQL WIN dbAPI
import pyPgSQL.PgSQL

class PgSqlDAO:
    def __init__(self):
        """..."""
        
    def connect(self,Host,Db,User):
        self.con=pyPgSQL.PgSQL.connect(host=Host,database=Db,user=User)            
        

    def getTableNameList(self):
        cur=self.con.cursor()
        sql="""SELECT tablename
               FROM pg_tables
               WHERE tablename !~* 'pg_*' and tablename !~* 'sql_*';"""
        cur.execute(sql)
        pgresult=cur.fetchall()
        tableNameList=[]
        for i in pgresult:
            tableNameList.append(i[0])
                
        return tableNameList

    def getTableColumnsList(self):
        cur=self.con.cursor()
        sql="""SELECT a.attnum, a.attname AS field, t.typname AS type,
               a.attlen AS length, a.atttypmod AS length_var,
               a.attnotnull AS not_null, a.atthasdef as has_default
               FROM pg_class c, pg_attribute a, pg_type t
               WHERE c.relname = 'tfoo'
               AND a.attnum > 0
               AND a.attrelid = c.oid
               AND a.atttypid = t.oid
               ORDER BY a.attnum;"""
        cur.execute(sql)
        pgresult=cur.fetchall()
        tableColList=[]
        for i in pgresult:
            tableColList.append(i[1])
        return tableColList

    def getTableColumnsData(self):
        cur=self.con.cursor()
        sql="""SELECT * from tfoo;"""
        cur.execute(sql)
        pgresult=cur.fetchall()
               
        return pgresult

    def insertTableColumnsData(self,colList,valueList):
        cur=self.con.cursor()        
        columns = colList
        cols = ", ".join(columns)
        value_placeholders = ", ".join(["%s"] * len(columns))
        values = valueList
        sql = """insert into tfoo (%(cols)s)
                 values (%(value_placeholders)s)""" % locals()
        cur.execute(sql, values)
        self.con.commit()

    def close():
        self.con.close()

xmlDAO Modul:

Code: Alles auswählen

#!/usr/bin/env python
#Modul xmlDAO

#Pythoninternes xmlAPI
import xml.dom.minidom

class XmlDAO:
    def __init__(self):
        """ --- """
        self.filename="db.xml"
        self.doc = xml.dom.minidom.parse(self.filename)              

        tables=[]
        mapping = {}

    def getTableNameList(self):
        tableNameList=[]
        for i in self.doc.getElementsByTagName("table"):
            name=i.getAttribute("name")
            tableNameList.append(name)
        return tableNameList

    def getTableColumnsList(self):
        for table in self.doc.getElementsByTagName("table"):
            if table.getAttribute("name")=='t1':
               rows=table.getElementsByTagName("row")
               if len(rows):
                  colList=[]
                  for col in rows[0].childNodes:
                      if col.nodeType==col.ELEMENT_NODE:
                         colList.append(col.tagName)
        return colList 


    def getTableColumnsData(self):  
        for table in self.doc.getElementsByTagName("table"):
            if table.getAttribute("name")=='t1':
               rows=table.getElementsByTagName("row")
               colList=self.getTableColumnsList()
               datalist=[]

               for i in range(len(rows)):
                   list=[]
                   datalist.append(list)

               rowid=-1
               for row in rows:
                   rowid=rowid+1

                   for name in colList:
                       for item in row.getElementsByTagName(name):
                           item.normalize()
                           datalist[rowid].append(item.firstChild.data)

        return datalist


    def insertTableColumnsData(self,colList,valueList):

        xmlstr='<row>'
        for i in range(len(colList)):
            xmlstr = xmlstr + "<" + colList[i] + ">" + valueList[i] + "</" + colList[i] + ">"
        rowxml = xmlstr + "</row>\n"

        rowMiniDOM=xml.dom.minidom.parseString(rowxml)
        rowDOM=rowMiniDOM.documentElement                        

        for table in self.doc.getElementsByTagName("table"):
            if table.getAttribute("name")=='t1':
               table.appendChild(rowDOM)

        #xml_save
        xmlfile=open(self.filename,'w')
        xmlfile.write(self.doc.toxml())
        xmlfile.close()
XML Datenbankdatei db.xml :

Code: Alles auswählen

<?xml version="1.0" ?>
<xmldb>

  <table name="t1">
    <row><id>101</id><username>rolgal</username><usertype>Poweruser</usertype></row>
    <row><id>102</id><username>Leonidas</username><usertype>Poweruser</usertype></row>
    <row><id>103</id><username>Olliminatore</username><usertype>User</usertype></row>
    <row><id>104</id><username>tabellar</username><usertype>User</usertype></row>   
  </table>

  <table name="t2">
    <row><titel>Python und XML</titel><author>xyz</author></row>
  </table>

 </xmldb>
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Somit hast du die GUI von der Datenbank entkoppelt.. also man könnte auch andere GUIs machen. Nett :)
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Gast

hallo tabellar,

sieht ja echt gut aus,....bin platt :D

ich krieg leider keine benachrichtigungen, wenn zu diesem thread antworten geschrieben werden, war also glücklicher zufall, dein post gesehen zu haben, keine ahnung was da los ist.

grüße

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

rolgal hat geschrieben:hallo tabellar,
sieht ja echt gut aus,....bin platt :D
Schön, wenn es Dir gefällt. Hättest Du nicht Lust die "mysqlDAO" zu machen? Ich habe leider keinen Zugriff von meinem WIN Notey auf meinen Gentoo Linux Rechner, auf dem MySQL aber wunderbar läuft. Auch Rechte setzen bringt nichts, hm, keine Ahnung warum der connect auf den MySQL Server von externen Rechnern nicht funktioniert :cry: .

Gruss Tabellar
Gast

hi tabellar,

ich werde gern das mysqlDAO machen,

bis bald oder auch etwas länger wenn es fuchst :D

rolgal
Gast

hallo tabellar,


also ich habe mich soweit ich konnte mit deinem code auseinandergesetzt und mir ist da einiges aufgefallen, was wir besprechen sollten,....

1. tablewrapper: import anweisungen würde ich hier machen:


Code: Alles auswählen

        if DbType=='MySQL':
           import mysqlDAO
           self.dbWrapper=mysqlDAO.MySqlDAO()
           self.dbWrapper.connect(Host,User,Db,Passwd)
es ist nicht so sinnvoll alle schnittstellen automatisch zu importieren.


2. db_client, es braucht meiner meinung nach auf jeden fall auch die ermitllung der aktuellen tabelle, sonst wird es schwierig die daten auszugeben, in deinem beispiel geht das nur weil du eine konstante verwendest:

Code: Alles auswählen

    def getTableColumnsData(self):
        cur=self.con.cursor()
        sql="""SELECT * from tfoo;"""
        cur.execute(sql)
        pgresult=cur.fetchall()
tfoo ist gemeint

daher:

Code: Alles auswählen

    def __init__(self):
        """,host,user,db,db_table,passwd=''"""
        self.root=Tk()
        self.root.wm_geometry('+20+20')
        self.prepare()
        self.root.title("Hauptfenster")
         
        #instantiierung DbWrapper...
        self.tablewrapper=tableWrapper.TableWrapper()

        # Manuelle Datenbankauswahl via Angabe
        # des Datenbanktyps: (später durch Dialogauswahl...)
        # PgSQL
        # MySQL
        # XML

        self.tablewrapper.connect('MySQL','localhost','ein user','eine db','ein passwort')

        #tabellen ermitteln
        self.db_tables=self.tablewrapper.getTableNameList()
        #erste tabelle als default verwenden, später kann diese variable ueber das auswahlfenster neu gesetzt werden
        self.db_table=self.db_tables[0]

        self.cols_names=self.tablewrapper.getTableColumnsList(self.db_table)

die tabelle self.db_table muss für diverse anweisungen übergeben werden.

mein nicht fertiges mysqlDAO sieht momentan so aus:

Code: Alles auswählen

import MySQLdb
from _mysql_exceptions import *


class MySqlDAO:

    def __init__(self):
        """..."""


    def connect(self,host,user,db,passwd=''):

        self.db=db
        
        try:
            self.conn=MySQLdb.connect(host=host,
                                      user=user,
                                      db=db,
                                      passwd=passwd)
             
        except OperationalError, msg:
            print msg[1]


    def getTableNameList(self):
        cur=self.conn.cursor()
        cur.execute("show tables from %s"%self.db)
        result=cur.fetchall()
        tableNameList=[]
        for i in result:
            tableNameList.append(i[0])
        return tableNameList
    
            

    def getTableColumnsList(self,db_table):
        cur=self.conn.cursor()
        cur.execute("show columns from %s"%db_table)
        result=cur.fetchall()
        tableColList=[]
        for i in result:
            if 'auto_increment' not in i:
                tableColList.append(i[0])
        return tableColList
         
    
    def getTableColumnsData(self,action,db_table):

        action=action%db_table
        cursor=self.conn.cursor()
        cursor.execute(action)
        result=cursor.fetchall()
        cursor.close()
        return result


    def insertTableColumnsData(self,action):

        cursor=self.conn.cursor()
        cursor.execute(action)
        cursor.close()
 

    def close(self):
        
        self.conn.close()
prinzipiell arbeite ich ja auch noch an der alten fassung, die bereits tabellen wechseln kann, es scheint sinnvoll den db_client, das hauptfenster so zu gestalten:

Code: Alles auswählen

class HauptFenster(Fenster):

    lt="Ausgabe"

       
    def __init__(self,host,user,db,passwd=''):

        self.root=Tk()

        self.root.wm_geometry('+20+20')

        self.prepare()

        self.root.title("Hauptfenster")
         
        self.database=MySqlApi(host,user,db,passwd)

        if self.database.table_number>1:
            auswahl=AuswahlFenster(self,self.root,self.database.db_tables)

        
       
        self.choice=StringVar()

        self.db_table=self.database.db_table
        self.db_table_cols=self.database.db_table
        self.cols_names=self.database.cols_names

        self.s_cols_names=", ".join(self.cols_names)

        self.li_1width=len(self.cols_names)*12
        
       # das ist es um was es geht, frames, die spaeter einzeln neu gesetzt werden koennen.
        self.frame_1=Frame(self.root)
        self.frame_1.grid(row=2,column=0,columnspan=2)
        self.set_frame_1()

        self.frame_2=Frame(self.root)
        self.frame_2.grid(row=5,column=0,columnspan=2)
        self.set_frame_2()

        self.frame_3=Frame(self.root)
        self.frame_3.grid(row=9,column=1)
        self.set_frame_3()



    def set_frame_1(self):

        self.scb_v=Scrollbar(self.frame_1, orient="vertical")
        self.scb_h=Scrollbar(self.frame_1, orient="horizontal")

        self.li_1=Listbox(self.frame_1,
                          width=self.li_1width,height=8,
                          yscrollcommand=self.scb_v.set,
                          xscrollcommand=self.scb_h.set)

      
        self.scb_v["command"]=self.li_1.yview
        self.scb_h["command"]=self.li_1.xview
       
        self.li_1.grid(row=3,column=0,
                       columnspan=2,
                       padx=self.a_x,pady=self.a_y)
       
        self.scb_v.grid(row=3,column=2,
                      padx=self.a_x,pady=self.a_y)
       
        self.scb_h.grid(row=4,column=0,
                        columnspan=2,
                        padx=self.a_x,pady=self.a_y)

        
        
    def set_frame_2(self):

        b_1=Button(self.frame_2,width=self.b,text="Alle Daten",
                        command=lambda:
                        self.list_data
                        (result=self.database.get_data
                        ("select %s from %s"%(self.s_cols_names,self.db_table))))
        b_1.grid(row=6,column=0,
                      padx=self.a_x,pady=self.a_y)
       

        b_2=Button(self.frame_2,width=self.b,
                        text="Loeschen",command=self.ask)
        b_2.grid(row=6,column=1,
                      padx=self.a_x,pady=self.a_y)
       
       
        b_3=Button(self.frame_2,width=self.b,text="Sortieren",
                        command=lambda:
                        self.list_data
                        (result=self.database.get_data
                        ("select %s from %s order by %s"%(self.s_cols_names,self.db_table,self.choice.get()))))
        b_3.grid(row=7,column=0,
                      padx=self.a_x,pady=self.a_y)
       

        b_4=Button(self.frame_2,width=self.b,text="Eingabe",
                        command=lambda:
                        EingabeFenster
                        (self.frame_2,self.database,
                         self.db_table,self.cols_names,
                         self.root.winfo_width()))
        b_4.grid(row=7,column=1,
                      padx=self.a_x,pady=self.a_y)


        b_5=Button(self.frame_2,width=self.b,text="Suchen",
                        command=lambda:
                        SuchFenster
                        (self,self.root,self.database,self.db_table))
        b_5.grid(row=8,column=0,
                      padx=self.a_x,pady=self.a_y)


        b_6=Button(self.frame_2,width=self.b,text="Bearbeiten",
                        command=lambda:
                        BearbeitungsFenster
                        (self.root,self.database,
                         self.db_table,self.cols_names,
                         self.li_1.get("active"),
                         self.root.winfo_width()))
        b_6.grid(row=8,column=1,padx=self.a_x,pady=self.a_y)

        

    def set_frame_3(self):


        for i in range(len(self.cols_names)):

            self.rb=Radiobutton(self.frame_3,text=self.cols_names[i],
                        value=self.cols_names[i],
                        variable=self.choice)
            self.rb.grid(row=i+9,column=1,
                         padx=self.a_x,pady=self.a_y)
         

            if i==0:
                self.rb.select()

        self.list_data(result=self.database.get_data("select %s from %s"%(self.s_cols_names,self.db_table)))

#usw.

auch habe ich mir ueberlegt gewisse dinge vielleicht doch im konstruktor zu setzen, z.b. im tablewrapper den datenbanktyp

das mit der übergabe von attributen ist net so einfach, will man dein ziel umsetzen einen uni db client zu haben.

Code: Alles auswählen

        b_1=Button(self.root,width=self.b,text="Alle Daten",
                        command=lambda:
                        self.list_data(self.tablewrapper.getTableColumnsData(action,self.db_table))
die auszuführende anweisung (action) und die betreffende tabelle (self.db_table) müssen hier übergeben werden, aber woher nehmen???

die tabelle ist klar, aber die action, da hirne ich noch

so, das waren mal die wichtigsten überlegungen für den moment, ich mach mal weiter,

bis bald,

wollte einfach auch mal ein lebenszeichen geben,

:D

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

Hi Rolgal,
schön, dass es auch hier ein wenig weiter geht... ;-)
rolgal hat geschrieben:1. tablewrapper: import anweisungen würde ich hier machen:

Code: Alles auswählen

        if DbType=='MySQL':
           import mysqlDAO
           self.dbWrapper=mysqlDAO.MySqlDAO()
           self.dbWrapper.connect(Host,User,Db,Passwd)
es ist nicht so sinnvoll alle schnittstellen automatisch zu importieren.
Klar, das ist natürlich so besser.

rolgal hat geschrieben:2. db_client, es braucht meiner meinung nach auf jeden fall auch die ermitllung der aktuellen tabelle, sonst wird es schwierig die daten auszugeben, in deinem beispiel geht das nur weil du eine konstante verwendest:

Code: Alles auswählen

    def getTableColumnsData(self):
        cur=self.con.cursor()
        sql="""SELECT * from tfoo;"""
        cur.execute(sql)
        pgresult=cur.fetchall()
tfoo ist gemeint

daher:

Code: Alles auswählen

...
        self.db_tables=self.tablewrapper.getTableNameList()
        #erste tabelle als default verwenden, später kann diese variable ueber das auswahlfenster neu gesetzt werden
        self.db_table=self.db_tables[0]
---
die tabelle self.db_table muss für diverse anweisungen übergeben werden.
In der TableWrapper Klasse war die Methode "getTableNameList()" ja schon vorgesehen und implementiert. Ich hab den Tabellennamen einfach fix verdrahtet, weil der Auswahldialog dazu noch nicht da war. Die Lösung mit "self.db_tables[0] ist natürlich gleich viel eleganter flexibler.
rolgal hat geschrieben:mein nicht fertiges mysqlDAO sieht momentan so aus:

Code: Alles auswählen

import MySQLdb
from _mysql_exceptions import *


class MySqlDAO:

    def __init__(self):
        """..."""
    def connect(self,host,user,db,passwd=''):
...
Super, wenn die MySqlDAO bald fertig ist. Dann ist eine reine GUI Entwicklung möglich... Hast Du vielleicht schon einen Screenshot von deiner neuen GUI (Tabellenauswahl) ?

GUI Ziele:
- DB Auswahl
- Connect
- Tabellenauswahl
- Ansichtsfenster in Tabellenform

rolgal hat geschrieben:so, das waren mal die wichtigsten überlegungen für den moment, ich mach mal weiter, bis bald, wollte einfach auch mal ein lebenszeichen geben, :D
Schön, mich freut es, wenn es hier so ein paar Projekte gibt... Meine Augen sind auf jeden Fall auch in Deinem Projekt ... :wink:

Gruss Tabellar
Gast

für folgendes problem nachstehender lösungsvorschlag, müsste man aber dann in allen DAOs so machen
das mit der übergabe von attributen ist net so einfach, will man dein ziel umsetzen einen uni db client zu haben.

Code: Alles auswählen


        b_1=Button(self.root,width=self.b,text="Alle Daten",
                        command=lambda:
                        self.list_data(self.tablewrapper.getTableColumnsData(action,self.db_table))

die auszuführende anweisung (action) und die betreffende tabelle (self.db_table) müssen hier übergeben werden, aber woher nehmen???

im konstruktor des mysqlDAO folgendes dictionary:


Code: Alles auswählen

    def __init__(self):

        self.actions={"all":"select * from %s","sort":"select %s from %s order by %s"}

die methode getTableColumnsData:

Code: Alles auswählen

    def getTableColumnsData(self,a_type,*values):

        action=self.actions[a_type]
        for element in values:
            action=action%element
            
        cursor=self.conn.cursor()
        cursor.execute(action)
        result=cursor.fetchall()
        cursor.close()
        return result
im db_client wären also die folgende änderungen notwendig:

Code: Alles auswählen

        b_1=Button(self.root,width=self.b,text="Alle Daten",
                   command=lambda:
                   self.list_data(self.tablewrapper.getTableColumnsData("all",(self.db_table))))
      
        b_1.grid(row=4,column=0,
                      padx=self.a_x,pady=self.a_y)
       
       #...b_2 interessiert jetzt keinen
       
        b_3=Button(self.root,width=self.b,text="Sortieren",
                   command=lambda:
                   self.list_data(self.tablewrapper.getTableColumnsData("sort",(self.s_cols_names,
                                                                                self.db_table,
                                                                                self.choice.get()))))
        b_3.grid(row=5,column=0,
                      padx=self.a_x,pady=self.a_y)
funktionieren tut es :D

ausserdem brauchen wir die spaltennamen 2mal, einmal für die gui, dem frontend und einmal für dateioperationen,
ich will keine id's u.ä. in der gui, schon gar nicht wenn sie automatisch hochgezählt werden, gerade für das bearbeitungsfenster, eingabefenster ist das sonst ein problem

mfg

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

Hi Rolgal,

ich hab auch ein bisschen weiter gemacht... :

Code: Alles auswählen

class TableWrapper:
    """dbwrapper"""
    def __init__(self):
        """init"""

    def connect(self,DbType,Host,Db,User):

        if DbType=='PgSQL':
           import pgsqlDAO 
           self.dbWrapper=pgsqlDAO.PgSqlDAO()
           self.dbWrapper.connect(Host,Db,User)

        if DbType=='MySQL':
           import mysqlDAO 
           self.dbWrapper=mysqlDAO.MySqlDAO()
           self.dbWrapper.connect(Host,Db,User)

        if DbType=='XML':
           import xmlDAO
           self.dbWrapper=xmlDAO.XmlDAO()
           self.dbWrapper.connect(Host,Db,User)           
           
    def getTableNameList(self):
        return self.dbWrapper.getTableNameList()

    def getTableColumnsList(self,tableName):
        return self.dbWrapper.getTableColumnsList(tableName)
    
    def getTableColumnsData(self,tableName):
        return self.dbWrapper.getTableColumnsData(tableName)

    def insertTableColumnsData(self,tableName,colList,valueList):
        self.dbWrapper.insertTableColumnsData(tableName,colList,valueList)
        
    def close(self):
        self.dbWrapper.close()
Du siehst, ich habe die "import" Anweisung in die if-condition gesteckt. Bei den Methoden siehst du jetzt ausserdem, dass überall entsprechende Argumente (tableName, etc. ) übergeben werden können. Sprich, keine "fixe" Tabellenverdrahtung mehr.

Code: Alles auswählen

        #Instantiierung DbWrapper... 
        self.tablewrapper=tableWrapper.TableWrapper()

        #Auswahl der Datenbank
        self.tablewrapper.connect('XML','10.2.0.1','db.xml','userName')
        #self.tablewrapper.connect('PgSQL','10.2.0.1','dbname','useName')

        #Festlegung der Tabelle 
        self.db_tables=self.tablewrapper.getTableNameList()
        self.db_table=self.db_tables[0]
        self.cols_names=self.tablewrapper.getTableColumnsList(self.db_table)
Hier ist der code für die "Datenbank- und Tabellenauswahl" im Konstruktor der "Hauptfenster" Klasse. Die Auswahl der XML DB ist jetzt auch dynamisch (allerdings hat sich die xmlDAO auch entspr. verändert).
rolgal hat geschrieben:<snip> ... im konstruktor des mysqlDAO folgendes dictionary:

Code: Alles auswählen

    def __init__(self):

        self.actions={"all":"select * from %s","sort":"select %s from %s order by %s"}
</snip>

Hm, ich bin nicht so der Freund von einzelnen SQL Commands in Dictionarys etc. Ich finde, dass die Übersichtlichkeit im Programm verloren geht. Ich persönlich finde es schöner, wenn sich die Funktionalität in den einzelnen Methodenaufrufen wiederspiegelt. Ausserdem haben wir bei der XML DB keine SQL Commands. Also wäre hier das Pendant zu deinem SQL-Dict eine neue Methode in der TableWrapper Class:

Code: Alles auswählen

def getTableColumnsDataSort(self,tableName,colNames,orderChoice):
        return self.dbWrapper.getTableColumnsDataSort(tableName,colNames,orderChoice)
Ich persönlich würde diese Form vorziehen, zumal es auch eine Art "Black Box" darstellt. ich warte aber mal, was du dazu meinst ... :wink: Ein nächster schöner Schritt wäre es, im DB-Client per Dialog die entsprechende Datenbank und die Tabellen auszuwählen...

Gruss Tabellar

PS: Vielleicht können wir dann bald den DB-Client für den "SQLator" verwenden ... :wink:
Gast

hallo tabellar,


Hm, ich bin nicht so der Freund von einzelnen SQL Commands in Dictionarys etc. Ich finde, dass die Übersichtlichkeit im Programm verloren geht.
man könnte das dictionary in eine datei auslagern und über pickle laden, irgendwie gefällt es mir besser wenn es für alle funktionen, die schlussendlich dasselbe tun, also z.b. lesen, es eine methode gibt. ich muss dir aber ehrlich sagen, dass das eine bauchsache ist. ich kann das nicht wirklich argumentieren.

wäre interessant zu wissen, was blackjack, leonidas u.a. python-gurus dazu meinen.
Ich persönlich würde diese Form vorziehen, ich warte aber mal, was du dazu meinst ... Wink Ein nächster schöner Schritt wäre es, im DB-Client per Dialog die entsprechende Datenbank und die Tabellen auszuwählen...
ich möchte zuerst wieder die ursprgl. funktionalität herstellen.
davor sollten wir uns einigen wie wir obigen punkt lösen, dann häng ich mich wieder in die tasten, zeitlich wird es bei mir nämlich bald wieder besser :D

liebe grüße

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

Hi rolgal,

ich hätte zu dem Thema mal folgenden guten Link. und hier auch noch.

Thema hier ist die parametrisierte Form von cursor.execute und die Vermeidung von "SQL-Injection-Angriffen" (quoten der Input Werte durch den Escape-Mechanismus der DB-API). Solange wir diese Dinge bei unseren SQL Commands der Form

Code: Alles auswählen

"select %s from %s order by %s"
beachten, sind wir in der schlussendlichen Implementierung frei.

rolgal hat geschrieben:...man könnte das dictionary in eine datei auslagern und über pickle laden, irgendwie gefällt es mir besser wenn es für alle funktionen, die schlussendlich dasselbe tun, also z.b. lesen, es eine methode gibt. ich muss dir aber ehrlich sagen, dass das eine bauchsache ist. ich kann das nicht wirklich argumentieren.

wäre interessant zu wissen, was blackjack, leonidas u.a. python-gurus dazu meinen.
Ja, gute Idee, vielleicht meldet sich ja der ein oder andere dazu ... :wink: ?
rolgal hat geschrieben:ich möchte zuerst wieder die ursprgl. funktionalität herstellen. davor sollten wir uns einigen wie wir obigen punkt lösen, dann häng ich mich wieder in die tasten, zeitlich wird es bei mir nämlich bald wieder besser :D
Gut. Eine gemeinsame Lösung, die auch noch OPTIMAL im Sinne der DB Abfrage ist, wäre natürlich der Hit.... :P Das sollten wir doch schaffen, oder :wink: ???

Gruassle Tabellar
Gast

hi tabellar,
ich hätte zu dem Thema mal folgenden guten Link. und hier auch noch.

Thema hier ist die parametrisierte Form von cursor.execute und die Vermeidung von "SQL-Injection-Angriffen" (quoten der Input Werte durch den Escape-Mechanismus der DB-API). Solange wir diese Dinge bei unseren SQL Commands der Form
Python-Code:
"select %s from %s order by %s"
beachten, sind wir in der schlussendlichen Implementierung frei.

das heisst nachstehende funktion passt so net, weil es nicht im execute geschieht? ganz blick ich bei der problematik net durch, habe aber in der eile die links nur überflogen.
kannst mir mal schnell in 2, 3 sätzen das verdeutlichen?

Code: Alles auswählen


    def getTableColumnsData(self,a_type,*values):

        action=self.actions[a_type]
        for element in values:
            action=action%element
           
        cursor=self.conn.cursor()
        cursor.execute(action)
        result=cursor.fetchall()
        cursor.close()
        return result 
Gut. Eine gemeinsame Lösung, die auch noch OPTIMAL im Sinne der DB Abfrage ist, wäre natürlich der Hit.... Razz Das sollten wir doch schaffen, oder Wink ???
ein freund sagte in einer meiner krisen mal zu mir: aufgeben tut man briefe...

seit dem halte ich mich dran :D

abgesehen davon gibts hier keine krise, sondern ein großes teil, das halt nur in kleinen schritten zusammengesetzt werden kann


grüße

rolgal
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

tabellar hat geschrieben:Thema hier ist die parametrisierte Form von cursor.execute und die Vermeidung von "SQL-Injection-Angriffen" (quoten der Input Werte durch den Escape-Mechanismus der DB-API). Solange wir diese Dinge bei unseren SQL Commands der Form

Code: Alles auswählen

"select %s from %s order by %s"
beachten, sind wir in der schlussendlichen Implementierung frei.
Solange die DB Module pyformat nutzen, wenn es aber ein anderer paramstyle ist könnte das wiederrum Probleme bereiten.
tabellar hat geschrieben:
rolgal hat geschrieben:wäre interessant zu wissen, was blackjack, leonidas u.a. python-gurus dazu meinen.
Ja, gute Idee, vielleicht meldet sich ja der ein oder andere dazu ... :wink: ?
Ist schon nett als Guru bezeichnet zu werden, jedoch bin ich mit Datenbanken nicht so vertraut, also solltet ihr da besser auf die Meinung von BlackJack warten, bevor ich hier irgendwelchen Schrott erzähle.
Ich persönlich würde aber zur "Black Box"-Variante von Tabellar tendieren, schaut mir irgendwie "sauberer" aus (obwohl die Funktionsnamen etwas kürzer sein dürften *g*).
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Antworten