View · Index
No registered users in community rubick
in last 10 minutes

Weblog Page

Showing 11 - 20 of 66 Postings (summary)

OpenACS articles

Created by Gustaf Neumann, last modified by Gustaf Neumann 25 Sep 2019, at 11:45 AM

These are a bunch of articles Jade Rubick has written on using OpenACS and related technologies. Some of them are rough -- just notes, and others are full-blown tutorials. Use all at your own risk. If you have anything you'd like to add to this collection, email me! I also give out permission to edit these pages to anyone in the OpenACS community I know, so even though most of this was written by me, I hope that over time, more and more will be written by other people as well.

Jade Rubick

OpenACS Home Page - If you're interested in finding out more about the technology behind this site, OpenACS.org is a good place to start. It's an excellent platform for building web sites if you have a technical background.

Getting Started With OpenACS

A Quick Intro to OpenACS Templating - describes how to make web pages with OpenACS.

Virtual Hosting in 10 Minutes or Less - Describes an easy way to set up virtual hosting. You can share several web servers on the same IP, each with a different host name.

Postgres notes - misc. notes on Postgres

Oracle notes - my notes on Oracle

Restoring from Backup - If you screw up your OpenACS installation, how to restore from backup.

Upgrading ACS to OpenACS - How to upgrade an ACS 3.4.10 installation to OpenACS

Using the Content Repository - describes how to use the content repository in OpenACS, and why you might want to do this. (added to OpenACS docs)

Adding Workflow to a Project - shows an example of how I add workflow to an OpenACS application. Very incomplete.

SQL Constraints Naming Conventions

Using acs-rels - describes what acs-rels are and why to use them.

Project Management on OpenACS - project management software for OpenACS

Limiting your installation to use SSL - how to make your site redirect to log in securely.

Using CVS with OpenACS - reference on how to use CVS with OpenACS

Using Tcl Namespaces

Things to Remember While Developing for OpenACS

Creating upgrade scripts - how to make an upgrade script for your packages.

Emacs notes - a few tips and tricks for using emacs, often for my own reference.

Notes on ad_form - ad_form is a utility for creating forms in OpenACS. Here are my notes.

Terminology guide

Edit this page: user documentation

Notes on OpenACS permissions

Using ACS service contracts - my notes on acs-service-contracts, and how to use them.

Moving an OpenACS instance to another server - describes how to move an OpenACS instance from one server to another.

Setting up a developmental server - tips for setting up a productive developmental server.

Using tree_sortkey for hierarchical queries in Postgres - Postgres currently lacks a mechanism like CONNECT BY, which is used in Oracle for hierarchical queries. In OpenACS, we use <code>tree_sortkey</code>, which allows us to make hierarchical queries. (Added to OpenACS documentation)

Using List Builder - list builder is a great tool for showing tables of data.

Howto: Expand and Use acs-subsite Based Member Roles - Describes how to set up several roles that are assigned levels of security.

Automated Testing - How to set up automated testing in OpenACS

Setting up auto-installs - OpenACS has facilities for automatically installing a set of packages with the initial install. This documents how to do that.

SOAP/web services on OpenACS

Graphing and drawing with OpenACS

Using OpenSSL to encrypt information - This article describes how to encrypt information from Tcl, using OpenSSL. You can then put this information in the database, or use it as you like.

Aolserver Notes

Jerry Asher's Virtual Hosting Article

Categories

Using Arch for source control management with OpenACS - Arch is so much better than CVS I can't stand it.

Dynamically generating PDF files with ReportLab

Using groups and parties in OpenACS

Writing Documentation for OpenACS

Script to import greenspun's forums to OpenACS forums - A script which (roughly) imports the dump file from Greenspun's older forums into OpenACS's forums package.

Creating a Dev Server - Shows text from my history command, showing how I set up a dev server instance.

OCT Statement Fall 2004
 
 
 

Folder Form (getting_started)

Created by , last modified by Vlad V 03 Aug 2019, at 10:07 PM

Oracle notes

Created by Gustaf Neumann, last modified by Gustaf Neumann 29 Oct 2017, at 12:07 PM

How do I use a literal ampersand within a SQL statement for INSERT, SELECT, etc.?

This is a sqlplus issue, I believe. See this link for a workaround: http://www.jlcomp.demon.co.uk/faq/litampersand.html
If you're on a *nix box - yasql is a great replacement for sql*plus.

Is there an equivalent of Postgres' vacuum analyze

No, not really, but you can do something similar to speed up a table:
 analyze table ticket_xrefs compute statistics;
This will look at the usage statistics, and update them. This can dramatically increase performance when the amount of data in a table has changed a lot.

Moving one Oracle instance to another server

Apparently, Oracle installations are fairly self-contained. In theory, at least, you should be able to move an installation from one server to another by shutting down the server, tarring up the /ora8 directory, sftping it to another server, untarring it, and possibly running the setup_stubs.sh script.

Needless to say, this is much easier than reinstalling, exporting the database, and importing it back in. No guarantees on how well it works, however.

Turning on autotrace

Turning on Autotrace

Hierarchical queries and getting around join problem with CONNECT BY

http://openacs.org/forums/message-view?message_id=125969

Getting [too long] messages?

If you're using Oracle, this thread describes how to set up Aolserver so that you receive the entire error message from the Oracle database driver. If you get an error message starting with SQL: [too long], then you need to read this. Sometimes, for long error messages, your error messages are truncated, which makes tracking down the errors more difficult.

Efficient updates

You can do this
UPDATE
  (SELECT col1, value
     FROM t1, t2
    WHERE t1.key = t2.key
    AND t2.col2 = :other_value)
SET col1 = value

Using lots of dynamic SQL (more than 32768 chars?)

You cannot use a clob, so use the dbms_sql package:
declare
    l_stmt          dbms_sql.varchar2s;
    l_cursor        integer default dbms_sql.open_cursor;
    l_rows          number  default 0;
begin
    l_stmt(1) := 'insert';
    l_stmt(2) := 'into foo';
    l_stmt(3) := 'values';
    l_stmt(4) := '( 1 )';

    dbms_sql.parse( c             =>   l_cursor,
                    statement     => l_stmt,
                    lb            => l_stmt.first,
                    ub            => l_stmt.last,
                    lfflg         => TRUE,
                    language_flag => dbms_sql.native );

    l_rows := dbms_sql.execute(l_cursor);

    dbms_sql.close_cursor( l_cursor );
end;
/ 
This is from Tom Kyte, Oracle God.

Online table updates

DBMS Redefinition package

PL/SQL exception handling

  • Exception handling in PL/SQL

    SPfile and Pfile startups

    $ sqlplus /nolog
    
    SQL*Plus: Release 10.2.0.1.0 - Production on Tue Feb 28 19:04:30 2006
    
    Copyright (c) 1982, 2005, Oracle.  All rights reserved.
    
    
    19:04:34 > connect / as sysdba
    Connected.
    19:05:21 sys@vs> startup pfile=/u01/app/oracle/admin/vs/pfile/init.ora.192006104950
    ORACLE instance started.
    
    Total System Global Area  285212672 bytes
    Fixed Size                  1218992 bytes
    Variable Size              92276304 bytes
    Database Buffers          188743680 bytes
    Redo Buffers                2973696 bytes
    
    Database mounted.
    Database opened.
    19:05:37 sys@vs> 19:05:37 sys@vs> 
    19:05:38 sys@vs> create spfile from pfile='/u01/app/oracle/admin/vs/pfile/init.ora.192006104950';
    
    File created.
    

Using acs-rels

Created by Gustaf Neumann, last modified by Gustaf Neumann 05 Aug 2017, at 10:48 AM

What are acs-rels?

Don't use this as a definitive document. I'm writing as I figure things out. But I finally found some documentation for them:

 

ACS-rels are used to link together ACS Objects. This may make it possible to arbitrarily map different OpenACS objects and applications. Dave B says: "About using acs_rels to associate arbitrary objects to a project (just another object.) The acs_rels system is very flexible and powerful. It can define relationships between two different object types. Because acs rel types are themselves object types, you can extend those object types with additional attributes."

