first pass at creating a book, it works, but I have messed up the series-div* html in book.html

This commit is contained in:
2020-12-23 12:53:27 +11:00
parent 7bd81d3119
commit 73001dd24c
5 changed files with 109 additions and 36 deletions

2
BUGs
View File

@@ -6,3 +6,5 @@
* show books, only shows first author * show books, only shows first author
* starting on series remove button. If we click a subbook, then remove series button needs to be clever (might need client-side validation / confirmation, eg. this is a subbook, you want to remove all of the sub books from the series?) * starting on series remove button. If we click a subbook, then remove series button needs to be clever (might need client-side validation / confirmation, eg. this is a subbook, you want to remove all of the sub books from the series?)
* series: 112, book/sub-book ordering is broken * series: 112, book/sub-book ordering is broken
** I have made a mess of the series_div stuff && its just complex, is it the best solution?

8
README
View File

@@ -31,13 +31,13 @@ python3 main.py
### TODO: ### TODO:
- when remove a Parent book from a series, what do we do? - when remove a Parent book from a series, what do we do?
(remove all sub books from series too?) (remove all sub books from series too?)
- need to add book to sub_book - need to add sub_book to parent book
- need to delete 1 book from sub_book ** okay, tried to overload /book/<id> .. too hard, need to overload /book if anything... so add_sub button needs to call different URL, and then maybe this would work? ( if parent, then show extra bit AND, if parent, add the sub_book_link when adding new book???
- need to create loan - need to delete 1 sub_book from book
- need to add books to loan (on loan page, and via a book search?) - need to add books to loan (on loan page, and via a book search?)
- need to delete book from loan - need to delete book from loan
- need to delete loan
- need to delete all classes (and watch for referential integrity) - need to delete all classes (and watch for referential integrity)
* book, condition, covertype, genre, loan
- show books on shelf list - show books on shelf list
- show books to buy view / printable - show books to buy view / printable
- with ORM: should I lazy load all books (ajax the 2nd->last pages in, or not use ORM, and do a quick db.execute()....) - with ORM: should I lazy load all books (ajax the 2nd->last pages in, or not use ORM, and do a quick db.execute()....)

71
main.py
View File

@@ -1,4 +1,4 @@
from flask import Flask, render_template, request from flask import Flask, render_template, request, redirect
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow from flask_marshmallow import Marshmallow
from flask_bootstrap import Bootstrap from flask_bootstrap import Bootstrap
@@ -71,7 +71,7 @@ class Book_Sub_Book_Link(db.Model):
return "<book_id: {}, sub_book_id: {}, sub_book_num: {}>".format(self.book_id, self.sub_book_id, self.sub_book_num) return "<book_id: {}, sub_book_id: {}, sub_book_num: {}>".format(self.book_id, self.sub_book_id, self.sub_book_num)
class Book(db.Model): class Book(db.Model):
id = db.Column(db.Integer, unique=True, nullable=False, primary_key=True) id = db.Column(db.Integer, db.Sequence('book_id_seq'), primary_key=True )
title = db.Column(db.String(100), unique=True, nullable=False) title = db.Column(db.String(100), unique=True, nullable=False)
author = db.relationship('Author', secondary=book_author_link) author = db.relationship('Author', secondary=book_author_link)
publisher = db.Column(db.Integer, db.ForeignKey('publisher.id')) publisher = db.Column(db.Integer, db.ForeignKey('publisher.id'))
@@ -170,12 +170,13 @@ class BookForm(FlaskForm):
owned = SelectField( 'owned', choices=[(c.id, c.name) for c in Owned.query.order_by('id')] ) owned = SelectField( 'owned', choices=[(c.id, c.name) for c in Owned.query.order_by('id')] )
covertype = SelectField( 'covertype', choices=[(c.id, c.name) for c in Covertype.query.order_by('id')] ) covertype = SelectField( 'covertype', choices=[(c.id, c.name) for c in Covertype.query.order_by('id')] )
condition = SelectField( 'condition', choices=[(c.id, c.name) for c in Condition.query.order_by('id')] ) condition = SelectField( 'condition', choices=[(c.id, c.name) for c in Condition.query.order_by('id')] )
year_published = IntegerField('Year Published:', [validators.NumberRange(min=1900, max=2100)] ) year_published = IntegerField('Year Published:', validators=[validators.DataRequired(), validators.NumberRange(min=1900, max=2100)] )
rating = SelectField( 'rating', choices=[(c.id, c.name) for c in Rating.query.order_by('id')] ) rating = SelectField( 'rating', choices=[(c.id, c.name) for c in Rating.query.order_by('id')] )
notes = TextAreaField('Notes:') notes = TextAreaField('Notes:')
blurb = TextAreaField('Blurb:') blurb = TextAreaField('Blurb:')
submit = SubmitField('Save' ) submit = SubmitField('Save' )
delete = SubmitField('Delete' ) delete = SubmitField('Delete' )
add_sub = SubmitField('Add Sub-Book' )
################################# helper functions ################################### ################################# helper functions ###################################
@@ -337,17 +338,69 @@ def subbooks_for_book(id):
return render_template("subbooks_for_book.html", sub_books=sub_book, s2=subs ) return render_template("subbooks_for_book.html", sub_books=sub_book, s2=subs )
###
### Need a route to /book then incorporate the below:
###
# elif 'add_sub' in request.form:
# alert="danger"
# message="Sorry, Adding a sub-book unsupported at present"
# book = Book.query.get(id)
# parent=request.form['add_sub_parent_id']
# parent_book = Book.query.get(parent)
################################################################################
# /book -> GET/POST -> creates a new book and when created, takes you back to
# /books (or /book/<parent_id> -- if you added a sub-book of parent_id
################################################################################
@app.route("/book", methods=["GET", "POST"])
def new_book():
form = BookForm(request.form)
author_list = GetAuthors()
genre_list = GetGenres()
if 'title' not in request.form:
return render_template("book.html", page_title='Create new Book', b=None, books=None, book_form=form, author_list=author_list, genre_list=genre_list, alert="", message="", poss_series_list=ListOfSeriesWithMissingBooks() )
elif form.validate():
book_authors=[]
for el in request.form:
if 'author-' in el:
book_authors.append( Author.query.get( request.form[el] ) )
book_genres = []
for genre in genre_list:
if "genre-{}".format(genre.id) in request.form:
book_genres.append( genre )
book = Book( title=request.form["title"], owned=request.form['owned'], covertype=request.form['covertype'], condition=request.form['condition'], publisher=request.form['publisher'], year_published=request.form['year_published'], rating=request.form['rating'], notes=request.form['notes'], blurb=request.form['blurb'], genre=book_genres, author=book_authors )
db.session.add(book)
db.session.commit()
st.SetMessage( "Created new Book ({})".format(book.title) )
cnt=1
for field in request.form:
if 'bsl-book_id-' in field and field != 'bsl-book_id-NUM':
cnt=int(re.findall( '\d+', field )[0])
sql="insert into book_series_link (book_id, series_id, book_num) values ( {}, {}, {} )".format( book.id, request.form['bsl-series_id-{}'.format(cnt)], request.form['bsl-book_num-{}'.format(cnt)])
db.engine.execute( sql )
cnt=cnt+1
return redirect( '/book/{}'.format(book.id) )
else:
alert="danger"
message="Failed to create Book"
for field in form.errors:
message = "{}<br>{}={}".format( message, field, form.errors[field] )
print( "Failed to create book: {}".format(message) )
return render_template("book.html", page_title='Create new Book', b=None, books=None, book_form=form, author_list=author_list, genre_list=genre_list, alert=alert, message=message, poss_series_list=ListOfSeriesWithMissingBooks() )
@app.route("/book/<id>", methods=["GET", "POST"]) @app.route("/book/<id>", methods=["GET", "POST"])
def book(id): def book(id):
alert="success" alert="success"
message="" message=""
book_form = BookForm(request.form) book_form = BookForm(request.form)
book = Book.query.get(id)
if request.method == 'POST': if request.method == 'POST':
if 'delete' in request.form: if 'delete' in request.form:
book = Book.query.get(id)
alert="danger" alert="danger"
message="Sorry, Deleting unsupported at present" message="Sorry, Deleting unsupported at present"
elif book_form.validate(): elif book_form.validate():
book = Book.query.get(id)
book.title = request.form['title'] book.title = request.form['title']
book.owned = request.form['owned'] book.owned = request.form['owned']
book.covertype = request.form['covertype'] book.covertype = request.form['covertype']
@@ -377,7 +430,6 @@ def book(id):
for field in request.form: for field in request.form:
if 'bsl-book_id-' in field and field != 'bsl-book_id-NUM': if 'bsl-book_id-' in field and field != 'bsl-book_id-NUM':
cnt=int(re.findall( '\d+', field )[0]) cnt=int(re.findall( '\d+', field )[0])
print( "found: bsl-{} book_id == {}, series_id={}, book_num={}".format(cnt, request.form['bsl-book_id-{}'.format(cnt)], request.form['bsl-series_id-{}'.format(cnt)], request.form['bsl-book_num-{}'.format(cnt)]) )
sql="insert into book_series_link (book_id, series_id, book_num) values ( {}, {}, {} )".format( request.form['bsl-book_id-{}'.format(cnt)], request.form['bsl-series_id-{}'.format(cnt)], request.form['bsl-book_num-{}'.format(cnt)]) sql="insert into book_series_link (book_id, series_id, book_num) values ( {}, {}, {} )".format( request.form['bsl-book_id-{}'.format(cnt)], request.form['bsl-series_id-{}'.format(cnt)], request.form['bsl-book_num-{}'.format(cnt)])
db.engine.execute( sql ) db.engine.execute( sql )
cnt=cnt+1 cnt=cnt+1
@@ -388,16 +440,19 @@ def book(id):
message="Successfully Updated Book (id={})".format(id) message="Successfully Updated Book (id={})".format(id)
else: else:
alert="danger" alert="danger"
print( book_form) message="Failed to update Book (id={})".format(id)
message="Err... Failed to update Book (id={})".format(id) for field in book_form.errors:
message = "{}<br>{}={}".format( message, field, book_form.errors[field] )
book = Book.query.get(id)
else: else:
book = Book.query.get(id)
book_form=BookForm(obj=book) book_form=BookForm(obj=book)
author_list = GetAuthors() author_list = GetAuthors()
genre_list = GetGenres() genre_list = GetGenres()
book_s = book_schema.dump(book) book_s = book_schema.dump(book)
return render_template("book.html", b=book, books=book_s, book_form=book_form, author_list=author_list, genre_list=genre_list, alert=alert, message=message, n=book_form.notes, poss_series_list=ListOfSeriesWithMissingBooks() ) return render_template("book.html", b=book, books=book_s, book_form=book_form, author_list=author_list, genre_list=genre_list, alert=alert, message=message, poss_series_list=ListOfSeriesWithMissingBooks() )
def GetCount( what, where ): def GetCount( what, where ):
st="select count(id) as count from book where " st="select count(id) as count from book where "

View File

@@ -24,7 +24,7 @@
<div class="nav-item dropdown"> <div class="nav-item dropdown">
<a class="nav-item dropdown nav-link dropdown-toggle" href="#" id="BookMenu" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Books</a> <a class="nav-item dropdown nav-link dropdown-toggle" href="#" id="BookMenu" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Books</a>
<div class="dropdown-menu" aria-labelledby="BookMenu"> <div class="dropdown-menu" aria-labelledby="BookMenu">
<a class="dropdown-item" href="{{url_for('books')}}">Create</a> <a class="dropdown-item" href="{{url_for('new_book')}}">Create</a>
<a class="dropdown-item" href="{{url_for('books')}}">Show All</a> <a class="dropdown-item" href="{{url_for('books')}}">Show All</a>
<a class="dropdown-item" href="{{url_for('stats')}}">Show Stats</a> <a class="dropdown-item" href="{{url_for('stats')}}">Show Stats</a>
</div> </div>

View File

@@ -3,8 +3,8 @@
<script> <script>
function AddBookToSeries(bid) { function AddBookToSeries() {
console.log("add Book (" +bid + ") to series") console.log("add Book to series")
// Read the Number from that DIV's ID (i.e: 3 from "series-div-3") -- could // Read the Number from that DIV's ID (i.e: 3 from "series-div-3") -- could
// also be the template (series-div-0), thats ok too // also be the template (series-div-0), thats ok too
@@ -34,7 +34,7 @@ console.log( "reset buttons on last_div is=" + last_div.prop('id') )
// reset id/names to new series-div- created (for id on div, name on select // reset id/names to new series-div- created (for id on div, name on select
// for series_id and input text for book_num) // for series_id and input text for book_num)
new_div.prop('id', 'series-div-'+num ); new_div.prop('id', 'series-div-'+num );
new_div.find('#series_minus_but').attr('onclick', "RemoveBookFromSeries({{books.id}},'series-div-"+num+"')" ) new_div.find('#series_minus_but').attr('onclick', "RemoveBookFromSeries('series-div-"+num+"')" )
new_div.find('#bsl-book_id-NUM').prop('name', 'bsl-book_id-'+num ) new_div.find('#bsl-book_id-NUM').prop('name', 'bsl-book_id-'+num )
new_div.find('#bsl-series_id-NUM').prop('name', 'bsl-series_id-'+num ) new_div.find('#bsl-series_id-NUM').prop('name', 'bsl-series_id-'+num )
new_div.find('#bsl-book_num-NUM').prop('name', 'bsl-book_num-'+num ) new_div.find('#bsl-book_num-NUM').prop('name', 'bsl-book_num-'+num )
@@ -44,8 +44,8 @@ console.log( "reset buttons on last_div is=" + last_div.prop('id') )
new_div.show() new_div.show()
} }
function RemoveBookFromSeries(bid, sid) { function RemoveBookFromSeries(sid) {
console.log("remove Book: " + bid + " from Series: " + sid ) console.log("remove Book from Series: " + sid )
$('#'+sid).remove() $('#'+sid).remove()
if( sid == 'series-div-1' ) { if( sid == 'series-div-1' ) {
console.log("remove Book: and it was the only series, so hide series-div-1 and put 'plus / empty' div back" ) console.log("remove Book: and it was the only series, so hide series-div-1 and put 'plus / empty' div back" )
@@ -94,7 +94,7 @@ function AddAuthorToBook(num) {
<div id="series_plus_only_tmpl" class="col input-group px-0" style="display:none"> <div id="series_plus_only_tmpl" class="col input-group px-0" style="display:none">
<span class="form-control"> </span> <span class="form-control"> </span>
<div id="series_plus_but_div" class="input-group-append"> <div id="series_plus_but_div" class="input-group-append">
<button class="btn btn-outline-success" type="button" onClick="AddBookToSeries({{books.id}},1)"><i class="fas fa-plus"></i></button> <button class="btn btn-outline-success" type="button" onClick="AddBookToSeries()"><i class="fas fa-plus"></i></button>
</div> </div>
</div> </div>
@@ -102,7 +102,7 @@ function AddAuthorToBook(num) {
<div class="container-fluid"> <div class="container-fluid">
{% if message|length %} {% if message|length %}
<div class="row alert alert-{{alert}}"> <div class="row alert alert-{{alert}}">
{{message}} {{message|safe}}
</div> </div>
{% endif %} {% endif %}
<div class="row"> <div class="row">
@@ -160,6 +160,16 @@ function AddAuthorToBook(num) {
</div class="col"> </div class="col">
{% set cnt.idx = cnt.idx+1 %} {% set cnt.idx = cnt.idx+1 %}
{% endfor %} {% endfor %}
{% if cnt.idx == 0 %}
<div id="auth-div-{{cnt.idx}}" class="col input-group px-0">
<select class="form-control" name="author-{{cnt.idx}}" id="author-{{cnt.idx}}">
{% for auth in author_list %}
{% set aname=auth.surname+", "+auth.firstnames %}
<option value="{{auth.id}}">{{aname}}</option>
{% endfor %}
</select>
</div class="col">
{% endif %}
<div class="input-group-append"> <div class="input-group-append">
<button id="author-plus" class="btn btn-outline-success" type="button" onClick="AddAuthorToBook({{cnt.idx}})"><i class="fas fa-plus"></i></button> <button id="author-plus" class="btn btn-outline-success" type="button" onClick="AddAuthorToBook({{cnt.idx}})"><i class="fas fa-plus"></i></button>
</div> </div>
@@ -181,11 +191,11 @@ function AddAuthorToBook(num) {
{% endif %} {% endif %}
</div class="form-row"> </div class="form-row">
{% endfor %} {% endfor %}
<div class="form-row"> <div id="series_row" class="form-row">
<label for="series" class="col-lg-2 col-form-label">Series:</label> <label for="series" class="col-lg-2 col-form-label">Series:</label>
<div class="form-row col mx-0"> <div class="form-row col mx-0">
{# use series-div-0 as a template & keep it hidden always #} {# use series-div-0 as a template & keep it hidden always #}
<div id="series-div-0" class="col input-group px-0" style="display:none" actual_series_data="0"> <div id="series-div-0" class="col input-group px-0" style="display:none">
<div class="input-group-prepend"> <div class="input-group-prepend">
<button id="series_minus_but" class="btn btn-outline-danger" type="button"><i class="fas fa-minus"></i></button> <button id="series_minus_but" class="btn btn-outline-danger" type="button"><i class="fas fa-minus"></i></button>
</div> </div>
@@ -199,15 +209,15 @@ function AddAuthorToBook(num) {
{% endfor %} {% endfor %}
</select> </select>
<div id="series_plus_but_div" class="input-group-append"> <div id="series_plus_but_div" class="input-group-append">
<button class="btn btn-outline-success" type="button" onClick="AddBookToSeries({{books.id}},0)"><i class="fas fa-plus"></i></button> <button class="btn btn-outline-success" type="button" onClick="AddBookToSeries()"><i class="fas fa-plus"></i></button>
</div> </div>
</div> </div id="series-div-0">
{% if books.series|length %} {% if books.series|length %}
{% for s in books.series %} {% for s in books.series %}
<div id="series-div-{{loop.index}}" class="col input-group px-0" actual_series_data="1"> <div id="series-div-{{loop.index}}" class="col input-group px-0">
{% if SeriesBookNum( s.id, books.id ) %} {% if SeriesBookNum( s.id, books.id ) %}
<div class="input-group-prepend"> <div class="input-group-prepend">
<button class="btn btn-outline-danger" type="button" onClick="RemoveBookFromSeries({{books.id}},'series-div-{{loop.index}}')"><i class="fas fa-minus"></i></button> <button class="btn btn-outline-danger" type="button" onClick="RemoveBookFromSeries('series-div-{{loop.index}}')"><i class="fas fa-minus"></i></button>
</div> </div>
<div class="form-control"> <div class="form-control">
Book {{ SeriesBookNum( s.id, books.id ) }} of {{s.num_books}} in <a href=/series/{{s.id}}>{{s.title}}</a> Book {{ SeriesBookNum( s.id, books.id ) }} of {{s.num_books}} in <a href=/series/{{s.id}}>{{s.title}}</a>
@@ -215,7 +225,7 @@ function AddAuthorToBook(num) {
<input type="hidden" name="bsl-book_num-{{loop.index}}" value={{SeriesBookNum( s.id, books.id )}}> <input type="hidden" name="bsl-book_num-{{loop.index}}" value={{SeriesBookNum( s.id, books.id )}}>
{% else %} {% else %}
<div class="input-group-prepend"> <div class="input-group-prepend">
<button class="btn btn-outline-danger" type="button" onClick="RemoveBookFromSeries({{books.id}},'series-div-{{loop.index}}')"><i class="fas fa-minus"></i></button> <button class="btn btn-outline-danger" type="button" onClick="RemoveBookFromSeries('series-div-{{loop.index}}')"><i class="fas fa-minus"></i></button>
</div> </div>
<div class="form-control"> <div class="form-control">
Contains books in <a href='/series/{{s.id}}'>{{s.title}}</a> Contains books in <a href='/series/{{s.id}}'>{{s.title}}</a>
@@ -224,21 +234,21 @@ function AddAuthorToBook(num) {
{% endif %} {% endif %}
<input type="hidden" name="bsl-book_id-{{loop.index}}" value={{books.id}}> <input type="hidden" name="bsl-book_id-{{loop.index}}" value={{books.id}}>
<input type="hidden" name="bsl-series_id-{{loop.index}}" value={{s.id}}> <input type="hidden" name="bsl-series_id-{{loop.index}}" value={{s.id}}>
</div id="series-div-{{loop.index}}>
{% endfor %} {% endfor %}
<div id="series_plus_but_div" class="input-group-append"> <div id="series_plus_but_div" class="input-group-append">
<button class="btn btn-outline-success" type="button" onClick="AddBookToSeries({{books.id}},1)"><i class="fas fa-plus"></i></button> <button class="btn btn-outline-success" type="button" onClick="AddBookToSeries()"><i class="fas fa-plus"></i></button>
</div> </div>
{% else %} {% else %}
<div id="series_plus_only" class="col input-group px-0" actual_series_data="0"> <div id="series_plus_only" class="col input-group px-0">
<span class="form-control"> </span> <span class="form-control"> </span>
<div id="series_plus_but_div" class="input-group-append"> <div id="series_plus_but_div" class="input-group-append">
<button class="btn btn-outline-success" type="button" onClick="AddBookToSeries({{books.id}},1)"><i class="fas fa-plus"></i></button> <button class="btn btn-outline-success" type="button" onClick="AddBookToSeries()"><i class="fas fa-plus"></i></button>
</div> </div>
</div> </div>
{% endif %} {% endif %}
</div> </div id="series_row" class="form-row col mx-0">
</div class="form-row col mx-0"> </div id="wwwww" class="form-row">
</div class="form-row">
{% if books.child_ref|length %} {% if books.child_ref|length %}
<div class="form-row"> <div class="form-row">
<label class="col-lg-2 col-form-label">Sub Books:</label> <label class="col-lg-2 col-form-label">Sub Books:</label>
@@ -247,9 +257,15 @@ function AddAuthorToBook(num) {
</div> </div>
{% endif %} {% endif %}
<div id="spacer"><br></div> <div id="spacer"><br></div>
<div class="form-row col-lg-12"> <div class="form-row">
{{ book_form.delete( class="btn btn-outline-danger offset-lg-2 col-lg-2" )}} <div class="form-row col mx-0">
{{ book_form.submit( class="btn btn-primary col-lg-2" )}} {{ book_form.delete( class="btn btn-outline-danger offset-lg-2 col-lg-2" )}}
{{ book_form.submit( class="btn btn-primary col-lg-2" )}}
{% if b.parent|length == 0 %}
<input type="hidden" name="add_sub_parent_id" value="{{books.id}}">
{{ book_form.add_sub( class="btn btn-outline-success offset-lg-4 col-lg-2" )}}
{% endif %}
</div class="form-row">
</div class="form-row"> </div class="form-row">
</form> </form>
{% if books.loan|length %} {% if books.loan|length %}