def update_classroom(): """Update classroom list of students.""" session = db_session() if request.method == 'GET': classrooms = session.query(Classroom).order_by(Classroom.name) return render_template('update_classroom.html', classrooms=classrooms) if request.method == 'POST': try: if 'classroom_file' not in request.files: raise FileNotFoundError("Could not find the file!") classroom_file = request.files['classroom_file'] if classroom_file.filename == '': raise ValueError("No files have been selected.") if not allowed_file(classroom_file.filename): raise ValueError("This file type is not permitted.") if classroom_file: filename = secure_filename(classroom_file.filename) file_path = os.path.join(UPLOAD_DIR, filename) classroom_file.save(file_path) if update_db(file_path): flash("Classroom details have been updated successfully!") except (FileNotFoundError, ValueError) as error: flash("<strong>Error! </strong> %s" % str(error), 'error') # pass return redirect('users/update')
def view_loans(): """Displays loans of a book, or of all books.""" session = db_session() if current_user.is_admin: found_books = session.query(Book).filter( Book.current_location == BookLocation.LOAN.value).all() else: found_books = session.query(Book).filter( Book.current_location == BookLocation.LOAN.value and Book.id in [l.book_id for l in Classroom.query( Classroom.user_id == current_user.id).open_loans]).all() # pagination total = len(found_books) page = request.args.get(get_page_parameter(), type=int, default=1) pagination = Pagination(page=page, total=total, per_page=PER_PAGE, css_framework="bootstrap3") # initialise loan forms new_loan_form, loan_return_form = init_loan_forms() return render_template('loans.html', books=found_books, new_loan_form=new_loan_form, loan_return_form=loan_return_form, pagination=pagination, thumbnails_dir=THUMBNAILS_DIR)
def record_loan(): """Records loan of a book to a pupil.""" session = db_session() new_loan_form = NewLoanForm() class_id = current_user.classroom.id new_loan_form.pupil_id.choices = \ [(p[0], p[1]) for p in session.query(Pupil.id, Pupil.name). filter(Pupil.classroom_id == class_id)] try: user = User.query.filter( User.id == int(new_loan_form.user_id.data) ).first() pupil = Pupil.query.filter( Pupil.id == int(new_loan_form.pupil_id.data) ).first() book = Book.query.filter( Book.id == int(new_loan_form.book_id.data) ).first() if not user or not pupil or not book: raise ValueError( "Invalid entries! Book: %s; Pupil: %s; User: %s" % (str(book), str(pupil), str(user)) ) if not new_loan_form.barcode_img.data: raise ValueError( "No barcode image provided. Please scan the barcode." ) isbnlist = scan_for_isbn(request.files[new_loan_form.barcode_img.name]) if len(isbnlist) < 1: raise ValueError( "No ISBN found in provided image." ) if book.isbn13 not in isbnlist: raise ValueError( "Barcode does not match selected book." ) loan = Loan() loan.pupil = pupil loan.book = book loan.start_date = datetime.date(datetime.now()) book.current_location = BookLocation.LOAN.value session.add(loan) session.commit() flash("Loan has been recorded for '%s' to %s" % (book.title, pupil.name)) except ValueError as val_err: flash(str(val_err), 'error') return redirect_to_previous(True)
def edit_book(): """Update a book in the library.""" session = db_session() lookup_isbns = [] lookup_titles = [] books = session.query(Book).distinct().\ values(Book.isbn10, Book.isbn13, Book.title) for book in books: if book[0]: lookup_isbns.append(book[0]) if book[1]: lookup_isbns.append(book[1]) if book[2]: lookup_titles.append(book[2]) if lookup_isbns: lookup_isbns.sort() if lookup_titles: lookup_titles.sort() if request.method == 'GET': return render_template('edit_book.html', lookup_isbns=lookup_isbns, lookup_titles=lookup_titles) found_books = [] if request.method == 'POST': search_isbn = request.form['search_isbn'] search_title = request.form['search_title'] if search_title and search_title.strip(): search_term = '%' + search_title.strip() + '%' found_books = session.query(Book).\ filter(Book.title.ilike(search_term)).all() if search_isbn and search_isbn.strip(): search_term = '%' + search_isbn.strip() + '%' found_books = found_books + session.query(Book).\ filter(or_(Book.isbn10.ilike(search_term), Book.isbn13.ilike(search_term))).all() result = render_template('edit_book.html', lookup_isbns=lookup_isbns, lookup_titles=lookup_titles, search_isbn=search_isbn, search_title=search_title, thumbnails_dir=THUMBNAILS_DIR, found_books=sorted(found_books, key=lambda b: b.title)) return result
def add_book(): """Add a book to the library.""" try: book = Book() # defaults book.is_available = True book.current_location = BookLocation.LIBRARY.value book.title = request.form['book_title'].strip() book.isbn10 = request.form['isbn10'].strip() book.isbn13 = request.form['isbn13'].strip() book.description = request.form['book_description'].strip() book.preview_url = request.form['preview_url'].strip() for author_name in request.form['book_authors'].split(','): author_name = author_name.strip() author = Author.query.filter(Author.name == author_name).first() if not author: author = Author(author_name) book.authors.append(author) for category_name in request.form['book_categories'].split(','): category_name = category_name.strip() category = Category.query.\ filter(Category.name == category_name).first() if not category: category = Category(category_name) book.categories.append(category) book.thumbnail_url = request.form['thumbnail_url'].strip() if book.thumbnail_url: title = [ c for c in book.title.replace(' ', '_') if re.match(r'\w', c) ] image_name = ''.join(title) + book.isbn13 + '.jpg' img = open(THUMBNAILS_ABSOLUTE_DIR + image_name, 'wb') img.write(urllib_request.urlopen(book.thumbnail_url).read()) book.image_name = image_name session = db_session() session.add(book) session.commit() flash("The book has been added to the library successfully!") except RuntimeError as rte: error_message = "Something has gone wrong!" if isinstance(rte, exc.IntegrityError): error_message += "<br>It seems that the book '%s' "\ "already exists in the library." % book.title flash(error_message, 'error') return render_template('add_book.html')
def add_user(): """Give access to a classroom, pupil or admin.""" session = db_session() new_access_form = NewAccessForm() new_access_form.classroom.choices = [(0, 'None')] + \ [(cr[0], '{} ({})'.format(cr[1], cr[2])) for cr in session.query(Classroom.id, Classroom.name, Classroom.year).distinct()] if request.method == 'POST': if new_access_form.validate_on_submit(): # check if it's an existing user user = User.query.filter( User.username.ilike(new_access_form.username.data)).first() # check if a classroom id has been passed classroom = Classroom.query.filter( Classroom.id == new_access_form.classroom.data).first() # for individual user; should be unique if user and not classroom: flash( "This username is already in use, " + "please chose a different username.", 'error') # for classroom username/password change, update db if user and classroom: user.username = new_access_form.username.data user.password = new_access_form.password.data user.is_admin = new_access_form.is_admin.data session.commit() flash("Login details have been updated!") # for new users, create elif not user: user = User() user.username = new_access_form.username.data user.password = new_access_form.password.data user.is_admin = new_access_form.is_admin.data # link to classroom if provided if classroom: classroom.user = user session.add(user) session.commit() flash("Access has been created successfully!") else: flash("Invalid entries!", 'error') return render_template('access.html', new_access_form=new_access_form)
def init_loan_forms(): """Initialise a new_loan and loan_return forms.""" session = db_session() new_loan_form = None loan_return_form = None if current_user.is_authenticated: loan_return_form = LoanReturnForm() if current_user.classroom: new_loan_form = NewLoanForm() class_id = current_user.classroom.id new_loan_form.pupil_id.choices = \ [(p[0], p[1]) for p in session.query(Pupil.id, Pupil.name). filter(Pupil.classroom_id == class_id)] return new_loan_form, loan_return_form
def record_return(): """Records return of a book.""" session = db_session() loan_return_form = LoanReturnForm() try: book = Book.query.filter( Book.id == int(loan_return_form.book_id.data) ).first() if not book: raise ValueError("Invalid entries!") if not loan_return_form.barcode_img.data: raise ValueError( "No barcode image provided. Please scan the barcode.") isbnlist = scan_for_isbn( request.files[loan_return_form.barcode_img.name]) if len(isbnlist) < 1: raise ValueError("No ISBN found in provided image.") if book.isbn13 not in isbnlist: raise ValueError("Barcode does not match selected book.") loan = book.current_loan if not loan: raise ValueError("No loans found for this book.") loan.end_date = datetime.date(datetime.now()) book.current_location = BookLocation.LIBRARY.value session.commit() flash("Book return has been recorded for '%s' by %s" % (book.title, loan.pupil.name)) except ValueError as val_err: flash(str(val_err), 'error') return redirect_to_previous(True)
def index(): """Render the home page. Initialising the search box with books titles, authors names, and categories names. """ session = db_session() search_terms = [] for book_title in session.query(Book.title).distinct(): search_terms.append(book_title[0]) for category_name in session.query(Category.name).distinct(): search_terms.append(category_name[0]) for author_name in session.query(Author.name).distinct(): search_terms.append(author_name[0]) search_terms.sort() return render_template('home.html', search_terms=search_terms)
def update_db(classroom_file): """Persist the the details in the file into the database.""" try: session = db_session() with open(classroom_file) as csv_file: reader = csv.reader(csv_file, delimiter=',', quotechar='"') current_classroom = None for row in reader: if not current_classroom or \ not row[0] == current_classroom.name: current_classroom = Classroom(row[0]) current_classroom.year = row[1] session.add(current_classroom) current_classroom.pupils.append(Pupil(row[2])) session.commit() return True except Exception as err: # pylint: disable=W0703 flash("Something has gone wrong!<br>" + str(err), 'error') return False
def auto_load_books(): """Automated book loading. Combining the lookup and add functions, this function aims at bulk loading of books in the background. """ session = db_session() lookup_limit = request.args.get('n') # lookup books succeeded = [] failed = [] counter = 1 try: for isbn in execute_sql('fetch_isbn', lookup_limit): # ensure we don't exceed google or amazon api usage limit if counter % 3 == 0: sleep(random.uniform(60, 90)) else: sleep(random.uniform(5, 10)) counter += 1 api_client = APIClient(isbn, None) found_books = api_client.find_books(direct_search_only=True) # to ensure only the right book is added, only search resulting # yielding 1 result is accepted. if len(found_books) != 1: failed.append( (isbn[0], "search results %d" % len(found_books))) continue # now we add the book. book = found_books[0] # defaults book.is_available = True book.current_location = BookLocation.LIBRARY.value if book.thumbnail_url: image_name = ''.join([ c for c in book.title.replace(' ', '_') if re.match(r'\w', c) ]) + book.isbn13 + '.jpg' img = open(THUMBNAILS_ABSOLUTE_DIR + image_name, 'wb') img.write(urllib_request.urlopen(book.thumbnail_url).read()) book.image_name = image_name for i in range(len(book.authors)): lookup_author = Author.query.filter( Author.name == book.authors[i].name).first() if lookup_author: book.authors[i] = None book.authors[i] = lookup_author for i in range(len(book.categories)): lookup_category = Category.query.filter( Category.name == book.categories[i].name).first() if lookup_category: book.categories[i] = None book.categories[i] = lookup_category session.add(book) session.commit() succeeded.append(isbn[0]) execute_sql('update_success', succeeded=succeeded) execute_sql('update_failed', failed=failed) except RuntimeError as rt_error: flash("Something has gone wrong! " + str(rt_error)) flash('success: ' + ','.join(str(i) for i in succeeded)) flash('failed: ' + ','.join(str(i) for i, e in failed)) return redirect('books/view')
def view_books(ready_books=None): """Display books in the library. Defaults to displaying available books only. If `include_unavailable` parameter is set in the `request`, it display all books; this is used in the admin view. """ session = db_session() # get search cache search_cache = [] for book_title in session.query(Book.title).distinct(): search_cache.append(book_title[0]) for category_name in session.query(Category.name).distinct(): search_cache.append(category_name[0]) for author_name in session.query(Author.name).distinct(): search_cache.append(author_name[0]) search_cache.sort() # initialise loan forms new_loan_form, loan_return_form = init_loan_forms() # requpest parameters page = request.args.get(get_page_parameter(), type=int, default=1) include_unavailable = request.args.get('include-unavailable') search_term = request.args.get('q') # if books have been provided, display them if ready_books: total = len(ready_books) books = ready_books # apply search criteria if provided elif search_term: search_term = '%' + search_term.strip() + '%' total = session.query(Book).\ join(Author.books).\ filter(or_(Book.title.ilike(search_term), Author.name.ilike(search_term))).\ union( session.query(Book). join(Category.books). filter(or_(Book.title.ilike(search_term), Category.name.ilike(search_term))) ).count() books = session.query(Book).\ join(Author.books).\ filter(or_(Book.title.ilike(search_term), Author.name.ilike(search_term))).\ union( session.query(Book). join(Category.books). filter(or_(Book.title.ilike(search_term), Category.name.ilike(search_term))) ).order_by(Book.title).\ limit(PER_PAGE).offset((page - 1) * PER_PAGE) # include all books if specified elif include_unavailable: total = session.query(Book.id).count() books = session.query(Book).order_by(Book.title).\ limit(PER_PAGE).offset((page - 1) * PER_PAGE) # in all other cases, display all vailable books else: total = session.query(Book).filter(Book.is_available == 1).count() books = session.query(Book).filter(Book.is_available == 1).\ order_by(Book.title).limit(PER_PAGE).offset((page - 1) * PER_PAGE) pagination = Pagination(page=page, total=total, per_page=PER_PAGE, css_framework="bootstrap3") return render_template('view_book.html', books=books, new_loan_form=new_loan_form, loan_return_form=loan_return_form, pagination=pagination, search_cache=search_cache, thumbnails_dir=THUMBNAILS_DIR)
def update_book(): """Update a book with provided details.""" try: session = db_session() update_id = request.form['book_id'] update_status = int(request.form['book_status'].strip()) update_title = request.form['book_title'].strip() update_description = request.form['book_description'].strip() update_thumbnail_url = request.form['book_thumbnail_url'].strip() update_preview_url = request.form['book_preview_url'].strip() update_categories = [ c.strip() for c in request.form['book_categories'].split(',') ] update_authors = [ a.strip() for a in request.form['book_authors'].split(',') ] book = session.query(Book).filter(Book.id == update_id).first() book.title = update_title book.description = update_description book.is_available = update_status # update categories # removed for category in book.categories: if category.name not in update_categories: book.categories.remove(category) # added for category_name in update_categories: if category_name not in [c.name for c in book.categories]: book.categories.append(Category(category_name)) # update authors # removed for author in book.authors: if author.name not in update_authors: book.authors.remove(author) # added for author_name in update_authors: if author_name not in [a.name for a in book.authors]: book.authors.append(Author(author_name)) # thumbnail if update_thumbnail_url and book.thumbnail_url != update_thumbnail_url: book.thumbnail_url = update_thumbnail_url title = [ c for c in book.title.replace(' ', '_') if re.match(r'\w', c) ] image_name = ''.join(title) + book.isbn13 + '.jpg' img = open(THUMBNAILS_ABSOLUTE_DIR + image_name, 'wb') img.write(urllib_request.urlopen(book.thumbnail_url).read()) book.image_name = image_name # preview url if update_preview_url and book.preview_url != update_preview_url: book.preview_url = update_preview_url session.commit() flash("The book has been updated successfully!") except RuntimeError as rte: flash("Something has gone wrong! <br>%s" % str(rte), 'error') return redirect('books/edit')