├── locallibrary ├── catalog │ ├── __init__.py │ ├── tests │ │ ├── __init__.py │ │ ├── test_models.py │ │ ├── test_forms.py │ │ └── test_views.py │ ├── migrations │ │ ├── __init__.py │ │ ├── 0004_auto_20190706_1425.py │ │ ├── 0003_auto_20190706_1353.py │ │ ├── 0002_auto_20190705_1527.py │ │ └── 0001_initial.py │ ├── apps.py │ ├── static │ │ ├── images │ │ │ ├── skull.jpg │ │ │ └── Avatar.png │ │ └── css │ │ │ └── styles.css │ ├── templates │ │ ├── 403.html │ │ ├── catalog │ │ │ ├── author_form.html │ │ │ ├── book_form.html │ │ │ ├── author_confirm_delete.html │ │ │ ├── book_confirm_delete.html │ │ │ ├── book_renew_librarian.html │ │ │ ├── author_list.html │ │ │ ├── search.html │ │ │ ├── book_list.html │ │ │ ├── author_detail.html │ │ │ ├── bookinstance_list_borrowed_user.html │ │ │ ├── list_borrowed.html │ │ │ └── book_detail.html │ │ ├── index.html │ │ └── base_generic.html │ ├── forms.py │ ├── urls.py │ ├── admin.py │ ├── models.py │ └── views.py ├── locallibrary │ ├── __init__.py │ ├── wsgi.py │ ├── urls.py │ └── settings.py ├── db.sqlite3 ├── templates │ └── registration │ │ ├── password_reset_email.html │ │ ├── logged_out.html │ │ ├── password_reset_complete.html │ │ ├── password_reset_done.html │ │ ├── password_reset_form.html │ │ ├── password_reset_confirm.html │ │ └── login.html └── manage.py ├── .gitignore ├── requirements.txt └── README.md /locallibrary/catalog/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /locallibrary/catalog/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /locallibrary/locallibrary/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /locallibrary/catalog/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .myvenv 2 | myvenv/ 3 | __pycache__/ 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==2.2.28 2 | pytz==2019.1 3 | sqlparse==0.3.0 4 | -------------------------------------------------------------------------------- /locallibrary/db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-akira/Django-Secret-Library/master/locallibrary/db.sqlite3 -------------------------------------------------------------------------------- /locallibrary/catalog/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CatalogConfig(AppConfig): 5 | name = 'catalog' 6 | -------------------------------------------------------------------------------- /locallibrary/catalog/static/images/skull.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-akira/Django-Secret-Library/master/locallibrary/catalog/static/images/skull.jpg -------------------------------------------------------------------------------- /locallibrary/catalog/static/images/Avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-akira/Django-Secret-Library/master/locallibrary/catalog/static/images/Avatar.png -------------------------------------------------------------------------------- /locallibrary/templates/registration/password_reset_email.html: -------------------------------------------------------------------------------- 1 | Someone asked for password reset for email {{ email }}. Follow the link below: 2 | {{ protocol}}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %} -------------------------------------------------------------------------------- /locallibrary/templates/registration/logged_out.html: -------------------------------------------------------------------------------- 1 | {% extends "base_generic.html" %} 2 | 3 | {% block content %} 4 |
5 |

Deslogado do sistema!

6 | Entrar novamente 7 |
8 | {% endblock %} -------------------------------------------------------------------------------- /locallibrary/templates/registration/password_reset_complete.html: -------------------------------------------------------------------------------- 1 | {% extends "base_generic.html" %} 2 | 3 | {% block content %} 4 |
5 |

The password has been changed!

6 |

log in again?

7 |
8 | {% endblock %} -------------------------------------------------------------------------------- /locallibrary/templates/registration/password_reset_done.html: -------------------------------------------------------------------------------- 1 | {% extends "base_generic.html" %} 2 | 3 | {% block content %} 4 |

We've emailed you instructions for setting your password. If they haven't arrived in a few minutes, check your spam folder.

5 | {% endblock %} -------------------------------------------------------------------------------- /locallibrary/catalog/templates/403.html: -------------------------------------------------------------------------------- 1 | {% extends "base_generic.html" %} 2 | 3 | {% block content %} 4 | 5 |
6 |

Error 403

7 |
8 |

Você não tem permissão para ver essa página

9 |
10 | 11 | {% endblock %} -------------------------------------------------------------------------------- /locallibrary/catalog/templates/catalog/author_form.html: -------------------------------------------------------------------------------- 1 | {% extends "base_generic.html" %} 2 | 3 | {% block content %} 4 |
5 | {% csrf_token %} 6 | {{ form.as_table }} 7 | 8 |
9 | {% endblock %} -------------------------------------------------------------------------------- /locallibrary/catalog/templates/catalog/book_form.html: -------------------------------------------------------------------------------- 1 | {% extends "base_generic.html" %} 2 | 3 | {% block content %} 4 |
5 | {% csrf_token %} 6 | {{ form.as_table }} 7 | 8 |
9 | {% endblock %} -------------------------------------------------------------------------------- /locallibrary/catalog/templates/catalog/author_confirm_delete.html: -------------------------------------------------------------------------------- 1 | {% extends "base_generic.html" %} 2 | 3 | {% block content %} 4 | 5 |

Deletar Autor

6 | 7 |

Tem certeza que você deseja remover o autor: {{ author }}?

8 | 9 |
10 | {% csrf_token %} 11 | 12 |
13 | 14 | {% endblock %} -------------------------------------------------------------------------------- /locallibrary/catalog/templates/catalog/book_confirm_delete.html: -------------------------------------------------------------------------------- 1 | {% extends "base_generic.html" %} 2 | 3 | {% block content %} 4 | 5 |

Deletar Livro

6 | 7 |

