diff --git a/Pipfile b/Pipfile index 943f777..6139ae9 100644 --- a/Pipfile +++ b/Pipfile @@ -4,6 +4,7 @@ url = "https://pypi.org/simple" verify_ssl = true [dev-packages] +pylint = "*" [packages] certifi = "==2019.3.9" diff --git a/Pipfile.lock b/Pipfile.lock index 5473506..687ae8d 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "f84436db95f07d008305f5a7e22b1be7aebe13aaeed8bfc8773828914b734cdc" + "sha256": "7ecc2d76a0552943e91394a8214b7749c2df5b4f08ff53840437f67700a6997f" }, "pipfile-spec": 6, "requires": { @@ -97,5 +97,89 @@ "version": "==5.0.1" } }, - "develop": {} + "develop": { + "astroid": { + "hashes": [ + "sha256:4c17cea3e592c21b6e222f673868961bad77e1f985cb1694ed077475a89229c1", + "sha256:d8506842a3faf734b81599c8b98dcc423de863adcc1999248480b18bd31a0f38" + ], + "version": "==2.4.1" + }, + "colorama": { + "hashes": [ + "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff", + "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1" + ], + "markers": "sys_platform == 'win32'", + "version": "==0.4.3" + }, + "isort": { + "hashes": [ + "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1", + "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd" + ], + "version": "==4.3.21" + }, + "lazy-object-proxy": { + "hashes": [ + "sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d", + "sha256:194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449", + "sha256:1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08", + "sha256:4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a", + "sha256:48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50", + "sha256:5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd", + "sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239", + "sha256:8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb", + "sha256:9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea", + "sha256:9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e", + "sha256:97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156", + "sha256:9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142", + "sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442", + "sha256:a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62", + "sha256:ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db", + "sha256:cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531", + "sha256:d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383", + "sha256:d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a", + "sha256:eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357", + "sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4", + "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0" + ], + "version": "==1.4.3" + }, + "mccabe": { + "hashes": [ + "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", + "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" + ], + "version": "==0.6.1" + }, + "pylint": { + "hashes": [ + "sha256:b95e31850f3af163c2283ed40432f053acbc8fc6eba6a069cb518d9dbf71848c", + "sha256:dd506acce0427e9e08fb87274bcaa953d38b50a58207170dbf5b36cf3e16957b" + ], + "index": "pypi", + "version": "==2.5.2" + }, + "six": { + "hashes": [ + "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", + "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" + ], + "version": "==1.14.0" + }, + "toml": { + "hashes": [ + "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", + "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e" + ], + "version": "==0.10.0" + }, + "wrapt": { + "hashes": [ + "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7" + ], + "version": "==1.12.1" + } + } } diff --git a/authentication/forms.py b/authentication/forms.py new file mode 100644 index 0000000..c31cefc --- /dev/null +++ b/authentication/forms.py @@ -0,0 +1,22 @@ +from django import forms +from django.contrib.auth.forms import UserCreationForm,AuthenticationForm +from django.forms import ModelForm + +from django.contrib.auth.models import User + + +class SignUpForm(UserCreationForm): + first_name = forms.CharField(max_length=30, required=False, help_text='Optional.') + last_name = forms.CharField(max_length=30, required=False, help_text='Optional.') + email = forms.EmailField(max_length=254, help_text='Required. Inform a valid email address.') + + class Meta: + model = User + fields = ('username', 'first_name', 'last_name', 'email', 'password1', 'password2', ) + +class LoginForm(forms.Form): + username = forms.CharField(max_length=254) + password = forms.CharField(label=("Password")) + class Meta: + model = User + fields = ('username','password',) diff --git a/authentication/templates/registration/base.html b/authentication/templates/registration/base.html new file mode 100644 index 0000000..505ca39 --- /dev/null +++ b/authentication/templates/registration/base.html @@ -0,0 +1,47 @@ + + + + {% block title %} + Library + {% endblock %} + + + + + {% load static %} + + + + +
+ +
+
+ {% block sidebar %} + + + + + + {% endblock %} +
+
+ {% block content %}{% endblock %} +
+
+ +
+ + \ No newline at end of file diff --git a/authentication/templates/registration/login.html b/authentication/templates/registration/login.html new file mode 100644 index 0000000..723db5d --- /dev/null +++ b/authentication/templates/registration/login.html @@ -0,0 +1,18 @@ +{% extends 'registration/base.html' %} + +{% block content %} +

