Esempio n. 1
0
def full_findingaid(request, id, mode, preview=False):
    """View the full contents of a single finding aid as PDF or plain html.

    :param id: eadid for the document to be displayed
    :param mode: one of 'html' or 'pdf' - note that the html mode is not publicly
            linked anywhere, and is intended mostly for development and testing
            of the PDF display
    :param preview: boolean indicating preview mode, defaults to False
    """
    fa = get_findingaid(id, preview=preview)
    series = _subseries_links(fa.dsc, url_ids=[fa.eadid], url_callback=_series_anchor, preview=preview)

    template = 'fa/full.html'
    template_args = {'ead': fa, 'series': series,
                     'mode': mode, 'preview': preview, 'request': request,
                     # normally supplied by context processor
                     'DEFAULT_DAO_LINK_TEXT': getattr(settings, 'DEFAULT_DAO_LINK_TEXT',
                                                      '[Resource available online]')
                     }
    if mode == 'html':
        return render(request, template, template_args)
    elif mode == 'pdf':
        return render_to_pdf(template, template_args, filename='%s.pdf' % fa.eadid.value)
    elif mode == 'xsl-fo':
        xslfo = html_to_xslfo(template, template_args)
        return HttpResponse(etree.tostring(xslfo), content_type='application/xml')
Esempio n. 2
0
def eadxml(request, id, preview=False):
    """Display the full EAD XML content of a finding aid.

    :param id: eadid for the document to be displayed
    :param preview: boolean indicating preview mode, defaults to False
    """
    fa = get_findingaid(id, preview=preview)
    xml_ead = fa.serialize(pretty=True)
    return HttpResponse(xml_ead, content_type='application/xml')
Esempio n. 3
0
def preview(request, archive):
    if request.method == 'POST':

        archive = get_object_or_404(Archive, slug=archive)
        filename = request.POST['filename']

        errors = []
        err = None

        try:
            # only load to exist if document passes publication check
            ok, response, dbpath, fullpath = _prepublication_check(request, filename,
                archive, mode='preview')
            if ok is not True:
                return response

            db = ExistDB()
            # load the document to the *preview* collection in eXist with the same fileneame
            preview_dbpath = settings.EXISTDB_PREVIEW_COLLECTION + "/" + filename
            # make sure the preview collection exists, but don't complain if it's already there
            success = db.load(open(fullpath, 'r'), preview_dbpath)
        except ExistDBException as err:
            success = False
            errors.append(err.message())

        if success:
            # load the file as a FindingAid object so we can generate the preview url
            ead = load_xmlobject_from_file(fullpath, FindingAid)
            messages.success(request, 'Successfully loaded <b>%s</b> for preview.' % filename)
            # redirect to document preview page with code 303 (See Other)
            return HttpResponseSeeOtherRedirect(reverse('fa-admin:preview:findingaid', kwargs={'id': ead.eadid}))
        else:
            # no exception but no success means the load failed;
            # *probably* due to insufficient permissions
            if errors == [] and success == False:
                errors.append('Failed to load the document to the preview collection')

            return render(request, 'fa_admin/publish-errors.html',
                    {'errors': errors, 'filename': filename, 'mode': 'preview', 'exception': err})

    # NOTE: preview list is not used anymore; functionality is handled
    # by main admin view; if we revisit preview list, to be more usable it
    # should be filterable by archive
    else:
        fa = get_findingaid(preview=True, only=['eadid', 'list_title', 'last_modified'],
                            order_by='last_modified')
        return render(request, 'fa_admin/preview_list.html',
                {'findingaids': fa, #'querytime': [fa.queryTime()]
                })
Esempio n. 4
0
    def get_previewed(self):
        """Date object was loaded to eXist preview collection, if currently
            available in preview."""
        if self._previewed is None:
            try:
                fa = get_findingaid(filter={'document_name': self.filename},
                                       only=['last_modified'], preview=True)
                if fa.count():
                    self._previewed = fa[0].last_modified
            except Exception:
                pass

            # not found or error - store so we don't look up again
            if self._previewed is None:
                self._previewed = False
        return self._previewed
