converted over to bal from author, and fixed up the way sub books / series interact

This commit is contained in:
2023-07-01 14:26:36 +10:00
parent 90792728b3
commit 6cef2e0d9c
5 changed files with 230 additions and 92 deletions

25
BUGs
View File

@@ -1,14 +1,23 @@
### fix to get this working with bs 5... #### BUGS (next-33)
#### BUGS (next-30) BUG-6: author does not have explicit ordering like sub-books...
- author inserts need author_num [DONE]
- books on shelf now works with new code [DONE]
TEST THIS - ITS A BREAKING CHANGE.... check every where I used
book.author -> book.bals[]->author
### DB/back-end
### UI not updating after DB change:
BUG-7: if you remove a series from a book, it won't appear in the series drop-down if it is the first 'missing' book in that series -- either reset the list, or show all series always? BUG-7: if you remove a series from a book, it won't appear in the series drop-down if it is the first 'missing' book in that series -- either reset the list, or show all series always?
### ordering of data in UI: BUG-30: failed to add a book with 2 x same author (need to catch error
BUG-6: author,series, etc. do not have explicit ordering like sub-books... sort of irritating / needs code and DB fix or maybe mess with author list to remove duplicate potentials?)
- 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)
#### SHOWSTOPPER:
BUG-31: series does not have 'series_num', so a book in 2 series, means he ordering is not explicit
-- UNSURE this is an issue, as 2 series would normally be 1 inside
a larger one, and numbering implies the inner (smaller number), and
outer has explicity overall ordering of books in both series, so
should be ok?
BUG-32: for book 1546 (with 2 sub-books, 1 with 2 authors), saving it causes 'series removal' dbox to come up
(no series though)

8
TODO
View File

@@ -0,0 +1,8 @@
TODO:
# to get from book.author to book.bal
alter table book_author_link add author_num integer;
update book_author_link set author_num = 0;
browse to /fix_an
* book.parent is an array, not a dict - should change it over

223
main.py
View File

