[Zope] Discussion: Best practice site struture (LONG)
Max M
maxm@mxm.dk
Fri, 05 Apr 2002 11:48:05 +0200
Hmm this i quite a long rambling, but I think it sums up most of the
problems and approaches I have experienced with Zope developing rather
large sites.
There is often complaints about "best practice" when building Zope
sites. I try to summ it up here as I see it. Unfortunately it cannot be
said in a few lines.
Hope somebody will comment on it and is interrested in discussing Zope
from the birds perspective.
If there is something I have misunderstood, and some best practises I
have been too dense to catch _please_ tell me.
Btw. English is not my native toungue so there _will_ be speling errors.
--------------------------------
Discussion: Best practice site struture (LONG)
==============================================
But hopefully readable :-)
I have been building sites in Zope for the longest while. Well at least
a couple of years by now. As my sites have grown larger and more complex
I have noticed some patterns, and weaknesses in the way Zope is used for
building sites.
So I have usually found some reasonable solutions to the problems.
Typical structure of site
-------------------------
When you start building sites in Zope you often make a directory
structure in which you put all your content. Just like you would do in a
normal webserver like Apache::
Home/
acl_users/
user 1
user 2
News/
article 1
article 2
Products/
Product 1
Product 2
About/
History
Where to find us
Adresses
Jobs
Contact/
This has the advantage of being conceptually easy. And it works sort of
ok if there is little relationship between objects in the site. It is
rather difficult to assign rights to individual users. Ie. how do yo
assign rights to edit 'articles' but not 'products' etc.
Another more complicated examples is a school. An intuitive structure
would be::
Home/
acl_users/
teacher 1 # roles = ['Teacher']
teacher 2 # roles = ['Teacher']
student 1 # roles = ['Student']
student 2 # roles = ['Student']
student 3 # roles = ['Student']
classes/
class 1/
teacher 1/
material 1
material 2
student 1/
content 1
content 2
student 2/
content 3
content 4
subject 1
subject 2
class 2/
teacher 1/
material 2
teacher 2/
material 3
material 4
student 2/
content 3
content 4
student 3/
content 3
content 4
subject 2
subject 3
But this has several problems.
For one thing it is hell to assing rights with many students, teacher
and classes.
And a user that is in more than one class will have copies of his
contents lying around several different places on the site. That breaks
with the DRY principle. (Don't Repeat Yourself)
So We will need to give the site a new structure. One that is much like
the one used in the CMF::
Home/
acl_users/
teacher 1 # roles = ['Teacher']
teacher 2 # roles = ['Teacher']
student 1 # roles = ['Student']
student 2 # roles = ['Student']
student 3 # roles = ['Student']
members/
teacher 1/
material 1
material 2
teacher 2/
material 3
material 4
student 1/
content 1
content 2
student 2/
content 3
content 4
student 3/
content 3
content 4
classes/
class 1/
subject 1
subject 2
class 2/
subject 2
subject 3
Here we will need to relate the students with the classes they attend.
This is typically done by making a list box that saves the id or the
path of the classes for each user.
This is one of the current problems in Zope. This is a general and often
reused practice, and should really be refactored out into a common
module. (I have tried to do this with my mxmRelations product.)
The common approach with a list of id's also has the problem that it is
one way only. If you store the relations under the student with a list
class id's, how do you then find which students attends which classes
from the class' point of view? You have to traverse all students and see
which students belongs to the class you are interrested in. Either by
brute force or by using the Catalog.
The CMF structure
-----------------
I have not used the CMF a lot, but as far as i can figure all content is
stored in a Members folder. This would cause a structure like::
Home/
acl_users/
teacher 1 # roles = ['Teacher']
teacher 2 # roles = ['Teacher']
student 1 # roles = ['Student']
student 2 # roles = ['Student']
student 3 # roles = ['Student']
members/
teacher 1/
material 1
material 2
subject 1
subject 2
subject 2
teacher 2/
material 3
material 4
subject 3
student 1/
content 1
content 2
student 2/
content 3
content 4
student 3/
content 3
content 4
classes/
class 1
class 2
Here the subject that is taught in each class belongs to a single teacher.
This is a particularly bad idea. What if a teacher stops working at the
school. Copright laws aside, who takes responsibility of his materials
and subjects? As soon as they are moved to another teachers folder, all
relations between classes are most likely broken.
Oh and who adds new classes by the way?
Furthermore the subjects under each class sticks out like a sore thumb
to me. Should subjects really be stored under the classes as the only
type of objects?
Wouldn't it be more natural to generalize that too?
Home/
acl_users/
teacher 1 # roles = ['Teacher']
teacher 2 # roles = ['Teacher']
student 1 # roles = ['Student']
student 2 # roles = ['Student']
student 3 # roles = ['Student']
members/
teacher 1/
material 1
material 2
teacher 2/
material 3
material 4
student 1/
content 1
content 2
student 2/
content 3
content 4
student 3/
content 3
content 4
subjects/
subject 1
subject 2
subject 2
subject 3
classes/
class 1
class 2
Model View Controller in Zope
-----------------------------
Most software with a user interface is built using the Model View
Controller (MVC) principle.
In a standard .php or .asp (.*p) project it is rather easy to seperate
the three parts as the relational database holds the model, the Views
are generated on different .*p pages, and the controllers are also built
on their own .*p page.
In zope the model and the view is part of the same structure if you are
not carefull.
The taxonomy of a site vs. the model
------------------------------------
In my example here I have restructured the site so that it makes the
most technical meaning, and avoids redundancy.
But for the users point of view it now has a totally bizarre structure.
Ie. if you are at "home/class 1" and you need to find "content 1" of
"student 1" how should that be listed? We could have a list of members
and then the name of all the members in that class. Both teachers and
students. Perhaps with the type of member beside, like::
teacher 1 (teacher)
teacher 2 (teacher)
student 1 (student)
student 2 (student)
student 3 (student)
Then the user would click in the "student 1" link and get to:
"home/members/user 1/content 1"
*Crunch* <- That is the sound of a mental model crashing. My bet is that
the user expected to get to::
"home/class 1/user 1/content 1"
It would perhaps be a little better if the user got to::
"home/class 1/members/user 1/content 1"
But not a lot.
There is a contradiction between the sites natural taxonomy (Tree
structure of subjects) and the the way the the site needs to be built to
avoid redundancy and support relations between objects efficiently.
I believe that the problem with structuring content in Zope is that it
severely
advocates mixing Models and views. All the How-To's and the Zope book
also support this as the "right way" to build Zope sites.
We need a better way to separate models from the views
------------------------------------------------------
When building a site in zope we need two seperate hierachal structures.
One for the model/content, and another for the view/presentation
Something like::
The site/
model/
content 1
content 2
content 3
taxonomy/
subject 1/
subject 2/
subject 3
subject 4/
subject 5/
subject 6/
subject 7/
The part that should be visible to the general user is the taxonomy.
This is how the site is viewed and presented. This is where the layout
and the navigations structure is seen. There should be _no_ content
objects in a subjects folder. Only methods used for viewing objects, and
lists of objects.
A school could then have the following structure::
The site/
# The model
acl_users/
teacher 1 # roles = ['Teacher']
teacher 2 # roles = ['Teacher']
student 1 # roles = ['Student']
student 2 # roles = ['Student']
student 3 # roles = ['Student']
subjects/
subject 1
subject 2
subject 2
subject 3
classes/
class 1
class 2
members/
teacher 1/
material 1
material 2
teacher 2/
material 3
material 4
student 1/
content 1
content 2
student 2/
content 3
content 4
student 3/
content 3
content 4
# the taxonomy
Home/
classes/
class 1/
subjects/
students/
student 1/
content 1
content 2
teachers/
teacher 1/
material 1
material 2
... etc.
But then how do we map the content to the structure? There are several
problems in this.
One way, is to use the catalog to pull out the content from the model
part of the site. But again this is subject to all the problems
mentioned above when relating objects.
Another way I have thought about recently is to build in the relations
in the subject folders. So that that if "subject 1" should present
"content 1" and "content 2", there would be generalized methods
for getting "related" objects like::
content2.subjectIds()
content2.subjectValues()
content2.subjectItems()
Doing this way in Zope right now is really not easy because it lacks a
general way of relating objects 2 ways. My mxmRelations products takes
us some of the way, but not quite.
There is also still the problem with the paths to the objects. The
content should ideally show up as::
home/subject 1/content 1
and not as::
home/model/content 1
or::
home/subject 1/model/content 1
It would be rather trivial to subclass a Folder class and add the
possibility to and relate objects to it. Also writing the subject*()
methods would be rather trivial, but if you want to show an object as::
home/subject 1/content 1
You can unfortunately not do that, as several objects can have the same
id, all over the model part of the site.
So I guess that the best we can do is to call the content part of the
site something sensible, and hope that the user doesn't get too confused::
home/subject 1/content/content 1
This also solves the problem of not having to assign rights in two
seperate structures.
Short piece of code
-------------------
I would like to show a short piece of code for why this structure can be
smart.
I have the following taxonomy::
home/
class 1
students
teachers
class 2
students
teachers
class 1 has student 1, student 2, teacher 1 and teacher 2 related to it.
So how do I show which teacher a student has? I call::
I would create a method "teachers()" in the
student1Teachers = self.class1.subjectValues('teacher')
Shared ownership and the Zmi interface
--------------------------------------
A problem with putting all the shared objects in their own folders is
that it breaks the sites internal logic for the "Members" of the site.
Those who edit and update content. They can go to their own folder and
add objects. But how do they add/edit objects in the shared folders?
If a teacher needs to add or edit a subject that is not in his own
folder, how does he then get to it?::
subjects/
subject 1
subject 2
subject 2
subject 3
classes/
class 1
class 2
members/
teacher 1/
material 1
material 2
teacher 2/
material 3
material 4
student 1/
content 1
content 2
student 2/
content 3
content 4
student 3/
content 3
content 4
Imagine if the teacher goes to "home/members/teacher 1/manage" to log on
to the site to be able to edit the content of his folder. All he can see
is::
user 1/
material 1
material 2
There are no signs of the subjects anywhere in sight. So he must
manually go to "home/subjects/manage" to add/edit subjects. And for each
new content type that is available one the site he must do this. That sucks.
The idea of letting a user log on in the root of the management
interface "home/manage" is also a bad idea. There is probably a lot of
stuff in it that will confuse most users. Users should really only see
what they can change.
I believe that the best solution to this problem is to make another tab
in the zmi in the member folder. It should be directly beside the
"Contents" tab, and could be called "Shared contents." In it there
should be a list of the objects that the user has rights to add or edit
or delete objects in.
So the user from his management interface he might see something like::
user 1 [Contents]/
material 1
material 2
user 1 [Shared contents]/
subjects/
subject 1
subject 2
subject 2
subject 3
classes/
class 1
class 2
That's all folks!
regards Max M