diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html new file mode 100644 index 00000000..cb412558 --- /dev/null +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -0,0 +1,79 @@ +{% extends "hosting/base_short.html" %} +{% load staticfiles bootstrap3 %} +{% load i18n %} +{% block content %} + +
+ {% if messages %} +
+
+
+
+ {% for message in messages %} + {{ message }} + {% endfor %} +
+
+
+ {% endif %} + {% if not error %} +
+
+
+

{% trans "Confirm Order"%}

{% trans "Order #"%} {{order.id}}

+
+
+
+
+
+

{% trans "Billed To:"%}

+ {{user.name}}
+ {{order.billing_address.street_address}},{{order.billing_address.postal_code}}
+ {{order.billing_address.city}}, {{order.billing_address.country}}. +
+
+
+
+ {% trans "Date"%}:
+ {{order.created_at}}

+
+ +
+
+
+
+
+ {% trans "Payment Method:"%}
+ {{order.cc_brand}} ending **** {{order.last4}}
+ {{user.email}} +
+
+
+
+
+ +
+
+

{% trans "Order summary"%}

+
+
+

{% trans "Cores"%} {{vm.cores}}

+
+

{% trans "Memory"%} {{vm.memory}} GB

+
+

{% trans "Disk space"%} {{vm.disk_size}} GB

+
+

{% trans "Total"%}

{{vm.price}} CHF

