From a7fa52490cee7bdad04cc116680e0875f41c1240 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 22 Sep 2018 08:16:26 +0200 Subject: [PATCH 01/82] Create GenericPaymentForm --- hosting/forms.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/hosting/forms.py b/hosting/forms.py index 7beab60f..bbfda8b8 100644 --- a/hosting/forms.py +++ b/hosting/forms.py @@ -52,6 +52,21 @@ class HostingUserLoginForm(forms.Form): raise forms.ValidationError(_("User does not exist")) +class GenericPaymentForm(forms.Form): + amount = forms.DecimalField(widget=forms.TextInput(), + max_digits=8, + decimal_places=2, + min_value=1) + recurring = forms.BooleanField() + description = forms.Textarea() + + class Meta: + fields = ['amount', 'recurring', 'description'] + + def clean(self): + return self.cleaned_data + + class HostingUserSignupForm(forms.ModelForm): confirm_password = forms.CharField(label=_("Confirm Password"), widget=forms.PasswordInput()) From 429dd10b750fdce83b36f5be823bb6da3235e601 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 22 Sep 2018 08:17:43 +0200 Subject: [PATCH 02/82] Modify dcl landing_payment.html to accomodate the new generic payment form --- .../datacenterlight/landing_payment.html | 72 +++++++++++-------- 1 file changed, 42 insertions(+), 30 deletions(-) diff --git a/datacenterlight/templates/datacenterlight/landing_payment.html b/datacenterlight/templates/datacenterlight/landing_payment.html index 4c43f41c..aa0edd35 100644 --- a/datacenterlight/templates/datacenterlight/landing_payment.html +++ b/datacenterlight/templates/datacenterlight/landing_payment.html @@ -67,36 +67,48 @@
-

{%trans "Your Order" %}

-
-
-

{% trans "Cores"%} {{request.session.specs.cpu|floatformat}}

-
-

{% trans "Memory"%} {{request.session.specs.memory|floatformat}} GB

-
-

{% trans "Disk space"%} {{request.session.specs.disk_size|floatformat}} GB

-
-

{% trans "Configuration"%} {{request.session.template.name}}

-
-

- {%trans "Total" %}   - - ({% if vm_pricing.vat_inclusive %}{%trans "including VAT" %}{% else %}{%trans "excluding VAT" %}{% endif %}) - - {{request.session.specs.price|intcomma}} CHF/{% trans "Month" %} -

-
- {% if vm_pricing.discount_amount %} -

- {%trans "Discount" as discount_name %} - {{ vm_pricing.discount_name|default:discount_name }}   - - {{ vm_pricing.discount_amount }} CHF/{% trans "Month" %} -

-

- ({% trans "Will be applied at checkout" %}) -

- {% endif %} -
+ {% if generic_payment_form %} +

{%trans "Make a payment" %}

+
+
+ {% for field in generic_payment_form %} + {% csrf_token %} + {% bootstrap_field field show_label=False type='fields'%} + {% endfor %} +

{{generic_payment_form.non_field_errors|striptags}}

+
+ {% else %} +

{%trans "Your Order" %}

+
+
+

{% trans "Cores"%} {{request.session.specs.cpu|floatformat}}

+
+

{% trans "Memory"%} {{request.session.specs.memory|floatformat}} GB

+
+

{% trans "Disk space"%} {{request.session.specs.disk_size|floatformat}} GB

+
+

{% trans "Configuration"%} {{request.session.template.name}}

+
+

+ {%trans "Total" %}   + + ({% if vm_pricing.vat_inclusive %}{%trans "including VAT" %}{% else %}{%trans "excluding VAT" %}{% endif %}) + + {{request.session.specs.price|intcomma}} CHF/{% trans "Month" %} +

+
+ {% if vm_pricing.discount_amount %} +

+ {%trans "Discount" as discount_name %} + {{ vm_pricing.discount_name|default:discount_name }}   + - {{ vm_pricing.discount_amount }} CHF/{% trans "Month" %} +

+

+ ({% trans "Will be applied at checkout" %}) +

