diff --git a/catalog/models.py b/catalog/models.py index f0b461f3..b1e1ef01 100644 --- a/catalog/models.py +++ b/catalog/models.py @@ -12,6 +12,10 @@ class Genre(models.Model): help_text="Enter a book genre (e.g. Science Fiction, French Poetry etc.)" ) + def get_absolute_url(self): + """Returns the url to access a particular genre instance.""" + return reverse('genre-detail', args=[str(self.id)]) + def __str__(self): """String for representing the Model object (in Admin site etc.)""" return self.name @@ -22,6 +26,10 @@ class Language(models.Model): name = models.CharField(max_length=200, help_text="Enter the book's natural language (e.g. English, French, Japanese etc.)") + def get_absolute_url(self): + """Returns the url to access a particular language instance.""" + return reverse('language-detail', args=[str(self.id)]) + def __str__(self): """String for representing the Model object (in Admin site etc.)""" return self.name @@ -42,7 +50,7 @@ class Book(models.Model): # ManyToManyField used because a genre can contain many books and a Book can cover many genres. # Genre class has already been defined so we can specify the object above. language = models.ForeignKey('Language', on_delete=models.SET_NULL, null=True) - + class Meta: ordering = ['title', 'author'] @@ -53,7 +61,7 @@ def display_genre(self): display_genre.short_description = 'Genre' def get_absolute_url(self): - """Returns the url to access a particular book instance.""" + """Returns the url to access a particular book record.""" return reverse('book-detail', args=[str(self.id)]) def __str__(self): @@ -99,6 +107,10 @@ class Meta: ordering = ['due_back'] permissions = (("can_mark_returned", "Set book as returned"),) + def get_absolute_url(self): + """Returns the url to access a particular book instance.""" + return reverse('bookinstance-detail', args=[str(self.id)]) + def __str__(self): """String for representing the Model object.""" return '{0} ({1})'.format(self.id, self.book.title) diff --git a/catalog/templates/base_generic.html b/catalog/templates/base_generic.html index c7a4b8cc..baa8cb14 100644 --- a/catalog/templates/base_generic.html +++ b/catalog/templates/base_generic.html @@ -1,13 +1,13 @@ - + {% block title %}Local Library{% endblock %} - + {% load static %} @@ -21,35 +21,46 @@ {% block sidebar %} - + - + {% if user.is_staff %}
{% endif %} - + {% endblock %}
{% block content %}{% endblock %} - + {% block pagination %} {% if is_paginated %} {% endif %} - {% endblock %} - - + {% endblock %} + +
diff --git a/catalog/templates/catalog/author_confirm_delete.html b/catalog/templates/catalog/author_confirm_delete.html index 6d4b6417..0c4a4553 100644 --- a/catalog/templates/catalog/author_confirm_delete.html +++ b/catalog/templates/catalog/author_confirm_delete.html @@ -2,9 +2,9 @@ {% block content %} -

Delete Author

+

Delete Author: {{ author }}

-

Are you sure you want to delete the author: {{ author }}?

+

Are you sure you want to delete the author?

{% csrf_token %} diff --git a/catalog/templates/catalog/book_detail.html b/catalog/templates/catalog/book_detail.html index aa57ea8f..55319141 100644 --- a/catalog/templates/catalog/book_detail.html +++ b/catalog/templates/catalog/book_detail.html @@ -6,21 +6,37 @@

Title: {{ book.title }}

Author: {{ book.author }}

Summary: {{ book.summary }}

-

ISBN: {{ book.isbn }}

-

Language: {{ book.language }}

+

ISBN: {{ book.isbn }}

+

Language: {{ book.language }}

Genre: {{ book.genre.all|join:", " }}

Copies

{% for copy in book.bookinstance_set.all %} -
-

{{ copy.get_status_display }}

-{% if copy.status != 'a' %}

Due to be returned: {{copy.due_back}}

{% endif %} -

Imprint: {{copy.imprint}}

-

Id: {{copy.id}}

- +
+

{{ copy.get_status_display }}

+ {% if copy.status != 'a' %}

Due to be returned: {{copy.due_back}}

{% endif %} +

Imprint: {{copy.imprint}}

+

Id: {{copy.id}}

+{% empty %} +

The library has no copies of this book.

{% endfor %}
{% endblock %} + +{% block sidebar %} + {{ block.super }} + + {% if perms.catalog.can_mark_returned %} +
+ + {% endif %} + +{% endblock %} diff --git a/catalog/templates/catalog/bookinstance_confirm_delete.html b/catalog/templates/catalog/bookinstance_confirm_delete.html new file mode 100644 index 00000000..f4b203e1 --- /dev/null +++ b/catalog/templates/catalog/bookinstance_confirm_delete.html @@ -0,0 +1,14 @@ +{% extends "base_generic.html" %} + +{% block content %} + +

