converted over to bal from author, and fixed up the way sub books / series interact
This commit is contained in:
25
BUGs
25
BUGs
@@ -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
8
TODO
@@ -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
223
main.py
@@ -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'))
|
||||||
|
|||||||
@@ -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] %}
|
||||||
|
|||||||
@@ -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 %}
|
||||||
|
|||||||
Reference in New Issue
Block a user