コード例 #1
0
ファイル: domain.py プロジェクト: mohalfaki/bungeni-portal
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")
コード例 #2
0
ファイル: domain.py プロジェクト: mohalfaki/bungeni-portal
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"]
コード例 #3
0
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")
コード例 #4
0
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")
コード例 #5
0
ファイル: domain.py プロジェクト: mohalfaki/bungeni-portal
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)
コード例 #6
0
ファイル: domain.py プロジェクト: mohalfaki/bungeni-portal
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")
コード例 #7
0
ファイル: domain.py プロジェクト: mohalfaki/bungeni-portal
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")
コード例 #8
0
ファイル: domain.py プロジェクト: mohalfaki/bungeni-portal
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"]
コード例 #9
0
ファイル: domain.py プロジェクト: bungeni-org/bungeni.main
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"
コード例 #10
0
ファイル: domain.py プロジェクト: mohalfaki/bungeni-portal
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")
コード例 #11
0
ファイル: domain.py プロジェクト: mohalfaki/bungeni-portal
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")
コード例 #12
0
ファイル: domain.py プロジェクト: mohalfaki/bungeni-portal
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")
コード例 #13
0
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
コード例 #14
0
ファイル: domain.py プロジェクト: mohalfaki/bungeni-portal
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")
コード例 #15
0
class AttachedFile(Entity):
    """Files attached to a parliamentary item.
    """

    versions = one2many("versions",
                        "bungeni.models.domain.AttachedFileVersionContainer",
                        "content_id")
コード例 #16
0
ファイル: domain.py プロジェクト: mohalfaki/bungeni-portal
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
コード例 #17
0
ファイル: domain.py プロジェクト: mohalfaki/bungeni-portal
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
コード例 #18
0
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")
コード例 #19
0
ファイル: domain.py プロジェクト: mohalfaki/bungeni-portal
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
コード例 #20
0
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")
コード例 #21
0
ファイル: domain.py プロジェクト: mohalfaki/bungeni-portal
class Government(Group):
    """A government.
    """
    sort_on = ["start_date"]
    ministries = one2many("ministries",
                          "bungeni.models.domain.MinistryContainer",
                          "parent_group_id")
コード例 #22
0
ファイル: domain.py プロジェクト: mohalfaki/bungeni-portal
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
コード例 #23
0
ファイル: domain.py プロジェクト: mohalfaki/bungeni-portal
class DocVersion(Version):
    """A version of a document.
    """
    # !+version_feature_attachment
    files = one2many("files",
                     "bungeni.models.domain.AttachmentVersionContainer",
                     "head_id")
コード例 #24
0
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
コード例 #25
0
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")
コード例 #26
0
ファイル: domain.py プロジェクト: mohalfaki/bungeni-portal
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")
コード例 #27
0
ファイル: domain.py プロジェクト: mohalfaki/bungeni-portal
class Speech(HansardItem):
    """
    A single speech made in a plenary or committee sitting
    """

    versions = one2many("versions",
                        "bungeni.models.domain.SpeechVersionContainer",
                        "content_id")
コード例 #28
0
ファイル: domain.py プロジェクト: mohalfaki/bungeni-portal
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")
コード例 #29
0
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")
コード例 #30
0
ファイル: domain.py プロジェクト: mohalfaki/bungeni-portal
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")
コード例 #31
0
ファイル: model.py プロジェクト: BenoitTalbot/bungeni-portal
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))
コード例 #32
0
ファイル: orm.py プロジェクト: BenoitTalbot/bungeni-portal
 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
コード例 #33
0
ファイル: orm.py プロジェクト: BenoitTalbot/bungeni-portal
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)