merged master

This commit is contained in:
Arvind Tiwari 2017-08-20 22:04:48 +05:30
commit 653b3654b8
20 changed files with 707 additions and 284 deletions

1
.gitignore vendored
View file

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

View file

@ -1096,19 +1096,6 @@ tech-sub-sec h2 {
padding: 0; padding: 0;
} }
.has-error .checkbox,
.has-error .checkbox-inline,
.has-error .control-label,
.has-error .help-block,
.has-error .radio,
.has-error .radio-inline,
.has-error.checkbox label,
.has-error.checkbox-inline label,
.has-error.radio label,
.has-error.radio-inline label {
color: #eb4d5c;
}
.form-group { .form-group {
margin: 0; margin: 0;
border-bottom: 1px solid rgba(128, 128, 128, 0.3); border-bottom: 1px solid rgba(128, 128, 128, 0.3);
@ -1536,3 +1523,39 @@ a#forgotpassword {
.w380 { .w380 {
max-width: 380px !important; max-width: 380px !important;
} }
/* bootstrap danger color override from #a94442 */
.text-danger,
.has-error .help-block,
.has-error .control-label,
.has-error .radio,
.has-error .checkbox,
.has-error .radio-inline,
.has-error .checkbox-inline,
.has-error.radio label,
.has-error.checkbox label,
.has-error.radio-inline label,
.has-error.checkbox-inline label,
.has-error .form-control,
.has-error .form-control-feedback,
.alert-danger,
.list-group-item-danger,
a.list-group-item-danger,
a.list-group-item-danger:hover,
a.list-group-item-danger:focus,
.panel-danger > .panel-heading {
color: #eb4d5c;
}
.has-error .input-group-addon {
color: #eb4d5c;
border-color: #eb4d5c;
}
a.list-group-item-danger.active,
a.list-group-item-danger.active:hover,
a.list-group-item-danger.active:focus {
background-color: #eb4d5c;
border-color: #eb4d5c;
}
.panel-danger > .panel-heading .badge {
background-color: #eb4d5c;
}

169
datacenterlight/tasks.py Normal file
View file

@ -0,0 +1,169 @@
from dynamicweb.celery import app
from celery.utils.log import get_task_logger
from django.conf import settings
from opennebula_api.models import OpenNebulaManager
from opennebula_api.serializers import VirtualMachineSerializer
from hosting.models import HostingOrder, HostingBill
from utils.forms import UserBillingAddressForm
from datetime import datetime
from membership.models import StripeCustomer
from django.core.mail import EmailMessage
from utils.models import BillingAddress
from celery.exceptions import MaxRetriesExceededError
logger = get_task_logger(__name__)
def retry_task(task, exception=None):
"""Retries the specified task using a "backing off countdown",
meaning that the interval between retries grows exponentially
with every retry.
Arguments:
task:
The task to retry.
exception:
Optionally, the exception that caused the retry.
"""
def backoff(attempts):
return 2 ** attempts
kwargs = {
'countdown': backoff(task.request.retries),
}
if exception:
kwargs['exc'] = exception
raise task.retry(**kwargs)
@app.task(bind=True, max_retries=settings.CELERY_MAX_RETRIES)
def create_vm_task(self, vm_template_id, user, specs, template, stripe_customer_id, billing_address_data,
billing_address_id,
charge):
vm_id = None
try:
final_price = specs.get('price')
billing_address = BillingAddress.objects.filter(id=billing_address_id).first()
customer = StripeCustomer.objects.filter(id=stripe_customer_id).first()
# Create OpenNebulaManager
manager = OpenNebulaManager(email=settings.OPENNEBULA_USERNAME,
password=settings.OPENNEBULA_PASSWORD)
# Create a vm using oneadmin, also specify the name
vm_id = manager.create_vm(
template_id=vm_template_id,
specs=specs,
ssh_key=settings.ONEADMIN_USER_SSH_PUBLIC_KEY,
vm_name="{email}-{template_name}-{date}".format(
email=user.get('email'),
template_name=template.get('name'),
date=int(datetime.now().strftime("%s")))
)
if vm_id is None:
raise Exception("Could not create VM")
# Create a Hosting Order
order = HostingOrder.create(
price=final_price,
vm_id=vm_id,
customer=customer,
billing_address=billing_address
)
# Create a Hosting Bill
HostingBill.create(
customer=customer, billing_address=billing_address)
# Create Billing Address for User if he does not have one
if not customer.user.billing_addresses.count():
billing_address_data.update({
'user': customer.user.id
})
billing_address_user_form = UserBillingAddressForm(
billing_address_data)
billing_address_user_form.is_valid()
billing_address_user_form.save()
# Associate an order with a stripe payment
charge_object = DictDotLookup(charge)
order.set_stripe_charge(charge_object)
# If the Stripe payment succeeds, set order status approved
order.set_approved()
vm = VirtualMachineSerializer(manager.get_vm(vm_id)).data
context = {
'name': user.get('name'),
'email': user.get('email'),
'cores': specs.get('cpu'),
'memory': specs.get('memory'),
'storage': specs.get('disk_size'),
'price': specs.get('price'),
'template': template.get('name'),
'vm.name': vm['name'],
'vm.id': vm['vm_id'],
'order.id': order.id
}
email_data = {
'subject': settings.DCL_TEXT + " Order from %s" % context['email'],
'from_email': settings.DCL_SUPPORT_FROM_ADDRESS,
'to': ['info@ungleich.ch'],
'body': "\n".join(["%s=%s" % (k, v) for (k, v) in context.items()]),
'reply_to': [context['email']],
}
email = EmailMessage(**email_data)
email.send()
except Exception as e:
logger.error(str(e))
try:
retry_task(self)
except MaxRetriesExceededError:
msg_text = 'Finished {} retries for create_vm_task'.format(self.request.retries)
logger.error(msg_text)
# Try sending email and stop
email_data = {
'subject': '{} CELERY TASK ERROR: {}'.format(settings.DCL_TEXT, msg_text),
'from_email': settings.DCL_SUPPORT_FROM_ADDRESS,
'to': ['info@ungleich.ch'],
'body': ',\n'.join(str(i) for i in self.request.args)
}
email = EmailMessage(**email_data)
email.send()
return
return vm_id
class DictDotLookup(object):
"""
Creates objects that behave much like a dictionaries, but allow nested
key access using object '.' (dot) lookups.
"""
def __init__(self, d):
for k in d:
if isinstance(d[k], dict):
self.__dict__[k] = DictDotLookup(d[k])
elif isinstance(d[k], (list, tuple)):
l = []
for v in d[k]:
if isinstance(v, dict):
l.append(DictDotLookup(v))
else:
l.append(v)
self.__dict__[k] = l
else:
self.__dict__[k] = d[k]
def __getitem__(self, name):
if name in self.__dict__:
return self.__dict__[name]
def __iter__(self):
return iter(self.__dict__.keys())

View file

@ -4,7 +4,6 @@ from .forms import BetaAccessForm
from .models import BetaAccess, BetaAccessVMType, BetaAccessVM, VMTemplate from .models import BetaAccess, BetaAccessVMType, BetaAccessVM, VMTemplate
from django.contrib import messages from django.contrib import messages
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.core.mail import EmailMessage
from utils.mailer import BaseEmail from utils.mailer import BaseEmail
from django.shortcuts import render from django.shortcuts import render
from django.shortcuts import redirect from django.shortcuts import redirect
@ -13,14 +12,14 @@ from django.core.exceptions import ValidationError
from django.views.decorators.cache import cache_control from django.views.decorators.cache import cache_control
from django.conf import settings from django.conf import settings
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from utils.forms import BillingAddressForm, UserBillingAddressForm from utils.forms import BillingAddressForm
from utils.models import BillingAddress from utils.models import BillingAddress
from hosting.models import HostingOrder, HostingBill from hosting.models import HostingOrder
from utils.stripe_utils import StripeUtils from utils.stripe_utils import StripeUtils
from datetime import datetime
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 VirtualMachineTemplateSerializer, VirtualMachineSerializer, VMTemplateSerializer from opennebula_api.serializers import VirtualMachineTemplateSerializer, VMTemplateSerializer
from datacenterlight.tasks import create_vm_task
class LandingProgramView(TemplateView): class LandingProgramView(TemplateView):
@ -33,7 +32,6 @@ class SuccessView(TemplateView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
if 'specs' not in request.session or 'user' not in request.session: if 'specs' not in request.session or 'user' not in request.session:
return HttpResponseRedirect(reverse('datacenterlight:index')) return HttpResponseRedirect(reverse('datacenterlight:index'))
elif 'token' not in request.session: elif 'token' not in request.session:
return HttpResponseRedirect(reverse('datacenterlight:payment')) return HttpResponseRedirect(reverse('datacenterlight:payment'))
elif 'order_confirmation' not in request.session: elif 'order_confirmation' not in request.session:
@ -79,8 +77,7 @@ class PricingView(TemplateView):
manager = OpenNebulaManager() manager = OpenNebulaManager()
template = manager.get_template(template_id) template = manager.get_template(template_id)
request.session['template'] = VirtualMachineTemplateSerializer( request.session['template'] = VirtualMachineTemplateSerializer(template).data
template).data
if not request.user.is_authenticated(): if not request.user.is_authenticated():
request.session['next'] = reverse('hosting:payment') request.session['next'] = reverse('hosting:payment')
@ -132,8 +129,7 @@ class BetaAccessView(FormView):
email = BaseEmail(**email_data) email = BaseEmail(**email_data)
email.send() email.send()
messages.add_message( messages.add_message(self.request, messages.SUCCESS, self.success_message)
self.request, messages.SUCCESS, self.success_message)
return render(self.request, 'datacenterlight/beta_success.html', {}) return render(self.request, 'datacenterlight/beta_success.html', {})
@ -185,8 +181,7 @@ class BetaProgramView(CreateView):
email = BaseEmail(**email_data) email = BaseEmail(**email_data)
email.send() email.send()
messages.add_message( messages.add_message(self.request, messages.SUCCESS, self.success_message)
self.request, messages.SUCCESS, self.success_message)
return HttpResponseRedirect(self.get_success_url()) return HttpResponseRedirect(self.get_success_url())
@ -230,8 +225,7 @@ class IndexView(CreateView):
storage_field = forms.IntegerField(validators=[self.validate_storage]) storage_field = forms.IntegerField(validators=[self.validate_storage])
price = request.POST.get('total') 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()
template_data = VMTemplateSerializer(template).data template_data = VMTemplateSerializer(template).data
name = request.POST.get('name') name = request.POST.get('name')
@ -243,40 +237,35 @@ class IndexView(CreateView):
cores = cores_field.clean(cores) cores = cores_field.clean(cores)
except ValidationError as err: except ValidationError as err:
msg = '{} : {}.'.format(cores, str(err)) msg = '{} : {}.'.format(cores, str(err))
messages.add_message( messages.add_message(self.request, messages.ERROR, msg, extra_tags='cores')
self.request, messages.ERROR, msg, extra_tags='cores')
return HttpResponseRedirect(reverse('datacenterlight:index') + "#order_form") return HttpResponseRedirect(reverse('datacenterlight:index') + "#order_form")
try: try:
memory = memory_field.clean(memory) memory = memory_field.clean(memory)
except ValidationError as err: except ValidationError as err:
msg = '{} : {}.'.format(memory, str(err)) msg = '{} : {}.'.format(memory, str(err))
messages.add_message( messages.add_message(self.request, messages.ERROR, msg, extra_tags='memory')
self.request, messages.ERROR, msg, extra_tags='memory')
return HttpResponseRedirect(reverse('datacenterlight:index') + "#order_form") return HttpResponseRedirect(reverse('datacenterlight:index') + "#order_form")
try: try:
storage = storage_field.clean(storage) storage = storage_field.clean(storage)
except ValidationError as err: except ValidationError as err:
msg = '{} : {}.'.format(storage, str(err)) msg = '{} : {}.'.format(storage, str(err))
messages.add_message( messages.add_message(self.request, messages.ERROR, msg, extra_tags='storage')
self.request, messages.ERROR, msg, extra_tags='storage')
return HttpResponseRedirect(reverse('datacenterlight:index') + "#order_form") return HttpResponseRedirect(reverse('datacenterlight:index') + "#order_form")
try: try:
name = name_field.clean(name) name = name_field.clean(name)
except ValidationError as err: except ValidationError as err:
msg = '{} {}.'.format(name, _('is not a proper name')) msg = '{} {}.'.format(name, _('is not a proper name'))
messages.add_message( messages.add_message(self.request, messages.ERROR, msg, extra_tags='name')
self.request, messages.ERROR, msg, extra_tags='name')
return HttpResponseRedirect(reverse('datacenterlight:index') + "#order_form") return HttpResponseRedirect(reverse('datacenterlight:index') + "#order_form")
try: try:
email = email_field.clean(email) email = email_field.clean(email)
except ValidationError as err: except ValidationError as err:
msg = '{} {}.'.format(email, _('is not a proper email')) msg = '{} {}.'.format(email, _('is not a proper email'))
messages.add_message( messages.add_message(self.request, messages.ERROR, msg, extra_tags='email')
self.request, messages.ERROR, msg, extra_tags='email')
return HttpResponseRedirect(reverse('datacenterlight:index') + "#order_form") return HttpResponseRedirect(reverse('datacenterlight:index') + "#order_form")
specs = { specs = {
@ -341,8 +330,7 @@ class IndexView(CreateView):
email = BaseEmail(**email_data) email = BaseEmail(**email_data)
email.send() email.send()
messages.add_message( messages.add_message(self.request, messages.SUCCESS, self.success_message)
self.request, messages.SUCCESS, self.success_message)
return super(IndexView, self).form_valid(form) return super(IndexView, self).form_valid(form)
@ -411,7 +399,6 @@ class PaymentOrderView(FormView):
# Create Billing Address # Create Billing Address
billing_address = form.save() billing_address = form.save()
request.session['billing_address_data'] = billing_address_data request.session['billing_address_data'] = billing_address_data
request.session['billing_address'] = billing_address.id request.session['billing_address'] = billing_address.id
request.session['token'] = token request.session['token'] = token
@ -436,8 +423,11 @@ class OrderConfirmationView(DetailView):
stripe_customer_id = request.session.get('customer') stripe_customer_id = request.session.get('customer')
customer = StripeCustomer.objects.filter(id=stripe_customer_id).first() customer = StripeCustomer.objects.filter(id=stripe_customer_id).first()
stripe_utils = StripeUtils() stripe_utils = StripeUtils()
card_details = stripe_utils.get_card_details( card_details = stripe_utils.get_card_details(customer.stripe_id, request.session.get('token'))
customer.stripe_id, request.session.get('token')) if not card_details.get('response_object') and not card_details.get('paid'):
msg = card_details.get('error')
messages.add_message(self.request, messages.ERROR, msg, extra_tags='failed_payment')
return HttpResponseRedirect(reverse('datacenterlight:payment') + '#payment_error')
context = { context = {
'site_url': reverse('datacenterlight:index'), 'site_url': reverse('datacenterlight:index'),
'cc_last4': card_details.get('response_object').get('last4'), 'cc_last4': card_details.get('response_object').get('last4'),
@ -453,8 +443,6 @@ class OrderConfirmationView(DetailView):
customer = StripeCustomer.objects.filter(id=stripe_customer_id).first() customer = StripeCustomer.objects.filter(id=stripe_customer_id).first()
billing_address_data = request.session.get('billing_address_data') billing_address_data = request.session.get('billing_address_data')
billing_address_id = request.session.get('billing_address') billing_address_id = request.session.get('billing_address')
billing_address = BillingAddress.objects.filter(
id=billing_address_id).first()
vm_template_id = template.get('id', 1) vm_template_id = template.get('id', 1)
final_price = specs.get('price') final_price = specs.get('price')
@ -462,83 +450,16 @@ class OrderConfirmationView(DetailView):
stripe_utils = StripeUtils() stripe_utils = StripeUtils()
charge_response = stripe_utils.make_charge(amount=final_price, charge_response = stripe_utils.make_charge(amount=final_price,
customer=customer.stripe_id) customer=customer.stripe_id)
charge = charge_response.get('response_object')
# Check if the payment was approved # Check if the payment was approved
if not charge: if not charge_response.get('response_object') and not charge_response.get('paid'):
context = {} msg = charge_response.get('error')
context.update({ messages.add_message(self.request, messages.ERROR, msg, extra_tags='make_charge_error')
'paymentError': charge_response.get('error') return HttpResponseRedirect(reverse('datacenterlight:payment') + '#payment_error')
})
return render(request, self.payment_template_name, context)
charge = charge_response.get('response_object') charge = charge_response.get('response_object')
create_vm_task.delay(vm_template_id, user, specs, template, stripe_customer_id, billing_address_data,
# Create OpenNebulaManager billing_address_id,
manager = OpenNebulaManager(email=settings.OPENNEBULA_USERNAME, charge)
password=settings.OPENNEBULA_PASSWORD)
# Create a vm using oneadmin, also specify the name
vm_id = manager.create_vm(
template_id=vm_template_id,
specs=specs,
ssh_key=settings.ONEADMIN_USER_SSH_PUBLIC_KEY,
vm_name="{email}-{template_name}-{date}".format(
email=user.get('email'),
template_name=template.get('name'),
date=int(datetime.now().strftime("%s")))
)
# Create a Hosting Order
order = HostingOrder.create(
price=final_price,
vm_id=vm_id,
customer=customer,
billing_address=billing_address
)
# Create a Hosting Bill
HostingBill.create(
customer=customer, billing_address=billing_address)
# Create Billing Address for User if he does not have one
if not customer.user.billing_addresses.count():
billing_address_data.update({
'user': customer.user.id
})
billing_address_user_form = UserBillingAddressForm(
billing_address_data)
billing_address_user_form.is_valid()
billing_address_user_form.save()
# Associate an order with a stripe payment
order.set_stripe_charge(charge)
# If the Stripe payment was successed, set order status approved
order.set_approved()
vm = VirtualMachineSerializer(manager.get_vm(vm_id)).data
context = {
'name': user.get('name'),
'email': user.get('email'),
'cores': specs.get('cpu'),
'memory': specs.get('memory'),
'storage': specs.get('disk_size'),
'price': specs.get('price'),
'template': template.get('name'),
'vm.name': vm['name'],
'vm.id': vm['vm_id'],
'order.id': order.id
}
email_data = {
'subject': settings.DCL_TEXT + " Order from %s" % context['email'],
'from_email': settings.DCL_SUPPORT_FROM_ADDRESS,
'to': ['info@ungleich.ch'],
'body': "\n".join(["%s=%s" % (k, v) for (k, v) in context.items()]),
'reply_to': [context['email']],
}
email = EmailMessage(**email_data)
email.send()
request.session['order_confirmation'] = True request.session['order_confirmation'] = True
return HttpResponseRedirect(reverse('datacenterlight:order_success')) return HttpResponseRedirect(reverse('datacenterlight:order_success'))

View file

@ -13,6 +13,7 @@ while true; do
case "$1" in case "$1" in
-h | --help ) HELP=true; shift ;; -h | --help ) HELP=true; shift ;;
-v | --verbose ) VERBOSE=true; shift ;; -v | --verbose ) VERBOSE=true; shift ;;
-D | --dbmakemigrations ) DB_MAKE_MIGRATIONS=true; shift ;;
-d | --dbmigrate ) DB_MIGRATE=true; shift ;; -d | --dbmigrate ) DB_MIGRATE=true; shift ;;
-n | --nogit ) NO_GIT=true; shift ;; -n | --nogit ) NO_GIT=true; shift ;;
-b | --branch ) BRANCH="$2"; shift 2 ;; -b | --branch ) BRANCH="$2"; shift 2 ;;
@ -31,13 +32,15 @@ if [ "$HELP" == "true" ]; then
echo "options are : " echo "options are : "
echo " -h, --help: Print this help message" echo " -h, --help: Print this help message"
echo " -v, --verbose: Show verbose output to stdout. Without this a deploy.log is written to ~/app folder" echo " -v, --verbose: Show verbose output to stdout. Without this a deploy.log is written to ~/app folder"
echo " -d, --dbmigrate: Do DB migrate" echo " -D, --dbmakemigrations: Do DB makemigrations"
echo " -n, --nogit: Don't execute git commands. With this --branch has no effect." echo " -d, --dbmigrate: Do DB migrate. To do both makemigrations and migrate, supply both switches -D and -d"
echo " -n, --nogit: Don't execute git commands. This is used to deploy the current code in the project repo. With this --branch has no effect."
echo " -b, --branch: The branch to pull from origin repo." echo " -b, --branch: The branch to pull from origin repo."
exit exit
fi fi
echo "BRANCH="$BRANCH echo "BRANCH="$BRANCH
echo "DB_MAKE_MIGRATIONS="$DB_MAKE_MIGRATIONS
echo "DB_MIGRATE="$DB_MIGRATE echo "DB_MIGRATE="$DB_MIGRATE
echo "NO_GIT="$NO_GIT echo "NO_GIT="$NO_GIT
echo "VERBOSE="$VERBOSE echo "VERBOSE="$VERBOSE
@ -45,7 +48,7 @@ echo "VERBOSE="$VERBOSE
# The project directory exists, we pull the specified branch # The project directory exists, we pull the specified branch
cd $APP_HOME_DIR cd $APP_HOME_DIR
if [ -z "$NO_GIT" ]; then if [ -z "$NO_GIT" ]; then
echo 'We are executing default git commands. Please -no_git to not use this.' echo 'We are executing default git commands. Please add --nogit to not do this.'
# Save any modified changes before git pulling # Save any modified changes before git pulling
git stash git stash
# Fetch all branches/tags # Fetch all branches/tags
@ -59,16 +62,23 @@ fi
source ~/pyvenv/bin/activate source ~/pyvenv/bin/activate
pip install -r requirements.txt > deploy.log 2>&1 pip install -r requirements.txt > deploy.log 2>&1
echo "###" >> deploy.log echo "###" >> deploy.log
if [ -z "$DB_MIGRATE" ]; then if [ -z "$DB_MAKE_MIGRATIONS" ]; then
echo 'We are not doing DB migration' echo 'We are not doing DB makemigrations'
else else
echo 'Doing DB makemigrations'
./manage.py makemigrations >> deploy.log 2>&1 ./manage.py makemigrations >> deploy.log 2>&1
echo "###" >> deploy.log echo "###" >> deploy.log
fi
if [ -z "$DB_MIGRATE" ]; then
echo 'We are not doing DB migrate'
else
echo 'Doing DB migrate'
./manage.py migrate >> deploy.log 2>&1 ./manage.py migrate >> deploy.log 2>&1
echo "###" >> deploy.log echo "###" >> deploy.log
fi fi
printf 'yes' | ./manage.py collectstatic >> deploy.log 2>&1 printf 'yes' | ./manage.py collectstatic >> deploy.log 2>&1
echo "###" >> deploy.log echo "###" >> deploy.log
django-admin compilemessages django-admin compilemessages
sudo systemctl restart celery.service
sudo systemctl restart uwsgi sudo systemctl restart uwsgi

