from flask import Flask, render_template, request from flask_sqlalchemy import SQLAlchemy from flask_marshmallow import Marshmallow from flask_bootstrap import Bootstrap from wtforms import SubmitField, StringField, HiddenField, SelectField, validators, Form from flask_wtf import FlaskForm app = Flask(__name__) ### what is this value? I gather I should chagne it? DB_URL = 'postgresql+psycopg2://ddp:NWNlfa01@127.0.0.1:5432/library' app.config['SQLALCHEMY_DATABASE_URI'] = DB_URL app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False app.config.from_mapping( SECRET_KEY=b'\xd6\x04\xbdj\xfe\xed$c\x1e@\xad\x0f\x13,@G') db = SQLAlchemy(app) ma = Marshmallow(app) Bootstrap(app) from author import Author, AuthorForm, AuthorSchema from publisher import Publisher, PublisherForm, PublisherSchema from genre import Genre, GenreForm, GenreSchema from condition import Condition, ConditionForm, ConditionSchema from covertype import Covertype, CovertypeForm, CovertypeSchema from owned import Owned, OwnedForm, OwnedSchema from rating import Rating, RatingForm, RatingSchema from loan import Loan, LoanForm, LoanSchema ####################################### CLASSES / DB model ####################################### 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_publisher_link = db.Table('book_publisher_link', db.Model.metadata, db.Column('book_id', db.Integer, db.ForeignKey('book.id')), db.Column('publisher_id', db.Integer, db.ForeignKey('publisher.id')) ) book_genre_link = db.Table('book_genre_link', db.Model.metadata, db.Column('book_id', db.Integer, db.ForeignKey('book.id')), db.Column('genre_id', db.Integer, db.ForeignKey('genre.id')) ) class Book_Loan_Link(db.Model): __tablename__ = "book_loan_link" book_id = db.Column(db.Integer, db.ForeignKey('book.id'), unique=True, nullable=False, primary_key=True) loan_id = db.Column(db.Integer, db.ForeignKey('loan.id'), unique=True, nullable=False, primary_key=True) class Book_Sub_Book_Link(db.Model): __tablename__ = "book_sub_book_link" book_id = db.Column(db.Integer, db.ForeignKey('book.id'), unique=True, nullable=False, primary_key=True) sub_book_id = db.Column(db.Integer, db.ForeignKey('book.id'), unique=True, nullable=False, primary_key=True) sub_book_num = db.Column(db.Integer) def __repr__(self): return "".format(self.book_id, self.sub_book_id, self.sub_book_num) class Book(db.Model): id = db.Column(db.Integer, unique=True, nullable=False, primary_key=True) title = db.Column(db.String(100), unique=True, nullable=False) author = db.relationship('Author', secondary=book_author_link) publisher = db.relationship('Publisher', secondary=book_publisher_link) genre = db.relationship('Genre', secondary=book_genre_link ) loan = db.relationship('Loan', secondary=Book_Loan_Link.__table__); year_published = db.Column(db.Integer) condition = db.Column(db.Integer, db.ForeignKey('condition.id')) covertype = db.Column(db.Integer, db.ForeignKey('covertype.id')) owned = db.Column(db.Integer, db.ForeignKey('owned.id')) rating = db.Column(db.Integer, db.ForeignKey('rating.id')) notes = db.Column(db.Text) blurb = db.Column(db.Text) created = db.Column(db.Date) modified = db.Column(db.Date) parent_ref = db.relationship('Book_Sub_Book_Link', secondary=Book_Sub_Book_Link.__table__, primaryjoin="Book.id==Book_Sub_Book_Link.sub_book_id", secondaryjoin="Book.id==Book_Sub_Book_Link.book_id" ) 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" ) def __repr__(self): return "".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 ) class Book_Sub_Book_LinkSchema(ma.SQLAlchemyAutoSchema): class Meta: model = Book_Sub_Book_Link # 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? # so I just hacked a list of keys in book.html class BookSchema(ma.SQLAlchemyAutoSchema): author = ma.Nested(AuthorSchema, many=True) publisher = ma.Nested(PublisherSchema, many=True) genre = ma.Nested(GenreSchema, many=True) loan = ma.Nested(LoanSchema, many=True) parent_ref = ma.Nested(Book_Sub_Book_LinkSchema, many=True) child_ref = ma.Nested(Book_Sub_Book_LinkSchema, many=True) class Meta: model = Book # # To be completed # class BookForm(FlaskForm): # I think I'll have to skip setting default on create, and using jquery to # change it when I create the from? (or maybe I could use a default=set_me # in the line below, then when I set create set_me = book.condition before # bf=BookForm() condition = SelectField( 'condition', choices=[(c.id, c.name) for c in Condition.query.order_by('id')] ) covertype = SelectField( 'covertype', choices=[(c.id, c.name) for c in Covertype.query.order_by('id')] ) owned = SelectField( 'owned', choices=[(c.id, c.name) for c in Owned.query.order_by('id')] ) rating = SelectField( 'rating', choices=[(c.id, c.name) for c in Rating.query.order_by('id')] ) ### DDP: do I need many=True on Author as books have many authors? (or in BookSchema declaration above?) book_schema = BookSchema() books_schema = BookSchema(many=True) ####################################### ROUTES ####################################### @app.route("/books", methods=["GET"]) def books(): books = Book.query.all() # ignore ORM, its too slow. Just select sub_book data and hand add it to # the books object, and use it in jinja2 to indent/order the books/sub books subs = db.engine.execute ( "select * from book_sub_book_link" ) for row in subs: index = next((i for i, item in enumerate(books) if item.id == row.sub_book_id), -1) books[index].parent_id = row.book_id books[index].sub_book_num = row.sub_book_num return render_template("books.html", books=books) @app.route("/books_for_loan/", methods=["GET"]) def books_for_loan(id): # books = db.engine.execute ( "select * from book_loan_link where loan_id = {}".format( id ) ) print( id ) books = Book.query.join(Book_Loan_Link).filter(Book_Loan_Link.loan_id==id).order_by(Book.id).all() print( books ) return render_template("books_for_loan.html", books=books) @app.route("/book/", methods=["GET"]) def book(id): book = Book.query.get(id) book_s = book_schema.dump(book) ###### ### ### okay, this manual hacking of sub_book is currently going to be needed, because in the jinja2 I want to list the book, and more than just the id number of the sub_book, I want the details... (sub_book_schema needs a book relationship BUT, dependencies mean I can't define a book schema inside sub_book schema, and I am definitely not sure how to join it anyway... for another time. ### ##### # force sub books for jinja2 to be able to use subs = db.engine.execute ( "select bsb.book_id, bsb.sub_book_id, bsb.sub_book_num, book.title, book.rating, book.year_published, book.notes, bal.author_id as author_id, author.surname||', '||author.firstnames as author from book_sub_book_link bsb, book, book_author_link bal, author where bsb.book_id = {} and book.id = bsb.sub_book_id and book.id = bal.book_id and bal.author_id = author.id".format( id ) ) sub_book=[] for row in subs: # get genres for sub book and add by hand first tmp_g = [] genres = db.engine.execute ( "select genre.id, genre.name from genre, book_genre_link bgl where genre.id = bgl.genre_id and bgl.book_id = {}".format( row.sub_book_id ) ) for genre in genres: tmp_g.append( { 'id': genre.id, 'name': genre.name } ) sub_book.append( { 'sub_book_id': row.sub_book_id, '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, 'genres' : tmp_g } ) book_s['sub_book'] = sub_book book_form=BookForm(request.form) # set defaults for drop-down's based on this book book_form.condition.default = book.condition book_form.covertype.default = book.covertype book_form.owned.default = book.owned book_form.rating.default = book.rating book_form.process() return render_template("book.html", books=book_s, subs=sub_book, book_form=book_form ) @app.route("/", methods=["GET"]) def main_page(): return render_template("base.html") if __name__ == "__main__": app.run(host="0.0.0.0", debug=True)