Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file removed .DS_Store
Binary file not shown.
8 changes: 4 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ jobs:

- name: Run Tests
env:
SECRET_KEY: "j+(#!sag4%^ay+oanu&t-&3x@2$!+s%x4u!4%pser4o9)2!ua1"
ENVIRONMENT: "local"
STRIPE_SECRET_KEY: "fail"
STRIPE_PUBLIC_KEY: "fail"
SECRET_KEY: ${{ secrets.SECRET_KEY }}
ENVIRONMENT: ${{ secrets.ENVIRONMENT }}
STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET_KEY }}
STRIPE_PUBLIC_KEY: ${{ secrets.STRIPE_PUBLIC_KEY }}
timeout-minutes: 5
run: poetry run python manage.py test
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ local_settings.py
db.sqlite3
db.sqlite3-journal
media
.DS_Store

# If your build process includes running collectstatic, then you probably don't need or want to include staticfiles/
# in your Git repository. Update and uncomment the following line accordingly.
Expand Down
3 changes: 0 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@ RUN adduser \
--disabled-password \
--gecos "" \
--home "/nonexistent" \
--shell "/sbin/nologin" \
--no-create-home \
--uid "${UID}" \
appuser

RUN pip install --no-cache-dir poetry
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ git clone https://github.com/dmytrik/LibraryServiceAPI.git
cd LibraryServiceAPI
```

### 2. 📄 Create a `.env` File
### 2. 📄 Environment
Create a `.env` file based on the `.env.sample` file. You can do this by copying the `.env.sample` to `.env`:

```bash
Expand Down
4 changes: 1 addition & 3 deletions book/management/commands/wait_for_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ def handle(self, *args, **options):
db_conn.cursor()
except OperationalError:
self.stdout.write(
self.style.WARNING(
"waiting for connection with database..."
)
self.style.WARNING("waiting for connection with database...")
)
time.sleep(1)
1 change: 1 addition & 0 deletions book/tests/test_book_model.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from django.test import TestCase

from book.models import Book


Expand Down
1 change: 1 addition & 0 deletions book/tests/test_book_serializer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from rest_framework.exceptions import ValidationError
from rest_framework.test import APITestCase

from book.models import Book
from book.serializers import BookSerializer

Expand Down
11 changes: 3 additions & 8 deletions book/tests/test_book_view_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from django.core.cache import cache
from rest_framework import status
from rest_framework.test import APITestCase

from book.models import Book

User = get_user_model()
Expand All @@ -18,12 +19,10 @@ def setUp(self):
daily_fee=10.00,
)

# Create a superuser using the custom user model
self.superuser = User.objects.create_superuser(
email="admin@example.com", password="admin123"
)

# Create a regular user using the custom user model
self.user = User.objects.create_user(
email="user@example.com", password="user123"
)
Expand Down Expand Up @@ -79,9 +78,7 @@ def test_update_book_as_superuser(self):
"cover": self.book.cover,
"daily_fee": self.book.daily_fee,
}
response = self.client.put(
f"/api/books/{self.book.id}/", data, format="json"
)
response = self.client.put(f"/api/books/{self.book.id}/", data, format="json")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.book.refresh_from_db()
self.assertEqual(self.book.inventory, 10)
Expand All @@ -96,9 +93,7 @@ def test_update_book_as_regular_user(self):
"cover": self.book.cover,
"daily_fee": self.book.daily_fee,
}
response = self.client.put(
f"/api/books/{self.book.id}/", data, format="json"
)
response = self.client.put(f"/api/books/{self.book.id}/", data, format="json")
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

def test_delete_book_as_superuser(self):
Expand Down
1 change: 1 addition & 0 deletions book/urls.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from rest_framework import routers

from book.views import BookViewSet

app_name = "book"
Expand Down
18 changes: 9 additions & 9 deletions borrowing/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,23 @@ class CustomFilter(filters.FilterSet):
is_active = filters.BooleanFilter(
method="filter_is_active", label="Active"
)
user_id = filters.NumberFilter(method="filter_user_id")
user_id = filters.NumberFilter(method="filter_by_user_id")

class Meta:
model = Borrowing
fields = ["is_active", "user_id"]

def filter_is_active(self, queryset, name, value):
if value:
def filter_is_active(self, queryset, value):
if value is True:
return queryset.filter(actual_return_date__isnull=True)
else:
elif value is False:
return queryset.filter(actual_return_date__isnull=False)
return queryset

def filter_user_id(self, queryset, name, value):
request = self.request
if request.user.is_staff:
def filter_by_user_id(self, queryset, value):
if self.request.user.is_staff:
if value:
return queryset.filter(user_id=value)
return queryset
else:
return queryset.filter(user=request.user)

return queryset.filter(user=self.request.user)
2 changes: 1 addition & 1 deletion borrowing/models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from django.db import models
from django.conf import settings

from book.models import Book
from django.conf import settings


class Borrowing(models.Model):
Expand Down
5 changes: 3 additions & 2 deletions borrowing/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ def validate(self, attrs):

if active_borrowings.exists():
raise serializers.ValidationError(
"You already have an active borrowing. "
"Please return the current book before borrowing another."
"It looks like you already have a book borrowed."
" Please return it before borrowing another."
" Thank you for your understanding!"
)
return attrs

Expand Down
3 changes: 2 additions & 1 deletion borrowing/signals.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from django.core.cache import cache
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from .models import Borrowing

from borrowing.models import Borrowing
from tg_bot.utils import send_telegram_notification


Expand Down
2 changes: 1 addition & 1 deletion borrowing/tasks.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import datetime

from celery import shared_task
from tg_bot.utils import send_telegram_notification

from tg_bot.utils import send_telegram_notification
from borrowing.models import Borrowing