Delete Book Copy: {{ bookinstance }}

+ +

Are you sure you want to delete this copy of the book?

+ + + {% csrf_token %} + +
+ +{% endblock %} diff --git a/catalog/templates/catalog/bookinstance_detail.html b/catalog/templates/catalog/bookinstance_detail.html new file mode 100644 index 00000000..a4429f16 --- /dev/null +++ b/catalog/templates/catalog/bookinstance_detail.html @@ -0,0 +1,32 @@ +{% extends "base_generic.html" %} + +{% block content %} + +

BookInstance: {{ bookinstance.book.title }}

+ +

Author: {{ bookinstance.book.author }}

+ +

Imprint: {{ bookinstance.imprint }}

+

Status: {{ bookinstance.get_status_display }} {% if bookinstance.status != 'a' %} (Due: {{bookinstance.due_back}}){% endif %}

+ +
+ +{% endblock %} + + +{% block sidebar %} + {{ block.super }} + + {% if perms.catalog.can_mark_returned %} +
+ + {% endif %} + +{% endblock %} diff --git a/catalog/templates/catalog/bookinstance_form.html b/catalog/templates/catalog/bookinstance_form.html new file mode 100644 index 00000000..8bae8bca --- /dev/null +++ b/catalog/templates/catalog/bookinstance_form.html @@ -0,0 +1,13 @@ +{% extends "base_generic.html" %} + +{% block content %} + +
+ {% csrf_token %} + + {{ form.as_table }} +
+ + +
+{% endblock %} diff --git a/catalog/templates/catalog/bookinstance_list.html b/catalog/templates/catalog/bookinstance_list.html new file mode 100644 index 00000000..0a650424 --- /dev/null +++ b/catalog/templates/catalog/bookinstance_list.html @@ -0,0 +1,16 @@ +{% extends "base_generic.html" %} + +{% block content %} +

Book Copies in Library

+ + + +{% endblock %} diff --git a/catalog/templates/catalog/genre_confirm_delete.html b/catalog/templates/catalog/genre_confirm_delete.html new file mode 100644 index 00000000..7fca32e6 --- /dev/null +++ b/catalog/templates/catalog/genre_confirm_delete.html @@ -0,0 +1,14 @@ +{% extends "base_generic.html" %} + +{% block content %} + +

Delete Genre: {{ genre }}

+ +

Are you sure you want to delete the genre?

+ +
+ {% csrf_token %} + +
+ +{% endblock %} diff --git a/catalog/templates/catalog/genre_detail.html b/catalog/templates/catalog/genre_detail.html new file mode 100644 index 00000000..29269bd5 --- /dev/null +++ b/catalog/templates/catalog/genre_detail.html @@ -0,0 +1,41 @@ +{% extends "base_generic.html" %} + +{% block content %} + +

Genre: {{ genre.name }}

+ +
+

Books in genre

+ + + +{% endblock %} + + +{% block sidebar %} + {{ block.super }} + + {% if perms.catalog.can_mark_returned %} +
+ + {% endif %} + +{% endblock %} + + + + + diff --git a/catalog/templates/catalog/genre_form.html b/catalog/templates/catalog/genre_form.html new file mode 100644 index 00000000..5c01e2ad --- /dev/null +++ b/catalog/templates/catalog/genre_form.html @@ -0,0 +1,13 @@ +{% extends "base_generic.html" %} + +{% block content %} + +
+ {% csrf_token %} + + {{ form.as_table }} +
+ + +
+{% endblock %} diff --git a/catalog/templates/catalog/genre_list.html b/catalog/templates/catalog/genre_list.html new file mode 100644 index 00000000..613fb95e --- /dev/null +++ b/catalog/templates/catalog/genre_list.html @@ -0,0 +1,25 @@ +{% extends "base_generic.html" %} + +{% block content %} + +

Genre List

+ +{% if genre_list %} + +{% else %} +

There are no genres available.

+{% endif %} + + +{% endblock %} + diff --git a/catalog/templates/catalog/language_confirm_delete.html b/catalog/templates/catalog/language_confirm_delete.html new file mode 100644 index 00000000..a27ed91b --- /dev/null +++ b/catalog/templates/catalog/language_confirm_delete.html @@ -0,0 +1,14 @@ +{% extends "base_generic.html" %} + +{% block content %} + +

