[Jifty-commit] r3698 - in jifty/branches/virtual-models: . inc/Module lib/Jifty lib/Jifty/Action lib/Jifty/Model lib/Jifty/Plugin lib/Jifty/Plugin/ActorMetadata lib/Jifty/Plugin/ActorMetadata/Mixin lib/Jifty/Plugin/ActorMetadata/Mixin/Model lib/Jifty/Plugin/Authentication/Password lib/Jifty/Plugin/Feedback lib/Jifty/Plugin/GoogleMap lib/Jifty/Plugin/REST lib/Jifty/Plugin/SiteNews/View lib/Jifty/Plugin/Userpic lib/Jifty/Script lib/Jifty/View/Declare lib/Jifty/View/Static lib/Jifty/Web/Form lib/Jifty/Web/Form/Field share/plugins/Jifty/Plugin/GoogleMap share/plugins/Jifty/Plugin/GoogleMap/web share/plugins/Jifty/Plugin/GoogleMap/web/static share/plugins/Jifty/Plugin/GoogleMap/web/static/css share/plugins/Jifty/Plugin/GoogleMap/web/static/js t/TestApp-Plugin-AppPluginHasModels t/TestApp-Plugin-AppPluginHasModels/bin t/TestApp-Plugin-AppPluginHasModels/doc t/TestApp-Plugin-AppPluginHasModels/etc t/TestApp-Plugin-AppPluginHasModels/lib t/TestApp-Plugin-AppPluginHasModels/lib/TestApp t/TestApp-Plugin-AppPluginHasModels/lib/TestApp/Plugin t/TestApp-Plugin-AppPluginHasModels/lib/TestApp/Plugin/AppPluginHasModels t/TestApp-Plugin-AppPluginHasModels/lib/TestApp/Plugin/AppPluginHasModels/Action t/TestApp-Plugin-AppPluginHasModels/lib/TestApp/Plugin/AppPluginHasModels/Model t/TestApp-Plugin-AppPluginHasModels/lib/TestApp/Plugin/AppPluginHasModels/Plugin t/TestApp-Plugin-AppPluginHasModels/lib/TestApp/Plugin/AppPluginHasModels/Plugin/MyAppPlugin t/TestApp-Plugin-AppPluginHasModels/lib/TestApp/Plugin/AppPluginHasModels/Plugin/MyAppPlugin/Model t/TestApp-Plugin-AppPluginHasModels/log t/TestApp-Plugin-AppPluginHasModels/share t/TestApp-Plugin-AppPluginHasModels/share/po t/TestApp-Plugin-AppPluginHasModels/share/web t/TestApp-Plugin-AppPluginHasModels/share/web/static t/TestApp-Plugin-AppPluginHasModels/share/web/templates t/TestApp-Plugin-AppPluginHasModels/t t/TestApp-Plugin-AppPluginHasModels/var t/TestApp-Plugin-AppPluginHasModels/var/mason t/TestApp-Plugin-AppPluginHasModels/var/mason/cache t/TestApp-Plugin-AppPluginHasModels/var/mason/obj t/TestApp/lib/TestApp t/TestApp/t

jifty-commit at lists.jifty.org jifty-commit at lists.jifty.org
Wed Jul 18 17:53:42 EDT 2007


Author: sterling
Date: Wed Jul 18 17:53:40 2007
New Revision: 3698

Added:
   jifty/branches/virtual-models/lib/Jifty/Plugin/ActorMetadata/
   jifty/branches/virtual-models/lib/Jifty/Plugin/ActorMetadata.pm
   jifty/branches/virtual-models/lib/Jifty/Plugin/ActorMetadata/Mixin/
   jifty/branches/virtual-models/lib/Jifty/Plugin/ActorMetadata/Mixin/Model/
   jifty/branches/virtual-models/lib/Jifty/Plugin/ActorMetadata/Mixin/Model/ActorMetadata.pm
   jifty/branches/virtual-models/lib/Jifty/Plugin/GoogleMap/
   jifty/branches/virtual-models/lib/Jifty/Plugin/GoogleMap.pm
   jifty/branches/virtual-models/lib/Jifty/Plugin/GoogleMap/Widget.pm
   jifty/branches/virtual-models/lib/Jifty/Plugin/Userpic/
   jifty/branches/virtual-models/lib/Jifty/Plugin/Userpic.pm
   jifty/branches/virtual-models/lib/Jifty/Plugin/Userpic/Dispatcher.pm
   jifty/branches/virtual-models/lib/Jifty/Plugin/Userpic/View.pm
   jifty/branches/virtual-models/lib/Jifty/Plugin/Userpic/Widget.pm
   jifty/branches/virtual-models/share/plugins/Jifty/Plugin/GoogleMap/
   jifty/branches/virtual-models/share/plugins/Jifty/Plugin/GoogleMap/web/
   jifty/branches/virtual-models/share/plugins/Jifty/Plugin/GoogleMap/web/static/
   jifty/branches/virtual-models/share/plugins/Jifty/Plugin/GoogleMap/web/static/css/
   jifty/branches/virtual-models/share/plugins/Jifty/Plugin/GoogleMap/web/static/css/google_map.css
   jifty/branches/virtual-models/share/plugins/Jifty/Plugin/GoogleMap/web/static/js/
   jifty/branches/virtual-models/share/plugins/Jifty/Plugin/GoogleMap/web/static/js/google_map.js
   jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/
   jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/Makefile.PL
   jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/bin/
   jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/bin/jifty   (contents, props changed)
   jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/doc/
   jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/etc/
   jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/etc/config.yml
   jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/lib/
   jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/lib/TestApp/
   jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/lib/TestApp/Plugin/
   jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/lib/TestApp/Plugin/AppPluginHasModels/
   jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/lib/TestApp/Plugin/AppPluginHasModels/Action/
   jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/lib/TestApp/Plugin/AppPluginHasModels/Model/
   jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/lib/TestApp/Plugin/AppPluginHasModels/Plugin/
   jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/lib/TestApp/Plugin/AppPluginHasModels/Plugin/MyAppPlugin/
   jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/lib/TestApp/Plugin/AppPluginHasModels/Plugin/MyAppPlugin.pm
   jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/lib/TestApp/Plugin/AppPluginHasModels/Plugin/MyAppPlugin/Model/
   jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/lib/TestApp/Plugin/AppPluginHasModels/Plugin/MyAppPlugin/Model/Color.pm
   jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/log/
   jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/share/
   jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/share/po/
   jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/share/web/
   jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/share/web/static/
   jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/share/web/templates/
   jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/t/
   jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/t/plugin-model.t
   jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/var/
   jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/var/mason/
   jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/var/mason/cache/
   jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/var/mason/obj/
Modified:
   jifty/branches/virtual-models/   (props changed)
   jifty/branches/virtual-models/META.yml
   jifty/branches/virtual-models/Makefile.PL
   jifty/branches/virtual-models/inc/Module/Install.pm
   jifty/branches/virtual-models/lib/Jifty/Action/Record.pm
   jifty/branches/virtual-models/lib/Jifty/ClassLoader.pm
   jifty/branches/virtual-models/lib/Jifty/Model/ModelClass.pm
   jifty/branches/virtual-models/lib/Jifty/Model/ModelClassColumn.pm
   jifty/branches/virtual-models/lib/Jifty/Notification.pm
   jifty/branches/virtual-models/lib/Jifty/Plugin.pm
   jifty/branches/virtual-models/lib/Jifty/Plugin/Authentication/Password/View.pm
   jifty/branches/virtual-models/lib/Jifty/Plugin/CompressedCSSandJS.pm
   jifty/branches/virtual-models/lib/Jifty/Plugin/Feedback/View.pm
   jifty/branches/virtual-models/lib/Jifty/Plugin/REST/Dispatcher.pm
   jifty/branches/virtual-models/lib/Jifty/Plugin/SiteNews/View/News.pm
   jifty/branches/virtual-models/lib/Jifty/Plugin/TabView/View.pm
   jifty/branches/virtual-models/lib/Jifty/Record.pm
   jifty/branches/virtual-models/lib/Jifty/Schema.pm
   jifty/branches/virtual-models/lib/Jifty/Script/Schema.pm
   jifty/branches/virtual-models/lib/Jifty/Util.pm
   jifty/branches/virtual-models/lib/Jifty/View/Declare/CRUD.pm
   jifty/branches/virtual-models/lib/Jifty/View/Declare/Page.pm
   jifty/branches/virtual-models/lib/Jifty/View/Static/Handler.pm
   jifty/branches/virtual-models/lib/Jifty/Web.pm
   jifty/branches/virtual-models/lib/Jifty/Web/Form/Field.pm
   jifty/branches/virtual-models/lib/Jifty/Web/Form/Field/Upload.pm
   jifty/branches/virtual-models/t/TestApp/lib/TestApp/View.pm
   jifty/branches/virtual-models/t/TestApp/t/02-dispatch-show-rule-in-wrong-ruleset.t
   jifty/branches/virtual-models/t/TestApp/t/08-notifications.t
   jifty/branches/virtual-models/t/TestApp/t/create-by-uuid.t

Log:
 r8111 at riddle:  andrew | 2007-07-18 16:52:24 -0500
 Mergedown from trunk. Test failures match that of trunk.


Modified: jifty/branches/virtual-models/META.yml
==============================================================================
--- jifty/branches/virtual-models/META.yml	(original)
+++ jifty/branches/virtual-models/META.yml	Wed Jul 18 17:53:40 2007
@@ -39,6 +39,7 @@
   Test::HTTP::Server::Simple: 0.02
   Test::MockModule: 0.05
   Test::MockObject: 1.07
+  WWW::Facebook::API: 0.3.6
 requires: 
   App::CLI: 0.03
   CGI: 3.19
@@ -62,6 +63,7 @@
   Email::LocalDelivery: 0
   Email::MIME: 0
   Email::MIME::ContentType: 0
+  Email::MIME::CreateHTML: 0
   Email::MIME::Creator: 0
   Email::Send: 1.99_01
   Email::Simple: 0
@@ -81,8 +83,9 @@
   Hash::Merge: 0
   Hook::LexWrap: 0
   IPC::PubSub: 0.23
+  IPC::Run3: 0
   JSON::Syck: 0.15