+ {% endif %} +
+ {% endif %}
From 730492089b34cd5e5dfbaa454fcc8a9c021690cd Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 22 Sep 2018 08:20:49 +0200 Subject: [PATCH 03/82] Modify PaymentOrderView to accomodate the new generic payment case (WIP) --- datacenterlight/views.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index be4e5700..2818f707 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -12,7 +12,7 @@ from django.utils.translation import get_language, ugettext_lazy as _ from django.views.decorators.cache import cache_control from django.views.generic import FormView, CreateView, DetailView -from hosting.forms import HostingUserLoginForm +from hosting.forms import HostingUserLoginForm, GenericPaymentForm from hosting.models import HostingOrder, UserCardDetail from membership.models import CustomUser, StripeCustomer from opennebula_api.serializers import VMTemplateSerializer @@ -242,15 +242,26 @@ class PaymentOrderView(FormView): 'login_form': HostingUserLoginForm(prefix='login_form'), 'billing_address_form': billing_address_form, 'cms_integration': get_cms_integration('default'), - 'vm_pricing': VMPricing.get_vm_pricing_by_name( - self.request.session['specs']['pricing_name'] - ) }) + + if self.request.session['generic_payment_type'] == 'generic': + context.update({'generic_payment_form': GenericPaymentForm( + prefix='generic_payment_form' + ), }) + else: + context.update({ + 'vm_pricing': VMPricing.get_vm_pricing_by_name( + self.request.session['specs']['pricing_name'] + ) + }) + return context @cache_control(no_cache=True, must_revalidate=True, no_store=True) def get(self, request, *args, **kwargs): - if 'specs' not in request.session: + if 'type' in request.GET and request.GET['type'] == 'generic': + request.session['generic_payment_type'] = request.GET['type'] + elif 'specs' not in request.session: return HttpResponseRedirect(reverse('datacenterlight:index')) return self.render_to_response(self.get_context_data()) @@ -296,8 +307,8 @@ class PaymentOrderView(FormView): except UserCardDetail.DoesNotExist as e: ex = str(e) logger.error("Card Id: {card_id}, Exception: {ex}".format( - card_id=card_id, ex=ex - ) + card_id=card_id, ex=ex + ) ) msg = _("An error occurred. Details: {}".format(ex)) messages.add_message( @@ -450,7 +461,8 @@ class OrderConfirmationView(DetailView): 'brand': card_details_response['brand'], '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: ucd = UserCardDetail.get_user_card_details( stripe_customer_obj, card_details_response From c7edcdc8b18d1b06721da15f3a929b41da59693b Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 22 Sep 2018 23:51:39 +0200 Subject: [PATCH 04/82] Change description to CharField and set its height --- hosting/forms.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hosting/forms.py b/hosting/forms.py index bbfda8b8..a593e906 100644 --- a/hosting/forms.py +++ b/hosting/forms.py @@ -58,7 +58,9 @@ class GenericPaymentForm(forms.Form): decimal_places=2, min_value=1) recurring = forms.BooleanField() - description = forms.Textarea() + description = forms.CharField( + widget=forms.Textarea(attrs={'style': "height: 150px;"}) + ) class Meta: fields = ['amount', 'recurring', 'description'] From 332e7d662461d8d794962aaaaaa5f4dc03f3fc5e Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 23 Sep 2018 12:34:13 +0200 Subject: [PATCH 05/82] Add generic_payment_id field to HostingOrder migration and reflect generic payments in adminsite --- .../0048_hostingorder_generic_payment_id.py | 20 +++++++++++++++++++ hosting/models.py | 16 ++++++++++++--- 2 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 hosting/migrations/0048_hostingorder_generic_payment_id.py diff --git a/hosting/migrations/0048_hostingorder_generic_payment_id.py b/hosting/migrations/0048_hostingorder_generic_payment_id.py new file mode 100644 index 00000000..36488efb --- /dev/null +++ b/hosting/migrations/0048_hostingorder_generic_payment_id.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2018-09-23 09:34 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hosting', '0047_auto_20180821_1240'), + ] + + operations = [ + migrations.AddField( + model_name='hostingorder', + name='generic_payment_id', + field=models.CharField(editable=False, max_length=128, null=True), + ), + ] diff --git a/hosting/models.py b/hosting/models.py index abc4c428..15ebb07c 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -9,8 +9,8 @@ from django.utils.functional import cached_property from datacenterlight.models import VMPricing, VMTemplate from membership.models import StripeCustomer, CustomUser -from utils.models import BillingAddress from utils.mixins import AssignPermissionsMixin +from utils.models import BillingAddress from utils.stripe_utils import StripeUtils logger = logging.getLogger(__name__) @@ -80,6 +80,9 @@ class HostingOrder(AssignPermissionsMixin, models.Model): OrderDetail, null=True, blank=True, default=None, on_delete=models.SET_NULL ) + generic_payment_id = models.CharField( + max_length=128, null=True, editable=False + ) permissions = ('view_hostingorder',) @@ -89,11 +92,18 @@ class HostingOrder(AssignPermissionsMixin, models.Model): ) def __str__(self): - return ("Order Nr: #{} - VM_ID: {} - {} - {} - " - "Specs: {} - Price: {}").format( + hosting_order_str = ("Order Nr: #{} - VM_ID: {} - {} - {} - " + "Specs: {} - Price: {}").format( self.id, self.vm_id, self.customer.user.email, self.created_at, self.order_detail, self.price ) + if self.generic_payment_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 def status(self): From e94ecfe52ceb49b03e9bcfbc542d37d4d16354bc Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 23 Sep 2018 12:49:52 +0200 Subject: [PATCH 06/82] Change recurring and description to non-required fields + Change amount to Floatfield --- hosting/forms.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/hosting/forms.py b/hosting/forms.py index a593e906..fb3ce98a 100644 --- a/hosting/forms.py +++ b/hosting/forms.py @@ -53,21 +53,18 @@ class HostingUserLoginForm(forms.Form): class GenericPaymentForm(forms.Form): - amount = forms.DecimalField(widget=forms.TextInput(), - max_digits=8, - decimal_places=2, - min_value=1) - recurring = forms.BooleanField() + amount = forms.FloatField( + widget=forms.TextInput(), max_value=999999, min_value=1 + ) + recurring = forms.BooleanField(required=False) description = forms.CharField( - widget=forms.Textarea(attrs={'style': "height: 150px;"}) + widget=forms.Textarea(attrs={'style': "height: 150px;"}), + required=False ) class Meta: fields = ['amount', 'recurring', 'description'] - def clean(self): - return self.cleaned_data - class HostingUserSignupForm(forms.ModelForm): confirm_password = forms.CharField(label=_("Confirm Password"), From bce47032aba2cf810a9c4214d8a2ca353787ed5f Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 23 Sep 2018 13:03:47 +0200 Subject: [PATCH 07/82] Handle generic payment separately in order_detail.html --- .../datacenterlight/order_detail.html | 127 +++++++++++------- 1 file changed, 78 insertions(+), 49 deletions(-) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index 49347ba2..82b5aaae 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -47,61 +47,82 @@

{% trans "Order summary" %}

-

- {% trans "Product" %}:  - {{ request.session.template.name }} -

-
-
-

- {% trans "Cores" %}: - {{vm.cpu|floatformat}} -

-

- {% trans "Memory" %}: - {{vm.memory|intcomma}} GB -

-

- {% trans "Disk space" %}: - {{vm.disk_size|intcomma}} GB -

-
-
-
-
- {% if vm.vat > 0 or vm.discount.amount > 0 %} -
-
- {% if vm.vat > 0 %} + {% if generic_payment_details %} +
+
+

+ {% trans "Amount" %}: + CHF {{generic_payment_details.amount|floatformat:2|intcomma}} +

+

+ {% trans "Description" %}: + {{generic_payment_details.description}} +

+ {% if generic_payment_details.recurring %}

- {% trans "Subtotal" %} - {{vm.price|floatformat:2|intcomma}} CHF -

-

- {% trans "VAT" %} ({{ vm.vat_percent|floatformat:2|intcomma }}%) - {{vm.vat|floatformat:2|intcomma}} CHF -

- {% endif %} - {% if vm.discount.amount > 0 %} -

- {%trans "Discount" as discount_name %} - {{ vm.discount.name|default:discount_name }} - - {{ vm.discount.amount }} CHF + {% trans "Recurring" %}: + Yes

{% endif %}
-
-
+ {% else %} +

+ {% trans "Product" %}:  + {{ request.session.template.name }} +

+
+
+

+ {% trans "Cores" %}: + {{vm.cpu|floatformat}} +

+

+ {% trans "Memory" %}: + {{vm.memory|intcomma}} GB +

+

+ {% trans "Disk space" %}: + {{vm.disk_size|intcomma}} GB +

+
+
+
+
+ {% if vm.vat > 0 or vm.discount.amount > 0 %} +
+
+ {% if vm.vat > 0 %} +

+ {% trans "Subtotal" %} + {{vm.price|floatformat:2|intcomma}} CHF +

+

+ {% trans "VAT" %} ({{ vm.vat_percent|floatformat:2|intcomma }}%) + {{vm.vat|floatformat:2|intcomma}} CHF +

+ {% endif %} + {% if vm.discount.amount > 0 %} +

+ {%trans "Discount" as discount_name %} + {{ vm.discount.name|default:discount_name }} + - {{ vm.discount.amount }} CHF +

+ {% endif %} +
+
+
+
+
+ {% endif %} +
+

+ {% trans "Total" %} + {{vm.total_price|floatformat:2|intcomma}} CHF +

+
{% endif %} -
-

- {% trans "Total" %} - {{vm.total_price|floatformat:2|intcomma}} CHF -

-
-

@@ -109,7 +130,15 @@ {% csrf_token %}
-
{% 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 %}.
+ {% if generic_payment_details %} + {% if generic_payment_details.recurring %} +
{% 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 %}.
+ {% else %} +
{% 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 %}.
+ {% endif %} + {% else %} +
{% 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 %}.
+ {% endif %}
{% else %} +

+ {% trans "Product" %}:  + {{ product_name }} +

{% trans "Amount" %}: {{order.price|floatformat:2|intcomma}} CHF

+ {% if order.generic_payment_description %} +

+ {% trans "Description" %}: + {{order.generic_payment_description}} +

+ {% endif %} {% if order.subscription_id %}

{% trans "Recurring" %}: @@ -179,9 +189,6 @@

{% endif %}
-
-
-
{% endif %}
diff --git a/hosting/views.py b/hosting/views.py index 8b6e061d..c9b7ab08 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -59,7 +59,8 @@ from .forms import ( ) from .mixins import ProcessVMSelectionMixin, HostingContextMixin from .models import ( - HostingOrder, HostingBill, HostingPlan, UserHostingKey, VMDetail + HostingOrder, HostingBill, HostingPlan, UserHostingKey, VMDetail, + GenericProduct ) logger = logging.getLogger(__name__) @@ -865,7 +866,9 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView): 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 logger.debug("Invoice of VM order") From 48ba6a61660b1e0582bf14da69ec0179198ccc31 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 26 Sep 2018 09:13:25 +0200 Subject: [PATCH 33/82] Remove disabled on amount/recurring fields --- hosting/forms.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/hosting/forms.py b/hosting/forms.py index aae50ac5..9eb5b2d2 100644 --- a/hosting/forms.py +++ b/hosting/forms.py @@ -52,29 +52,34 @@ class HostingUserLoginForm(forms.Form): 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 = forms.ModelChoiceField( + product_name = ProductModelChoiceField( queryset=GenericProduct.objects.all().order_by('product_name'), empty_label=_("Choose a product"), - to_field_name='product_name' ) amount = forms.FloatField( widget=forms.TextInput( - attrs={'placeholder': _('Amount in CHF')} + attrs={'placeholder': _('Amount in CHF'), + 'readonly': 'readonly'} ), max_value=999999, min_value=1, - disabled=True ) recurring = forms.BooleanField(required=False, label=_("Recurring monthly"), - disabled=True) + ) description = forms.CharField( widget=forms.Textarea(attrs={'style': "height: 100px;"}), required=False ) class Meta: + model = GenericProduct fields = ['product_name', 'amount', 'recurring', 'description'] From 508360472acc19366668d60a90a92942ffa94cf0 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 26 Sep 2018 09:14:11 +0200 Subject: [PATCH 34/82] Add amount/recurring form fields validation --- hosting/forms.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/hosting/forms.py b/hosting/forms.py index 9eb5b2d2..928f910e 100644 --- a/hosting/forms.py +++ b/hosting/forms.py @@ -82,6 +82,20 @@ class GenericPaymentForm(forms.Form): 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 HostingUserSignupForm(forms.ModelForm): confirm_password = forms.CharField(label=_("Confirm Password"), From 4575ff60ecb890547032ee3d1d4b546193b8656f Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 26 Sep 2018 09:15:24 +0200 Subject: [PATCH 35/82] Refactor validation code + Add product_id to context --- datacenterlight/views.py | 48 ++++++++-------------------------------- 1 file changed, 9 insertions(+), 39 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 736d2e63..66f8c642 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -279,8 +279,7 @@ class PaymentOrderView(FormView): product = None try: product = GenericProduct.objects.get( - product_name= - request.POST['generic_payment_form-product_name'] + id=request.POST['generic_payment_form-product_name'] ) except GenericProduct.DoesNotExist as dne: logger.error( @@ -341,37 +340,11 @@ class PaymentOrderView(FormView): ) if generic_payment_form.is_valid(): logger.debug("Generic payment form is valid.") - product = None - try: - product = GenericProduct.objects.get( - product_name= - request.POST['generic_payment_form-product_name'] - ) - except GenericProduct.DoesNotExist as dne: - err_msg = _( - "The requested product '{}' does not exist".format( - request.POST[ - 'generic_payment_form-product_name'] - ) - ) - logger.error(err_msg) - raise ValidationError(err_msg) - 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() + product = generic_payment_form.cleaned_data.get( + 'product_name' + ) gp_details = { - "product_name": generic_payment_form.cleaned_data.get( - 'product_name' - ), + "product_name": product.product_name, "amount": generic_payment_form.cleaned_data.get( 'amount' ), @@ -381,14 +354,10 @@ class PaymentOrderView(FormView): "description": generic_payment_form.cleaned_data.get( 'description' ), + "product_id": product.id } - if (product.get_actual_price() != gp_details['amount'] or - product.isSubscription != - (True if gp_details["recurring"] else False)): - raise ValidationError( - _("Product parameters do not match") - ) - gp_details['product_id'] = product.id + + # gp_details['product_id'] = product.id request.session["generic_payment_details"] = ( gp_details ) @@ -858,6 +827,7 @@ class OrderConfirmationView(DetailView): 'description': gp_details['description'], 'recurring': gp_details['recurring'], 'product_name': gp_details['product_name'], + 'product_id': gp_details['product_id'], 'order_id': order.id } From 41cba9daa332a9e3032b8511530d24c36629e508 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 26 Sep 2018 09:15:49 +0200 Subject: [PATCH 36/82] Show product name in dcl order detail template --- .../templates/datacenterlight/order_detail.html | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index 82b5aaae..53a5d427 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -48,16 +48,22 @@

{% trans "Order summary" %}

{% if generic_payment_details %} +

+ {% trans "Product" %}:  + {{ generic_payment_details.product_name }} +

{% trans "Amount" %}: CHF {{generic_payment_details.amount|floatformat:2|intcomma}}

-

- {% trans "Description" %}: - {{generic_payment_details.description}} -

+ {% if generic_payment_details.description %} +

+ {% trans "Description" %}: + {{generic_payment_details.description}} +

+ {% endif %} {% if generic_payment_details.recurring %}

{% trans "Recurring" %}: From 1f990b1ab706c27beeabecc2c604d31eedcff2da Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 26 Sep 2018 09:24:47 +0200 Subject: [PATCH 37/82] Update migration --- hosting/migrations/0048_auto_20180926_0723.py | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 hosting/migrations/0048_auto_20180926_0723.py diff --git a/hosting/migrations/0048_auto_20180926_0723.py b/hosting/migrations/0048_auto_20180926_0723.py new file mode 100644 index 00000000..a5428ca5 --- /dev/null +++ b/hosting/migrations/0048_auto_20180926_0723.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2018-09-26 07:23 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion +import filer.fields.image +import utils.mixins + + +class Migration(migrations.Migration): + + dependencies = [ + ('filer', '0005_auto_20180217_1137'), + ('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_description', models.CharField(default='', max_length=500)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('product_url', models.URLField(blank=True, max_length=1000, null=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)), + ('product_image', filer.fields.image.FilerImageField(blank=True, help_text='The product image', null=True, on_delete=django.db.models.deletion.CASCADE, to='filer.Image')), + ], + 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'), + ), + ] From 99b11f013ff4d176dec2733b9acb854a58fb947b Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 26 Sep 2018 09:25:03 +0200 Subject: [PATCH 38/82] Remove old migration --- hosting/migrations/0048_auto_20180924_2354.py | 44 ------------------- 1 file changed, 44 deletions(-) delete mode 100644 hosting/migrations/0048_auto_20180924_2354.py diff --git a/hosting/migrations/0048_auto_20180924_2354.py b/hosting/migrations/0048_auto_20180924_2354.py deleted file mode 100644 index f7fbf8ac..00000000 --- a/hosting/migrations/0048_auto_20180924_2354.py +++ /dev/null @@ -1,44 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.4 on 2018-09-24 23:54 -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion -import filer.fields.image -import utils.mixins - - -class Migration(migrations.Migration): - - dependencies = [ - ('filer', '0005_auto_20180217_1137'), - ('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_description', models.CharField(default='', max_length=500)), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('product_url', models.URLField(blank=True, max_length=1000, null=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)), - ('product_image', filer.fields.image.FilerImageField(blank=True, help_text='The product image', null=True, on_delete=django.db.models.deletion.CASCADE, to='filer.Image')), - ], - 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'), - ), - ] From b021a8ed6ee788d3355be19ef11ade6f0e4e6b5a Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 26 Sep 2018 09:29:10 +0200 Subject: [PATCH 39/82] Remove filer dependency in migration --- hosting/migrations/0048_auto_20180926_0723.py | 1 - 1 file changed, 1 deletion(-) diff --git a/hosting/migrations/0048_auto_20180926_0723.py b/hosting/migrations/0048_auto_20180926_0723.py index a5428ca5..6c21ab03 100644 --- a/hosting/migrations/0048_auto_20180926_0723.py +++ b/hosting/migrations/0048_auto_20180926_0723.py @@ -11,7 +11,6 @@ import utils.mixins class Migration(migrations.Migration): dependencies = [ - ('filer', '0005_auto_20180217_1137'), ('hosting', '0047_auto_20180821_1240'), ] From 737681136f451388c2017d2edd6de0bddc434be6 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 26 Sep 2018 21:23:46 +0200 Subject: [PATCH 40/82] Correct flake8 error --- datacenterlight/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 66f8c642..b9a28487 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -295,8 +295,8 @@ class PaymentOrderView(FormView): ) ) product = GenericProduct.objects.all( - product_name= - request.POST['generic_payment_form-product_name'] + product_name=request. + POST['generic_payment_form-product_name'] ).first() if product is None: return JsonResponse({}) From 104128486627b3768587caf60f5523b1be6e5df6 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 26 Sep 2018 21:51:55 +0200 Subject: [PATCH 41/82] Refactor moment.js locale date code to virutal_machine_detail.js --- .../templates/datacenterlight/order_detail.html | 11 ----------- hosting/static/hosting/js/virtual_machine_detail.js | 12 ++++++++++++ hosting/templates/hosting/order_detail.html | 11 ----------- 3 files changed, 12 insertions(+), 22 deletions(-) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index 53a5d427..31933e12 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -186,16 +186,5 @@ {%endblock%} \ No newline at end of file diff --git a/hosting/static/hosting/js/virtual_machine_detail.js b/hosting/static/hosting/js/virtual_machine_detail.js index 43a5a01d..8f90933b 100644 --- a/hosting/static/hosting/js/virtual_machine_detail.js +++ b/hosting/static/hosting/js/virtual_machine_detail.js @@ -134,3 +134,15 @@ $(document).ready(function() { $(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'; + } +}; \ No newline at end of file diff --git a/hosting/templates/hosting/order_detail.html b/hosting/templates/hosting/order_detail.html index 0f92ef67..4a62e9fa 100644 --- a/hosting/templates/hosting/order_detail.html +++ b/hosting/templates/hosting/order_detail.html @@ -256,17 +256,6 @@ {%endblock%} From fcc113e9d9d00a9ed466a9d70ba6cd7c4289c8c4 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 26 Sep 2018 22:06:14 +0200 Subject: [PATCH 42/82] Add locale_date class to date fields so that we can localize --- hosting/templates/hosting/orders.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/templates/hosting/orders.html b/hosting/templates/hosting/orders.html index 140cc4c6..96d9e9e3 100644 --- a/hosting/templates/hosting/orders.html +++ b/hosting/templates/hosting/orders.html @@ -28,7 +28,7 @@ {% for order in orders %} {{ order.id }} - {{ order.created_at | date:"M d, Y H:i" }} + {{ order.created_at | date:'Y-m-d h:i a' }} {{ order.price|floatformat:2|intcomma }} {% trans 'See Invoice' %} From 84056a5b36006e2848a4a8405e37bdef718e7c0e Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 26 Sep 2018 22:21:59 +0200 Subject: [PATCH 43/82] Correct generic payment email as per Sanghee's corrections --- datacenterlight/views.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index b9a28487..18f770f0 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -847,14 +847,15 @@ class OrderConfirmationView(DetailView): "Confirmation of your payment"), 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, 'to': [user.get('email')], - 'body': ("Hi {name},\n\n" - "We just received a payment of CHF {amount} " - "from you. {recurring}.\n\n" + '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" "Your DataCenterLight Team".format( name=user.get('name'), amount=gp_details['amount'], recurring=( - 'This is a monthly recurring plan.' + ' This is a monthly recurring plan.' if gp_details['recurring'] else '' ) ) From 3148dbccf8f1c6b7a34b8c1d39d6759cd68672b1 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 26 Sep 2018 22:31:06 +0200 Subject: [PATCH 44/82] Translate text + reformat --- datacenterlight/views.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 18f770f0..420f095f 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -848,15 +848,15 @@ class OrderConfirmationView(DetailView): '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" - "Your DataCenterLight Team".format( + "thank you for your order!\n" + "We have just received a payment of CHF {amount:.2f}" + " from you.{recurring}\n\n" + "Your DataCenterLight Team".format( name=user.get('name'), amount=gp_details['amount'], recurring=( - ' This is a monthly recurring plan.' - if gp_details['recurring'] else '' + _(' This is a monthly recurring plan.') + if gp_details['recurring'] else '' ) ) ), From 52d048a555e31aa68137f0b33eae572bbec750f9 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 26 Sep 2018 22:35:11 +0200 Subject: [PATCH 45/82] Add missing Cheers string in email --- datacenterlight/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 420f095f..d5407a85 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -851,7 +851,7 @@ class OrderConfirmationView(DetailView): "thank you for your order!\n" "We have just received a payment of CHF {amount:.2f}" " from you.{recurring}\n\n" - "Your DataCenterLight Team".format( + "Cheers\n,Your Data Center Light team".format( name=user.get('name'), amount=gp_details['amount'], recurring=( From 72c16713a78a7ccfed2d54630b24405ee8939300 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 26 Sep 2018 22:37:32 +0200 Subject: [PATCH 46/82] Update .po of datacenterlight --- .../locale/de/LC_MESSAGES/django.po | 60 +++++++++++++++++-- 1 file changed, 54 insertions(+), 6 deletions(-) diff --git a/datacenterlight/locale/de/LC_MESSAGES/django.po b/datacenterlight/locale/de/LC_MESSAGES/django.po index 1b66b640..bb92910e 100644 --- a/datacenterlight/locale/de/LC_MESSAGES/django.po +++ b/datacenterlight/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-07-05 23:11+0000\n" +"POT-Creation-Date: 2018-09-26 20:35+0000\n" "PO-Revision-Date: 2018-03-30 23:22+0000\n" "Last-Translator: b'Anonymous User '\n" "Language-Team: LANGUAGE \n" @@ -293,6 +293,9 @@ msgstr "Registrieren" msgid "Billing Address" msgstr "Rechnungsadresse" +msgid "Make a payment" +msgstr "" + msgid "Your Order" msgstr "Deine Bestellung" @@ -336,9 +339,9 @@ msgid "" "database." msgstr "" "Bitte wähle eine der zuvor genutzten Kreditkarten oder gib Deine " -"Kreditkartendetails unten an. Die Bezahlung wird über " -"Stripe abgewickelt. " -"Wir speichern Deine Kreditkartendetails nicht in unserer Datenbank." +"Kreditkartendetails unten an. Die Bezahlung wird über Stripe abgewickelt. Wir speichern Deine " +"Kreditkartendetails nicht in unserer Datenbank." msgid "" "Please fill in your credit card information below. We are using Date: Wed, 26 Sep 2018 22:42:30 +0200 Subject: [PATCH 47/82] Change generic payment subject and correct misplaced comma Data Center LightConfirmation of your payment -> Confirmation of your payment --- datacenterlight/views.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index d5407a85..a544d41c 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -843,15 +843,14 @@ class OrderConfirmationView(DetailView): send_plain_email_task.delay(email_data) email_data = { - 'subject': (settings.DCL_TEXT + - "Confirmation of your payment"), + '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\n,Your Data Center Light team".format( + "Cheers,\nYour Data Center Light team".format( name=user.get('name'), amount=gp_details['amount'], recurring=( From b7929a16e2f56e1be327337fc370308543688631 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 26 Sep 2018 22:45:05 +0200 Subject: [PATCH 48/82] Update datacenterlight django.po --- datacenterlight/locale/de/LC_MESSAGES/django.po | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/datacenterlight/locale/de/LC_MESSAGES/django.po b/datacenterlight/locale/de/LC_MESSAGES/django.po index bb92910e..d43e91ea 100644 --- a/datacenterlight/locale/de/LC_MESSAGES/django.po +++ b/datacenterlight/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-09-26 20:35+0000\n" +"POT-Creation-Date: 2018-09-26 20:44+0000\n" "PO-Revision-Date: 2018-03-30 23:22+0000\n" "Last-Translator: b'Anonymous User '\n" "Language-Team: LANGUAGE \n" @@ -570,6 +570,9 @@ msgid "An error occurred while associating the card. Details: {details}" msgstr "" "Beim Verbinden der Karte ist ein Fehler aufgetreten. Details: {details}" +msgid "Confirmation of your payment" +msgstr "" + msgid " This is a monthly recurring plan." msgstr "" @@ -580,8 +583,8 @@ msgid "" "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" +"Cheers,\n" +"Your Data Center Light team" msgstr "" msgid "Thank you for the payment." From 232022aaaf5679cf15524e03da3eddd979f4b7fc Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 26 Sep 2018 22:51:34 +0200 Subject: [PATCH 49/82] Fix flake8 errors --- datacenterlight/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index a544d41c..dc68dfa3 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -855,10 +855,10 @@ class OrderConfirmationView(DetailView): amount=gp_details['amount'], recurring=( _(' This is a monthly recurring plan.') - if gp_details['recurring'] else '' + if gp_details['recurring'] else '' ) ) - ), + ), 'reply_to': ['info@ungleich.ch'], } send_plain_email_task.delay(email_data) From 3075cffd771a0cd97c88e0960e78abf612c10f44 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 26 Sep 2018 23:00:57 +0200 Subject: [PATCH 50/82] Include product_id in generic payment Stripe plan name --- datacenterlight/views.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index dc68dfa3..8cacacb4 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -356,8 +356,6 @@ class PaymentOrderView(FormView): ), "product_id": product.id } - - # gp_details['product_id'] = product.id request.session["generic_payment_details"] = ( gp_details ) @@ -642,7 +640,10 @@ class OrderConfirmationView(DetailView): 2 ) ) - plan_name = "generic-{0:.2f}".format(amount_to_be_charged) + plan_name = "generic-{}-{0:.2f}".format( + request.session['generic_payment_details']['product_id'], + amount_to_be_charged + ) stripe_plan_id = plan_name else: template = request.session.get('template') From 930333357e59db23b39a1a95832959060a0674f1 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 2 Oct 2018 09:27:20 +0200 Subject: [PATCH 51/82] GenericProduct: Remove image field and add slug field --- ...o_20180926_0723.py => 0048_auto_20181002_0725.py} | 5 ++--- hosting/models.py | 12 +++++++----- 2 files changed, 9 insertions(+), 8 deletions(-) rename hosting/migrations/{0048_auto_20180926_0723.py => 0048_auto_20181002_0725.py} (86%) diff --git a/hosting/migrations/0048_auto_20180926_0723.py b/hosting/migrations/0048_auto_20181002_0725.py similarity index 86% rename from hosting/migrations/0048_auto_20180926_0723.py rename to hosting/migrations/0048_auto_20181002_0725.py index 6c21ab03..645067a7 100644 --- a/hosting/migrations/0048_auto_20180926_0723.py +++ b/hosting/migrations/0048_auto_20181002_0725.py @@ -1,10 +1,9 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.9.4 on 2018-09-26 07:23 +# Generated by Django 1.9.4 on 2018-10-02 07:25 from __future__ import unicode_literals from django.db import migrations, models import django.db.models.deletion -import filer.fields.image import utils.mixins @@ -26,7 +25,7 @@ class Migration(migrations.Migration): ('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)), - ('product_image', filer.fields.image.FilerImageField(blank=True, help_text='The product image', null=True, on_delete=django.db.models.deletion.CASCADE, to='filer.Image')), + ('product_slug', models.SlugField(blank=True, help_text='An optional html id for the Section. Required to set as target of a link on page', null=True)), ], bases=(utils.mixins.AssignPermissionsMixin, models.Model), ), diff --git a/hosting/models.py b/hosting/models.py index ba503753..ca41452d 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -8,7 +8,6 @@ from django.utils import timezone from django.utils.functional import cached_property from datacenterlight.models import VMPricing, VMTemplate -from filer.fields.image import FilerImageField from membership.models import StripeCustomer, CustomUser from utils.mixins import AssignPermissionsMixin from utils.models import BillingAddress @@ -67,14 +66,17 @@ class GenericProduct(AssignPermissionsMixin, models.Model): product_name = models.CharField(max_length=128, default="") product_description = models.CharField(max_length=500, default="") created_at = models.DateTimeField(auto_now_add=True) - product_image = FilerImageField( - on_delete=models.CASCADE, null=True, blank=True, - help_text='The product image' - ) product_url = models.URLField(max_length=1000, null=True, blank=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) + product_slug = models.SlugField( + blank=True, null=True, + help_text=( + 'An optional html id for the Section. Required to set as target ' + 'of a link on page' + ) + ) def __str__(self): return self.product_name From 3bad37c6058c8cfa9802ad67d603b46f6292d269 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 2 Oct 2018 10:00:59 +0200 Subject: [PATCH 52/82] Make GenericProduct slug unique --- ...{0048_auto_20181002_0725.py => 0048_auto_20181002_0757.py} | 4 ++-- hosting/models.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) rename hosting/migrations/{0048_auto_20181002_0725.py => 0048_auto_20181002_0757.py} (94%) diff --git a/hosting/migrations/0048_auto_20181002_0725.py b/hosting/migrations/0048_auto_20181002_0757.py similarity index 94% rename from hosting/migrations/0048_auto_20181002_0725.py rename to hosting/migrations/0048_auto_20181002_0757.py index 645067a7..bbce4183 100644 --- a/hosting/migrations/0048_auto_20181002_0725.py +++ b/hosting/migrations/0048_auto_20181002_0757.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.9.4 on 2018-10-02 07:25 +# Generated by Django 1.9.4 on 2018-10-02 07:57 from __future__ import unicode_literals from django.db import migrations, models @@ -25,7 +25,7 @@ class Migration(migrations.Migration): ('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)), - ('product_slug', models.SlugField(blank=True, help_text='An optional html id for the Section. Required to set as target of a link on page', null=True)), + ('product_slug', models.SlugField(blank=True, help_text='An optional html id for the Section. Required to set as target of a link on page', null=True, unique=True)), ], bases=(utils.mixins.AssignPermissionsMixin, models.Model), ), diff --git a/hosting/models.py b/hosting/models.py index ca41452d..1ab4c61f 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -72,6 +72,7 @@ class GenericProduct(AssignPermissionsMixin, models.Model): product_is_subscription = models.BooleanField(default=True) product_slug = models.SlugField( blank=True, null=True, + unique=True, help_text=( 'An optional html id for the Section. Required to set as target ' 'of a link on page' From e47f4f05b4cfd68683b359b6fb30d2dd033233f3 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 2 Oct 2018 10:02:02 +0200 Subject: [PATCH 53/82] Handler if product_slug is given (wip) --- datacenterlight/views.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 8cacacb4..e9babd74 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -6,7 +6,7 @@ from django.contrib import messages from django.contrib.auth import login, authenticate from django.core.exceptions import ValidationError 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.utils.translation import get_language, ugettext_lazy as _ from django.views.decorators.cache import cache_control @@ -254,6 +254,9 @@ class PaymentOrderView(FormView): context.update({'generic_payment_form': GenericPaymentForm( prefix='generic_payment_form' ), }) + # TODO: handle if we have a product id + #if 'product_id' in self.request.session: + else: context.update({ 'vm_pricing': VMPricing.get_vm_pricing_by_name( @@ -269,6 +272,19 @@ class PaymentOrderView(FormView): request.session['generic_payment_type'] = request.GET['type'] if 'generic_payment_details' in request.session: request.session.pop('generic_payment_details') + 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 self.render_to_response(self.get_context_data()) From e3bd963600668de1069deb4c06f0f952716ff9e8 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 2 Oct 2018 10:02:38 +0200 Subject: [PATCH 54/82] Test product_slug url (wip) --- dynamicweb/urls.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dynamicweb/urls.py b/dynamicweb/urls.py index 7e2d58a1..0fadb91b 100644 --- a/dynamicweb/urls.py +++ b/dynamicweb/urls.py @@ -10,6 +10,7 @@ from django.conf import settings from hosting.views import ( RailsHostingView, DjangoHostingView, NodeJSHostingView ) +from datacenterlight.views import PaymentOrderView from membership import urls as membership_urls from ungleich_page.views import LandingView from django.views.generic import RedirectView @@ -29,6 +30,7 @@ urlpatterns = [ url(r'^nosystemd/', include('nosystemd.urls', namespace="nosystemd")), url(r'^taggit_autosuggest/', include('taggit_autosuggest.urls')), url(r'^jsi18n/(?P\S+?)/$', i18n.javascript_catalog), + url(r'^product/(?P[\w-]+)/$', PaymentOrderView.as_view()), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) urlpatterns += i18n_patterns( From a4065c7e24371859711468fe83af4ae379abbe27 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 3 Oct 2018 07:55:56 +0200 Subject: [PATCH 55/82] Handle product_slug --- datacenterlight/views.py | 7 ++++--- dynamicweb/urls.py | 4 +++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index e9babd74..606a9b61 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -268,8 +268,9 @@ class PaymentOrderView(FormView): @cache_control(no_cache=True, must_revalidate=True, no_store=True) def get(self, request, *args, **kwargs): - if 'type' in request.GET and request.GET['type'] == 'generic': - request.session['generic_payment_type'] = request.GET['type'] + 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') if 'product_slug' in kwargs: @@ -897,7 +898,7 @@ class OrderConfirmationView(DetailView): for session_var in ['specs', 'template', 'billing_address', 'billing_address_data', 'card_id', 'token', 'customer', 'generic_payment_type', - 'generic_payment_details']: + 'generic_payment_details', 'product_slug']: if session_var in request.session: del request.session[session_var] diff --git a/dynamicweb/urls.py b/dynamicweb/urls.py index 0fadb91b..37bb69a4 100644 --- a/dynamicweb/urls.py +++ b/dynamicweb/urls.py @@ -30,7 +30,9 @@ urlpatterns = [ url(r'^nosystemd/', include('nosystemd.urls', namespace="nosystemd")), url(r'^taggit_autosuggest/', include('taggit_autosuggest.urls')), url(r'^jsi18n/(?P\S+?)/$', i18n.javascript_catalog), - url(r'^product/(?P[\w-]+)/$', PaymentOrderView.as_view()), + url(r'^product/(?P[\w-]+)/$', + PaymentOrderView.as_view(), + name='show_product'), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) urlpatterns += i18n_patterns( From 1cdc9ea6577f211382a5f906f4693121c31d64c0 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 3 Oct 2018 08:18:19 +0200 Subject: [PATCH 56/82] Clear product_id from session --- datacenterlight/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 606a9b61..1fae0fde 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -898,7 +898,7 @@ class OrderConfirmationView(DetailView): for session_var in ['specs', 'template', 'billing_address', 'billing_address_data', 'card_id', 'token', 'customer', 'generic_payment_type', - 'generic_payment_details', 'product_slug']: + 'generic_payment_details', 'product_id']: if session_var in request.session: del request.session[session_var] From 530bbcd5f6500919f41c594fcd9e002ad3110f79 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 3 Oct 2018 08:18:45 +0200 Subject: [PATCH 57/82] Create ProductPaymentForm from GenericPaymentForm --- hosting/forms.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/hosting/forms.py b/hosting/forms.py index 928f910e..c941e28f 100644 --- a/hosting/forms.py +++ b/hosting/forms.py @@ -97,6 +97,14 @@ class GenericPaymentForm(forms.Form): return recurring +class ProductPaymentForm(GenericPaymentForm): + def __init__(self, *args, **kwargs): + super(GenericPaymentForm, self).__init__(*args, **kwargs) + self.fields['product_name'].widget = forms.TextInput( + attrs={'placeholder': _('Product name'), 'readonly': 'readonly'} + ) + + class HostingUserSignupForm(forms.ModelForm): confirm_password = forms.CharField(label=_("Confirm Password"), widget=forms.PasswordInput()) From 495ac0c6d60901208922fbbe0c67ffbf4a3e8cbc Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 3 Oct 2018 08:19:29 +0200 Subject: [PATCH 58/82] Use ProductPaymentForm instead of GenericPaymentForm Only if product_id is in the session, which identifies that we are coming here via product_slug --- datacenterlight/views.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 1fae0fde..d66461ce 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -12,7 +12,9 @@ from django.utils.translation import get_language, ugettext_lazy as _ from django.views.decorators.cache import cache_control from django.views.generic import FormView, CreateView, DetailView -from hosting.forms import HostingUserLoginForm, GenericPaymentForm +from hosting.forms import ( + HostingUserLoginForm, GenericPaymentForm, ProductPaymentForm +) from hosting.models import ( HostingBill, HostingOrder, UserCardDetail, GenericProduct ) @@ -251,12 +253,22 @@ class PaymentOrderView(FormView): if ('generic_payment_type' in self.request.session and self.request.session['generic_payment_type'] == 'generic'): - context.update({'generic_payment_form': GenericPaymentForm( - prefix='generic_payment_form' - ), }) - # TODO: handle if we have a product id - #if 'product_id' in self.request.session: - + 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, + } + ), }) + else: + context.update({'generic_payment_form': GenericPaymentForm( + prefix='generic_payment_form', + ), }) else: context.update({ 'vm_pricing': VMPricing.get_vm_pricing_by_name( From 97693f0bb38ec9416cb4f1702b1ee45f2030aa5f Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 3 Oct 2018 08:27:22 +0200 Subject: [PATCH 59/82] Format code --- hosting/forms.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/hosting/forms.py b/hosting/forms.py index c941e28f..873c0344 100644 --- a/hosting/forms.py +++ b/hosting/forms.py @@ -63,16 +63,15 @@ class GenericPaymentForm(forms.Form): empty_label=_("Choose a product"), ) amount = forms.FloatField( - widget=forms.TextInput( - attrs={'placeholder': _('Amount in CHF'), - 'readonly': 'readonly'} - ), - max_value=999999, - min_value=1, - ) + widget=forms.TextInput( + attrs={'placeholder': _('Amount in CHF'), + 'readonly': 'readonly'} + ), + max_value=999999, + min_value=1, + ) recurring = forms.BooleanField(required=False, - label=_("Recurring monthly"), - ) + label=_("Recurring monthly"), ) description = forms.CharField( widget=forms.Textarea(attrs={'style': "height: 100px;"}), required=False @@ -164,7 +163,7 @@ class UserHostingKeyForm(forms.ModelForm): public_key=openssh_pubkey_str).first().name KEY_EXISTS_MESSAGE = _( "This key exists already with the name \"%(name)s\"") % { - 'name': key_name} + 'name': key_name} raise forms.ValidationError(KEY_EXISTS_MESSAGE) with tempfile.NamedTemporaryFile(delete=True) as tmp_public_key_file: From 8a2734fa0ed76437212f24009ead1bef9678cdc2 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 3 Oct 2018 08:36:21 +0200 Subject: [PATCH 60/82] Show GenericPaymentForm labels --- datacenterlight/templates/datacenterlight/landing_payment.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/templates/datacenterlight/landing_payment.html b/datacenterlight/templates/datacenterlight/landing_payment.html index 5cc3875f..fb6d51b0 100644 --- a/datacenterlight/templates/datacenterlight/landing_payment.html +++ b/datacenterlight/templates/datacenterlight/landing_payment.html @@ -74,7 +74,7 @@ {% csrf_token %} {% for field in generic_payment_form %} - {% bootstrap_field field show_label=False type='fields'%} + {% bootstrap_field field type='fields'%} {% endfor %}

{{generic_payment_form.non_field_errors|striptags}}

From 27a92780a6c7b934465fc9b6e51ff6c0f501b865 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 3 Oct 2018 08:38:20 +0200 Subject: [PATCH 61/82] Add amount label + Reset textarea height --- hosting/forms.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/hosting/forms.py b/hosting/forms.py index 873c0344..91454681 100644 --- a/hosting/forms.py +++ b/hosting/forms.py @@ -65,15 +65,16 @@ class GenericPaymentForm(forms.Form): amount = forms.FloatField( widget=forms.TextInput( attrs={'placeholder': _('Amount in CHF'), - 'readonly': 'readonly'} + '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: 100px;"}), + widget=forms.Textarea(attrs={'style': "height: 60px;"}), required=False ) From e1c91d886bc7a41817765b2a0ff7008679eca90b Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 3 Oct 2018 09:35:20 +0200 Subject: [PATCH 62/82] Use explicit index in plan name formatting --- datacenterlight/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index d66461ce..c0f3d2a4 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -669,7 +669,7 @@ class OrderConfirmationView(DetailView): 2 ) ) - plan_name = "generic-{}-{0:.2f}".format( + plan_name = "generic-{0}-{1:.2f}".format( request.session['generic_payment_details']['product_id'], amount_to_be_charged ) From ca180048199e27de9623bd495764f9337adbf21c Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 3 Oct 2018 09:36:00 +0200 Subject: [PATCH 63/82] Remove stale reference to product_id --- datacenterlight/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index c0f3d2a4..20206d1b 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -285,6 +285,7 @@ class PaymentOrderView(FormView): 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: From 193b87bbb5f2286343e48e1fb0a9d462f2f5a5f2 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 3 Oct 2018 09:36:43 +0200 Subject: [PATCH 64/82] Use proper payment form --- datacenterlight/views.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 20206d1b..d66eb8c3 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -365,14 +365,23 @@ class PaymentOrderView(FormView): # 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'): - generic_payment_form = GenericPaymentForm( - data=request.POST, prefix='generic_payment_form' - ) + 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.") - product = generic_payment_form.cleaned_data.get( - 'product_name' - ) + 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( From e4bfdec0b64eb32263682e80f1657ab1b471ac8b Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 3 Oct 2018 09:38:49 +0200 Subject: [PATCH 65/82] Update ProductPaymentForm's validation --- hosting/forms.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/hosting/forms.py b/hosting/forms.py index 91454681..4092f4a1 100644 --- a/hosting/forms.py +++ b/hosting/forms.py @@ -99,11 +99,31 @@ class GenericPaymentForm(forms.Form): class ProductPaymentForm(GenericPaymentForm): def __init__(self, *args, **kwargs): - super(GenericPaymentForm, self).__init__(*args, **kwargs) - self.fields['product_name'].widget = forms.TextInput( - attrs={'placeholder': _('Product name'), 'readonly': 'readonly'} + 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' } + ) ) + 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): confirm_password = forms.CharField(label=_("Confirm Password"), From 74ec39498ef01acef82ffdabd7eee8b9290093db Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 3 Oct 2018 09:58:40 +0200 Subject: [PATCH 66/82] Update GenericProduct Remove url field Make slug mandatory --- ...o_20181002_0757.py => 0048_auto_20181003_0757.py} | 5 ++--- hosting/models.py | 12 +++++------- 2 files changed, 7 insertions(+), 10 deletions(-) rename hosting/migrations/{0048_auto_20181002_0757.py => 0048_auto_20181003_0757.py} (82%) diff --git a/hosting/migrations/0048_auto_20181002_0757.py b/hosting/migrations/0048_auto_20181003_0757.py similarity index 82% rename from hosting/migrations/0048_auto_20181002_0757.py rename to hosting/migrations/0048_auto_20181003_0757.py index bbce4183..7b80958a 100644 --- a/hosting/migrations/0048_auto_20181002_0757.py +++ b/hosting/migrations/0048_auto_20181003_0757.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.9.4 on 2018-10-02 07:57 +# Generated by Django 1.9.4 on 2018-10-03 07:57 from __future__ import unicode_literals from django.db import migrations, models @@ -19,13 +19,12 @@ class Migration(migrations.Migration): 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_url', models.URLField(blank=True, max_length=1000, null=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)), - ('product_slug', models.SlugField(blank=True, help_text='An optional html id for the Section. Required to set as target of a link on page', null=True, unique=True)), ], bases=(utils.mixins.AssignPermissionsMixin, models.Model), ), diff --git a/hosting/models.py b/hosting/models.py index 1ab4c61f..b895b81d 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -64,20 +64,18 @@ class OrderDetail(AssignPermissionsMixin, models.Model): class GenericProduct(AssignPermissionsMixin, models.Model): permissions = ('view_genericproduct',) product_name = models.CharField(max_length=128, default="") - product_description = models.CharField(max_length=500, default="") - created_at = models.DateTimeField(auto_now_add=True) - product_url = models.URLField(max_length=1000, null=True, blank=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) product_slug = models.SlugField( - blank=True, null=True, unique=True, help_text=( 'An optional html id for the Section. Required to set as target ' 'of a link on page' ) ) + 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 From ec70cd1c8332f67bf18f8b864a8a97edfc5db447 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 3 Oct 2018 22:53:24 +0200 Subject: [PATCH 67/82] Pass product_id to ProductPaymentForm --- datacenterlight/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index d66eb8c3..63ed94c7 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -263,7 +263,8 @@ class PaymentOrderView(FormView): '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( From b3e3af1c1a428a01bcfbf1abd313e6aba3013d81 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 3 Oct 2018 22:54:45 +0200 Subject: [PATCH 68/82] Make ProductPaymentForm's recurring field hidden --- hosting/forms.py | 11 +++++++++++ hosting/static/hosting/js/payment.js | 7 ++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/hosting/forms.py b/hosting/forms.py index 4092f4a1..6bf9cdd0 100644 --- a/hosting/forms.py +++ b/hosting/forms.py @@ -109,6 +109,17 @@ class ProductPaymentForm(GenericPaymentForm): '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() def clean_amount(self): amount = self.cleaned_data.get('amount') diff --git a/hosting/static/hosting/js/payment.js b/hosting/static/hosting/js/payment.js index 52dd511a..fa89f218 100644 --- a/hosting/static/hosting/js/payment.js +++ b/hosting/static/hosting/js/payment.js @@ -165,9 +165,14 @@ $(document).ready(function () { function submitBillingForm() { var billing_form = $('#billing-form'); + var recurring_input = $('#id_generic_payment_form-recurring'); billing_form.append(''); billing_form.append(''); - billing_form.append(''); + if (recurring_input.attr('type') === 'hidden') { + billing_form.append(''); + } else { + billing_form.append(''); + } billing_form.append(''); billing_form.submit(); } From 1100d61b5d5860aa64039ecbc0563ccb594ca848 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 3 Oct 2018 23:17:25 +0200 Subject: [PATCH 69/82] Fix flake8 warnings --- hosting/forms.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hosting/forms.py b/hosting/forms.py index 6bf9cdd0..50885e6a 100644 --- a/hosting/forms.py +++ b/hosting/forms.py @@ -106,7 +106,7 @@ class ProductPaymentForm(GenericPaymentForm): self.fields['product_name'] = forms.CharField( widget=forms.TextInput( attrs={'placeholder': _('Product name'), - 'readonly': 'readonly' } + 'readonly': 'readonly'} ) ) if self.product.product_is_subscription: @@ -117,9 +117,9 @@ class ProductPaymentForm(GenericPaymentForm): else: self.fields['amount'].label = "{amt} ({payment_type})".format( amt=_('Amount in CHF'), - payment_type = _('One time payment') + payment_type=_('One time payment') ) - self.fields['recurring'].widget=forms.HiddenInput() + self.fields['recurring'].widget = forms.HiddenInput() def clean_amount(self): amount = self.cleaned_data.get('amount') From 8758cd1cd862517a05dfd884fb087d6c063ac22e Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 5 Oct 2018 08:57:20 +0200 Subject: [PATCH 70/82] Add style input-no-border --- datacenterlight/static/datacenterlight/css/common.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/datacenterlight/static/datacenterlight/css/common.css b/datacenterlight/static/datacenterlight/css/common.css index 28674b30..e1dea021 100644 --- a/datacenterlight/static/datacenterlight/css/common.css +++ b/datacenterlight/static/datacenterlight/css/common.css @@ -179,4 +179,10 @@ footer .dcl-link-separator::before { .new-card-button-margin button{ margin-top: 5px; margin-bottom: 5px; +} + +.input-no-border { + border: none !important; + background: transparent !important; + resize: none; } \ No newline at end of file From e3ec67d32c5caff2cf7d3f33abeff54c558221a3 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 5 Oct 2018 08:58:10 +0200 Subject: [PATCH 71/82] ProductPaymentForm: Set input fields input-no-border style --- hosting/forms.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hosting/forms.py b/hosting/forms.py index 50885e6a..16b06fe0 100644 --- a/hosting/forms.py +++ b/hosting/forms.py @@ -120,6 +120,9 @@ class ProductPaymentForm(GenericPaymentForm): 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') From 12eabc5f6c56580d00e098bee17dcf028e4f5bbc Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 5 Oct 2018 08:58:54 +0200 Subject: [PATCH 72/82] Redirect user to product page on login For the case when user is on product page and tries logging in --- datacenterlight/views.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 63ed94c7..aa404db5 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -346,6 +346,13 @@ class PaymentOrderView(FormView): auth_user = authenticate(email=email, password=password) if 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( reverse('datacenterlight:payment') ) From f4579595c3be72246ca609febdee2a718abe8756 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 5 Oct 2018 09:05:37 +0200 Subject: [PATCH 73/82] Add newline character to end of file --- datacenterlight/static/datacenterlight/css/common.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/static/datacenterlight/css/common.css b/datacenterlight/static/datacenterlight/css/common.css index e1dea021..00ee52cc 100644 --- a/datacenterlight/static/datacenterlight/css/common.css +++ b/datacenterlight/static/datacenterlight/css/common.css @@ -185,4 +185,4 @@ footer .dcl-link-separator::before { border: none !important; background: transparent !important; resize: none; -} \ No newline at end of file +} From 1dafa592a2dd50b94e4ed0253070abc94e001fa2 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 5 Oct 2018 09:23:39 +0200 Subject: [PATCH 74/82] Update Changelog --- Changelog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changelog b/Changelog index 190a7af8..edce80f8 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,5 @@ +Next: + * #5690: Generic payment page -- allow admin to add a onetime/monthly product and lets user pay for this product (PR #666) 2.2.2: 2018-09-28 * #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) From 2fbee916cceabd7983053fce33fe2badc8b9cb15 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 5 Oct 2018 09:37:57 +0200 Subject: [PATCH 75/82] Correct help text for product slug field --- hosting/migrations/0049_auto_20181005_0736.py | 20 +++++++++++++++++++ hosting/models.py | 3 +-- 2 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 hosting/migrations/0049_auto_20181005_0736.py diff --git a/hosting/migrations/0049_auto_20181005_0736.py b/hosting/migrations/0049_auto_20181005_0736.py new file mode 100644 index 00000000..b091ef45 --- /dev/null +++ b/hosting/migrations/0049_auto_20181005_0736.py @@ -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), + ), + ] diff --git a/hosting/models.py b/hosting/models.py index b895b81d..707b072d 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -67,8 +67,7 @@ class GenericProduct(AssignPermissionsMixin, models.Model): product_slug = models.SlugField( unique=True, help_text=( - 'An optional html id for the Section. Required to set as target ' - 'of a link on page' + 'An mandatory unique slug for the product' ) ) product_description = models.CharField(max_length=500, default="") From 5d9b2ee41a83acc7d264197e1d64b259255bc116 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 5 Oct 2018 10:36:13 +0200 Subject: [PATCH 76/82] Refactor clearing all session variables --- datacenterlight/utils.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index 8da408a0..b9edc994 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -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) - for session_var in ['specs', 'template', 'billing_address', - 'billing_address_data', 'card_id', - 'token', 'customer']: - if session_var in request.session: - del request.session[session_var] + 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', + 'billing_address_data', 'card_id', + 'token', 'customer','generic_payment_type', + 'generic_payment_details', 'product_id']: + if session_var in request.session: + del request.session[session_var] \ No newline at end of file From 806726614e3d7fa1cc435f56fe1ef83b34cfb968 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 5 Oct 2018 10:37:23 +0200 Subject: [PATCH 77/82] Clear all session variables before loading DCLCalculatorPlugin --- datacenterlight/cms_plugins.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/datacenterlight/cms_plugins.py b/datacenterlight/cms_plugins.py index b7841de8..cac73e8a 100644 --- a/datacenterlight/cms_plugins.py +++ b/datacenterlight/cms_plugins.py @@ -9,6 +9,7 @@ from .cms_models import ( DCLSectionPromoPluginModel, DCLCalculatorPluginModel ) from .models import VMTemplate +from datacenterlight.utils import clear_all_session_vars @plugin_pool.register_plugin @@ -85,6 +86,7 @@ class DCLCalculatorPlugin(CMSPluginBase): require_parent = True def render(self, context, instance, placeholder): + clear_all_session_vars(context['request']) context = super(DCLCalculatorPlugin, self).render( context, instance, placeholder ) From 10dab1350aa08c0470e4de8bf341ff4ae863e77b Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 5 Oct 2018 11:01:49 +0200 Subject: [PATCH 78/82] Use refactored method clear_all_session_vars --- datacenterlight/views.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index aa404db5..0086e880 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -29,7 +29,7 @@ from utils.stripe_utils import StripeUtils from utils.tasks import send_plain_email_task from .forms import ContactForm 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__) @@ -98,10 +98,7 @@ class IndexView(CreateView): @cache_control(no_cache=True, must_revalidate=True, no_store=True) def get(self, request, *args, **kwargs): - for session_var in ['specs', 'user', 'billing_address_data', - 'pricing_name']: - if session_var in request.session: - del request.session[session_var] + clear_all_session_vars(request) return HttpResponseRedirect(reverse('datacenterlight:cms_index')) def post(self, request): @@ -925,12 +922,7 @@ class OrderConfirmationView(DetailView): 'info@ungleich.ch for any question that you may have.') ) } - for session_var in ['specs', 'template', 'billing_address', - 'billing_address_data', 'card_id', - 'token', 'customer', 'generic_payment_type', - 'generic_payment_details', 'product_id']: - if session_var in request.session: - del request.session[session_var] + clear_all_session_vars(request) return JsonResponse(response) From 2b7d4bbef5eb815413bf72b18038c9b0b583f0f7 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 5 Oct 2018 11:10:10 +0200 Subject: [PATCH 79/82] Redirect to product page on error --- datacenterlight/views.py | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 0086e880..b7c606e9 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -398,7 +398,8 @@ class PaymentOrderView(FormView): "description": generic_payment_form.cleaned_data.get( 'description' ), - "product_id": product.id + "product_id": product.id, + "product_slug": product.product_slug } request.session["generic_payment_details"] = ( gp_details @@ -570,7 +571,12 @@ class OrderConfirmationView(DetailView): response = { 'status': False, 'redirect': "{url}#{section}".format( - url=reverse('datacenterlight:payment'), + url=(reverse('show_product', kwargs={ + 'product_slug': kwargs['product_slug']} + ) if 'generic_payment_details' in + request.session else + reverse('datacenterlight:payment') + ), section='payment_error'), 'msg_title': str(_('Error.')), 'msg_body': str( @@ -609,7 +615,12 @@ class OrderConfirmationView(DetailView): response = { 'status': False, 'redirect': "{url}#{section}".format( - url=reverse('hosting:payment'), + url=(reverse('show_product', kwargs={ + 'product_slug': kwargs['product_slug']} + ) if 'generic_payment_details' in + request.session else + reverse('datacenterlight:payment') + ), section='payment_error'), 'msg_title': str(_('Error.')), 'msg_body': str( @@ -664,8 +675,12 @@ class OrderConfirmationView(DetailView): response = { 'status': False, 'redirect': "{url}#{section}".format( - url=(reverse('datacenterlight:payment') + - "?type=generic"), + url=(reverse('show_product', kwargs={ + 'product_slug': kwargs['product_slug']} + ) if 'generic_payment_details' in + request.session else + reverse('datacenterlight:payment') + ), section='payment_error'), 'msg_title': str(_('Error.')), 'msg_body': str( @@ -736,7 +751,12 @@ class OrderConfirmationView(DetailView): response = { 'status': False, 'redirect': "{url}#{section}".format( - url=reverse('datacenterlight:payment'), + url=(reverse('show_product', kwargs={ + 'product_slug': kwargs['product_slug']} + ) if 'generic_payment_details' in + request.session else + reverse('datacenterlight:payment') + ), section='payment_error'), 'msg_title': str(_('Error.')), 'msg_body': str( From 5985ded36f6f515dd043e6f3cf747f274a9e6408 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 6 Oct 2018 07:31:38 +0200 Subject: [PATCH 80/82] Obtain product_slug from session Obtaining slug from kwargs won't work in OrderConfirmation page because we do not set the kwargs for that page. To resolve this, I add the product_slug to the generic_payment_details dict in the session --- datacenterlight/views.py | 43 ++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index b7c606e9..19faa2d1 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -571,12 +571,14 @@ class OrderConfirmationView(DetailView): response = { 'status': False, 'redirect': "{url}#{section}".format( - url=(reverse('show_product', kwargs={ - 'product_slug': kwargs['product_slug']} - ) if 'generic_payment_details' in - request.session else + 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'), 'msg_title': str(_('Error.')), 'msg_body': str( @@ -615,12 +617,16 @@ class OrderConfirmationView(DetailView): response = { 'status': False, 'redirect': "{url}#{section}".format( - url=(reverse('show_product', kwargs={ - 'product_slug': kwargs['product_slug']} - ) if 'generic_payment_details' in - request.session else + 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'), 'msg_title': str(_('Error.')), 'msg_body': str( @@ -676,7 +682,7 @@ class OrderConfirmationView(DetailView): 'status': False, 'redirect': "{url}#{section}".format( url=(reverse('show_product', kwargs={ - 'product_slug': kwargs['product_slug']} + 'product_slug': gp_details['product_slug']} ) if 'generic_payment_details' in request.session else reverse('datacenterlight:payment') @@ -751,13 +757,16 @@ class OrderConfirmationView(DetailView): response = { 'status': False, 'redirect': "{url}#{section}".format( - url=(reverse('show_product', kwargs={ - 'product_slug': kwargs['product_slug']} - ) if 'generic_payment_details' in - request.session else + 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_body': str( _('There was a payment related error.' From 5770c231ee60063aa60b93c273c03dcedb0826fb Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 6 Oct 2018 07:47:40 +0200 Subject: [PATCH 81/82] Fix flake8 warnings --- datacenterlight/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index b9edc994..bbcb16ab 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -96,7 +96,7 @@ def clear_all_session_vars(request): if request.session is not None: for session_var in ['specs', 'template', 'billing_address', 'billing_address_data', 'card_id', - 'token', 'customer','generic_payment_type', + 'token', 'customer', 'generic_payment_type', 'generic_payment_details', 'product_id']: if session_var in request.session: - del request.session[session_var] \ No newline at end of file + del request.session[session_var] From 27ee5ca5ace4e08c49d8704a3177aeceb28c250d Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 8 Oct 2018 07:28:04 +0200 Subject: [PATCH 82/82] Update Changelog for 2.3 --- Changelog | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Changelog b/Changelog index edce80f8..6d1b57d2 100644 --- a/Changelog +++ b/Changelog @@ -1,5 +1,5 @@ -Next: - * #5690: Generic payment page -- allow admin to add a onetime/monthly product and lets user pay for this product (PR #666) +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 * #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)