You are on page 1of 18

4.

Database Abstraction Layer (DAL)


Το web2py έρχεται εφοδιασμένο με ένα σύστημα αναπαράστασης των δεδομένων που αποκαλείται
Αφαιρετικό Επίπεδο Βάσης Δεδομένων (καλά, πολύ κούβεντος αυτό!!!), στα ελληνικά Database
Abstraction Layer (από εδώ και στο εξής DAL).

Το DAL παράγει αυτόματα και δυναμικά κώδικα SQL σε πραγματικό χρόνο χρησιμοποιώντας την
κατάλληλη διάλεκτο για τον επιλεγμένο τύπο βάσης δεδομένων κάθε φορά. Αυτό σημαίνει ότι μια
εφαρμογή που γράφουμε έχοντας στο νου μας ότι θα αντλούμε δεδομένα από Oracle για
παράδειγμα, είναι δυνατόν με την αλλαγή του αντικειμένου DAL και μόνο να συνδεθεί με MySQL
ή PostgreSQL ή Ingres κ.λπ. χωρίς να χρειάζεται περαιτέρω τροποποίηση στον κώδικα. Εκτός
αυτού, προσφέρει επίσης το πλεονέκτημα να μη χρειάζεται να μαθαίνουμε τις πλείστες όσες
διαλέκτους βάσεων δεδομένων που υπάρχουν, αλλά να επικεντρωθούμε στην εκμάθηση του DAL
και μόνο. Από εκεί και πέρα αφήνουμε το web2py να «συνεννοηθεί» με τη βάση.

Από προεπιλογή το DAL συνδέεται σε μια ενσωματωμένη βάση δεδομένων, την SQLite,
ουσιαστικά ένα text αρχείο τοπικά στον υπολογιστή μας (κατάλογος databases), η οποία είναι μεν
σχεσιακή, αλλά με πολύ περιορισμένες δυνατότητες. Η SQLite χρησιμοποιείται κυρίως σε επίπεδο
ανάπτυξης της εφαρμογής μας. Όταν είμαστε ικανοποιημένοι με το αποτέλεσμα μπορούμε πλέον
να συνδεθούμε σε μια πιο σοβαρή βάση. Το web2py συνεπώς μπορεί να συνδεθεί σε πολλές ΒΔ και
να εφαρμόσει τον ίδιο κώδικα υλοποιώντας την ίδια εφαρμογή. Αυτό το επιτυγχάνει μέσω
συγκεκριμένων οδηγών (drivers) εξειδικευμένων για κάθε βάση. Εφόσον τρέχουμε το web2py από
πηγαίο κώδικα, πρέπει απαραίτητα να έχουμε εγκαταστήσει τον αντίστοιχο οδηγό στην Python του
συστήματός μας πριν καταφέρουμε να συνδεθούμε με τη βάση δεδομένων της αρεσκείας μας.
Ακολουθεί πλήρης κατάλογος των απαραίτητων οδηγών:

Βάση Δεδομένων Οδηγός (source)


SQLite sqlite3, pysqlite2 zxJDBC (on Jython)
PostgreSQL psycopg2 [psycopg2] , pg8000 [pg8000] , zxJDBC [zxjdbc] (on Jython)
MySQL pymysql [pymysql] , MySQLdb [mysqldb]
Oracle cx_Oracle [cxoracle]
MSSQL pyodbc [pyodbc]
FireBird kinterbasdb [kinterbasdb] , fdb , pyodbc
DB2 pyodbc [pyodbc]
Informix informixdb [informixdb]
Ingres ingresdbi [ingresdbi]
Cubrid cubriddb [cubridb] [cubridb]
Sybase Sybase [Sybase]
Teradata pyodbc [Teradata]
SAPDB sapdb [SAPDB]
MongoDB pymongo [pymongo]
IMAP imaplib [IMAP]

Οι οδηγοί αυτοί είναι ελεύθερου λογισμικού. Μπορείτε να εγκαταστήσετε τον οδηγό για τη βάση
της αρεσκείας σας ευκολότατα από το Διαδίκτυο.

Από τη στιγμή που το θέμα των οδηγών έχει τακτοποιηθεί είμαστε έτοιμοι πλέον να φτιάξουμε την
πρώτη μας σύνδεση σε μια βάση δεδομένων. Προς το παρόν θα αρκεστούμε στην SQLite. Πρώτα
όμως, ας δημιουργήσουμε μια καινούρια εφαρμογή
Άσκηση: Δημιουργήστε μια καινούρια εφαρμογή με την ονομασία services. Μετατρέψτε την
εφαρμογή σας, έτσι ώστε κάθε σελίδα της να έχει από προεπιλογή μια συγκεκριμένη
επικεφαλίδα (π.χ. την ονομασία της Διεύθυνσής σας) και υποσέλιδο (π.χ. την
ονομασία του Τμήματός σας και την τρέχουσα χρονολογία ή ώρα κ.λπ.),
χρησιμοποιώντας κατά την κρίση σας μέγεθος γραμματοσειράς και χρώμα.
(Υπόδειξη: Για να λύσετε την άσκηση επαναλάβετε το Κεφάλαιο 1 παράγραφος 1.3. Η
άσκηση είναι παραλλαγή της αντίστοιχης άσκησης της παραγράφου αυτής, αλλά εδώ
μιλάμε για μια οριζόντια αλλαγή που θα εφαρμόζεται αυτόματα σε κάθε νέα σελίδα
της εφαρμογής).

Απάντηση:

4.1. Μεταβλητές σύνδεσης

Σύνδεση στη βάση δεδομένων επιτυγχάνεται δημιουργώντας ένα στιγμιότυπο (instance) του
αντικειμένου DAL:

>>> db = DAL('sqlite://storage.db')

Το db δεν είναι λέξη-κλειδί της γλώσσας. Είναι απλά το όνομα μιας μεταβλητής η οποία
αποθηκεύει το αντικείμενο DAL και όλες του τις μεθόδους. Θα μπορούσαμε κάλλιστα να το
έχουμε ονομάσει gandalf για παράδειγμα, χωρίς να αλλάξει η λειτουργικότητα. Η μεταβλητή db
πλέον εξαρτάται από τη συγκεκριμένη βάση δεδομένων. Στον ακόλουθο πίνακα θα βρείτε οδηγούς
σύνδεσης για όλες τις υποστηριζόμενες βάσεις (σε όλες τις περιπτώσεις θεωρούμε ότι η ΒΔ τρέχει
στο localhost στην προεπιλεγμένη θύρα και ονομάζεται "test"):