View file

@ -0,0 +1,5 @@
# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
from .celery import app as celery_app
__all__ = ['celery_app']

21
dynamicweb/celery.py Normal file
View file

@ -0,0 +1,21 @@
import os
from celery import Celery
# set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dynamicweb.settings')
app = Celery('dynamicweb')
# Using a string here means the worker don't have to serialize
# the configuration object to child processes.
# - namespace='CELERY' means all celery-related configuration keys
# should have a `CELERY_` prefix.
app.config_from_object('django.conf:settings', namespace='CELERY')
# Load task modules from all registered Django app configs.
app.autodiscover_tasks()
@app.task(bind=True)
def debug_task(self):
print('Request: {0!r}'.format(self.request))

View file

@ -10,6 +10,9 @@ from django.utils.translation import ugettext_lazy as _
# dotenv # dotenv
import dotenv import dotenv
import logging
logger = logging.getLogger(__name__)
def gettext(s): def gettext(s):
@ -25,6 +28,21 @@ def bool_env(val):
return True if os.environ.get(val, False) == 'True' else False return True if os.environ.get(val, False) == 'True' else False
def int_env(val, default_value=0):
"""Replaces string based environment values with Python integers
Return default_value if val is not set or cannot be parsed, otherwise
returns the python integer equal to the passed val
"""
return_value = default_value
try:
return_value = int(os.environ.get(val))
except Exception as e:
logger.error("Encountered exception trying to get env value for {}\nException details: {}".format(
val, str(e)))
return return_value
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PROJECT_DIR = os.path.abspath( PROJECT_DIR = os.path.abspath(
@ -120,7 +138,8 @@ INSTALLED_APPS = (
'datacenterlight.templatetags', 'datacenterlight.templatetags',
'alplora', 'alplora',
'rest_framework', 'rest_framework',
'opennebula_api' 'opennebula_api',
'django_celery_results',
) )
MIDDLEWARE_CLASSES = ( MIDDLEWARE_CLASSES = (
@ -524,6 +543,15 @@ GOOGLE_ANALYTICS_PROPERTY_IDS = {
'dynamicweb-staging.ungleich.ch': 'staging' 'dynamicweb-staging.ungleich.ch': 'staging'
} }
# CELERY Settings
CELERY_BROKER_URL = env('CELERY_BROKER_URL')
CELERY_RESULT_BACKEND = env('CELERY_RESULT_BACKEND')
CELERY_ACCEPT_CONTENT = ['application/json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TIMEZONE = 'Europe/Zurich'
CELERY_MAX_RETRIES = int_env('CELERY_MAX_RETRIES', 5)
ENABLE_DEBUG_LOGGING = bool_env('ENABLE_DEBUG_LOGGING') ENABLE_DEBUG_LOGGING = bool_env('ENABLE_DEBUG_LOGGING')
if ENABLE_DEBUG_LOGGING: if ENABLE_DEBUG_LOGGING:

View file

@ -487,17 +487,17 @@ msgstr ""
msgid "Virtual Machines" msgid "Virtual Machines"
msgstr "Virtuelle Maschinen" msgstr "Virtuelle Maschinen"
msgid "Create VM" msgid "To create a new virtual machine, click \"Create VM\""
msgstr "Neue VM"
msgid "ID"
msgstr "" msgstr ""
msgid "Ipv4" msgid "CREATE VM"
msgstr "IPv4" msgstr "NEUE VM"
msgid "Ipv6" msgid "Page"
msgstr "IPv6" msgstr ""
msgid "of"
msgstr ""
msgid "login" msgid "login"
msgstr "einloggen" msgstr "einloggen"
@ -543,6 +543,12 @@ msgstr ""
#~ msgid "Terminate Virtual Machine" #~ msgid "Terminate Virtual Machine"
#~ msgstr "Virtuelle Maschine beenden" #~ msgstr "Virtuelle Maschine beenden"
#~ msgid "Ipv4"
#~ msgstr "IPv4"
#~ msgid "Ipv6"
#~ msgstr "IPv6"
#~ msgid "Close" #~ msgid "Close"
#~ msgstr "Schliessen" #~ msgstr "Schliessen"

View file

@ -1,13 +1,9 @@
import os import os
import logging import logging
from django.db import models from django.db import models
from django.utils.functional import cached_property from django.utils.functional import cached_property
from Crypto.PublicKey import RSA from Crypto.PublicKey import RSA
from membership.models import StripeCustomer, CustomUser from membership.models import StripeCustomer, CustomUser
from utils.models import BillingAddress from utils.models import BillingAddress
from utils.mixins import AssignPermissionsMixin from utils.mixins import AssignPermissionsMixin
@ -42,7 +38,6 @@ class HostingPlan(models.Model):
class HostingOrder(AssignPermissionsMixin, models.Model): class HostingOrder(AssignPermissionsMixin, models.Model):
ORDER_APPROVED_STATUS = 'Approved' ORDER_APPROVED_STATUS = 'Approved'
ORDER_DECLINED_STATUS = 'Declined' ORDER_DECLINED_STATUS = 'Declined'

View file

@ -169,31 +169,68 @@
vertical-align: middle; vertical-align: middle;
margin-right: -4px; margin-right: -4px;
} }
} }
@media (min-width: 768px) { @media (min-width: 768px) {
.modal-dialog { .modal-dialog {
/* width: 520px; */ /* width: 520px; */
margin: 15px auto; margin: 15px auto;
} }
} }
.modal { .modal {
text-align: center; text-align: center;
} }
.modal-dialog { .modal-dialog {
display: inline-block; display: inline-block;
text-align: left; text-align: left;
vertical-align: middle; vertical-align: middle;
} }
/* Duplicate */
.un-icon { .un-icon {
width: 15px; width: 15px;
height: 15px; height: 15px;
opacity: 0.5; opacity: 0.5;
margin-top: -1px; margin-top: -1px;
} }
.css-plus {
position: relative;
width: 16px;
height: 20px;
display: inline-block;
vertical-align: middle;
/* top: -1px; */
}
.css-plus + span {
vertical-align: middle;
}
.css-plus:before {
content: '';
width: 10px;
height: 2px;
background: #f6f7f9;
position: absolute;
left: 50%;
top: 50%;
-webkit-transform: translate(-50%,-50%);
-ms-transform: translate(-50%,-50%);
transform: translate(-50%,-50%);
}
.css-plus:after {
content: '';
width: 2px;
height: 10px;
background: #f6f7f9;
position: absolute;
left: 50%;
top: 50%;
-webkit-transform: translate(-50%,-50%);
-ms-transform: translate(-50%,-50%);
transform: translate(-50%,-50%);
}

