Merge branch 'master' into task/5681/offer-512mb-ram
This commit is contained in:
		
				commit
				
					
						42d6f38f0c
					
				
			
		
					 20 changed files with 1017 additions and 305 deletions
				
			
		|  | @ -1,3 +1,5 @@ | ||||||
|  | 2.3: 2018-10-08 | ||||||
|  |     * #5690: Generic payment page - allow admin to add a onetime/monthly product and the frontend for user to pay for this product (PR #666) | ||||||
| 2.2.2: 2018-09-28 | 2.2.2: 2018-09-28 | ||||||
|     * #5721: Set calculator OS list in alphabetical order and set `Devuan Ascii` as the default (PR #668) |     * #5721: Set calculator OS list in alphabetical order and set `Devuan Ascii` as the default (PR #668) | ||||||
|     * bugfix: Fix some typos and correct DE translations (PR #667) |     * bugfix: Fix some typos and correct DE translations (PR #667) | ||||||
|  |  | ||||||
|  | @ -9,6 +9,7 @@ from .cms_models import ( | ||||||
|     DCLSectionPromoPluginModel, DCLCalculatorPluginModel |     DCLSectionPromoPluginModel, DCLCalculatorPluginModel | ||||||
| ) | ) | ||||||
| from .models import VMTemplate | from .models import VMTemplate | ||||||
|  | from datacenterlight.utils import clear_all_session_vars | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @plugin_pool.register_plugin | @plugin_pool.register_plugin | ||||||
|  | @ -85,6 +86,7 @@ class DCLCalculatorPlugin(CMSPluginBase): | ||||||
|     require_parent = True |     require_parent = True | ||||||
| 
 | 
 | ||||||
