Japanse New-Girl Monkey Network

Wishes & Reinventing the Wheel Part II

I'm not really into the wishlist thing, but I couldn't help being tempted. Really tempted. It would be so nice to have a machine with OSX, now that all my software is steadily becoming completely obsolete. On the other hand, there are almost 4 days to go on the auction and the price is already nearly AU$700. Who knows where it'll be tomorrow. Anybody wanna donate a wad of cash to me?

Well, back to the feasible wishlist -- the one where I get to pick all my own customized features of my blog. Here's a text file of my new database structure. I'm sure it's not perfectly in fourth normal form, and database design gurus everywhere can freely mock me. But I think it should work reasonably well. And I even have an unreadable ASCII diagram of the table relationships. Whee!

If anybody does look at it, they'll notice one table I haven't mentioned before: blogs_article_associations. That's because I'm still mulling that one over. I'd like a way to associate articles directly with each other (for example, if I refer back to a particular article, that maybe doesn't have a cognitive association for me as defined by projects or categories). But I think it might be something I save for the second pass of this rewrite.

So I guess that's the database, in all its unholy entirety. What next? Babble about the code?

I wrote a little data module type thing. It basically builds SQL queries for me so I don't have to keep typing "SELECT blah blah blah FROM blah WHERE blah" all the time. It also contains a little function that automatically does some stuff with the Python postgresql module I use, like format the results of a query the way I want it. It's eloquently named "data." I'm not going to include any samples of that code because it's nothing particularly revolutionary.

I also wrote a package called DBObjects. I created a base class to grab stuff from the database, slice it, dice it, update it, whatever. In the current version of ARJLog I'm just doing a lot of database calls and manipulating them by hand. It got old really fast, especially since I'm well rehearsed in the principles of OOP. But at that stage I just wanted to get something working, and I was still really low on the Python learning curve. In the new version, all of my basic weblog pieces will inherit from DatabaseObject, which does 90% of the work. This makes changing stuff much easier. So here's some of my beautiful base class (stored in __init__.py). Please excuse the crummy coding style.

class DatabaseObject:

"""Model an atomic object in the database.
Parameters:
Fields: List of strings, ['field1',..,'fieldn']
Links: Foreign key relationships {'linkedfield':(('ftable','fkey')...)}
IDField: Primary key (assumes only 1)
Table: string name of the table
ID: Get data from DB for this ID
Values: Data already retrieved, load these values"""
def __init__(self,Fields=[],Links={},IDField='',Table='',ID=None,Values={}):
"""This is what runs when the object is created. Yay. It
expects either an ID from the database so it can go fetch all the rest of the
information about the object, or a dictionary of values:
{'attribute name':'attribute value'}
Otherwise it assumes you are going to insert a new object into the database."""
dictvals = {}
self.__dict__['Fields'] = Fields
self.__dict__['Links'] = Links
self.__dict__['ID_Field'] = IDField
self.__dict__['Table'] = Table

if len(Links): self.__initlinks(Links)

if len(Values): dictvals = Values
elif ID:
sel = data.SelectQuery(self.__dict__['Table'], self.__dict__['Fields'], self.__dict__['ID_Field'] + '=' + str(ID))
dictvals = data.ExecuteQuery(sel,DBNAME)
else:
for fld in self.__dict__['Fields']: dictvals[fld] = [None]
self.__setvalues(dictvals)

def __setvalues(self,Values):
"""This is a little helper function to store the fields and
their values in the object. It also makes sure that you're not adding any
funky random fields that don't correspond with anything in the database."""
for fld in Values.keys():
if fld in self.__dict__['Fields']: self.__dict__[fld] = Values[fld][0]

def __initlinks(self,Links):
"""A helper function to store 'links' -- e.g. foreign key
relationships with other tables. It could probably use some graceful error
handling and checking. It's somewhat under development still."""
ftbls = {}
for fld in Links.keys():
linklst = Links[fld]
for tpl in linklst:
ftable,fkey = tpl
if ftbls.has_key(ftable): ftbls[ftable].append((fkey,fld))
else: ftbls[ftable] = [(fkey,fld)]
self.__dict__['__ftables'] = ftbls

def __getvalues(self,Fields):
"""Retrieve all the values currently stored in this object."""
Values = []
for fld in Fields:
if self.__dict__.has_key(fld):
Values.append(self.__dict__[fld])
else: Values.append(None)
return Values

def __getattr__(self,Key):
""" __getattr__ and __setattr__ are explained in the Python
documentation. What I'm doing here is making sure that you can only get
object attributes which correspond with database fields."""
if Key in self.__dict__['Fields'] or Key in self.__dict__.keys():
if self.__dict__.has_key(Key): return self.__dict__[Key]
else: return None
else: raise AttributeError,Key

def __setattr__(self,Key,Value):
"""Since all my database tables have serialized primary keys,
I don't want anyone arbitrarily setting a row's ID. So I make sure you can't
do that here."""
if Key == self.__dict__['ID_Field']: raise AttributeError,Key + ' is read-only'
if Key in self.__dict__['Fields']:
self.__dict__[Key] = Value
else: raise AttributeError,Key

def __str__(self):
objstr = "Database Object from " + self.__dict__['Table']+": " + ("=" * 20) + " "
for key in self.__dict__.keys():
if key in self.__dict__['Fields']: objstr = objstr + str(key) + ' = ' + str(self.__dict__[key]) + ' '
return objstr

def __getlvals__(self,FTable,Field,Filter="",Order=""):
"""An internal function to return values from a linked table,
say, for example, getting all the articles in a category from
blogs_article_categories."""
FKey = ''
KeyFields = self.__dict__['__ftables'][FTable]
for tpl in KeyFields:
if Field in tpl: FKey,f=tpl

if FKey: return GetValuesCollection(Tables={('', self.Table):[], ('',FTable):['*']}, Fields=[((self.Table,Field),(FTable,FKey))], Condition=Filter, Order=Order)
else: return None

def New(self):
"""Insert a new row in the database using the attributes of
this object. If ID isn't null, it just doesn't do anything. And it doesn't
check to see what fields are required by the database. I might fix that."""
if getattr(self,self.ID_Field) is None:
shortfields = self.__dict__['Fields'][:]
shortfields.remove(self.__dict__['ID_Field'])
values = self.__getvalues(shortfields)
ins = data.InsertQuery(self.__dict__['Table'],shortfields,values)
oid = data.ExecuteQuery(ins,DBNAME)
sel = data.SelectQuery(self.__dict__['Table'],[],'oid = ' + str(oid))
dictvals = data.ExecuteQuery(sel,DBNAME)
self.__setvalues(dictvals)

def Update(self):
"""Sets the fields in the database to the object's current
attribute values. The object has to already exist in the database."""
if getattr(self,self.ID_Field):
shortfields = self.__dict__['Fields'][:]
shortfields.remove(self.__dict__['ID_Field'])
values = self.__getvalues(shortfields)
upd = data.UpdateQuery(self.__dict__['Table'], shortfields, values, self.__dict__['ID_Field'] + "=" + str(getattr(self,self.ID_Field)))
data.ExecuteQuery(upd,DBNAME)

Hey, this is fun. I don't even have to think up stuff to write. I just post code. No wonder TechBloggers are so prolific. Except no one's probably reading this anymore...

{ link me }

ARJLog is now defunct. For more exciting stuff see: