Skip to content

Commit

Permalink
Expand tutorial to allow proper deletion code (mdn#124)
Browse files Browse the repository at this point in the history
* Update TestViews - remove deprecation in assertFormError

* Minimal changes for 4.2 - inc use STORAGES

* Update requirements and fixup whitenoise link

* Add settings 4.2 for dj url:

* Change gunicorn to version that is supported on railway 21.2.0

* Expand tutorial to allow create/delete/update through UI

* Remove supurious git stuff

* Minor fixes - layout etc

* Views delete

* Update catalog/views.py

* Update views.py - restore order

* minor subedit

* Authors with books can't be deleted

* Use modern formatting for author string

* Update bootstrap to latest

* Fix up case on base template

* Fix up permissions to use the default ones

* Fix up tests for permissions

* Minor fixes
  • Loading branch information
hamishwillee authored Nov 12, 2023
1 parent 0fcfea3 commit f88794b
Show file tree
Hide file tree
Showing 24 changed files with 942 additions and 422 deletions.
40 changes: 29 additions & 11 deletions catalog/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,13 @@ class Genre(models.Model):
"""Model representing a book genre (e.g. Science Fiction, Non Fiction)."""
name = models.CharField(
max_length=200,
unique=True,
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.)"""
Expand All @@ -20,8 +25,13 @@ def __str__(self):
class Language(models.Model):
"""Model representing a Language (e.g. English, French, Japanese, etc.)"""
name = models.CharField(max_length=200,
unique=True,
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
Expand All @@ -30,19 +40,22 @@ def __str__(self):
class Book(models.Model):
"""Model representing a book (but not a specific copy of a book)."""
title = models.CharField(max_length=200)
author = models.ForeignKey('Author', on_delete=models.SET_NULL, null=True)
# Foreign Key used because book can only have one author, but authors can have multiple books
author = models.ForeignKey('Author', on_delete=models.RESTRICT, null=True)
# Foreign Key used because book can only have one author, but authors can have multiple books.
# Author as a string rather than object because it hasn't been declared yet in file.
summary = models.TextField(max_length=1000, help_text="Enter a brief description of the book")
summary = models.TextField(
max_length=1000, help_text="Enter a brief description of the book")
isbn = models.CharField('ISBN', max_length=13,
unique=True,
help_text='13 Character <a href="https://www.isbn-international.org/content/what-isbn'
'">ISBN number</a>')
genre = models.ManyToManyField(Genre, help_text="Select a genre for this book")
genre = models.ManyToManyField(
Genre, help_text="Select a genre for this book")
# 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)

language = models.ForeignKey(
'Language', on_delete=models.SET_NULL, null=True)

class Meta:
ordering = ['title', 'author']

Expand All @@ -53,7 +66,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):
Expand All @@ -74,7 +87,8 @@ class BookInstance(models.Model):
book = models.ForeignKey('Book', on_delete=models.RESTRICT, null=True)
imprint = models.CharField(max_length=200)
due_back = models.DateField(null=True, blank=True)
borrower = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True)
borrower = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True)

@property
def is_overdue(self):
Expand All @@ -99,9 +113,13 @@ 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)
return f'{self.id} ({self.book.title})'


class Author(models.Model):
Expand All @@ -120,4 +138,4 @@ def get_absolute_url(self):

def __str__(self):
"""String for representing the Model object."""
return '{0}, {1}'.format(self.last_name, self.first_name)
return f'{self.last_name}, {self.first_name}'
45 changes: 29 additions & 16 deletions catalog/templates/base_generic.html
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>

{% block title %}<title>Local Library</title>{% endblock %}
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">


<!-- Add additional CSS in static file -->
{% load static %}
<link rel="stylesheet" href="{% static 'css/styles.css' %}">
Expand All @@ -21,35 +20,51 @@
{% block sidebar %}
<ul class="sidebar-nav">
<li><a href="{% url 'index' %}">Home</a></li>
<li><a href="{% url 'bookinstances' %}">All book copies</a></li>
<li><a href="{% url 'books' %}">All books</a></li>
<li><a href="{% url 'authors' %}">All authors</a></li>
<li><a href="{% url 'genres' %}">All genres</a></li>
<li><a href="{% url 'languages' %}">All languages</a></li>
</ul>

<ul class="sidebar-nav">
{% if user.is_authenticated %}
<li>User: {{ user.get_username }}</li>
<li><a href="{% url 'my-borrowed' %}">My Borrowed</a></li>
<li><a href="{% url 'logout'%}?next={{request.path}}">Logout</a></li>
<li><a href="{% url 'my-borrowed' %}">My borrowed</a></li>
<li><a href="{% url 'logout'%}?next={{request.path}}">Logout</a></li>
{% else %}
<li><a href="{% url 'login'%}?next={{request.path}}">Login</a></li>
{% endif %}
<li><a href="{% url 'login'%}?next={{request.path}}">Login</a></li>
{% endif %}
</ul>

{% if user.is_staff %}
<hr>
<ul class="sidebar-nav">
<li>Staff</li>
{% if perms.catalog.can_mark_returned %}
<li><a href="{% url 'all-borrowed' %}">All borrowed</a></li>
{% if perms.catalog.add_genre %}
<li><a href="{% url 'genre-create' %}">Create genre</a></li>
{% endif %}
{% if perms.catalog.add_genre %}
<li><a href="{% url 'language-create' %}">Create language</a></li>
{% endif %}
{% if perms.catalog.add_author %}
<li><a href="{% url 'author-create' %}">Create author</a></li>
{% endif %}
{% if perms.catalog.add_book %}
<li><a href="{% url 'book-create' %}">Create book</a></li>
{% endif %}
{% if perms.catalog.add_bookinstance %}
<li><a href="{% url 'bookinstance-create' %}">create BookInstance</a></li>
{% endif %}
</ul>
{% endif %}
{% endif %}

{% endblock %}
</div>
<div class="col-sm-10 ">
{% block content %}{% endblock %}

{% block pagination %}
{% if is_paginated %}
<div class="pagination">
Expand All @@ -66,9 +81,7 @@
</span>
</div>
{% endif %}
{% endblock %}


{% endblock %}
</div>
</div>

Expand Down
15 changes: 13 additions & 2 deletions catalog/templates/catalog/author_confirm_delete.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,24 @@

{% block content %}

<h1>Delete Author</h1>
<h1>Delete Author: {{ author }}</h1>

<p>Are you sure you want to delete the author: {{ author }}?</p>
{% if author.book_set.all %}

<p>You can't delete this author until all their books have been deleted:</p>
<ul>
{% for book in author.book_set.all %}
<li><a href="{% url 'book-detail' book.pk %}">{{book}}</a> ({{book.bookinstance_set.all.count}})</li>
{% endfor %}
</ul>

{% else %}
<p>Are you sure you want to delete the author?</p>

<form action="" method="POST">
{% csrf_token %}
<input type="submit" action="" value="Yes, delete.">
</form>
{% endif %}

{% endblock %}
19 changes: 19 additions & 0 deletions catalog/templates/catalog/author_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,27 @@ <h4>Books</h4>
{% for book in author.book_set.all %}
<dt><a href="{% url 'book-detail' book.pk %}">{{book}}</a> ({{book.bookinstance_set.all.count}})</dt>
<dd>{{book.summary}}</dd>
{% empty %}
<p>This author has no books.</p>
{% endfor %}
</dl>

</div>
{% endblock %}

{% block sidebar %}
{{ block.super }}

{% if perms.catalog.change_author or perms.catalog.delete_author %}
<hr>
<ul class="sidebar-nav">
{% if perms.catalog.change_author %}
<li><a href="{% url 'author-update' author.id %}">Update author</a></li>
{% endif %}
{% if not author.book_set.all and perms.catalog.delete_author %}
<li><a href="{% url 'author-delete' author.id %}">Delete author</a></li>
{% endif %}
</ul>
{% endif %}

{% endblock %}
1 change: 0 additions & 1 deletion catalog/templates/catalog/author_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,5 @@
{{ form.as_table }}
</table>
<input type="submit" value="Submit">

</form>
{% endblock %}
11 changes: 11 additions & 0 deletions catalog/templates/catalog/book_confirm_delete.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,22 @@

<h1>Delete Book</h1>

{% if book.bookinstance_set.all %}
<p>You can't delete this book until all copies have been deleted:</p>

<ul>
{% for copy in book.bookinstance_set.all %}
<li><a href="{{ copy.get_absolute_url }}">{{copy.id}}</a> (Imprint: {{copy.imprint}})</li>
{% endfor %}
</ul>

{% else %}
<p>Are you sure you want to delete the book: {{ book }}?</p>

<form action="" method="POST">
{% csrf_token %}
<input type="submit" action="" value="Yes, delete.">
</form>
{% endif %}

{% endblock %}
34 changes: 26 additions & 8 deletions catalog/templates/catalog/book_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,39 @@ <h1>Title: {{ book.title }}</h1>

<p><strong>Author:</strong> <a href="{{ book.author.get_absolute_url }}">{{ book.author }}</a></p>
<p><strong>Summary:</strong> {{ book.summary }}</p>
<p><strong>ISBN:</strong> {{ book.isbn }}</p>
<p><strong>Language:</strong> {{ book.language }}</p>
<p><strong>ISBN:</strong> {{ book.isbn }}</p>
<p><strong>Language:</strong> {{ book.language }}</p>
<p><strong>Genre:</strong> {{ book.genre.all|join:", " }}</p>

<div style="margin-left:20px;margin-top:20px">
<h4>Copies</h4>

{% for copy in book.bookinstance_set.all %}
<hr>
<p class="{% if copy.status == 'a' %}text-success{% elif copy.status == 'd' %}text-danger{% else %}text-warning{% endif %}">{{ copy.get_status_display }}</p>
{% if copy.status != 'a' %}<p><strong>Due to be returned:</strong> {{copy.due_back}}</p>{% endif %}
<p><strong>Imprint:</strong> {{copy.imprint}}</p>
<p class="text-muted"><strong>Id:</strong> {{copy.id}}</p>

<hr>
<p class="{% if copy.status == 'a' %}text-success{% elif copy.status == 'd' %}text-danger{% else %}text-warning{% endif %}">{{ copy.get_status_display }}</p>
{% if copy.status != 'a' %}<p><strong>Due to be returned:</strong> {{copy.due_back}}</p>{% endif %}
<p><strong>Imprint:</strong> {{copy.imprint}}</p>
<p class="text-muted"><strong>Id:</strong> <a href="{{ copy.get_absolute_url }}">{{copy.id}}</a></p>
{% empty %}
<p>The library has no copies of this book.</p>
{% endfor %}
</div>
{% endblock %}


{% block sidebar %}
{{ block.super }}

{% if perms.catalog.change_book or perms.catalog.delete_book %}
<hr>
<ul class="sidebar-nav">
{% if perms.catalog.change_book %}
<li><a href="{% url 'book-update' book.id %}">Update Book</a></li>
{% endif %}
{% if not book.bookinstance_set.all and perms.catalog.delete_book %}
<li><a href="{% url 'book-delete' book.id %}">Delete Book</a></li>
{% endif %}
</ul>
{% endif %}

{% endblock %}
14 changes: 14 additions & 0 deletions catalog/templates/catalog/bookinstance_confirm_delete.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{% extends "base_generic.html" %}

{% block content %}

<h1>Delete Book Copy: {{ bookinstance }}</h1>

<p>Are you sure you want to delete this copy of the book?</p>

<form action="" method="POST">
{% csrf_token %}
<input type="submit" action="" value="Yes, delete.">
</form>

{% endblock %}
36 changes: 36 additions & 0 deletions catalog/templates/catalog/bookinstance_detail.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{% extends "base_generic.html" %}

{% block content %}

<h1>BookInstance: {{ bookinstance.book.title }}</h1>

<p><strong>Author:</strong> <a href="{{ bookinstance.book.author.get_absolute_url }}">{{ bookinstance.book.author }}</a></p>

<p><strong>Imprint:</strong> {{ bookinstance.imprint }}</p>
<p><strong>Status:</strong> {{ bookinstance.get_status_display }} {% if bookinstance.status != 'a' %} (Due: {{bookinstance.due_back}}){% endif %}</p>

<hr>
<ul>
<li>
<a href="{{ bookinstance.book.get_absolute_url }}">All copies</a></p>
</li>
</ul>
{% endblock %}


{% block sidebar %}
{{ block.super }}

{% if perms.catalog.change_bookinstance or perms.catalog.delete_bookinstance %}
<hr>
<ul class="sidebar-nav">
{% if perms.catalog.change_bookinstance %}
<li><a href="{% url 'bookinstance-update' bookinstance.id %}">Update BookInstance</a></li>
{% endif %}
{% if perms.catalog.delete_bookinstance %}
<li><a href="{% url 'bookinstance-delete' bookinstance.id %}">Delete BookInstance</a></li>
{% endif %}
</ul>
{% endif %}

{% endblock %}
13 changes: 13 additions & 0 deletions catalog/templates/catalog/bookinstance_form.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{% extends "base_generic.html" %}

{% block content %}

<form action="" method="post">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type="submit" value="Submit">

</form>
{% endblock %}
Loading

0 comments on commit f88794b

Please sign in to comment.