+
+
+ {% url 'datacenterlight:payment' as payment_url %} + {% if payment_url in request.META.HTTP_REFERER %} + + {% endif %} +
+
+ {% endif %} +
+{%endblock%} diff --git a/datacenterlight/urls.py b/datacenterlight/urls.py index aad6442e..65401b13 100644 --- a/datacenterlight/urls.py +++ b/datacenterlight/urls.py @@ -1,6 +1,6 @@ from django.conf.urls import url -from .views import IndexView, BetaProgramView, LandingProgramView, BetaAccessView, PricingView, SuccessView +from .views import IndexView, BetaProgramView, LandingProgramView, BetaAccessView, PricingView, SuccessView, PaymentOrderView, OrderConfirmationView urlpatterns = [ @@ -8,6 +8,8 @@ urlpatterns = [ url(r'^/beta-program/?$', BetaProgramView.as_view(), name='beta'), url(r'^/landing/?$', LandingProgramView.as_view(), name='landing'), url(r'^/pricing/?$', PricingView.as_view(), name='pricing'), + url(r'^/payment/?$', PaymentOrderView.as_view(), name='payment'), + url(r'^/order-confirmation/(?P\d+)/?$', OrderConfirmationView.as_view(), name='order_confirmation'), url(r'^/order-success/?$', SuccessView.as_view(), name='order_success'), url(r'^/beta_access?$', BetaAccessView.as_view(), name='beta_access'), ] diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 427f8c70..e4329b89 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -1,4 +1,4 @@ -from django.views.generic import FormView, CreateView, TemplateView +from django.views.generic import FormView, CreateView, TemplateView, DetailView from django.http import HttpResponseRedirect from .forms import BetaAccessForm from .models import BetaAccess, BetaAccessVMType, BetaAccessVM @@ -10,9 +10,17 @@ from django.shortcuts import render from django.shortcuts import redirect from django import forms from django.core.exceptions import ValidationError +from django.views.decorators.cache import cache_control +from django.conf import settings +from utils.forms import BillingAddressForm, UserBillingAddressForm +from membership.models import StripeCustomer +from hosting.models import HostingOrder, HostingBill +from utils.stripe_utils import StripeUtils +from datetime import datetime +from membership.models import CustomUser, StripeCustomer from opennebula_api.models import OpenNebulaManager -from opennebula_api.serializers import VirtualMachineTemplateSerializer +from opennebula_api.serializers import VirtualMachineTemplateSerializer, VirtualMachineSerializer class LandingProgramView(TemplateView): template_name = "datacenterlight/landing.html" @@ -20,6 +28,14 @@ class LandingProgramView(TemplateView): class SuccessView(TemplateView): template_name = "datacenterlight/success.html" + def get(self, request, *args, **kwargs): + if 'specs' not in request.session or 'user' not in request.session: + return HttpResponseRedirect(reverse('datacenterlight:index')) + else : + del request.session['specs'] + del request.session['user'] + return render(request, self.template_name) + class PricingView(TemplateView): template_name = "datacenterlight/pricing.html" @@ -170,7 +186,12 @@ class IndexView(CreateView): success_url = "/datacenterlight#requestform" success_message = "Thank you, we will contact you as soon as possible" + @cache_control(no_cache=True, must_revalidate=True, no_store=True) def get(self, request, *args, **kwargs): + if 'specs' in request.session : + del request.session['specs'] + if 'user' in request.session : + del request.session['user'] try: manager = OpenNebulaManager() templates = manager.get_templates() @@ -213,26 +234,22 @@ class IndexView(CreateView): messages.add_message(self.request, messages.ERROR, '%(value) is not a proper email.'.format(email)) return HttpResponseRedirect(reverse('datacenterlight:index')) - context = { - 'name': name, - 'email': email, - 'cores': cores, + specs = { + 'cpu': cores, 'memory': memory, - 'storage': storage, - 'price': price, - 'template': template_data['name'], + 'disk_size': storage, + 'price': price } - email_data = { - 'subject': "Data Center Light Order from %s" % context['email'], - 'from_email': '(datacenterlight) datacenterlight Support ', - 'to': ['info@ungleich.ch'], - 'body': "\n".join(["%s=%s" % (k, v) for (k, v) in context.items()]), - 'reply_to': [context['email']], + + this_user = { + 'name': name, + 'email': email } - email = EmailMessage(**email_data) - email.send() - - return HttpResponseRedirect(reverse('datacenterlight:order_success')) + + request.session['specs'] = specs + request.session['template'] = template_data + request.session['user'] = this_user + return HttpResponseRedirect(reverse('datacenterlight:payment')) def get_success_url(self): success_url = reverse('datacenterlight:index') @@ -281,3 +298,167 @@ class IndexView(CreateView): messages.add_message(self.request, messages.SUCCESS, self.success_message) return super(IndexView, self).form_valid(form) + + +class PaymentOrderView(FormView): + template_name = 'hosting/payment.html' + form_class = BillingAddressForm + + def get_context_data(self, **kwargs): + context = super(PaymentOrderView, self).get_context_data(**kwargs) + context.update({ + 'stripe_key': settings.STRIPE_API_PUBLIC_KEY + }) + 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 or 'user' not in request.session: + return HttpResponseRedirect(reverse('datacenterlight:index')) + return self.render_to_response(self.get_context_data()) + + def post(self, request, *args, **kwargs): + form = self.get_form() + if form.is_valid(): + # Get billing address data + billing_address_data = form.cleaned_data + context = self.get_context_data() + template = request.session.get('template') + specs = request.session.get('specs') + user = request.session.get('user') + vm_template_id = template.get('id', 1) + final_price = specs.get('price') + token = form.cleaned_data.get('token') + try: + custom_user = CustomUser.objects.get(email=user.get('email')) + except CustomUser.DoesNotExist: + password = CustomUser.get_random_password() + # Register the user, and do not send emails + CustomUser.register(user.get('name'), + password, + user.get('email'), + app='dcl', + base_url=None, send_email=False) + + + # Get or create stripe customer + customer = StripeCustomer.get_or_create(email=user.get('email'), + token=token) + if not customer: + form.add_error("__all__", "Invalid credit card") + return self.render_to_response(self.get_context_data(form=form)) + + # Create Billing Address + billing_address = form.save() + + # Make stripe charge to a customer + stripe_utils = StripeUtils() + charge_response = stripe_utils.make_charge(amount=final_price, + customer=customer.stripe_id) + charge = charge_response.get('response_object') + + # Check if the payment was approved + if not charge: + context.update({ + 'paymentError': charge_response.get('error'), + 'form': form + }) + return render(request, self.template_name, context) + + charge = charge_response.get('response_object') + + # Create OpenNebulaManager + manager = OpenNebulaManager(email=settings.OPENNEBULA_USERNAME, + password=settings.OPENNEBULA_PASSWORD) + + # Create a vm using logged user + vm_id = manager.create_vm( + template_id=vm_template_id, + specs=specs, + vm_name="{email}-{template_name}-{date}".format( + email=user.get('email'), + template_name=template.get('name'), + date=int(datetime.now().strftime("%s"))) + ) + + # Create a Hosting Order + order = HostingOrder.create( + price=final_price, + vm_id=vm_id, + customer=customer, + billing_address=billing_address + ) + + # Create a Hosting Bill + bill = HostingBill.create( + customer=customer, billing_address=billing_address) + + # Create Billing Address for User if he does not have one + if not customer.user.billing_addresses.count(): + billing_address_data.update({ + 'user': customer.user.id + }) + billing_address_user_form = UserBillingAddressForm( + billing_address_data) + billing_address_user_form.is_valid() + billing_address_user_form.save() + + # Associate an order with a stripe payment + order.set_stripe_charge(charge) + + # If the Stripe payment was successed, set order status approved + order.set_approved() + + vm = VirtualMachineSerializer(manager.get_vm(vm_id)).data + + context = { + 'name': user.get('name'), + 'email': user.get('email'), + 'cores': specs.get('cpu'), + 'memory': specs.get('memory'), + 'storage': specs.get('disk_size'), + 'price': specs.get('price'), + 'template': template.get('name'), + 'vm.name': vm['name'], + 'vm.id': vm['vm_id'], + 'order.id': order.id + } + email_data = { + 'subject': "Data Center Light Order from %s" % context['email'], + 'from_email': '(Data Center Light) Data Center Light Support ', + 'to': ['info@ungleich.ch'], + 'body': "\n".join(["%s=%s" % (k, v) for (k, v) in context.items()]), + 'reply_to': [context['email']], + } + email = EmailMessage(**email_data) + email.send() + return HttpResponseRedirect(reverse('datacenterlight:order_confirmation', kwargs={'pk': order.id})) + else: + return self.form_invalid(form) + +class OrderConfirmationView(DetailView): + template_name = "datacenterlight/order_detail.html" + context_object_name = "order" + model = HostingOrder + def get_context_data(self, **kwargs): + # Get context + context = super(DetailView, self).get_context_data(**kwargs) + obj = self.get_object() + manager = OpenNebulaManager(email=settings.OPENNEBULA_USERNAME, + password=settings.OPENNEBULA_PASSWORD) + try: + vm = manager.get_vm(obj.vm_id) + context['vm'] = VirtualMachineSerializer(vm).data + context['next_url'] = reverse('datacenterlight:order_success') + except WrongIdError: + messages.error(self.request, + 'The VM you are looking for is unavailable at the moment. \ + Please contact Data Center Light support.' + ) + self.kwargs['error'] = 'WrongIdError' + context['error'] = 'WrongIdError' + except ConnectionRefusedError: + messages.error(self.request, + 'In order to create a VM, you need to create/upload your SSH KEY first.' + ) + return context diff --git a/hosting/templates/hosting/base_short.html b/hosting/templates/hosting/base_short.html index cf9f54d9..0ccfe5f9 100644 --- a/hosting/templates/hosting/base_short.html +++ b/hosting/templates/hosting/base_short.html @@ -46,7 +46,7 @@ - {% if request.user.is_authenticated %} + - {% endif %} +
{% block content %} diff --git a/membership/models.py b/membership/models.py index db0155b5..16fe71e8 100644 --- a/membership/models.py +++ b/membership/models.py @@ -7,6 +7,7 @@ from django.core.validators import RegexValidator from django.contrib.auth.models import User from django.contrib.sites.models import Site from django.conf import settings +from django.utils.crypto import get_random_string from utils.stripe_utils import StripeUtils from utils.mailer import DigitalGlarusRegistrationMailer @@ -74,7 +75,7 @@ class CustomUser(AbstractBaseUser, PermissionsMixin): REQUIRED_FIELDS = ['name', 'password'] @classmethod - def register(cls, name, password, email, app='digital_glarus', base_url=None): + def register(cls, name, password, email, app='digital_glarus', base_url=None, send_email=True): user = cls.objects.filter(email=email).first() if not user: user = cls.objects.create_user(name=name, email=email, password=password) @@ -86,19 +87,20 @@ class CustomUser(AbstractBaseUser, PermissionsMixin): dcl_text = settings.DCL_TEXT dcl_from_address = settings.DCL_SUPPORT_FROM_ADDRESS user.is_active = False - email_data = { - 'subject': str(_('Activate your ')) + dcl_text + str(_(' account')), - 'from_address': settings.DCL_SUPPORT_FROM_ADDRESS, - 'to': user.email, - 'context': {'base_url' : base_url, - 'activation_link' : reverse('hosting:validate', kwargs={'validate_slug': user.validation_slug}), - 'dcl_text' : dcl_text - }, - 'template_name': 'user_activation', - 'template_path': 'datacenterlight/emails/' - } - email = BaseEmail(**email_data) - email.send() + if send_email is True: + email_data = { + 'subject': str(_('Activate your ')) + dcl_text + str(_(' account')), + 'from_address': settings.DCL_SUPPORT_FROM_ADDRESS, + 'to': user.email, + 'context': {'base_url' : base_url, + 'activation_link' : reverse('hosting:validate', kwargs={'validate_slug': user.validation_slug}), + 'dcl_text' : dcl_text + }, + 'template_name': 'user_activation', + 'template_path': 'datacenterlight/emails/' + } + email = BaseEmail(**email_data) + email.send() return user else: return None @@ -118,6 +120,10 @@ class CustomUser(AbstractBaseUser, PermissionsMixin): return True return False + @classmethod + def get_random_password(cls): + return get_random_string(24) + def is_superuser(self): return False diff --git a/opennebula_api/models.py b/opennebula_api/models.py index 397c0f0c..d18524c5 100644 --- a/opennebula_api/models.py +++ b/opennebula_api/models.py @@ -239,7 +239,7 @@ class OpenNebulaManager(): ) ) - def create_vm(self, template_id, specs, ssh_key=None): + def create_vm(self, template_id, specs, ssh_key=None, vm_name=None): template = self.get_template(template_id) vm_specs_formatter = """ - """.format(ssh=ssh_key) + + """ vm_id = self.client.call(oca.VmTemplate.METHODS['instantiate'], template.id, '', @@ -307,6 +306,13 @@ class OpenNebulaManager(): 'release', vm_id ) + + if vm_name is not None: + self.oneadmin_client.call( + 'vm.rename', + vm_id, + vm_name + ) return vm_id def delete_vm(self, vm_id):