SQLite sqlite://storage.db
MySQL mysql://username:password@localhost/test
PostgreSQL postgres://username:password@localhost/test
MSSQL mssql://username:password@localhost/test
FireBird firebird://username:password@localhost/test
Oracle oracle://username/password@test
DB2 db2://username:password@test
Ingres ingres://username:password@localhost/test
Sybase sybase://username:password@localhost/test
Informix informix://username:password@test
Teradata teradata://DSN=dsn;UID=user;PWD=pass;DATABASE=name
Cubrid cubrid://username:password@localhost/test
SAPDB sapdb://username:password@localhost/test
IMAP imap://user:password@server:port
MongoDB mongodb://username:password@localhost/test
Google/SQL google:sql
Google/NoSQL google:datastore

Στην περίπτωση της SQLite η ΒΔ αποτελείται από ένα και μοναδικό αρχείο, το οποίο κλειδώνει
όταν το προσπελαύνει κάποιος χρήστης και δεν μπορεί να συνδεθεί δεύτερος. Στην περίπτωση των
MySQL, PostgreSQL, MSSQL, FireBird, Oracle, DB2, Ingres και Informix η ΒΔ "test" θα πρέπει
ήδη να έχει δημιουργηθεί έξω από το web2py. Από τη στιγμή που η σύνδεση αποκαθίσταται, το
web2py μπορεί να δημιουργεί, να αλλάζει και να ρίχνει πίνακες κατά το δοκούν.
4.2. Models

Όλες οι εργασίες που σχετίζονται με την αναπαράσταση δεδομένων από μηχανισμούς μόνιμης
κατανεμημένης αποθήκευσης (RDBMS) επιτελούνται στον κατάλογο models της εφαρμογής σας.
Εκεί «ζουν» αρχεία Python τα οποία καθορίζουν:

• τη σύνδεση με κάποια βάση δεδομένων


• τον καθορισμό των πινάκων που απαρτίζουν τον αποθηκευτικό χώρο της εφαρμογής μας

• τον καθορισμό των μεταξύ των πινάκων σχέσεων

• τον καθορισμό συγκεκριμένων κανόνων πλήρωσης των πινάκων

• τον καθορισμό αυτοματοποιημένων περιορισμών και την πυροδότηση ενεργειών με βάση


συγκεκριμένα κριτήρια

• τον ορισμό κεντρικών menu και sub-menu της εφαρμογής μας

Το web2py, για να αναγκάσει τον προγραμματιστή να ακολουθήσει ορθές προγραμματιστικές


πρακτικές, χειρίζεται τα modules (αρχεία .py) που βρίσκονται στον κατάλογο models με ένα τρόπο
που ίσως αρχικά σας ξενίσει: «εκτελεί» τα αρχεία αυτά με αλφαβητική σειρά ονόματος! Συνεπώς
μπορούμε να ακολουθήσουμε δύο στρατηγικές:

• Είτε να έχουμε ένα και μόνο αρχείο μέσα στο οποίο θα καθορίσουμε όλες τις παραμέτρους
αναπαράστασης των δεδομένων που απαιτεί η εφαρμογή μας
• Είτε να «σπάσουμε» τη λογική αναπαράστασης σε περισσότερα αρχεία

Η πρώτη επιλογή είναι περισσότερο «χαοτική» συνεπώς θα πάμε με τη δεύτερη: σε άλλο αρχείο θα
έχουμε τις μεταβλητές σύνδεσης, σε άλλο τον καθορισμό των πινάκων και σε ένα ακόμα τρίτο τα
menu της εφαρμογής.

4.2.1 0.py !!!

Θα πρότεινα επίσης ένα ακόμη, αρχικό αρχείο, στο οποίο θα καθορίζουμε το στάδιο στο οποίο
βρίσκεται η εφαρμογή: αν βρισκόμαστε σε στάδιο ανάπτυξης ακόμη, θα χρησιμοποιήσουμε ως
βάση δεδομένων την ενσωματωμένη SQLite. Εφόσον η εφαρμογή μας βγει σε φάση παραγωγής η
βάση δεδομένων θα αλλάξει σε MySQL.

Στον υποκατάλογο models της νέας εφαρμογής services που μόλις κατασκευάσατε, δημιουργήστε
ένα αρχείο1 και αποθηκεύστε το με ονομασία 0.py. Σε αυτό γράψτε τον εξής κώδικα:

1. # -*- coding: utf-8 -*-


2. from gluon.storage import Storage
3. settings = Storage()
4. settings.production = False
5. if settings.production:
6. settings.db_uri = 'dbmsEngine://userName:passWord@server:port/database'
7. settings.migrate = False
8. else:
9. settings.db_uri = 'sqlite://development.sqlite'
10. settings.migrate = True

1
Από εδώ και πέρα θα σας πρότεινα τη χρήση του καταπληκτικού freeware IDE Komodo Edit, εφόσον χρησιμοποιείτε
ένα σχετικά σύγχρονο Η/Υ.
και αποθηκεύστε.

Σας συνιστώ τη σειρά 1 να τη χρησιμοποιείτε σε κάθε νέο αρχείο Python που δημιουργείτε.
Καθοδηγεί τη γλώσσα να χειρισθεί κατάλληλα τα αλφαριθμητικά των μεταβλητών σας ώστε να
αποθηκεύουν σωστά τα ελληνικά. Στις σειρές 2, 3 ανοίγουμε το μεγάλο χώρο των ρυθμίσεων και
αποθησαυρισμού (Storage) του web2py και τον αποδίδουμε για ευκολία σε μια μεταβλητή
(settings). Στη συνέχεια αποδίδουμε στη settings το χαρακτηριστικό production (σειρά 4) που θα
είναι λογικού τύπου (Σωστό/Λάθος) με προφανή ερμηνεία, και την οποία θα αλλάζουμε εμείς.
Προς το παρόν την ορίζουμε ως ‘False’, δηλαδή η εφαρμογή μας είναι ακόμη σε κατάσταση
ανάπτυξης.

