Merge branch 'master' into feature/6561/invoices-webhook

This commit is contained in:
PCoder 2019-07-18 08:26:15 +05:30
commit 8300babead
23 changed files with 434 additions and 252 deletions

View file

@ -1,3 +1,7 @@
2.6.1: 2019-07-09
* #6941: [hosting dashboard] Show the card's expiry year & month too in the list of added cards (MR!710)
2.6: 2019-07-03
* #5509: Getting rid of our key by still supporting multiple user keys (MR!709)
2.5.11: 2019-06-11 2.5.11: 2019-06-11
* #6672: [api] Check VM belongs to user in the infrastructure directly (MR!707) * #6672: [api] Check VM belongs to user in the infrastructure directly (MR!707)
* #bugfix: DE translation fix "Learn mehr" -> "Lerne mehr" (MR!708) * #bugfix: DE translation fix "Learn mehr" -> "Lerne mehr" (MR!708)

View file

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-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" "PO-Revision-Date: 2018-03-30 23:22+0000\n"
"Last-Translator: b'Anonymous User <coder.purple+25@gmail.com>'\n" "Last-Translator: b'Anonymous User <coder.purple+25@gmail.com>'\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -26,6 +26,22 @@ msgstr ""
msgid "Your New VM %(vm_name)s at Data Center Light" msgid "Your New VM %(vm_name)s at Data Center Light"
msgstr "Deine neue VM %(vm_name)s bei 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 youll 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" msgid "All Rights Reserved"
msgstr "Alle Rechte vorbehalten" msgstr "Alle Rechte vorbehalten"
@ -134,6 +150,10 @@ msgstr "Unser Angebot beginnt bei 15 CHF pro Monat. Probier's jetzt aus!"
msgid "ORDER VM" msgid "ORDER VM"
msgstr "VM BESTELLEN" 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" msgid "VM hosting"
msgstr "" msgstr ""
@ -152,9 +172,6 @@ msgstr "Standort: Schweiz"
msgid "Please enter a value in range 1 - 48." msgid "Please enter a value in range 1 - 48."
msgstr "Bitte gib einen Wert von 1 bis 48 ein." 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." msgid "Please enter a value in range 10 - 2000."
msgstr "Bitte gib einen Wert von 10 bis 2000 ein." msgstr "Bitte gib einen Wert von 10 bis 2000 ein."
@ -413,6 +430,10 @@ msgstr "Zwischensumme"
msgid "VAT" msgid "VAT"
msgstr "Mehrwertsteuer" 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 "" msgid ""
"By clicking \"Place order\" this plan will charge your credit card account " "By clicking \"Place order\" this plan will charge your credit card account "
"with %(total_price)s CHF/month" "with %(total_price)s CHF/month"
@ -420,6 +441,10 @@ msgstr ""
"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit " "Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit "
"%(vm_total_price)s CHF pro Monat belastet" "%(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 "" msgid ""
"By clicking \"Place order\" this payment will charge your credit card " "By clicking \"Place order\" this payment will charge your credit card "
"account with a one time amount of %(total_price)s CHF" "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" msgid "Invalid number of cores"
msgstr "Ungültige Anzahle CPU-Kerne" msgstr "Ungültige Anzahle CPU-Kerne"
msgid "Invalid calculator properties"
msgstr ""
msgid "Invalid RAM size" msgid "Invalid RAM size"
msgstr "Ungültige RAM-Grösse" msgstr "Ungültige RAM-Grösse"

View file

@ -186,3 +186,8 @@ footer .dcl-link-separator::before {
background: transparent !important; background: transparent !important;
resize: none; resize: none;
} }
.existing-keys-title {
font-weight: bold;
font-size: 14px;
}

View file

