CSV export for bookbags
authorLebbeous Fogle-Weekley <lebbeous@esilibrary.com>
Tue, 16 Aug 2011 20:22:31 +0000 (16:22 -0400)
committerLebbeous Fogle-Weekley <lebbeous@esilibrary.com>
Wed, 31 Aug 2011 17:26:11 +0000 (13:26 -0400)
Sorted the same way the user is currently viewing his/her bookbags.

Signed-off-by: Lebbeous Fogle-Weekley <lebbeous@esilibrary.com>
Open-ILS/src/perlmods/lib/OpenILS/Application/AppUtils.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger/Reactor.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger/Reactor/ContainerCSV.pm [new file with mode: 0644]
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm
Open-ILS/src/sql/Pg/upgrade/YYYY.schema.bookbag-goodies.sql
Open-ILS/web/templates/default/opac/myopac/list/print.tt2 [new file with mode: 0644]
Open-ILS/web/templates/default/opac/myopac/lists.tt2

index d21a8cc..a47f6fd 100644 (file)
@@ -1874,5 +1874,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;
 
index cfeeca6..4214cab 100644 (file)
@@ -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 (file)
index 0000000..28a3419
--- /dev/null
@@ -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;
index a9d0565..07284d6 100644 (file)
@@ -144,6 +144,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|;
index 6ae5444..055a2bb 100644 (file)
@@ -1207,6 +1207,7 @@ sub load_myopac_bookbags {
 
     my $sorter = $self->cgi->param("sort") || "";
     my $modifier = ($sorter =~ /\.(.+$)/) ? $1 : undef;
+    $sorter =~ s/\..+$// if $sorter;
 
     $e->xact_begin; # replication...
 
@@ -1499,4 +1500,56 @@ sub update_bookbag_item_notes {
     return 1;   # success
 }
 
-1
+sub load_myopac_bookbag_print {
+    my ($self) = @_;
+
+    $self->apache->content_type("text/plain; encoding=utf8");
+
+    my $id = int($self->cgi->param("list"));
+
+    # Prepare bib search query for bookbag's items, but don't use it yet.
+    my $sorter = $self->cgi->param("sort") || "";
+    my $modifier = ($sorter =~ /\.(.+$)/) ? $1 : undef;
+    $sorter =~ s/\..+$// if $sorter;
+
+    my $item_search = sprintf(
+        "container(bre,bookbag,%d,%s)%s%s",
+        $id, $self->editor->authtoken,
+        ($sorter ? " sort($sorter)" : ""),
+        ($modifier ? "#$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;
index d6f82a4..df4e456 100644 (file)
@@ -16,4 +16,54 @@ ALTER TABLE container.copy_bucket
 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/web/templates/default/opac/myopac/list/print.tt2 b/Open-ILS/web/templates/default/opac/myopac/list/print.tt2
new file mode 100644 (file)
index 0000000..fa3988f
--- /dev/null
@@ -0,0 +1 @@
+[%- ctx.csv.template_output.data -%]
index 3fe465b..17f2f8d 100644 (file)
                         <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' %]