diff --git a/app/__init__.py b/app/__init__.py index b2dc543..ceebaa6 100755 --- a/app/__init__.py +++ b/app/__init__.py @@ -40,4 +40,3 @@ app.config.from_object(__name__) from app import views flask_whooshalchemyplus.init_app(app) # initialize - diff --git a/app/forms.py b/app/forms.py index e3b74cc..0db4fdf 100755 --- a/app/forms.py +++ b/app/forms.py @@ -4,7 +4,7 @@ from wtforms.validators import InputRequired, DataRequired from wtforms import FieldList from wtforms import Form as NoCsrfForm from wtforms.fields import StringField, FormField, SubmitField, SelectField -from app.models import Book, BookSchema, Author +from app.models import Book, BookSchema, Author, Stack, StackSchema # - - - Forms - - - class AuthorForm(NoCsrfForm): @@ -30,6 +30,17 @@ class ChatForm(FlaskForm): message = StringField('message', validators=[InputRequired()]) send = SubmitField(label='Send') +class StackForm(FlaskForm): + stack_name = StringField('Stack', validators=[InputRequired()]) + stack_description = StringField('Description', validators=[InputRequired()]) + create = SubmitField(label='Create') + +class AddtoStackForm(FlaskForm): + select_stack = SelectField('Stacks', validators=[InputRequired()]) + +class EditStackForm(FlaskForm): + edit_stack_name = StringField('Stack', validators=[InputRequired()]) + edit_stack_description = StringField('Description', validators=[InputRequired()]) class SearchForm(FlaskForm): choices = [('All', 'All'), diff --git a/app/models.py b/app/models.py index 5445c0e..57b4d49 100755 --- a/app/models.py +++ b/app/models.py @@ -119,7 +119,7 @@ class Stack(db.Model): self.stack_description = stack_description def __repr__(self): - return '' % self.stack + return '' % self.stack_name class AuthorSchema(Schema): id = fields.Int(dump_only=True) diff --git a/app/static/css/style.css b/app/static/css/style.css index 023daa4..63d80ad 100755 --- a/app/static/css/style.css +++ b/app/static/css/style.css @@ -97,10 +97,17 @@ background-color: #E8E8E8!important; .library_table li{ list-style-type:none; +text-align: center; padding-right: 5px; display: inline-block; } +#plus { + text-align: center; + font-size: 10px; +} + + .library_table tr:nth-child(even){ background-color: #fafafa; } @@ -176,15 +183,16 @@ font-family:'Courier New'; font-size: 12px; } -.ui-tabs-vertical { width: 55em; } -.ui-tabs-vertical .ui-tabs-nav { padding: .2em .1em .2em .2em; float: left; width: 12em; } -.ui-tabs-vertical .ui-tabs-nav li { clear: left; width: 100%; border-bottom-width: 1px !important; border-right-width: 0 !important; margin: 0 -1px .2em 0; } +.ui-tabs-vertical { width: 100em; border-top: 0;} +.ui-tabs-vertical .ui-tabs-nav { padding: .2em .2em .2em .2em; float: left; width: 15em; } +.ui-tabs-vertical .ui-tabs-nav li { clear: left; width: 100%; border-bottom-width: 0 !important; border-right-width: 0 !important; margin: 0 -1px .2em 0; } .ui-tabs-vertical .ui-tabs-nav li a { display:block; } -.ui-tabs-vertical .ui-tabs-nav li.ui-tabs-active { padding-bottom: 0; padding-right: .1em; border-right-width: 1px; } -.ui-tabs-vertical .ui-tabs-panel { padding: 1em; float: right; width: 40em;} +.ui-tabs-vertical .ui-tabs-nav li.ui-tabs-active { padding-bottom: 0; padding-right: .1em; border-right-width: 0; background-color: #A9A9A9 !important;} +.ui-tabs-vertical .ui-tabs-panel { padding: 1em; float: left; width: 50em; font-size: 12px;} #draggable { width: 100px; height: 100px; padding: 0.5em; float: left; margin: 10px 10px 10px 0; } #droppable { width: 150px; height: 150px; padding: 0.5em; float: left; margin: 10px; } + #newstext{ width: 100%; margin: 0; @@ -300,3 +308,19 @@ box-sizing: border-box; font-size: 16px; font-style: italic; } + +.widget { + resize: both; + overflow: hidden; + width: 300px; + height: 300px; + display: inline-block; + +} + +.widget iframe { + + width: 100%; + height: 100%; + border: none; +} diff --git a/app/static/js/app.js b/app/static/js/app.js index 5236b0f..29adeff 100755 --- a/app/static/js/app.js +++ b/app/static/js/app.js @@ -157,3 +157,37 @@ $(document).ready(function() } ); + +// Autocomplete for search - Contact Joca in case of trouble + + +$('#search').on("input", function() { + var query = this.value; + + $.ajax({ + url: "/autocomplete_suggestions", + data: $('form').serialize(), + type: "POST", + success: function(response) { + //console.log("Got your query!"); + } + }); + + + $.ajax({ + type: "GET", + url: "/autocomplete_suggestions", + dataType: "json", + success: function(data) { + + // Start autocomplete + var availableTags = data; + console.log(availableTags); + $( "#search" ).autocomplete({ + source: availableTags + }); + // End of autocomplete + } + }); + +}); diff --git a/app/templates/about.html b/app/templates/about.html index 657081d..802a4dc 100755 --- a/app/templates/about.html +++ b/app/templates/about.html @@ -2,5 +2,12 @@ {% block main %}