-  Jifty::DBI: 0.40
+  Jifty::DBI: 0.42
   LWP::UserAgent: 0
   Locale::Maketext::Extract: 0.20
   Locale::Maketext::Lexicon: 0.60
@@ -100,8 +103,9 @@
   Scalar::Defer: 0.10
   Shell::Command: 0
   String::Koremutake: 0
-  Template::Declare: 0.07
+  Template::Declare: 0.21
   Test::Base: 0
+  Test::Log4perl: 0
   Test::LongString: 0
   Test::More: 0.62
   Test::Pod::Coverage: 0

Modified: jifty/branches/virtual-models/Makefile.PL
==============================================================================
--- jifty/branches/virtual-models/Makefile.PL	(original)
+++ jifty/branches/virtual-models/Makefile.PL	Wed Jul 18 17:53:40 2007
@@ -25,6 +25,7 @@
 requires('Email::MIME');
 requires('Email::MIME::Creator');
 requires('Email::MIME::ContentType');
+requires('Email::MIME::CreateHTML');
 requires('Email::Send' => '1.99_01'); # Email::Send::Jifty::Test
 requires('Email::Simple');
 requires('Email::Simple::Creator');
@@ -44,7 +45,7 @@
 requires('Hook::LexWrap');
 requires('IPC::PubSub' => '0.23' );
 requires('IPC::Run3');
-requires('Jifty::DBI' => '0.40' );            # Jifty::DBI::Collection Jifty::DBI::Handle Jifty::DBI::Record::Cachable Jifty::DBI::SchemaGenerator
+requires('Jifty::DBI' => '0.42' );            # Jifty::DBI::Collection Jifty::DBI::Handle Jifty::DBI::Record::Cachable Jifty::DBI::SchemaGenerator
 requires('Locale::Maketext::Extract' => '0.20');
 requires('Locale::Maketext::Lexicon' => '0.60');
 requires('Log::Log4perl' => '1.04');
@@ -68,6 +69,7 @@
 requires('Template::Declare' => '0.21');                # Template::Declare::Tags
 requires('Test::Base');
 requires('Test::LongString');
+requires('Test::Log4perl');
 requires('Test::More' => 0.62 ),
 requires('Test::Pod::Coverage'),
 requires('Test::WWW::Mechanize' => 1.04 ),

Modified: jifty/branches/virtual-models/inc/Module/Install.pm
==============================================================================
--- jifty/branches/virtual-models/inc/Module/Install.pm	(original)
+++ jifty/branches/virtual-models/inc/Module/Install.pm	Wed Jul 18 17:53:40 2007
@@ -86,7 +86,7 @@
             # delegate back to parent dirs
             goto &$code unless $cwd eq $pwd;
         }
-        $$sym =~ /([^:]+)$/ or Carp::confess "Cannot autoload $who - $sym";
+        $$sym =~ /([^:]+)$/ or die "Cannot autoload $who - $sym";
         unshift @_, ($self, $1);
         goto &{$self->can('call')} unless uc($1) eq $1;
     };

Modified: jifty/branches/virtual-models/lib/Jifty/Action/Record.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Action/Record.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Action/Record.pm	Wed Jul 18 17:53:40 2007
@@ -309,7 +309,7 @@
             }
 
             # If we're hand-coding a render_as, hints or label, let's use it.