Log In

+
+ {% csrf_token %} + {{ form.as_p}} + + +
+ {% if messages %} + +{% endif %} +{% endblock %} \ No newline at end of file diff --git a/authentication/templates/registration/signup.html b/authentication/templates/registration/signup.html new file mode 100644 index 0000000..19740dd --- /dev/null +++ b/authentication/templates/registration/signup.html @@ -0,0 +1,10 @@ +{% extends 'registration/base.html' %} + +{% block content %} +

Sign up

+
+ {% csrf_token %} + {{ form.as_p}} + +
+{% endblock %} \ No newline at end of file diff --git a/authentication/urls.py b/authentication/urls.py index e69de29..9a7b665 100644 --- a/authentication/urls.py +++ b/authentication/urls.py @@ -0,0 +1,9 @@ +from django.urls import path +from authentication.views import * + +urlpatterns = [ + + path('signup/',registerView, name='user-signup'), + path('login/',loginView, name='user-login'), + path('logout/',logoutView, name='user-logout'), +] diff --git a/authentication/views.py b/authentication/views.py index 14dd530..e8556a5 100644 --- a/authentication/views.py +++ b/authentication/views.py @@ -1,13 +1,59 @@ -from django.shortcuts import render +from django.shortcuts import render,redirect from django.contrib.auth import login,logout,authenticate +from authentication.forms import * +from django.contrib import messages # Create your views here. def loginView(request): - pass + if request.method == 'POST': + print(request.POST) + form = LoginForm(request.POST) + if form.is_valid(): + print("helloworld") + + username = form.cleaned_data.get('username') + password = form.cleaned_data.get('password') + user = authenticate(username=username, password=password) + if user: + login(request, user) + return redirect('index') + + else: + messages.info(request, 'Invalid Username or Password') + form = LoginForm() + + else: + form = LoginForm() + context = { + 'form': form + + } + return render(request, 'registration/login.html', context) def logoutView(request): - pass + logout(request) + return redirect('index') + + + def registerView(request): - pass \ No newline at end of file + if request.method == 'POST': + form = SignUpForm(request.POST) + if form.is_valid(): + form.save() + username = form.cleaned_data.get('username') + raw_password = form.cleaned_data.get('password1') + user = authenticate(username=username, password=raw_password) + login(request, user) + return redirect('index') + else: + form = SignUpForm() + + context = { + 'form': form + } + return render(request, 'registration/signup.html', context) + + \ No newline at end of file diff --git a/library/urls.py b/library/urls.py index 60b7957..e2a0300 100644 --- a/library/urls.py +++ b/library/urls.py @@ -20,6 +20,7 @@ urlpatterns = [ path('',include('store.urls')), + path('',include('authentication.urls')), path('admin/', admin.site.urls), path('accounts/',include('django.contrib.auth.urls')), ]+static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) diff --git a/store/forms.py b/store/forms.py new file mode 100644 index 0000000..4a68e57 --- /dev/null +++ b/store/forms.py @@ -0,0 +1,6 @@ +from django import forms + +class RatingForm(forms.Form): + rating = forms.FloatField(max_value=10, min_value=0,) + class Meta: + fields=('rating') \ No newline at end of file diff --git a/store/migrations/0003_auto_20200503_1735.py b/store/migrations/0003_auto_20200503_1735.py new file mode 100644 index 0000000..7c94f50 --- /dev/null +++ b/store/migrations/0003_auto_20200503_1735.py @@ -0,0 +1,25 @@ +# Generated by Django 2.2.1 on 2020-05-03 12:05 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('store', '0002_auto_20190607_1302'), + ] + + operations = [ + migrations.AlterField( + model_name='bookcopy', + name='borrow_date', + field=models.DateField(blank=True, null=True), + ), + migrations.AlterField( + model_name='bookcopy', + name='borrower', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='borrower', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/store/migrations/0004_bookrating.py b/store/migrations/0004_bookrating.py new file mode 100644 index 0000000..25b51e7 --- /dev/null +++ b/store/migrations/0004_bookrating.py @@ -0,0 +1,25 @@ +# Generated by Django 2.2.1 on 2020-05-05 22:27 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('store', '0003_auto_20200503_1735'), + ] + + operations = [ + migrations.CreateModel( + name='BookRating', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('rating', models.FloatField()), + ('book', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='store.Book')), + ('rating_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rating_by', to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/store/models.py b/store/models.py index 17c63a3..bb95c1d 100644 --- a/store/models.py +++ b/store/models.py @@ -30,3 +30,8 @@ def __str__(self): else: return f'{self.book.title} - Available' +class BookRating(models.Model): + book = models.ForeignKey(Book, on_delete=models.CASCADE) + rating_by = models.ForeignKey(User, related_name='rating_by', null=False, blank=False, on_delete=models.CASCADE) + rating = models.FloatField() + \ No newline at end of file diff --git a/store/templates/store/base.html b/store/templates/store/base.html index 64b7a6e..505ca39 100644 --- a/store/templates/store/base.html +++ b/store/templates/store/base.html @@ -25,11 +25,12 @@ diff --git a/store/templates/store/book_detail.html b/store/templates/store/book_detail.html index 2e4b9e8..a74a920 100644 --- a/store/templates/store/book_detail.html +++ b/store/templates/store/book_detail.html @@ -19,7 +19,16 @@

