2
\$\begingroup\$

I have the following function that is called via signal. 1) check if incluencer exists. 2) Increase discount by +1. Currently, I hit the database twice. Is there a better way to write that? Additionally, I wonder I can check 2) all on a database level so I don't need the for-loop.

@receiver(signal=charge_succeeded)
@transaction.atomic
def create_influencer_transaction(sender, order, charge, **kwargs):
 order_exists = Transaction.objects.filter(order=order).exists()
 if order_exists:
 return None
 # 1) Check if influencer exists
 first_order_item = order.order_items.first()
 influencer = first_order_item.social_referral
 if not influencer:
 return None
 total_points_earned = order.order_items.aggregate(
 total=Sum(
 F('unit_points_earned') * F('quantity'),
 output_field=IntegerField()
 )
 )['total'] or 0
 Transaction.objects.create(
 [...]
 )
 # 2) Redeemed amount + 1
 for order_item in order.order_items.all():
 social_discount = order_item.social_discount
 if social_discount:
 social_discount.redeemed_amount = F('redeemed_amount') + 1
 social_discount.save(update_fields=['redeemed_amount'])
 break

models.py

class OrderItem(AbstractItem, AbstractDiscountItem, AbstractSocialItem):
 order = models.ForeignKey(
 [...]
 )
 social_discount = models.ForeignKey(
 'discounts.SocialDiscount',
 on_delete=models.PROTECT,
 related_name='order_items',
 null=True,
 blank=True,
 ) # PROTECT = don't allow to delete the discount if an order_item exists
class SocialDiscount(AbstractDiscount):
 event = models.OneToOneField(
 [...]
 ) # CASCADE = delete the discount if the event is deleted
 tickets = models.ManyToManyField(
 [...]
 )
asked Feb 22, 2019 at 12:13
\$\endgroup\$
3
  • \$\begingroup\$ @MathiasEttinger yes of course. I added the models.py schema. It's handled as ForeignKey. Many order_items can have the same socialdiscount \$\endgroup\$ Commented Feb 22, 2019 at 12:40
  • \$\begingroup\$ Thanks for that, just one last thing, am I safe assuming redeemed_amount is an IntegerField? \$\endgroup\$ Commented Feb 22, 2019 at 13:04
  • \$\begingroup\$ Yes, that's correct. \$\endgroup\$ Commented Feb 22, 2019 at 13:05

1 Answer 1

2
\$\begingroup\$

Your step 2 can be done entirely in DB by filtering and limiting your order_items. And then updating it to contain the desired value. Note that the F objects documentation already mention that use of a queryset update method. So we could imagine something like:

order.order_items.filter(social_discount__isnull=False)[:1].update(social_discount__redeemed_amount=F('social_discount__redeemed_amount') + 1)

Except this doesn't work:

  1. we cannot use update after slicing a queryset;
  2. we cannot update fields of related models anyway.

Instead we must use the relationship backwards and filter/update on the SocialDiscount model, the filter being the subset of OrderItems we are interested in:

order_item = order.order_items.filter(social_discount__isnull=False)[:1]
# Note that order_item is still a queryset and has not hit the database yet
SocialDiscount.objects.filter(order_items__in=order_item).update(redeemed_amount=F('redeemed_amount') + 1)

Even though this only hit the database once, this will update all SocialDiscount objects that are related to the one order_item specified by the queryset above. If it ever can mean that more than one object is updated and you don't want that, you can still remove the for loop using this two-queries code:

social_discount = order.order_items.filter(social_discount__isnull=False).select_related('social_discount').first().social_discount
social_discount.redeemed_amount = F('redeemed_amount') + 1
social_discount.save()
answered Feb 22, 2019 at 13:56
\$\endgroup\$
2
  • \$\begingroup\$ Thank you so much. One more question for my understanding. SocialDiscount.objects.filter(order_items > Will filter then go into the model where my order_items are saved and look for the SocialDiscount used in that specific order_item? \$\endgroup\$ Commented Feb 22, 2019 at 15:24
  • \$\begingroup\$ @JoeyCoder See update. \$\endgroup\$ Commented Feb 22, 2019 at 15:43

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.