-            for (qw(render_as label hints max_length mandatory sort_order)) {
+            for (qw(render_as label hints max_length mandatory sort_order container)) {
 
                 if ( defined (my $val = $column->$_) ) {
                     $info->{$_} = $val;
@@ -352,7 +352,7 @@
 
 sub possible_fields {
     my $self = shift;
-    return map { $_->name } grep { $_->type ne "serial" } $self->record->columns;
+    return map { $_->name } grep { $_->container || $_->type ne "serial" } $self->record->columns;
 }
 
 =head2 take_action

Modified: jifty/branches/virtual-models/lib/Jifty/ClassLoader.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/ClassLoader.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/ClassLoader.pm	Wed Jul 18 17:53:40 2007
@@ -120,62 +120,75 @@
     # The quick check. We only want to handle things for our app
     return undef unless $module =~ /^$base/;
 
+    # If the module is the same as the base, build the application class
     if ( $module =~ /^(?:$base)$/ ) {
         return $self->return_class( "package " . $base . ";\n");
     }
+
+    # Handle most of the standard App::Class ISA Jifty::Class
     elsif ( $module =~ /^(?:$base)::(Record|Collection|Notification|
-                                      Dispatcher|Bootstrap|Upgrade|
+                                      Dispatcher|Bootstrap|Upgrade|CurrentUser|
                                       Handle|Event|Event::Model|Action|
                                       Action::Record::\w+)$/x ) {
         return $self->return_class(
                   "package $module;\n"
                 . "use base qw/Jifty::$1/; sub _autogenerated { 1 };\n"
             );
-    } elsif ( $module =~ /^(?:$base)::View$/ ) {
+    } 
+    
+    # Autogenerate an empty View if none is defined
+    elsif ( $module =~ /^(?:$base)::View$/ ) {
         return $self->return_class(
                   "package $module;\n"
                 . "use Jifty::View::Declare -base; sub _autogenerated { 1 };\n"
             );
-    } elsif ( $module =~ /^(?:$base)::CurrentUser$/ ) {
-        return $self->return_class(
-                  "package $module;\n"
-                . "use base qw/Jifty::CurrentUser/; sub _autogenerated { 1 };\n"
-            );
-    } elsif ( $module =~ /^(?:$base)::Model::([^\.]+)Collection$/ ) {
+    } 
+    
+    # Autogenerate the Collection class for a Model
+    elsif ( $module =~ /^(?:$base)::Model::([^\.]+)Collection$/ ) {
         return $self->return_class(
                   "package $module;\n"
                 . "use base qw/@{[$base]}::Collection/;\n"
                 . "sub record_class { '@{[$base]}::Model::$1' }\n"
+                . "sub _autogenerated { 1 };\n"
             );
-    } elsif ( $module =~ /^(?:$base)::Event::Model::([^\.]+)$/ ) {
+    } 
+    
+    # Autogenerate the the event class for model changes
+    elsif ( $module =~ /^(?:$base)::Event::Model::([^\.]+)$/ ) {
+        
+        # Determine the model class and load it
         my $modelclass = $base . "::Model::" . $1;
         Jifty::Util->require($modelclass);
 
-        return undef unless eval { $modelclass->table };
+        # Don't generate an event unless it really is a model
+        return undef unless eval { $modelclass->isa('Jifty::Record') };
 
         return $self->return_class(
                   "package $module;\n"
                 . "use base qw/${base}::Event::Model/;\n"
                 . "sub record_class { '$modelclass' };\n"
-                . "sub autogenerated { 1 };\n"
+                . "sub _autogenerated { 1 };\n"
             );
-    } elsif ( $module =~ /^(?:$base)::Action::
+    } 
+    
+    # Autogenerate the record actions for a model
+    elsif ( $module =~ /^(?:$base)::Action::
                         (Create|Update|Delete|Search)([^\.]+)$/x ) {
-        my $modelclass = $base . "::Model::" . $2;
 
+        # Determine the model class and load it
+        my $modelclass = $base . "::Model::" . $2;
         Jifty::Util->_require( module => $modelclass, quiet => 1);
 
-        local $@;
-            eval { $modelclass->table } ;
-        if(!$@) {
+        # Don't generate the action unless it really is a model
+        return undef unless eval { $modelclass->isa('Jifty::Record') };
 
         return $self->return_class(
                   "package $module;\n"
                 . "use base qw/$base\::Action::Record::$1/;\n"
                 . "sub record_class { '$modelclass' };\n"
-                . "sub autogenerated { 1 };\n"
+                . "sub _autogenerated { 1 };\n"
             );
-        }
 
     }
 
@@ -193,7 +206,7 @@
         return $self->return_class(
                   "package $module;\n"
                 . "use base qw/$class/;\n"
-                . "sub autogenerated { 1 };\n"
+                . "sub _autogenerated { 1 };\n"
             );
 
 

Modified: jifty/branches/virtual-models/lib/Jifty/Model/ModelClass.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Model/ModelClass.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Model/ModelClass.pm	Wed Jul 18 17:53:40 2007
@@ -106,7 +106,7 @@
 
 sub qualified_class {
     my $self = shift;
-    my $fully_qualified_class = Jifty->config->framework('ApplicationClass')."::Model::".$self->name;
+    my $fully_qualified_class = Jifty->app_class('Model', $self->name);
     return $fully_qualified_class; 
 }
 

Modified: jifty/branches/virtual-models/lib/Jifty/Model/ModelClassColumn.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Model/ModelClassColumn.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Model/ModelClassColumn.pm	Wed Jul 18 17:53:40 2007
@@ -167,8 +167,8 @@
     # Referals need special treatment
     if (defined $args->{refers_to_class}) {
 
-        # Refer to a collection and your column is virtual
-        if ($args->{refers_to_class}->isa('Jifty::DBI::Collection')) {
+        # If "by" is set, it's going to be a virtual column
+        if (defined $args->{refers_to_by} and $args->{refers_to_by}) {
             $args->{virtual} = 1;
         }
 

Modified: jifty/branches/virtual-models/lib/Jifty/Notification.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Notification.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Notification.pm	Wed Jul 18 17:53:40 2007
@@ -6,9 +6,10 @@
 use base qw/Jifty::Object Class::Accessor::Fast/;
 use Email::Send            ();
 use Email::MIME::Creator;
+use Email::MIME::CreateHTML;
 
 __PACKAGE__->mk_accessors(
-    qw/body preface footer subject from _recipients _to_list to/);
+    qw/body html_body preface footer subject from _recipients _to_list to/);
 
 =head1 NAME
 
@@ -17,9 +18,9 @@
 =head1 USAGE
 
 It is recommended that you subclass L<Jifty::Notification> and
-override C<body>, C<subject>, C<recipients>, and C<from> for each
-message.  (You may want a base class to provide C<from>, C<preface>
-and C<footer> for example.)  This lets you keep all of your
+override C<body>, C<html-body>, C<subject>, C<recipients>, and C<from>
+for each message.  (You may want a base class to provide C<from>,
+C<preface> and C<footer> for example.)  This lets you keep all of your
 notifications in the same place.
 
 However, if you really want to make a notification type in code
@@ -78,8 +79,13 @@
 if mail was actually sent.  Note errors are not the only cause of mail
 not being sent -- for example, the recipients list could be empty.
 
-Be aware that if you haven't set C<recipients>, this will fail silently
-and return without doing anything useful.
+If you wish to send HTML mail, set C<html_body>.  If this is not set
+(for backwards compatibility) a plain-text email is sent.  If
+C<html_body> and C<body> are both set, a multipart mail is sent.  See
+L<Email::MIME::CreateHTML> for how this is done.
+
+Be aware that if you haven't set C<recipients>, this will fail
+silently and return without doing anything useful.
 
 =cut
 
@@ -90,18 +96,31 @@
         map { ( $_->can('email') ? $_->email : $_ ) } grep {$_} @recipients );
     $self->log->debug("Sending a ".ref($self)." to $to"); 
     return unless ($to);
-
+    my $message = "";
     my $appname = Jifty->config->framework('ApplicationName');
-
-    my $message = Email::MIME->create(
-        header => [
-            From    => ($self->from    || _('%1 <%2>' , $appname, Jifty->config->framework('AdminEmail'))) ,
-            To      => $to,
-            Subject => Encode::encode('MIME-Header', $self->subject || _("A notification from %1!",$appname )),
-        ],
-        attributes => { charset => 'UTF-8' },
-        parts => $self->parts
-    );
+    if ($self->html_body) {
+      $message = Email::MIME->create_html(
+					     header => [
+							From    => ($self->from    || _('%1 <%2>' , $appname, Jifty->config->framework('AdminEmail'))) ,
+							To      => $to,
+							Subject => Encode::encode('MIME-Header', $self->subject || _("A notification from %1!",$appname )),
+						       ],
+					     attributes => { charset => 'UTF-8' },
+					     text_body => $self->full_body,
+					     body => $self->full_html
+					    );
+    } else {
+            $message = Email::MIME->create(
+					     header => [
+							From    => ($self->from    || _('%1 <%2>' , $appname, Jifty->config->framework('AdminEmail'))) ,
+							To      => $to,
+							Subject => Encode::encode('MIME-Header', $self->subject || _("A notification from %1!",$appname )),
+						       ],
+					     attributes => { charset => 'UTF-8' },
+					     
+					     parts => $self->parts
+					    );
+	  }
     $message->encoding_set('8bit')
         if (scalar $message->parts == 1);
     $self->set_headers($message);
@@ -263,6 +282,17 @@
   return join( "\n", grep { defined } $self->preface, $self->body, $self->footer );
 }
 
+=head2 full_html
+
+Same as full_body, but with HTML.
+
+=cut
+
+sub full_html {
+  my $self = shift;
+  return join( "\n", grep { defined } $self->preface, $self->html_body, $self->footer );
+}
+
 =head2 parts
 
 The parts of the message.  You want to override this if you want to

Modified: jifty/branches/virtual-models/lib/Jifty/Plugin.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Plugin.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Plugin.pm	Wed Jul 18 17:53:40 2007
@@ -183,4 +183,56 @@
     return ();
 }
 
+=head2 version
+
+Returns the database version of the plugin. Needs to be bumped any time the database schema needs to be updated. Plugins that do not directly define any models don't need to worry about this.
+
+=cut
+
+sub version {
+    return '0.0.1';
+}
+
+=head2 bootstrapper
+
+Returns the name of the class that can be used to bootstrap the database models. This normally returns the plugin's class name with C<::Bootstrap> added to the end. Plugin bootstrappers can be built in exactly the same way as application bootstraps.
+
+See L<Jifty::Bootstrap>.
+
+=cut
+
+sub bootstrapper {
+    my $self = shift;
+    my $class = ref $self;
+    return $class . '::Bootstrap';
+}
+
+=head2 upgrade_class
+
+Returns the name of the class that can be used to upgrade the database models and schema (such as adding new data, fixing default values, and renaming columns). This normally returns the plugin's class name with C<::Upgrade> added to the end. Plugin upgraders can be built in exactly the same was as application upgrade classes.
+
+See L<Jifty::Upgrade>.
+
+=cut
+
+sub upgrade_class {
+    my $self = shift;
+    my $class = ref $self;
+    return $class . '::Upgrade';
+}
+
+=head2 table_prefix
+
+Returns a prefix that will be placed in the front of all table names for plugin models. Be default, the plugin name is converted to an identifier based upon the class name.
+
+=cut
+
+sub table_prefix {
+    my $self = shift;
+    my $class = ref $self;
+    $class =~ s/\W+/_/g;
+    $class .= '_';
+    return lc $class;
+}
+
 1;

Added: jifty/branches/virtual-models/lib/Jifty/Plugin/ActorMetadata.pm
==============================================================================
--- (empty file)
+++ jifty/branches/virtual-models/lib/Jifty/Plugin/ActorMetadata.pm	Wed Jul 18 17:53:40 2007
@@ -0,0 +1,7 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::ActorMetadata;
+use base qw/Jifty::Plugin/;
+
+1;

Added: jifty/branches/virtual-models/lib/Jifty/Plugin/ActorMetadata/Mixin/Model/ActorMetadata.pm
==============================================================================
--- (empty file)
+++ jifty/branches/virtual-models/lib/Jifty/Plugin/ActorMetadata/Mixin/Model/ActorMetadata.pm	Wed Jul 18 17:53:40 2007
@@ -0,0 +1,114 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::ActorMetadata::Mixin::Model::ActorMetadata;
+use Jifty::DBI::Schema;
+use base 'Jifty::DBI::Record::Plugin';
+
+our @EXPORT = qw(current_user_can);
+
+=head1 NAME
+
+Jifty::Plugin::ActorMetadata::Mixin::Model::ActorMetadata - ActorMetadata mixin
+
+=head1 SYNOPSIS
+
+  package MyApp::Model::CoffeeShop;
+  use Jifty::DBI::Schema;
+  use MyApp::Record schema {
+      # custom column defrinitions
+  };
+
+  use Jifty::Plugin::ActorMetadata::Mixin::Model::ActorMetadata; # created_by, created_on, updated_on
+
+=head1 DESCRIPTION
+
+=head1 SCHEMA
+
+This mixin adds the following columns to the model schema:
+
+=head2 created_by
+
+=head2 created_on
+
+=head2 updated_on
+
+=cut
+
+# XXX: move this to somewhere
+my $app_user;
+BEGIN {
+
+# Do not call ->app_class within the schmea {} block.
+$app_user = Jifty->app_class('Model', 'User');
+Jifty::DBI::Schema->register_types(
+    Date =>
+        sub { type is 'date', input_filters are qw/Jifty::DBI::Filter::Date/ },
+    Time =>
+        sub { type is 'time', input_filters are qw/Jifty::DBI::Filter::Time/ },
+    DateTime => sub {
+        type is 'datetime',
+        input_filters are qw/Jifty::DBI::Filter::DateTime/ },
+    TimeStamp => sub {
+        type is 'timestamp',
+        filters are qw( Jifty::Filter::DateTime Jifty::DBI::Filter::DateTime),
+    }
+);
+}
+
+use Jifty::Record schema {
+
+column created_by =>
+  render_as 'hidden',
+  refers_to $app_user;
+
+column created_on => is TimeStamp,
+  render_as 'hidden';
+column updated_on => is TimeStamp,
+  render_as 'hidden';
+
+};
+
+=head1 METHODS
+
+# XXX: podcoverage should count parent classes.  these pods are useless
+
+=head2 register_triggers
+
+Adds the triggers to the model this mixin is added to.
+
+=cut
+
+sub register_triggers {
+    my $self = shift;
+    $self->add_trigger(name => 'before_create', callback => \&before_create);
+}
+
+sub before_create {
+    my $self = shift;
+    my $args = shift;
+
+    $args->{'created_by'} = $self->current_user->id;
+    $args->{'created_on'} = $args->{'updated_on'} = Jifty::DateTime->now;
+
+    return 1;
+}
+
+# XXX: Move this to an abortable trigger
+sub current_user_can {
+    my $self = shift;
+    my $action = shift;
+    my %args = (@_);
+
+    if ($action eq 'create') {
+        return undef unless ($self->current_user and $self->current_user->id);
+    }
+
+    if ($action eq 'update' or $action eq 'delete') {
+        return undef unless ($self->current_user and $self->current_user->id eq $self->created_by->id);
+    }
+
+    return 1;
+}
+
+1;

Modified: jifty/branches/virtual-models/lib/Jifty/Plugin/Authentication/Password/View.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Plugin/Authentication/Password/View.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Plugin/Authentication/Password/View.pm	Wed Jul 18 17:53:40 2007
@@ -55,14 +55,14 @@
 =cut
 
 template login => page { title => _('Login!') } content {
-    show('login_widget');
+    show('/login_widget');
 };
 
 =head2 login_widget
 
 A handy template for embedding the login form. Just include it in your templates via:
 
-  show('login_widget');
+  show('/login_widget');
 
 See L<Jifty::Plugin::Authentication::Password::Action::Login>.
 

Modified: jifty/branches/virtual-models/lib/Jifty/Plugin/CompressedCSSandJS.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Plugin/CompressedCSSandJS.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Plugin/CompressedCSSandJS.pm	Wed Jul 18 17:53:40 2007
@@ -107,20 +107,8 @@
         or Jifty->config->framework('DevelMode') ) {
         Jifty->log->debug("Generating JS...");
 
-        my @roots = (
-            Jifty::Util->absolute_path(
-                File::Spec->catdir(
-                    Jifty->config->framework('Web')->{'StaticRoot'}, 'js'
-                )
-            ),
-
-            Jifty::Util->absolute_path(
-                File::Spec->catdir(
-                    Jifty->config->framework('Web')->{'DefaultStaticRoot'},
-                    'js'
-                )
-            ),
-        );
+        my @roots = map { Jifty::Util->absolute_path( File::Spec->catdir( $_, 'js' ) ) }
+                        Jifty->handler->view('Jifty::View::Static::Handler')->roots;
 
         my $js = "";
 

Modified: jifty/branches/virtual-models/lib/Jifty/Plugin/Feedback/View.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Plugin/Feedback/View.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Plugin/Feedback/View.pm	Wed Jul 18 17:53:40 2007
@@ -19,9 +19,8 @@
         attr { id => 'feedback_wrapper' };
 
         h3 { _('Send us feedback!') } p {
-            "Tell us what's good, what's bad, and what else you want "
-                . Jifty->config->framework('ApplicationName')
-                . " to do!";
+            _("Tell us what's good, what's bad, and what else you want %1 to do!",
+	      Jifty->config->framework('ApplicationName'));
         };
         render_region(
             'feedback',

Added: jifty/branches/virtual-models/lib/Jifty/Plugin/GoogleMap.pm
==============================================================================
--- (empty file)
+++ jifty/branches/virtual-models/lib/Jifty/Plugin/GoogleMap.pm	Wed Jul 18 17:53:40 2007
@@ -0,0 +1,76 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::GoogleMap;
+use base qw/Jifty::Plugin Class::Accessor::Fast/;
+
+
+=head1 NAME
+
+Jifty::Plugin::GoogleMap
+
+=head1 SYNOPSIS
+
+# In your jifty config.yml under the framework section:
+
+  Plugins:
+    - GoogleMap:
+        apikey: ABQIAAAA66LEkTHjdh-UhDZ_NkfdjBTb-vLQlFZmc2N8bgWI8YDPp5FEVBTjCfxPSocuJ53SPMNQDO7Sywpp_w
+
+# note that this is an api for http://localhost:8888/
+
+
+In your model class schema description, add the following:
+
+    column location => is GeoLocation;
+
+
+=head1 DESCRIPTION
+
+This plugin provides a Google-map widget for Jifty, as well as a new GeoLocation column type.
+
+
+=cut
+
+__PACKAGE__->mk_accessors(qw(apikey));
+
+=head2 init
+
+=cut
+
+sub init {
+    my $self = shift;
+    my %opt  = @_;
+    $self->apikey( $opt{apikey} );
+    Jifty->web->add_external_javascript("http://maps.google.com/maps?file=api&v=2&key=".$self->apikey);
+    Jifty->web->add_javascript(qw( google_map.js ) );
+    Jifty->web->add_css('google_map.css');
+}
+
+sub _geolocation {
+    my ($column, $from) = @_;
+    my $name = $column->name;
+    $column->virtual(1);
+    $column->container(1);
+    for (qw(x y)) {
+        Jifty::DBI::Schema::_init_column_for(
+            Jifty::DBI::Column->new({ type => 'double precision',
+                                      name => $name."_$_",
+                                      render_as => 'hidden',
+                                      writable => $column->writable,
+                                      readable => $column->readable }),
+            $from);
+    }
+    no strict 'refs';
+    *{$from.'::'.$name} = sub { return { map { my $method = "${name}_$_"; $_ => $_[0]->$method } qw(x y) } };
+    *{$from.'::'.'set_'.$name} = sub { die "not yet" };
+}
+
+use Jifty::DBI::Schema;
+Jifty::DBI::Schema->register_types(
+    GeoLocation =>
+        sub { _init_handler is \&_geolocation, render_as 'Jifty::Plugin::GoogleMap::Widget' },
+);
+
+
+1;

Added: jifty/branches/virtual-models/lib/Jifty/Plugin/GoogleMap/Widget.pm
==============================================================================
--- (empty file)
+++ jifty/branches/virtual-models/lib/Jifty/Plugin/GoogleMap/Widget.pm	Wed Jul 18 17:53:40 2007
@@ -0,0 +1,78 @@
+use warnings;
+use strict;
+ 
+package Jifty::Plugin::GoogleMap::Widget;
+
+use base qw/Jifty::Web::Form::Field/;
+
+=head1 NAME
+
+Jifty::Plugin::GoogleMap::Widget - google map widget for geolocation display and editing
+
+=head1 METHODS
+
+
+=cut
+
+sub accessors { shift->SUPER::accessors() };
+
+=head2 render_widget
+
+Renders form fields as googlemap widget.
+
+=cut
+
+# XXX: doesn't work
+#use Template::Declare;
+#use Template::Declare::Tags;
+
+sub render_widget {
+    my $self = shift;
+    my $readonly = shift;
+    my $action = $self->action;
+    $readonly = $readonly ? 1 : 0;
+
+    my ($x, $y) = map { $action->form_field($self->name . "_$_")->current_value } qw( x y );
+    my ($xid, $yid) = map { $action->form_field($self->name . "_$_")->element_id } qw( x y );
+    my $use_default = defined $x ? 0 : 1;
+    ($x, $y) = (-71.2, 42.4) if $use_default;
+    my $zoom_level = $use_default ? 1 : 13;
+    my $element_id = $self->element_id;
+    Jifty->web->out(qq{<div class="googlemap-widget-wrapper" style="width: 250px; height: 250px"><div @{[$self->_widget_class]} id="$element_id" style="width: 250px; height: 250px"></div>});
+    Jifty->web->out(qq{<div class="googlemap-search-results" id="$element_id-result"></div></div>});
+    Jifty->web->out(qq{<script type="text/javascript">
+Jifty.GMap.location_editor( \$("$element_id"), $x, $y, "$xid", "$yid", $zoom_level, $use_default, $readonly);
+</script>
+});
+
+
+    return '';
+    Template::Declare->new_buffer_frame;
+
+    div { { id is $self->element_id, style is "width: 200px; height: 200px" } };
+    outs('hi');
+    script { { type is "text/javascript" };
+      qq{if (GBrowserIsCompatible()) {
+         var map = new GMap2(document.getElementById("@{[$self->element_id]}"));
+         map.setCenter(new GLatLng(37.4419, -122.1419), 13);
+      }}
+    };
+
+warn "-----------_".Template::Declare->buffer->data;
+    Jifty->web->out(Template::Declare->buffer->data);
+    Template::Declare->end_buffer_frame;
+    return '';
+}
+
+=head2 render_value
+
+Renders value as a checkbox widget.
+
+=cut
+
+sub render_value {
+    $_[0]->render_widget('readonly');
+    return '';
+}
+
+1;

Modified: jifty/branches/virtual-models/lib/Jifty/Plugin/REST/Dispatcher.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Plugin/REST/Dispatcher.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Plugin/REST/Dispatcher.pm	Wed Jul 18 17:53:40 2007
@@ -155,9 +155,12 @@
     my $record = shift;
     # We could use ->as_hash but this method avoids transforming refers_to
     # columns into JDBI objects
+
+    # XXX: maybe just test ->virtual?
     my %data   = map {
                     $_ => (UNIVERSAL::isa( $record->column( $_ )->refers_to,
-                                           'Jifty::DBI::Collection' )
+                                           'Jifty::DBI::Collection' ) ||
+                           $record->column($_)->container
                              ? undef
                              : stringify( $record->_value( $_ ) ) )
                  } $record->readable_attributes;

Modified: jifty/branches/virtual-models/lib/Jifty/Plugin/SiteNews/View/News.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Plugin/SiteNews/View/News.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Plugin/SiteNews/View/News.pm	Wed Jul 18 17:53:40 2007
@@ -25,15 +25,6 @@
 
 sub object_type { 'News' }
 
-=head2 fragment_base_path
-
-/news
-
-=cut
-
-sub fragment_base_path {'/news'}
-
-
 template search_region => sub {''};
 
 template 'index.html' => page {
@@ -41,7 +32,7 @@
     form {
             render_region(
                 name     => 'newslist',
-                path     => '/news/list');
+                path     => 'list');
     }
 
 };

