Thursday, February 21, 2019

How can I refresh admin_urls

In a 'Substance' app I have a Stripe payment facility working from within the Admin. The problem is it spoils admin_urls so the user cannot get back to the Substance Admin. When the Stripe work is done, how do I re-initialise the admin urls?

I want to display a success page with a link back to the substance page. I can provide exactly the correct url but all it shows is an admin page with no content. The correct url is /admin/substance/substance/<pk>/ and that works only if the Stripe mechanism has not been called since the dev server reloaded itself.

I probably have an incorrect diagnosis here. The problem is most likely my misunderstanding of how to use the Admin properly. Any help will be very much appreciated.

Thanks

Mike

The process uses ModelAdmin.change_view()[1] to call my billing_payment_view()[2] which uses PaymentForm[3] and payment.html[4] template to pass the necessary hidden vars.

When the view executes (depending on whether payment is required) it pops up a (Stripe js) credit card collection form with a [Pay] button and that submits the detail to the Stripe API which in turn generates a token if successful or an error message and re-executes the billing_payment_view with request.POST including that detail plus all the hidden fields in the form. The view goes on to process the detail, generate a receipt, send an email to the payer and launch the billing_success_view()[5] and success.html[6] template.

[1] SubstanceAdmin.change_view()  def change_view(self, request, object_id, form_url='', extra_context=None):      """      self = SubstanceAdmin      request = wsgi request object      object_id = substance      form_url = no idea!      extra_context = dict of apps, models, admin_urls and permissions        sm2mi stands for substance m2m ingredient. Ingredients are substances in      a many-to-many relationship with another substance (ie a mixture). The       through table is called Substance_Ingredients      """      ingredients = Substance_Ingredients.objects.filter(substance_id=object_id)      subscription = None      for sm2mi in ingredients:          payable, fee_type = sm2mi.fee_payable()  # eg., True, PAID_DATA          if payable:              subscription = billing_subscribe(sm2mi, fee_type)              if subscription:    # we need to collect money for the owner                  self.change_form_template = 'payment.html'                  context = billing_collect_context(                      sm2mi,                      subscription,                  )                  # get everything into the payment_view context                  if not extra_context:                      extra_context = dict()                  extra_context.update(self.admin_site.each_context(request))                  extra_context.update(context)                  self.admin_site.admin_view(                      # now call the Stripe mechanism                      billing_payment_view(                          request,                          sm2mi,                          subscription,                          context=extra_context,                      )                  )                  # only one sm2mi at a time                  break      return super(SubstanceAdmin, self).change_view(          request, object_id, form_url, extra_context      )      [2] billing_payment_view()  @ensure_csrf_cookie  def payment_view(request, sm2mi=None, subscription=None, context=None):      display_message = ''      display_insert = ''      email_message = ''      subscription_message = ''      subscription_insert = ''      charge = None      context = context or {}      templt = 'payment.html'      form = PaymentForm()      if request.method == "POST":          resp = dict(request.POST)          # data has been submitted to Stripe via Stripe js          # stripe submits the js form which comes back without identifying args          form = PaymentForm(request.POST)          if form.is_valid():              go_back = '/admin/substance/substance/'              try:                  token = form.cleaned_data['stripeToken']                  if token:                      email = form.cleaned_data['stripeEmail']                      sm2mi_id = form.cleaned_data['sm2mi_id']                      if sm2mi_id and sm2mi is None:                          sm2mi = Substance_Ingredients.objects.get(pk=int(sm2mi_id))                        subscription_id = form.cleaned_data['subscription_id']                      if subscription_id and subscription is None:                          subscription = Subscription.objects.get(pk=int(subscription_id))                        if sm2mi and subscription:                          ingredient = sm2mi.ingredient                          cents = subscription.get_cents()                          fee_total = subscription.calc_fee(total=True)  # gst in                          charge = stripe.Charge.create(                              amount=int(float(fee_total) * cents),                              currency=subscription.fee_currency,                              description='{0}'.format(subscription.ingredient.name),                              source=token,                          )                      if charge:                          go_back = '/admin/substance/substance/%s/' % sm2mi.substance.id                          subscription.token = token     # see above                          try:                              with transaction.atomic():                                  subscription.save()                          except Exception:                              raise                          # transaction is done now make a receipt                          amount_paid = charge['amount']                          if cents > 1:  # cents may be zero eg', JPY                              amount_paid = round(charge['amount'] / cents, 2)                          receipt = Receipt.objects.create(                              licensee=sm2mi.substance.division.company,                              ingredient=ingredient,                              currency=subscription.fee_currency,                              amount_paid=amount_paid,                              charge=charge,                          )                          receipt.save()  # defaults need all methods to execute                          # make a received message for display and emailing                          para = '<p>'                          endpara = '</p>'                          strong = '<strong>'                          endstrong = '</strong>'                          display_message = '<p>Thank you for your {0} {1}{2} '\                          'payment to {3} for <strong>{4}</strong></p>'.format(                              subscription.fee_currency,                              subscription.fee_symbol,                              amount_paid,                              settings.BRANDING,                              ingredient.name.capitalize(),                          )                          display_insert = '<p>A receipt will be emailed to you '\                          'shortly</p>'                          subscription_message = '<p>You are now '\                          'subscribed to {0} until 30 September. You may include '\                          'it in as many mixtures as desired.</p>'.format(                              ingredient.name.capitalize(),                          )                          subscription_insert = '<p>If you have difficulty '\                          'accessing <a href="{0}">{1}</a> or you have any '\                          'questions please get in touch via {2}. Easy questions '\                          'might have an answer on the <a href="https://{3}/user-'\                          'docs/sharing/">sharing page</a>.</p>'.format(                              ingredient.get_absolute_url(),                              ingredient.name.capitalize(),                              settings.MANAGERS[0][1],                              settings.WEBSITE,                          )                            email_message = '{0}{1}{2}'.format(                              display_message,                              '\n',                              subscription_message,                          ).replace(                              para, ''                          ).replace(                              endpara, '\n'                          ).replace(                              strong, '*'                          ).replace(                              endstrong, '*'                          )                            display_message = '{0}{1}{2}{3}'.format(                              display_message,                              display_insert,                              subscription_message,                              subscription_insert,                          )                          from_address = get_admin_email(full=True)                          to_address = [charge['source']['name']]                          subject = 'Subscription receipt for {0}'.format(                              ingredient.name.capitalize()                          )                            try:                              send_mail(                                  subject,                                  email_message,                                  from_address,                                  to_address,                                  fail_silently=False,                              )                          except Exception as err:                              print('\n315 billing.views %s' % err)                              pass                            context['sm2mi'] = sm2mi                          context['receipt'] = receipt                          context['subscription'] = subscription                          context['message'] = mark_safe(display_message)                          #context['link'] = sm2mi.substance.get_substance_url()                          context['link'] = go_back                          return success_view(request, context)                      else:                          raise Exception('Gateway error')              except Exception as err:                  err = '%s' % err                  if 'cannot use a Stripe token' in err:                      backto = ''                      if sm2mi:                          backto = '<p><a href="{0}">Back to {1}</a>'\                          '</p>'.format(                              go_back,                              sm2mi.substance.name.capitalize()                          )                      display_message = '<p>Cannot process the same payment '\                      'twice.</p><p>If an emailed receipt is not received at the '\                      'address just specified in the payment popup please get in '\                      'touch with <em>{0}</em> to ascertain whether payment was '\                      'successful. Please copy and paste the token below as a '\                      'reference.</p><p>{1}</p><p>{2}<p>'.format(                          get_manager_email(),                          token or 'No token at this point (375)',                          backto                      )                  else:                      display_message = '{0}. Please report this error message to '\                      '{1}'.format(err, get_admin_email())                  context['message'] = mark_safe(display_message)                  # report failure to the card payer                  form.add_error(None, '%s' % err)              return success_view(request, context)      context['form'] = form      return render(          request=request,          template_name=templt,          context=context      )      [3] PaymentForm  class PaymentForm(forms.Form):      sm2mi_id = forms.CharField(widget=forms.HiddenInput, required=False)      ingredient_id = forms.CharField(widget=forms.HiddenInput, required=False)      subscription_id = forms.CharField(widget=forms.HiddenInput, required=False)        stripeToken = forms.CharField(widget=forms.HiddenInput, required=False)      stripeEmail = forms.CharField(widget=forms.HiddenInput, required=False)      stripeBillingName = forms.CharField(widget=forms.HiddenInput, required=False)      stripeBillingAddressLine1 = forms.CharField(widget=forms.HiddenInput, required=False)      stripeBillingAddressZip = forms.CharField(widget=forms.HiddenInput, required=False)      stripeBillingAddressState = forms.CharField(widget=forms.HiddenInput, required=False)      stripeBillingAddressCity = forms.CharField(widget=forms.HiddenInput, required=False)      stripeBillingAddressCountry = forms.CharField(widget=forms.HiddenInput, required=False)        [4] payment.html  {% extends "admin/base_site.html" %}  {% load admin_urls %}  {% load i18n %}  {% load common_tags %}  {% block title %}{{ original }}{% endblock %}  {% block content %}  {% if subscription %}    <div class="payment">      <p>{{ prepay_message }}</p>      <p>Please proceed to payment.</p>      <blockquote><strong>      We do not store your payment detail here. Our PCI DSS compliant gateway      service provider interfaces your card directly with your bank. Links to      their terms and privacy policy will be visible when you click the <em>Pay      with Card</em> button. See also our own privacy policy (link above).      </strong> &nbsp; (PCI DSS = Payment Card Industry Data Security Standard)      </blockquote>      <p>&nbsp;</p>      <form action="payment" method="POST">          {% csrf_token %}          {% if form.errors or form.non_field_errors %}              {{ form.errors }}              {{ form.non_field_errors }}          {% endif %}          {% for field in form %}              {% if field.name == "sm2mi_id" %}                  <input type="hidden" name="sm2mi_id" id="id_sm2mi_id" value="{{ sm2mi.id }}" />              {% elif field.name == "ingredient_id" %}                  <input type="hidden" name="ingredient_id" id="id_ingredient_id" value="{{ ingredient.id }}" />              {% elif field.name == "subscription_id" %}                  <input type="hidden" name="subscription_id" id="id_subscription_id" value="{{ subscription.id }}" />              {% else %}                  {{ field }}              {% endif %}          {% endfor %}          <script            src="https://checkout.stripe.com/checkout.js" class="stripe-button"            data-key="{{ data_key }}"            data-amount="{{ data_amount }}"            data-name="{{ data_name }}"            data-description="{{ data_description }}"            data-image="{{ data_image }}"            data-locale="{{ data_locale }}"            data-currency="{{ data_currency | lower}}"            data-zip-code="true"            data-allow-remember-me="true"            data-panel-label="Pay">          </script>      </form>    </div>  {% endif %}  {{ block.super }}  {% endblock %}          [5] billing_success_view  def success_view(request, context=None):      """ launched after a Stripe token is received by PaymentForm        context is passed in by payment_view and has context['message']      """      if context is None:          context = dict()          context['message'] = 'Sorry - payment details cannot be reproduced'        if 'Thank you for your' in context['message']:          pass        return render(          request=request,          template_name='success.html',          context=context,      )      [6] success.html  {% extends "admin/base_site.html" %}  {% load admin_urls %}  {% load i18n %}  {% load common_tags %}  {% block title %}{{ original }}{% endblock %}  {% block content %}    <div id="container">      <div id="content" class="colM">      {% if message %}{{ message }}{% endif %}      {% if link %}          <p>Please do not refresh this page but rather click the 'Home' link above or          <a href="{{ link }}">go back to {{ sm2mi.substance.name }}</a></p>      {% endif %}      </div>    </div>  {{ block.super }}  {% endblock %}    


Python 3.6, Django 1.11.20, dev server on Windows 10.

Haven't been *able* to get it working yet on Ubuntu 16.04 with Python 2.7 and haven't tried it yet on Python 2.7 on the dev server.

Getting Python 3.x running with Apache2 on Ubuntu is non-trivial due to co-existence with Trac (2.7 only) which also requires my SVN to cohabit on the same machine.



No comments:

Post a Comment