Tem certeza que você deseja remover o livro: {{ book }}?

8 | 9 |
10 | {% csrf_token %} 11 | 12 |
13 | 14 | {% endblock %} -------------------------------------------------------------------------------- /locallibrary/locallibrary/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for locallibrary project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'locallibrary.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /locallibrary/templates/registration/password_reset_form.html: -------------------------------------------------------------------------------- 1 | {% extends "base_generic.html" %} 2 | 3 | {% block content %} 4 |
5 |
6 | {% csrf_token %} 7 | {% if form.email.errors %} 8 | {{ form.email.errors }} 9 | {% endif %} 10 | 11 |

{{ form.email }}

12 | 13 |
14 |
15 | {% endblock %} -------------------------------------------------------------------------------- /locallibrary/catalog/migrations/0004_auto_20190706_1425.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.1 on 2019-07-06 17:25 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('catalog', '0003_auto_20190706_1353'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='bookinstance', 15 | options={'ordering': ['due_back'], 'permissions': (('can_mark_returned', 'Set book as returned'),)}, 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /locallibrary/locallibrary/urls.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.urls import path, include 3 | from django.views.generic import RedirectView 4 | from django.conf import settings 5 | from django.conf.urls.static import static 6 | 7 | urlpatterns = [ 8 | path('admin/', admin.site.urls), 9 | path('catalog/', include('catalog.urls')), 10 | path('', RedirectView.as_view(url='/catalog/', permanent=True)), 11 | path('accounts/', include('django.contrib.auth.urls')), 12 | ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) 13 | -------------------------------------------------------------------------------- /locallibrary/catalog/templates/catalog/book_renew_librarian.html: -------------------------------------------------------------------------------- 1 | {% extends "base_generic.html" %} 2 | 3 | {% block content %} 4 |

Renovar: {{ book_instance.book.title }}

5 |

Receptor: {{ book_instance.borrower }}

6 | Data de entrega: {{ book_instance.due_back }}

7 |
8 | {% csrf_token %} 9 | 10 | {{ form.as_table }} 11 |
12 | 13 |
14 | {% endblock %} -------------------------------------------------------------------------------- /locallibrary/catalog/templates/catalog/author_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base_generic.html" %} 2 | 3 | {% block content %} 4 |
5 |

Autores

6 |
7 | {% if author_list %} 8 | 15 | {% else %} 16 |

There are no books in the library.

17 | {% endif %} 18 |
19 | {% endblock %} -------------------------------------------------------------------------------- /locallibrary/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'locallibrary.settings') 9 | try: 10 | from django.core.management import execute_from_command_line 11 | except ImportError as exc: 12 | raise ImportError( 13 | "Couldn't import Django. Are you sure it's installed and " 14 | "available on your PYTHONPATH environment variable? Did you " 15 | "forget to activate a virtual environment?" 16 | ) from exc 17 | execute_from_command_line(sys.argv) 18 | 19 | 20 | if __name__ == '__main__': 21 | main() 22 | -------------------------------------------------------------------------------- /locallibrary/catalog/templates/catalog/search.html: -------------------------------------------------------------------------------- 1 | {% extends "base_generic.html" %} 2 | 3 | {% block content %} 4 |
5 |

Livros

6 |
7 | 8 |
9 |
10 | {% if books %} 11 |
    12 | {% for book in books %} 13 |
  • 14 | {{ book.title }} ({{book.author}}) 15 |

    {{ book.summary }}

    16 |
  • 17 | {% endfor %} 18 |
19 | {% else %} 20 |

Livro não encontrado.

21 | {% endif %} 22 |
23 | {% endblock %} -------------------------------------------------------------------------------- /locallibrary/catalog/templates/catalog/book_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base_generic.html" %} 2 | 3 | {% block content %} 4 |
5 |

Livros

6 |
7 | 8 |
9 |
10 | {% if book_list %} 11 |
    12 | {% for book in book_list %} 13 |
  • 14 | {{ book.title }} ({{book.author}}) 15 |
  • 16 | {% endfor %} 17 |
18 | {% else %} 19 |

There are no books in the library.

20 | {% endif %} 21 |
22 | {% endblock %} -------------------------------------------------------------------------------- /locallibrary/catalog/templates/catalog/author_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "base_generic.html" %} 2 | 3 | {% block content %} 4 |
5 |
6 |

{{ author.first_name }} {{ author.last_name }}

7 | {% if perms.catalog.can_mark_returned %} 8 | Atualizar 9 | Remover 10 | {% endif %} 11 |
12 |
13 |
14 | {% for book in author.book_set.all %} 15 |
{{book}} ({{book.bookinstance_set.all.count}})
16 |
{{book.summary}}
17 | {% endfor %} 18 |
19 |
20 |
21 | {% endblock %} -------------------------------------------------------------------------------- /locallibrary/catalog/templates/catalog/bookinstance_list_borrowed_user.html: -------------------------------------------------------------------------------- 1 | {% extends "base_generic.html" %} 2 | 3 | {% block content %} 4 |
5 |

Livros Emprestados

6 |
7 | {% if bookinstance_list %} 8 |
    9 | {% for bookinst in bookinstance_list %} 10 |
  • 11 | {{bookinst.book.title}} ({{ bookinst.due_back }}) 12 | {% if perms.catalog.can_mark_returned %}- Renew {% endif %} 13 |
  • 14 | {% endfor %} 15 |
16 | {% else %} 17 |

There are no books borrowed.

18 | {% endif %} 19 |
20 | {% endblock %} -------------------------------------------------------------------------------- /locallibrary/catalog/templates/catalog/list_borrowed.html: -------------------------------------------------------------------------------- 1 | {% extends "base_generic.html" %} 2 | 3 | {% block content %} 4 |
5 |