Modified: jifty/branches/virtual-models/lib/Jifty/Plugin/TabView/View.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Plugin/TabView/View.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Plugin/TabView/View.pm	Wed Jul 18 17:53:40 2007
@@ -48,8 +48,8 @@
 			hyperlink(url => '#tab'.++$i, label => $tab,
 				  $tab =~ s/_tab$// ? 
 				  (onclick =>
-				  { region       => Jifty->web->current_region->qualified_name."-$tab-tab",
-				    replace_with => $tab,#$self->fragment_for($tab),
+				  { region       => Jifty->web->current_region ? Jifty->web->current_region->qualified_name."-$tab-tab" : "$tab-tab",
+				    replace_with => $self->can('fragment_for') ? $self->fragment_for($tab) : $tab, # XXX: should have higher level function handling mount point
 				    args => { map { $_ => get($_)} @$args },
 				  }) : ()
 				 ) }

Added: jifty/branches/virtual-models/lib/Jifty/Plugin/Userpic.pm
==============================================================================
--- (empty file)
+++ jifty/branches/virtual-models/lib/Jifty/Plugin/Userpic.pm	Wed Jul 18 17:53:40 2007
@@ -0,0 +1,42 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Userpic;
+use base qw/Jifty::Plugin Class::Accessor::Fast/;
+
+
+=head1 NAME
+
+Jifty::Plugin::Userpic
+
+=head1 SYNOPSIS
+
+In your model class schema description, add the following:
+
+    column userpic => is Userpic;
+
+
+=head1 DESCRIPTION
+
+This plugin provides user pictures for Jifty;
+
+
+=cut
+
+use Jifty::DBI::Schema;
+
+sub _userpic {
+    my ($column, $from) = @_;
+    my $name = $column->name;
+    $column->type('blob');
+}
+
+use Jifty::DBI::Schema;
+
+Jifty::DBI::Schema->register_types(
+    Userpic =>
+        sub { _init_handler is \&_userpic,  render_as 'Jifty::Plugin::Userpic::Widget'},
+);
+
+
+1;

Added: jifty/branches/virtual-models/lib/Jifty/Plugin/Userpic/Dispatcher.pm
==============================================================================
--- (empty file)
+++ jifty/branches/virtual-models/lib/Jifty/Plugin/Userpic/Dispatcher.pm	Wed Jul 18 17:53:40 2007
@@ -0,0 +1,23 @@
+package Jifty::Plugin::Userpic::Dispatcher;
+
+use Jifty::Dispatcher -base;
+
+on '/=/plugin/userpic/*/#/*' => run {
+    my $class = $1;
+    my $id = $2;
+    my $field = $3;
+
+    if ($class->isa('Jifty::Record')) {
+
+        my $item = $class->new();
+        $item->load($id);
+
+        if ($item->id) {
+            set item => $item;
+            set field => $field;
+            show '/userpic/image';
+        }
+    }
+};
+
+1;

