updated dump
[sfa.git] / sfa / storage / model.py
index 7bb12aa..8a23f06 100644 (file)
@@ -78,15 +78,50 @@ class AlchemyObj:
         xml_record.parse_dict (input_dict)
         return xml_record.toxml()
 
-    def dump(self, dump_parents=False):
-        for key in self.fields:
-            if key == 'gid' and self.gid:
-                gid = GID(string=self.gid)
-                print "    %s:" % key
-                gid.dump(8, dump_parents)
-            elif getattr(self,key,None):    
-                print "    %s: %s" % (key, getattr(self,key))
+    def dump(self, format=None, dump_parents=False):
+        if not format:
+            format = 'text'
+        else:
+            format = format.lower()
+        if format == 'text':
+            self.dump_text(dump_parents)
+        elif format == 'xml':
+            print self.save_to_string()
+        elif format == 'simple':
+            print self.dump_simple()
+        else:
+            raise Exception, "Invalid format %s" % format
+   
+    def dump_text(self, dump_parents=False):
+        # print core fields in this order
+        core_fields = ['hrn', 'type', 'authority', 'gid', 'date_created', 'last_updated']
+        print "".join(['=' for i in range(40)])
+        print "RECORD"
+        print "    hrn:", self.hrn
+        print "    type:", self.type
+        print "    authority:", self.authority
+        date_created = utcparse(datetime_to_string(self.date_created))    
+        print "    date created:", date_created
+        last_updated = utcparse(datetime_to_string(self.last_updated))    
+        print "    last updated:", last_updated
+        print "    gid:"
+        print "\t\t", self.get_gid_object().dump_string(8, dump_parents)  
+        
+        # print remaining fields
+        for attrib_name in dir(self):
+            # skip core fields
+            if attrib_name in core_fields:
+                continue
+            # skik callables 
+            attrib = getattr(self, attrib_name)
+            if callable(attrib):
+                continue
+            print "     %s: %s" % (attrib_name, attrib)
     
+    def dump_simple(self):
+        return "Record(record_id=%s, hrn=%s, type=%s, authority=%s, pointer=%s)" % \
+                (self.record_id, self.hrn, self.type, self.authority, self.pointer)
+      
 #    # only intended for debugging 
 #    def inspect (self, logger, message=""):
 #        logger.info("%s -- Inspecting AlchemyObj -- attrs"%message)
@@ -111,6 +146,8 @@ class RegRecord (Base,AlchemyObj):
     record_id           = Column (Integer, primary_key=True)
     # this is the discriminator that tells which class to use
     classtype           = Column (String)
+    # in a first version type was the discriminator
+    # but that could not accomodate for 'authority+sa' and the like
     type                = Column (String)
     hrn                 = Column (String)
     gid                 = Column (String)
@@ -136,12 +173,12 @@ class RegRecord (Base,AlchemyObj):
         if dict:                                self.load_from_dict (dict)
 
     def __repr__(self):
-        result="[Record id=%s, type=%s, hrn=%s, authority=%s, pointer=%s" % \
+        result="<Record id=%s, type=%s, hrn=%s, authority=%s, pointer=%s" % \
                 (self.record_id, self.type, self.hrn, self.authority, self.pointer)
         # skip the uniform '--- BEGIN CERTIFICATE --' stuff
         if self.gid: result+=" gid=%s..."%self.gid[28:36]
         else: result+=" nogid"
-        result += "]"
+        result += ">"
         return result
 
     @validates ('gid')
@@ -165,51 +202,123 @@ class RegRecord (Base,AlchemyObj):
         self.last_updated=now
 
 ##############################
-class RegUser (RegRecord):
-    __tablename__       = 'users'
-    # these objects will have type='user' in the records table
-    __mapper_args__     = { 'polymorphic_identity' : 'user' }
-    record_id           = Column (Integer, ForeignKey ("records.record_id"), primary_key=True)
-    email               = Column ('email', String)
-    
-    # append stuff at the end of the record __repr__
-    def __repr__ (self): 
-        result = RegRecord.__repr__(self).replace("Record","User")
-        result.replace ("]"," email=%s"%self.email)
-        return result
-    
-    @validates('email') 
-    def validate_email(self, key, address):
-        assert '@' in address
-        return address
-
+# all subclasses define a convenience constructor with a default value for type, 
+# and when applicable a way to define local fields in a kwd=value argument
+####################
 class RegAuthority (RegRecord):
     __tablename__       = 'authorities'
     __mapper_args__     = { 'polymorphic_identity' : 'authority' }
     record_id           = Column (Integer, ForeignKey ("records.record_id"), primary_key=True)
     
+    def __init__ (self, **kwds):
+        # fill in type if not previously set
+        if 'type' not in kwds: kwds['type']='authority'
+        # base class constructor
+        RegRecord.__init__(self, **kwds)
+
     # no proper data yet, just hack the typename
     def __repr__ (self):
         return RegRecord.__repr__(self).replace("Record","Authority")
 
