[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>