add/remove series from a book now works, havent thought about removing a parent book fro a series, but otherwise you can add more than one series, remove as well, and it works in the DB

This commit is contained in:
2020-12-13 22:21:38 +11:00
parent e3d21a9438
commit c102b93872
5 changed files with 154 additions and 17 deletions

2
BUGs
View File

@@ -4,3 +4,5 @@
* book form is not validating (year published) * book form is not validating (year published)
* add/remove authors, and after save they are ordered by author.id, not order of addition (prob. needs book_author_link to have an auth_num) * add/remove authors, and after save they are ordered by author.id, not order of addition (prob. needs book_author_link to have an auth_num)
* 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?)
* series: 112, book/sub-book ordering is broken

2
README
View File

@@ -29,7 +29,7 @@ python3 main.py
offer to move parent/child series orders as well to match (think Thomas Covenant) offer to move parent/child series orders as well to match (think Thomas Covenant)
### TODO: ### TODO:
- need to create series, loan, book classes (and make green + button work) - when remove a Parent book from a series, what do we do?
- need to delete all classes (and watch for referential integrity) - need to delete all classes (and watch for referential integrity)
- need to add 1 author to book, book to sub_book, series - need to add 1 author to book, book to sub_book, series
- need to delete 1 author from book - need to delete 1 author from book

19
main.py
View File

@@ -5,6 +5,7 @@ from flask_bootstrap import Bootstrap
from wtforms import SubmitField, StringField, HiddenField, SelectField, IntegerField, TextAreaField, validators from wtforms import SubmitField, StringField, HiddenField, SelectField, IntegerField, TextAreaField, validators
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from status import st, Status from status import st, Status
import re
####################################### Flask App globals ####################################### ####################################### Flask App globals #######################################
app = Flask(__name__) app = Flask(__name__)
@@ -26,7 +27,7 @@ from covertype import Covertype, CovertypeForm, CovertypeSchema, GetCovertypeByI
from owned import Owned, OwnedForm, OwnedSchema, GetOwnedById from owned import Owned, OwnedForm, OwnedSchema, GetOwnedById
from rating import Rating, RatingForm, RatingSchema from rating import Rating, RatingForm, RatingSchema
from loan import Loan, LoanForm, LoanSchema from loan import Loan, LoanForm, LoanSchema
from series import Series, SeriesForm, SeriesSchema from series import Series, SeriesForm, SeriesSchema, ListOfSeriesWithMissingBooks
####################################### CLASSES / DB model ####################################### ####################################### CLASSES / DB model #######################################
book_author_link = db.Table('book_author_link', db.Model.metadata, book_author_link = db.Table('book_author_link', db.Model.metadata,
@@ -77,6 +78,7 @@ class Book(db.Model):
genre = db.relationship('Genre', secondary=book_genre_link ) genre = db.relationship('Genre', secondary=book_genre_link )
loan = db.relationship('Loan', secondary=Book_Loan_Link.__table__); loan = db.relationship('Loan', secondary=Book_Loan_Link.__table__);
series = db.relationship('Series', secondary=Book_Series_Link.__table__); series = db.relationship('Series', secondary=Book_Series_Link.__table__);
bsl = db.relationship('Book_Series_Link' )
year_published = db.Column(db.Integer) year_published = db.Column(db.Integer)
condition = db.Column(db.Integer, db.ForeignKey('condition.id')) condition = db.Column(db.Integer, db.ForeignKey('condition.id'))
covertype = db.Column(db.Integer, db.ForeignKey('covertype.id')) covertype = db.Column(db.Integer, db.ForeignKey('covertype.id'))
@@ -212,6 +214,7 @@ app.jinja_env.globals['GetConditionById'] = GetConditionById
app.jinja_env.globals['GetPublisherById'] = GetPublisherById app.jinja_env.globals['GetPublisherById'] = GetPublisherById
app.jinja_env.globals['SeriesBookNum'] = SeriesBookNum app.jinja_env.globals['SeriesBookNum'] = SeriesBookNum
app.jinja_env.globals['ClearStatus'] = st.ClearMessage app.jinja_env.globals['ClearStatus'] = st.ClearMessage
app.jinja_env.globals['ListOfSeriesWithMissingBooks'] = ListOfSeriesWithMissingBooks
book_schema = BookSchema() book_schema = BookSchema()
@@ -367,6 +370,18 @@ def book(id):
if 'author-' in el: if 'author-' in el:
book.author.append( Author.query.get( request.form[el] ) ) book.author.append( Author.query.get( request.form[el] ) )
print( "bsl={}".format( book.bsl ))
# delete all bsls
db.engine.execute("delete from book_series_link where book_id = {}".format( book.id ) )
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])
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)])
db.engine.execute( sql )
cnt=cnt+1
## TODO: ## TODO:
# what about series?, subbooks?, loan?, etc. # what about series?, subbooks?, loan?, etc.
db.session.commit() db.session.commit()
@@ -382,7 +397,7 @@ def book(id):
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 ) 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() )
@app.route("/", methods=["GET"]) @app.route("/", methods=["GET"])
def main_page(): def main_page():