Added: jifty/branches/virtual-models/lib/Jifty/Plugin/Userpic/View.pm
==============================================================================
--- (empty file)
+++ jifty/branches/virtual-models/lib/Jifty/Plugin/Userpic/View.pm	Wed Jul 18 17:53:40 2007
@@ -0,0 +1,12 @@
+package Jifty::Plugin::Userpic::View;
+
+use Jifty::View::Declare -base;
+
+template 'userpic/image' => sub {
+    my ($item,$field) = get(qw(item field));
+    Jifty->handler->apache->content_type("image/jpeg");
+    outs_raw($item->$field());
+
+};
+
+1;

Added: jifty/branches/virtual-models/lib/Jifty/Plugin/Userpic/Widget.pm
==============================================================================
--- (empty file)
+++ jifty/branches/virtual-models/lib/Jifty/Plugin/Userpic/Widget.pm	Wed Jul 18 17:53:40 2007
@@ -0,0 +1,59 @@
+use warnings;
+use strict;
+ 
+package Jifty::Plugin::Userpic::Widget;
+
+use base qw/Jifty::Web::Form::Field/;
+
+=head1 NAME
+
+Jifty::Plugin::Userpic::Widget - google map widget for geolocation display and editing
+
+=head1 METHODS
+
+
+=cut
+
+sub accessors { shift->SUPER::accessors() };
+
+=head2 render_widget
+
+Renders form fields as googlemap widget.
+
+=cut
+
+sub render_widget {
+    my $self     = shift;
+    my $readonly = shift;
+    my $action   = $self->action;
+    $readonly = $readonly ? 1 : 0;
+
+    if ( $self->current_value ) {
+        Jifty->web->out( qq{<img src="/=/plugin/userpic/}
+                . $self->action->record_class . "/"
+                . $action->record->id . '/'
+                . $self->name
+                . qq{">} );
+    }
+    unless ($readonly) {
+        my $field = qq!<input type="file" name="@{[ $self->input_name ]}" !;
+        $field .= $self->_widget_class();
+        $field .= qq!/>!;
+        Jifty->web->out($field);
+    }
+    '';
+}
+
+
+=head2 render_value
+
+Renders value as a checkbox widget.
+
+=cut
+
+sub render_value {
+    $_[0]->render_widget('readonly');
+    return '';
+}
+
+1;

Modified: jifty/branches/virtual-models/lib/Jifty/Record.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Record.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Record.pm	Wed Jul 18 17:53:40 2007
@@ -189,6 +189,41 @@
     return ($id,$msg);
 }
 
+=head2 _guess_table_name
+
+Guesses a table name based on the class's last part. In addition to the work performed in L<Jifty::DBI::Record>, this method also prefixes the table name with the plugin table prefix, if the model belongs to a plugin.
+
+=cut
+
+sub _guess_table_name {
+    my $self = shift;
+    my $table = $self->SUPER::_guess_table_name;
+
+    # Add plugin table prefix if a plugin model
+    my $class = ref($self) ? ref($self) : $self;
+    my $app_plugin_root = Jifty->app_class('Plugin');
+    if ($class =~ /^(?:Jifty::Plugin::|$app_plugin_root)/) {
+
+        # Guess the plugin class name
+        my $plugin_class = $class;
+        $plugin_class =~ s/::Model::(.*)$//;
+
+        # Try to load that plugin's configuration
+        my ($plugin) = grep { ref $_ eq $plugin_class } Jifty->plugins;
+
+        # Add the prefix if found
+        if (defined $plugin) {
+            $table = $plugin->table_prefix . $table;
+        }
+
+        # Uh oh. Warn, but try to keep going.
+        else {
+            warn "Model $class looks like a plugin model, but $plugin_class could not be found.";
+        }
+    }
+
+    return $table;
+}
 
 =head2 current_user_can RIGHT [ATTRIBUTES]
 

Modified: jifty/branches/virtual-models/lib/Jifty/Schema.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Schema.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Schema.pm	Wed Jul 18 17:53:40 2007
@@ -48,16 +48,18 @@
 =cut
 
 sub _init_model_list {
-
     my $self = shift;
 
+    # Plugins can have models too
+    my @plugins = map { (ref $_).'::Model' } Jifty->plugins;
+
     # This creates a sub "models" which when called, finds packages under
     # the application's ::Model, requires them, and returns a list of their
     # names.
     Jifty::Module::Pluggable->import(
         require     => 1,
         except      => qr/\.#/,
-        search_path => [ "Jifty::Model", Jifty->app_class("Model") ],
+        search_path => [ "Jifty::Model", Jifty->app_class("Model"), @plugins ],
         sub_name    => 'models',
     );
 }

Modified: jifty/branches/virtual-models/lib/Jifty/Script/Schema.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Script/Schema.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Script/Schema.pm	Wed Jul 18 17:53:40 2007
@@ -50,6 +50,7 @@
     } elsif ( $self->{'setup_tables'} ) {
         $self->upgrade_jifty_tables();
         $self->upgrade_application_tables();
+        $self->upgrade_plugin_tables();
     } else {
         print "Done.\n";
     }
@@ -182,6 +183,12 @@
     Jifty::Model::Metadata->store( application_db_version => $appv );
     Jifty::Model::Metadata->store( jifty_db_version       => $jiftyv );
 
+    # For each plugin, update the plugin version
+    for my $plugin (Jifty->plugins) {
+        my $pluginv = version->new( $plugin->version );
+        Jifty::Model::Metadata->store( (ref $plugin).'_db_version' => $pluginv );
+    }
+
     # Load initial data
     local $@;
     eval {
@@ -189,6 +196,11 @@
         Jifty::Util->require($bootstrapper);
         $bootstrapper->run() if $bootstrapper->can('run');
 
+        for my $plugin (Jifty->plugins) {
+            my $plugin_bootstrapper = $plugin->bootstrapper;
+            Jifty::Util->require($plugin_bootstrapper);
+            $plugin_bootstrapper->run() if $bootstrapper->can('run');
+        }
     };
     die $@ if $@;
     # Commit it all
@@ -219,20 +231,52 @@
     my $appv = version->new( Jifty->config->framework('Database')->{'Version'} );
     my $jiftyv = version->new( $Jifty::VERSION  );
 
+    my %pluginvs;
+    for my $plugin (Jifty->plugins) {
+        my $plugin_class = ref $plugin;
+        $pluginvs{ $plugin_class } = version->new( $plugin->version );
+    }
 
     for my $model ( @models) {
-       # TODO XXX FIXME:
-       #   This *will* try to generate SQL for abstract base classes you might
-       #   stick in $AC::Model::.
-        if ( $model->can( 'since' ) and ($model =~ /^Jifty::Model::/ ? $jiftyv : $appv) < $model->since ) {
-            $log->info( "Skipping $model, as it should already be in the database");
-            next;
+        # TODO XXX FIXME:
+        #   This *will* try to generate SQL for abstract base classes you might
+        #   stick in $AC::Model::.
+        if ($model->can('since')) {
+            my $app_class   = Jifty->app_class;
+            my $plugin_root = Jifty->app_class('Plugin');
+
+            my $installed_version = 0;
+
+            # Is it a Jifty core model?
+            if ($model =~ /^Jifty::Model::/) {
+                $installed_version = $jiftyv;
+            }
+
+            # Is it a Jifty or application plugin model?
+            elsif ($model =~ /^(?:Jifty::Plugin::|$plugin_root)/) {
+                my $plugin_class = $model;
+                $plugin_class =~ s/::Model::(.*)$//;
+
+                $installed_version = $pluginvs{ $plugin_class };
+            }
+
+            # Otherwise, an application model
+            else {
+                $installed_version = $appv;
+            }
+
+            if ($installed_version < $model->since) {
+                # XXX Is this message correct? 
+                $log->info("Skipping $model, as it should already be in the database");
+                next;
+            }
         }
+
         $log->info("Using $model, as it appears to be new.");
 
-            $self->schema->_check_reserved($model)
+        $self->schema->_check_reserved($model)
         unless ( $self->{'ignore_reserved'}
-            or !Jifty->config->framework('Database')->{'CheckSchema'} );
+                or !Jifty->config->framework('Database')->{'CheckSchema'} );
 
         if ( $self->{'print'} ) {
             print $model->printable_table_schema;
@@ -289,6 +333,29 @@
     }
 }
 
+=head2 upgrade_plugin_tables
+
+Upgrade the tables for each plugin.
+
+=cut
+
+sub upgrade_plugin_tables {
+    my $self   = shift;
+
+    for my $plugin (Jifty->plugins) {
+        my $plugin_class = ref $plugin;
+        my $dbv  = version->new( Jifty::Model::Metadata->load($plugin_class . '_version') || '0.0.1' );
+        my $appv = version->new( $plugin->version );
+
+        return unless $self->upgrade_tables( $plugin_class, $dbv, $appv, $plugin->upgrade_class );
+        if ( $self->{print} ) {
+            warn "Need to upgrade ${plugin_class}_db_version to $appv here!";
+        } else {
+            Jifty::Model::Metadata->store( $plugin_class . '_db_version' => $appv );
+        }
+    }
+}
+
 =head2 upgrade_tables BASECLASS, FROM, TO, [UPGRADECLASS]
 
 Given a C<BASECLASS> to upgrade, and two L<version> objects, C<FROM>

Modified: jifty/branches/virtual-models/lib/Jifty/Util.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Util.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Util.pm	Wed Jul 18 17:53:40 2007
@@ -94,7 +94,8 @@
 =head2 share_root
 
 Returns the 'share' directory of the installed Jifty module.  This is
-currently only used to store the common Mason components.
+currently only used to store the common Mason components, CSS, and JS
+of Jifty and it's plugins.
 
 =cut
 

Modified: jifty/branches/virtual-models/lib/Jifty/View/Declare/CRUD.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/View/Declare/CRUD.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/View/Declare/CRUD.pm	Wed Jul 18 17:53:40 2007
@@ -13,7 +13,7 @@
 
 This class provides a set of views that may be used by a model to
 display Create/Read/Update/Delete views using the L<Template::Declare>
-templating langauge.
+templating language.
 
 =head1 METHODS
 
@@ -35,7 +35,6 @@
     eval qq{package $caller;
             alias $vclass under '$path'; 1} or die $@;
     no strict 'refs';
-    *{$vclass."::fragment_base_path"} = sub { "/$path" };
     *{$vclass."::object_type"} = sub { $model };
 }
 