Title: {{ book.title }}

MRP:
Rs. {{ book.mrp }}
Available Copies:
-
{{ num_available }}
+
{{ num_available }}
+ {% if user.is_authenticated %} + {% if issued %} +
Rate this book
+ {% else %} +
You can't rate this book because you have not issued it.
+ {%endif%} + {% else %} +
You need to login to rate a book.
+ {%endif%} {% endblock %} \ No newline at end of file diff --git a/store/templates/store/rate.html b/store/templates/store/rate.html new file mode 100644 index 0000000..87a7c94 --- /dev/null +++ b/store/templates/store/rate.html @@ -0,0 +1,9 @@ +{% extends "store/base.html" %} +{%block content%} +

Rate {{title}}

+
+{% csrf_token %} +{{form.as_p}} + +
+{%endblock content%} \ No newline at end of file diff --git a/store/urls.py b/store/urls.py index 4520334..9c5438a 100644 --- a/store/urls.py +++ b/store/urls.py @@ -8,4 +8,6 @@ path('books/loaned/', viewLoanedBooks, name="view-loaned"), path('books/loan/', loanBookView, name="loan-book"), path('books/return/', returnBookView, name="return-book"), + path('books//rate',rateBook,name="rate-book") + ] diff --git a/store/views.py b/store/views.py index dc411b9..5dec20d 100644 --- a/store/views.py +++ b/store/views.py @@ -1,20 +1,47 @@ -from django.shortcuts import render + from django.shortcuts import get_object_or_404 -from store.models import * + from django.contrib.auth.decorators import login_required from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt +from store.models import Book, BookCopy, BookRating + + +from django.db.models import Q +from django.contrib.auth import login, authenticate +from django.contrib.auth.forms import UserCreationForm +from django.shortcuts import render, redirect +import datetime +from store import forms + + + # Create your views here. + def index(request): return render(request, 'store/index.html') def bookDetailView(request, bid): template_name = 'store/book_detail.html' + desired_book = get_object_or_404(Book,id=bid) + + count = BookCopy.objects.filter(Q(book=desired_book) & Q(status=True)).count() + issued = False + if request.user.is_authenticated: + user_book_copies = request.user.borrower.all() + for bc in user_book_copies: + if bc.book == desired_book: + issued = True + break + + + context = { - 'book': None, # set this to an instance of the required book - 'num_available': None, # set this to the number of copies of the book available, or 0 if the book isn't available + 'book': desired_book, # set this to an instance of the required book + 'num_available': count, + 'issued': issued # set this to the number of copies of the book available, or 0 if the book isn't available } # START YOUR CODE HERE @@ -25,11 +52,33 @@ def bookDetailView(request, bid): @csrf_exempt def bookListView(request): template_name = 'store/book_list.html' - context = { - 'books': None, # set this to the list of required books upon filtering using the GET parameters - # (i.e. the book search feature will also be implemented in this view) - } get_data = request.GET + print(get_data,"hello") + if(len(get_data)>0): + + required_books=Book.objects.filter(title__icontains=get_data['title'],author__icontains=get_data['author'],genre__icontains=get_data['genre']) + print(get_data.get('title'),get_data['title']) + else: + required_books=Book.objects.all() + + + context = { + 'books':required_books, # set this to the list of required books upon filtering using the GET parameters + } # (i.e. the book search feature will also be implemented in this view) + # (i.e. the book search feature will also be implemented in this view) + + + + + + + + + + # queryset = Book.objects.all() + # print(queryset,"hello") + + # START YOUR CODE HERE @@ -38,8 +87,10 @@ def bookListView(request): @login_required def viewLoanedBooks(request): template_name = 'store/loaned_books.html' + + issued_books = request.user.borrower.all() context = { - 'books': None, + 'books': issued_books, } ''' The above key 'books' in the context dictionary should contain a list of instances of the @@ -54,15 +105,32 @@ def viewLoanedBooks(request): @csrf_exempt @login_required def loanBookView(request): + book_id = request.POST.get('bid') + count = BookCopy.objects.filter(Q(book=Book.objects.get(id=book_id)) & Q(status=True)).count() + + + + if count>0: + book_copy = BookCopy.objects.filter(Q(book=Book.objects.get(id=book_id)) & Q(status=True))[0] + print(book_copy.id) + book_copy.status = False + book_copy.borrow_date = datetime.date.today() + book_copy.borrower = request.user + book_copy.save() + msg = 'success' + else: + msg = 'failure' + response_data = { - 'message': None, + 'message': msg, } + ''' Check if an instance of the asked book is available. If yes, then set the message to 'success', otherwise 'failure' ''' # START YOUR CODE HERE - book_id = None # get the book id from post data + # get the book id from post data return JsonResponse(response_data) @@ -77,6 +145,64 @@ def loanBookView(request): @csrf_exempt @login_required def returnBookView(request): - pass + + bookcopy_id = request.POST.get('bid') + bookcopy = get_object_or_404(BookCopy,id=bookcopy_id) + bookcopy.borrower = None + bookcopy.borrow_date = None + bookcopy.status = True + bookcopy.save() + print(request.POST) + msg = 'success' + + response_data={ + 'message': msg + } + + return JsonResponse(response_data) + +def rateBook(request,bid): + if request.method == 'POST': + form = forms.RatingForm(request.POST) + if form.is_valid(): + rating = form.cleaned_data.get('rating') + ratings_by_user =request.user.rating_by.all() + print(ratings_by_user,'hellllsaldlasldlasldl') + if ratings_by_user.count() > 0: + rating_object = ratings_by_user[0] + rating_object.rating = rating + rating_object.save() + else: + book_rating = BookRating.objects.create(book=Book.objects.get(id=bid), rating_by = request.user, rating = rating) + book=Book.objects.get(id=bid) + all_BookRating_objects = BookRating.objects.all() + total_ratings = all_BookRating_objects.count() + sum_of_ratings = 0 + for obj in all_BookRating_objects: + sum_of_ratings = sum_of_ratings + obj.rating + book.rating = sum_of_ratings/total_ratings + book.save() + + + str_url = '/book/' + str(bid) +'/' + return redirect(str_url) + else: + form = forms.RatingForm() + book_title = Book.objects.get(id=bid).title + context = { + 'form': form, + 'title': book_title + } + + return render(request,'store/rate.html',context) + + + + + + + + +