Delete Language: {{ language }}

+ +

Are you sure you want to delete the language?

+ +
+ {% csrf_token %} + +
+ +{% endblock %} diff --git a/catalog/templates/catalog/language_detail.html b/catalog/templates/catalog/language_detail.html new file mode 100644 index 00000000..20ece7f5 --- /dev/null +++ b/catalog/templates/catalog/language_detail.html @@ -0,0 +1,41 @@ +{% extends "base_generic.html" %} + +{% block content %} + +

Language: {{ language.name }}

+ +
+

Books in language

+ + + +{% endblock %} + + +{% block sidebar %} + {{ block.super }} + + {% if perms.catalog.can_mark_returned %} +
+ + {% endif %} + +{% endblock %} + + + + + diff --git a/catalog/templates/catalog/language_form.html b/catalog/templates/catalog/language_form.html new file mode 100644 index 00000000..5c01e2ad --- /dev/null +++ b/catalog/templates/catalog/language_form.html @@ -0,0 +1,13 @@ +{% extends "base_generic.html" %} + +{% block content %} + +
+ {% csrf_token %} + + {{ form.as_table }} +
+ + +
+{% endblock %} diff --git a/catalog/templates/catalog/language_list.html b/catalog/templates/catalog/language_list.html new file mode 100644 index 00000000..747a7a49 --- /dev/null +++ b/catalog/templates/catalog/language_list.html @@ -0,0 +1,18 @@ +{% extends "base_generic.html" %} + +{% block content %} + +

Language List