|     def render(self, context, instance, placeholder): |     def render(self, context, instance, placeholder): | ||||||
|  |         clear_all_session_vars(context['request']) | ||||||
|         context = super(DCLCalculatorPlugin, self).render( |         context = super(DCLCalculatorPlugin, self).render( | ||||||
|             context, instance, placeholder |             context, instance, placeholder | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  | @ -8,7 +8,7 @@ msgid "" | ||||||
| msgstr "" | msgstr "" | ||||||
| "Project-Id-Version: PACKAGE VERSION\n" | "Project-Id-Version: PACKAGE VERSION\n" | ||||||
| "Report-Msgid-Bugs-To: \n" | "Report-Msgid-Bugs-To: \n" | ||||||
| "POT-Creation-Date: 2018-07-05 23:11+0000\n" | "POT-Creation-Date: 2018-09-26 20:44+0000\n" | ||||||
| "PO-Revision-Date: 2018-03-30 23:22+0000\n" | "PO-Revision-Date: 2018-03-30 23:22+0000\n" | ||||||
| "Last-Translator: b'Anonymous User <coder.purple+25@gmail.com>'\n" | "Last-Translator: b'Anonymous User <coder.purple+25@gmail.com>'\n" | ||||||
| "Language-Team: LANGUAGE <LL@li.org>\n" | "Language-Team: LANGUAGE <LL@li.org>\n" | ||||||
|  | @ -293,6 +293,9 @@ msgstr "Registrieren" | ||||||
| msgid "Billing Address" | msgid "Billing Address" | ||||||
| msgstr "Rechnungsadresse" | msgstr "Rechnungsadresse" | ||||||
| 
 | 
 | ||||||
|  | msgid "Make a payment" | ||||||
|  | msgstr "" | ||||||
|  | 
 | ||||||
| msgid "Your Order" | msgid "Your Order" | ||||||
| msgstr "Deine Bestellung" | msgstr "Deine Bestellung" | ||||||
| 
 | 
 | ||||||
|  | @ -336,9 +339,9 @@ msgid "" | ||||||
| "database." | "database." | ||||||
| msgstr "" | msgstr "" | ||||||
| "Bitte wähle eine der zuvor genutzten Kreditkarten oder gib Deine " | "Bitte wähle eine der zuvor genutzten Kreditkarten oder gib Deine " | ||||||
| "Kreditkartendetails unten an. Die Bezahlung wird über " | "Kreditkartendetails unten an. Die Bezahlung wird über <a href=\"https://" | ||||||
| "<a href=\"https://stripe.com\" target=\"_blank\">Stripe</a> abgewickelt. " | "stripe.com\" target=\"_blank\">Stripe</a> abgewickelt. Wir speichern Deine " | ||||||
| "Wir speichern Deine Kreditkartendetails nicht in unserer Datenbank." | "Kreditkartendetails nicht in unserer Datenbank." | ||||||
| 
 | 
 | ||||||
| msgid "" | msgid "" | ||||||
| "Please fill in your credit card information below. We are using <a href=" | "Please fill in your credit card information below. We are using <a href=" | ||||||
|  | @ -395,12 +398,35 @@ msgstr "Bestellungsübersicht" | ||||||
| msgid "Product" | msgid "Product" | ||||||
| msgstr "Produkt" | msgstr "Produkt" | ||||||
| 
 | 
 | ||||||
|  | msgid "Amount" | ||||||
|  | msgstr "" | ||||||
|  | 
 | ||||||
|  | msgid "Description" | ||||||
|  | msgstr "" | ||||||
|  | 
 | ||||||
|  | msgid "Recurring" | ||||||
|  | msgstr "" | ||||||
|  | 
 | ||||||
| msgid "Subtotal" | msgid "Subtotal" | ||||||
| msgstr "Zwischensumme" | msgstr "Zwischensumme" | ||||||
| 
 | 
 | ||||||
| msgid "VAT" | msgid "VAT" | ||||||
| msgstr "Mehrwertsteuer" | msgstr "Mehrwertsteuer" | ||||||
| 
 | 
 | ||||||
|  | msgid "" | ||||||
|  | "By clicking \"Place order\" this plan will charge your credit card account " | ||||||
|  | "with %(total_price)s CHF/month" | ||||||
|  | msgstr "" | ||||||
|  | "Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit " | ||||||
|  | "%(vm_total_price)s CHF pro Monat belastet" | ||||||
|  | 
 | ||||||
|  | msgid "" | ||||||
|  | "By clicking \"Place order\" this payment will charge your credit card " | ||||||
|  | "account with a one time amount of %(total_price)s CHF" | ||||||
|  | msgstr "" | ||||||
|  | "Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit " | ||||||
|  | "%(vm_total_price)s CHF pro Monat belastet" | ||||||
|  | 
 | ||||||
| #, python-format | #, python-format | ||||||
| msgid "" | msgid "" | ||||||
| "By clicking \"Place order\" this plan will charge your credit card account " | "By clicking \"Place order\" this plan will charge your credit card account " | ||||||
|  | @ -541,8 +567,33 @@ msgstr "" | ||||||
| 
 | 
 | ||||||
| #, python-brace-format | #, python-brace-format | ||||||
| msgid "An error occurred while associating the card. Details: {details}" | msgid "An error occurred while associating the card. Details: {details}" | ||||||
| msgstr "Beim Verbinden der Karte ist ein Fehler aufgetreten. Details: " | msgstr "" | ||||||
| "{details}" | "Beim Verbinden der Karte ist ein Fehler aufgetreten. Details: {details}" | ||||||
|  | 
 | ||||||
|  | msgid "Confirmation of your payment" | ||||||
|  | msgstr "" | ||||||
|  | 
 | ||||||
|  | msgid " This is a monthly recurring plan." | ||||||
|  | msgstr "" | ||||||
|  | 
 | ||||||
|  | #, python-brace-format | ||||||
|  | msgid "" | ||||||
|  | "Hi {name},\n" | ||||||
|  | "\n" | ||||||
|  | "thank you for your order!\n" | ||||||
|  | "We have just received a payment of CHF {amount:.2f} from you.{recurring}\n" | ||||||
|  | "\n" | ||||||
|  | "Cheers,\n" | ||||||
|  | "Your Data Center Light team" | ||||||
|  | msgstr "" | ||||||
|  | 
 | ||||||
|  | msgid "Thank you for the payment." | ||||||
|  | msgstr "Danke für Deine Bestellung." | ||||||
|  | 
 | ||||||
|  | msgid "" | ||||||
|  | "You will soon receive a confirmation email of the payment. You can always " | ||||||
|  | "contact us at info@ungleich.ch for any question that you may have." | ||||||
|  | msgstr "" | ||||||
| 
 | 
 | ||||||
| msgid "Thank you for the order." | msgid "Thank you for the order." | ||||||
| msgstr "Danke für Deine Bestellung." | msgstr "Danke für Deine Bestellung." | ||||||
|  |  | ||||||
|  | @ -180,3 +180,9 @@ footer .dcl-link-separator::before { | ||||||
|     margin-top: 5px; |     margin-top: 5px; | ||||||
|     margin-bottom: 5px; |     margin-bottom: 5px; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | .input-no-border { | ||||||
|  |   border: none !important; | ||||||
|  |   background: transparent !important; | ||||||
|  |   resize: none; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -67,6 +67,18 @@ | ||||||
|             </div> |             </div> | ||||||
|             <div class="dcl-payment-box"> |             <div class="dcl-payment-box"> | ||||||
|                 <div class="dcl-payment-section"> |                 <div class="dcl-payment-section"> | ||||||
|  |                     {% if generic_payment_form %} | ||||||
|  |                         <h3>{%trans "Make a payment" %}</h3> | ||||||
|  |                         <hr class="top-hr"> | ||||||
|  |                         <form role="form" id="generic-payment-form" method="post" action="" novalidate> | ||||||
|  |                             {% csrf_token %} | ||||||
|  |                             <input type="hidden" name="product" value="1" /> | ||||||
|  |                             {% for field in generic_payment_form %} | ||||||
|  |                             {% bootstrap_field field type='fields'%} | ||||||
|  |                             {% endfor %} | ||||||
|  |                             <p class="text-danger">{{generic_payment_form.non_field_errors|striptags}}</p> | ||||||
|  |                         </form> | ||||||
|  |                     {% else %} | ||||||
|                         <h3>{%trans "Your Order" %}</h3> |                         <h3>{%trans "Your Order" %}</h3> | ||||||
|                         <hr class="top-hr"> |                         <hr class="top-hr"> | ||||||
|                         <div class="dcl-payment-order"> |                         <div class="dcl-payment-order"> | ||||||
|  | @ -97,6 +109,7 @@ | ||||||
|                             </p> |                             </p> | ||||||
|                             {% endif %} |                             {% endif %} | ||||||
|                         </div> |                         </div> | ||||||
|  |                     {% endif %} | ||||||
|                 </div> |                 </div> | ||||||
|             </div> |             </div> | ||||||
|             <div class="dcl-payment-box"> |             <div class="dcl-payment-box"> | ||||||
|  |  | ||||||
|  | @ -47,6 +47,32 @@ | ||||||
|             <hr> |             <hr> | ||||||
|             <div> |             <div> | ||||||
|                 <h4>{% trans "Order summary" %}</h4> |                 <h4>{% trans "Order summary" %}</h4> | ||||||
|  |                     {% if generic_payment_details %} | ||||||
|  |                         <p> | ||||||
|  |                             <strong>{% trans "Product" %}:</strong>  | ||||||
|  |                             {{ generic_payment_details.product_name }} | ||||||
|  |                         </p> | ||||||
|  |                         <div class="row"> | ||||||
|  |                             <div class="col-sm-6"> | ||||||
|  |                                 <p> | ||||||
|  |                                     <span>{% trans "Amount" %}: </span> | ||||||
|  |                                     <strong class="pull-right">CHF {{generic_payment_details.amount|floatformat:2|intcomma}}</strong> | ||||||
|  |                                 </p> | ||||||
|  |                                 {% if generic_payment_details.description %} | ||||||
|  |                                     <p> | ||||||
|  |                                         <span>{% trans "Description" %}: </span> | ||||||
|  |                                         <strong class="pull-right">{{generic_payment_details.description}}</strong> | ||||||
|  |                                     </p> | ||||||
|  |                                 {% endif %} | ||||||
|  |                                 {% if generic_payment_details.recurring %} | ||||||
|  |                                     <p> | ||||||
|  |                                         <span>{% trans "Recurring" %}: </span> | ||||||
|  |                                         <strong class="pull-right">Yes</strong> | ||||||
|  |                                     </p> | ||||||
|  |                                 {% endif %} | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                     {% else %} | ||||||
|                         <p> |                         <p> | ||||||
|                             <strong>{% trans "Product" %}:</strong>  |                             <strong>{% trans "Product" %}:</strong>  | ||||||
|                             {{ request.session.template.name }} |                             {{ request.session.template.name }} | ||||||
|  | @ -102,6 +128,7 @@ | ||||||
|                                 </p> |                                 </p> | ||||||
|                             </div> |                             </div> | ||||||
|                         </div> |                         </div> | ||||||
|  |                     {% endif %} | ||||||
|             </div> |             </div> | ||||||
|             <hr class="thin-hr"> |             <hr class="thin-hr"> | ||||||
|         </div> |         </div> | ||||||
|  | @ -109,7 +136,15 @@ | ||||||
|             {% csrf_token %} |             {% csrf_token %} | ||||||
|             <div class="row"> |             <div class="row"> | ||||||
|                 <div class="col-sm-8"> |                 <div class="col-sm-8"> | ||||||
|  |                     {% if generic_payment_details %} | ||||||
|  |                         {% if generic_payment_details.recurring %} | ||||||
|  |                             <div class="dcl-place-order-text">{% blocktrans with total_price=generic_payment_details.amount|floatformat:2|intcomma %}By clicking "Place order" this plan will charge your credit card account with {{total_price}} CHF/month{% endblocktrans %}.</div> | ||||||
|  |                         {% else %} | ||||||
|  |                             <div class="dcl-place-order-text">{% blocktrans with total_price=generic_payment_details.amount|floatformat:2|intcomma %}By clicking "Place order" this payment will charge your credit card account with a one time amount of {{total_price}} CHF{% endblocktrans %}.</div> | ||||||
|  |                         {% endif %} | ||||||
|  |                     {% else %} | ||||||
|                         <div class="dcl-place-order-text">{% blocktrans with vm_total_price=vm.total_price|floatformat:2|intcomma %}By clicking "Place order" this plan will charge your credit card account with {{vm_total_price}} CHF/month{% endblocktrans %}.</div> |                         <div class="dcl-place-order-text">{% blocktrans with vm_total_price=vm.total_price|floatformat:2|intcomma %}By clicking "Place order" this plan will charge your credit card account with {{vm_total_price}} CHF/month{% endblocktrans %}.</div> | ||||||
|  |                     {% endif %} | ||||||
|                 </div> |                 </div> | ||||||
|                 <div class="col-sm-4 order-confirm-btn text-right"> |                 <div class="col-sm-4 order-confirm-btn text-right"> | ||||||
|                     <button class="btn choice-btn" id="btn-create-vm" data-toggle="modal" data-target="#createvm-modal"> |                     <button class="btn choice-btn" id="btn-create-vm" data-toggle="modal" data-target="#createvm-modal"> | ||||||
|  | @ -151,16 +186,5 @@ | ||||||
| <script type="text/javascript"> | <script type="text/javascript"> | ||||||
|     {% trans "Some problem encountered. Please try again later." as err_msg %} |     {% trans "Some problem encountered. Please try again later." as err_msg %} | ||||||
|     var create_vm_error_message = '{{err_msg|safe}}'; |     var create_vm_error_message = '{{err_msg|safe}}'; | ||||||
|     window.onload = function () { |  | ||||||
|         var locale_dates = document.getElementsByClassName("locale_date"); |  | ||||||
|         var formats = ['YYYY-MM-DD hh:mm a'] |  | ||||||
|         var i; |  | ||||||
|         for (i = 0; i < locale_dates.length; i++) { |  | ||||||
|             var oldDate = moment.utc(locale_dates[i].textContent, formats); |  | ||||||
|             var outputFormat = locale_dates[i].getAttribute('data-format') || oldDate._f; |  | ||||||
|             locale_dates[i].innerHTML = oldDate.local().format(outputFormat); |  | ||||||
|             locale_dates[i].className += ' done'; |  | ||||||
|         } |  | ||||||
|     }; |  | ||||||
| </script> | </script> | ||||||
| {%endblock%} | {%endblock%} | ||||||
|  | @ -89,8 +89,14 @@ def create_vm(billing_address_data, stripe_customer_id, specs, | ||||||
| 
 | 
 | ||||||
|     create_vm_task.delay(vm_template_id, user, specs, template, order.id) |     create_vm_task.delay(vm_template_id, user, specs, template, order.id) | ||||||
| 
 | 
 | ||||||
|  |     clear_all_session_vars(request) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def clear_all_session_vars(request): | ||||||
|  |     if request.session is not None: | ||||||
|         for session_var in ['specs', 'template', 'billing_address', |         for session_var in ['specs', 'template', 'billing_address', | ||||||
|                             'billing_address_data', 'card_id', |                             'billing_address_data', 'card_id', | ||||||
|                         'token', 'customer']: |                             'token', 'customer', 'generic_payment_type', | ||||||
|  |                             'generic_payment_details', 'product_id']: | ||||||
|             if session_var in request.session: |             if session_var in request.session: | ||||||
|                 del request.session[session_var] |                 del request.session[session_var] | ||||||
|  |  | ||||||
|  | @ -6,24 +6,31 @@ from django.contrib import messages | ||||||
| from django.contrib.auth import login, authenticate | from django.contrib.auth import login, authenticate | ||||||
| from django.core.exceptions import ValidationError | from django.core.exceptions import ValidationError | ||||||
| from django.core.urlresolvers import reverse | from django.core.urlresolvers import reverse | ||||||
| from django.http import HttpResponseRedirect, JsonResponse | from django.http import HttpResponseRedirect, JsonResponse, Http404 | ||||||
| from django.shortcuts import render | from django.shortcuts import render | ||||||
| from django.utils.translation import get_language, ugettext_lazy as _ | from django.utils.translation import get_language, ugettext_lazy as _ | ||||||
| from django.views.decorators.cache import cache_control | from django.views.decorators.cache import cache_control | ||||||
| from django.views.generic import FormView, CreateView, DetailView | from django.views.generic import FormView, CreateView, DetailView | ||||||
| 
 | 
 | ||||||
| from hosting.forms import HostingUserLoginForm | from hosting.forms import ( | ||||||
| from hosting.models import HostingOrder, UserCardDetail |     HostingUserLoginForm, GenericPaymentForm, ProductPaymentForm | ||||||
|  | ) | ||||||
|  | from hosting.models import ( | ||||||
|  |     HostingBill, HostingOrder, UserCardDetail, GenericProduct | ||||||
|  | ) | ||||||
| from membership.models import CustomUser, StripeCustomer | from membership.models import CustomUser, StripeCustomer | ||||||
| from opennebula_api.serializers import VMTemplateSerializer | from opennebula_api.serializers import VMTemplateSerializer | ||||||
| from utils.forms import BillingAddressForm, BillingAddressFormSignup | from utils.forms import ( | ||||||
|  |     BillingAddressForm, BillingAddressFormSignup, UserBillingAddressForm, | ||||||
|  |     BillingAddress | ||||||
|  | ) | ||||||
| from utils.hosting_utils import get_vm_price_with_vat | from utils.hosting_utils import get_vm_price_with_vat | ||||||
| from utils.stripe_utils import StripeUtils | from utils.stripe_utils import StripeUtils | ||||||
| from utils.tasks import send_plain_email_task | from utils.tasks import send_plain_email_task | ||||||
| from .cms_models import DCLCalculatorPluginModel | from .cms_models import DCLCalculatorPluginModel | ||||||
| from .forms import ContactForm | from .forms import ContactForm | ||||||
| from .models import VMTemplate, VMPricing | from .models import VMTemplate, VMPricing | ||||||
| from .utils import get_cms_integration, create_vm | from .utils import get_cms_integration, create_vm, clear_all_session_vars | ||||||
| 
 | 
 | ||||||
| logger = logging.getLogger(__name__) | logger = logging.getLogger(__name__) | ||||||
| 
 | 
 | ||||||
|  | @ -114,10 +121,7 @@ class IndexView(CreateView): | ||||||
| 
 | 
 | ||||||
|     @cache_control(no_cache=True, must_revalidate=True, no_store=True) |     @cache_control(no_cache=True, must_revalidate=True, no_store=True) | ||||||
|     def get(self, request, *args, **kwargs): |     def get(self, request, *args, **kwargs): | ||||||
|         for session_var in ['specs', 'user', 'billing_address_data', |         clear_all_session_vars(request) | ||||||
|                             'pricing_name']: |  | ||||||
|             if session_var in request.session: |  | ||||||
|                 del request.session[session_var] |  | ||||||
|         return HttpResponseRedirect(reverse('datacenterlight:cms_index')) |         return HttpResponseRedirect(reverse('datacenterlight:cms_index')) | ||||||
| 
 | 
 | ||||||
|     def post(self, request): |     def post(self, request): | ||||||
|  | @ -265,19 +269,93 @@ class PaymentOrderView(FormView): | ||||||
|             'login_form': HostingUserLoginForm(prefix='login_form'), |             'login_form': HostingUserLoginForm(prefix='login_form'), | ||||||
|             'billing_address_form': billing_address_form, |             'billing_address_form': billing_address_form, | ||||||
|             'cms_integration': get_cms_integration('default'), |             'cms_integration': get_cms_integration('default'), | ||||||
|  |         }) | ||||||
|  | 
 | ||||||
|  |         if ('generic_payment_type' in self.request.session and | ||||||
|  |                 self.request.session['generic_payment_type'] == 'generic'): | ||||||
|  |             if 'product_id' in self.request.session: | ||||||
|  |                 product = GenericProduct.objects.get( | ||||||
|  |                     id=self.request.session['product_id'] | ||||||
|  |                 ) | ||||||
|  |                 context.update({'generic_payment_form': ProductPaymentForm( | ||||||
|  |                     prefix='generic_payment_form', | ||||||
|  |                     initial={'product_name': product.product_name, | ||||||
|  |                              'amount': float(product.get_actual_price()), | ||||||
|  |                              'recurring': product.product_is_subscription, | ||||||
|  |                              'description': product.product_description, | ||||||
|  |                              }, | ||||||
|  |                     product_id=product.id | ||||||
|  |                 ), }) | ||||||
|  |             else: | ||||||
|  |                 context.update({'generic_payment_form': GenericPaymentForm( | ||||||
|  |                     prefix='generic_payment_form', | ||||||
|  |                 ), }) | ||||||
|  |         else: | ||||||
|  |             context.update({ | ||||||
|                 'vm_pricing': VMPricing.get_vm_pricing_by_name( |                 'vm_pricing': VMPricing.get_vm_pricing_by_name( | ||||||
|                     self.request.session['specs']['pricing_name'] |                     self.request.session['specs']['pricing_name'] | ||||||
|                 ) |                 ) | ||||||
|             }) |             }) | ||||||
|  | 
 | ||||||
|         return context |         return context | ||||||
| 
 | 
 | ||||||
|     @cache_control(no_cache=True, must_revalidate=True, no_store=True) |     @cache_control(no_cache=True, must_revalidate=True, no_store=True) | ||||||
|     def get(self, request, *args, **kwargs): |     def get(self, request, *args, **kwargs): | ||||||
|         if 'specs' not in request.session: |         if (('type' in request.GET and request.GET['type'] == 'generic') | ||||||
|  |                 or 'product_slug' in kwargs): | ||||||
|  |             request.session['generic_payment_type'] = 'generic' | ||||||
|  |             if 'generic_payment_details' in request.session: | ||||||
|  |                 request.session.pop('generic_payment_details') | ||||||
|  |                 request.session.pop('product_id') | ||||||
|  |             if 'product_slug' in kwargs: | ||||||
|  |                 logger.debug("Product slug is " + kwargs['product_slug']) | ||||||
|  |                 try: | ||||||
|  |                     product = GenericProduct.objects.get( | ||||||
|  |                         product_slug=kwargs['product_slug'] | ||||||
|  |                     ) | ||||||
|  |                 except GenericProduct.DoesNotExist as dne: | ||||||
|  |                     logger.error( | ||||||
|  |                         "Product '{}' does " | ||||||
|  |                         "not exist".format(kwargs['product_slug']) | ||||||
|  |                     ) | ||||||
|  |                     raise Http404() | ||||||
|  |                 request.session['product_id'] = product.id | ||||||
|  |         elif 'specs' not in request.session: | ||||||
|             return HttpResponseRedirect(reverse('datacenterlight:index')) |             return HttpResponseRedirect(reverse('datacenterlight:index')) | ||||||
|         return self.render_to_response(self.get_context_data()) |         return self.render_to_response(self.get_context_data()) | ||||||
| 
 | 
 | ||||||
|     def post(self, request, *args, **kwargs): |     def post(self, request, *args, **kwargs): | ||||||
|  |         if 'product' in request.POST: | ||||||
|  |             # query for the supplied product | ||||||
|  |             product = None | ||||||
|  |             try: | ||||||
|  |                 product = GenericProduct.objects.get( | ||||||
|  |                     id=request.POST['generic_payment_form-product_name'] | ||||||
|  |                 ) | ||||||
|  |             except GenericProduct.DoesNotExist as dne: | ||||||
|  |                 logger.error( | ||||||
|  |                     "The requested product '{}' does not exist".format( | ||||||
|  |                         request.POST['generic_payment_form-product_name'] | ||||||
|  |                     ) | ||||||
|  |                 ) | ||||||
|  |             except GenericProduct.MultipleObjectsReturned as mpe: | ||||||
|  |                 logger.error( | ||||||
|  |                     "There seem to be more than one product with " | ||||||
|  |                     "the name {}".format( | ||||||
|  |                         request.POST['generic_payment_form-product_name'] | ||||||
|  |                     ) | ||||||
|  |                 ) | ||||||
|  |                 product = GenericProduct.objects.all( | ||||||
|  |                     product_name=request. | ||||||
|  |                     POST['generic_payment_form-product_name'] | ||||||
|  |                 ).first() | ||||||
|  |             if product is None: | ||||||
|  |                 return JsonResponse({}) | ||||||
|  |             else: | ||||||
|  |                 return JsonResponse({ | ||||||
|  |                     'amount': product.get_actual_price(), | ||||||
|  |                     'isSubscription': product.product_is_subscription | ||||||
|  |                 }) | ||||||
|         if 'login_form' in request.POST: |         if 'login_form' in request.POST: | ||||||
|             login_form = HostingUserLoginForm( |             login_form = HostingUserLoginForm( | ||||||
|                 data=request.POST, prefix='login_form' |                 data=request.POST, prefix='login_form' | ||||||
|  | @ -288,6 +366,13 @@ class PaymentOrderView(FormView): | ||||||
|                 auth_user = authenticate(email=email, password=password) |                 auth_user = authenticate(email=email, password=password) | ||||||
|                 if auth_user: |                 if auth_user: | ||||||
|                     login(self.request, auth_user) |                     login(self.request, auth_user) | ||||||
|  |                     if 'product_slug' in kwargs: | ||||||
|  |                         return HttpResponseRedirect( | ||||||
|  |                             reverse('show_product', | ||||||
|  |                                     kwargs={ | ||||||
|  |                                         'product_slug': kwargs['product_slug']} | ||||||
|  |                                     ) | ||||||
|  |                         ) | ||||||
|                     return HttpResponseRedirect( |                     return HttpResponseRedirect( | ||||||
|                         reverse('datacenterlight:payment') |                         reverse('datacenterlight:payment') | ||||||
|                     ) |                     ) | ||||||
|  | @ -304,6 +389,50 @@ class PaymentOrderView(FormView): | ||||||
|                 data=request.POST, |                 data=request.POST, | ||||||
|             ) |             ) | ||||||
|         if address_form.is_valid(): |         if address_form.is_valid(): | ||||||
|  |             # Check if we are in a generic payment case and handle the generic | ||||||
|  |             # payment details form before we go on to verify payment | ||||||
|  |             if ('generic_payment_type' in request.session and | ||||||
|  |                     self.request.session['generic_payment_type'] == 'generic'): | ||||||
|  |                 if 'product_id' in request.session: | ||||||
|  |                     generic_payment_form = ProductPaymentForm( | ||||||
|  |                         data=request.POST, prefix='generic_payment_form', | ||||||
|  |                         product_id=request.session['product_id'] | ||||||
|  |                     ) | ||||||
|  |                 else: | ||||||
|  |                     generic_payment_form = GenericPaymentForm( | ||||||
|  |                         data=request.POST, prefix='generic_payment_form' | ||||||
|  |                     ) | ||||||
|  |                 if generic_payment_form.is_valid(): | ||||||
|  |                     logger.debug("Generic payment form is valid.") | ||||||
|  |                     if 'product_id' in request.session: | ||||||
|  |                         product = generic_payment_form.product | ||||||
|  |                     else: | ||||||
|  |                         product = generic_payment_form.cleaned_data.get( | ||||||
|  |                             'product_name' | ||||||
|  |                         ) | ||||||
|  |                     gp_details = { | ||||||
|  |                         "product_name": product.product_name, | ||||||
|  |                         "amount": generic_payment_form.cleaned_data.get( | ||||||
|  |                             'amount' | ||||||
|  |                         ), | ||||||
|  |                         "recurring": generic_payment_form.cleaned_data.get( | ||||||
|  |                             'recurring' | ||||||
|  |                         ), | ||||||
|  |                         "description": generic_payment_form.cleaned_data.get( | ||||||
|  |                             'description' | ||||||
|  |                         ), | ||||||
|  |                         "product_id": product.id, | ||||||
|  |                         "product_slug": product.product_slug | ||||||
|  |                     } | ||||||
|  |                     request.session["generic_payment_details"] = ( | ||||||
|  |                         gp_details | ||||||
|  |                     ) | ||||||
|  |                 else: | ||||||
|  |                     logger.debug("Generic payment form invalid") | ||||||
|  |                     context = self.get_context_data() | ||||||
|  |                     context['generic_payment_form'] = generic_payment_form | ||||||
|  |                     context['billing_address_form'] = address_form | ||||||
|  |                     return self.render_to_response(context) | ||||||
|             token = address_form.cleaned_data.get('token') |             token = address_form.cleaned_data.get('token') | ||||||
|             if token is '': |             if token is '': | ||||||
|                 card_id = address_form.cleaned_data.get('card') |                 card_id = address_form.cleaned_data.get('card') | ||||||
|  | @ -409,7 +538,8 @@ class OrderConfirmationView(DetailView): | ||||||
|     @cache_control(no_cache=True, must_revalidate=True, no_store=True) |     @cache_control(no_cache=True, must_revalidate=True, no_store=True) | ||||||
|     def get(self, request, *args, **kwargs): |     def get(self, request, *args, **kwargs): | ||||||
|         context = {} |         context = {} | ||||||
|         if 'specs' not in request.session or 'user' not in request.session: |         if (('specs' not in request.session or 'user' not in request.session) | ||||||
|  |                 and 'generic_payment_type' not in request.session): | ||||||
|             return HttpResponseRedirect(reverse('datacenterlight:index')) |             return HttpResponseRedirect(reverse('datacenterlight:index')) | ||||||
|         if 'token' in self.request.session: |         if 'token' in self.request.session: | ||||||
|             token = self.request.session['token'] |             token = self.request.session['token'] | ||||||
|  | @ -427,9 +557,19 @@ class OrderConfirmationView(DetailView): | ||||||
|             card_detail = UserCardDetail.objects.get(id=card_id) |             card_detail = UserCardDetail.objects.get(id=card_id) | ||||||
|             context['cc_last4'] = card_detail.last4 |             context['cc_last4'] = card_detail.last4 | ||||||
|             context['cc_brand'] = card_detail.brand |             context['cc_brand'] = card_detail.brand | ||||||
|  | 
 | ||||||
|  |         if ('generic_payment_type' in request.session and | ||||||
|  |                 self.request.session['generic_payment_type'] == 'generic'): | ||||||
|  |             context.update({ | ||||||
|  |                 'generic_payment_details': | ||||||
|  |                     request.session['generic_payment_details'], | ||||||
|  |             }) | ||||||
|  |         else: | ||||||
|  |             context.update({ | ||||||
|  |                 'vm': request.session.get('specs'), | ||||||
|  |             }) | ||||||
|         context.update({ |         context.update({ | ||||||
|             'site_url': reverse('datacenterlight:index'), |             'site_url': reverse('datacenterlight:index'), | ||||||
|             'vm': request.session.get('specs'), |  | ||||||
|             'page_header_text': _('Confirm Order'), |             'page_header_text': _('Confirm Order'), | ||||||
|             'billing_address_data': ( |             'billing_address_data': ( | ||||||
|                 request.session.get('billing_address_data') |                 request.session.get('billing_address_data') | ||||||
|  | @ -439,11 +579,8 @@ class OrderConfirmationView(DetailView): | ||||||
|         return render(request, self.template_name, context) |         return render(request, self.template_name, context) | ||||||
| 
 | 
 | ||||||
|     def post(self, request, *args, **kwargs): |     def post(self, request, *args, **kwargs): | ||||||
|         template = request.session.get('template') |  | ||||||
|         specs = request.session.get('specs') |  | ||||||
|         user = request.session.get('user') |         user = request.session.get('user') | ||||||
|         stripe_api_cus_id = request.session.get('customer') |         stripe_api_cus_id = request.session.get('customer') | ||||||
|         vm_template_id = template.get('id', 1) |  | ||||||
|         stripe_utils = StripeUtils() |         stripe_utils = StripeUtils() | ||||||
| 
 | 
 | ||||||
|         if 'token' in request.session: |         if 'token' in request.session: | ||||||
|  | @ -457,7 +594,14 @@ class OrderConfirmationView(DetailView): | ||||||
|                 response = { |                 response = { | ||||||
|                     'status': False, |                     'status': False, | ||||||
|                     'redirect': "{url}#{section}".format( |                     'redirect': "{url}#{section}".format( | ||||||
|                         url=reverse('datacenterlight:payment'), |                         url=(reverse( | ||||||
|  |                             'show_product', | ||||||
|  |                             kwargs={'product_slug': | ||||||
|  |                                     request.session['generic_payment_details'] | ||||||
|  |                                     ['product_slug']} | ||||||
|  |                         ) if 'generic_payment_details' in request.session else | ||||||
|  |                                 reverse('datacenterlight:payment') | ||||||
|  |                         ), | ||||||
|                         section='payment_error'), |                         section='payment_error'), | ||||||
|                     'msg_title': str(_('Error.')), |                     'msg_title': str(_('Error.')), | ||||||
|                     'msg_body': str( |                     'msg_body': str( | ||||||
|  | @ -473,7 +617,8 @@ class OrderConfirmationView(DetailView): | ||||||
|                 'brand': card_details_response['brand'], |                 'brand': card_details_response['brand'], | ||||||
|                 'card_id': card_details_response['card_id'] |                 'card_id': card_details_response['card_id'] | ||||||
|             } |             } | ||||||
|             stripe_customer_obj = StripeCustomer.objects.filter(stripe_id=stripe_api_cus_id).first() |             stripe_customer_obj = StripeCustomer.objects.filter( | ||||||
|  |                 stripe_id=stripe_api_cus_id).first() | ||||||
|             if stripe_customer_obj: |             if stripe_customer_obj: | ||||||
|                 ucd = UserCardDetail.get_user_card_details( |                 ucd = UserCardDetail.get_user_card_details( | ||||||
|                     stripe_customer_obj, card_details_response |                     stripe_customer_obj, card_details_response | ||||||
|  | @ -495,7 +640,16 @@ class OrderConfirmationView(DetailView): | ||||||
|                         response = { |                         response = { | ||||||
|                             'status': False, |                             'status': False, | ||||||
|                             'redirect': "{url}#{section}".format( |                             'redirect': "{url}#{section}".format( | ||||||
|                                 url=reverse('hosting:payment'), |                                 url=(reverse( | ||||||
|  |                                     'show_product', | ||||||
|  |                                     kwargs={'product_slug': | ||||||
|  |                                             request.session | ||||||
|  |                                             ['generic_payment_details'] | ||||||
|  |                                             ['product_slug']} | ||||||
|  |                                     ) if 'generic_payment_details' in | ||||||
|  |                                      request.session else | ||||||
|  |                                      reverse('datacenterlight:payment') | ||||||
|  |                                 ), | ||||||
|                                 section='payment_error'), |                                 section='payment_error'), | ||||||
|                             'msg_title': str(_('Error.')), |                             'msg_title': str(_('Error.')), | ||||||
|                             'msg_body': str( |                             'msg_body': str( | ||||||
|  | @ -527,6 +681,63 @@ class OrderConfirmationView(DetailView): | ||||||
|             } |             } | ||||||
|             return JsonResponse(response) |             return JsonResponse(response) | ||||||
| 
 | 
 | ||||||
|  |         if ('generic_payment_type' in request.session and | ||||||
|  |                 self.request.session['generic_payment_type'] == 'generic'): | ||||||
|  |             gp_details = self.request.session['generic_payment_details'] | ||||||
|  |             if gp_details['recurring']: | ||||||
|  |                 # generic recurring payment | ||||||
|  |                 logger.debug("Commencing a generic recurring payment") | ||||||
|  |             else: | ||||||
|  |                 # generic one time payment | ||||||
|  |                 logger.debug("Commencing a one time payment") | ||||||
|  |                 charge_response = stripe_utils.make_charge( | ||||||
|  |                     amount=gp_details['amount'], | ||||||
|  |                     customer=stripe_api_cus_id | ||||||
|  |                 ) | ||||||
|  |                 stripe_onetime_charge = charge_response.get('response_object') | ||||||
|  | 
 | ||||||
|  |                 # Check if the payment was approved | ||||||
|  |                 if not stripe_onetime_charge: | ||||||
|  |                     msg = charge_response.get('error') | ||||||
|  |                     messages.add_message(self.request, messages.ERROR, msg, | ||||||
|  |                                          extra_tags='failed_payment') | ||||||
|  |                     response = { | ||||||
|  |                         'status': False, | ||||||
|  |                         'redirect': "{url}#{section}".format( | ||||||
|  |                             url=(reverse('show_product', kwargs={ | ||||||
|  |                                 'product_slug': gp_details['product_slug']} | ||||||
|  |                                          ) if 'generic_payment_details' in | ||||||
|  |                                               request.session else | ||||||
|  |                                  reverse('datacenterlight:payment') | ||||||
|  |                                  ), | ||||||
|  |                             section='payment_error'), | ||||||
|  |                         'msg_title': str(_('Error.')), | ||||||
|  |                         'msg_body': str( | ||||||
|  |                             _('There was a payment related error.' | ||||||
|  |                               ' On close of this popup, you will be redirected' | ||||||
|  |                               ' back to the payment page.')) | ||||||
|  |                     } | ||||||
|  |                     return JsonResponse(response) | ||||||
|  | 
 | ||||||
|  |         if ('generic_payment_type' not in request.session or | ||||||
|  |                 (request.session['generic_payment_details']['recurring'])): | ||||||
|  |             if 'generic_payment_details' in request.session: | ||||||
|  |                 amount_to_be_charged = ( | ||||||
|  |                     round( | ||||||
|  |                         request.session['generic_payment_details']['amount'], | ||||||
|  |                         2 | ||||||
|  |                     ) | ||||||
|  |                 ) | ||||||
|  |                 plan_name = "generic-{0}-{1:.2f}".format( | ||||||
|  |                     request.session['generic_payment_details']['product_id'], | ||||||
|  |                     amount_to_be_charged | ||||||
|  |                 ) | ||||||
|  |                 stripe_plan_id = plan_name | ||||||
|  |             else: | ||||||
|  |                 template = request.session.get('template') | ||||||
|  |                 specs = request.session.get('specs') | ||||||
|  |                 vm_template_id = template.get('id', 1) | ||||||
|  | 
 | ||||||
|                 cpu = specs.get('cpu') |                 cpu = specs.get('cpu') | ||||||
|                 memory = specs.get('memory') |                 memory = specs.get('memory') | ||||||
|                 disk_size = specs.get('disk_size') |                 disk_size = specs.get('disk_size') | ||||||
|  | @ -569,8 +780,16 @@ class OrderConfirmationView(DetailView): | ||||||
|                 response = { |                 response = { | ||||||
|                     'status': False, |                     'status': False, | ||||||
|                     'redirect': "{url}#{section}".format( |                     'redirect': "{url}#{section}".format( | ||||||
|                     url=reverse('datacenterlight:payment'), |                         url=(reverse( | ||||||
|                     section='payment_error'), |                             'show_product', | ||||||
|  |                             kwargs={'product_slug': | ||||||
|  |                                     request.session['generic_payment_details'] | ||||||
|  |                                     ['product_slug']} | ||||||
|  |                         ) if 'generic_payment_details' in request.session else | ||||||
|  |                                 reverse('datacenterlight:payment') | ||||||
|  |                         ), | ||||||
|  |                         section='payment_error' | ||||||
|  |                     ), | ||||||
|                     'msg_title': str(_('Error.')), |                     'msg_title': str(_('Error.')), | ||||||
|                     'msg_body': str( |                     'msg_body': str( | ||||||
|                         _('There was a payment related error.' |                         _('There was a payment related error.' | ||||||
|  | @ -647,6 +866,118 @@ class OrderConfirmationView(DetailView): | ||||||
|             'user': custom_user.id |             'user': custom_user.id | ||||||
|         }) |         }) | ||||||
| 
 | 
 | ||||||
|  |         if 'generic_payment_type' in request.session: | ||||||
|  |             stripe_cus = StripeCustomer.objects.filter( | ||||||
|  |                 stripe_id=stripe_api_cus_id | ||||||
|  |             ).first() | ||||||
|  |             billing_address = BillingAddress( | ||||||
|  |                 cardholder_name=billing_address_data['cardholder_name'], | ||||||
|  |                 street_address=billing_address_data['street_address'], | ||||||
|  |                 city=billing_address_data['city'], | ||||||
|  |                 postal_code=billing_address_data['postal_code'], | ||||||
|  |                 country=billing_address_data['country'] | ||||||
|  |             ) | ||||||
|  |             billing_address.save() | ||||||
|  | 
 | ||||||
|  |             order = HostingOrder.create( | ||||||
|  |                 price=self.request | ||||||
|  |                           .session['generic_payment_details']['amount'], | ||||||
|  |                 customer=stripe_cus, | ||||||
|  |                 billing_address=billing_address, | ||||||
|  |                 vm_pricing=VMPricing.get_default_pricing() | ||||||
|  |             ) | ||||||
|  | 
 | ||||||
|  |             # Create a Hosting Bill | ||||||
|  |             HostingBill.create(customer=stripe_cus, | ||||||
|  |                                billing_address=billing_address) | ||||||
|  | 
 | ||||||
|  |             # Create Billing Address for User if he does not have one | ||||||
|  |             if not stripe_cus.user.billing_addresses.count(): | ||||||
|  |                 billing_address_data.update({ | ||||||
|  |                     'user': stripe_cus.user.id | ||||||
|  |                 }) | ||||||
|  |                 billing_address_user_form = UserBillingAddressForm( | ||||||
|  |                     billing_address_data | ||||||
|  |                 ) | ||||||
|  |                 billing_address_user_form.is_valid() | ||||||
|  |                 billing_address_user_form.save() | ||||||
|  | 
 | ||||||
|  |             if self.request.session['generic_payment_details']['recurring']: | ||||||
|  |                 # Associate the given stripe subscription with the order | ||||||
|  |                 order.set_subscription_id( | ||||||
|  |                     stripe_subscription_obj.id, card_details_dict | ||||||
|  |                 ) | ||||||
|  |             else: | ||||||
|  |                 # Associate the given stripe charge id with the order | ||||||
|  |                 order.set_stripe_charge(stripe_onetime_charge) | ||||||
|  | 
 | ||||||
|  |             # Set order status approved | ||||||
|  |             order.set_approved() | ||||||
|  |             order.generic_payment_description = gp_details["description"] | ||||||
|  |             order.generic_product_id = gp_details["product_id"] | ||||||
|  |             order.save() | ||||||
|  |             # send emails | ||||||
|  |             context = { | ||||||
|  |                 'name': user.get('name'), | ||||||
|  |                 'email': user.get('email'), | ||||||
|  |                 'amount': gp_details['amount'], | ||||||
|  |                 'description': gp_details['description'], | ||||||
|  |                 'recurring': gp_details['recurring'], | ||||||
|  |                 'product_name': gp_details['product_name'], | ||||||
|  |                 'product_id': gp_details['product_id'], | ||||||
|  |                 'order_id': order.id | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             email_data = { | ||||||
|  |                 'subject': (settings.DCL_TEXT + | ||||||
|  |                             " Payment received from %s" % context['email']), | ||||||
|  |                 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, | ||||||
|  |                 'to': ['info@ungleich.ch'], | ||||||
|  |                 'body': "\n".join( | ||||||
|  |                     ["%s=%s" % (k, v) for (k, v) in context.items()]), | ||||||
|  |                 'reply_to': [context['email']], | ||||||
|  |             } | ||||||
|  |             send_plain_email_task.delay(email_data) | ||||||
|  | 
 | ||||||
|  |             email_data = { | ||||||
|  |                 'subject': _("Confirmation of your payment"), | ||||||
|  |                 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, | ||||||
|  |                 'to': [user.get('email')], | ||||||
|  |                 'body': _("Hi {name},\n\n" | ||||||
|  |                           "thank you for your order!\n" | ||||||
|  |                           "We have just received a payment of CHF {amount:.2f}" | ||||||
|  |                           " from you.{recurring}\n\n" | ||||||
|  |                           "Cheers,\nYour Data Center Light team".format( | ||||||
|  |                                  name=user.get('name'), | ||||||
|  |                                  amount=gp_details['amount'], | ||||||
|  |                                  recurring=( | ||||||
|  |                                      _(' This is a monthly recurring plan.') | ||||||
|  |                                      if gp_details['recurring'] else '' | ||||||
|  |                                  ) | ||||||
|  |                              ) | ||||||
|  |                           ), | ||||||
|  |                 'reply_to': ['info@ungleich.ch'], | ||||||
|  |             } | ||||||
|  |             send_plain_email_task.delay(email_data) | ||||||
|  | 
 | ||||||
|  |             response = { | ||||||
|  |                 'status': True, | ||||||
|  |                 'redirect': ( | ||||||
|  |                     reverse('hosting:orders') | ||||||
|  |                     if request.user.is_authenticated() | ||||||
|  |                     else reverse('datacenterlight:index') | ||||||
|  |                 ), | ||||||
|  |                 'msg_title': str(_('Thank you for the payment.')), | ||||||
|  |                 'msg_body': str( | ||||||
|  |                     _('You will soon receive a confirmation email of the ' | ||||||
|  |                       'payment. You can always contact us at ' | ||||||
|  |                       'info@ungleich.ch for any question that you may have.') | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |             clear_all_session_vars(request) | ||||||
|  | 
 | ||||||
|  |             return JsonResponse(response) | ||||||
|  | 
 | ||||||
|         user = { |         user = { | ||||||
|             'name': custom_user.name, |             'name': custom_user.name, | ||||||
|             'email': custom_user.email, |             'email': custom_user.email, | ||||||
|  |  | ||||||
|  | @ -10,6 +10,7 @@ from django.conf import settings | ||||||
| from hosting.views import ( | from hosting.views import ( | ||||||
|     RailsHostingView, DjangoHostingView, NodeJSHostingView |     RailsHostingView, DjangoHostingView, NodeJSHostingView | ||||||
| ) | ) | ||||||
|  | from datacenterlight.views import PaymentOrderView | ||||||
| from membership import urls as membership_urls | from membership import urls as membership_urls | ||||||
| from ungleich_page.views import LandingView | from ungleich_page.views import LandingView | ||||||
| from django.views.generic import RedirectView | from django.views.generic import RedirectView | ||||||
|  | @ -29,6 +30,9 @@ urlpatterns = [ | ||||||
|     url(r'^nosystemd/', include('nosystemd.urls', namespace="nosystemd")), |     url(r'^nosystemd/', include('nosystemd.urls', namespace="nosystemd")), | ||||||
|     url(r'^taggit_autosuggest/', include('taggit_autosuggest.urls')), |     url(r'^taggit_autosuggest/', include('taggit_autosuggest.urls')), | ||||||
|     url(r'^jsi18n/(?P<packages>\S+?)/$', i18n.javascript_catalog), |     url(r'^jsi18n/(?P<packages>\S+?)/$', i18n.javascript_catalog), | ||||||
|  |     url(r'^product/(?P<product_slug>[\w-]+)/$', | ||||||
|  |         PaymentOrderView.as_view(), | ||||||
|  |         name='show_product'), | ||||||
| ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) | ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) | ||||||
| 
 | 
 | ||||||
| urlpatterns += i18n_patterns( | urlpatterns += i18n_patterns( | ||||||
|  |  | ||||||
|  | @ -1,8 +1,8 @@ | ||||||
| from django.contrib import admin | from django.contrib import admin | ||||||
| 
 | 
 | ||||||
| from .models import HostingOrder, HostingBill, HostingPlan | from .models import HostingOrder, HostingBill, HostingPlan, GenericProduct | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| admin.site.register(HostingOrder) | admin.site.register(HostingOrder) | ||||||
| admin.site.register(HostingBill) | admin.site.register(HostingBill) | ||||||
| admin.site.register(HostingPlan) | admin.site.register(HostingPlan) | ||||||
|  | admin.site.register(GenericProduct) | ||||||
|  |  | ||||||
|  | @ -10,7 +10,7 @@ from django.utils.translation import ugettext_lazy as _ | ||||||
| 
 | 
 | ||||||
| from membership.models import CustomUser | from membership.models import CustomUser | ||||||
| from utils.hosting_utils import get_all_public_keys | from utils.hosting_utils import get_all_public_keys | ||||||
| from .models import UserHostingKey | from .models import UserHostingKey, GenericProduct | ||||||
| 
 | 
 | ||||||
| logger = logging.getLogger(__name__) | logger = logging.getLogger(__name__) | ||||||
| 
 | 
 | ||||||
|  | @ -52,6 +52,93 @@ class HostingUserLoginForm(forms.Form): | ||||||
|             raise forms.ValidationError(_("User does not exist")) |             raise forms.ValidationError(_("User does not exist")) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | class ProductModelChoiceField(forms.ModelChoiceField): | ||||||
|  |     def label_from_instance(self, obj): | ||||||
|  |         return obj.product_name | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class GenericPaymentForm(forms.Form): | ||||||
|  |     product_name = ProductModelChoiceField( | ||||||
|  |         queryset=GenericProduct.objects.all().order_by('product_name'), | ||||||
|  |         empty_label=_("Choose a product"), | ||||||
|  |     ) | ||||||
|  |     amount = forms.FloatField( | ||||||
|  |         widget=forms.TextInput( | ||||||
|  |             attrs={'placeholder': _('Amount in CHF'), | ||||||
|  |                    'readonly': 'readonly', } | ||||||
|  |         ), | ||||||
|  |         max_value=999999, | ||||||
|  |         min_value=1, | ||||||
|  |         label=_('Amount in CHF') | ||||||
|  |     ) | ||||||
|  |     recurring = forms.BooleanField(required=False, | ||||||
|  |                                    label=_("Recurring monthly"), ) | ||||||
|  |     description = forms.CharField( | ||||||
|  |         widget=forms.Textarea(attrs={'style': "height: 60px;"}), | ||||||
|  |         required=False | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     class Meta: | ||||||
|  |         model = GenericProduct | ||||||
|  |         fields = ['product_name', 'amount', 'recurring', 'description'] | ||||||
|  | 
 | ||||||
|  |     def clean_amount(self): | ||||||
|  |         amount = self.cleaned_data.get('amount') | ||||||
|  |         if (float(self.cleaned_data.get('product_name').get_actual_price()) != | ||||||
|  |                 amount): | ||||||
|  |             raise forms.ValidationError(_("Amount field does not match")) | ||||||
|  |         return amount | ||||||
|  | 
 | ||||||
|  |     def clean_recurring(self): | ||||||
|  |         recurring = self.cleaned_data.get('recurring') | ||||||
|  |         if (self.cleaned_data.get('product_name').product_is_subscription != | ||||||
|  |                 (True if recurring else False)): | ||||||
|  |             raise forms.ValidationError(_("Recurring field does not match")) | ||||||
|  |         return recurring | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class ProductPaymentForm(GenericPaymentForm): | ||||||
|  |     def __init__(self, *args, **kwargs): | ||||||
|  |         product_id = kwargs.pop('product_id', None) | ||||||
|  |         if product_id is not None: | ||||||
|  |             self.product = GenericProduct.objects.get(id=product_id) | ||||||
|  |         super(ProductPaymentForm, self).__init__(*args, **kwargs) | ||||||
|  |         self.fields['product_name'] = forms.CharField( | ||||||
|  |             widget=forms.TextInput( | ||||||
|  |                 attrs={'placeholder': _('Product name'), | ||||||
|  |                        'readonly': 'readonly'} | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |         if self.product.product_is_subscription: | ||||||
|  |             self.fields['amount'].label = "{amt} ({payment_type})".format( | ||||||
|  |                 amt=_('Amount in CHF'), | ||||||
|  |                 payment_type=_('Monthly subscription') | ||||||
|  |             ) | ||||||
|  |         else: | ||||||
|  |             self.fields['amount'].label = "{amt} ({payment_type})".format( | ||||||
|  |                 amt=_('Amount in CHF'), | ||||||
|  |                 payment_type=_('One time payment') | ||||||
|  |             ) | ||||||
|  |         self.fields['recurring'].widget = forms.HiddenInput() | ||||||
|  |         self.fields['product_name'].widget.attrs['class'] = 'input-no-border' | ||||||
|  |         self.fields['amount'].widget.attrs['class'] = 'input-no-border' | ||||||
|  |         self.fields['description'].widget.attrs['class'] = 'input-no-border' | ||||||
|  | 
 | ||||||
|  |     def clean_amount(self): | ||||||
|  |         amount = self.cleaned_data.get('amount') | ||||||
|  |         if (self.product is None or | ||||||
|  |                 float(self.product.get_actual_price()) != amount): | ||||||
|  |             raise forms.ValidationError(_("Amount field does not match")) | ||||||
|  |         return amount | ||||||
|  | 
 | ||||||
|  |     def clean_recurring(self): | ||||||
|  |         recurring = self.cleaned_data.get('recurring') | ||||||
|  |         if (self.product.product_is_subscription != | ||||||
|  |                 (True if recurring else False)): | ||||||
|  |             raise forms.ValidationError(_("Recurring field does not match")) | ||||||
|  |         return recurring | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class HostingUserSignupForm(forms.ModelForm): | class HostingUserSignupForm(forms.ModelForm): | ||||||
|     confirm_password = forms.CharField(label=_("Confirm Password"), |     confirm_password = forms.CharField(label=_("Confirm Password"), | ||||||
|                                        widget=forms.PasswordInput()) |                                        widget=forms.PasswordInput()) | ||||||
|  |  | ||||||
							
								
								
									
										41
									
								
								hosting/migrations/0048_auto_20181003_0757.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								hosting/migrations/0048_auto_20181003_0757.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,41 @@ | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  | # Generated by Django 1.9.4 on 2018-10-03 07:57 | ||||||
|  | from __future__ import unicode_literals | ||||||
|  | 
 | ||||||
|  | from django.db import migrations, models | ||||||
|  | import django.db.models.deletion | ||||||
|  | import utils.mixins | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('hosting', '0047_auto_20180821_1240'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name='GenericProduct', | ||||||
|  |             fields=[ | ||||||
|  |                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||||||
|  |                 ('product_name', models.CharField(default='', max_length=128)), | ||||||
|  |                 ('product_slug', models.SlugField(help_text='An optional html id for the Section. Required to set as target of a link on page', unique=True)), | ||||||
|  |                 ('product_description', models.CharField(default='', max_length=500)), | ||||||
|  |                 ('created_at', models.DateTimeField(auto_now_add=True)), | ||||||
|  |                 ('product_price', models.DecimalField(decimal_places=2, max_digits=6)), | ||||||
|  |                 ('product_vat', models.DecimalField(decimal_places=4, default=0, max_digits=6)), | ||||||
|  |                 ('product_is_subscription', models.BooleanField(default=True)), | ||||||
|  |             ], | ||||||
|  |             bases=(utils.mixins.AssignPermissionsMixin, models.Model), | ||||||
|  |         ), | ||||||
|  |         migrations.AddField( | ||||||
|  |             model_name='hostingorder', | ||||||
|  |             name='generic_payment_description', | ||||||
|  |             field=models.CharField(max_length=500, null=True), | ||||||
|  |         ), | ||||||
|  |         migrations.AddField( | ||||||
|  |             model_name='hostingorder', | ||||||
|  |             name='generic_product', | ||||||
|  |             field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='hosting.GenericProduct'), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
							
								
								
									
										20
									
								
								hosting/migrations/0049_auto_20181005_0736.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								hosting/migrations/0049_auto_20181005_0736.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  | # Generated by Django 1.9.4 on 2018-10-05 07:36 | ||||||
|  | from __future__ import unicode_literals | ||||||
|  | 
 | ||||||
|  | from django.db import migrations, models | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('hosting', '0048_auto_20181003_0757'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AlterField( | ||||||
|  |             model_name='genericproduct', | ||||||
|  |             name='product_slug', | ||||||
|  |             field=models.SlugField(help_text='An mandatory unique slug for the product', unique=True), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
|  | @ -9,8 +9,8 @@ from django.utils.functional import cached_property | ||||||
| 
 | 
 | ||||||
| from datacenterlight.models import VMPricing, VMTemplate | from datacenterlight.models import VMPricing, VMTemplate | ||||||
| from membership.models import StripeCustomer, CustomUser | from membership.models import StripeCustomer, CustomUser | ||||||
| from utils.models import BillingAddress |  | ||||||
| from utils.mixins import AssignPermissionsMixin | from utils.mixins import AssignPermissionsMixin | ||||||
|  | from utils.models import BillingAddress | ||||||
| from utils.stripe_utils import StripeUtils | from utils.stripe_utils import StripeUtils | ||||||
| 
 | 
 | ||||||
| logger = logging.getLogger(__name__) | logger = logging.getLogger(__name__) | ||||||
|  | @ -61,6 +61,30 @@ class OrderDetail(AssignPermissionsMixin, models.Model): | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | class GenericProduct(AssignPermissionsMixin, models.Model): | ||||||
|  |     permissions = ('view_genericproduct',) | ||||||
|  |     product_name = models.CharField(max_length=128, default="") | ||||||
|  |     product_slug = models.SlugField( | ||||||
|  |         unique=True, | ||||||
|  |         help_text=( | ||||||
|  |             'An mandatory unique slug for the product' | ||||||
|  |         ) | ||||||
|  |     ) | ||||||
|  |     product_description = models.CharField(max_length=500, default="") | ||||||
|  |     created_at = models.DateTimeField(auto_now_add=True) | ||||||
|  |     product_price = models.DecimalField(max_digits=6, decimal_places=2) | ||||||
|  |     product_vat = models.DecimalField(max_digits=6, decimal_places=4, default=0) | ||||||
|  |     product_is_subscription = models.BooleanField(default=True) | ||||||
|  | 
 | ||||||
|  |     def __str__(self): | ||||||
|  |         return self.product_name | ||||||
|  | 
 | ||||||
|  |     def get_actual_price(self): | ||||||
|  |         return round( | ||||||
|  |             self.product_price + (self.product_price * self.product_vat), 2 | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class HostingOrder(AssignPermissionsMixin, models.Model): | class HostingOrder(AssignPermissionsMixin, models.Model): | ||||||
|     ORDER_APPROVED_STATUS = 'Approved' |     ORDER_APPROVED_STATUS = 'Approved' | ||||||
|     ORDER_DECLINED_STATUS = 'Declined' |     ORDER_DECLINED_STATUS = 'Declined' | ||||||
|  | @ -80,7 +104,13 @@ class HostingOrder(AssignPermissionsMixin, models.Model): | ||||||
|         OrderDetail, null=True, blank=True, default=None, |         OrderDetail, null=True, blank=True, default=None, | ||||||
|         on_delete=models.SET_NULL |         on_delete=models.SET_NULL | ||||||
|     ) |     ) | ||||||
| 
 |     generic_product = models.ForeignKey( | ||||||
|  |         GenericProduct, null=True, blank=True, default=None, | ||||||
|  |         on_delete=models.SET_NULL | ||||||
|  |     ) | ||||||
|  |     generic_payment_description = models.CharField( | ||||||
|  |         max_length=500, null=True | ||||||
|  |     ) | ||||||
|     permissions = ('view_hostingorder',) |     permissions = ('view_hostingorder',) | ||||||
| 
 | 
 | ||||||
|     class Meta: |     class Meta: | ||||||
|  | @ -89,11 +119,18 @@ class HostingOrder(AssignPermissionsMixin, models.Model): | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|     def __str__(self): |     def __str__(self): | ||||||
|         return ("Order Nr: #{} - VM_ID: {} - {} - {} - " |         hosting_order_str = ("Order Nr: #{} - VM_ID: {} - {} - {} - " | ||||||
|                              "Specs: {} - Price: {}").format( |                              "Specs: {} - Price: {}").format( | ||||||
|             self.id, self.vm_id, self.customer.user.email, self.created_at, |             self.id, self.vm_id, self.customer.user.email, self.created_at, | ||||||
|             self.order_detail, self.price |             self.order_detail, self.price | ||||||
|         ) |         ) | ||||||
|  |         if self.generic_product_id is not None: | ||||||
|  |             hosting_order_str += " - Generic Payment" | ||||||
|  |             if self.stripe_charge_id is not None: | ||||||
|  |                 hosting_order_str += " - One time charge" | ||||||
|  |             else: | ||||||
|  |                 hosting_order_str += " - Recurring" | ||||||
|  |         return hosting_order_str | ||||||
| 
 | 
 | ||||||
|     @cached_property |     @cached_property | ||||||
|     def status(self): |     def status(self): | ||||||
|  |  | ||||||
|  | @ -22,6 +22,39 @@ function setBrandIcon(brand) { | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| $(document).ready(function () { | $(document).ready(function () { | ||||||
|  |     $(function () { | ||||||
|  |         $("select#id_generic_payment_form-product_name").change(function () { | ||||||
|  |             var gp_form = $('#generic-payment-form'); | ||||||
|  |             $.ajax({ | ||||||
|  |                 url: gp_form.attr('action'), | ||||||
|  |                 type: 'POST', | ||||||
|  |                 data: gp_form.serialize(), | ||||||
|  |                 init: function () { | ||||||
|  |                     console.log("init") | ||||||
|  |                 }, | ||||||
|  |                 success: function (data) { | ||||||
|  |                     if (data.amount !== undefined) { | ||||||
|  |                         $("#id_generic_payment_form-amount").val(data.amount); | ||||||
|  |                         if (data.isSubscription) { | ||||||
|  |                             $('#id_generic_payment_form-recurring').prop('checked', true); | ||||||
|  |                         } else { | ||||||
|  |                             $('#id_generic_payment_form-recurring').prop('checked', false); | ||||||
|  |                         } | ||||||
|  |                     } else { | ||||||
|  |                         $("#id_generic_payment_form-amount").val(''); | ||||||
|  |                         $('#id_generic_payment_form-recurring').prop('checked', false); | ||||||
|  |                         console.log("No product found") | ||||||
|  |                     } | ||||||
|  |                 }, | ||||||
|  |                 error: function (xmlhttprequest, textstatus, message) { | ||||||
|  |                     $("#id_generic_payment_form-amount").val(''); | ||||||
|  |                     $('#id_generic_payment_form-recurring').prop('checked', false); | ||||||
|  |                     console.log("Error fetching product") | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |         }) | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|     $.ajaxSetup({ |     $.ajaxSetup({ | ||||||
|         beforeSend: function (xhr, settings) { |         beforeSend: function (xhr, settings) { | ||||||
|             function getCookie(name) { |             function getCookie(name) { | ||||||
|  | @ -124,17 +157,35 @@ $(document).ready(function () { | ||||||
|         $('#billing-form').submit(); |         $('#billing-form').submit(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     function getCookie(name) { | ||||||
|  |         var value = "; " + document.cookie; | ||||||
|  |         var parts = value.split("; " + name + "="); | ||||||
|  |         if (parts.length === 2) return parts.pop().split(";").shift(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     function submitBillingForm() { | ||||||
|  |         var billing_form = $('#billing-form'); | ||||||
|  |         var recurring_input = $('#id_generic_payment_form-recurring'); | ||||||
|  |         billing_form.append('<input type="hidden" name="generic_payment_form-product_name" value="' + $('#id_generic_payment_form-product_name').val() + '" />'); | ||||||
|  |         billing_form.append('<input type="hidden" name="generic_payment_form-amount" value="' + $('#id_generic_payment_form-amount').val() + '" />'); | ||||||
|  |         if (recurring_input.attr('type') === 'hidden') { | ||||||
|  |             billing_form.append('<input type="hidden" name="generic_payment_form-recurring" value="' + (recurring_input.val() === 'True' ? 'on' : '') + '" />'); | ||||||
|  |         } else { | ||||||
|  |             billing_form.append('<input type="hidden" name="generic_payment_form-recurring" value="' + (recurring_input.prop('checked') ? 'on' : '') + '" />'); | ||||||
|  |         } | ||||||
|  |         billing_form.append('<input type="hidden" name="generic_payment_form-description" value="' + $('#id_generic_payment_form-description').val() + '" />'); | ||||||
|  |         billing_form.submit(); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     var $form_new = $('#payment-form-new'); |     var $form_new = $('#payment-form-new'); | ||||||
|     $form_new.submit(payWithStripe_new); |     $form_new.submit(payWithStripe_new); | ||||||
| 
 |  | ||||||
|     function payWithStripe_new(e) { |     function payWithStripe_new(e) { | ||||||
|         e.preventDefault(); |         e.preventDefault(); | ||||||
| 
 | 
 | ||||||
|         function stripeTokenHandler(token) { |         function stripeTokenHandler(token) { | ||||||
|             // Insert the token ID into the form so it gets submitted to the server
 |             // Insert the token ID into the form so it gets submitted to the server
 | ||||||
|             $('#id_token').val(token.id); |             $('#id_token').val(token.id); | ||||||
|             $('#billing-form').submit(); |             submitBillingForm(); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -199,7 +250,7 @@ $(document).ready(function () { | ||||||
|     $('.credit-card-info .btn.choice-btn').click(function () { |     $('.credit-card-info .btn.choice-btn').click(function () { | ||||||
|         var id = this.dataset['id_card']; |         var id = this.dataset['id_card']; | ||||||
|         $('#id_card').val(id); |         $('#id_card').val(id); | ||||||
|             $('#billing-form').submit(); |         submitBillingForm(); | ||||||
|     }); |     }); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -134,3 +134,15 @@ $(document).ready(function() { | ||||||
|         $(this).find('.modal-footer .btn').addClass('hide'); |         $(this).find('.modal-footer .btn').addClass('hide'); | ||||||
|     }) |     }) | ||||||
| }); | }); | ||||||
|  | 
 | ||||||
|  | window.onload = function () { | ||||||
|  |     var locale_dates = document.getElementsByClassName("locale_date"); | ||||||
|  |     var formats = ['YYYY-MM-DD hh:mm a']; | ||||||
|  |     var i; | ||||||
|  |     for (i = 0; i < locale_dates.length; i++) { | ||||||
|  |         var oldDate = moment.utc(locale_dates[i].textContent, formats); | ||||||
|  |         var outputFormat = locale_dates[i].getAttribute('data-format') || oldDate._f; | ||||||
|  |         locale_dates[i].innerHTML = oldDate.local().format(outputFormat); | ||||||
|  |         locale_dates[i].className += ' done'; | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  | @ -39,7 +39,7 @@ | ||||||
|                     {% endif %} |                     {% endif %} | ||||||
|                 </span> |                 </span> | ||||||
|             </p> |             </p> | ||||||
|             {% if order %} |             {% if order and vm %} | ||||||
|                 <p> |                 <p> | ||||||
|                     <strong>{% trans "Status" %}: </strong> |                     <strong>{% trans "Status" %}: </strong> | ||||||
|                     <strong> |                     <strong> | ||||||
|  | @ -93,6 +93,7 @@ | ||||||
|             <hr> |             <hr> | ||||||
|             <div> |             <div> | ||||||
|                 <h4>{% trans "Order summary" %}</h4> |                 <h4>{% trans "Order summary" %}</h4> | ||||||
|  |                 {% if vm %} | ||||||
|                     <p> |                     <p> | ||||||
|                         <strong>{% trans "Product" %}:</strong>  |                         <strong>{% trans "Product" %}:</strong>  | ||||||
|                         {% if vm.name %} |                         {% if vm.name %} | ||||||
|  | @ -164,6 +165,32 @@ | ||||||
|                             </p> |                             </p> | ||||||
|                         </div> |                         </div> | ||||||
|                     </div> |                     </div> | ||||||
|  |                 {% else %} | ||||||
|  |                     <p> | ||||||
|  |                         <strong>{% trans "Product" %}:</strong>  | ||||||
|  |                         {{ product_name }} | ||||||
|  |                     </p> | ||||||
|  |                     <div class="row"> | ||||||
|  |                         <div class="col-sm-6"> | ||||||
|  |                             <p> | ||||||
|  |                                 <span>{% trans "Amount" %}: </span> | ||||||
|  |                                 <strong class="pull-right">{{order.price|floatformat:2|intcomma}} CHF</strong> | ||||||
|  |                             </p> | ||||||
|  |                             {% if order.generic_payment_description %} | ||||||
|  |                                 <p> | ||||||
|  |                                     <span>{% trans "Description" %}: </span> | ||||||
|  |                                     <strong class="pull-right">{{order.generic_payment_description}}</strong> | ||||||
|  |                                 </p> | ||||||
|  |                             {% endif %} | ||||||
|  |                             {% if order.subscription_id %} | ||||||
|  |                             <p> | ||||||
|  |                                 <span>{% trans "Recurring" %}: </span> | ||||||
|  |                                 <strong class="pull-right">{{order.created_at|date:'d'|ordinal}} {% trans "of every month" %}</strong> | ||||||
|  |                             </p> | ||||||
|  |                             {% endif %} | ||||||
|  |                         </div> | ||||||
|  |                     </div> | ||||||
|  |                 {% endif %} | ||||||
|             </div> |             </div> | ||||||
|             <hr class="thin-hr"> |             <hr class="thin-hr"> | ||||||
|         </div> |         </div> | ||||||
|  | @ -229,17 +256,6 @@ | ||||||
| <script type="text/javascript"> | <script type="text/javascript"> | ||||||
|     {% trans "Some problem encountered. Please try again later." as err_msg %} |     {% trans "Some problem encountered. Please try again later." as err_msg %} | ||||||
|     var create_vm_error_message = '{{err_msg|safe}}'; |     var create_vm_error_message = '{{err_msg|safe}}'; | ||||||
|     window.onload = function () { |  | ||||||
|         var locale_dates = document.getElementsByClassName("locale_date"); |  | ||||||
|         var formats = ['YYYY-MM-DD hh:mm a'] |  | ||||||
|         var i; |  | ||||||
|         for (i = 0; i < locale_dates.length; i++) { |  | ||||||
|             var oldDate = moment.utc(locale_dates[i].textContent, formats); |  | ||||||
|             var outputFormat = locale_dates[i].getAttribute('data-format') || oldDate._f; |  | ||||||
|             locale_dates[i].innerHTML = oldDate.local().format(outputFormat); |  | ||||||
|             locale_dates[i].className += ' done'; |  | ||||||
|         } |  | ||||||
|     }; |  | ||||||
| </script> | </script> | ||||||
| {%endblock%} | {%endblock%} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -28,7 +28,7 @@ | ||||||
|             {% for order in orders %} |             {% for order in orders %} | ||||||
|                 <tr> |                 <tr> | ||||||
|                     <td class="xs-td-inline" data-header="{% trans 'Order Nr.' %}">{{ order.id }}</td> |                     <td class="xs-td-inline" data-header="{% trans 'Order Nr.' %}">{{ order.id }}</td> | ||||||
|                     <td class="xs-td-bighalf" data-header="{% trans 'Date' %}">{{ order.created_at | date:"M d, Y H:i" }}</td> |                     <td class="xs-td-bighalf locale_date" data-header="{% trans 'Date' %}">{{ order.created_at | date:'Y-m-d h:i a' }}</td> | ||||||
|                     <td class="xs-td-smallhalf" data-header="{% trans 'Amount' %}">{{ order.price|floatformat:2|intcomma }}</td> |                     <td class="xs-td-smallhalf" data-header="{% trans 'Amount' %}">{{ order.price|floatformat:2|intcomma }}</td> | ||||||
|                     <td class="text-right last-td"> |                     <td class="text-right last-td"> | ||||||
|                         <a class="btn btn-order-detail" href="{% url 'hosting:orders' order.pk %}">{% trans 'See Invoice' %}</a> |                         <a class="btn btn-order-detail" href="{% url 'hosting:orders' order.pk %}">{% trans 'See Invoice' %}</a> | ||||||
|  |  | ||||||
|  | @ -18,8 +18,8 @@ | ||||||
|                     <h3>{%trans "Billing Address" %}</h3> |                     <h3>{%trans "Billing Address" %}</h3> | ||||||
|                     <hr> |                     <hr> | ||||||
|                     <form role="form" id="billing-form" method="post" action="" novalidate> |                     <form role="form" id="billing-form" method="post" action="" novalidate> | ||||||
|                         {% for field in form %} |  | ||||||
|                         {% csrf_token %} |                         {% csrf_token %} | ||||||
|  |                         {% for field in form %} | ||||||
|                             {% bootstrap_field field show_label=False type='fields' bound_css_class='' %} |                             {% bootstrap_field field show_label=False type='fields' bound_css_class='' %} | ||||||
|                         {% endfor %} |                         {% endfor %} | ||||||
|                         <div class="form-group text-right"> |                         <div class="form-group text-right"> | ||||||
|  |  | ||||||
|  | @ -59,7 +59,8 @@ from .forms import ( | ||||||
| ) | ) | ||||||
| from .mixins import ProcessVMSelectionMixin, HostingContextMixin | from .mixins import ProcessVMSelectionMixin, HostingContextMixin | ||||||
| from .models import ( | from .models import ( | ||||||
|     HostingOrder, HostingBill, HostingPlan, UserHostingKey, VMDetail |     HostingOrder, HostingBill, HostingPlan, UserHostingKey, VMDetail, | ||||||
|  |     GenericProduct | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| logger = logging.getLogger(__name__) | logger = logging.getLogger(__name__) | ||||||
|  | @ -862,7 +863,15 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView): | ||||||
|                 raise Http404 |                 raise Http404 | ||||||
| 
 | 
 | ||||||
|         if obj is not None: |         if obj is not None: | ||||||
|  |             if obj.generic_product_id is not None: | ||||||
|  |                 # generic payment case | ||||||
|  |                 logger.debug("Generic payment case") | ||||||
|  |                 context['product_name'] = GenericProduct.objects.get( | ||||||
|  |                     id=obj.generic_product_id | ||||||
|  |                 ).product_name | ||||||
|  |             else: | ||||||
|                 # invoice for previous order |                 # invoice for previous order | ||||||
|  |                 logger.debug("Invoice of VM order") | ||||||
|                 try: |                 try: | ||||||
|                     vm_detail = VMDetail.objects.get(vm_id=obj.vm_id) |                     vm_detail = VMDetail.objects.get(vm_id=obj.vm_id) | ||||||
|                     context['vm'] = vm_detail.__dict__ |                     context['vm'] = vm_detail.__dict__ | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue