diff --git a/.gitignore b/.gitignore index c18dd8d..7a1aba1 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ __pycache__/ +DB_BACKUP/ +static/ diff --git a/author.py b/author.py index 2a1b8ae..59560de 100644 --- a/author.py +++ b/author.py @@ -1,6 +1,7 @@ from wtforms import SubmitField, StringField, HiddenField, validators, Form from flask_wtf import FlaskForm from flask import request, render_template, redirect +from flask_login import login_required, current_user from main import db, app, ma from sqlalchemy import Sequence from sqlalchemy.exc import SQLAlchemyError @@ -41,6 +42,7 @@ class AuthorForm(FlaskForm): # /authors -> GET only -> prints out list of all authors ################################################################################ @app.route("/authors", methods=["GET"]) +@login_required def authors(): authors = Author.query.all() return render_template("authors.html", authors=authors, alert=st.GetAlert(), message=st.GetMessage() ) @@ -50,6 +52,7 @@ def authors(): # /author -> GET/POST -> creates a new author type and when created, takes you back to /authors ################################################################################ @app.route("/author", methods=["GET", "POST"]) +@login_required def new_author(): form = AuthorForm(request.form) page_title='Create new Author' @@ -71,6 +74,7 @@ def new_author(): # /author/ -> GET/POST(save or delete) -> shows/edits/delets a single author ################################################################################ @app.route("/author/", methods=["GET", "POST"]) +@login_required def author(id): ### DDP: should this be request.form or request.values? form = AuthorForm(request.form) diff --git a/condition.py b/condition.py index 6668317..eb850c1 100644 --- a/condition.py +++ b/condition.py @@ -1,6 +1,7 @@ from wtforms import SubmitField, StringField, HiddenField, SelectField, validators from flask import request, render_template, redirect from flask_wtf import FlaskForm +from flask_login import login_required, current_user from main import db, app, ma from sqlalchemy import Sequence from sqlalchemy.exc import SQLAlchemyError @@ -37,6 +38,7 @@ class ConditionForm(FlaskForm): # /conditions -> GET only -> prints out list of all conditions ################################################################################ @app.route("/conditions", methods=["GET"]) +@login_required def conditions(): objects = Condition.query.order_by('id').all() return render_template("show_id_name.html", objects=objects, page_title='Show All Conditions', url_base='condition', alert=st.GetAlert(), message=st.GetMessage() ) @@ -45,6 +47,7 @@ def conditions(): # /condition -> GET/POST -> creates a new condition type and when created, takes you back to /conditions ################################################################################ @app.route("/condition", methods=["GET", "POST"]) +@login_required def new_condition(): form = ConditionForm(request.form) page_title='Create new Condition' @@ -66,6 +69,7 @@ def new_condition(): # /condition/ -> GET/POST(save or delete) -> shows/edits/delets a single condition ################################################################################ @app.route("/condition/", methods=["GET", "POST"]) +@login_required def condition(id): ### DDP: should this be request.form or request.values? form = ConditionForm(request.form) diff --git a/covertype.py b/covertype.py index 9e54807..4c0bd58 100644 --- a/covertype.py +++ b/covertype.py @@ -1,6 +1,7 @@ from wtforms import SubmitField, StringField, HiddenField, SelectField, validators from flask import request, render_template, redirect from flask_wtf import FlaskForm +from flask_login import login_required, current_user from main import db, app, ma from sqlalchemy import Sequence from sqlalchemy.exc import SQLAlchemyError @@ -37,6 +38,7 @@ class CovertypeForm(FlaskForm): # /covertypes -> GET only -> prints out list of all covertypes ################################################################################ @app.route("/covertypes", methods=["GET"]) +@login_required def covertypes(): objects = Covertype.query.order_by('id').all() return render_template("show_id_name.html", objects=objects, page_title='Show All Covertypes', url_base='covertype', alert=st.GetAlert(), message=st.GetMessage() ) @@ -45,6 +47,7 @@ def covertypes(): # /covertype -> GET/POST -> creates a new covertype type and when created, takes you back to /covertypes ################################################################################ @app.route("/covertype", methods=["GET", "POST"]) +@login_required def new_covertype(): form = CovertypeForm(request.form) page_title='Create new Covertype' @@ -67,6 +70,7 @@ def new_covertype(): # /covertype/ -> GET/POST(save or delete) -> shows/edits/delets a single covertype ################################################################################ @app.route("/covertype/", methods=["GET", "POST"]) +@login_required def covertype(id): ### DDP: should this be request.form or request.values? form = CovertypeForm(request.form) diff --git a/genre.py b/genre.py index 83cf304..c6ca2dd 100644 --- a/genre.py +++ b/genre.py @@ -1,6 +1,7 @@ from wtforms import SubmitField, StringField, HiddenField, SelectField, validators from flask import request, render_template, redirect from flask_wtf import FlaskForm +from flask_login import login_required, current_user from main import db, app, ma from sqlalchemy import func, Sequence from sqlalchemy.exc import SQLAlchemyError @@ -37,6 +38,7 @@ class GenreForm(FlaskForm): # /genres -> GET only -> prints out list of all genres ################################################################################ @app.route("/genres", methods=["GET"]) +@login_required def genres(): objects = Genre.query.order_by('id').all() return render_template("show_id_name.html", objects=objects, page_title='Show All Genres', url_base='genre', alert=st.GetAlert(), message=st.GetMessage() ) @@ -45,6 +47,7 @@ def genres(): # /genre -> GET/POST -> creates a new genre type and when created, takes you back to /genres ################################################################################ @app.route("/genre", methods=["GET", "POST"]) +@login_required def new_genre(): form = GenreForm(request.form) page_title='Create new Genre' @@ -66,6 +69,7 @@ def new_genre(): # /genre/ -> GET/POST(save or delete) -> shows/edits/delets a single genre ################################################################################ @app.route("/genre/", methods=["GET", "POST"]) +@login_required def genre(id): ### DDP: should this be request.form or request.values? form = GenreForm(request.form) diff --git a/loan.py b/loan.py index 100f6d1..4a64818 100644 --- a/loan.py +++ b/loan.py @@ -1,6 +1,7 @@ from wtforms import SubmitField, StringField, HiddenField, validators, TextAreaField from flask_wtf import FlaskForm from flask import request, render_template, redirect +from flask_login import login_required, current_user from wtforms import DateField from main import db, app, ma from datetime import date @@ -48,6 +49,7 @@ class LoanForm(FlaskForm): # /loans -> GET only -> prints out list of all loans ################################################################################ @app.route("/loans", methods=["GET"]) +@login_required def loans(): loans = Loan.query.all() return render_template("loans.html", loans=loans, alert=st.GetAlert(), message=st.GetMessage()) @@ -56,6 +58,7 @@ def loans(): # /loan -> GET/POST -> creates a new loan type and when created, takes you back to /loans ################################################################################ @app.route("/loan", methods=["GET", "POST"]) +@login_required def new_loan(): form = LoanForm(request.form) page_title='Create new Loan' @@ -78,6 +81,7 @@ def new_loan(): # /loan/ -> GET/POST(save or delete) -> shows/edits/delets a single loan ################################################################################ @app.route("/loan/", methods=["GET", "POST"]) +@login_required def loan(id): ### DDP: should this be request.form or request.values? form = LoanForm(request.form) diff --git a/main.py b/main.py index d9bb605..f220277 100644 --- a/main.py +++ b/main.py @@ -1,11 +1,16 @@ -from flask import Flask, render_template, request, redirect, jsonify +from flask import Flask, render_template, request, redirect, jsonify, url_for from flask_sqlalchemy import SQLAlchemy from sqlalchemy.exc import SQLAlchemyError from flask_marshmallow import Marshmallow from flask_bootstrap import Bootstrap from wtforms import SubmitField, StringField, HiddenField, SelectField, IntegerField, TextAreaField, validators from flask_wtf import FlaskForm +from flask_compress import Compress from status import st, Status +# for ldap auth +from flask_ldap3_login import LDAP3LoginManager +from flask_login import LoginManager, login_user, login_required, UserMixin, current_user, logout_user +from flask_ldap3_login.forms import LDAPLoginForm import re import os @@ -21,10 +26,30 @@ else: 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') + +# ldap config vars: (the last one is required, or python ldap freaks out) +app.config['LDAP_HOST'] = 'mara.ddp.net' +app.config['LDAP_BASE_DN'] = 'dc=depaoli,dc=id,dc=au' +app.config['LDAP_USER_DN'] = 'ou=users' +app.config['LDAP_GROUP_DN'] = 'ou=groups' +app.config['LDAP_USER_RDN_ATTR'] = 'cn' +app.config['LDAP_USER_LOGIN_ATTR'] = 'uid' +app.config['LDAP_BIND_USER_DN'] = None +app.config['LDAP_BIND_USER_PASSWORD'] = None +app.config['LDAP_GROUP_OBJECT_FILTER'] = '(objectclass=posixGroup)' + db = SQLAlchemy(app) ma = Marshmallow(app) Bootstrap(app) +# setup ldap for auth +login_manager = LoginManager(app) # Setup a Flask-Login Manager +ldap_manager = LDAP3LoginManager(app) # Setup a LDAP3 Login Manager. +login_manager.login_view = "login" # default login route, failed with url_for, so hard-coded + +# enable compression for http / speed +Compress(app) + ################################# Now, import non-book classes ################################### from author import Author, AuthorForm, AuthorSchema, GetAuthors from publisher import Publisher, PublisherForm, PublisherSchema, GetPublisherById @@ -35,6 +60,7 @@ from owned import Owned, OwnedForm, OwnedSchema, GetOwnedById from rating import Rating, RatingForm, RatingSchema, GetRatingById from loan import Loan, LoanForm, LoanSchema from series import Series, SeriesForm, SeriesSchema, ListOfSeriesWithMissingBooks, CalcAvgRating +from user import BDBUser ####################################### CLASSES / DB model ####################################### class QuickParentBook: @@ -246,12 +272,67 @@ app.jinja_env.globals['GetRatingById'] = GetRatingById app.jinja_env.globals['SeriesBookNum'] = SeriesBookNum app.jinja_env.globals['ClearStatus'] = st.ClearStatus app.jinja_env.globals['ListOfSeriesWithMissingBooks'] = ListOfSeriesWithMissingBooks +app.jinja_env.globals['current_user'] = current_user book_schema = BookSchema() books_schema = BookSchema(many=True) +# Declare a User Loader for Flask-Login. +# Returns the User if it exists in our 'database', otherwise returns None. +@login_manager.user_loader +def load_user(id): + bdbu=BDBUser.query.filter(BDBUser.dn==id).first() + return bdbu + +# Declare The User Saver for Flask-Ldap3-Login +# This method is called whenever a LDAPLoginForm() successfully validates. +# store the user details / session in the DB if it is not in there already +@ldap_manager.save_user +def save_user(dn, username, data, memberships): + bdbu=BDBUser.query.filter(BDBUser.dn==dn).first() + # if we already have a valid user/session, and say the web has restarted, just re-use it, dont make more users + if bdbu: + return bdbu + bdbu=BDBUser(dn=dn) + db.session.add(bdbu) + db.session.commit() + return bdbu + +# POST is when user submits pwd & uses flask-login to hit ldap, validate pwd +# if valid, then we save user/session into the DB via login_user() -> calls save_user() +@app.route('/login', methods=['GET', 'POST']) +def login(): + # Instantiate a LDAPLoginForm which has a validator to check if the user + # exists in LDAP. + form = LDAPLoginForm() + form.submit.label.text="Login" + + # the re matches on any special LDAP chars, we dont want someone + # ldap-injecting our username, so send them back to the login page instead + if request.method == 'POST' and re.search( r'[()\\*&!]', request.form['username']): + print( f"WARNING: Detected special LDAP chars in username: {request.form['username']}") + return redirect(url_for('login')) + if form.validate_on_submit(): + # Successfully logged in, We can now access the saved user object via form.user. + login_user(form.user, remember=True) # Tell flask-login to log them in. + next = request.args.get("next") + if next: + return redirect(next) # Send them back where they came from + else: + return redirect( url_for('main_page') ) + + return render_template("login.html", form=form) + +@app.route('/logout') +@login_required +def logout(): + logout_user() + return redirect('/login') + + ####################################### ROUTES ####################################### @app.route("/search", methods=["POST"]) +@login_required def search(): if 'InDBox' in request.form: # removes already loaned books from list of books to loan out @@ -264,12 +345,14 @@ def search(): return render_template("books.html", books=books, InDBox=InDBox) @app.route("/books", methods=["GET"]) +@login_required def books(): books = Book.query.all() AddSubs(books) return render_template("books.html", books=books ) @app.route("/books_for_loan/", methods=["GET", "POST"]) +@login_required def books_for_loan(id): books = Book.query.join(Book_Loan_Link).filter(Book_Loan_Link.loan_id==id).order_by(Book.id).all() if request.method == 'POST': @@ -332,6 +415,7 @@ def CalcMoveForBookInSeries( id, bid, dir ): book2.MoveBookInSeries( id, b2_move_by ) @app.route("/books_for_series/", methods=["GET", "POST"]) +@login_required def books_for_series(id): if request.method == 'POST': if 'move_button' in request.form: @@ -346,6 +430,7 @@ def books_for_series(id): return render_template("books_for_series.html", books=books, series=series) @app.route("/subbooks_for_book/", methods=["GET", "POST"]) +@login_required def subbooks_for_book(id): if request.method == 'POST': if 'move_button' in request.form: @@ -389,6 +474,7 @@ def subbooks_for_book(id): # /books (or /book/ -- if you added a sub-book of parent_id ################################################################################ @app.route("/remove_subbook", methods=["POST"]) +@login_required def remove_sub_book(): try: db.engine.execute("delete from book_sub_book_link where book_id = {} and sub_book_id = {}".format( request.form['rem_sub_parent_id'], request.form['rem_sub_sub_book_id']) ) @@ -404,6 +490,7 @@ def remove_sub_book(): # /books (or /book/ -- if you added a sub-book of parent_id ################################################################################ @app.route("/book", methods=["GET", "POST"]) +@login_required def new_book(): form = BookForm(request.form) page_title='Create new Book' @@ -475,6 +562,7 @@ def new_book(): @app.route("/book/", methods=["GET", "POST"]) +@login_required def book(id): book_form = BookForm(request.form) page_title='Edit Book' @@ -619,6 +707,7 @@ def GetCount( what, where ): return rtn @app.route("/stats", methods=["GET"]) +@login_required def stats(): stats=[] @@ -637,6 +726,7 @@ def stats(): return render_template("stats.html", stats=stats ) @app.route("/rem_books_from_loan/", methods=["POST"]) +@login_required def rem_books_from_loan(id): for field in request.form: rem_id=int(re.findall( '\d+', field )[0]) @@ -649,6 +739,7 @@ def rem_books_from_loan(id): return jsonify(success=True) @app.route("/add_books_to_loan/", methods=["POST"]) +@login_required def add_books_to_loan(id): for field in request.form: add_id=int(re.findall( '\d+', field )[0]) @@ -662,6 +753,7 @@ def add_books_to_loan(id): return jsonify(success=True) @app.route("/rem_parent_books_from_series/", methods=["POST"]) +@login_required def rem_parent_books_from_series(pid): print ("pid={}".format(pid) ) try: @@ -674,12 +766,14 @@ def rem_parent_books_from_series(pid): return jsonify(success=True) @app.route("/books_on_shelf", methods=["GET"]) +@login_required def books_on_shelf(): books = Book.query.join(Owned).filter(Owned.name=='Currently Owned').all() RemSubs(books) return render_template("books.html", books=books, page_title="Books on Shelf", order_by="Author(s)", show_cols='', hide_cols='' ) @app.route("/unrated_books", methods=["GET"]) +@login_required def unrated_books(): books = Book.query.join(Condition,Owned).filter(Rating.name=='Undefined',Owned.name=='Currently Owned').all() return render_template("books.html", books=books, page_title="Books with no rating", show_cols='Rating', hide_cols='' ) @@ -710,39 +804,50 @@ def FindMissingBooks(): return books @app.route("/missing_books", methods=["GET"]) +@login_required def missing_books(): books=FindMissingBooks() return render_template("missing.html", books=books ) @app.route("/wishlist", methods=["GET"]) +@login_required def wishlist(): books = Book.query.join(Owned).filter(Owned.name=='On Wish List').all() return render_template("books.html", books=books, page_title="Books On Wish List", show_cols='', hide_cols='Publisher,Condition,Covertype' ) @app.route("/books_to_buy", methods=["GET"]) +@login_required def books_to_buy(): books = Book.query.join(Owned).filter(Owned.name=='On Wish List').all() missing = FindMissingBooks() return render_template("to_buy.html", books=books, missing=missing, page_title="Books To Buy") @app.route("/needs_replacing", methods=["GET"]) +@login_required def needs_replacing(): books = Book.query.join(Condition,Owned).filter(Condition.name=='Needs Replacing',Owned.name=='Currently Owned').all() return render_template("books.html", books=books, page_title="Books that Need Replacing", show_cols='', hide_cols='' ) @app.route("/sold", methods=["GET"]) +@login_required def sold_books(): books = Book.query.join(Owned).filter(Owned.name=='Sold').all() return render_template("books.html", books=books, page_title="Books that were Sold", show_cols='', hide_cols='' ) @app.route("/poor_rating", methods=["GET"]) +@login_required def poor_rating_books(): books = Book.query.join(Rating,Owned).filter(Rating.id>6,Rating.name!='Undefined',Owned.name=='Currently Owned').all() return render_template("books.html", books=books, page_title="Books that have a Poor Rating (<5 out of 10)", show_cols='Rating', hide_cols='' ) +# default page, just the navbar @app.route("/", methods=["GET"]) +@login_required def main_page(): + # Redirect users who are not logged in. + if not current_user or current_user.is_anonymous: + return redirect(url_for('login')) return render_template("base.html", alert=st.GetAlert(), message=st.GetMessage()) if __name__ == "__main__": @@ -750,3 +855,17 @@ if __name__ == "__main__": app.run(ssl_context=('/etc/letsencrypt/live/book.depaoli.id.au/cert.pem', '/etc/letsencrypt/live/book.depaoli.id.au/privkey.pem'), host="0.0.0.0", debug=False) else: app.run(host="0.0.0.0", debug=True) + +############################################################################### +# This func creates a new filter in jinja2 to test to hand back the username +# from the ldap dn +################################################################################ +@app.template_filter('Username') +def _jinja2_filter_parentpath(dn): + # pull apart a dn (uid=xxx,dn=yyy,etc), and return the xxx + username=str(dn) + s=username.index('=') + s+=1 + f=username.index(',') + return username[s:f] + diff --git a/owned.py b/owned.py index 2d5884f..4012575 100644 --- a/owned.py +++ b/owned.py @@ -1,6 +1,7 @@ from wtforms import SubmitField, StringField, HiddenField, SelectField, validators from flask import request, render_template, redirect from flask_wtf import FlaskForm +from flask_login import login_required, current_user from main import db, app, ma from sqlalchemy import func, Sequence from sqlalchemy.exc import SQLAlchemyError @@ -37,6 +38,7 @@ class OwnedForm(FlaskForm): # /owneds -> GET only -> prints out list of all owneds ################################################################################ @app.route("/owneds", methods=["GET"]) +@login_required def owneds(): objects = Owned.query.order_by('id').all() return render_template("show_id_name.html", objects=objects, page_title='Show All Ownership types', url_base='owned', alert=st.GetAlert(), message=st.GetMessage() ) @@ -45,6 +47,7 @@ def owneds(): # /owned -> GET/POST -> creates a new owned type and when created, takes you back to /owneds ################################################################################ @app.route("/owned", methods=["GET", "POST"]) +@login_required def new_owned(): form = OwnedForm(request.form) page_title='Create new Ownership Type' @@ -66,6 +69,7 @@ def new_owned(): # /owned/ -> GET/POST(save or delete) -> shows/edits/delets a single owned ################################################################################ @app.route("/owned/", methods=["GET", "POST"]) +@login_required def owned(id): ### DDP: should this be request.form or request.values? form = OwnedForm(request.form) diff --git a/publisher.py b/publisher.py index c285144..54e8461 100644 --- a/publisher.py +++ b/publisher.py @@ -1,6 +1,7 @@ from wtforms import SubmitField, StringField, HiddenField, SelectField, validators from flask import request, render_template, redirect from flask_wtf import FlaskForm +from flask_login import login_required, current_user from main import db, app, ma from sqlalchemy import Sequence from sqlalchemy.exc import SQLAlchemyError @@ -37,6 +38,7 @@ class PublisherForm(FlaskForm): # /publishers -> GET only -> prints out list of all publishers ################################################################################ @app.route("/publishers", methods=["GET"]) +@login_required def publishers(): objects = Publisher.query.order_by('id').all() return render_template("show_id_name.html", objects=objects, page_title='Show All Publishers', url_base='publisher', alert=st.GetAlert(), message=st.GetMessage() ) @@ -45,6 +47,7 @@ def publishers(): # /publisher -> GET/POST -> creates a new publisher type and when created, takes you back to /publishers ################################################################################ @app.route("/publisher", methods=["GET", "POST"]) +@login_required def new_publisher(): form = PublisherForm(request.form) page_title='Create new Publisher' @@ -66,6 +69,7 @@ def new_publisher(): # /publisher/ -> GET/POST(save or delete) -> shows/edits/delets a single publisher ################################################################################ @app.route("/publisher/", methods=["GET", "POST"]) +@login_required def publisher(id): ### DDP: should this be request.form or request.values? form = PublisherForm(request.form) diff --git a/rating.py b/rating.py index be60771..616370d 100644 --- a/rating.py +++ b/rating.py @@ -1,6 +1,7 @@ from wtforms import SubmitField, StringField, HiddenField, SelectField, validators from flask import request, render_template, redirect from flask_wtf import FlaskForm +from flask_login import login_required, current_user from main import db, app, ma from sqlalchemy import Sequence from sqlalchemy.exc import SQLAlchemyError @@ -37,6 +38,7 @@ class RatingForm(FlaskForm): # /ratings -> GET only -> prints out list of all ratings ################################################################################ @app.route("/ratings", methods=["GET"]) +@login_required def ratings(): objects = Rating.query.order_by('id').all() return render_template("show_id_name.html", objects=objects, page_title='Show All Ratings', url_base='rating', alert=st.GetAlert(), message=st.GetMessage() ) @@ -45,6 +47,7 @@ def ratings(): # /rating -> GET/POST -> creates a new rating type and when created, takes you back to /ratings ################################################################################ @app.route("/rating", methods=["GET", "POST"]) +@login_required def new_rating(): form = RatingForm(request.form) page_title='Create new Rating' @@ -66,6 +69,7 @@ def new_rating(): # /rating/ -> GET/POST(save or delete) -> shows/edits/delets a single rating ################################################################################ @app.route("/rating/", methods=["GET", "POST"]) +@login_required def rating(id): ### DDP: should this be request.form or request.values? form = RatingForm(request.form) diff --git a/series.py b/series.py index 1f1f3cb..e5cc997 100644 --- a/series.py +++ b/series.py @@ -1,6 +1,7 @@ from wtforms import SubmitField, StringField, HiddenField, validators, TextAreaField, IntegerField from flask_wtf import FlaskForm from flask import request, render_template, redirect +from flask_login import login_required, current_user from wtforms import DateField from main import db, app, ma from sqlalchemy.exc import SQLAlchemyError @@ -58,6 +59,7 @@ def ListOfSeriesWithMissingBooks(): # /seriess -> GET only -> prints out list of all seriess ################################################################################ @app.route("/seriess", methods=["GET"]) +@login_required def seriess(): seriess = Series.query.all() return render_template("seriess.html", seriess=seriess, message=st.GetMessage(), alert=st.GetAlert()) @@ -66,6 +68,7 @@ def seriess(): # /series -> GET/POST -> creates a new series type and when created, takes you back to /seriess ################################################################################ @app.route("/series", methods=["GET", "POST"]) +@login_required def new_series(): form = SeriesForm(request.form) page_title='Create new Series' @@ -87,6 +90,7 @@ def new_series(): # /series/ -> GET/POST(save or delete) -> shows/edits/delets a single series ################################################################################ @app.route("/series/", methods=["GET", "POST"]) +@login_required def series(id): ### DDP: should this be request.form or request.values? form = SeriesForm(request.form) @@ -125,6 +129,7 @@ def series(id): # /series/rating_reset -> forces a reset of calculated ratings of all series ################################################################################ @app.route("/seriess/rating_reset", methods=["GET"]) +@login_required def reset_all_series_ratings(): s_list = Series.query.all() try: diff --git a/templates/base.html b/templates/base.html index 67b71c4..3c59bd5 100644 --- a/templates/base.html +++ b/templates/base.html @@ -1,12 +1,16 @@ {% if not InDBox %} - + + + Book DB + + + + - - @@ -100,11 +104,19 @@ Recalculate all series ratings - -
- - -
+
+ + +
+ + diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 0000000..4165083 --- /dev/null +++ b/templates/login.html @@ -0,0 +1,66 @@ + + + + +Book DB Login + + + + + + + + + + + + + + +{% import "bootstrap/wtf.html" as wtf %} + + + +
+{% if form.errors|length > 0 %} +
+ + + {% set last_err = namespace(txt="") %} + {% for e in form.errors %} + {% if last_err.txt != form.errors[e] %} + {% set err = form.errors[e]|replace("['", "" ) %} + {% set err = err|replace("']", "" ) %} + {{err}} + {% set last_err.txt=form.errors[e] %} + {% endif %} + {% endfor %} + +
+{% endif %} + +
+

+  Book DB Login

+
+
+ + +
+
+ + +
+ +
+ {{ form.submit( class="form-control text-info") }} +
+ {{ form.hidden_tag() }} +
+
+
+ + + diff --git a/user.py b/user.py new file mode 100644 index 0000000..464d00c --- /dev/null +++ b/user.py @@ -0,0 +1,29 @@ +from main import db +from sqlalchemy import Sequence +from flask_login import UserMixin, login_required +from main import db, app, ma + + +# pylint: disable=no-member + +################################################################################ +# Class describing Person in the database and DB via sqlalchemy +# id is unique id in DB +# dn is ldap distinguised name +# any entry in this DB is effectively a record you already authed successfully +# so acts as a session marker. If you fail ldap auth, you dont get a row here +################################################################################ +class BDBUser(UserMixin,db.Model): + __tablename__ = "bdb_user" + id = db.Column(db.Integer, db.Sequence('bdb_user_id_seq'), primary_key=True) + dn = db.Column(db.String) + + def __repr__(self): + str=f"<{self.__class__.__name__}(" + for k, v in self.__dict__.items(): + str += f"{k}={v!r}, " + str=str.rstrip(", ") + ")>" + return str + + def get_id(self): + return self.dn