+ + + +{% endblock %} + diff --git a/catalog/urls.py b/catalog/urls.py index b65bd17d..51e08549 100644 --- a/catalog/urls.py +++ b/catalog/urls.py @@ -38,3 +38,32 @@ path('book//update/', views.BookUpdate.as_view(), name='book-update'), path('book//delete/', views.BookDelete.as_view(), name='book-delete'), ] + + + +# Add URLConf to list, view, create, update, and delete genre +urlpatterns += [ + path('genres/', views.GenreListView.as_view(), name='genres'), + path('genre/', views.GenreDetailView.as_view(), name='genre-detail'), + path('genre/create/', views.GenreCreate.as_view(), name='genre-create'), + path('genre//update/', views.GenreUpdate.as_view(), name='genre-update'), + path('genre//delete/', views.GenreDelete.as_view(), name='genre-delete'), +] + +# Add URLConf to list, view, create, update, and delete languages +urlpatterns += [ + path('languages/', views.LanguageListView.as_view(), name='languages'), + path('language/', views.LanguageDetailView.as_view(), name='language-detail'), + path('language/create/', views.LanguageCreate.as_view(), name='language-create'), + path('language//update/', views.LanguageUpdate.as_view(), name='language-update'), + path('language//delete/', views.LanguageDelete.as_view(), name='language-delete'), +] + +# Add URLConf to list, view, create, update, and delete bookinstances +urlpatterns += [ + path('bookinstances/', views.BookInstanceListView.as_view(), name='bookinstances'), + path('bookinstance/', views.BookInstanceDetailView.as_view(), name='bookinstance-detail'), + path('bookinstance/create/', views.BookInstanceCreate.as_view(), name='bookinstance-create'), + path('bookinstance//update/', views.BookInstanceUpdate.as_view(), name='bookinstance-update'), + path('bookinstance//delete/', views.BookInstanceDelete.as_view(), name='bookinstance-delete'), +] diff --git a/catalog/views.py b/catalog/views.py index 1cdf7ba0..3f46cb93 100644 --- a/catalog/views.py +++ b/catalog/views.py @@ -1,8 +1,20 @@ +from .models import Author +from django.urls import reverse_lazy +from django.views.generic.edit import CreateView, UpdateView, DeleteView +from catalog.forms import RenewBookForm +from django.contrib.auth.decorators import login_required, permission_required +import datetime +from django.urls import reverse +from django.http import HttpResponseRedirect +from django.shortcuts import get_object_or_404 +from django.contrib.auth.mixins import PermissionRequiredMixin +from django.contrib.auth.mixins import LoginRequiredMixin +from django.views import generic from django.shortcuts import render # Create your views here. -from .models import Book, Author, BookInstance, Genre +from .models import Book, Author, BookInstance, Genre, Language def index(request): @@ -11,7 +23,8 @@ def index(request): num_books = Book.objects.all().count() num_instances = BookInstance.objects.all().count() # Available copies of books - num_instances_available = BookInstance.objects.filter(status__exact='a').count() + num_instances_available = BookInstance.objects.filter( + status__exact='a').count() num_authors = Author.objects.count() # The 'all()' is implied by default. # Number of visits to this view, as counted in the session variable. @@ -28,9 +41,6 @@ def index(request): ) -from django.views import generic - - class BookListView(generic.ListView): """Generic class-based view for a list of books.""" model = Book @@ -53,7 +63,37 @@ class AuthorDetailView(generic.DetailView): model = Author -from django.contrib.auth.mixins import LoginRequiredMixin +class GenreDetailView(generic.DetailView): + """Generic class-based detail view for a genre.""" + model = Genre + + +class GenreListView(generic.ListView): + """Generic class-based list view for a list of genres.""" + model = Genre + paginate_by = 10 + + +class LanguageDetailView(generic.DetailView): + """Generic class-based detail view for a genre.""" + model = Language + + +class LanguageListView(generic.ListView): + """Generic class-based list view for a list of genres.""" + model = Language + paginate_by = 10 + + +class BookInstanceListView(generic.ListView): + """Generic class-based view for a list of books.""" + model = BookInstance + paginate_by = 10 + + +class BookInstanceDetailView(generic.DetailView): + """Generic class-based detail view for a book.""" + model = BookInstance class LoanedBooksByUserListView(LoginRequiredMixin, generic.ListView): @@ -71,7 +111,6 @@ def get_queryset(self): # Added as part of challenge! -from django.contrib.auth.mixins import PermissionRequiredMixin class LoanedBooksAllListView(PermissionRequiredMixin, generic.ListView): @@ -85,14 +124,7 @@ def get_queryset(self): return BookInstance.objects.filter(status__exact='o').order_by('due_back') -from django.shortcuts import get_object_or_404 -from django.http import HttpResponseRedirect -from django.urls import reverse -import datetime -from django.contrib.auth.decorators import login_required, permission_required - # from .forms import RenewBookForm -from catalog.forms import RenewBookForm @login_required @@ -129,11 +161,6 @@ def renew_book_librarian(request, pk): return render(request, 'catalog/book_renew_librarian.html', context) -from django.views.generic.edit import CreateView, UpdateView, DeleteView -from django.urls import reverse_lazy -from .models import Author - - class AuthorCreate(PermissionRequiredMixin, CreateView): model = Author fields = ['first_name', 'last_name', 'date_of_birth', 'date_of_death'] @@ -143,7 +170,8 @@ class AuthorCreate(PermissionRequiredMixin, CreateView): class AuthorUpdate(PermissionRequiredMixin, UpdateView): model = Author - fields = '__all__' # Not recommended (potential security issue if more fields added) + # Not recommended (potential security issue if more fields added) + fields = '__all__' permission_required = 'catalog.can_mark_returned' @@ -170,3 +198,58 @@ class BookDelete(PermissionRequiredMixin, DeleteView): model = Book success_url = reverse_lazy('books') permission_required = 'catalog.can_mark_returned' + + +class GenreCreate(PermissionRequiredMixin, CreateView): + model = Genre + fields = ['name', ] + permission_required = 'catalog.can_mark_returned' + + +class GenreUpdate(PermissionRequiredMixin, UpdateView): + model = Genre + fields = ['name', ] + permission_required = 'catalog.can_mark_returned' + + +class GenreDelete(PermissionRequiredMixin, DeleteView): + model = Genre + success_url = reverse_lazy('genres') + permission_required = 'catalog.can_mark_returned' + + +class LanguageCreate(PermissionRequiredMixin, CreateView): + model = Language + fields = ['name', ] + permission_required = 'catalog.can_mark_returned' + + +class LanguageUpdate(PermissionRequiredMixin, UpdateView): + model = Language + fields = ['name', ] + permission_required = 'catalog.can_mark_returned' + + +class LanguageDelete(PermissionRequiredMixin, DeleteView): + model = Language + success_url = reverse_lazy('languages') + permission_required = 'catalog.can_mark_returned' + + +class BookInstanceCreate(PermissionRequiredMixin, CreateView): + model = BookInstance + fields = ['book','imprint', 'due_back', 'borrower', 'status'] + permission_required = 'catalog.can_mark_returned' + + +class BookInstanceUpdate(PermissionRequiredMixin, UpdateView): + model = BookInstance + # fields = "__all__" + fields = ['imprint', 'due_back', 'borrower', 'status'] + permission_required = 'catalog.can_mark_returned' + + +class BookInstanceDelete(PermissionRequiredMixin, DeleteView): + model = BookInstance + success_url = reverse_lazy('bookinstances') + permission_required = 'catalog.can_mark_returned'