Τέλος, αν η settings.production είναι True (σειρά 5) η μεταβλητή σύνδεσης (settings.db_uri2) της


εφαρμογής μας θα την οδηγεί σε μια «βαρβάτη» ΒΔ (σειρά 6), αλλιώς θα χρησιμοποιεί την … έρμη
την SQLite (σειρές 8,9). Γράψτε, χωρίς να ρωτάτε τις σειρές 7, 10 (migrations) και θα σας ‘ξηγήσω
τ’ όνειρο … αργότερα.

Ακόμη, βέβαια, δεν έχουμε δημιουργήσει κανένα αντικείμενο σύνδεσης. Θα το ορίσουμε στο
επόμενο αρχείο.

4.2.2 db.py

Το web2py καθόρισε ένα αρχείο db.py στον υποκατάλογο models κατά τη δημιουργία της
εφαρμογής σας. Περιλαμβάνει κάποιες προεπιλεγμένες τιμές για διάφορες εκφάνσεις μιας πιθανής
σύνδεσης. Σας ενδιαφέρουν κάποια – όχι όλα.

Σβήστε τελείως από τη σειρά 3 έως και τη σειρά 43. Επίσης σβήστε τελείως από την καινούρια
σειρά 23 έως το τέλος του αρχείου. Μια χαρά το κουρέψαμε - ούτε καταθέσεις να ήταν!...

Στη νέα σειρά 3 προσθέστε το εξής:

3. db = DAL(settings.db_uri)

Στη νέα σειρά 10 αλλάξτε τον κώδικα σε:

10. auth.define_tables(migrate=settings.migrate,username=True)

Συνοπτικά, το νέο αρχείο db.py θα πρέπει να περιλαμβάνει τα εξής:

1. # -*- coding: utf-8 -*-


2.
3. db = DAL(settings.db_uri)
4.
5. from gluon.tools import Auth, Crud, Service, PluginManager, prettydate
6. auth = Auth(db)
7. crud, service, plugins = Crud(db), Service(), PluginManager()
8.
9. ## create all tables needed by auth if not custom tables
10. auth.define_tables(migrate=settings.migrate,username=True)
11.
12. ## configure email
13. mail = auth.settings.mailer

2
Όπου κι αν παραπέμπει, ο όρος uri προέρχεται από το Uniform Resource Identifier – ψάξτε το.
14. mail.settings.server = 'logging' or 'smtp.gmail.com:587'
15. mail.settings.sender = 'you@gmail.com'
16. mail.settings.login = 'username:password'
17.
18. ## configure auth policy
19. auth.settings.registration_requires_verification = False
20. auth.settings.registration_requires_approval = False
21. auth.settings.reset_password_requires_verification = True

Στην 3η σειρά καθορίζεται το νέο αντικείμενο αναπαράστασης δεδομένων db, με παράμετρο τη


settings.db_uri που, θυμηθείτε, καθορίζεται στο 0.py. Στη συνέχεια εισάγουμε τις βασικές κλάσεις
του web2py: Auth, Crud, Service, PluginManager και τις αρχικοποιούμε στις αντίστοιχες
μεταβλητές (σειρές 5,6,7).

Η Auth χρησιμοποιείται για τη δημιουργία των πινάκων των χρηστών της εφαρμογής μας, των
groups στα οποία αυτοί πιθανόν να ανήκουν, των επιτρεπόμενων ενεργειών τους κ.λπ. Η
δημιουργία του συνόλου πινάκων auth_ υλοποιείται από τη γραμμή 10, στην οποία παρατηρούμε
δύο νέες παραμέτρους

• username (Προεπιλεγμένη τιμή: False.): καθορίζει αν θα ζητείται το όνομα χρήστη (True) ή το


email (False) κατά την εισαγωγή των διαπιστευτηρίων του χρήστη. Χρησιμοποιώντας το
username έχουμε καλύτερο έλεγχο στις ενέργειες του χρήστη.
• migrations (Προεπιλεγμένη τιμή: True.): Προσέξτε καλά γιατί αυτό είναι SOS !!! Όταν
καθορίζουμε τους πίνακες σε κάποιο αρχείο model του web2py, ΔΕΝ δημιουργούμε πίνακες
στη βάση δεδομένων με την οποία έχουμε συνδεθεί. Στην πραγματικότητα περιγράφουμε ένα
πρότυπο (ένα μοντέλο, εξ ου και models) για το πώς θα θέλαμε να είχαν δημιουργηθεί οι
πίνακες αυτοί, αν μπορούσαμε να γράψουμε SQL έναντι της ΒΔ. Έλα όμως που δεν ξέρουμε
SQL. Εσύ που διαβάζεις πρόσεξε και μη γελάς! ΔΕΝ ΞΕΡΕΙΣ ΤΗΝ SQL ΔΙΑΛΕΚΤΟ ΤΗΣ
MySQL ΓΙΑ ΠΑΡΑΔΕΙΓΜΑ (ή της Ingres/DB2 κ.λπ.). Από το αδιέξοδο αναλαμβάνει να μας
βγάλει το web2py. Όταν το migrate είναι True, το framework αναλαμβάνει να γράψει τον
κατάλληλο SQL κώδικα για τη βάση με την οποία έχει συνδεθεί και να «συγχρονίσει» όλο τον
υποκατάλογο models με αυτή! Όταν αλλάζει για παράδειγμα η μεταβλητή σύνδεσης, αλλάζει
δηλαδή το RDBMS, τότε το web2py τροποποιεί κατάλληλα τον κώδικα SQL, ώστε να
ανταποκρίνεται στις νέες συνθήκες (στο πιθανά νέο RDBMS). Σε φάση ανάπτυξης θέτουμε τα
migrations ως True, ούτως ώστε να δημιουργούμε ή να τροποποιούμε (έμμεσα) διάφορα
αντικείμενα στη βάση. Σε φάση παραγωγής το θέτουμε False για να απομονώσουμε τη βάση
και να την προστατεύσουμε έτι περισσότερο.

Οι υπόλοιπες γραμμές του κώδικα είναι μάλλον αυτεξήγητες: Οι σειρές 12-16 αφήνουν μια
αναμονή (αμ πώς – μόνο οι ηλεκτρολόγοι;) για ρύθμιση επικοινωνίας server – χρήστη μέσω email.
Οι γραμμές 18-21 καθορίζουν την πολιτική που θα κρατήσει ο server έναντι της εγγραφής και της
αλλαγής password εκ μέρους του χρήστη.

