Django: Refactoring the "right" way - python

My DetailView currently gets longer and longer. And therefore I am trying to learn more about some better practices to shorten it again. Right now, I am looking into the def post() part. The full code is even longer. My best idea currently is to create a utils.py in the same folder and take out the code snippets inside the if else statements. Is that best practice or would you do it in a different way?
class EventDetail(DetailView):
[...]
#transaction.atomic
def post(self, request, *args, **kwargs):
# Discount code POST
if self.discount_form.is_valid():
discount_code = self.discount_form.cleaned_data['code']
request.session[discount_cookie_name(request.event)] = discount_code
return redirect(reverse_obj(self.request.event, 'events:detail'))
# Consent form POST
elif self.consent_form.is_valid():
# Check if email entry exists
contact = request.event.organizer.contacts.filter(
email=self.consent_form.cleaned_data['email']
).first()
success_message = _("You successfully signed up to receive event updates.")
if contact:
# Check if current event is allocated to entered email.
event_entry = contact.contact_relation.filter(
event=request.event
).first()
if event_entry:
event_entry.lead = True
event_entry.save(update_fields=['lead'])
messages.success(request, success_message)
else:
# Email entry exists, but event allocation must be added.
ContactRelation.objects.create(
lead=True, contact=contact, event=request.event
)
messages.success(request, success_message)
# Create new email entry and allocate event.
else:
instance = self.consent_form.save(commit=False)
instance.organizer = request.event.organizer
instance.save()
instance.contact_relations.add(
request.event, through_defaults={'lead': True}
)
messages.success(request, success_message)
return redirect(reverse_obj(self.request.event, 'events:detail'))
elif self.formset.is_valid():
# Check if the user already has an order_reference in the session
old_order_reference = request.session.get('order_reference')
if old_order_reference is not None:
ReservedItem.objects.filter(
order_reference=old_order_reference
).delete()
# Generate a new order_reference and save it in the session
order_reference = unique_order_reference_generator()
request.session['order_reference'] = order_reference
for form in self.formset:
quantity = form.cleaned_data['quantity']
qty = 0 if not quantity else quantity
if qty > 0:
reserved_item = form.save(commit=False)
ticket = form.cleaned_data['ticket']
reserved_item.order_reference = order_reference
reserved_item.ticket_name = ticket.name
if ticket.tax:
reserved_item.ticket_tax = ticket.tax
reserved_item.tax_name = ticket.tax.name
reserved_item.tax_percentage = ticket.tax.percentage
# Prevent AttributeError
discount_obj = None
if self.discount_code:
discount_obj = self.discount_code
if self.social_ticketing_referral:
# That part is not inside generate_reserved_discount_items
# as the influencer is still getting points. Even, if the ticket
# has no discount.
reserved_item.social_ticketing_referral = (
self.social_ticketing_referral.ambassador
)
# Add points earned per ticket
reserved_item.social_ticketing_unit_points_earned = (
ticket.points.points
if self.social_ticketing.is_point_mode_manual()
else monetary_amount_to_points(
ticket.price_gross, request.event.currency
)
)
# Check if Social Ticketing has incentive discount (not talking point).
# That also means self.discount_code doesn't apply
if self.social_ticketing.is_incentive_discount():
discount_obj = self.social_ticketing.discount
reserved_item.discount_code = (
self.social_ticketing_referral.code
)
if discount_obj:
generate_reserved_discount_items(
reserved_item=reserved_item,
discount=discount_obj,
ticket=ticket,
quantity=quantity,
)
# Save reserved item
reserved_item.save()
return redirect('checkout:checkout', step='start')
return super().get(request, *args, **kwargs)

Related

How i can to prevent to create multiple instance while multi request in django?