@@ -72,7 +71,10 @@
 
 sub fragment_base_path {
     my $self = shift;
-    return $self->package_variable('base_path') || '/crud';
+    my @parts = split('/', current_template());
+    pop @parts;
+    my $path = join('/', @parts);
+    return $path;
 }
 
 =head2 _get_record $id
@@ -191,19 +193,20 @@
 };
 
 private template view_item_controls  => sub {
+    my $self = shift;
+    my $record = shift;
 
-        my $self = shift;
-        my $record = shift;
-        my $action = shift;
+    if ( $record->current_user_can('update') ) {
         hyperlink(
-            label   => "Edit",
+            label   => _("Edit"),
             class   => "editlink",
             onclick => {
                 replace_with => $self->fragment_for('update'),
                 args         => { object_type => $self->object_type, id => $record->id }
             },
         );
-    };
+    }
+};
 
 
 
@@ -251,6 +254,13 @@
 
     my $object_type = $self->object_type;
     my $id = $record->id;
+
+    my $delete = Jifty->web->form->add_action(
+        class   => 'Delete' . $object_type,
+        moniker => 'delete-' . Jifty->web->serial,
+        record  => $record
+    );
+
         div {
             { class is 'crud editlink' };
             hyperlink(
@@ -268,8 +278,20 @@
                     replace_with => $self->fragment_for('view'),
                     args         => { object_type => $object_type, id => $id }
                 },
-                as_button => 1
+                as_button => 1,
+                class => 'cancel'
             );
+            if ( $record->current_user_can('delete') ) {
+                $delete->button(
+                    label   => 'Delete',
+                    onclick => {
+                        submit => $delete,
+                        confirm => 'Really delete?',
+                        refresh => Jifty->web->current_region->parent,
+                    },
+                    class => 'delete'
+                );
+            }
         };
 
 };
@@ -284,7 +306,6 @@
     my $self = shift;
 
     my ( $page ) = get(qw(page ));
-    my $fragment_for_new_item = get('fragment_for_new_item') || $self->fragment_for('new_item');
     my $item_path = get('item_path') || $self->fragment_for("view");
     my $collection =  $self->_current_collection();
 
@@ -292,17 +313,17 @@
     show( './paging_top',    $collection, $page );
     show( './list_items',    $collection, $item_path );
     show( './paging_bottom', $collection, $page );
-    show( './new_item_region', $fragment_for_new_item );
+    show( './new_item_region');
 
 };
 
+sub per_page { 25 }
 
 sub _current_collection {
     my $self =shift; 
     my ( $page, $search_collection ) = get(qw(page  search_collection));
-
     my $collection_class = Jifty->app_class( "Model", $self->object_type . "Collection" );
-    my $search = $search_collection || Jifty->web->response->result('search');
+    my $search = $search_collection || ( Jifty->web->response->result('search') ? Jifty->web->response->result('search')->content('search') : undef );
     my $collection;
     if ( $search ) {
         $collection = $search;
@@ -311,7 +332,7 @@
         $collection->unlimit();
     }
 
-    $collection->set_page_info( current_page => $page, per_page => 25 );
+    $collection->set_page_info( current_page => $page, per_page => $self->per_page );
 
     return $collection;    
 }
