diff --git a/instance/papers.db b/instance/papers.db index e69de29..35b4507 100644 Binary files a/instance/papers.db and b/instance/papers.db differ diff --git a/scipaperloader/templates/papers.html b/scipaperloader/templates/papers.html new file mode 100644 index 0000000..a328b8f --- /dev/null +++ b/scipaperloader/templates/papers.html @@ -0,0 +1,258 @@ +{% extends "base.html" %} +{% block title %}Papers{% endblock %} +{% block content %} + +{# --- Sort direction logic for each column --- #} +{% set title_sort = 'asc' if sort_by != 'title' or sort_dir == 'desc' else 'desc' %} +{% set journal_sort = 'asc' if sort_by != 'journal' or sort_dir == 'desc' else 'desc' %} +{% set doi_sort = 'asc' if sort_by != 'doi' or sort_dir == 'desc' else 'desc' %} +{% set issn_sort = 'asc' if sort_by != 'issn' or sort_dir == 'desc' else 'desc' %} +{% set status_sort = 'asc' if sort_by != 'status' or sort_dir == 'desc' else 'desc' %} +{% set created_sort = 'asc' if sort_by != 'created_at' or sort_dir == 'desc' else 'desc' %} +{% set updated_sort = 'asc' if sort_by != 'updated_at' or sort_dir == 'desc' else 'desc' %} + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ + + +
+ +
+
+
+ Total Papers + {{ total_papers }} +
+ {% for status, count in status_counts.items() %} +
+ {{ status }}: + {{ count }} +
+ {% endfor %} +
+
+ + + + + +
+ Export CSV +
+
+ + + + + + + + + + + + + + {% for paper in papers %} + + + + + + + + + + {% endfor %} + +
+ {% set params = request.args.to_dict() %} + {% set params = params.update({'sort_by': 'title', 'sort_dir': title_sort}) or params %} + Title + + {% set params = request.args.to_dict() %} + {% set params = params.update({'sort_by': 'journal', 'sort_dir': journal_sort}) or params %} + Journal + + {% set params = request.args.to_dict() %} + {% set params = params.update({'sort_by': 'doi', 'sort_dir': doi_sort}) or params %} + DOI + + {% set params = request.args.to_dict() %} + {% set params = params.update({'sort_by': 'issn', 'sort_dir': issn_sort}) or params %} + ISSN + + {% set params = request.args.to_dict() %} + {% set params = params.update({'sort_by': 'status', 'sort_dir': status_sort}) or params %} + Status + + {% set params = request.args.to_dict() %} + {% set params = params.update({'sort_by': 'created_at', 'sort_dir': created_sort}) or params %} + Created + + {% set params = request.args.to_dict() %} + {% set params = params.update({'sort_by': 'updated_at', 'sort_dir': updated_sort}) or params %} + Updated +
{{ paper.title }}{{ paper.journal }}{{ paper.doi }}{{ paper.issn }}{{ paper.status }}{{ paper.created_at.strftime('%Y-%m-%d %H:%M:%S') }}{{ paper.updated_at.strftime('%Y-%m-%d %H:%M:%S') }}
+ + + + + +{% endblock %} diff --git a/scipaperloader/templates/partials/paper_detail_modal.html b/scipaperloader/templates/partials/paper_detail_modal.html new file mode 100644 index 0000000..dfbc466 --- /dev/null +++ b/scipaperloader/templates/partials/paper_detail_modal.html @@ -0,0 +1,14 @@ + + + \ No newline at end of file diff --git a/scipaperloader/views.py b/scipaperloader/views.py index 5fcc419..0a7c7f6 100644 --- a/scipaperloader/views.py +++ b/scipaperloader/views.py @@ -1,9 +1,13 @@ -from flask import Blueprint, render_template, current_app, request, flash, redirect, url_for +from flask import Blueprint, render_template, current_app, request, flash, redirect, url_for, send_file from .models import ScheduleConfig, VolumeConfig, PaperMetadata from .db import db import pandas as pd from io import StringIO import codecs +import datetime +import io +import csv +from sqlalchemy import asc, desc bp = Blueprint('main', __name__) @@ -12,6 +16,7 @@ bp = Blueprint('main', __name__) def index(): return render_template("index.html") + REQUIRED_COLUMNS = {"alternative_id", "journal", "doi", "issn", "title"} @@ -53,7 +58,7 @@ def upload(): type=row.get('type'), language=row.get('language'), published_online=parse_date(row.get('published_online')), - status=None, + status="New", file_path=None, error_msg=None ) @@ -70,10 +75,126 @@ def upload(): return render_template('upload.html') +@bp.route('/papers') +def list_papers(): + page = request.args.get('page', 1, type=int) + per_page = 50 -@bp.route("/papers") -def papers(): - return render_template("papers.html", app_title="PaperScraper") + # Filters + status = request.args.get('status') + created_from = request.args.get('created_from') + created_to = request.args.get('created_to') + updated_from = request.args.get('updated_from') + updated_to = request.args.get('updated_to') + sort_by = request.args.get('sort_by', 'created_at') + sort_dir = request.args.get('sort_dir', 'desc') + + query = PaperMetadata.query + + # Apply filters + if status: + query = query.filter(PaperMetadata.status == status) + + def parse_date(val): + from datetime import datetime + try: + return datetime.strptime(val, '%Y-%m-%d') + except (ValueError, TypeError): + return None + + if created_from := parse_date(created_from): + query = query.filter(PaperMetadata.created_at >= created_from) + if created_to := parse_date(created_to): + query = query.filter(PaperMetadata.created_at <= created_to) + if updated_from := parse_date(updated_from): + query = query.filter(PaperMetadata.updated_at >= updated_from) + if updated_to := parse_date(updated_to): + query = query.filter(PaperMetadata.updated_at <= updated_to) + + # Sorting + sort_col = getattr(PaperMetadata, sort_by, PaperMetadata.created_at) + sort_func = desc if sort_dir == 'desc' else asc + query = query.order_by(sort_func(sort_col)) + + # Pagination + pagination = query.paginate(page=page, per_page=per_page, error_out=False) + + # Statistics + total_papers = PaperMetadata.query.count() + status_counts = ( + db.session.query(PaperMetadata.status, db.func.count(PaperMetadata.status)) + .group_by(PaperMetadata.status) + .all() + ) + status_counts = {status: count for status, count in status_counts} + + return render_template( + 'papers.html', + papers=pagination.items, + pagination=pagination, + total_papers=total_papers, + status_counts=status_counts, + sort_by=sort_by, + sort_dir=sort_dir, + ) + + +@bp.route('/papers/export') +def export_papers(): + query = PaperMetadata.query + + # Filters + status = request.args.get('status') + created_from = request.args.get('created_from') + created_to = request.args.get('created_to') + updated_from = request.args.get('updated_from') + updated_to = request.args.get('updated_to') + sort_by = request.args.get('sort_by', 'created_at') + sort_dir = request.args.get('sort_dir', 'desc') + + query = PaperMetadata.query + + # Apply filters + if status: + query = query.filter(PaperMetadata.status == status) + + def parse_date(val): + try: + return datetime.datetime.strptime(val, "%Y-%m-%d") + except Exception: + return None + + output = io.StringIO() + writer = csv.writer(output) + writer.writerow(['ID', 'Title', 'Journal', 'DOI', 'ISSN', + 'Status', 'Created At', 'Updated At']) + + for paper in query: + writer.writerow([ + paper.id, + paper.title, + getattr(paper, 'journal', ''), + paper.doi, + paper.issn, + paper.status, + paper.created_at, + paper.updated_at + ]) + + output.seek(0) + return send_file(io.BytesIO(output.read().encode('utf-8')), + mimetype='text/csv', + as_attachment=True, + download_name='papers.csv') + + from flask import jsonify, render_template + + +@bp.route('/papers//detail') +def paper_detail(paper_id): + paper = PaperMetadata.query.get_or_404(paper_id) + + return render_template('partials/paper_detail_modal.html', paper=paper) @bp.route("/schedule", methods=["GET", "POST"])