About

-

This an interface to the XPUB Library.

+

+XPPL is a project aimed at people who are studying the field of media culture, or as we like to call them: knowledge comrades. +
+This digital library gathers all the books and articles floating around on PZI shelves and our hard drives and memory sticks, so that they can be shared. +
+ Its web interface hosts a curated catalogue of books and articles, and its distributed architecture provides instances for uploading and downloading. +
+ It starts at XPUB, but can go anywhere we want it to.

{% endblock %} diff --git a/app/templates/add_stack.html b/app/templates/add_stack.html new file mode 100644 index 0000000..0fe1e5f --- /dev/null +++ b/app/templates/add_stack.html @@ -0,0 +1,36 @@ +{% extends 'base.html' %} + +{% block main %} +
+ +{% from "_formhelpers.html" import render_field %} + +

Add Stack

+ {% with messages = get_flashed_messages() %} + {% if messages %} +
+
    + {% for message in messages %} +
  • {{ message }}
  • + {% endfor %} +
+
+ {% endif %} + {% endwith %} + +
+ {{form.hidden_tag()}} +
+{{ render_field(form.stack_name)}} +{{ render_field(form.stack_description)}} + + + + +
+ + + + +
+{% endblock %} diff --git a/app/templates/add_to_stacks.html b/app/templates/add_to_stacks.html new file mode 100644 index 0000000..c2ed248 --- /dev/null +++ b/app/templates/add_to_stacks.html @@ -0,0 +1,21 @@ +{% extends 'base.html' %} + +{% block main %} +
+ +
Chosen book: + +

{{ book.title }}

+ + +
+

These are all the stacks that have been built so far.

+{% from "_formhelpers.html" import render_field %} +
+ + +
+ +
+ +{% endblock %} diff --git a/app/templates/base.html b/app/templates/base.html index b37e96c..965bce3 100755 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -15,6 +15,7 @@ + {% block css %} {% endblock%} diff --git a/app/templates/edit_stack_detail.html b/app/templates/edit_stack_detail.html new file mode 100644 index 0000000..6390e23 --- /dev/null +++ b/app/templates/edit_stack_detail.html @@ -0,0 +1,25 @@ +{% extends 'base.html' %} + +{% block main %} +
+ + +
+ {{ form.csrf_token }} +

+
+ {{ form.edit_stack_name.label }} {{ form.edit_stack_name(size=20, class="form-control") }} +