The documentation doesn't make it look that complicated. Basically acs-rels are a long skinny table that contains references to two other Objects, and has its own object_id as well. Apparently, it is also a general table, meant to be used not just for permissions but for any sort of association between objects.

The part I'm less clear of is that the data model has types of associations. I'm not clear on what that means yet.

Future cool things to do with acs-rels

Ignore this part if you're trying to learn about acs-rels. It is just some thinking I wanted to jot down, and will refine later A really neat idea would be to create a "related items" package built on top of acs-rels, which would relate any two packages together. DaveB suggested this to me.

Let's say you're building a project management software package for OpenACS. You'd like to let users associate all sorts of things with a project: photos, files, whatever.

You've been good, so you're using the object system, as described in the developer tutorial. Now you get to see some of the benefits of using objects.

Let's say you'd like to link surveys (a package) to projects. I haven't used the surveys package, so this is just an example. You extend the project view page to:

  • First, check that the survey package is installed. If not, don't show any of these links.
  • Show links for administrators on the project page (they would need admin on survey too), to create a survey.
  • After the survey is created, it would be linked in to the project page.
  • The project page would show surveys that project members could participate in.
  • When they click on the survey, they could fill it out.
  • When done, they would be returned to the project page.
My understanding is that this is not yet the way everything works. However, that is what developers are working towards, and parts of this are in place.

First of all, you could get a list of surveys, or files, or whatever, once you can get a URL for a given object_id. This work is described in this OpenACS thread.

Using Arch for source control management with OpenACS

Created by Gustaf Neumann, last modified by Gustaf Neumann 27 Jul 2017, at 10:50 AM

Table of contents

Email me if you have corrections or additions to this document.

Why use Arch

I've used CVS for a couple of years, and even though you can't live without version control, and it's decent, it is not well-suited to web application development. You run into problems with branching, tracking local customizations and keeping track of a changing code stream.

Arch handles these much better than CVS:
  • Branching
  • Renaming files
  • Moving directories
Compared to the competition (Subversion for example):
  • Handles distributed development
Subversion is also a very worthy tool -- I use Subversion every day. But I chose to go with Arch for an earlier project. Arch is pretty interesting, and if I didn't occasionally run into annoying error messages I would recommend it more. I also find that the "user interface" is too focused on people who understand the mechanics of how Arch works, rather than on abstracting those details from developers.

Learning about Arch

The best place to start with is GNU Arch. It has instructions for installing and learning Arch. You should at least look over the tutorial there to make sure you understand how to get help.

How we'll set things up

First of all, we're not going to tackle the more complicated issue of setting up a CVS to Arch gateway. This is very possible, but not something we're going to get into at this point.

We are setting up a very rigorous setup, which will have a developmental server, a staging server, and the live server. This will allow you to have localized changes to your OpenACS installation, use the remote repository without doing a bunch of CVS chicanery, and in general allow you to test changes before going live with them.

We will set up an OpenACS Arch repository that will mirror the stable releases of OpenACS core (currently 5.1.3). The website we are building is safe4all.

User Purpose Archive Category Branch Version Ports
safe4all-dev Mirror OpenACS core releases jade@safe4all.org--2005 openacs stable 5.1.0 None
safe4all-dev Import packages, html pages jade@safe4all.org--2005 openacs dev 5.1.0 8000/8443
safe4all-staging Local customization jade@safe4all.org--2005 openacs staging 5.1.0 8001/8444
safe4all Live site jade@safe4all.org--2005 openacs staging 5.1.0 80/443

Initial setup

# groupadd web
# useradd -m -g web safe4all-dev
# useradd -m -g web safe4all-staging
# useradd -m -g web safe4all
# apt-get install tla tla-doc
See the OpenACS wiki guide to installing OpenACS on Debian. Set up the Arch IDs for all the users
# su - safe4all-dev
$ tla my-id "Jade Rubick <safe4all-dev@safe4all.org>"
$ exit
# su - safe4all-staging
$ tla my-id "Jade Rubick <safe4all-staging@safe4all.org>"
$ exit
# su - safe4all
$ tla my-id "Jade Rubick <safe4all@safe4all.org>"
$ exit
You should also edit your .bash_profile or equivalent to make your umask 0002 instead of 0022. This helps you with permissions issues later. Do this for safe4all-dev and safe4all-staging.

Setup OpenACS core release mirror (stable branch)

What we'll do is track the latest stable release of OpenACS from CVS. Currently, that is 5.1.5. What we will do is start with 5.1.0 (start later if you want to save yourself some time), and upgrade each version to get to the current one. This tests our upgrade process and makes sure we're on the right path.

The basic way we're setting things up is to setting up your own arch repository.

