ZopeMag's mascot the ZOPE fish


Article Finder
People
Issue 6 - Revision 8  /   January 18, 2003 


 
  ZopeMag Links:
Latest Issue
About the Fish
Issue 10
Issue 09
Issue 08
Issue 07
Issue 06
Issue 05
Issue 04
Issue 03
Issue 02
Issue 01
 
 
Downloads
     
  Letter from the Editor:
   Issue 6

Interviews:
Each issue we interview important people in the Zope world.

  Alan Runyan

Articles:
Throughout the quarter we cover topics of interest to Zope developers, designers, and users.

  Intro to Archetypes

  Customized User Folders Revisited

  Plone Workflows

  Enriching Zope UIs With XML

Product Review:
Too many Products, too little time? ZopeMag keeps you up-to-date which Zope Products are worthwhile downloading.

  Formulator
  ZStyleSheets


Guides:
This quarter we bring you a new miniGuide and our first SuperGuide. These Guides give you the background knowledge you need to mastering Zope.

  miniGuide to importing a Website
  SuperGuide for Zope Newbies.
 
 
Downloads
     
  URLs / Download
Products we talk about in this issues Articles and Reviews

  Formulator
  ZSQL Catalog
     

Illustration by Lia Avant
tutorial
Customized User Folders Revisited

Customized User Folders Revisited
- Another look
- - - - - - - - - - - -

By Kristoph Kirchner | December 15, 2003

print

A short while ago, a ZopeMag reader suggested that I continue the 'Customized UserFolder' series by showing how writing your own UserFolder can help you provide user-specific content. This article gives some examples of this: for example, how to send mails containing News compilations to users who have asked to receive News items in certain pre-defined categories. first article

Introduction

The following article is meant for intermediate Zope users and those who are new to working with Zope together with a database and is meant to demonstrate how easy it is to create personalized Websites with Zope. This is done using a revised version of DBUserFolder (which was the topic of part two of the Customized User Folders series), i.e. a UserFolder that stored the user data in a database (in this case: MS Access).

In the first article we simply expanded the default Zope user folder by adding certain properties to it. In the second article we described how to store user data in an external database. This is useful when other applications have to have access to the user data. Further, more complex queries are possible and backing up the data is easier with a database. The present article expands on the previous one by giving examples for use of such customized user folders together with a database.

For the examples below you will need the revised DBUserFolder, which you can download here. The zipped file also contains zexp-files of the code examples described in this article. The earlier version of the DBUserFolder product used a table called UserTable. The new version uses a modified table of the same name, as well as several other tables. In the zip file you will find a file readme.txt which describes how to proceed with the examples, as well as a description of the tables.

First, let me shortly describe the three examples:

The first example shows how a programmmer can design a Webpage using different stylesheets depending on which stylesheets users have selected from a pre-defined list. The stylesheets are DTML Documents in Zope (called, for example, default.css). For each stylesheet there is an entry in a table Stylesheets. This table defines a unique ID, a name and a filename for each stylesheet.

The reason I call it a "fantasy" is because the mechanisms for this don't all exist in Zope 3X right now. But, I am confident that most of them will before a beta release.

The second example shows how to send e-mails to users who have selected up to three categories from a list of News categories. Whenever a new News item is added to a category, all users that selected the respective News category in their preferences receive an e-mail with the News item's headline and intro.

The third example describes how to render modules on a Webpage depending on which modules the logged-in user has selected from a list of available modules. The modules are defined in the database as HTML code containing TAL attributes.

As mentioned above, in the examples to be described in detail below we want to create Webpages depending on options that a logged-in user has selected. For this we need some extra fields in the user table (as well as some more tables in the database).

IDLong Integer
LoginText
LnameText
FnameText
EmailText
PasswordText
RolesText
DomainsText
StylesheetLong Integer
ModulesText
Pref1Long Integer
Pref2Long Integer
Pref3Long Integer
Table 1 -Field names and types in the table UserTable (MS Access database)