Esempio n. 5
0
def findingaid(request, id, preview=False):
    """View a single finding aid.   In preview mode, pulls the document from the
    configured eXist-db preview collection instead of the default public one.

    :param id: eadid for the document to view
    :param preview: boolean indicating preview mode, defaults to False
    """
    if 'keywords' in request.GET:
        search_terms = request.GET['keywords']
        url_params = '?' + urlencode({'keywords': search_terms.encode('utf-8')})
        filter = {'highlight': search_terms}
    else:
        url_params = ''
        filter = {}
    fa = get_findingaid(id, preview=preview, filter=filter)
    last_modified = ead_lastmodified(request, id, preview)
    series = _subseries_links(fa.dsc, url_ids=[fa.eadid], preview=preview,
                              url_params=url_params)

    extra_ns = RDFA_NAMESPACES.copy()
    # add any non-default namespaces from the EAD document
    extra_ns.update(dict((prefix, ns) for prefix, ns in fa.node.nsmap.iteritems()
                    if prefix is not None))
    context = {
        'ead': fa,
        'series': series,
        'all_indexes': fa.archdesc.index,
        'preview': preview,
        'url_params': url_params,
        'docsearch_form': KeywordSearchForm(),
        'last_search': request.session.get('last_search', None),
        'feedback_opts': _get_feedback_options(request, id),
        'extra_ns': extra_ns,
        'last_modified': last_modified,
    }

    # provide series list without keyword params to use in RDFa uris
    if url_params and not preview:
        context['series_noparam'] = _subseries_links(fa.dsc, url_ids=[fa.eadid])

    response = render(request, 'fa/findingaid.html', context,
                      current_app='preview')
    # Set Cache-Control to private when there is a last_search
    if "last_search" in request.session:
        response['Cache-Control'] = 'private'

    return response
Esempio n. 6
0
    def get_published(self):
        "Date object was modified in eXist, if published"
        # TODO: previewed & published logic substantially the same; consolidate
        if self._published is None:
            try:
                fa = get_findingaid(filter={'document_name': self.filename},
                                       only=['last_modified'])
                if fa.count():
                    self._published = fa[0].last_modified
            except Exception:
                # FIXME: distinguish between not found and eXist error?
                pass

            # not found or error - store so we don't look it up again
            if self._published is None:
                self._published = False
        return self._published
Esempio n. 7
0
def feedback(request):
    '''Feedback form. On GET, displays the form; on POST, processes the submitted
    form and sends an email (if all required fields are present).'''
    ead = None
    if request.method == 'POST':
        data = request.POST.copy()
        data['remote_ip'] = request.META['REMOTE_ADDR']
        form = FeedbackForm(data)
        if form.is_valid():
            err = None
            try:
                email_ok = form.send_email()
                # if it processed without exception, email should be sent ok
            except Exception as ex:
                err = ex
                email_ok = False
            # display a success/thank you page
            response = render_to_response('content/feedback.html', {
                    'email_sent': email_ok,
                    'err': err,
                }, context_instance=RequestContext(request))
            # if the email didn't go through, don't return a 200 ok status
            if not email_ok:
                response.status_code = 500
            return response
    else:
        ead = None
        if 'eadid' in request.GET:
            # retrieve minimal ead info to display ead title to user on the form
            try:
                ead = get_findingaid(eadid=request.GET['eadid'], only=['title'])
            except:
                # if retrieval fails, ignore it - not required for the form to work
                pass

        # GET may include eadid & url; use as initial data to populate those fields
        form = FeedbackForm(initial=request.GET)

    captcha_theme = getattr(settings, 'RECAPTCHA_THEME', None)

    return render_to_response('content/feedback.html', {
                'form': form,
                'findingaid': ead,
                'captcha_theme': captcha_theme,
            }, context_instance=RequestContext(request))
Esempio n. 8
0
    def send_email(self):
        '''Send an email based on the posted form data.
        This method should not be called unless the form has validated.

        Sends a "Site Feedback" email to feedback email address configured
        in Django settings with the message submitted on the form.  If email
        and name are specified, they will be used as the From: address in the
        email that is generated; otherwise, the email will be sent from the
        SERVER_EMAIL setting configured in Django settings.

        Returns true when the email send.  Could raise an
        :class:`smtplib.SMTPException` if something goes wrong at the SMTP
        level.
        '''
        # construct a simple text message with the data from the form
        msg_parts = []
        # name & email are optional - repeat in email message if set
        if self.cleaned_data['email'] or self.cleaned_data['name']:
            msg_parts.append('Feedback from %(name)s %(email)s' % self.cleaned_data)
        # eadid & url are optional, hidden - will be set together for single-finding aid feedback
        if self.cleaned_data['eadid'] and self.cleaned_data['url']:
            eadid = self.cleaned_data['eadid']
            ead = get_findingaid(eadid=eadid, only=['title'])
            msg_parts.append("Feedback about %s (%s)\nFrom url: %s" % \
                (unicode(ead.title), eadid, self.cleaned_data['url']))
        # message is required, so should always be set
        msg_parts.append(self.cleaned_data['message'])
        # join together whatever parts of the message should be present
        message = '\n\n'.join(msg_parts)
      
        # send an email with settings comparable to mail_admins or mail_managers
        return send_mail(
            settings.EMAIL_SUBJECT_PREFIX + self.email_subject, # subject
            message,                                           # email text
            generate_from_email(self.cleaned_data),            # from address
            settings.FEEDBACK_EMAIL                           # list of recipient emails
        )
