├── .gitignore ├── LICENSE ├── README.md ├── accounts ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ └── __init__.py ├── models.py ├── templates │ ├── registration │ │ └── login.html │ └── signup.html ├── templatetags │ ├── __init__.py │ └── tags.py ├── tests.py └── views.py ├── api ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ └── __init__.py ├── models.py ├── serializers.py ├── tests.py ├── urls.py └── views.py ├── books_and_chapters ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20181008_1420.py │ ├── 0003_auto_20181010_1646.py │ ├── 0004_book_slug.py │ ├── 0005_auto_20181015_1701.py │ ├── 0006_auto_20181021_0222.py │ └── __init__.py ├── models.py ├── quotes_dump.pckl ├── static │ ├── css │ │ └── b4_sidebar.css │ ├── images │ │ ├── bookworm.png │ │ ├── logomark.png │ │ └── logomarkv2.ico │ └── js │ │ └── b4_sidebar.js ├── summarize.py ├── templates │ ├── base.html │ ├── book_detail.html │ ├── books.html │ ├── messages.html │ ├── modals │ │ ├── book_detail_edit_modal.html │ │ └── chapter_edit_modal.html │ ├── navbar.html │ └── registration_base.html ├── templatetags │ └── active.py ├── tests.py ├── urls.py └── views.py ├── django_bookworm ├── __init__.py ├── settings.py ├── urls.py └── wsgi.py ├── logo ├── bookworm.png ├── logomark.png ├── logomark.svg ├── logomarkv2.png ├── vertical.png └── vertical.svg ├── manage.py ├── prerequisites.py ├── requirements.txt └── result.gif /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Omkar Pathak 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | [![GitHub](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/OmkarPathak/Django-Bookworm/blob/master/LICENSE) ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/Django.svg) [![Say Thanks!](https://img.shields.io/badge/Say%20Thanks-:D-1EAEDB.svg)](https://saythanks.io/to/OmkarPathak) 6 | 7 | # Django-Bookworm 8 | Love reading books, Have problem remembers main points from book like me? this is a fun project to store learning from each book. 9 | 10 | # Features 11 | - Easy to use GUI 12 | - Chapter-wise bifurcation 13 | - Generate summary from whatever your learning from each chapter are 14 | - Get basic statistics about the books you read 15 | - Get inspirational quotes on your homepage 16 | 17 | # Installation 18 | 19 | - Download or clone this repository 20 | - Change directory to the recently cloned repository folder or unziped folder 21 | - Run the following command to install dependencies: 22 | ```bash 23 | $pip install -r requirements.txt 24 | ``` 25 | - For summarization Python `nltk` is used. To download required files, execute: 26 | ```bash 27 | python prerequisites.py 28 | ``` 29 | - Then create a file named `.env` inside `django_bookworm/` and add following details with your details 30 | ``` 31 | SECRET_KEY= 32 | ``` 33 | - Now, you can start the django server by executing following commands: 34 | ```bash 35 | python manage.py migrate 36 | python manage.py runserver 37 | ``` 38 | 39 | # Working 40 | 41 | ![Working](result.gif) 42 | 43 | # Special Mentions 44 | 45 | - Special thanks to [@reallinfo](https://github.com/reallinfo) for the beautiful and thoughtful logo :smile: 46 | -------------------------------------------------------------------------------- /accounts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmkarPathak/Django-Bookworm/14ecfbff0b2819d3e57365dbe440053a1cff0271/accounts/__init__.py -------------------------------------------------------------------------------- /accounts/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /accounts/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class AccountsConfig(AppConfig): 5 | name = 'accounts' 6 | -------------------------------------------------------------------------------- /accounts/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmkarPathak/Django-Bookworm/14ecfbff0b2819d3e57365dbe440053a1cff0271/accounts/migrations/__init__.py -------------------------------------------------------------------------------- /accounts/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /accounts/templates/registration/login.html: -------------------------------------------------------------------------------- 1 | {% extends 'registration_base.html' %} 2 | {% load tags %} 3 | {% block title %}Login{% endblock %} 4 | 5 | {% block content %} 6 |
7 |
8 | {% if form.non_field_errors %} 9 | {% for error in form.non_field_errors %} 10 |
{{ error }}
11 | {% endfor %} 12 | {% endif %} 13 |
14 |

Login

15 |
16 |
17 |
18 |
19 |
20 |
21 | {% csrf_token %} 22 | 23 |
24 |
25 | 26 | {{ form.username | add_css:"form-control" }} 27 |
28 |
29 |
30 |
31 | 32 | {{ form.password | add_css:"form-control" }} 33 |
34 |
35 | 36 |
37 |
38 |
39 |
40 | {% endblock %} -------------------------------------------------------------------------------- /accounts/templates/signup.html: -------------------------------------------------------------------------------- 1 | {% extends 'registration_base.html' %} 2 | {% load tags %} 3 | {% block title %}Sign Up{% endblock %} 4 | 5 | {% block content %} 6 |
7 |
8 | {% if form.non_field_errors %} 9 | {% for error in form.non_field_errors %} 10 |
{{ error }}
11 | {% endfor %} 12 | {% endif %} 13 |
14 |

Sign Up

15 |
16 |
17 |
18 |
19 |
20 |
21 | {% csrf_token %} 22 |
23 | {% for field in form %} 24 |
25 | 26 | {{ field | add_css:"form-control" }} 27 |
{{ field.errors|striptags }}
28 |
29 | {% endfor %} 30 |
31 | 32 |
33 |
34 |
35 |
36 | {% endblock %} -------------------------------------------------------------------------------- /accounts/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmkarPathak/Django-Bookworm/14ecfbff0b2819d3e57365dbe440053a1cff0271/accounts/templatetags/__init__.py -------------------------------------------------------------------------------- /accounts/templatetags/tags.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | 3 | register = template.Library() 4 | 5 | @register.filter(name='add_css') 6 | def add_css(field, css): 7 | """Removes all values of arg from the given string""" 8 | return field.as_widget(attrs={"class": css}) -------------------------------------------------------------------------------- /accounts/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /accounts/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import login, authenticate 2 | from django.contrib.auth.forms import UserCreationForm 3 | from django.shortcuts import render, redirect 4 | 5 | def signup(request): 6 | if request.method == 'POST': 7 | form = UserCreationForm(request.POST) 8 | if form.is_valid(): 9 | form.save() 10 | username = form.cleaned_data.get('username') 11 | raw_password = form.cleaned_data.get('password1') 12 | user = authenticate(username=username, password=raw_password) 13 | login(request, user) 14 | return redirect('books') 15 | else: 16 | form = UserCreationForm() 17 | return render(request, 'signup.html', {'form': form}) -------------------------------------------------------------------------------- /api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmkarPathak/Django-Bookworm/14ecfbff0b2819d3e57365dbe440053a1cff0271/api/__init__.py -------------------------------------------------------------------------------- /api/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /api/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ApiConfig(AppConfig): 5 | name = 'api' 6 | -------------------------------------------------------------------------------- /api/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmkarPathak/Django-Bookworm/14ecfbff0b2819d3e57365dbe440053a1cff0271/api/migrations/__init__.py -------------------------------------------------------------------------------- /api/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /api/serializers.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | from rest_framework import serializers 3 | from books_and_chapters.models import Book, Chapter 4 | 5 | class BookSerializer(serializers.ModelSerializer): 6 | class Meta: 7 | model = Book 8 | fields = '__all__' 9 | 10 | class ChapterSerializer(serializers.ModelSerializer): 11 | class Meta: 12 | model = Chapter 13 | fields = '__all__' -------------------------------------------------------------------------------- /api/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /api/urls.py: -------------------------------------------------------------------------------- 1 | """django_bookworm.api URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.1/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.urls import path 17 | from . import views 18 | from rest_framework.authtoken import views as rest_auth 19 | 20 | urlpatterns = [ 21 | path('books/', views.BookList.as_view()), 22 | path('books//', views.BookDetail.as_view()), 23 | path('chapters/', views.ChapterList.as_view()), 24 | path('chapters//', views.ChapterDetail.as_view()), 25 | path('login/', rest_auth.obtain_auth_token), 26 | ] 27 | -------------------------------------------------------------------------------- /api/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, get_object_or_404 2 | from django.contrib.auth.models import User 3 | from .serializers import BookSerializer, ChapterSerializer 4 | from rest_framework.views import APIView 5 | from rest_framework.response import Response 6 | from rest_framework import status 7 | from books_and_chapters.models import Book, Chapter 8 | 9 | class BookList(APIView): 10 | ''' 11 | List all books or create a new book 12 | ''' 13 | def get(self, request): 14 | books = Book.objects.all() 15 | serializer = BookSerializer(books, many=True) 16 | return Response(serializer.data) 17 | 18 | def post(self, request): 19 | serializer = BookSerializer(data=request.data) 20 | if serializer.is_valid(): 21 | serializer.save() 22 | return Response(serializer.data, status=status.HTTP_201_CREATED) 23 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 24 | 25 | class BookDetail(APIView): 26 | ''' 27 | Retrieve, update or delete a book instance 28 | ''' 29 | def get(self, request, pk): 30 | book = get_object_or_404(Book, pk=pk) 31 | serializer = BookSerializer(book) 32 | return Response(serializer.data) 33 | 34 | def put(self, request, pk): 35 | book = get_object_or_404(Book, pk=pk) 36 | serializer = BookSerializer(book, data=request.data) 37 | if serializer.is_valid(): 38 | serializer.save() 39 | return Response(serializer.data) 40 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 41 | 42 | def delete(self, request, pk): 43 | book = get_object_or_404(Book, pk=pk) 44 | book.delete() 45 | return Response(status=status.HTTP_204_NO_CONTENT) 46 | 47 | class ChapterList(APIView): 48 | ''' 49 | List all books or create a new chapter 50 | ''' 51 | def get(self, request): 52 | chapters = Chapter.objects.all() 53 | serializer = ChapterSerializer(chapters, many=True) 54 | return Response(serializer.data) 55 | 56 | def post(self, request): 57 | serializer = ChapterSerializer(data=request.data) 58 | if serializer.is_valid(): 59 | serializer.save() 60 | return Response(serializer.data, status=status.HTTP_201_CREATED) 61 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 62 | 63 | class ChapterDetail(APIView): 64 | ''' 65 | Retrieve, update or delete a chapter instance 66 | ''' 67 | def get(self, request, pk): 68 | chapter = get_object_or_404(Chapter, pk=pk) 69 | serializer = ChapterSerializer(chapter) 70 | return Response(serializer.data) 71 | 72 | def put(self, request, pk): 73 | chapter = get_object_or_404(Chapter, pk=pk) 74 | serializer = ChapterSerializer(chapter, data=request.data) 75 | if serializer.is_valid(): 76 | serializer.save() 77 | return Response(serializer.data) 78 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 79 | 80 | def delete(self, request, pk): 81 | chapter = get_object_or_404(Chapter, pk=pk) 82 | chapter.delete() 83 | return Response(status=status.HTTP_204_NO_CONTENT) -------------------------------------------------------------------------------- /books_and_chapters/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmkarPathak/Django-Bookworm/14ecfbff0b2819d3e57365dbe440053a1cff0271/books_and_chapters/__init__.py -------------------------------------------------------------------------------- /books_and_chapters/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Book, Chapter 3 | 4 | @admin.register(Book) 5 | class BookAdmin(admin.ModelAdmin): 6 | pass 7 | 8 | @admin.register(Chapter) 9 | class ChapterAdmin(admin.ModelAdmin): 10 | pass -------------------------------------------------------------------------------- /books_and_chapters/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class BooksAndChaptersConfig(AppConfig): 5 | name = 'books_and_chapters' 6 | -------------------------------------------------------------------------------- /books_and_chapters/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.2 on 2018-10-07 02:46 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='Book', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('book_name', models.CharField(max_length=200)), 20 | ('author_name', models.CharField(max_length=200)), 21 | ('book_read_on', models.DateField()), 22 | ('created_at', models.DateTimeField(auto_now_add=True)), 23 | ('updated_at', models.DateTimeField(auto_now=True)), 24 | ], 25 | ), 26 | migrations.CreateModel( 27 | name='Chapter', 28 | fields=[ 29 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 30 | ('chapter_number', models.IntegerField()), 31 | ('description', models.TextField()), 32 | ('book', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='books_and_chapters.Book')), 33 | ], 34 | ), 35 | ] 36 | -------------------------------------------------------------------------------- /books_and_chapters/migrations/0002_auto_20181008_1420.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.2 on 2018-10-08 14:20 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('books_and_chapters', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='book', 15 | name='book_name', 16 | field=models.CharField(max_length=200, unique=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /books_and_chapters/migrations/0003_auto_20181010_1646.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.2 on 2018-10-10 16:46 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 | ('books_and_chapters', '0002_auto_20181008_1420'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='chapter', 16 | name='book', 17 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='books_and_chapters.Book'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /books_and_chapters/migrations/0004_book_slug.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.2 on 2018-10-15 16:25 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('books_and_chapters', '0003_auto_20181010_1646'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='book', 15 | name='slug', 16 | field=models.SlugField(default='djangodbmodelsfieldscharfield', max_length=200, unique=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /books_and_chapters/migrations/0005_auto_20181015_1701.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.2 on 2018-10-15 17:01 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('books_and_chapters', '0004_book_slug'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='book', 15 | name='slug', 16 | field=models.SlugField(max_length=200, unique=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /books_and_chapters/migrations/0006_auto_20181021_0222.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.2 on 2018-10-21 02:22 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 | ('books_and_chapters', '0005_auto_20181015_1701'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name='book', 18 | name='added_by_user', 19 | field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), 20 | preserve_default=False, 21 | ), 22 | migrations.AlterField( 23 | model_name='book', 24 | name='book_name', 25 | field=models.CharField(max_length=200), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /books_and_chapters/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmkarPathak/Django-Bookworm/14ecfbff0b2819d3e57365dbe440053a1cff0271/books_and_chapters/migrations/__init__.py -------------------------------------------------------------------------------- /books_and_chapters/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django import forms 3 | from django.utils.text import slugify 4 | from django.contrib.auth.models import User 5 | 6 | def generate_unique_slug(_class, field): 7 | """ 8 | return unique slug if origin slug is exist. 9 | eg: `foo-bar` => `foo-bar-1` 10 | 11 | :param `field` is specific field for title. 12 | """ 13 | origin_slug = slugify(field) 14 | unique_slug = origin_slug 15 | numb = 1 16 | while _class.objects.filter(slug=unique_slug).exists(): 17 | unique_slug = '%s-%d' % (origin_slug, numb) 18 | numb += 1 19 | return unique_slug 20 | 21 | class Book(models.Model): 22 | added_by_user = models.ForeignKey(User, on_delete=models.CASCADE) 23 | book_name = models.CharField(max_length=200) 24 | author_name = models.CharField(max_length=200) 25 | book_read_on = models.DateField() 26 | created_at = models.DateTimeField(auto_now_add=True) 27 | updated_at = models.DateTimeField(auto_now=True) 28 | slug = models.SlugField(max_length=200, unique=True) 29 | 30 | def __str__(self): 31 | return self.book_name 32 | 33 | def save(self, *args, **kwargs): 34 | if self.slug: 35 | if slugify(self.book_name) != self.slug: 36 | self.slug = generate_unique_slug(Book, self.book_name) 37 | else: 38 | self.slug = generate_unique_slug(Book, self.book_name) 39 | super(Book, self).save(*args, **kwargs) 40 | 41 | 42 | class Chapter(models.Model): 43 | book = models.ForeignKey(Book, on_delete=models.CASCADE) 44 | chapter_number = models.IntegerField() 45 | description = models.TextField() 46 | 47 | def __str__(self): 48 | return self.book.book_name + ': ' + str(self.chapter_number) 49 | 50 | class DateInput(forms.DateInput): 51 | input_type = 'date' 52 | 53 | class BookForm(forms.ModelForm): 54 | class Meta: 55 | model = Book 56 | fields = '__all__' 57 | exclude = ('slug', 'added_by_user') 58 | widgets = { 59 | 'book_read_on': DateInput() 60 | } 61 | 62 | class ChapterForm(forms.ModelForm): 63 | class Meta: 64 | model = Chapter 65 | fields = ['chapter_number', 'description'] -------------------------------------------------------------------------------- /books_and_chapters/quotes_dump.pckl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmkarPathak/Django-Bookworm/14ecfbff0b2819d3e57365dbe440053a1cff0271/books_and_chapters/quotes_dump.pckl -------------------------------------------------------------------------------- /books_and_chapters/static/css/b4_sidebar.css: -------------------------------------------------------------------------------- 1 | body{position:relative}.overlay,.sideMenu{position:fixed;bottom:0}.overlay{top:0;left:-100%;right:100%;margin:auto;background-color:rgba(0,0,0,.5);z-index:998;transition:all ease 0.2s}.overlay.open{left:0;right:0}.sidebarNavigation{margin-bottom:0;z-index:999;justify-content:flex-start}.sidebarNavigation .leftNavbarToggler{margin-right:10px;order:-1}.sideMenu{left:-100%;top:52px;transition:all ease 0.5s;overflow:hidden;width:100%;z-index:999;max-width:80%;margin-bottom:0;padding:1rem}.sideMenu.open{left:0;display:block;overflow-y:auto}.sideMenu ul{margin:0;padding:0 15px} 2 | 3 | #sidebar { 4 | width: inherit; 5 | min-width: 300px; 6 | max-width: 300px; 7 | background-color:#f5f5f5; 8 | float: left; 9 | height:100vh; 10 | position:relative; 11 | overflow-y:auto; 12 | overflow-x:hidden; 13 | } 14 | 15 | #main { 16 | height:100%; 17 | overflow:auto; 18 | } 19 | 20 | /* 21 | * off Canvas sidebar 22 | * -------------------------------------------------- 23 | */ 24 | @media screen and (max-width: 768px) { 25 | .row-offcanvas { 26 | position: relative; 27 | -webkit-transition: all 0.25s ease-out; 28 | -moz-transition: all 0.25s ease-out; 29 | transition: all 0.25s ease-out; 30 | width:calc(100% + 300px); 31 | } 32 | 33 | .row-offcanvas-left 34 | { 35 | left: -300px; 36 | } 37 | 38 | .row-offcanvas-left.active { 39 | left: 0; 40 | } 41 | 42 | .sidebar-offcanvas { 43 | position: absolute; 44 | top: 0; 45 | } 46 | } 47 | 48 | .row-offcanvas { 49 | height:100%; 50 | } 51 | 52 | .nav-pills > .nav-item > .nav-link{ 53 | border-radius: 0; 54 | } 55 | 56 | #book_delete{ 57 | display: none; 58 | } 59 | 60 | #book_details:hover #book_delete{ 61 | display: block; 62 | } 63 | 64 | #search_book{ 65 | margin: 0; 66 | padding-bottom: 0; 67 | padding-right: 0; 68 | padding-top: 0; 69 | line-height: 2.5; 70 | border-radius: 0; 71 | } 72 | 73 | .ui-autocomplete { 74 | position: absolute; 75 | top: 0; 76 | left: 0; 77 | right: 0; 78 | z-index: 1; 79 | float: left; 80 | display: none; 81 | padding: 4px 0; 82 | list-style: none; 83 | background-color: #ffffff; 84 | border-color: #ccc; 85 | border-color: rgba(0, 0, 0, 0.2); 86 | border-style: solid; 87 | border-width: 1px; 88 | -webkit-border-radius: 5px; 89 | -moz-border-radius: 5px; 90 | border-radius: 0; 91 | -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); 92 | -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); 93 | box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); 94 | -webkit-background-clip: padding-box; 95 | -moz-background-clip: padding; 96 | background-clip: padding-box; 97 | *border-right-width: 2px; 98 | *border-bottom-width: 2px; 99 | max-width: 265px; 100 | } 101 | 102 | .ui-menu-item > a.ui-corner-all { 103 | display: block; 104 | padding: 3px 15px; 105 | clear: both; 106 | font-weight: normal; 107 | line-height: 18px; 108 | color: #555555; 109 | white-space: nowrap; 110 | text-decoration: none; 111 | } 112 | 113 | .ui-state-hover, .ui-state-active { 114 | color: #ffffff; 115 | text-decoration: none; 116 | background-color: #0088cc; 117 | border-radius: 0px; 118 | -webkit-border-radius: 0px; 119 | -moz-border-radius: 0px; 120 | background-image: none; 121 | } 122 | 123 | #book-homepage .card{ 124 | border-radius: 0px; 125 | box-shadow: 5px 8px 8px gray; 126 | } 127 | 128 | @media only screen and (max-width: 768px){ 129 | .col-sm-12{ 130 | margin-bottom: 5%; 131 | } 132 | } -------------------------------------------------------------------------------- /books_and_chapters/static/images/bookworm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmkarPathak/Django-Bookworm/14ecfbff0b2819d3e57365dbe440053a1cff0271/books_and_chapters/static/images/bookworm.png -------------------------------------------------------------------------------- /books_and_chapters/static/images/logomark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmkarPathak/Django-Bookworm/14ecfbff0b2819d3e57365dbe440053a1cff0271/books_and_chapters/static/images/logomark.png -------------------------------------------------------------------------------- /books_and_chapters/static/images/logomarkv2.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmkarPathak/Django-Bookworm/14ecfbff0b2819d3e57365dbe440053a1cff0271/books_and_chapters/static/images/logomarkv2.ico -------------------------------------------------------------------------------- /books_and_chapters/static/js/b4_sidebar.js: -------------------------------------------------------------------------------- 1 | window.onload=function(){window.jQuery?$(document).ready(function(){$(".sidebarNavigation .navbar-collapse").hide().clone().appendTo("body").removeAttr("class").addClass("sideMenu").show(),$("body").append("
"),$(".sideMenu").addClass($(".sidebarNavigation").attr("data-sidebarClass")),$(".navbar-toggle, .navbar-toggler").on("click",function(){$(".sideMenu, .overlay").toggleClass("open"),$(".overlay").on("click",function(){$(this).removeClass("open"),$(".sideMenu").removeClass("open")})}),$("body").on("click",".sideMenu.open .nav-item",function(){$(this).hasClass("dropdown")||$(".sideMenu, .overlay").toggleClass("open")}),$(window).resize(function(){$(".navbar-toggler").is(":hidden")?$(".sideMenu, .overlay").hide():$(".sideMenu, .overlay").show()})}):console.log("sidebarNavigation Requires jQuery")}; -------------------------------------------------------------------------------- /books_and_chapters/summarize.py: -------------------------------------------------------------------------------- 1 | from nltk.corpus import stopwords 2 | from nltk.tokenize import word_tokenize, sent_tokenize 3 | from nltk.stem import PorterStemmer 4 | import heapq 5 | import re 6 | 7 | # based on brilliant Blog post for summarization: https://stackabuse.com/text-summarization-with-nltk-in-python/ 8 | 9 | stemmer = PorterStemmer() 10 | 11 | class Summarizer(object): 12 | def __init__(self, text): 13 | self.__text = text 14 | self.__stop_words = stopwords.words('english') 15 | self.__sentence = sent_tokenize(text) 16 | self.__f_text = self.create_formatted_text() 17 | self.__word_freq = self.calc_word_frequencies() 18 | 19 | def get_lenth(self): 20 | return len(self.__sentence) 21 | 22 | def create_formatted_text(self): 23 | ''' 24 | removes digits, spaces and special symbols from sentences 25 | 26 | :return: list of formatted text tokenized as words 27 | ''' 28 | formatted_article_text = re.sub(r'\[[0-9]*\]', ' ', self.__text) # remove digits 29 | formatted_article_text = re.sub('[^a-zA-Z]', ' ', formatted_article_text ) # remove special symbols 30 | formatted_article_text = re.sub(r'\s+', ' ', formatted_article_text) # remove spaces 31 | return formatted_article_text.split(' ') 32 | 33 | def calc_word_frequencies(self): 34 | ''' 35 | calculates weighted word frequencies 36 | 37 | :return: dict of weighted word frequencies 38 | ''' 39 | word_frequencies = {} 40 | for word in self.__f_text: 41 | word = word.lower() 42 | if word not in self.__stop_words: 43 | if word in word_frequencies: 44 | word_frequencies[word] += 1 45 | else: 46 | word_frequencies[word] = 1 47 | 48 | # calculate weighted frequencies 49 | max_frequency = max(word_frequencies.values()) 50 | for word in word_frequencies.keys(): 51 | word_frequencies[word] += (word_frequencies.get(word) // max_frequency) 52 | 53 | return word_frequencies 54 | 55 | def get_summary(self, number_of_sentences=5): 56 | ''' 57 | generates summary based on weighted word frequencies 58 | 59 | :param number_of_sentences: total number of sentences to return in summary 60 | :return: string of summary 61 | ''' 62 | sentence_value = {} 63 | for sentence in self.__sentence: 64 | for word in self.__word_freq.keys(): 65 | if word in word_tokenize(sentence.lower()): 66 | if sentence in sentence_value: 67 | sentence_value[sentence] += self.__word_freq.get(word) 68 | else: 69 | sentence_value[sentence] = self.__word_freq.get(word, 0) 70 | 71 | summary_sentences = heapq.nlargest(number_of_sentences, sentence_value, key=sentence_value.get) 72 | summary = ' '.join(summary_sentences) 73 | return summary 74 | 75 | if __name__ == '__main__': 76 | summarizer = Summarizer('So, keep working. Keep striving. Never give up. Fall down seven times, get up eight. Ease is a greater threat to progress than hardship. Ease is a greater threat to progress than hardship. So, keep moving, keep growing, keep learning. See you at work.') 77 | # print('Original:', summarizer._Summarizer__text) 78 | print('Summary:', summarizer.get_summary(4)) -------------------------------------------------------------------------------- /books_and_chapters/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | {% load static %} 3 | {% load active %} 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | {% block title %}{% endblock %} 35 | 36 | 37 | {% include 'navbar.html' %} 38 |
39 | 55 |
56 |
57 |

58 | 59 |

60 |
61 | {% block content %} 62 | {% endblock %} 63 |
64 |
65 | 66 | 67 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | {% block javascript %} 110 | {% endblock %} 111 | 131 | {% if form_error %} 132 | 135 | {% endif %} 136 | 137 | 142 | 143 | -------------------------------------------------------------------------------- /books_and_chapters/templates/book_detail.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load crispy_forms_tags %} 3 | {% block title %}{{ book_detail.book_name }}{% endblock %} 4 | {% block content %} 5 |
6 |
7 |
8 | {% if book_detail %} 9 |

10 | {{ book_detail.book_name }} 11 | 12 | 13 | Edit 14 | 15 | 16 | Delete 17 | 18 | 19 |

20 |

21 | {{ book_detail.author_name }} 22 |

You read this on: {{ book_detail.book_read_on }}

23 |

24 |
25 | {% endif %} 26 |
27 |
28 | 29 |
30 |
{% include 'messages.html' %}
31 |
32 | {% if summary %} 33 |
34 | Summary: {{ summary }} 35 |
36 |
37 | {% endif %} 38 |
39 |

Chapters

40 | 41 |
42 | 43 |
44 |
45 | {% if chapters %} 46 | {% for chapter in chapters %} 47 |
48 |
Chapter {{ chapter.chapter_number }} 49 | 50 |
51 | 52 | 53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | {{ chapter.description }} 61 |
62 |
63 |
64 |
65 |
66 | {% endfor %} 67 | {% else %} 68 |
69 |
70 |
71 |

72 |
73 |
74 |

75 | Seems like you have just started to read this book. Come on, get a pace 76 | and fill me up 77 |

78 |
79 |
80 | {% endif %} 81 |
82 |
83 |
84 | 85 | 86 | 112 | 113 | 114 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 176 | 177 | {% if form_error %} 178 | 181 | {% endif %} 182 | {% endblock %} 183 | 184 | {% block javascript %} 185 | 191 | 192 | 205 | 206 | 219 | {% endblock %} -------------------------------------------------------------------------------- /books_and_chapters/templates/books.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %}Bookworm{% endblock %} 3 | 4 | {% block content %} 5 |
6 |
7 |
{% include 'messages.html' %}
8 |
9 | 10 |
11 | {% if total_books == 0 %} 12 |
13 |

14 | Hi, welcome to Django Bookworm where you can store all your favorite books and 15 | what all you have learned from them. A simple and fun way to get the summary of 16 | your learning which is easily accessible through the beautiful GUI. 17 |

18 | 19 |

20 | You can add a new book to get started from left side menu by clicking on the sign. 21 |

22 |
23 | {% else %} 24 |
25 |
26 |

{{ quote.quote }}

27 |
{{ quote.author }}
28 |
29 |
30 | 31 |
32 |
33 |

34 | {{ total_books }} 35 |

36 |

37 | Total Books
Read 38 |

39 |
40 |
41 | 42 |
43 |
44 |

45 | {{ total_chapters }} 46 |

47 |

48 | Total Chapters
Read 49 |

50 |
51 |
52 | 53 |
54 |
55 |

56 | {{ last_month_books_count }} 57 |

58 |

59 | Total Books Read
This Month 60 |

61 |
62 |
63 | {% endif %} 64 |
65 |
66 | {% endblock %} 67 | 68 | {% block javascript %} 69 | 82 | {% endblock %} -------------------------------------------------------------------------------- /books_and_chapters/templates/messages.html: -------------------------------------------------------------------------------- 1 | {% if messages %} 2 |
3 | {% for message in messages %} 4 |
5 | {% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %}Important: {% endif %} 6 | {{ message }} 7 |
8 | {% endfor %} 9 |
10 | {% endif %} -------------------------------------------------------------------------------- /books_and_chapters/templates/modals/book_detail_edit_modal.html: -------------------------------------------------------------------------------- 1 | {% load crispy_forms_tags %} 2 | -------------------------------------------------------------------------------- /books_and_chapters/templates/modals/chapter_edit_modal.html: -------------------------------------------------------------------------------- 1 | {% load crispy_forms_tags %} 2 | -------------------------------------------------------------------------------- /books_and_chapters/templates/navbar.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | -------------------------------------------------------------------------------- /books_and_chapters/templates/registration_base.html: -------------------------------------------------------------------------------- 1 | 2 | {% load static %} 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | {% block title %}{% endblock %} 32 | 33 | 34 | {% include 'navbar.html' %} 35 |
36 | {% block content %} 37 | {% endblock %} 38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 | {% block javascript %} 46 | 53 | {% endblock %} 54 | 55 | -------------------------------------------------------------------------------- /books_and_chapters/templatetags/active.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from django.shortcuts import reverse 3 | 4 | register = template.Library() 5 | 6 | @register.simple_tag 7 | def add_active(request, name, slug): 8 | if slug: 9 | path = reverse(name, kwargs={'slug': slug}) 10 | else : 11 | path = reverse(name) 12 | print(path) 13 | if request.path == path: 14 | return "active" 15 | return "" 16 | 17 | @register.filter(name='add_css') 18 | def add_css(field, css): 19 | """Removes all values of arg from the given string""" 20 | return field.as_widget(attrs={"class": css}) -------------------------------------------------------------------------------- /books_and_chapters/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /books_and_chapters/urls.py: -------------------------------------------------------------------------------- 1 | """django_bookworm.books_and_chapters URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.1/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.urls import path 17 | from . import views 18 | from django.views.generic.base import RedirectView 19 | from django.contrib.auth.decorators import login_required 20 | 21 | urlpatterns = [ 22 | path('books/', login_required(views.homepage), name='books'), # for adding a new book 23 | path('books/search/', views.search_book, name='search_book'), 24 | path('books//', login_required(views.get_book_details), name='book_detail'), 25 | path('books//delete/', login_required(views.delete_book), name='delete_single_book'), 26 | path('books//edit/', login_required(views.edit_book_details), name='book_details_edit'), 27 | path('chapters/add/', views.add_chapter, name='add_chapter'), 28 | path('chapters//delete/', views.delete_chapter, name='delete_chapter'), 29 | path('chapters//edit/', views.edit_chapter, name='edit_chapter'), 30 | path('', RedirectView.as_view(url='/accounts/login/', permanent=False)) 31 | ] 32 | -------------------------------------------------------------------------------- /books_and_chapters/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, get_object_or_404, redirect 2 | from django.http import HttpResponse 3 | from .models import Book, Chapter, BookForm, ChapterForm 4 | from django.contrib import messages 5 | from .summarize import Summarizer 6 | import json 7 | from datetime import datetime, timedelta 8 | import pickle, random 9 | import os 10 | 11 | 12 | def get_random_quote(): 13 | module_dir = os.path.dirname(__file__) # get current directory 14 | file_path = os.path.join(module_dir, 'quotes_dump.pckl') 15 | with open(file_path, 'rb') as file: 16 | obj = pickle.load(file) 17 | quote = random.choice(obj) 18 | return quote 19 | 20 | def homepage(request): 21 | books = Book.objects.filter(added_by_user=request.user).order_by('book_read_on') 22 | form_error = False 23 | last_month = datetime.today() - timedelta(days=30) 24 | last_month_books_count = Book.objects.filter( 25 | added_by_user=request.user, 26 | book_read_on__gt=last_month 27 | ).count() 28 | total_chapters = Chapter.objects.filter(book__added_by_user=request.user).count() 29 | quote = get_random_quote() 30 | if request.method == 'POST': 31 | form = BookForm(request.POST) 32 | if form.is_valid(): 33 | form = form.save(commit=False) 34 | form.added_by_user = request.user 35 | form.save() 36 | form = BookForm() 37 | messages.success(request, 'Book added successfully!') 38 | else: 39 | form_error = True 40 | else: 41 | form = BookForm() 42 | context = { 43 | 'quote': quote, 44 | 'books': books, 45 | 'add_book_form': form, 46 | 'form_error': form_error, 47 | 'last_month_books_count': last_month_books_count, 48 | 'total_chapters': total_chapters, 49 | 'total_books': len(books), 50 | } 51 | return render(request, 'books.html', context) 52 | 53 | def get_book_details(request, slug): 54 | book = get_object_or_404(Book, slug=slug) 55 | if book.added_by_user != request.user: 56 | messages.error(request, 'You are not authenticated to perform this action') 57 | return redirect('books') 58 | try: 59 | chapter = Chapter.objects.filter(book=book).order_by('chapter_number') 60 | except Chapter.DoesNotExist: 61 | chapter = None 62 | if chapter: 63 | text = '' 64 | for chap in chapter: 65 | text += chap.description 66 | summarizer = Summarizer(text) 67 | summary = summarizer.get_summary(int(summarizer.get_lenth() * 0.4)) 68 | else: 69 | summary = '' 70 | books = Book.objects.filter(added_by_user=request.user).order_by('book_read_on') 71 | add_book_form = BookForm() 72 | add_chapter_form = ChapterForm(initial={ 73 | 'book': book 74 | }) 75 | context = { 76 | 'books': books, 77 | 'chapters': chapter, 78 | 'book_detail': book, 79 | 'add_book_form': add_book_form, 80 | 'add_chapter_form': add_chapter_form, 81 | 'summary': summary, 82 | } 83 | return render(request, 'book_detail.html', context) 84 | 85 | def edit_book_details(request, pk): 86 | book = get_object_or_404(Book, pk=pk) 87 | if book.added_by_user != request.user: 88 | messages.error(request, 'You are not authenticated to perform this action') 89 | return redirect('books') 90 | if request.method == 'POST': 91 | form = BookForm(request.POST, instance=book) 92 | if form.is_valid(): 93 | form.save() 94 | return redirect('book_detail', slug=book.slug) 95 | else: 96 | form = BookForm(initial={ 97 | 'book_name': book.book_name, 98 | 'author_name': book.author_name, 99 | 'book_read_on': book.book_read_on 100 | }, instance=book) 101 | return render(request, 'modals/book_detail_edit_modal.html', {'form': form}) 102 | 103 | def delete_book(request, pk): 104 | book = get_object_or_404(Book, pk=pk) 105 | if book.added_by_user != request.user: 106 | messages.error(request, 'You are not authenticated to perform this action') 107 | return redirect('books') 108 | book.delete() 109 | return redirect('books') 110 | 111 | def add_chapter(request): 112 | form_error = False 113 | if request.method == 'POST': 114 | book = get_object_or_404(Book, pk=request.POST.get('pk')) 115 | if book.added_by_user != request.user: 116 | messages.error(request, 'You are not authenticated to perform this action') 117 | return redirect('books') 118 | form = ChapterForm(request.POST) 119 | if form.is_valid(): 120 | form = form.save(commit=False) 121 | form.book = book 122 | form.save() 123 | messages.success(request, 'Chapter added successfully!') 124 | return redirect('book_detail', slug=book.slug) 125 | else: 126 | form_error = True 127 | context = { 128 | 'books': Book.objects.all(), 129 | 'book_detail': book, 130 | 'add_chapter_form': form, 131 | 'form_error': form_error 132 | } 133 | return render(request, 'book_detail.html', context) 134 | 135 | def edit_chapter(request, pk): 136 | chapter = get_object_or_404(Chapter, pk=pk) 137 | if chapter.book.added_by_user != request.user: 138 | messages.error(request, 'You are not authenticated to perform this action') 139 | return redirect('books') 140 | if request.method == 'POST': 141 | chapter = Chapter.objects.get(pk=pk) 142 | form = ChapterForm(request.POST, instance=chapter) 143 | if form.is_valid(): 144 | form.save() 145 | messages.success(request, 'Chapter edited!') 146 | return redirect('book_detail', slug=chapter.book.slug) 147 | else: 148 | form = ChapterForm(initial={ 149 | 'book':chapter.book, 150 | 'chapter_number':chapter.chapter_number, 151 | 'description':chapter.description 152 | }, instance=chapter) 153 | return render(request, 'modals/chapter_edit_modal.html', {'form': form}) 154 | 155 | def delete_chapter(request, pk): 156 | chapter = get_object_or_404(Chapter, pk=pk) 157 | if chapter.book.added_by_user != request.user: 158 | messages.error(request, 'You are not authenticated to perform this action') 159 | return redirect('books') 160 | chapter.delete() 161 | messages.success(request, 'Chapter deleted!') 162 | return redirect('book_detail', slug=chapter.book.slug) 163 | 164 | def search_book(request): 165 | if request.is_ajax(): 166 | q = request.GET.get('term') 167 | books = Book.objects.filter( 168 | book_name__icontains=q, 169 | added_by_user=request.user 170 | )[:10] 171 | results = [] 172 | for book in books: 173 | book_json = {} 174 | book_json['slug'] = book.slug 175 | book_json['label'] = book.book_name 176 | book_json['value'] = book.book_name 177 | results.append(book_json) 178 | data = json.dumps(results) 179 | else: 180 | book_json = {} 181 | book_json['slug'] = None 182 | book_json['label'] = None 183 | book_json['value'] = None 184 | data = json.dumps(book_json) 185 | return HttpResponse(data) -------------------------------------------------------------------------------- /django_bookworm/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmkarPathak/Django-Bookworm/14ecfbff0b2819d3e57365dbe440053a1cff0271/django_bookworm/__init__.py -------------------------------------------------------------------------------- /django_bookworm/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for django_bookworm project. 3 | 4 | Generated by 'django-admin startproject' using Django 2.1.2. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.1/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/2.1/ref/settings/ 11 | """ 12 | 13 | import os 14 | from decouple import config 15 | 16 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 17 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 18 | 19 | 20 | # Quick-start development settings - unsuitable for production 21 | # See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/ 22 | 23 | # SECURITY WARNING: keep the secret key used in production secret! 24 | SECRET_KEY = config('SECRET_KEY') 25 | 26 | # SECURITY WARNING: don't run with debug turned on in production! 27 | DEBUG = True 28 | 29 | ALLOWED_HOSTS = ['*'] 30 | 31 | 32 | # Application definition 33 | 34 | INSTALLED_APPS = [ 35 | 'django.contrib.admin', 36 | 'django.contrib.auth', 37 | 'django.contrib.contenttypes', 38 | 'django.contrib.sessions', 39 | 'django.contrib.messages', 40 | 'django.contrib.staticfiles', 41 | 'crispy_forms', 42 | 'rest_framework', 43 | 'rest_framework.authtoken', 44 | 'books_and_chapters', 45 | 'accounts', 46 | 'api' 47 | ] 48 | 49 | MIDDLEWARE = [ 50 | 'django.middleware.security.SecurityMiddleware', 51 | 'django.contrib.sessions.middleware.SessionMiddleware', 52 | 'django.middleware.common.CommonMiddleware', 53 | 'django.middleware.csrf.CsrfViewMiddleware', 54 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 55 | 'django.contrib.messages.middleware.MessageMiddleware', 56 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 57 | ] 58 | 59 | ROOT_URLCONF = 'django_bookworm.urls' 60 | 61 | TEMPLATES = [ 62 | { 63 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 64 | 'DIRS': [os.path.join(BASE_DIR, 'templates')], 65 | 'APP_DIRS': True, 66 | 'OPTIONS': { 67 | 'context_processors': [ 68 | 'django.template.context_processors.debug', 69 | 'django.template.context_processors.request', 70 | 'django.contrib.auth.context_processors.auth', 71 | 'django.contrib.messages.context_processors.messages', 72 | ], 73 | }, 74 | }, 75 | ] 76 | 77 | WSGI_APPLICATION = 'django_bookworm.wsgi.application' 78 | 79 | 80 | # Database 81 | # https://docs.djangoproject.com/en/2.1/ref/settings/#databases 82 | 83 | DATABASES = { 84 | 'default': { 85 | 'ENGINE': 'django.db.backends.sqlite3', 86 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 87 | } 88 | } 89 | 90 | 91 | # Password validation 92 | # https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators 93 | 94 | AUTH_PASSWORD_VALIDATORS = [ 95 | { 96 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 97 | }, 98 | { 99 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 100 | }, 101 | { 102 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 103 | }, 104 | { 105 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 106 | }, 107 | ] 108 | 109 | 110 | # Internationalization 111 | # https://docs.djangoproject.com/en/2.1/topics/i18n/ 112 | 113 | LANGUAGE_CODE = 'en-us' 114 | 115 | TIME_ZONE = 'UTC' 116 | 117 | USE_I18N = True 118 | 119 | USE_L10N = True 120 | 121 | USE_TZ = True 122 | 123 | 124 | # Static files (CSS, JavaScript, Images) 125 | # https://docs.djangoproject.com/en/2.1/howto/static-files/ 126 | 127 | STATIC_URL = '/static/' 128 | 129 | REST_FRAMEWORK = { 130 | 'DEFAULT_AUTHENTICATION_CLASSES': ( 131 | 'rest_framework.authentication.TokenAuthentication', 132 | ), 133 | 'DEFAULT_PERMISSION_CLASSES': ( 134 | 'rest_framework.permissions.IsAuthenticated', 135 | ) 136 | } 137 | 138 | LOGIN_REDIRECT_URL = '/books/' 139 | 140 | LOGOUT_REDIRECT_URL = '/accounts/login/' 141 | 142 | # Bootstrap Crispy-Forms config 143 | CRISPY_TEMPLATE_PACK = 'bootstrap4' -------------------------------------------------------------------------------- /django_bookworm/urls.py: -------------------------------------------------------------------------------- 1 | """django_bookworm URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.1/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path, include 18 | from accounts import views 19 | from django.shortcuts import redirect 20 | from django.contrib.auth.views import LoginView 21 | 22 | urlpatterns = [ 23 | path('admin/', admin.site.urls), 24 | path('api/', include('api.urls')), 25 | path('accounts/signup/', views.signup, name='signup'), 26 | path('accounts/login/', LoginView.as_view(redirect_authenticated_user=True), name='login'), 27 | path('accounts/', include('django.contrib.auth.urls')), 28 | path('', include('books_and_chapters.urls')) 29 | ] 30 | -------------------------------------------------------------------------------- /django_bookworm/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for django_bookworm 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.1/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', 'django_bookworm.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /logo/bookworm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmkarPathak/Django-Bookworm/14ecfbff0b2819d3e57365dbe440053a1cff0271/logo/bookworm.png -------------------------------------------------------------------------------- /logo/logomark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmkarPathak/Django-Bookworm/14ecfbff0b2819d3e57365dbe440053a1cff0271/logo/logomark.png -------------------------------------------------------------------------------- /logo/logomark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 18 | 19 | -------------------------------------------------------------------------------- /logo/logomarkv2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmkarPathak/Django-Bookworm/14ecfbff0b2819d3e57365dbe440053a1cff0271/logo/logomarkv2.png -------------------------------------------------------------------------------- /logo/vertical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmkarPathak/Django-Bookworm/14ecfbff0b2819d3e57365dbe440053a1cff0271/logo/vertical.png -------------------------------------------------------------------------------- /logo/vertical.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 16 | 24 | 32 | 34 | 36 | 44 | 47 | 54 | 55 | 56 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == '__main__': 6 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_bookworm.settings') 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError as exc: 10 | raise ImportError( 11 | "Couldn't import Django. Are you sure it's installed and " 12 | "available on your PYTHONPATH environment variable? Did you " 13 | "forget to activate a virtual environment?" 14 | ) from exc 15 | execute_from_command_line(sys.argv) 16 | -------------------------------------------------------------------------------- /prerequisites.py: -------------------------------------------------------------------------------- 1 | import nltk 2 | 3 | nltk.download('stopwords') -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | certifi==2018.8.24 2 | chardet==3.0.4 3 | coreapi==2.3.3 4 | coreschema==0.0.4 5 | Django==2.1.11 6 | django-crispy-forms==1.7.2 7 | django-rest-swagger==2.2.0 8 | djangorestframework==3.8.2 9 | idna==2.7 10 | itypes==1.1.0 11 | Jinja2==2.10 12 | MarkupSafe==1.0 13 | openapi-codec==1.3.2 14 | python-decouple==3.1 15 | pytz==2018.5 16 | requests==2.20.0 17 | simplejson==3.16.0 18 | uritemplate==3.0.0 19 | urllib3==1.23 20 | -------------------------------------------------------------------------------- /result.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmkarPathak/Django-Bookworm/14ecfbff0b2819d3e57365dbe440053a1cff0271/result.gif --------------------------------------------------------------------------------