#get_object() method – what is alternative?

112 messages · Page 1 of 1 (latest)

indigo belfry
#

Hello, everyone.
I acutely need a help with my @action functionality.
So, going in details.

  1. I have particularly 2 models in my project – Borrowing and Payment (payment for borrowing)
  2. I have Stripe gateway payment system, implemented in my project.
  3. Now I want to implement logic of refreshing Stripe session url (in case it was expired).
    So, I created the following action inside PaymentViewSet:
@action(
    methods=["POST"],
    detail=True,
    url_path="update_session_url",
)
def recreate_expired_borrowing_session_url(self, request, pk=None):
    borrowing = self.get_object()
    payment = Payment.objects.get(borrowing=borrowing, type="Payment")
    if payment.status == "Expired":
        new_session_for_borrowing = create_stripe_session(
            borrowing=borrowing, request=self.request
        )
        payment.status = "Pending"
        payment.session_id = new_session_for_borrowing.id
        payment.session_url = new_session_for_borrowing.url
        payment.save()
        return Response({"status": "Session url has been updated"})

    return Response({"status": "Session url is still active"})
#

My current issue is that from very beginning I can’t get Borrowing object using get_object() method
My general assumption as of now is that I can do it moving such action to BorrowingViewSet (I really evidenced that).
But point is that I want to keep this action in PaymentViewSet. And in this case I don’t understand how can I get Borrowing object within PaymentViewSet in other ways than using get_object() method.
Please help me with my issue.
PS. Getting Borrowing object is crucial because based on it I can receive Payment object and do subsequent action with it.

fierce bloom
#

get_object is just a method on the viewset which other methods call. There is nothing inherently special about it. By this I simply mean you could do the query directly as required in your case.
ie borrowing = Borrowing.objects.get(pk=pk)

indigo belfry
#

I just wanna to say that even in case I follow your suggestion, code also outputs an error

#

payment.models.Payment.DoesNotExist: Payment matching query does not exist.

#

I mean - having Borrowing object, I can't even get Payment object by filtration

#
payment = Payment.objects.get(borrowing=borrowing, type="Payment")
#

but point is that within my test function I initially create a Borrowing object and subsequently tie it to Payment object

indigo belfry
# fierce bloom `get_object` is just a method on the viewset which other methods call. There is ...
def test_recreate_expired_borrowing_session_url(self):
    # Manually create a new Stripe session for testing purposes
    borrowing = sample_borrowing(user=self.user)
    price_data = stripe.Price.create(
        unit_amount=1200,
        currency="usd",
        product_data={
            "name": f"Payment for borrowing of {self.book_1.title}",
        },
    )
    session = stripe.checkout.Session.create(
        line_items=[
            {
                "price": price_data.id,
                "quantity": 1,
            }
        ],
        mode="payment",
        success_url="http://localhost:8000/success/",
        cancel_url="http://localhost:8000/cancel/",

    )

    payment = sample_payment(
        borrowing=borrowing,
        session_url=session.url,
        session_id=session.id,
        money_to_pay=self.borrowing_1.book.daily_fee
    )
    payment.status = "EX"
    url = reverse('payment:payment-recreate-expired-borrowing-session-url', args=[borrowing.id])
    request = self.factory.post(url)
    request.user = self.user
    new_session = create_stripe_session(borrowing=borrowing, request=request)

    response = self.client.post(url)
    payment.session_id = new_session.id
    payment.session_url = new_session.url

    payment.refresh_from_db()

    # self.assertEqual(response.status_code, status.HTTP_200_OK)
    # self.assertEqual(payment.session_id, new_session.id)
    # self.assertEqual(payment.session_url, new_session.url)
    self.assertEqual(payment.status, "PN")

    self.assertEqual(response.data['status'], "Session url has been updated")
#

so, it showes that something is wrong maybe on initial step - getting Borrowing object.

indigo belfry
#

well, I mean let's say so

#
  1. In test function everything is correct - I can find Payment by linked Borrowing
#
  1. But inside view - not
fierce bloom
#

Can you share the whole view set?

indigo belfry
indigo belfry
# fierce bloom Can you share the whole view set?
class PaymentViewSet(
    generics.ListAPIView,
    generics.RetrieveAPIView,
    generics.CreateAPIView,
    viewsets.GenericViewSet
):
    queryset = Payment.objects.all()
    serializer_class = PaymentSerializer
    # permission_classes = (IsAuthenticated,)

    def get_serializer_class(self):
        if self.action == "list":
            return PaymentListSerializer
        if self.action == "retrieve":
            return PaymentDetailSerializer
        return PaymentSerializer

    def get_queryset(self):
        if self.request.user.is_superuser:
            return super().get_queryset()
        print("QUERYSET-T-T-T-T-T-T-T-T", super().get_queryset())
        return super().get_queryset().filter(borrowing__user=self.request.user)

    @action(
        methods=["GET"],
        detail=False,
        url_path="payment_success",
        url_name="success",
    )
    def payment_success(self, request: Request):
        session_id = request.query_params.get("session_id")
        payment = Payment.objects.get(session_id=session_id)

        session = stripe.checkout.Session.retrieve(session_id)
        if session.payment_status == "PD":
            serializer = PaymentSerializer(
                payment, data={"status": "PD"}, partial=True
            )
            serializer.is_valid(raise_exception=True)
            serializer.save()
            return Response(serializer.data, status=status.HTTP_200_OK)
        return Response(status=status.HTTP_400_BAD_REQUEST)
#

second part

