merged master

This commit is contained in:
Arvind Tiwari 2017-09-28 03:06:47 +05:30
commit 88706b0088
30 changed files with 433 additions and 298 deletions

1
.gitignore vendored
View file

@ -36,3 +36,4 @@ secret-key
.env .env
*.mo *.mo
*.log *.log
*.sql

View file

@ -1,4 +1,11 @@
Pre-changelog: 1.2.3 2017-09-20 Next:
* #3764: [hosting] Show cancelled VMs' invoices
* #3736: [dcl] Refactor the place where we compute the VM price
* #3730: [dcl] Refactor price parameter passed in the DCL flow
* #3807: [dcl] Remove PricingView as it is no more used
* #3813: [hosting] JS error in create ssh key page
1.2.3: 2017-09-25
* #3484: [dcl, hosting] Refactored account activation, password reset, VM order and cancellation email * #3484: [dcl, hosting] Refactored account activation, password reset, VM order and cancellation email
* #3731: [dcl, hosting] Added cdist ssh key handler * #3731: [dcl, hosting] Added cdist ssh key handler
* #3628: [dcl] on hosting, VM is created at credit card info submit * #3628: [dcl] on hosting, VM is created at credit card info submit
@ -6,7 +13,9 @@ Pre-changelog: 1.2.3 2017-09-20
* #3786: [hosting] Redesigned the hosting invoice and order-confirmation page * #3786: [hosting] Redesigned the hosting invoice and order-confirmation page
* #3728: [hosting] VM Termination animation added * #3728: [hosting] VM Termination animation added
* #3777: [hosting] Create new VM calculator added like dcl landing * #3777: [hosting] Create new VM calculator added like dcl landing
* #3781: [hosting] Resend activation mail
* #3806: [hosting] Fix can not create VMs after password reset * #3806: [hosting] Fix can not create VMs after password reset
* #3812: [hosting] Modal check icon made thin and font-size fixed
* Feature: [cms, blog] Added /cms prefix for all the django-cms generated urls * Feature: [cms, blog] Added /cms prefix for all the django-cms generated urls
* Bugfix: [dcl, hosting] added host to celery error mails * Bugfix: [dcl, hosting] added host to celery error mails
* Bugfix: [ungleich] Fixed wrong subdomain digitalglarus.ungleich.ch * Bugfix: [ungleich] Fixed wrong subdomain digitalglarus.ungleich.ch

View file