If you haven't already:
# su - safe4all-dev
$ mkdir ~/{archives}
Now set up your openacs-stable archive.
$ tla make-archive jade@safe4all.org--2005 ~/{archives}/2005
$ tla my-default-archive jade@safe4all.org--2005
$ tla archive-setup openacs--stable--5.1.0
* creating category jade@safe4all.org--2005/openacs
* creating branch jade@safe4all.org--2005/openacs--stable
* creating version jade@safe4all.org--2005/openacs--stable--5.1.0
$ mkdir ~/wd
$ cd ~/wd
$ cvs -z5 -d :pserver:anonymous@cvs.openacs.org:/cvsroot co -r openacs-5-1-0-final acs-core  
(see http://openacs.org/4/checkout)
$ mv openacs-4 openacs-5-1-0
$ cd openacs-5-1-0
$ tla init-tree openacs--stable--5.1.0
We have to get all the source into Arch. You should look up the inventory and tree-lint commands if you don't understand what they do. They essentially help us make sure that we're importing all the files into Arch the way we want to.
$tla inventory --names --both --source |xargs tla add
$tla tree-lint      
We have to get to a point where tla tree-lint doesn't report any issues. Here are some hints:

We now need to edit the =tagging-method file, to ensure that log files aren't stored in Arch, that the daemontools' supervise directory isn't stored in Arch, etc..
emacs {arch}/=tagging-method
Change this line:
precious ^(\+.*|\.gdbinit|\.#ckpts-lock|=build\.*|=install\.*|CVS|CVS\.adm|RCS|RCSLOG|SCCS|TAGS|\.svn)$
to
precious ^(\+.*|\.gdbinit|\.#ckpts-lock|=build\.*|=install\.*|CVS|CVS\.adm|RCS|RCSLOG|SCCS|TAGS|\.svn|supervise|content-repository-content-files)$
You also will need to add a .arch-inventory file to prevent Arch from storing database backups, server certificates, etc... See: https://www.gnu.org/software/gnu-arch/
echo "precious ^log|apm-workspace|rss|database-backup$" > .arch-inventory
echo "precious ^certs$" > etc/.arch-inventory
echo "precious ^down$" > etc/daemontools/.arch-inventory

Add in code that hasn't been added in yet (check without xargs first)
$ tla tree-lint -t |xargs tla add
Do this until tree-lint doesn't complain.

Now we commit the new stable branch:
$ tla import
* imported jade@safe4all.org--2005/openacs--stable--5.1.0
$ tla commit --summary "Imported OpenACS 5.1.0"
* update pristine tree (jade@safe4all.org--2005/openacs--stable--5.1.0--base-0 => openacs--stable--5.1.0--patch-1)
* committed jade@safe4all.org--2005/openacs--stable--5.1.0--patch-1

Set up a dev branch

At this point, we need to set up a dev branch, where we'll use to keep track of new packages we install, and HTML code we're given. At my company, the graphics department works on the HTML code, but there often are modifications I have to make after they have given it to me. For example, I may change a link after installing a package. The dev branch is where I upload their code, and the code for new packages I install via the remote repository.

We want to create a website called safe4all. You'll base this dev instance on the openacs stable version, by creating a branch:

See also Elementary branches.
$ cd ~wd
$ tla archive-setup openacs--dev--5.1.0
* creating branch jade@safe4all.org--2005/openacs--dev
* creating version jade@safe4all.org--2005/openacs--dev--5.1.0
$ tla tag jade@safe4all.org--2005/openacs--stable--5.1.0 openacs--dev--5.1.0 
Now we have a dev branch of the openacs-stable archive.

Now you can set up a dev server. This step is not strictly necessary. When you edit your config.tcl file, I recommend setting up the email redirecting, so email isn't sent from your staging server. See "Configure an AOLserver Service for OpenACS" on the OpenACS documentation.
$ cd
$ tla get openacs--dev--5.1.0 safe4all-dev
* from import revision: jade@safe4all.org--2005/openacs--stable--5.1.0--base-0
* patching for revision: jade@safe4all.org--2005/openacs--stable--5.1.0--patch-1
* patching for revision: jade@safe4all.org--2005/openacs--dev--5.1.0--base-0
* making pristine copy
* tree version set jade@safe4all.org--2005/openacs--dev--5.1.0
$ ln -s /home/safe4all-dev/safe4all-dev /var/lib/aolserver/safe4all-dev
$ mkdir ~/safe4all-dev/log
$ emacs ~/safe4all-dev/etc/config.tcl
$ emacs ~/safe4all-dev/etc/daemontools/run
$ touch ~/safe4all-dev/etc/daemontools/down
Now set up the database. If you are starting from scratch:
Set up the database as per Prepare PostgreSQL for an OpenACS Service on the OpenACS install docs.

If you are importing an existing database, then, if you have a database dump to import, you can use Perl to make sure you are loading the information correctly into the dev and staging instances:

# su - postgres
$ createuser -a -d safe4all-dev
$ exit
# su - safe4all-dev
$ createdb -E UNICODE safe4all-dev
$ perl -pi -e "s/^\\connect olduser$/\\connect safe4all-dev/" dumpfilename.dmp
$ psql -f dumpfilename.dmp safe4all-dev
$ locate vacuum |grep vacuumdb
$ export EDITOR=emacs;crontab -e
Put this in the crontab, changing the directory to pgsql depending on what which said for vacuum. Note the first line is commented out -- it is no longer necessary as of Postgres 7.4.
# 0 1-23 * * * /usr/lib/postgresql/bin/vacuumdb --analyze safe4all-dev > /dev/null 2>&1
0 0 * * * /usr/lib/postgresql/bin/vacuumdb --full --analyze safe4all-dev > /dev/null 2>&1

Now let's make sure everything is set up correctly. Run the etc/daemontools/run script by hand.

$ su - 
# cd /home/safe4all-dev/safe4all-dev/etc/daemontools
# ./run
If you get this error message:
/usr/local/aolserver/bin/nsd: error while loading shared libraries: libnsd.so: cannot open shared object file: No such file or directory
See: error while loading shared libraries: libnsd.so

Check that the server actually starts up, look at the ~safe4all-dev/log/error.log files to troubleshoot. Post on the OpenACS Forums if you have problems. Once it works, then link it in.

$ su -
# ln -s /var/lib/aolserver/safe4all-dev/etc/daemontools /service/safe4all-dev
# svgroup web /service/safe4all-dev
# exit
$ svc -u /service/safe4all-dev

If the svgroup command doesn't work, you need to install it (see the OpenACS installation guide (here).

Once you get it right, commit the changes you've made. You can go through the web interface now and set up OpenACS if you didn't import anything.

Set up a staging branch

At this point, we need to set up a staging branch, where we'll use to keep track of our local customizations to OpenACS and the HTML code we have. For example, I may change the appearance of the OpenACS pages or packages, or change the wording of something in the OpenACS package.

We want to create a website called safe4all. We base this website on the safe4all-dev branch, but create another branch:

See also Elementary branches.
# su - safe4all-staging
$ cd 
$ tla register-archive /home/safe4all-dev/\{archives\}/2005/
$ tla my-default-archive jade@safe4all.org--2005
$ tla archive-setup openacs--staging--5.1.0
* creating branch jade@safe4all.org--2005/openacs--staging
* creating version jade@safe4all.org--2005/openacs--staging--5.1.0
$ tla tag jade@safe4all.org--2005/openacs--dev--5.1.0 openacs--staging--5.1.0 
Now we have a staging branch of the dev branch.

Now set up the staging server:
$ cd
$ tla get openacs--staging--5.1.0 safe4all-staging
* from pristine cache: jade@safe4all.org--2005/openacs--dev--5.1.0--base-0
* patching for revision: jade@safe4all.org--2005/openacs--staging--5.1.0--base-0
* making pristine copy
* tree version set jade@safe4all.org--2005/openacs--staging--5.1.0
$ ln -s /home/safe4all-staging/safe4all-staging /var/lib/aolserver/safe4all-staging
$ emacs ~/safe4all-staging/etc/config.tcl
$ emacs ~/safe4all-staging/etc/daemontools/run
$ touch ~/safe4all-staging/etc/daemontools/down
When you edit your config.tcl file, I recommend setting up the email redirecting, so email isn't sent from your staging server.

Now set up the database. If you are starting from scratch:
Set up the database as per Prepare PostgreSQL for an OpenACS Service on the OpenACS install docs.

If you are importing an existing database, then, if you have a database dump to import, you can use Perl to make sure you are loading the information correctly into the dev and staging instances:

# su - postgres
$ createuser -a -d safe4all-staging
$ exit
# su - safe4all-staging
$ createdb -E UNICODE safe4all-staging
$ perl -pi -e "s/^\\connect olduser$/\\connect safe4all-staging/" dumpfilename.dmp
$ psql -f dumpfilename.dmp safe4all-staging
$ export EDITOR=emacs;crontab -e
Add to the crontab entries for safe4all-staging:
#15 1-23 * * * /usr/lib/postgresql/bin/vacuumdb --analyze safe4all-staging > /dev/null 2>&1
15 0 * * * /usr/lib/postgresql/bin/vacuumdb --full --analyze safe4all-staging > /dev/null 2>&1

Now let's make sure everything is set up correctly. Run the etc/daemontools/run script by hand.

$ su - 
# cd /home/safe4all-staging/safe4all-staging/etc/daemontools
# ./run
Check that the server actually starts up, look at the error.log files to troubleshoot. Once it works, then:
$ su -
# ln -s /var/lib/aolserver/safe4all-staging/etc/daemontools /service/safe4all-staging
# svgroup web /service/safe4all-staging
# exit
$ svc -u /service/safe4all-staging

Set up the live server server

The live server then is based on the staging branch: openacs--staging--5.1.0

$ cd 
$ tla register-archive /home/safe4all-dev/\{archives\}/2005/
$ tla my-default-archive jade@safe4all.org--2005
Now set up the live server:
$ cd
$ tla get openacs--staging--5.1.0 safe4all
$ ln -s /home/safe4all/safe4all /var/lib/aolserver/safe4all
$ emacs ~/safe4all/etc/config.tcl
$ emacs ~/safe4all/etc/daemontools/run
Now set up the database. If you are starting from scratch:
Set up the database as per Prepare PostgreSQL for an OpenACS Service on the OpenACS install docs.

If you are importing an existing database, then, if you have a database dump to import, you can use Perl to make sure you are loading the information correctly into the dev and staging instances:

# su - postgres
$ createuser -a -d safe4all
$ exit
# su - safe4all
$ createdb -E UNICODE safe4all
$ perl -pi -e "s/^\\connect olduser$/\\connect safe4all/" dumpfilename.dmp
$ psql -f dumpfilename.dmp safe4all
Add to the crontab entries for safe4all:
$ export EDITOR=emacs;crontab -e
#30 1-23 * * * /usr/lib/postgresql/bin/vacuumdb --analyze safe4all > /dev/null 2>&1
30 0 * * * /usr/lib/postgresql/bin/vacuumdb --full --analyze safe4all > /dev/null 2>&1

Now let's make sure everything is set up correctly. Run the etc/daemontools/run script by hand.

$ su - 
# cd /home/safe4all/safe4all/etc/daemontools
# ./run
Check that the server actually starts up, look at the error.log files to troubleshoot. Once it works, then:
$ su -
# ln -s /var/lib/aolserver/safe4all/etc/daemontools /service/safe4all
# svgroup web /service/safe4all
# exit
$ svc -u /service/safe4all

When a new version of OpenACS comes out

When updates are made to OpenACS core (such as 5.1.3 coming out), you update it in the oacs archive.

Since we started with OpenACS 5.1.0, I'll now bring in 5.1.1 into the stable branch:
# su - safe4all-dev
$ cd wd
$ cvs -z5 -d :pserver:anonymous@cvs.openacs.org:/cvsroot export -r openacs-5-1-1-final acs-core
$ mv openacs-4 openacs-5-1-1
The old version of OpenACS is at ~/wd/openacs-5-1-0, and the new version is at ~/wd/openacs-5-1-1.
$ rsync -av --delete-after --force --exclude='{arch}' --exclude='**/.arch-ids**' openacs-5-1-1/* openacs-5-1-0
$ cd openacs-5-1-0
$ find . \( -name {arch} -o -name ".arch-ids" \) -prune -o -print | sort | \
   perl -ne 'chomp; print "$o\n" if -d $o && !/^\Q$o\E/; $o=$_'
$ find . \( -name {arch} -o -name ".arch-ids" \) -prune -o -print | sort | \
   perl -ne 'chomp; print "$o\n" if -d $o && !/^\Q$o\E/; $o=$_' | xargs rm -rf
$ tla tree-lint -m     (if you get anything do the next line)
$ tla tree-lint -m | xargs rm 
$ tla tree-lint -t | xargs tla add-id
$ tla tree-lint
Make sure tree-lint doesn't complain before continuing.
$ tla commit --summary "Imported OpenACS 5.1.1"
We made the ~/wd/openacs-5-1-0 directory have all the changes from openacs-5-1-1, so just to avoid confusion, let's clean up the working directory:
$ cd ~/wd
$ mv openacs-5-1-1 /tmp
$ mv openacs-5-1-0 openacs-5-1-1

Updating dev with stable changes

First make sure you don't have any local changes to commit:
$ cd safe4all-dev
$ tla changes
If you do have changes to commit, then commit them first, using the directions below.
# su - safe4all-dev
$ cd safe4all-dev
$ tla missing --summary openacs--stable--5.1.0
$ tla replay openacs--stable--5.1.0
$ tla tree-lint -m | xargs rm
$ tla tree-lint -t |xargs tla add
$ tla changes 
$ tla log-for-merge >> $(tla make-log)
$ emacs +(TAB)    (edit the log entry)
$ tla commit

Installing and upgrading OpenACS packages

First make sure you don't have any local changes to commit:
$ cd safe4all-dev
$ tla changes
If you do have changes to commit, then commit them first, using the directions below.

I usually check which packages are available using the acs-admin/install page. I don't actually install from there. Here is an example using calendar:
# su - safe4all-dev
$ cd
$ cvs -d:pserver:anonymous@cvs.openacs.org:/cvsroot export -r openacs-5-1-compat calendar 
$ rsync -av --delete-after --force --exclude='{arch}' --exclude='**/.arch-ids**' calendar/* dev-server/packages/calendar
$ find dev-server \( -name {arch} -o -name ".arch-ids" \) -prune -o -print | sort | perl -ne 'chomp; print "$o\n" if -d $o && !/^\Q$o\E/; $o=$_'
If you see any results from the find command, you can delete those directories as appropriate.
$ cd dev-server
$ tla tree-lint 
$ tla tree-lint -m | xargs rm
$ tla tree-lint -t |xargs tla add-id   (may have to do multiple times)
$ tla changes 
$ tla make-log
$ emacs +(TAB)    (edit the log entry)
$ tla commit
$ cd
$ mv calendar /tmp

Updating staging with dev changes

First make sure you don't have any local changes to commit:
$ cd safe4all-staging
$ tla changes
If you do have changes to commit, then commit them first, using the directions below.
# su - safe4all-staging
$ cd safe4all-staging
$ tla missing --summary openacs--dev--5.1.0
$ tla replay openacs--dev--5.1.0
If you get conflicts during the replay, see how to deal with .rej files, which are the files that you get when there are conflicts.

Now commit the changes to the staging branch

$ tla changes 
$ tla log-for-merge >> $(tla make-log)
$ emacs +(TAB)    (edit the log entry)
$ tla commit

Updating the live server

This part is really easy.
$ su - safe4all
$ cd safe4all
$ tla missing -s
$ tla replay
You can make local changes to files, and updates will not overwrite them. So you can edit the etc/config.tcl file for example. If there are conflicts, you will have to resolve them, however.

Making changes on the dev branch

As you recall, the dev branch is for adding new OpenACS packages. You may also want to make local changes such as the change to the etc/config.tcl and etc/daemontools/run file. When you have added code or made any changes, you simply:
$ tla changes --diffs
$ tla tree-lint -m | xargs rm
$ tla tree-lint -t |xargs tla add      # (may need to do this multiple times)
$ tla commit --summary "Description of my changes"

Making customizations

When you want to make a customization, you do it on the safe4all-staging server. So first, we bring up the server (leave it off most times):
$ svc -u /service/safe4all-staging
Make your changes.

Commit your changes
$ tla changes 
$ tla tree-lint 
$ tla tree-lint -t
$ tla tree-lint -t |xargs tla add
$ tla commit --summary "Describe my customization here"
You'll have to resolve any conflicts if you find any. 

Now that we're done, we'll take down the staging server, then update the live server
$ svc -d /service/safe4all-staging

Resetting the database

If you ever want to load more current data into your staging or dev branches. The Perl is to change the username in the backup dump.
$ su - safe4all-staging
$ svc -d /service/safe4all-staging
$ dropdb safe4all-staging

$ createdb -E UNICODE safe4all-staging
$ cp ~safe4all/safe4all/database-backup/backup.dmp ~/safe4all-staging/database-backup/
$ perl -pi -e "s/^\\connect safe4all$/\\connect safe4all-staging/" backup.dmp
$ psql < backup.dmp safe4all-staging
$ svc -u /service/safe4all-staging

Making upstream contributions from your changes

This section is still being worked on.

If you have a changeset that you'd like to commit to OpenACS, here's how I currently I am doing it:
$ su - safe4all-dev
$ ls -1
You'll see this:
dev-server
oacs-5-1
oacs-HEAD
wd
{archives}
The directory: oacs-5-1 is a cvs checkout of OpenACS, from the oacs-5-1 branch.

Let's say the change I want to commit is patch-8.
$ tla get openacs--dev--5.1.0--patch-7 patch-7
$ tla get openacs--dev--5.1.0--patch-8 patch-8
$ diff -ur patch-7 patch-8 -x '{arch}' > patch-file
Then you can use that patch-file to patch oacs-5-1, using the patch command
$ man patch

Changes to this document

If you want to keep up to date with the changes in this document, you shouldn't have to read every bit of the page and scan it for changes. Just look in this section, and I'll detail what you have to do to get up to the latest changes. For example, if you last used this document on January 12, 2005, then you'll need to make the January 13, 2005 changes. Then you'll be exactly on the same page as everything else in this document.
  • January 13, 2005:
    By default, the dev and staging servers should not start up, and only the production server should. With daemontools, this is accomplished by putting a file called down in the daemontools directory. We want Arch to ignore this.
    # su - safe4all-dev $ touch ~safe4all-dev/dev-server/etc/daemontools/down # exit # su - safe4all-staging $ cd staging-server $ touch etc/daemontools/down $ echo "precious ^down$" > etc/daemontools/.arch-inventory $ tla tree-lint $ tla tree-lint -t |xargs tla add (will need to do this several times) $ tla tree-lint -m |xargs rm $ tla make-log (edit the log file) $ tla commit
     
              
    • December 12, 2005:
      As of Postgres 7.4, you do not need to explicitly run vacuum all the time. So you may need to update your crontab entries:
      # 0 1-23 * * * /usr/lib/postgresql/bin/vacuumdb --analyze safe4all-dev > /dev/null 2>&1 0 0 * * * /usr/lib/postgresql/bin/vacuumdb --full --analyze safe4all-dev > /dev/null 2>&1
      This can be done for dev, staging, and production.
      
      
      
      
      
      

Using List Builder

Created by Gustaf Neumann, last modified by Gustaf Neumann 20 Jun 2017, at 08:43 AM

Using List Builder

List Builder (LB) was introduced in acs-templating 4.6.4, and it allows you to make tables very easily. It took me about two hours to convert a table of mine to LB, and the time is well worth it, because you don't have to manually put in all the code for sorting. It also seems very flexible.

Plus, since you are benefiting from my learning process, you probably won't take two hours to learn it!

Example

The best example I've found is at the logger admin page. It uses list-builder well, and does nifty things with filters, etc..

The basic idea

Walk with me as I convert my index.tcl and index.adp file to list-builder. This is the end of the .tcl file:
# An HTML block for the breadcrumb trail                                         
set context_bar [ad_context_bar]
set title "Organizations"

db_multirow orgs orgs_query { }
The index.adp file looks something like this:
<master>                                                                   

<property name="title">@title@</property>                            
<property name="context_bar">@context_bar@</property>                

<if @orgs:rowcount@ gt 0>                                                  
        <table cellpadding="3" cellspacing="0">                            

        <tr>                                                               
        <th>Action                                                         
        <th>Name                                                           
        <th>Notes                                                          
</if>                                                                      

<multiple name="orgs">                                                     

        <tr>                                                               

        <td>                                                               
        <a href="one?organization_id=@orgs.organization_id@">Details</a>                                                                             
        <if @write_p@>                                                     
          <a href="add-edit?organization_id=@orgs.organization_id@">Edit</a>                                                                         
        </if>                                                              
        <if @delete_p@>                                                    
          <a href="delete?organization_id=@orgs.organization_id@">Delete</a>                                                                         
        </if>                                                              
        </td>                                                              

        <td>@orgs.name@</td>                                         
        <td>@orgs.notes@</td>                                        
 ...
        </tr>                                                              

</multiple>                                                                

<if @orgs:rowcount@ gt 0>                                                  
        </table>                                                           
</if>                                                                      

<if @create_p@>                                                            
<ul>                                                                       
<li><a href="add-edit">Add an organization</a></p>       
</ul>                                                                      
</if>
You would then change the .tcl file to look like this:
template::list::create \
    -name orgs \
    -multirow orgs \
    -key organization_id \
    -elements {
        organization_id {
        }
        name {
        }
        notes {
        }
    } \
    -main_class {
        narrow
    }

db_multirow -extend { item_url } orgs orgs_query {
} {
    set item_url [export_vars -base "one" {organization_id}]
}
Then you change the .adp file to look like this:
<master>                                                                   

<property name="title">@title@</property>                            
<property name="context_bar">@context_bar@</property>       

<listtemplate name="orgs"></listtemplate>

Common errors

If you get an error like this:
Too many positional parameters specified
     while executing
"template::list::create__arg_parser"
     (procedure "template::list::create" line 1)
     invoked from within
"template::list::create \ "
     ("uplevel" body line 86)
     invoked from within
Then the problem is this:
This error means one of your arguments is being interpreted not as a named switch (-elements ...) but as a positional argument (no switch).

This could easiliy be a line that doesn't have the \ at the end, or that has a space character after the \, or some other little typo like that.

Thanks, Lars!

Where the data comes from

From Alex Vorobiev:
it is not clear from the docs, and may not be obvious, but the
-multirow switch lets developer point to a db_multirow statement.
in other words, one MUST create a db_multirow statement with a name
that matches -multrow switch of the list-builder.  the db_multirow
statement, in turn, points to an sql query in the corresponding
.xql file
db_multirow
-to add url columns, use extend with export_vars
-to get row number, use rownum; also see rowcount (in docs for the "multiple" template tag)

Adding in sorting

See adding in sorting From Alex Vorobiev:

for the default case, one must specify column name FOLLOWED by comma and sort order such as ASC or DESC

Several lines from the database in one list-builder cell?

OpenACS has a very cool <group> tag, which allows you to show several rows from the database in one row. For example, if you have the table foo, with columns x and y, with values of
x  y
---
1  3
1  4
2  5
You may want the output to look like:
1   3 4
2   5
or close to that, using an HTML table. Well, the <group> tag does that. Can we do that with list-builder?

Well, quite easily, it turns out.

Just put something like this in the definition of your column (preferably the last one):

        last_name {
            label "Who"
            display_template {
                <group column="item_id">@tasks.first_names@ @tasks.last_name@<br></group>
            }

        }
Unfortunately, this will mess up the alternating bands of light and dark, because the odd and even rows will not be valid.

Formatting your table

If you want your table to be more compact, put this in:
    -main_class {
        narrow
    } \

Adding in action buttons on top

Add as part of your definition:
    -actions {
        "Use process" "process-use" "Use a process"
        "Task calendar" "my_url" "View task calendar"
    } \
If you put variables or functions in there, you'll notice they don't get substituted. So instead, you do this:
    set actions [list "Use process" "process-use" "Use a process" "Task calendar" "my_url" "View task calendar"]

    -actions $actions \
Thanks, Dave.

Pagination

See pagination for list-builder

From Alex Vorobiev:
-multirow vs -page_query it is not immediately clear from the docs, and may not be obvious, but when using pagination, both switches are necessary. the purpose of the -page_query is to fetch the ENTIRE result set, whereas the sql statement used by multirow would fetch only the current page set.
caching with current paginator (not jon griffin's new paginator) use "-page_flush_p 1" to prevent paginator from caching the page set and confusing the hell out of you (when newly added records are suddenly not appearing on the page)

Returning CSV files

If you'd like to return a comma-separated-value file, follow Lars' instructions on returning a CSV from list-builder. These CSV files are opened by Excel or other spreadsheet programs, and make an easy way of getting information out of the database for office workers.

Getting list-builder to work on OpenACS 4.6.3

From Alex Vorobiev:
- copy acs-templating/tcl-list-procs.tcl and
acs-templating/resources/lists from CVS/HEAD to the appropriate
locations in 4.6.3 distribution
- 4.6.3 does not support the new "noquote" tag; any such tags can
be safely removed from templates in versions of openacs prior to 5.0
Caroline Meeks adds:
- you have to use list-procs.tcl version 1.3.  
- Restart the server, don't just watch the files.

Displaying hierarchies

Alex Vorobiev:
[ any information below that mentions pagination refers to old "broken"
paginator ]
- basically not possible with the current implementation of list-builder, 
see http://openacs.org/forums/message-view?message_id=28810
- poor man's hierarchies: one can build db_multirow as a join that
would fetch parent and children records in the same query, and then
use <group column=group_key> tag to iterate over the records with
the same "key" until the key changes (see "group" template tag docs).
in this case, pagination is very hairy, especially with joins:
     -> group "key" has to be same as the -page_query "key", where
     "key" is the query column
     -> if multirow query and page_query use the same joins, then the
     page_where_clause will list the same "key" id multiple times:
     for instance, project_id in ('455','455','466') - notice 455
     appears twice.  this causes fewer rows (than page_size) to be
     displayed on a page.  the solution is to change page_query -
     use table aliasing 'spm_projects j', the same as in the
     multirow query, but no joins.  if we don't do table aliasing,
     then the order by will break (order by j.project_id)

Joins with identical column names in two or more tables tables

Alex Vorobiev:
- pagination: add the aliased column name to -key: j.project_id
- sorting: add the aliased column name to orderby statements in the orderby block
- sorting by columns in more than one table (j.project_id
p.proposal_id) breaks because both tables need to appear in the
page_query, which in turn breaks # of records per page...
i thought that wrapping the page_query select in a "select distinct"
or select with a group by would yield, but both actually re-sort by
the distinct or groupby column, so there is no way to preserve the
sort order of the inner select statement... giving up for now until the new
paginator is out which may make all of this a moot point.

Misc. issues

Documentation

  • The best documentation is the api-doc documentation for template::list (since the openacs.org website is on a version of OpenACS before 5.0, it doesn't include documentation for list-builder. You'll need to look on your installation).
  • Look in cvs.openacs.org to see examples of list-builder usage. Lars has added it in to bug-tracker, forums, and logger, so you can use them as examples. Project-manager (in /contrib) also uses list-builder.
  • More documentation is available.

Moving an OpenACS instance to another server

Created by Gustaf Neumann, last modified by Gustaf Neumann 20 Jun 2017, at 08:30 AM

Background

First of all, let me describe my situation:

I have two OpenACS servers, each serving a different site. I'm going to move site A to site B's hardware, so that both of them will run off of the same hardware.

The name of the database will be called siteA.

This all took me less than an hour, including writing up this documentation. However, I leave off with the documentation before the whole job is done :-)

Steps

  1. Make a dump of the database from site A.
    pg_dump -f db_backup_030729.dmp siteA
    
  2. Any information that changes on the web site after the database backup will be lost in the transition. I was worried about new registrations after that date, so I looked at the admin page for users to see who had been registered recently. If there were any additions after the transition, I would add them back in manually. You could also take down the site during the transition.
  3. I have siteA on CVS, and siteB on CVS too. However, both of them are using CVS repositories on their own server. So I want to move one repository to the other location. Fortunately, they have different names (siteA and siteB in the /var/cvs directory). The CVS guide to moving repositories claims that it is a simple matter to move your repository from one server to another. We'll see.
  4. As root, tar up the repository for siteA.
    cd /cvsroot
    tar -cf siteA.tar siteA
    gzip siteA.tar
    
  5. Transfer cvs repository to site B:
    Log in to site B, as a normal user
    sftp root@siteA.com
    sftp> cd /cvsroot
    sftp> get siteA.tar.gz
    sftp> cd /web/siteA/database-backup
    sftp> get db_backup_030729.dmp
    sftp> exit
    su -
    cd ~username
    mv siteA.tar.gz /cvsroot
    cd /cvsroot
    tar -xzf siteA.tar.gz
    
  6. Oops, we need to create the user to own these files. This command may vary depending on which distribution you have (read the man pages first).
    useradd -m -g web siteA
    su - siteA
    
    Follow the rest of the directions on how to set up the user on the OpenACS Official Documentation. Specifically, on setting up the .bashrc and/or .bash_profile files. Make sure you add in the CVSROOT.
  7. Change permissions on the cvs repository. As root:
    ls -lh
    chown -R siteA.web siteA
    
  8. Check out a copy of the repository
    su - siteA
    cd /web
    cvs checkout siteA
    
  9. Continue with the OpenACS Official Install Guide except...
  10. When you get to the part about setting up the database, instead get the data from your database backup. See Restoring from backup for details.

    If you get an error like this while restoring:

    psql -f db_backup_030622.dmp siteA
    You are now connected as new user postgres.
    psql:db_backup_030622.dmp:12: ERROR:  function plpgsql_call_handler already exists with same argument types
    psql:db_backup_030622.dmp:20: ERROR:  Language plpgsql already exists
    psql:db_backup_030622.dmp:22: \connect: FATAL 1:  user "siteA" does not exist
    
    The problem is you have users with different names. The way I got around this is...

    I opened up the .dmp file in emacs and replaces all the references to the old siteA user to the new one.

    
            

A powerpoint presentation on adding workflow

Created by Gustaf Neumann, last modified by Gustaf Neumann 17 Jun 2017, at 11:42 AM

NameContent TypeLast ModifiedBy UserSize (Bytes)
pump-oacs-workflow.pptapplication/vnd.ms-powerpoint2017-06-17 11:42:48+02Gustaf Neumann64512

Notes on ad_form

Created by Gustaf Neumann, last modified by Gustaf Neumann 05 Dec 2016, at 12:53 PM

Introduction

OpenACS has a decent form manager called ad_form. Ad_form has nice error handing, and a very slick UI, including inline error reporting. However, it is very opaque sometimes, and after using some other form APIs on other platforms, it is confusing by comparison (writing a new form API for OpenACS would be a great contribution I think). My documentation here mostly deals with advanced issues with ad_form, or with common errors. Other documentation deals more with the basics.

Other references

How to debug your ad_forms

Jerry Asher came up with this very useful way of debugging ad_forms. Put this at the top of your ad_form page:
ns_log notice it's my page!
set mypage [ns_getform]
if {$mypage eq ""} {
    ns_log notice "no form was submitted on my page"
} else {
    ns_log notice "the following form was submitted on my page"
    ns_set print $mypage
}

Different behavior for add and edit pages

No problem! Try using [ad_form_new_p -key mykeyvarname] I haven't, but it might work :-)

Getting the values into your form while editing

From Alex Vorobiev:
- put {var ""} into ad_page_contract
- put {var {value $var}} into ad_form element

Checkboxes

There is a bug in the current version of ad_form which doesn't allow you to have a checkbox filled in as checked. There is a patch to fix it, but until then, here is a workaround, suggested by the ever-helpful daveb:
if { something or another } 
  set checked_p [list checked ""]
} else {
  set checked_p ""
}
Then, in your ad_form declaration:
 {
  use_dependency_p:text(checkbox) 
  {label "Use dependency"} 
  {options {{"" "t"}}} 
  {html $checked_p} 
}
However, the options section doesn't seem to work. If it is checked, the value will be "on"

Dynamically filling in select forms

In the ad_form declaration:
    {category_type:text(select)
        {label "Type"}
        {options {[db_list_of_lists get_category_types { }]} }
    }
In the .xql file:
  <fullquery name="get_category_types">
    <querytext>
        select
        short_name,
        category_id as my_cat_id
        from
        rl_resource_category_type
    </querytext>
  </fullquery>

Using dates with ad_form

The template builder, upon which ad_form is built, has poor support for dates. As a result, when Don built ad_form as a wrapper on top of the template builder, problems carried over. If you're a great programmer, you could go into the underlying core, add in better support for dates, and make a lot of programmers happy. Until then, you must suffer along with the rest of us. However, I'll try to make it a little easier for you.

First of all, at the top of your .tcl page:

ad_page_contract {

...

} {

    {planned_end_date ""}
    ...
}
Then, in your ad_form declaration:
    {planned_end_date:date(date),optional 
        {label "Planned end date"}
        {help}
    }
In your .xql file, when inserting:
  <fullquery name="new_project_item">
    <querytext>
        select pm_project__new_project_item (
                :project_id,
...
                to_timestamp(:planned_end_date,'YYYY MM DD HH24 MI SS'),
...
        );
    </querytext>
  </fullquery>
In your .xql file, when selecting:
<fullquery name="project_query">
    <querytext>
      select
        item_id,
...
        to_char(planned_end_date,'YYYY MM DD') as planned_end_date,
...
        FROM
        pm_projectsx
       where project_id = :project_id
    </querytext>
  </fullquery>
</queryset>

Substituting dynamic values into an ad_form definition

Let's say you want to make the label for an ad_form item be determined at run-time. I did this for project manager, because I let the terminology be determined in a parameter set by the user. You can do this by:
    {goal:text(textarea)
        {label "[set project_term] goal"}
        {optional}
        {value $goal}
        {html { rows 5 cols 40 wrap soft}}}

Creating dynamic forms in a for loop

I was faced with a situation in which I wanted to create anumber of Tasks. The previous page had a form which passed in the number of Tasks to create, but I wanted to have the ad_form let me create several tasks (number) on the same page.

Initially, I tried to just use ad_form -extend inside a for loop. However, you get an error like this:

Element 'task_title' already exists in form 'add_edit'.
Roberto Mello suggested having the names of each element be different, and building a string to feed to ad_form. This is the route I went. I did something like this after my initial declaration of an ad_form:
for {set i 0} {$i <= $number} {incr i} {
    
    append add_edit_definition " 
    {task_title_$i:text
        {label \"Subject #$i\"}
        {html {size 59}}
    }"

}

ad_form -extend -name add_edit -form $add_edit_definition
An even better solution, proposed by DaveB: http://www.openacs.org/irc/log/2003-08-01#T18-23-45 or http://openacs.org/forums/message-view?message_id=52016

Before I did this, I would look at Jeff's posting, which describes using lists instead of the string I built above.

Note that some of these methods may be incompatible with dates.

Using multiple hidden values

There is no facility for having multiple hidden values with the same name. There are workarounds linked in from this bug report.

Using multiple dates with ad_form

Things really get sticky when you try to use multiple dates with ad_form. It's a mess, really. The problem is that dates are stored using arrays already. If you have a date in your form, try looking at the HTML generated. You'll see that it will say something like deadline.format deadline.year deadline.month deadline.day

After these are passed in, the ad_page_contract looks at them, and if the variable is specified as a date, then it handles it intelligently.

Unfortunately, this conflicts with the standard way of having multiple items with the same name in ad_form. So we have to resort to massive hacking, of the worst sort.

ad_page_contract {
...
    end_date:array,optional
...
}
...

# The evilest hack of all time. 
# -------------------------------------------------------
# This is a workaround the fact that using multiple dates
# with ad_form is extremely difficult. Dates are formatted
# like arrays, with values like end_date.day, end_date.month,
# end_date.year, and end_date.format . The problem is we want
# to have multiple end_dates. Using the multiple method, we
# then get entries like this: end_date.1.year, end_date.2.year,
# end_date.2.day, etc..
# 
# What this loop does is go through the array, and rename the 
# values into other variables. We then feed these variables into
# the SQL function that creates the new tasks. This works. I'm 
# sure there must be a better way to do it, but my posting on 
# the forums didn't result in any other suggestions.

if {[info exists end_date]} {

    set searchToken [array startsearch end_date]

    while {[array anymore end_date $searchToken]} {

        set keyname [array nextelement end_date $searchToken]
        set keyvalu $end_date($keyname)

        # element_num is 1...n, element_type is year, format, day, month
        regexp {(.*)\.(.*)} $keyname match element_num element_type

        set end_date_[set element_type]($element_num) $keyvalu

    }

    for {set i 1} {$i <= $number} {incr i} {
        # set up date variable names
        set end_date_$i [list $end_date_year($i) $end_date_month($i) $end_date_day($i) {} {} {}]
    }
}

...

ad_form -name add_edit -form {
    task_id:key
...
} -new_data {

    set task_revisions [list]
    # number is the number of dates or items
    for {set i 1} {$i <= $number} {incr i} {
        lappend task_revisions [db_exec_plsql new_task_item { *SQL* }]
    }

}

for {set i 0} {$i <= $number} {incr i} {
    
    # reading this code, you may wonder why we put the .$i at the end.
    # DaveB showed me this trick. It lets you make a multiple out of
    # the items by stuffing them in an array. Long live DaveB.

    ad_form -extend -name add_edit -form \
        [list \
             [list \
                  end_date.$i:date,to_sql(linear_date) \
                  {label "Deadline"} \
                  {format "MONTH DD YYYY"} \
                  {value {[util::date::today]}} \
                  {help} \
                 ] \
            ] 
}
Then, inside the .xql file:
  <fullquery name="new_task_item">
    <querytext>
        select pm_task__new_task_item (
...
                to_timestamp('[set end_date_$i]','YYYY MM DD HH24 MI SS'),
...
                now(),
                :user_id,
                :peeraddr,
                :package_id
        );
    </querytext>
  </fullquery>

Using a validation block

This is from Alex Vorobiev:

To take advantage of the ad_form built-in validation, remove validation rules from ad_page_contract, such as "integer,notnull". ad_page_contract validation returns a brief ugly error that instructs user to use the Back button and fix input. instead use the ad_form -validate block for complex validation, or nothing - this because ad_form by default makes all fields required (unless specified as optional). if a validation rule is not satisfied, ad_form will display an inline error message.

One can have multiple validation rules for the same input field within the ad_form -validate block.

one can write validation rules that check input against the database, such when it is necessary to make sure that a duplicate value is not being inserted into a column with a "unique" constraint.

See also: how to use validation blocks.

Adding sections

You can add a headline bar above an element like this:
{myelement:text(text)
   {section "A section header"}}
and thus group the form elements into different sections.

Customizing the layout

You can customize the layout of your forms. See this posting to see how. Or look at the code for project-manager.

Setting __refreshing_p

I don't completely understand why you'd want to do this, but if you do: Setting __refreshing_p

Setting the minute intervals of ad_form date widget

See http://openacs.org/forums/message-view?message_id=121082 and http://openacs.org/forums/message-view?message_id=125030.

Add, edit, view page

forum thread on add, edit, view

Getting errors like no command [something]? (and how to get around this huge security problem)

See: http://openacs.org/forums/message-view?message_id=133637

Using variables from ad_form in your pages

See how to include variables from ad_forms in your pages

Getting the number of formerrors in the standard template

See getting the number of formerrors in the standard template

Compound keys, or when one key doesn't fit

forum thread on compound keys

Short tutorial on ad_form

This is a simple summary of things I learned while working with ad_form for the first time. Although many of the things I learned should probably have been intuitive they were not for me so I decided to write a simple tutorial in case others have similar problems. Before you go though these examples it would be a good idea to pick up Roberto's ad_form quick reference sheet at:

http://www.brasileiro.net/writings/openacs/ad-form-quick-ref.pdf


Example 1: Simple Multiple select list

see /doc/form-builder.html from your openACS instance.


Example 2: Setting variables in the on_request section. This is really straight forward you just need to


set foo value


where foo is a widget defined in the -form section and value is the value that you would like to set it to. Note That you will not be able to retrieve other values that are set in this section. If you will need the value in another location it would be better to set it above the ad_form section



Example 3: Using -extend


This is really pretty well documented but just make sure that you define your form elements before you start the submit or other sections. This really becomes useful when you need to leave out part of the form or dynamically build the form.


#this is where we are placing the header information

ad_form -name accomplish -form {

{period_id:text(hidden) }

{work_id:text(hidden) }

{period_title:text(inform) {label "Progress report for"} }

{employee:text(inform) {label "Employee"} }


}


...some extra tcl code here


#This is the logic that will omit past data if it is not present

if {$prev_info_p == 1} {

ad_form -extend -name accomplish -form {

{emp_eval:text(inform) {section "Information from last year" } \

{value $prev_info(emp_eval)} \

{label "Action Plans/Training and Career Development Goals"}}

{sup_eval:text(inform) {value $prev_info(sup_eval)} \

{label "Supervisor Summary Comments"} }

{emp_response:text(inform) {value $prev_info(emp_response)} \

{label "Employee Comments/Responses (Optional)" } }

}

}

This will make is if there is no previous information for this employee this section of the form is omitted.



Example 4: Validating the data -validate

In this case the ad_form documentation does a great job in explaining this feature. For the lazy here is the code from the api-doc.


-validate {        {value         {[string length $value] >= 3}         "\"value\" must be a string containing three or more characters"        }


These statements can be as complex as you would like them to be. Here is a snippet from a form that has two submit buttons. If you save your work no password is required but to sign your work you need to include a password. This is the code that does that


{passwd

{ ( [string length $passwd] == 0 && $submit == "Save Work" ) \

|| [ad_check_password $user_id $passwd] }

"You Have entered an incorrect password" }


Speaking of two submit buttons on a form to get the second one I had to use the after_html segment as part of one of the widget's. The after_html and before_html are easy to use and do just what you think that they do.


I am sure there is a lot I have missed here but this should put you will on your way to using ad_form.


-Trenton Cameron

Dynamically generating PDF files with ReportLab

Created by Gustaf Neumann, last modified by Gustaf Neumann 15 Sep 2016, at 10:23 AM

Introduction

It is pretty easy to generate dynamic PDF files from OpenACS. The hardest part is learnng Python and the Reportlab API. Getting to a 'Hello World' PDF is an easy process. In my case, I pulled the current date from the database and displayed it tiled on the PDF file.

 

You'll need to be familiar enough with packages that you can set up a new package. You should probably refer to the following thread:

 

Thread on PDF generation

 

This document is released under GPL.

 

Install Reportlab

First of all, you need to install Reportlab, a Python based program which generates PDF files for you. For Debian Linux:
  • apt-get install python2.2 (other Linux distributions: install python in some way)
  • Download the tgz file for ReportLab
  • You then tar -xzf ReportLab.tgz
  • For Debian, you then want to move the reportlab directory to /usr/local/lib/python2.2/site-packages/
  • become another user
  • $ python2.2
  • import reportlab (see if it works)

 

Download the User Guide (in PDF format, of course), and go to the installation section. Follow their directions.

 

Install the Python Imaging Library

 

If you'd like to include bitmap images in your PDF files, you need to install the Python Imaging Library. I didn't do this, but I may come back to it later. Directions for installing this are in the User Guide, but the link in it was incorrect. Go to http://www.pythonware.com/products/pil/index.htm instead.

 

Create a new package, and set up procedures

If you're creating an OpenACS package, you probably should set it up now (or copy Tillmann's file as a starting point). In your packagename/tcl directory, place the following procedures.
# /packagename/tcl/pdf-defs.tcl

ad_library {

    by Jade Rubick (jade@bread.com)
    based on code by Tilmann Singer (tils@tils.net)

    License: GPL

    Generate pdf, preferably in the background.

    Note that there is no locking yet, so there may be more than 1 process
    generating the same pdf. But besides being inefficient this has no
    side-effects. Note this should also use namespaces!
}



ad_proc pdf_file_stub { type id } {

    # add in a security checking field, so that
    # you aren't vulnerable to attacks of the variety:
    # pdf_file_stub \"../../wherever/i/want/to/go\"
    #if { regexp { ([\.\.]) } $type } {
        #return "thats illegal bucko"
    #}

    return "/tmp/$type-$id"
}


ad_proc pdf_generate_pdf_if_necessary { type id } {

    if { ![file exists "[pdf_file_stub $type $id].pdf"] } {
        pdf_generate_pdf $type $id
    }
}


ad_proc pdf_return_pdf { type id } {

    pdf_generate_pdf_if_necessary $type $id
    ns_returnfile 200 application/pdf "[pdf_file_stub $type $id].pdf"
}

ad_proc pdf_generate_pdf { type id } {

    ns_log notice "!> Starting to generate pdf for type: $type id $id"

    file delete "[pdf_file_stub $type $id].txt"
    file delete "[pdf_file_stub $type $id].pdf"

    # there is probably a better way to do this, but you
    # can put checks in here for each type of PDF that you
    # generate, according to "type", and use this procedure
    # to generate the PDF file.

    # the PDF file is generated from a text file, which is
    # read directly from the database. Yes, this is less than
    # an optimal solution. So improve it and share your code
    # with me!

    # example
    if {$type == "test"} {

        # pull something from the database
        db_1row date "select to_char(sysdate,'Mon fmDD, YYYY HH:MI am') as today from dual"
        set txt_file [open "[pdf_file_stub $type $id].txt" w]

        fconfigure $txt_file -encoding "iso8859-1"
        puts $txt_file "$today"
        close $txt_file

        # the logic for creating the businesscard is in the Python file.
        exec "/web/intranet/bin/pdf-test.py" [pdf_file_stub $type $id] >> /tmp/$type.log 2>> /tmp/$type.log

    } elseif {$type == "business_card"} {
        db_1row card "select * from bu_cards where card_id=:card_id"

        # uh, ugly. no xml, just one value per line. saves me typing.
        set txt_file [open "[pdf_file_stub $type $id].txt" w]
        fconfigure $txt_file -encoding "iso8859-1"
        puts $txt_file "$name\n$subtitle\n$line1\n$line2\n$line3"
        close $txt_file

        # the logic for creating the businesscard is in the Python file.
        exec "/web/bin/pdf-businesscard.py" [pdf_file_stub $type $id] >> /tmp/$type.log 2>> /tmp/$type.log

    }
    ns_log notice "!> Finished with type: $type id $id"

}


4. Insert the pdf-xxx.py Python files. I put mine in /web/bin/, but you can put it
  where you like, as long as you modify the Tcl procedures. /web/bin is probably a bad idea.

Here are the Python files:

# pdf-test.py

#!/usr/bin/python


import sys, copy, string, os, fileinput

from reportlab.lib.units import cm
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
from reportlab.lib.styles import getSampleStyleSheet

from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4



bottom_margin = 1*cm
left_margin = 2*cm

height=5.4*cm
width=8.5*cm

file_stub = sys.argv[1]

# never got around dealing with stdin that comes unicode encoded from
# aolserver, grr. The file approach works though.

lines = fileinput.input(file_stub + ".txt")
today = lines[0]


def onecard(c):
    t = c.beginText()
    t.setTextOrigin(0.5*cm, 3.6*cm)
    t.setFont("Helvetica-Bold", 12)
    t.textLines(today)
    c.drawText(t)

    #c.rect(0, 0, width, height)


def grid(c):
    for i in range(0, 6):
        y = bottom_margin + i * height
        c.line(5, y, 20, y)
        c.line(A4[0] - 5, y, A4[0] - 20, y)

    for i in range(0, 3):
        x = left_margin + i * width
        c.line(x, 5, x, 20)
        c.line(x, A4[1] - 5, x, A4[1] - 20)
        


def myFirstPage(c, doc):
    c.line(line_margin,height-margin_from_top,width-line_margin,height-margin_fr
om_top)
    onecard()


        
c = canvas.Canvas(file_stub + ".pdf")

grid(c)


c.translate(left_margin, bottom_margin)

c.saveState()

for i in range(0, 5):
    onecard(c)
    c.translate(0, height)

c.restoreState()

c.translate(width, 0)



for i in range(0, 5):
    onecard(c)
    c.translate(0, height)


c.save()


# pdf-businesscard.py

#!/usr/bin/python


import sys, copy, string, os, fileinput

from reportlab.lib.units import cm
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
from reportlab.lib.styles import getSampleStyleSheet

from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4



bottom_margin = 1*cm
left_margin = 2*cm

height=5.4*cm
width=8.5*cm

file_stub = sys.argv[1]

# never got around dealing with stdin that comes unicode encoded from
# aolserver, grr. The file approach works though.

lines = fileinput.input(file_stub + ".txt")
name = lines[0]
subtitle = lines[1]
line1 = lines[2]
line2 = lines[3]
line3 = lines[4]



def onecard(c):
    t = c.beginText()
    t.setTextOrigin(0.5*cm, 3.6*cm)
    t.setFont("Helvetica-Bold", 12)
    t.textLines(name)
    t.setFont("Helvetica", 10)
    t.textLines(subtitle)
    t.moveCursor(0, 10)
    t.textLines(line1)
    t.textLines(line2)
    t.textLines(line3)
    c.drawText(t)

    #c.rect(0, 0, width, height)


def grid(c):
    for i in range(0, 6):
        y = bottom_margin + i * height
        c.line(5, y, 20, y)
        c.line(A4[0] - 5, y, A4[0] - 20, y)

    for i in range(0, 3):
        x = left_margin + i * width
        c.line(x, 5, x, 20)
        c.line(x, A4[1] - 5, x, A4[1] - 20)
        


def myFirstPage(c, doc):
    c.line(line_margin,height-margin_from_top,width-line_margin,height-margin_fr
om_top)
    onecard()


        
c = canvas.Canvas(file_stub + ".pdf")

grid(c)


c.translate(left_margin, bottom_margin)

c.saveState()

for i in range(0, 5):
    onecard(c)
    c.translate(0, height)

c.restoreState()

c.translate(width, 0)



for i in range(0, 5):
    onecard(c)
    c.translate(0, height)


c.save()

How to set up a new PDF file

You need to edit your pdf-defs.tcl file to include the new type in pdf_generate_pdf. Also create a new pdf-xxx.py Python file, and set it up as you like it. That's basically it.

Next Page