class Ministry(Group): """A government ministry. """ ministers = one2many("ministers", "bungeni.models.domain.MinisterContainer", "group_id") questions = one2many("questions", "bungeni.models.domain.QuestionContainer", "ministry_id") bills = one2many("bills", "bungeni.models.domain.BillContainer", "ministry_id")
class Motion(ParliamentaryItem): cosignatory = one2many("cosignatory", "bungeni.models.domain.CosignatoryContainer", "item_id") event = one2many("event", "bungeni.models.domain.EventItemContainer", "item_id") versions = one2many("versions", "bungeni.models.domain.MotionVersionContainer", "content_id") sort_on = ParliamentaryItem.sort_on + ["motion_number", "submission_date"]
class Office(Group): """Parliamentary Office like speakers office, clerks office etc. Internal only. """ officemembers = one2many("officemembers", "bungeni.models.domain.OfficeMemberContainer", "group_id") title_types = one2many("title_types", "bungeni.models.domain.TitleTypeContainer", "group_id")
class ParliamentaryItem(Entity): """ """ interface.implements(interfaces.IBungeniContent, interfaces.IBungeniParliamentaryContent, interfaces.ITranslatable) # interfaces.IHeadFileAttachments) sort_on = ["parliamentary_items.status_date"] sort_dir = "desc" sort_replace = {"owner_id": ["last_name", "first_name"]} files = one2many("files", "bungeni.models.domain.AttachedFileContainer", "item_id") signatories = one2many("signatories", "bungeni.models.domain.SignatoryContainer", "item_id") # !+NAMING(mr, jul-2011) plural! event = one2many("event", "bungeni.models.domain.EventItemContainer", "item_id") # !+NAMING(mr, jul-2011) inconsistent... should be assigned_groups assignedgroups = one2many( "assignedgroups", "bungeni.models.domain.GroupGroupItemAssignmentContainer", "item_id") # votes # schedule # object log # changes - @auditable, set as a property # versions - @versionable, set as a property def _get_workflow_date(self, *states): """ (states:seq(str) -> date Get the date of the most RECENT workflow transition to any one of the workflow states specified as input parameters. Returns None if no such workflow states has been transited to as yet. """ assert states, "Must specify at least one workflow state." # order of self.changes is chronological--we want latest first for c in reversed(self.changes): if c.action != "workflow": continue extras = c.extras if extras: if extras.get("destination") in states: return c.date_active @property def submission_date(self): # As base meaning of "submission_date" we take the most recent date # of workflow transition to "submit" to clerk. Subclasses may need # to overload as appropriate for their respective workflows. return self._get_workflow_date("submitted", "gazetted")
class Constituency(Entity): """A locality region, which elects an MP. """ cdetail = one2many("cdetail", "bungeni.models.domain.ConstituencyDetailContainer", "constituency_id") parliamentmembers = one2many( "parliamentmembers", "bungeni.models.domain.MemberOfParliamentContainer", "constituency_id") #sort_replace = {"province_id": ["province"], "region_id": ["region"]} interface.implements(interfaces.ITranslatable)
class Motion(ParliamentaryItem, _AdmissibleMixin): cosignatory = one2many("cosignatory", "bungeni.models.domain.CosignatoryContainer", "item_id") event = one2many("event", "bungeni.models.domain.EventItemContainer", "item_id") versions = one2many("versions", "bungeni.models.domain.MotionVersionContainer", "content_id") sort_on = ParliamentaryItem.sort_on + ["motion_number"] @property def notice_date(self): return self._get_workflow_date("scheduled")
class Bill(ParliamentaryItem): cosignatory = one2many("cosignatory", "bungeni.models.domain.CosignatoryContainer", "item_id") event = one2many("event", "bungeni.models.domain.EventItemContainer", "item_id") assignedgroups = one2many("assignedgroups", "bungeni.models.domain.GroupGroupItemAssignmentContainer", "item_id") versions = one2many("versions", "bungeni.models.domain.BillVersionContainer", "content_id") @property def submission_date(self): return self._get_workflow_date("working_draft")
class Bill(ParliamentaryItem): cosignatory = one2many("cosignatory", "bungeni.models.domain.CosignatoryContainer", "item_id") event = one2many("event", "bungeni.models.domain.EventItemContainer", "item_id") assignedgroups = one2many( "assignedgroups", "bungeni.models.domain.GroupGroupItemAssignmentContainer", "item_id") versions = one2many("versions", "bungeni.models.domain.BillVersionContainer", "content_id") sort_on = ParliamentaryItem.sort_on + ["submission_date"]
class DebateRecord(Entity): """Debate record object associated with a sitting """ available_dynamic_features = [ "audit", "version", "workspace", "notification", "email", "user_assignment" ] interface.implements(interfaces.IDebateRecord) media = one2many("media", "bungeni.models.domain.DebateMediaContainer", "debate_record_id") takes = one2many("takes", "bungeni.models.domain.DebateTakeContainer", "debate_record_id") type = "debate_record"
class GroupSitting(Entity): """Scheduled meeting for a group (parliament, committee, etc). """ interface.implements(interfaces.ITranslatable) __dynamic_features__ = True # !+ False attendance = one2many("attendance", "bungeni.models.domain.GroupSittingAttendanceContainer", "group_sitting_id") items = one2many("items", "bungeni.models.domain.ItemScheduleContainer", "group_sitting_id") sreports = one2many("sreports", "bungeni.models.domain.Report4SittingContainer", "group_sitting_id")
class ParliamentaryItem(Entity): """ """ __dynamic_features__ = True interface.implements( interfaces.IBungeniContent, interfaces.IBungeniParliamentaryContent, interfaces.ITranslatable ) sort_on = ["parliamentary_items.status_date"] sort_dir = "desc" sort_replace = {"owner_id": ["last_name", "first_name"]} files = one2many("files", "bungeni.models.domain.AttachedFileContainer", "item_id") signatories = one2many("signatories", "bungeni.models.domain.SignatoryContainer", "item_id") # !+NAMING(mr, jul-2011) plural! event = one2many("event", "bungeni.models.domain.EventContainer", "head_id") # votes # schedule # object log # changes - @auditable, set as a property # versions - @versionable, set as a property def _get_workflow_date(self, *states): """ (states:seq(str) -> date Get the date of the most RECENT workflow transition to any one of the workflow states specified as input parameters. Returns None if no such workflow states has been transited to as yet. """ assert states, "Must specify at least one workflow state." # merge into Session to avoid sqlalchemy.orm.exc.DetachedInstanceError # when lazy loading; # order of self.changes is chronological--we want latest first for c in reversed(get_changes(Session().merge(self), "workflow")): if c.extras: if c.extras.get("destination") in states: return c.date_active @property def submission_date(self): # As base meaning of "submission_date" we take the most recent date # of workflow transition to "submit" to clerk. Subclasses may need # to overload as appropriate for their respective workflows. return self._get_workflow_date("submitted")
class GroupSitting(Entity): """Scheduled meeting for a group (parliament, committee, etc). """ interface.implements(interfaces.ITranslatable) attendance = one2many("attendance", "bungeni.models.domain.GroupSittingAttendanceContainer", "group_sitting_id") items = one2many("items", "bungeni.models.domain.ItemScheduleContainer", "group_sitting_id") sreports = one2many("sreports", "bungeni.models.domain.Report4SittingContainer", "group_sitting_id") @property def short_name(self): return self.start_date.strftime("%d %b %y %H:%M")
class Group(Entity): """ an abstract collection of users """ __dynamic_features__ = True # !+ False interface.implements(interfaces.IBungeniGroup, interfaces.ITranslatable) sort_on = ["short_name", "full_name"] sort_dir = "asc" sort_replace = { "group_id": [ "short_name", ] } #users = one2many("users", # "bungeni.models.domain.GroupMembershipContainer", "group_id") #sittings = one2many("sittings", # "bungeni.models.domain.GroupSittingContainer", "group_id") addresses = one2many("addresses", "bungeni.models.domain.GroupAddressContainer", "group_id") def active_membership(self, user_id): session = Session() query = session.query(GroupMembership).filter( sql.and_(GroupMembership.group_id == self.group_id, GroupMembership.user_id == user_id, GroupMembership.active_p == True)) if query.count() == 0: return False else: return True
class Hansard(ParliamentaryItem): """ The hansard report of a sitting It is made up of all the speechs of a sitting proceeding """ speeches = one2many("speech", "bungeni.models.domain.SpeechContainer", "speech_id")
class AttachedFile(Entity): """Files attached to a parliamentary item. """ versions = one2many("versions", "bungeni.models.domain.AttachedFileVersionContainer", "content_id")
class Group(Principal): """An abstract collection of users. """ principal_type = "group" available_dynamic_features = ["address"] interface.implements(interfaces.IBungeniGroup, interfaces.ITranslatable, interfaces.ISerializable) publications = one2many("publications", "bungeni.models.domain.ReportContainer", "group_id") def __init__(self, **kw): super(Group, self).__init__(**kw) def on_create(self): """Application-internal creation logic i.e. logic NOT subject to config. """ # requires self db id to have been updated from bungeni.core.workflows import utils utils.assign_ownership(self) utils.unset_group_local_role(self) def active_membership(self, user_id): session = alchemist.Session() query = session.query(GroupMembership).filter( sql.and_(GroupMembership.group_id == self.group_id, GroupMembership.user_id == user_id, GroupMembership.active_p == True)) if query.count() == 0: return False else: return True
class Question(ParliamentaryItem, _AdmissibleMixin): #supplementaryquestions = one2many("supplementaryquestions", #"bungeni.models.domain.QuestionContainer", "supplement_parent_id") event = one2many("event", "bungeni.models.domain.EventItemContainer", "item_id") cosignatory = one2many("cosignatory", "bungeni.models.domain.CosignatoryContainer", "item_id") versions = one2many("versions", "bungeni.models.domain.QuestionVersionContainer", "content_id") sort_on = ParliamentaryItem.sort_on + ["question_number"] def getParentQuestion(self): if self.supplement_parent_id: session = Session() parent = session.query(Question).get(self.supplement_parent_id) return parent.short_name
class AgendaItem(AdmissibleMixin, Doc): """Generic Agenda Item that can be scheduled on a sitting. """ dynamic_features = ["audit", "version", "attachment", "schedule"] interface.implements( interfaces.IBungeniParliamentaryContent, ) files = one2many("files", "bungeni.models.domain.AttachmentContainer", "head_id") # !+signatories on AgendaItems? signatories = one2many("signatories", "bungeni.models.domain.SignatoryContainer", "head_id") # !+events on AgendaItems? events = one2many("events", "bungeni.models.domain.EventContainer", "head_id")
class Group(Entity): """ an abstract collection of users """ interface.implements(interfaces.IBungeniGroup, interfaces.ITranslatable) #users = one2many("users", # "bungeni.models.domain.GroupMembershipContainer", "group_id") #sittings = one2many("sittings", # "bungeni.models.domain.GroupSittingContainer", "group_id") addresses = one2many("addresses", "bungeni.models.domain.GroupAddressContainer", "group_id") def active_membership(self, user_id): session = Session() query = session.query(GroupMembership).filter( sql.and_( GroupMembership.group_id == self.group_id, GroupMembership.user_id == user_id, GroupMembership.active_p == True ) ) if query.count() == 0: return False else: return True
class Sitting(Entity): """Scheduled meeting for a group (parliament, committee, etc). """ dynamic_features = ["audit", "version", "attachment"] interface.implements( interfaces.ITranslatable, ) attendance = one2many("attendance", "bungeni.models.domain.SittingAttendanceContainer", "sitting_id") items = one2many("items", "bungeni.models.domain.ItemScheduleContainer", "sitting_id") sreports = one2many("sreports", "bungeni.models.domain.Report4SittingContainer", "sitting_id") hansards = one2many("hansards", "bungeni.models.domain.HansardContainer", "sitting_id")
class Government(Group): """A government. """ sort_on = ["start_date"] ministries = one2many("ministries", "bungeni.models.domain.MinistryContainer", "parent_group_id")
class ItemSchedule(Entity): """For which sitting was a parliamentary item scheduled. """ discussions = one2many("discussions", "bungeni.models.domain.ItemScheduleDiscussionContainer", "schedule_id") def _get_item(self): """Query for scheduled item by type and ORM mapped primary key """ domain_class = DOMAIN_CLASSES.get(self.item_type, None) if domain_class is None: log.error("There is no item assigned to this schedule entry") return None return Session().query(domain_class).get(self.item_id) def _set_item(self, schedule_item): mapper = object_mapper(schedule_item) self.item_id = mapper.primary_key_from_instance(schedule_item)[0] self.item_type = schedule_item.type item = property(_get_item, _set_item) @property def getItem(self): s_item = self.item s_item.__parent__ = self return s_item @property def getDiscussion(self): s_discussion = self.discussion s_discussion.__parent__ = self return s_discussion
class DocVersion(Version): """A version of a document. """ # !+version_feature_attachment files = one2many("files", "bungeni.models.domain.AttachmentVersionContainer", "head_id")
class DocVersion(Version): """A version of a document. """ files = one2many("files", "bungeni.models.domain.AttachmentContainer", "head_id") #!+eventable items supporting feature "event": submission_date = None # !+bypass error when loading a doc version view
class MemberOfParliament(GroupMembership): """Defined by groupmembership and additional data. """ titles = one2many("titles", "bungeni.models.domain.MemberTitleContainer", "membership_id") addresses = one2manyindirect("addresses", "bungeni.models.domain.UserAddressContainer", "user_id")
class Sitting(Entity): """Scheduled meeting for a group (parliament, committee, etc). """ available_dynamic_features = [ "audit", "version", "attachment", "notification", "email" ] # !+SITTING_AUDIT cannot support audit/version without a sitting_audit db table ?! interface.implements(interfaces.ISitting, interfaces.ITranslatable, interfaces.IScheduleContent, interfaces.ISerializable) attendance = one2many("attendance", "bungeni.models.domain.SittingAttendanceContainer", "sitting_id") items = one2many("items", "bungeni.models.domain.ItemScheduleContainer", "sitting_id") sreports = one2many("sreports", "bungeni.models.domain.SittingReportContainer", "sitting_id")
class Speech(HansardItem): """ A single speech made in a plenary or committee sitting """ versions = one2many("versions", "bungeni.models.domain.SpeechVersionContainer", "content_id")
class User(Principal): """Domain Object For A User. General representation of a person. """ principal_type = "user" available_dynamic_features = ["address"] interface.implements(interfaces.IBungeniUser, interfaces.ITranslatable, interfaces.ISerializable) def __init__(self, login=None, **kw): if login: self.login = login super(User, self).__init__(**kw) self.salt = self._makeSalt() def on_create(self): from bungeni.core.workflows import utils utils.assign_ownership(self) def _makeSalt(self): return "".join(random.sample(string.letters[:52], 12)) def _password(): doc = "Set the password, encrypting it. Cannot retrieve." def fget(self): return None def fset(self, password): self.password = self.encode(password) return locals() _password = property(**_password()) def encode(self, password): return md5.md5(password + self.salt).hexdigest() def checkPassword(self, password_attempt): attempt = self.encode(password_attempt) return attempt == self.password def status(): doc = """A "status" attribute as alias onto "active_p" attribute.""" def fget(self): return self.active_p def fset(self, value): self.active_p = value return locals() status = property(**status()) delegations = one2many("delegations", "bungeni.models.domain.UserDelegationContainer", "user_id")
class Committee(Group): """A parliamentary committee of MPs. """ # !+ManagedContainer(mr, oct-2010) why do all these Managed container # attributes return a list of processed-id-derived strings instead of the # list of actual objects in question? # e.g. committee.committeemembers returns: ['obj-41', 'obj-42'] # committeemembers = one2many("committeemembers", "bungeni.models.domain.CommitteeMemberContainer", "group_id") committeestaff = one2many("committeestaff", "bungeni.models.domain.CommitteeStaffContainer", "group_id") agendaitems = one2many("agendaitems", "bungeni.models.domain.AgendaItemContainer", "group_id") sittings = one2many("sittings", "bungeni.models.domain.SittingContainer", "group_id") title_types = one2many("title_types", "bungeni.models.domain.TitleTypeContainer", "group_id")
class MemberOfParliament(GroupMembership): """Defined by groupmembership and additional data. """ sort_on = ["last_name", "first_name", "middle_name"] sort_replace = {"user_id": ["last_name", "first_name"], "constituency_id":["name"], "province_id":["name"], "region_id":["name"], "party_id":["name"]} titles = one2many("titles", "bungeni.models.domain.MemberRoleTitleContainer", "membership_id")
def add_container_property_to_model(domain_model, name, container_qualname, rel_attr): """Add an alchemist container attribute to domain_model. These attributes are only catalysed (re-instrumented on domain_model) if defined directly on domain_model i.e. are not inherited, must be defined on each class. """ assert not domain_model.__dict__.has_key(name), \ "type %s already has a %r attribute %r" % ( domain_model, name, domain_model.__dict__[name]) setattr(domain_model, name, one2many(name, container_qualname, rel_attr))
def configurable_properties(kls, mapper_properties): """Add properties, as per configured features for a domain type. """ # auditable if interfaces.IAuditable.implementedBy(kls): change_kls = getattr(domain, "%sChange" % (name)) mapper_properties["changes"] = relation( change_kls, backref="origin", lazy=False, cascade="all, delete-orphan", passive_deletes=False ) # versionable if interfaces.IVersionable.implementedBy(kls): kls.versions = one2many("versions", "bungeni.models.domain.%sVersionContainer" % (name), "content_id") return mapper_properties
def configurable_mappings(kls): """Add mappings, as per configured features for a domain type. Executed on adapters.load_workflow() """ name = kls.__name__ # auditable, determine properties and set mapper for change class/table if interfaces.IAuditable.implementedBy(kls): change_kls = getattr(domain, "%sChange" % (name)) change_tbl = getattr(schema, "%s_changes" % (schema.un_camel(name))) def changes_properties(item_class, change_tbl): return { "user": relation(domain.User, primaryjoin=(change_tbl.c.user_id == schema.users.c.user_id), uselist=False, lazy=True ), } mapper(change_kls, change_tbl, properties=changes_properties(kls, change_tbl) ) # versionable, determine properties and set mapper for version class/table if interfaces.IVersionable.implementedBy(kls): assert change_kls, "May not be IVersionable and not IAuditable" version_kls = getattr(domain, "%sVersion" % (name)) version_tbl = getattr(schema, "%s_versions" % (schema.un_camel(name))) def versions_properties(item_class, change_class, versions_table): props = { "change": relation(change_class, uselist=False), "head": relation(item_class, uselist=False) } return props mapper(version_kls, version_tbl, properties=versions_properties(kls, change_kls, version_tbl) ) # attachmentable, add related properties to version class/table if interfaces.IAttachmentable.implementedBy(kls): # !+ current constrain assert version_kls, "May not be IAttachmentable and not IVersionable" class_mapper(version_kls).add_property("attached_files", relation(domain.AttachedFileVersion, primaryjoin=rdb.and_( version_tbl.c.content_id == schema.attached_file_versions.c.item_id, version_tbl.c.version_id == schema.attached_file_versions.c.file_version_id ), foreign_keys=[ schema.attached_file_versions.c.item_id, schema.attached_file_versions.c.file_version_id ] ) ) version_kls.files = one2many("files", "bungeni.models.domain.AttachedFileContainer", "file_version_id") # finally, add any properties to the master kls itself def mapper_add_configurable_properties(kls): kls_mapper = class_mapper(kls) def configurable_properties(kls, mapper_properties): """Add properties, as per configured features for a domain type. """ # auditable if interfaces.IAuditable.implementedBy(kls): change_kls = getattr(domain, "%sChange" % (name)) mapper_properties["changes"] = relation(change_kls, backref="head", lazy=True, cascade="all, delete-orphan", passive_deletes=False ) # versionable if interfaces.IVersionable.implementedBy(kls): kls.versions = one2many("versions", "bungeni.models.domain.%sVersionContainer" % (name), "content_id") # attachmentable if interfaces.IAttachmentable.implementedBy(kls): pass # nothing to do return mapper_properties for key, prop in configurable_properties(kls, {}).items(): kls_mapper.add_property(key, prop) # mapper_add_configurable_properties(kls)