@ -1655,3 +1655,20 @@ a.list-group-item-danger.active:focus {
.panel-danger > .panel-heading .badge { .panel-danger > .panel-heading .badge {
background-color: #eb4d5c; background-color: #eb4d5c;
} }
.checkmark {
display: inline-block;
}
.checkmark:after {
/*Add another block-level blank space*/
content: '';
display: block;
/*Make it a small rectangle so the border will create an L-shape*/
width: 25px;
height: 60px;
/*Add a white border on the bottom and left, creating that 'L' */
border: solid #777;
border-width: 0 3px 3px 0;
/*Rotate the L 45 degrees to turn it into a checkmark*/
transform: rotate(45deg);
}

View file

@ -155,9 +155,7 @@
function _calcPricing() { function _calcPricing() {
var total = (cardPricing['cpu'].value * 5) + (2 * cardPricing['ram'].value) + (0.6 * cardPricing['storage'].value); var total = (cardPricing['cpu'].value * 5) + (2 * cardPricing['ram'].value) + (0.6 * cardPricing['storage'].value);
total = parseFloat(total.toFixed(2)); total = parseFloat(total.toFixed(2));
$("#total").text(total); $("#total").text(total);
$('input[name=total]').val(total);
} }
function form_success() { function form_success() {

View file

@ -13,7 +13,7 @@ from hosting.models import HostingOrder, HostingBill
from membership.models import StripeCustomer, CustomUser from membership.models import StripeCustomer, 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 get_all_public_keys from utils.hosting_utils import get_all_public_keys, get_or_create_vm_detail
from utils.forms import UserBillingAddressForm from utils.forms import UserBillingAddressForm
from utils.mailer import BaseEmail from utils.mailer import BaseEmail
from utils.models import BillingAddress from utils.models import BillingAddress
@ -52,7 +52,8 @@ def create_vm_task(self, vm_template_id, user, specs, template,
stripe_customer_id, billing_address_data, stripe_customer_id, billing_address_data,
billing_address_id, billing_address_id,
charge, cc_details): charge, cc_details):
logger.debug("Running create_vm_task on {}".format(current_task.request.hostname)) logger.debug(
"Running create_vm_task on {}".format(current_task.request.hostname))
vm_id = None vm_id = None
try: try:
final_price = specs.get('price') final_price = specs.get('price')
@ -144,7 +145,8 @@ def create_vm_task(self, vm_template_id, user, specs, template,
if 'pass' in user: if 'pass' in user:
lang = 'en-us' lang = 'en-us'
if user.get('language') is not None: if user.get('language') is not None:
logger.debug("Language is set to {}".format(user.get('language'))) logger.debug(
"Language is set to {}".format(user.get('language')))
lang = user.get('language') lang = user.get('language')
translation.activate(lang) translation.activate(lang)
# Send notification to the user as soon as VM has been booked # Send notification to the user as soon as VM has been booked
@ -174,6 +176,7 @@ def create_vm_task(self, vm_template_id, user, specs, template,
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 new_host is not None: if new_host is not None:
custom_user = CustomUser.objects.get(email=user.get('email')) custom_user = CustomUser.objects.get(email=user.get('email'))
get_or_create_vm_detail(custom_user, manager, vm_id)
if custom_user is not None: if custom_user is not None:
public_keys = get_all_public_keys(custom_user) public_keys = get_all_public_keys(custom_user)
keys = [{'value': key, 'state': True} for key in keys = [{'value': key, 'state': True} for key in

View file

@ -8,7 +8,7 @@
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button> <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="modal-icon"><i class="fa fa-check" aria-hidden="true"></i></div> <div class="modal-icon"><i class="checkmark" aria-hidden="true"></i></div>
<h4 class="modal-title">{% trans "Request Sent" %}</h4> <h4 class="modal-title">{% trans "Request Sent" %}</h4>
<p class="modal-text">{% trans "Thank you for your subscription! You will receive a confirmation mail from our team" %}</p> <p class="modal-text">{% trans "Thank you for your subscription! You will receive a confirmation mail from our team" %}</p>
</div> </div>

View file

@ -77,7 +77,6 @@
{% endfor %} {% endfor %}
</select> </select>
</div> </div>
<input type="hidden" name="total">
<!--<div class="description check-ip"> <!--<div class="description check-ip">
<input type="checkbox" name="ipv6"> Ipv6 Only<br> <input type="checkbox" name="ipv6"> Ipv6 Only<br>
</div>--> </div>-->

View file

@ -1,96 +0,0 @@
{% extends "datacenterlight/base.html" %}
{% load staticfiles i18n%}
{% get_current_language as LANGUAGE_CODE %}
{% block content %}
<div class="intro-pricing">
<div class="intro-message">
<h2 class="section-heading">{% trans "We are cutting down the costs significantly!" %}</h2>
</div>
</div>
<div class="price-calc-section">
<div class="card">
<img class="img-beta" src="{% static 'datacenterlight/img/beta-img.png' %}" alt="">
<div class="caption">
<form method="POST" action="">
{% csrf_token %}
<div class="title">
<h3>{% trans "VM hosting" %} </h3>
</div>
<div class="price">
<span id="total">15</span>
<span>CHF</span>
<div class="price-text">
<p>{% trans "VAT included" %}</p>
</div>
</div>
<div class="descriptions">
<div class="description">
<p>{% trans "Hosted in Switzerland" %}</p>
</div>
<div class="description">
<i class="fa fa-minus-circle left" data-minus="cpu" aria-hidden="true"></i>
<input class="input-price" type="number" min="1" max="42" id="coreValue" name="cpu">
<span> Core</span>
<i class="fa fa-plus-circle right" data-plus="cpu" aria-hidden="true"></i>
</div>
<div class="description">
<i class="fa fa-minus-circle left" data-minus="ram" aria-hidden="true"></i>
<input id="ramValue" class="input-price" type="number" min="2" max="200" name="ram">
<span> GB RAM</span>
<i class="fa fa-plus-circle right" data-plus="ram" aria-hidden="true"></i>
</div>
<div class="description">
<i class="fa fa-minus-circle left" data-minus="storage" aria-hidden="true"></i>
<input id="storageValue" class="input-price" type="number" min="10" max="500" step="10" name="storage">
<span>{% trans "GB Storage (SSD)" %}</span>
<i class="fa fa-plus-circle right" data-plus="storage" aria-hidden="true"></i>
</div>
<div class="description select-configuration input">
<label for="name">OS</label>
<select name="config" id="">
{% for template in templates %}
<option value="{{template.id}}">{{template.name}} </option>
{% endfor %}
</select>
</div>
<input type="hidden" name="total">
<!-- <div class="description input">
<label for="name">Name</label>
<input type="text" name="name" placeholder="Your Name">
</div>
<div class="description input">
<label for="email">Email</label>
<input type="email" name="email" placeholder="Your Email">
</div> -->
<!--<div class="description check-ip">
<input type="checkbox" name="ipv6"> Ipv6 Only<br>
</div>-->
</div>
<input type="submit" class="btn btn-primary" value="{% trans 'Order Now!' %}"></input>
</form>
</div>
</div>
<div class="text">
<h2 class="section-heading">{% trans "Simple and affordable: Try our virtual machine with featherlight price." %}</h2>
<div class="description">
<p>{% trans "Our VMs are hosted in Glarus, Switzerland, and our website is currently running in BETA mode. If you want more information that you did not find on our website, or if your order is more detailed, or if you encounter any technical hiccups, please contact us at support@datacenterlight.ch, our team will get in touch with you asap." %}</p>
</div>
</div>
</div>
{% endblock %}

View file

@ -12,6 +12,7 @@ from datacenterlight.models import VMTemplate
from datacenterlight.tasks import create_vm_task from datacenterlight.tasks import create_vm_task
from membership.models import StripeCustomer from membership.models import StripeCustomer
from opennebula_api.serializers import VMTemplateSerializer from opennebula_api.serializers import VMTemplateSerializer
from utils.hosting_utils import get_vm_price
from utils.models import BillingAddress from utils.models import BillingAddress
from utils.stripe_utils import StripeUtils from utils.stripe_utils import StripeUtils
@ -94,12 +95,11 @@ class CeleryTaskTestCase(TestCase):
cpu = specs.get('cpu') cpu = specs.get('cpu')
memory = specs.get('memory') memory = specs.get('memory')
disk_size = specs.get('disk_size') disk_size = specs.get('disk_size')
amount_to_be_charged = (cpu * 5) + (memory * 2) + (disk_size * 0.6) amount_to_be_charged = get_vm_price(cpu=cpu, memory=memory,
plan_name = "{cpu} Cores, {memory} GB RAM, {disk_size} GB SSD".format( disk_size=disk_size)
cpu=cpu, plan_name = StripeUtils.get_stripe_plan_name(cpu=cpu,
memory=memory, memory=memory,
disk_size=disk_size) disk_size=disk_size)
stripe_plan_id = StripeUtils.get_stripe_plan_id(cpu=cpu, stripe_plan_id = StripeUtils.get_stripe_plan_id(cpu=cpu,
ram=memory, ram=memory,
ssd=disk_size, ssd=disk_size,

View file

@ -1,7 +1,7 @@
from django.conf.urls import url from django.conf.urls import url
from .views import IndexView, BetaProgramView, LandingProgramView, \ from .views import IndexView, BetaProgramView, LandingProgramView, \
BetaAccessView, PricingView, SuccessView, \ BetaAccessView, SuccessView, \
PaymentOrderView, OrderConfirmationView, \ PaymentOrderView, OrderConfirmationView, \
WhyDataCenterLightView, ContactUsView WhyDataCenterLightView, ContactUsView
@ -15,7 +15,6 @@ urlpatterns = [
name='whydatacenterlight'), name='whydatacenterlight'),
url(r'^beta-program/?$', BetaProgramView.as_view(), name='beta'), url(r'^beta-program/?$', BetaProgramView.as_view(), name='beta'),
url(r'^landing/?$', LandingProgramView.as_view(), name='landing'), url(r'^landing/?$', LandingProgramView.as_view(), name='landing'),
url(r'^pricing/?$', PricingView.as_view(), name='pricing'),
url(r'^payment/?$', PaymentOrderView.as_view(), name='payment'), url(r'^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'),

View file

@ -4,7 +4,6 @@ from django.contrib import messages
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.shortcuts import redirect
from django.shortcuts import render from django.shortcuts import render
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.views.decorators.cache import cache_control from django.views.decorators.cache import cache_control
@ -13,10 +12,9 @@ from django.views.generic import FormView, CreateView, TemplateView, DetailView
from datacenterlight.tasks import create_vm_task from datacenterlight.tasks import create_vm_task
from hosting.models import HostingOrder from hosting.models import HostingOrder
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 VirtualMachineTemplateSerializer, \
VMTemplateSerializer
from utils.forms import BillingAddressForm from utils.forms import BillingAddressForm
from utils.hosting_utils import get_vm_price
from utils.mailer import BaseEmail from utils.mailer import BaseEmail
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
@ -88,56 +86,6 @@ class SuccessView(TemplateView):
return render(request, self.template_name) return render(request, self.template_name)
class PricingView(TemplateView):
template_name = "datacenterlight/pricing.html"
def get(self, request, *args, **kwargs):
try:
manager = OpenNebulaManager()
templates = manager.get_templates()
context = {
'templates': VirtualMachineTemplateSerializer(templates,
many=True).data,
}
except:
messages.error(request,
'We have a temporary problem to connect to our backend. \
Please try again in a few minutes'
)
context = {
'error': 'connection'
}
return render(request, self.template_name, context)
def post(self, request):
cores = request.POST.get('cpu')
memory = request.POST.get('ram')
storage = request.POST.get('storage')
price = request.POST.get('total')
template_id = int(request.POST.get('config'))
manager = OpenNebulaManager()
template = manager.get_template(template_id)
request.session['template'] = VirtualMachineTemplateSerializer(
template).data
if not request.user.is_authenticated():
request.session['next'] = reverse('hosting:payment')
request.session['specs'] = {
'cpu': cores,
'memory': memory,
'disk_size': storage,
'price': price,
}
return redirect(reverse('hosting:payment'))
class BetaAccessView(FormView): class BetaAccessView(FormView):
template_name = "datacenterlight/beta_access.html" template_name = "datacenterlight/beta_access.html"
form_class = BetaAccessForm form_class = BetaAccessForm
@ -274,7 +222,6 @@ class IndexView(CreateView):
memory_field = forms.IntegerField(validators=[self.validate_memory]) memory_field = forms.IntegerField(validators=[self.validate_memory])
storage = request.POST.get('storage') storage = request.POST.get('storage')
storage_field = forms.IntegerField(validators=[self.validate_storage]) storage_field = forms.IntegerField(validators=[self.validate_storage])
price = request.POST.get('total')
template_id = int(request.POST.get('config')) template_id = int(request.POST.get('config'))
template = VMTemplate.objects.filter( template = VMTemplate.objects.filter(
opennebula_vm_template_id=template_id).first() opennebula_vm_template_id=template_id).first()
@ -334,7 +281,6 @@ class IndexView(CreateView):
'cpu': cores, 'cpu': cores,
'memory': memory, 'memory': memory,
'disk_size': storage, 'disk_size': storage,
'price': price
} }
this_user = { this_user = {
@ -534,12 +480,12 @@ class OrderConfirmationView(DetailView):
cpu = specs.get('cpu') cpu = specs.get('cpu')
memory = specs.get('memory') memory = specs.get('memory')
disk_size = specs.get('disk_size') disk_size = specs.get('disk_size')
amount_to_be_charged = (cpu * 5) + (memory * 2) + (disk_size * 0.6) amount_to_be_charged = get_vm_price(cpu=cpu, memory=memory,
plan_name = "{cpu} Cores, {memory} GB RAM, {disk_size} GB SSD".format( disk_size=disk_size)
cpu=cpu, specs['price'] = amount_to_be_charged
memory=memory, plan_name = StripeUtils.get_stripe_plan_name(cpu=cpu,
disk_size=disk_size) memory=memory,
disk_size=disk_size)
stripe_plan_id = StripeUtils.get_stripe_plan_id(cpu=cpu, stripe_plan_id = StripeUtils.get_stripe_plan_id(cpu=cpu,
ram=memory, ram=memory,
ssd=disk_size, ssd=disk_size,

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: 2017-09-23 19:00+0530\n" "POT-Creation-Date: 2017-09-24 12:34+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"
@ -306,6 +306,9 @@ msgstr "Registrieren"
msgid "Forgot your password ? " msgid "Forgot your password ? "
msgstr "Passwort vergessen?" msgstr "Passwort vergessen?"
msgid "Resend activation link"
msgstr "Aktivierungslink noch einmal senden"
msgid "Notifications" msgid "Notifications"
msgstr "Benachrichtigungen" msgstr "Benachrichtigungen"
@ -385,7 +388,7 @@ msgid "Processing..."
msgstr "Abarbeitung..." msgstr "Abarbeitung..."
msgid "Hold tight, we are processing your request" msgid "Hold tight, we are processing your request"
msgstr "Bitte warten - wir verbeiten Deine Anfrage gerade" msgstr "Bitte warten - wir bearbeiten Deine Anfrage gerade"
msgid "Some problem encountered. Please try again later." msgid "Some problem encountered. Please try again later."
msgstr "Ein Problem ist aufgetreten. Bitte versuche es später noch einmal." msgstr "Ein Problem ist aufgetreten. Bitte versuche es später noch einmal."

View file

@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2017-09-24 18:12
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 = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('hosting', '0042_hostingorder_subscription_id'),
]
operations = [
migrations.CreateModel(
name='VMDetail',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('vm_id', models.IntegerField(default=0)),
('disk_size', models.FloatField(default=0.0)),
('cores', models.FloatField(default=0.0)),
('memory', models.FloatField(default=0.0)),
('configuration', models.CharField(default='', max_length=25)),
('ipv4', models.TextField(default='')),
('ipv6', models.TextField(default='')),
('created_at', models.DateTimeField(auto_now_add=True)),
('terminated_at', models.DateTimeField(null=True)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View file

@ -159,3 +159,16 @@ class HostingBill(AssignPermissionsMixin, models.Model):
instance = cls.objects.create(customer=customer, instance = cls.objects.create(customer=customer,
billing_address=billing_address) billing_address=billing_address)
return instance return instance
class VMDetail(models.Model):
user = models.ForeignKey(CustomUser)
vm_id = models.IntegerField(default=0)
disk_size = models.FloatField(default=0.0)
cores = models.FloatField(default=0.0)
memory = models.FloatField(default=0.0)
configuration = models.CharField(default='', max_length=25)
ipv4 = models.TextField(default='')
ipv6 = models.TextField(default='')
created_at = models.DateTimeField(auto_now_add=True)
terminated_at = models.DateTimeField(null=True)

View file

@ -870,3 +870,41 @@ a.list-group-item-danger.active:focus {
.panel-danger > .panel-heading .badge { .panel-danger > .panel-heading .badge {
background-color: #eb4d5c; background-color: #eb4d5c;
} }
.checkmark {
display: inline-block;
}
.checkmark:after {
/*Add another block-level blank space*/
content: '';
display: block;
/*Make it a small rectangle so the border will create an L-shape*/
width: 25px;
height: 60px;
/*Add a white border on the bottom and left, creating that 'L' */
border: solid #777;
border-width: 0 3px 3px 0;
/*Rotate the L 45 degrees to turn it into a checkmark*/
transform: rotate(45deg);
}
.closemark {
display: inline-block;
width: 50px;
height: 50px;
position: relative;
}
.closemark:before, .closemark:after {
position: absolute;
left: 25px;
content: ' ';
height: 50px;
width: 2px;
background-color: #777;
}
.closemark:before {
transform: rotate(45deg);
}
.closemark:after {
transform: rotate(-45deg);
}

View file

@ -79,7 +79,6 @@ $(document).ready(function() {
$('html,body').scrollTop(scrollmem); $('html,body').scrollTop(scrollmem);
}); });
$('.modal-text').removeClass('hide');
var create_vm_form = $('#virtual_machine_create_form'); var create_vm_form = $('#virtual_machine_create_form');
create_vm_form.submit(function () { create_vm_form.submit(function () {
$('#btn-create-vm').prop('disabled', true); $('#btn-create-vm').prop('disabled', true);
@ -90,26 +89,28 @@ $(document).ready(function() {
success: function (data) { success: function (data) {
if (data.status === true) { if (data.status === true) {
fa_icon = $('.modal-icon > .fa'); fa_icon = $('.modal-icon > .fa');
fa_icon.attr('class', 'fa fa-check'); fa_icon.attr('class', 'checkmark');
$('.modal-header > .close').attr('class', 'close'); // $('.modal-header > .close').removeClass('hidden');
$('#createvm-modal-title').text(data.msg_title); $('#createvm-modal-title').text(data.msg_title);
$('#createvm-modal-body').text(data.msg_body); $('#createvm-modal-body').text(data.msg_body);
$('#createvm-modal').on('hidden.bs.modal', function () { $('#createvm-modal-done-btn')
window.location = data.redirect; .attr('href', data.redirect)
}) .removeClass('hide');
} }
}, },
error: function (xmlhttprequest, textstatus, message) { error: function (xmlhttprequest, textstatus, message) {
fa_icon = $('.modal-icon > .fa'); fa_icon = $('.modal-icon > .fa');
fa_icon.attr('class', 'fa fa-times'); fa_icon.attr('class', 'fa fa-close');
$('.modal-header > .close').attr('class', 'close');
$('.modal-text').addClass('hide');
if (typeof(create_vm_error_message) !== 'undefined') { if (typeof(create_vm_error_message) !== 'undefined') {
$('#createvm-modal-title').text(create_vm_error_message); $('#createvm-modal-text').text(create_vm_error_message);
} }
$('#btn-create-vm').prop('disabled', false); $('#btn-create-vm').prop('disabled', false);
$('#createvm-modal-close-btn').removeClass('hide');
} }
}); });
return false; return false;
}); });
$('#createvm-modal').on('hidden.bs.modal', function () {
$(this).find('.modal-footer .btn').addClass('hide');
})
}); });

View file

@ -47,20 +47,5 @@
window.location.href = '{{next_url}}'; window.location.href = '{{next_url}}';
</script> </script>
{% endif %} {% endif %}
<script type="text/javascript">
window.onload = function () {
{% for user_key in keys %}
var locale_date = moment.utc(document.getElementById("ssh-created_at-{{user_key.id}}").textContent,'YYYY-MM-DD HH:mm').toDate();
locale_date = moment(locale_date).format("YYYY-MM-DD h:mm:ss a");
document.getElementById('ssh-created_at-{{user_key.id}}').innerHTML = locale_date;
{% endfor %}
};
</script>
{%endblock%} {%endblock%}

View file

@ -44,6 +44,8 @@
<a class="unlink" href="{% url 'hosting:signup' %}">{% trans "Sign up"%}</a> <a class="unlink" href="{% url 'hosting:signup' %}">{% trans "Sign up"%}</a>
<span class="text"> or </span> <span class="text"> or </span>
<a class="unlink" href="{% url 'hosting:reset_password' %}">{% trans "Forgot your password ? "%}</a> <a class="unlink" href="{% url 'hosting:reset_password' %}">{% trans "Forgot your password ? "%}</a>
<span class="text"> or </span><br/>
<a class="unlink" href="{% url 'hosting:resend_activation_link' %}">{% trans "Resend activation link"%}</a>
</div> </div>
</div> </div>
</div> </div>

View file

@ -44,7 +44,9 @@
<p> <p>
<strong>{% trans "Status" %}: </strong> <strong>{% trans "Status" %}: </strong>
<strong> <strong>
{% if order.status == 'Approved' %} {% if vm.terminated_at %}
<span class="vm-color-failed">{% trans "Terminated" %}</span>
{% elif order.status == 'Approved' %}
<span class="vm-color-online">{% trans "Approved" %}</span> <span class="vm-color-online">{% trans "Approved" %}</span>
{% else %} {% else %}
<span class="vm-status-failed">{% trans "Declined" %}</span> <span class="vm-status-failed">{% trans "Declined" %}</span>
@ -102,14 +104,14 @@
</p> </p>
<div class="row"> <div class="row">
<div class="col-sm-6"> <div class="col-sm-6">
{% comment %} {% if vm.created_at %}
<p>
<span>{% trans "Period" %}: </span>
<span>{{ vm.created_at|date:'Y/m/d' }} - {% if vm.terminated_at %}{{ vm.terminated_at|date:'Y/m/d' }}{% else %}{% now 'Y/m/d' %}{% endif %}</span>
</p>
{% endif %}
<p> <p>
<span>{% trans "Period" %}</span> <span>{% trans "Cores" %}: </span>
<span class="pull-right">{{}}</span>
</p>
{% endcomment %}
<p>
<span>{% trans "Cores" %}</span>
{% if vm.cores %} {% if vm.cores %}
<span class="pull-right">{{vm.cores|floatformat}}</span> <span class="pull-right">{{vm.cores|floatformat}}</span>
{% else %} {% else %}
@ -117,11 +119,11 @@
{% endif %} {% endif %}
</p> </p>
<p> <p>
<span>{% trans "Memory" %}</span> <span>{% trans "Memory" %}: </span>
<span class="pull-right">{{vm.memory}} GB</span> <span class="pull-right">{{vm.memory}} GB</span>
</p> </p>
<p> <p>
<span>{% trans "Disk space" %}</span> <span>{% trans "Disk space" %}: </span>
<span class="pull-right">{{vm.disk_size}} GB</span> <span class="pull-right">{{vm.disk_size}} GB</span>
</p> </p>
<p> <p>
@ -168,22 +170,19 @@
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<button type="button" class="close hidden" data-dismiss="modal"
aria-label="create-vm-close">
<span aria-hidden="true">&times;</span>
</button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="modal-icon"> <div class="modal-icon">
<i class="fa fa-cog fa-spin fa-3x fa-fw"></i> <i class="fa fa-cog fa-spin fa-3x fa-fw"></i>
<span class="sr-only">{% trans "Processing..." %}</span> <span class="sr-only">{% trans "Processing..." %}</span>
</div> </div>
<h4 class="modal-title" id="createvm-modal-title"> <h4 class="modal-title" id="createvm-modal-title"></h4>
</h4>
<div class="modal-text" id="createvm-modal-body"> <div class="modal-text" id="createvm-modal-body">
{% trans "Hold tight, we are processing your request" %} {% trans "Hold tight, we are processing your request" %}
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<a id="createvm-modal-done-btn" class="btn btn-success btn-ok btn-wide hide" href="{% url 'hosting:virtual_machines' %}">{% trans "OK" %}</a>
<button id="createvm-modal-close-btn" type="button" class="btn btn-danger btn-ok btn-wide hide" data-dismiss="modal" aria-label="create-vm-close">{% trans "Close" %}</button>
</div> </div>
</div> </div>
</div> </div>

View file

@ -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 %}
<div class="auth-container">
<div class="auth-bg"></div>
<div class="auth-center">
<div class="auth-title">
<h2>{% trans "Your VM hosted in Switzerland"%}</h2>
</div>
<div class="auth-content">
<div class="intro-message auth-box sign-up">
<h2 class="section-heading">{% trans "Resend activation link"%}</h2>
<form action="{% url 'hosting:resend_activation_link' %}" method="post" class="form" novalidate>
{% csrf_token %}
{% for field in form %}
{% bootstrap_field field show_label=False %}
{% endfor %}
{% buttons %}
<button type="submit" class="btn btn-block btn-success">
{% trans "Submit"%}
</button>
{% endbuttons %}
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View file

@ -101,21 +101,5 @@
window.location.href = '{{next_url}}'; window.location.href = '{{next_url}}';
</script> </script>
{% endif %} {% endif %}
<script type="text/javascript">
window.onload = function () {
{% for user_key in keys %}
var locale_date = moment.utc(document.getElementById("ssh-created_at-{{user_key.id}}").textContent,'YYYY-MM-DD HH:mm').toDate();
locale_date = moment(locale_date).format("YYYY-MM-DD h:mm:ss a");
document.getElementById('ssh-created_at-{{user_key.id}}').innerHTML = locale_date;
{% endfor %}
};
</script>
{%endblock%} {%endblock%}

View file

@ -109,7 +109,7 @@
<p><strong>{{virtual_machine.name}}</strong></p> <p><strong>{{virtual_machine.name}}</strong></p>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<a class="btn btn-danger btn-ok btn-wide">{% trans "OK" %}</a> <a class="btn btn-danger btn-ok btn-wide">{% trans "OK" %}</a>
</div> </div>
</div> </div>
</div> </div>
@ -123,8 +123,9 @@
<div class="modal-header"> <div class="modal-header">
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="modal-icon"><i class="fa fa-check" aria-hidden="true"></i></div> <div class="modal-icon"><i class="checkmark" aria-hidden="true"></i></div>
<h4 class="modal-title" id="ModalLabel">{% blocktrans with machine_name=virtual_machine.name %}Your Virtual Machine <strong>{{machine_name}}</strong> is successfully terminated!{% endblocktrans %}</h4> <h4 class="modal-title"></h4>
<div class="modal-text" id="ModalLabel">{% blocktrans with machine_name=virtual_machine.name %}Your Virtual Machine <strong>{{machine_name}}</strong> is successfully terminated!{% endblocktrans %}</div>
<div class="modal-footer"> <div class="modal-footer">
<a href="{% url 'hosting:virtual_machines' %}" class="btn btn-success btn-wide">{% trans "OK" %}</a> <a href="{% url 'hosting:virtual_machines' %}" class="btn btn-success btn-wide">{% trans "OK" %}</a>
</div> </div>

View file

@ -8,8 +8,7 @@ from .views import (
MarkAsReadNotificationView, PasswordResetView, PasswordResetConfirmView, MarkAsReadNotificationView, PasswordResetView, PasswordResetConfirmView,
HostingPricingView, CreateVirtualMachinesView, HostingBillListView, HostingPricingView, CreateVirtualMachinesView, HostingBillListView,
HostingBillDetailView, SSHKeyDeleteView, SSHKeyCreateView, SSHKeyListView, HostingBillDetailView, SSHKeyDeleteView, SSHKeyCreateView, SSHKeyListView,
SSHKeyChoiceView, DashboardView, SettingsView) SSHKeyChoiceView, DashboardView, SettingsView, ResendActivationEmailView)
urlpatterns = [ urlpatterns = [
url(r'index/?$', IndexView.as_view(), name='index'), url(r'index/?$', IndexView.as_view(), name='index'),
@ -52,6 +51,8 @@ urlpatterns = [
url(r'signup/?$', SignupView.as_view(), name='signup'), url(r'signup/?$', SignupView.as_view(), name='signup'),
url(r'signup-validate/?$', SignupValidateView.as_view(), url(r'signup-validate/?$', SignupValidateView.as_view(),
name='signup-validate'), name='signup-validate'),
url(r'resend-activation-link/?$', ResendActivationEmailView.as_view(),
name='resend_activation_link'),
url(r'reset-password/?$', PasswordResetView.as_view(), url(r'reset-password/?$', PasswordResetView.as_view(),
name='reset_password'), name='reset_password'),
url(r'reset-password-confirm/(?P<uidb64>[0-9A-Za-z]+)-(?P<token>.+)/$', url(r'reset-password-confirm/(?P<uidb64>[0-9A-Za-z]+)-(?P<token>.+)/$',

View file

@ -1,6 +1,7 @@
import json import json
import logging import logging
import uuid import uuid
from datetime import datetime
from time import sleep from time import sleep
from django import forms from django import forms
@ -33,20 +34,25 @@ from membership.models import CustomUser, StripeCustomer
from opennebula_api.models import OpenNebulaManager from opennebula_api.models import OpenNebulaManager
from opennebula_api.serializers import VirtualMachineSerializer, \ from opennebula_api.serializers import VirtualMachineSerializer, \
VirtualMachineTemplateSerializer, VMTemplateSerializer VirtualMachineTemplateSerializer, VMTemplateSerializer
from utils.forms import BillingAddressForm, PasswordResetRequestForm, \ from utils.forms import (
UserBillingAddressForm BillingAddressForm, PasswordResetRequestForm, UserBillingAddressForm,
ResendActivationEmailForm
)
from utils.hosting_utils import get_vm_price
from utils.mailer import BaseEmail from utils.mailer import BaseEmail
from utils.stripe_utils import StripeUtils from utils.stripe_utils import StripeUtils
from utils.views import ( from utils.views import (
PasswordResetViewMixin, PasswordResetConfirmViewMixin, LoginViewMixin PasswordResetViewMixin, PasswordResetConfirmViewMixin, LoginViewMixin,
ResendActivationLinkViewMixin
) )
from .forms import HostingUserSignupForm, HostingUserLoginForm, \ from .forms import HostingUserSignupForm, HostingUserLoginForm, \
UserHostingKeyForm, generate_ssh_key_name UserHostingKeyForm, generate_ssh_key_name
from .mixins import ProcessVMSelectionMixin from .mixins import ProcessVMSelectionMixin
from .models import HostingOrder, HostingBill, HostingPlan, UserHostingKey from .models import (
HostingOrder, HostingBill, HostingPlan, UserHostingKey, VMDetail
)
from datacenterlight.models import VMTemplate from datacenterlight.models import VMTemplate
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
CONNECTION_ERROR = "Your VMs cannot be displayed at the moment due to a \ CONNECTION_ERROR = "Your VMs cannot be displayed at the moment due to a \
@ -282,6 +288,14 @@ class SignupValidatedView(SignupValidateView):
return context 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): class PasswordResetView(PasswordResetViewMixin):
site = 'dcl' site = 'dcl'
template_name = 'hosting/reset_password.html' template_name = 'hosting/reset_password.html'
@ -678,25 +692,30 @@ class OrdersHostingDetailView(LoginRequiredMixin,
if obj is not None: if obj is not None:
# invoice for previous order # invoice for previous order
try: try:
manager = OpenNebulaManager( vm_detail = VMDetail.objects.get(vm_id=obj.vm_id)
email=owner.email, password=owner.password context['vm'] = vm_detail.__dict__
) context['vm']['name'] = '{}-{}'.format(context['vm']['configuration'], context['vm']['vm_id'])
vm = manager.get_vm(obj.vm_id) except VMDetail.DoesNotExist:
context['vm'] = VirtualMachineSerializer(vm).data try:
except WrongIdError: manager = OpenNebulaManager(
messages.error( email=owner.email, password=owner.password
self.request, )
_('The VM you are looking for is unavailable at the ' vm = manager.get_vm(obj.vm_id)
'moment. Please contact Data Center Light support.') context['vm'] = VirtualMachineSerializer(vm).data
) except WrongIdError:
self.kwargs['error'] = 'WrongIdError' messages.error(
context['error'] = 'WrongIdError' self.request,
except ConnectionRefusedError: _('The VM you are looking for is unavailable at the '
messages.error( 'moment. Please contact Data Center Light support.')
self.request, )
_('In order to create a VM, you need to create/upload ' self.kwargs['error'] = 'WrongIdError'
'your SSH KEY first.') 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'): elif not card_details.get('response_object'):
# new order, failed to get card details # new order, failed to get card details
context['failed_payment'] = True context['failed_payment'] = True
@ -756,12 +775,11 @@ class OrdersHostingDetailView(LoginRequiredMixin,
cpu = specs.get('cpu') cpu = specs.get('cpu')
memory = specs.get('memory') memory = specs.get('memory')
disk_size = specs.get('disk_size') disk_size = specs.get('disk_size')
amount_to_be_charged = (cpu * 5) + (memory * 2) + (disk_size * 0.6) amount_to_be_charged = get_vm_price(cpu=cpu, memory=memory,
plan_name = "{cpu} Cores, {memory} GB RAM, {disk_size} GB SSD".format( disk_size=disk_size)
cpu=cpu, plan_name = StripeUtils.get_stripe_plan_name(cpu=cpu,
memory=memory, memory=memory,
disk_size=disk_size) disk_size=disk_size)
stripe_plan_id = StripeUtils.get_stripe_plan_id(cpu=cpu, stripe_plan_id = StripeUtils.get_stripe_plan_id(cpu=cpu,
ram=memory, ram=memory,
ssd=disk_size, ssd=disk_size,
@ -806,9 +824,10 @@ class OrdersHostingDetailView(LoginRequiredMixin,
'status': True, 'status': True,
'redirect': reverse('hosting:virtual_machines'), 'redirect': reverse('hosting:virtual_machines'),
'msg_title': str(_('Thank you for the order.')), 'msg_title': str(_('Thank you for the order.')),
'msg_body': str(_('Your VM will be up and running in a few moments.' 'msg_body': str(
' We will send you a confirmation email as soon as' _('Your VM will be up and running in a few moments.'
' it is ready.')) ' We will send you a confirmation email as soon as'
' it is ready.'))
} }
return HttpResponse(json.dumps(response), return HttpResponse(json.dumps(response),
@ -905,7 +924,6 @@ class CreateVirtualMachinesView(LoginRequiredMixin, View):
memory_field = forms.IntegerField(validators=[self.validate_memory]) memory_field = forms.IntegerField(validators=[self.validate_memory])
storage = request.POST.get('storage') storage = request.POST.get('storage')
storage_field = forms.IntegerField(validators=[self.validate_storage]) storage_field = forms.IntegerField(validators=[self.validate_storage])
price = request.POST.get('total')
template_id = int(request.POST.get('config')) template_id = int(request.POST.get('config'))
template = VMTemplate.objects.filter( template = VMTemplate.objects.filter(
opennebula_vm_template_id=template_id).first() opennebula_vm_template_id=template_id).first()
@ -937,7 +955,8 @@ class CreateVirtualMachinesView(LoginRequiredMixin, View):
extra_tags='storage') extra_tags='storage')
return HttpResponseRedirect( return HttpResponseRedirect(
reverse('datacenterlight:index') + "#order_form") reverse('datacenterlight:index') + "#order_form")
price = get_vm_price(cpu=cores, memory=memory,
disk_size=storage)
specs = { specs = {
'cpu': cores, 'cpu': cores,
'memory': memory, 'memory': memory,
@ -1043,6 +1062,10 @@ class VirtualMachineView(LoginRequiredMixin, View):
except WrongIdError: except WrongIdError:
response['status'] = True response['status'] = True
response['text'] = ugettext('Terminated') 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 break
except BaseException: except BaseException:
break break

View file

@ -96,5 +96,4 @@ pyflakes==1.5.0
billiard==3.5.0.3 billiard==3.5.0.3
amqp==2.2.1 amqp==2.2.1
vine==1.1.4 vine==1.1.4
#git+https://github.com/ungleich/cdist.git#egg=cdist cdist==4.7.0
file:///home/app/cdist#egg=cdist

View file

@ -18,7 +18,8 @@ class SignupFormMixin(forms.ModelForm):
model = CustomUser model = CustomUser
fields = ['name', 'email', 'password'] fields = ['name', 'email', 'password']
widgets = { 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): def clean_confirm_password(self):
@ -42,7 +43,7 @@ class LoginFormMixin(forms.Form):
is_auth = authenticate(email=email, password=password) is_auth = authenticate(email=email, password=password)
if not is_auth: if not is_auth:
raise forms.ValidationError( raise forms.ValidationError(
"Your username and/or password were incorrect.") _("Your username and/or password were incorrect."))
return self.cleaned_data return self.cleaned_data
def clean_email(self): def clean_email(self):
@ -51,7 +52,24 @@ class LoginFormMixin(forms.Form):
CustomUser.objects.get(email=email) CustomUser.objects.get(email=email)
return email return email
except CustomUser.DoesNotExist: 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): class PasswordResetRequestForm(forms.Form):
@ -66,7 +84,7 @@ class PasswordResetRequestForm(forms.Form):
CustomUser.objects.get(email=email) CustomUser.objects.get(email=email)
return email return email
except CustomUser.DoesNotExist: except CustomUser.DoesNotExist:
raise forms.ValidationError("User does not exist") raise forms.ValidationError(_("User does not exist"))
class SetPasswordForm(forms.Form): class SetPasswordForm(forms.Form):
@ -75,11 +93,11 @@ class SetPasswordForm(forms.Form):
password password
""" """
error_messages = { 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) widget=forms.PasswordInput)
new_password2 = forms.CharField(label=("New password confirmation"), new_password2 = forms.CharField(label=_("New password confirmation"),
widget=forms.PasswordInput) widget=forms.PasswordInput)
def clean_new_password2(self): def clean_new_password2(self):

View file

@ -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): 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( return UserHostingKey.objects.filter(user_id=customer.id).values_list(
"public_key", flat=True) "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)

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: 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" "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"
@ -738,6 +738,24 @@ msgstr ""
msgid "Enter your name or company name" msgid "Enter your name or company name"
msgstr "Geben Sie Ihren Namen oder der Ihrer Firma ein" 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" msgid "Cardholder Name"
msgstr "Name des Kartenbesitzer" msgstr "Name des Kartenbesitzer"
@ -768,8 +786,16 @@ msgstr "Telefon"
msgid "Message" msgid "Message"
msgstr "Nachricht" 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" 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" msgid "Password Reset"
msgstr "Passwort zurücksetzen" msgstr "Passwort zurücksetzen"

View file

@ -238,7 +238,7 @@ class StripeUtils(object):
@staticmethod @staticmethod
def get_stripe_plan_id(cpu, ram, ssd, version, app='dcl', hdd=None): 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 `dcl-v1-cpu-2-ram-5gb-ssd-10gb` based on the input parameters
:param cpu: The number of cores :param cpu: The number of cores
@ -256,7 +256,19 @@ class StripeUtils(object):
if hdd is not None: if hdd is not None:
dcl_plan_string = '{dcl_plan_string}-hdd-{hdd}gb'.format( dcl_plan_string = '{dcl_plan_string}-hdd-{hdd}gb'.format(
dcl_plan_string=dcl_plan_string, hdd=hdd) dcl_plan_string=dcl_plan_string, hdd=hdd)
stripe_plan_id_string = '{app}-v{version}-{plan}'.format(app=app, stripe_plan_id_string = '{app}-v{version}-{plan}'.format(
version=version, app=app,
plan=dcl_plan_string) version=version,
plan=dcl_plan_string)
return stripe_plan_id_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)

View file

@ -2,6 +2,7 @@ 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.urlresolvers import reverse_lazy
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
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
@ -63,9 +64,45 @@ class LoginViewMixin(FormView):
return super(LoginViewMixin, self).get(request, *args, **kwargs) 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): class PasswordResetViewMixin(FormView):
# template_name = 'hosting/reset_password.html'
# form_class = PasswordResetRequestForm
success_message = _( success_message = _(
"The link to reset your email has been sent to your email") "The link to reset your email has been sent to your email")
site = '' site = ''
@ -78,7 +115,6 @@ class PasswordResetViewMixin(FormView):
'site_name': 'ungleich' if self.site != 'dcl' else settings.DCL_TEXT, 'site_name': 'ungleich' if self.site != 'dcl' else settings.DCL_TEXT,
'base_url': "{0}://{1}".format(self.request.scheme, 'base_url': "{0}://{1}".format(self.request.scheme,
self.request.get_host()) self.request.get_host())
} }
return context return context
@ -104,11 +140,8 @@ class PasswordResetViewMixin(FormView):
class PasswordResetConfirmViewMixin(FormView): class PasswordResetConfirmViewMixin(FormView):
# template_name = 'hosting/confirm_reset_password.html'
form_class = SetPasswordForm form_class = SetPasswordForm
# success_url = reverse_lazy('hosting:login')
def post(self, request, uidb64=None, token=None, *arg, **kwargs): def post(self, request, uidb64=None, token=None, *arg, **kwargs):
try: try:
uid = urlsafe_base64_decode(uidb64) uid = urlsafe_base64_decode(uidb64)