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
4 changes: 3 additions & 1 deletion .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ POSTGRES_PASSWORD=
POSTGRES_USER=
POSTGRES_DB=
POSTGRES_HOST=
POSTGRES_PORT=
POSTGRES_PORT=
STRIPE_SECRET_KEY=
STRIPE_PUBLIC_KEY=
2 changes: 2 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,7 @@ jobs:
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"
timeout-minutes: 5
run: poetry run python manage.py test
5 changes: 1 addition & 4 deletions book/permissions.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
from rest_framework.permissions import (
SAFE_METHODS,
BasePermission
)
from rest_framework.permissions import SAFE_METHODS, BasePermission


class IsAdminOrReadOnly(BasePermission):
Expand Down
15 changes: 9 additions & 6 deletions book/tests/test_book_view_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

User = get_user_model()


class BookViewSetTest(APITestCase):
def setUp(self):
"""Set up test data before running tests"""
Expand All @@ -18,14 +19,12 @@ def setUp(self):

# Create a superuser using the custom user model
self.superuser = User.objects.create_superuser(
email="admin@example.com",
password="admin123"
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"
email="user@example.com", password="user123"
)

def test_list_books_unauthenticated(self):
Expand Down Expand Up @@ -76,7 +75,9 @@ 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 @@ -91,7 +92,9 @@ 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
4 changes: 3 additions & 1 deletion book/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@ class BookViewSet(viewsets.ModelViewSet):

queryset = Book.objects.prefetch_related("borrowings")
serializer_class = BookSerializer
permission_classes = [IsAdminOrReadOnly,]
permission_classes = [
IsAdminOrReadOnly,
]
7 changes: 5 additions & 2 deletions borrowing/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ class CustomFilter(filters.FilterSet):
"""
FilterSet for CustomFilter
"""
is_active = filters.BooleanFilter(method="filter_is_active", label="Active")

is_active = filters.BooleanFilter(
method="filter_is_active", label="Active"
)
user_id = filters.NumberFilter(method="filter_user_id")

class Meta:
Expand All @@ -27,4 +30,4 @@ def filter_user_id(self, queryset, name, value):
return queryset.filter(user_id=value)
return queryset
else:
return queryset.filter(user=request.user)
return queryset.filter(user=request.user)
33 changes: 25 additions & 8 deletions borrowing/migrations/0001_initial.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,39 @@


class Migration(migrations.Migration):

initial = True

dependencies = [
('book', '0001_initial'),
("book", "0001_initial"),
]

operations = [
migrations.CreateModel(
name='Borrowing',
name="Borrowing",
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('borrow_date', models.DateField(auto_now_add=True)),
('expected_return_date', models.DateField()),
('actual_return_date', models.DateField(blank=True, null=True)),
('book', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='borrowings', to='book.book')),
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("borrow_date", models.DateField(auto_now_add=True)),
("expected_return_date", models.DateField()),
(
"actual_return_date",
models.DateField(blank=True, null=True),
),
(
"book",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="borrowings",
to="book.book",
),
),
],
),
]
13 changes: 8 additions & 5 deletions borrowing/migrations/0002_initial.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,21 @@


class Migration(migrations.Migration):

initial = True

