hosting form view modified

This commit is contained in:
Arvind Tiwari 2017-09-15 20:03:52 +05:30
parent c2b5c62014
commit 87327d028a
8 changed files with 324 additions and 204 deletions

View file

@ -26,9 +26,9 @@
<div class="help-block with-errors"> <div class="help-block with-errors">
{% for message in messages %} {% for message in messages %}
{% if 'cores' in message.tags %} {% if 'cores' in message.tags %}
<ul class="list-unstyled"><li> <ul class="list-unstyled">
{{ message|safe }} <li>{{ message|safe }}</li>
</li></ul> </ul>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</div> </div>

View file

@ -1,12 +1,13 @@
/*Pricing page*/ /* Create VM calculator */
.price-calc-section { .price-calc-section {
padding: 80px 40px !important; padding: 80px 40px !important;
background: -webkit-linear-gradient(top, #f0f4f7, #fff) no-repeat; }
background: linear-gradient(to bottom, #f0f4f7, #fff) no-repeat;
display: flex; @media (max-width: 768px) {
/*font-family: 'Lato', sans-serif;*/ .price-calc-section {
/* font-weight: normal; */ margin-top: 40px;
}
} }
.price-calc-section .text { .price-calc-section .text {
@ -42,12 +43,12 @@
width: 50%; width: 50%;
margin: 0 auto; margin: 0 auto;
background: #fff; background: #fff;
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23); box-shadow: 1px 3px 6px 2px rgba(0, 0, 0, 0.2);
padding-bottom: 40px; padding-bottom: 30px;
border-radius: 7px; /* border-radius: 7px; */
text-align: center; text-align: center;
/* margin-right: auto; */ /* margin-right: auto; */
max-width: 400px; max-width: 320px;
position: relative; position: relative;
} }
@ -87,7 +88,7 @@
} }
.price-calc-section .card .description { .price-calc-section .card .description {
padding: 12px; padding: 7px 8px 2px;
position: relative; position: relative;
display: flex; display: flex;
justify-content: space-around !important; justify-content: space-around !important;
@ -95,24 +96,29 @@
} }
.price-calc-section .card .description span { .price-calc-section .card .description span {
font-size: 16px; font-size: 14px;
margin-left: 4px; margin-left: 5px;
margin-left: 0px; /* margin-left: 0px; */
/* justify-self: start; */ /* justify-self: start; */
width: 30%; width: 29%;
text-align: left; text-align: left;
line-height: 16px;
/* font-weight: normal; */
} }
.price-calc-section .card .description .select-number{ .price-calc-section .card .description .select-number{
font-size: 20px; font-size: 16px;
text-align: center; text-align: center;
width: 85px; width: 85px;
} }
.price-calc-section .card .description i { .price-calc-section .card .description i {
color: #29427A; color: #29427a;
cursor: pointer; cursor: pointer;
font-size: 24px; font-size: 20px;
border: 1px solid #eee;
padding: 5px 6px 3px;
border-radius: 5px;
} }
.price-calc-section .card .description .left { .price-calc-section .card .description .left {
@ -124,7 +130,7 @@
} }
.price-calc-section .card .descriptions { .price-calc-section .card .descriptions {
padding: 10px 30px; padding: 10px;
} }
.price-calc-section .card .description p { .price-calc-section .card .description p {
@ -132,9 +138,9 @@
} }
.price-calc-section .card .btn { .price-calc-section .card .btn {
margin-top: 20px; margin-top: 15px;
font-size: 20px; font-size: 20px;
width: 200px; width: 150px;
border: none; border: none;
} }
@ -142,11 +148,13 @@
outline: none; outline: none;
background: #fff; background: #fff;
border-color: #d0d0d0; border-color: #d0d0d0;
height: 40px; height: 32px;
width: 200px; width: 150px;
text-align: center; text-align: center;
font-size: 16px; font-size: 14px;
margin-left: 10px; margin-left: 10px;
padding: 6px;
border-radius: 4px;
} }
.price-calc-section .card .check-ip { .price-calc-section .card .check-ip {
@ -173,7 +181,7 @@
width: 200px; width: 200px;
font-size: 14px; font-size: 14px;
text-align: left; text-align: left;
padding: 5px 10px; padding: 4px 10px;
border-radius: 4px; border-radius: 4px;
border: 1px solid #d0d0d0; border: 1px solid #d0d0d0;
background: #fff; background: #fff;
@ -183,4 +191,43 @@
.price-calc-section .card .check-ip input[type=checkbox] { .price-calc-section .card .check-ip input[type=checkbox] {
font-size: 17px; font-size: 17px;
margin: 0 8px; margin: 0 8px;
} }
.help-block.with-errors {
text-align: center;
margin: 0 0;
padding: 0 0 5px;
}
.help-block.with-errors ul {
margin-bottom: 0;
}
.form-group {
margin: 0;
position: relative;
}
.form-group:after {
content: ' ';
display: block;
position: absolute;
bottom: 0;
left: 18%;
z-index: 20;
height: 1px;
width: 65%;
background: rgba(128, 128, 128, 0.2);
}
.btn-primary {
background: #29427A;
border-color: #29427A;
color: #fff;
width: auto;
}
@media(min-width: 768px) {
.create-vm-container {
padding-top: 120px;
}
}

View file

@ -1,73 +1,76 @@
(function($){ (function($){
"use strict"; // Start of use strict "use strict"; // Start of use strict
$(window).load(function(){
});
$(document).ready(function(){
_initOs();
});
$(window).resize(function(){
});
var cardPricing = {
function _initOs(){ 'cpu': {
'id': 'coreValue',
'value': 1,
$('.os-circle').click(function(event){ 'min': 1,
$('.os-circle').removeClass('active'); 'max': 48,
$(this).addClass('active'); 'interval': 1
},
var idTemplate = $(this).data('id'); 'ram': {
$('input[name=vm_template_id]').val(idTemplate); 'id': 'ramValue',
}); 'value': 2,
$('.config-box').click(function(event){ 'min': 2,
$('.config-box').removeClass('active'); 'max': 200,
$(this).addClass('active'); 'interval': 1
var idConfig = $(this).data('id'); },
var price = $(this).data('price'); 'storage': {
$('input[name=configuration]').val(idConfig); 'id': 'storageValue',
$('.container-button').fadeIn(); 'value': 10,
$('#priceValue').text(price); 'min': 10,
}); 'max': 2000,
'interval': 10
$('.owl-carousel').owlCarousel({
items:4,
nav: true,
margin:30,
responsiveClass:true,
navText: ['<i class="fa fa-angle-left"></i>', '<i class="fa fa-angle-right"></i>'],
responsive:{
0:{
items:1,
nav:true
},
600:{
items:2,
nav:true
},
768:{
items:3,
nav:true
},
990:{
items:4,
nav:true
}
} }
}); };
}
function _initPricing() {
_fetchPricing();
})(jQuery); $('.fa-minus.left').click(function(event) {
var data = $(this).data('minus');
if (cardPricing[data].value > cardPricing[data].min) {
cardPricing[data].value = Number(cardPricing[data].value) - cardPricing[data].interval;
}
_fetchPricing();
});
$('.fa-plus.right').click(function(event) {
var data = $(this).data('plus');
if (cardPricing[data].value < cardPricing[data].max) {
cardPricing[data].value = Number(cardPricing[data].value) + cardPricing[data].interval;
}
_fetchPricing();
});
$('.input-price').change(function() {
var data = $(this).attr("name");
cardPricing[data].value = $('input[name=' + data + ']').val();
_fetchPricing();
});
}
function _fetchPricing() {
Object.keys(cardPricing).map(function(element) {
//$('#'+cardPricing[element].id).val(cardPricing[element].value);
$('input[name=' + element + ']').val(cardPricing[element].value);
});
_calcPricing();
}
function _calcPricing() {
var total = (cardPricing['cpu'].value * 5) + (2 * cardPricing['ram'].value) + (0.6 * cardPricing['storage'].value);
total = parseFloat(total.toFixed(2));
$("#total").text(total);
$('input[name=total]').val(total);
}
$(document).ready(function() {
_initPricing();
});
})(jQuery);

View file

@ -0,0 +1,73 @@
(function($){
"use strict"; // Start of use strict
$(window).load(function(){
});
$(document).ready(function(){
_initOs();
});
$(window).resize(function(){
});
function _initOs(){
$('.os-circle').click(function(event){
$('.os-circle').removeClass('active');
$(this).addClass('active');
var idTemplate = $(this).data('id');
$('input[name=vm_template_id]').val(idTemplate);
});
$('.config-box').click(function(event){
$('.config-box').removeClass('active');
$(this).addClass('active');
var idConfig = $(this).data('id');
var price = $(this).data('price');
$('input[name=configuration]').val(idConfig);
$('.container-button').fadeIn();
$('#priceValue').text(price);
});
$('.owl-carousel').owlCarousel({
items:4,
nav: true,
margin:30,
responsiveClass:true,
navText: ['<i class="fa fa-angle-left"></i>', '<i class="fa fa-angle-right"></i>'],
responsive:{
0:{
items:1,
nav:true
},
600:{
items:2,
nav:true
},
768:{
items:3,
nav:true
},
990:{
items:4,
nav:true
}
}
});
}
})(jQuery);

View file

@ -74,8 +74,8 @@
{% endif %} {% endif %}
<!-- jQuery --> <!-- jQuery -->
<script src="{% static 'hosting/js/jquery.js' %}"></script> <script src="{% static 'hosting/js/jquery.js' %}"></script>
<script type="text/javascript" src="//cdn.jsdelivr.net/jquery.validation/1.13.1/jquery.validate.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.16.0/jquery.validate.min.js"></script>
<script src="{% static 'hosting/js/vendor/owl.carousel.min.js'%}"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/1000hz-bootstrap-validator/0.11.9/validator.min.js"></script>
<!-- Copy Clipboard --> <!-- Copy Clipboard -->
<script src="//cdnjs.cloudflare.com/ajax/libs/clipboard.js/1.5.10/clipboard.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/clipboard.js/1.5.10/clipboard.min.js"></script>
@ -106,8 +106,6 @@
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js"></script> <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment-with-locales.js"></script> <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment-with-locales.js"></script>
</body> </body>
</html> </html>

View file

@ -1,27 +1,20 @@
{% load staticfiles i18n%} {% load staticfiles i18n%}
<form id="order_form" method="POST" action="" data-toggle="validator" role="form"> <form id="order_form" method="POST" action="" data-toggle="validator" role="form">
{% csrf_token %} {% csrf_token %}
<div class="title">
<h3>{% trans "VM hosting" %} </h3>
</div>
<div class="price"> <div class="price">
<span id="total">15</span> <span id="total">15</span>
<span>CHF/{% trans "month" %}</span> <span>CHF/30 {% trans "days" %}</span>
<div class="price-text"> <div class="price-text">
<p>{% trans "VAT included" %}</p> <p>{% trans "VAT included" %}</p>
</div> </div>
</div> </div>
<div class="descriptions"> <div class="descriptions">
<div class="description form-group">
<p>{% trans "Hosted in Switzerland" %}</p>
</div>
<div class="form-group"> <div class="form-group">
<div class="description input"> <div class="description input">
<i class="fa fa-minus-circle left" data-minus="cpu" aria-hidden="true"></i> <i class="fa fa-minus left" data-minus="cpu" aria-hidden="true"></i>
<input class="input-price select-number" type="number" min="1" max="48" id="coreValue" name="cpu" <input class="input-price select-number" type="number" min="1" max="48" id="coreValue" name="cpu" data-error="{% trans 'Please enter a value in range 1 - 48.' %}" required>
data-error="{% trans 'Please enter a value in range 1 - 48.' %}" required>
<span> Core</span> <span> Core</span>
<i class="fa fa-plus-circle right" data-plus="cpu" aria-hidden="true"></i> <i class="fa fa-plus right" data-plus="cpu" aria-hidden="true"></i>
</div> </div>
<div class="help-block with-errors"> <div class="help-block with-errors">
{% for message in messages %} {% for message in messages %}
@ -35,11 +28,11 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="description input"> <div class="description input">
<i class="fa fa-minus-circle left" data-minus="ram" aria-hidden="true"></i> <i class="fa fa-minus left" data-minus="ram" aria-hidden="true"></i>
<input id="ramValue" class="input-price select-number" type="number" min="2" max="200" name="ram" <input id="ramValue" class="input-price select-number" type="number" min="2" max="200" name="ram"
data-error="{% trans 'Please enter a value in range 2 - 200.' %}" required> data-error="{% trans 'Please enter a value in range 2 - 200.' %}" required>
<span> GB RAM</span> <span> GB RAM</span>
<i class="fa fa-plus-circle right" data-plus="ram" aria-hidden="true"></i> <i class="fa fa-plus right" data-plus="ram" aria-hidden="true"></i>
</div> </div>
<div class="help-block with-errors"> <div class="help-block with-errors">
{% for message in messages %} {% for message in messages %}
@ -53,11 +46,11 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="description input"> <div class="description input">
<i class="fa fa-minus-circle left" data-minus="storage" aria-hidden="true"></i> <i class="fa fa-minus left" data-minus="storage" aria-hidden="true"></i>
<input id="storageValue" class="input-price select-number" type="number" min="10" max="2000" step="10" <input id="storageValue" class="input-price select-number" type="number" min="10" max="2000" step="10"
name="storage" data-error="{% trans 'Please enter a value in range 10 - 2000.' %}" required> name="storage" data-error="{% trans 'Please enter a value in range 10 - 2000.' %}" required>
<span>{% trans "GB Storage (SSD)" %}</span> <span>{% trans "GB Storage (SSD)" %}</span>
<i class="fa fa-plus-circle right" data-plus="storage" aria-hidden="true"></i> <i class="fa fa-plus right" data-plus="storage" aria-hidden="true"></i>
</div> </div>
<div class="help-block with-errors"> <div class="help-block with-errors">
{% for message in messages %} {% for message in messages %}
@ -69,55 +62,26 @@
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
<div class="description select-configuration input form-group justify-center"> <div class="form-group">
<label for="config">OS</label> <div class="description select-configuration input justify-center">
<select name="config" id=""> <label for="config">OS</label>
{% for template in templates %} <select name="config" id="">
<option value="{{template.opennebula_vm_template_id}}">{{template.name}}</option> {% for template in templates %}
<option value="{{template.opennebula_vm_template_id}}">{{template.name}}</option>
{% endfor %}
</select>
</div>
<div class="help-block with-errors">
{% for message in messages %}
{% if 'cores' in message.tags %}
<ul class="list-unstyled"><li>
{{ message|safe }}
</li></ul>
{% endif %}
{% endfor %} {% endfor %}
</select> </div>
</div> </div>
<input type="hidden" name="total"> <input type="hidden" name="total">
<!--<div class="description check-ip">
<input type="checkbox" name="ipv6"> Ipv6 Only<br>
</div>-->
<div class="form-group">
<div class="description input justify-center">
<label for="name" class="control-label">{% trans "Name"%}</label>
<input type="text" name="name" class="form-control" placeholder="{% trans 'Your Name'%}"
data-minlength="3" data-error="{% trans 'Please enter your name.' %}" required>
</div>
<div class="help-block with-errors">
{% for message in messages %}
{% if 'name' in message.tags %}
<ul class="list-unstyled">
<li>
{{ message|safe }}
</li>
</ul>
{% endif %}
{% endfor %}
</div>
</div>
<div class="form-group">
<div class="description input justify-center">
<label for="email" class="control-label">{% trans "Email"%}</label>
<input name="email" type="email" pattern="^[^@\s]+@([^@\s]+\.)+[^@\s]+$" class="form-control"
placeholder="{% trans 'Your Email' %}"
data-error="{% trans 'Please enter a valid email address.' %}" required>
</div>
<div class="help-block with-errors">
{% for message in messages %}
{% if 'email' in message.tags %}
<ul class="list-unstyled">
<li>
{{ message|safe }}
</li>
</ul>
{% endif %}
{% endfor %}
</div>
</div>
</div> </div>
<input type="submit" class="btn btn-primary disabled" value="{% trans 'Continue' %}"></input> <input type="submit" class="btn btn-primary disabled" value="{% trans 'Continue' %}"></input>
</form> </form>

View file

@ -2,24 +2,23 @@
{% load staticfiles bootstrap3 i18n %} {% load staticfiles bootstrap3 i18n %}
{% block content %} {% block content %}
<div class="dashboard-container"> <div class="dashboard-container create-vm-container">
<div class="row"> <div class="row">
<div class="col-sm-6"> <div class="col-sm-6">
<div class="dashboard-container-head"> <div class="dashboard-container-head">
<h3 class="dashboard-title-thin"><img src="{% static 'hosting/img/plusVM.svg' %}" class="un-icon" style="margin-top: -18px;width: 42px;height: 42px;"> {% trans "Create VM" %}</h3> <h3 class="dashboard-title-thin"><img src="{% static 'hosting/img/plusVM.svg' %}" class="un-icon" style="margin-top: -18px;width: 42px;height: 42px;"> {% trans "Create VM" %}</h3>
{% if messages %} {% if messages %}
<div class="alert alert-warning"> <div class="alert alert-warning">
{% for message in messages %} {% for message in messages %}
<span>{{ message }}</span> <span>{{ message }}</span>
{% endfor %} {% endfor %}
</div> </div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
<div class="col-sm-6"> <div class="col-sm-6">
<div class="price-calc-section no-padding"> <div class="price-calc-section no-padding">
<div class="landing card"> <div class="landing card">
<img class="img-beta" src="{% static 'datacenterlight/img/beta-img.png' %}" alt="">
<div class="caption"> <div class="caption">
{% include "hosting/calculator_form.html" %} {% include "hosting/calculator_form.html" %}
</div> </div>

View file

@ -1,9 +1,11 @@
import uuid import uuid
from django import forms
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.tokens import default_token_generator from django.contrib.auth.tokens import default_token_generator
from django.core.exceptions import ValidationError
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from django.core.urlresolvers import reverse_lazy, reverse from django.core.urlresolvers import reverse_lazy, reverse
from django.http import Http404 from django.http import Http404
@ -25,7 +27,7 @@ from stored_messages.settings import stored_messages_settings
from membership.models import CustomUser, StripeCustomer 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 VirtualMachineTemplateSerializer, VMTemplateSerializer
from utils.forms import BillingAddressForm, PasswordResetRequestForm, \ from utils.forms import BillingAddressForm, PasswordResetRequestForm, \
UserBillingAddressForm UserBillingAddressForm
from utils.mailer import BaseEmail from utils.mailer import BaseEmail
@ -36,6 +38,8 @@ 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
from datacenterlight.models import VMTemplate
CONNECTION_ERROR = "Your VMs cannot be displayed at the moment due to a backend \ CONNECTION_ERROR = "Your VMs cannot be displayed at the moment due to a backend \
connection error. please try again in a few minutes." connection error. please try again in a few minutes."
@ -711,7 +715,7 @@ class PaymentVMView(LoginRequiredMixin, FormView):
request.get_host()), request.get_host()),
'page_header': _( 'page_header': _(
'Your New VM %(vm_name)s at Data Center Light') % { 'Your New VM %(vm_name)s at Data Center Light') % {
'vm_name': vm.get('name')} 'vm_name': vm.get('name')}
} }
email_data = { email_data = {
'subject': context.get('page_header'), 'subject': context.get('page_header'),
@ -827,48 +831,80 @@ class CreateVirtualMachinesView(LoginRequiredMixin, View):
template_name = "hosting/create_virtual_machine.html" template_name = "hosting/create_virtual_machine.html"
login_url = reverse_lazy('hosting:login') login_url = reverse_lazy('hosting:login')
def get(self, request, *args, **kwargs): def validate_cores(self, value):
if (value > 48) or (value < 1):
raise ValidationError(_('Invalid number of cores'))
def validate_memory(self, value):
if (value > 200) or (value < 2):
raise ValidationError(_('Invalid RAM size'))
def validate_storage(self, value):
if (value > 2000) or (value < 10):
raise ValidationError(_('Invalid storage size'))
def get(self, request, *args, **kwargs):
if not UserHostingKey.objects.filter(user=self.request.user).exists(): if not UserHostingKey.objects.filter(user=self.request.user).exists():
messages.success( messages.success(
request, request,
_( _(
'In order to create a VM, you need to create/upload your SSH KEY first.') 'In order to create a VM, you need to'
'create/upload your SSH KEY first.'
)
) )
return HttpResponseRedirect(reverse('hosting:ssh_keys')) return HttpResponseRedirect(reverse('hosting:ssh_keys'))
context = {'templates': VMTemplate.objects.all()}
try:
manager = OpenNebulaManager()
templates = manager.get_templates()
configuration_options = HostingPlan.get_serialized_configs()
context = {
'templates': VirtualMachineTemplateSerializer(templates,
many=True).data,
'configuration_options': configuration_options,
}
except:
messages.error(
request,
'We could not load the VM templates due to a backend connection \
error. Please try again in a few minutes'
)
context = {
'error': 'connection'
}
return render(request, self.template_name, context) return render(request, self.template_name, context)
def post(self, request): def post(self, request):
manager = OpenNebulaManager() cores = request.POST.get('cpu')
template_id = request.POST.get('vm_template_id') cores_field = forms.IntegerField(validators=[self.validate_cores])
template = manager.get_template(template_id) memory = request.POST.get('ram')
configuration_id = int(request.POST.get('configuration')) memory_field = forms.IntegerField(validators=[self.validate_memory])
configuration = HostingPlan.objects.get(id=configuration_id) storage = request.POST.get('storage')
request.session['template'] = VirtualMachineTemplateSerializer( storage_field = forms.IntegerField(validators=[self.validate_storage])
template).data price = request.POST.get('total')
template_id = int(request.POST.get('config'))
template = VMTemplate.objects.filter(
opennebula_vm_template_id=template_id).first()
template_data = VMTemplateSerializer(template).data
request.session['specs'] = configuration.serialize() try:
cores = cores_field.clean(cores)
except ValidationError as err:
msg = '{} : {}.'.format(cores, str(err))
messages.add_message(self.request, messages.ERROR, msg,
extra_tags='cores')
return HttpResponseRedirect(
reverse('datacenterlight:index') + "#order_form")
try:
memory = memory_field.clean(memory)
except ValidationError as err:
msg = '{} : {}.'.format(memory, str(err))
messages.add_message(self.request, messages.ERROR, msg,
extra_tags='memory')
return HttpResponseRedirect(
reverse('datacenterlight:index') + "#order_form")
try:
storage = storage_field.clean(storage)
except ValidationError as err:
msg = '{} : {}.'.format(storage, str(err))
messages.add_message(self.request, messages.ERROR, msg,
extra_tags='storage')
return HttpResponseRedirect(
reverse('datacenterlight:index') + "#order_form")
specs = {
'cpu': cores,
'memory': memory,
'disk_size': storage,
'price': price
}
request.session['specs'] = specs
request.session['template'] = template_data
return redirect(reverse('hosting:payment')) return redirect(reverse('hosting:payment'))