[open-ils-commits] [GIT] Evergreen ILS branch master updated. 9f273271093aa9226c5a27613bb5f7f1d3f0e7db

Evergreen Git git at git.evergreen-ils.org
Mon Sep 19 11:35:04 EDT 2011


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "Evergreen ILS".

The branch, master has been updated
       via  9f273271093aa9226c5a27613bb5f7f1d3f0e7db (commit)
      from  22dc530aeb26749ebaf6121353d97f8ce4a252b3 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 9f273271093aa9226c5a27613bb5f7f1d3f0e7db
Author: Lebbeous Fogle-Weekley <lebbeous at esilibrary.com>
Date:   Fri Sep 16 18:31:56 2011 -0400

    Bookbag enhancements in TTOPAC
    
    Bookbags have descriptions now (and they're reflected in feeds).
    Bookbag item notes are editable.
    Bookbags can now be sorted by title or author using QP tricks.
    You can export a bookbag as CSV.
    
    Signed-off-by: Lebbeous Fogle-Weekley <lebbeous at esilibrary.com>
    Signed-off-by: Mike Rylander <mrylander at gmail.com>

diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml
index 8c587e3..97875ee 100644
--- a/Open-ILS/examples/fm_IDL.xml
+++ b/Open-ILS/examples/fm_IDL.xml
@@ -3648,6 +3648,7 @@ SELECT  usr,
 			<field name="btype" reporter:datatype="text"/>
 			<field name="id" reporter:datatype="id" />
 			<field name="name" reporter:datatype="text"/>
+			<field name="description" reporter:datatype="text"/>
 			<field name="owner" reporter:datatype="link"/>
 			<field name="pub" reporter:datatype="bool"/>
 			<field name="create_time" reporter:datatype="timestamp" />
@@ -4605,6 +4606,7 @@ SELECT  usr,
 			<field name="btype" reporter:datatype="text"/>
 			<field name="id" reporter:datatype="id" />
 			<field name="name"  reporter:datatype="text"/>
+			<field name="description" reporter:datatype="text"/>
 			<field name="owner" reporter:datatype="link"/>
 			<field name="pub" reporter:datatype="bool"/>
 			<field name="create_time" reporter:datatype="timestamp" />
@@ -4711,6 +4713,7 @@ SELECT  usr,
 			<field name="btype" reporter:datatype="text"/>
 			<field name="id" reporter:datatype="id" />
 			<field name="name"  reporter:datatype="text"/>
+			<field name="description" reporter:datatype="text"/>
 			<field name="owner" reporter:datatype="link"/>
 			<field name="pub" reporter:datatype="bool"/>
 			<field name="create_time" reporter:datatype="timestamp" />
@@ -5400,6 +5403,7 @@ SELECT  usr,
 			<field name="btype" reporter:datatype="text"/>
 			<field name="id" reporter:datatype="id" />
 			<field name="name" reporter:datatype="text"/>
+			<field name="description" reporter:datatype="text"/>
 			<field name="owner" reporter:datatype="link"/>
 			<field name="pub" reporter:datatype="bool"/>
 			<field name="create_time" reporter:datatype="timestamp" />
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor/Container.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor/Container.pm
index ec381ab..537f35f 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor/Container.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor/Container.pm
@@ -131,29 +131,53 @@ __PACKAGE__->register_method(
 
 sub item_note_cud {
     my($self, $conn, $auth, $class, $note) = @_;
+
+    return new OpenILS::Event("BAD_PARAMS") unless
+        $note->class_name =~ /bucket_item_note$/;
+
     my $e = new_editor(authtoken => $auth, xact => 1);
     return $e->die_event unless $e->checkauth;
 
-    my $meth = 'retrieve_' . $ctypes{$class};
-    my $nclass = $note->class_name;
-    (my $iclass = $nclass) =~ s/n$//og;
+    my $meat = $ctypes{$class} . "_item_note";
+    my $meth = "retrieve_$meat";
 
-    my $db_note = $e->$meth($note->id, {
-        flesh => 2,
-        flesh_fields => {
-            $nclass => ['item'],
-            $iclass => ['bucket']
-        }
-    });
+    my $item_meat = $ctypes{$class} . "_item";
+    my $item_meth = "retrieve_$item_meat";
+
+    my $nhint = $Fieldmapper::fieldmap->{$note->class_name}->{hint};
+    (my $ihint = $nhint) =~ s/n$//og;
+
+    my ($db_note, $item);
+
+    if ($note->isnew) {
+        $db_note = $note;
+
+        $item = $e->$item_meth([
+            $note->item, {
+                flesh => 1, flesh_fields => {$ihint => ["bucket"]}
+            }
+        ]) or return $e->die_event;
+    } else {
+        $db_note = $e->$meth([
+            $note->id, {
+                flesh => 2,
+                flesh_fields => {
+                    $nhint => ['item'],
+                    $ihint => ['bucket']
+                }
+            }
+        ]) or return $e->die_event;
+
+        $item = $db_note->item;
+    }
 
-    if($db_note->item->bucket->owner ne $e->requestor->id) {
-        return $e->die_event unless 
-            $e->allowed('UPDATE_CONTAINER', $db_note->item->bucket);
+    if($item->bucket->owner ne $e->requestor->id) {
+        return $e->die_event unless $e->allowed("UPDATE_CONTAINER");
     }
 
-    $meth = 'create_' . $ctypes{$class} if $note->isnew;
-    $meth = 'update_' . $ctypes{$class} if $note->ischanged;
-    $meth = 'delete_' . $ctypes{$class} if $note->isdeleted;
+    $meth = 'create_' . $meat if $note->isnew;
+    $meth = 'update_' . $meat if $note->ischanged;
+    $meth = 'delete_' . $meat if $note->isdeleted;
     return $e->die_event unless $e->$meth($note);
     $e->commit;
 }
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/AppUtils.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/AppUtils.pm
index d31bc2a..c0dbf7b 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/AppUtils.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/AppUtils.pm
@@ -1854,5 +1854,57 @@ sub get_bre_attrs {
     return $attrs;
 }
 
+sub bib_container_items_via_search {
+    my ($class, $container_id, $search_query, $search_args) = @_;
+
+    # First, Use search API to get container items sorted in any way that crad
+    # sorters support.
+    my $search_result = $class->simplereq(
+        "open-ils.search", "open-ils.search.biblio.multiclass.query",
+        $search_args, $search_query
+    );
+    unless ($search_result) {
+        # empty result sets won't cause this, but actual errors should.
+        $logger->warn("bib_container_items_via_search() got nothing from search");
+        return;
+    }
+
+    # Throw away other junk from search, keeping only bib IDs.
+    my $id_list = [ map { pop @$_ } @{$search_result->{ids}} ];
+
+    return [] unless @$id_list;
+
+    # Now get the bib container items themselves...
+    my $e = new OpenILS::Utils::CStoreEditor;
+    unless ($e) {
+        $logger->warn("bib_container_items_via_search() couldn't get cstoreeditor");
+        return;
+    }
+
+    my $items = $e->search_container_biblio_record_entry_bucket_item([
+        {
+            "target_biblio_record_entry" => $id_list,
+            "bucket" => $container_id
+        }, {
+            flesh => 1,
+            flesh_fields => {"cbrebi" => [qw/notes target_biblio_record_entry/]}
+        }
+    ]);
+    unless ($items) {
+        $logger->warn(
+            "bib_container_items_via_search() couldn't get bucket items: " .
+            $e->die_event->{textcode}
+        );
+        return;
+    }
+
+    $e->disconnect;
+
+    # ... and put them in the same order that the search API said they
+    # should be in.
+    my %ordering_hash = map { $_->target_biblio_record_entry->id, $_ } @$items;
+    return [map { $ordering_hash{$_} } @$id_list];
+}
+
 1;
 
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger/Reactor.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger/Reactor.pm
index cfeeca6..4214cab 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger/Reactor.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger/Reactor.pm
@@ -5,6 +5,7 @@ use Template;
 use DateTime;
 use DateTime::Format::ISO8601;
 use Unicode::Normalize;
+use XML::LibXML;
 use OpenSRF::Utils qw/:datetime/;
 use OpenSRF::Utils::Logger qw(:logger);
 use OpenILS::Application::AppUtils;
@@ -199,6 +200,22 @@ my $_TT_helpers = {
         return;
     },
 
+    csv_datum => sub {
+        my ($str) = @_;
+
+        if ($str =~ /\,/ || $str =~ /"/) {
+            $str =~ s/"/""/g;
+            $str = '"' . $str . '"';
+        }
+
+        return $str;
+    },
+
+    xml_doc => sub {
+        my ($str) = @_;
+        return $str ? (new XML::LibXML)->parse_string($str) : undef;
+    }
+
 };
 
 
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger/Reactor/ContainerCSV.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger/Reactor/ContainerCSV.pm
new file mode 100644
index 0000000..28a3419
--- /dev/null
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger/Reactor/ContainerCSV.pm
@@ -0,0 +1,49 @@
+package OpenILS::Application::Trigger::Reactor::ContainerCSV;
+use base "OpenILS::Application::Trigger::Reactor";
+use strict;
+use warnings;
+use OpenSRF::Utils::Logger qw/:logger/;
+use Data::Dumper;
+$Data::Dumper::Indent = 0;
+my $U = "OpenILS::Application::AppUtils";
+
+sub ABOUT {
+    return q|
+
+The ContainerCSV Reactor Module processes the configured template after
+fetching the items from the bookbag refererred to in $env->{target}
+by using the search api with the query in $env->{params}{search}.  It's
+the event-creator's responsibility to build a correct search query and check
+permissions and do that sort of thing.
+
+open-ils.trigger is not a public service, so that should be ok.
+
+The output, like all processed templates, is stored in the event_output table.
+
+|;
+}
+
+sub handler {
+    my ($self, $env) = @_;
+
+    # get items for bookbags (bib containers of btype bookbag)
+    if ($env->{user_data}{item_search}) {
+        # use the search api for bib container items
+        my $items = $U->bib_container_items_via_search(
+            $env->{target}->id, $env->{user_data}{item_search}
+        ) or return 0;  # TODO build error output for db?
+
+        $env->{items} = $items;
+    } else {
+        # XXX TODO If we're going to support other types of containers here,
+        # we'll probably just want to flesh those containers' items directly,
+        # not involve the search API.
+
+        $logger->warn("ContainerCSV reactor used without item_search, doesn't know what to do."); # XXX
+    }
+
+    return 1 if $self->run_TT($env);
+    return 0;
+}
+
+1;
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm
index 651ac48..b6281ce 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm
@@ -145,6 +145,7 @@ sub load {
     return $self->load_myopac_update_password if $path =~ m|opac/myopac/update_password|;
     return $self->load_myopac_update_username if $path =~ m|opac/myopac/update_username|;
     return $self->load_myopac_bookbags if $path =~ m|opac/myopac/lists|;
+    return $self->load_myopac_bookbag_print if $path =~ m|opac/myopac/list/print|;
     return $self->load_myopac_bookbag_update if $path =~ m|opac/myopac/list/update|;
     return $self->load_myopac_circ_history if $path =~ m|opac/myopac/circ_history|;
     return $self->load_myopac_hold_history if $path =~ m|opac/myopac/hold_history|;
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm
index 92858eb..4b0477e 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm
@@ -225,6 +225,34 @@ sub _load_user_with_prefs {
     return undef;
 }
 
+sub _get_bookbag_sort_params {
+    my ($self) = @_;
+
+    # The interface that feeds this cgi parameter will provide a single
+    # argument for a QP sort filter, and potentially a modifier after a period.
+    # In practice this means the "sort" parameter will be something like
+    # "titlesort" or "authorsort.descending".
+    my $sorter = $self->cgi->param("sort") || "";
+    my $modifier;
+    if ($sorter) {
+        $sorter =~ s/^(.*?)\.(.*)/$1/;
+        $modifier = $2 || undef;
+    }
+
+    return ($sorter, $modifier);
+}
+
+sub _prepare_bookbag_container_query {
+    my ($self, $container_id, $sorter, $modifier) = @_;
+
+    return sprintf(
+        "container(bre,bookbag,%d,%s)%s%s",
+        $container_id, $self->editor->authtoken,
+        ($sorter ? " sort($sorter)" : ""),
+        ($modifier ? "#$modifier" : "")
+    );
+}
+
 sub load_myopac_prefs_settings {
     my $self = shift;
 
@@ -1214,6 +1242,7 @@ sub load_myopac_bookbags {
     my $e = $self->editor;
     my $ctx = $self->ctx;
 
+    my ($sorter, $modifier) = $self->_get_bookbag_sort_params;
     $e->xact_begin; # replication...
 
     my $rv = $self->load_mylist;
@@ -1222,17 +1251,14 @@ sub load_myopac_bookbags {
         return $rv;
     }
 
-    my $args = {
-        order_by => {cbreb => 'name'},
-        limit => $self->cgi->param('limit') || 10,
-        offset => $self->cgi->param('offset') || 0
-    };
-
     $ctx->{bookbags} = $e->search_container_biblio_record_entry_bucket(
         [
-            {owner => $self->editor->requestor->id, btype => 'bookbag'},
-            {"flesh" => 1, "flesh_fields" => {"cbreb" => ["items"]}, %$args}
-        ], 
+            {owner => $e->requestor->id, btype => 'bookbag'}, {
+                order_by => {cbreb => 'name'},
+                limit => $self->cgi->param('limit') || 10,
+                offset => $self->cgi->param('offset') || 0
+            }
+        ],
         {substream => 1}
     );
 
@@ -1241,17 +1267,33 @@ sub load_myopac_bookbags {
         return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
     }
     
-    # get unique record IDs
-    my %rec_ids = ();
-    foreach my $bbag (@{$ctx->{bookbags}}) {
-        foreach my $rec_id (
-            map { $_->target_biblio_record_entry } @{$bbag->items}
-        ) {
-            $rec_ids{$rec_id} = 1;
+    # Here is the loop that uses search to find the bib records in each
+    # bookbag.  XXX This should be parallelized.  Should this be done
+    # with OpenSRF::MultiSession, or is it enough to use OpenSRF::AppSession
+    # and call ->request() without calling ->gather() on any of those objects
+    # until all the requests have been issued?
+
+    foreach my $bookbag (@{$ctx->{bookbags}}) {
+        my $query = $self->_prepare_bookbag_container_query(
+            $bookbag->id, $sorter, $modifier
+        );
+
+        # XXX we need to limit the number of records per bbag; use third arg
+        # of bib_container_items_via_search() i think.
+        my $items = $U->bib_container_items_via_search($bookbag->id, $query)
+            or return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
+
+        # Maybe save a little memory by creating only one XML::LibXML::Document
+        # instance for each record, even if record is repeated across bookbags.
+
+        foreach my $rec (map { $_->target_biblio_record_entry } @$items) {
+            next if $ctx->{bookbags_marc_xml}{$rec->id};
+            $ctx->{bookbags_marc_xml}{$rec->id} =
+                (new XML::LibXML)->parse_string($rec->marc);
         }
-    }
 
-    $ctx->{bookbags_marc_xml} = $self->fetch_marc_xml_by_id([keys %rec_ids]);
+        $bookbag->items($items);
+    }
 
     $e->rollback;
     return Apache2::Const::OK;
@@ -1265,19 +1307,33 @@ sub load_myopac_bookbag_update {
     my $e = $self->editor;
     my $cgi = $self->cgi;
 
+    # save_notes is effectively another action, but is passed in a separate
+    # CGI parameter for what are really just layout reasons.
+    $action = 'save_notes' if $cgi->param('save_notes');
     $action ||= $cgi->param('action');
+
     $list_id ||= $cgi->param('list');
 
     my @add_rec = $cgi->param('add_rec') || $cgi->param('record');
     my @selected_item = $cgi->param('selected_item');
     my $shared = $cgi->param('shared');
     my $name = $cgi->param('name');
+    my $description = $cgi->param('description');
     my $success = 0;
     my $list;
 
-    if($action eq 'create') {
+    # This url intentionally leaves off the edit_notes parameter, but
+    # may need to add some back in for paging.
+
+    my $url = "https://" . $self->apache->hostname .
+        $self->ctx->{opac_root} . "/myopac/lists?";
+
+    $url .= 'sort=' . uri_escape($cgi->param("sort")) if $cgi->param("sort");
+
+    if ($action eq 'create') {
         $list = Fieldmapper::container::biblio_record_entry_bucket->new;
         $list->name($name);
+        $list->description($description);
         $list->owner($e->requestor->id);
         $list->btype('bookbag');
         $list->pub($shared ? 't' : 'f');
@@ -1352,13 +1408,144 @@ sub load_myopac_bookbag_update {
             );
             last unless $success;
         }
+    } elsif ($action eq 'save_notes') {
+        $success = $self->update_bookbag_item_notes;
     }
 
-    return $self->generic_redirect if $success;
+    return $self->generic_redirect($url) if $success;
+
+    # XXX FIXME Bucket failure doesn't have a page to show the user anything
+    # right now. User just sees a 404 currently.
 
     $self->ctx->{bucket_action} = $action;
     $self->ctx->{bucket_action_failed} = 1;
     return Apache2::Const::OK;
 }
 
-1
+sub update_bookbag_item_notes {
+    my ($self) = @_;
+    my $e = $self->editor;
+
+    my @note_keys = grep /^note-\d+/, keys(%{$self->cgi->Vars});
+    my @item_keys = grep /^item-\d+/, keys(%{$self->cgi->Vars});
+
+    # We're going to leverage an API call that's already been written to check
+    # permissions appropriately.
+
+    my $a = create OpenSRF::AppSession("open-ils.actor");
+    my $method = "open-ils.actor.container.item_note.cud";
+
+    for my $note_key (@note_keys) {
+        my $note;
+
+        my $id = ($note_key =~ /(\d+)/)[0];
+
+        if (!($note =
+            $e->retrieve_container_biblio_record_entry_bucket_item_note($id))) {
+            my $event = $e->die_event;
+            $self->apache->log->warn(
+                "error retrieving cbrebin id $id, got event " .
+                $event->{textcode}
+            );
+            $a->kill_me;
+            $self->ctx->{bucket_action_event} = $event;
+            return;
+        }
+
+        if (length($self->cgi->param($note_key))) {
+            $note->ischanged(1);
+            $note->note($self->cgi->param($note_key));
+        } else {
+            $note->isdeleted(1);
+        }
+
+        my $r = $a->request($method, $e->authtoken, "biblio", $note)->gather(1);
+
+        if (defined $U->event_code($r)) {
+            $self->apache->log->warn(
+                "attempt to modify cbrebin " . $note->id .
+                " returned event " .  $r->{textcode}
+            );
+            $e->rollback;
+            $a->kill_me;
+            $self->ctx->{bucket_action_event} = $r;
+            return;
+        }
+    }
+
+    for my $item_key (@item_keys) {
+        my $id = int(($item_key =~ /(\d+)/)[0]);
+        my $text = $self->cgi->param($item_key);
+
+        chomp $text;
+        next unless length $text;
+
+        my $note = new Fieldmapper::container::biblio_record_entry_bucket_item_note;
+        $note->isnew(1);
+        $note->item($id);
+        $note->note($text);
+
+        my $r = $a->request($method, $e->authtoken, "biblio", $note)->gather(1);
+
+        if (defined $U->event_code($r)) {
+            $self->apache->log->warn(
+                "attempt to create cbrebin for item " . $note->item .
+                " returned event " .  $r->{textcode}
+            );
+            $e->rollback;
+            $a->kill_me;
+            $self->ctx->{bucket_action_event} = $r;
+            return;
+        }
+    }
+
+    $a->kill_me;
+    return 1;   # success
+}
+
+sub load_myopac_bookbag_print {
+    my ($self) = @_;
+
+    $self->apache->content_type("text/plain; encoding=utf8");
+
+    my $id = int($self->cgi->param("list"));
+
+    my ($sorter, $modifier) = $self->_get_bookbag_sort_params;
+
+    my $item_search =
+        $self->_prepare_bookbag_container_query($id, $sorter, $modifier);
+
+    my $bbag;
+
+    # Get the bookbag object itself, assuming we're allowed to.
+    if ($self->editor->allowed("VIEW_CONTAINER")) {
+
+        $bbag = $self->editor->retrieve_container_biblio_record_entry_bucket($id) or return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
+    } else {
+        my $bookbags = $self->editor->search_container_biblio_record_entry_bucket(
+            {
+                "id" => $id,
+                "-or" => {
+                    "owner" => $self->editor->requestor->id,
+                    "pub" => "t"
+                }
+            }
+        ) or return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
+
+        $bbag = pop @$bookbags;
+    }
+
+    # If we have a bookbag we're allowed to look at, issue the A/T event
+    # to get CSV, passing as a user param that search query we built before.
+    if ($bbag) {
+        $self->ctx->{csv} = $U->fire_object_event(
+            undef, "container.biblio_record_entry_bucket.csv",
+            $bbag, $self->editor->requestor->home_ou,
+            undef, {"item_search" => $item_search}
+        );
+    }
+
+    return Apache2::Const::OK;
+}
+
+1;
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/SuperCat.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/SuperCat.pm
index e148565..4ca4b9d 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/SuperCat.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/SuperCat.pm
@@ -1024,6 +1024,7 @@ sub bookbag_feed {
     $feed->id($bucket_tag);
 
     $feed->title("Items in Book Bag [".$bucket->name."]");
+    $feed->description($bucket->description || ("Items in Book Bag [".$bucket->name."]"));
     $feed->creator($host);
     $feed->update_ts();
 
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/SuperCat/Feed.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/SuperCat/Feed.pm
index cd11a75..fde04eb 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/SuperCat/Feed.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/SuperCat/Feed.pm
@@ -322,8 +322,11 @@ sub title {
 	my $self = shift;
 	my $text = shift;
 	$self->_create_node('/rss/channel',undef,'title', $text);
-	# RSS2 demands a /channel/description element; just dupe title until we give
-	# users the ability to provide a description for their bookbags
+}
+
+sub description {
+	my $self = shift;
+	my $text = shift;
 	$self->_create_node('/rss/channel',undef,'description', $text);
 }
 
diff --git a/Open-ILS/src/sql/Pg/070.schema.container.sql b/Open-ILS/src/sql/Pg/070.schema.container.sql
index 6bbd17c..0e21c5f 100644
--- a/Open-ILS/src/sql/Pg/070.schema.container.sql
+++ b/Open-ILS/src/sql/Pg/070.schema.container.sql
@@ -35,6 +35,7 @@ CREATE TABLE container.copy_bucket (
 								INITIALLY DEFERRED,
 	name		TEXT				NOT NULL,
 	btype		TEXT				NOT NULL DEFAULT 'misc' REFERENCES container.copy_bucket_type (code) DEFERRABLE INITIALLY DEFERRED,
+	description TEXT,
 	pub		BOOL				NOT NULL DEFAULT FALSE,
 	create_time	TIMESTAMP WITH TIME ZONE	NOT NULL DEFAULT NOW(),
 	CONSTRAINT cb_name_once_per_owner UNIQUE (owner,name,btype)
@@ -88,6 +89,7 @@ CREATE TABLE container.call_number_bucket (
 				INITIALLY DEFERRED,
 	name	TEXT	NOT NULL,
 	btype	TEXT	NOT NULL DEFAULT 'misc' REFERENCES container.call_number_bucket_type (code) DEFERRABLE INITIALLY DEFERRED,
+	description TEXT,
 	pub	BOOL	NOT NULL DEFAULT FALSE,
 	create_time	TIMESTAMP WITH TIME ZONE	NOT NULL DEFAULT NOW(),
 	CONSTRAINT cnb_name_once_per_owner UNIQUE (owner,name,btype)
@@ -142,6 +144,7 @@ CREATE TABLE container.biblio_record_entry_bucket (
 				INITIALLY DEFERRED,
 	name	TEXT	NOT NULL,
 	btype	TEXT	NOT NULL DEFAULT 'misc' REFERENCES container.biblio_record_entry_bucket_type (code) DEFERRABLE INITIALLY DEFERRED,
+	description TEXT,
 	pub	BOOL	NOT NULL DEFAULT FALSE,
 	create_time	TIMESTAMP WITH TIME ZONE	NOT NULL DEFAULT NOW(),
 	CONSTRAINT breb_name_once_per_owner UNIQUE (owner,name,btype)
@@ -194,6 +197,7 @@ CREATE TABLE container.user_bucket (
 				INITIALLY DEFERRED,
 	name	TEXT	NOT NULL,
 	btype	TEXT	NOT NULL DEFAULT 'misc' REFERENCES container.user_bucket_type (code) DEFERRABLE INITIALLY DEFERRED,
+	description TEXT,
 	pub	BOOL	NOT NULL DEFAULT FALSE,
 	create_time	TIMESTAMP WITH TIME ZONE	NOT NULL DEFAULT NOW(),
 	CONSTRAINT ub_name_once_per_owner UNIQUE (owner,name,btype)
diff --git a/Open-ILS/src/sql/Pg/950.data.seed-values.sql b/Open-ILS/src/sql/Pg/950.data.seed-values.sql
index 0dd096c..42e029c 100644
--- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql
+++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql
@@ -10046,6 +10046,56 @@ INSERT INTO action_trigger.environment ( event_def, path) VALUES (
     ,( 47, 'record.queue.owner')
 ;
 
+INSERT INTO action_trigger.hook (key, core_type, description, passive)
+VALUES (
+    'container.biblio_record_entry_bucket.csv',
+    'cbreb',
+    oils_i18n_gettext(
+        'container.biblio_record_entry_bucket.csv',
+        'Produce a CSV file representing a bookbag',
+        'ath',
+        'description'
+    ),
+    FALSE
+);
+
+INSERT INTO action_trigger.reactor (module, description)
+VALUES (
+    'ContainerCSV',
+    oils_i18n_gettext(
+        'ContainerCSV',
+        'Facilitates produce a CSV file representing a bookbag by introducing an "items" variable into the TT environment, sorted as dictated according to user params',
+        'atr',
+        'description'
+    )
+);
+
+INSERT INTO action_trigger.event_definition (
+    id, active, owner,
+    name, hook, reactor,
+    validator, template
+) VALUES (
+    48, TRUE, 1,
+    'Bookbag CSV', 'container.biblio_record_entry_bucket.csv', 'ContainerCSV',
+    'NOOP_True',
+$$
+[%-
+# target is the bookbag itself. The 'items' variable does not need to be in
+# the environment because a special reactor will take care of filling it in.
+
+FOR item IN items;
+    bibxml = helpers.xml_doc(item.target_biblio_record_entry.marc);
+    title = "";
+    FOR part IN bibxml.findnodes('//*[@tag="245"]/*[@code="a" or @code="b"]');
+        title = title _ part.textContent;
+    END;
+    author = bibxml.findnodes('//*[@tag="100"]/*[@code="a"]').textContent;
+
+    helpers.csv_datum(title) %],[% helpers.csv_datum(author) %],[% FOR note IN item.notes; helpers.csv_datum(note.note); ","; END; "\n";
+END -%]
+$$
+);
+
 SELECT SETVAL('authority.control_set_id_seq'::TEXT, 100);
 SELECT SETVAL('authority.control_set_authority_field_id_seq'::TEXT, 1000);
 SELECT SETVAL('authority.control_set_bib_field_id_seq'::TEXT, 1000);
diff --git a/Open-ILS/src/sql/Pg/upgrade/YYYY.schema.bookbag-goodies.sql b/Open-ILS/src/sql/Pg/upgrade/YYYY.schema.bookbag-goodies.sql
new file mode 100644
index 0000000..df4e456
--- /dev/null
+++ b/Open-ILS/src/sql/Pg/upgrade/YYYY.schema.bookbag-goodies.sql
@@ -0,0 +1,69 @@
+-- Evergreen DB patch YYYY.schema.bookbag-goodies.sql
+
+BEGIN;
+
+SELECT evergreen.upgrade_deps_block_check('YYYY', :eg_version);
+
+ALTER TABLE container.biblio_record_entry_bucket
+    ADD COLUMN description TEXT;
+
+ALTER TABLE container.call_number_bucket
+    ADD COLUMN description TEXT;
+
+ALTER TABLE container.copy_bucket
+    ADD COLUMN description TEXT;
+
+ALTER TABLE container.user_bucket
+    ADD COLUMN description TEXT;
+
+INSERT INTO action_trigger.hook (key, core_type, description, passive)
+VALUES (
+    'container.biblio_record_entry_bucket.csv',
+    'cbreb',
+    oils_i18n_gettext(
+        'container.biblio_record_entry_bucket.csv',
+        'Produce a CSV file representing a bookbag',
+        'ath',
+        'description'
+    ),
+    FALSE
+);
+
+INSERT INTO action_trigger.reactor (module, description)
+VALUES (
+    'ContainerCSV',
+    oils_i18n_gettext(
+        'ContainerCSV',
+        'Facilitates produce a CSV file representing a bookbag by introducing an "items" variable into the TT environment, sorted as dictated according to user params',
+        'atr',
+        'description'
+    )
+);
+
+INSERT INTO action_trigger.event_definition (
+    id, active, owner,
+    name, hook, reactor,
+    validator, template
+) VALUES (
+    48, TRUE, 1,
+    'Bookbag CSV', 'container.biblio_record_entry_bucket.csv', 'ContainerCSV',
+    'NOOP_True',
+$$
+[%-
+# target is the bookbag itself. The 'items' variable does not need to be in
+# the environment because a special reactor will take care of filling it in.
+
+FOR item IN items;
+    bibxml = helpers.xml_doc(item.target_biblio_record_entry.marc);
+    title = "";
+    FOR part IN bibxml.findnodes('//*[@tag="245"]/*[@code="a" or @code="b"]');
+        title = title _ part.textContent;
+    END;
+    author = bibxml.findnodes('//*[@tag="100"]/*[@code="a"]').textContent;
+
+    helpers.csv_datum(title) %],[% helpers.csv_datum(author) %],[% FOR note IN item.notes; helpers.csv_datum(note.note); ","; END; "\n";
+END -%]
+$$
+);
+
+COMMIT;
diff --git a/Open-ILS/src/templates/opac/myopac/list/print.tt2 b/Open-ILS/src/templates/opac/myopac/list/print.tt2
new file mode 100644
index 0000000..fa3988f
--- /dev/null
+++ b/Open-ILS/src/templates/opac/myopac/list/print.tt2
@@ -0,0 +1 @@
+[%- ctx.csv.template_output.data -%]
diff --git a/Open-ILS/src/templates/opac/myopac/lists.tt2 b/Open-ILS/src/templates/opac/myopac/lists.tt2
index 0c22f6d..863bb1a 100644
--- a/Open-ILS/src/templates/opac/myopac/lists.tt2
+++ b/Open-ILS/src/templates/opac/myopac/lists.tt2
@@ -27,6 +27,8 @@
             <tr>
                 <td>
                     <label for="list_create_name">[% l('Enter the name of the new list:') %]</label>
+                </td>
+                <td>
                     <input id="list_create_name" type="text" name="name" />
                     <input type="hidden" name="action" value="create" />
                 </td>
@@ -51,9 +53,27 @@
                         class="opac-button" />
                 </td>
             </tr>
+            <tr>
+                <td class="text-right-top">
+                    <label for="list_description">[% l("List description (optional):") %]</label>
+                </td>
+                <td colspan="3">
+                    <textarea cols="40" rows="3" name="description"
+                        id="list_description"></textarea>
+                </td>
         </table>
     </form>
 
+    <h2>[% l("Your existing lists") %]</h2>
+    <p>
+        <form method="GET">
+            <label for="opac.result.sort">[% l("Sort list items by: ") %]</label>
+            [% INCLUDE "opac/parts/filtersort.tt2"
+                value=CGI.param('sort') %]
+            <input type="submit" value="[% l('Sort') %]" />
+        </form>
+    </p>
+
     [% INCLUDE "opac/parts/anon_list.tt2" %]
     [% IF ctx.bookbags.size %]
     <div id='acct_lists_prime'>
@@ -79,6 +99,13 @@
                         <input type="submit" value="[% l('Delete List') %]" />
                     </div>
                 </form>
+                <form action="[% ctx.opac_root %]/myopac/list/print" method="POST" target="_blank">
+                    <div class="bookbag-controls">
+                        <input type="hidden" name="list" value="[% bbag.id %]" />
+                        <input type="hidden" name="sort" value="[% CGI.param('sort') | html %]" />
+                        <input type="submit" value="[% l('Download CSV') %]" />
+                    </div>
+                </form>
                 <div class="bookbag-controls">
                     <big><strong>
                     [% IF bbag.pub == 't' %]
@@ -88,6 +115,7 @@
                     [% bbag.name | html %]
                     [% END %]
                     </strong></big>
+                    [% IF bbag.description %]<br /><em>[% bbag.description | html %]</em>[% END %]
                 </div>
                 <div class="bookbag-controls">
                     [% IF bbag.pub == 't'; %]
@@ -100,6 +128,7 @@
             </div>
             <form action="[% ctx.opac_root %]/myopac/list/update" method="POST">
             <input type="hidden" name="list" value="[% bbag.id %]" />
+            <input type="hidden" name="sort" value="[% CGI.param('sort') | uri %]" />
             <table cellpadding='0' cellspacing='0' border='0'>
                 <thead id="acct_list_header">
                     <tr>
@@ -111,8 +140,18 @@
                                     inputs[i].checked = this.checked;}"/>
 
                         </td>
-                        <td width="49%" style="padding-left: 5px;">[% l('Title') %]</td>
-                        <td width="49%">[% l('Author(s)') %]</td>
+                        <td width="32%" style="padding-left: 5px;">
+                            <a href="[% ctx.opac_root %]/myopac/lists?sort=titlesort">[% l('Title') %]</a>
+                        </td>
+                        <td width="33%">
+                            <a href="[% ctx.opac_root %]/myopac/lists?sort=authorsort">[% l('Author(s)') %]</a>
+                        </td>
+                        <td width="32%">
+                            [% l('Notes') %]
+                            [% IF CGI.param("edit_notes") != bbag.id %]
+                            | <a href="[% ctx.opac_root %]/myopac/lists?[% IF CGI.param('sort'); 'sort='; (CGI.param('sort')) | uri; '&amp;'; END %]edit_notes=[% bbag.id %]">[% l('Edit') %]</a>
+                            [% END %]
+                        </td>
                         <td width="1%" class="nowrap">
                             <select class="selector_actions_for_list" name="action">
                                 <option>[% l('-- Actions for this list --') %]</option>
@@ -130,10 +169,10 @@
                     </td></tr>
                     [% END %]
                     [% FOR item IN bbag.items;
-                        rec_id = item.target_biblio_record_entry;
+                        rec_id = item.target_biblio_record_entry.id;
                         attrs = {marc_xml => ctx.bookbags_marc_xml.$rec_id};
                         PROCESS get_marc_attrs args=attrs %]
-                    <tr>
+                    <tr class="bookbag-item-row">
                         <td class="item_list_padding" style="padding-left: 10px;">
                             <input type="checkbox" name="selected_item" value="[% item.id %]" bbag='[% bbag.id %]'/>
                         </td>
@@ -145,13 +184,34 @@
                                 authorquery = attrs.author | replace('[,\.:;]', '');
                                 mkurl(ctx.opac_root _ '/results', {qtype => 'author', query => authorquery}, ['page'])
                                 -%]">[% attrs.author | html %]</a>
+                        [% IF CGI.param("edit_notes") == bbag.id %]
+                        <td class="opac-auto-097b">
+                            [% FOR note IN item.notes %]
+                            <input type="text" name="note-[% note.id %]" value="[% note.note | html %]" />
+                            [% END %]
+                            <input type="text" name="item-[% item.id %]" />
+                        </td>
+                        [% ELSE %]
+                        <td class="opac-auto-097b">
+                            [% FOR note IN item.notes %]
+                            <div>[% note.note | html %]</div>
+                            [% END %]
+                        </td>
+                        [% END %]
+                    </tr>
+                    [% END %]
+                    [% IF CGI.param("edit_notes") == bbag.id %]
+                    <tr>
+                        <td colspan="3"><!-- All space left of notes column --></td>
+                        <td>
+                            <input type="submit" name="save_notes" value="[% l('Save Notes') %]" />
                         </td>
                     </tr>
                     [% END %]
                 </tbody>
             </table>
             </form>
-            <br /><br />
+            <hr /><br />
         </div>
         [% END %]
     </div>
diff --git a/Open-ILS/src/templates/opac/parts/advanced/search.tt2 b/Open-ILS/src/templates/opac/parts/advanced/search.tt2
index 6b97540..1c28bb5 100644
--- a/Open-ILS/src/templates/opac/parts/advanced/search.tt2
+++ b/Open-ILS/src/templates/opac/parts/advanced/search.tt2
@@ -51,7 +51,7 @@
                 <tr>
                     <td align='center' width='100%'>
                         [% INCLUDE "opac/parts/filtersort.tt2"
-                            value=CGI.param('sort') %]
+                            value=CGI.param('sort') class='results_header_sel' %]
                     </td>
                 </tr>
               </table>
diff --git a/Open-ILS/src/templates/opac/parts/filtersort.tt2 b/Open-ILS/src/templates/opac/parts/filtersort.tt2
index 664be17..74d9c8a 100644
--- a/Open-ILS/src/templates/opac/parts/filtersort.tt2
+++ b/Open-ILS/src/templates/opac/parts/filtersort.tt2
@@ -1,16 +1,15 @@
-<select class="results_header_sel" id='opac.result.sort' name="sort"
-    [% IF submit_on_change %]onchange='this.form.submit()'[% END %]>
+<select [% class ? ('class="' _ class _ '"') : '' %] id='opac.result.sort' name="[% name || 'sort' %]" [% IF submit_on_change %]onchange='this.form.submit()'[% END %]>
     <option value=''>[% l("Sort by Relevance") %]</option>
     <optgroup label='[% l("Sort by Title") %]'>
         <option value='titlesort'[% value == 'titlesort' ? ' selected="selected"' : '' %]>[% l("Title: A to Z") %]</option>
-        <option value='titlesort.desc'[% value == 'titlesort.desc' ? ' selected="selected"' : '' %]>[% l("Title: Z to A") %]</option>
+        <option value='titlesort.descending'[% value == 'titlesort.descending' ? ' selected="selected"' : '' %]>[% l("Title: Z to A") %]</option>
     </optgroup>
     <optgroup label='[% l("Sort by Author") %]'>
         <option value='authorsort'[% value == 'authorsort' ? ' selected="selected"' : '' %]>[% l("Author: A to Z") %]</option>
-        <option value='authorsort.desc'[% value == 'authorsort.desc' ? ' selected="selected"' : '' %]>[% l("Author: Z to A") %]</option>
+        <option value='authorsort.descending'[% value == 'authorsort.descending' ? ' selected="selected"' : '' %]>[% l("Author: Z to A") %]</option>
     </optgroup>
     <optgroup label='[% l("Sort by Publication Date") %]'>
-        <option value='pubdate.desc'[% value == 'pubdate.desc' ? ' selected="selected"' : '' %]>[% l("Date: Newest to Oldest") %]</option>
+        <option value='pubdate.descending'[% value == 'pubdate.descending' ? ' selected="selected"' : '' %]>[% l("Date: Newest to Oldest") %]</option>
         <option value='pubdate'[% value == 'pubdate' ? ' selected="selected"' : '' %]>[% l("Date: Oldest to Newest") %]</option>
     </optgroup>
 </select>
diff --git a/Open-ILS/web/css/skin/default/opac/style.css b/Open-ILS/web/css/skin/default/opac/style.css
index 3664dff..6a0cec6 100644
--- a/Open-ILS/web/css/skin/default/opac/style.css
+++ b/Open-ILS/web/css/skin/default/opac/style.css
@@ -966,6 +966,7 @@ a.dash-link:hover { text-decoration: underline !important; }
 .hold-editor-controls a { padding-left: 2em; }
 
 .text-right { text-align: right; }
+.text-right-top { text-align: right; vertical-align: top; }
 .rdetail-author-div { padding-bottom: 10px; }
 
 .invisible { visibility: hidden; }
@@ -1030,3 +1031,4 @@ a.opac-button {
 }
 
 
+.bookbag-item-row td { vertical-align: top; }

-----------------------------------------------------------------------

Summary of changes:
 Open-ILS/examples/fm_IDL.xml                       |    4 +
 .../lib/OpenILS/Application/Actor/Container.pm     |   56 ++++--
 .../perlmods/lib/OpenILS/Application/AppUtils.pm   |   52 +++++
 .../lib/OpenILS/Application/Trigger/Reactor.pm     |   17 ++
 .../Application/Trigger/Reactor/ContainerCSV.pm    |   49 +++++
 .../src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm    |    1 +
 .../lib/OpenILS/WWW/EGCatLoader/Account.pm         |  229 ++++++++++++++++++--
 Open-ILS/src/perlmods/lib/OpenILS/WWW/SuperCat.pm  |    1 +
 .../src/perlmods/lib/OpenILS/WWW/SuperCat/Feed.pm  |    7 +-
 Open-ILS/src/sql/Pg/070.schema.container.sql       |    4 +
 Open-ILS/src/sql/Pg/950.data.seed-values.sql       |   50 +++++
 .../sql/Pg/upgrade/YYYY.schema.bookbag-goodies.sql |   69 ++++++
 Open-ILS/src/templates/opac/myopac/list/print.tt2  |    1 +
 Open-ILS/src/templates/opac/myopac/lists.tt2       |   70 ++++++-
 .../src/templates/opac/parts/advanced/search.tt2   |    2 +-
 Open-ILS/src/templates/opac/parts/filtersort.tt2   |    9 +-
 Open-ILS/web/css/skin/default/opac/style.css       |    2 +
 17 files changed, 573 insertions(+), 50 deletions(-)
 create mode 100644 Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger/Reactor/ContainerCSV.pm
 create mode 100644 Open-ILS/src/sql/Pg/upgrade/YYYY.schema.bookbag-goodies.sql
 create mode 100644 Open-ILS/src/templates/opac/myopac/list/print.tt2


hooks/post-receive
-- 
Evergreen ILS


More information about the open-ils-commits mailing list