diff --git a/web/app/corpora/views.py b/web/app/corpora/views.py index 6d78c8b4a7c46c7e83e60630f5ae9da23de42fbe..3f1bb8d8bec472de7ebb9b582c1bcc50df0f5f4e 100644 --- a/web/app/corpora/views.py +++ b/web/app/corpora/views.py @@ -44,7 +44,17 @@ def corpus(corpus_id): corpus = Corpus.query.get_or_404(corpus_id) if not (corpus.creator == current_user or current_user.is_administrator()): abort(403) - return render_template('corpora/corpus.html.j2', corpus=corpus, + corpus_files = [dict(filename=corpus_file.filename, + author=corpus_file.author, + title=corpus_file.title, + publishing_year=corpus_file.publishing_year, + corpus_id=corpus.id, + id=corpus_file.id + ) + for corpus_file in corpus.files] + return render_template('corpora/corpus.html.j2', + corpus=corpus, + corpus_files=corpus_files, title='Corpus') @@ -216,7 +226,7 @@ def prepare_corpus(corpus_id): abort(403) if corpus.files.all(): tasks.build_corpus(corpus_id) - flash('Corpus gets build now.', 'corpus') + flash('Building Corpus...', 'corpus') else: flash('Can not build corpus, please add corpus file(s).', 'corpus') return redirect(url_for('corpora.corpus', corpus_id=corpus_id)) diff --git a/web/app/results/views.py b/web/app/results/views.py index 4b591122b7f378b19f0cfa6705ab1a766935476b..1ea6440782597d4e5ce9c4fbbacb5c0ec089a85a 100644 --- a/web/app/results/views.py +++ b/web/app/results/views.py @@ -6,10 +6,11 @@ from ..models import Result, ResultFile, User from .forms import ImportResultsForm from datetime import datetime from flask import (abort, render_template, current_app, request, redirect, - flash, url_for, make_response) + flash, url_for, make_response, send_from_directory) from flask_login import current_user, login_required import json import os +from .. import logger @results.route('/import_results', methods=['GET', 'POST']) @@ -69,10 +70,12 @@ def results_overview(): ''' # get all results of current user results = User.query.get(current_user.id).results + logger.warning(results) def __p_time(time_str): # helper to convert the datetime into a nice readable string return datetime.strptime(time_str, '%Y-%m-%dT%H:%M:%S.%f') + # convert results into a list of dicts to add the measier to list.js in # the template results = [dict(query=r.corpus_metadata['query'], @@ -81,6 +84,7 @@ def results_overview(): corpus_creation_date=__p_time(r.corpus_metadata['corpus_creation_date']), # noqa corpus_analysis_date=__p_time(r.corpus_metadata['corpus_analysis_date']), # noqa corpus_type=r.corpus_metadata['corpus_type'], + file_id=r.file[0].id, id=r.id) for r in results] return render_template('results/results.html.j2', @@ -140,3 +144,19 @@ def result_delete(result_id): tasks.delete_result(result_id) flash('Result deleted!') return redirect(url_for('results.results_overview')) + + +@results.route('/<int:result_id>/file/<int:result_file_id>/download') +@login_required +def result_download(result_id, result_file_id): + result_file = ResultFile.query.get_or_404(result_file_id) + if not result_file.result_id == result_id: + abort(404) + if not (result_file.result.creator == current_user + or current_user.is_administrator()): + abort(403) + dir = os.path.join(current_app.config['NOPAQUE_STORAGE'], + result_file.dir) + return send_from_directory(as_attachment=True, + directory=dir, + filename=result_file.filename) diff --git a/web/app/static/js/nopaque.lists.js b/web/app/static/js/nopaque.lists.js index a2584e71ca367996a1336ea9ea6e014a203fb9d3..8c464263d884c414519d200b4ebf55e6fe655416 100644 --- a/web/app/static/js/nopaque.lists.js +++ b/web/app/static/js/nopaque.lists.js @@ -1,6 +1,7 @@ class RessourceList extends List { constructor(idOrElement, subscriberList, type, options={}) { - if (!["corpus", "job", "result", "user", "job_input"].includes(type)) { + if (!["corpus", "job", "result", "user", "job_input", + "corpus_file"].includes(type)) { console.error("Unknown Type!"); return; } @@ -78,7 +79,17 @@ RessourceList.dataMapper = { "analyse-link": ["analysing", "prepared", "start analysis"].includes(corpus.status) ? `/corpora/${corpus.id}/analyse` : "", "edit-link": `/corpora/${corpus.id}`, status: corpus.status, - title: corpus.title}), + title: corpus.title + }), + // Mapping for corpus file entities shown in the corpus overview + corpus_file: corpus_file => ({filename: corpus_file.filename, + author: corpus_file.author, + title: corpus_file.title, + publishing_year: corpus_file.publishing_year, + "edit-link": `${corpus_file.corpus_id}/files/${corpus_file.id}/edit`, + "download-link": `${corpus_file.corpus_id}/files/${corpus_file.id}/download`, + "delete-modal": `delete-corpus-file-${corpus_file.id}-modal` + }), // Mapping for job entities shown in the dashboard table. job: job => ({creation_date: job.creation_date, description: job.description, @@ -86,11 +97,13 @@ RessourceList.dataMapper = { link: `/jobs/${job.id}`, service: job.service, status: job.status, - title: job.title}), + title: job.title + }), // Mapping for job input files shown in table on every job page job_input: job_input => ({filename: job_input.filename, id: job_input.job_id, - "download-link": `${job_input.job_id}/inputs/${job_input.id}/download`}), + "download-link": `${job_input.job_id}/inputs/${job_input.id}/download` + }), // Mapping for imported result entities from corpus analysis. // Shown in imported results table result: result => ({ query: result.query, @@ -101,14 +114,17 @@ RessourceList.dataMapper = { corpus_type : result.corpus_type, "details-link": `${result.id}/details`, "inspect-link": `${result.id}/inspect`, - "delete-modal": `delete-result-${result.id}-modal`}), + "download-link": `${result.id}/file/${result.file_id}/download`, + "delete-modal": `delete-result-${result.id}-modal` + }), // Mapping for user entities shown in admin table user: user => ({username: user.username, email: user.email, role_id: user.role_id, confirmed: user.confirmed, id: user.id, - "profile-link": `user/${user.id}`}) + "profile-link": `user/${user.id}` + }) }; @@ -146,12 +162,12 @@ RessourceList.options = { <span class="badge new status" data-badge-caption=""> </span> </td> - <td class="right-align"> - <a class="btn-floating edit-link waves-effect waves-light"> + <td class="actions center-align"> + <a class="btn-floating edit-link waves-effect waves-light" data-tooltip="Edit"> <i class="material-icons">edit</i> </a> <a class="btn-floating analyse-link waves-effect waves-light"> - <i class="material-icons right">search</i> + <i class="material-icons">search</i> </a> </td> </tr>`, @@ -165,6 +181,42 @@ RessourceList.options = { {name: "edit-link", attr: "href"}, {name: "status", attr: "data-status"}] }, + // Corpus file entity blueprint setting html strucuture per entity per row + // Link classes have to correspond with Links defined in the Mapping process + corpus_file: {item: `<tr> + <td class="filename" style="word-break: break-word;"></td> + <td class="author" style="word-break: break-word;"></td> + <td class="title" style="word-break: break-word;"></td> + <td class="publishing_year" style="word-break: break-word;"></td> + <td class="actions center-align"> + <a class="btn-floating tooltipped edit-link + waves-effect waves-light" + data-position="top" + data-tooltip="Edit"> + <i class="material-icons">edit</i> + </a> + <a class="btn-floating tooltipped download-link + waves-effect waves-light" + data-position="top" + data-tooltip="Download"> + <i class="material-icons">file_download</i> + </a> + <a class="btn-floating tooltipped modal-trigger red + waves-effect waves-light delete-modal" + data-position="top" + data-tooltip="Delete"> + <i class="material-icons">delete</i> + </a> + </td> + </tr>`, + valueNames: ["filename", + "author", + "title", + "publishing_year", + {name: "edit-link", attr: "href"}, + {name: "download-link", attr: "href"}, + {name: "delete-modal", attr: "data-target"}] + }, // Job entity blueprint setting html strucuture per entity per row // Link classes have to correspond with Links defined in the Mapping process job: {item: `<tr> @@ -180,9 +232,9 @@ RessourceList.options = { <td> <span class="badge new status" data-badge-caption=""></span> </td> - <td class="right-align"> + <td class="actions center-align"> <a class="btn-floating link waves-effect waves-light"> - <i class="material-icons right">send</i> + <i class="material-icons">send</i> </a> </td> </tr>`, @@ -198,8 +250,12 @@ RessourceList.options = { }, job_input: {item : `<tr> <td class="filename"></td> - <td class="actions"> - <a class="btn-floating download-link waves-effect waves-light"><i class="material-icons">file_download</i> + <td class="actions center-align"> + <a class="btn-floating tooltipped download-link + waves-effect waves-light" + data-position="top" + data-tooltip="Download"> + <i class="material-icons">file_download</i> </a> </td> </tr>`, @@ -217,12 +273,30 @@ RessourceList.options = { <td class="corpus_creation_date"></td> <td class="corpus_analysis_date"></td> <td class="corpus_type"></td> - <td class="actions right-align"> - <a class="btn-floating details-link waves-effect waves-light"><i class="material-icons">info_outline</i> + <td class="actions center-align"> + <a class="btn-floating tooltipped details-link + waves-effect waves-light" + data-position="top" + data-tooltip="Metadata Info"> + <i class="material-icons">info_outline</i> + </a> + <a class="btn-floating tooltipped inspect-link + waves-effect waves-light" + data-position="top" + data-tooltip="View Results"> + <i class="material-icons">search</i> </a> - <a class="btn-floating inspect-link waves-effect waves-light"><i class="material-icons">search</i> + <a class="btn-floating tooltipped download-link + waves-effect waves-light" + data-position="top" + data-tooltip="Download"> + <i class="material-icons">file_download</i> </a> - <a class="btn-floating red delete-modal waves-effect waves-light modal-trigger"><i class="material-icons">delete</i> + <a class="btn-floating tooltipped red delete-modal + waves-effect waves-light modal-trigger" + data-position="top" + data-tooltip="Delete"> + <i class="material-icons">delete</i> </a> </td> </tr>`, @@ -236,6 +310,7 @@ RessourceList.options = { "corpus_type", {name: "details-link", attr: "href"}, {name: "inspect-link", attr: "href"}, + {name: "download-link", attr: "href"}, {name: "delete-modal", attr: "data-target"}] }, // User entity blueprint setting html strucuture per entity per row @@ -246,8 +321,12 @@ RessourceList.options = { <td class="role_id"></td> <td class="confirmed"></td> <td class="id"></td> - <td class="actions"> - <a class="btn-floating profile-link waves-effect waves-light"><i class="material-icons">edit</i> + <td class="actions center-align"> + <a class="btn-floating tooltipped profile-link waves-effect + waves-light" + data-position="top" + data-tooltip="Edit User"> + <i class="material-icons">edit</i> </a> </td> </tr>`, diff --git a/web/app/templates/corpora/corpus.html.j2 b/web/app/templates/corpora/corpus.html.j2 index 0a0a8c76ec6ef01d38354def5a94ff979bfac4d4..fdbe2b8b896185172eec4b269ab8a2c7b6151ad4 100644 --- a/web/app/templates/corpora/corpus.html.j2 +++ b/web/app/templates/corpora/corpus.html.j2 @@ -59,41 +59,34 @@ <div class="col s12"> <div class="card"> - <div class="card-content" style="overflow: hidden;"> + <div class="card-content" id="corpus-files" style="overflow: hidden;"> <span class="card-title">Files</span> - + <div class="input-field"> + <i class="material-icons prefix">search</i> + <input id="search-results" class="search" type="search"></input> + <label for="search-results">Search results</label> + </div> + <ul class="pagination paginationTop"></ul> <table class="highlight responsive-table"> <thead> <tr> - <th>Filename</th> - <th>Author</th> - <th>Title</th> - <th>Publishing year</th> - <th></th> + <th class="sort" data-sort="filename">Filename</th> + <th class="sort" data-sort="author">Author</th> + <th class="sort" data-sort="title">Title</th> + <th class="sort" data-sort="publishing_year">Publishing year</th> + <th>{# Actions #}</th> </tr> </thead> - <tbody> + <tbody class="list"> <tr class="show-if-only-child"> <td colspan="5"> <span class="card-title"><i class="material-icons left">book</i>Nothing here...</span> <p>Corpus is empty. Add texts using the option below.</p> </td> </tr> - {% for file in corpus.files %} - <tr> - <td style="word-break: break-word;">{{ file.filename }}</td> - <td style="word-break: break-word;">{{ file.author }}</td> - <td style="word-break: break-word;">{{ file.title }}</td> - <td>{{ file.publishing_year }}</td> - <td class="right-align"> - <a class="btn-floating waves-effect waves-light" href="{{ url_for('corpora.edit_corpus_file', corpus_file_id=file.id, corpus_id=corpus.id) }}"><i class="material-icons">edit</i></a> - <a class="btn-floating waves-effect waves-light" href="{{ url_for('corpora.download_corpus_file', corpus_file_id=file.id, corpus_id=corpus.id) }}"><i class="material-icons">file_download</i></a> - <a data-target="delete-corpus-file-{{ file.id }}-modal" class="btn-floating modal-trigger red waves-effect waves-light"><i class="material-icons">delete</i></a> - </td> - </tr> - {% endfor %} </tbody> </table> + <ul class="pagination paginationBottom"></ul> </div> <div class="card-action right-align"> <a href="{{ url_for('corpora.add_corpus_file', corpus_id=corpus.id) }}" class="btn waves-effect waves-light"><i class="material-icons left">add</i>Add corpus file</a> @@ -129,6 +122,11 @@ <script> + // create corpus file table + var ressources = {{ corpus_files|tojson|safe }}; + var corpusFilesList = new RessourceList("corpus-files", null, "corpus_file"); + corpusFilesList.addRessources(ressources); + class InformationUpdater { constructor(corpusId, foreignCorpusFlag) { this.corpusId = corpusId; diff --git a/web/app/templates/jobs/job.html.j2 b/web/app/templates/jobs/job.html.j2 index b264cb5236f83835f48661a46c43e7e853c4ac8f..50d8798038bad98c585d34de5de6fb2e83ee2df8 100644 --- a/web/app/templates/jobs/job.html.j2 +++ b/web/app/templates/jobs/job.html.j2 @@ -120,7 +120,7 @@ <tr> <th>Result Type</th> <th>Archive Name</th> - <th>Download</th> + <th>{# Actions #}</th> </tr> </thead> <tbody class="results"> @@ -159,7 +159,6 @@ <script> // job_input_table code var ressources = {{ job_inputs|tojson|safe }}; - console.log(ressources); var jobInputsList = new RessourceList("inputs", null, "job_input"); jobInputsList.addRessources(ressources); @@ -244,9 +243,12 @@ <tr> <td>${resultType}</td> <td>${result.filename}</td> - <td> - <a class="btn-floating waves-effect waves-light" download href="/jobs/${result.job_id}/results/${result.id}/download"> - <i class="material-icons left">file_download</i> + <td class="center-align"> + <a class="btn-floating tooltipped waves-effect waves-light" + download href="/jobs/${result.job_id}/results/${result.id}/download" + data-position="top" + data-tooltip="Download"> + <i class="material-icons">file_download</i> </a> </td> </tr> diff --git a/web/app/templates/macros/materialize.html.j2 b/web/app/templates/macros/materialize.html.j2 index 3ba6b85549c1ed70a659899789243044f5ee891e..a20006d31187f7d4670d969fabbea3bec019c102 100644 --- a/web/app/templates/macros/materialize.html.j2 +++ b/web/app/templates/macros/materialize.html.j2 @@ -85,7 +85,8 @@ </button> {% endmacro %} -{% macro delete_modal_file(file) %} +{# maybe create a modal template later on for actions like delete etc #} +{# {% macro delete_modal_file(file) %} <div id="delete-corpus-file-{{ file.id }}-modal" class="modal"> <div class="modal-content"> <h4>Confirm corpus file deletion</h4> @@ -96,4 +97,4 @@ <a class="btn modal-close red waves-effect waves-light" href="{{ url_for('corpora.delete_corpus_file', corpus_file_id=resource_id, corpus_id=corpus.id) }}"><i class="material-icons left">delete</i>Delete</a> </div> </div> -{% endmacro %} +{% endmacro %} #} diff --git a/web/requirements.txt b/web/requirements.txt index c916b22ba87359b7f9f66fed990dd2e1d301ffe9..eb572e0b5005aad4ab13d3e825aadb6f9fc4947c 100644 --- a/web/requirements.txt +++ b/web/requirements.txt @@ -8,7 +8,6 @@ Flask-Migrate Flask-Paranoid Flask-SocketIO Flask-SQLAlchemy -Flask-Table Flask-WTF jsonpatch psycopg2