Build a Package

This section requires that you first complete Installation Guide. That section tries to eliminate all variables so that you have the smoothest possible installation. But this part assumes that you want to learn how to create your own package, and every package (unlike every installation) is different. So in this part, you'll have to make lots of changes in order to get results. At the end of this part, you should have your own, slightly customized package, and you then install a reference version and compare.

Create a new package

The OpenACS package manager will set up the initial directories, meta-information files, and database entries for a new package.

Browse to http://yourserver:8000 and click on Package Manager.

Click Create a New Package.

Fill in the fields listed below. Tab through the rest. (Some will change automatically. Don't mess with those.)

  • Package Key: tutorialapp

  • Package Name: Tutorial App

  • Package Plural: Tutorial Apps

  • Initial Version: 0.1d

  • Summary: This is my first package.

At the bottom, click Create Package.

Write the Requirements and Design Specs

Now it's time to document. For a new package you should start by copying the documentation template from /web/openacs-dev/packages/acs-core-docs/xml/docs/xml/package-documentation-template.xml to yourpackage/www/docs/xml/package-documentation.xml.

You then open that file with emacs, write the requirements and design section, generate html, and start coding. For this tutorial, you should instead install the pre-written documentation files for the tutorial app, examine them, generate html, read it, and then proceed to build the package. Store any diagrams in native format in the xml directory, and store png or jpg versions of the diagrams in the doc direcory.

In this case, though, just copy the pre-written documentation files. You should be logged in as nsadmin throughout this section.

mkdir -p /web/openacs-dev/packages/tutorialapp/www/doc/xml
cd /web/openacs-dev/packages/tutorialapp/www/doc/
cp /tmp/package-documentation.xml  xml
cp /tmp/*.png .
cp /tmp/*.dia .
emacs xml/package-documentation.xml

OpenACS uses DocBook for documentation. DocBook is an XML standard for semantic markup of documentation. That means that the tags you use indicate meaning, not intended appearance. The style sheet will determine appearance.

Examine the file. Find the version history (look for the tag <revhistory>). Add a new record to the document version history. Look for the <authorgroup> tag and add yourself as a second author. Save and exit. For tips on editing SGML files in emacs, see [[Staflin]]

Process the xml file to create html documentation. The html documentation is stored in the tutorialapp/www/docs/ directory. When the package is mounted in the site map, the html will automatically be published at http://yoursite:8000/tutorialapp/doc

cd /web/openacs-dev/packages/tutorialapp/www/
xsltproc -o doc/ /web/openacs-dev/packages/acs-core-docs/www/xml/ja-openacs.xsl doc/xml/package-documentation.xml 

Add the new package to CVS

cd /web/openacs-dev/packages
cvs add tutorialapp
cd tutorialapp
cvs add tutorialapp.info
cvs add www
cd www
cvs add doc
cd doc
cvs add *
cvs add xml
cd xml
cvs add *
cd ../../..
mkdir tcl
cvs add tcl
mkdir -p sql/postgresql
cvs add sql
cd sql
cvs add postgresql
cd ..
cvs commit -m "new package"

Mount the Package

Every package must be mounted within the site map in order to function.

Browse to http://yourserver:8000 and click on Site Map

Click the new sub folder link on the Main Site line.

Type tutorialapp and click New.

On the tutorialapp line, click the new application link.

Choose Tutorial App and enter tutorial as the name, then click New.

Browse to http://yourserver:8000/tutorialapp/doc/ and examine the documentation. Make sure that your changes are reflected.

Code the data model

cd /web/openacs-dev/packages/tutorialapp/sql/postgresql
emacs tutorialapp-create.sql

Paste this into the file and save and exit.

/* That was the standard comment bar at the top of the file. Everything between the dollar signs in the cvs-id tag will be updated automatically by cvs. This file does all of the database setup necessary for our package, creating a table and related functions, and calling many functions to connect the table into the ACS system, including into full text search. */

-- -- packages/tutorialapp/sql/postgresql/tutorialapp-create.sql -- -- @author rhs@mit.edu -- @creation-date 2000-10-22 -- @cvs-id $Id: ch04s02.html,v 1.1 2004/05/07 15:04:29 aufrecht Exp $ -- --

/* This creates a temporary function which calls the __create_type function on the acs_object_type table. That function tells ACS that each record in our new table is an instance of a new object called tutorialnote, and that tutorialnote is a subtype of the generic acs_object.*/

create function inline_0 () returns integer as ' begin PERFORM acs_object_type__create_type ( ''tutorialnote'', -- object_type ''Tutorialnote'', -- pretty_name ''Tutorialnotes'', -- pretty_plural ''acs_object'', -- supertype ''tutorialapp'', -- table_name ''tutorialnote_id'', -- id_column null, -- package_name ''f'', -- abstract_p null, -- type_extension_table ''tutorialnote.name'' -- name_method ); return 0; end;' language 'plpgsql'; select inline_0 (); drop function inline_0 ();

/* This creates a temporary function which twice calls the __create_attribute function on the acs_attribute table. That function tells ACS that two of the fields in our new table, title and body, are special - they are attributes of the tutorialnote object that we created earlier. Hooking up this fields like this will provide some unspecified benefit in the future.*/

create function inline_1 () returns integer as ' begin PERFORM acs_attribute__create_attribute ( ''tutorialnote'', -- object_type ''TITLE'', -- attribute_name ''string'', -- datatype ''Title'', -- pretty_name ''Titles'', -- pretty_plural null, -- table_name null, -- column_name null, -- default_value 1, -- min_n_values 1, -- max_n_values null, -- sort_order ''type_specific'', -- storage ''f'' -- static_p ); PERFORM acs_attribute__create_attribute ( ''tutorialnote'', -- object_type ''BODY'', -- attribute_name ''string'', -- datatype ''Body'', -- pretty_name ''Bodies'', -- pretty_plural null, -- table_name null, -- column_name null, -- default_value 1, -- min_n_values 1, -- max_n_values null, -- sort_order ''type_specific'', -- storage ''f'' -- static_p ); return 0; end;' language 'plpgsql'; select inline_1 (); drop function inline_1 ();

/* Now we actually create the tutorialapp table. The tutorialnote_id field is based on the acs_objects id, and owner comes from the users table. */

create table tutorialapp ( tutorialnote_id integer constraint tutorialapp_tutorialnote_id_fk references acs_objects(object_id) constraint tutorialapp_tutorialnote_id_pk primary key, owner_id integer constraint tutorialapp_owner_id_fk references users(user_id), title varchar(255) constraint tutorialapp_title_nn not null, body varchar(1024) );

/* Whenever a new record is created, it will be done through this function, tutorialnote__new, instead of through a simple "insert into" command. The __new function sets default values for most fields. It creates a new acs_object and puts that object's id in a temporary variable, v_tutorialnote_id. Then it actually creates the record, grants admin permission on that object to the owner, and returns the new object's id.*/

create function tutorialnote__new (integer,integer,varchar,varchar,varchar,timestamp,integer,varchar,integer) returns integer as ' declare p_tutorialnote_id alias for $1; -- default null p_owner_id alias for $2; -- default null p_title alias for $3; p_body alias for $4; p_object_type alias for $5; -- default ''tutorialnote'' p_creation_date alias for $6; -- default now() p_creation_user alias for $7; -- default null p_creation_ip alias for $8; -- default null p_context_id alias for $9; -- default null v_tutorialnote_id tutorialapp.tutorialnote_id%TYPE; begin v_tutorialnote_id := acs_object__new ( p_tutorialnote_id, p_object_type, p_creation_date, p_creation_user, p_creation_ip, p_context_id ); insert into tutorialapp (tutorialnote_id, owner_id, title, body) values (v_tutorialnote_id, p_owner_id, p_title, p_body); PERFORM acs_permission__grant_permission( v_tutorialnote_id, p_owner_id, ''admin'' ); return v_tutorialnote_id; end;' language 'plpgsql';

/* Records will be deleted through this function, tutorialnote__delete. It removes the permissions for the record, then removes the record, then removes the corresponding acs_object.*/

create function tutorialnote__delete (integer) returns integer as ' declare p_tutorialnote_id alias for $1; begin delete from acs_permissions where object_id = p_tutorialnote_id; delete from tutorialapp where tutorialnote_id = p_tutorialnote_id; raise NOTICE ''Deleting tutorialnote...''; PERFORM acs_object__delete(p_tutorialnote_id); return 0; end;' language 'plpgsql';

/* The tutorialnote__name function returns the name. This function is present (I assume) by convention, so that even tables and objects that don't have fields called "name" can return useful names. In this case, it returns the field title as the name.*/

create function tutorialnote__name (integer) returns varchar as ' declare p_tutorialnote_id alias for $1; v_tutorialnote_name tutorialapp.title%TYPE; begin select title into v_tutorialnote_name from tutorialapp where tutorialnote_id = p_tutorialnote_id; return v_tutorialnote_name; end; ' language 'plpgsql';

/* This calls another file to set up full text search. */

\i tutorialapp-sc-create.sql

Create a tcl file to hold a procedure to support text search.


emacs ../../tcl/tutorialapp-procs.tcl

Paste this into the file and save and exit

ad_proc tutorialapp__datasource {
    object_id
} {
    @author Neophytos Demetriou
} {
    db_0or1row tutorialapp_datasource {
        select t.tutorialnote_id as object_id,
               t.title as title,
               t.body as content,
               'text/plain' as mime,
               '' as keywords,
               'text' as storage_type
        from tutorialapp t
        where tutorialnote_id = :object_id
    } -column_array datasource

    return [array get datasource]
}

ad_proc tutorialapp__url {
    object_id
} {
    @author Neophytos Demetriou
} {

    set package_id [apm_package_id_from_key tutorialapp]
    db_1row get_url_stub "
        select site_node__url(node_id) as url_stub
        from site_nodes
        where object_id=:package_id
    "

    set url "${url_stub}note?tutorialnote_id=$object_id"

    return $url
}

Create a database file to create the full text search functions for our package.

emacs tutorialapp-sc-create.sql

Paste this into the file and save and exit

-- packages/tutorialapp/sql/tutorialapp-sc-create.sql
--
-- @cvs-id $Id: ch04s02.html,v 1.1 2004/05/07 15:04:29 aufrecht Exp $
--

/* These three function calls tell the system that our package provides information for full text search*/

select acs_sc_impl__new( 'FtsContentProvider', -- impl_contract_name 'tutorialnote', -- impl_name 'tutorialapp' -- impl_owner_name ); select acs_sc_impl_alias__new( 'FtsContentProvider', -- impl_contract_name 'tutorialnote', -- impl_name 'datasource', -- impl_operation_name 'tutorialapp__datasource', -- impl_alias 'TCL' -- impl_pl ); select acs_sc_impl_alias__new( 'FtsContentProvider', -- impl_contract_name 'tutorialnote', -- impl_name 'url', -- impl_operation_name 'tutorialapp__url', -- impl_alias 'TCL' -- impl_pl );

/* Whenever a record is changed, a trigger calls a function to update the full text search index. */

create function tutorialapp__itrg () returns opaque as ' begin perform search_observer__enqueue(new.tutorialnote_id,''INSERT''); return new; end;' language 'plpgsql'; create function tutorialapp__dtrg () returns opaque as ' begin perform search_observer__enqueue(old.tutorialnote_id,''DELETE''); return old; end;' language 'plpgsql'; create function tutorialapp__utrg () returns opaque as ' begin perform search_observer__enqueue(old.tutorialnote_id,''UPDATE''); return old; end;' language 'plpgsql'; create trigger tutorialapp__itrg after insert on tutorialapp for each row execute procedure tutorialapp__itrg (); create trigger tutorialapp__dtrg after delete on tutorialapp for each row execute procedure tutorialapp__dtrg (); create trigger tutorialapp__utrg after update on tutorialapp for each row execute procedure tutorialapp__utrg ();

Create a database file to drop everything for uninstall.

emacs tutorialapp-drop.sql

Paste this into the file and save and exit.

-- packages/tutorialapp/sql/tutorialapp-drop.sql
-- drop script
-- Vinod Kurup, vkurup@massmed.org
--

-- This script removes from the database everything associated with our table.

-- This calls another file to remove the related full text search functions

\i tutorialapp-sc-drop.sql --drop functions drop function tutorialnote__new (integer,integer,varchar,varchar,varchar,timestamp,integer,varchar,integer); drop function tutorialnote__delete (integer); drop function tutorialnote__name (integer); --drop permissions delete from acs_permissions where object_id in (select tutorialnote_id from tutorialapp); --drop objects create function inline_0 () returns integer as ' declare object_rec record; begin for object_rec in select object_id from acs_objects where object_type=''tutorialnote'' loop perform acs_object__delete( object_rec.object_id ); end loop; return 0; end;' language 'plpgsql'; select inline_0(); drop function inline_0(); --drop table drop table tutorialapp; --drop attributes select acs_attribute__drop_attribute ( 'tutorialnote', 'TITLE' ); select acs_attribute__drop_attribute ( 'tutorialnote', 'BODY' ); --drop type select acs_object_type__drop_type( 'tutorialnote', 't' );

Create another database file to drop the full text search functions.

emacs tutorialapp-sc-drop.sql

Paste this into the file and save and exit

-- packages/tutorialapp/sql/tutorialapp-sc-drop.sql
--
-- @cvs-id $Id: ch04s02.html,v 1.1 2004/05/07 15:04:29 aufrecht Exp $
--

-- This script removes all full text search functions for tutorialapp from the database.

select acs_sc_impl__delete( 'FtsContentProvider', -- impl_contract_name 'tutorialnote' -- impl_name ); drop trigger tutorialapp__utrg on tutorialapp; drop trigger tutorialapp__dtrg on tutorialapp; drop trigger tutorialapp__itrg on tutorialapp; drop function tutorialapp__utrg (); drop function tutorialapp__dtrg (); drop function tutorialapp__itrg ();

Add the database files to cvs.

cvs add *.sql
cd ..
cvs add tcl
cvs add tcl/*
cvs commit -m "new files"
psql -f tutorialapp-create.sql openacs-dev
restart-aolserver openacs-dev

The psql command executes the create script and loads the table and related functions into the database.

Build the "Index" page

Start adding the user interface and logic pages. The first file we're going to create is index.tcl, which is the default page for the pacage. It is supported by two other files, index.adp and index-postgresql.xql. All of these files live in the www subdirectory in the package.

Create index.tcl, the default page for the package.

cd www
emacs index.tcl

Paste this into the file and save and exit

# main index page for tutorialapp.

# The ad_page_contract function lists the public variables and their types.

ad_page_contract { @author rhs@mit.edu @creation-date 2000-10-23 @cvs-id $Id: ch04s02.html,v 1.1 2004/05/07 15:04:29 aufrecht Exp $ } -properties { tutorialnote:multirow context_bar:onevalue create_p:onevalue }

# The unique identifier for this package.

set package_id [ad_conn package_id]

# The id of the person logged in and browsing this page

set user_id [ad_conn user_id]

# An HTML block for the breadcrumb trail

set context_bar [ad_context_bar]

# Does the current user have permission to create?

set create_p [ad_permission_p $package_id create]

# Call a database function labelled tutorialnote, and store the # results in an array called tutorialnote. The contents of the query # are stored in other files specific to the database type.

db_multirow tutorialnote tutorialnote {} ad_return_template

# Return the results through an adp template. By default, look for a file with the same name, # so index.tcl invokes index.adp.

Create the file with the database query.

emacs index-postgresql.xql

Paste this into the file and save and exit

<queryset>
<rdbms><type>postgresql</type><version>7.2</version></rdbms>
<fullquery name="tutorialnote">
      <querytext>
  	select tutorialnote_id, 
               owner_id, 
	       title, 
               body,
	       case 
                 when acs_permission__permission_p(tutorialnote_id,:user_id,'write')='t'
                 then 1 
                 else 0 
                 end 
		 as write_p,
               case 
                 when acs_permission__permission_p(tutorialnote_id,:user_id,'admin')='t'
                 then 1 
                 else 0 
                 end 
                 as admin_p,
               case 
               when acs_permission__permission_p(tutorialnote_id,:user_id,'delete')='t'
                 then 1 
                 else 0 
                 end 
                 as delete_p
 	from tutorialapp n, acs_objects o
  	where n.tutorialnote_id = o.object_id
  	  and o.context_id = :package_id
  	  and acs_permission__permission_p(tutorialnote_id, :user_id, 'read') = 't'
  	order by creation_date
      </querytext>
</fullquery>
</queryset>

Xql files hold database-specific language. Because our package has only an index-postgresql.xql file and not an index-oracle.xql file, the index page won't work in Oracle. This SELECT statement returns all of the tutorialapp that the selector has permission to read. It uses case statements to convert some procedure calls (__permission_p) into binary (0 or 1) return values.

Create the user-visible page.

emacs index.adp

Paste this into the file and save and exit

<master>

<!-- Put the contents of the variable context_bar here -->

@context_bar@ <hr> <center> <table border=0 cellpadding=1 cellspacing=0 width=80%> <tr><td bgcolor=#aaaaaa> <table border=0 cellpadding=3 cellspacing=0 width=100%>

<!-- Iterate through the rows of the variable tutorialnote, generating HTML table rows for each record -->

<multiple name=tutorialnote> <if @tutorialnote.rownum@ odd> <tr bgcolor=#eeeeee> </if> <else> <tr bgcolor=#ffffff> </else> <td valign=top width=1%>

<!-- If the user can delete records, show a delete button -->

<if @tutorialnote.delete_p@ eq 1> <a href=delete?tutorialnote_id=@tutorialnote.tutorialnote_id@><img border=0 src=x alt=" [Delete] "></a> </if> <else> <img border=0 src=x-disabled alt=" [Can't Delete] "> </else> </td> <td>  <a href=note?tutorialnote_id=@tutorialnote.tutorialnote_id@>@tutorialnote.title@</a>

<!-- If the user can add records, show an add button -->

<if @tutorialnote.write_p@ eq 1> [<a href=add-edit?tutorialnote_id=@tutorialnote.tutorialnote_id@>Edit</a>] </if> <table border=0 cellpadding=4 cellspacing=0> <tr> <td> </td> <td>

<!-- Insert the body variable, replacing line feeds with html line feeds-->

<% regsub -all "\n" $tutorialnote(body) "<br>" body adp_puts $body %> </td> </tr> </table> </td> <td valign=top align=right> <if @tutorialnote.admin_p@ eq 1> <font size=-1>(<a href=../permissions/one?object_id=@tutorialnote.tutorialnote_id@>admin</a>)</font> </if> <else>   </else> </td> </tr> </multiple> <if @tutorialnote:rowcount@ eq 0> <tr bgcolor=#eeeeee> <td colspan=2 align=center><br>(no tutorialnote)<br> </td> </tr> </if> <tr bgcolor=#aaaaaa> <td colspan=2 align=center> <if @create_p@ eq 1> <a href=add-edit><img border=0 src=add alt=" [Add] "></a> </if> <else> <img border=0 src=add-disabled alt=" [Can't Add Tutorialnote] "> </else> </td> </tr> </table> </td><tr> </table> </center>

Build the "add-edit" page

Create add-edit.tcl, the page used for adding new notes.

emacs add-edit.tcl

Paste this into the file and save and exit.

# packages/tutorialapp/www/add-edit.tcl
ad_page_contract {

  @author rhs@mit.edu
  @creation-date 2000-10-23
  @cvs-id $Id: ch04s02.html,v 1.1 2004/05/07 15:04:29 aufrecht Exp $
} {
	tutorialnote_id:integer,notnull,optional
	{title:html,notnull,optional ""}
	{body ""}
} -properties {
	context_bar:onevalue
}

# the second argument of ad_page_contract specifies the expected incoming # variables. This page has no required variables, but can accept tutorialnote_id, # title, and body. It will make title and body empty strings if they are # not specified.

set package_id [ad_conn package_id] if {[info exists tutorialnote_id]} { ad_require_permission $tutorialnote_id write set context_bar [ad_context_bar "Edit Tutorialnote"] } else { ad_require_permission $package_id create set context_bar [ad_context_bar "New Tutorialnote"] }

# If an existing tutorialnote id is provided, make sure the user has permission to # edit it. If no id is provided, make sure the user has permission to # create a new tutorialnote. In either case, update the context bar (breadcrumb # trail) with the appropriate title.

# Use the template system to prepare an html form.

template::form create new_tutorialnote if {[template::form is_request new_tutorialnote] && [info exists tutorialnote_id]} { template::element create new_tutorialnote tutorialnote_id \ -widget hidden \ -datatype number \ -value $tutorialnote_id db_1row tutorialnote_select {} } template::element create new_tutorialnote title \ -datatype text \ -label "Title" \ -html { size 20 } \ -value $title template::element create new_tutorialnote body \ -widget textarea \ -datatype text \ -label "Body" \ -html { rows 10 cols 40 wrap soft } \ -value $body

# This page is used both to display forms for editing and # to process submitted forms. If the form was submitted, # update or create it as appropriate, then redirect to the # index page

if [template::form is_valid new_tutorialnote] { set user_id [ad_conn user_id] set peeraddr [ad_conn peeraddr] if [info exists tutorialnote_id] { db_dml tutorialnote_update {} } else { db_exec_plsql new_tutorialnote {} } ad_returnredirect "." } ad_return_template

Create the database query file.

emacs add-edit-postgresql.xql

Paste this into the file and save and exit

<?xml version="1.0"?>

<queryset>
   <rdbms><type>postgresql</type><version>7.1</version></rdbms>

<fullquery name="new_tutorialnote">      
      <querytext>
        select tutorialnote__new(
		  null,
		  :user_id,
          :title,
          :body,
          'tutorialnote',
		  now(),
		  :user_id,
		  :peeraddr,
		  :package_id
        );

      </querytext>
</fullquery>

<fullquery name="tutorialnote_select">
    <querytext>
      select title, 
             body
        from tutorialapp
       where tutorialnote_id = :tutorialnote_id
    </querytext>
</fullquery>

<fullquery name="tutorialnote_update">
    <querytext>
      update tutorialapp
      set title = :title,
           body = :body
      where tutorialnote_id = :tutorialnote_id
    </querytext>
</fullquery>
 
</queryset>

Create the user-visible page.

emacs add-edit.adp

Paste this into the file and save and exit

<master>

@context_bar@

<hr>

<center>
<formtemplate id="new_tutorialnote"></formtemplate>
</center>

Build the "delete" page

Create delete.tcl, the page used for adding new notes.

emacs delete.tcl

Paste this into the file and save and exit.

# packages/tutorialapp/www/delete.tcl
ad_page_contract {

  @author rhs@mit.edu
  @creation-date 2000-10-23
  @cvs-id $Id: ch04s02.html,v 1.1 2004/05/07 15:04:29 aufrecht Exp $
} {
  note_id:integer,notnull
}

# Check that the current user has permission to # delete the indicated note.

ad_require_permission $note_id delete

# call the database procudure to delete the note

db_exec_plsql note_delete { begin note.delete(:note_id); end; }

# return the viewer to the default page of the directory

ad_returnredirect "."

Create the database query file.

emacs delete-postgresql.xql

Paste this into the file and save and exit

<?xml version="1.0"?>

<queryset>
   <rdbms><type>postgresql</type><version>7.1</version></rdbms>

<fullquery name="note_delete">
      <querytext>

    select note__delete( :note_id );

      </querytext>
</fullquery>
</queryset>

Add the "note" page

The note page displays individual notes.

emacs note.tcl

Paste this into the file and save and exit.

# packages/tutorialapp/www/note.tcl
ad_page_contract {
    @author Neophytos Demetriou <k2pts@yahoo.com>
    @creation-date 2001-09-02
} {
    tutorialnote_id:integer,notnull
} -properties {
    context_bar:onevalue
    title:onevalue
    body:onevalue
}

set context_bar [ad_context_bar]

db_1row tutorialnote_select {}

ad_return_template
	

Create the database query file.

emacs note-postgresql.xql

Paste this into the file and save and exit

<?xml version="1.0"?>

<queryset>
   <rdbms><type>postgresql</type><version>7.2</version></rdbms>

<fullquery name="tutorialnote_select">
    <querytext>
      select title, 
             body
        from tutorialapp
       where tutorialnote_id = :tutorialnote_id
    </querytext>
</fullquery>

</queryset>

Create the user-visible page.

emacs note.adp

Paste this into the file and save and exit

<master>
<h2>@title@</h2>
@context_bar@
<hr>

@body@

Update CVS

Add all this to cvs.

cvs add *
cvs commit -m ""

Update the Package

Before this page will work, we have to inform the package manager of the .xql file, so that it can handle database requests.

Go to the package manager and click on tutorialapp.

Click on Manage file information

Click on Scan the packages/tutorialapp directory for additional files in this package

Click Add Checked Files

restart-aolserver openacs-dev

Browse to http://yourserver:8000/tutorialapp/ and verify that the page comes up without an error.

Manual test

Make a list of basic tests to make sure it works

Test NumActionExpected Result
001Browse to the index page while not logged in and while one or more notes exist.No edit or delete or add links should appear.
002Browse to the index page while logged in. An Edit link should appear. Click on it. Fill out the form and click Submit.The text added in the form should be visible on the index page.

Other things to test: try to delete someone else's note. Try to delete your own note. Edit your own note. Search for a note.

Write automated tests

(Forthcoming.)

Use Content Repository API

(Forthcoming.)

Set up Package Dependencies

Browse to the package manager. Click on tutorialapp.

Click on Manage dependency information

Click on Add a service required by this package

Select acs-service-contract, version 4.6 and Add Dependency

Tag the package in CVS

Assuming that the package passed testing and is ready to deploy, we need to indicate that it's ready to go to production. This bit is a little tricky: Because the package includes database changes, the best way to deploy into production is through the APM. But once it's in production, we'll want a way to make non-database changes and deploy them. So we're going to tag the package as production-ready, then create a matching package for the APM.

cd /web/openacs-dev/packages/tutorialapp
cvs tag -F current

Prepare the package for distribution.

Browse to the package manager. Click on tutorialapp.

Click on Generate a distribution file for this package from the filesystem.

Click on the file size (37.1KB) after the label Distribution File: and save the file to /tmp.

Add a comment
Last modified: Fri May 07 10:04:29 CDT 2004