@ -8,7 +8,6 @@ from django.core.mail import EmailMessage
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.utils import translation from django.utils import translation
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from time import sleep
from dynamicweb.celery import app from dynamicweb.celery import app
from hosting.models import HostingOrder from hosting.models import HostingOrder
@ -16,7 +15,7 @@ from membership.models import CustomUser
from opennebula_api.models import OpenNebulaManager from opennebula_api.models import OpenNebulaManager
from opennebula_api.serializers import VirtualMachineSerializer from opennebula_api.serializers import VirtualMachineSerializer
from utils.hosting_utils import ( from utils.hosting_utils import (
get_all_public_keys, get_or_create_vm_detail, ping_ok get_all_public_keys, get_or_create_vm_detail
) )
from utils.mailer import BaseEmail from utils.mailer import BaseEmail
from utils.stripe_utils import StripeUtils from utils.stripe_utils import StripeUtils
@ -79,10 +78,14 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id):
# Create OpenNebulaManager # Create OpenNebulaManager
manager = OpenNebulaManager(email=on_user, password=on_pass) manager = OpenNebulaManager(email=on_user, password=on_pass)
custom_user = CustomUser.objects.get(email=user.get('email'))
pub_keys = get_all_public_keys(custom_user)
if manager.email != settings.OPENNEBULA_USERNAME:
manager.save_key_in_opennebula_user('\n'.join(pub_keys))
vm_id = manager.create_vm( vm_id = manager.create_vm(
template_id=vm_template_id, template_id=vm_template_id,
specs=specs, specs=specs,
ssh_key=settings.ONEADMIN_USER_SSH_PUBLIC_KEY, ssh_key='\n'.join(pub_keys),
vm_name=vm_name vm_name=vm_name
) )
@ -188,65 +191,9 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id):
email = BaseEmail(**email_data) email = BaseEmail(**email_data)
email.send() email.send()
# try to see if we have the IPv6 of the new vm and that if the ssh
# keys can be configured
vm_ipv6 = manager.get_ipv6(vm_id)
logger.debug("New VM ID is {vm_id}".format(vm_id=vm_id)) logger.debug("New VM ID is {vm_id}".format(vm_id=vm_id))
if vm_ipv6 is not None: if vm_id > 0:
custom_user = CustomUser.objects.get(email=user.get('email'))
get_or_create_vm_detail(custom_user, manager, vm_id) get_or_create_vm_detail(custom_user, manager, vm_id)
if custom_user is not None:
public_keys = get_all_public_keys(custom_user)
keys = [{'value': key, 'state': True} for key in
public_keys]
if len(keys) > 0:
logger.debug(
"Calling configure on {host} for "
"{num_keys} keys".format(
host=vm_ipv6, num_keys=len(keys)
)
)
# Let's wait until the IP responds to ping before we
# run the cdist configure on the host
did_manage_public_key = False
for i in range(0, 15):
if ping_ok(vm_ipv6):
logger.debug(
"{} is pingable. Doing a "
"manage_public_key".format(vm_ipv6)
)
sleep(10)
manager.manage_public_key(
keys, hosts=[vm_ipv6]
)
did_manage_public_key = True
break
else:
logger.debug(
"Can't ping {}. Wait 5 secs".format(
vm_ipv6
)
)
sleep(5)
if not did_manage_public_key:
emsg = ("Waited for over 75 seconds for {} to be "
"pingable. But the VM was not reachable. "
"So, gave up manage_public_key. Please do "
"this manually".format(vm_ipv6))
logger.error(emsg)
email_data = {
'subject': '{} CELERY TASK INCOMPLETE: {} not '
'pingable for 75 seconds'.format(
settings.DCL_TEXT, vm_ipv6
),
'from_email': current_task.request.hostname,
'to': settings.DCL_ERROR_EMAILS_TO_LIST,
'body': emsg
}
email = EmailMessage(**email_data)
email.send()
else:
logger.debug("VM's ipv6 is None. Hence not created VMDetail")
except Exception as e: except Exception as e:
logger.error(str(e)) logger.error(str(e))
try: try:

View file

@ -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 youll 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%}

View file

@ -131,6 +131,7 @@
<h5 class="billing-head">{% trans "Credit Card" %}</h5> <h5 class="billing-head">{% trans "Credit Card" %}</h5>
<h5 class="membership-lead">{% trans "Last" %} 4: ***** {{card.last4}}</h5> <h5 class="membership-lead">{% trans "Last" %} 4: ***** {{card.last4}}</h5>
<h5 class="membership-lead">{% trans "Type" %}: {{card.brand}}</h5> <h5 class="membership-lead">{% trans "Type" %}: {{card.brand}}</h5>
<h5 class="membership-lead">{% trans "Expiry" %}: {{card.exp_month}}/{{card.exp_year}}</h5>
</div> </div>
<div class="col-xs-6 text-right align-bottom"> <div class="col-xs-6 text-right align-bottom">
<a class="btn choice-btn choice-btn-faded" href="#" data-id_card="{{card.id}}">{% trans "SELECT" %}</a> <a class="btn choice-btn choice-btn-faded" href="#" data-id_card="{{card.id}}">{% trans "SELECT" %}</a>

View file

@ -41,6 +41,7 @@
<h4>{% trans "Payment method" %}:</h4> <h4>{% trans "Payment method" %}:</h4>
<p> <p>
{{cc_brand|default:_('Credit Card')}} {% trans "ending in" %} ****{{cc_last4}}<br> {{cc_brand|default:_('Credit Card')}} {% trans "ending in" %} ****{{cc_last4}}<br>
{% trans "Expiry" %} {{cc_exp_year}}/{{cc_exp_month}}<br/>
{{request.user.email}} {{request.user.email}}
</p> </p>
</div> </div>

View file