dependencies = [
('borrowing', '0001_initial'),
("borrowing", "0001_initial"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.AddField(
model_name='borrowing',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='borrowings', to=settings.AUTH_USER_MODEL),
model_name="borrowing",
name="user",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="borrowings",
to=settings.AUTH_USER_MODEL,
),
),
]
11 changes: 9 additions & 2 deletions borrowing/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,18 @@ class Borrowing(models.Model):
Borrowing model with attributes:
borrow_date, expected_return_date, actual_return_date, book, user
"""

borrow_date = models.DateField(auto_now_add=True)
expected_return_date = models.DateField()
actual_return_date = models.DateField(null=True, blank=True)
book = models.ForeignKey(Book, on_delete=models.CASCADE, related_name="borrowings")
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="borrowings")
book = models.ForeignKey(
Book, on_delete=models.CASCADE, related_name="borrowings"
)
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="borrowings",
)

def __str__(self):
return str(self.borrow_date)
33 changes: 23 additions & 10 deletions borrowing/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,26 @@
from book.models import Book
from book.serializers import BookSerializer
from borrowing.models import Borrowing
from payment.models import Payment


class BorrowingSerializer(serializers.ModelSerializer):
"""
Borrowing Serializer with validation for only one active borrowing.
"""

book = serializers.SlugRelatedField(
queryset=Book.objects.all(),
slug_field="title"
queryset=Book.objects.all(), slug_field="title"
)

class Meta:
model = Borrowing
fields = [
"id",
"expected_return_date",
"book"
]
fields = ["id", "expected_return_date", "book"]

def validate(self, attrs):
user = self.context["request"].user
active_borrowings = Borrowing.objects.filter(
user=user,
actual_return_date__isnull=True
user=user, actual_return_date__isnull=True
)

if active_borrowings.exists():
Expand All @@ -39,22 +35,37 @@ def validate(self, attrs):

class BorrowingReturnBookSerializer(serializers.ModelSerializer):
"""
Borrowing Serializer for returning a borrowing book.
Borrowing Serializer for returning a borrowing book.
"""

return_book = serializers.BooleanField()

class Meta:
model = Borrowing
fields = ["return_book"]


class PaymentInfoSerializer(serializers.ModelSerializer):
class Meta:
model = Payment
fields = (
"id",
"status",
"type",
"money_to_pay",
)
read_only_fields = fields


class BorrowingListSerializer(serializers.ModelSerializer):
"""
Borrowing Serializer for borrowing list.
"""

book = serializers.SlugRelatedField(
many=False, read_only=True, slug_field="title"
)
payments = PaymentInfoSerializer(many=True, read_only=True)

class Meta:
model = Borrowing
Expand All @@ -64,6 +75,7 @@ class Meta:
"expected_return_date",
"actual_return_date",
"book",
"payments",
]

def to_representation(self, instance):
Expand All @@ -77,6 +89,7 @@ class BorrowingDetailSerializer(serializers.ModelSerializer):
"""
Borrowing Serializer for detail of a borrowing book.
"""

book = BookSerializer(many=False, read_only=True)

class Meta:
Expand Down
29 changes: 13 additions & 16 deletions borrowing/tests/test_borrowing_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,20 @@

User = get_user_model()


class BorrowingViewSetTest(APITestCase):
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.book = Book.objects.create(title="Test Book", author="Author", inventory=5, daily_fee=1)
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.book = Book.objects.create(
title="Test Book", author="Author", inventory=5, daily_fee=1
)
self.client.force_authenticate(user=self.user)

def test_create_borrowing(self):
"""Test creating a borrowing record."""
data = {
"expected_return_date": (now().date() + timedelta(days=7)),
"book": self.book.title,
}
url = reverse("borrowing:borrowings-list")
response = self.client.post(url, data, format="json")
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(Borrowing.objects.count(), 1)
self.assertEqual(Borrowing.objects.first().user, self.user)

def test_create_borrowing_no_inventory(self):
"""Test creating a borrowing record when no copies are available."""
self.book.inventory = 0
Expand Down Expand Up @@ -96,7 +91,9 @@ def test_return_book_already_returned(self):
)
response = self.client.post(f"/api/borrowings/{borrowing.id}/return/")
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(response.data[0], "This borrowing has already been returned.")
self.assertEqual(
response.data[0], "This borrowing has already been returned."
)

def test_permission_denied_for_non_authenticated_user(self):
"""Test that non-authenticated users are denied access."""
Expand Down
13 changes: 6 additions & 7 deletions borrowing/tests/test_borrowing_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,14 @@

User = get_user_model()


class BorrowingModelTest(TestCase):
def setUp(self):
self.user = User.objects.create_user(
email="test@user.com",
password="password123"
email="test@user.com", password="password123"
)
self.book = Book.objects.create(
title="Test Book",
author="Test Author",
inventory=1,
daily_fee=1
title="Test Book", author="Test Author", inventory=1, daily_fee=1
)
self.expected_return_date = now().date() + timedelta(days=7)
self.borrowing = Borrowing.objects.create(
Expand All @@ -31,7 +28,9 @@ def test_borrowing_creation(self):
borrowing = Borrowing.objects.first()
self.assertEqual(borrowing.user, self.user)
self.assertEqual(borrowing.book, self.book)
self.assertEqual(borrowing.expected_return_date, self.expected_return_date)
self.assertEqual(
borrowing.expected_return_date, self.expected_return_date
)
self.assertIsNone(borrowing.actual_return_date)
self.assertEqual(borrowing.borrow_date, now().date())

Expand Down
Loading
Loading