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 modified .DS_Store
Binary file not shown.
3 changes: 3 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ jobs:
- name: Run Ruff
run: poetry run ruff check --output-format=github .

- name: make
run: poetry run ruff check --output-format=github .

- name: Run Tests
env:
SECRET_KEY: "j+(#!sag4%^ay+oanu&t-&3x@2$!+s%x4u!4%pser4o9)2!ua1"
Expand Down
10 changes: 5 additions & 5 deletions borrowing/tests/test_borrowing_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def test_create_borrowing(self):
"expected_return_date": (now().date() + timedelta(days=7)),
"book": self.book.title,
}
url = reverse("borrowings:borrowings-list")
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)
Expand All @@ -38,7 +38,7 @@ def test_create_borrowing_no_inventory(self):
"expected_return_date": (now().date() + timedelta(days=7)),
"book": self.book.title,
}
url = reverse("borrowings:borrowings-list")
url = reverse("borrowing:borrowings-list")
response = self.client.post(url, data, format="json")

self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
Expand All @@ -50,7 +50,7 @@ def test_list_borrowings_for_user(self):
book=self.book,
user=self.user,
)
url = reverse("borrowings:borrowings-list")
url = reverse("borrowing:borrowings-list")
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data), 1)
Expand All @@ -64,7 +64,7 @@ def test_list_borrowings_for_staff(self):
book=self.book,
user=self.user,
)
url = reverse("borrowings:borrowings-list")
url = reverse("borrowing:borrowings-list")
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data), 1)
Expand Down Expand Up @@ -101,6 +101,6 @@ def test_return_book_already_returned(self):
def test_permission_denied_for_non_authenticated_user(self):
"""Test that non-authenticated users are denied access."""
self.client.logout()
url = reverse("borrowings:borrowings-list")
url = reverse("borrowing:borrowings-list")
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
3 changes: 1 addition & 2 deletions borrowing/urls.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
from django.urls import path, include
from rest_framework import routers

from borrowing.views import BorrowingViewSet

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

urlpatterns = [path("", include(router.urls))]
urlpatterns = router.urls

