diff --git a/datacenterlight/locale/de/LC_MESSAGES/django.po b/datacenterlight/locale/de/LC_MESSAGES/django.po index d43e91ea..b5ff3ca5 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:44+0000\n" +"POT-Creation-Date: 2019-07-03 11:18+0000\n" "PO-Revision-Date: 2018-03-30 23:22+0000\n" "Last-Translator: b'Anonymous User '\n" "Language-Team: LANGUAGE \n" @@ -26,6 +26,22 @@ msgstr "" msgid "Your New VM %(vm_name)s at Data Center Light" msgstr "Deine neue VM %(vm_name)s bei Data Center Light" +msgid "Your VM is almost ready!" +msgstr "Deine VM ist fast fertig!" + +msgid "" +"You need to specify your public SSH key to access your VM. You can either " +"add your existing key, or generate a new key pair by clicking the generate " +"button below. After choosing your public SSH key option you’ll be directed " +"to the order confirmation page." +msgstr "" +"Du musst deinen öffentlichen SSH-Schlüssel angeben, um auf deine VM " +"zugreifen zu können. Du kannst entweder deinen vorhandenen Schlüssel " +"hinzufügen oder ein neues Schlüsselpaar generieren, indem du auf die " +"Schaltfläche \"Generieren\" unten klickst. Nachdem du deine öffentliche SSH-" +"Schlüsseloption ausgewählt hast, wirst du zur Bestellbestätigungsseite " +"weitergeleitet. " + msgid "All Rights Reserved" msgstr "Alle Rechte vorbehalten" @@ -134,6 +150,10 @@ msgstr "Unser Angebot beginnt bei 15 CHF pro Monat. Probier's jetzt aus!" msgid "ORDER VM" msgstr "VM BESTELLEN" +#, python-format +msgid "Please enter a value in range %(min_ram)s - 200." +msgstr "Bitte gib einen Wert von %(min_ram)s bis 200 ein." + msgid "VM hosting" msgstr "" @@ -152,9 +172,6 @@ msgstr "Standort: Schweiz" msgid "Please enter a value in range 1 - 48." msgstr "Bitte gib einen Wert von 1 bis 48 ein." -msgid "Please enter a value in range 1 - 200." -msgstr "Bitte gib einen Wert von 1 bis 200 ein." - msgid "Please enter a value in range 10 - 2000." msgstr "Bitte gib einen Wert von 10 bis 2000 ein." @@ -413,6 +430,10 @@ msgstr "Zwischensumme" msgid "VAT" msgstr "Mehrwertsteuer" +#, fuzzy, python-format +#| msgid "" +#| "By clicking \"Place order\" this plan will charge your credit card " +#| "account with %(total_price)s CHF/month" msgid "" "By clicking \"Place order\" this plan will charge your credit card account " "with %(total_price)s CHF/month" @@ -420,6 +441,10 @@ msgstr "" "Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit " "%(vm_total_price)s CHF pro Monat belastet" +#, fuzzy, python-format +#| msgid "" +#| "By clicking \"Place order\" this payment will charge your credit card " +#| "account with a one time amount of %(total_price)s CHF" msgid "" "By clicking \"Place order\" this payment will charge your credit card " "account with a one time amount of %(total_price)s CHF" @@ -535,6 +560,9 @@ msgstr "Tagen sagen mehr als Worte – Teste jetzt unsere VM!" msgid "Invalid number of cores" msgstr "Ungültige Anzahle CPU-Kerne" +msgid "Invalid calculator properties" +msgstr "" + msgid "Invalid RAM size" msgstr "Ungültige RAM-Grösse" diff --git a/datacenterlight/templates/datacenterlight/add_ssh_key.html b/datacenterlight/templates/datacenterlight/add_ssh_key.html new file mode 100644 index 00000000..44048bad --- /dev/null +++ b/datacenterlight/templates/datacenterlight/add_ssh_key.html @@ -0,0 +1,10 @@ + +{% load staticfiles bootstrap3 i18n custom_tags humanize %} + +{% block content %} + {% block userkey_form %} + {% with form_title=_("Your VM is almost ready!") form_sub_title=_("You need to specify your public SSH key to access your VM. You can either add your existing key, or generate a new key pair by clicking the generate button below. After choosing your public SSH key option you’ll be directed to the order confirmation page.") %} + {% include 'hosting/user_key.html' with title=form_title sub_title=form_sub_title %} + {% endwith %} + {% endblock userkey_form %} +{%endblock%} \ No newline at end of file diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index e1cc5853..31933e12 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -134,38 +134,6 @@
{% csrf_token %} - {% if generic_payment_details %} - {% else %} - {% comment %} - We are in VM buy flow and we want user to click the "Place order" button. - At this point, we also want the user to input the SSH key for the VM. - {% endcomment %} - - {% if messages %} -
- {% for message in messages %} - {{ message }} - {% endfor %} -
- {% endif %} -
-

 {% trans "Add your public SSH key" %}