4.2.3 db_defineTables.py

Η καινούρια μας εφαρμογή θα διαχειρίζεται τις ηλεκτρονικές υπηρεσίες τις οποίες προσφέρει η
Διεύθυνση Πληροφορικής του Υπ.Α.Α.Τ. Κατά την ανάλυση καταλήγουμε στο ότι η πληροφορία
θα πρέπει να αποθηκεύεται σε δύο πίνακες ενός RDBMS: στον ένα θα καταγράφονται στοιχεία της
η-υπηρεσίας (πίνακας Service) και στον άλλο στοιχεία των servers που τη διεκπεραιώνουν (πίνακας
Server). Η σχέση μεταξύ των πινάκων, καθώς και οι ιδιότητες (πεδία) καθενός από αυτούς δίνονται
στο παρακάτω διάγραμμα σχέσεων οντοτήτων (Entity Relationship Diagram: ERD):
Ανοίξτε ένα καινούριο αρχείο db_defineTables.py στον κατάλογο models, γράψτε τα παρακάτω και
αποθηκεύστε:

1. # -*- coding: utf-8 -*-

2.

3. db.define_table('server'

4. ,Field('name', 'string', length=30, label='Όνομα', default="")

5. ,Field('role', 'string', length=25, label='Ρόλος')

6. ,Field('manufacturer', 'string', length=15, label='Κατασκευαστής')

7. ,Field('productSeries', 'string', length=30, label='Σειρά', default="")

8. ,Field('model', 'string', length=30, label='Μοντέλο', default="")

9. ,Field('serialNo', 'string', length=50, label='Σειριακός Αρ.', default="")

10. ,Field('partNo', 'string', length=50, label='Αρ. Παρτίδας', default="")

11 ,Field('rack', 'string', length=10, label='Rack', default="")

12 ,Field('raidController', 'string', length=50, label='Ελεγκτής RAID', default="")

13 ,Field('domain', 'string', length=50, label='Τομέας', default="")

14 ,Field('realIPAddress', 'string', length=50, label='Net IP', default="")

15 ,Field('osUserID', 'string', length=15, label='OS UserID', default="")

16 ,Field('osPassword', 'string', length=20, label='OS Password', default="")

17 ,Field('managementIPAddress', 'string', length=50, label='Management IP', default="")

18 ,Field('managementUserID', 'string', length=15, label='Management UserID', default="")

19 ,Field('managementPassword', 'string', length=20, label='Management Password',


default="")

20 ,Field('notes', 'text', label='Σημειώσεις', default="")

21 ,Field('created_on', 'datetime', default=request.now, update=request.now, writable=False)


22 ,Field('created_by', 'reference auth_user', default=auth.user_id, update=auth.user_id,
writable=False)

23 ,format = '%(name)s'

24 )

25 db.server.id.readable = False

26

27 db.define_table('service'

28 ,Field('systitle', 'string', length=150, label='Τίτλος', default="")

29 ,Field('dbSrvr', label='DataBase Server')

30 ,Field('appSrvr', label='Application Server')

31 ,Field('ipefthinos', 'string', length=45, label='Υπεύθυνος', default="")

32 ,Field('dbType', 'string')

33 ,Field('isweb','boolean',label='Web?', default="F")

34 ,Field('description','text', label='Περιγραφή', default="")

35 ,Field('created_on', 'datetime', default=request.now, update=request.now, writable=False)

36 ,Field('created_by','reference auth_user',default=auth.user_id,update=auth.user_id,
writable=False)

37 ,singular='Εφαρμογή'

38 ,plural='Εφαρμογές'

39 ,format = '%(systitle)s'

40 )

41 db.service.id.readable = False

Η κυριότερη μέθοδος του DAL (το οποίο, θυμηθείτε, έχει αρχικοποιηθεί στο αντικείμενο db) είναι
η define_table (γραμμές 3, 27):

>>> db.define_table(server, Field(‘. . .’), Field(‘. . .’),. . . )

Αρχικοποιεί, αποθηκεύει και επιστρέφει ένα αντικείμενο της κλάσης Table του web2py, του
αποδίδει την ονομασία "server" και ενσωματώνει σε αυτό ένα η περισσότερα πεδία με τα
καθορισμένα από τον κώδικα ονόματα. Παρατηρήστε ότι κατά τον καθορισμό των πινάκων δεν
ορίσαμε το πεδίο “id”, καθώς το framework δηλώνει ένα τέτοιο πεδίο αυτόματα κατά τον
καθορισμό του πίνακα, ούτως ώστε κάθε πίνακας να έχει ένα πεδίο “id”, από προεπιλογή.
Πρόκειται για πεδίο αριθμητικού τύπου και μάλιστα ακεραίου, αυτόματης επαύξησης (auto-
increment) κατά μια μονάδα, που ξεκινά από το 1. Το πεδίο θεωρείται ως το πρωτεύον κλειδί του
πίνακα. Η συμπεριφορά αυτή του framework μπορεί να τροποποιηθεί εάν ένα πεδίο ορισθεί με
type='id'. Στην περίπτωση αυτή το web2py θα χρησιμοποιήσει το πεδίο αυτό ως πρωτεύον
κλειδί.
Στην ανάλυση του διαγράμματος σχέσεων οντοτήτων του μοντέλου μας φαίνεται ότι οι δύο πίνακες
δε σχετίζονται με σχέση ένα-προς-πολλά ή πολλά-προς-πολλά. Το μόνο που απαιτείται είναι μια
σχέση ένα-προς-ένα σε δύο πεδία του πίνακα service, ουσιαστικά δηλαδή ζητείται η δημιουργία
λίστας τιμών (List Of Values: LOV) στο πεδίο του app ή db Server της υπηρεσίας. Η λίστα τιμών
πρέπει να τροφοδοτείται από τον πίνακα των servers. Λοιπόν κι αυτό θα το αφήσουμε γι’ αργότερα.
Προς το παρόν στο μοντέλο μας θα έχουμε δυο ανεξάρτητους πίνακες.

Αντιπροσώπευση των εγγραφών (format)

