mirror of
				https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque.git
				synced 2025-10-31 02:32:45 +00:00 
			
		
		
		
	Merge branch 'development' of gitlab.ub.uni-bielefeld.de:sfb1288inf/nopaque into development
This commit is contained in:
		| @@ -52,6 +52,10 @@ def create_app(config_name): | ||||
|     from .profile import profile as profile_blueprint | ||||
|     app.register_blueprint(profile_blueprint, url_prefix='/profile') | ||||
|  | ||||
|     from .query_results import query_results as query_results_blueprint | ||||
|     app.register_blueprint(query_results_blueprint, | ||||
|                            url_prefix='/query_results') | ||||
|  | ||||
|     from .services import services as services_blueprint | ||||
|     app.register_blueprint(services_blueprint, url_prefix='/services') | ||||
|  | ||||
|   | ||||
| @@ -135,6 +135,10 @@ class User(UserMixin, db.Model): | ||||
|                            cascade='save-update, merge, delete') | ||||
|     results = db.relationship('Result', backref='creator', lazy='dynamic', | ||||
|                               cascade='save-update, merge, delete') | ||||
|     query_results = db.relationship('QueryResult', | ||||
|                                     backref='creator', | ||||
|                                     cascade='save-update, merge, delete', | ||||
|                                     lazy='dynamic') | ||||
|  | ||||
|     def to_dict(self): | ||||
|         return {'id': self.id, | ||||
| @@ -151,7 +155,9 @@ class User(UserMixin, db.Model): | ||||
|                                  self.setting_job_status_site_notifications}, | ||||
|                 'corpora': {corpus.id: corpus.to_dict() | ||||
|                             for corpus in self.corpora}, | ||||
|                 'jobs': {job.id: job.to_dict() for job in self.jobs}} | ||||
|                 'jobs': {job.id: job.to_dict() for job in self.jobs}, | ||||
|                 'query_results': {query_result.id: query_result.to_dict() | ||||
|                                   for query_result in self.query_results}} | ||||
|  | ||||
|     def __repr__(self): | ||||
|         ''' | ||||
| @@ -616,6 +622,43 @@ class Corpus(db.Model): | ||||
|         return '<Corpus {corpus_title}>'.format(corpus_title=self.title) | ||||
|  | ||||
|  | ||||
| class QueryResult(db.Model): | ||||
|     ''' | ||||
|     Class to define a corpus analysis result. | ||||
|     ''' | ||||
|     __tablename__ = 'query_results' | ||||
|     # Primary key | ||||
|     id = db.Column(db.Integer, primary_key=True) | ||||
|     # Foreign keys | ||||
|     user_id = db.Column(db.Integer, db.ForeignKey('users.id')) | ||||
|     # Fields | ||||
|     description = db.Column(db.String(255)) | ||||
|     filename = db.Column(db.String(255)) | ||||
|     query_metadata = db.Column(db.JSON()) | ||||
|     title = db.Column(db.String(32)) | ||||
|  | ||||
|     def delete(self): | ||||
|         query_result_dir = os.path.join(current_app.config['NOPAQUE_STORAGE'], | ||||
|                                         str(self.user_id), | ||||
|                                         'query_results', | ||||
|                                         str(self.id)) | ||||
|         shutil.rmtree(query_result_dir, ignore_errors=True) | ||||
|         db.session.delete(self) | ||||
|  | ||||
|     def to_dict(self): | ||||
|         return {'id': self.id, | ||||
|                 'user_id': self.user_id, | ||||
|                 'description': self.description, | ||||
|                 'filename': self.filename, | ||||
|                 'title': self.title} | ||||
|  | ||||
|     def __repr__(self): | ||||
|         ''' | ||||
|         String representation of the CorpusAnalysisResult. For human readability. | ||||
|         ''' | ||||
|         return '<QueryResult {}>'.format(self.title) | ||||
|  | ||||
|  | ||||
| class Result(db.Model): | ||||
|     ''' | ||||
|     Class to define a result set of one query. | ||||
|   | ||||
							
								
								
									
										5
									
								
								web/app/query_results/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								web/app/query_results/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| from flask import Blueprint | ||||
|  | ||||
|  | ||||
| query_results = Blueprint('query_results', __name__) | ||||
| from . import views  # noqa | ||||
							
								
								
									
										21
									
								
								web/app/query_results/forms.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								web/app/query_results/forms.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| from flask_wtf import FlaskForm | ||||
| from werkzeug.utils import secure_filename | ||||
| from wtforms import FileField, StringField, SubmitField, ValidationError | ||||
| from wtforms.validators import DataRequired, Length | ||||
|  | ||||
|  | ||||
| class AddQueryResultForm(FlaskForm): | ||||
|     ''' | ||||
|     Form used to import one result json file. | ||||
|     ''' | ||||
|     description = StringField('Description', | ||||
|                               validators=[DataRequired(), Length(1, 255)]) | ||||
|     file = FileField('File', validators=[DataRequired()]) | ||||
|     title = StringField('Title', validators=[DataRequired(), Length(1, 32)]) | ||||
|     submit = SubmitField() | ||||
|  | ||||
|     def validate_file(self, field): | ||||
|         if not field.data.filename.lower().endswith('.json'): | ||||
|             raise ValidationError('File does not have an approved extension: ' | ||||
|                                   '.json') | ||||
|         field.data.filename = secure_filename(field.data.filename) | ||||
							
								
								
									
										13
									
								
								web/app/query_results/tasks.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								web/app/query_results/tasks.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| from .. import db | ||||
| from ..decorators import background | ||||
| from ..models import QueryResult | ||||
|  | ||||
|  | ||||
| @background | ||||
| def delete_query_result(query_result_id, *args, **kwargs): | ||||
|     with kwargs['app'].app_context(): | ||||
|         query_result = QueryResult.query.get(query_result_id) | ||||
|         if query_result is None: | ||||
|             raise Exception('QueryResult {} not found'.format(query_result_id)) | ||||
|         query_result.delete() | ||||
|         db.session.commit() | ||||
							
								
								
									
										144
									
								
								web/app/query_results/views.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								web/app/query_results/views.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,144 @@ | ||||
