import os from io import BytesIO from tempfile import TemporaryDirectory from typing import Optional from django.contrib.auth.decorators import login_required from django.http import FileResponse, HttpRequest, HttpResponse from django.shortcuts import get_object_or_404, redirect, render from django.utils.text import slugify from django.utils.translation import ugettext as _ import labels import PIL from django_tables2 import RequestConfig from reportlab.graphics import barcode, shapes from biscuit.core.models import School from biscuit.core.util import messages from .forms import ( BookAddISBNForm, BookCopiesBulkAddForm, BookCopyEditForm, BookEditForm, BookGetCopyForm, PersonBorrowForm, ) from .models import Book, BookCopy from .tables import BookCopiesTable, BooksTable from .util import get_book_meta @login_required def books(request: HttpRequest) -> HttpResponse: context = {} # Get all books books = Book.objects.all() # Build table books_table = BooksTable(books) RequestConfig(request).configure(books_table) context["books_table"] = books_table return render(request, "exlibris/books.html", context) @login_required def add_book(request: HttpRequest) -> HttpResponse: context = {} book_add_isbn_form = BookAddISBNForm(request.POST or None) if request.method == "POST": if book_add_isbn_form.is_valid(): return redirect( "edit_book_by_isbn", isbn=book_add_isbn_form.cleaned_data["isbn"] ) context["book_add_isbn_form"] = book_add_isbn_form return render(request, "exlibris/add_book.html", context) @login_required def edit_book( request: HttpRequest, id_: Optional[int] = None, isbn: Optional[str] = None ) -> HttpResponse: context = {} if id_: # If an id is passed, get this book to edit it book = get_object_or_404(Book, pk=id_) book_edit_form = BookEditForm( request.POST or None, request.FILES or None, instance=book ) context["book"] = book else: # If no id was passed, create a new book # An ISBN can be passed as argument. If so, the # form will be pre-populated with data on that book. initial = get_book_meta(isbn) if initial: messages.info(request, _("Information found for this ISBN was pre-filled.")) else: messages.warning(request, _("No information found for this ISBN.")) book_edit_form = BookEditForm( request.POST or None, request.FILES or None, initial=initial ) if request.method == "POST": if book_edit_form.is_valid(): book_edit_form.save(commit=True) messages.success(request, _("The book has been saved.")) return redirect("books") context["book_edit_form"] = book_edit_form return render(request, "exlibris/edit_book.html", context) @login_required def delete_book(request: HttpRequest, id_: int) -> HttpResponse: book = get_object_or_404(Book, pk=id_) title = book.title if book.is_deleteable(): book.delete() messages.success(request, _("Book %s has been deleted.") % title) else: messages.error( request, _("Books with copies that have been borrowed cannot be deleted.") ) return redirect("books") @login_required def book(request: HttpRequest, id_: int, template: str) -> HttpResponse: context = {} book = get_object_or_404(Book, pk=id_) context["book"] = book # Get all copies copies = book.copies.all() # Build table copies_table = BookCopiesTable(copies) RequestConfig(request).configure(copies_table) context["copies_table"] = copies_table return render(request, "exlibris/book_%s.html" % template, context) @login_required def add_book_copies(request: HttpRequest, id_: int) -> HttpResponse: context = {} book = get_object_or_404(Book, pk=id_) context["book"] = book bulk_add_form = BookCopiesBulkAddForm(request.POST or None) if request.method == "POST": if bulk_add_form.is_valid(): new_copies = [ BookCopy(book=book) for _ in range(bulk_add_form.cleaned_data["count"]) ] for new_copy in new_copies: new_copy.save() messages.success( request, _("%d copies have been created.") % bulk_add_form.cleaned_data["count"], ) return redirect("book_by_id", id_=book.pk) context["book"] = book context["bulk_add_form"] = bulk_add_form return render(request, "exlibris/add_book_copies.html", context) @login_required def edit_book_copy(request: HttpRequest, id_: int) -> HttpResponse: context = {} book_copy = get_object_or_404(BookCopy, pk=id_) book_copy_edit_form = BookCopyEditForm(request.POST or None, instance=book_copy) context["book_copy"] = book_copy if request.method == "POST": if book_copy_edit_form.is_valid(): book_copy_edit_form.save(commit=True) messages.success(request, _("The book copy has been saved.")) return redirect("book_by_id", id_=book_copy.book.pk) context["book_copy_edit_form"] = book_copy_edit_form return render(request, "exlibris/edit_book_copy.html", context) @login_required def delete_book_copy(request: HttpRequest, id_: int) -> HttpResponse: book_copy = get_object_or_404(BookCopy, pk=id_) book = book_copy.book if book_copy.is_deleteable(): book_copy.delete() messages.success(request, _("Book copy of %s has been deleted.") % book.title) else: messages.error( request, _("Book copies that have been borrowed cannot be deleted.") ) return redirect("book_by_id", id_=book.id) @login_required def book_copies_labels(request: HttpRequest, id_: int) -> HttpResponse: book = get_object_or_404(Book, pk=id_) copies = book.copies.all() with TemporaryDirectory() as temp_dir: def draw_label(label, width, height, obj): title = shapes.String(0, height - 10, obj.book.title, fontSize=10) label.add(title) copy_desc = shapes.String( 0, height - 20, _("Copy number: %d") % obj.copy_num, fontSize=8 ) label.add(copy_desc) barcode_raw = barcode.createBarcodeImageInMemory( "Code128", value=obj.barcode, width=400, height=120 ) barcode_image = PIL.Image.open(BytesIO(barcode_raw)) barcode_file = os.path.join(temp_dir, "%s.png" % obj.barcode) barcode_image.save(barcode_file, "png") label.add( shapes.Image(0, 30, width, 30, os.path.join(temp_dir, barcode_file)) ) school = shapes.String(0, 8, School.objects.first().name, fontSize=10) label.add(school) bottom_line = shapes.String( 0, 0, "BiscuIT, the Free School Information System", fontSize=6 ) label.add(bottom_line) specs = labels.Specification( 210, 297, 3, 7, 63, 40, left_padding=3, top_padding=3, bottom_padding=3, right_padding=3, ) sheet = labels.Sheet(specs, draw_label) sheet.add_labels(copies) pdf_file = os.path.join(temp_dir, "label_%s.pdf" % slugify(book.title)) sheet.save(pdf_file) return FileResponse(open(pdf_file, "rb"), as_attachment=True) @login_required def get_copy(request: HttpRequest) -> HttpResponse: context = {} book_copy_form = BookGetCopyForm(request.POST or None) if request.method == "POST": if book_copy_form.is_valid(): return redirect( "book_copy_by_barcode", barcode=book_copy_form.cleaned_data["barcode"] ) context["book_copy_form"] = book_copy_form return render(request, "exlibris/get_copy.html", context) @login_required def book_copy(request: HttpRequest, barcode: str, template: str) -> HttpResponse: context = {} book_copy = get_object_or_404(BookCopy, barcode=barcode) context["book_copy"] = book_copy return render(request, "exlibris/copy_%s.html" % template, context) @login_required def person_borrow(request: HttpRequest) -> HttpResponse: context = {} person_borrow_form = PersonBorrowForm(request.POST or None) if request.method == "POST": if person_borrow_form.is_valid(): person = person_borrow_form.cleaned_data["borrower"] for barcode_line in person_borrow_form.cleaned_data[ "barcodes" ].splitlines(): try: book_copy = BookCopy.objects.get(barcode=barcode_line) except BookCopy.DoesNotExist: messages.error( request, _("Barcode %(barcode)s is invalid.") % {"barcode": barcode_line}, ) continue book_copy.borrower = person book_copy.borrow_count = book_copy.borrow_count + 1 book_copy.save() messages.success( request, _("Book %(title)s borrowed to %(person)s.") % {"title": book_copy.book.title, "person": person}, ) person_borrow_form = PersonBorrowForm() context["person_borrow_form"] = person_borrow_form return render(request, "exlibris/person_borrow.html", context)