+
+ {{ form.edit_stack_description.label }} {{ form.edit_stack_description(size=20, class="form-control") }} +
+ +
+ +
+ +
+{% endblock %} diff --git a/app/templates/results.html b/app/templates/results.html index 8114fcd..aa1acf7 100644 --- a/app/templates/results.html +++ b/app/templates/results.html @@ -33,6 +33,8 @@ Filetype Category Stack + Add to stack + {% for book in books %} @@ -51,6 +53,10 @@
  • {{ stack.stack_name }}
  • {% endfor %} + + + ==> + {% endfor %} @@ -68,6 +74,8 @@ Filetype Category Stack + Add to stack + {% for book in books_all %} @@ -86,6 +94,9 @@
  • {{ stack.stack_name }}
  • {% endfor %} + + ==> + {% endfor %}

    diff --git a/app/templates/show_books.html b/app/templates/show_books.html index 7aa0a2c..e4860e7 100755 --- a/app/templates/show_books.html +++ b/app/templates/show_books.html @@ -3,6 +3,7 @@ {% block main %}

    {% from "_formhelpers.html" import render_field %} +
    {{ form.select(style="width: 100px; margin: 10px; float: left; font-size: 20px") }}
    {% endblock %} diff --git a/app/templates/show_stack_detail_tab.html b/app/templates/show_stack_detail_tab.html new file mode 100644 index 0000000..d074c2c --- /dev/null +++ b/app/templates/show_stack_detail_tab.html @@ -0,0 +1,27 @@ +{% block main %} +
    + +

    + + + {{ stack.stack_name }} +

    + + + +

    {{ stack.stack_description }}

    + +

    Books in this stack: {% for book in stack.books %} + +

  • {{book.title}}
  • + +

    + + Add to another stack +

    + {% endfor %}

    + + + +
    +{% endblock %} diff --git a/app/templates/show_stacks.html b/app/templates/show_stacks.html index 27a01ce..f1388a5 100644 --- a/app/templates/show_stacks.html +++ b/app/templates/show_stacks.html @@ -4,46 +4,30 @@

    Stacks

    These are all the stacks that have been built so far.

    +

    Add a new stack

    + - - -
    -
    -

    Stack description

    -

    This stack is nice.

    -
    +
    +
    -

    Build a stack

    - -
    -

    List of books

    -
    - -
    -

    Stack

    -
    diff --git a/app/views.py b/app/views.py index 7450d05..7c11b44 100755 --- a/app/views.py +++ b/app/views.py @@ -6,10 +6,10 @@ This file creates your application. """ from app import app, db, socketio, DOMAIN -from flask import Flask, Response, render_template, request, redirect, url_for, flash, send_from_directory, jsonify, abort +from flask import Flask, Response, session, render_template, request, redirect, url_for, flash, send_from_directory, jsonify, abort import json from sqlalchemy.sql.expression import func, select -from app.forms import UploadForm, EditForm, SearchForm, ChatForm +from app.forms import UploadForm, EditForm, SearchForm, ChatForm, StackForm, AddtoStackForm, EditStackForm from app.models import Book, BookSchema, Author, AuthorSchema, Stack, StackSchema, UserIns, Chat, ChatSchema, Instance from app.cover import get_cover from app.extractText import extract_text @@ -20,6 +20,8 @@ import time from csv import DictWriter, DictReader import io from sqlalchemy.inspection import inspect +import autocomplete +import sys import os from werkzeug.utils import secure_filename @@ -307,13 +309,60 @@ def show_stacks(): stacks = db.session.query(Stack).all() return render_template('show_stacks.html', stacks=stacks) +@app.route('/stacks/add_stack', methods=['POST', 'GET']) +def add_stack(): + form = StackForm() + stacks = db.session.query(Stack).all() + + if form.validate_on_submit(): + stack_name = form.stack_name.data + stack_description = form.stack_description.data + stack = Stack(stack_name, stack_description) + if form.stack_name.data: + stack = Stack(stack_name, stack_description) + db.session.add(stack) + stacks = db.session.query(Stack).all() + return redirect(url_for('show_stacks')) + flash("%s stack created" % (stack_name)) + return render_template('add_stack.html', stacks=stacks, form=form) + +@app.route('/stacks/tab/', methods=['POST', 'GET']) +def show_stack_in_tab(id): + return show_stack_by_id(id, is_tab=True) + + @app.route('/stacks/', methods=['POST', 'GET']) -def show_stack_by_id(id): +def show_stack_by_id(id, is_tab=False): + stack = Stack.query.get(id) if not stack: abort (404) else: - return render_template('show_stack_detail.html', stack=stack) + if is_tab == False: + return render_template('show_stack_detail.html', stack=stack) + else: + return render_template('show_stack_detail_tab.html', stack=stack) + +@app.route('/stacks//delete', methods=['POST', 'GET']) +def remove_stack_by_id(id): + Stack.query.filter_by(id=id).delete() + db.session.commit() + return redirect(url_for('show_stacks')) + +@app.route('/stacks//edit', methods=['POST', 'GET']) +def edit_stack_by_id(id): + stack = Stack.query.filter_by(id=id).first() + form = EditStackForm(edit_stack_name = stack.stack_name, edit_stack_description = stack.stack_description) + + if request.method == 'POST': + if form.validate_on_submit(): + stack_name = form.edit_stack_name.data + stack_description = form.edit_stack_description.data + stack.stack_name = stack_name + stack.stack_description = stack_description + db.session.commit() + return redirect(url_for('show_stack_by_id', id=id)) + return render_template('edit_stack_detail.html', stack=stack, form=form) @app.route('/instances', methods=['POST', 'GET']) def show_instances(): @@ -350,6 +399,8 @@ def show_instances(): @app.route('/books', methods= ['POST','GET']) def show_books(): + autocomplete.load() #Train markov model once, for autocomplete in search + books = db.session.query(Book).all() search = SearchForm(request.form) if request.method == 'POST': @@ -383,6 +434,46 @@ def search_results(searchtype, query): return render_template('results.html', form=search, books=results, books_all=random_order, searchtype=search.select.data, query=query) +## Search - autocomplete +autocomplete_suggestions = [] +autocomplete.load() #Train markov model once, for autocomplete in search + +@app.route('/autocomplete_suggestions', methods=['GET', 'POST']) +def test1(): + if request.method == 'POST': + query = request.form['search'] + query_tokenized = query.lower().split() + print(query_tokenized) + word_1 = query_tokenized[-2] + word_2 = query_tokenized[-1] + #print(word_1) + autocomplete_output = autocomplete.predict(word_1 , word_2) + autocomplete_suggestions.clear() + for suggestion, score in autocomplete_output: + autocomplete_suggestions.append(suggestion) + + session['autocomplete_suggestions'] = str(autocomplete_suggestions) + + print(session['autocomplete_suggestions']) + + return Response(json.dumps(session['autocomplete_suggestions']), mimetype='application/json') + +## STACKS! + +@app.route('/add_to_stack/', methods=['GET', 'POST']) +def add_to_stack(id): + stacks = db.session.query(Stack).all() + add_form = AddtoStackForm(request.form) + add_form.select_stack.choices = [(stack.id, stack.stack_name) for stack in stacks] + if request.method == 'GET': + book = Book.query.get(id) + return render_template('add_to_stacks.html', id=id, stacks=stacks, book=book, add_form=add_form) + else: + stack = Stack.query.get(int(add_form.select_stack.data)) + book = Book.query.get(id) + stack.books.append(book) + db.session.commit() + return render_template('show_stack_detail.html', stack=stack) @app.route('/export/csv', methods=['GET']) diff --git a/import_csv.py b/import_csv.py index b0a178e..9aa06d1 100644 --- a/import_csv.py +++ b/import_csv.py @@ -20,7 +20,7 @@ with open(args.csv) as f: print ('get_cover', fullpath, name) cover = get_cover(fullpath, name) - book = Book(row['Title'], row['Filename'], cover, row['Format'], row['Shelf'], None) + book = Book(row['Title'], row['Filename'], cover, row['Format'], row['Category'], None) db.session.add(book) authors = row['Author'].split(',') @@ -39,7 +39,7 @@ with open(args.csv) as f: if stack: b = db.session.query(Stack).filter_by(stack_name=stack).first() if b == None: - b = Stack(stack_name=stack, stack_description="test") + b = Stack(stack_name=stack, stack_description=stack_description) db.session.add(b) book.stacks.append(b) diff --git a/requirements.txt b/requirements.txt index a572570..542394a 100755 --- a/requirements.txt +++ b/requirements.txt @@ -16,6 +16,12 @@ WTForms==2.1 flask-marshmallow==0.9.0 Wand==0.4.4 PyPDF2==1.26.0 +<<<<<<< HEAD flask-socketio==2.9.2 flask-whooshalchemyplus==0.7.5 python-dotenv==0.7.1 +======= +autocomplete==0.0.104 + + +>>>>>>> 30d8bade54d8646ad4a5e314022d62e2dbf81755 diff --git a/xpublibrary.csv b/xpublibrary.csv index acd4299..54f8003 100644 --- a/xpublibrary.csv +++ b/xpublibrary.csv @@ -1,5 +1,4 @@ -Title,Author,Shelf,Format,OCR,Downloaded,Origin,Filename,Stack -Mac OS X Leopard Edition,David Pogue,Technical,pdf,1,1,LibGen,, +Title,Author,Category,Format,OCR,Downloaded,Origin,Filename,Stack The Qmail Handbook,Dave Sill,Technical,pdf,1,1,LibGen,, Hardening Network Infrastructure: Bulletproof Your Systems Before You Are Hacked!,Wes Noonan,Technical,"chm, pdf",1,1,LibGen,,Make a library Cocoa Programming for Mac OS X Second Edition,Aaron Hillegaas,Technical,pdf,1,1,LibGen,,