Esempio n. 9
0
def publish(request):
    """
    Admin publication form.  Allows publishing an EAD file by updating or adding
    it to the configured eXist database so it will be immediately visible on
    the public site.  Files can only be published if they pass an EAD sanity check,
    implemented in :meth:`~findingaids.fa_admin.utils.check_ead`.

    On POST, sanity-check the EAD file specified in request from the configured
    and (if it passes all checks), publish it to make it immediately visible on
    the site.  If publish is successful, redirects the user to main admin page
    with a success message that links to the published document on the site.
    If the sanity-check fails, displays a page with any problems found.
    """
    # formerly supported publish from filename, but now only supports
    # publish from preview
    if 'preview_id' not in request.POST:
        messages.error(request, "No preview document specified for publication")
        return HttpResponseSeeOtherRedirect(reverse('fa-admin:index'))

    id = request.POST['preview_id']

    # retrieve info about the document from preview collection
    try:
        # because of the way existdb.query.queryset constructs returns with 'also' fields,
        # it is simpler and better to retrieve document name separately
        ead = get_findingaid(id, preview=True)
        ead_docname = get_findingaid(id, preview=True, only=['document_name'])
        filename = ead_docname.document_name
    except (ExistDBException, Http404):     # not found in exist OR permission denied
        messages.error(request,
            '''Publish failed. Could not retrieve <b>%s</b> from preview collection.
            Please reload and try again.''' % id)

        # if ead could not be retrieved from preview mode, skip processing
        return HttpResponseSeeOtherRedirect(reverse('fa-admin:index'))

    # determine archive this ead is associated with
    archive = None
    if not ead.repository:
        messages.error(request,
            '''Publish failed. Could not determine which archive <b>%s</b> belongs to.
            Please update subarea, reload, and try again.''' % id)
    else:
        archive_name = ead.repository[0]
        # NOTE: EAD supports multiple subarea tags, but in practice we only
        # use one, so it should be safe to assume the first should be used for permissions
        try:
            archive = Archive.objects.get(name=archive_name)
        except ObjectDoesNotExist:
            messages.error(request,
            '''Publish failed. Could not find archive <b>%s</b>.''' % archive_name)

    # bail out if archive could not be identified
    if archive is None:
        return HttpResponseSeeOtherRedirect(reverse('fa-admin:index'))

    # check that user is allowed to publish this document
    if not archive_access(request.user, archive.slug):
        messages.error(request,
            '''You do not have permission to publish <b>%s</b> materials.''' \
            % archive.label)
        return HttpResponseSeeOtherRedirect(reverse('fa-admin:index'))

    errors = []
    try:
        # NOTE: *not* using serialized xml here, because it may introduce
        # whitespace errors not present in the original file.
        ok, response, dbpath, fullpath = _prepublication_check(request, filename, archive)
        if ok is not True:
            # publication check failed - do not publish
            return response

        # only load to exist if there are no errors found
        db = ExistDB()
        # get information to determine if an existing file is being replaced
        replaced = db.describeDocument(dbpath)

        try:
            # move the document from preview collection to configured public collection
            success = db.moveDocument(settings.EXISTDB_PREVIEW_COLLECTION,
                    settings.EXISTDB_ROOT_COLLECTION, filename)
            # FindingAid instance ead already set above
        except ExistDBException, e:
            # special-case error message
            errors.append("Failed to move document %s from preview collection to main collection." \
                            % filename)
            # re-raise and let outer exception handling take care of it
            raise e

    except ExistDBException as err:
        errors.append(err.message())
        success = False

    if success:
        # request the cache to reload the PDF - queue asynchronous task
        result = reload_cached_pdf.delay(ead.eadid.value)
        task = TaskResult(label='PDF reload', object_id=ead.eadid.value,
            url=reverse('fa:findingaid', kwargs={'id': ead.eadid.value}),
            task_id=result.task_id)
        task.save()

        ead_url = reverse('fa:findingaid', kwargs={'id': ead.eadid.value})
        change = "updated" if replaced else "added"
        messages.success(request, 'Successfully %s <b>%s</b>. View <a href="%s">%s</a>.'
                % (change, filename, ead_url, unicode(ead.unittitle)))

        # redirect to main admin page and display messages
        return HttpResponseSeeOtherRedirect(reverse('fa-admin:index'))
    else:
        return render(request, 'fa_admin/publish-errors.html',
            {'errors': errors, 'filename': filename, 'mode': 'publish', 'exception': err})
