Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • AlekSIS/onboarding/AlekSIS-App-Exlibris
1 result
Show changes
Commits on Source (1)
import pkg_resources import pkg_resources
try: try:
__version__ = pkg_resources.get_distribution('BiscuIT-App-Exlibris').version __version__ = pkg_resources.get_distribution("BiscuIT-App-Exlibris").version
except Exception: except Exception:
__version__ = 'unknown' __version__ = "unknown"
default_app_config = 'biscuit.apps.exlibris.apps.ExlibrisConfig' default_app_config = "biscuit.apps.exlibris.apps.ExlibrisConfig"
# Additional apps to add to BiscuIT's INSTALLED_APPS on loading # Additional apps to add to BiscuIT's INSTALLED_APPS on loading
INSTALLED_APPS = ['isbn_field'] INSTALLED_APPS = ["isbn_field"]
...@@ -2,5 +2,5 @@ from biscuit.core.util.apps import AppConfig ...@@ -2,5 +2,5 @@ from biscuit.core.util.apps import AppConfig
class ExlibrisConfig(AppConfig): class ExlibrisConfig(AppConfig):
name = 'biscuit.apps.exlibris' name = "biscuit.apps.exlibris"
verbose_name = 'BiscuIT - Exlibris (School book management)' verbose_name = "BiscuIT - Exlibris (School book management)"
from django import forms from django import forms
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django_select2.forms import Select2Widget from django_select2.forms import Select2Widget
from biscuit.core.models import Person from biscuit.core.models import Person
...@@ -10,47 +11,47 @@ from .models import Book, BookCopy ...@@ -10,47 +11,47 @@ from .models import Book, BookCopy
class BookAddISBNForm(forms.ModelForm): class BookAddISBNForm(forms.ModelForm):
class Meta: class Meta:
model = Book model = Book
fields = ['isbn'] fields = ["isbn"]
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.fields['isbn'].widget.attrs.update({'autofocus': 'autofocus'}) self.fields["isbn"].widget.attrs.update({"autofocus": "autofocus"})
class BookGetCopyForm(forms.ModelForm): class BookGetCopyForm(forms.ModelForm):
class Meta: class Meta:
model = BookCopy model = BookCopy
fields = ['barcode'] fields = ["barcode"]
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.fields['barcode'].widget.attrs.update({'autofocus': 'autofocus'}) self.fields["barcode"].widget.attrs.update({"autofocus": "autofocus"})
class BookEditForm(forms.ModelForm): class BookEditForm(forms.ModelForm):
class Meta: class Meta:
model = Book model = Book
fields = ['isbn', 'title', 'author', fields = ["isbn", "title", "author", "publisher", "year", "edition", "cover"]
'publisher', 'year', 'edition', 'cover']
class BookCopyEditForm(forms.ModelForm): class BookCopyEditForm(forms.ModelForm):
class Meta: class Meta:
model = BookCopy model = BookCopy
fields = ['borrower', 'borrow_count', fields = ["borrower", "borrow_count", "condition", "remarks", "barcode"]
'condition', 'remarks', 'barcode']
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.fields['borrower'].disabled = True self.fields["borrower"].disabled = True
class BookCopiesBulkAddForm(forms.Form): class BookCopiesBulkAddForm(forms.Form):
count = forms.IntegerField(label=_('Number of copies')) count = forms.IntegerField(label=_("Number of copies"))
class PersonBorrowForm(forms.Form): class PersonBorrowForm(forms.Form):
borrower = forms.ModelChoiceField(label=_('Person'), queryset=Person.objects.all(), widget=Select2Widget) borrower = forms.ModelChoiceField(
barcodes = forms.CharField(label=_('Barcodes'), widget=forms.Textarea) label=_("Person"), queryset=Person.objects.all(), widget=Select2Widget
)
barcodes = forms.CharField(label=_("Barcodes"), widget=forms.Textarea)
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
MENUS = { MENUS = {
'NAV_MENU_CORE': [ "NAV_MENU_CORE": [
{ {
'name': _('Books'), "name": _("Books"),
'url': '#', "url": "#",
'root': True, "root": True,
'validators': ['menu_generator.validators.is_authenticated', 'biscuit.core.util.core_helpers.has_person'], "validators": [
'submenu': [ "menu_generator.validators.is_authenticated",
"biscuit.core.util.core_helpers.has_person",
],
"submenu": [
{ {
'name': _('List of books'), "name": _("List of books"),
'url': 'books', "url": "books",
'validators': ['menu_generator.validators.is_authenticated'] "validators": ["menu_generator.validators.is_authenticated"],
}, },
{ {
'name': _('Add book'), "name": _("Add book"),
'url': 'add_book', "url": "add_book",
'validators': ['menu_generator.validators.is_authenticated'] "validators": ["menu_generator.validators.is_authenticated"],
}, },
{ {
'name': _('Open copy'), "name": _("Open copy"),
'url': 'get_copy', "url": "get_copy",
'validators': ['menu_generator.validators.is_authenticated'] "validators": ["menu_generator.validators.is_authenticated"],
}, },
{ {
'name': _('Person borrowing'), "name": _("Person borrowing"),
'url': 'person_borrow', "url": "person_borrow",
'validators': ['menu_generator.validators.is_authenticated'] "validators": ["menu_generator.validators.is_authenticated"],
} },
] ],
} }
] ]
} }
# Generated by Django 2.2.5 on 2019-09-03 18:30 # Generated by Django 2.2.5 on 2019-09-03 18:30
import biscuit.core.util.core_helpers
from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models
import isbn_field.fields import isbn_field.fields
import isbn_field.validators import isbn_field.validators
import biscuit.core.util.core_helpers
class Migration(migrations.Migration): class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
('core', '0001_initial'), ("core", "0001_initial"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Book', name="Book",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('title', models.CharField(max_length=50, verbose_name='Book title')), "id",
('author', models.CharField(blank=True, max_length=50, verbose_name='Author name')), models.AutoField(
('publisher', models.CharField(blank=True, max_length=50, verbose_name='Publishing company')), auto_created=True,
('edition', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='Number of edition')), primary_key=True,
('year', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='Publishing year')), serialize=False,
('isbn', isbn_field.fields.ISBNField(max_length=28, unique=True, validators=[isbn_field.validators.ISBNValidator], verbose_name='ISBN number')), verbose_name="ID",
('cover', models.ImageField(blank=True, null=True, upload_to='', verbose_name='Photo of cover')), ),
('school', models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='core.School')), ),
("title", models.CharField(max_length=50, verbose_name="Book title")),
(
"author",
models.CharField(
blank=True, max_length=50, verbose_name="Author name"
),
),
(
"publisher",
models.CharField(
blank=True, max_length=50, verbose_name="Publishing company"
),
),
(
"edition",
models.PositiveSmallIntegerField(
blank=True, null=True, verbose_name="Number of edition"
),
),
(
"year",
models.PositiveSmallIntegerField(
blank=True, null=True, verbose_name="Publishing year"
),
),
(
"isbn",
isbn_field.fields.ISBNField(
max_length=28,
unique=True,
validators=[isbn_field.validators.ISBNValidator],
verbose_name="ISBN number",
),
),
(
"cover",
models.ImageField(
blank=True,
null=True,
upload_to="",
verbose_name="Photo of cover",
),
),
(
"school",
models.ForeignKey(
default=1,
on_delete=django.db.models.deletion.CASCADE,
to="core.School",
),
),
], ],
options={ options={
'ordering': ['title', 'year', 'edition'], "ordering": ["title", "year", "edition"],
'unique_together': {('school', 'title', 'author', 'edition', 'year')}, "unique_together": {("school", "title", "author", "edition", "year")},
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='BookCopy', name="BookCopy",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('copy_num', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='Current number of copy')), "id",
('borrow_count', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='Number of borrowings')), models.AutoField(
('condition', models.CharField(blank=True, choices=[('U', 'New, unused'), ('N', 'New, used'), ('S', 'Slightly worn'), ('H', 'Heavily worn')], max_length=1, verbose_name='Condition')), auto_created=True,
('remarks', models.CharField(blank=True, max_length=100, verbose_name='Remarks')), primary_key=True,
('barcode', models.CharField(max_length=20, unique=True, verbose_name='Barcode')), serialize=False,
('book', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='copies', to='exlibris.Book')), verbose_name="ID",
('borrower', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='books', to='core.Person')), ),
),
(
"copy_num",
models.PositiveSmallIntegerField(
blank=True, null=True, verbose_name="Current number of copy"
),
),
(
"borrow_count",
models.PositiveSmallIntegerField(
blank=True, null=True, verbose_name="Number of borrowings"
),
),
(
"condition",
models.CharField(
blank=True,
choices=[
("U", "New, unused"),
("N", "New, used"),
("S", "Slightly worn"),
("H", "Heavily worn"),
],
max_length=1,
verbose_name="Condition",
),
),
(
"remarks",
models.CharField(
blank=True, max_length=100, verbose_name="Remarks"
),
),
(
"barcode",
models.CharField(
max_length=20, unique=True, verbose_name="Barcode"
),
),
(
"book",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="copies",
to="exlibris.Book",
),
),
(
"borrower",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="books",
to="core.Person",
),
),
], ],
options={ options={
'ordering': ['book__title', 'book__year', 'book__edition', 'copy_num'], "ordering": ["book__title", "book__year", "book__edition", "copy_num"],
}, },
), ),
] ]
...@@ -6,13 +6,15 @@ from django.db import migrations, models ...@@ -6,13 +6,15 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0001_initial'), ("core", "0001_initial"),
('exlibris', '0001_initial'), ("exlibris", "0001_initial"),
] ]
operations = [ operations = [
migrations.AddIndex( migrations.AddIndex(
model_name='bookcopy', model_name="bookcopy",
index=models.Index(fields=['barcode'], name='exlibris_bo_co_barc_55ac33_idx'), index=models.Index(
fields=["barcode"], name="exlibris_bo_co_barc_55ac33_idx"
),
), ),
] ]
...@@ -7,24 +7,31 @@ from isbn_field import ISBNField ...@@ -7,24 +7,31 @@ from isbn_field import ISBNField
class Book(models.Model): class Book(models.Model):
"""A book that is available for borrowing by persons.""" """A book that is available for borrowing by persons."""
title = models.CharField(verbose_name=_('Book title'), max_length=50) title = models.CharField(verbose_name=_("Book title"), max_length=50)
author = models.CharField(verbose_name=_( author = models.CharField(verbose_name=_("Author name"), max_length=50, blank=True)
'Author name'), max_length=50, blank=True) publisher = models.CharField(
publisher = models.CharField(verbose_name=_( verbose_name=_("Publishing company"), max_length=50, blank=True
'Publishing company'), max_length=50, blank=True) )
edition = models.PositiveSmallIntegerField( edition = models.PositiveSmallIntegerField(
verbose_name=_('Number of edition'), blank=True, null=True) verbose_name=_("Number of edition"), blank=True, null=True
)
year = models.PositiveSmallIntegerField( year = models.PositiveSmallIntegerField(
verbose_name=_('Publishing year'), blank=True, null=True) verbose_name=_("Publishing year"), blank=True, null=True
)
isbn = ISBNField(verbose_name=_('ISBN number'), unique=True) isbn = ISBNField(verbose_name=_("ISBN number"), unique=True)
cover = models.ImageField(verbose_name=_( cover = models.ImageField(verbose_name=_("Photo of cover"), blank=True, null=True)
'Photo of cover'), blank=True, null=True)
def __str__(self) -> str: def __str__(self) -> str:
return '%s (%s, %d. %d %d)' % (self.title, self.author, self.edition, _('edition'), self.year) return "%s (%s, %d. %d %d)" % (
self.title,
self.author,
self.edition,
_("edition"),
self.year,
)
def is_deleteable(self) -> bool: def is_deleteable(self) -> bool:
for book_copy in self.copies.all(): for book_copy in self.copies.all():
...@@ -34,8 +41,8 @@ class Book(models.Model): ...@@ -34,8 +41,8 @@ class Book(models.Model):
return True return True
class Meta: class Meta:
unique_together = [['title', 'author', 'edition', 'year']] unique_together = [["title", "author", "edition", "year"]]
ordering = ['title', 'year', 'edition'] ordering = ["title", "year", "edition"]
class BookCopy(models.Model): class BookCopy(models.Model):
...@@ -44,40 +51,52 @@ class BookCopy(models.Model): ...@@ -44,40 +51,52 @@ class BookCopy(models.Model):
""" """
CONDITION_CHOICES = [ CONDITION_CHOICES = [
('U', _('New, unused')), ("U", _("New, unused")),
('N', _('New, used')), ("N", _("New, used")),
('S', _('Slightly worn')), ("S", _("Slightly worn")),
('H', _('Heavily worn')) ("H", _("Heavily worn")),
] ]
book = models.ForeignKey( book = models.ForeignKey(
'Book', blank=True, null=True, on_delete=models.CASCADE, related_name='copies') "Book", blank=True, null=True, on_delete=models.CASCADE, related_name="copies"
)
borrower = models.ForeignKey( borrower = models.ForeignKey(
'core.Person', blank=True, null=True, on_delete=models.CASCADE, related_name='books') "core.Person",
blank=True,
null=True,
on_delete=models.CASCADE,
related_name="books",
)
copy_num = models.PositiveSmallIntegerField( copy_num = models.PositiveSmallIntegerField(
verbose_name=_('Current number of copy'), blank=True, null=True) verbose_name=_("Current number of copy"), blank=True, null=True
)
borrow_count = models.PositiveSmallIntegerField( borrow_count = models.PositiveSmallIntegerField(
verbose_name=_('Number of borrowings'), blank=True, null=True) verbose_name=_("Number of borrowings"), blank=True, null=True
)
condition = models.CharField( condition = models.CharField(
verbose_name=_('Condition'), choices=CONDITION_CHOICES, max_length=1, blank=True) verbose_name=_("Condition"), choices=CONDITION_CHOICES, max_length=1, blank=True
remarks = models.CharField( )
verbose_name=_('Remarks'), max_length=100, blank=True) remarks = models.CharField(verbose_name=_("Remarks"), max_length=100, blank=True)
barcode = models.CharField(verbose_name=_( barcode = models.CharField(verbose_name=_("Barcode"), max_length=20, unique=True)
'Barcode'), max_length=20, unique=True)
def save(self, *args, **kwargs) -> None: def save(self, *args, **kwargs) -> None:
if not self.copy_num: if not self.copy_num:
if self.book.copies.exclude(copy_num__isnull=True).exists(): if self.book.copies.exclude(copy_num__isnull=True).exists():
self.copy_num = self.book.copies.exclude( self.copy_num = (
copy_num__isnull=True).order_by('copy_num').last().copy_num + 1 self.book.copies.exclude(copy_num__isnull=True)
.order_by("copy_num")
.last()
.copy_num
+ 1
)
else: else:
self.copy_num = 1 self.copy_num = 1
if not self.barcode: if not self.barcode:
self.barcode = '%s-%s' % (self.book.isbn, self.copy_num) self.barcode = "%s-%s" % (self.book.isbn, self.copy_num)
super().save(*args, **kwargs) super().save(*args, **kwargs)
...@@ -85,5 +104,5 @@ class BookCopy(models.Model): ...@@ -85,5 +104,5 @@ class BookCopy(models.Model):
return not self.borrow_count and not self.borrower return not self.borrow_count and not self.borrower
class Meta: class Meta:
ordering = ['book__title', 'book__year', 'book__edition', 'copy_num'] ordering = ["book__title", "book__year", "book__edition", "copy_num"]
indexes = [models.Index(fields=['barcode'])] indexes = [models.Index(fields=["barcode"])]
...@@ -8,34 +8,48 @@ from django_tables2.utils import A ...@@ -8,34 +8,48 @@ from django_tables2.utils import A
class BooksTable(tables.Table): class BooksTable(tables.Table):
class Meta: class Meta:
attrs = { attrs = {
'class': 'table table-striped table-bordered table-hover table-responsive-xl'} "class": "table table-striped table-bordered table-hover table-responsive-xl"
}
isbn = tables.LinkColumn('book_by_id', args=[A('id')])
isbn = tables.LinkColumn("book_by_id", args=[A("id")])
title = tables.LinkColumn('book_by_id', args=[A('id')], attrs={'td': {
'data-poload': lambda record: reverse('book_card_by_id', kwargs={'id_': record.id})}}) title = tables.LinkColumn(
"book_by_id",
args=[A("id")],
attrs={
"td": {
"data-poload": lambda record: reverse(
"book_card_by_id", kwargs={"id_": record.id}
)
}
},
)
author = tables.Column() author = tables.Column()
num_copies = tables.Column(verbose_name=_( num_copies = tables.Column(verbose_name=_("Copies"), accessor="copies.count")
'Copies'), accessor='copies.count')
edit = tables.LinkColumn('edit_book_by_id', args=[ edit = tables.LinkColumn(
A('id')], verbose_name='', text=_('Edit')) "edit_book_by_id", args=[A("id")], verbose_name="", text=_("Edit")
delete = tables.LinkColumn('delete_book_by_id', args=[ )
A('id')], verbose_name='', text=_('Delete')) delete = tables.LinkColumn(
"delete_book_by_id", args=[A("id")], verbose_name="", text=_("Delete")
)
class BookCopiesTable(tables.Table): class BookCopiesTable(tables.Table):
class Meta: class Meta:
attrs = { attrs = {
'class': 'table table-striped table-bordered table-hover table-responsive-xl'} "class": "table table-striped table-bordered table-hover table-responsive-xl"
}
copy_num = tables.Column() copy_num = tables.Column()
borrower = tables.Column() borrower = tables.Column()
condition = tables.Column() condition = tables.Column()
borrow_count = tables.Column() borrow_count = tables.Column()
edit = tables.LinkColumn('edit_book_copy_by_id', args=[ edit = tables.LinkColumn(
A('id')], verbose_name='', text=_('Edit')) "edit_book_copy_by_id", args=[A("id")], verbose_name="", text=_("Edit")
delete = tables.LinkColumn('delete_book_copy_by_id', args=[ )
A('id')], verbose_name='', text=_('Delete')) delete = tables.LinkColumn(
"delete_book_copy_by_id", args=[A("id")], verbose_name="", text=_("Delete")
)
...@@ -2,27 +2,36 @@ from django.urls import path ...@@ -2,27 +2,36 @@ from django.urls import path
from . import views from . import views
urlpatterns = [ urlpatterns = [
path('book/<int:id_>', views.book, path("book/<int:id_>", views.book, {"template": "full"}, name="book_by_id"),
{'template': 'full'}, name='book_by_id'), path(
path('book/<int:id_>/card', views.book, "book/<int:id_>/card", views.book, {"template": "card"}, name="book_card_by_id"
{'template': 'card'}, name='book_card_by_id'), ),
path('book/<int:id_>/edit', views.edit_book, name='edit_book_by_id'), path("book/<int:id_>/edit", views.edit_book, name="edit_book_by_id"),
path('book/<int:id_>/delete', views.delete_book, name='delete_book_by_id'), path("book/<int:id_>/delete", views.delete_book, name="delete_book_by_id"),
path('book/get_copy', views.get_copy, name='get_copy'), path("book/get_copy", views.get_copy, name="get_copy"),
path('book/copy/<barcode>', views.book_copy, path(
{'template': 'full'}, name='book_copy_by_barcode'), "book/copy/<barcode>",
path('book/<int:id_>/copies/labels', views.book_copies_labels, views.book_copy,
name='copies_labels_by_book_id'), {"template": "full"},
path('book/copy/<int:id_>/edit', views.edit_book_copy, name="book_copy_by_barcode",
name='edit_book_copy_by_id'), ),
path('book/copy/<int:id_>/delete', views.delete_book_copy, path(
name='delete_book_copy_by_id'), "book/<int:id_>/copies/labels",
path('book/<int:id_>/copies/add', views.add_book_copies, views.book_copies_labels,
name='add_copies_by_book_id'), name="copies_labels_by_book_id",
path('book/add', views.add_book, name='add_book'), ),
path('book/isbn/<isbn>', views.edit_book, name='edit_book_by_isbn'), path("book/copy/<int:id_>/edit", views.edit_book_copy, name="edit_book_copy_by_id"),
path('books', views.books, name='books'), path(
path('books/borrowing/person', views.person_borrow, name='person_borrow') "book/copy/<int:id_>/delete",
views.delete_book_copy,
name="delete_book_copy_by_id",
),
path(
"book/<int:id_>/copies/add", views.add_book_copies, name="add_copies_by_book_id"
),
path("book/add", views.add_book, name="add_book"),
path("book/isbn/<isbn>", views.edit_book, name="edit_book_by_isbn"),
path("books", views.books, name="books"),
path("books/borrowing/person", views.person_borrow, name="person_borrow"),
] ]
...@@ -6,24 +6,24 @@ import isbnlib ...@@ -6,24 +6,24 @@ import isbnlib
def get_book_meta(isbn: str) -> Dict[str, str]: def get_book_meta(isbn: str) -> Dict[str, str]:
meta_result = {} meta_result = {}
for provider in 'dnb', 'openl', 'goob': for provider in "dnb", "openl", "goob":
try: try:
meta_result.update(isbnlib.meta(isbn, provider)) meta_result.update(isbnlib.meta(isbn, provider))
except: except:
pass pass
meta_translated = {} meta_translated = {}
if 'Title' in meta_result: if "Title" in meta_result:
meta_translated['title'] = meta_result['Title'] meta_translated["title"] = meta_result["Title"]
if 'Authors' in meta_result: if "Authors" in meta_result:
meta_translated['author'] = ', '.join(meta_result['Authors']) meta_translated["author"] = ", ".join(meta_result["Authors"])
if 'Publisher' in meta_result: if "Publisher" in meta_result:
meta_translated['publisher'] = meta_result['Publisher'] meta_translated["publisher"] = meta_result["Publisher"]
if 'Year' in meta_result: if "Year" in meta_result:
try: try:
meta_translated['year'] = int(meta_result['Year']) meta_translated["year"] = int(meta_result["Year"])
except ValueError: except ValueError:
pass pass
meta_translated['isbn'] = isbn meta_translated["isbn"] = isbn
return meta_translated return meta_translated
from io import BytesIO
import os import os
from io import BytesIO
from tempfile import TemporaryDirectory from tempfile import TemporaryDirectory
from typing import Optional from typing import Optional
...@@ -9,17 +9,24 @@ from django.shortcuts import get_object_or_404, redirect, render ...@@ -9,17 +9,24 @@ from django.shortcuts import get_object_or_404, redirect, render
from django.utils.text import slugify from django.utils.text import slugify
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django_tables2 import RequestConfig
import labels import labels
import PIL import PIL
from django_tables2 import RequestConfig
from reportlab.graphics import barcode, shapes from reportlab.graphics import barcode, shapes
from biscuit.core.models import School from biscuit.core.models import School
from biscuit.core.util import messages from biscuit.core.util import messages
from .forms import BookAddISBNForm, BookEditForm, BookCopiesBulkAddForm, BookCopyEditForm, BookGetCopyForm, PersonBorrowForm from .forms import (
BookAddISBNForm,
BookCopiesBulkAddForm,
BookCopyEditForm,
BookEditForm,
BookGetCopyForm,
PersonBorrowForm,
)
from .models import Book, BookCopy from .models import Book, BookCopy
from .tables import BooksTable, BookCopiesTable from .tables import BookCopiesTable, BooksTable
from .util import get_book_meta from .util import get_book_meta
...@@ -33,9 +40,9 @@ def books(request: HttpRequest) -> HttpResponse: ...@@ -33,9 +40,9 @@ def books(request: HttpRequest) -> HttpResponse:
# Build table # Build table
books_table = BooksTable(books) books_table = BooksTable(books)
RequestConfig(request).configure(books_table) RequestConfig(request).configure(books_table)
context['books_table'] = books_table context["books_table"] = books_table
return render(request, 'exlibris/books.html', context) return render(request, "exlibris/books.html", context)
@login_required @login_required
...@@ -44,17 +51,21 @@ def add_book(request: HttpRequest) -> HttpResponse: ...@@ -44,17 +51,21 @@ def add_book(request: HttpRequest) -> HttpResponse:
book_add_isbn_form = BookAddISBNForm(request.POST or None) book_add_isbn_form = BookAddISBNForm(request.POST or None)
if request.method == 'POST': if request.method == "POST":
if book_add_isbn_form.is_valid(): if book_add_isbn_form.is_valid():
return redirect('edit_book_by_isbn', isbn=book_add_isbn_form.cleaned_data['isbn']) return redirect(
"edit_book_by_isbn", isbn=book_add_isbn_form.cleaned_data["isbn"]
)
context['book_add_isbn_form'] = book_add_isbn_form context["book_add_isbn_form"] = book_add_isbn_form
return render(request, 'exlibris/add_book.html', context) return render(request, "exlibris/add_book.html", context)
@login_required @login_required
def edit_book(request: HttpRequest, id_: Optional[int] = None, isbn: Optional[str] = None) -> HttpResponse: def edit_book(
request: HttpRequest, id_: Optional[int] = None, isbn: Optional[str] = None
) -> HttpResponse:
context = {} context = {}
if id_: if id_:
...@@ -62,9 +73,10 @@ def edit_book(request: HttpRequest, id_: Optional[int] = None, isbn: Optional[st ...@@ -62,9 +73,10 @@ def edit_book(request: HttpRequest, id_: Optional[int] = None, isbn: Optional[st
book = get_object_or_404(Book, pk=id_) book = get_object_or_404(Book, pk=id_)
book_edit_form = BookEditForm( book_edit_form = BookEditForm(
request.POST or None, request.FILES or None, instance=book) request.POST or None, request.FILES or None, instance=book
)
context['book'] = book context["book"] = book
else: else:
# If no id was passed, create a new book # If no id was passed, create a new book
...@@ -73,24 +85,24 @@ def edit_book(request: HttpRequest, id_: Optional[int] = None, isbn: Optional[st ...@@ -73,24 +85,24 @@ def edit_book(request: HttpRequest, id_: Optional[int] = None, isbn: Optional[st
initial = get_book_meta(isbn) initial = get_book_meta(isbn)
if initial: if initial:
messages.info(request, _( messages.info(request, _("Information found for this ISBN was pre-filled."))
'Information found for this ISBN was pre-filled.'))
else: else:
messages.warning(request, _('No information found for this ISBN.')) messages.warning(request, _("No information found for this ISBN."))
book_edit_form = BookEditForm( book_edit_form = BookEditForm(
request.POST or None, request.FILES or None, initial=initial) request.POST or None, request.FILES or None, initial=initial
)
if request.method == 'POST': if request.method == "POST":
if book_edit_form.is_valid(): if book_edit_form.is_valid():
book_edit_form.save(commit=True) book_edit_form.save(commit=True)
messages.success(request, _('The book has been saved.')) messages.success(request, _("The book has been saved."))
return redirect('books') return redirect("books")
context['book_edit_form'] = book_edit_form context["book_edit_form"] = book_edit_form
return render(request, 'exlibris/edit_book.html', context) return render(request, "exlibris/edit_book.html", context)
@login_required @login_required
...@@ -100,12 +112,13 @@ def delete_book(request: HttpRequest, id_: int) -> HttpResponse: ...@@ -100,12 +112,13 @@ def delete_book(request: HttpRequest, id_: int) -> HttpResponse:
if book.is_deleteable(): if book.is_deleteable():
book.delete() book.delete()
messages.success(request, _('Book %s has been deleted.') % title) messages.success(request, _("Book %s has been deleted.") % title)
else: else:
messages.error(request, messages.error(
_('Books with copies that have been borrowed cannot be deleted.')) request, _("Books with copies that have been borrowed cannot be deleted.")
)
return redirect('books') return redirect("books")
@login_required @login_required
...@@ -113,7 +126,7 @@ def book(request: HttpRequest, id_: int, template: str) -> HttpResponse: ...@@ -113,7 +126,7 @@ def book(request: HttpRequest, id_: int, template: str) -> HttpResponse:
context = {} context = {}
book = get_object_or_404(Book, pk=id_) book = get_object_or_404(Book, pk=id_)
context['book'] = book context["book"] = book
# Get all copies # Get all copies
copies = book.copies.all() copies = book.copies.all()
...@@ -121,9 +134,9 @@ def book(request: HttpRequest, id_: int, template: str) -> HttpResponse: ...@@ -121,9 +134,9 @@ def book(request: HttpRequest, id_: int, template: str) -> HttpResponse:
# Build table # Build table
copies_table = BookCopiesTable(copies) copies_table = BookCopiesTable(copies)
RequestConfig(request).configure(copies_table) RequestConfig(request).configure(copies_table)
context['copies_table'] = copies_table context["copies_table"] = copies_table
return render(request, 'exlibris/book_%s.html' % template, context) return render(request, "exlibris/book_%s.html" % template, context)
@login_required @login_required
...@@ -131,25 +144,28 @@ def add_book_copies(request: HttpRequest, id_: int) -> HttpResponse: ...@@ -131,25 +144,28 @@ def add_book_copies(request: HttpRequest, id_: int) -> HttpResponse:
context = {} context = {}
book = get_object_or_404(Book, pk=id_) book = get_object_or_404(Book, pk=id_)
context['book'] = book context["book"] = book
bulk_add_form = BookCopiesBulkAddForm(request.POST or None) bulk_add_form = BookCopiesBulkAddForm(request.POST or None)
if request.method == 'POST': if request.method == "POST":
if bulk_add_form.is_valid(): if bulk_add_form.is_valid():
new_copies = [BookCopy(book=book) for _ in range( new_copies = [
bulk_add_form.cleaned_data['count'])] BookCopy(book=book) for _ in range(bulk_add_form.cleaned_data["count"])
]
for new_copy in new_copies: for new_copy in new_copies:
new_copy.save() new_copy.save()
messages.success(request, _('%d copies have been created.') % messages.success(
bulk_add_form.cleaned_data['count']) request,
return redirect('book_by_id', id_=book.pk) _("%d copies have been created.") % bulk_add_form.cleaned_data["count"],
)
return redirect("book_by_id", id_=book.pk)
context['book'] = book context["book"] = book
context['bulk_add_form'] = bulk_add_form context["bulk_add_form"] = bulk_add_form
return render(request, 'exlibris/add_book_copies.html', context) return render(request, "exlibris/add_book_copies.html", context)
@login_required @login_required
...@@ -157,21 +173,20 @@ def edit_book_copy(request: HttpRequest, id_: int) -> HttpResponse: ...@@ -157,21 +173,20 @@ def edit_book_copy(request: HttpRequest, id_: int) -> HttpResponse:
context = {} context = {}
book_copy = get_object_or_404(BookCopy, pk=id_) book_copy = get_object_or_404(BookCopy, pk=id_)
book_copy_edit_form = BookCopyEditForm( book_copy_edit_form = BookCopyEditForm(request.POST or None, instance=book_copy)
request.POST or None, instance=book_copy)
context['book_copy'] = book_copy context["book_copy"] = book_copy
if request.method == 'POST': if request.method == "POST":
if book_copy_edit_form.is_valid(): if book_copy_edit_form.is_valid():
book_copy_edit_form.save(commit=True) book_copy_edit_form.save(commit=True)
messages.success(request, _('The book copy has been saved.')) messages.success(request, _("The book copy has been saved."))
return redirect('book_by_id', id_=book_copy.book.pk) return redirect("book_by_id", id_=book_copy.book.pk)
context['book_copy_edit_form'] = book_copy_edit_form context["book_copy_edit_form"] = book_copy_edit_form
return render(request, 'exlibris/edit_book_copy.html', context) return render(request, "exlibris/edit_book_copy.html", context)
@login_required @login_required
...@@ -181,13 +196,13 @@ def delete_book_copy(request: HttpRequest, id_: int) -> HttpResponse: ...@@ -181,13 +196,13 @@ def delete_book_copy(request: HttpRequest, id_: int) -> HttpResponse:
if book_copy.is_deleteable(): if book_copy.is_deleteable():
book_copy.delete() book_copy.delete()
messages.success(request, _( messages.success(request, _("Book copy of %s has been deleted.") % book.title)
'Book copy of %s has been deleted.') % book.title)
else: else:
messages.error(request, messages.error(
_('Book copies that have been borrowed cannot be deleted.')) request, _("Book copies that have been borrowed cannot be deleted.")
)
return redirect('book_by_id', id_=book.id) return redirect("book_by_id", id_=book.id)
@login_required @login_required
...@@ -196,32 +211,46 @@ def book_copies_labels(request: HttpRequest, id_: int) -> HttpResponse: ...@@ -196,32 +211,46 @@ def book_copies_labels(request: HttpRequest, id_: int) -> HttpResponse:
copies = book.copies.all() copies = book.copies.all()
with TemporaryDirectory() as temp_dir: with TemporaryDirectory() as temp_dir:
def draw_label(label, width, height, obj): def draw_label(label, width, height, obj):
title = shapes.String(0, height - 10, obj.book.title, fontSize=10) title = shapes.String(0, height - 10, obj.book.title, fontSize=10)
label.add(title) label.add(title)
copy_desc = shapes.String( copy_desc = shapes.String(
0, height - 20, _('Copy number: %d') % obj.copy_num, fontSize=8) 0, height - 20, _("Copy number: %d") % obj.copy_num, fontSize=8
)
label.add(copy_desc) label.add(copy_desc)
barcode_raw = barcode.createBarcodeImageInMemory( barcode_raw = barcode.createBarcodeImageInMemory(
'Code128', value=obj.barcode, width=400, height=120) "Code128", value=obj.barcode, width=400, height=120
)
barcode_image = PIL.Image.open(BytesIO(barcode_raw)) barcode_image = PIL.Image.open(BytesIO(barcode_raw))
barcode_file = os.path.join(temp_dir, "%s.png" % obj.barcode) barcode_file = os.path.join(temp_dir, "%s.png" % obj.barcode)
barcode_image.save(barcode_file, 'png') barcode_image.save(barcode_file, "png")
label.add(shapes.Image(0, 30, width, 30, label.add(
os.path.join(temp_dir, barcode_file))) shapes.Image(0, 30, width, 30, os.path.join(temp_dir, barcode_file))
)
school = shapes.String( school = shapes.String(0, 8, School.objects.first().name, fontSize=10)
0, 8, School.objects.first().name, fontSize=10)
label.add(school) label.add(school)
bottom_line = shapes.String( bottom_line = shapes.String(
0, 0, 'BiscuIT, the Free School Information System', fontSize=6) 0, 0, "BiscuIT, the Free School Information System", fontSize=6
)
label.add(bottom_line) label.add(bottom_line)
specs = labels.Specification( specs = labels.Specification(
210, 297, 3, 7, 63, 40, left_padding=3, top_padding=3, bottom_padding=3, right_padding=3) 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 = labels.Sheet(specs, draw_label)
sheet.add_labels(copies) sheet.add_labels(copies)
...@@ -229,7 +258,7 @@ def book_copies_labels(request: HttpRequest, id_: int) -> HttpResponse: ...@@ -229,7 +258,7 @@ def book_copies_labels(request: HttpRequest, id_: int) -> HttpResponse:
pdf_file = os.path.join(temp_dir, "label_%s.pdf" % slugify(book.title)) pdf_file = os.path.join(temp_dir, "label_%s.pdf" % slugify(book.title))
sheet.save(pdf_file) sheet.save(pdf_file)
return FileResponse(open(pdf_file, 'rb'), as_attachment=True) return FileResponse(open(pdf_file, "rb"), as_attachment=True)
@login_required @login_required
...@@ -238,13 +267,15 @@ def get_copy(request: HttpRequest) -> HttpResponse: ...@@ -238,13 +267,15 @@ def get_copy(request: HttpRequest) -> HttpResponse:
book_copy_form = BookGetCopyForm(request.POST or None) book_copy_form = BookGetCopyForm(request.POST or None)
if request.method == 'POST': if request.method == "POST":
if book_copy_form.is_valid(): if book_copy_form.is_valid():
return redirect('book_copy_by_barcode', barcode=book_copy_form.cleaned_data['barcode']) return redirect(
"book_copy_by_barcode", barcode=book_copy_form.cleaned_data["barcode"]
)
context['book_copy_form'] = book_copy_form context["book_copy_form"] = book_copy_form
return render(request, 'exlibris/get_copy.html', context) return render(request, "exlibris/get_copy.html", context)
@login_required @login_required
...@@ -252,9 +283,9 @@ def book_copy(request: HttpRequest, barcode: str, template: str) -> HttpResponse ...@@ -252,9 +283,9 @@ def book_copy(request: HttpRequest, barcode: str, template: str) -> HttpResponse
context = {} context = {}
book_copy = get_object_or_404(BookCopy, barcode=barcode) book_copy = get_object_or_404(BookCopy, barcode=barcode)
context['book_copy'] = book_copy context["book_copy"] = book_copy
return render(request, 'exlibris/copy_%s.html' % template, context) return render(request, "exlibris/copy_%s.html" % template, context)
@login_required @login_required
...@@ -263,27 +294,35 @@ def person_borrow(request: HttpRequest) -> HttpResponse: ...@@ -263,27 +294,35 @@ def person_borrow(request: HttpRequest) -> HttpResponse:
person_borrow_form = PersonBorrowForm(request.POST or None) person_borrow_form = PersonBorrowForm(request.POST or None)
if request.method == 'POST': if request.method == "POST":
if person_borrow_form.is_valid(): if person_borrow_form.is_valid():
person = person_borrow_form.cleaned_data['borrower'] person = person_borrow_form.cleaned_data["borrower"]
for barcode_line in person_borrow_form.cleaned_data['barcodes'].splitlines(): for barcode_line in person_borrow_form.cleaned_data[
"barcodes"
].splitlines():
try: try:
book_copy = BookCopy.objects.get(barcode=barcode_line) book_copy = BookCopy.objects.get(barcode=barcode_line)
except BookCopy.DoesNotExist: except BookCopy.DoesNotExist:
messages.error(request, _('Barcode %(barcode)s is invalid.') % messages.error(
{'barcode': barcode_line}) request,
_("Barcode %(barcode)s is invalid.")
% {"barcode": barcode_line},
)
continue continue
book_copy.borrower = person book_copy.borrower = person
book_copy.borrow_count = book_copy.borrow_count + 1 book_copy.borrow_count = book_copy.borrow_count + 1
book_copy.save() book_copy.save()
messages.success(request, _('Book %(title)s borrowed to %(person)s.') % messages.success(
{'title': book_copy.book.title, 'person': person}) request,
_("Book %(title)s borrowed to %(person)s.")
% {"title": book_copy.book.title, "person": person},
)
person_borrow_form = PersonBorrowForm() person_borrow_form = PersonBorrowForm()
context['person_borrow_form'] = person_borrow_form context["person_borrow_form"] = person_borrow_form
return render(request, 'exlibris/person_borrow.html', context) return render(request, "exlibris/person_borrow.html", context)