@@ -355,7 +376,7 @@
 
 private template 'new_item_region' => sub {
     my $self        = shift;
-    my $fragment_for_new_item = shift;
+    my $fragment_for_new_item = get('fragment_for_new_item') || $self->fragment_for('new_item');
     my $object_type = $self->object_type;
 
     if ($fragment_for_new_item) {
@@ -376,13 +397,15 @@
 
 =cut
 
+private template 'no_items_found' => sub { outs(_("No items found.")) };
+
 private template 'list_items' => sub {
     my $self        = shift;
     my $collection  = shift;
     my $item_path   = shift;
     my $object_type = $self->object_type;
     if ( $collection->pager->total_entries == 0 ) {
-        outs( _("No items found") );
+        show('no_items_found');
     }
 
     div {
@@ -437,7 +460,7 @@
             span {
                 { class is 'prev-page' };
                 hyperlink(
-                    label   => "Previous Page",
+                    label   => _("Previous Page"),
                     onclick => {
                         args => { page => $collection->pager->previous_page }
                     }
@@ -448,7 +471,7 @@
             span {
                 { class is 'next-page' };
                 hyperlink(
-                    label   => "Next Page",
+                    label   => _("Next Page"),
                     onclick =>
                         { args => { page => $collection->pager->next_page } }
                 );
@@ -518,7 +541,7 @@
 =head1 LICENSE
 
 Jifty is Copyright 2005-2007 Best Practical Solutions, LLC.
-Jifty is distributed under the same terms as Perl tiself.
+Jifty is distributed under the same terms as Perl itself.
 
 =cut
 

Modified: jifty/branches/virtual-models/lib/Jifty/View/Declare/Page.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/View/Declare/Page.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/View/Declare/Page.pm	Wed Jul 18 17:53:40 2007
@@ -82,8 +82,8 @@
 
     div {
         div {
-            show 'salutation';
-            show 'menu';
+            show '/salutation';
+            show '/menu';
         };
         div {
             attr { id is 'content' };
@@ -143,7 +143,7 @@
     my $self = shift;
     my $oldt = get('title');
     set( title => $self->_title );
-    show 'heading_in_wrapper';
+    show '/heading_in_wrapper';
     set( title => $oldt );
 }
 
@@ -187,7 +187,7 @@
 
 sub render_jifty_page_detritus {
 
-    show('keybindings');
+    show('/keybindings');
     with( id => "jifty-wait-message", style => "display: none" ),
         div { _('Loading...') };
 
@@ -204,7 +204,7 @@
     my $title = shift || '';
     $title =~ s/<.*?>//g;    # remove html
     HTML::Entities::decode_entities($title);
-    with( title => $title ), show('header');
+    with( title => $title ), show('/header');
 }
 
 1;

Modified: jifty/branches/virtual-models/lib/Jifty/View/Static/Handler.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/View/Static/Handler.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/View/Static/Handler.pm	Wed Jul 18 17:53:40 2007
@@ -24,7 +24,7 @@
 
 * Static files are served out of a separate root
 * If static files go through apache:
-    * How do we merge together the two static roots?
+    * How do we merge together the N static roots?
 * If static files go through Jifty::Handler
     * We need a flag to allow them to go through the dispatcher, too
     * return "True" (304) for if-modified-since
@@ -61,12 +61,19 @@
     }
     push @roots, (Jifty->config->framework('Web')->{DefaultStaticRoot});
 
-    my $self = {
-        roots => \@roots
-    };
-    bless $self, $class;
+    return bless { roots => \@roots }, $class;
 }
 
+=head2 roots
+
+Returns all the static roots the handler will search
+
+=cut
+
+sub roots {
+    my $self = shift;
+    return wantarray ? @{$self->{roots}} : $self->{roots};
+}
 
 =head2 show $path
 
@@ -155,7 +162,7 @@
     # Chomp a leading "/static" - should this be configurable?
     $file =~ s/^\/*?static//; 
 
-    foreach my $path ( @{$self->{'roots'}} ) {
+    foreach my $path ( $self->roots ) {
         my $abspath = Jifty::Util->absolute_path( File::Spec->catdir($path,$file ));
         # If the user is trying to request something outside our static root, 
         # decline the request

Modified: jifty/branches/virtual-models/lib/Jifty/Web.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Web.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Web.pm	Wed Jul 18 17:53:40 2007
@@ -26,8 +26,12 @@
 );
 
 __PACKAGE__->mk_classdata($_)
-    for qw(cached_css        cached_css_digest        cached_css_time
-           javascript_libs);
+    for qw(cached_css cached_css_digest cached_css_time
+           css_files  javascript_libs   external_javascript_libs);
+
+__PACKAGE__->css_files([qw( main.css )]);
+
+__PACKAGE__->external_javascript_libs([]);
 
 __PACKAGE__->javascript_libs([qw(
     jsan/JSAN.js
@@ -1087,6 +1091,20 @@
     return '';
 }
 
+=head3 add_css FILE1, FILE2, ...
+
+Pushes files onto C<Jifty->web->css_files>
+
+=cut
+
+sub add_css {
+    my $self = shift;
+    Jifty->web->css_files([
+        @{ Jifty->web->css_files },
+        @_
+    ]);
+}
+
 =head3 generate_css
 
 Checks if the compressed CSS is generated, and if it isn't, generates
@@ -1102,29 +1120,15 @@
     {
         Jifty->log->debug("Generating CSS...");
         
-        my $app   = File::Spec->catdir(
-                        Jifty->config->framework('Web')->{'StaticRoot'},
-                        'css'
-                    );
-
-        my $jifty = File::Spec->catdir(
-                        Jifty->config->framework('Web')->{'DefaultStaticRoot'},
-                        'css'
-                    );
-
-        my $file = Jifty::Util->absolute_path(
-                        File::Spec->catpath( '', $app, 'main.css' )
-                   );
-
-        if ( not -e $file ) {
-            $file = Jifty::Util->absolute_path(
-                         File::Spec->catpath( '', $jifty, 'main.css' )
-                    );
-        }
+        my @roots = map { Jifty::Util->absolute_path( File::Spec->catdir( $_, 'css' ) ) }
+                        Jifty->handler->view('Jifty::View::Static::Handler')->roots;
 
-        CSS::Squish->roots( Jifty::Util->absolute_path( $app ), $jifty );
+        CSS::Squish->roots( @roots );
         
-        my $css = CSS::Squish->concatenate( $file );
+        my $css = CSS::Squish->concatenate(
+            map { CSS::Squish->_resolve_file( $_, @roots ) }
+                @{ $self->css_files }
+        );
 
         __PACKAGE__->cached_css( $css );
         __PACKAGE__->cached_css_digest( md5_hex( $css ) );
@@ -1161,13 +1165,19 @@
 directly.
 
 Jifty will look for javascript libraries under share/web/static/js/ by
-default.
+default as well as any plugin static roots.
 
 =cut
 
 sub include_javascript {
     my $self  = shift;
 
+    for my $url ( @{ __PACKAGE__->external_javascript_libs } ) {
+        $self->out(
+            qq[<script type="text/javascript" src="$url"></script>\n]
+        );
+    }
+
     # if there's no trigger, 0 is returned.  if aborted/handled, undef
     # is returned.
     defined $self->call_trigger('include_javascript', @_) or return '';
@@ -1195,6 +1205,20 @@
     ]);
 }
 
+=head3 add_external_javascript URL1, URL2, ...
+
+Pushes urls onto C<Jifty->web->external_javascript_libs>
+
+=cut
+
+sub add_external_javascript {
+    my $self = shift;
+    Jifty->web->external_javascript_libs([
+        @{ Jifty->web->external_javascript_libs },
+        @_
+    ]);
+}
+
 =head2 STATE VARIABLES
 
 =head3 get_variable NAME

Modified: jifty/branches/virtual-models/lib/Jifty/Web/Form/Field.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Web/Form/Field.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Web/Form/Field.pm	Wed Jul 18 17:53:40 2007
@@ -79,8 +79,9 @@
 
     # If they key and/or value imply that this argument is going to be
     # a mapped argument, then do the mapping and mark the field as hidden.
+    # but ignore that if the field is a container in the model
     my ($key, $value) = Jifty::Request::Mapper->query_parameters($self->input_name, $self->current_value);
-    if ($key ne $self->input_name) {
+    if ($key ne $self->input_name && !$self->action->arguments->{$self->name}{container}) {
         Jifty::Util->require('Jifty::Web::Form::Field::Hidden');
         bless $self, "Jifty::Web::Form::Field::Hidden";
         $self->input_name($key);

Modified: jifty/branches/virtual-models/lib/Jifty/Web/Form/Field/Upload.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Web/Form/Field/Upload.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Web/Form/Field/Upload.pm	Wed Jul 18 17:53:40 2007
@@ -44,4 +44,15 @@
     '';
 }
 
+=head2 classes
+
+Add 'upload' to the rest of the classes
+
+=cut
+
+sub classes {
+    my $self = shift;
+    return join(' ', 'upload', ($self->SUPER::classes));
+}
+
 1;

Added: jifty/branches/virtual-models/share/plugins/Jifty/Plugin/GoogleMap/web/static/css/google_map.css
==============================================================================
--- (empty file)
+++ jifty/branches/virtual-models/share/plugins/Jifty/Plugin/GoogleMap/web/static/css/google_map.css	Wed Jul 18 17:53:40 2007
@@ -0,0 +1,15 @@
+/* XXX: move to Plugin::GoogleMap */
+
+div .googlemap-search-results {
+    background-color:white;
+    border-width: 2px;
+    border-color: black;
+    border-style:dashed;
+    display:none;
+    height:450px;
+    left:50px;
+    position:relative;
+    top:-300px;
+    width:250px;
+    z-index:10;
+}

Added: jifty/branches/virtual-models/share/plugins/Jifty/Plugin/GoogleMap/web/static/js/google_map.js
==============================================================================
--- (empty file)
+++ jifty/branches/virtual-models/share/plugins/Jifty/Plugin/GoogleMap/web/static/js/google_map.js	Wed Jul 18 17:53:40 2007
@@ -0,0 +1,196 @@
+// XXX: move me to Plugin/GoogleMap's share when that works
+
+if (GMap2) {
+    //document.body.onunload = "GUnload()";
+
+if ( typeof Jifty == 'undefined' ) {
+    Jifty = {}
+}
+
+Jifty.GMap = function() {};
+Jifty.GMap.location_editor = function(element, x, y, xid, yid, zoom_level, no_marker, readonly) {
+    if (!GBrowserIsCompatible())
+	return;
+
+    var map = new GMap2(element);
+    map.enableScrollWheelZoom();
+    map._jifty_search_result = element.nextSibling;
+    map.addControl(new GSmallZoomControl());
+    if(!readonly)
+	map.addControl(new EditLocationControl());
+    map.setCenter(new GLatLng(y, x), zoom_level);
+    map._jifty_form_x = xid;
+    map._jifty_form_y = yid;
+    if (!no_marker) {
+	map._jifty_location = new GMarker(new GLatLng(y, x));
+	map.addOverlay(map._jifty_location);
+    }
+    GEvent.addListener(map, "click", function(marker, point) {
+	if (!marker && map._jifty_edit_control.editing) {
+	    map.removeOverlay(map._jifty_location);
+	    map._jifty_location = new GMarker(point)
+	    map.addOverlay(map._jifty_location);
+	}});
+}
+
+// TODO: separate edit location control and location search control
+
+function EditLocationControl() {}
+EditLocationControl.prototype = new GControl();
+
+EditLocationControl.prototype.initialize = function(map) {
+  var container = document.createElement("div");
+
+  var EditDiv = document.createElement("div");
+  this.setButtonStyle_(EditDiv);
+  EditDiv.appendChild(document.createTextNode("Edit"));
+
+  var CancelDiv = document.createElement("div");
+  this.setButtonStyle_(CancelDiv);
+  CancelDiv.appendChild(document.createTextNode("Cancel"));
+
+  var SearchDiv = document.createElement("div");
+  this.setButtonStyle_(SearchDiv);
+  SearchDiv.appendChild(document.createTextNode("Go to..."));
+
+  if(map._search_only) {
+    container.appendChild(SearchDiv);
+      map._search_result_callback = function(map, placemark) {
+	  var point = placemark.Point.coordinates;
+	  map.setCenter(new GLatLng(point[1], point[0]), 8+placemark.AddressDetails.Accuracy);
+      }
+  }
+  else {
+    container.appendChild(EditDiv);
+    map._search_result_callback = _mark_new_location;
+  }
+  var editctl = this;
+  GEvent.addDomListener(EditDiv, "click", function() {
+    if (editctl.editing) {
+        var point = map._jifty_location.getPoint();
+	$(map._jifty_form_x).value = point.lng()
+	$(map._jifty_form_y).value = point.lat()
+	EditDiv.innerHTML = "Edit";
+	container.removeChild(container.lastChild);
+	container.removeChild(container.lastChild);
+	editctl.editing = false;
+    }
+    else {
+	map._jifty_location_orig = map._jifty_location;
+        container.appendChild(CancelDiv);
+        container.appendChild(SearchDiv);
+	EditDiv.innerHTML = "Done";
+	editctl.editing = true;
+    }
+  });
+
+  GEvent.addDomListener(CancelDiv, "click", function() {
+      map.removeOverlay(map._jifty_location);
+      map._jifty_location = map._jifty_location_orig;
+      map.addOverlay(map._jifty_location);
+
+      container.removeChild(container.lastChild);
+      container.removeChild(container.lastChild);
+      EditDiv.innerHTML = "Edit";
+      editctl.editing = false;
+  });
+
+  GEvent.addDomListener(SearchDiv, "click", function() {
+      var element = document.createElement('div');
+      var field= document.createElement('input');
+      field.setAttribute('type', 'text');
+      field.style.width = '150px';
+      // port to yui event
+      field.onkeypress = function(e) {
+	  var event = e || window.event;
+	  if ((event.charCode || event.keyCode) == 13) {
+	      this.nextSibling.onclick();
+	      event.returnValue = false;
+	      return false;
+	  }
+      };
+      element.appendChild(field);
+      var submit= document.createElement('input');
+      submit.id = 'blahblah';
+      submit.value = 'Query';
+      submit.setAttribute('type', 'button');
+      submit.onclick = function() { _handle_search(map, field.value) };
+      element.appendChild(submit);
+      map.openInfoWindow(map.getCenter(), element, { maxWidth: 100 } );
+  });
+
+  map.getContainer().appendChild(container);
+  map._jifty_edit_control = this;
+  this.editing = false;
+  return container;
+}
+
+function _mark_new_location(map, placemark) {
+    var point = placemark.Point.coordinates;
+    if (map._jifty_location)
+	map.removeOverlay(map._jifty_location);
+    map._jifty_location = new GMarker(new GLatLng(point[1], point[0]));
+    map.addOverlay(map._jifty_location);
+    map.closeInfoWindow();
+    map.setCenter(map._jifty_location.getPoint(), 8+placemark.AddressDetails.Accuracy);
+}
+
+function _handle_search(map, address) {
+    var geocoder = new GClientGeocoder();
+    geocoder.getLocations
+      (address,
+       function (result) {
+	   if(result.Placemark) {
+	       if (result.Placemark.length == 1)
+		   map._search_result_callback(map, result.Placemark[0]);
+	       else
+		   _handle_multiple_results(map, result);
+	   }
+	   else {
+	       // TODO: show error in warning box in infowindow rather than alert
+	       alert('address not found');
+	   }
+       });
+}
+
+function _handle_multiple_results(map, result) {
+    var buf = '<a href="#" onclick="_handle_result_click(this.parentNode, null); return false;">Close</a><ul>';
+    for (var i = 0; i < result.Placemark.length; ++i) {
+	var data = result.Placemark[i];
+	buf += '<li><a href="#" onclick='+"'"+
+            '_handle_result_click(this.parentNode.parentNode.parentNode, '+JSON.stringify(data)+'); return false;' +
+          "'>"+data.address+'</a></li>';
+    }
+    buf += '</ul>';
+    map._jifty_search_result.innerHTML = buf;
+    map._jifty_search_result.style.display = "block";
+    map._jifty_search_result._map = map;
+}
+
+function _handle_result_click(e, data) {
+    e.style.display = 'none';
+    var map = e._map; e._map = null; /* circular reference? */
+    if (data)
+	map._search_result_callback(map, data);
+}
+
+EditLocationControl.prototype.getDefaultPosition = function() {
+  return new GControlPosition(G_ANCHOR_BOTTOM_RIGHT, new GSize(7, 7));
+}
+
+EditLocationControl.prototype.setButtonStyle_ = function(button) {
+  button.style.textDecoration = "underline";
+  button.style.color = "#0000cc";
+  button.style.backgroundColor = "white";
+  button.style.font = "small Arial";
+  button.style.fontSize = "0.8em";
+  button.style.border = "1px solid black";
+  button.style.padding = "2px";
+  button.style.marginBottom = "3px";
+  button.style.textAlign = "center";
+  button.style.width = "4em";
+  button.style.cursor = "pointer";
+}
+
+
+}

Added: jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/Makefile.PL
==============================================================================
--- (empty file)
+++ jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/Makefile.PL	Wed Jul 18 17:53:40 2007
@@ -0,0 +1,7 @@
+use inc::Module::Install;
+
+name        'TestApp::Plugin::AppPluginHasModels';
+version     '0.01';
+requires    'Jifty' => '0.70129';
+
+WriteAll;

Added: jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/bin/jifty
==============================================================================
--- (empty file)
+++ jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/bin/jifty	Wed Jul 18 17:53:40 2007
@@ -0,0 +1,11 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+use File::Basename qw(dirname); 
+use UNIVERSAL::require;
+
+use Jifty;
+use Jifty::Script;
+
+local $SIG{INT} = sub { warn "Stopped\n"; exit; };
+Jifty::Script->dispatch();

Added: jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/etc/config.yml
==============================================================================
--- (empty file)
+++ jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/etc/config.yml	Wed Jul 18 17:53:40 2007
@@ -0,0 +1,54 @@
+--- 
+framework: 
+  AdminMode: 1
+  ApplicationClass: TestApp::Plugin::AppPluginHasModels
+  ApplicationName: TestApp-Plugin-AppPluginHasModels
+  ApplicationUUID: 646FD662-32DD-11DC-AD79-2A0157C3B83B
+  ConfigFileVersion: 2
+  Database: 
+    CheckSchema: 1
+    Database: testapp_plugin_apppluginhasmodels
+    Driver: SQLite
+    Host: localhost
+    Password: ''
+    RecordBaseClass: Jifty::DBI::Record::Cachable
+    RecordUUIDs: active
+    User: ''
+    Version: 0.0.1
+  DevelMode: 1
+  L10N: 
+    PoDir: share/po
+  LogLevel: INFO
+  Mailer: Sendmail
+  MailerArgs: []
+
+  Plugins: 
+    - LetMe: {}
+    - SkeletonApp: {}
+    - REST: {}
+    - Halo: {}
+    - ErrorTemplates: {}
+    - OnlineDocs: {}
+    - CompressedCSSandJS: {}
+    - AdminUI: {}
+    - TestApp::Plugin::AppPluginHasModels::Plugin::MyAppPlugin: {}
+
+  PubSub: 
+    Backend: Memcached
+    Enable: ~
+  SkipAccessControl: 0
+  TemplateClass: TestApp::Plugin::AppPluginHasModels::View
+  Web: 
+    BaseURL: http://localhost
+    DataDir: var/mason
+    Globals: []
+
+    MasonConfig: 
+      autoflush: 0
+      default_escape_flags: h
+      error_format: text
+      error_mode: fatal
+    Port: 8888
+    ServeStaticFiles: 1
+    StaticRoot: share/web/static
+    TemplateRoot: share/web/templates

Added: jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/lib/TestApp/Plugin/AppPluginHasModels/Plugin/MyAppPlugin.pm
==============================================================================
--- (empty file)
+++ jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/lib/TestApp/Plugin/AppPluginHasModels/Plugin/MyAppPlugin.pm	Wed Jul 18 17:53:40 2007
@@ -0,0 +1,7 @@
+use strict;
+use warnings;
+
+package TestApp::Plugin::AppPluginHasModels::Plugin::MyAppPlugin;
+use base qw/ Jifty::Plugin /;
+
+1;

Added: jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/lib/TestApp/Plugin/AppPluginHasModels/Plugin/MyAppPlugin/Model/Color.pm
==============================================================================
--- (empty file)
+++ jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/lib/TestApp/Plugin/AppPluginHasModels/Plugin/MyAppPlugin/Model/Color.pm	Wed Jul 18 17:53:40 2007
@@ -0,0 +1,15 @@
+use strict;
+use warnings;
+
+package TestApp::Plugin::AppPluginHasModels::Plugin::MyAppPlugin::Model::Color;
+use Jifty::DBI::Schema;
+
+use TestApp::Plugin::AppPluginHasModels::Plugin::MyAppPlugin::Record schema {
+    column name =>
+        type is 'text';
+
+    column contrasting_color =>
+        type is 'text';
+};
+
+1;

Added: jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/t/plugin-model.t
==============================================================================
--- (empty file)
+++ jifty/branches/virtual-models/t/TestApp-Plugin-AppPluginHasModels/t/plugin-model.t	Wed Jul 18 17:53:40 2007
@@ -0,0 +1,54 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+
+=head1 DESCRIPTION
+
+A basic test harness for the Color model.
+
+=cut
+
+use lib 't/lib';
+use Jifty::SubTest;
+use Jifty::Test tests => 12;
+
+# Make sure we can load the model
+use_ok('TestApp::Plugin::AppPluginHasModels::Plugin::MyAppPlugin::Model::Color');
+
+# Grab a system user
+my $system_user = TestApp::Plugin::AppPluginHasModels::CurrentUser->superuser;
+ok($system_user, "Found a system user");
+
+# Try testing a create
+my $o = TestApp::Plugin::AppPluginHasModels::Plugin::MyAppPlugin::Model::Color->new(current_user => $system_user);
+my ($id) = $o->create();
+ok($id, "Color create returned success");
+ok($o->id, "New Color has valid id set");
+is($o->id, $id, "Create returned the right id");
+
+# Does it use a prefixed table
+is($o->table, 'testapp_plugin_apppluginhasmodels_plugin_myappplugin_colors', 'plugin table prefix');
+
+# And another
+$o->create();
+ok($o->id, "Color create returned another value");
+isnt($o->id, $id, "And it is different from the previous one");
+
+# Searches in general
+my $collection = TestApp::Plugin::AppPluginHasModels::Plugin::MyAppPlugin::Model::ColorCollection->new(current_user => $system_user);
+$collection->unlimit;
+is($collection->count, 2, "Finds two records");
+
+# Searches in specific
+$collection->limit(column => 'id', value => $o->id);
+is($collection->count, 1, "Finds one record with specific id");
+
+# Delete one of them
+$o->delete;
+$collection->redo_search;
+is($collection->count, 0, "Deleted row is gone");
+
+# And the other one is still there
+$collection->unlimit;
+is($collection->count, 1, "Still one left");
+

Modified: jifty/branches/virtual-models/t/TestApp/lib/TestApp/View.pm
==============================================================================
--- jifty/branches/virtual-models/t/TestApp/lib/TestApp/View.pm	(original)
+++ jifty/branches/virtual-models/t/TestApp/lib/TestApp/View.pm	Wed Jul 18 17:53:40 2007
@@ -54,7 +54,7 @@
     if ($model =~ /^.*::(.*?)$/) {
         $bare_model = $1;
     }
-    alias Jifty::View::Declare::CRUD under '/crud/'.$bare_model,  { object_type => $bare_model, base_path => '/crud/'.$bare_model };
+    alias Jifty::View::Declare::CRUD under '/crud/'.$bare_model,  { object_type => $bare_model };
 
 }
 

Modified: jifty/branches/virtual-models/t/TestApp/t/02-dispatch-show-rule-in-wrong-ruleset.t
==============================================================================
--- jifty/branches/virtual-models/t/TestApp/t/02-dispatch-show-rule-in-wrong-ruleset.t	(original)
+++ jifty/branches/virtual-models/t/TestApp/t/02-dispatch-show-rule-in-wrong-ruleset.t	Wed Jul 18 17:53:40 2007
@@ -6,6 +6,7 @@
 use Jifty::SubTest;
 use Jifty::Test tests => 8;
 use Jifty::Test::WWW::Mechanize;
+use Test::Log4perl;
 
 my $server  = Jifty::Test->make_server;
 
@@ -14,11 +15,14 @@
 my $URL     = $server->started_ok;
 my $mech    = Jifty::Test::WWW::Mechanize->new();
 
+{
+    my $log = Test::Log4perl->expect(['', warn => qr/You can't call a 'show' rule in a 'before' or 'after' block in the dispatcher/ ]);
 $mech->get("$URL/before_stage_show", "Got /before_stage_show");
 $mech->content_lacks("This is content");
 is( $mech->status , '404');
-
+}
 $mech->get_ok("$URL/on_stage_show", "Got /on_stage_show");
+#diag $mech->content;
 $mech->content_contains("his is content");
 
 $mech->get("$URL/after_stage_show", "Got /after_stage_show");

Modified: jifty/branches/virtual-models/t/TestApp/t/08-notifications.t
==============================================================================
--- jifty/branches/virtual-models/t/TestApp/t/08-notifications.t	(original)
+++ jifty/branches/virtual-models/t/TestApp/t/08-notifications.t	Wed Jul 18 17:53:40 2007
@@ -5,9 +5,29 @@
 use lib 't/lib';
 use Jifty::SubTest;
 
-use Jifty::Test tests => 2;
+use Jifty::Test tests => 4;
+
 use_ok('Jifty::Notification');
+use_ok('Email::MIME::CreateHTML');
+use_ok('Email::MIME');
+use_ok('Email::Send');
+
+my $html = "<html><body>This is the HTML portion of the test email</body></html>";
+
+my $text = "This is the text portion of the text email";
+
+my $test_email = Email::MIME->create_html(
+					  header => [
+						     From => 'test at test',
+						     To => 'test2 at test2',
+						     Subject => 'This is a test email',
+						     ],
+					  body => $html,
+					  text_body => $text
+					  );
+
+
 
-TODO: {local $TODO = "Actually write tests"; ok(0, "Test notifications")};
+# TODO: {local $TODO = "Actually write tests"; ok(0, "Test notifications")};
 
 1;

Modified: jifty/branches/virtual-models/t/TestApp/t/create-by-uuid.t
==============================================================================
--- jifty/branches/virtual-models/t/TestApp/t/create-by-uuid.t	(original)
+++ jifty/branches/virtual-models/t/TestApp/t/create-by-uuid.t	Wed Jul 18 17:53:40 2007
@@ -5,7 +5,7 @@
 use lib 't/lib';
 use Jifty::SubTest;
 
-use Jifty::Test tests => 6;
+use Jifty::Test tests => 8;
 
 use_ok('TestApp::Model::User');
 use_ok('TestApp::Model::Address');
@@ -16,6 +16,9 @@
 my $user = TestApp::Model::User->new( current_user => $system_user );
 $user->create( name => $$, email => $$, password => $$ );
 ok($user->id, 'created a user');
+ok($user->__uuid, 'user has a UUID');
+like($user->__uuid, qr{[A-F0-9]{8}-(?:[A-F0-9]{4}-){3}[A-F0-9]{12}}i, 
+    'the UUID is in the correct format');
 
 my $address = TestApp::Model::Address->new( current_user => $system_user );
 $address->create( person => $user->__uuid, name => $$, street => $$ );


More information about the Jifty-commit mailing list