Livros Emprestados

6 |
7 | {% if bookinstance_list %} 8 |
    9 | {% for bookinst in bookinstance_list %} 10 |
  • 11 | {{bookinst.book.title}} ({{ bookinst.due_back }}) - {{ bookinst.borrower }} 12 | {% if perms.catalog.can_mark_returned %}- Renew {% endif %} 13 |
  • 14 | {% endfor %} 15 |
16 | {% else %} 17 |

There are no books borrowed.

18 | {% endif %} 19 |
20 | {% endblock %} -------------------------------------------------------------------------------- /locallibrary/catalog/migrations/0003_auto_20190706_1353.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.1 on 2019-07-06 16:53 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ('catalog', '0002_auto_20190705_1527'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterModelOptions( 17 | name='author', 18 | options={'ordering': ['first_name']}, 19 | ), 20 | migrations.AddField( 21 | model_name='bookinstance', 22 | name='borrower', 23 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /locallibrary/catalog/forms.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from django import forms 3 | from django.core.exceptions import ValidationError 4 | from django.utils.translation import ugettext_lazy as _ 5 | 6 | class RenewBookForm(forms.Form): 7 | renewal_date = forms.DateField(help_text="Enter a date between now and 4 weeks (default 3).") 8 | 9 | def clean_renewal_date(self): 10 | data = self.cleaned_data['renewal_date'] 11 | 12 | # Check if a date is not in the past. 13 | if data < datetime.date.today(): 14 | raise ValidationError(_('Invalid date - renewal in past')) 15 | 16 | # Check if a date is in the allowed range (+4 weeks from today). 17 | if data > datetime.date.today() + datetime.timedelta(weeks=4): 18 | raise ValidationError(_('Invalid date - renewal more than 4 weeks ahead')) 19 | 20 | # Remember to always return the cleaned data. 21 | return data -------------------------------------------------------------------------------- /locallibrary/catalog/migrations/0002_auto_20190705_1527.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.3 on 2019-07-05 18:27 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('catalog', '0001_initial'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Language', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('name', models.CharField(help_text="Enter the book's natural language (e.g. English, French, Japanese etc.)", max_length=200)), 19 | ], 20 | ), 21 | migrations.AddField( 22 | model_name='book', 23 | name='language', 24 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='catalog.Language'), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /locallibrary/catalog/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base_generic.html" %} 2 | {% load static %} 3 | 4 | {% block content %} 5 |
6 |

Biblioteca Secreta

7 |
8 | 9 |
10 |

"Seja muito bem vindo à Grande Biblioteca Secreta!"

11 |

Conteúdo Dinâmico

12 |

Nossa biblioteca conta atualmente com os seguintes contadores de registros:

13 |
    14 |
  • Livros: {{ num_books }}
  • 15 |
  • Cópias: {{ num_instances }}
  • 16 |
  • Cópias disponíveis: {{ num_instances_available }}
  • 17 |
  • Autores: {{ num_authors }}
  • 18 |
19 |
20 |

Você visitou a biblioteca secreta {{ num_visits }}{% if num_visits == 1 %} vez{% else %} vezes{% endif %}.

21 |
22 | {% endblock %} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Django Secret Library 2 | 3 | ![img](https://raw.githubusercontent.com/the-akira/Django-Secret-Library/master/locallibrary/catalog/static/images/Avatar.png) 4 | 5 | Full Featured Library System made with Django. 6 | 7 | ## Some features included 8 | 9 | - User Registration 10 | - Books CRUD 11 | - Authors CRUD 12 | - Search System 13 | - Loan System 14 | - Admin Interface provided by Django 15 | 16 | ## Installation 17 | 18 | ### Clone the Repository 19 | 20 | ``` 21 | git clone https://github.com/the-akira/Django-Secret-Library.git 22 | ``` 23 | 24 | ### Inside the Main Directory 25 | 26 | Create a Virtual Environment 27 | 28 | ``` 29 | python -m venv myvenv 30 | ``` 31 | 32 | Activate the Virtual Environment 33 | 34 | ``` 35 | source myvenv/bin/activate 36 | ``` 37 | 38 | Install Requirements 39 | 40 | ``` 41 | pip install -r requirements.txt 42 | ``` 43 | 44 | Navigate to `locallibrary` and Run the Application 45 | 46 | ``` 47 | python manage.py runserver 48 | ``` 49 | 50 | You can now open your Web Browser and navigate to `http://127.0.0.1:8000/` to see the Web Application. 51 | 52 | Inspired by the great tutorial of **[MDN Web Docs](https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website)**. -------------------------------------------------------------------------------- /locallibrary/catalog/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | 4 | urlpatterns = [ 5 | path('', views.index, name='index'), 6 | path('books/', views.BookListView.as_view(), name='books'), 7 | path('book/', views.BookDetailView.as_view(), name='book-detail'), 8 | path('authors/', views.AuthorListView.as_view(), name='authors'), 9 | path('authors/', views.AuthorDetailView.as_view(), name='author-detail'), 10 | path('mybooks/', views.LoanedBooksByUserListView.as_view(), name='my-borrowed'), 11 | path('book//renew/', views.renew_book_librarian, name='renew-book-librarian'), 12 | path('author/create/', views.AuthorCreate.as_view(), name='author_create'), 13 | path('author//update/', views.AuthorUpdate.as_view(), name='author_update'), 14 | path('author//delete/', views.AuthorDelete.as_view(), name='author_delete'), 15 | path('book/create/', views.BookCreate.as_view(), name='book_create'), 16 | path('book//update/', views.BookUpdate.as_view(), name='book_update'), 17 | path('book//delete/', views.BookDelete.as_view(), name='book_delete'), 18 | path('search/', views.search_screen_view, name='search'), 19 | path('borrowed/', views.BooksBorrowed.as_view(), name='borrowed'), 20 | ] -------------------------------------------------------------------------------- /locallibrary/templates/registration/password_reset_confirm.html: -------------------------------------------------------------------------------- 1 | {% extends "base_generic.html" %} 2 | 3 | {% block content %} 4 |
5 | {% if validlink %} 6 |

Please enter (and confirm) your new password.

7 |
8 | {% csrf_token %} 9 | 10 | 11 | 13 | 14 | 15 | 16 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
{{ form.new_password1.errors }} 12 | {{ form.new_password1 }}
{{ form.new_password2.errors }} 17 | {{ form.new_password2 }}
25 |
26 | {% else %} 27 |

Password reset failed

28 |

The password reset link was invalid, possibly because it has already been used. Please request a new password reset.

29 | {% endif %} 30 |
31 | {% endblock %} -------------------------------------------------------------------------------- /locallibrary/templates/registration/login.html: -------------------------------------------------------------------------------- 1 | {% extends "base_generic.html" %} 2 | 3 | {% block content %} 4 | 5 |
6 | {% if form.errors %} 7 |
8 |

Seu usuário e senha não correspondem. Por favor, tente novamente.

9 | {% endif %} 10 | 11 | {% if next %} 12 | {% if user.is_authenticated %} 13 |
14 |

Your account doesn't have access to this page. To proceed, 15 | please login with an account that has access.

16 | {% else %} 17 |
18 |

Por favor faça o Login para acessar o Sistema.

19 | {% endif %} 20 | {% endif %} 21 |
22 | {% csrf_token %} 23 |
24 | 25 | {{ form.username }} 26 |
27 |
28 | 29 | {{ form.password }} 30 |
31 | 32 | 33 |

Lost password?

34 |
35 | 36 | {# Assumes you setup the password_reset view in your URLconf #} 37 | 38 | {% endblock %} -------------------------------------------------------------------------------- /locallibrary/catalog/templates/catalog/book_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "base_generic.html" %} 2 | 3 | {% block content %} 4 |
5 |

Título: {{ book.title }}

6 |
7 |

Autor: {{ book.author }}

8 |

Sumário: {{ book.summary }}

9 |

ISBN: {{ book.isbn }}

10 |

Idioma: {{ book.language }}

11 |

Gênero: {% for genre in book.genre.all %} {{ genre }}{% if not forloop.last %}, {% endif %}{% endfor %}

12 | {% if perms.catalog.can_mark_returned %} 13 | Atualizar 14 | Remover 15 | {% endif %} 16 |
17 |
18 |

Cópias:

19 | {% for copy in book.bookinstance_set.all %} 20 |

{{ copy.get_status_display }}

21 | {% if copy.status != 'a' %} 22 |

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

23 | {% endif %} 24 |

Imprint: {{copy.imprint}}

25 |

Id: {{copy.id}}

26 | {% endfor %} 27 |
28 |
29 | {% endblock %} -------------------------------------------------------------------------------- /locallibrary/catalog/tests/test_models.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | from catalog.models import Author 4 | 5 | class AuthorModelTest(TestCase): 6 | @classmethod 7 | def setUpTestData(cls): 8 | # Set up non-modified objects used by all test methods 9 | Author.objects.create(first_name='Big', last_name='Bob') 10 | 11 | def test_first_name_label(self): 12 | author = Author.objects.get(id=1) 13 | field_label = author._meta.get_field('first_name').verbose_name 14 | self.assertEquals(field_label, 'first name') 15 | 16 | def test_date_of_death_label(self): 17 | author=Author.objects.get(id=1) 18 | field_label = author._meta.get_field('date_of_death').verbose_name 19 | self.assertEquals(field_label, 'died') 20 | 21 | def test_first_name_max_length(self): 22 | author = Author.objects.get(id=1) 23 | max_length = author._meta.get_field('first_name').max_length 24 | self.assertEquals(max_length, 100) 25 | 26 | def test_object_name_is_last_name_comma_first_name(self): 27 | author = Author.objects.get(id=1) 28 | expected_object_name = f'{author.last_name}, {author.first_name}' 29 | self.assertEquals(expected_object_name, str(author)) 30 | 31 | def test_get_absolute_url(self): 32 | author = Author.objects.get(id=1) 33 | # This will also fail if the urlconf is not defined. 34 | self.assertEquals(author.get_absolute_url(), '/catalog/authors/1') -------------------------------------------------------------------------------- /locallibrary/catalog/tests/test_forms.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from django.test import TestCase 4 | from django.utils import timezone 5 | 6 | from catalog.forms import RenewBookForm 7 | 8 | class RenewBookFormTest(TestCase): 9 | def test_renew_form_date_field_label(self): 10 | form = RenewBookForm() 11 | self.assertTrue(form.fields['renewal_date'].label == None or form.fields['renewal_date'].label == 'renewal date') 12 | 13 | def test_renew_form_date_field_help_text(self): 14 | form = RenewBookForm() 15 | self.assertEqual(form.fields['renewal_date'].help_text, 'Enter a date between now and 4 weeks (default 3).') 16 | 17 | def test_renew_form_date_in_past(self): 18 | date = datetime.date.today() - datetime.timedelta(days=1) 19 | form = RenewBookForm(data={'renewal_date': date}) 20 | self.assertFalse(form.is_valid()) 21 | 22 | def test_renew_form_date_too_far_in_future(self): 23 | date = datetime.date.today() + datetime.timedelta(weeks=4) + datetime.timedelta(days=1) 24 | form = RenewBookForm(data={'renewal_date': date}) 25 | self.assertFalse(form.is_valid()) 26 | 27 | def test_renew_form_date_today(self): 28 | date = datetime.date.today() 29 | form = RenewBookForm(data={'renewal_date': date}) 30 | self.assertTrue(form.is_valid()) 31 | 32 | def test_renew_form_date_max(self): 33 | date = timezone.now() + datetime.timedelta(weeks=4) 34 | form = RenewBookForm(data={'renewal_date': date}) 35 | self.assertTrue(form.is_valid()) 36 | -------------------------------------------------------------------------------- /locallibrary/catalog/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from catalog.models import Author, Genre, Book, BookInstance, Language 3 | 4 | # Register your models here. 5 | #admin.site.register(Book) 6 | #admin.site.register(Author) 7 | admin.site.register(Genre) 8 | #admin.site.register(BookInstance) 9 | admin.site.register(Language) 10 | 11 | class AuthorInstanceInline(admin.TabularInline): 12 | model = Book 13 | extra = 0 14 | 15 | # Define the admin class 16 | class AuthorAdmin(admin.ModelAdmin): 17 | list_display = ('last_name', 'first_name', 'date_of_birth', 'date_of_death') 18 | fields = ['first_name', 'last_name', ('date_of_birth', 'date_of_death')] 19 | inlines = [AuthorInstanceInline] 20 | 21 | # Register the admin class with the associated model 22 | admin.site.register(Author, AuthorAdmin) 23 | 24 | class BooksInstanceInline(admin.TabularInline): 25 | model = BookInstance 26 | extra = 0 27 | 28 | # Register the Admin classes for Book using the decorator 29 | @admin.register(Book) 30 | class BookAdmin(admin.ModelAdmin): 31 | list_display = ('title', 'author', 'display_genre') 32 | inlines = [BooksInstanceInline] 33 | 34 | # Register the Admin classes for BookInstance using the decorator 35 | @admin.register(BookInstance) 36 | class BookInstanceAdmin(admin.ModelAdmin): 37 | list_display = ('book', 'status', 'borrower', 'due_back', 'id') 38 | list_filter = ('status', 'due_back') 39 | 40 | fieldsets = ( 41 | (None, { 42 | 'fields': ('book','imprint', 'id') 43 | }), 44 | ('Availability', { 45 | 'fields': ('status', 'due_back','borrower') 46 | }), 47 | ) -------------------------------------------------------------------------------- /locallibrary/catalog/static/css/styles.css: -------------------------------------------------------------------------------- 1 | .nav{ 2 | padding: 20px; 3 | border: 1px solid white; 4 | margin: auto; 5 | background-color: #222222; 6 | border-radius: 10px; 7 | } 8 | 9 | a{ 10 | color: white !important; 11 | } 12 | 13 | strong { 14 | color: white; 15 | } 16 | 17 | b{ 18 | color: black; 19 | } 20 | 21 | .container{ 22 | padding: 20px; 23 | margin-top: 20px; 24 | background-color:#100109; 25 | margin-bottom: 20px; 26 | } 27 | 28 | .caixa-principal{ 29 | border: 1px solid white; 30 | box-shadow: 5px 5px 5px #222222; 31 | border-radius: 14px; 32 | } 33 | 34 | .titulo{ 35 | text-shadow:2px 2px #222222, 0 0 4px black; 36 | color:#908d94; 37 | font-weight: bold; 38 | font-style: oblique; 39 | } 40 | 41 | .list-group-item{ 42 | margin-bottom:4px; 43 | } 44 | 45 | hr{ 46 | background-color: white; 47 | } 48 | 49 | .btn-primary{ 50 | margin-top: 10px; 51 | background-color: gray !important; 52 | border: 1px solid white !important; 53 | } 54 | 55 | .page-current{ 56 | background-color:#222222 !important; 57 | } 58 | 59 | .livro{ 60 | color: white; 61 | } 62 | 63 | .skull{ 64 | display: block; 65 | margin-left: auto; 66 | margin-right: auto; 67 | width: 50%; 68 | } 69 | 70 | .list-group-horizontal .list-group-item { 71 | margin-bottom: 4px !important; 72 | } 73 | 74 | form{ 75 | padding: 20px; 76 | border: 1px solid white; 77 | background-color: #222222; 78 | } 79 | 80 | input { 81 | width: 100%; 82 | padding: 6px 12px; 83 | border: 1px solid black; 84 | border-radius: 10px; 85 | color: black; 86 | } 87 | 88 | input:focus{ 89 | background-color: gray; 90 | } 91 | 92 | textarea{ 93 | resize: none; 94 | display: block; 95 | } 96 | 97 | .form-group > label:nth-child(6){ 98 | display: block; 99 | } 100 | 101 | #id_genre, #id_language, #id_author{ 102 | display: block; 103 | } 104 | 105 | .form-group > label:nth-child(14){ 106 | display: block; 107 | margin-top: 5px; 108 | } -------------------------------------------------------------------------------- /locallibrary/catalog/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.3 on 2019-07-05 18:20 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | import uuid 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='Author', 18 | fields=[ 19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('first_name', models.CharField(max_length=100)), 21 | ('last_name', models.CharField(max_length=100)), 22 | ('date_of_birth', models.DateField(blank=True, null=True)), 23 | ('date_of_death', models.DateField(blank=True, null=True, verbose_name='Died')), 24 | ], 25 | options={ 26 | 'ordering': ['last_name', 'first_name'], 27 | }, 28 | ), 29 | migrations.CreateModel( 30 | name='Book', 31 | fields=[ 32 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 33 | ('title', models.CharField(max_length=200)), 34 | ('summary', models.TextField(help_text='Enter a brief description of the book', max_length=1000)), 35 | ('isbn', models.CharField(help_text='13 Character ISBN number', max_length=13, verbose_name='ISBN')), 36 | ('author', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='catalog.Author')), 37 | ], 38 | ), 39 | migrations.CreateModel( 40 | name='Genre', 41 | fields=[ 42 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 43 | ('name', models.CharField(help_text='Enter a book genre (e.g. Science Fiction)', max_length=200)), 44 | ], 45 | ), 46 | migrations.CreateModel( 47 | name='BookInstance', 48 | fields=[ 49 | ('id', models.UUIDField(default=uuid.uuid4, help_text='Unique ID for this particular book across whole library', primary_key=True, serialize=False)), 50 | ('imprint', models.CharField(max_length=200)), 51 | ('due_back', models.DateField(blank=True, null=True)), 52 | ('status', models.CharField(blank=True, choices=[('m', 'Maintenance'), ('o', 'On loan'), ('a', 'Available'), ('r', 'Reserved')], default='m', help_text='Book availability', max_length=1)), 53 | ('book', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='catalog.Book')), 54 | ], 55 | options={ 56 | 'ordering': ['due_back'], 57 | }, 58 | ), 59 | migrations.AddField( 60 | model_name='book', 61 | name='genre', 62 | field=models.ManyToManyField(help_text='Select a genre for this book', to='catalog.Genre'), 63 | ), 64 | ] 65 | -------------------------------------------------------------------------------- /locallibrary/catalog/templates/base_generic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% block title %}Biblioteca Secreta{% endblock %} 5 | 6 | 7 | 8 | {% load static %} 9 | 10 | 11 | 12 |
13 |
14 |
15 | {% block sidebar %} 16 | 33 | {% endblock %} 34 |
35 |
36 | {% block content %}{% endblock %} 37 | {% block pagination %} 38 | {% if is_paginated %} 39 | 52 | {% endif %} 53 | {% endblock %} 54 |
55 |
56 |
57 | 58 | -------------------------------------------------------------------------------- /locallibrary/locallibrary/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for locallibrary project. 3 | 4 | Generated by 'django-admin startproject' using Django 2.2.3. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/2.2/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = '_7q^ww*$e@(^u2*47+&9%#_l#i7kc=s^h9b0ozx7&6(#9%pigb' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 'catalog', 41 | ] 42 | 43 | MIDDLEWARE = [ 44 | 'django.middleware.security.SecurityMiddleware', 45 | 'django.contrib.sessions.middleware.SessionMiddleware', 46 | 'django.middleware.common.CommonMiddleware', 47 | 'django.middleware.csrf.CsrfViewMiddleware', 48 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 49 | 'django.contrib.messages.middleware.MessageMiddleware', 50 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 51 | ] 52 | 53 | ROOT_URLCONF = 'locallibrary.urls' 54 | 55 | TEMPLATES = [ 56 | { 57 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 58 | 'DIRS': [os.path.join(BASE_DIR, 'templates')], 59 | 'APP_DIRS': True, 60 | 'OPTIONS': { 61 | 'context_processors': [ 62 | 'django.template.context_processors.debug', 63 | 'django.template.context_processors.request', 64 | 'django.contrib.auth.context_processors.auth', 65 | 'django.contrib.messages.context_processors.messages', 66 | ], 67 | }, 68 | }, 69 | ] 70 | 71 | WSGI_APPLICATION = 'locallibrary.wsgi.application' 72 | 73 | 74 | # Database 75 | # https://docs.djangoproject.com/en/2.2/ref/settings/#databases 76 | 77 | DATABASES = { 78 | 'default': { 79 | 'ENGINE': 'django.db.backends.sqlite3', 80 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 81 | } 82 | } 83 | 84 | 85 | # Password validation 86 | # https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators 87 | 88 | AUTH_PASSWORD_VALIDATORS = [ 89 | { 90 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 91 | }, 92 | { 93 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 94 | }, 95 | { 96 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 97 | }, 98 | { 99 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 100 | }, 101 | ] 102 | 103 | 104 | # Internationalization 105 | # https://docs.djangoproject.com/en/2.2/topics/i18n/ 106 | 107 | LANGUAGE_CODE = 'en-us' 108 | 109 | TIME_ZONE = 'America/Sao_Paulo' 110 | 111 | USE_I18N = True 112 | 113 | USE_L10N = True 114 | 115 | USE_TZ = True 116 | 117 | 118 | # Static files (CSS, JavaScript, Images) 119 | # https://docs.djangoproject.com/en/2.2/howto/static-files/ 120 | 121 | STATIC_URL = '/static/' 122 | 123 | # Redirect to home URL after login (Default redirects to /accounts/profile/) 124 | LOGIN_REDIRECT_URL = '/' 125 | 126 | EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' -------------------------------------------------------------------------------- /locallibrary/catalog/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.urls import reverse 3 | from django.contrib.auth.models import User 4 | from datetime import date 5 | import uuid 6 | 7 | class Genre(models.Model): 8 | """Model representing a book genre.""" 9 | name = models.CharField(max_length=200, help_text='Enter a book genre (e.g. Science Fiction)') 10 | 11 | def __str__(self): 12 | """String for representing the Model object.""" 13 | return self.name 14 | 15 | class Language(models.Model): 16 | """Model representing a Language (e.g. English, French, Japanese, etc.)""" 17 | name = models.CharField(max_length=200, help_text="Enter the book's natural language (e.g. English, French, Japanese etc.)") 18 | 19 | def __str__(self): 20 | return self.name 21 | 22 | class Book(models.Model): 23 | """Model representing a book (but not a specific copy of a book).""" 24 | title = models.CharField(max_length=200) 25 | 26 | # Foreign Key used because book can only have one author, but authors can have multiple books 27 | # Author as a string rather than object because it hasn't been declared yet in the file 28 | author = models.ForeignKey('Author', on_delete=models.SET_NULL, null=True) 29 | 30 | summary = models.TextField(max_length=1000, help_text='Enter a brief description of the book') 31 | isbn = models.CharField('ISBN', max_length=13, help_text='13 Character ISBN number') 32 | 33 | # ManyToManyField used because genre can contain many books. Books can cover many genres. 34 | # Genre class has already been defined so we can specify the object above. 35 | genre = models.ManyToManyField(Genre, help_text='Select a genre for this book') 36 | language = models.ForeignKey('Language', on_delete=models.SET_NULL, null=True) 37 | 38 | def __str__(self): 39 | """String for representing the Model object.""" 40 | return self.title 41 | 42 | def get_absolute_url(self): 43 | """Returns the url to access a detail record for this book.""" 44 | return reverse('book-detail', args=[str(self.id)]) 45 | 46 | def display_genre(self): 47 | """Create a string for the Genre. This is required to display genre in Admin.""" 48 | return ', '.join(genre.name for genre in self.genre.all()[:3]) 49 | 50 | display_genre.short_description = 'Genre' 51 | 52 | class BookInstance(models.Model): 53 | """Model representing a specific copy of a book (i.e. that can be borrowed from the library).""" 54 | id = models.UUIDField(primary_key=True, default=uuid.uuid4, help_text='Unique ID for this particular book across whole library') 55 | book = models.ForeignKey('Book', on_delete=models.SET_NULL, null=True) 56 | imprint = models.CharField(max_length=200) 57 | due_back = models.DateField(null=True, blank=True) 58 | borrower = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True) 59 | 60 | LOAN_STATUS = ( 61 | ('m', 'Maintenance'), 62 | ('o', 'On loan'), 63 | ('a', 'Available'), 64 | ('r', 'Reserved'), 65 | ) 66 | 67 | status = models.CharField( 68 | max_length=1, 69 | choices=LOAN_STATUS, 70 | blank=True, 71 | default='m', 72 | help_text='Book availability', 73 | ) 74 | 75 | class Meta: 76 | ordering = ['due_back'] 77 | permissions = (("can_mark_returned", "Set book as returned"),) 78 | 79 | @property 80 | def is_overdue(self): 81 | if self.due_back and date.today() > self.due_back: 82 | return True 83 | return False 84 | 85 | def __str__(self): 86 | """String for representing the Model object.""" 87 | return f'{self.id} ({self.book.title})' 88 | 89 | class Author(models.Model): 90 | """Model representing an author.""" 91 | first_name = models.CharField(max_length=100) 92 | last_name = models.CharField(max_length=100) 93 | date_of_birth = models.DateField(null=True, blank=True) 94 | date_of_death = models.DateField('died', null=True, blank=True) 95 | 96 | class Meta: 97 | ordering = ['last_name', 'first_name'] 98 | 99 | def get_absolute_url(self): 100 | """Returns the url to access a particular author instance.""" 101 | return reverse('author-detail', args=[str(self.id)]) 102 | 103 | def __str__(self): 104 | """String for representing the Model object.""" 105 | return f'{self.last_name}, {self.first_name}' 106 | 107 | class Meta: 108 | ordering = ['first_name'] -------------------------------------------------------------------------------- /locallibrary/catalog/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, get_object_or_404 2 | from django.contrib.auth.decorators import permission_required 3 | from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin 4 | import datetime 5 | from operator import attrgetter 6 | from django.http import HttpResponseRedirect 7 | from django.db.models import Q 8 | from django.urls import reverse, reverse_lazy 9 | from catalog.forms import RenewBookForm 10 | from django.views import generic 11 | from catalog.models import Book, Author, BookInstance, Genre 12 | from django.contrib.auth.mixins import LoginRequiredMixin 13 | from django.views.generic.edit import CreateView, UpdateView, DeleteView 14 | from catalog.models import Author 15 | 16 | def index(request): 17 | """View function for home page of site.""" 18 | # Generate counts of some of the main objects 19 | num_books = Book.objects.all().count() 20 | num_instances = BookInstance.objects.all().count() 21 | 22 | # Available books (status = 'a') 23 | num_instances_available = BookInstance.objects.filter(status__exact='a').count() 24 | 25 | # The 'all()' is implied by default. 26 | num_authors = Author.objects.count() 27 | 28 | # Number of visits to this view, as counted in the session variable. 29 | num_visits = request.session.get('num_visits', 0) 30 | request.session['num_visits'] = num_visits + 1 31 | 32 | context = { 33 | 'num_books': num_books, 34 | 'num_instances': num_instances, 35 | 'num_instances_available': num_instances_available, 36 | 'num_authors': num_authors, 37 | 'num_visits': num_visits, 38 | } 39 | 40 | # Render the HTML template index.html with the data in the context variable 41 | return render(request, 'index.html', context=context) 42 | 43 | class BookListView(generic.ListView): 44 | model = Book 45 | paginate_by = 8 46 | 47 | class BookDetailView(generic.DetailView): 48 | model = Book 49 | 50 | class AuthorListView(generic.ListView): 51 | model = Author 52 | paginate_by = 8 53 | 54 | class AuthorDetailView(generic.DetailView): 55 | model = Author 56 | 57 | class LoanedBooksByUserListView(LoginRequiredMixin,generic.ListView): 58 | """Generic class-based view listing books on loan to current user.""" 59 | model = BookInstance 60 | template_name ='catalog/bookinstance_list_borrowed_user.html' 61 | paginate_by = 10 62 | 63 | def get_queryset(self): 64 | return BookInstance.objects.filter(borrower=self.request.user).filter(status__exact='o').order_by('due_back') # 'o' é o código para on loan 65 | 66 | @permission_required('catalog.can_mark_returned') 67 | def renew_book_librarian(request, pk): 68 | """View function for renewing a specific BookInstance by librarian.""" 69 | book_instance = get_object_or_404(BookInstance, pk=pk) 70 | 71 | # If this is a POST request then process the Form data 72 | if request.method == 'POST': 73 | 74 | # Create a form instance and populate it with data from the request (binding): 75 | form = RenewBookForm(request.POST) 76 | 77 | # Check if the form is valid: 78 | if form.is_valid(): 79 | # process the data in form.cleaned_data as required (here we just write it to the model due_back field) 80 | book_instance.due_back = form.cleaned_data['renewal_date'] 81 | book_instance.save() 82 | 83 | # redirect to a new URL: 84 | return HttpResponseRedirect(reverse('my-borrowed') ) 85 | 86 | # If this is a GET (or any other method) create the default form. 87 | else: 88 | proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3) 89 | form = RenewBookForm(initial={'renewal_date': proposed_renewal_date}) 90 | 91 | context = { 92 | 'form': form, 93 | 'book_instance': book_instance, 94 | } 95 | 96 | return render(request, 'catalog/book_renew_librarian.html', context) 97 | 98 | class AuthorCreate(LoginRequiredMixin, CreateView): 99 | model = Author 100 | fields = '__all__' 101 | initial = {'date_of_death': '05/01/2018'} 102 | 103 | class AuthorUpdate(LoginRequiredMixin, UpdateView): 104 | model = Author 105 | fields = ['first_name', 'last_name', 'date_of_birth', 'date_of_death'] 106 | 107 | class AuthorDelete(LoginRequiredMixin, DeleteView): 108 | model = Author 109 | success_url = reverse_lazy('authors') 110 | 111 | class BookCreate(LoginRequiredMixin, PermissionRequiredMixin, CreateView): 112 | permission_required = 'catalog.can_mark_returned' 113 | model = Book 114 | fields = '__all__' 115 | 116 | class BookUpdate(LoginRequiredMixin, PermissionRequiredMixin, UpdateView): 117 | permission_required = 'catalog.can_mark_returned' 118 | model = Book 119 | fields = ['title', 'author', 'summary', 'isbn', 'genre', 'language'] 120 | 121 | class BookDelete(LoginRequiredMixin, PermissionRequiredMixin, DeleteView): 122 | model = Book 123 | success_url = reverse_lazy('books') 124 | permission_required = 'catalog.can_mark_returned' 125 | 126 | class BooksBorrowed(LoginRequiredMixin, PermissionRequiredMixin, generic.ListView): 127 | permission_required = 'catalog.can_mark_returned' 128 | model = BookInstance 129 | template_name ='catalog/list_borrowed.html' 130 | paginate_by = 10 131 | 132 | def get_book_queryset(query=None): 133 | queryset = [] 134 | queries = query.split(' ') # python install 2019 = ['python', 'install', '2019'] 135 | for q in queries: 136 | posts = Book.objects.filter(Q(title__contains=q)| Q(summary__icontains=q) ).distinct() 137 | 138 | for post in posts: 139 | queryset.append(post) 140 | 141 | return list(set(queryset)) 142 | 143 | def search_screen_view(request): 144 | context = {} 145 | 146 | query = "" 147 | if request.GET: 148 | query = request.GET.get('q', '') 149 | context['query'] = str(query) 150 | 151 | books = sorted(get_book_queryset(query), key=attrgetter('title'), reverse=True) 152 | 153 | context['books'] = books 154 | 155 | return render(request, 'catalog/search.html', context) -------------------------------------------------------------------------------- /locallibrary/catalog/tests/test_views.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | import uuid 3 | from django.urls import reverse 4 | from django.utils import timezone 5 | from django.contrib.auth.models import User, Permission 6 | import datetime 7 | from catalog.models import BookInstance, Book, Genre, Language, Author 8 | from catalog.forms import RenewBookForm 9 | 10 | class AuthorListViewTest(TestCase): 11 | @classmethod 12 | def setUpTestData(cls): 13 | # Create 13 authors for pagination tests 14 | number_of_authors = 13 15 | 16 | for author_id in range(number_of_authors): 17 | Author.objects.create( 18 | first_name=f'Christian {author_id}', 19 | last_name=f'Surname {author_id}', 20 | ) 21 | 22 | def test_view_url_exists_at_desired_location(self): 23 | response = self.client.get('/catalog/authors/') 24 | self.assertEqual(response.status_code, 200) 25 | 26 | def test_view_url_accessible_by_name(self): 27 | response = self.client.get(reverse('authors')) 28 | self.assertEqual(response.status_code, 200) 29 | 30 | def test_view_uses_correct_template(self): 31 | response = self.client.get(reverse('authors')) 32 | self.assertEqual(response.status_code, 200) 33 | self.assertTemplateUsed(response, 'catalog/author_list.html') 34 | 35 | def test_pagination_is_ten(self): 36 | response = self.client.get(reverse('authors')) 37 | self.assertEqual(response.status_code, 200) 38 | self.assertTrue('is_paginated' in response.context) 39 | self.assertTrue(response.context['is_paginated'] == True) 40 | self.assertTrue(len(response.context['author_list']) == 10) 41 | 42 | def test_lists_all_authors(self): 43 | # Get second page and confirm it has (exactly) remaining 3 items 44 | response = self.client.get(reverse('authors')+'?page=2') 45 | self.assertEqual(response.status_code, 200) 46 | self.assertTrue('is_paginated' in response.context) 47 | self.assertTrue(response.context['is_paginated'] == True) 48 | self.assertTrue(len(response.context['author_list']) == 3) 49 | 50 | class LoanedBookInstancesByUserListViewTest(TestCase): 51 | def setUp(self): 52 | # Create two users 53 | test_user1 = User.objects.create_user(username='testuser1', password='1X