View file

@ -333,7 +333,7 @@ h6 {
} }
.auth-box .form .red { .auth-box .form .red {
color: #ea3a3a; color: #eb4d5c;
} }
.auth-box .form .btn { .auth-box .form .btn {
@ -557,6 +557,10 @@ a.unlink:hover {
border-radius: 3px; border-radius: 3px;
padding: 5px; padding: 5px;
} }
.card-warning-error {
border: 1px solid #EB4D5C;
color: #EB4D5C;
}
.card-warning-addtional-margin { .card-warning-addtional-margin {
margin-top: 15px; margin-top: 15px;
@ -743,3 +747,39 @@ a.unlink:hover {
.footer-light a:hover, .footer-light a:focus, .footer-light a:active { .footer-light a:hover, .footer-light a:focus, .footer-light a:active {
color: #ddd; color: #ddd;
} }
/* bootstrap danger color override from #a94442 */
.text-danger,
.has-error .help-block,
.has-error .control-label,
.has-error .radio,
.has-error .checkbox,
.has-error .radio-inline,
.has-error .checkbox-inline,
.has-error.radio label,
.has-error.checkbox label,
.has-error.radio-inline label,
.has-error.checkbox-inline label,
.has-error .form-control,
.has-error .form-control-feedback,
.alert-danger,
.list-group-item-danger,
a.list-group-item-danger,
a.list-group-item-danger:hover,
a.list-group-item-danger:focus,
.panel-danger > .panel-heading {
color: #eb4d5c;
}
.has-error .input-group-addon {
color: #eb4d5c;
border-color: #eb4d5c;
}
a.list-group-item-danger.active,
a.list-group-item-danger.active:hover,
a.list-group-item-danger.active:focus {
background-color: #eb4d5c;
border-color: #eb4d5c;
}
.panel-danger > .panel-heading .badge {
background-color: #eb4d5c;
}

View file

@ -231,12 +231,6 @@
/* Vm Details */ /* Vm Details */
/* might be duplicated from other PR */
.dashboard-title-thin {
font-weight: 300;
font-size: 30px;
}
.vm-detail-item, .vm-contact-us { .vm-detail-item, .vm-contact-us {
overflow: hidden; overflow: hidden;
border: 1px solid #ddd; border: 1px solid #ddd;
@ -425,3 +419,157 @@
.vm-contact-us-text { .vm-contact-us-text {
letter-spacing: 0.4px; letter-spacing: 0.4px;
} }
/* New styles */
.dashboard-container-head {
padding: 0 8px;
}
.dashboard-title-thin {
font-weight: 300;
font-size: 32px;
}
.dashboard-title-thin .un-icon {
height: 34px;
margin-right: 5px;
margin-top: -1px;
width: 20px;
}
.dashboard-subtitle {
font-weight: 300;
margin-bottom: 25px;
}
.btn-vm {
background: #1596DA;
color: #fff;
font-weight: 400;
letter-spacing: 0.8px;
border-radius: 3px;
padding-bottom: 7px;
border: 2px solid #1596DA;
}
.btn-vm:hover, .btn-vm:focus {
color: #1596DA;
background: #fff;
}
.btn-vm:hover .css-plus:after,
.btn-vm:focus .css-plus:after,
.btn-vm:hover .css-plus:before,
.btn-vm:focus .css-plus:before {
background: #1596DA;
}
.btn-vm-detail {
background: #3770CC;
color: #fff;
font-weight: 400;
letter-spacing: 0.6px;
font-size: 14px;
border-radius: 3px;
border: 2px solid #3770CC;
padding: 4px 20px;
/* padding-bottom: 7px; */
}
.btn-vm-detail:hover, .btn-vm-detail:focus {
background: #fff;
color: #3770CC;
}
.vm-status, .vm-status-active, .vm-status-failed {
font-weight: 600;
}
.vm-status-active {
color: #4A90E2;
}
.vm-status-failed {
color: #eb4d5c;
}
@media (min-width:768px) {
.dashboard-subtitle {
display: flex;
justify-content: space-between;
font-size: 16px;
}
}
@media (max-width:767px) {
.dashboard-title-thin {
font-size: 22px;
}
.dashboard-title-thin .un-icon {
height: 20px;
width: 18px;
margin-top: -3px;
}
.dashboard-subtitle p {
width: 200px;
}
}
.table-switch {
color: #555;
}
.table-switch > tbody > tr > td {
padding: 12px 8px;
}
@media (min-width: 768px) {
.table-switch > tbody > tr > td:nth-child(1) {
padding-right: 45px;
}
.table-switch > tbody > tr:last-child > td {
border-bottom: 1px solid #ddd;
}
}
.table-switch .un-icon {
margin-left: 5px;
}
@media (max-width:767px) {
.dashboard-subtitle {
margin-bottom: 15px;
}
.table-switch .un-icon {
float: right;
margin-top: 0;
}
.table-switch thead {
display: none;
}
.table-switch tbody tr {
display: block;
position: relative;
border-top: 1px solid #ddd;
/* margin-top: 15px; */
padding-top: 5px;
padding-bottom: 15px;
}
.table-switch tbody tr:last-child {
border-bottom: 1px solid #ddd;
}
.table-switch tbody tr td {
display: block;
padding-top: 28px;
padding-bottom: 6px;
position: relative;
border: 0;
}
.table-switch td:before {
content: attr(data-header);
font-weight: 600;
position: absolute;
top: 5px;
}
.table-switch .last-td {
position: absolute;
bottom: 20px;
right: 0;
}
}

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Svg Vector Icons : http://www.onlinewebfonts.com/icon -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1000 1000" enable-background="new 0 0 1000 1000" xml:space="preserve">
<metadata> Svg Vector Icons : http://www.onlinewebfonts.com/icon </metadata>
<g><path d="M724.6,224.4c0,28.2,22.9,51,51,51s51-22.9,51-51c0-28.2-22.9-51-51-51S724.6,196.2,724.6,224.4z M724.6,551c0,28.2,22.9,51,51,51s51-22.9,51-51c0-28.2-22.9-51-51-51S724.6,522.9,724.6,551L724.6,551z M10,683.7c0,45.1,36.5,81.7,81.7,81.7h347.1v56.4c-10,6.7-18.6,15.3-25.3,25.3l-250.3,0c-28.2,0-51,22.9-51,51s22.9,51,51,51h250.3c16.5,24.7,44.5,40.8,76.4,40.8c31.8,0,59.8-16.1,76.4-40.8h270.7c28.2,0,51-22.9,51-51s-22.9-51-51-51H566.2c-6.7-10-15.3-18.6-25.3-25.3v-56.3h367.5c45.1,0,81.7-36.5,81.7-81.7V91.7c0-45.1-36.5-81.7-81.7-81.7H91.7C46.5,10,10,46.5,10,91.7L10,683.7L10,683.7z M157,112.1h686c24.9,0,44.9,20,44.9,44.9v134.7c0,24.9-20,44.9-44.9,44.9H157c-24.9,0-44.9-20-44.9-44.9V157C112.1,132.1,132.1,112.1,157,112.1L157,112.1L157,112.1z M157,438.7h686c24.9,0,44.9,20,44.9,44.9v134.7c0,24.9-20,44.9-44.9,44.9H157c-24.9,0-44.9-20-44.9-44.9V483.7C112.1,458.8,132.1,438.7,157,438.7L157,438.7L157,438.7z"/></g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -59,7 +59,6 @@
{% csrf_token %} {% csrf_token %}
{% bootstrap_field field show_label=False type='fields'%} {% bootstrap_field field show_label=False type='fields'%}
{% endfor %} {% endfor %}
{% bootstrap_form_errors form type='non_fields'%}
</form> </form>
</div> </div>
<div class="col-xs-12 col-sm-7 col-md-6 creditcard-box dcl-creditcard"> <div class="col-xs-12 col-sm-7 col-md-6 creditcard-box dcl-creditcard">
@ -86,12 +85,28 @@
</form> </form>
<div class="row"> <div class="row">
<div class="col-xs-12"> <div class="col-xs-12">
{% if not messages and not form.non_field_errors %}
<p class="card-warning-content card-warning-addtional-margin"> <p class="card-warning-content card-warning-addtional-margin">
{% blocktrans %} {% blocktrans %}
You are not making any payment yet. After submitting your card You are not making any payment yet. After submitting your card
information, you will be taken to the Confirm Order Page. information, you will be taken to the Confirm Order Page.
{% endblocktrans %} {% endblocktrans %}
</p> </p>
{% endif %}
<div id='payment_error'>
{% for message in messages %}
{% if 'failed_payment' or 'make_charge_error' in message.tags %}
<ul class="list-unstyled"><li>
<p class="card-warning-content card-warning-error">{{ message|safe }}</p>
</li></ul>
{% endif %}
{% endfor %}
{% for error in form.non_field_errors %}
<p class="card-warning-content card-warning-error">
{{ error|escape }}
</p>
{% endfor %}
</div>
</div> </div>
<div class="col-xs-12"> <div class="col-xs-12">
<div class="col-xs-6 pull-right"> <div class="col-xs-6 pull-right">
@ -130,12 +145,29 @@
<div id="card-errors" role="alert"></div> <div id="card-errors" role="alert"></div>
<div class="row"> <div class="row">
<div class="col-xs-12"> <div class="col-xs-12">
{% if not messages and not form.non_field_errors %}
<p class="card-warning-content"> <p class="card-warning-content">
{% blocktrans %} {% blocktrans %}
You are not making any payment yet. After submitting your card You are not making any payment yet. After submitting your card
information, you will be taken to the Confirm Order Page. information, you will be taken to the Confirm Order Page.
{% endblocktrans %} {% endblocktrans %}
</p> </p>
{% endif %}
<div id='payment_error'>
{% for message in messages %}
{% if 'failed_payment' or 'make_charge_error' in message.tags %}
<ul class="list-unstyled"><li>
<p class="card-warning-content card-warning-error">{{ message|safe }}</p>
</li></ul>
{% endif %}
{% endfor %}
{% for error in form.non_field_errors %}
<p class="card-warning-content card-warning-error">
{{ error|escape }}
</p>
{% endfor %}
</div>
</div> </div>
<div class="col-xs-12"> <div class="col-xs-12">
<div class="col-xs-6 pull-right"> <div class="col-xs-6 pull-right">
@ -150,15 +182,6 @@
<p class="payment-errors"></p> <p class="payment-errors"></p>
</div> </div>
</div> </div>
{% if paymentError %}
<div class="row">
<div class="col-xs-12">
<p>
{% bootstrap_alert paymentError alert_type='danger' %}
</p>
</div>
</div>
{% endif %}
</form> </form>
{% endif %} {% endif %}

View file

@ -77,7 +77,7 @@
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<h4 class="modal-title" id="ModalLabel_Public_Key">{% trans "Public SSH key" %}</h4> <h4 class="modal-title" id="ModalLabel_Public_Key">{% trans "Public SSH Key" %}</h4>
<p class="key_contain" style="margin-top: 10px;">{{ user_key.public_key }}</p> <p class="key_contain" style="margin-top: 10px;">{{ user_key.public_key }}</p>
<div class="modal-footer"> <div class="modal-footer">
</div> </div>

View file

@ -1,14 +1,9 @@
{% extends "hosting/base_short.html" %} {% extends "hosting/base_short.html" %}
{% load staticfiles bootstrap3 i18n %} {% load staticfiles bootstrap3 i18n %}
{% block content %} {% block content %}
<div> <div class="dashboard-container">
<div class="dashboard-container"> <div class="dashboard-container-head">
<div class="row"> <h3 class="dashboard-title-thin"><img src="{% static 'hosting/img/vm.svg' %}" class="un-icon"> {% trans "Virtual Machines" %}</h3>
<div class="col-xs-12 container-table">
<table class="table borderless table-hover">
<h3 class="pull-left"><i class="fa fa-server fa-separate" aria-hidden="true"></i> {% trans "Virtual Machines"%}</h3>
<div class="col-md-12">
<br/>
{% if messages %} {% if messages %}
<div class="alert alert-warning"> <div class="alert alert-warning">
{% for message in messages %} {% for message in messages %}
@ -16,74 +11,67 @@
{% endfor %} {% endfor %}
</div> </div>
{% endif %} {% endif %}
</div>
{% if not error %} {% if not error %}
<p class="pull-right btn-create-vm"> <div class="dashboard-subtitle">
<a class="btn btn-success" href="{% url 'hosting:create_virtual_machine' %}" >{% trans "Create VM"%} </a> <p>{% trans 'To create a new virtual machine, click "Create VM"' %}</p>
</p> <div class="text-right">
<br/> <a class="btn btn-vm" href="{% url 'hosting:create_virtual_machine' %}"><span class="css-plus"></span> <span>{% trans "CREATE VM" %}</span></a>
</div>
</div>
{% endif %}
</div>
{% if not error %}
<table class="table table-switch">
<thead> <thead>
<tr> <tr>
<th>{% trans "ID"%}</th> <th>ID</th>
<th>{% trans "Ipv4"%}</th> <th>IPv4</th>
<th>{% trans "Ipv6"%}</th> <th>IPv6</th>
<th>{% trans "Status"%}</th> <th>{% trans "Status" %}</th>
<th></th> <th></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for vm in vms %} {% for vm in vms %}
<tr> <tr>
<td scope="row">{{vm.vm_id}}</td> <td data-header="ID">{{vm.vm_id}}</td>
{% if vm.ipv6 %} {% if vm.ipv6 %}
<td>{{vm.ipv4}}</td> <td data-header="IPv4">{{vm.ipv4}}</td>
<td data-header="IPv6">{{vm.ipv6}}</td>
<td>{{vm.ipv6}}</td>
{% endif %} {% endif %}
<td data-header="{% trans 'Status' %}">
<td>
{% if vm.state == 'ACTIVE' %} {% if vm.state == 'ACTIVE' %}
<span class="h3 label label-success"><strong> {{vm.state}}</strong></span> <span class="vm-status-active"><strong>{{vm.state|title}}</strong></span>
{% elif vm.state == 'FAILED' %} {% elif vm.state == 'FAILED' %}
<span class="h3 label label-danger"><strong>{{vm.state}}</strong></span> <span class="vm-status-failed"><strong>{{vm.state|title}}</strong></span>
{% else %} {% else %}
<span class="h3 label label-warning"><strong>{{vm.state}}</strong></span> <span class="vm-status"><strong>{{vm.state|title}}</strong></span>
{% endif %} {% endif %}
</td> </td>
<td> <td class="text-right last-td">
<button type="button" class="btn btn-default"><a <a class="btn btn-vm-detail" href="{% url 'hosting:virtual_machines' vm.vm_id %}">{% trans "View Detail" %}</a>
href="{% url 'hosting:virtual_machines' vm.vm_id %}">{% trans "View Detail"%}</a></button>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
{% endif %}
</table> </table>
{% endif %}
{% if is_paginated %} {% if is_paginated %}
<div class="pagination"> <div class="pagination">
<span class="page-links"> <span class="page-links">
{% if page_obj.has_previous %} {% if page_obj.has_previous %}
<a href="{{request.path}}?page={{ page_obj.previous_page_number }}">{% trans "previous"%}</a> <a href="{{request.path}}?page={{ page_obj.previous_page_number }}">{% trans "previous" %}</a>
{% endif %} {% endif %}
<span class="page-current"> <span class="page-current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}. {% trans "Page" %} {{ page_obj.number }} {% trans "of" %} {{ page_obj.paginator.num_pages }}.
</span> </span>
{% if page_obj.has_next %} {% if page_obj.has_next %}
<a href="{{request.path}}?page={{ page_obj.next_page_number }}">{% trans "next"%}</a> <a href="{{request.path}}?page={{ page_obj.next_page_number }}">{% trans "next" %}</a>
{% endif %} {% endif %}
</span> </span>
</div> </div>
{% endif %} {% endif %}
</div>
</div>
</div>
</div> </div>
{%endblock%} {%endblock%}

View file

@ -557,8 +557,9 @@ class PaymentVMView(LoginRequiredMixin, FormView):
customer = StripeCustomer.get_or_create(email=owner.email, customer = StripeCustomer.get_or_create(email=owner.email,
token=token) token=token)
if not customer: if not customer:
form.add_error("__all__", "Invalid credit card") msg = _("Invalid credit card")
return self.render_to_response(self.get_context_data(form=form)) messages.add_message(self.request, messages.ERROR, msg, extra_tags='make_charge_error')
return HttpResponseRedirect(reverse('hosting:payment') + '#payment_error')
# Create Billing Address # Create Billing Address
billing_address = form.save() billing_address = form.save()
@ -567,15 +568,12 @@ class PaymentVMView(LoginRequiredMixin, FormView):
stripe_utils = StripeUtils() stripe_utils = StripeUtils()
charge_response = stripe_utils.make_charge(amount=final_price, charge_response = stripe_utils.make_charge(amount=final_price,
customer=customer.stripe_id) customer=customer.stripe_id)
charge = charge_response.get('response_object')
# Check if the payment was approved # Check if the payment was approved
if not charge: if not charge_response.get('response_object') and not charge_response.get('paid'):
context.update({ msg = charge_response.get('error')
'paymentError': charge_response.get('error'), messages.add_message(self.request, messages.ERROR, msg, extra_tags='make_charge_error')
'form': form return HttpResponseRedirect(reverse('hosting:payment') + '#payment_error')
})
return render(request, self.template_name, context)
charge = charge_response.get('response_object') charge = charge_response.get('response_object')

View file

@ -86,3 +86,6 @@ git+https://github.com/ungleich/python-oca.git#egg=python-oca
djangorestframework djangorestframework
flake8==3.3.0 flake8==3.3.0
python-memcached==1.58 python-memcached==1.58
celery==4.0.2
redis==2.10.5
django-celery-results==1.0.1

View file

@ -11,7 +11,7 @@ def handleStripeError(f):
'error': None 'error': None
} }
common_message = "Currently its not possible to make payments." common_message = "Currently it's not possible to make payments."
try: try:
response_object = f(*args, **kwargs) response_object = f(*args, **kwargs)
response = { response = {