@@ -79,16 +79,22 @@ class QuickParentBook:
return "<parent: {}, publisher: {}, owned: {}, covertype: {}, condition: {}, blurb: {}>".format(self.parent, self.publisher, self.owned, self.covertype, self.condition, self.blurb ) return "<parent: {}, publisher: {}, owned: {}, covertype: {}, condition: {}, blurb: {}>".format(self.parent, self.publisher, self.owned, self.covertype, self.condition, self.blurb )
book_author_link = db.Table('book_author_link', db.Model.metadata,
db.Column('book_id', db.Integer, db.ForeignKey('book.id')),
db.Column('author_id', db.Integer, db.ForeignKey('author.id'))
)
book_genre_link = db.Table('book_genre_link', db.Model.metadata, book_genre_link = db.Table('book_genre_link', db.Model.metadata,
db.Column('book_id', db.Integer, db.ForeignKey('book.id')), db.Column('book_id', db.Integer, db.ForeignKey('book.id')),
db.Column('genre_id', db.Integer, db.ForeignKey('genre.id')) db.Column('genre_id', db.Integer, db.ForeignKey('genre.id'))
) )
class Book_Author_Link(db.Model):
__tablename__ = "book_author_link"
book_id = db.Column( db.Integer, db.ForeignKey('book.id'), primary_key=True)
author_id = db.Column( db.Integer, db.ForeignKey('author.id'), primary_key=True)
author_num = db.Column( db.Integer, primary_key=True)
author = db.relationship('Author' )
def __repr__(self):
return f"<book_author_link: id: {self.book_id}, author_id: {self.author_id}, author_num: {self.author_num}>"
class Book_Loan_Link(db.Model): class Book_Loan_Link(db.Model):
__tablename__ = "book_loan_link" __tablename__ = "book_loan_link"
book_id = db.Column(db.Integer, db.ForeignKey('book.id'), unique=True, nullable=False, primary_key=True) book_id = db.Column(db.Integer, db.ForeignKey('book.id'), unique=True, nullable=False, primary_key=True)
@@ -126,7 +132,6 @@ class Book_Sub_Book_Link(db.Model):
class Book(db.Model): class Book(db.Model):
id = db.Column(db.Integer, db.Sequence('book_id_seq'), 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)
publisher = db.Column(db.Integer, db.ForeignKey('publisher.id')) publisher = db.Column(db.Integer, db.ForeignKey('publisher.id'))
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__);
@@ -147,6 +152,8 @@ class Book(db.Model):
# but use child_ref as sub_book_num is per book, and I can't connect an empty array of sub_book_nums to a child book array in "child" # but use child_ref as sub_book_num is per book, and I can't connect an empty array of sub_book_nums to a child book array in "child"
child_ref = db.relationship('Book_Sub_Book_Link', secondary=Book_Sub_Book_Link.__table__, primaryjoin="Book.id==Book_Sub_Book_Link.book_id", secondaryjoin="Book.id==Book_Sub_Book_Link.sub_book_id", order_by="Book_Sub_Book_Link.sub_book_num", overlaps="parent" ) child_ref = db.relationship('Book_Sub_Book_Link', secondary=Book_Sub_Book_Link.__table__, primaryjoin="Book.id==Book_Sub_Book_Link.book_id", secondaryjoin="Book.id==Book_Sub_Book_Link.sub_book_id", order_by="Book_Sub_Book_Link.sub_book_num", overlaps="parent" )
# use this to manage author now that author_num is used to order them
bals = db.relationship('Book_Author_Link', order_by="Book_Author_Link.author_num" )
def IsParent(self): def IsParent(self):
if len(self.child_ref): if len(self.child_ref):
@@ -201,7 +208,7 @@ class Book(db.Model):
return redirect( '/series/{}'.format(series_id) ) return redirect( '/series/{}'.format(series_id) )
def __repr__(self): def __repr__(self):
return "<id: {}, author: {}, title: {}, year_published: {}, rating: {}, condition: {}, owned: {}, covertype: {}, notes: {}, blurb: {}, created: {}, modified: {}, publisher: {}, genre: {}, parent: {}>".format(self.id, self.author, self.title, self.year_published, self.rating, self.condition, self.owned, self.covertype, self.notes, self.blurb, self.created, self.modified, self.publisher, self.genre, self.parent ) return "<id: {}, title: {}, year_published: {}, rating: {}, condition: {}, owned: {}, covertype: {}, notes: {}, blurb: {}, created: {}, modified: {}, publisher: {}, genre: {}, parent: {}>".format(self.id, self.title, self.year_published, self.rating, self.condition, self.owned, self.covertype, self.notes, self.blurb, self.created, self.modified, self.publisher, self.genre, self.parent )
class Book_Sub_Book_LinkSchema(ma.SQLAlchemyAutoSchema): class Book_Sub_Book_LinkSchema(ma.SQLAlchemyAutoSchema):
class Meta: model = Book_Sub_Book_Link class Meta: model = Book_Sub_Book_Link
@@ -209,11 +216,16 @@ class Book_Sub_Book_LinkSchema(ma.SQLAlchemyAutoSchema):
class Book_Series_LinkSchema(ma.SQLAlchemyAutoSchema): class Book_Series_LinkSchema(ma.SQLAlchemyAutoSchema):
class Meta: model = Book_Series_Link class Meta: model = Book_Series_Link
class Book_Author_LinkSchema(ma.SQLAlchemyAutoSchema):
class Meta: model = Book_Author_Link
author = ma.Nested(AuthorSchema )
# Note not ordering this in the code below as, I can't work out how to use # Note not ordering this in the code below as, I can't work out how to use
# jinja2 to iterate over orderedDict - seems it doesnt support it? # jinja2 to iterate over orderedDict - seems it doesnt support it?
# so I just hacked a list of keys in book.html # so I just hacked a list of keys in book.html
class BookSchema(ma.SQLAlchemyAutoSchema): class BookSchema(ma.SQLAlchemyAutoSchema):
author = ma.Nested(AuthorSchema, many=True) author = ma.Nested(AuthorSchema, many=True)
bals = ma.Nested(Book_Author_LinkSchema, many=True)
genre = ma.Nested(GenreSchema, many=True) genre = ma.Nested(GenreSchema, many=True)
loan = ma.Nested(LoanSchema, many=True) loan = ma.Nested(LoanSchema, many=True)
series = ma.Nested(SeriesSchema, many=True) series = ma.Nested(SeriesSchema, many=True)
@@ -239,7 +251,6 @@ class BookForm(FlaskForm):
rem_sub = SubmitField('Remove Sub-Book from Parent' ) rem_sub = SubmitField('Remove Sub-Book from Parent' )
def validate(self): def validate(self):
print( f"type: self.errors={type(self.errors)}" )
# if on wish list, just accept year_published # if on wish list, just accept year_published
if int(self.owned.data) == ON_WISHLIST: if int(self.owned.data) == ON_WISHLIST:
return True return True
@@ -489,20 +500,28 @@ def subbooks_for_book(id):
# force sub books for jinja2 to be able to use (ORM is not giving all the details # force sub books for jinja2 to be able to use (ORM is not giving all the details
#################################### ####################################
with db.engine.connect() as conn: with db.engine.connect() as conn:
subs = conn.exec_driver_sql( "select bsb.book_id, bsb.sub_book_id, bsb.sub_book_num, book.title, \ subs = conn.exec_driver_sql( f"select bsb.book_id, bsb.sub_book_id, bsb.sub_book_num, book.title, \
r.name as rating, book.year_published, book.notes, \ r.name as rating, book.year_published, book.notes, \
bal.author_id as author_id, author.surname||', '||author.firstnames as author \ author.surname||', '||author.firstnames as author \
from book_sub_book_link bsb, book, book_author_link bal, author, rating r\ from book_sub_book_link bsb, book, book_author_link bal, author, rating r\
where bsb.book_id = {} and book.id = bsb.sub_book_id and book.id = bal.book_id and \ where bsb.book_id = {id} and book.id = bsb.sub_book_id and book.id = bal.book_id and \
bal.author_id = author.id and r.id = book.rating \ bal.author_id = author.id and r.id = book.rating \
order by bsb.sub_book_num".format( id ) ) order by bsb.sub_book_num, bal.author_num" )
sub_book=[] sub_book=[]
added=[]
for row in subs: for row in subs:
if row.sub_book_num not in added:
sub_book.append( { 'book_id': row.book_id, 'sub_book_id': row.sub_book_id, \ sub_book.append( { 'book_id': row.book_id, 'sub_book_id': row.sub_book_id, \
'sub_book_num': row.sub_book_num, 'title' : row.title, 'rating': row.rating,\ 'sub_book_num': row.sub_book_num, 'title' : row.title, 'rating': row.rating,\
'year_published' : row.year_published, 'notes' : row.notes, 'author_id' : row.author_id, 'author' : row.author } ) 'year_published' : row.year_published, 'notes' : row.notes, 'author' : row.author } )
else:
# okay, same sub_book_num, so its a second author for this sub_book add name to author
for s in sub_book:
if s['sub_book_num'] == row.sub_book_num:
s['author'] += ", " + row.author
added.append(row.sub_book_num)
return render_template("subbooks_for_book.html", sub_books=sub_book, s2=subs ) return render_template("subbooks_for_book.html", sub_books=sub_book )
################################################################################ ################################################################################
# /remove_subbook -> POST -> removes this subbook from parent, takes you back to # /remove_subbook -> POST -> removes this subbook from parent, takes you back to
@@ -525,6 +544,7 @@ def remove_sub_book():
sb.sub_book_num=sb.sub_book_num-1 sb.sub_book_num=sb.sub_book_num-1
# now remove subbook itself # now remove subbook itself
ClearAuthorsForBook(sub_book.id)
db.session.delete(sub_book) db.session.delete(sub_book)
db.session.commit() db.session.commit()
return redirect( '/book/{}'.format(request.form['rem_sub_parent_id']) ) return redirect( '/book/{}'.format(request.form['rem_sub_parent_id']) )
@@ -549,18 +569,26 @@ def new_book():
page_title='Create new Book' page_title='Create new Book'
author_list = GetAuthors() author_list = GetAuthors()
genre_list = GetGenres() genre_list = GetGenres()
book_authors=[] book_genres = []
bals=[]
auth_cnt=1
if request.method == 'POST':
# handle author info for new book
for el in request.form: for el in request.form:
if 'author-' in el: if 'author-' in el:
book_authors.append( Author.query.get( request.form[el] ) ) bals.append( Book_Author_Link( author_id=request.form[el], author_num=auth_cnt ) )
book_genres = [] auth_cnt+=1
# handle genre info for new book
for genre in genre_list: for genre in genre_list:
if "genre-{}".format(genre.id) in request.form: if "genre-{}".format(genre.id) in request.form:
book_genres.append( genre ) book_genres.append( genre )
if request.method == 'POST':
# handle creating a new sub-book of an existing book (add_sub_parent_id) - html / with form data for the new book...
if 'add_sub' in request.form: if 'add_sub' in request.form:
parent=request.form['add_sub_parent_id'] book=Book.query.get(request.form['add_sub_parent_id'])
book = Book.query.get(parent)
bb=QuickParentBook() bb=QuickParentBook()
bb.parent=[] bb.parent=[]
bb.parent.append( { 'id': parent, 'title': book.title } ) bb.parent.append( { 'id': parent, 'title': book.title } )
@@ -573,20 +601,24 @@ def new_book():
return render_template("book.html", page_title='Create new (sub) Book', b=bb, books=None, book_form=form, author_list=author_list, genre_list=genre_list, alert="", message="", poss_series_list=ListOfSeriesWithMissingBooks() ) return render_template("book.html", page_title='Create new (sub) Book', b=bb, books=None, book_form=form, author_list=author_list, genre_list=genre_list, alert="", message="", poss_series_list=ListOfSeriesWithMissingBooks() )
elif form.validate_on_submit() and len(book_genres): elif form.validate_on_submit() and len(book_genres):
if request.form['year_published'].isnumeric(): if request.form['year_published'].isnumeric():
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 ) 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, bals=bals )
else: else:
book = Book( title=request.form['title'], owned=request.form['owned'], covertype=request.form['covertype'], condition=request.form['condition'], publisher=request.form['publisher'], rating=request.form['rating'], notes=request.form['notes'], blurb=request.form['blurb'], genre=book_genres, author=book_authors ) book = Book( title=request.form['title'], owned=request.form['owned'], covertype=request.form['covertype'], condition=request.form['condition'], publisher=request.form['publisher'], rating=request.form['rating'], notes=request.form['notes'], blurb=request.form['blurb'], genre=book_genres, bals=bals )
db.session.add(book) db.session.add(book)
db.session.commit() db.session.commit()
# this is a sub-book we have added for tmp_bal in bals:
tmp_bal.book_id=book.id
books.bals=bals
db.session.commit()
# this is a sub-book we have added (after the data has been entered, now we commit it to DB)
if 'parent_id' in request.form: if 'parent_id' in request.form:
with db.engine.connect() as conn: with db.engine.connect() as conn:
conn.exec_driver_sql( "insert into book_sub_book_link ( book_id, sub_book_id, sub_book_num ) values ( {}, {}, (select COALESCE(MAX(sub_book_num),0)+1 from book_sub_book_link where book_id = {}) )".format( request.form['parent_id'], book.id, request.form['parent_id'] ) ) conn.exec_driver_sql( "insert into book_sub_book_link ( book_id, sub_book_id, sub_book_num ) values ( {}, {}, (select COALESCE(MAX(sub_book_num),0)+1 from book_sub_book_link where book_id = {}) )".format( request.form['parent_id'], book.id, request.form['parent_id'] ) )
parent=Book.query.get(request.form['parent_id']) parent=Book.query.get(request.form['parent_id'])
if len(parent.series) > 0: if len(parent.series) > 0:
# we have added a sub-book to something in a series # we have added a sub-book to something in a series already, so add a bsl for the next book_num
# already, so add a bsl for the next book_num
for s in parent.bsl: for s in parent.bsl:
with db.engine.connect() as conn: with db.engine.connect() as conn:
conn.exec_driver_sql( "insert into book_series_link ( series_id, book_id, book_num ) values ( {}, {}, (select COALESCE(MAX(book_num),0)+1 from book_series_link where series_id={}) )".format( s.series_id, book.id, s.series_id ) ) conn.exec_driver_sql( "insert into book_series_link ( series_id, book_id, book_num ) values ( {}, {}, (select COALESCE(MAX(book_num),0)+1 from book_series_link where series_id={}) )".format( s.series_id, book.id, s.series_id ) )
@@ -607,12 +639,15 @@ def new_book():
for field in form.errors: for field in form.errors:
message = "{}<br>{}={}".format( message, field, form.errors[field] ) message = "{}<br>{}={}".format( message, field, form.errors[field] )
if len(book_genres) == 0: if len(book_genres) == 0:
message = "{}<br>genre=book has to have a genre selected".format( message ) message = "{}<br>book has to have a genre selected".format( message )
if request.form['owned'] != ON_WISHLIST and not request.form['year_published'].isnumeric():
message = "{}<br>book is not on wish list, so needs a year_published between 1850 & 2100".format( message )
print( "ERROR: Failed to create book: {}".format(message) ) print( "ERROR: Failed to create book: {}".format(message) )
if request.form['year_published'].isnumeric(): if request.form['year_published'].isnumeric():
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 ) 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, bals=bals )
else: else:
book = Book( title=request.form["title"], owned=request.form['owned'], covertype=request.form['covertype'], condition=request.form['condition'], publisher=request.form['publisher'], rating=request.form['rating'], notes=request.form['notes'], blurb=request.form['blurb'], genre=book_genres, author=book_authors ) book = Book( title=request.form["title"], owned=request.form['owned'], covertype=request.form['covertype'], condition=request.form['condition'], publisher=request.form['publisher'], rating=request.form['rating'], notes=request.form['notes'], blurb=request.form['blurb'], genre=book_genres, bals=bals )
if 'parent_id' in request.form: if 'parent_id' in request.form:
bb=QuickParentBook() bb=QuickParentBook()
@@ -625,6 +660,45 @@ def new_book():
return render_template("book.html", page_title=page_title, b=None, books=None, book_form=form, author_list=author_list, genre_list=genre_list, alert="success", message="", poss_series_list=ListOfSeriesWithMissingBooks() ) return render_template("book.html", page_title=page_title, b=None, books=None, book_form=form, author_list=author_list, genre_list=genre_list, alert="success", message="", poss_series_list=ListOfSeriesWithMissingBooks() )
def ClearAuthorsForBook(id):
with db.engine.connect() as conn:
res = conn.exec_driver_sql( f"delete from book_author_link where book_id = {id}" )
db.session.commit()
# helper function to reduce code size for /book/<id>/ route - handles deleting book
def DeleteBook(id):
book = Book.query.get(id)
if book.IsParent():
st.SetAlert( "danger" )
st.SetMessage( "This is a parent book, cannot delete it without deleting sub books first" )
return id
else:
st.SetAlert("success")
st.SetMessage("Deleted {}".format(book.title) )
pid = 0
if book.IsChild():
pid = book.parent[0].id
try:
ClearAuthorsForBook(book.id)
db.session.delete(book)
db.session.commit()
except SQLAlchemyError as e:
st.SetAlert( "danger" )
st.SetMessage( e.orig )
return id
except Exception as e:
st.SetAlert( "danger" )
if "Dependency rule tried to blank-out primary key":
st.SetMessage( f"<b>Failed to delete book:</b> The book has a link to an another table (ddp messed up DB integrity) -- { str(e) }" )
else:
st.SetMessage( f"<b>Failed to delete book:</b>nbsp; {str(e)} ")
return id
if pid > 0:
return pid
else:
return None
# handle book view / update / delete route
@app.route("/book/<id>", methods=["GET", "POST"]) @app.route("/book/<id>", methods=["GET", "POST"])
@login_required @login_required
def book(id): def book(id):
@@ -638,36 +712,13 @@ def book(id):
CheckSeriesChange=None CheckSeriesChange=None
if request.method == 'POST': if request.method == 'POST':
if 'delete' in request.form: if 'delete' in request.form:
book = Book.query.get(id) redirect_to=DeleteBook(id)
if book.IsParent(): if redirect_to == None:
st.SetAlert( "danger" ) # happens in error conditions only
st.SetMessage( "This is a parent book, cannot delete it without deleting sub books first" )
return redirect( '/book/{}'.format(book.id) )
else:
st.SetAlert("success")
st.SetMessage("Deleted {}".format(book.title) )
pid = 0
if book.IsChild():
pid = book.parent[0].id
try:
db.session.delete(book)
db.session.commit()
except SQLAlchemyError as e:
st.SetAlert( "danger" )
st.SetMessage( e.orig )
return redirect( '/book/{}'.format(id) )
except Exception as e:
st.SetAlert( "danger" )
print("generic error")
if 'Dependency rule tried to blank-out primary key':
st.SetMessage( "<b>Failed to delete book:</b> The book has a link to an another table (probably a series) -- {} ".format( str(e) ) )
else:
st.SetMessage( "<b>Failed to delete book:</b>nbsp; {} ".format( str(e) ) )
return redirect( '/book/{}'.format(id) )
if pid > 0:
return redirect( '/book/{}'.format(pid) )
else:
return redirect( '/' ) return redirect( '/' )
else:
# could return to parent book, or current book depending on what was delted
return redirect( f"/book/{redirect_to}" )
# save/update of book # save/update of book
elif book_form.validate(): elif book_form.validate():
book = Book.query.get(id) book = Book.query.get(id)
@@ -687,23 +738,23 @@ def book(id):
for genre in genre_list: for genre in genre_list:
if "genre-{}".format(genre.id) in request.form: if "genre-{}".format(genre.id) in request.form:
book.genre.append( genre ) book.genre.append( genre )
# set book author (empty list, in form they are in author-0, author-1, ... author-n) # set book author (empty list) - cant use ORM, not sure why
# so use find them all and append them back to now empty list ClearAuthorsForBook( book.id )
book.author=[] # then use form data -> author-0, author-1, ... author-n) & append them back to now empty list
cnt=1
for el in request.form: for el in request.form:
if 'author-' in el: if 'author-' in el:
book.author.append( Author.query.get( request.form[el] ) ) book.bals.append( Book_Author_Link( author_id=request.form[el], book_id=id, author_num=cnt ) )
cnt += 1
# go through form, if we have removed a series, then copy data out of form to be passed into html for a pop-up
removing_series=[] removing_series=[]
for field in request.form: for field in request.form:
if 'removed-book_num' in field: if 'removed-book_num' in field:
cnt=int(re.findall( '\d+', field )[0]) cnt=int(re.findall( '\d+', field )[0])
removing_series.append( { 'series_id' : request.form['removed-series_id-{}'.format(cnt)] } ) removing_series.append( { 'series_id' : request.form['removed-series_id-{}'.format(cnt)] } )
still_in_series=0
if book.IsParent(): if book.IsParent():
for field in request.form:
if 'bsl-book_num-' in field and field != 'bsl-book_num-NUM' and request.form[field] == 'PARENT':
still_in_series=1
# go through children, and force sync any changes of physical parts of parent book # go through children, and force sync any changes of physical parts of parent book
tmp_book=book_schema.dump(book) tmp_book=book_schema.dump(book)
for ch_ref in tmp_book['child_ref']: for ch_ref in tmp_book['child_ref']:
@@ -715,14 +766,17 @@ def book(id):
child_book.condition=book.condition child_book.condition=book.condition
child_book.blurb=book.blurb child_book.blurb=book.blurb
# if removing_series has something in it, then handle it # okay, saving a parent book and we ARE removing a series, then
if book.IsChild() or (book.IsParent() and not still_in_series): # need to pop-up for user, to ask what they want to do with sub-books
if book.IsParent(): # and the series (likely remove them all too, but maybe just the parent?)
if book.IsParent() and len(removing_series) > 0:
CheckSeriesChange={'type':'parent', 'pid': book.id, 'bid': book.id, 'removing_series': removing_series } CheckSeriesChange={'type':'parent', 'pid': book.id, 'bid': book.id, 'removing_series': removing_series }
else: # saving a child / sub_book, consider series
if book.IsChild() and len(removing_series) > 0:
CheckSeriesChange={'type':'child', 'pid': book.parent[0].id, 'bid': book.id, 'removing_series': removing_series } CheckSeriesChange={'type':'child', 'pid': book.parent[0].id, 'bid': book.id, 'removing_series': removing_series }
else: else:
# delete all bsls # either we are a normal book (no parent/child), OR not removing a series, might be adding though, so easiest is to
# delete all bsls and then add them back based on the request.form
Book_Series_Link.query.filter(Book_Series_Link.book_id == book.id ).delete() Book_Series_Link.query.filter(Book_Series_Link.book_id == book.id ).delete()
cnt=1 cnt=1
@@ -736,9 +790,14 @@ def book(id):
newbsl=Book_Series_Link( book_id=request.form[f'bsl-book_id-{cnt}'], newbsl=Book_Series_Link( book_id=request.form[f'bsl-book_id-{cnt}'],
series_id=request.form[f'bsl-series_id-{cnt}'], series_id=request.form[f'bsl-series_id-{cnt}'],
book_num=request.form[f'bsl-book_num-{cnt}'] ) book_num=request.form[f'bsl-book_num-{cnt}'] )
# add the contains (null for book_num) bsl for the parent book
if book.IsChild():
parent_bsl=Book_Series_Link( book_id=book.parent[0].id, series_id=request.form[f'bsl-series_id-{cnt}'] )
db.session.add(parent_bsl)
db.session.add(newbsl) db.session.add(newbsl)
db.session.commit() db.session.commit()
# reset rating on this series as the book has changed (and maybe the rating has changed)
# reset rating on this/these series as the book has changed (and maybe the rating has changed)
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])
@@ -849,7 +908,8 @@ def books_on_shelf():
# author, title & the moment we hit a series, the rest of those books are ordered by series # author, title & the moment we hit a series, the rest of those books are ordered by series
# start with all books owned by title, but it includes sub-books, so remove them... # start with all books owned by title, but it includes sub-books, so remove them...
books = Book.query.join(Owned).filter(Owned.name=='Currently Owned').order_by(Book.title).all() # books = Book.query.join(Owned).filter(Owned.name=='Currently Owned').order_by(Book.title).all()
books = Book.query.join(Owned,Book_Author_Link,Author).filter(Owned.name=='Currently Owned',Book_Author_Link.author_num==1).order_by(Author.surname,Author.firstnames,Book.title).all()
RemSubs(books) RemSubs(books)
# because a book can be in multiple series, without any ordering we need to find # because a book can be in multiple series, without any ordering we need to find
@@ -890,7 +950,8 @@ def books_on_shelf():
# book not in a series or a sub-book, so just add this book to the ordered list # book not in a series or a sub-book, so just add this book to the ordered list
ordered_books.append(b) ordered_books.append(b)
return render_template("books.html", books=ordered_books, page_title="Books on Shelf", order_by="Author(s)", show_cols='', hide_cols='' ) # return render_template("books.html", books=ordered_books, page_title="Books on Shelf", order_by="Author(s)", show_cols='', hide_cols='' )
return render_template("books.html", books=ordered_books, page_title="Books on Shelf", order_by="", show_cols='', hide_cols='' )
@app.route("/unrated_books", methods=["GET"]) @app.route("/unrated_books", methods=["GET"])
@login_required @login_required
@@ -962,10 +1023,26 @@ def poor_rating_books():
return render_template("books.html", books=books, page_title="Books that have a Poor Rating (<5 out of 10)", show_cols='Rating', hide_cols='' ) return render_template("books.html", books=books, page_title="Books that have a Poor Rating (<5 out of 10)", show_cols='Rating', hide_cols='' )
@app.route("/fix_an")
@login_required
def fix_an():
books=Book.query.all();
for b in books:
cnt=1
for a in b.author:
bal=Book_Author_Link.query.filter(Book_Author_Link.book_id==b.id,Book_Author_Link.author_id==a.id,Book_Author_Link.author_num == 0 ).first()
bal.author_num=cnt
db.session.add(bal)
cnt+=1
db.session.commit()
return render_template("base.html", alert="success", message="Fixed author numbering" )
# default page, just the navbar # default page, just the navbar
@app.route("/", methods=["GET"]) @app.route("/", methods=["GET"])
@login_required @login_required
def main_page(): def main_page():
# Redirect users who are not logged in. # Redirect users who are not logged in.
if not current_user or current_user.is_anonymous: if not current_user or current_user.is_anonymous:
return redirect(url_for('login')) return redirect(url_for('login'))

View File

@@ -200,7 +200,11 @@ function AddAuthorToBook(num) {
} }
</script> </script>
{% set keys = [ 'title', 'author', 'publisher', 'genre', 'owned', 'covertype', 'condition', 'year_published', 'rating', 'notes', 'blurb' ] %} {#
{% set keys = [ 'title', 'bals', 'author', 'publisher', 'genre', 'owned', 'covertype', 'condition', 'year_published', 'rating', 'notes', 'blurb' ] %}
#}
{% set keys = [ 'title', 'bals', 'publisher', 'genre', 'owned', 'covertype', 'condition', 'year_published', 'rating', 'notes', 'blurb' ] %}
<div class="container-fluid"> <div class="container-fluid">
<div class="form-row"><h3 class="offset-2">{{page_title}}</h3></div> <div class="form-row"><h3 class="offset-2">{{page_title}}</h3></div>
<div class="row" id="main-row"> <div class="row" id="main-row">
@@ -223,7 +227,11 @@ function AddAuthorToBook(num) {
{% endif %} {% endif %}
{% for key in keys %} {% for key in keys %}
<div class="form-row input-group"> <div class="form-row input-group">
{% if key == "bals" %}
<label for="author(s)" class="input-group-text col-2 justify-content-end">author(s):</label>
{% else %}
<label for="{{key}}" class="input-group-text col-2 justify-content-end">{{key}}:</label> <label for="{{key}}" class="input-group-text col-2 justify-content-end">{{key}}:</label>
{% endif %}
{% if key == "genre" %} {% if key == "genre" %}
<div class="row col-10"> <div class="row col-10">
{% for genre in genre_list %} {% for genre in genre_list %}
@@ -239,6 +247,42 @@ function AddAuthorToBook(num) {
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
{% elif key == "bals" %}
{% set cnt = namespace(idx=1) %}
{% for a in books[key] %}
{% if cnt.idx > 1 %}
<button id="author-rem-{{cnt.idx}}" class="btn btn-outline-danger input-group-append" type="button" onClick="RemoveAuthorFromBook({{cnt.idx}})">
<svg width="22" height="22" fill="currentColor">
<use xlink:href="{{url_for('static', filename='icons.svg')}}#minus"/>
</svg>
</button>
{% endif %}
<select class="form-select input-group-append" name="author-{{cnt.idx}}" id="author-{{cnt.idx}}">
{% for auth in author_list %}
{% set aname=auth.surname+", "+auth.firstnames %}
<option value="{{auth.id}}"
{% if a.author.id == auth.id %}
selected
{% endif %}
>{{aname}}</option>
{% endfor %}
</select>
{% set cnt.idx = cnt.idx+1 %}
{% endfor %}
{# if idx is still 1, then no authors - new book, just put one there - book HAS to have an author #}
{% if cnt.idx == 1 %}
<select class="form-select input-group-append" name="author-1" id="author-1">
{% for auth in author_list %}
{% set aname=auth.surname+", "+auth.firstnames %}
<option value="{{auth.id}}">{{aname}}</option>
{% endfor %}
</select>
<button id="author-plus" class="btn btn-outline-success input-group-append" type="button" onClick="AddAuthorToBook(2)">
{% else %}
<button id="author-plus" class="btn btn-outline-success input-group-append" type="button" onClick="AddAuthorToBook({{cnt.idx}})">
{% endif %}
<svg width="22" height="22" fill="currentColor"><use xlink:href="{{url_for('static', filename='icons.svg')}}#plus"/></svg>
</button>
{% elif key == "author" %} {% elif key == "author" %}
{% set cnt = namespace(idx=0) %} {% set cnt = namespace(idx=0) %}
{% for objects in books[key] %} {% for objects in books[key] %}

View File

@@ -39,8 +39,8 @@
<td data-sort="{{book.id}}"><a href="/book/{{book.id}}">{{book.title}}</a></td> <td data-sort="{{book.id}}"><a href="/book/{{book.id}}">{{book.title}}</a></td>
{% endif %} {% endif %}
<td> <td>
{% for auth in book.author %} {% for auth in book.bals %}
{{ auth['surname'] }}, {{auth['firstnames']}} {{ auth.author.surname}}, {{auth.author.firstnames}}
{% endfor %} {% endfor %}
</td> </td>
{% if not InDBox %} {% if not InDBox %}