Hi everyone i have project include about payment function between user and user. But i have a problem when many user buy a product.the many payment object will created more than product avaliable. How i can to solve this ?
products_avaliable = Product.objects.filter(on_sell=true)
for product in products_avaliable:
payment = Payment()
payment.buyer = ....
payment.seller = product.owner
payment.save()
product.on_sell = False
product.save()
when add deley
products_avaliable = Product.objects.filter(on_sell=true)
for product in products_avaliable:
payment = Payment()
payment.buyer = ....
payment.seller = product.owner
payment.save()
time.sleep(10) # simulate to slow server or many request
product.on_sell = False
product.save()
when i try to time.delay before payment create (simulate when server to slow or may request by user) the payment will create many object
You can check if the object is on_sell before committing to the DB:
for product in products_avaliable:
payment = Payment()
payment.buyer = ....
payment.seller = product.owner
product.on_sell = False
product.refresh_from_db() # update the object
if product.on_sell:
payment.save()
product.save()
else:
...

How to process webhook request coming from a 3rd party application?

I need help to evaluate weather i am doing it right or is there a better way, the scenario is an 3rd party application is sending an webhook request after a successful payment but the problem is that sometimes this application may send the same notification more than once.so it is recommended to ensure that implementation of the webhook is idempotent.so steps that i am implementing for this are
if signature is correct (assume it is corect),Find orders record in the database using orderId in the request params.
Please note: orderId in request params is payment_gateway_order_identifier in orders table.
if txStatus = 'SUCCESS' AND haven't already processed COLLECTION payment for this same order,
Create payments record.
201 response with nothing in the response body.
else
201 response with nothing in the response body.
else
422 response with {message: "Signature is incorrect"} in response body
views.py
#api_view(['POST'])
def cashfree_request(request):
if request.method == 'POST':
data=request.POST.dict()
payment_gateway_order_identifier= data['orderId']
amount = data['orderAmount']
transaction_status = data['txStatus']
signature = data['signature']
if(computedsignature==signature): #assume it to be true
order=Orders.objects.get(
payment_gateway_order_identifier=payment_gateway_order_identifier)
if transaction_status=='SUCCESS':
try:
payment= Payments.objects.get(orders=order)
return Response({"Payment":"Done"},status=status.HTTP_200_OK)
except (Payments.DoesNotExist):
payment = Payments(orders=order,amount=amount,datetime=datetime)
payment.save()
return Response(status=status.HTTP_200_OK)
else:
return Response(status=status.HTTP_422_UNPROCESSABLE_ENTITY)
models.py
class Orders(models.Model):
id= models.AutoField(primary_key=True)
amount = models.DecimalField(max_digits=19, decimal_places=4)
payment_gateway_order_identifier = models.UUIDField(
primary_key=False,default=uuid.uuid4,editable=False,unique=True)
sussessfull
class Payments(models.Model):
id = models.AutoField(primary_key=True)
orders = models.ForeignKey(Orders, on_delete=models.CASCADE)
amount = models.DecimalField(max_digits=19, decimal_places=4, verbose_name='Price in INR')
datetime = models.DateTimeField(auto_now=False,auto_now_add=False)
This rather belongs to Codereview site. Anyway - you are doing up to 3 consecutive SQL queries, so there's a chance for a race condition. A simple way how to prevent that: use some KV storage like Redis/Memcache as a lock - save the value you're using as a nonce on the start of the function, and delete it on the end.
#api_view(['POST'])
def cashfree_request(request):
data = request.POST.dict()
payment_gateway_order_identifier = data['orderId']
# `nx` will set & return only if the key does not exist
# set a timeout in case it wont reach `delete()` on the end
if not redis.set("lock_%s" % payment_gateway_order_identifier, "1", nx=True, ex=2):
return Response(status=status.HTTP_409_CONFLICT)
amount = data['orderAmount']
transaction_status = data['txStatus']
signature = data['signature']
if computedsignature == signature:
order = Orders.objects.get(payment_gateway_order_identifier=payment_gateway_order_identifier)
if transaction_status == 'SUCCESS':
try:
Payments.objects.get(orders=order)
res = Response({"Payment": "Done"}, status=status.HTTP_200_OK)
except Payments.DoesNotExist:
payment = Payments(orders=order, amount=amount, datetime=datetime)
payment.save()
res = Response(status=status.HTTP_200_OK)
else:
res = Response(status=status.HTTP_422_UNPROCESSABLE_ENTITY)
# unlock for another request
redis.delete("lock_%s" % payment_gateway_order_identifier)
return res
you dont need if request.method == 'POST': since the code is accessible via POST only anyway, that will make your code less indented.
notice you dont handle the case where transaction_status is not SUCCESS