The table UserTable contains five more fields now than the earlier version of the DBUserFolder, which only used the first eight fields shown in Table 1. The field Stylesheet contains a stylesheet id which corresponds to an id from the Stylesheets table (see Table 2). This is the same for the fields Pref1, Pref2 and Pref3, which correspond to ids from the table Categories. The Modules field contains a list of module ids corresponding to ids from the Modules table (see Table 4). This list is stored as a string and needs to be converted to a Python list to be of use.

IDLong Integer
NameText
Table 2 - Field names and types for the tables Categories (MS Access database)


IDLong Integer
NameText
FilenameText
Table 3 - Field names and types in the table Stylesheets (MS Access database)


IDLong Integer
NameText
SourcecodeMemo
Table 4 - Table 1 - Field names and types in the table Modules (MS Access database)

Figure 1: Editing a DBUser

The figure above shows the modified edit page for a DBUser. This edit page contains checkboxes which are dynamically created from the Categories table. From the checkboxes, a user can select up to three categories to subscribe to (in the above example there are only two categories). Whenever a News item is added to one of the selected categories, the user receives a corresponding e-mail.

The Stylesheet drop-down menu is also created dynamically, this time from the Stylesheet table. The option values are the ids of the stylesheets, while the stylesheet names are shown in the menu (e.g. Default Stylesheet, New Stylesheet).

The Modules section is a multiple selection list which again is created dynamically from a table in the database, the Modules table.

User-specific Stylesheets

As mentioned above, for this example to work we need a table, which we have called Stylesheets, which contains the information about the various stylesheets available. Let's say this table contains the following two stylesheets:

IDNameFilename
1Default Stylesheetdefault.css
2New Stylesheetnew.css

Further, we have two DTML Documents called default.css and new.css in Zope, defining what links are supposed to look like when the respective stylesheet is used.

default.css:
	a   {font-family: Arial, Helvetica, sans-serif; font-size: 10px; }

new.css:
	a   {font-family: Times New Roman, serif; font-size: 12; }

The table UserTable contains a field called Stylesheet which holds the ID of a stylesheet. Using this field, we can retrieve the filename of the stylesheet selected from the Stylesheet table and use this filename in a Zope Page Template.

Let's say we have a Page Template standard_template.pt, which defines a macro that is to be used by all other pages of the Website so that all pages will have the same look. This macro can be programmed so that it uses whatever stylesheet is defined in the user's Stylesheet field. The following listing shows such a macro:

1.	<html metal:define-macro="main">
2.	<head>
3.	<title tal:content="template/title">The title</title>
4.	
5.	<link rel="stylesheet" tal:attributes="href python:here.py_getUserStylesheet(1)">
6.	</head>
7.	<body>
8.	
9.	<div metal:define-slot="mainslot">
10.	</div>
11.	</body>
12.   </html>
Listing 1 - ZPT standard_template.pt

In line 5 you can see that the href attribute of the link to the stylesheet is created using TAL. The tal:attributes expression makes use of a Python Script which is shown in the next listing:

1.	request = container.REQUEST
2.	
3.	userdata = container. acl_users.getUserQuery(login=request['AUTHENTICATED_USER'])
4.	if len(userdata) > 0:
5.	  userstylesheet = userdata[0]['stylesheet']
6.	else:
7.	  userstylesheet = 0
8.	if userstylesheet:
9.	  return container.acl_users.sql_getStylesheetByID(id=userstylesheet)[0]['filename']
10.	else:
11.	  return 'default.css'
Listing 2 - Python Script py_getUserStylesheet

The Python Script uses the Z SQL Method getUserQuery, which is defined in the acl_users folder. The Z SQL Method returns a user's data record (here of the user who is currently logged in, i.e. AUTHENTICATED_USER). If there is a record for the logged-in user, i.e. if the length of the Z SQL Method result is not zero, the value of the stylesheet field is stored in the variable userstylesheet.

If the Z SQL Method returns no data record, e.g. if AUTHENTICATED_USER is the anonymous user or the logged-in user is not defined in the DBUserFolder, the variable userstylesheet is set to 0. This is necessary for the if-construct in line 8. If the variable userstylesheet contains a value other than 0, the Python Script returns the filename for the stylesheet id stored in userstylesheet using the Z SQL Method sql_getStylesheetByID (line 9), which is also defined in the acl_users folder. If userstylesheet contains 0, the Python Script returns the default stylesheet 'default.css'.