Esempio n. 10
0
def document_search(request, id):
    "Keyword search on file-level items in a single Finding Aid."

    form = KeywordSearchForm(request.GET)
    query_error = False
    # get the findingaid - will 404 if not found
    # document/collection name required to generate document path
    ead = get_findingaid(id, only=['eadid', 'title', 'repository',
                                   'document_name', 'collection_name'])
    if form.is_valid():
        search_terms = form.cleaned_data['keywords']
        try:
            # do a full-text search at the file level
            # include parent series information and enough ancestor series ids
            # in order to generate link to containing series at any level (c01-c03)

            # use path to restrict query to a single document (much faster)
            path = '%s/%s' % (ead.collection_name, ead.document_name)
            files = FileComponent.objects.filter(document_path=path)

            # at least one of search terms or dao may be present,
            # but both are optional

            # filter by keyword if present
            if search_terms:
                files = files.filter(fulltext_terms=search_terms)

            # restrict to publicly-accessible dao items, if set
            if form.cleaned_data['dao']:
                files = files.filter(did__dao_list__exists=True)
                # if user can view internal daos, no additional filter is needed
                # otherwise, restrict to publicly-accessible dao content
                if not request.user.has_perm('fa_admin.can_view_internal_dao'):
                    files = files.filter(public_dao_count__gte=1)

            files = files.also('parent__id', 'parent__did',
                               'series1__id', 'series1__did', 'series2__id', 'series2__did')

            # if there is a keyword search term, pass on for highlighting
            url_params = ''
            if search_terms:
                url_params = '?' + urlencode({'keywords': search_terms.encode('utf-8')})

            return render(request, 'fa/document_search.html', {
                'files': files,
                'ead': ead,
                # 'querytime': [files.queryTime(), ead.queryTime()],
                'keywords': search_terms,
                'dao': form.cleaned_data['dao'],
                'url_params': url_params,
                'docsearch_form': KeywordSearchForm(),
            })
        except ExistDBTimeout:
            # error for exist db timeout
            messages.error(request, "Your search has resulted in too many hits, \
                please make your terms more specific by using a direct phrase \
                search (e.g. \"Martin Luther King\").")
        except ExistDBException as err:
            # for an invalid full-text query (e.g., missing close quote), eXist
            # error reports 'Cannot parse' and 'Lexical error'
            # NOTE: some duplicate logic from error handling in main keyword search
            query_error = True
            if 'Cannot parse' in err.message():
                messages.error(request,
                               'Your search query could not be parsed. ' +
                               'Please revise your search and try again.')
            else:
                # generic error message for any other exception
                messages.error(request, 'There was an error processing your search.')
    else:
        # invalid form
        messages.error(request, 'Please enter a search term.')
    # display empty search results
    response = render(request, 'fa/document_search.html', {
        'files': [],
        'ead': ead,
        'docsearch_form': KeywordSearchForm(),
    })
     # if query could not be parsed, set a 'Bad Request' status code on the response
    if query_error:
        response.status_code = 400
    return response
