make all this have a login page, use ldap, put a logo on, moved some upstream stuff to static/ -- need to do more here to be consistent with bootstrap 5, but for another day
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1 +1,3 @@
|
|||||||
__pycache__/
|
__pycache__/
|
||||||
|
DB_BACKUP/
|
||||||
|
static/
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from wtforms import SubmitField, StringField, HiddenField, validators, Form
|
from wtforms import SubmitField, StringField, HiddenField, validators, Form
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from flask import request, render_template, redirect
|
from flask import request, render_template, redirect
|
||||||
|
from flask_login import login_required, current_user
|
||||||
from main import db, app, ma
|
from main import db, app, ma
|
||||||
from sqlalchemy import Sequence
|
from sqlalchemy import Sequence
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
@@ -41,6 +42,7 @@ class AuthorForm(FlaskForm):
|
|||||||
# /authors -> GET only -> prints out list of all authors
|
# /authors -> GET only -> prints out list of all authors
|
||||||
################################################################################
|
################################################################################
|
||||||
@app.route("/authors", methods=["GET"])
|
@app.route("/authors", methods=["GET"])
|
||||||
|
@login_required
|
||||||
def authors():
|
def authors():
|
||||||
authors = Author.query.all()
|
authors = Author.query.all()
|
||||||
return render_template("authors.html", authors=authors, alert=st.GetAlert(), message=st.GetMessage() )
|
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
|
# /author -> GET/POST -> creates a new author type and when created, takes you back to /authors
|
||||||
################################################################################
|
################################################################################
|
||||||
@app.route("/author", methods=["GET", "POST"])
|
@app.route("/author", methods=["GET", "POST"])
|
||||||
|
@login_required
|
||||||
def new_author():
|
def new_author():
|
||||||
form = AuthorForm(request.form)
|
form = AuthorForm(request.form)
|
||||||
page_title='Create new Author'
|
page_title='Create new Author'
|
||||||
@@ -71,6 +74,7 @@ def new_author():
|
|||||||
# /author/<id> -> GET/POST(save or delete) -> shows/edits/delets a single author
|
# /author/<id> -> GET/POST(save or delete) -> shows/edits/delets a single author
|
||||||
################################################################################
|
################################################################################
|
||||||
@app.route("/author/<id>", methods=["GET", "POST"])
|
@app.route("/author/<id>", methods=["GET", "POST"])
|
||||||
|
@login_required
|
||||||
def author(id):
|
def author(id):
|
||||||
### DDP: should this be request.form or request.values?
|
### DDP: should this be request.form or request.values?
|
||||||
form = AuthorForm(request.form)
|
form = AuthorForm(request.form)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from wtforms import SubmitField, StringField, HiddenField, SelectField, validators
|
from wtforms import SubmitField, StringField, HiddenField, SelectField, validators
|
||||||
from flask import request, render_template, redirect
|
from flask import request, render_template, redirect
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
|
from flask_login import login_required, current_user
|
||||||
from main import db, app, ma
|
from main import db, app, ma
|
||||||
from sqlalchemy import Sequence
|
from sqlalchemy import Sequence
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
@@ -37,6 +38,7 @@ class ConditionForm(FlaskForm):
|
|||||||
# /conditions -> GET only -> prints out list of all conditions
|
# /conditions -> GET only -> prints out list of all conditions
|
||||||
################################################################################
|
################################################################################
|
||||||
@app.route("/conditions", methods=["GET"])
|
@app.route("/conditions", methods=["GET"])
|
||||||
|
@login_required
|
||||||
def conditions():
|
def conditions():
|
||||||
objects = Condition.query.order_by('id').all()
|
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() )
|
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
|
# /condition -> GET/POST -> creates a new condition type and when created, takes you back to /conditions
|
||||||
################################################################################
|
################################################################################
|
||||||
@app.route("/condition", methods=["GET", "POST"])
|
@app.route("/condition", methods=["GET", "POST"])
|
||||||
|
@login_required
|
||||||
def new_condition():
|
def new_condition():
|
||||||
form = ConditionForm(request.form)
|
form = ConditionForm(request.form)
|
||||||
page_title='Create new Condition'
|
page_title='Create new Condition'
|
||||||
@@ -66,6 +69,7 @@ def new_condition():
|
|||||||
# /condition/<id> -> GET/POST(save or delete) -> shows/edits/delets a single condition
|
# /condition/<id> -> GET/POST(save or delete) -> shows/edits/delets a single condition
|
||||||
################################################################################
|
################################################################################
|
||||||
@app.route("/condition/<id>", methods=["GET", "POST"])
|
@app.route("/condition/<id>", methods=["GET", "POST"])
|
||||||
|
@login_required
|
||||||
def condition(id):
|
def condition(id):
|
||||||
### DDP: should this be request.form or request.values?
|
### DDP: should this be request.form or request.values?
|
||||||
form = ConditionForm(request.form)
|
form = ConditionForm(request.form)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from wtforms import SubmitField, StringField, HiddenField, SelectField, validators
|
from wtforms import SubmitField, StringField, HiddenField, SelectField, validators
|
||||||
from flask import request, render_template, redirect
|
from flask import request, render_template, redirect
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
|
from flask_login import login_required, current_user
|
||||||
from main import db, app, ma
|
from main import db, app, ma
|
||||||
from sqlalchemy import Sequence
|
from sqlalchemy import Sequence
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
@@ -37,6 +38,7 @@ class CovertypeForm(FlaskForm):
|
|||||||
# /covertypes -> GET only -> prints out list of all covertypes
|
# /covertypes -> GET only -> prints out list of all covertypes
|
||||||
################################################################################
|
################################################################################
|
||||||
@app.route("/covertypes", methods=["GET"])
|
@app.route("/covertypes", methods=["GET"])
|
||||||
|
@login_required
|
||||||
def covertypes():
|
def covertypes():
|
||||||
objects = Covertype.query.order_by('id').all()
|
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() )
|
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
|
# /covertype -> GET/POST -> creates a new covertype type and when created, takes you back to /covertypes
|
||||||
################################################################################
|
################################################################################
|
||||||
@app.route("/covertype", methods=["GET", "POST"])
|
@app.route("/covertype", methods=["GET", "POST"])
|
||||||
|
@login_required
|
||||||
def new_covertype():
|
def new_covertype():
|
||||||
form = CovertypeForm(request.form)
|
form = CovertypeForm(request.form)
|
||||||
page_title='Create new Covertype'
|
page_title='Create new Covertype'
|
||||||
@@ -67,6 +70,7 @@ def new_covertype():
|
|||||||
# /covertype/<id> -> GET/POST(save or delete) -> shows/edits/delets a single covertype
|
# /covertype/<id> -> GET/POST(save or delete) -> shows/edits/delets a single covertype
|
||||||
################################################################################
|
################################################################################
|
||||||
@app.route("/covertype/<id>", methods=["GET", "POST"])
|
@app.route("/covertype/<id>", methods=["GET", "POST"])
|
||||||
|
@login_required
|
||||||
def covertype(id):
|
def covertype(id):
|
||||||
### DDP: should this be request.form or request.values?
|
### DDP: should this be request.form or request.values?
|
||||||
form = CovertypeForm(request.form)
|
form = CovertypeForm(request.form)
|
||||||
|
|||||||
4
genre.py
4
genre.py
@@ -1,6 +1,7 @@
|
|||||||
from wtforms import SubmitField, StringField, HiddenField, SelectField, validators
|
from wtforms import SubmitField, StringField, HiddenField, SelectField, validators
|
||||||
from flask import request, render_template, redirect
|
from flask import request, render_template, redirect
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
|
from flask_login import login_required, current_user
|
||||||
from main import db, app, ma
|
from main import db, app, ma
|
||||||
from sqlalchemy import func, Sequence
|
from sqlalchemy import func, Sequence
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
@@ -37,6 +38,7 @@ class GenreForm(FlaskForm):
|
|||||||
# /genres -> GET only -> prints out list of all genres
|
# /genres -> GET only -> prints out list of all genres
|
||||||
################################################################################
|
################################################################################
|
||||||
@app.route("/genres", methods=["GET"])
|
@app.route("/genres", methods=["GET"])
|
||||||
|
@login_required
|
||||||
def genres():
|
def genres():
|
||||||
objects = Genre.query.order_by('id').all()
|
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() )
|
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
|
# /genre -> GET/POST -> creates a new genre type and when created, takes you back to /genres
|
||||||
################################################################################
|
################################################################################
|
||||||
@app.route("/genre", methods=["GET", "POST"])
|
@app.route("/genre", methods=["GET", "POST"])
|
||||||
|
@login_required
|
||||||
def new_genre():
|
def new_genre():
|
||||||
form = GenreForm(request.form)
|
form = GenreForm(request.form)
|
||||||
page_title='Create new Genre'
|
page_title='Create new Genre'
|
||||||
@@ -66,6 +69,7 @@ def new_genre():
|
|||||||
# /genre/<id> -> GET/POST(save or delete) -> shows/edits/delets a single genre
|
# /genre/<id> -> GET/POST(save or delete) -> shows/edits/delets a single genre
|
||||||
################################################################################
|
################################################################################
|
||||||
@app.route("/genre/<id>", methods=["GET", "POST"])
|
@app.route("/genre/<id>", methods=["GET", "POST"])
|
||||||
|
@login_required
|
||||||
def genre(id):
|
def genre(id):
|
||||||
### DDP: should this be request.form or request.values?
|
### DDP: should this be request.form or request.values?
|
||||||
form = GenreForm(request.form)
|
form = GenreForm(request.form)
|
||||||
|
|||||||
4
loan.py
4
loan.py
@@ -1,6 +1,7 @@
|
|||||||
from wtforms import SubmitField, StringField, HiddenField, validators, TextAreaField
|
from wtforms import SubmitField, StringField, HiddenField, validators, TextAreaField
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from flask import request, render_template, redirect
|
from flask import request, render_template, redirect
|
||||||
|
from flask_login import login_required, current_user
|
||||||
from wtforms import DateField
|
from wtforms import DateField
|
||||||
from main import db, app, ma
|
from main import db, app, ma
|
||||||
from datetime import date
|
from datetime import date
|
||||||
@@ -48,6 +49,7 @@ class LoanForm(FlaskForm):
|
|||||||
# /loans -> GET only -> prints out list of all loans
|
# /loans -> GET only -> prints out list of all loans
|
||||||
################################################################################
|
################################################################################
|
||||||
@app.route("/loans", methods=["GET"])
|
@app.route("/loans", methods=["GET"])
|
||||||
|
@login_required
|
||||||
def loans():
|
def loans():
|
||||||
loans = Loan.query.all()
|
loans = Loan.query.all()
|
||||||
return render_template("loans.html", loans=loans, alert=st.GetAlert(), message=st.GetMessage())
|
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
|
# /loan -> GET/POST -> creates a new loan type and when created, takes you back to /loans
|
||||||
################################################################################
|
################################################################################
|
||||||
@app.route("/loan", methods=["GET", "POST"])
|
@app.route("/loan", methods=["GET", "POST"])
|
||||||
|
@login_required
|
||||||
def new_loan():
|
def new_loan():
|
||||||
form = LoanForm(request.form)
|
form = LoanForm(request.form)
|
||||||
page_title='Create new Loan'
|
page_title='Create new Loan'
|
||||||
@@ -78,6 +81,7 @@ def new_loan():
|
|||||||
# /loan/<id> -> GET/POST(save or delete) -> shows/edits/delets a single loan
|
# /loan/<id> -> GET/POST(save or delete) -> shows/edits/delets a single loan
|
||||||
################################################################################
|
################################################################################
|
||||||
@app.route("/loan/<id>", methods=["GET", "POST"])
|
@app.route("/loan/<id>", methods=["GET", "POST"])
|
||||||
|
@login_required
|
||||||
def loan(id):
|
def loan(id):
|
||||||
### DDP: should this be request.form or request.values?
|
### DDP: should this be request.form or request.values?
|
||||||
form = LoanForm(request.form)
|
form = LoanForm(request.form)
|
||||||
|
|||||||
121
main.py
121
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 flask_sqlalchemy import SQLAlchemy
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
from flask_marshmallow import Marshmallow
|
from flask_marshmallow import Marshmallow
|
||||||
from flask_bootstrap import Bootstrap
|
from flask_bootstrap import Bootstrap
|
||||||
from wtforms import SubmitField, StringField, HiddenField, SelectField, IntegerField, TextAreaField, validators
|
from wtforms import SubmitField, StringField, HiddenField, SelectField, IntegerField, TextAreaField, validators
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
|
from flask_compress import Compress
|
||||||
from status import st, Status
|
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 re
|
||||||
import os
|
import os
|
||||||
|
|
||||||
@@ -21,10 +26,30 @@ else:
|
|||||||
app.config['SQLALCHEMY_DATABASE_URI'] = DB_URL
|
app.config['SQLALCHEMY_DATABASE_URI'] = DB_URL
|
||||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||||||
app.config.from_mapping( SECRET_KEY=b'\xd6\x04\xbdj\xfe\xed$c\x1e@\xad\x0f\x13,@G')
|
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)
|
db = SQLAlchemy(app)
|
||||||
ma = Marshmallow(app)
|
ma = Marshmallow(app)
|
||||||
Bootstrap(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 ###################################
|
################################# Now, import non-book classes ###################################
|
||||||
from author import Author, AuthorForm, AuthorSchema, GetAuthors
|
from author import Author, AuthorForm, AuthorSchema, GetAuthors
|
||||||
from publisher import Publisher, PublisherForm, PublisherSchema, GetPublisherById
|
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 rating import Rating, RatingForm, RatingSchema, GetRatingById
|
||||||
from loan import Loan, LoanForm, LoanSchema
|
from loan import Loan, LoanForm, LoanSchema
|
||||||
from series import Series, SeriesForm, SeriesSchema, ListOfSeriesWithMissingBooks, CalcAvgRating
|
from series import Series, SeriesForm, SeriesSchema, ListOfSeriesWithMissingBooks, CalcAvgRating
|
||||||
|
from user import BDBUser
|
||||||
|
|
||||||
####################################### CLASSES / DB model #######################################
|
####################################### CLASSES / DB model #######################################
|
||||||
class QuickParentBook:
|
class QuickParentBook:
|
||||||
@@ -246,12 +272,67 @@ app.jinja_env.globals['GetRatingById'] = GetRatingById
|
|||||||
app.jinja_env.globals['SeriesBookNum'] = SeriesBookNum
|
app.jinja_env.globals['SeriesBookNum'] = SeriesBookNum
|
||||||
app.jinja_env.globals['ClearStatus'] = st.ClearStatus
|
app.jinja_env.globals['ClearStatus'] = st.ClearStatus
|
||||||
app.jinja_env.globals['ListOfSeriesWithMissingBooks'] = ListOfSeriesWithMissingBooks
|
app.jinja_env.globals['ListOfSeriesWithMissingBooks'] = ListOfSeriesWithMissingBooks
|
||||||
|
app.jinja_env.globals['current_user'] = current_user
|
||||||
|
|
||||||
book_schema = BookSchema()
|
book_schema = BookSchema()
|
||||||
books_schema = BookSchema(many=True)
|
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 #######################################
|
####################################### ROUTES #######################################
|
||||||
@app.route("/search", methods=["POST"])
|
@app.route("/search", methods=["POST"])
|
||||||
|
@login_required
|
||||||
def search():
|
def search():
|
||||||
if 'InDBox' in request.form:
|
if 'InDBox' in request.form:
|
||||||
# removes already loaned books from list of books to loan out
|
# 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)
|
return render_template("books.html", books=books, InDBox=InDBox)
|
||||||
|
|
||||||
@app.route("/books", methods=["GET"])
|
@app.route("/books", methods=["GET"])
|
||||||
|
@login_required
|
||||||
def books():
|
def books():
|
||||||
books = Book.query.all()
|
books = Book.query.all()
|
||||||
AddSubs(books)
|
AddSubs(books)
|
||||||
return render_template("books.html", books=books )
|
return render_template("books.html", books=books )
|
||||||
|
|
||||||
@app.route("/books_for_loan/<id>", methods=["GET", "POST"])
|
@app.route("/books_for_loan/<id>", methods=["GET", "POST"])
|
||||||
|
@login_required
|
||||||
def books_for_loan(id):
|
def books_for_loan(id):
|
||||||
books = Book.query.join(Book_Loan_Link).filter(Book_Loan_Link.loan_id==id).order_by(Book.id).all()
|
books = Book.query.join(Book_Loan_Link).filter(Book_Loan_Link.loan_id==id).order_by(Book.id).all()
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
@@ -332,6 +415,7 @@ def CalcMoveForBookInSeries( id, bid, dir ):
|
|||||||
book2.MoveBookInSeries( id, b2_move_by )
|
book2.MoveBookInSeries( id, b2_move_by )
|
||||||
|
|
||||||
@app.route("/books_for_series/<id>", methods=["GET", "POST"])
|
@app.route("/books_for_series/<id>", methods=["GET", "POST"])
|
||||||
|
@login_required
|
||||||
def books_for_series(id):
|
def books_for_series(id):
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
if 'move_button' in request.form:
|
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)
|
return render_template("books_for_series.html", books=books, series=series)
|
||||||
|
|
||||||
@app.route("/subbooks_for_book/<id>", methods=["GET", "POST"])
|
@app.route("/subbooks_for_book/<id>", methods=["GET", "POST"])
|
||||||
|
@login_required
|
||||||
def subbooks_for_book(id):
|
def subbooks_for_book(id):
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
if 'move_button' in request.form:
|
if 'move_button' in request.form:
|
||||||
@@ -389,6 +474,7 @@ def subbooks_for_book(id):
|
|||||||
# /books (or /book/<parent_id> -- if you added a sub-book of parent_id
|
# /books (or /book/<parent_id> -- if you added a sub-book of parent_id
|
||||||
################################################################################
|
################################################################################
|
||||||
@app.route("/remove_subbook", methods=["POST"])
|
@app.route("/remove_subbook", methods=["POST"])
|
||||||
|
@login_required
|
||||||
def remove_sub_book():
|
def remove_sub_book():
|
||||||
try:
|
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']) )
|
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/<parent_id> -- if you added a sub-book of parent_id
|
# /books (or /book/<parent_id> -- if you added a sub-book of parent_id
|
||||||
################################################################################
|
################################################################################
|
||||||
@app.route("/book", methods=["GET", "POST"])
|
@app.route("/book", methods=["GET", "POST"])
|
||||||
|
@login_required
|
||||||
def new_book():
|
def new_book():
|
||||||
form = BookForm(request.form)
|
form = BookForm(request.form)
|
||||||
page_title='Create new Book'
|
page_title='Create new Book'
|
||||||
@@ -475,6 +562,7 @@ def new_book():
|
|||||||
|
|
||||||
|
|
||||||
@app.route("/book/<id>", methods=["GET", "POST"])
|
@app.route("/book/<id>", methods=["GET", "POST"])
|
||||||
|
@login_required
|
||||||
def book(id):
|
def book(id):
|
||||||
book_form = BookForm(request.form)
|
book_form = BookForm(request.form)
|
||||||
page_title='Edit Book'
|
page_title='Edit Book'
|
||||||
@@ -619,6 +707,7 @@ def GetCount( what, where ):
|
|||||||
return rtn
|
return rtn
|
||||||
|
|
||||||
@app.route("/stats", methods=["GET"])
|
@app.route("/stats", methods=["GET"])
|
||||||
|
@login_required
|
||||||
def stats():
|
def stats():
|
||||||
stats=[]
|
stats=[]
|
||||||
|
|
||||||
@@ -637,6 +726,7 @@ def stats():
|
|||||||
return render_template("stats.html", stats=stats )
|
return render_template("stats.html", stats=stats )
|
||||||
|
|
||||||
@app.route("/rem_books_from_loan/<id>", methods=["POST"])
|
@app.route("/rem_books_from_loan/<id>", methods=["POST"])
|
||||||
|
@login_required
|
||||||
def rem_books_from_loan(id):
|
def rem_books_from_loan(id):
|
||||||
for field in request.form:
|
for field in request.form:
|
||||||
rem_id=int(re.findall( '\d+', field )[0])
|
rem_id=int(re.findall( '\d+', field )[0])
|
||||||
@@ -649,6 +739,7 @@ def rem_books_from_loan(id):
|
|||||||
return jsonify(success=True)
|
return jsonify(success=True)
|
||||||
|
|
||||||
@app.route("/add_books_to_loan/<id>", methods=["POST"])
|
@app.route("/add_books_to_loan/<id>", methods=["POST"])
|
||||||
|
@login_required
|
||||||
def add_books_to_loan(id):
|
def add_books_to_loan(id):
|
||||||
for field in request.form:
|
for field in request.form:
|
||||||
add_id=int(re.findall( '\d+', field )[0])
|
add_id=int(re.findall( '\d+', field )[0])
|
||||||
@@ -662,6 +753,7 @@ def add_books_to_loan(id):
|
|||||||
return jsonify(success=True)
|
return jsonify(success=True)
|
||||||
|
|
||||||
@app.route("/rem_parent_books_from_series/<pid>", methods=["POST"])
|
@app.route("/rem_parent_books_from_series/<pid>", methods=["POST"])
|
||||||
|
@login_required
|
||||||
def rem_parent_books_from_series(pid):
|
def rem_parent_books_from_series(pid):
|
||||||
print ("pid={}".format(pid) )
|
print ("pid={}".format(pid) )
|
||||||
try:
|
try:
|
||||||
@@ -674,12 +766,14 @@ def rem_parent_books_from_series(pid):
|
|||||||
return jsonify(success=True)
|
return jsonify(success=True)
|
||||||
|
|
||||||
@app.route("/books_on_shelf", methods=["GET"])
|
@app.route("/books_on_shelf", methods=["GET"])
|
||||||
|
@login_required
|
||||||
def books_on_shelf():
|
def books_on_shelf():
|
||||||
books = Book.query.join(Owned).filter(Owned.name=='Currently Owned').all()
|
books = Book.query.join(Owned).filter(Owned.name=='Currently Owned').all()
|
||||||
RemSubs(books)
|
RemSubs(books)
|
||||||
return render_template("books.html", books=books, page_title="Books on Shelf", order_by="Author(s)", show_cols='', hide_cols='' )
|
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"])
|
@app.route("/unrated_books", methods=["GET"])
|
||||||
|
@login_required
|
||||||
def unrated_books():
|
def unrated_books():
|
||||||
books = Book.query.join(Condition,Owned).filter(Rating.name=='Undefined',Owned.name=='Currently Owned').all()
|
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='' )
|
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
|
return books
|
||||||
|
|
||||||
@app.route("/missing_books", methods=["GET"])
|
@app.route("/missing_books", methods=["GET"])
|
||||||
|
@login_required
|
||||||
def missing_books():
|
def missing_books():
|
||||||
books=FindMissingBooks()
|
books=FindMissingBooks()
|
||||||
return render_template("missing.html", books=books )
|
return render_template("missing.html", books=books )
|
||||||
|
|
||||||
@app.route("/wishlist", methods=["GET"])
|
@app.route("/wishlist", methods=["GET"])
|
||||||
|
@login_required
|
||||||
def wishlist():
|
def wishlist():
|
||||||
books = Book.query.join(Owned).filter(Owned.name=='On Wish List').all()
|
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' )
|
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"])
|
@app.route("/books_to_buy", methods=["GET"])
|
||||||
|
@login_required
|
||||||
def books_to_buy():
|
def books_to_buy():
|
||||||
books = Book.query.join(Owned).filter(Owned.name=='On Wish List').all()
|
books = Book.query.join(Owned).filter(Owned.name=='On Wish List').all()
|
||||||
missing = FindMissingBooks()
|
missing = FindMissingBooks()
|
||||||
return render_template("to_buy.html", books=books, missing=missing, page_title="Books To Buy")
|
return render_template("to_buy.html", books=books, missing=missing, page_title="Books To Buy")
|
||||||
|
|
||||||
@app.route("/needs_replacing", methods=["GET"])
|
@app.route("/needs_replacing", methods=["GET"])
|
||||||
|
@login_required
|
||||||
def needs_replacing():
|
def needs_replacing():
|
||||||
books = Book.query.join(Condition,Owned).filter(Condition.name=='Needs Replacing',Owned.name=='Currently Owned').all()
|
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='' )
|
return render_template("books.html", books=books, page_title="Books that Need Replacing", show_cols='', hide_cols='' )
|
||||||
|
|
||||||
@app.route("/sold", methods=["GET"])
|
@app.route("/sold", methods=["GET"])
|
||||||
|
@login_required
|
||||||
def sold_books():
|
def sold_books():
|
||||||
books = Book.query.join(Owned).filter(Owned.name=='Sold').all()
|
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='' )
|
return render_template("books.html", books=books, page_title="Books that were Sold", show_cols='', hide_cols='' )
|
||||||
|
|
||||||
@app.route("/poor_rating", methods=["GET"])
|
@app.route("/poor_rating", methods=["GET"])
|
||||||
|
@login_required
|
||||||
def poor_rating_books():
|
def poor_rating_books():
|
||||||
books = Book.query.join(Rating,Owned).filter(Rating.id>6,Rating.name!='Undefined',Owned.name=='Currently Owned').all()
|
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='' )
|
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"])
|
@app.route("/", methods=["GET"])
|
||||||
|
@login_required
|
||||||
def main_page():
|
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())
|
return render_template("base.html", alert=st.GetAlert(), message=st.GetMessage())
|
||||||
|
|
||||||
if __name__ == "__main__":
|
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)
|
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:
|
else:
|
||||||
app.run(host="0.0.0.0", debug=True)
|
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]
|
||||||
|
|
||||||
|
|||||||
4
owned.py
4
owned.py
@@ -1,6 +1,7 @@
|
|||||||
from wtforms import SubmitField, StringField, HiddenField, SelectField, validators
|
from wtforms import SubmitField, StringField, HiddenField, SelectField, validators
|
||||||
from flask import request, render_template, redirect
|
from flask import request, render_template, redirect
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
|
from flask_login import login_required, current_user
|
||||||
from main import db, app, ma
|
from main import db, app, ma
|
||||||
from sqlalchemy import func, Sequence
|
from sqlalchemy import func, Sequence
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
@@ -37,6 +38,7 @@ class OwnedForm(FlaskForm):
|
|||||||
# /owneds -> GET only -> prints out list of all owneds
|
# /owneds -> GET only -> prints out list of all owneds
|
||||||
################################################################################
|
################################################################################
|
||||||
@app.route("/owneds", methods=["GET"])
|
@app.route("/owneds", methods=["GET"])
|
||||||
|
@login_required
|
||||||
def owneds():
|
def owneds():
|
||||||
objects = Owned.query.order_by('id').all()
|
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() )
|
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
|
# /owned -> GET/POST -> creates a new owned type and when created, takes you back to /owneds
|
||||||
################################################################################
|
################################################################################
|
||||||
@app.route("/owned", methods=["GET", "POST"])
|
@app.route("/owned", methods=["GET", "POST"])
|
||||||
|
@login_required
|
||||||
def new_owned():
|
def new_owned():
|
||||||
form = OwnedForm(request.form)
|
form = OwnedForm(request.form)
|
||||||
page_title='Create new Ownership Type'
|
page_title='Create new Ownership Type'
|
||||||
@@ -66,6 +69,7 @@ def new_owned():
|
|||||||
# /owned/<id> -> GET/POST(save or delete) -> shows/edits/delets a single owned
|
# /owned/<id> -> GET/POST(save or delete) -> shows/edits/delets a single owned
|
||||||
################################################################################
|
################################################################################
|
||||||
@app.route("/owned/<id>", methods=["GET", "POST"])
|
@app.route("/owned/<id>", methods=["GET", "POST"])
|
||||||
|
@login_required
|
||||||
def owned(id):
|
def owned(id):
|
||||||
### DDP: should this be request.form or request.values?
|
### DDP: should this be request.form or request.values?
|
||||||
form = OwnedForm(request.form)
|
form = OwnedForm(request.form)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from wtforms import SubmitField, StringField, HiddenField, SelectField, validators
|
from wtforms import SubmitField, StringField, HiddenField, SelectField, validators
|
||||||
from flask import request, render_template, redirect
|
from flask import request, render_template, redirect
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
|
from flask_login import login_required, current_user
|
||||||
from main import db, app, ma
|
from main import db, app, ma
|
||||||
from sqlalchemy import Sequence
|
from sqlalchemy import Sequence
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
@@ -37,6 +38,7 @@ class PublisherForm(FlaskForm):
|
|||||||
# /publishers -> GET only -> prints out list of all publishers
|
# /publishers -> GET only -> prints out list of all publishers
|
||||||
################################################################################
|
################################################################################
|
||||||
@app.route("/publishers", methods=["GET"])
|
@app.route("/publishers", methods=["GET"])
|
||||||
|
@login_required
|
||||||
def publishers():
|
def publishers():
|
||||||
objects = Publisher.query.order_by('id').all()
|
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() )
|
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
|
# /publisher -> GET/POST -> creates a new publisher type and when created, takes you back to /publishers
|
||||||
################################################################################
|
################################################################################
|
||||||
@app.route("/publisher", methods=["GET", "POST"])
|
@app.route("/publisher", methods=["GET", "POST"])
|
||||||
|
@login_required
|
||||||
def new_publisher():
|
def new_publisher():
|
||||||
form = PublisherForm(request.form)
|
form = PublisherForm(request.form)
|
||||||
page_title='Create new Publisher'
|
page_title='Create new Publisher'
|
||||||
@@ -66,6 +69,7 @@ def new_publisher():
|
|||||||
# /publisher/<id> -> GET/POST(save or delete) -> shows/edits/delets a single publisher
|
# /publisher/<id> -> GET/POST(save or delete) -> shows/edits/delets a single publisher
|
||||||
################################################################################
|
################################################################################
|
||||||
@app.route("/publisher/<id>", methods=["GET", "POST"])
|
@app.route("/publisher/<id>", methods=["GET", "POST"])
|
||||||
|
@login_required
|
||||||
def publisher(id):
|
def publisher(id):
|
||||||
### DDP: should this be request.form or request.values?
|
### DDP: should this be request.form or request.values?
|
||||||
form = PublisherForm(request.form)
|
form = PublisherForm(request.form)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from wtforms import SubmitField, StringField, HiddenField, SelectField, validators
|
from wtforms import SubmitField, StringField, HiddenField, SelectField, validators
|
||||||
from flask import request, render_template, redirect
|
from flask import request, render_template, redirect
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
|
from flask_login import login_required, current_user
|
||||||
from main import db, app, ma
|
from main import db, app, ma
|
||||||
from sqlalchemy import Sequence
|
from sqlalchemy import Sequence
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
@@ -37,6 +38,7 @@ class RatingForm(FlaskForm):
|
|||||||
# /ratings -> GET only -> prints out list of all ratings
|
# /ratings -> GET only -> prints out list of all ratings
|
||||||
################################################################################
|
################################################################################
|
||||||
@app.route("/ratings", methods=["GET"])
|
@app.route("/ratings", methods=["GET"])
|
||||||
|
@login_required
|
||||||
def ratings():
|
def ratings():
|
||||||
objects = Rating.query.order_by('id').all()
|
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() )
|
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
|
# /rating -> GET/POST -> creates a new rating type and when created, takes you back to /ratings
|
||||||
################################################################################
|
################################################################################
|
||||||
@app.route("/rating", methods=["GET", "POST"])
|
@app.route("/rating", methods=["GET", "POST"])
|
||||||
|
@login_required
|
||||||
def new_rating():
|
def new_rating():
|
||||||
form = RatingForm(request.form)
|
form = RatingForm(request.form)
|
||||||
page_title='Create new Rating'
|
page_title='Create new Rating'
|
||||||
@@ -66,6 +69,7 @@ def new_rating():
|
|||||||
# /rating/<id> -> GET/POST(save or delete) -> shows/edits/delets a single rating
|
# /rating/<id> -> GET/POST(save or delete) -> shows/edits/delets a single rating
|
||||||
################################################################################
|
################################################################################
|
||||||
@app.route("/rating/<id>", methods=["GET", "POST"])
|
@app.route("/rating/<id>", methods=["GET", "POST"])
|
||||||
|
@login_required
|
||||||
def rating(id):
|
def rating(id):
|
||||||
### DDP: should this be request.form or request.values?
|
### DDP: should this be request.form or request.values?
|
||||||
form = RatingForm(request.form)
|
form = RatingForm(request.form)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from wtforms import SubmitField, StringField, HiddenField, validators, TextAreaField, IntegerField
|
from wtforms import SubmitField, StringField, HiddenField, validators, TextAreaField, IntegerField
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from flask import request, render_template, redirect
|
from flask import request, render_template, redirect
|
||||||
|
from flask_login import login_required, current_user
|
||||||
from wtforms import DateField
|
from wtforms import DateField
|
||||||
from main import db, app, ma
|
from main import db, app, ma
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
@@ -58,6 +59,7 @@ def ListOfSeriesWithMissingBooks():
|
|||||||
# /seriess -> GET only -> prints out list of all seriess
|
# /seriess -> GET only -> prints out list of all seriess
|
||||||
################################################################################
|
################################################################################
|
||||||
@app.route("/seriess", methods=["GET"])
|
@app.route("/seriess", methods=["GET"])
|
||||||
|
@login_required
|
||||||
def seriess():
|
def seriess():
|
||||||
seriess = Series.query.all()
|
seriess = Series.query.all()
|
||||||
return render_template("seriess.html", seriess=seriess, message=st.GetMessage(), alert=st.GetAlert())
|
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
|
# /series -> GET/POST -> creates a new series type and when created, takes you back to /seriess
|
||||||
################################################################################
|
################################################################################
|
||||||
@app.route("/series", methods=["GET", "POST"])
|
@app.route("/series", methods=["GET", "POST"])
|
||||||
|
@login_required
|
||||||
def new_series():
|
def new_series():
|
||||||
form = SeriesForm(request.form)
|
form = SeriesForm(request.form)
|
||||||
page_title='Create new Series'
|
page_title='Create new Series'
|
||||||
@@ -87,6 +90,7 @@ def new_series():
|
|||||||
# /series/<id> -> GET/POST(save or delete) -> shows/edits/delets a single series
|
# /series/<id> -> GET/POST(save or delete) -> shows/edits/delets a single series
|
||||||
################################################################################
|
################################################################################
|
||||||
@app.route("/series/<id>", methods=["GET", "POST"])
|
@app.route("/series/<id>", methods=["GET", "POST"])
|
||||||
|
@login_required
|
||||||
def series(id):
|
def series(id):
|
||||||
### DDP: should this be request.form or request.values?
|
### DDP: should this be request.form or request.values?
|
||||||
form = SeriesForm(request.form)
|
form = SeriesForm(request.form)
|
||||||
@@ -125,6 +129,7 @@ def series(id):
|
|||||||
# /series/rating_reset -> forces a reset of calculated ratings of all series
|
# /series/rating_reset -> forces a reset of calculated ratings of all series
|
||||||
################################################################################
|
################################################################################
|
||||||
@app.route("/seriess/rating_reset", methods=["GET"])
|
@app.route("/seriess/rating_reset", methods=["GET"])
|
||||||
|
@login_required
|
||||||
def reset_all_series_ratings():
|
def reset_all_series_ratings():
|
||||||
s_list = Series.query.all()
|
s_list = Series.query.all()
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
{% if not InDBox %}
|
{% if not InDBox %}
|
||||||
<html>
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
|
<title>Book DB</title>
|
||||||
<!-- Required meta tags -->
|
<!-- Required meta tags -->
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
<meta name="description" content="Book DB">
|
||||||
|
|
||||||
<!-- Bootstrap CSS -->
|
<!-- Bootstrap CSS -->
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
|
<link rel="stylesheet" href="{{ url_for( 'static', filename='upstream/bootstrap-5.0.2-dist/css/bootstrap.min.css' ) }}">
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://unpkg.com/bootstrap-table@1.18.0/dist/bootstrap-table.min.css">
|
<link rel="stylesheet" href="https://unpkg.com/bootstrap-table@1.18.0/dist/bootstrap-table.min.css">
|
||||||
<link rel="stylesheet" href="https://cdn.datatables.net/1.10.22/css/dataTables.bootstrap4.min.css">
|
<link rel="stylesheet" href="https://cdn.datatables.net/1.10.22/css/dataTables.bootstrap4.min.css">
|
||||||
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
||||||
@@ -100,11 +104,19 @@
|
|||||||
<a class="dropdown-item" href="{{url_for('reset_all_series_ratings')}}">Recalculate all series ratings</a>
|
<a class="dropdown-item" href="{{url_for('reset_all_series_ratings')}}">Recalculate all series ratings</a>
|
||||||
</div class="dropdow-menu">
|
</div class="dropdow-menu">
|
||||||
</div class="nav-item dropdown">
|
</div class="nav-item dropdown">
|
||||||
</div clas="navbar-nav">
|
<form class="d-flex col ms-5" method="POST" action="/search">
|
||||||
<form class="form-inline my-2 my-lg-0" method="POST" action="/search">
|
|
||||||
<input class="form-control mr-sm-2" type="search" placeholder="Search by title" aria-label="Search" name="term">
|
<input class="form-control mr-sm-2" type="search" placeholder="Search by title" aria-label="Search" name="term">
|
||||||
<button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button>
|
<button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button>
|
||||||
</form>
|
</form>
|
||||||
|
<div class="nav-item dropdown">
|
||||||
|
<a class="nav-link dropdown-toggle" href="#" id="UserMenu" role="button" data-toggle="dropdown" aria-expanded="false">
|
||||||
|
<svg width="20" height="20" fill="currentColor"><use xlink:href="{{url_for('static', filename='icons.svg')}}#user"/></svg>
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="UserMenu">
|
||||||
|
<div><a class="dropdown-item" href="{{url_for('logout')}}">Logout</a></div>
|
||||||
|
</div class="dropdown-menu">
|
||||||
|
</div class="nav-item dropdown">
|
||||||
|
</div class="navbar-nav">
|
||||||
</div class="collapse navbar-collapse">
|
</div class="collapse navbar-collapse">
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
|||||||
66
templates/login.html
Normal file
66
templates/login.html
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Book DB Login</title>
|
||||||
|
<!-- Required meta tags -->
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
<meta name="description" content="Photo Assistant">
|
||||||
|
|
||||||
|
<!-- Bootstrap CSS -->
|
||||||
|
<link rel="stylesheet" href="{{ url_for( 'static', filename='upstream/bootstrap-5.0.2-dist/css/bootstrap.min.css' ) }}">
|
||||||
|
|
||||||
|
<!-- code to get bootstrap to work -->
|
||||||
|
<script src="{{ url_for( 'static', filename='upstream/jquery-3.6.0.min.js')}}"></script>
|
||||||
|
<script src="{{ url_for( 'static', filename='upstream/bootstrap-5.0.2-dist/js/bootstrap.min.js')}}"></script>
|
||||||
|
|
||||||
|
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
||||||
|
|
||||||
|
{% import "bootstrap/wtf.html" as wtf %}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
{% if form.errors|length > 0 %}
|
||||||
|
<div class="row my-5">
|
||||||
|
<alert id="err" class="alert alert-danger alert-dismissible fade show">
|
||||||
|
<button type="button" class="close btn border-secondary me-3" data-dismiss="alert" aria-label="Close" onClick="$('#err').hide()">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
{% 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 %}
|
||||||
|
</alert>
|
||||||
|
</div class="row">
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="row px-3 my-5 col-6" style="border: 3px solid #5bc0de; border-radius: 15px;">
|
||||||
|
<h3 class="my-3 text-center" style="color: #5bc0de">
|
||||||
|
<svg width="64" height="64" fill="currentColor"><use xlink:href="{{url_for('static', filename='book.svg')}}#logo" /></svg> Book DB Login</h3>
|
||||||
|
<form class="" method="POST">
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="username" class="text-right input-group-text col-4 text-info">Username:</label>
|
||||||
|
<input class="form-control" type="text" id="username" name="username"></input>
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="password" class="text-right input-group-text col-4 text-info">Password:</label>
|
||||||
|
<input class="form-control col-8" type="password" id="password" name="password"></input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 my-2 text-center">
|
||||||
|
{{ form.submit( class="form-control text-info") }}
|
||||||
|
</div>
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
|
</form>
|
||||||
|
</div class="row">
|
||||||
|
</div class="container">
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
29
user.py
Normal file
29
user.py
Normal file
@@ -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
|
||||||
Reference in New Issue
Block a user