+####################
+# slice x user (researchers) association
+slice_researcher_table = \
+    Table ( 'slice_researcher', Base.metadata,
+            Column ('slice_id', Integer, ForeignKey ('records.record_id'), primary_key=True),
+            Column ('researcher_id', Integer, ForeignKey ('records.record_id'), primary_key=True),
+            )
+
+####################
 class RegSlice (RegRecord):
     __tablename__       = 'slices'
     __mapper_args__     = { 'polymorphic_identity' : 'slice' }
     record_id           = Column (Integer, ForeignKey ("records.record_id"), primary_key=True)
-    
+    #### extensions come here
+    reg_researchers     = relationship \
+        ('RegUser', 
+         secondary=slice_researcher_table,
+         primaryjoin=RegRecord.record_id==slice_researcher_table.c.slice_id,
+         secondaryjoin=RegRecord.record_id==slice_researcher_table.c.researcher_id,
+         backref="reg_slices_as_researcher")
+
+    def __init__ (self, **kwds):
+        if 'type' not in kwds: kwds['type']='slice'
+        RegRecord.__init__(self, **kwds)
+
     def __repr__ (self):
         return RegRecord.__repr__(self).replace("Record","Slice")
 
+####################
 class RegNode (RegRecord):
     __tablename__       = 'nodes'
     __mapper_args__     = { 'polymorphic_identity' : 'node' }
     record_id           = Column (Integer, ForeignKey ("records.record_id"), primary_key=True)
     
+    def __init__ (self, **kwds):
+        if 'type' not in kwds: kwds['type']='node'
+        RegRecord.__init__(self, **kwds)
+
     def __repr__ (self):
         return RegRecord.__repr__(self).replace("Record","Node")
 
+####################
+class RegUser (RegRecord):
+    __tablename__       = 'users'
+    # these objects will have type='user' in the records table
+    __mapper_args__     = { 'polymorphic_identity' : 'user' }
+    record_id           = Column (Integer, ForeignKey ("records.record_id"), primary_key=True)
+    #### extensions come here
+    email               = Column ('email', String)
+    # can't use name 'keys' here because when loading from xml we're getting
+    # a 'keys' tag, and assigning a list of strings in a reference column like this crashes
+    reg_keys            = relationship \
+        ('RegKey', backref='reg_user',
+         cascade="all, delete, delete-orphan")
+    
+    # so we can use RegUser (email=.., hrn=..) and the like
+    def __init__ (self, **kwds):
+        # handle local settings
+        if 'email' in kwds: self.email=kwds.pop('email')
+        if 'type' not in kwds: kwds['type']='user'
+        RegRecord.__init__(self, **kwds)
+
+    # append stuff at the end of the record __repr__
+    def __repr__ (self): 
+        result = RegRecord.__repr__(self).replace("Record","User")
+        result.replace (">"," email=%s"%self.email)
+        result += ">"
+        return result
+
+    @validates('email') 
+    def validate_email(self, key, address):
+        assert '@' in address
+        return address
+
+####################
+# xxx tocheck : not sure about eager loading of this one
+# meaning, when querying the whole records, we expect there should
+# be a single query to fetch all the keys 
+# or, is it enough that we issue a single query to retrieve all the keys 
+class RegKey (Base):
+    __tablename__       = 'keys'
+    key_id              = Column (Integer, primary_key=True)
+    record_id             = Column (Integer, ForeignKey ("records.record_id"))
+    key                 = Column (String)
+    pointer             = Column (Integer, default = -1)
+    
+    def __init__ (self, key, pointer=None):
+        self.key=key
+        if pointer: self.pointer=pointer
+
+    def __repr__ (self):
+        result="<key id=%s key=%s..."%(self.key_id,self.key[8:16],)
+        try:    result += " user=%s"%self.reg_user.record_id
+        except: result += " no-user"
+        result += ">"
+        return result
+
 ##############################
-# although the db needs of course to be reachable,
+# although the db needs of course to be reachable for the following functions
 # the schema management functions are here and not in alchemy
 # because the actual details of the classes need to be known
 # migrations: this code has no notion of the previous versions
@@ -244,6 +353,7 @@ def make_record_dict (record_dict):
     elif type=='node':
         result=RegNode (dict=record_dict)
     else:
+        logger.debug("Untyped RegRecord instance")
         result=RegRecord (dict=record_dict)
     logger.info ("converting dict into Reg* with type=%s"%type)
     logger.info ("returning=%s"%result)
@@ -256,3 +366,4 @@ def make_record_xml (xml):
     xml_dict = xml_record.todict()
     logger.info("load from xml, keys=%s"%xml_dict.keys())
     return make_record_dict (xml_dict)
+