When the macro is used in another Page Template, it calls the Python Script to see which stylesheet is to be used.

Sending E-Mails to Interested Users

For this example, let's say you have a News management system where you can add, edit and delete News items that consist of a headline, an intro text, a body text and a category. Now you want to give registered users of your Website the option of subscribing to up to three different News categories. Whenever a News item is added to a given category, an e-mail is to be sent to every registered user that has subscribed to the category. So, in the Python Script (or DTML Method or External Method, whichever you use) where the News item is actually created and stored, you need to insert the code that will send the e-mails.

For example, in a Python Script you could use the following line to redirect the browser to a DTML Method we've called send_mails.

# send mail to everyone who's interested in this category
return container.REQUEST.RESPONSE.redirect('send_mails?catid='+str(category)+'&headline=
'+str(headline)+'&intro='+str(intro))

The easiest way to send e-mails in Zope is to use a DTML Method and the dtml-sendmail-tag. The following listing shows what the DTML Method send_mails could look like.

1.	<html>
2.	<body>
3.	
4.	<dtml-try>
5.	<dtml-sendmail smtphost="smtp.mydomain.com">
6.	from: News Admin <news@mydomain.com>
7.	to: <dtml-in "sql_getUsersByCategory(catid=catid)">
<dtml-var "_['sequence-item']['Email']"><dtml-unless sequence-end>, </dtml-unless></dtml-in>
8.	
9.	Subject: News-Item: <dtml-var headline>
10.	
11.	New News-Item in category <dtml-var "sql_getCategoryByID(id=catid)[0]['Name']">:
12.	
13.	<dtml-var headline>
14.	
15.	<dtml-var intro>
16.	
17.	</dtml-sendmail>
18.	
19.	E-Mails sent to:
20.	<dtml-in "sql_getUsersByCategory(catid=catid)">
21.	<ul>
22.	<li><dtml-var "_['sequence-item']['Email']"></li>
23.	</ul>
24.	</dtml-in>
25.	<dtml-except>
26.	No E-Mails sent!
27.	</dtml-try>
28.	
29.	</body>
30.	</html>
Listing 3 - DTML Method send_mails

The important part of this code is in line 7. Here the Z SQL Method sql_getUsersByCategory (see Listing 4) is used to get all users that subscribed to the category for this News item. Iteration is performed through the list of users returned by the SQL query and their e-mail addresses are added to the to-line.

1.	Arguments:		catid
2.	
3.	select * from UserTable
4.	where 
5.	pref1 = <dtml-sqlvar catid type=int> or
6.	pref2 = <dtml-sqlvar catid type=int> or
7.	pref3 = <dtml-sqlvar catid type=int>
Listing 4 - Z SQL Method sql_getUsersByCategory

Since the categories that a user has subscribed to are stored in the three fields Pref1, Pref2 and Pref3, the Z SQL Method sql_getUsersByCategory has to reflect this and must return all users where the chosen category is stored in either the Pref1, the Pref2 or the Pref3 field (lines 5-7, Listing 4).

Showing User-specific Modules

For this example, we want to store module code in the database, retrieve it and render it on an index page. Let's say we have the following entry in the Modules table:

IDNameSourcecode
1First Module <div><h3>First Module</h3><ul><li tal:repeat="item python:here.objectValues()" tal:content="item/id"></li></ul></div>
Table 5 - Sample record in the table Modules (MS Access database)

In order to retrieve the modules selected by a user, we need to retrieve the value of the Modules field from UserTable. As mentioned above, this field contains a list of the module ids as a string, e.g. '1,3,7'. To be useful, this string will have to be split into a list whenever the field is used. How this is done is shown in Listing 5 below.

The following Python Script called py_getUserModules serves two purposes: 1) it returns the list of modules (line 15, Listing 5); and 2) it returns the 'Sourcecode' field of a specific module (line 20, Listing 5).

# Parameters: 	num=0, flag=0