-
-
- {% if keys|length > 0 %} -
Existing keys
- {% endif %} - {% for key in keys %} - -
- {% endfor %} -
- {% for field in form %} - {% bootstrap_field field %} - {% endfor %} - {% endif %}
{% if generic_payment_details %} diff --git a/datacenterlight/urls.py b/datacenterlight/urls.py index 006e7fc3..13296de7 100644 --- a/datacenterlight/urls.py +++ b/datacenterlight/urls.py @@ -1,12 +1,12 @@ from django.conf.urls import url from django.views.generic import TemplateView, RedirectView +from utils.views import AskSSHKeyView from .views import ( IndexView, PaymentOrderView, OrderConfirmationView, WhyDataCenterLightView, ContactUsView ) - urlpatterns = [ url(r'^$', IndexView.as_view(), name='index'), url(r'^t/$', IndexView.as_view(), name='index_t'), @@ -20,6 +20,8 @@ urlpatterns = [ url(r'^payment/?$', PaymentOrderView.as_view(), name='payment'), url(r'^order-confirmation/?$', OrderConfirmationView.as_view(), name='order_confirmation'), + url(r'^add-ssh-key/?$', AskSSHKeyView.as_view(), + name='add_ssh_key'), url(r'^contact/?$', ContactUsView.as_view(), name='contact_us'), url(r'glasfaser/?$', TemplateView.as_view(template_name='ungleich_page/glasfaser.html'), diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index 208d39f3..11d2b82e 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -1,8 +1,9 @@ import logging + import pyotp import requests -from django.contrib.sites.models import Site from django.conf import settings +from django.contrib.sites.models import Site from datacenterlight.tasks import create_vm_task from hosting.models import HostingOrder, HostingBill, OrderDetail @@ -99,7 +100,8 @@ def clear_all_session_vars(request): for session_var in ['specs', 'template', 'billing_address', 'billing_address_data', 'card_id', 'token', 'customer', 'generic_payment_type', - 'generic_payment_details', 'product_id']: + 'generic_payment_details', 'product_id', + 'order_confirm_url', 'new_user_hosting_key_id']: if session_var in request.session: del request.session[session_var] diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 76f50aec..3763d465 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -17,9 +17,10 @@ from hosting.forms import ( UserHostingKeyForm ) from hosting.models import ( - HostingBill, HostingOrder, UserCardDetail, GenericProduct + HostingBill, HostingOrder, UserCardDetail, GenericProduct, UserHostingKey ) from membership.models import CustomUser, StripeCustomer +from opennebula_api.models import OpenNebulaManager from opennebula_api.serializers import VMTemplateSerializer from utils.forms import ( BillingAddressForm, BillingAddressFormSignup, UserBillingAddressForm, @@ -522,8 +523,16 @@ class PaymentOrderView(FormView): request.session['customer'] = customer.stripe_id else: request.session['customer'] = customer - return HttpResponseRedirect( - reverse('datacenterlight:order_confirmation')) + + # For generic payment we take the user directly to confirmation + if ('generic_payment_type' in request.session and + self.request.session['generic_payment_type'] == 'generic'): + return HttpResponseRedirect( + reverse('datacenterlight:order_confirmation')) + else: + self.request.session['order_confirm_url'] = reverse('datacenterlight:order_confirmation') + return HttpResponseRedirect( + reverse('datacenterlight:add_ssh_key')) else: context = self.get_context_data() context['billing_address_form'] = address_form @@ -588,31 +597,6 @@ class OrderConfirmationView(DetailView, FormView): return render(request, self.template_name, context) def post(self, request, *args, **kwargs): - # Check ssh public key and then proceed - form = self.get_form() - required = True - - # SSH key validation is required only if the user doesn't have an - # existing key and user has input some value in the add ssh key fields - if (len(get_all_public_keys(self.request.user)) > 0 and - (len(form.data.get('public_key')) == 0 and - len(form.data.get('name')) == 0)): - required = False - form.fields['name'].required = required - form.fields['public_key'].required = required - if not form.is_valid(): - response = { - 'status': False, - 'msg_title': str(_('SSH key related error occurred')), - 'msg_body': "
".join([str(v) for k,v in form.errors.items()]), - } - return JsonResponse(response) - - if required: - # We have a valid SSH key from the user, save it in opennebula and - # db and proceed further - form.save() - user = request.session.get('user') stripe_api_cus_id = request.session.get('customer') stripe_utils = StripeUtils() @@ -864,6 +848,18 @@ class OrderConfirmationView(DetailView, FormView): new_user = authenticate(username=custom_user.email, password=password) login(request, new_user) + if 'new_user_hosting_key_id' in self.request.session: + user_hosting_key = UserHostingKey.objects.get(id=self.request.session['new_user_hosting_key_id']) + user_hosting_key.user = new_user + user_hosting_key.save() + + owner = new_user + manager = OpenNebulaManager( + email=owner.email, + password=owner.password + ) + keys_to_save = get_all_public_keys(new_user) + manager.save_key_in_opennebula_user('\n'.join(keys_to_save)) else: # We assume that if the user is here, his/her StripeCustomer # object already exists diff --git a/hosting/forms.py b/hosting/forms.py index 797bc700..1bc99b8f 100644 --- a/hosting/forms.py +++ b/hosting/forms.py @@ -222,7 +222,7 @@ class UserHostingKeyForm(forms.ModelForm): return self.data.get('name') def clean_user(self): - return self.request.user + return self.request.user if self.request.user.is_authenticated() else None def clean(self): cleaned_data = self.cleaned_data diff --git a/hosting/migrations/0055_auto_20190701_1614.py b/hosting/migrations/0055_auto_20190701_1614.py new file mode 100644 index 00000000..4a2744fb --- /dev/null +++ b/hosting/migrations/0055_auto_20190701_1614.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2019-07-01 16:14 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('hosting', '0054_auto_20190508_2141'), + ] + + operations = [ + migrations.AlterField( + model_name='userhostingkey', + name='user', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/hosting/models.py b/hosting/models.py index aead430c..311ee953 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -1,11 +1,11 @@ import json import logging import os -import pytz +from datetime import datetime +import pytz from Crypto.PublicKey import RSA from dateutil.relativedelta import relativedelta -from datetime import datetime from django.db import models from django.utils import timezone from django.utils.functional import cached_property @@ -187,7 +187,7 @@ class HostingOrder(AssignPermissionsMixin, models.Model): class UserHostingKey(models.Model): - user = models.ForeignKey(CustomUser) + user = models.ForeignKey(CustomUser, blank=True, null=True) public_key = models.TextField() private_key = models.FileField(upload_to='private_keys', blank=True) created_at = models.DateTimeField(auto_now_add=True) diff --git a/hosting/templates/hosting/order_detail.html b/hosting/templates/hosting/order_detail.html index 7ab79378..4a62e9fa 100644 --- a/hosting/templates/hosting/order_detail.html +++ b/hosting/templates/hosting/order_detail.html @@ -198,35 +198,6 @@ {% block submit_btn %} {% csrf_token %} - {% comment %} - We are in VM buy flow and we want user to click the "Place order" button. - At this point, we also want the user to input the SSH key for the VM. - {% endcomment %} - - {% if messages %} -
- {% for message in messages %} - {{ message }} - {% endfor %} -
- {% endif %} -
-

 {% trans "Add your public SSH key" %}

-
-
- {% if keys|length > 0 %} -
Existing keys
- {% endif %} - {% for key in keys %} - -
- {% endfor %} -
- {% for field in form %} - {% bootstrap_field field %} - {% endfor %}
{% blocktrans with vm_price=vm.total_price|floatformat:2|intcomma %}By clicking "Place order" this plan will charge your credit card account with {{ vm_price }} CHF/month{% endblocktrans %}.
diff --git a/hosting/templates/hosting/user_key.html b/hosting/templates/hosting/user_key.html index 804d661a..247551b5 100644 --- a/hosting/templates/hosting/user_key.html +++ b/hosting/templates/hosting/user_key.html @@ -8,7 +8,8 @@ {% csrf_token %} {% if messages %}
diff --git a/hosting/urls.py b/hosting/urls.py index 4779f67c..5b2b87b0 100644 --- a/hosting/urls.py +++ b/hosting/urls.py @@ -1,6 +1,7 @@ from django.conf.urls import url from django.contrib.auth import views as auth_views +from utils.views import SSHKeyCreateView, AskSSHKeyView from .views import ( DjangoHostingView, RailsHostingView, PaymentVMView, NodeJSHostingView, LoginView, SignupView, SignupValidateView, SignupValidatedView, IndexView, @@ -8,7 +9,7 @@ from .views import ( VirtualMachinesPlanListView, VirtualMachineView, OrdersHostingDeleteView, MarkAsReadNotificationView, PasswordResetView, PasswordResetConfirmView, HostingPricingView, CreateVirtualMachinesView, HostingBillListView, - HostingBillDetailView, SSHKeyDeleteView, SSHKeyCreateView, SSHKeyListView, + HostingBillDetailView, SSHKeyDeleteView, SSHKeyListView, SSHKeyChoiceView, DashboardView, SettingsView, ResendActivationEmailView, InvoiceListView, InvoiceDetailView, CheckUserVM ) @@ -27,6 +28,8 @@ urlpatterns = [ url(r'invoices/?$', InvoiceListView.as_view(), name='invoices'), url(r'order-confirmation/?$', OrdersHostingDetailView.as_view(), name='order-confirmation'), + url(r'^add-ssh-key/?$', AskSSHKeyView.as_view(), + name='add_ssh_key'), url(r'orders/(?P\d+)/?$', OrdersHostingDetailView.as_view(), name='orders'), url(r'invoice/(?P[-\w]+)/?$', InvoiceDetailView.as_view(), diff --git a/hosting/views.py b/hosting/views.py index f5146fbf..201d58e7 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -523,67 +523,6 @@ class SSHKeyChoiceView(LoginRequiredMixin, View): return redirect(reverse_lazy('hosting:ssh_keys'), foo='bar') -@method_decorator(decorators, name='dispatch') -class SSHKeyCreateView(LoginRequiredMixin, FormView): - form_class = UserHostingKeyForm - model = UserHostingKey - template_name = 'hosting/user_key.html' - login_url = reverse_lazy('hosting:login') - context_object_name = "virtual_machine" - success_url = reverse_lazy('hosting:ssh_keys') - - def get_form_kwargs(self): - kwargs = super(SSHKeyCreateView, self).get_form_kwargs() - kwargs.update({'request': self.request}) - return kwargs - - def form_valid(self, form): - form.save() - if settings.DCL_SSH_KEY_NAME_PREFIX in form.instance.name: - content = ContentFile(form.cleaned_data.get('private_key')) - filename = form.cleaned_data.get( - 'name') + '_' + str(uuid.uuid4())[:8] + '_private.pem' - form.instance.private_key.save(filename, content) - context = self.get_context_data() - - next_url = self.request.session.get( - 'next', - reverse('hosting:create_virtual_machine') - ) - - if 'next' in self.request.session: - context.update({ - 'next_url': next_url - }) - del (self.request.session['next']) - - if form.cleaned_data.get('private_key'): - context.update({ - 'private_key': form.cleaned_data.get('private_key'), - 'key_name': form.cleaned_data.get('name'), - 'form': UserHostingKeyForm(request=self.request), - }) - - owner = self.request.user - manager = OpenNebulaManager( - email=owner.email, - password=owner.password - ) - keys_to_save = get_all_public_keys(self.request.user) - manager.save_key_in_opennebula_user('\n'.join(keys_to_save)) - return HttpResponseRedirect(self.success_url) - - def post(self, request, *args, **kwargs): - form = self.get_form() - required = 'add_ssh' in self.request.POST - form.fields['name'].required = required - form.fields['public_key'].required = required - if form.is_valid(): - return self.form_valid(form) - else: - return self.form_invalid(form) - - @method_decorator(decorators, name='dispatch') class SettingsView(LoginRequiredMixin, FormView): template_name = "hosting/settings.html" @@ -830,10 +769,10 @@ class PaymentVMView(LoginRequiredMixin, FormView): reverse('hosting:payment') + '#payment_error') request.session['token'] = token request.session['billing_address_data'] = billing_address_data - return HttpResponseRedirect("{url}?{query_params}".format( - url=reverse('hosting:order-confirmation'), - query_params='page=payment') - ) + self.request.session['order_confirm_url'] = "{url}?{query_params}".format( + url=reverse('hosting:order-confirmation'), + query_params='page=payment') + return HttpResponseRedirect(reverse('hosting:add_ssh_key')) else: return self.form_invalid(form) @@ -1002,31 +941,6 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): @method_decorator(decorators) def post(self, request): - # Check ssh public key and then proceed - form = self.get_form() - required = True - - # SSH key validation is required only if the user doesn't have an - # existing key and user has input some value in the add ssh key fields - if (len(get_all_public_keys(self.request.user)) > 0 and - (len(form.data.get('public_key')) == 0 and - len(form.data.get('name')) == 0)): - required = False - form.fields['name'].required = required - form.fields['public_key'].required = required - if not form.is_valid(): - response = { - 'status': False, - 'msg_title': str(_('SSH key related error occurred')), - 'msg_body': "
".join([str(v) for k,v in form.errors.items()]), - } - return JsonResponse(response) - - if required: - # We have a valid SSH key from the user, save it in opennebula and - # db and proceed further - form.save() - template = request.session.get('template') specs = request.session.get('specs') stripe_utils = StripeUtils() diff --git a/opennebula_api/models.py b/opennebula_api/models.py index 478758fc..f8ef6481 100644 --- a/opennebula_api/models.py +++ b/opennebula_api/models.py @@ -376,7 +376,7 @@ class OpenNebulaManager(): """ return_value = self.oneadmin_client.call( 'user.update', - self.opennebula_user.id, + self.opennebula_user if type(self.opennebula_user) == int else self.opennebula_user.id, '%s' % ssh_key, update_type ) diff --git a/utils/views.py b/utils/views.py index 394a9fc2..05d0fdc2 100644 --- a/utils/views.py +++ b/utils/views.py @@ -1,16 +1,25 @@ +import uuid + from django.conf import settings from django.contrib import messages from django.contrib.auth import authenticate, login from django.contrib.auth.tokens import default_token_generator +from django.core.files.base import ContentFile from django.core.urlresolvers import reverse_lazy from django.http import HttpResponseRedirect +from django.shortcuts import render from django.utils.encoding import force_bytes from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode from django.utils.translation import ugettext_lazy as _ -from django.views.generic import FormView, CreateView from django.views.decorators.cache import cache_control +from django.views.generic import FormView, CreateView +from datacenterlight.utils import get_cms_integration +from hosting.forms import UserHostingKeyForm +from hosting.models import UserHostingKey from membership.models import CustomUser +from opennebula_api.models import OpenNebulaManager +from utils.hosting_utils import get_all_public_keys from .forms import SetPasswordForm from .mailer import BaseEmail @@ -174,3 +183,87 @@ class PasswordResetConfirmViewMixin(FormView): form.add_error(None, _('The reset password link is no longer valid.')) return self.form_invalid(form) + + +class SSHKeyCreateView(FormView): + form_class = UserHostingKeyForm + model = UserHostingKey + template_name = 'hosting/user_key.html' + login_url = reverse_lazy('hosting:login') + context_object_name = "virtual_machine" + success_url = reverse_lazy('hosting:ssh_keys') + + def get_form_kwargs(self): + kwargs = super(SSHKeyCreateView, self).get_form_kwargs() + kwargs.update({'request': self.request}) + return kwargs + + def form_valid(self, form): + form.save() + if settings.DCL_SSH_KEY_NAME_PREFIX in form.instance.name: + content = ContentFile(form.cleaned_data.get('private_key')) + filename = form.cleaned_data.get( + 'name') + '_' + str(uuid.uuid4())[:8] + '_private.pem' + form.instance.private_key.save(filename, content) + context = self.get_context_data() + + next_url = self.request.session.get( + 'next', + reverse_lazy('hosting:create_virtual_machine') + ) + + if 'next' in self.request.session: + context.update({ + 'next_url': next_url + }) + del (self.request.session['next']) + + if form.cleaned_data.get('private_key'): + context.update({ + 'private_key': form.cleaned_data.get('private_key'), + 'key_name': form.cleaned_data.get('name'), + 'form': UserHostingKeyForm(request=self.request), + }) + + if self.request.user.is_authenticated(): + owner = self.request.user + manager = OpenNebulaManager( + email=owner.email, + password=owner.password + ) + keys_to_save = get_all_public_keys(self.request.user) + manager.save_key_in_opennebula_user('\n'.join(keys_to_save)) + else: + self.request.session["new_user_hosting_key_id"] = form.instance.id + return HttpResponseRedirect(self.success_url) + + def post(self, request, *args, **kwargs): + form = self.get_form() + required = 'add_ssh' in self.request.POST + form.fields['name'].required = required + form.fields['public_key'].required = required + if form.is_valid(): + return self.form_valid(form) + else: + return self.form_invalid(form) + + +class AskSSHKeyView(SSHKeyCreateView): + form_class = UserHostingKeyForm + template_name = "datacenterlight/add_ssh_key.html" + success_url = reverse_lazy('datacenterlight:order_confirmation') + context_object_name = "dcl_vm_buy_add_ssh_key" + + @cache_control(no_cache=True, must_revalidate=True, no_store=True) + def get(self, request, *args, **kwargs): + context = { + 'site_url': reverse_lazy('datacenterlight:index'), + 'cms_integration': get_cms_integration('default'), + 'form': UserHostingKeyForm(request=self.request), + 'keys': get_all_public_keys(self.request.user) + } + return render(request, self.template_name, context) + + def post(self, request, *args, **kwargs): + self.success_url = self.request.session.get("order_confirm_url") + return super(AskSSHKeyView, self).post(self, request, *args, **kwargs) \ No newline at end of file