Esempio n. 11
0
def publish(request):
    """
    Admin publication form.  Allows publishing an EAD file by updating or adding
    it to the configured eXist database so it will be immediately visible on
    the public site.  Files can only be published if they pass an EAD sanity check,
    implemented in :meth:`~findingaids.fa_admin.utils.check_ead`.

    On POST, sanity-check the EAD file specified in request from the configured
    and (if it passes all checks), publish it to make it immediately visible on
    the site.  If publish is successful, redirects the user to main admin page
    with a success message that links to the published document on the site.
    If the sanity-check fails, displays a page with any problems found.
    """
    # formerly supported publish from filename, but now only supports
    # publish from preview
    if 'preview_id' not in request.POST:
        messages.error(request, "No preview document specified for publication")
        return HttpResponseSeeOtherRedirect(reverse('fa-admin:index'))

    id = request.POST['preview_id']

    # retrieve info about the document from preview collection
    try:
        # because of the way eulcore.existdb.queryset constructs returns with 'also' fields,
        # it is simpler and better to retrieve document name separately
        ead = get_findingaid(id, preview=True)
        ead_docname = get_findingaid(id, preview=True, only=['document_name'])
        filename = ead_docname.document_name
    except Http404:     # not found in exist
        messages.error(request,
            '''Publish failed. Could not retrieve <b>%s</b> from preview collection.
            Please reload and try again.''' % id)

        # if ead could not be retrieved from preview mode, skip processing
        return HttpResponseSeeOtherRedirect(reverse('fa-admin:index'))

    # determine archive this ead is associated with

    xml = ead.serialize()
    archive = None
    if not ead.repository:
        messages.error(request,
            '''Publish failed. Could not determine which archive <b>%s</b> belongs to.
            Please update subarea, reload, and try again.''' % id)
    else:
        archive_name = ead.repository[0]
        # NOTE: EAD supports multiple subarea tags, but in practice we only
        # use one, so it should be safe to assume the first should be used for permissions
        try:
            archive = Archive.objects.get(name=archive_name)
        except ObjectDoesNotExist:
            messages.error(request,
            '''Publish failed. Could not find archive <b>%s</b>.''' % archive_name)

    # bail out if archive could not be identified
    if archive is None:
        return HttpResponseSeeOtherRedirect(reverse('fa-admin:index'))

    # check that user is allowed to publish this document
    if not archive_access(request.user, archive.slug):
        messages.error(request,
            '''You do not have permission to publish <b>%s</b> materials.''' \
            % archive.label)
        return HttpResponseSeeOtherRedirect(reverse('fa-admin:index'))


    errors = []
    try:
        ok, response, dbpath, fullpath = _prepublication_check(request, filename, archive, xml=xml)
        if ok is not True:
            # publication check failed - do not publish
            return response

        # only load to exist if there are no errors found
        db = ExistDB()
        # get information to determine if an existing file is being replaced
        replaced = db.describeDocument(dbpath)

        try:
            # move the document from preview collection to configured public collection
            success = db.moveDocument(settings.EXISTDB_PREVIEW_COLLECTION,
                    settings.EXISTDB_ROOT_COLLECTION, filename)
            # FindingAid instance ead already set above
        except ExistDBException, e:
            # special-case error message
            errors.append("Failed to move document %s from preview collection to main collection." \
                            % filename)
            # re-raise and let outer exception handling take care of it
            raise e

    except ExistDBException, e:
        errors.append(e.message())
        success = False
Esempio n. 12
0
            errors.append(e.message())

        if success:
            # load the file as a FindingAid object so we can generate the preview url
            ead = load_xmlobject_from_file(fullpath, FindingAid)
            messages.success(request, 'Successfully loaded <b>%s</b> for preview.' % filename)
            # redirect to document preview page with code 303 (See Other)
            return HttpResponseSeeOtherRedirect(reverse('fa-admin:preview:findingaid', kwargs={'id': ead.eadid}))
        else:
            return render(request, 'fa_admin/publish-errors.html',
                    {'errors': errors, 'filename': filename, 'mode': 'preview', 'exception': e})

    # TODO: preview list needs to either go away (not currently used)
    # or be filtered by archive
    else:
        fa = get_findingaid(preview=True, only=['eadid', 'list_title', 'last_modified'],
                            order_by='last_modified')
        return render(request, 'fa_admin/preview_list.html',
                {'findingaids': fa, 'querytime': [fa.queryTime()]})


@permission_required_with_403('fa_admin.can_prepare')
@cache_page(1)  # cache this view and use it as source for prep diff/summary views
@user_passes_test_with_ajax(archive_access)
def prepared_eadxml(request, archive, filename):
    """On GET, serves out a prepared version of the EAD file in the specified
    archive subversion directory. Response header is set so the user should
    be prompted to download the xml, with a filename matching that of
    the original document.

    On POST, commits the prepared version of the EAD file to the subversion
    directory of the specified archive, with a log message indicating the user