- {% comment %}
+ {% if vm.created_at %}
+
+ {% trans "Period" %}:
+ {{ vm.created_at|date:'Y/m/d' }} - {% if vm.terminated_at %}{{ vm.terminated_at|date:'Y/m/d' }}{% else %}{% now 'Y/m/d' %}{% endif %}
+
+ {% endif %}
- {% trans "Period" %}
- {{}}
-
- {% endcomment %}
-
- {% trans "Cores" %}
+ {% trans "Cores" %}:
{% if vm.cores %}
{{vm.cores|floatformat}}
{% else %}
@@ -117,11 +119,11 @@
{% endif %}
- {% trans "Memory" %}
+ {% trans "Memory" %}:
{{vm.memory}} GB
- {% trans "Disk space" %}
+ {% trans "Disk space" %}:
{{vm.disk_size}} GB
@@ -168,22 +170,19 @@
{% trans "Processing..." %}
-
-
+
{% trans "Hold tight, we are processing your request" %}
@@ -212,4 +211,4 @@
{% endif %}
-{% endblock js_extra %}
\ No newline at end of file
+{% endblock js_extra %}
diff --git a/hosting/templates/hosting/resend_activation_link.html b/hosting/templates/hosting/resend_activation_link.html
new file mode 100644
index 00000000..fffb6e59
--- /dev/null
+++ b/hosting/templates/hosting/resend_activation_link.html
@@ -0,0 +1,36 @@
+{% extends "hosting/base_short.html" %}
+{% load staticfiles bootstrap3%}
+{% load i18n %}
+
+{% block navbar %}
+ {% include 'hosting/includes/_navbar_transparent.html' %}
+{% endblock navbar %}
+
+
+{% block content %}
+
+
+
+
+
{% trans "Your VM hosted in Switzerland"%}
+
+
+
+
{% trans "Resend activation link"%}
+
+
+
+
+
+
+{% endblock %}
diff --git a/hosting/templates/hosting/user_keys.html b/hosting/templates/hosting/user_keys.html
index 1cfb880c..6810efdf 100644
--- a/hosting/templates/hosting/user_keys.html
+++ b/hosting/templates/hosting/user_keys.html
@@ -101,21 +101,5 @@
window.location.href = '{{next_url}}';
{% endif %}
-
-
-
-
-
{%endblock%}
diff --git a/hosting/templates/hosting/virtual_machine_detail.html b/hosting/templates/hosting/virtual_machine_detail.html
index 06d86032..0b882055 100644
--- a/hosting/templates/hosting/virtual_machine_detail.html
+++ b/hosting/templates/hosting/virtual_machine_detail.html
@@ -109,7 +109,7 @@
{{virtual_machine.name}}
@@ -123,8 +123,9 @@
-
-
{% blocktrans with machine_name=virtual_machine.name %}Your Virtual Machine {{machine_name}} is successfully terminated!{% endblocktrans %}
+
+
+
{% blocktrans with machine_name=virtual_machine.name %}Your Virtual Machine {{machine_name}} is successfully terminated!{% endblocktrans %}
diff --git a/hosting/urls.py b/hosting/urls.py
index 2868c717..f40e803a 100644
--- a/hosting/urls.py
+++ b/hosting/urls.py
@@ -8,8 +8,7 @@ from .views import (
MarkAsReadNotificationView, PasswordResetView, PasswordResetConfirmView,
HostingPricingView, CreateVirtualMachinesView, HostingBillListView,
HostingBillDetailView, SSHKeyDeleteView, SSHKeyCreateView, SSHKeyListView,
- SSHKeyChoiceView, DashboardView, SettingsView)
-
+ SSHKeyChoiceView, DashboardView, SettingsView, ResendActivationEmailView)
urlpatterns = [
url(r'index/?$', IndexView.as_view(), name='index'),
@@ -52,6 +51,8 @@ urlpatterns = [
url(r'signup/?$', SignupView.as_view(), name='signup'),
url(r'signup-validate/?$', SignupValidateView.as_view(),
name='signup-validate'),
+ url(r'resend-activation-link/?$', ResendActivationEmailView.as_view(),
+ name='resend_activation_link'),
url(r'reset-password/?$', PasswordResetView.as_view(),
name='reset_password'),
url(r'reset-password-confirm/(?P
[0-9A-Za-z]+)-(?P.+)/$',
diff --git a/hosting/views.py b/hosting/views.py
index 1c007780..4d736aa8 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -1,6 +1,7 @@
import json
import logging
import uuid
+from datetime import datetime
from time import sleep
from django import forms
@@ -33,20 +34,25 @@ from membership.models import CustomUser, StripeCustomer
from opennebula_api.models import OpenNebulaManager
from opennebula_api.serializers import VirtualMachineSerializer, \
VirtualMachineTemplateSerializer, VMTemplateSerializer
-from utils.forms import BillingAddressForm, PasswordResetRequestForm, \
- UserBillingAddressForm
+from utils.forms import (
+ BillingAddressForm, PasswordResetRequestForm, UserBillingAddressForm,
+ ResendActivationEmailForm
+)
+from utils.hosting_utils import get_vm_price
from utils.mailer import BaseEmail
from utils.stripe_utils import StripeUtils
from utils.views import (
- PasswordResetViewMixin, PasswordResetConfirmViewMixin, LoginViewMixin
+ PasswordResetViewMixin, PasswordResetConfirmViewMixin, LoginViewMixin,
+ ResendActivationLinkViewMixin
)
from .forms import HostingUserSignupForm, HostingUserLoginForm, \
UserHostingKeyForm, generate_ssh_key_name
from .mixins import ProcessVMSelectionMixin
-from .models import HostingOrder, HostingBill, HostingPlan, UserHostingKey
+from .models import (
+ HostingOrder, HostingBill, HostingPlan, UserHostingKey, VMDetail
+)
from datacenterlight.models import VMTemplate
-
logger = logging.getLogger(__name__)
CONNECTION_ERROR = "Your VMs cannot be displayed at the moment due to a \
@@ -282,6 +288,14 @@ class SignupValidatedView(SignupValidateView):
return context
+class ResendActivationEmailView(ResendActivationLinkViewMixin):
+ template_name = 'hosting/resend_activation_link.html'
+ form_class = ResendActivationEmailForm
+ success_url = reverse_lazy('hosting:login')
+ email_template_path = 'datacenterlight/emails/'
+ email_template_name = 'user_activation'
+
+
class PasswordResetView(PasswordResetViewMixin):
site = 'dcl'
template_name = 'hosting/reset_password.html'
@@ -678,25 +692,30 @@ class OrdersHostingDetailView(LoginRequiredMixin,
if obj is not None:
# invoice for previous order
try:
- manager = OpenNebulaManager(
- email=owner.email, password=owner.password
- )
- vm = manager.get_vm(obj.vm_id)
- context['vm'] = VirtualMachineSerializer(vm).data
- 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.')
- )
+ vm_detail = VMDetail.objects.get(vm_id=obj.vm_id)
+ context['vm'] = vm_detail.__dict__
+ context['vm']['name'] = '{}-{}'.format(context['vm']['configuration'], context['vm']['vm_id'])
+ except VMDetail.DoesNotExist:
+ try:
+ manager = OpenNebulaManager(
+ email=owner.email, password=owner.password
+ )
+ vm = manager.get_vm(obj.vm_id)
+ context['vm'] = VirtualMachineSerializer(vm).data
+ 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.')
+ )
elif not card_details.get('response_object'):
# new order, failed to get card details
context['failed_payment'] = True
@@ -756,12 +775,11 @@ class OrdersHostingDetailView(LoginRequiredMixin,
cpu = specs.get('cpu')
memory = specs.get('memory')
disk_size = specs.get('disk_size')
- amount_to_be_charged = (cpu * 5) + (memory * 2) + (disk_size * 0.6)
- plan_name = "{cpu} Cores, {memory} GB RAM, {disk_size} GB SSD".format(
- cpu=cpu,
- memory=memory,
- disk_size=disk_size)
-
+ amount_to_be_charged = get_vm_price(cpu=cpu, memory=memory,
+ disk_size=disk_size)
+ plan_name = StripeUtils.get_stripe_plan_name(cpu=cpu,
+ memory=memory,
+ disk_size=disk_size)
stripe_plan_id = StripeUtils.get_stripe_plan_id(cpu=cpu,
ram=memory,
ssd=disk_size,
@@ -806,9 +824,10 @@ class OrdersHostingDetailView(LoginRequiredMixin,
'status': True,
'redirect': reverse('hosting:virtual_machines'),
'msg_title': str(_('Thank you for the order.')),
- 'msg_body': str(_('Your VM will be up and running in a few moments.'
- ' We will send you a confirmation email as soon as'
- ' it is ready.'))
+ 'msg_body': str(
+ _('Your VM will be up and running in a few moments.'
+ ' We will send you a confirmation email as soon as'
+ ' it is ready.'))
}
return HttpResponse(json.dumps(response),
@@ -905,7 +924,6 @@ class CreateVirtualMachinesView(LoginRequiredMixin, View):
memory_field = forms.IntegerField(validators=[self.validate_memory])
storage = request.POST.get('storage')
storage_field = forms.IntegerField(validators=[self.validate_storage])
- price = request.POST.get('total')
template_id = int(request.POST.get('config'))
template = VMTemplate.objects.filter(
opennebula_vm_template_id=template_id).first()
@@ -937,7 +955,8 @@ class CreateVirtualMachinesView(LoginRequiredMixin, View):
extra_tags='storage')
return HttpResponseRedirect(
reverse('datacenterlight:index') + "#order_form")
-
+ price = get_vm_price(cpu=cores, memory=memory,
+ disk_size=storage)
specs = {
'cpu': cores,
'memory': memory,
@@ -1043,6 +1062,10 @@ class VirtualMachineView(LoginRequiredMixin, View):
except WrongIdError:
response['status'] = True
response['text'] = ugettext('Terminated')
+ vm_detail_obj = VMDetail.objects.filter(
+ vm_id=opennebula_vm_id).first()
+ vm_detail_obj.terminated_at = datetime.utcnow()
+ vm_detail_obj.save()
break
except BaseException:
break
diff --git a/requirements.txt b/requirements.txt
index 6446a5c9..89285c83 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -96,5 +96,4 @@ pyflakes==1.5.0
billiard==3.5.0.3
amqp==2.2.1
vine==1.1.4
-#git+https://github.com/ungleich/cdist.git#egg=cdist
-file:///home/app/cdist#egg=cdist
+cdist==4.7.0
diff --git a/utils/forms.py b/utils/forms.py
index c3f3b6db..a12034dd 100644
--- a/utils/forms.py
+++ b/utils/forms.py
@@ -18,7 +18,8 @@ class SignupFormMixin(forms.ModelForm):
model = CustomUser
fields = ['name', 'email', 'password']
widgets = {
- 'name': forms.TextInput(attrs={'placeholder': _('Enter your name or company name')}),
+ 'name': forms.TextInput(
+ attrs={'placeholder': _('Enter your name or company name')}),
}
def clean_confirm_password(self):
@@ -42,7 +43,7 @@ class LoginFormMixin(forms.Form):
is_auth = authenticate(email=email, password=password)
if not is_auth:
raise forms.ValidationError(
- "Your username and/or password were incorrect.")
+ _("Your username and/or password were incorrect."))
return self.cleaned_data
def clean_email(self):
@@ -51,7 +52,24 @@ class LoginFormMixin(forms.Form):
CustomUser.objects.get(email=email)
return email
except CustomUser.DoesNotExist:
- raise forms.ValidationError("User does not exist")
+ raise forms.ValidationError(_("User does not exist"))
+
+
+class ResendActivationEmailForm(forms.Form):
+ email = forms.CharField(widget=forms.EmailInput())
+
+ class Meta:
+ fields = ['email']
+
+ def clean_email(self):
+ email = self.cleaned_data.get('email')
+ try:
+ c = CustomUser.objects.get(email=email)
+ if c.validated == 1:
+ raise forms.ValidationError(_("The account is already active."))
+ return email
+ except CustomUser.DoesNotExist:
+ raise forms.ValidationError(_("User does not exist"))
class PasswordResetRequestForm(forms.Form):
@@ -66,7 +84,7 @@ class PasswordResetRequestForm(forms.Form):
CustomUser.objects.get(email=email)
return email
except CustomUser.DoesNotExist:
- raise forms.ValidationError("User does not exist")
+ raise forms.ValidationError(_("User does not exist"))
class SetPasswordForm(forms.Form):
@@ -75,11 +93,11 @@ class SetPasswordForm(forms.Form):
password
"""
error_messages = {
- 'password_mismatch': ("The two password fields didn't match."),
+ 'password_mismatch': _("The two password fields didn't match."),
}
- new_password1 = forms.CharField(label=("New password"),
+ new_password1 = forms.CharField(label=_("New password"),
widget=forms.PasswordInput)
- new_password2 = forms.CharField(label=("New password confirmation"),
+ new_password2 = forms.CharField(label=_("New password confirmation"),
widget=forms.PasswordInput)
def clean_new_password2(self):
diff --git a/utils/hosting_utils.py b/utils/hosting_utils.py
index 7c1a83ad..3c193ad7 100644
--- a/utils/hosting_utils.py
+++ b/utils/hosting_utils.py
@@ -1,4 +1,10 @@
-from hosting.models import UserHostingKey
+import logging
+from oca.pool import WrongIdError
+
+from hosting.models import UserHostingKey, VMDetail
+from opennebula_api.serializers import VirtualMachineSerializer
+
+logger = logging.getLogger(__name__)
def get_all_public_keys(customer):
@@ -9,3 +15,48 @@ def get_all_public_keys(customer):
"""
return UserHostingKey.objects.filter(user_id=customer.id).values_list(
"public_key", flat=True)
+
+
+def get_or_create_vm_detail(user, manager, vm_id):
+ """
+ Returns VMDetail object related to given vm_id. Creates the object
+ if it does not exist
+
+ :param vm_id: The ID of the VM which should be greater than 0.
+ :param user: The CustomUser object that owns this VM
+ :param manager: The OpenNebulaManager object
+ :return: The VMDetail object. None if vm_id is less than or equal to 0.
+ Also, for the cases where the VMDetail does not exist and we can not
+ fetch data about the VM from OpenNebula, the function returns None
+ """
+ if vm_id <= 0:
+ return None
+ try:
+ vm_detail_obj = VMDetail.objects.get(vm_id=vm_id)
+ except VMDetail.DoesNotExist:
+ try:
+ vm_obj = manager.get_vm(vm_id)
+ except (WrongIdError, ConnectionRefusedError) as e:
+ logger.error(str(e))
+ return None
+ vm = VirtualMachineSerializer(vm_obj).data
+ vm_detail_obj = VMDetail.objects.create(
+ user=user, vm_id=vm_id, disk_size=vm['disk_size'],
+ cores=vm['cores'], memory=vm['memory'],
+ configuration=vm['configuration'], ipv4=vm['ipv4'],
+ ipv6=vm['ipv6']
+ )
+ return vm_detail_obj
+
+
+def get_vm_price(cpu, memory, disk_size):
+ """
+ A helper function that computes price of a VM from given cpu, ram and
+ ssd parameters
+
+ :param cpu: Number of cores of the VM
+ :param memory: RAM of the VM
+ :param disk_size: Disk space of the VM
+ :return: The price of the VM
+ """
+ return (cpu * 5) + (memory * 2) + (disk_size * 0.6)
diff --git a/utils/locale/de/LC_MESSAGES/django.po b/utils/locale/de/LC_MESSAGES/django.po
index 794b5fd9..f8374e4e 100644
--- a/utils/locale/de/LC_MESSAGES/django.po
+++ b/utils/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: 2017-09-02 11:50+0000\n"
+"POT-Creation-Date: 2017-09-25 20:11+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -738,6 +738,24 @@ msgstr ""
msgid "Enter your name or company name"
msgstr "Geben Sie Ihren Namen oder der Ihrer Firma ein"
+msgid "Your username and/or password were incorrect."
+msgstr "Dein Benutzername und/oder Dein Passwort ist falsch."
+
+msgid "User does not exist"
+msgstr "Der Benutzer existiert nicht"
+
+msgid "The account is already active."
+msgstr "Das Benutzerkonto ist bereits aktiv."
+
+msgid "The two password fields didn't match."
+msgstr "Die beiden Passwörter stimmen nicht überein."
+
+msgid "New password"
+msgstr "Neues Passwort"
+
+msgid "New password confirmation"
+msgstr "Neues Passwort Bestätigung"
+
msgid "Cardholder Name"
msgstr "Name des Kartenbesitzer"
@@ -768,8 +786,16 @@ msgstr "Telefon"
msgid "Message"
msgstr "Nachricht"
+msgid "An email with the activation link has been sent to your email"
+msgstr ""
+"Der Link zum Zurücksetzen deines Passwortes wurde an deine E-Mail gesendet"
+
+msgid "Account Activation"
+msgstr "Accountaktivierung"
+
msgid "The link to reset your email has been sent to your email"
-msgstr "Der Link zum Zurücksetzen deines Passwortes wurde an deine E-Mail gesendet"
+msgstr ""
+"Der Link zum Zurücksetzen deines Passwortes wurde an deine E-Mail gesendet"
msgid "Password Reset"
msgstr "Passwort zurücksetzen"
diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py
index f35a6b9c..98f85d62 100644
--- a/utils/stripe_utils.py
+++ b/utils/stripe_utils.py
@@ -238,7 +238,7 @@ class StripeUtils(object):
@staticmethod
def get_stripe_plan_id(cpu, ram, ssd, version, app='dcl', hdd=None):
"""
- Returns the stripe plan id string of the form
+ Returns the Stripe plan id string of the form
`dcl-v1-cpu-2-ram-5gb-ssd-10gb` based on the input parameters
:param cpu: The number of cores
@@ -256,7 +256,19 @@ class StripeUtils(object):
if hdd is not None:
dcl_plan_string = '{dcl_plan_string}-hdd-{hdd}gb'.format(
dcl_plan_string=dcl_plan_string, hdd=hdd)
- stripe_plan_id_string = '{app}-v{version}-{plan}'.format(app=app,
- version=version,
- plan=dcl_plan_string)
+ stripe_plan_id_string = '{app}-v{version}-{plan}'.format(
+ app=app,
+ version=version,
+ plan=dcl_plan_string)
return stripe_plan_id_string
+
+ @staticmethod
+ def get_stripe_plan_name(cpu, memory, disk_size):
+ """
+ Returns the Stripe plan name
+ :return:
+ """
+ return "{cpu} Cores, {memory} GB RAM, {disk_size} GB SSD".format(
+ cpu=cpu,
+ memory=memory,
+ disk_size=disk_size)
diff --git a/utils/views.py b/utils/views.py
index 3150fa6d..4ec39bce 100644
--- a/utils/views.py
+++ b/utils/views.py
@@ -2,6 +2,7 @@ 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.urlresolvers import reverse_lazy
from django.http import HttpResponseRedirect
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
@@ -63,9 +64,45 @@ class LoginViewMixin(FormView):
return super(LoginViewMixin, self).get(request, *args, **kwargs)
+class ResendActivationLinkViewMixin(FormView):
+ success_message = _(
+ "An email with the activation link has been sent to your email")
+
+ def generate_email_context(self, user):
+ context = {
+ 'base_url': "{0}://{1}".format(self.request.scheme,
+ self.request.get_host()),
+ 'activation_link': reverse_lazy(
+ 'hosting:validate',
+ kwargs={'validate_slug': user.validation_slug}
+ ),
+ 'dcl_text': settings.DCL_TEXT,
+ }
+ return context
+
+ def form_valid(self, form):
+ email = form.cleaned_data.get('email')
+ user = CustomUser.objects.get(email=email)
+ messages.add_message(self.request, messages.SUCCESS,
+ self.success_message)
+ context = self.generate_email_context(user)
+ email_data = {
+ 'subject': '{dcl_text} {account_activation}'.format(
+ dcl_text=settings.DCL_TEXT,
+ account_activation=_('Account Activation')
+ ),
+ 'to': email,
+ 'context': context,
+ 'template_name': self.email_template_name,
+ 'template_path': self.email_template_path,
+ 'from_address': settings.DCL_SUPPORT_FROM_ADDRESS
+ }
+ email = BaseEmail(**email_data)
+ email.send()
+ return HttpResponseRedirect(self.get_success_url())
+
+
class PasswordResetViewMixin(FormView):
- # template_name = 'hosting/reset_password.html'
- # form_class = PasswordResetRequestForm
success_message = _(
"The link to reset your email has been sent to your email")
site = ''
@@ -78,7 +115,6 @@ class PasswordResetViewMixin(FormView):
'site_name': 'ungleich' if self.site != 'dcl' else settings.DCL_TEXT,
'base_url': "{0}://{1}".format(self.request.scheme,
self.request.get_host())
-
}
return context
@@ -104,11 +140,8 @@ class PasswordResetViewMixin(FormView):
class PasswordResetConfirmViewMixin(FormView):
- # template_name = 'hosting/confirm_reset_password.html'
form_class = SetPasswordForm
- # success_url = reverse_lazy('hosting:login')
-
def post(self, request, uidb64=None, token=None, *arg, **kwargs):
try:
uid = urlsafe_base64_decode(uidb64)