| from . import query_results | ||||
| from . import tasks | ||||
| from .. import db | ||||
| from ..corpora.forms import DisplayOptionsForm | ||||
| from ..models import QueryResult | ||||
| from .forms import AddQueryResultForm | ||||
| from flask import (abort, current_app, flash, make_response, redirect, | ||||
|                    render_template, request, send_from_directory, url_for) | ||||
| from flask_login import current_user, login_required | ||||
| import json | ||||
| import os | ||||
| from jsonschema import validate | ||||
|  | ||||
|  | ||||
| @query_results.route('/add', methods=['GET', 'POST']) | ||||
| @login_required | ||||
| def add_query_result(): | ||||
|     ''' | ||||
|     View to import a result as a json file. | ||||
|     ''' | ||||
|     add_query_result_form = AddQueryResultForm(prefix='add-query-result-form') | ||||
|     if add_query_result_form.is_submitted(): | ||||
|         if not add_query_result_form.validate(): | ||||
|             return make_response(add_query_result_form.errors, 400) | ||||
|         query_result = QueryResult( | ||||
|             creator=current_user, | ||||
|             description=add_query_result_form.description.data, | ||||
|             filename=add_query_result_form.file.data.filename, | ||||
|             title=add_query_result_form.title.data | ||||
|         ) | ||||
|         db.session.add(query_result) | ||||
|         db.session.commit() | ||||
|         # create paths to save the uploaded json file | ||||
|         query_result_dir = os.path.join(current_app.config['NOPAQUE_STORAGE'], | ||||
|                                         str(current_user.id), | ||||
|                                         'query_results', | ||||
|                                         str(query_result.id)) | ||||
|         try: | ||||
|             os.makedirs(query_result_dir) | ||||
|         except Exception: | ||||
|             db.session.delete(query_result) | ||||
|             db.session.commit() | ||||
|             flash('Internal Server Error', 'error') | ||||
|             redirect_url = url_for('query_results.add_query_result') | ||||
|             return make_response({'redirect_url': redirect_url}, 500) | ||||
|         # save the uploaded file | ||||
|         query_result_file_path = os.path.join(query_result_dir, | ||||
|                                               query_result.filename) | ||||
|         add_query_result_form.file.data.save(query_result_file_path) | ||||
|         # parse json from file | ||||
|         with open(query_result_file_path, 'r') as file: | ||||
|             query_result_file_content = json.load(file) | ||||
|         # parse json schema | ||||
|         with open('app/static/json_schema/nopaque_cqi_py_results_schema.json', 'r') as file:  # noqa | ||||
|             schema = json.load(file) | ||||
|         try: | ||||
|             # validate imported json file | ||||
|             validate(instance=query_result_file_content, schema=schema) | ||||
|         except Exception: | ||||
|             tasks.delete_query_result(query_result.id) | ||||
|             flash('Uploaded file is invalid', 'result') | ||||
|             redirect_url = url_for('query_results.add_query_result') | ||||
|             return make_response({'redirect_url': redirect_url}, 201) | ||||
|         query_result_file_content.pop('matches') | ||||
|         query_result_file_content.pop('cpos_lookup') | ||||
|         query_result.query_metadata = query_result_file_content | ||||
|         db.session.commit() | ||||
|         flash('Query result added!', 'result') | ||||
|         redirect_url = url_for('query_results.query_result', | ||||
|                                query_result_id=query_result.id) | ||||
|         return make_response({'redirect_url': redirect_url}, 201) | ||||
|     return render_template('query_results/add_query_result.html.j2', | ||||
|                            add_query_result_form=add_query_result_form, | ||||
|                            title='Add query result') | ||||
|  | ||||
|  | ||||
| @query_results.route('/<int:query_result_id>') | ||||
| @login_required | ||||
| def query_result(query_result_id): | ||||
|     query_result = QueryResult.query.get_or_404(query_result_id) | ||||
|     if not (query_result.creator == current_user | ||||
|             or current_user.is_administrator()): | ||||
|         abort(403) | ||||
|     return render_template('query_results/query_result.html.j2', | ||||
|                            query_result=query_result, | ||||
|                            title='Query result') | ||||
|  | ||||
|  | ||||
| @query_results.route('/<int:query_result_id>/inspect') | ||||
| @login_required | ||||
| def inspect_query_result(query_result_id): | ||||
|     ''' | ||||
|     View to inspect one importe result file in a corpus analysis like interface | ||||
|     ''' | ||||
|     query_result = QueryResult.query.get_or_404(query_result_id) | ||||
|     if not (query_result.creator == current_user | ||||
|             or current_user.is_administrator()): | ||||
|         abort(403) | ||||
|     display_options_form = DisplayOptionsForm( | ||||
|         prefix='display-options-form', | ||||
|         results_per_page=request.args.get('results_per_page', 30), | ||||
|         result_context=request.args.get('context', 20) | ||||
|     ) | ||||
|     query_result_file_path = os.path.join( | ||||
|         current_app.config['NOPAQUE_STORAGE'], | ||||
|         str(current_user.id), | ||||
|         'query_results', | ||||
|         str(query_result.id), | ||||
|         query_result.filename | ||||
|     ) | ||||
|     with open(query_result_file_path, 'r') as query_result_file: | ||||
|         query_result_content = json.load(query_result_file) | ||||
|     return render_template('query_results/inspect_query_result.html.j2', | ||||
|                            display_options_form=display_options_form, | ||||
|                            query_result_content=query_result_content, | ||||
|                            title='Inspect query result') | ||||
|  | ||||
|  | ||||
| @query_results.route('/<int:query_result_id>/delete') | ||||
| @login_required | ||||
| def delete_query_result(query_result_id): | ||||
|     query_result = QueryResult.query.get_or_404(query_result_id) | ||||
|     if not (query_result.creator == current_user | ||||
|             or current_user.is_administrator()): | ||||
|         abort(403) | ||||
|     tasks.delete_result(query_result_id) | ||||
|     flash('Query result deleted!', 'result') | ||||
|     return redirect(url_for('main.dashboard')) | ||||
|  | ||||
|  | ||||
| @query_results.route('/<int:query_result_id>/download') | ||||
| @login_required | ||||
| def download_query_result(query_result_id): | ||||
|     query_result = QueryResult.query.get_or_404(query_result_id) | ||||
|     if not (query_result.creator == current_user | ||||
|             or current_user.is_administrator()): | ||||
|         abort(403) | ||||
|     query_result_dir = os.path.join(current_app.config['NOPAQUE_STORAGE'], | ||||
|                                     str(current_user.id), | ||||
|                                     'query_results', | ||||
|                                     str(query_result.id)) | ||||
|     return send_from_directory(as_attachment=True, | ||||
|                                directory=query_result_dir, | ||||
|                                filename=query_result.filename) | ||||
| @@ -14,6 +14,7 @@ nopaque.user.settings = {}; | ||||
| nopaque.user.settings.darkMode = undefined; | ||||
| nopaque.corporaSubscribers = []; | ||||
| nopaque.jobsSubscribers = []; | ||||
| nopaque.queryResultsSubscribers = []; | ||||
|  | ||||
| // Foreign user (user inspected with admin credentials) data | ||||
| nopaque.foreignUser = {}; | ||||
| @@ -22,6 +23,7 @@ nopaque.foreignUser.settings = {}; | ||||
| nopaque.foreignUser.settings.darkMode = undefined; | ||||
| nopaque.foreignCorporaSubscribers = []; | ||||
| nopaque.foreignJobsSubscribers = []; | ||||
| nopaque.foreignQueryResultsSubscribers = []; | ||||
|  | ||||
| nopaque.flashedMessages = undefined; | ||||
|  | ||||
| @@ -38,6 +40,9 @@ nopaque.socket.init = function() { | ||||
|     for (let subscriber of nopaque.jobsSubscribers) { | ||||
|       subscriber._init(nopaque.user.jobs); | ||||
|     } | ||||
|     for (let subscriber of nopaque.queryResultsSubscribers) { | ||||
|       subscriber._init(nopaque.user.query_results); | ||||
|     } | ||||
|     RessourceList.modifyTooltips(false) | ||||
|   }); | ||||
|  | ||||
| @@ -48,12 +53,16 @@ nopaque.socket.init = function() { | ||||
|     nopaque.user = jsonpatch.apply_patch(nopaque.user, patch); | ||||
|     corpora_patch = patch.filter(operation => operation.path.startsWith("/corpora")); | ||||
|     jobs_patch = patch.filter(operation => operation.path.startsWith("/jobs")); | ||||
|     query_results_patch = patch.filter(operation => operation.path.startsWith("/query_results")); | ||||
|     for (let subscriber of nopaque.corporaSubscribers) { | ||||
|       subscriber._update(corpora_patch); | ||||
|     } | ||||
|     for (let subscriber of nopaque.jobsSubscribers) { | ||||
|       subscriber._update(jobs_patch); | ||||
|     } | ||||
|     for (let subscriber of nopaque.queryResultsSubscribers) { | ||||
|       subscriber._update(query_results_patch); | ||||
|     } | ||||
|     if (["all", "end"].includes(nopaque.user.settings.job_status_site_notifications)) { | ||||
|       for (operation of jobs_patch) { | ||||
|         /* "/jobs/{jobId}/..." -> ["{jobId}", ...] */ | ||||
| @@ -74,6 +83,9 @@ nopaque.socket.init = function() { | ||||
|     for (let subscriber of nopaque.foreignJobsSubscribers) { | ||||
|       subscriber._init(nopaque.foreignUser.jobs); | ||||
|     } | ||||
|     for (let subscriber of nopaque.foreignQueryResultsSubscribers) { | ||||
|       subscriber._init(nopaque.foreignUser.query_results); | ||||
|     } | ||||
|     RessourceList.modifyTooltips(false) | ||||
|   }); | ||||
|  | ||||
| @@ -84,8 +96,10 @@ nopaque.socket.init = function() { | ||||
|     nopaque.foreignUser = jsonpatch.apply_patch(nopaque.foreignUser, patch); | ||||
|     corpora_patch = patch.filter(operation => operation.path.startsWith("/corpora")); | ||||
|     jobs_patch = patch.filter(operation => operation.path.startsWith("/jobs")); | ||||
|     query_results_patch = patch.filter(operation => operation.path.startsWith("/query_results")); | ||||
|     for (let subscriber of nopaque.foreignCorporaSubscribers) {subscriber._update(corpora_patch);} | ||||
|     for (let subscriber of nopaque.foreignJobsSubscribers) {subscriber._update(jobs_patch);} | ||||
|     for (let subscriber of nopaque.foreignQueryResultsSubscribers) {subscriber._update(query_results_patch);} | ||||
|   }); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,16 +1,15 @@ | ||||
| class RessourceList extends List { | ||||
|   constructor(idOrElement, subscriberList, type, options={}) { | ||||
|     if (!["corpus", "job", "result", "user", "job_input", | ||||
|           "corpus_file"].includes(type)) { | ||||
|     if (!["corpus", "corpus_file", "job", "job_input", "query_result", "result", "user"].includes(type)) { | ||||
|       console.error("Unknown Type!"); | ||||
|       return; | ||||
|     } | ||||
|     if (subscriberList) { | ||||
|     super(idOrElement, {...RessourceList.options['common'], | ||||
|                         ...RessourceList.options[type], | ||||
|                         ...options}); | ||||
|     this.type = type; | ||||
|     subscriberList.push(this); | ||||
|       super(idOrElement, {...RessourceList.options['common'], | ||||
|                           ...RessourceList.options[type], | ||||
|                           ...options}); | ||||
|       this.type = type; | ||||
|       subscriberList.push(this); | ||||
|     } else { | ||||
|       super(idOrElement, {...RessourceList.options['extended'], | ||||
|                           ...RessourceList.options[type], | ||||
| @@ -81,8 +80,7 @@ class RessourceList extends List { | ||||
|  | ||||
|  | ||||
| RessourceList.dataMapper = { | ||||
|   // ### Mapping Genera Info | ||||
|   //The Mapping describes entitys rendered per row. One key value pair holds | ||||
|   // A data mapper describes entitys rendered per row. One key value pair holds | ||||
|   // the data to be rendered in the list.js table. Key has to correspond | ||||
|   // with the ValueNames defined below in RessourceList.options ValueNames. | ||||
|   // Links are declared with double ticks(") around them. The key for links | ||||
| @@ -96,8 +94,7 @@ 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, | ||||
| @@ -105,8 +102,7 @@ RessourceList.dataMapper = { | ||||
|                                 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` | ||||
|                               }), | ||||
|                                 "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, | ||||
| @@ -114,34 +110,34 @@ 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, | ||||
|                        match_count: result.match_count, | ||||
|                        corpus_name: result.corpus_name, | ||||
|                        corpus_creation_date: result.corpus_creation_date, | ||||
|                        corpus_analysis_date: result.corpus_analysis_date, | ||||
|                        corpus_type : result.corpus_type, | ||||
|                        "details-link": `${result.id}/details`, | ||||
|                        "inspect-link": `${result.id}/inspect`, | ||||
|                        "download-link": `${result.id}/file/${result.file_id}/download`, | ||||
|                        "delete-modal": `delete-result-${result.id}-modal` | ||||
|                      }), | ||||
|   query_result: query_result => ({description: query_result.description, | ||||
|                                   id: query_result.id, | ||||
|                                   link: `/query_results/${query_result.id}`, | ||||
|                                   title: query_result.title}), | ||||
|   result: result => ({query: result.query, | ||||
|                       match_count: result.match_count, | ||||
|                       corpus_name: result.corpus_name, | ||||
|                       corpus_creation_date: result.corpus_creation_date, | ||||
|                       corpus_analysis_date: result.corpus_analysis_date, | ||||
|                       corpus_type : result.corpus_type, | ||||
|                       "details-link": `${result.id}/details`, | ||||
|                       "inspect-link": `${result.id}/inspect`, | ||||
|                       "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}`}) | ||||
| }; | ||||
|  | ||||
|  | ||||
| @@ -289,6 +285,32 @@ RessourceList.options = { | ||||
|                            "id", | ||||
|                            {name: "download-link", attr: "href"}] | ||||
|               }, | ||||
|   query_result: {item: `<tr> | ||||
|                           <td> | ||||
|                             <a class="btn-floating disabled"> | ||||
|                               <i class="material-icons service">book</i> | ||||
|                             </a> | ||||
|                           </td> | ||||
|                           <td> | ||||
|                             <b class="title"></b><br> | ||||
|                             <i class="description"></i> | ||||
|                           </td> | ||||
|                           <td class="actions right-align"> | ||||
|                             <a class="btn-floating tooltipped link waves-effect | ||||
|                                       waves-light" | ||||
|                                data-position="top" | ||||
|                                data-tooltip="Go to query result"> | ||||
|                              <i class="material-icons">send</i> | ||||
|                            </a> | ||||
|                           </td> | ||||
|                         </tr>`, | ||||
|   // Job Value Names per column. Have to correspond with the keys from the | ||||
|   // Mapping step above. | ||||
|         valueNames: ["description", | ||||
|                      "title", | ||||
|                      {data: ["id"]}, | ||||
|                      {name: "link", attr: "href"}] | ||||
|         }, | ||||
|   // Result (imported from corpus analysis) entity blueprint setting html | ||||
|   // strucuture per entity per row | ||||
|   // Link classes have to correspond with Links defined in the Mapping process | ||||
|   | ||||
							
								
								
									
										44
									
								
								web/app/templates/query_results/add_query_result.html.j2
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								web/app/templates/query_results/add_query_result.html.j2
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| {% extends "nopaque.html.j2" %} | ||||
|  | ||||
| {% block page_content %} | ||||
| <div class="col s12 m4"> | ||||
|   <p>Fill out the following form to upload and view your exported query data from the corpus analsis.</p> | ||||
|   <a class="waves-effect waves-light btn" href="{{ url_for('main.dashboard') }}"><i class="material-icons left">arrow_back</i>Back to dashboard</a> | ||||
| </div> | ||||
|  | ||||
| <div class="col s12 m8"> | ||||
|   <form class="nopaque-submit-form" data-progress-modal="progress-modal"> | ||||
|     <div class="card"> | ||||
|       <div class="card-content"> | ||||
|         {{ add_query_result_form.hidden_tag() }} | ||||
|         <div class="row"> | ||||
|           <div class="col s12 m4"> | ||||
|             {{ M.render_field(add_query_result_form.title, data_length='32', material_icon='title') }} | ||||
|           </div> | ||||
|           <div class="col s12 m8"> | ||||
|             {{ M.render_field(add_query_result_form.description, data_length='255', material_icon='description') }} | ||||
|           </div> | ||||
|           <div class="col s12"> | ||||
|             {{ M.render_field(add_query_result_form.file, accept='.json', placeholder='Choose your .json file') }} | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="card-action right-align"> | ||||
|         {{ M.render_field(add_query_result_form.submit, material_icon='send') }} | ||||
|       </div> | ||||
|     </div> | ||||
|   </form> | ||||
| </div> | ||||
|  | ||||
| <div id="progress-modal" class="modal"> | ||||
|   <div class="modal-content"> | ||||
|     <h4><i class="material-icons prefix">file_upload</i> Uploading file...</h4> | ||||
|     <div class="progress"> | ||||
|       <div class="determinate" style="width: 0%"></div> | ||||
|     </div> | ||||
|   </div> | ||||
|   <div class="modal-footer"> | ||||
|     <a href="#!" class="modal-close waves-effect waves-light btn red abort-request">Cancel</a> | ||||
|   </div> | ||||
| </div> | ||||
| {% endblock %} | ||||
							
								
								
									
										211
									
								
								web/app/templates/query_results/inspect_query_result.html.j2
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										211
									
								
								web/app/templates/query_results/inspect_query_result.html.j2
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,211 @@ | ||||
| {% extends "nopaque.html.j2" %} | ||||
|  | ||||
| {% set headline = ' ' %} | ||||
|  | ||||
| {% set full_width = True %} | ||||
|  | ||||
| {% block page_content %} | ||||
| <div class="col s12" id="query-display"> | ||||
|   <div class="card"> | ||||
|     <div class="card-content" id="result-list" style="overflow: hidden;"> | ||||
|       <div class="row" style="margin-bottom: 0px;"> | ||||
|         <div class="col s12 m3 l3" id="results-info"> | ||||
|           <div class="row section"> | ||||
|             <h6 style="margin-top: 0px;">Infos</h6> | ||||
|             <div class="divider" style="margin-bottom: 10px;"></div> | ||||
|             <div class="col" id="infos"> | ||||
|               <p> | ||||
|                 Displaying | ||||
|                 <span id="received-match-count"> | ||||
|                 </span> of | ||||
|                 <span id="match-count"></span> | ||||
|                 matches. | ||||
|                 <br> | ||||
|                 Matches occured in | ||||
|                 <span id="text-lookup-count"></span> | ||||
|                 corpus files: | ||||
|                 <br> | ||||
|                 <span id=text-titles></span> | ||||
|               </p> | ||||
|               <div class="progress hide" id="query-results-progress"> | ||||
|                 <div class="determinate" id="query-results-determinate"></div> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|         <div class="col s12 m9 l9" id="actions-and-tools"> | ||||
|           <div class="row section"> | ||||
|             <div class="col s12 m3 l3" id="display"> | ||||
|               <h6 style="margin-top: 0px;">Display</h6> | ||||
|               <div class="divider" style="margin-bottom: 10px;"></div> | ||||
|               <div class="row"> | ||||
|                 <div class="col s12"> | ||||
|                   <form id="display-options-form"> | ||||
|                     {{ M.render_field(display_options_form.results_per_page, | ||||
|                                       material_icon='format_list_numbered') }} | ||||
|                     {{ M.render_field(display_options_form.result_context, | ||||
|                                       material_icon='short_text') }} | ||||
|                     {{ M.render_field(display_options_form.expert_mode) }} | ||||
|                   </form> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|       <!-- Table showing the query results --> | ||||
|         <div class="col s12"> | ||||
|           <ul class="pagination paginationTop"></ul> | ||||
|           <table class="responsive-table highlight"> | ||||
|             <thead> | ||||
|               <tr> | ||||
|                 <th style="width: 2%">Nr.</th> | ||||
|                 <th style="width: 3%">Title</th> | ||||
|                 <th style="width: 25%">Left context</th> | ||||
|                 <th style="width: 35%">Match</th> | ||||
|                 <th style="width: 10%">Actions</th> | ||||
|                 <th style="width: 25%">Right Context</th> | ||||
|               </tr> | ||||
|             </thead> | ||||
|             <tbody class="list" id="query-results"> | ||||
|             </tbody> | ||||
|           </table> | ||||
|           <ul class="pagination paginationBottom"></ul> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </div> | ||||
|  | ||||
| <script src="{{ url_for('static', filename='js/nopaque.Results.js') }}"> | ||||
| </script> | ||||
| <script src="{{ url_for('static', filename='js/nopaque.callbacks.js') }}"> | ||||
| </script> | ||||
| <script src="{{ url_for('static', filename='js/nopaque.InteractionElement.js') }}"> | ||||
| </script> | ||||
| <script> | ||||
|   // ###### global variables ###### | ||||
|   var full_result_json; | ||||
|   var result_json; | ||||
|   var queryResultsDeterminateElement;  // The progress bar for recieved results | ||||
|   var receivedMatchCountElement;  // Nr. of loaded matches will be displayed in this element | ||||
|   var textLookupCountElement  // Nr of texts the matches occured in will be shown in this element | ||||
|   var textTitlesElement;  // matched text titles | ||||
|   var progress;  // global progress value | ||||
|   var queryResultsProgressElement;  // Div element holding the progress bar | ||||
|   var expertModeSwitchElement; // Expert mode switch Element | ||||
|   var matchCountElement;  // Total nr. of matches will be displayed in this element | ||||
|   var interactionElements;  // Interaction elements and their parameters | ||||
|  | ||||
|   // ###### Defining local scope variables | ||||
|   let displayOptionsFormElement;  // Form holding the display informations | ||||
|   let resultItems;  // array of built html result items row element. This is called when results are transmitted and being recieved | ||||
|   let hitsPerPageInputElement;let contextPerItemElement;  // Form Element for display option | ||||
|   let paginationElements; | ||||
|  | ||||
|   // ###### Initializing variables ###### | ||||
|   displayOptionsFormElement = document.getElementById("display-options-form"); | ||||
|   resultItems = []; | ||||
|   queryResultsDeterminateElement = document.getElementById("query-results-determinate"); | ||||
|   receivedMatchCountElement = document.getElementById("received-match-count"); | ||||
|   textLookupCountElement = document.getElementById("text-lookup-count"); | ||||
|   textTitlesElement = document.getElementById("text-titles"); | ||||
|   queryResultsProgressElement = document.getElementById("query-results-progress"); | ||||
|   expertModeSwitchElement = document.getElementById("display-options-form-expert_mode"); | ||||
|   matchCountElement = document.getElementById("match-count"); | ||||
|   hitsPerPageInputElement = document.getElementById("display-options-form-results_per_page"); | ||||
|   contextPerItemElement = document.getElementById("display-options-form-result_context"); | ||||
|   paginationElements = document.getElementsByClassName("pagination"); | ||||
|  | ||||
|   // js list options | ||||
|   displayOptionsData = ResultsList.getDisplayOptions(displayOptionsFormElement); | ||||
|   resultsListOptions = {page: displayOptionsData["resultsPerPage"], | ||||
|   pagination: [{ | ||||
|       name: "paginationTop", | ||||
|       paginationClass: "paginationTop", | ||||
|       innerWindow: 8, | ||||
|       outerWindow: 1 | ||||
|     }, { | ||||
|       paginationClass: "paginationBottom", | ||||
|       innerWindow: 8, | ||||
|       outerWindow: 1 | ||||
|     }], | ||||
|     valueNames: ["titles", "lc", "c", "rc", {data: ["index"]}], | ||||
|     item: `<span></span>` | ||||
|   }; | ||||
|  | ||||
|   document.addEventListener("DOMContentLoaded", () => { | ||||
|     // ###### recreating chunk structure to reuse callback queryRenderResults() | ||||
|     full_result_json = {{ result_json|tojson|safe }}; | ||||
|     result_json = {}; | ||||
|     result_json.chunk = {}; | ||||
|     result_json.chunk["cpos_lookup"] = full_result_json.cpos_lookup; | ||||
|     result_json.chunk["cpos_ranges"] = full_result_json.cpos_ranges; | ||||
|     result_json.chunk["matches"] = full_result_json.matches; | ||||
|     result_json.chunk["text_lookup"] = full_result_json.text_lookup; | ||||
|  | ||||
|     // Init corpus analysis components | ||||
|     data = new Data(); | ||||
|     resultsList = new ResultsList("result-list", resultsListOptions); | ||||
|     resultsMetaData = new MetaData(); | ||||
|     results = new Results(data, resultsList, resultsMetaData); | ||||
|     results.clearAll();  // inits some object keys and values | ||||
|     // TODO: save metadate into results.metaData | ||||
|  | ||||
|     // setting some initial values for user feedback | ||||
|     matchCountElement.innerText = full_result_json.match_count; | ||||
|  | ||||
|     // Initialization of interactionElemnts | ||||
|     // An interactionElement is an object identifing a switch or button via | ||||
|     // htmlID. Callbacks are set for these elements which will be triggered on | ||||
|     // a pagination interaction by the user or if the status of the element has | ||||
|     // been altered. (Like the switche has ben turned on or off). | ||||
|     interactionElements = new Array(); | ||||
|     let expertModeInteraction = new InteractionElement("display-options-form-expert_mode"); | ||||
|     expertModeInteraction.setCallback("on", | ||||
|                                       results.jsList.expertModeOn, | ||||
|                                       results.jsList, | ||||
|                                       ["query-display"]) | ||||
|     expertModeInteraction.setCallback("off", | ||||
|                                       results.jsList.expertModeOff, | ||||
|                                       results.jsList, | ||||
|                                       ["query-display"]) | ||||
|  | ||||
|     let activateInspectInteraction = new InteractionElement("inspect", | ||||
|                                                             false); | ||||
|     activateInspectInteraction.setCallback("noCheck", | ||||
|                                             results.jsList.activateInspect, | ||||
|                                             results.jsList); | ||||
|  | ||||
|     let changeContextInteraction = new InteractionElement("display-options-form-results_per_page", | ||||
|                                                           false); | ||||
|     changeContextInteraction.setCallback("noCheck", | ||||
|                                         results.jsList.changeContext, | ||||
|                                         results.jsList) | ||||
|     interactionElements.push(expertModeInteraction, activateInspectInteraction, changeContextInteraction); | ||||
|  | ||||
|     // checks if a change for every interactionElement happens and executes | ||||
|     // the callbacks accordingly | ||||
|     InteractionElement.onChangeExecute(interactionElements); | ||||
|  | ||||
|     // eventListener if pagination is used to apply new context size to new page | ||||
|     // and also activate inspect match if progress is 100 | ||||
|     // also adds more interaction buttons like add to sub results | ||||
|     for (let element of paginationElements) { | ||||
|       element.addEventListener("click", (event) => { | ||||
|         results.jsList.pageChangeEventInteractionHandler(interactionElements); | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     // render results in table imported parameter is true | ||||
|     queryRenderResults(result_json, true) | ||||
|  | ||||
|     // live update of hits per page if hits per page value is changed | ||||
|     let changeHitsPerPageBind = results.jsList.changeHitsPerPage.bind(results.jsList); | ||||
|     hitsPerPageInputElement.onchange = changeHitsPerPageBind; | ||||
|  | ||||
|     // live update of lr context per item if context value is changed | ||||
|     contextPerItemElement.onchange = results.jsList.changeContext; | ||||
|   }); | ||||
| </script> | ||||
| {% endblock %} | ||||
							
								
								
									
										119
									
								
								web/app/templates/query_results/query_result.html.j2
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								web/app/templates/query_results/query_result.html.j2
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,119 @@ | ||||
| {% extends "nopaque.html.j2" %} | ||||
|  | ||||
| {% block page_content %} | ||||
|  | ||||
|  | ||||
| <div class="col s12"> | ||||
|   <p>Below the metadata for the results from the Corpus | ||||
|     <i>{{ query_result.query_metadata.corpus_name }}</i> generated with the query | ||||
|     <i>{{ query_result.query_metadata.query }}</i> are shown. | ||||
|   </p> | ||||
|  | ||||
| </div> | ||||
|  | ||||
| <div class="col s12"> | ||||
|   <div class="card"> | ||||
|     <div class="card-content" id="results"> | ||||
|       <table class="responsive-table highlight"> | ||||
|         <thead> | ||||
|           <tr> | ||||
|             <th>Metadata Description</th> | ||||
|             <th>Value</th> | ||||
|           </tr> | ||||
|         </thead> | ||||
|         <tbody> | ||||
|           {% for pair in query_result.query_metadata|dictsort %} | ||||
|           <tr> | ||||
|             <td>{{ pair[0] }}</td> | ||||
|             {% if pair[0] == 'corpus_all_texts' | ||||
|                or pair[0] == 'text_lookup'  %} | ||||
|               <td> | ||||
|               <table> | ||||
|               {% for key, value  in pair[1].items()  %} | ||||
|                   <tr style="border-bottom: none;"> | ||||
|                     <td> | ||||
|                       <i>{{ value['title'] }}</i> written | ||||
|                       by <i>{{ value['author'] }}</i> | ||||
|                       in <i>{{ value['publishing_year'] }}</i> | ||||
|                       <a class="waves-effect | ||||
|                                 waves-light | ||||
|                                 btn | ||||
|                                 right | ||||
|                                 more-text-detials" | ||||
|                          data-metadata-key="{{ pair[0] }}" | ||||
|                          data-text-key="{{ key }}" | ||||
|                          href="#modal-text-details">More | ||||
|                           <i class="material-icons right" | ||||
|                              data-metadata-key="{{ pair[0] }}" | ||||
|                              data-text-key="{{ key }}"> | ||||
|                              info_outline | ||||
|                           </i> | ||||
|                       </a> | ||||
|                     </td> | ||||
|                   </tr> | ||||
|               {% endfor %} | ||||
|               </table> | ||||
|               </td> | ||||
|             {% else %} | ||||
|               <td>{{ pair[1] }}</td> | ||||
|             {% endif %} | ||||
|           </tr> | ||||
|           {% endfor %} | ||||
|         </tbody> | ||||
|       </table> | ||||
|     </div> | ||||
|     <div class="card-action right-align"> | ||||
|       <a class="waves-effect waves-light btn left-align" href="{{ url_for('services.service', service='corpus_analysis') }}">Back To Overview<i class="material-icons right">arrow_back</i></a> | ||||
|       <a class="waves-effect waves-light btn" href="{{ url_for('query_results.inspect_query_result', query_result_id=query_result.id) }}">Inspect Results<i class="material-icons right">search</i></a> | ||||
|     </div> | ||||
|   </div> | ||||
| </div> | ||||
|  | ||||
| <!-- Modal Structure --> | ||||
| <div id="modal-text-details" class="modal modal-fixed-footer"> | ||||
|   <div class="modal-content"> | ||||
|     <h4>Bibliographic data</h4> | ||||
|     <p id="bibliographic-data"></p> | ||||
|   </div> | ||||
|   <div class="modal-footer"> | ||||
|     <a href="#!" class="modal-close waves-effect waves-green red btn">Close</a> | ||||
|   </div> | ||||
| </div> | ||||
|  | ||||
| <script> | ||||
| var moreTextDetailsButtons; | ||||
| moreTextDetailsButtons = document.getElementsByClassName("more-text-detials"); | ||||
| for (var btn of moreTextDetailsButtons) { | ||||
|   btn.onclick = () => { | ||||
|     let modal = document.getElementById("modal-text-details"); | ||||
|     modal = M.Modal.init(modal, {"dismissible": true}); | ||||
|     modal.open(); | ||||
|     let metadataKey = event.target.dataset.metadataKey; | ||||
|     let textKey = event.target.dataset.textKey; | ||||
|     let textData = {{ query_result.query_metadata|tojson|safe }}[metadataKey][textKey]; | ||||
|     console.log(textData); | ||||
|     let bibliographicData = document.getElementById("bibliographic-data"); | ||||
|     bibliographicData.innerHTML = ""; | ||||
|     let table = document.createElement("table"); | ||||
|     for (let [key, value] of Object.entries(textData)) { | ||||
|       table.insertAdjacentHTML("afterbegin", | ||||
|       ` | ||||
|         <tr> | ||||
|           <td>${key}</td> | ||||
|           <td>${value}</td> | ||||
|         </tr> | ||||
|       `); | ||||
|     } | ||||
|     table.insertAdjacentHTML("afterbegin", | ||||
|       ` | ||||
|         <thead> | ||||
|           <th>Description</th> | ||||
|           <th>Value</th> | ||||
|         </thead> | ||||
|       `) | ||||
|     bibliographicData.appendChild(table); | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|  | ||||
| {% endblock %} | ||||
							
								
								
									
										71
									
								
								web/app/templates/query_results/results.html.j2
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								web/app/templates/query_results/results.html.j2
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | ||||
| {% extends "nopaque.html.j2" %} | ||||
|  | ||||
| {% set full_width = True %} | ||||
|  | ||||
| {% block page_content %} | ||||
|  | ||||
| <div class="col s12"> | ||||
|   <p>This is an overview of all your imported results.</p> | ||||
| </div> | ||||
|  | ||||
| <div class="col s12"> | ||||
|   <div class="card"> | ||||
|     <div class="card-content" id="results"> | ||||
|       <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 class="sort" data-sort="query">Query</th> | ||||
|             <th class="sort" data-sort="match_count">Match count</th> | ||||
|             <th class="sort" data-sort="corpus_name">Corpus name</th> | ||||
|             <th class="sort" data-sort="corpus_creation_date">Corpus creation date</th> | ||||
|             <th class="sort" data-sort="corpus_analysis_date">Corpus analysis date</th> | ||||
|             <th class="sort" data-sort="corpus_type">Corpus type</th> | ||||
|             <th>{# Actions #}</th> | ||||
|           </tr> | ||||
|         </thead> | ||||
|         <tbody class="list"> | ||||
|           <tr class="show-if-only-child"> | ||||
|             <td colspan="5"> | ||||
|               <span class="card-title"><i class="material-icons left">folder</i>Nothing here...</span> | ||||
|               <p>No results yet imported.</p> | ||||
|             </td> | ||||
|           </tr> | ||||
|         </tbody> | ||||
|       </table> | ||||
|       <ul class="pagination paginationBottom"></ul> | ||||
|     </div> | ||||
|     <div class="card-action right-align"> | ||||
|       <a class="waves-effect waves-light btn" href="{{ url_for('results.import_results') }}">Import Results<i class="material-icons right">file_upload</i></a> | ||||
|     </div> | ||||
|   </div> | ||||
| </div> | ||||
|  | ||||
| {# Delete modals #} | ||||
| {% for result in results %} | ||||
| <div id="delete-result-{{ result.id }}-modal" class="modal"> | ||||
|   <div class="modal-content"> | ||||
|     <h4>Confirm result file deletion</h4> | ||||
|     <p>Do you really want to delete the result file created on <i>{{ result.corpus_analysis_date }}</i>? | ||||
|       <p>The file holds results for the query <i>{{ result.query }}</i>.</p> | ||||
|     <p>The file will be permanently deleted!</p> | ||||
|   </div> | ||||
|   <div class="modal-footer"> | ||||
|     <a href="#!" class="btn modal-close waves-effect waves-light">Cancel</a> | ||||
|     <a class="btn modal-close red waves-effect waves-light" href="{{ url_for('results.result_delete', result_id=result.id) }}"><i class="material-icons left">delete</i>Delete</a> | ||||
|   </div> | ||||
| </div> | ||||
| {% endfor %} | ||||
|  | ||||
| <script> | ||||
| var ressources = {{ results|tojson|safe }}; | ||||
| var importedResultsList = new RessourceList("results", null, "result"); | ||||
| importedResultsList.addRessources(ressources); | ||||
| RessourceList.modifyTooltips(); | ||||
| </script> | ||||
| {% endblock %} | ||||
| @@ -12,6 +12,7 @@ | ||||
| </div> | ||||
|  | ||||
| <div class="col s12"> | ||||
|   <h3>My Corpora</h3> | ||||
|   <div class="card"> | ||||
|     <div class="card-content" id="corpora"> | ||||
|       <div class="input-field"> | ||||
| @@ -43,8 +44,49 @@ | ||||
|   </div> | ||||
| </div> | ||||
|  | ||||
| <div class="col s12"> | ||||
|   <h3>My query results</h3> | ||||
|   <div class="card"> | ||||
|     <div class="card-content" id="query-results"> | ||||
|       <div class="input-field"> | ||||
|         <i class="material-icons prefix">search</i> | ||||
|         <input id="search-query-results" class="search" type="search"></input> | ||||
|         <label for="search-query-results">Search query result</label> | ||||
|       </div> | ||||
|       <ul class="pagination paginationTop"></ul> | ||||
|       <table class="highlight responsive-table"> | ||||
|         <thead> | ||||
|           <tr> | ||||
|             <th>{# Service #}</th> | ||||
|             <th> | ||||
|               <span class="sort" data-sort="title">Title</span> | ||||
|               <span class="sort" data-sort="description">Description</span> | ||||
|             </th> | ||||
|             <th>{# Actions #}</th> | ||||
|           </tr> | ||||
|         </thead> | ||||
|         <tbody class="list"> | ||||
|           <tr class="show-if-only-child"> | ||||
|             <td colspan="5"> | ||||
|               <span class="card-title"><i class="material-icons left">folder</i>Nothing here...</span> | ||||
|               <p>No query results yet imported.</p> | ||||
|             </td> | ||||
|           </tr> | ||||
|         </tbody> | ||||
|       </table> | ||||
|       <ul class="pagination paginationBottom"></ul> | ||||
|     </div> | ||||
|     <div class="card-action right-align"> | ||||
|       <a class="waves-effect waves-light btn" href="{{ url_for('query_results.add_query_result') }}">Add query result<i class="material-icons right">file_upload</i></a> | ||||
|     </div> | ||||
|   </div> | ||||
| </div> | ||||
|  | ||||
| <script> | ||||
|   var corpusList = new RessourceList("corpora", nopaque.corporaSubscribers, | ||||
|                                      "corpus", {page: 10}); | ||||
|   var queryResultList = new RessourceList("query-results", | ||||
|                                           nopaque.queryResultsSubscribers, | ||||
|                                           "query_result", {page: 10}); | ||||
| </script> | ||||
| {% endblock %} | ||||
|   | ||||
							
								
								
									
										30
									
								
								web/migrations/versions/33ec4d09b4ca_.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								web/migrations/versions/33ec4d09b4ca_.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| """empty message | ||||
|  | ||||
| Revision ID: 33ec4d09b4ca | ||||
| Revises: 4cf5e5606a83 | ||||
| Create Date: 2020-07-13 09:07:19.297185 | ||||
|  | ||||
| """ | ||||
| from alembic import op | ||||
| import sqlalchemy as sa | ||||
|  | ||||
|  | ||||
| # revision identifiers, used by Alembic. | ||||
| revision = '33ec4d09b4ca' | ||||
| down_revision = '4cf5e5606a83' | ||||
| branch_labels = None | ||||
| depends_on = None | ||||
|  | ||||
|  | ||||
| def upgrade(): | ||||
|     # ### commands auto generated by Alembic - please adjust! ### | ||||
|     op.add_column('query_results', sa.Column('description', sa.String(length=255), nullable=True)) | ||||
|     op.add_column('query_results', sa.Column('title', sa.String(length=32), nullable=True)) | ||||
|     # ### end Alembic commands ### | ||||
|  | ||||
|  | ||||
| def downgrade(): | ||||
|     # ### commands auto generated by Alembic - please adjust! ### | ||||
|     op.drop_column('query_results', 'title') | ||||
|     op.drop_column('query_results', 'description') | ||||
|     # ### end Alembic commands ### | ||||
							
								
								
									
										35
									
								
								web/migrations/versions/4cf5e5606a83_.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								web/migrations/versions/4cf5e5606a83_.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| """empty message | ||||
|  | ||||
| Revision ID: 4cf5e5606a83 | ||||
| Revises: e256f5cac75d | ||||
| Create Date: 2020-07-13 08:30:57.369850 | ||||
|  | ||||
| """ | ||||
| from alembic import op | ||||
| import sqlalchemy as sa | ||||
|  | ||||
|  | ||||
| # revision identifiers, used by Alembic. | ||||
| revision = '4cf5e5606a83' | ||||
| down_revision = 'e256f5cac75d' | ||||
| branch_labels = None | ||||
| depends_on = None | ||||
|  | ||||
|  | ||||
| def upgrade(): | ||||
|     # ### commands auto generated by Alembic - please adjust! ### | ||||
|     op.create_table('query_results', | ||||
|     sa.Column('id', sa.Integer(), nullable=False), | ||||
|     sa.Column('user_id', sa.Integer(), nullable=True), | ||||
|     sa.Column('filename', sa.String(length=255), nullable=True), | ||||
|     sa.Column('query_metadata', sa.JSON(), nullable=True), | ||||
|     sa.ForeignKeyConstraint(['user_id'], ['users.id'], ), | ||||
|     sa.PrimaryKeyConstraint('id') | ||||
|     ) | ||||
|     # ### end Alembic commands ### | ||||
|  | ||||
|  | ||||
| def downgrade(): | ||||
|     # ### commands auto generated by Alembic - please adjust! ### | ||||
|     op.drop_table('query_results') | ||||
|     # ### end Alembic commands ### | ||||
| @@ -2,8 +2,8 @@ import eventlet | ||||
| eventlet.monkey_patch()  # noqa | ||||
| from app import create_app, db, socketio | ||||
| from app.models import (Corpus, CorpusFile, Job, JobInput, JobResult, | ||||
|                         NotificationData, NotificationEmailData, Result, | ||||
|                         ResultFile, Role, User) | ||||
|                         NotificationData, NotificationEmailData, QueryResult, | ||||
|                         Result, ResultFile, Role, User) | ||||
| from flask_migrate import Migrate, upgrade | ||||
| import os | ||||
|  | ||||
| @@ -21,6 +21,7 @@ def make_shell_context(): | ||||
|             'JobResult': JobResult, | ||||
|             'NotificationData': NotificationData, | ||||
|             'NotificationEmailData': NotificationEmailData, | ||||
|             'QueryResult': QueryResult, | ||||
|             'Result': Result, | ||||
|             'ResultFile': ResultFile, | ||||
|             'Role': Role, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user