Ο καθορισμός συγκεκριμένου μορφότυπου (format) για κάθε πίνακα είναι απαραίτητος για να
λειτουργήσει σωστά η αντιστοίχιση των δευτερευόντων κλειδιών, όπως περιγράφηκε πιο πάνω.
Είναι μια δήλωση προαιρετική, βέβαια, αλλά σας τη συνιστώ για κάθε πίνακα, ούτως ώστε να
γίνεται σωστά η αντιπροσώπευση των εγγραφών του. Η δήλωση γίνεται μετά τον ορισμό των
πεδίων, ως εξής:

format = '%(name)s' (γραμμή 23)

format = '%(systitle)s' (γραμμή 39)

Θα μπορούσαμε επίσης να ορίσουμε και πιο πολύπλοκες μορφοποιήσεις όπως:

format = '%(name)s - %(realIPAddress)s'

ή ακόμη και

>>> db.define_table('person', Field('name'),


format=lambda r: r.name or 'anonymous')

Καθορισμός πεδίων (Field constructor)

Ο κατασκευαστής πεδίων (Field) περιλαμβάνει τις ακόλουθες μεθόδους με τις προεπιλογές τους:

Field(name, 'string', length=None, default=None,


required=False, requires='<default>',
ondelete='CASCADE', notnull=False, unique=False,
uploadfield=True, widget=None, label=None, comment=None,
writable=True, readable=True, update=None, authorize=None,
autodelete=False, represent=None, compute=None,
uploadfolder=os.path.join(request.folder,'uploads'),
uploadseparate=None,uploadfs=None)

… οι οποίες δεν σχετίζονται όλες με κάθε τύπο πεδίου. Για παράδειγμα η "length" έχει νόημα μόνο
για πεδία τύπου "string". Η "uploadfield" ή η μέθοδος "authorize" σχετίζονται με πεδία τύπου
"upload". Η μέθοδος "ondelete" σχετίζεται μόνο με πεδία τύπου "reference" και "upload".

• length: καθορίζει το μέγιστο μήκος ενός πεδίου τύπου "string", "password" ή "upload".
Εάν δεν προσδιορίζεται το web2py χρησιμοποιεί την προκαθορισμένη τιμή του RDBMS.
Σας συνιστώ να την καθορίζετε πάντα.
• default: καθορίζει την προεπιλεγμένη τιμή που θα λάβει το πεδίο όταν γίνει κάποιο insert.
Προσέξτε τις γραμμές 21, 22, 35, 36 του κώδικα: εκτελούν ένα αθέατο από όποιον εισάγει ή
επικαιροποιεί εγγραφές auditing, κρατώντας πληροφορίες για το πότε και από ποιόν
εισήχθη ή τροποποιήθηκε η εγγραφή Παρατηρήστε ότι εκτός του ότι η default μπορεί να
είναι μια σταθερή τιμή, επίσης μπορεί να πάρει τη μορφή συνάρτησης
(συμπεριλαμβανομένης της lambda). Στην περίπτωση αυτή, η συνάρτηση καλείται μια φορά
για κάθε εγγραφή που εισάγεται ή τροποποιείται.
• required: απαγορεύει την εισαγωγή εγγραφής στον πίνακα, εάν δεν καθορισθεί
συγκεκριμένη τιμή γι’αυτό το πεδίο.
• requires: είναι ένας επικυρωτής (validator) ή μια λίστα επικυρωτών. Χρησιμοποιείται από
το DAL κυρίως για τη μέθοδο SQLFORM, όπως θα δούμε στα επόμενα. Η προεπιλεγμένοι
επικυρωτές για δεδομένους τύπους πεδίων δίνονται στον ακόλουθο πίνακα:

Τύπος πεδίου Προεπιλεγμένος επικυρωτής


String IS_LENGTH(length) default length is 512
Text IS_LENGTH(65536)
Blob None
Boolean None
Integer IS_INT_IN_RANGE(-1e100, 1e100)
Double IS_FLOAT_IN_RANGE(-1e100, 1e100)
decimal(n,m) IS_DECIMAL_IN_RANGE(-1e100, 1e100)
Date IS_DATE()
Time IS_TIME()
Datetime IS_DATETIME()
Password None
Upload None
reference <table> IS_IN_DB(db,table.field,format)
list:string None
list:integer None
list:reference <table> IS_IN_DB(db,table.field,format,multiple=True)
Json IS_JSON()
Bigint None
big-id None
big-reference None

Το decimal απαιτεί μια tuple τιμών (n,m) εκ των οποίων n = ο συνολικός αριθμός ψηφίων (και η
υποδιαστολή) και m = ο αριθμός των δεκαδικών ψηφίων.

Παρατηρήστε ότι η requires=... εκτελείται σε επίπεδο φορμών, η required=True επιβάλλεται


στο επίπεδο του DAL (κατά την εισαγωγή εγγραφής), ενώ οι notnull, unique και ondelete
επιβάλλονται σε επίπεδο βάσης δεδομένων. Θα δούμε τη requires=... ως παράδειγμα στα
επόμενα

Το σύστημα χειρισμού Βάσεων Δεδομένων του web2py

Αφού καθορίσαμε το μοντέλο των πινάκων μας, είναι ώρα να δούμε με ποιο τρόπο το framework
μας επιτρέπει πλέον να χειριστούμε τις εγγραφές τους.

Στον περιηγητή σας πλοηγηθείτε στον υποσύστημα διαχείρισης του web2py και ανοίξτε για
επεξεργασία την εφαρμογή services.

Κάνετε κλικ στο κουμπί «Database administration»


οπότε θα εμφανισθεί η παρακάτω οθόνη στην οποία αναφέρονται όλοι οι πίνακες που το
framework δημιούργησε στο RDBMS για λογαριασμό σας:

Όπως βλέπετε, όλοι οι πίνακες αποτελούν αντικείμενα του αρχικού αντικειμένου db που «δείχνει»
στη βάση δεδομένων που έχουμε επιλέξει. Οι πίνακες τα ονόματα των οποίων αρχίζουν με auth_
(auth_user, auth_group, auth_membership, auth_permission, auth_event και auth_cas)
δημιουργήθηκαν αυτόματα από το web2py χωρίς εμείς να γράψουμε ούτε μια γραμμή κώδικα και
σχετίζονται με τη διαχείριση των χρηστών της εφαρμογής μας (στην πραγματικότητα οι πίνακες
αυτοί προέρχονται από τον κώδικα στη γραμμή 10, αρχείο db.py του υποκαταλόγου models). Ας
ρίξουμε μια ματιά στη λειτουργία τους:

Κάνετε κλικ στο New record δεξιά από το db.auth_user


και εισάγετε τα στοιχεία ενός χρήστη της εφαρμογής (για αρχή, εισάγετε τα δικά σας στοιχεία). Τα
τρία τελευταία πεδία αφήστε τα κενά.

Όταν πατήσετε submit, η εγγραφή σας θα καταχωρηθεί στη ΒΔ. Πατήστε κλικ στο δεσμό ‘db’
ακριβώς δεξιά από το ‘Database’ για να επιστρέψετε στον κατάλογο των πινάκων της βάσης σας.
Αν κάνετε κλικ στο όνομα του πίνακα db.auth_user, θα δείτε την πρώτη σας εγγραφή.

Με όμοιο τρόπο καταχωρήστε δύο νέες εγγραφές στον πίνακα db.auth_group και ονομάστε τις
‘hw_managers’, ‘service_managers’.

Άσκηση: Εισάγετε μερικές φανταστικές εγγραφές στους πίνακες για servers και η-υπηρεσίες
4.3. Controllers και Views.

Στον υποκατάλογο controllers δημιουργήστε ένα νέο αρχείο με ονομασία

manageItems.py

και σε αυτό εισάγετε τον εξής κώδικα:

1. # -*- coding: utf-8 -*-


2.
3. def manageServers():
4. formSrvr = crud.create(db.server)
5. return dict(form=formSrvr)

Τώρα, στον υποκατάλογο views δημιουργήστε ένα νέο υποκατάλογο manageItems και μέσα σε
αυτόν δημιουργήστε ένα νέο αρχείο με την ονομασία

manageServers.html

Στο τελευταίο αυτό αρχείο εισάγετε τον κώδικα:

1. {{extend ‘layout.html’}}
2.
3. {{=form}}

Αν επισκεφθείτε τον ιστότοπο της εφαρμογής σας στη συγκεκριμένη view (θυμάστε πώς;) θα δείτε
μια φόρμα crud του web2py, έτοιμη να δεχθεί τα στοιχεία ενός νέου server. Προσέξτε ότι για το
νέο controller manageItems.py φτιάξαμε έναν ολόκληρο νέο υποκατάλογο με το ίδιο όνομα (χωρίς
κατάληξη βέβαια) στις views. Για κάθε συνάρτηση που θα περιλαμβάνεται μέσα στο νέο controller
θα απαιτείται να φτιάχνουμε μια νέα σελίδα μέσα στο νέο υποκατάλογο, με το ίδιο όνομα με τη
συνάρτηση και κατάληξη html. Γι’ αυτό φτιάξαμε τη νέα σελίδα manageServer.html μέσα στον
υποκατάλογο manageItems των views.

Με το μηχανισμό αυτόν το web2py γνωρίζει πού είναι η αντίστοιχη view της συνάρτησης, κι έτσι
μπορεί να «περνάει» μεταβλητές από τον controller προς αυτήν. Στο παράδειγμά μας, ο controller
manageItems.py περιλαμβάνει μια συνάρτηση, την manageServers() (σειρά 3). Αυτή αρχικοποιεί
μια φόρμα crud (τι θυμάστε από τα προηγούμενα μαθήματα;) (σειρά 4) με το όνομα formSrvr και
την πακετάρει στο λεξικό form (σειρά 5). Στη συνέχεια τη στέλνει προς την view που έχει το ίδιο
όνομα με αυτό της συνάρτησης και ταυτόχρονα βρίσκεται στον υποκατάλογο των views εκείνο που
έχει όνομα ίδιο με το όνομα του controller που την περιλαμβάνει. (Τα παίξατε; Δεν είναι δύσκολο,
θα συνηθίσετε: Practice makes perfect! … Εξάλλου υπάρχει πάντα και η Java!)

Ένας controller συνήθως στέλνει τα αντικείμενα τα οποία φτιάχνει μέσω του πανίσχυρου εργαλείου
λεξικό (dict-ionary) της Python. Υπάρχουν όμως και άλλοι τρόποι. Θα μπορούσαμε για παράδειγμα
να στείλουμε κατευθείαν τις τοπικές (local) μεταβλητές της συνάρτησης (θυμάστε τις τοπικές
μεταβλητές της Python;) μέσω της δήλωσης return locals(). Αλλάξτε τον controller ως εξής:

1. # -*- coding: utf-8 -*-


2.
3. def manageServers():
4. formSrvr = crud.create(db.server)
5. #return dict(form=formSrvr)
6. return locals()
Στον προηγούμενο κώδικα μετατρέψαμε την σειρά 5 σε σχόλιο προσθέτοντας στην αρχή της το
σύμβολο #. Αυτός είναι ένας γρήγορος τρόπος στην Python (όπως και σε όλες βεβαίως τις γλώσσες
προγραμματισμού) να απενεργοποιούμε ολόκληρες γραμμές κώδικα, χωρίς να τον χάνουμε
σβήνοντάς τον! Στη συνέχεια προσθέσαμε τη σειρά 6 η οποία επιστρέφει όλες τις τοπικές
μεταβλητές (όλα τα εσωτερικά της συνάρτησης δημιουργημένα αντικείμενα) με το όνομά τους και
τις «στέλνει» στην αντίστοιχη view. Αποθηκεύστε και ξανα-επισκεφθείτε τη σελίδα
manageServers.

Άσκηση: Τι; Μήνυμα λάθους; Πώς είναι δυνατόν; Ερμηνεύστε το λάθος και διορθώστε το
γρήγορα!

Απάντηση: Φυσικά είναι λάθος διότι η συνάρτηση του controller τώρα επιστρέφει τις τοπικές
μεταβλητές με το όνομά τους. Συνεπώς επιστρέφει τη φόρμα crud με το όνομα formSrvr.
Η αντίστοιχη view παραλαμβάνει ακόμη το αντικείμενο με την ονομασία {{=form}}. Είτε
θα αλλάξουμε αυτό σε {{=formSrvr}}, είτε θα αλλάξουμε το όνομα του αντικειμένου του
controller σε form. Διαλέγετε και παίρνετε…

