[Zope] ANN: New product for managing relations

maxm maxm@mxm.dk
Sun, 13 Jan 2002 13:13:06 +0100


It is stable and functional, but only used in one project. So I considder it
version 0.0.1.

http://www.zope.org/Members/maxm/productList/mxmRelations/


------------------------------------------------

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 very simple API with only five methods::

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


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

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

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

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

        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, or lists of
checkboxes.
            "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".
            """

    So now the site from before will have the following structure::

        relations_class_students # instance of the relation product

        classes/
            class_1
            class_2
            class_3
            ... etc.

        students/
            student_1
            student_2
            student_3
            student_4

    And the code to make a list widget is::

        <select name="students_in_class:list" multiple>
        <dtml-in "relations_class_students.getSmartList(this(),
students.objectValues())" sort=id>
            <option value="<dtml-var path>"<dtml-if selected
                > selected</dtml-if>><dtml-var title></option>
        </dtml-in>
        </select>