View File

@@ -44,6 +44,13 @@ def CalcAvgRating(sid):
print( row ) print( row )
return row.rating return row.rating
def ListOfSeriesWithMissingBooks():
res=db.engine.execute("select id, title, num_books, count from ( select s.id, s.title, s.num_books, count(bsl.book_id) from series s, book_series_link bsl where s.id = bsl.series_id group by s.id ) as foo where num_books > count")
tmp=[]
for row in res:
tmp.append({'id': row[0], 'title': row[1]})
return tmp
################################################################################ ################################################################################
# Routes for series data # Routes for series data
# #

View File

@@ -2,6 +2,69 @@
{% block main_content %} {% block main_content %}
<script> <script>
function AddBookToSeries(bid) {
console.log("add Book (" +bid + ") to series")
// 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
var last_div = $('div[id^="series-div-"]:last');
var num = parseInt( last_div.prop("id").match(/\d+/g), 10 );
console.log( "num=" + num )
console.log( "last_div is=" + last_div.prop('id') )
// Is it shown? IF yes, copy / add a new one ELSE its the first one, so don't make another, just show it
$('#series_plus_only').hide();
num++
// if we have more than 1 series, lets fix the buttons
if( num > 1 ) {
// disable minus button and hide plus button in old div
last_div.find('#series_minus_but').addClass('disabled')
last_div.find('#series_minus_but').prop('disabled', true )
last_div.find('#series_plus_but_div').hide()
console.log( "reset buttons on last_div is=" + last_div.prop('id') )
}
// clone the template
var new_div = $('#series-div-0').clone()
// reset id/names to new series-div- created (for id on div, name on select
// for series_id and input text for book_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('#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-book_num-NUM').prop('name', 'bsl-book_num-'+num )
// insert new_div after the old div
last_div.after( new_div );
new_div.show()
}
function RemoveBookFromSeries(bid, sid) {
console.log("remove Book: " + bid + " from Series: " + sid )
$('#'+sid).remove()
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" )
if( $('#series_plus_only').length == 0 ) {
new_div = $('#series_plus_only_tmpl').clone()
new_div.prop('id', 'series_plus_only')
$('#series-div-0').after( new_div )
}
$('#series_plus_only').show();
} else {
console.log("remove Book: and there are more than 1 'new' series (not in DB), so delete this one")
//now find 'last div still visible, re-enable its minus button and add back plus button
var div = $('div[id^="series-div-"]:last');
div.find('#series_minus_but').removeClass('disabled')
div.find('#series_minus_but').prop('disabled', false )
div.find('#series_plus_but_div').show()
}
}
function RemoveAuthorFromBook(num) { function RemoveAuthorFromBook(num) {
console.log("remove an author at slot: " + num ) console.log("remove an author at slot: " + num )
$('#auth-div-'+num).remove() $('#auth-div-'+num).remove()
@@ -28,6 +91,13 @@ function AddAuthorToBook(num) {
} }
</script> </script>
<div id="series_plus_only_tmpl" class="col input-group px-0" style="display:none">
<span class="form-control"> </span>
<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>
</div>
</div>
{% set keys = [ 'title', 'author', 'publisher', 'genre', 'owned', 'covertype', 'condition', 'year_published', 'rating', 'notes', 'blurb' ] %} {% set keys = [ 'title', 'author', 'publisher', 'genre', 'owned', 'covertype', 'condition', 'year_published', 'rating', 'notes', 'blurb' ] %}
<div class="container-fluid"> <div class="container-fluid">
{% if message|length %} {% if message|length %}
@@ -111,22 +181,64 @@ function AddAuthorToBook(num) {
{% endif %} {% endif %}
</div class="form-row"> </div class="form-row">
{% endfor %} {% endfor %}
{% if books.series|length %}
<div class="form-row"> <div 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>
<table> <div class="form-row col mx-0">
{% for s in books.series %} {# use series-div-0 as a template & keep it hidden always #}
<tr><td> <div id="series-div-0" class="col input-group px-0" style="display:none" actual_series_data="0">
{% if SeriesBookNum( s.id, books.id ) %} <div class="input-group-prepend">
<label class="form-control-plaintext">Book {{ SeriesBookNum( s.id, books.id ) }} of {{s.num_books}} in <a href="/series/{{s.id}}">{{s.title}}</a></label> <button id="series_minus_but" class="btn btn-outline-danger" type="button"><i class="fas fa-minus"></i></button>
{% else %} </div>
<label class="form-control-plaintext">Contains books in <a href='/series/{{s.id}}'>{{s.title}}</a></label> <input type="hidden" id="bsl-book_id-NUM" name="bsl-book_id-NUM" value="{{books.id}}">
{% endif %} <span class="form-control col-lg-2">Book&nbsp;</span>
</td></tr> <input type="text" id="bsl-book_num-NUM" name="bsl-book_num-NUM" class="form-control col-lg-1" placeholder="number">
{% endfor %} <span class="form-control col-lg-1">&nbsp;of&nbsp;</span>
</table> <select class="form-control col" id="bsl-series_id-NUM" name="bsl-series_id-NUM">
</div> {% for s in poss_series_list %}
{% endif %} <option value="{{s.id}}">{{s.title}}</option>
{% endfor %}
</select>
<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>
</div>
</div>
{% if books.series|length %}
{% for s in books.series %}
<div id="series-div-{{loop.index}}" class="col input-group px-0" actual_series_data="1">
{% if SeriesBookNum( s.id, books.id ) %}
<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>
</div>
<div class="form-control">
Book {{ SeriesBookNum( s.id, books.id ) }} of {{s.num_books}} in <a href=/series/{{s.id}}>{{s.title}}</a>
</div>
<input type="hidden" name="bsl-book_num-{{loop.index}}" value={{SeriesBookNum( s.id, books.id )}}>
{% else %}
<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>
</div>
<div class="form-control">
Contains books in <a href='/series/{{s.id}}'>{{s.title}}</a>
</div>
<input type="hidden" name="bsl-book_num-{{loop.index}}" value="">
{% endif %}
<input type="hidden" name="bsl-book_id-{{loop.index}}" value={{books.id}}>
<input type="hidden" name="bsl-series_id-{{loop.index}}" value={{s.id}}>
{% endfor %}
<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>
</div>
{% else %}
<div id="series_plus_only" class="col input-group px-0" actual_series_data="0">
<span class="form-control"> </span>
<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>
</div>
</div>
{% endif %}
</div>
</div class="form-row col mx-0">
</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>
@@ -134,6 +246,7 @@ function AddAuthorToBook(num) {
</div> </div>
</div> </div>
{% endif %} {% endif %}
<div id="spacer"><br></div>
<div class="form-row col-lg-12"> <div class="form-row col-lg-12">
{{ book_form.delete( class="btn btn-outline-danger offset-lg-2 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" )}} {{ book_form.submit( class="btn btn-primary col-lg-2" )}}