Όλες οι φόρμες στις οποίες καταλήγουμε όταν πατάμε «New record» στο υποσύστημα διαχείρισης
βάσεων δεδομένων του web2py προέρχονται από τέτοιες απλές φόρμες CRUD, τις οποίες
αναλαμβάνει αυτόματα να δημιουργήσει το framework αμέσως μετά τον ορισμό κάποιου νέου
πίνακα στο μοντέλο.

Παρατηρήστε ότι η CRUD δε λειτουργεί ποτέ μόνη της, αλλά σε συνδυασμό με συγκεκριμένες
μεθόδους. Για παράδειγμα η μέθοδος create δημιουργεί μια καινούρια εγγραφή στο πίνακα που
δηλώνεται ως όρισμα (σειρά 4). Οι βασικές μέθοδοι που συνδυάζονται με την CRUD, είναι:

• crud.tables() επιστρέφει κατάλογο των πινάκων του μοντέλου.


• crud.create(db.tablename) δημιουργεί νέα εγγραφή.
• crud.read(db.tablename, id) επιδεικνύει μια εγγραφή, βάσει του id της.
• crud.update(db.tablename, id) επιστρέφει μια φόρμα επικαιροποίησης μιας εγγραφής
βάσει id.
• crud.delete(db.tablename, id) διαγράφει μια εγγραφή βάσει id.
• crud.select(db.tablename, query) επιστρέφει ένα σύνολο εγγραφών του πίνακα βάσει
της δεδομένης αναζήτησης (query).
• crud.search(db.tablename) επιστρέφει μια tuple (form, records) όπου form είναι μια
φόρμα αναζήτησης και records είναι μια λίστα εγγραφών που έχει επιστραφεί από την
φόρμα αναζήτησης.
• crud() λειτουργεί βάσει της request.args() επιστρέφοντας ένα από τα παραπάνω.

Πρόκειται για μια χονδροειδή με την πρώτη ματιά, σε κάποιες όμως περιπτώσεις, σας διαβεβαιώ,
πολύ λεπτεπίλεπτη μέθοδο αναζήτησης εισαγωγής ή και διαγραφής εγγραφών στους διάφορους
πίνακες του μοντέλου. Είμαι σίγουρος ότι σε πρώτη φάση δεν θα σας απασχολήσει πολύ, παρά
μόνο αφού έχετε δουλέψει αρκετά με το framework και ζητάτε να δημιουργήσετε εξωτικά
πράγματα. Αντ’ αυτής, μάλλον πολύ περισσότερο θα ασχοληθούμε με τις φόρμες που θα
αναπτυχθούν στα επόμενα.

Προς το παρόν όμως αρκεί για να επιδειχθούν κάποιες βασικές μορφοποιήσεις που είναι δυνατό να
επιτευχθούν με πολύ λίγη προσπάθεια λόγω των δυνατοτήτων του framework.

Άσκηση: Δημιουργήστε μια νέα crud φόρμα για τις η-υπηρεσίες. Ζητείται ο controller να είναι
ο manageItems, συνάρτηση η manageServices και όνομα αντικειμένου φόρμας η
formServices.
Απάντηση:

4.4. Menu και Επικυρωτές (Validators).

Ανοίξτε το αρχείο menu.py που βρίσκεται στον υποκατάλογο models και αντικαταστήσετε τα
response.title και response.subtitle με «Υπ.Α.Α.Τ. - Δ/νση Πληροφορικής» και «Ηλεκτρονικές
Υπηρεσίες» αντίστοιχα.

Επίσης αντικαταστήσετε τις ‘meta’ πληροφορίες (author, description, keywords) με τα αντίστοιχα


δικά σας.

Άσκηση: Βρείτε τη γραμμή response.menu και φτιάξτε ένα menu που να δείχνει στις δύο
σελίδες εισαγωγής servers και υπηρεσιών που δημιουργήσατε στο προηγούμενο βήμα.
Επίσης προσθέστε μια καταχώρηση για την αρχική σελίδα (Υπόδειξη: Επαναλάβετε
το κεφάλαιο 2)

Αποθηκεύστε τη μέχρι εδώ εργασία σας και κλείστε το αρχείο menu.py

Ας ξαναγυρίσουμε στο παλιό καλό μας αρχείο db_defineTables.py. Αλλάξτε τις γραμμές 29 και 30,
ώστε να είναι:
29. ,Field('dbSrvr',label='DB Server Dynamic',requires=IS_IN_DB(db,db.server.id,'%(name)s'))
30. ,Field('appSrvr',label='App Server Dynamic',requires=IS_IN_DB(db,db.server.id,'%(name)s'))

και αποθηκεύστε το αρχείο. Προσθέσαμε στα δύο πεδία τον validator IS_IN_DB(), ο οποίος
ουσιαστικά λειτουργεί σαν λίστα πεδίων (LOV). Ο εν λόγω επικυρωτής δέχεται τρία ορίσματα: Το
πρώτο δηλώνει το αντικείμενο σύνδεσης με τη ΒΔ (db). Το δεύτερο όρισμα καθορίζει το πεδίο που
πραγματικά θα αποθηκεύεται στη ΒΔ (id) και το τρίτο τον τρόπο με τον οποίο θα μας εμφανίζεται
η λίστα πεδίων (για σκεφθείτε: αν μας δίνει μια λίστα μόνο με ids δεν θα μας ήταν και πολύ
χρήσιμη, έτσι;)