How to separate subscription and common checkout webhook in stripe?

I have made a webhook (flask) for stripe.
My app has both one time payment and subscription.
I would like to know how can I detect if it's normal checkout or subscription?
This is how I am separating now, but I think it's not recommended way.
I am checking stripe_subscription_id and if exist, I considered it as subscription.
(charge.succeeded is called for subscription succeeded also)
#api.route('/stripe/webhook', methods=['GET', 'POST'])
def webhook():
event = None
payload = request.data
try:
event = json.loads(payload)
except Exception as e:
print('Webhook error while parsing basic request.' + str(e))
if not event:
return jsonify({'status': 'failed'}), 400
# one time payout
if event['type'] == 'charge.succeeded':
event_intent = event['data']['object']
payment_intent = event_intent['payment_intent']
# payment
if event['type'] == 'checkout.session.completed':
checkout_session = event['data']['object']
stripe_subscription_id = checkout_session['subscription']
# customer_email = checkout_session['customer_email']
if stripe_subscription_id:
# Create a new subscription and mark it as paid this month.
customer_subscription_mark_paid(checkout_session['id'], stripe_subscription_id)
elif event['type'] == 'invoice.paid':
invoice = event['data']['object']
stripe_subscription_id = invoice['subscription']
if stripe_subscription_id:
# Check if this is the first invoice or a later invoice in the
# subscription lifecycle.
first_invoice = invoice['billing_reason'] == 'subscription_create'
# You already handle marking the first invoice as paid in the
# `checkout.session.completed` handler.
# Only use this for the 2nd invoice and later, so it doesn't conflict.
if not first_invoice:
# Mark the subscription as paid.
customer_subscription_mark_paid(None, stripe_subscription_id)
elif event['type'] == 'invoice.payment_failed':
invoice = event['data']['object']
stripe_subscription_id = invoice['subscription']
if stripe_subscription_id:
customer_subscription_mark_past_due(None, stripe_subscription_id)
Checking to see if the subscription field is filled is fine, but to be extra sure you can check the contents of the mode field.

using django cleaned_data to reset data before commit to db

