#custom validation method failure (not functioning)

21 messages · Page 1 of 1 (latest)

jagged silo
#

Hello, everyone.
I am facing a pitfall with calling custom validation function within serializer.
Here is the full scrutiny:

  1. I have 2 linked models in my project – Borrowing and Payment. And I have certain business logic – in case there are still payments with pending status (unpaid payments) user can’t create new Borrowing in a system.
  2. For that purpose, I have custom validation method within my Borrowing model:
class Borrowing(models.Model):

    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"
    )

    @staticmethod
    def validate_pending_payment(user, error_to_raise):
        borrowings = user.borrowings.all()
        pending_payments = []

        for borrowing in borrowings:
            pending_payments.extend(borrowing.payments.filter(status="PN"))

        if pending_payments:
            raise error_to_raise("You have not yet completed your paying. "
                                 "Please complete it before borrowing a new book.")
#
  1. Subsequently I call this custom validation method inside validate() method within a serializer:
class BorrowingCreateSerializer(serializers.ModelSerializer):
    class Meta:
        model = Borrowing
        fields = ("id", "borrow_date", "expected_return_date", "book")

    def validate(self, attrs):
        data = super().validate(attrs)
        user = self.context["request"].user
        Borrowing.validate_inventory(
            attrs["book"],
            ValidationError
        )
        Borrowing.validate_pending_payment(user, ValidationError)
        return data
  1. Finally, I test this functionality like this:
def test_borrowing_create_not_allowed_if_previous_not_payed(self):
    Payment.objects.create(
        status="PN",
        type="P",
        borrowing=self.borrowing_1,
        session_url="http:/example_1.com",
        session_id="fjnvdkjfnvkve",
        money_to_pay=10.0,
    )

    book = self.book_1
    payload = {
        "expected_return_date": datetime(2024, 9, 30).date(),
        "book": book.id,
        "user": self.user.id
    }

    response = self.client.post(BORROWING_URL, payload)

    self.assertEquals(response.status_code, status.HTTP_400_BAD_REQUEST)
    self.assertEquals(
        response.data["non_field_errors"][0],
        "You have not yet completed your paying. Please complete it before borrowing a new book.",
    )
#

Unfortunately, validate() method within serializer is not called for some reason (not understandable for me).
But I have done everything depended on me:

  • doublechecked that user has pending (unpaid) payments.
  • even tried to move custom validation method directly inside serializer.
    Unfortunately failure stays and so far root cause of it is not obvious to me at all.
    What is root problem of such failure and how can I detect and resolve it?
    Thank you in advance for helping me.
wintry path
#

What is the content of the response returning in your test?

jagged silo
#
{'id': 8, 'borrow_date': '2024-06-09', 'expected_return_date': '2024-09-30', 'actual_return_date': None, 'book': 5, 'user': 4}
#

Genuinely Borrowing object has been created

#

but it is undesired scenario

#

because I expected ValidationError to be raised without such creation to happen

wintry path
#

What does you view look like?

jagged silo
# wintry path What does you view look like?
class BorrowingViewSet(
    viewsets.ModelViewSet 
):
    queryset = Borrowing.objects.all()
    serializer_class = BorrowingSerializer
    permission_classes = (IsAuthenticated,)

    def perform_create(self, serializer):
        serializer.save(user=self.request.user)

    @staticmethod
    def _params_to_ints(qs):
        """Converts a list of string IDs to a list of integers"""
        return [int(str_id) for str_id in qs.split(",")]

    def get_queryset(self):
        if self.action in ("list", "retrieve") and self.request.user.is_staff:
            return super().get_queryset()
        return super().get_queryset().filter(user_id=self.request.user.id)

        """Filtering by user and is active borrowing"""
        user = self.request.query_params.get("user")
        is_active = self.request.query_params.get("is_active")
        if is_active:
            self.queryset = self.queryset.filter(
                actual_return_date__isnull=True
            )
        if self.request.user.is_staff:
            """Filtering for admin users"""
            if user:
                user_id = self._params_to_ints(user)
                self.queryset = queryset.filter(user_id__in=user_id)
     
        return self.queryset

    def get_serializer_class(self):
        if self.action == "list":
            return BorrowingListSerializer
        if self.action == "retrieve":
            return BorrowingDetailSerializer
        return self.serializer_class

    

#

it is without action - return-borrowing

#

not sure it is relevant to me issue

#

to show that action

wintry path
#

also this method would be more efficient as:

    @staticmethod
    def validate_pending_payment(user, error_to_raise):
        pending_borrowings = user.borrowings.filter(payments__status="PN")

        if pending_borrowings:
            raise error_to_raise("You have not yet completed your paying. "
                                 "Please complete it before borrowing a new book.")
jagged silo
#

for filtration purposes

#

but even this didn't raise ValidatioError as well

wintry path
wintry path
jagged silo