@ -1,12 +1,12 @@
from django.conf.urls import url from django.conf.urls import url
from django.views.generic import TemplateView, RedirectView from django.views.generic import TemplateView, RedirectView
from utils.views import AskSSHKeyView
from .views import ( from .views import (
IndexView, PaymentOrderView, OrderConfirmationView, IndexView, PaymentOrderView, OrderConfirmationView,
WhyDataCenterLightView, ContactUsView WhyDataCenterLightView, ContactUsView
) )
urlpatterns = [ urlpatterns = [
url(r'^$', IndexView.as_view(), name='index'), url(r'^$', IndexView.as_view(), name='index'),
url(r'^t/$', IndexView.as_view(), name='index_t'), url(r'^t/$', IndexView.as_view(), name='index_t'),
@ -20,6 +20,8 @@ urlpatterns = [
url(r'^payment/?$', PaymentOrderView.as_view(), name='payment'), url(r'^payment/?$', PaymentOrderView.as_view(), name='payment'),
url(r'^order-confirmation/?$', OrderConfirmationView.as_view(), url(r'^order-confirmation/?$', OrderConfirmationView.as_view(),
name='order_confirmation'), 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'^contact/?$', ContactUsView.as_view(), name='contact_us'),
url(r'glasfaser/?$', url(r'glasfaser/?$',
TemplateView.as_view(template_name='ungleich_page/glasfaser.html'), TemplateView.as_view(template_name='ungleich_page/glasfaser.html'),

View file

@ -1,8 +1,9 @@
import logging import logging
import pyotp import pyotp
import requests import requests
from django.contrib.sites.models import Site
from django.conf import settings from django.conf import settings
from django.contrib.sites.models import Site
from datacenterlight.tasks import create_vm_task from datacenterlight.tasks import create_vm_task
from hosting.models import HostingOrder, HostingBill, OrderDetail 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', for session_var in ['specs', 'template', 'billing_address',
'billing_address_data', 'card_id', 'billing_address_data', 'card_id',
'token', 'customer', 'generic_payment_type', '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: if session_var in request.session:
del request.session[session_var] del request.session[session_var]

View file

@ -13,18 +13,20 @@ from django.views.decorators.cache import cache_control
from django.views.generic import FormView, CreateView, DetailView from django.views.generic import FormView, CreateView, DetailView
from hosting.forms import ( from hosting.forms import (
HostingUserLoginForm, GenericPaymentForm, ProductPaymentForm HostingUserLoginForm, GenericPaymentForm, ProductPaymentForm,
UserHostingKeyForm
) )
from hosting.models import ( from hosting.models import (
HostingBill, HostingOrder, UserCardDetail, GenericProduct HostingBill, HostingOrder, UserCardDetail, GenericProduct, UserHostingKey
) )
from membership.models import CustomUser, StripeCustomer from membership.models import CustomUser, StripeCustomer
from opennebula_api.models import OpenNebulaManager
from opennebula_api.serializers import VMTemplateSerializer from opennebula_api.serializers import VMTemplateSerializer
from utils.forms import ( from utils.forms import (
BillingAddressForm, BillingAddressFormSignup, UserBillingAddressForm, BillingAddressForm, BillingAddressFormSignup, UserBillingAddressForm,
BillingAddress BillingAddress
) )
from utils.hosting_utils import get_vm_price_with_vat from utils.hosting_utils import get_vm_price_with_vat, get_all_public_keys
from utils.stripe_utils import StripeUtils from utils.stripe_utils import StripeUtils
from utils.tasks import send_plain_email_task from utils.tasks import send_plain_email_task
from .cms_models import DCLCalculatorPluginModel from .cms_models import DCLCalculatorPluginModel
@ -521,20 +523,34 @@ class PaymentOrderView(FormView):
request.session['customer'] = customer.stripe_id request.session['customer'] = customer.stripe_id
else: else:
request.session['customer'] = customer 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: else:
context = self.get_context_data() context = self.get_context_data()
context['billing_address_form'] = address_form context['billing_address_form'] = address_form
return self.render_to_response(context) return self.render_to_response(context)
class OrderConfirmationView(DetailView): class OrderConfirmationView(DetailView, FormView):
form_class = UserHostingKeyForm
template_name = "datacenterlight/order_detail.html" template_name = "datacenterlight/order_detail.html"
payment_template_name = 'datacenterlight/landing_payment.html' payment_template_name = 'datacenterlight/landing_payment.html'
context_object_name = "order" context_object_name = "order"
model = HostingOrder model = HostingOrder
def get_form_kwargs(self):
kwargs = super(OrderConfirmationView, self).get_form_kwargs()
kwargs.update({'request': self.request})
return kwargs
@cache_control(no_cache=True, must_revalidate=True, no_store=True) @cache_control(no_cache=True, must_revalidate=True, no_store=True)
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
context = {} context = {}
@ -552,11 +568,15 @@ class OrderConfirmationView(DetailView):
card_details_response = card_details['response_object'] card_details_response = card_details['response_object']
context['cc_last4'] = card_details_response['last4'] context['cc_last4'] = card_details_response['last4']
context['cc_brand'] = card_details_response['brand'] context['cc_brand'] = card_details_response['brand']
context['cc_exp_year'] = card_details_response['exp_year']
context['cc_exp_month'] = '{:02d}'.format(card_details_response['exp_month'])
else: else:
card_id = self.request.session.get('card_id') card_id = self.request.session.get('card_id')
card_detail = UserCardDetail.objects.get(id=card_id) card_detail = UserCardDetail.objects.get(id=card_id)
context['cc_last4'] = card_detail.last4 context['cc_last4'] = card_detail.last4
context['cc_brand'] = card_detail.brand context['cc_brand'] = card_detail.brand
context['cc_exp_year'] = card_detail.exp_year
context['cc_exp_month'] ='{:02d}'.format(card_detail.exp_month)
if ('generic_payment_type' in request.session and if ('generic_payment_type' in request.session and
self.request.session['generic_payment_type'] == 'generic'): self.request.session['generic_payment_type'] == 'generic'):
@ -567,6 +587,8 @@ class OrderConfirmationView(DetailView):
else: else:
context.update({ context.update({
'vm': request.session.get('specs'), 'vm': request.session.get('specs'),
'form': UserHostingKeyForm(request=self.request),
'keys': get_all_public_keys(self.request.user)
}) })
context.update({ context.update({
'site_url': reverse('datacenterlight:index'), 'site_url': reverse('datacenterlight:index'),
@ -830,6 +852,18 @@ class OrderConfirmationView(DetailView):
new_user = authenticate(username=custom_user.email, new_user = authenticate(username=custom_user.email,
password=password) password=password)
login(request, new_user) 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: else:
# We assume that if the user is here, his/her StripeCustomer # We assume that if the user is here, his/her StripeCustomer
# object already exists # object already exists

View file

@ -1,8 +1,8 @@
import datetime import datetime
import logging import logging
import subprocess import subprocess
import tempfile import tempfile
from django import forms from django import forms
from django.conf import settings from django.conf import settings
from django.contrib.auth import authenticate from django.contrib.auth import authenticate
@ -187,7 +187,8 @@ class UserHostingKeyForm(forms.ModelForm):
alerts the user of it. alerts the user of it.
:return: :return:
""" """
if 'generate' in self.request.POST: if ('generate' in self.request.POST
or not self.fields['public_key'].required):
return self.data.get('public_key') return self.data.get('public_key')
KEY_ERROR_MESSAGE = _("Please input a proper SSH key") KEY_ERROR_MESSAGE = _("Please input a proper SSH key")
openssh_pubkey_str = self.data.get('public_key').strip() openssh_pubkey_str = self.data.get('public_key').strip()
@ -214,10 +215,14 @@ class UserHostingKeyForm(forms.ModelForm):
return openssh_pubkey_str return openssh_pubkey_str
def clean_name(self): def clean_name(self):
INVALID_NAME_MESSAGE = _("Comma not accepted in the name of the key")
if "," in self.data.get('name'):
logger.debug(INVALID_NAME_MESSAGE)
raise forms.ValidationError(INVALID_NAME_MESSAGE)
return self.data.get('name') return self.data.get('name')
def clean_user(self): def clean_user(self):
return self.request.user return self.request.user if self.request.user.is_authenticated() else None
def clean(self): def clean(self):
cleaned_data = self.cleaned_data cleaned_data = self.cleaned_data

View file

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-09-08 08:45+0000\n" "POT-Creation-Date: 2019-07-09 15:21+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -27,6 +27,30 @@ msgstr "Dein Account wurde noch nicht aktiviert."
msgid "User does not exist" msgid "User does not exist"
msgstr "Der Benutzer existiert nicht" msgstr "Der Benutzer existiert nicht"
msgid "Choose a product"
msgstr ""
msgid "Amount in CHF"
msgstr "Betrag"
msgid "Recurring monthly"
msgstr ""
msgid "Amount field does not match"
msgstr ""
msgid "Recurring field does not match"
msgstr ""
msgid "Product name"
msgstr "Produkt"
msgid "Monthly subscription"
msgstr ""
msgid "One time payment"
msgstr ""
msgid "Confirm Password" msgid "Confirm Password"
msgstr "Passwort Bestätigung" msgstr "Passwort Bestätigung"
@ -52,6 +76,9 @@ msgstr "Bitte verwende einen gültigen SSH-Key"
msgid "This key exists already with the name \"%(name)s\"" msgid "This key exists already with the name \"%(name)s\""
msgstr "Der SSH-Key mit dem Name \"%(name)s\" existiert bereits" msgstr "Der SSH-Key mit dem Name \"%(name)s\" existiert bereits"
msgid "Comma not accepted in the name of the key"
msgstr ""
msgid "All Rights Reserved" msgid "All Rights Reserved"
msgstr "Alle Rechte vorbehalten" msgstr "Alle Rechte vorbehalten"
@ -209,7 +236,8 @@ msgstr "Du hast eine neue virtuelle Maschine bestellt!"
#, python-format #, python-format
msgid "Your order of <strong>%(vm_name)s</strong> has been charged." msgid "Your order of <strong>%(vm_name)s</strong> has been charged."
msgstr "Deine Bestellung von <strong>%(vm_name)s</strong> wurde entgegengenommen." msgstr ""
"Deine Bestellung von <strong>%(vm_name)s</strong> wurde entgegengenommen."
msgid "You can view your VM detail by clicking the button below." msgid "You can view your VM detail by clicking the button below."
msgstr "Um die Rechnung zu sehen, klicke auf den Button unten." msgstr "Um die Rechnung zu sehen, klicke auf den Button unten."
@ -305,6 +333,100 @@ msgstr "Dashboard"
msgid "Logout" msgid "Logout"
msgstr "Abmelden" msgstr "Abmelden"
#, python-format
msgid "%(page_header_text)s"
msgstr ""
msgid "Invoice #"
msgstr "Rechnung"
msgid "Date"
msgstr "Datum"
msgid "Status"
msgstr ""
msgid "Terminated"
msgstr "Beendet"
msgid "Approved"
msgstr "Akzeptiert"
msgid "Declined"
msgstr "Abgelehnt"
msgid "Billed to"
msgstr "Rechnungsadresse"
msgid "Payment method"
msgstr "Bezahlmethode"
msgid "ending in"
msgstr "endend in"
msgid "Invoice summary"
msgstr ""
msgid "Product"
msgstr "Produkt"
msgid "Period"
msgstr "Periode"
msgid "Cores"
msgstr "Prozessorkerne"
msgid "Memory"
msgstr "Arbeitsspeicher"
msgid "Disk space"
msgstr "Festplattenkapazität"
msgid "Subtotal"
msgstr "Zwischensumme"
msgid "VAT"
msgstr "Mehrwertsteuer"
msgid "Discount"
msgstr "Rabatt"
msgid "Total"
msgstr "Gesamt"
msgid "Amount"
msgstr "Betrag"
msgid "Description"
msgstr ""
msgid "Recurring"
msgstr ""
msgid "of every month"
msgstr ""
msgid "BACK TO LIST"
msgstr "ZURÜCK ZUR LISTE"
msgid "Some problem encountered. Please try again later."
msgstr "Ein Problem ist aufgetreten. Bitte versuche es später noch einmal."
msgid "VM ID"
msgstr ""
msgid "IP Address"
msgstr ""
msgid "See Invoice"
msgstr "Siehe Rechnung"
msgid "Page"
msgstr ""
msgid "of"
msgstr ""
msgid "Log in" msgid "Log in"
msgstr "Anmelden" msgstr "Anmelden"
@ -338,67 +460,15 @@ msgstr "Als gelesen markieren"
msgid "All notifications" msgid "All notifications"
msgstr "Alle Benachrichtigungen" msgstr "Alle Benachrichtigungen"
#, python-format
msgid "%(page_header_text)s"
msgstr ""
msgid "Date"
msgstr "Datum"
msgid "Status"
msgstr ""
msgid "Terminated"
msgstr "Beendet"
msgid "Approved"
msgstr "Akzeptiert"
msgid "Declined"
msgstr "Abgelehnt"
msgid "Billed to"
msgstr "Rechnungsadresse"
msgid "Payment method"
msgstr "Bezahlmethode"
msgid "ending in"
msgstr "endend in"
msgid "Credit Card" msgid "Credit Card"
msgstr "Kreditkarte" msgstr "Kreditkarte"
msgid "Expiry"
msgstr "Gültig bis"
msgid "Order summary" msgid "Order summary"
msgstr "Bestellungsübersicht" msgstr "Bestellungsübersicht"
msgid "Product"
msgstr "Produkt"
msgid "Period"
msgstr "Periode"
msgid "Cores"
msgstr "Prozessorkerne"
msgid "Memory"
msgstr "Arbeitsspeicher"
msgid "Disk space"
msgstr "Festplattenkapazität"
msgid "Subtotal"
msgstr "Zwischensumme"
msgid "VAT"
msgstr "Mehrwertsteuer"
msgid "Discount"
msgstr "Rabatt"
msgid "Total"
msgstr "Gesamt"
#, python-format #, python-format
msgid "" msgid ""
"By clicking \"Place order\" this plan will charge your credit card account " "By clicking \"Place order\" this plan will charge your credit card account "
@ -410,9 +480,6 @@ msgstr ""
msgid "Place order" msgid "Place order"
msgstr "Bestellen" msgstr "Bestellen"
msgid "BACK TO LIST"
msgstr "ZURÜCK ZUR LISTE"
msgid "Processing..." msgid "Processing..."
msgstr "Abarbeitung..." msgstr "Abarbeitung..."
@ -425,24 +492,9 @@ msgstr ""
msgid "Close" msgid "Close"
msgstr "Schliessen" msgstr "Schliessen"
msgid "Some problem encountered. Please try again later."
msgstr "Ein Problem ist aufgetreten. Bitte versuche es später noch einmal."
msgid "Order Nr." msgid "Order Nr."
msgstr "Bestellung Nr." msgstr "Bestellung Nr."
msgid "Amount"
msgstr "Betrag"
msgid "See Invoice"
msgstr "Siehe Rechnung"
msgid "Page"
msgstr ""
msgid "of"
msgstr ""
msgid "Your Order" msgid "Your Order"
msgstr "Deine Bestellung" msgstr "Deine Bestellung"
@ -539,9 +591,6 @@ msgstr ""
"Wir nutzen <a href=\"https://stripe.com\" target=\"_blank\">Stripe</a> für " "Wir nutzen <a href=\"https://stripe.com\" target=\"_blank\">Stripe</a> für "
"die Bezahlung und speichern keine Informationen in unserer Datenbank." "die Bezahlung und speichern keine Informationen in unserer Datenbank."
msgid "Add your public SSH key"
msgstr "Füge deinen öffentlichen SSH-Key hinzu"
msgid "Use your created key to access to the VM" msgid "Use your created key to access to the VM"
msgstr "Benutze deinen erstellten SSH-Key um auf deine VM zugreifen zu können" msgstr "Benutze deinen erstellten SSH-Key um auf deine VM zugreifen zu können"
@ -783,6 +832,9 @@ msgstr ""
msgid "Invalid number of cores" msgid "Invalid number of cores"
msgstr "Ungültige Anzahle CPU-Kerne" msgstr "Ungültige Anzahle CPU-Kerne"
msgid "Invalid calculator properties"
msgstr ""
msgid "Invalid RAM size" msgid "Invalid RAM size"
msgstr "Ungültige RAM-Grösse" msgstr "Ungültige RAM-Grösse"
@ -821,6 +873,9 @@ msgstr ""
"Es gab einen Fehler bei der Bearbeitung Deine Anfrage. Bitte versuche es " "Es gab einen Fehler bei der Bearbeitung Deine Anfrage. Bitte versuche es "
"noch einmal." "noch einmal."
#~ msgid "Add your public SSH key"
#~ msgstr "Füge deinen öffentlichen SSH-Key hinzu"
#~ msgid "Do you want to cancel your Virtual Machine" #~ msgid "Do you want to cancel your Virtual Machine"
#~ msgstr "Bist Du sicher, dass Du Deine virtuelle Maschine beenden willst" #~ msgstr "Bist Du sicher, dass Du Deine virtuelle Maschine beenden willst"
@ -830,9 +885,6 @@ msgstr ""
#~ msgid "My VM page" #~ msgid "My VM page"
#~ msgstr "Meine VM page" #~ msgstr "Meine VM page"
#~ msgid "Invoice Date"
#~ msgstr "Rechnung Datum"
#~ msgid "VM %(VM_ID)s terminated successfully" #~ msgid "VM %(VM_ID)s terminated successfully"
#~ msgstr "VM %(VM_ID)s erfolgreich beendet" #~ msgstr "VM %(VM_ID)s erfolgreich beendet"

View file

@ -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),
),
]

View file

@ -187,7 +187,7 @@ class HostingOrder(AssignPermissionsMixin, models.Model):
class UserHostingKey(models.Model): class UserHostingKey(models.Model):
user = models.ForeignKey(CustomUser) user = models.ForeignKey(CustomUser, blank=True, null=True)
public_key = models.TextField() public_key = models.TextField()
private_key = models.FileField(upload_to='private_keys', blank=True) private_key = models.FileField(upload_to='private_keys', blank=True)
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
@ -613,6 +613,8 @@ class UserCardDetail(AssignPermissionsMixin, models.Model):
for card in user_card_details: for card in user_card_details:
cards_list.append({ cards_list.append({
'last4': card.last4, 'brand': card.brand, 'id': card.id, 'last4': card.last4, 'brand': card.brand, 'id': card.id,
'exp_year': card.exp_year,
'exp_month': '{:02d}'.format(card.exp_month),
'preferred': card.preferred 'preferred': card.preferred
}) })
return cards_list return cards_list

View file

@ -109,8 +109,11 @@ $(document).ready(function() {
modal_btn = $('#createvm-modal-done-btn'); modal_btn = $('#createvm-modal-done-btn');
$('#createvm-modal-title').text(data.msg_title); $('#createvm-modal-title').text(data.msg_title);
$('#createvm-modal-body').html(data.msg_body); $('#createvm-modal-body').html(data.msg_body);
modal_btn.attr('href', data.redirect) if (data.redirect) {
.removeClass('hide'); modal_btn.attr('href', data.redirect).removeClass('hide');
} else {
modal_btn.attr('href', "");
}
if (data.status === true) { if (data.status === true) {
fa_icon.attr('class', 'checkmark'); fa_icon.attr('class', 'checkmark');
} else { } else {

View file

@ -82,6 +82,7 @@
{{user.email}} {{user.email}}
{% else %} {% else %}
{{cc_brand|default:_('Credit Card')}} {% trans "ending in" %} ****{{cc_last4}}<br> {{cc_brand|default:_('Credit Card')}} {% trans "ending in" %} ****{{cc_last4}}<br>
{% trans "Expiry" %} {{cc_exp_year}}/{{cc_exp_month}}<br/>
{% if request.user.is_authenticated %} {% if request.user.is_authenticated %}
{{request.user.email}} {{request.user.email}}
{% else %} {% else %}

View file

@ -131,6 +131,7 @@
<h5 class="billing-head">{% trans "Credit Card" %}</h5> <h5 class="billing-head">{% trans "Credit Card" %}</h5>
<h5 class="membership-lead">{% trans "Last" %} 4: ***** {{card.last4}}</h5> <h5 class="membership-lead">{% trans "Last" %} 4: ***** {{card.last4}}</h5>
<h5 class="membership-lead">{% trans "Type" %}: {{card.brand}}</h5> <h5 class="membership-lead">{% trans "Type" %}: {{card.brand}}</h5>
<h5 class="membership-lead">{% trans "Expiry" %}: {{card.exp_month}}/{{card.exp_year}}</h5>
</div> </div>
<div class="col-xs-6 text-right align-bottom"> <div class="col-xs-6 text-right align-bottom">
<a class="btn choice-btn choice-btn-faded" href="#" data-id_card="{{card.id}}">{% trans "SELECT" %}</a> <a class="btn choice-btn choice-btn-faded" href="#" data-id_card="{{card.id}}">{% trans "SELECT" %}</a>

View file

@ -37,6 +37,7 @@
<h5 class="billing-head">{% trans "Credit Card" %}</h5> <h5 class="billing-head">{% trans "Credit Card" %}</h5>
<h5 class="membership-lead">{% trans "Last" %} 4: ***** {{card.last4}}</h5> <h5 class="membership-lead">{% trans "Last" %} 4: ***** {{card.last4}}</h5>
<h5 class="membership-lead">{% trans "Type" %}: {{card.brand}}</h5> <h5 class="membership-lead">{% trans "Type" %}: {{card.brand}}</h5>
<h5 class="membership-lead">{% trans "Expiry" %}: {{card.exp_month}}/{{card.exp_year}}</h5>
<div class="credit-card-details-opt"> <div class="credit-card-details-opt">
<div class="row"> <div class="row">
{% if card_list_len > 1 %} {% if card_list_len > 1 %}

View file

@ -8,7 +8,8 @@
<form method="POST" action="" novalidate class="form-ssh"> <form method="POST" action="" novalidate class="form-ssh">
{% csrf_token %} {% csrf_token %}
<div class="page-header"> <div class="page-header">
<h1 class="h1-thin"><i class="fa fa-key" aria-hidden="true"></i>&nbsp;{% trans "Add your public SSH key" %}</h1> <h1 class="h1-thin"><i class="fa fa-key" aria-hidden="true"></i>&nbsp;{% if title %}{% trans title %}{% else %} {% endif %}</h1>
{% if sub_title %}<span>{% trans sub_title %}</span>{% else %}{% endif %}
</div> </div>
{% if messages %} {% if messages %}
<div class="alert alert-warning"> <div class="alert alert-warning">

View file

@ -1,5 +1,7 @@
from django.conf.urls import url from django.conf.urls import url
from django.contrib.auth import views as auth_views from django.contrib.auth import views as auth_views
from utils.views import SSHKeyCreateView, AskSSHKeyView
from .views import ( from .views import (
DjangoHostingView, RailsHostingView, PaymentVMView, NodeJSHostingView, DjangoHostingView, RailsHostingView, PaymentVMView, NodeJSHostingView,
LoginView, SignupView, SignupValidateView, SignupValidatedView, IndexView, LoginView, SignupView, SignupValidateView, SignupValidatedView, IndexView,
@ -7,12 +9,11 @@ from .views import (
VirtualMachinesPlanListView, VirtualMachineView, OrdersHostingDeleteView, VirtualMachinesPlanListView, VirtualMachineView, OrdersHostingDeleteView,
MarkAsReadNotificationView, PasswordResetView, PasswordResetConfirmView, MarkAsReadNotificationView, PasswordResetView, PasswordResetConfirmView,
HostingPricingView, CreateVirtualMachinesView, HostingBillListView, HostingPricingView, CreateVirtualMachinesView, HostingBillListView,
HostingBillDetailView, SSHKeyDeleteView, SSHKeyCreateView, SSHKeyListView, HostingBillDetailView, SSHKeyDeleteView, SSHKeyListView,
SSHKeyChoiceView, DashboardView, SettingsView, ResendActivationEmailView, SSHKeyChoiceView, DashboardView, SettingsView, ResendActivationEmailView,
InvoiceListView, InvoiceDetailView, CheckUserVM InvoiceListView, InvoiceDetailView, CheckUserVM
) )
urlpatterns = [ urlpatterns = [
url(r'index/?$', IndexView.as_view(), name='index'), url(r'index/?$', IndexView.as_view(), name='index'),
url(r'django/?$', DjangoHostingView.as_view(), name='djangohosting'), url(r'django/?$', DjangoHostingView.as_view(), name='djangohosting'),
@ -27,6 +28,8 @@ urlpatterns = [
url(r'invoices/?$', InvoiceListView.as_view(), name='invoices'), url(r'invoices/?$', InvoiceListView.as_view(), name='invoices'),
url(r'order-confirmation/?$', OrdersHostingDetailView.as_view(), url(r'order-confirmation/?$', OrdersHostingDetailView.as_view(),
name='order-confirmation'), name='order-confirmation'),
url(r'^add-ssh-key/?$', AskSSHKeyView.as_view(),
name='add_ssh_key'),
url(r'orders/(?P<pk>\d+)/?$', OrdersHostingDetailView.as_view(), url(r'orders/(?P<pk>\d+)/?$', OrdersHostingDetailView.as_view(),
name='orders'), name='orders'),
url(r'invoice/(?P<invoice_id>[-\w]+)/?$', InvoiceDetailView.as_view(), url(r'invoice/(?P<invoice_id>[-\w]+)/?$', InvoiceDetailView.as_view(),

View file

@ -49,6 +49,7 @@ from utils.forms import (
BillingAddressForm, PasswordResetRequestForm, UserBillingAddressForm, BillingAddressForm, PasswordResetRequestForm, UserBillingAddressForm,
ResendActivationEmailForm ResendActivationEmailForm
) )
from utils.hosting_utils import get_all_public_keys
from utils.hosting_utils import get_vm_price_with_vat, HostingUtils from utils.hosting_utils import get_vm_price_with_vat, HostingUtils
from utils.mailer import BaseEmail from utils.mailer import BaseEmail
from utils.stripe_utils import StripeUtils from utils.stripe_utils import StripeUtils
@ -466,7 +467,9 @@ class SSHKeyDeleteView(LoginRequiredMixin, DeleteView):
pk = self.kwargs.get('pk') pk = self.kwargs.get('pk')
# Get user ssh key # Get user ssh key
public_key = UserHostingKey.objects.get(pk=pk).public_key public_key = UserHostingKey.objects.get(pk=pk).public_key
manager.manage_public_key([{'value': public_key, 'state': False}]) keys = UserHostingKey.objects.filter(user=self.request.user)
keys_to_save = [k.public_key for k in keys if k.public_key != public_key]
manager.save_key_in_opennebula_user('\n'.join(keys_to_save), update_type=0)
return super(SSHKeyDeleteView, self).delete(request, *args, **kwargs) return super(SSHKeyDeleteView, self).delete(request, *args, **kwargs)
@ -515,74 +518,11 @@ class SSHKeyChoiceView(LoginRequiredMixin, View):
email=owner.email, email=owner.email,
password=owner.password password=owner.password
) )
public_key_str = public_key.decode() keys = get_all_public_keys(request.user)
manager.manage_public_key([{'value': public_key_str, 'state': True}]) manager.save_key_in_opennebula_user('\n'.join(keys))
return redirect(reverse_lazy('hosting:ssh_keys'), foo='bar') 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
)
public_key = form.cleaned_data['public_key']
if type(public_key) is bytes:
public_key = public_key.decode()
manager.manage_public_key([{'value': public_key, 'state': True}])
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') @method_decorator(decorators, name='dispatch')
class SettingsView(LoginRequiredMixin, FormView): class SettingsView(LoginRequiredMixin, FormView):
template_name = "hosting/settings.html" template_name = "hosting/settings.html"
@ -829,21 +769,27 @@ class PaymentVMView(LoginRequiredMixin, FormView):
reverse('hosting:payment') + '#payment_error') reverse('hosting:payment') + '#payment_error')
request.session['token'] = token request.session['token'] = token
request.session['billing_address_data'] = billing_address_data request.session['billing_address_data'] = billing_address_data
return HttpResponseRedirect("{url}?{query_params}".format( self.request.session['order_confirm_url'] = "{url}?{query_params}".format(
url=reverse('hosting:order-confirmation'), url=reverse('hosting:order-confirmation'),
query_params='page=payment') query_params='page=payment')
) return HttpResponseRedirect(reverse('hosting:add_ssh_key'))
else: else:
return self.form_invalid(form) return self.form_invalid(form)
class OrdersHostingDetailView(LoginRequiredMixin, DetailView): class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView):
form_class = UserHostingKeyForm
template_name = "hosting/order_detail.html" template_name = "hosting/order_detail.html"
context_object_name = "order" context_object_name = "order"
login_url = reverse_lazy('hosting:login') login_url = reverse_lazy('hosting:login')
permission_required = ['view_hostingorder'] permission_required = ['view_hostingorder']
model = HostingOrder model = HostingOrder
def get_form_kwargs(self):
kwargs = super(OrdersHostingDetailView, self).get_form_kwargs()
kwargs.update({'request': self.request})
return kwargs
def get_object(self, queryset=None): def get_object(self, queryset=None):
order_id = self.kwargs.get('pk') order_id = self.kwargs.get('pk')
try: try:
@ -868,6 +814,8 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
if self.request.GET.get('page') == 'payment': if self.request.GET.get('page') == 'payment':
context['page_header_text'] = _('Confirm Order') context['page_header_text'] = _('Confirm Order')
context['form'] = UserHostingKeyForm(request=self.request)
context['keys'] = get_all_public_keys(self.request.user)
else: else:
context['page_header_text'] = _('Invoice') context['page_header_text'] = _('Invoice')
if not self.request.user.has_perm( if not self.request.user.has_perm(
@ -958,11 +906,15 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
card_details_response = card_details['response_object'] card_details_response = card_details['response_object']
context['cc_last4'] = card_details_response['last4'] context['cc_last4'] = card_details_response['last4']
context['cc_brand'] = card_details_response['brand'] context['cc_brand'] = card_details_response['brand']
context['cc_exp_year'] = card_details_response['exp_year']
context['cc_exp_month'] = card_details_response['exp_month']
else: else:
card_id = self.request.session.get('card_id') card_id = self.request.session.get('card_id')
card_detail = UserCardDetail.objects.get(id=card_id) card_detail = UserCardDetail.objects.get(id=card_id)
context['cc_last4'] = card_detail.last4 context['cc_last4'] = card_detail.last4
context['cc_brand'] = card_detail.brand context['cc_brand'] = card_detail.brand
context['cc_exp_year'] = card_detail.exp_year
context['cc_exp_month'] = '{:02d}'.format(card_detail.exp_month)
context['site_url'] = reverse('hosting:create_virtual_machine') context['site_url'] = reverse('hosting:create_virtual_machine')
context['vm'] = self.request.session.get('specs') context['vm'] = self.request.session.get('specs')
return context return context
@ -1587,6 +1539,7 @@ class VirtualMachineView(LoginRequiredMixin, View):
'order': HostingOrder.objects.get( 'order': HostingOrder.objects.get(
vm_id=serializer.data['vm_id'] vm_id=serializer.data['vm_id']
), ),
'keys': UserHostingKey.objects.filter(user=request.user),
'has_invoices': False 'has_invoices': False
} }
try: try:
@ -1665,7 +1618,7 @@ class VirtualMachineView(LoginRequiredMixin, View):
"manager.delete_vm returned False. Hence, error making " "manager.delete_vm returned False. Hence, error making "
"xml-rpc call to delete vm failed." "xml-rpc call to delete vm failed."
) )
response['text'] = ugettext('Error terminating VM') + vm.id response['text'] = str(_('Error terminating VM')) + str(vm.id)
else: else:
for t in range(15): for t in range(15):
try: try:

View file

@ -207,22 +207,8 @@ class OpenNebulaManager():
else: else:
vm_pool.info() vm_pool.info()
return vm_pool return vm_pool
except AttributeError: except AttributeError as ae:
logger.error( logger.error("AttributeError : %s" % str(ae))
'Could not connect via client, using oneadmin instead')
try:
vm_pool = oca.VirtualMachinePool(self.oneadmin_client)
if infoextended:
vm_pool.infoextended(
filter=-1, # User's resources and any of his groups
vm_state=-1 # Look for VMs in any state, except DONE
)
else:
vm_pool.info(filter=-2)
return vm_pool
except:
raise ConnectionRefusedError
except ConnectionRefusedError: except ConnectionRefusedError:
logger.error( logger.error(
'Could not connect to host: {host} via protocol {protocol}'.format( 'Could not connect to host: {host} via protocol {protocol}'.format(
@ -377,6 +363,31 @@ class OpenNebulaManager():
return vm_terminated return vm_terminated
def save_key_in_opennebula_user(self, ssh_key, update_type=1):
"""
Save the given ssh key in OpenNebula user
# Update type: 0: Replace the whole template.
1: Merge new template with the existing one.
:param ssh_key: The ssh key to be saved
:param update_type: The update type as explained above
:return:
"""
return_value = self.oneadmin_client.call(
'user.update',
self.opennebula_user if type(self.opennebula_user) == int else self.opennebula_user.id,
'<CONTEXT><SSH_PUBLIC_KEY>%s</SSH_PUBLIC_KEY></CONTEXT>' % ssh_key,
update_type
)
if type(return_value) == int:
logger.debug(
"Saved the key in opennebula successfully : %s" % return_value)
else:
logger.error(
"Could not save the key in opennebula. %s" % return_value)
return
def _get_template_pool(self): def _get_template_pool(self):
try: try:
template_pool = oca.VmTemplatePool(self.oneadmin_client) template_pool = oca.VmTemplatePool(self.oneadmin_client)

View file

@ -1,16 +1,25 @@
import uuid
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.auth import authenticate, login from django.contrib.auth import authenticate, login
from django.contrib.auth.tokens import default_token_generator 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.core.urlresolvers import reverse_lazy
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.utils.encoding import force_bytes from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
from django.utils.translation import ugettext_lazy as _ 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.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 membership.models import CustomUser
from opennebula_api.models import OpenNebulaManager
from utils.hosting_utils import get_all_public_keys
from .forms import SetPasswordForm from .forms import SetPasswordForm
from .mailer import BaseEmail from .mailer import BaseEmail
@ -174,3 +183,87 @@ class PasswordResetConfirmViewMixin(FormView):
form.add_error(None, form.add_error(None,
_('The reset password link is no longer valid.')) _('The reset password link is no longer valid.'))
return self.form_invalid(form) 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)