1.	import string
2.	request = container.REQUEST
3.	RESPONSE =  request.RESPONSE
4.	
5.	umodules = container.acl_users.getUserQuery(login=request['AUTHENTICATED_USER'])
6.	
7.	if len(umodules) != 0:
8.	  umodules = umodules[0]['modules']
9.	  umodules_list = string.split(umodules, ',')
10.	else:
11.	  umodules_list = []
12.	
13.	if flag == 1:
14.	  # in case we just want the list
15.	  return umodules_list
16.	
17.	if len(umodules_list) != 0:
18.	  modulecode = container.acl_users.sql_getModuleByID(id=umodules_list[num])
19.	  if len(modulecode) != 0:
20.	    return modulecode[0]['Sourcecode']
21.	  else:
22.	    return None
23.	else:
24.	  return None
Listing 5 - The Python Script py_getUserModules

The Python Script has two parameters. The num parameter is used when a specific module is to be accessed. The flag parameter must be set to 1 if only the list of modules is to be returned.

Let's take a closer look at the Python Script code:

In line 5 we use the Z SQL Method getUserQuery of the acl_users folder to get the user record for the currently logged-in user. If there is a user (line 7), the value in the modules field is retrieved (line 8) and the string in the modules field is converted to a Python list using the split() method of the string module (line 9). Otherwise the list is empty (line 11).

If the list is not empty (line 17), i.e. modules have been selected for this user, the Z SQL Method sql_getModuleByID is used to retrieve the module data record from the database depending on the num parameter (line 18). If there is a module with this specific id, its source code is returned (line 19).

We can render the modules' source code on the index page as follows:

1.	Modules:
2.	<div tal:repeat="num python:range(len(here.py_getUserModules(flag=1)))">
3.	  <div tal:content="structure python:here.talrenderer(here.py_getUserModules(num))">
4.	  </div>
5.	</div>
Listing 6 - Code snippet from the index page (Page Template)

First, we use the Python Script to get the list of modules so we can iterate over the length of the list (line 2). Then we use it again to retrieve the source code of the current module (line 3). Since the source code contains TAL code and not just simple HTML code, it is not enough to use the word structure to render the code.

As you can see in line 3, the source code for the module (which is the return value of the Python Script called here.py_getUserModules(num) ) is given as a parameter to talrenderer. talrenderer is an External Method that takes text, i.e. HTML and TAL code, and instantiates a Zope Page Template using this text. This leads to the TAL code being rendered when the Zope Page Template is returned, as shown in Listing 7.

1.	from Products.PageTemplates.ZopePageTemplate import ZopePageTemplate
2.	
3.	def render_pt(self, text):
4.		""" renders tal in a string """
5.		zptid = 'foobar'
6.		z = ZopePageTemplate(id=zptid, text=text, content_type="text/html")	
7.		return z.__of__(self)()
Listing 7 - The External Method talrenderer (taken from the How-To section of Zope.org)
Conclusion

I hope these examples have given you some idea of what you can do with a customized user folder. This is of course only a small selection of what is possible, and there are also various ways of achieving what I've done here. The main goal of this article was to show how to provide user-specific content.

This article and its examples were inspired by John Tynan. I thank John for his interest and his positive feedback to my earlier articles and hope this one contains what he had in mind for a follow-up.

Further Resources

Download .ZIP of Customized UserFolder:
Customized User Folder Revisited ZIP File


Kristoph Kirchner: was born in Berlin in 1977 and finished school in 1996. After completing a degree in Commercial Correspondence, Kirchner went on to study Computer Sciences at the Technical University of Berlin. Since then Kirchner has been working for beehive writing e-books on Zope and documentation for projects of beehive's customers. Kirchner also co-authored the first German Zope book "Zope: Content Management and Web Application Server", the book "Zope: Web Application Construction Kit" and the book "Zope: How to Build and Deliver Web Applications".


shim
shim  ZopeMag is committed to bringing you the best in Zope Documentation. shim
shim


Home   Subscribe   FAQ   Contact   Write for us   Privacy Policy   Weekly News   PyZine   opensourcexperts.com  

Reproduction of material from any of ZopeMag's pages without prior written permission is strictly prohibited. Copyright 2003 - 2005 ZopeMag Zope/Plone hosting by Nidelven IT