formwizard.tcl
- Location:
- /packages/bug-tracker/www/admin/formwizard.tcl
- Author:
- Luke Pond
- Created:
- 2002-03-06 Code generator to give you a head start with using ATS forms. Generates template::form::element commands for each of the attributes in a table. PostgreSQL only.
Related Files
[ hide source ] | [ make this the default ]
File Contents
ad_page_contract { @author Luke Pond @creation-date 2002-03-06 Code generator to give you a head start with using ATS forms. Generates template::form::element commands for each of the attributes in a table. PostgreSQL only. } { {table_name "acs_objects"} {object_name "object"} {primary_key "object_id"} } if {$table_name ne "" && ![db_0or1row table_name_check { select oid from pg_class where lower(relname) = lower(:table_name) }]} { ad_returnredirect "formwizard" } set form_name $table_name # maps PG datatype to ATS widget array set widget_map { bool select date date timestamp date time date} # maps PG datatype to ATS date format # (can be more extensively customized; see ATS date-procs.tcl) array set date_format_map { date "MONTH DD, YYYY" timestamp "MONTH DD, YYYY HH24:MI" time "HH24:MI" } # maps PG datatype to ATS datatype used for automatic form validation array set datatype_map { int4 integer date date timestamp date time date} # maps PG column name to option list (obtained from check constraints) array set options_map { } db_foreach get_checks { select rcsrc from pg_relcheck r, pg_class c where r.rcrelid = c.oid and lower(c.relname) = lower(:table_name) } { # This works for check constraints of the form "check (col_name in ('a', 'b', 'c'))" set options {} set column_name "" while {[regexp {([A-Za-z_]+) = '([^']+)'(.*)$} $rcsrc match key val rcsrc]} { lappend options [list $val $val] if {$column_name eq ""} { set column_name $key } elseif {$column_name != $key} { # bail out - this isn't what we think it is set options {} break } } if {[llength $options] > 0} { set options_map($key) $options } } set form_elements "" set set_values "" set get_values "" set code "" db_foreach get_columns { select a.oid, a.attname, a.attnotnull, a.atthasdef, a.attnum, t.typname, t.typlen from pg_attribute a, pg_type t, pg_class c where a.attrelid = c.oid and a.atttypid = t.oid and lower(c.relname) = lower(:table_name) and a.attnum > 0 order by a.attnum } { # Make a nicer looking element name set element_name $attname set label [string totitle [join [split $attname _]]] set widget text set datatype text set optional "" set format "" set options "" # See if the datatype implies another widget if {[info exists widget_map($typname)]} { set widget $widget_map($typname) } # See if the datatype implies another form-validation datatype if {[info exists datatype_map($typname)]} { set datatype $datatype_map($typname) } # If the not null constraint does not exist, make it optional if {$attnotnull == "f"} { set optional "-optional" } # If we're using the ATS date widget, look up the format if {$widget eq "date"} { set format "-format \"$date_format_map($typname)\"" } # If the column name ends in "_id", make it a hidden form variable # (which won't always be correct; sometimes it might be a foreign # key that you want to set with a select widget...this is just a guess. if {[regexp {_id$} $attname]} { set element_name $attname set datatype integer set widget hidden } # If the column has a check constraint with a set of # allowable values, make a select widget if {[info exists options_map($attname)]} { set widget select set options "-options {$options_map($attname)}" } # If it's a boolean, add "Yes" and "No" options # Note: this is not necessarily the best UI choice. # Checkbox and radio elements are also available. if {$typname eq "bool"} { set options "-options {{Yes t} {No f}}" } # TODO: If I could figure out that this attribute references the # primary key of another table, I would like to make a select # widget and populate it with a database query. Unfortunately # I don't know how to get that info from PostgreSQL. append form_elements "template::element create $form_name $element_name -label \"$label\" -widget $widget -datatype $datatype $optional $format $options\n" append set_values " template::element set_properties $form_name $element_name -value \$$element_name\n" append get_values " set $element_name \[template::element::get_value $form_name $element_name\]\n" append insert_columns "$attname, " if {$widget eq "date"} { append get_values " if {\$$element_name\ ne \"\"} { set $element_name \[template::util::date::get_property sql_date \$$element_name\] } else { set $element_name NULL } " append insert_bind_vars "\$$attname, " append update_columns "$attname=\$$attname, " append select_columns "to_char($attname, 'YYYY MM DD HH24 MI') as $attname, " } else { append insert_bind_vars ":$attname, " append update_columns "$attname=:$attname, " append select_columns "$attname, " } } if {[info exists insert_columns]} { regsub {, $} $insert_columns {} insert_columns regsub {, $} $insert_bind_vars {} insert_bind_vars regsub {, $} $update_columns {} update_columns regsub {, $} $select_columns {} select_columns # The magic with the cvs-id below is to prevent CVS from catching this instance # and expanding it. set user_id [ad_conn user_id] if { $user_id eq "" || $user_id == 0 } { set author "formwizard.tcl" } else { set author [db_string author { select first_names || ' ' || last_name || ' (' || email || ')' from cc_users where user_id = :user_id }] } set code " ad_page_contract { Add/Edit form for $object_name. (Auto-generated by formwizard.tcl) @author $author @creation-date [ns_fmttime [ns_time] "%B %d, %Y"] @cvs-id $Id$ } { cancel:optional {$primary_key \"\"} {return_url \"\"} } # If the user hit cancel, ignore everything else if { \[info exists cancel\] && \$cancel ne \"\" } { ad_returnredirect \$return_url return } # Set some common bug-tracker variables set project_name \[bug_tracker::conn project_name\] set package_id \[ad_conn package_id\] set package_key \[ad_conn package_key\] # TODO: check that the handling of the primary key is okay. If there is # no primary key and you're only inserting, you can just ignore it. # Add handling for any other incoming URL variables that should become part of the form. template::form create $form_name $form_elements template::element create $form_name insert_or_update -widget hidden -datatype text template::element create $form_name return_url -widget hidden -datatype text -value \$return_url if { \[template::form is_request $form_name\] } { if {\$$primary_key\ eq \"\"} { set insert_or_update insert template::element set_properties $form_name insert_or_update -value insert # TODO: If the form contains hidden elements that represent # primary keys or foreign keys that were passed to this # page as URL parameters, set them here as follows: set $primary_key \[db_string get_seq {select nextval('${table_name}_${primary_key}_seq')}\] template::element set_properties $form_name $primary_key -value \$$primary_key } else { set insert_or_update update template::element set_properties $form_name insert_or_update -value update # Since we're editing a row, get the current values # TODO: make sure none of the columns being selected are # clobbering URL variables you added to ad_page_contract!! db_1row get_current_values \" select $select_columns from $table_name where $primary_key = :$primary_key \" $set_values } } set insert_or_update \[template::element::get_value $form_name insert_or_update\] if {\$insert_or_update == \"insert\"} { set page_title \"Adding a new $object_name\" set context_bar \[ad_context_bar \"Add $object_name\"\] } else { set page_title \"Editing $object_name \$$primary_key\" set context_bar \[ad_context_bar \"Edit $object_name\"\] } if { \[template::form is_valid $form_name\] } { # valid form submission $get_values if {\$insert_or_update == \"insert\"} { if {\[db_0or1row check_exists \" select 1 from $table_name where $primary_key = :$primary_key \"\]} { # detected a double form submission - you can return # an error if you want, but it's not really necessary } else { db_dml insert_row \" insert into $table_name ($insert_columns) values ($insert_bind_vars) \" } } else { db_dml update_row \" update $table_name set $update_columns where $primary_key = :$primary_key \" } ad_returnredirect \$return_url ad_script_abort } " } set page_title "Form Wizard" ns_return 200 text/html " <html> <head><title>$page_title</title></head> <body> <h2>$page_title</h2> automatically generates code for ATS forms from PostgreSQL data model <hr> <form action=formwizard method=get> <center><table> <tr><td><b>Table name:</b></td><td><input name=table_name value=\"$table_name\"></td></tr> <tr><td><b>Object name:</b></td><td><input name=object_name value=\"$object_name\"> <font size=-1>(human-readable name)</font></td></tr> <tr><td><b>Primary key:</b></td><td><input name=primary_key value=\"$primary_key\"></td></tr> <tr><td colspan=2 align=center><input type=submit value=\"Run Form Wizard\"></td></tr> </table></center> </form> <script language=\"javascript\"> // <!-- function code_copy() { field = eval(\"document.code.code\"); field.focus(); field.select(); range = field.createTextRange(); range.execCommand(\"Copy\"); } // --> </script> <h3>In a textarea for easy copying</h3> <form name=\"code\"> <textarea name=\"code\" cols=80 rows=10>$code</textarea> <input type=submit value=\"Copy to clipboard\" onclick=\"javascript:code_copy();\"> </form> <h3>In the page for easy reading</h3> <pre> $code </pre> </body> </html> "