I started this thread and managed to figure out the issue yesterday.
However, another related issue has occurred.
When I have a 2nd select list in the model, and I select '8888' or '9999' to remove / reset the values when the form is submitted, how can I set the value of the 2nd select list to the default value?
Here is an example of the models.py code I have:
.....
DISPLAY_ONLY_AWARD_AND_GRANT_DESCRIPTION_WITH_PROMPT = 8888
DISPLAY_ONLY_AWARD_AND_GRANT_DESCRIPTION_WITHOUT_PROMPT = 9999
.....
AWARD_GRANT_TYPES = (
(SELECT_AWARD_AND_GRANT_TYPE, _('Select Type')),
....
(OTHER_GRANT, _('Other Grant')),
(WRITE_MY_OWN_AWARD_AND_GRANT_TYPE_DESCRIPTION, _('Write my own Type description')), #7777
(DISPLAY_ONLY_AWARD_AND_GRANT_DESCRIPTION_WITH_PROMPT, _('Display only Description with prompt')), #8888
(DISPLAY_ONLY_AWARD_AND_GRANT_DESCRIPTION_WITHOUT_PROMPT, _('Display only Description without prompt')) #9999
)
....
AWARD_GRANT_DISPLAY_COMMENCEMENT_DATE_AND_COMPLETION_DATE_AS_DATE_PROMPTS = 1
....
AWARD_GRANT_DETAILS_DATE_DISPLAY_TYPES = (
(AWARD_GRANT_DISPLAY_COMMENCEMENT_DATE_AND_COMPLETION_DATE_AS_DATE_PROMPTS, _('Display Commencement and Completion as date prompts')),
....
)
user = models.ForeignKey(User)
language_version = models.ForeignKey('LanguageVersion')
award_grant_type = models.PositiveIntegerField(choices=AWARD_GRANT_TYPES, default=SELECT_AWARD_AND_GRANT_TYPE, validators=[MinValueValidator(1)])
award_grant_type_description = models.CharField(null=True, blank=True, max_length=250)
award_grant_date = models.DateField(null=True, blank=True)
award_grant_date_display_type = models.PositiveIntegerField(choices=AWARD_GRANT_DETAILS_DATE_DISPLAY_TYPES, default=AWARD_GRANT_DISPLAY_COMMENCEMENT_DATE_AND_COMPLETION_DATE_AS_DATE_PROMPTS)
award_grant_description = models.TextField(null=False, blank=False, max_length=5000)
Here is my forms.py clean code that should reset the award_grant_date_display_type to 1 when the user has selected 8888 or 9999 from the select list award_grant_type before being committed to the db:
def clean(self):
cd_agdf = super(AwardGrantDetailsForm, self).clean()
if 'award_grant_type' in cd_agdf:
if cd_agdf['award_grant_type'] == '':
self._errors['award_grant_type'] = self.error_class([_("You must select a Type.")])
elif cd_agdf['award_grant_type'] == '8888' or cd_agdf['award_grant_type'] == '9999':
# remove / reset the entered values when the award grant type only requires minimum data.
self.cleaned_data.pop('award_grant_type_description', None)
self.cleaned_data.pop('award_grant_date', None)
self.cleaned_data['award_grant_date_display_type'] = 1
else:
....
return cd_agdf
It appears that the line of code self.cleaned_data['award_grant_date_display_type'] = 1 is ignored and the original value retained.
I have tried placing the value in quotation marks, searched SO, Google and django docs, but I cannot figure this out.
EDIT
Here is my forms.py code as requested:
class AwardGrantDetailsForm(forms.ModelForm):
def __init__(self, available_languages, language_preference, *args, **kwargs):
"""
available_languages should be a valid choices list
"""
super(AwardGrantDetailsForm, self).__init__(*args, **kwargs)
self.fields['language_code'] = forms.ChoiceField(choices=available_languages, initial=language_preference, label=_('Language'),)
self.fields['award_grant_date'].input_formats = (settings.DATE_INPUT_FORMATS)
class Meta:
model = AwardGrantDetails
fields = (
'award_grant_type',
'award_grant_type_description',
'award_grant_date',
'award_grant_date_display_type',
'award_grant_description',
)
labels = {
'award_grant_type': _('Type'),
'award_grant_type_description': _('Type Description'),
'award_grant_date': _('Date'),
'award_grant_date_display_type': _('Date Prompts'),
'award_grant_description': _('Description'),
}
help_texts = {
'award_grant_description': _('5,000 character limit'),
}
input_formats = {
'award_grant_date': settings.DATE_INPUT_FORMATS,
}
widgets = {
#'award_grant_description': CKEditorWidget(),
'award_grant_date': forms.DateInput(attrs={'id': 'id_award_grant_date'}, format='%m/%Y'),
#'award_grant_date': forms.DateInput(attrs={'id': 'id_award_grant_date'}, format=settings.DATE_INPUT_FORMATS),
}
error_messages = {
'award_grant_type': {'validate_min': _('This field is required.')}, # validate_min used on award_grant_type in lieu of required field.
}
EDIT
Here is my views.py code as requested:
#login_required
#resume_menu_required(entry_number=settings.MENU_DETAIL_VALUE_AWARD_GRANT_DETAILS)
#complete_profile_required
def award_grant_details_edit(request, award_grant_details_id):
try:
award_grant_details = AwardGrantDetails.objects.get(pk=award_grant_details_id, user=request.user)
except AwardGrantDetails.DoesNotExist:
return redirect(settings.MENU_DETAIL_LINK_AWARD_GRANT_DETAILS)
language_versions = LanguageVersion.objects.filter(user=request.user).select_related('language_version')
available_languages = get_available_language_details(language_versions, request.user.userprofile.language_preference)
award_grant_details_num = request.user.awardgrantdetails_set.count()
language_code = award_grant_details.language_version.language_code
language_code_disabled = award_grant_details.language_version.language_code_disabled
preview_labels = get_award_grant_types(available_languages)
selected_resume_menu_entries = (request.user.userprofile.selected_resume_menu_entries)
if language_code_disabled:
return redirect(settings.MENU_DETAIL_LINK_AWARD_GRANT_DETAILS)
if request.method == 'GET':
language_code = award_grant_details.language_version.language_code
form = AwardGrantDetailsForm(
available_languages=available_languages,
language_preference=request.user.userprofile.language_preference,
initial=dict(model_to_dict(award_grant_details), language_code=language_code))
elif request.method == 'POST':
form = AwardGrantDetailsForm(
available_languages=available_languages,
language_preference=request.user.userprofile.language_preference,
data=request.POST)
if form.is_valid():
award_grant_details.fill(form.cleaned_data)
award_grant_details.save()
messages.success(request, _('successfully updated.'))
return redirect(settings.MENU_DETAIL_LINK_AWARD_GRANT_DETAILS)
return render(request,'resume_details/award_grant_details_edit.html',{
'address_details_count': get_address_details_count(request.user),
'award_grant_details': award_grant_details,
'award_grant_details_num': award_grant_details_num,
'contact_details_count': get_contact_details_count(request.user),
'display_default_language': display_default_language(request.user),
'form': form,
'language_versions': get_language_versions(user=request.user),
'language_versions_num': len(language_versions), # the count of all users Language Versions (enabled & disabled).
'languages': LANGUAGES,
'max_award_grant_details': settings.MAX_AWARD_GRANT_DETAILS,
'max_details_count': settings.MAX_AWARD_GRANT_DETAILS,
'name_details_count': get_name_details_count(request.user),
'preview_labels': preview_labels,
'resume_details_menu_link': settings.MENU_DETAIL_LINK_AWARD_GRANT_DETAILS,
'resume_details_menu_num': settings.MENU_DETAIL_VALUE_AWARD_GRANT_DETAILS,
'selected_resume_menu_entries': selected_resume_menu_entries,
})
I never discovered the answer to this issue.
However, a workaround is that on form submit when 8888 or 9999 is selected and there are no errors, I manually set the value to 1 using jquery:
$('#id_of_element_type').val('1');
I hope that this will assist someone.

