updated with covers and authors
This commit is contained in:
parent
9461301d21
commit
7e1c504e09
@ -7,6 +7,7 @@ from werkzeug.utils import secure_filename
|
|||||||
basedir = os.path.abspath(os.path.dirname(__file__))
|
basedir = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
|
||||||
UPLOAD_FOLDER = os.path.join(basedir, 'uploads')
|
UPLOAD_FOLDER = os.path.join(basedir, 'uploads')
|
||||||
|
UPLOAD_FOLDER_COVER = os.path.join(basedir, 'uploads/cover')
|
||||||
ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'])
|
ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'])
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
17
app/forms.py
17
app/forms.py
@ -1,8 +1,21 @@
|
|||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import StringField, FileField
|
from wtforms import StringField, FileField
|
||||||
from wtforms.validators import InputRequired
|
from wtforms.validators import InputRequired, DataRequired
|
||||||
|
from wtforms import FieldList
|
||||||
|
from wtforms import Form as NoCsrfForm
|
||||||
|
from wtforms.fields import StringField, FormField, SubmitField
|
||||||
|
from app.models import Book, BookSchema, Author
|
||||||
|
|
||||||
|
# - - - Forms - - -
|
||||||
|
class AuthorForm(NoCsrfForm):
|
||||||
|
# this forms is never exposed so we can user the non CSRF version
|
||||||
|
author_name = StringField('Author Name', validators=[DataRequired()])
|
||||||
|
|
||||||
class UserForm(FlaskForm):
|
class UserForm(FlaskForm):
|
||||||
title = StringField('title', validators=[InputRequired()])
|
title = StringField('title', validators=[InputRequired()])
|
||||||
author = StringField('author', validators=[InputRequired()])
|
author = FieldList(FormField(AuthorForm, default=lambda: Author()), min_entries=1)
|
||||||
file = FileField()
|
file = FileField()
|
||||||
|
|
||||||
|
class UserForm_Edit(FlaskForm):
|
||||||
|
title = StringField('title', validators=[InputRequired()])
|
||||||
|
author = FieldList(FormField(AuthorForm, default=lambda: Author()), min_entries=1)
|
||||||
|
@ -2,26 +2,44 @@ from app import db
|
|||||||
from marshmallow import Schema, fields, ValidationError, pre_load
|
from marshmallow import Schema, fields, ValidationError, pre_load
|
||||||
|
|
||||||
class Book(db.Model):
|
class Book(db.Model):
|
||||||
|
__tablename__ = 'books'
|
||||||
id = db.Column(db.Integer, primary_key = True)
|
id = db.Column(db.Integer, primary_key = True)
|
||||||
title = db.Column(db.String(255))
|
title = db.Column(db.String(255))
|
||||||
author = db.Column(db.String(255))
|
|
||||||
file = db.Column(db.String(255))
|
file = db.Column(db.String(255))
|
||||||
|
cover = db.Column(db.String(255))
|
||||||
|
fileformat = db.Column(db.String(255))
|
||||||
|
author = db.relationship('Author')
|
||||||
|
|
||||||
|
def __init__(self, title, file, cover, fileformat):
|
||||||
def __init__(self, title, author, file):
|
|
||||||
self.title = title
|
self.title = title
|
||||||
self.author = author
|
|
||||||
self.file = file
|
self.file = file
|
||||||
|
self.cover = cover
|
||||||
|
self.fileformat = fileformat
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<Title %r>' % self.title
|
return '<Title %r>' % self.title
|
||||||
|
|
||||||
|
def get_id(self):
|
||||||
|
return self.id
|
||||||
|
|
||||||
|
|
||||||
|
class Author(db.Model):
|
||||||
|
__tablename__ = 'authors'
|
||||||
|
id = db.Column(db.Integer(), primary_key=True)
|
||||||
|
user_id = db.Column(db.Integer(), db.ForeignKey('books.id'))
|
||||||
|
author_name = db.Column(db.String(50))
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self, author_name):
|
||||||
|
self.author_name = author_name
|
||||||
|
|
||||||
class BookSchema(Schema):
|
class BookSchema(Schema):
|
||||||
id = fields.Int(dump_only=True)
|
id = fields.Int(dump_only=True)
|
||||||
title = fields.Str()
|
title = fields.Str()
|
||||||
author = fields.Str()
|
author = fields.Str()
|
||||||
file = fields.Str()
|
file = fields.Str()
|
||||||
|
cover = fields.Str()
|
||||||
|
fileformat = fields.Str()
|
||||||
|
|
||||||
def must_not_be_blank(data):
|
def must_not_be_blank(data):
|
||||||
if not data:
|
if not data:
|
||||||
|
@ -31,8 +31,42 @@ font-style: italic;
|
|||||||
display: table;
|
display: table;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.container{
|
||||||
.header{
|
padding: 0px 8px;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.header input{
|
||||||
|
height:40px;
|
||||||
|
width: 500px;
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author input{
|
||||||
|
height:20px;
|
||||||
|
width: 500px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.footer{
|
||||||
|
width: 100%;
|
||||||
|
font-family:'Courier New';
|
||||||
|
font-weight:100;
|
||||||
|
font-size:12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer pre{
|
||||||
|
width: 60px;
|
||||||
|
margin:0 auto;
|
||||||
|
font-family:'Courier New';
|
||||||
|
}
|
||||||
|
.footer p{
|
||||||
|
width: 30%;
|
||||||
|
margin:0 auto;
|
||||||
|
text-align: center;
|
||||||
|
font-family:'Courier New';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1 +1,32 @@
|
|||||||
/* Add your Application JavaScript */
|
/* Add your Application JavaScript */
|
||||||
|
$(function() {
|
||||||
|
$("div[data-toggle=fieldset]").each(function() {
|
||||||
|
var $this = $(this);
|
||||||
|
|
||||||
|
//Add new entry
|
||||||
|
$this.find("button[data-toggle=fieldset-add-row]").click(function() {
|
||||||
|
var target = $($(this).data("target"))
|
||||||
|
console.log(target);
|
||||||
|
var oldrow = target.find("[data-toggle=fieldset-entry]:last");
|
||||||
|
var row = oldrow.clone(true, true);
|
||||||
|
console.log(row.find(":input")[0]);
|
||||||
|
var elem_id = row.find(":input")[0].id;
|
||||||
|
var elem_num = parseInt(elem_id.replace(/.*-(\d{1,4})-.*/m, '$1')) + 1;
|
||||||
|
row.attr('data-id', elem_num);
|
||||||
|
row.find(":input").each(function() {
|
||||||
|
console.log(this);
|
||||||
|
var id = $(this).attr('id').replace('-' + (elem_num - 1) + '-', '-' + (elem_num) + '-');
|
||||||
|
$(this).attr('name', id).attr('id', id).val('').removeAttr("checked");
|
||||||
|
});
|
||||||
|
oldrow.after(row);
|
||||||
|
}); //End add new entry
|
||||||
|
|
||||||
|
//Remove row
|
||||||
|
$this.find("button[data-toggle=fieldset-remove-row]").click(function() {
|
||||||
|
if($this.find("[data-toggle=fieldset-entry]").length > 1) {
|
||||||
|
var thisRow = $(this).closest("[data-toggle=fieldset-entry]");
|
||||||
|
thisRow.remove();
|
||||||
|
}
|
||||||
|
}); //End remove row
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -17,7 +17,24 @@
|
|||||||
<form method="POST" action="{{ url_for('add_book') }}" enctype=multipart/form-data>
|
<form method="POST" action="{{ url_for('add_book') }}" enctype=multipart/form-data>
|
||||||
{{ form.csrf_token }}
|
{{ form.csrf_token }}
|
||||||
<div class="form-group">{{ form.title.label }} {{ form.title(size=20, class="form-control") }}</div>
|
<div class="form-group">{{ form.title.label }} {{ form.title(size=20, class="form-control") }}</div>
|
||||||
<div class="form-group">{{ form.author.label }} {{ form.author(size=20, class="form-control") }}</div>
|
<br>
|
||||||
|
<div data-toggle="fieldset" id="phone-fieldset">
|
||||||
|
{{ form.author.label }} <button type="button" data-toggle="fieldset-add-row"
|
||||||
|
data-target="#phone-fieldset">+</button>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
{% for author in form.author %}
|
||||||
|
<tr data-toggle="fieldset-entry">
|
||||||
|
<td>{{ author.author_name }}</td>
|
||||||
|
<td><button type="button" data-toggle="fieldset-remove-row" id="phone-{{loop.index0}}-remove">-</button></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
{{ form.file }}
|
{{ form.file }}
|
||||||
<button type="submit" class="btn btn-primary">Upload</button>
|
<button type="submit" class="btn btn-primary">Upload</button>
|
||||||
</form>
|
</form>
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
|
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
|
||||||
<title>XPUB LIB</title>
|
<title>XPPL</title>
|
||||||
|
|
||||||
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
|
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
|
||||||
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
||||||
@ -14,12 +14,15 @@
|
|||||||
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
|
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
|
||||||
<![endif]-->
|
<![endif]-->
|
||||||
<link rel="stylesheet" href="/static/css/style.css">
|
<link rel="stylesheet" href="/static/css/style.css">
|
||||||
|
|
||||||
{% block css %} {% endblock%}
|
{% block css %} {% endblock%}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
{% block header %}
|
||||||
<header>
|
<header>
|
||||||
{% include "header.html" %}
|
{% include "header.html" %}
|
||||||
</header>
|
</header>
|
||||||
|
{% endblock %}
|
||||||
<main>
|
<main>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
{% block main %}{% endblock %}
|
{% block main %}{% endblock %}
|
||||||
@ -32,5 +35,7 @@
|
|||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
{% block js %} {% endblock%}
|
{% block js %} {% endblock%}
|
||||||
|
<script src="https://code.jquery.com/jquery-2.1.4.js"></script>
|
||||||
|
<script src="{{ url_for("static", filename="js/app.js") }}"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
|
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
<pre style="font-family:'Courier New'; font-weight:100; font-size:12px;">
|
<div class="footer">
|
||||||
|
<pre>
|
||||||
, ,
|
, ,
|
||||||
/////|
|
/////|
|
||||||
///// |
|
///// |
|
||||||
@ -9,7 +10,9 @@
|
|||||||
|===| |
|
|===| |
|
||||||
|x | |
|
|x | |
|
||||||
| p | |
|
| p | |
|
||||||
|u b| /
|
|p l| /
|
||||||
|===|/
|
|===|/
|
||||||
'---'
|
'---'
|
||||||
</pre>
|
</pre>
|
||||||
|
<p>XPPL. MADE POSSIBLE BY EXPERIMENTAL PUBLISHING, PZI.</p>
|
||||||
|
</div>
|
||||||
|
@ -6,5 +6,4 @@
|
|||||||
<li><a href="{{ url_for('about') }}">About</a></li>
|
<li><a href="{{ url_for('about') }}">About</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
|
|
||||||
</nav>
|
</nav>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
<h1 class="header">XPUB LIB</h1>
|
<h1 class="header">XPPL</h1>
|
||||||
<p class="lead">This is the awesome library of Experimental Publishing. <br>
|
<p class="lead">This is the awesome library of Experimental Publishing. <br>
|
||||||
This might only be one interface to this library:
|
This might only be one interface to this library:
|
||||||
…
|
…
|
||||||
|
@ -4,9 +4,19 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
|
|
||||||
<h1 class="header">{{ book.title }}</h1>
|
<h1 class="header">{{ book.title }}</h1>
|
||||||
<p>Author: {{ book.author }}</p>
|
|
||||||
|
|
||||||
<a href="../uploads/{{ book.file }}">download file</a>
|
<img src="../uploads/cover/{{ book.cover }}" width="200">
|
||||||
|
|
||||||
|
<p>Author(s): {% for author in book.author %}
|
||||||
|
|
||||||
|
<li> {{ author.author_name }}</li>
|
||||||
|
|
||||||
|
{% endfor %}</p>
|
||||||
|
|
||||||
|
<a href="../uploads/{{ book.file }}">download {{ book.fileformat }}</a>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<a href="{{ url_for('edit_book_by_id', id=book.id )}}">edit</a>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -17,13 +17,21 @@
|
|||||||
|
|
||||||
<table style="width:100%">
|
<table style="width:100%">
|
||||||
<tr>
|
<tr>
|
||||||
|
<th>Cover</th>
|
||||||
<th>Title</th>
|
<th>Title</th>
|
||||||
<th>Author</th>
|
<th>Author</th>
|
||||||
|
<th>Filetype</th>
|
||||||
</tr>
|
</tr>
|
||||||
{% for book in books %}
|
{% for book in books %}
|
||||||
<tr>
|
<tr>
|
||||||
|
<td><img src="../uploads/cover/{{ book.cover }}" width="80"></td>
|
||||||
<td><a href="books/{{ book.id }}">{{ book.title }}</a></td>
|
<td><a href="books/{{ book.id }}">{{ book.title }}</a></td>
|
||||||
<td>{{ book.author }}</td>
|
<td> {% for author in book.author %}
|
||||||
|
|
||||||
|
<li> {{ author.author_name }}</li>
|
||||||
|
|
||||||
|
{% endfor %}</td>
|
||||||
|
<td>{{ book.fileformat }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
72
app/views.py
72
app/views.py
@ -8,11 +8,13 @@ This file creates your application.
|
|||||||
from app import app, db
|
from app import app, db
|
||||||
from flask import Flask, render_template, request, redirect, url_for, flash, send_from_directory, jsonify, abort
|
from flask import Flask, render_template, request, redirect, url_for, flash, send_from_directory, jsonify, abort
|
||||||
|
|
||||||
from app.forms import UserForm
|
from app.forms import UserForm, UserForm_Edit
|
||||||
from app.models import Book, BookSchema
|
from app.models import Book, BookSchema, Author
|
||||||
|
from app.cover import get_cover
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
|
|
||||||
# import sqlite3
|
# import sqlite3
|
||||||
ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'])
|
ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'])
|
||||||
|
|
||||||
@ -31,6 +33,9 @@ def home():
|
|||||||
"""Render website's home page."""
|
"""Render website's home page."""
|
||||||
return render_template('home.html')
|
return render_template('home.html')
|
||||||
|
|
||||||
|
@app.route('/hello/<name>')
|
||||||
|
def hello(name):
|
||||||
|
return "Hello " + name
|
||||||
|
|
||||||
@app.route('/about/')
|
@app.route('/about/')
|
||||||
def about():
|
def about():
|
||||||
@ -42,20 +47,61 @@ def uploaded_file(filename):
|
|||||||
return send_from_directory(app.config['UPLOAD_FOLDER'],
|
return send_from_directory(app.config['UPLOAD_FOLDER'],
|
||||||
filename)
|
filename)
|
||||||
|
|
||||||
|
@app.route('/uploads/cover/<filename>')
|
||||||
|
def uploaded_file_cover(filename):
|
||||||
|
return send_from_directory(app.config['UPLOAD_FOLDER_COVER'],
|
||||||
|
filename)
|
||||||
|
|
||||||
@app.route('/books')
|
@app.route('/books')
|
||||||
def show_books():
|
def show_books():
|
||||||
books = db.session.query(Book).all() # or you could have used User.query.all()
|
books = db.session.query(Book).all() # or you could have used User.query.all()
|
||||||
|
|
||||||
return render_template('show_books.html', books=books)
|
return render_template('show_books.html', books=books)
|
||||||
|
|
||||||
@app.route('/books/<int:id>')
|
@app.route('/books/<int:id>')
|
||||||
def show_book_by_id(id):
|
def show_book_by_id(id):
|
||||||
book = Book.query.get(id)
|
book = Book.query.get(id)
|
||||||
if not book:
|
if not book:
|
||||||
abort(404)
|
return render_template('red_link.html', id=id)
|
||||||
else:
|
else:
|
||||||
return render_template('show_book_detail.html', book=book)
|
return render_template('show_book_detail.html', book=book)
|
||||||
|
|
||||||
|
@app.route('/books/<int:id>/delete', methods=['POST', 'GET'])
|
||||||
|
def remove_book_by_id(id):
|
||||||
|
book_to_edit = Book.query.filter_by(id=id).first()
|
||||||
|
title = book_to_edit.title
|
||||||
|
Book.query.filter_by(id=id).delete()
|
||||||
|
author_table = Author.query.filter_by(user_id=book_to_edit.id).delete()
|
||||||
|
db.session.commit()
|
||||||
|
flash("%s deleted from library" % (title))
|
||||||
|
return redirect(url_for('show_books'))
|
||||||
|
|
||||||
|
@app.route('/books/<int:id>/edit', methods=['POST', 'GET'])
|
||||||
|
def edit_book_by_id(id):
|
||||||
|
book_to_edit = Book.query.filter_by(id=id).first()
|
||||||
|
user_form = UserForm_Edit(title = book_to_edit.title, author =book_to_edit.author)
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
if user_form.validate_on_submit():
|
||||||
|
# check if the post request has the file part
|
||||||
|
title = user_form.title.data # You could also have used request.form['name']
|
||||||
|
author = user_form.author.data # You could also have used request.form['email']
|
||||||
|
# save user to database
|
||||||
|
#book = Book(title, author, filename, cover, file_extension)
|
||||||
|
book_to_edit.title = title
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
book = Book.query.filter_by(title=title).first()
|
||||||
|
author_table = Author.query.filter_by(user_id=book.id).delete()
|
||||||
|
for this_author in author:
|
||||||
|
this_author = Author(this_author.get('author_name'))
|
||||||
|
book.author.append(this_author)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
flash("%s updated" % (title))
|
||||||
|
return redirect(url_for('show_books'))
|
||||||
|
|
||||||
|
return render_template('edit_book_detail.html', book=book_to_edit, form=user_form)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/add-book', methods=['POST', 'GET'])
|
@app.route('/add-book', methods=['POST', 'GET'])
|
||||||
def add_book():
|
def add_book():
|
||||||
@ -77,15 +123,25 @@ def add_book():
|
|||||||
if file and allowed_file(file.filename):
|
if file and allowed_file(file.filename):
|
||||||
filename = secure_filename(file.filename)
|
filename = secure_filename(file.filename)
|
||||||
fullpath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
fullpath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
||||||
|
name, file_extension = os.path.splitext(filename)
|
||||||
file.save(fullpath)
|
file.save(fullpath)
|
||||||
|
cover = get_cover(fullpath, name)
|
||||||
title = user_form.title.data # You could also have used request.form['name']
|
title = user_form.title.data # You could also have used request.form['name']
|
||||||
author = user_form.author.data # You could also have used request.form['email']
|
author = user_form.author.data # You could also have used request.form['email']
|
||||||
# save user to database
|
print(author)
|
||||||
book = Book(title, author, filename)
|
print(len(author))
|
||||||
|
book = Book(title, filename, cover, file_extension)
|
||||||
db.session.add(book)
|
db.session.add(book)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
book = Book.query.filter_by(title=title).first()
|
||||||
|
for this_author in author:
|
||||||
|
this_author = Author(this_author.get('author_name'))
|
||||||
|
book.author.append(this_author)
|
||||||
|
db.session.commit()
|
||||||
|
#author = "hallo"
|
||||||
|
# save user to database
|
||||||
|
|
||||||
|
|
||||||
flash("%s added to the library" % (title))
|
flash("%s added to the library" % (title))
|
||||||
return redirect(url_for('show_books'))
|
return redirect(url_for('show_books'))
|
||||||
else:
|
else:
|
||||||
|
Loading…
Reference in New Issue
Block a user