app_name = "borrowing"
6 changes: 3 additions & 3 deletions borrowing/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ class BorrowingViewSet(
Viewset for borrowing related objects.
Provides actions: list, create, retrieve.
"""
queryset = Borrowing.objects.all()
permission_classes = [IsAuthenticated]
filter_backends = [DjangoFilterBackend]
filterset_class = CustomFilter

def get_queryset(self):
queryset = Borrowing.objects.select_related("book", "user")
if self.request.user.is_staff:
return Borrowing.objects.all().order_by("actual_return_date")
return Borrowing.objects.filter(user=self.request.user).order_by("actual_return_date")
return queryset.order_by("actual_return_date")
return queryset.filter(user=self.request.user).order_by("actual_return_date")

def get_serializer_class(self):
if self.action == "list":
Expand Down
9 changes: 7 additions & 2 deletions core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"rest_framework_simplejwt",
"drf_spectacular",
"django_filters",
"debug_toolbar",

# custom apps
"book",
Expand All @@ -59,6 +60,7 @@

MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"debug_toolbar.middleware.DebugToolbarMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
Expand Down Expand Up @@ -118,8 +120,8 @@
}

SIMPLE_JWT = {
"ACCESS_TOKEN_LIFETIME": timedelta(hours=3),
"REFRESH_TOKEN_LIFETIME": timedelta(days=1),
"ACCESS_TOKEN_LIFETIME": timedelta(days=10),
"REFRESH_TOKEN_LIFETIME": timedelta(days=30),
"ROTATE_REFRESH_TOKENS": False,
"AUTH_HEADER_NAME": "HTTP_AUTHORIZE",
}
Expand Down Expand Up @@ -149,6 +151,9 @@
},
]

INTERNAL_IPS = [
"127.0.0.1",
]

# Internationalization
# https://docs.djangoproject.com/en/5.1/topics/i18n/
Expand Down
6 changes: 4 additions & 2 deletions core/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@
SpectacularRedocView,
SpectacularSwaggerView
)
from debug_toolbar.toolbar import debug_toolbar_urls

urlpatterns = [
path("admin/", admin.site.urls),
path("api/user/", include("user.urls", namespace="user")),
path("api/borrowings/", include("borrowing.urls", namespace="borrowings")),
path("api/borrowings/", include("borrowing.urls", namespace="borrowing")),
path("api/payments/", include("payment.urls", namespace="payment")),
path("api/schema/", SpectacularAPIView.as_view(), name="schema"),
path("api/schema/swagger-ui/", SpectacularSwaggerView.as_view(url_name="schema"), name="swagger-ui"),
path("api/schema/redoc/", SpectacularRedocView.as_view(url_name="schema"), name="redoc"),
path("api/books/", include("book.urls", namespace="book")),
]
] + debug_toolbar_urls()
5 changes: 4 additions & 1 deletion payment/admin.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
from django.contrib import admin

# Register your models here.
from payment.models import Payment

admin.site.register(Payment)
16 changes: 14 additions & 2 deletions payment/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,17 @@


class PaymentConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'payment'
"""
Configuration class for the Payment app.

This class sets the default auto field type for models in the Payment app
and specifies the app's name for Django to recognize it.

Attributes:
default_auto_field (str): Specifies the type of primary key to use by default
for models that do not explicitly define one.
name (str): The name of the app, used by Django to locate and reference it.
"""

default_auto_field = "django.db.models.BigAutoField"
name = "payment"
67 changes: 67 additions & 0 deletions payment/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Generated by Django 5.1.4 on 2025-01-03 12:42

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):
initial = True

dependencies = [
("borrowing", "0002_initial"),
]

operations = [
migrations.CreateModel(
name="Payment",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"status",
models.CharField(
choices=[("PENDING", "Pending"), ("PAID", "Paid")],
default="PENDING",
max_length=7,
),
),
(
"type",
models.CharField(
choices=[("PAYMENT", "Payment"), ("FINE", "Fine")],
default="PAYMENT",
max_length=7,
),
),
(
"session_url",
models.URLField(blank=True, max_length=500, null=True),
),
(
"session_id",
models.CharField(blank=True, max_length=255, null=True),
),
(
"money_to_pay",
models.DecimalField(decimal_places=2, max_digits=10),
),
(
"borrowing",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="borrowing.borrowing",
),
),
],
options={
"ordering": ["-borrowing__borrow_date"],
},
),
]
68 changes: 67 additions & 1 deletion payment/models.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,68 @@
from django.db import models

# Create your models here.
from borrowing.models import Borrowing


class Payment(models.Model):
"""
Represents a payment or fine associated with a borrowing transaction.

This model tracks the status and type of payments, linking them to specific
borrowings. Payments can be marked as pending or paid, and can either represent
a standard payment or a fine.

Attributes:
status (str): The current status of the payment (PENDING or PAID).
type (str): The type of payment (PAYMENT or FINE).
borrowing (Borrowing): The borrowing record this payment is associated with.
session_url (str, optional): URL for the payment session (e.g., Stripe session).
session_id (str, optional): Identifier for the payment session.
money_to_pay (Decimal): The amount to be paid or fined, with up to 10 digits
and 2 decimal places.

Meta:
ordering (list): Orders the payments by the borrowing date in descending order.

Methods:
__str__(): Returns a human-readable string representation of the payment,
showing the user's email, amount, and payment status.
"""

class Status(models.TextChoices):
"""
Enum-like class for defining possible statuses of a payment.

Attributes:
PENDING (str): Payment is pending.
PAID (str): Payment is completed.
"""

PENDING = ("PENDING",)
PAID = ("PAID",)

class Type(models.TextChoices):
"""
Enum-like class for defining possible types of payments.

Attributes:
PAYMENT (str): Standard payment for borrowing.
FINE (str): Fine for late return or damage.
"""

PAYMENT = ("PAYMENT",)
FINE = ("FINE",)

status = models.CharField(
max_length=7, choices=Status, default=Status.PENDING
)
type = models.CharField(max_length=7, choices=Type, default=Type.PAYMENT)
borrowing = models.ForeignKey(Borrowing, on_delete=models.CASCADE)
session_url = models.URLField(max_length=500, blank=True, null=True)
session_id = models.CharField(max_length=255, blank=True, null=True)
money_to_pay = models.DecimalField(max_digits=10, decimal_places=2)

class Meta:
ordering = ["-borrowing__borrow_date"]

def __str__(self):
return f"{self.borrowing.user.email} - {self.money_to_pay} USD - {self.status}"
75 changes: 75 additions & 0 deletions payment/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
from rest_framework import serializers

from payment.models import Payment
from borrowing.serializers import (
BorrowingListSerializer,
BorrowingDetailSerializer,
)


class PaymentSerializer(serializers.ModelSerializer):
"""
Serializer for the Payment model.

This serializer handles the basic serialization and deserialization
of Payment instances, allowing for easy conversion between complex
Payment model instances and native Python datatypes (e.g., JSON).

Meta:
model (Payment): The model that this serializer is based on.
fields (tuple): The fields to include in the serialized output.
- id (int): Unique identifier for the payment.
- status (str): Current status of the payment (PENDING or PAID).
- type (str): The type of payment (PAYMENT or FINE).
- borrowing (int): The ID of the related borrowing record.
- session_url (str): URL to the payment session.
- session_id (str): Payment session identifier.
- money_to_pay (Decimal): Amount to be paid.
read_only_fields (tuple): Fields that cannot be modified through this serializer.
- id: The primary key is read-only and automatically generated.
"""

class Meta:
model = Payment
fields = (
"id",
"status",
"type",
"borrowing",
"session_url",
"session_id",
"money_to_pay",
)
read_only_fields = ("id",)


class PaymentListSerializer(PaymentSerializer):
"""
Serializer for listing Payment records.

This serializer extends PaymentSerializer by replacing the borrowing field
with a nested BorrowingListSerializer to provide additional details about
the borrowing record in list views.

Attributes:
borrowing (BorrowingListSerializer): Provides summarized borrowing data
instead of just the ID.
"""

borrowing = BorrowingListSerializer()


class PaymentDetailSerializer(PaymentListSerializer):
"""
Serializer for detailed view of a Payment record.

This serializer extends PaymentListSerializer by using BorrowingDetailSerializer
for the borrowing field, providing a more in-depth view of the related borrowing
record.

Attributes:
borrowing (BorrowingDetailSerializer): Provides detailed borrowing data
for a specific payment.
"""

borrowing = BorrowingDetailSerializer()
2 changes: 0 additions & 2 deletions payment/tests.py

This file was deleted.

Empty file added payment/tests/__init__.py
Empty file.
Loading
Loading