Στη συνέχεια ξανα-αλλάξτε τις δύο αυτές συγκεκριμένες γραμμές, ώστε να είναι:
29. ,Field('dbSrvr', label='DB Server Dynamic', requires=IS_EMPTY_OR(IS_IN_DB(db,db.server.id,'%
(name)s')))
30. ,Field('appSrvr', label='App Server Dynamic',
requires=IS_EMPTY_OR(IS_IN_DB(db,db.server.id,'%(name)s')))

Εδώ, προσθέσαμε γύρω από τον προηγούμενο επικυρωτή τον IS_EMPTY_OR(). Αυτός δέχεται ως
όρισμα έναν και μόνο άλλον επικυρωτή, δίνοντας τη δυνατότητα στη λίστα πεδίων να περιέχει και
το κενό. Όπως καταλαβαίνετε, για να λειτουργήσει αυτός ο επικυρωτής, θα πρέπει το μοντέλο μας
να επιτρέπει στο συγκεκριμένο πεδίο να δέχεται κενά (θα ήταν καλύτερα να πω: θα πρέπει το
μοντέλο μας να μην απαγορεύει τα κενά. Αλλά … δεν θέλω να σας μπερδέψω).

Ακόμη βρισκόμαστε στο αρχείο db_defineTables.py. Προσθέστε μια λίστα Python στην αρχή
(αφήστε κενή σειρά κάτω από την πρώτη γραμμή):

type_of_database = ['Oracle','MySQL','PostgreSQL','DB2','Informix','MS SQL


Server','SyBase IQ']

Αφήστε άλλη μια κενή σειρά κάτω από τη λίστα πριν αρχίσετε τον ορισμό των πινάκων σας. Στη
συνέχεια τροποποιήστε το πεδίο dbType του πίνακα service ώστε να είναι:
34. ,Field('dbType', 'string', requires=IS_EMPTY_OR(IS_IN_SET(type_of_database)))
Με την παραπάνω τεχνική δημιουργούμε στατικές λίστες πεδίων, που δεν προέρχονται από πίνακα
της ΒΔ, αλλά από μια λίστα Python την οποία έχουμε δηλώσει στο μοντέλο μας. Για να
προσθέσουμε ή να αφαιρέσουμε στοιχεία της λίστας αυτής πρέπει να επεξεργαστούμε το αρχείο
db_defineTables.py. Με άλλα λόγια, ο χρήστης που εισάγει ή ενημερώνει εγγραφές δεν έχει τη
δυνατότητα να τροποποιήσει αυτή τη λίστα τιμών.

4.4. Σχέσεις πινάκων ένα προς πολλά.

Κατόπιν ενδελεχούς μελέτης του μοντέλου μας, ανακαλύψαμε ότι είναι ανάγκη να καταγραφεί
επίσης και το software το οποίο κάθε server περιλαμβάνει. Το διάγραμμα σχέσεων οντοτήτων
αναγκαστικά θα τροποποιηθεί ως εξής:

Στο αρχείο db_defineTables.py, αμέσως μετά τον ορισμό του πίνακα server (ξεκινώντας από τη
γραμμή 29) ορίσετε τον παρακάτω πίνακα software, ως εξής:

29. db.define_table('software'
30. ,Field('title', 'string', length=50, label='Τίτλος', default="")
31. ,Field('version', 'string', label='Έκδοση', default="")
32. ,Field('type', 'string', label='Τύπος')
33. ,Field('serialNo', 'string', length=50, label='Σειριακός Αρ.', default="")
34. ,Field('activationNo', 'string', length=50, label='Αρ. Ενεργοποίησης', default="")
35. ,Field('server', 'reference server')
36. ,Field('created_on', 'datetime', default=request.now, update=request.now, writable=False)
37. ,Field('created_by', 'reference auth_user', default=auth.user_id, update=auth.user_id,
writable=False)
38. ,format = '%(title)s %(version)s'
39. ,singular='Λογισμικό'
40. ,plural='Λογισμικό'
41. )
42. db.software.id.readable = False

Σχέσεις μεταξύ πινάκων (πρωτευόντων – δευτερευόντων κλειδιών) ορίζονται με τη δήλωση


‘reference <όνομα πίνακα>’.(γραμμή 7). Κάθε φορά το web2py γνωρίζει ακριβώς τι πρέπει να
κάνει με τη δήλωση αυτή. Στη συγκεκριμένη περίπτωση ουσιαστικά δημιουργεί μια σχέση ένα-
προς-πολλά ανάμεσα στους πίνακες Server και Software.

Άσκηση: Μετά από περαιτέρω ανάλυση της εφαρμογής, αποφασίσαμε ότι το διάγραμμα
σχέσεων -οντοτήτων θα πρέπει να τροποποιηθεί ως εξής:
Τα πεδία με την ονομασία ‘server’ στους νέους πίνακες Cpu, Ram και Hdd είναι τα εξωτερικά
κλειδιά (Foreign Keys) στη σχέση Ένα-Προς-Πολλά με τον πίνακα Server. Να υλοποιήσετε το
επικαιροποιημένο διάγραμμα στην εφαρμογή σας. Ορίσετε τους νέους πίνακες και
μοντελοποιήστε τις σχέσεις του διαγράμματος. Συγκεκριμένα πεδία των πινάκων της εφαρμογής
λαμβάνουν τιμές από λίστες πεδίων. Αυτά είναι:

ΠΙΝΑΚΑΣ ΛΙΣΤΑ ΠΕΔΙΩΝ

server.role Database Server, Application Server,


Database/Application Server, DHCP Server, SMTP Server,
Web Server, Net Management

server.manufacture Altec, Compaq, Dell, Fujitsu, Hewlett Packard, IBM


r

cpu.manufacturer AMD, Intel

ram.manufacturer ADATA, APACER, CORSAIR, CRUCIAL, GEIL, G.SKILL,


KINGSTON, MUSHKIN, PATRIOT, SAMSUNG, TRANSCEND

ram.form SIMM, DIMM, RIMM

ram.type SD, DDR SD, RD, DDR-1, DDR-2, DDR-3

hdd.manufacturer BUSlink, Diamond, Fujitsu, Hitatchi, Hewlett Packard,


IBM, Maxtor, Onspec Electronics, Q Logic, Quantum,
Samsung Electronics, Seagate Technology, Western
Digital

hdd.raid 0, 1, 2, 3, 4, 5
software.type Operating System, Database Server, Application Server,
Web Server, Development Tool, Designer Tool, Office
Suite, Mail Server, Programming Language, Web
Framework, Audio Processing, Image Processing, Video
Processing

service.dbType Oracle, MySQL, PostgreSQL, DB2, Informix, MS SQL


Server, SyBase IQ

Ακολουθεί κατάλογος που δείχνει το μορφότυπο (format) για κάθε πίνακα:

ΠΙΝΑΚΑΣ ΜΟΡΦΟΤΥΠΟ

server name

cpu manufacturer-productSeries-model

ram manufacturer-productSeries-model-capacity

hdd manufacturer-productSeries-model-capacity

software title-version

service systitle

Άσκηση: Αναρτήστε την εφαρμογή σας στον server shadow με ονομασία ‘Services-<το
username σας στο δίκτυο>

You might also like