You are not logged in Log in Join
You are here: Home » Members » maxm » Products By mxm » mxm Relation product

Log in
Name

Password

 
 

mxm Relation product

This is the mxmRelations product

It is made to solve a few common problems with Zope's object oriented database.

Normally in a relational database some tables will be used to show relations between rows. It can be both "one to many", and "many to many".

In Zope, doing the same thing is more difficult than it has to be.

The usual way of doing relations in Zope is by using the list widget in a zClass, or to store the relations in a home made list or dictionary for the pupose.

There is several problems with this approach. Let's take an examle.

The students in classes example

Imagine a school with several classes and several students. Some of the students wil take some of the classes. So theres is a relation between some students, and some classes.

A simple structure for this in Zope would be to have two folders "classes" and "students":

        classes/
            class_1
            class_2
            class_3
            ... etc.
        students/
            student_1
            student_2
            student_3
            student_4

Ordinarily in zope you would then write two classes something like:

        class Klass: # hmmm, stupid example
            def __init__(self, id, title):
                self.id    = id
                self.title = title
                self.students_in_class = []

        class Student: # hmmm, stupid example
            def __init__(self, id, title):
                self.id    = id
                self.title = title
                self.taking_classes = []

The problem is that the two classes have to maintain the same relations. So the picture could be like:

        class_1.students_in_class = ['student_1', 'student_2']
        class_2.students_in_class = ['student_3', 'student_4']
        class_3.students_in_class = ['student_1', 'student_4']

Or you might even keep backwards relations so that you can easily see whitch students takes which classes:

        student_1.taking_classes = ['class_1','class_3']
        student_2.taking_classes = ['class_1']
        student_3.taking_classes = ['class_2']
        student_4.taking_classes = ['class_2','class_3']

This is the bad situation where you have to keep two otherwise unrelated sets of relations up to date. In some cases the objects might even be spread out in different folders. Then it can get real ugly fast. You get direct paths in the code, duplication of code and functionality.

So then you want to make a listwidget where the user can select which classes that the students take:

        <select name="students_in_class:list" multiple>
        <dtml-in "students.objectValues()">
            <option value="<dtml-var "getId()">"><dtml-var "title_or_id()"></option>
        </dtml-in>
        </select>

And you want those selected to be hilited:

        <select name="students_in_class:list" multiple>
        <dtml-in "students.objectValues()">
            <option<dtml-if "getId() in taking_classes"
                > selected</dtml-if
                > value="<dtml-var "getId()">"><dtml-var "title_or_id()"></option>
        </dtml-in>
        </select>

Here is another problem. What if a student drops out, and is deleted from Zope, but is still in some of the "students_in_class" list under some of the class objects. How do we remove the dead relations? One way is to make code ignoring objects no longer in existance:

        <select name="students_in_class:list" multiple>
        <dtml-in "students.objectValues()">
        <dtml-try>
            <option<dtml-if "getId() in taking_classes"
                > selected</dtml-if
                > value="<dtml-var "getId()">"><dtml-var "title_or_id()"></option>
        <dtml-except>
        </dtml-try>
        </dtml-in>
        </select>

But that's not really a solution is it? And you have to do it for each and every object that reference another object. Again repetition of code and functionality.

Another problem is that a student also can be referenced in the student councel, the school mailinglist, the holiday list etc. Suddenly an object can be related to many different objects. And all of then have to have the same code doing the relational-housholding stuff. Boring!

So here's my solution

The mxmRelations product handles all the relations. Shows only the valid ones, and deletes the dead and rotten ones.

It has a rather simple API with a four main methods and a few others to make life more simple in the real world:

        ########################################################
        # Public methods.

        """
        These are most often used programatically. If you want the user to 
        relate objects via a webform, you probably want to use the "smart" 
        methods.
        """

        def relate(self, objs1, objs2):
            """
            Sets relations between objects.
            If there allready is a key it appends the relations
            else it creates a new key with a list of relations
            """    

        def unrelate(self, objs1, objs2):
            """
            Removes relations between objects
            """

        def delete(self, obj):
            """
            Removes all references to the object.
            Used ie. if an object is deleted.
            """

        def get(self, obj, meta_types=None):
            """
            Returns all relations to this object, or an empty list
            """

        #########################
        # Used when relating from web form. They make life very simple ;-)

        def getSmartList(self, obj, objects, meta_types=None):
            """
            returns a list of objects with parameters (id, title, path, 
            selected) Very usable for making selection widgets. "obj" is the 
            object that we are looking for relations too. "objects" is a 
            list of objects that we want to know whether they are related to 
            "obj".
            """

        def smartRelate(self, obj, pathsList):
            """
            Works in tandem with 'getSmartList' and takes what is generated
            by that and creates relations from them.
            """
            # list with empty string can be returned by
            # checkboxes with hidden '' as default

        This form "relate_object" creates a list of checkboxes, where 
        related objects are checked, "relations" is the relation object.

            <input type="hidden" name="my_relations:list:default" value="">
            <dtml-in "relations.getSmartList(this(), objects_to_relate_to)" sort=title>
                    <input type="checkbox" name="my_relations:list"
                           value="<dtml-var path>"<dtml-if selected> checked</dtml-if>>
            </dtml-in>

        This code takes the objects from the form and relates them to the 
        object:

            def relateStudentsAction(self, my_relations):
                "Relates objects to this object"
                self.relations.smartRelate(self, my_relations)
                return self.relate_object(self, self.REQUEST)

        #########################

        """

        The following methods are Used when copy/pasting or moving objects. 
        Most often you don't want to loose relations when you move an object 
        to somewhere else in the Zope hierachy. These methods helps you 
        avoid this.

        The general idea is that just before you move an object you deposit 
        your relations in the relations bank :-) The bank gives you a 
        receipt that you put in a safe place in the object you want to move.

        Then when your object has been moved it withdraws it's relations 
        again by handing back the receipt.

        """

        def deposit(self, obj):
            """
            Used to return a receipt in a "manage_beforeDelete()" hook.
            Puts the relations of an object in a deposit, and returns a receipt.
            receipt = self.relations.deposit(self)
            """

        def withdraw(self, obj, receipt):
            """
            Used to de-relate an object after a move in a "manage_afterAdd()" hook.
            When given an object and a receipt, it gets the relations belonging to
            the receipt out of the deposit, and relates them to the object.
            self.relations.withDraw(self, receipt)
            """

        An example:

            class TheObjectToMove:

                manage_beforeDelete(self, item, container):            
                    """
                    Put receipt in shortlived 'temp' attribute. Ie. 
                    "_receipt_for_relations" that only exists during then 
                    move "relations" is a relation object
                    """
                    self._receipt_for_relations = self.relations.deposit(self)

                def manage_afterAdd(self, item, container):

                    """
                    manage_afterAdd is also called in other circumstances 
                    than when an object has been moved. But if the objects 
                    has the "_receipts_for_relations" attribute we can be 
                    shure it is because of a move. That is if we remember to 
                    delete the attribute when we are done using it.
                    """

                    if hasattr(self, '_receipts_for_relations'):
                        self.relations.withdraw(self, self._receipts_for_relations)
                        del(self._receipts_for_relations)

Latest Release: 1.1.1
Last Updated: 2004-05-03 12:03:30
Author: maxm
License: GPL
Categories:
Maturity: Stable
  Information

Available Releases

Version Maturity Platform Released
mxmrelations111 Stable   2004-05-03 06:15:19
  mxmRelations.1.1.1.tar.gz (8 K) All md5
1.1.0 Stable   2004-02-09 03:46:03
  mxmRelations.1.1.0.zip (10 K) All md5