#
 @action(
        methods=["GET"],
        detail=False,
        url_path="payment_cancel",
        url_name="cancel",
    )
    def payment_cancel(self, request: Request):
        session_id = request.query_params.get("session_id")
        payment = Payment.objects.get(session_id=session_id)
        serializer = PaymentSerializer(payment)
        data = {
            "message": "You can make a payment during the next 2 hours.",
            **serializer.data,
        }
        return Response(data=data, status=status.HTTP_200_OK)

    @action(
        methods=["POST"],
        detail=True,
        url_path="update_session_url",
    )
    def recreate_expired_borrowing_session_url(self, request, pk=None):
        borrowing = Borrowing.objects.get(pk=pk)
        if payment.status == "Expired":
            new_session_for_borrowing = create_stripe_session(
                borrowing=borrowing, request=self.request
            )
            payment.status = "Pending"
            payment.session_id = new_session_for_borrowing.id
            payment.session_url = new_session_for_borrowing.url
            payment.save()
            return Response({"status": "Session url has been updated"})

        return Response({"status": "Session url is still active"})
#

here is the entire code ofg view

#

but I talk specifically about last action - recreate session url

raw trench
#

It's a detail URL for a payment object. It needs the Payment's PK in the URL, not the Borrowing's PK

indigo belfry
#

I can filter Payment objects by related Borrowing objects

#

Nothing wrong with that

#

If I am not mistaken

raw trench
#

This is a PaymentViewSet. The PK in the URL for detail views is expected to be for a Payment object.

indigo belfry
#

In other way how can I get related object?

#

I mean the entire process

raw trench
#

Do you have a PK for a Payment object? If not, what URL are you calling for this action?

indigo belfry
raw trench
#

No I can't

indigo belfry
#

Sounds very strange because it oblivious

raw trench
#

I'm asking where you're getting the PK for that URL from

indigo belfry
#

Api/payments/pk/...

indigo belfry
#

It's action, it implies having pk

raw trench
#

But you're taking that PK and expecting it to be a Borrowing object. It's attached to a Payment view set? So it's expected to be a Payment object.

indigo belfry
#

Got it...

indigo belfry
#

Not having pk ...

#

You mean just as related object?

#

Payment.borrowings.all()

#

But pk for borrowing is unreachable to me

raw trench
#

I don't understand what this endpoint is meant to do, and why it's on a Payment view set

#

If it's an action to apply to a Borrowing object then it should be on the Borrowing view set

indigo belfry
#
  1. Endpoint is for purpose - to create new Stripe session because previous is expired
#

As payment process

#
  1. If you run through all actions within PaymentViewSet you can see that all endpoints related to payment process is pulled in one viewset
#

Recreate session endpoint is related to payment process as well

raw trench
#

Then give it a payment PK, why do you need the Borrowing object?

indigo belfry
raw trench
#

But isn't that just a FK from Payment?

#

It's very hard to reason about API endpoints when you don't know what's calling it or from what context

indigo belfry
#

Context is

#

To provide the entire payment process

#
  1. Payment successful - url, on which customer will be redirected in case of successful payment
#
  1. Payment cancel - endpoint on which customer will be redirected in case of payment failure
#
  1. Recreate session url endpoint - endpoint on which customer will be redirected in case os payment session expires
#

It's oblivious Ben

raw trench
#

I think you mean "obvious", and it's obviously not obvious.

indigo belfry
#

Yes

raw trench
#

Customers shouldn't be redirected to API endpoints? I presume there's some JS going on here

indigo belfry
#

It's just mistake

raw trench
#

Does your front end know a primary key for the Payment object

indigo belfry
#

Yes

raw trench
#

Then use that.

indigo belfry
#

It's presumed

raw trench
#

Get the Payment object.

#

Get the borrowing object from the payment object.

indigo belfry
#

How get?

raw trench
raw trench
indigo belfry
#

Get_object is related to Payment object

raw trench
#

Yes.

indigo belfry
#

Not to Borrowing

raw trench
#

Which I said.

#

Get the payment object.

#

Then get the borrowing object from the payment object.

#

they have a foreign key relationship

indigo belfry
#

Why we so long time discuss that oblivious thing?

#

I am just puzzled

raw trench
#

"obvious"

indigo belfry
#

Sorry, obvious

indigo belfry
#

I mean - PaymentViewSet, BorrowingViewSet

raw trench
#

Well you told me it was obvious it should be in the payment view set 🤷‍♂️

indigo belfry
#

But occasionally you said it could be in Borrowing View Set

#

You have some pros to that?

raw trench
#

You were adamant you needed the Borrowing object, so it sounded like it was an action being applied to the Borrowing object 🤷‍♂️

indigo belfry
#

Ok, I see.

indigo belfry
#

I hope it will work out

raw trench
#

But all of this "where should it be" is just speculation without knowing the full data model design and how the front end uses it

indigo belfry
#

There are books, you can borrow those and give back till expected return date

#

Also payment consists of 2 parts

#
  1. Main part - payment for borrowing itself
#
  1. Fine for overdue period
#

Payments are processed by Stripe gateway as the most effective payment handler

#

Comparing to other payments systems even PayPal and liqpay

#

Payment system is a peak of this project

#

The most difficult part

indigo belfry
#

Unfortunately I tried to get Payment object using both approaches.

#

And I want to say - neither using self.get_object(), nor using pk - didn't give results at all

#

Using both methods doesn't allow to get Payment object inside PaymentViewSet

#

Eventhough it must but for some reason doesn't

#

only approach which is really works as of now is using - Payment.objects.all()

#

and then just using this - queryset.first()