Django calling a view from another view

So I have a view that displays some data based on the Person that is searched from the home page:
def film_chart_view(request):
if 'q' in request.GET and request.GET['q']:
q = request.GET['q']
# grab the first person on the list
try:
person_search = Person.objects.filter(short = q)[0]
filminfo = filmInfo(person_search.film_set.all())
film_graph_data = person_search.film_set.all().order_by('date')
#Step 1: Create a DataPool
return render_to_response('home/search_results.html',{'query': q, 'high': filminfo[0],
'graph_data': film_graph_data}, RequestContext(request))
except IndexError:
return render_to_response('home/not_found.html',{'query': q}, RequestContext(request))
On the homepage I also want to have a random button that displays some data from a random person on the database and display it with the above view. So far I have this view:
def random_person(request):
# 1282302 is max number of people currently
get_random = random.randint(1,1282302)
get_person = Person.objects.get(pk=get_random)
person_name = get_person.full
but I'm not sure how to complete it so it redirects to the film_chart_view.
You can redirect from random view appropriate url to the specified view as
def random_person(request):
# 1282302 is max number of people currently
get_random = random.randint(1,1282302)
get_person = Person.objects.get(pk=get_random)
person_name = get_person.full
return HttpResponseRedirect(reverse('film_chart_view')+"?q="+get_person.short)

Categories