Expand Down
7 changes: 4 additions & 3 deletions borrowing/tests/test_borrowing_api.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from datetime import timedelta

from rest_framework.test import APITestCase
from rest_framework import status
from django.urls import reverse
from django.core.cache import cache
from datetime import timedelta
from django.utils.timezone import now
from django.contrib.auth import get_user_model

Expand All @@ -17,8 +18,8 @@ def setUp(self):
self.user = User.objects.create_user(
email="test@user.com", password="password123"
)
self.staff_user = User.objects.create_user(
email="test@admin.com", password="adminpassword", is_staff=True
self.staff_user = User.objects.create_superuser(
email="test@admin.com", password="adminpassword"
)
self.book = Book.objects.create(
title="Test Book", author="Author", inventory=5, daily_fee=1
Expand Down
5 changes: 4 additions & 1 deletion borrowing/tests/test_borrowing_models.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
from datetime import timedelta

from django.test import TestCase
from django.contrib.auth import get_user_model
from django.utils.timezone import now
from datetime import timedelta

from borrowing.models import Borrowing, Book


User = get_user_model()


Expand Down
3 changes: 2 additions & 1 deletion borrowing/tests/test_borrowing_serializers.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from datetime import timedelta
from unittest.mock import MagicMock

from rest_framework.test import APITestCase
from rest_framework.exceptions import ValidationError
from datetime import timedelta
from django.utils.timezone import now
from django.contrib.auth import get_user_model

Expand All @@ -15,6 +15,7 @@
BorrowingDetailSerializer,
)


User = get_user_model()


Expand Down
1 change: 1 addition & 0 deletions borrowing/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from borrowing.views import BorrowingViewSet


router = routers.DefaultRouter()
router.register("", BorrowingViewSet, basename="borrowings")

Expand Down
41 changes: 23 additions & 18 deletions borrowing/views.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import datetime

from django.utils import timezone
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page
from django.http import HttpResponseRedirect
Expand Down Expand Up @@ -38,7 +39,10 @@ class BorrowingViewSet(
filterset_class = CustomFilter

def get_queryset(self):
queryset = Borrowing.objects.select_related("book", "user")
queryset = (
Borrowing.objects.select_related("book", "user").
prefetch_related("payments")
)
if self.request.user.is_staff:
return queryset.order_by("actual_return_date")
return queryset.filter(user=self.request.user).order_by(
Expand All @@ -58,30 +62,31 @@ def get_serializer_class(self):
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
book = serializer.validated_data["book"]
with transaction.atomic():
book = serializer.validated_data["book"]

expected_return_date = serializer.validated_data[
"expected_return_date"
]
expected_return_date = serializer.validated_data[
"expected_return_date"
]

if datetime.date.today() > expected_return_date:
raise ValidationError("No valid expected return date")
if datetime.date.today() > expected_return_date:
raise ValidationError("No valid expected return date")

if book.inventory <= 0:
raise ValidationError("No copies available in inventory.")
if book.inventory <= 0:
raise ValidationError("No copies available in inventory.")

book.inventory -= 1
book.save()
book.inventory -= 1
book.save()

borrowing = serializer.save(user=self.request.user)
borrowing = serializer.save(user=self.request.user)

create_stripe_session(borrowing, request)
create_stripe_session(borrowing, request)

payment = Payment.objects.get(borrowing=borrowing)
payment = Payment.objects.get(borrowing=borrowing)

return HttpResponseRedirect(
payment.session_url, status=status.HTTP_302_FOUND
)
return HttpResponseRedirect(
payment.session_url, status=status.HTTP_302_FOUND
)

@action(
methods=["POST"],
Expand All @@ -101,7 +106,7 @@ def return_book(self, request, pk=None):
"This borrowing has already been returned."
)

borrowing.actual_return_date = datetime.date.today()
borrowing.actual_return_date = timezone.now().date()
borrowing.save()

book = borrowing.book
Expand Down
3 changes: 2 additions & 1 deletion core/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import absolute_import, unicode_literals

from .celery import app as celery_app
from core.celery import app as celery_app


__all__ = ("celery_app",)
1 change: 1 addition & 0 deletions core/asgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from django.core.asgi import get_asgi_application


os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings")

application = get_asgi_application()
9 changes: 2 additions & 7 deletions core/celery.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
from __future__ import absolute_import, unicode_literals
import os

from celery import Celery

# set the default Django settings module for the 'celery' program.

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings")

app = Celery("core")

# Using a string here means the worker doesn't have to serialize
# the configuration object to child processes.
# - namespace='CELERY' means all celery-related configuration keys
# should have a `CELERY_` prefix.
app.config_from_object("django.conf:settings", namespace="CELERY")

# Load task modules from all registered Django app configs.
app.autodiscover_tasks()


@app.task(bind=True)
def debug_task(self):
print("Request: {0!r}".format(self.request))
1 change: 1 addition & 0 deletions core/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
)
from debug_toolbar.toolbar import debug_toolbar_urls


urlpatterns = [
path("admin/", admin.site.urls),
path("api/users/", include("user.urls", namespace="user")),
Expand Down
1 change: 1 addition & 0 deletions core/wsgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from django.core.wsgi import get_wsgi_application


os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings")

application = get_wsgi_application()
1 change: 1 addition & 0 deletions payment/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@

from payment.models import Payment


admin.site.register(Payment)
2 changes: 1 addition & 1 deletion payment/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def calculate_money_to_pay(borrowing: Borrowing):
borrowing.actual_return_date - borrowing.expected_return_date
).days

count_of_money = overdue_days * borrowing.book.daily_fee * 2
count_of_money = overdue_days * borrowing.book.daily_fee * OVERDUE_COEFFICIENT
result = (count_of_money, "FINE")
return result

Expand Down
Loading
Loading