Compare commits

..

No commits in common. "master" and "1.6.4" have entirely different histories.

108 changed files with 1559 additions and 4624 deletions

1
.gitignore vendored
View file

@ -42,4 +42,3 @@ secret-key
# to keep empty dirs # to keep empty dirs
!.gitkeep !.gitkeep
*.orig

View file

@ -1,78 +1,3 @@
Next:
* bugfix: Use correct version of django-multisite (MR #676)
2.4.1: 2018-10-18
* bugfix: Update pycryptodome module from 3.4 to 3.6.6 (PR #674)
2.4: 2018-10-18
* #5681: [hosting,dcl] Allow admin to lower minimum RAM to 512 MB (PR #672)
2.3.1: 2018-10-17
* bugfix: [hosting, dcl] Show VAT percent rounded to 2 decimal places in the order confirmation page (PR #673)
2.3: 2018-10-08
* #5690: Generic payment page - allow admin to add a onetime/monthly product and the frontend for user to pay for this product (PR #666)
2.2.2: 2018-09-28
* #5721: Set calculator OS list in alphabetical order and set `Devuan Ascii` as the default (PR #668)
* bugfix: Fix some typos and correct DE translations (PR #667)
2.2.1: 2018-09-25
* feature: Change DCLNavbarPlugin to show login option only if set (PR #665)
* bugfix: Log opennebula errors and send proper message when vm terminate is not completed in the stipulated time (PR #648)
2.2: 2018-09-06
* bugfix: Include price in the Stripe plan name to make it distinct and to correct pricing since version 1.9
2.1.2: 2018-08-30
* bugfix: [blog, comic] Set blog rss feed for all blog templates
2.1.1: 2018-08-24
* #5487: [hosting] Add explicit warning message for teminating VM (PR #656)
* bugfix: [dg] Send email to admin on dg subscription and increase cc_brand field to 128 characters (PR #652)
* #5458: [admin] Make hostingorder more readable (PR #657)
* bugfix: [CMS templates] Set description meta field of ungleich template (was missing before) and set ungleich glarus ag uniformly as author of various CMS pages (PR #653)
* #5473: Ping a VM before saving ssh key of the user (PR #655)
2.1: 2018-08-21
* Bugfix: Increase CC brand name fields from 10 to 128 characters (PR #654)
2.0.5: 2018-08-08
* Fix IPv6 VM name in the billing invoice
2.0.4: 2018-08-07
* Add RSS feed link to the footer of the blog template (PR #651)
* #5308: [ipv6only] Fix - when creating a VM, the name begins with v6only (PR #649)
* #5293: Use `terminate-hard` action instead of `terminate` in the opennebula call to terminate a vm (PR #650)
2.0.3: 2018-07-18
* Remove unused /comic url (PR #644)
* #5126: Allow dynamicweb sites to be iframed on other by setting `X_FRAME_OPTIONS_ALLOW_FROM_URI` (PR #645)
2.0.2: 2018-07-14
* bugfix: [blog] Add missing content block in the blog_ungleich.html template file
2.0.1: 2018-07-14
* bugfix: [blog] Enable content/structure mode in blog page
2.0: 2018-07-07
* #3747: [dcl,hosting] Add multiple cards support (PR #530)
* #3934: [dcl,hosting] Create HostingOrder outside celery task and add and associate OrderDetail with HostingOrder (PR #624)
* #4890: [hosting] Manage SSH keys using IPv6 of the VM (PR #640)
* bugfix: Fix flake8 error that was ignored in release 1.9.1
1.9.1: 2018-06-24
* #4799: [dcl] Show selected vm templates only in calculator (PR #638)
* #4847: [comic] Add google analytics code for comic.ungleich.ch (PR #639)
* feature: add vm_type option to vm_template and dcl calculator to distinguish between public and ipv6only templates (PR #635)
1.9: 2018-05-16
* #4559: [cms] enable discount on cms calculator
1.8: 2018-05-01
* #4527: [hosting] cms calculator on non-cms pages for the hosting app
* bgfix: [dcl] navbar dropdown target fix
* bgfix: [hosting] login/signup pages footer link fix
1.7.2: 2018-04-30
* bgfix: [cms] add favicon extension to ungleich cms pages
* #4474: [cms] reduce heading slider side padding
1.7.1: 2018-04-21
* #4481: [blog] fix de blog pages 500 error
* #4370: [comic] new url /comic to show only comic blogs
1.7: 2018-04-20
* bgfix: [all] Make /blog available on all domains
* #4367: [dcl] email logo resolution fix
* #4376: [cms] dcl promo section plugin link color changed to brighter shade
* #4379: [dcl] pricing without VAT
* bgfix: [blog] fix top menu items to show only one item
* #4297: [cms] favicon as a page attribute for dcl template
1.6.5: 2018-04-08
* #4396: [ungleich] add favicon to ungleich blog
* #4327: [dcl] fix navbar logo repeat
* bgfix: [hosting] fix broken footer links
* bgfix: [dcl] remove ghost migrations from squashed migration
* bgfix: [cms] redirect multi-tenant urls to /cms also
1.6.4: 2018-04-06 1.6.4: 2018-04-06
* #4362: [cms] Fix the need of dummy home page for different CMS-based sites * #4362: [cms] Fix the need of dummy home page for different CMS-based sites
1.6.3: 2018-04-05 1.6.3: 2018-04-05

View file

@ -1,19 +1,10 @@
from django.contrib import admin from django.contrib import admin
from cms.admin.placeholderadmin import PlaceholderAdminMixin from cms.admin.placeholderadmin import PlaceholderAdminMixin
from cms.extensions import PageExtensionAdmin from .cms_models import CMSIntegration
from .cms_models import CMSIntegration, CMSFaviconExtension
from .models import VMPricing, VMTemplate
class CMSIntegrationAdmin(PlaceholderAdminMixin, admin.ModelAdmin): class CMSIntegrationAdmin(PlaceholderAdminMixin, admin.ModelAdmin):
list_display = ('name', 'domain') list_display = ('name', 'domain')
class CMSFaviconExtensionAdmin(PageExtensionAdmin):
pass
admin.site.register(CMSIntegration, CMSIntegrationAdmin) admin.site.register(CMSIntegration, CMSIntegrationAdmin)
admin.site.register(CMSFaviconExtension, CMSFaviconExtensionAdmin)
admin.site.register(VMPricing)
admin.site.register(VMTemplate)

View file

@ -1,19 +1,11 @@
from cms.extensions import PageExtension
from cms.extensions.extension_pool import extension_pool
from cms.models.fields import PlaceholderField from cms.models.fields import PlaceholderField
from cms.models.pluginmodel import CMSPlugin from cms.models.pluginmodel import CMSPlugin
from django import forms
from django.conf import settings
from django.contrib.postgres.fields import ArrayField
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.db import models from django.db import models
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from djangocms_text_ckeditor.fields import HTMLField from djangocms_text_ckeditor.fields import HTMLField
from filer.fields.file import FilerFileField
from filer.fields.image import FilerImageField from filer.fields.image import FilerImageField
from datacenterlight.models import VMPricing, VMTemplate
class CMSIntegration(models.Model): class CMSIntegration(models.Model):
name = models.CharField( name = models.CharField(
@ -29,10 +21,6 @@ class CMSIntegration(models.Model):
navbar_placeholder = PlaceholderField( navbar_placeholder = PlaceholderField(
'datacenterlight_navbar', related_name='dcl-navbar-placeholder+' 'datacenterlight_navbar', related_name='dcl-navbar-placeholder+'
) )
calculator_placeholder = PlaceholderField(
'datacenterlight_calculator',
related_name='dcl-calculator-placeholder+'
)
domain = models.ForeignKey(Site, null=True, blank=True) domain = models.ForeignKey(Site, null=True, blank=True)
class Meta: class Meta:
@ -42,15 +30,9 @@ class CMSIntegration(models.Model):
return self.name return self.name
class CMSFaviconExtension(PageExtension):
favicon = FilerFileField(related_name="cms_favicon_image")
extension_pool.register(CMSFaviconExtension)
# Models for CMS Plugins # Models for CMS Plugins
class DCLSectionPluginModel(CMSPlugin): class DCLSectionPluginModel(CMSPlugin):
heading = models.CharField( heading = models.CharField(
blank=True, null=True, max_length=100, blank=True, null=True, max_length=100,
@ -180,10 +162,6 @@ class DCLNavbarPluginModel(CMSPlugin):
default=True, default=True,
help_text='Select to include the language selection dropdown.' help_text='Select to include the language selection dropdown.'
) )
show_login_option = models.BooleanField(
default=True,
help_text='Uncheck this if you do not want to show login/dashboard.'
)
def get_logo_dark(self): def get_logo_dark(self):
# used only if atleast one logo exists # used only if atleast one logo exists
@ -297,68 +275,3 @@ class DCLSectionPromoPluginModel(CMSPlugin):
if self.background_image: if self.background_image:
extra_classes += ' promo-with-bg' extra_classes += ' promo-with-bg'
return extra_classes return extra_classes
class MultipleChoiceArrayField(ArrayField):
"""
A field that allows us to store an array of choices.
Uses Django's Postgres ArrayField
and a MultipleChoiceField for its formfield.
"""
VMTemplateChoices = []
if settings.OPENNEBULA_DOMAIN != 'test_domain':
VMTemplateChoices = list(
(
str(obj.opennebula_vm_template_id),
(obj.name + ' - ' + VMTemplate.IPV6.title()
if obj.vm_type == VMTemplate.IPV6 else obj.name
)
)
for obj in VMTemplate.objects.all()
)
def formfield(self, **kwargs):
defaults = {
'form_class': forms.MultipleChoiceField,
'choices': self.VMTemplateChoices,
}
defaults.update(kwargs)
# Skip our parent's formfield implementation completely as we don't
# care for it.
# pylint:disable=bad-super-call
return super(ArrayField, self).formfield(**defaults)
class DCLCalculatorPluginModel(CMSPlugin):
pricing = models.ForeignKey(
VMPricing,
related_name="dcl_custom_pricing_vm_pricing",
help_text='Choose a pricing that will be associated with this '
'Calculator'
)
vm_type = models.CharField(
max_length=50, choices=VMTemplate.VM_TYPE_CHOICES,
default=VMTemplate.PUBLIC
)
vm_templates_to_show = MultipleChoiceArrayField(
base_field=models.CharField(
blank=True,
max_length=256,
),
default=list,
blank=True,
help_text="Recommended: If you wish to show all templates of the "
"corresponding VM Type (public/ipv6only), please do not "
"select any of the items in the above field. "
"This will allow any new template(s) added "
"in the backend to be automatically listed in this "
"calculator instance."
)
default_selected_template = models.CharField(
default="Devuan Ascii",
null=True,
max_length=128,
help_text="Write the name of the template that you need selected as"
" default when the calculator loads"
)
enable_512mb_ram = models.BooleanField(default=False)

View file

@ -6,10 +6,9 @@ from .cms_models import (
DCLFooterPluginModel, DCLLinkPluginModel, DCLNavbarDropdownPluginModel, DCLFooterPluginModel, DCLLinkPluginModel, DCLNavbarDropdownPluginModel,
DCLSectionIconPluginModel, DCLSectionImagePluginModel, DCLSectionIconPluginModel, DCLSectionImagePluginModel,
DCLSectionPluginModel, DCLNavbarPluginModel, DCLSectionPluginModel, DCLNavbarPluginModel,
DCLSectionPromoPluginModel, DCLCalculatorPluginModel DCLSectionPromoPluginModel
) )
from .models import VMTemplate from .models import VMTemplate
from datacenterlight.utils import clear_all_session_vars
@plugin_pool.register_plugin @plugin_pool.register_plugin
@ -22,7 +21,7 @@ class DCLSectionPlugin(CMSPluginBase):
allow_children = True allow_children = True
child_classes = [ child_classes = [
'DCLSectionIconPlugin', 'DCLSectionImagePlugin', 'DCLSectionIconPlugin', 'DCLSectionImagePlugin',
'DCLSectionPromoPlugin', 'UngleichHTMLPlugin', 'DCLCalculatorPlugin' 'DCLSectionPromoPlugin', 'UngleichHTMLPlugin'
] ]
def render(self, context, instance, placeholder): def render(self, context, instance, placeholder):
@ -31,17 +30,14 @@ class DCLSectionPlugin(CMSPluginBase):
) )
context['children_to_side'] = [] context['children_to_side'] = []
context['children_to_content'] = [] context['children_to_content'] = []
context['children_calculator'] = []
if instance.child_plugin_instances is not None: if instance.child_plugin_instances is not None:
right_children = [ right_children = [
'DCLSectionImagePluginModel', 'DCLSectionImagePluginModel',
'DCLSectionIconPluginModel', 'DCLSectionIconPluginModel'
] ]
for child in instance.child_plugin_instances: for child in instance.child_plugin_instances:
if child.__class__.__name__ in right_children: if child.__class__.__name__ in right_children:
context['children_to_side'].append(child) context['children_to_side'].append(child)
elif child.plugin_type == 'DCLCalculatorPlugin':
context['children_calculator'].append(child)
else: else:
context['children_to_content'].append(child) context['children_to_content'].append(child)
return context return context
@ -80,27 +76,25 @@ class DCLSectionPromoPlugin(CMSPluginBase):
class DCLCalculatorPlugin(CMSPluginBase): class DCLCalculatorPlugin(CMSPluginBase):
module = "Datacenterlight" module = "Datacenterlight"
name = "DCL Calculator Plugin" name = "DCL Calculator Plugin"
model = DCLCalculatorPluginModel model = DCLSectionPluginModel
render_template = "datacenterlight/cms/calculator.html" render_template = "datacenterlight/cms/calculator.html"
cache = False cache = False
require_parent = True allow_children = True
child_classes = [
'DCLSectionPromoPlugin', 'UngleichHTMLPlugin'
]
def render(self, context, instance, placeholder): def render(self, context, instance, placeholder):
clear_all_session_vars(context['request'])
context = super(DCLCalculatorPlugin, self).render( context = super(DCLCalculatorPlugin, self).render(
context, instance, placeholder context, instance, placeholder
) )
ids = instance.vm_templates_to_show context['templates'] = VMTemplate.objects.all()
if ids: context['children_to_side'] = []
context['templates'] = VMTemplate.objects.filter( context['children_to_content'] = []
vm_type=instance.vm_type if instance.child_plugin_instances is not None:
).filter(opennebula_vm_template_id__in=ids).order_by('name') context['children_to_content'].extend(
else: instance.child_plugin_instances
context['templates'] = VMTemplate.objects.filter( )
vm_type=instance.vm_type
).order_by('name')
context['instance'] = instance
context['min_ram'] = 0.5 if instance.enable_512mb_ram else 1
return context return context

View file

@ -1,24 +0,0 @@
from cms.extensions.toolbar import ExtensionToolbar
from cms.toolbar_pool import toolbar_pool
from django.utils.translation import ugettext_lazy as _
from .cms_models import CMSFaviconExtension
@toolbar_pool.register
class CMSFaviconExtensionToolbar(ExtensionToolbar):
# defineds the model for the current toolbar
model = CMSFaviconExtension
def populate(self):
# setup the extension toolbar with permissions and sanity checks
current_page_menu = self._setup_extension_toolbar()
# if it's all ok
if current_page_menu:
# retrieves the instance of the current extension (if any) and the toolbar item url
page_extension, url = self.get_page_extension_admin()
if url:
# adds a toolbar item
current_page_menu.add_modal_item(
_('CMS Favicon'), url=url, disabled=not self.toolbar.edit_mode
)

View file

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-09-26 20:44+0000\n" "POT-Creation-Date: 2018-03-30 21:29+0000\n"
"PO-Revision-Date: 2018-03-30 23:22+0000\n" "PO-Revision-Date: 2018-03-30 23:22+0000\n"
"Last-Translator: b'Anonymous User <coder.purple+25@gmail.com>'\n" "Last-Translator: b'Anonymous User <coder.purple+25@gmail.com>'\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -19,9 +19,6 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Translated-Using: django-rosetta 0.8.1\n" "X-Translated-Using: django-rosetta 0.8.1\n"
msgid "CMS Favicon"
msgstr ""
#, python-format #, python-format
msgid "Your New VM %(vm_name)s at Data Center Light" msgid "Your New VM %(vm_name)s at Data Center Light"
msgstr "Deine neue VM %(vm_name)s bei Data Center Light" msgstr "Deine neue VM %(vm_name)s bei Data Center Light"
@ -75,9 +72,9 @@ msgstr "Data Center Light Account Aktivierung"
#, python-format #, python-format
msgid "" msgid ""
"You can activate your Data Center Light account by clicking <a href=" "You can activate your Data Center Light account by clicking <a "
"\"%(base_url)s%(activation_link)s\" style=\"text-decoration: none; color: " "href=\"%(base_url)s%(activation_link)s\" style=\"text-decoration: none; "
"#4382c8; font-weight: 400;\">here</a>." "color: #4382c8; font-weight: 400;\">here</a>."
msgstr "" msgstr ""
"Klicke <a href=\"%(base_url)s%(activation_link)s\"style=\"text-decoration: " "Klicke <a href=\"%(base_url)s%(activation_link)s\"style=\"text-decoration: "
"none; color: #4382c8; font-weight: 400;\">hier</a> um deinen Data Center " "none; color: #4382c8; font-weight: 400;\">hier</a> um deinen Data Center "
@ -100,13 +97,12 @@ msgstr "Deine E-Mail-Adresse"
msgid "Password" msgid "Password"
msgstr "Passwort" msgstr "Passwort"
#, python-format
msgid "" msgid ""
"You can reset your password <a href=\"%(base_url)s%(reset_password_url)s\" " "You can reset your password <a href=\"%(base_url)s%(reset_password_url)s\" "
"style=\"text-decoration: none; color: #4382c8; font-weight: 400;\">here</a>." "style=\"text-decoration: none; color: #4382c8; font-weight: 400;\">here</a>."
msgstr "" msgstr ""
"Du kannst dein Passwort <a href=\"%(base_url)s%(reset_password_url)s\" style=" "Du kannst dein Passwort <a href=\"%(base_url)s%(reset_password_url)s\" "
"\"text-decoration: none; color: #4382c8; font-weight: 400;\">hier</a> " "style=\"text-decoration: none; color: #4382c8; font-weight: 400;\">hier</a> "
"zurücksetzen." "zurücksetzen."
msgid "Your Data Center Light Team" msgid "Your Data Center Light Team"
@ -143,9 +139,6 @@ msgstr "Monat"
msgid "VAT included" msgid "VAT included"
msgstr "MwSt. inklusive" msgstr "MwSt. inklusive"
msgid "You save"
msgstr "Du sparst"
msgid "Hosted in Switzerland" msgid "Hosted in Switzerland"
msgstr "Standort: Schweiz" msgstr "Standort: Schweiz"
@ -167,6 +160,21 @@ msgstr "Weiter"
msgid "Home" msgid "Home"
msgstr "Home" msgstr "Home"
msgid "Highlights"
msgstr ""
msgid "Scale out"
msgstr "Skalierung"
msgid "Reliable and light"
msgstr "Zuverlässig und leicht"
msgid "Pricing"
msgstr "Preise"
msgid "Order VM"
msgstr "VM bestellen"
msgid "Contact" msgid "Contact"
msgstr "Kontakt" msgstr "Kontakt"
@ -176,9 +184,6 @@ msgstr "Nutzungsbedingungen"
msgid "Finally, an affordable VM hosting in Switzerland!" msgid "Finally, an affordable VM hosting in Switzerland!"
msgstr "Endlich: bezahlbares VM Hosting in der Schweiz" msgstr "Endlich: bezahlbares VM Hosting in der Schweiz"
msgid "Highlights"
msgstr ""
msgid "I want it!" msgid "I want it!"
msgstr "Das will ich haben!" msgstr "Das will ich haben!"
@ -198,8 +203,8 @@ msgid ""
"order to make it more sustainable and affordable at the same time." "order to make it more sustainable and affordable at the same time."
msgstr "" msgstr ""
"Ist kreativ, indem es sich ein modernes und alternatives Layout zu Nutze " "Ist kreativ, indem es sich ein modernes und alternatives Layout zu Nutze "
"macht um Nachhaltigkeit zu fördern und somit erschwingliche Preise bieten zu " "macht um Nachhaltigkeit zu fördern und somit erschwingliche Preise bieten zu"
"können." " können."
msgid "" msgid ""
"Cuts down the costs for you by using FOSS (Free Open Source Software) " "Cuts down the costs for you by using FOSS (Free Open Source Software) "
@ -209,9 +214,6 @@ msgstr ""
"mit FOSS (Free Open Source Software) arbeitet und wir daher auf " "mit FOSS (Free Open Source Software) arbeitet und wir daher auf "
"Lizenzgebühren verzichten können." "Lizenzgebühren verzichten können."
msgid "Scale out"
msgstr "Skalierung"
msgid "" msgid ""
"We don't use special hardware. We use commodity hardware: we buy computers " "We don't use special hardware. We use commodity hardware: we buy computers "
"that you buy. Just many more and put them in a cozy home for computers " "that you buy. Just many more and put them in a cozy home for computers "
@ -221,9 +223,6 @@ msgstr ""
"erschwingliche Systeme. Bei grösserer Auslastung werden mehr " "erschwingliche Systeme. Bei grösserer Auslastung werden mehr "
"Standardkomponenten hinzugekauft und skalieren so das Datencenter." "Standardkomponenten hinzugekauft und skalieren so das Datencenter."
msgid "Reliable and light"
msgstr "Zuverlässig und leicht"
msgid "" msgid ""
"Our VMs are located in Switzerland, with reliable power supply and fast " "Our VMs are located in Switzerland, with reliable power supply and fast "
"internet connection. Our VM costs less thanks to our featherlight " "internet connection. Our VM costs less thanks to our featherlight "
@ -233,7 +232,8 @@ msgstr ""
"Energieversorgung, sowie schneller Internetverbindung ausgestattet. Unser " "Energieversorgung, sowie schneller Internetverbindung ausgestattet. Unser "
"Angebot ist aufgrund unserer leichten Infrastruktur überaus kostengünstig." "Angebot ist aufgrund unserer leichten Infrastruktur überaus kostengünstig."
msgid "Simple and affordable: Try our virtual machine with featherlight price." msgid ""
"Simple and affordable: Try our virtual machine with featherlight price."
msgstr "" msgstr ""
"Einfach und bezahlbar: Teste nun unsere virtuellen Maschinen mit " "Einfach und bezahlbar: Teste nun unsere virtuellen Maschinen mit "
"federleichten Preisen." "federleichten Preisen."
@ -293,9 +293,6 @@ msgstr "Registrieren"
msgid "Billing Address" msgid "Billing Address"
msgstr "Rechnungsadresse" msgstr "Rechnungsadresse"
msgid "Make a payment"
msgstr ""
msgid "Your Order" msgid "Your Order"
msgstr "Deine Bestellung" msgstr "Deine Bestellung"
@ -317,58 +314,46 @@ msgstr "Gesamt"
msgid "including VAT" msgid "including VAT"
msgstr "inkl. Mehrwertsteuer" msgstr "inkl. Mehrwertsteuer"
msgid "excluding VAT"
msgstr "exkl. Mehrwertsteuer"
msgid "Month" msgid "Month"
msgstr "Monat" msgstr "Monat"
msgid "Discount"
msgstr "Rabatt"
msgid "Will be applied at checkout"
msgstr "wird an der Kasse angewendet"
msgid "Credit Card" msgid "Credit Card"
msgstr "Kreditkarte" msgstr "Kreditkarte"
msgid "" msgid ""
"Please select one of the cards that you used before or fill in your credit " "Please fill in your credit card information below. We are using <a "
"card information below. We are using <a href=\"https://stripe.com\" target=" "href=\"https://stripe.com\" target=\"_blank\">Stripe</a> for payment and do "
"\"_blank\">Stripe</a> for payment and do not store your information in our " "not store your information in our database."
"database."
msgstr "" msgstr ""
"Bitte wähle eine der zuvor genutzten Kreditkarten oder gib Deine " "Bitte fülle Deine Kreditkarteninformationen unten aus. Wir nutzen <a "
"Kreditkartendetails unten an. Die Bezahlung wird über <a href=\"https://" "href=\"https://stripe.com\" target=\"_blank\">Stripe</a> für die Bezahlung "
"stripe.com\" target=\"_blank\">Stripe</a> abgewickelt. Wir speichern Deine " "und speichern keine Informationen in unserer Datenbank."
"Kreditkartendetails nicht in unserer Datenbank."
msgid "" msgid ""
"Please fill in your credit card information below. We are using <a href=" "You are not making any payment yet. After submitting your card information, "
"\"https://stripe.com\" target=\"_blank\">Stripe</a> for payment and do not " "you will be taken to the Confirm Order Page."
"store your information in our database."
msgstr "" msgstr ""
"Bitte fülle Deine Kreditkarteninformationen unten aus. Wir nutzen <a href=" "Es wird noch keine Bezahlung vorgenommen. Die Bezahlung wird erst ausgelöst,"
"\"https://stripe.com\" target=\"_blank\">Stripe</a> für die Bezahlung und " " nachdem Du die Bestellung auf der nächsten Seite bestätigt hast."
"speichern keine Informationen in unserer Datenbank."
msgid "Last" msgid "Card Number"
msgstr "Letzten" msgstr "Kreditkartennummer"
msgid "Type" msgid "Expiry Date"
msgstr "Typ" msgstr "Ablaufdatum"
msgid "SELECT" msgid "CVC"
msgstr "AUSWÄHLEN" msgstr ""
msgid "Add a new credit card" msgid "Card Type"
msgstr "Eine neue Kreditkarte hinzufügen" msgstr "Kartentyp"
msgid "NEW CARD" msgid ""
msgstr "NEUE KARTE" "You are not making any payment yet. After placing your order, you will be "
"taken to the Submit Payment Page."
msgid "New Credit Card" msgstr ""
msgstr "Neue Kreditkarte" "Es wird noch keine Bezahlung vorgenommen. Die Bezahlung wird erst ausgelöst,"
" nachdem Du die Bestellung auf der nächsten Seite bestätigt hast."
msgid "Processing" msgid "Processing"
msgstr "Weiter" msgstr "Weiter"
@ -398,42 +383,13 @@ msgstr "Bestellungsübersicht"
msgid "Product" msgid "Product"
msgstr "Produkt" msgstr "Produkt"
msgid "Amount"
msgstr ""
msgid "Description"
msgstr ""
msgid "Recurring"
msgstr ""
msgid "Subtotal"
msgstr "Zwischensumme"
msgid "VAT"
msgstr "Mehrwertsteuer"
msgid ""
"By clicking \"Place order\" this plan will charge your credit card account "
"with %(total_price)s CHF/month"
msgstr ""
"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit "
"%(vm_total_price)s CHF pro Monat belastet"
msgid ""
"By clicking \"Place order\" this payment will charge your credit card "
"account with a one time amount of %(total_price)s CHF"
msgstr ""
"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit "
"%(vm_total_price)s CHF pro Monat belastet"
#, python-format #, python-format
msgid "" msgid ""
"By clicking \"Place order\" this plan will charge your credit card account " "By clicking \"Place order\" this plan will charge your credit card account "
"with %(vm_total_price)s CHF/month" "with the fee of %(vm_price)sCHF/month"
msgstr "" msgstr ""
"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit " "Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit %(vm_price)sCHF "
"%(vm_total_price)s CHF pro Monat belastet" "pro Monat belastet"
msgid "Place order" msgid "Place order"
msgstr "Bestellen" msgstr "Bestellen"
@ -499,25 +455,25 @@ msgstr "Wir unterstützen die FOSS Community."
msgid "" msgid ""
"Data Center Light is the child of free and open source software (FOSS) " "Data Center Light is the child of free and open source software (FOSS) "
"movement. <br>We grew up with it, live by it, and believe in it.<br> The " "movement. <br>We grew up with it, live by it, and believe in it.<br> The "
"more we work on our data center,<br> the more we contribute back to the FOSS " "more we work on our data center,<br> the more we contribute back to the FOSS"
"community." " community."
msgstr "" msgstr ""
"Data Center Light ist ein Teil der Free und Opens Source Software (FOSS) " "Data Center Light ist ein Teil der Free und Opens Source Software (FOSS) "
"Bewegung.<br/> Wir sind damit gross geworden, leben damit und glauben daran." "Bewegung.<br/> Wir sind damit gross geworden, leben damit und glauben "
"<br/> Je weiter wir mit unserem Data Center Light vorankommen, desto mehr " "daran.<br/> Je weiter wir mit unserem Data Center Light vorankommen, desto "
"können wir etwas an die FOSS Community zurückgeben." "mehr können wir etwas an die FOSS Community zurückgeben."
msgid "We bring the future to you." msgid "We bring the future to you."
msgstr "Wir bringen die Zukunft zu dir." msgstr "Wir bringen die Zukunft zu dir."
msgid "" msgid ""
"Data Center Light uses the most modern technologies out there.<br>Your VM " "Data Center Light uses the most modern technologies out there.<br>Your VM "
"needs only IPv6. Data Center Light provides<br> transparent two-way IPv6/" "needs only IPv6. Data Center Light provides<br> transparent two-way "
"IPv4 translation." "IPv6/IPv4 translation."
msgstr "" msgstr ""
"Data Center Light verwendet die zur Zeit modernsten Technologien.<br/>Deine " "Data Center Light verwendet die zur Zeit modernsten Technologien.<br/>Deine "
"VM läuft mit IPv6. Data Center Light bietet eine transparente IPv6/IPv4-" "VM läuft mit IPv6. Data Center Light bietet eine transparente "
"Zweiweglösung." "IPv6/IPv4-Zweiweglösung."
msgid "" msgid ""
" No more spinning metal plates! Data Center Light uses only SSDs. We keep " " No more spinning metal plates! Data Center Light uses only SSDs. We keep "
@ -541,17 +497,6 @@ msgstr "Ungültige RAM-Grösse"
msgid "Invalid storage size" msgid "Invalid storage size"
msgstr "Ungültige Speicher-Grösse" msgstr "Ungültige Speicher-Grösse"
#, python-brace-format
msgid "Incorrect pricing name. Please contact support{support_email}"
msgstr ""
#, python-brace-format
msgid "{user} does not have permission to access the card"
msgstr "{user} hat keine Erlaubnis auf diese Karte zuzugreifen"
msgid "An error occurred. Details: {}"
msgstr "Ein Fehler ist aufgetreten. Details: {}"
msgid "Confirm Order" msgid "Confirm Order"
msgstr "Bestellung Bestätigen" msgstr "Bestellung Bestätigen"
@ -562,38 +507,8 @@ msgid ""
"There was a payment related error. On close of this popup, you will be " "There was a payment related error. On close of this popup, you will be "
"redirected back to the payment page." "redirected back to the payment page."
msgstr "" msgstr ""
"Es ist ein Fehler bei der Zahlung betreten. Du wirst nach dem Schliessen vom " "Es ist ein Fehler bei der Zahlung betreten. Du wirst nach dem Schliessen vom"
"Popup zur Bezahlseite weitergeleitet." " Popup zur Bezahlseite weitergeleitet."
#, python-brace-format
msgid "An error occurred while associating the card. Details: {details}"
msgstr ""
"Beim Verbinden der Karte ist ein Fehler aufgetreten. Details: {details}"
msgid "Confirmation of your payment"
msgstr ""
msgid " This is a monthly recurring plan."
msgstr ""
#, python-brace-format
msgid ""
"Hi {name},\n"
"\n"
"thank you for your order!\n"
"We have just received a payment of CHF {amount:.2f} from you.{recurring}\n"
"\n"
"Cheers,\n"
"Your Data Center Light team"
msgstr ""
msgid "Thank you for the payment."
msgstr "Danke für Deine Bestellung."
msgid ""
"You will soon receive a confirmation email of the payment. You can always "
"contact us at info@ungleich.ch for any question that you may have."
msgstr ""
msgid "Thank you for the order." msgid "Thank you for the order."
msgstr "Danke für Deine Bestellung." msgstr "Danke für Deine Bestellung."
@ -602,36 +517,8 @@ msgid ""
"Your VM will be up and running in a few moments. We will send you a " "Your VM will be up and running in a few moments. We will send you a "
"confirmation email as soon as it is ready." "confirmation email as soon as it is ready."
msgstr "" msgstr ""
"Deine VM ist gleich bereit. Wir senden Dir eine Bestätigungsemail, sobald Du " "Deine VM ist gleich bereit. Wir senden Dir eine Bestätigungsemail, sobald Du"
"auf sie zugreifen kannst." " auf sie zugreifen kannst."
#~ msgid ""
#~ "You are not making any payment yet. After submitting your card "
#~ "information, you will be taken to the Confirm Order Page."
#~ msgstr ""
#~ "Es wird noch keine Bezahlung vorgenommen. Die Bezahlung wird erst "
#~ "ausgelöst, nachdem Du die Bestellung auf der nächsten Seite bestätigt "
#~ "hast."
#~ msgid "Card Number"
#~ msgstr "Kreditkartennummer"
#~ msgid "Expiry Date"
#~ msgstr "Ablaufdatum"
#~ msgid ""
#~ "You are not making any payment yet. After placing your order, you will be "
#~ "taken to the Submit Payment Page."
#~ msgstr ""
#~ "Es wird noch keine Bezahlung vorgenommen. Die Bezahlung wird erst "
#~ "ausgelöst, nachdem Du die Bestellung auf der nächsten Seite bestätigt "
#~ "hast."
#~ msgid "Pricing"
#~ msgstr "Preise"
#~ msgid "Order VM"
#~ msgstr "VM bestellen"
#~ msgid "Enter name" #~ msgid "Enter name"
#~ msgstr "Name" #~ msgstr "Name"
@ -646,19 +533,18 @@ msgstr ""
#~ msgstr "Anfrage verschickt" #~ msgstr "Anfrage verschickt"
#~ msgid "" #~ msgid ""
#~ "Thank you for your subscription! You will receive a confirmation mail " #~ "Thank you for your subscription! You will receive a confirmation mail from "
#~ "from our team" #~ "our team"
#~ msgstr "" #~ msgstr ""
#~ "Vielen dank für Ihre Anmeldung. Sie erhalten in kürze eine " #~ "Vielen dank für Ihre Anmeldung. Sie erhalten in kürze eine Bestätigungsmail "
#~ "Bestätigungsmail von unserem Team" #~ "von unserem Team"
#~ msgid "Thank you for your request." #~ msgid "Thank you for your request."
#~ msgstr "Vielen Dank für Deine Anfrage." #~ msgstr "Vielen Dank für Deine Anfrage."
#~ msgid "You are one step away from being our beta tester!" #~ msgid "You are one step away from being our beta tester!"
#~ msgstr "" #~ msgstr ""
#~ "Sie sind nur noch einen Schritt davon entfernt, unser Beta-Tester zu " #~ "Sie sind nur noch einen Schritt davon entfernt, unser Beta-Tester zu werden!"
#~ "werden!"
#~ msgid "" #~ msgid ""
#~ "Currently we are running our tests to make sure everything runs perfectly." #~ "Currently we are running our tests to make sure everything runs perfectly."
@ -667,8 +553,8 @@ msgstr ""
#~ "sicherzustellen." #~ "sicherzustellen."
#~ msgid "" #~ msgid ""
#~ "In the meantime, we would like to ask you a little patience<br/> until " #~ "In the meantime, we would like to ask you a little patience<br/> until our "
#~ "our team contacts you with beta access." #~ "team contacts you with beta access."
#~ msgstr "" #~ msgstr ""
#~ "Wir werden dann sobald als möglich Ihren Beta-Zugang erstellen und Sie " #~ "Wir werden dann sobald als möglich Ihren Beta-Zugang erstellen und Sie "
#~ "daraufhin kontaktieren.Bis dahin bitten wir Sie um etwas Geduld." #~ "daraufhin kontaktieren.Bis dahin bitten wir Sie um etwas Geduld."
@ -678,8 +564,8 @@ msgstr ""
#~ msgid "Thank you for order! Our team will contact you via email" #~ msgid "Thank you for order! Our team will contact you via email"
#~ msgstr "" #~ msgstr ""
#~ "Vielen Dank für die Bestellung. Unser Team setzt sich sobald wie möglich " #~ "Vielen Dank für die Bestellung. Unser Team setzt sich sobald wie möglich mit"
#~ "mit Dir via E-Mail in Verbindung." #~ " Dir via E-Mail in Verbindung."
#~ msgid "Affordable VM hosting based in Switzerland" #~ msgid "Affordable VM hosting based in Switzerland"
#~ msgstr "Bezahlbares VM Hosting in der Schweiz" #~ msgstr "Bezahlbares VM Hosting in der Schweiz"
@ -695,18 +581,18 @@ msgstr ""
#~ msgid "" #~ msgid ""
#~ "Our VMs are hosted in Glarus, Switzerland, and our website is currently " #~ "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 " #~ "running in BETA mode. If you want more information that you did not find on "
#~ "on our website, or if your order is more detailed, or if you encounter " #~ "our website, or if your order is more detailed, or if you encounter any "
#~ "any technical hiccups, please contact us at support@datacenterlight.ch, " #~ "technical hiccups, please contact us at support@datacenterlight.ch, our team"
#~ "our team will get in touch with you asap." #~ " will get in touch with you asap."
#~ msgstr "" #~ msgstr ""
#~ "Unsere VMs werden in der Schweiz im Kanton Glarus gehostet und befinden " #~ "Unsere VMs werden in der Schweiz im Kanton Glarus gehostet und befinden sich"
#~ "sich zur Zeit noch in der BETA-Phase. Möchtest du mehr über uns erfahren " #~ " zur Zeit noch in der BETA-Phase. Möchtest du mehr über uns erfahren und "
#~ "und hast auf unserer Website nicht genügend Informationen gefunden? " #~ "hast auf unserer Website nicht genügend Informationen gefunden? Möchtest "
#~ "Möchtest eine detailliertere Bestellung aufgeben? Bist du auf technische " #~ "eine detailliertere Bestellung aufgeben? Bist du auf technische Probleme "
#~ "Probleme gestossen, die du uns mitteilen möchtest? Dann zögere nicht und " #~ "gestossen, die du uns mitteilen möchtest? Dann zögere nicht und kontaktiere "
#~ "kontaktiere uns unter support@datacenterlight.ch. Unser Team wird sich " #~ "uns unter support@datacenterlight.ch. Unser Team wird sich umgehend um dein "
#~ "umgehend um dein Anliegen kümmern!" #~ "Anliegen kümmern!"
#~ msgid "is not a proper name" #~ msgid "is not a proper name"
#~ msgstr "ist kein gültiger Name" #~ msgstr "ist kein gültiger Name"
@ -724,14 +610,12 @@ msgstr ""
#~ "\n" #~ "\n"
#~ "Hi,\n" #~ "Hi,\n"
#~ "\n" #~ "\n"
#~ "You can activate your %(dcl_text)s account by clicking here %(base_url)s" #~ "You can activate your %(dcl_text)s account by clicking here %(base_url)s%(activation_link)s\n"
#~ "%(activation_link)s\n"
#~ msgstr "" #~ msgstr ""
#~ "\n" #~ "\n"
#~ "Hallo,\n" #~ "Hallo,\n"
#~ "\n" #~ "\n"
#~ "Du kannst deinen %(dcl_text)s Account aktivieren, indem du hier klickst " #~ "Du kannst deinen %(dcl_text)s Account aktivieren, indem du hier klickst %(base_url)s%(activation_link)s\n"
#~ "%(base_url)s%(activation_link)s\n"
#~ msgid "Your" #~ msgid "Your"
#~ msgstr "Dein" #~ msgstr "Dein"
@ -766,14 +650,12 @@ msgstr ""
#~ msgid "I want to have it!" #~ msgid "I want to have it!"
#~ msgstr "Das möchte ich haben!" #~ msgstr "Das möchte ich haben!"
#~ msgid "" #~ msgid "Reuse existing factory halls intead of building an expensive building."
#~ "Reuse existing factory halls intead of building an expensive building."
#~ msgstr "" #~ msgstr ""
#~ "Nachhaltigkeit: Wiederverwendung ehemaliger Fabrikhallen an Stelle der " #~ "Nachhaltigkeit: Wiederverwendung ehemaliger Fabrikhallen an Stelle der "
#~ "Errichtung eines neuen Gebäudes" #~ "Errichtung eines neuen Gebäudes"
#~ msgid "" #~ msgid "Being creative, using modern and alternative design for a datacenter."
#~ "Being creative, using modern and alternative design for a datacenter."
#~ msgstr "" #~ msgstr ""
#~ "Kreativität: Verwendung eines modernen und alternativen Designs für unser " #~ "Kreativität: Verwendung eines modernen und alternativen Designs für unser "
#~ "Datencenter" #~ "Datencenter"
@ -796,8 +678,8 @@ msgstr ""
#~ msgstr "Standort des Datacenters ist in der Schweiz" #~ msgstr "Standort des Datacenters ist in der Schweiz"
#~ msgid "" #~ msgid ""
#~ " WARNING: We are currently running in BETA mode. We hope you won't " #~ " WARNING: We are currently running in BETA mode. We hope you won't encounter"
#~ "encounter any hiccups, but if you do, please let us know at " #~ " any hiccups, but if you do, please let us know at "
#~ "support@datacenterlight.ch" #~ "support@datacenterlight.ch"
#~ msgstr "" #~ msgstr ""
#~ " Achtung: Wir befinden uns zurzeit im Beta-Release. Wir hoffen, dass Sie " #~ " Achtung: Wir befinden uns zurzeit im Beta-Release. Wir hoffen, dass Sie "
@ -811,8 +693,8 @@ msgstr ""
#~ msgstr "Unser Versprechen" #~ msgstr "Unser Versprechen"
#~ msgid "" #~ msgid ""
#~ "Instead of creating an expensive SLA for availability, we promise that we " #~ "Instead of creating an expensive SLA for availability, we promise that we do"
#~ "do our best to run things as smooth as possible." #~ " our best to run things as smooth as possible."
#~ msgstr "" #~ msgstr ""
#~ "Anstatt eines SLAs (Service Levle Agreements) zu vereinbaren,setzen wir " #~ "Anstatt eines SLAs (Service Levle Agreements) zu vereinbaren,setzen wir "
#~ "unsere persönliche Arbeitskraft ein, um Ihnen ein sorgenfreiesHosting zu " #~ "unsere persönliche Arbeitskraft ein, um Ihnen ein sorgenfreiesHosting zu "

View file

@ -1,36 +0,0 @@
from django.core.management.base import BaseCommand
from datacenterlight.models import VMPricing
class Command(BaseCommand):
help = '''Creates default VMPricing object'''
DEFAULT_VMPRICING_NAME = 'default'
def handle(self, *args, **options):
self.create_default_vm_pricing()
def create_default_vm_pricing(self):
obj, created = VMPricing.objects.get_or_create(
name=self.DEFAULT_VMPRICING_NAME,
defaults={
"vat_inclusive": True,
"cores_unit_price": 5,
"ram_unit_price": 2,
"ssd_unit_price": 0.6,
"hdd_unit_price": 0.01
}
)
if created:
print(
'Successfully created {} VMPricing object'.format(
self.DEFAULT_VMPRICING_NAME
)
)
else:
print(
'{} VMPricing exists already.'.format(
self.DEFAULT_VMPRICING_NAME
)
)

View file

@ -10,28 +10,16 @@ class Command(BaseCommand):
help = '''Fetches the VM templates from OpenNebula and populates the dcl help = '''Fetches the VM templates from OpenNebula and populates the dcl
VMTemplate model''' VMTemplate model'''
def get_templates(self, manager, prefix):
templates = manager.get_templates('%s-' % prefix)
dcl_vm_templates = []
for template in templates:
template_name = template.name.lstrip('%s-' % prefix)
template_id = template.id
dcl_vm_template = VMTemplate.create(
template_name, template_id, prefix
)
dcl_vm_templates.append(dcl_vm_template)
return dcl_vm_templates
def handle(self, *args, **options): def handle(self, *args, **options):
try: try:
manager = OpenNebulaManager() manager = OpenNebulaManager()
templates = manager.get_templates()
dcl_vm_templates = [] dcl_vm_templates = []
dcl_vm_templates.extend( for template in templates:
self.get_templates(manager, VMTemplate.PUBLIC) template_name = template.name.lstrip('public-')
) template_id = template.id
dcl_vm_templates.extend( dcl_vm_template = VMTemplate.create(template_name, template_id)
self.get_templates(manager, VMTemplate.IPV6) dcl_vm_templates.append(dcl_vm_template)
)
old_vm_templates = VMTemplate.objects.all() old_vm_templates = VMTemplate.objects.all()
old_vm_templates.delete() old_vm_templates.delete()

View file

@ -8,6 +8,8 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
replaces = [('datacenterlight', '0018_auto_20180403_1930'), ('datacenterlight', '0019_auto_20180403_2054')]
dependencies = [ dependencies = [
('datacenterlight', '0017_auto_20180329_0056'), ('datacenterlight', '0017_auto_20180329_0056'),
('sites', '0002_alter_domain_unique'), ('sites', '0002_alter_domain_unique'),

View file

@ -1,45 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2018-04-15 22:36
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cms', '0014_auto_20160404_1908'),
('datacenterlight', '0018_auto_20180403_1930'),
]
operations = [
migrations.CreateModel(
name='DCLCustomPricingModel',
fields=[
('cmsplugin_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='cms.CMSPlugin')),
],
options={
'abstract': False,
},
bases=('cms.cmsplugin',),
),
migrations.CreateModel(
name='VMPricing',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, unique=True)),
('vat_inclusive', models.BooleanField(default=True)),
('vat_percentage', models.DecimalField(blank=True, decimal_places=5, default=0, max_digits=7)),
('cores_unit_price', models.DecimalField(decimal_places=5, default=0, max_digits=7)),
('ram_unit_price', models.DecimalField(decimal_places=5, default=0, max_digits=7)),
('ssd_unit_price', models.DecimalField(decimal_places=5, default=0, max_digits=7)),
('hdd_unit_price', models.DecimalField(decimal_places=6, default=0, max_digits=7)),
],
),
migrations.AddField(
model_name='dclcustompricingmodel',
name='pricing',
field=models.ForeignKey(help_text='Choose a pricing that will be associated with this Calculator', on_delete=django.db.models.deletion.CASCADE, related_name='dcl_custom_pricing_vm_pricing', to='datacenterlight.VMPricing'),
),
]

View file

@ -1,29 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2018-04-12 03:16
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import filer.fields.file
class Migration(migrations.Migration):
dependencies = [
('datacenterlight', '0018_auto_20180403_1930'),
]
operations = [
migrations.CreateModel(
name='CMSFaviconExtension',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('extended_object', models.OneToOneField(editable=False, on_delete=django.db.models.deletion.CASCADE, to='cms.Page')),
('favicon', filer.fields.file.FilerFileField(on_delete=django.db.models.deletion.CASCADE, related_name='cms_favicon_image', to='filer.File')),
('public_extension', models.OneToOneField(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='draft_extension', to='datacenterlight.CMSFaviconExtension')),
],
options={
'abstract': False,
},
),
]

View file

@ -1,16 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2018-04-20 15:04
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('datacenterlight', '0019_auto_20180415_2236'),
('datacenterlight', '0019_cmsfaviconextension'),
]
operations = [
]

View file

@ -1,28 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2018-04-25 09:20
from __future__ import unicode_literals
import cms.models.fields
from django.db import migrations
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('datacenterlight', '0020_merge'),
('cms', '0014_auto_20160404_1908'),
]
operations = [
migrations.AddField(
model_name='cmsintegration',
name='calculator_placeholder',
field=cms.models.fields.PlaceholderField(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE,
related_name='dcl-calculator-placeholder+', slotname='datacenterlight_calculator', to='cms.Placeholder'),
),
migrations.RenameModel(
old_name='DCLCustomPricingModel',
new_name='DCLCalculatorPluginModel',
),
]

View file

@ -1,26 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2018-05-07 02:19
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('datacenterlight', '0021_cmsintegration_calculator_placeholder'),
]
operations = [
migrations.AddField(
model_name='vmpricing',
name='discount_amount',
field=models.DecimalField(
decimal_places=2, default=0, max_digits=6),
),
migrations.AddField(
model_name='vmpricing',
name='discount_name',
field=models.CharField(blank=True, max_length=255, null=True),
),
]

View file

@ -1,25 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2018-05-23 22:19
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('datacenterlight', '0022_auto_20180506_1950'),
]
operations = [
migrations.AddField(
model_name='dclcalculatorpluginmodel',
name='vm_type',
field=models.CharField(choices=[('public', 'Public'), ('ipv6only', 'Ipv6Only')], default='public', max_length=50),
),
migrations.AddField(
model_name='vmtemplate',
name='vm_type',
field=models.CharField(choices=[('public', 'Public'), ('ipv6only', 'Ipv6Only')], default='public', max_length=50),
),
]

View file

@ -1,21 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2018-06-24 08:23
from __future__ import unicode_literals
import datacenterlight.cms_models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('datacenterlight', '0023_auto_20180524_0349'),
]
operations = [
migrations.AddField(
model_name='dclcalculatorpluginmodel',
name='vm_templates_to_show',
field=datacenterlight.cms_models.MultipleChoiceArrayField(base_field=models.CharField(blank=True, max_length=256), blank=True, default=list, help_text='Recommended: If you wish to show all templates of the corresponding VM Type (public/ipv6only), please do not select any of the items in the above field. This will allow any new template(s) added in the backend to be automatically listed in this calculator instance.', size=None),
),
]

View file

@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2018-09-25 20:27
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('datacenterlight', '0024_dclcalculatorpluginmodel_vm_templates_to_show'),
]
operations = [
migrations.AddField(
model_name='dclnavbarpluginmodel',
name='show_login_option',
field=models.BooleanField(default=True, help_text='Uncheck this if you do not want to show login/dashboard.'),
),
]

View file

@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2018-09-27 20:32
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('datacenterlight', '0025_dclnavbarpluginmodel_show_login_option'),
]
operations = [
migrations.AddField(
model_name='dclcalculatorpluginmodel',
name='default_selected_template',
field=models.CharField(default='Devuan Ascii', help_text='Write the name of the template that you need selected as default when the calculator loads', max_length=128, null=True),
),
]

View file

@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2018-09-29 05:36
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('datacenterlight', '0026_dclcalculatorpluginmodel_default_selected_template'),
]
operations = [
migrations.AddField(
model_name='dclcalculatorpluginmodel',
name='enable_512mb_ram',
field=models.BooleanField(default=False),
),
]

View file

@ -1,103 +1,17 @@
import logging
from django.db import models from django.db import models
logger = logging.getLogger(__name__)
class VMTemplate(models.Model): class VMTemplate(models.Model):
PUBLIC = 'public'
IPV6 = 'ipv6only'
VM_TYPE_CHOICES = (
(PUBLIC, PUBLIC.title()),
(IPV6, IPV6.title()),
)
name = models.CharField(max_length=50) name = models.CharField(max_length=50)
opennebula_vm_template_id = models.IntegerField() opennebula_vm_template_id = models.IntegerField()
vm_type = models.CharField(
max_length=50, choices=VM_TYPE_CHOICES, default=PUBLIC
)
def __str__(self):
return '%s - %s - %s' % (
self.opennebula_vm_template_id, self.vm_type, self.name
)
@classmethod @classmethod
def create(cls, name, opennebula_vm_template_id, vm_type): def create(cls, name, opennebula_vm_template_id):
vm_template = cls( vm_template = cls(
name=name, opennebula_vm_template_id=opennebula_vm_template_id, name=name, opennebula_vm_template_id=opennebula_vm_template_id)
vm_type=vm_type
)
return vm_template return vm_template
class VMPricing(models.Model):
name = models.CharField(max_length=255, unique=True)
vat_inclusive = models.BooleanField(default=True)
vat_percentage = models.DecimalField(
max_digits=7, decimal_places=5, blank=True, default=0
)
cores_unit_price = models.DecimalField(
max_digits=7, decimal_places=5, default=0
)
ram_unit_price = models.DecimalField(
max_digits=7, decimal_places=5, default=0
)
ssd_unit_price = models.DecimalField(
max_digits=7, decimal_places=5, default=0
)
hdd_unit_price = models.DecimalField(
max_digits=7, decimal_places=6, default=0
)
discount_name = models.CharField(max_length=255, null=True, blank=True)
discount_amount = models.DecimalField(
max_digits=6, decimal_places=2, default=0
)
def __str__(self):
display_str = self.name + ' => ' + ' - '.join([
'{}/Core'.format(self.cores_unit_price.normalize()),
'{}/GB RAM'.format(self.ram_unit_price.normalize()),
'{}/GB SSD'.format(self.ssd_unit_price.normalize()),
'{}/GB HDD'.format(self.hdd_unit_price.normalize()),
'{}% VAT'.format(self.vat_percentage.normalize())
if not self.vat_inclusive else 'VAT-Incl',
])
if self.discount_amount:
display_str = ' - '.join([
display_str,
'{} {}'.format(
self.discount_amount,
self.discount_name if self.discount_name else 'Discount'
)
])
return display_str
@classmethod
def get_vm_pricing_by_name(cls, name):
try:
pricing = VMPricing.objects.get(name=name)
except Exception as e:
logger.error(
"Error getting VMPricing with name {name}. "
"Details: {details}. Attempting to return default"
"pricing.".format(name=name, details=str(e))
)
pricing = VMPricing.get_default_pricing()
return pricing
@classmethod
def get_default_pricing(cls):
""" Returns the default pricing or None """
try:
default_pricing = VMPricing.objects.get(name='default')
except Exception as e:
logger.error(str(e))
default_pricing = None
return default_pricing
class StripePlan(models.Model): class StripePlan(models.Model):
""" """
A model to store Data Center Light's created Stripe plans A model to store Data Center Light's created Stripe plans

View file

@ -74,10 +74,6 @@ a.list-group-item-danger.active:focus {
padding: 10px; padding: 10px;
} }
.navbar-brand > img {
height: 100%;
}
#logoWhite, #logoWhite,
.navbar-transparent #logoBlack { .navbar-transparent #logoBlack {
display: none; display: none;
@ -86,6 +82,7 @@ a.list-group-item-danger.active:focus {
#logoBlack, #logoBlack,
.navbar-transparent #logoWhite { .navbar-transparent #logoWhite {
display: block; display: block;
width: 220px;
} }
@media (min-width: 768px) { @media (min-width: 768px) {
@ -150,39 +147,3 @@ footer .dcl-link-separator::before {
border-radius: 100%; border-radius: 100%;
background: #777; background: #777;
} }
.mb-0 {
margin-bottom: 0;
}
.thin-hr {
margin-top: 10px;
margin-bottom: 10px;
}
.payment-container .credit-card-info {
padding-bottom: 15px;
border-bottom: 1px solid #eee;
}
.credit-card-info {
display: flex;
}
.credit-card-info .align-bottom {
align-self: flex-end;
padding-right: 0 !important;
}
.new-card-head {
margin-top: 10px;
}
.new-card-button-margin button{
margin-top: 5px;
margin-bottom: 5px;
}
.input-no-border {
border: none !important;
background: transparent !important;
resize: none;
}

View file

@ -55,7 +55,7 @@
flex: 1; flex: 1;
} }
.header_slider > .carousel .item .container-fluid { .header_slider > .carousel .item .container {
overflow: auto; overflow: auto;
padding: 50px 20px 60px; padding: 50px 20px 60px;
height: 100%; height: 100%;
@ -104,9 +104,9 @@
.header_slider .carousel-control .fa { .header_slider .carousel-control .fa {
font-size: 4em; font-size: 4em;
} }
.header_slider > .carousel .item .container-fluid { .header_slider > .carousel .item .container {
overflow: auto; overflow: auto;
padding: 75px; padding: 75px 50px;
} }
.header_slider .btn-trans { .header_slider .btn-trans {
padding: 8px 15px; padding: 8px 15px;

View file

@ -482,7 +482,6 @@
margin: 100px auto 40px; margin: 100px auto 40px;
border: 1px solid #ccc; border: 1px solid #ccc;
padding: 30px 30px 20px; padding: 30px 30px 20px;
color: #595959;
} }
.order-detail-container .dashboard-title-thin { .order-detail-container .dashboard-title-thin {
@ -504,6 +503,10 @@
margin-bottom: 15px; margin-bottom: 15px;
} }
.order-detail-container .order-details strong {
color: #595959;
}
.order-detail-container h4 { .order-detail-container h4 {
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;
@ -512,28 +515,13 @@
.order-detail-container p { .order-detail-container p {
margin-bottom: 5px; margin-bottom: 5px;
color: #595959;
} }
.order-detail-container hr { .order-detail-container hr {
margin: 15px 0; margin: 15px 0;
} }
.order-detail-container .thin-hr {
margin: 10px 0;
}
.order-detail-container .subtotal-price {
font-size: 16px;
}
.order-detail-container .subtotal-price .text-primary {
font-size: 17px;
}
.order-detail-container .total-price {
font-size: 18px;
}
@media (max-width: 767px) { @media (max-width: 767px) {
.order-detail-container { .order-detail-container {
padding: 15px; padding: 15px;

View file

@ -89,6 +89,10 @@ textarea {
border-radius: 6px; border-radius: 6px;
} }
.navbar-brand > img {
max-height: 30px;
}
@media (max-width: 767px) { @media (max-width: 767px) {
.navbar-default .navbar-nav>li>a{ .navbar-default .navbar-nav>li>a{
font-weight: 400; font-weight: 400;
@ -776,7 +780,7 @@ textarea {
width: 100%; width: 100%;
margin: 0 auto; margin: 0 auto;
background: #fff; background: #fff;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1), 0 0 6px rgba(0, 0, 0, 0.15); box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23);
padding-bottom: 40px; padding-bottom: 40px;
border-radius: 7px; border-radius: 7px;
text-align: center; text-align: center;
@ -929,7 +933,7 @@ textarea {
} }
@media(max-width:767px) { @media(max-width:991px) {
.section-sm-center .split-text, .section-sm-center .split-text,
.section-sm-center .space { .section-sm-center .space {
text-align: center !important; text-align: center !important;
@ -1231,15 +1235,6 @@ footer {
background-position: center; background-position: center;
} }
.promo-section.promo-with-bg a {
color: #87B6EA;
}
.promo-section.promo-with-bg a:hover,
.promo-section.promo-with-bg a:focus {
color: #77a6da;
}
.promo-section h3 { .promo-section h3 {
font-weight: 700; font-weight: 700;
font-size: 36px; font-size: 36px;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5 KiB

Before After
Before After

View file

@ -5,10 +5,6 @@
/* --------------------------------------------- /* ---------------------------------------------
Scripts initialization Scripts initialization
--------------------------------------------- */ --------------------------------------------- */
var minRam = 1;
if(window.minRam){
minRam = window.minRam;
}
var cardPricing = { var cardPricing = {
'cpu': { 'cpu': {
'id': 'coreValue', 'id': 'coreValue',
@ -20,7 +16,7 @@
'ram': { 'ram': {
'id': 'ramValue', 'id': 'ramValue',
'value': 2, 'value': 2,
'min': minRam, 'min': 1,
'max': 200, 'max': 200,
'interval': 1 'interval': 1
}, },
@ -44,7 +40,6 @@
_initNavUrl(); _initNavUrl();
_initPricing(); _initPricing();
ajaxForms(); ajaxForms();
$('#ramValue').data('old-value', $('#ramValue').val());
}); });
$(window).resize(function() { $(window).resize(function() {
@ -149,54 +144,21 @@
var data = $(this).data('minus'); var data = $(this).data('minus');
if (cardPricing[data].value > cardPricing[data].min) { if (cardPricing[data].value > cardPricing[data].min) {
if(data === 'ram' && String(cardPricing[data].value) === "1" && minRam === 0.5){
cardPricing[data].value = 0.5;
$('#ramValue').val('0.5');
$("#ramValue").attr('step', 0.5);
} else {
cardPricing[data].value = Number(cardPricing[data].value) - cardPricing[data].interval; cardPricing[data].value = Number(cardPricing[data].value) - cardPricing[data].interval;
} }
}
_fetchPricing(); _fetchPricing();
$('#ramValue').data('old-value', $('#ramValue').val());
}); });
$('.fa-plus-circle.right').click(function(event) { $('.fa-plus-circle.right').click(function(event) {
var data = $(this).data('plus'); var data = $(this).data('plus');
if (cardPricing[data].value < cardPricing[data].max) { if (cardPricing[data].value < cardPricing[data].max) {
if(data === 'ram' && String(cardPricing[data].value) === "0.5" && minRam === 0.5){
cardPricing[data].value = 1;
$('#ramValue').val('1');
$("#ramValue").attr('step', 1);
} else {
cardPricing[data].value = Number(cardPricing[data].value) + cardPricing[data].interval; cardPricing[data].value = Number(cardPricing[data].value) + cardPricing[data].interval;
} }
}
_fetchPricing(); _fetchPricing();
$('#ramValue').data('old-value', $('#ramValue').val());
}); });
$('.input-price').change(function() { $('.input-price').change(function() {
var data = $(this).attr("name"); var data = $(this).attr("name");
var input = $('input[name=' + data + ']'); cardPricing[data].value = $('input[name=' + data + ']').val();
var inputValue = input.val();
if(data === 'ram') {
var ramInput = $('#ramValue');
if ($('#ramValue').data('old-value') < $('#ramValue').val()) {
if($('#ramValue').val() === '1' && minRam === 0.5) {
$("#ramValue").attr('step', 1);
$('#ramValue').val('1');
}
} else {
if($('#ramValue').val() === '0' && minRam === 0.5) {
$("#ramValue").attr('step', 0.5);
$('#ramValue').val('0.5');
}
}
inputValue = $('#ramValue').val();
$('#ramValue').data('old-value', $('#ramValue').val());
}
cardPricing[data].value = inputValue;
_fetchPricing(); _fetchPricing();
}); });
} }
@ -209,22 +171,7 @@
} }
function _calcPricing() { function _calcPricing() {
if(typeof window.coresUnitPrice === 'undefined'){ var total = (cardPricing['cpu'].value * 5) + (2 * cardPricing['ram'].value) + (0.6 * cardPricing['storage'].value);
window.coresUnitPrice = 5;
}
if(typeof window.ramUnitPrice === 'undefined'){
window.ramUnitPrice = 2;
}
if(typeof window.ssdUnitPrice === 'undefined'){
window.ssdUnitPrice = 0.6;
}
if(typeof window.discountAmount === 'undefined'){
window.discountAmount = 0;
}
var total = (cardPricing['cpu'].value * window.coresUnitPrice) +
(cardPricing['ram'].value * window.ramUnitPrice) +
(cardPricing['storage'].value * window.ssdUnitPrice) -
window.discountAmount;
total = parseFloat(total.toFixed(2)); total = parseFloat(total.toFixed(2));
$("#total").text(total); $("#total").text(total);
} }

View file

@ -1,26 +1,23 @@
from datetime import datetime from datetime import datetime
from celery import current_task
from celery.exceptions import MaxRetriesExceededError from celery.exceptions import MaxRetriesExceededError
from celery.utils.log import get_task_logger from celery.utils.log import get_task_logger
from celery import current_task
from django.conf import settings from django.conf import settings
from django.core.mail import EmailMessage from django.core.mail import EmailMessage
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.utils import translation from django.utils import translation
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from time import sleep
from dynamicweb.celery import app from dynamicweb.celery import app
from hosting.models import HostingOrder from hosting.models import HostingOrder, HostingBill
from membership.models import 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 ( from utils.hosting_utils import get_all_public_keys, get_or_create_vm_detail
get_all_public_keys, get_or_create_vm_detail, ping_ok from utils.forms import UserBillingAddressForm
)
from utils.mailer import BaseEmail from utils.mailer import BaseEmail
from utils.stripe_utils import StripeUtils from utils.models import BillingAddress
from .models import VMPricing
logger = get_task_logger(__name__) logger = get_task_logger(__name__)
@ -52,15 +49,23 @@ def retry_task(task, exception=None):
@app.task(bind=True, max_retries=settings.CELERY_MAX_RETRIES) @app.task(bind=True, max_retries=settings.CELERY_MAX_RETRIES)
def create_vm_task(self, vm_template_id, user, specs, template, order_id): def create_vm_task(self, vm_template_id, user, specs, template,
stripe_customer_id, billing_address_data,
stripe_subscription_id, cc_details):
logger.debug( logger.debug(
"Running create_vm_task on {}".format(current_task.request.hostname)) "Running create_vm_task on {}".format(current_task.request.hostname))
vm_id = None vm_id = None
try: try:
final_price = ( final_price = specs.get('price')
specs.get('total_price') if 'total_price' in specs billing_address = BillingAddress(
else specs.get('price') cardholder_name=billing_address_data['cardholder_name'],
street_address=billing_address_data['street_address'],
city=billing_address_data['city'],
postal_code=billing_address_data['postal_code'],
country=billing_address_data['country']
) )
billing_address.save()
customer = StripeCustomer.objects.filter(id=stripe_customer_id).first()
if 'pass' in user: if 'pass' in user:
on_user = user.get('email') on_user = user.get('email')
@ -89,43 +94,33 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id):
if vm_id is None: if vm_id is None:
raise Exception("Could not create VM") raise Exception("Could not create VM")
# Update HostingOrder with the created vm_id # Create a Hosting Order
hosting_order = HostingOrder.objects.filter(id=order_id).first() order = HostingOrder.create(
error_msg = None price=final_price,
vm_id=vm_id,
try: customer=customer,
hosting_order.vm_id = vm_id billing_address=billing_address
hosting_order.save()
logger.debug(
"Updated hosting_order {} with vm_id={}".format(
hosting_order.id, vm_id
)
)
except Exception as ex:
error_msg = (
"HostingOrder with id {order_id} not found. This means that "
"the hosting order was not created and/or it is/was not "
"associated with VM with id {vm_id}. Details {details}".format(
order_id=order_id, vm_id=vm_id, details=str(ex)
)
)
logger.error(error_msg)
stripe_utils = StripeUtils()
result = stripe_utils.set_subscription_metadata(
subscription_id=hosting_order.subscription_id,
metadata={"VM_ID": str(vm_id)}
) )
if result.get('error') is not None: # Create a Hosting Bill
emsg = "Could not update subscription metadata for {sub}".format( HostingBill.create(
sub=hosting_order.subscription_id customer=customer, billing_address=billing_address)
)
logger.error(emsg) # Create Billing Address for User if he does not have one
if error_msg: if not customer.user.billing_addresses.count():
error_msg += ". " + emsg billing_address_data.update({
else: 'user': customer.user.id
error_msg = emsg })
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 subscription
order.set_subscription_id(stripe_subscription_id, cc_details)
# If the Stripe payment succeeds, set order status approved
order.set_approved()
vm = VirtualMachineSerializer(manager.get_vm(vm_id)).data vm = VirtualMachineSerializer(manager.get_vm(vm_id)).data
@ -135,19 +130,12 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id):
'cores': specs.get('cpu'), 'cores': specs.get('cpu'),
'memory': specs.get('memory'), 'memory': specs.get('memory'),
'storage': specs.get('disk_size'), 'storage': specs.get('disk_size'),
'price': final_price, 'price': specs.get('price'),
'template': template.get('name'), 'template': template.get('name'),
'vm_name': vm.get('name'), 'vm_name': vm.get('name'),
'vm_id': vm['vm_id'], 'vm_id': vm['vm_id'],
'order_id': order_id 'order_id': order.id
} }
if error_msg:
context['errors'] = error_msg
if 'pricing_name' in specs:
context['pricing'] = str(VMPricing.get_vm_pricing_by_name(
name=specs['pricing_name']
))
email_data = { email_data = {
'subject': settings.DCL_TEXT + " Order from %s" % context['email'], 'subject': settings.DCL_TEXT + " Order from %s" % context['email'],
'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS,
@ -171,7 +159,7 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id):
'base_url': "{0}://{1}".format(user.get('request_scheme'), 'base_url': "{0}://{1}".format(user.get('request_scheme'),
user.get('request_host')), user.get('request_host')),
'order_url': reverse('hosting:orders', 'order_url': reverse('hosting:orders',
kwargs={'pk': order_id}), kwargs={'pk': order.id}),
'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')},
@ -188,11 +176,11 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id):
email = BaseEmail(**email_data) email = BaseEmail(**email_data)
email.send() email.send()
# try to see if we have the IPv6 of the new vm and that if the ssh # try to see if we have the IP and that if the ssh keys can
# keys can be configured # be configured
vm_ipv6 = manager.get_ipv6(vm_id) new_host = manager.get_primary_ipv4(vm_id)
logger.debug("New VM ID is {vm_id}".format(vm_id=vm_id)) logger.debug("New VM ID is {vm_id}".format(vm_id=vm_id))
if vm_ipv6 is not None: if 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) get_or_create_vm_detail(custom_user, manager, vm_id)
if custom_user is not None: if custom_user is not None:
@ -203,48 +191,13 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id):
logger.debug( logger.debug(
"Calling configure on {host} for " "Calling configure on {host} for "
"{num_keys} keys".format( "{num_keys} keys".format(
host=vm_ipv6, num_keys=len(keys) host=new_host, num_keys=len(keys)))
) # Let's delay the task by 75 seconds to be sure
) # that we run the cdist configure after the host
# Let's wait until the IP responds to ping before we # is up
# run the cdist configure on the host manager.manage_public_key(keys,
did_manage_public_key = False hosts=[new_host],
for i in range(0, 15): countdown=75)
if ping_ok(vm_ipv6):
logger.debug(
"{} is pingable. Doing a "
"manage_public_key".format(vm_ipv6)
)
sleep(10)
manager.manage_public_key(
keys, hosts=[vm_ipv6]
)
did_manage_public_key = True
break
else:
logger.debug(
"Can't ping {}. Wait 5 secs".format(
vm_ipv6
)
)
sleep(5)
if not did_manage_public_key:
emsg = ("Waited for over 75 seconds for {} to be "
"pingable. But the VM was not reachable. "
"So, gave up manage_public_key. Please do "
"this manually".format(vm_ipv6))
logger.error(emsg)
email_data = {
'subject': '{} CELERY TASK INCOMPLETE: {} not '
'pingable for 75 seconds'.format(
settings.DCL_TEXT, vm_ipv6
),
'from_email': current_task.request.hostname,
'to': settings.DCL_ERROR_EMAILS_TO_LIST,
'body': emsg
}
email = EmailMessage(**email_data)
email.send()
except Exception as e: except Exception as e:
logger.error(str(e)) logger.error(str(e))
try: try:

View file

@ -8,9 +8,9 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Data Center Light by ungleich">
<meta name="author" content="ungleich glarus ag"> <meta name="author" content="ungleich glarus ag">
<meta name="description" content="{% page_attribute 'meta_description' %}"> <title>{% page_attribute page_title %}</title>
<title>{% page_attribute "page_title" %}</title>
<!-- Vendor CSS --> <!-- Vendor CSS -->
<!-- Bootstrap Core CSS --> <!-- Bootstrap Core CSS -->
@ -30,11 +30,7 @@
<!-- External Fonts --> <!-- External Fonts -->
<link href="//fonts.googleapis.com/css?family=Lato:300,400,600,700" rel="stylesheet" type="text/css"> <link href="//fonts.googleapis.com/css?family=Lato:300,400,600,700" rel="stylesheet" type="text/css">
{% if request.current_page.cmsfaviconextension %}
<link rel="shortcut icon" href="{% static request.current_page.cmsfaviconextension.favicon.url %}" type="image/x-icon">
{% else %}
<link rel="shortcut icon" href="{% static 'datacenterlight/img/favicon.ico' %}" type="image/x-icon"> <link rel="shortcut icon" href="{% static 'datacenterlight/img/favicon.ico' %}" type="image/x-icon">
{% endif %}
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries --> <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// --> <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
@ -56,12 +52,11 @@
{% placeholder 'Datacenterlight Header' or %} {% placeholder 'Datacenterlight Header' or %}
<div class="dcl-header"> <div class="dcl-header">
<div class="container"> <div class="container">
<h1>{% page_attribute "page_title" %}</h1> <h1>{% page_attribute page_title %}</h1>
</div> </div>
</div> </div>
{% endplaceholder %} {% endplaceholder %}
{% url 'datacenterlight:index' as calculator_form_url %}
{% placeholder 'Datacenterlight Content' %} {% placeholder 'Datacenterlight Content' %}
{% placeholder 'datacenterlight_footer'%} {% placeholder 'datacenterlight_footer'%}

View file

@ -1,5 +1,16 @@
<div class="price-calc-section"> <div class="split-section {{ instance.get_extra_classes }}" id="{{ instance.html_id }}">
<div class="container">
<div class="row">
<div class="col-sm-6 {% if instance.text_direction == 'right' %}col-sm-push-6{% endif %} split-text">
{% include "datacenterlight/cms/includes/_section_split_content.html" %}
</div>
<div class="col-sm-6 {% if instance.text_direction == 'right' %}col-sm-pull-6{% endif %}">
<div class="price-calc-section">
<div class="card"> <div class="card">
{% include "datacenterlight/includes/_calculator_form.html" with vm_pricing=instance.pricing %} {% include "datacenterlight/includes/_calculator_form.html" %}
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>

View file

@ -35,7 +35,6 @@
{% endif %} {% endif %}
</li> </li>
{% endif %} {% endif %}
{% if instance.show_login_option %}
{% if not request.user.is_authenticated %} {% if not request.user.is_authenticated %}
<li> <li>
<a href="{% url 'hosting:login' %}">{% trans "Login" %}&nbsp;&nbsp;<span class="fa fa-sign-in"></span></a> <a href="{% url 'hosting:login' %}">{% trans "Login" %}&nbsp;&nbsp;<span class="fa fa-sign-in"></span></a>
@ -45,7 +44,6 @@
<a href="{% url 'hosting:dashboard' %}">{% trans "Dashboard" %}</a> <a href="{% url 'hosting:dashboard' %}">{% trans "Dashboard" %}</a>
</li> </li>
{% endif %} {% endif %}
{% endif %}
{% comment %} {% comment %}
<!-- to be used when more than one option for language --> <!-- to be used when more than one option for language -->
<li class="nav-language"> <li class="nav-language">

View file

@ -1,7 +1,7 @@
{% load cms_tags %} {% load cms_tags %}
<div class="dropdown highlights-dropdown"> <div class="dropdown highlights-dropdown">
<a class="dropdown-toggle url-init dcl-link" href="{{ instance.target|default:'#' }}" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ instance.text }}&nbsp;<span class="caret"></span></a> <a class="dropdown-toggle url-init dcl-link" href="{{ instance.url|default:'#' }}" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ instance.text }}&nbsp;<span class="caret"></span></a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
{% for plugin in instance.child_plugin_instances %} {% for plugin in instance.child_plugin_instances %}
{% render_plugin plugin %} {% render_plugin plugin %}

View file

@ -2,24 +2,17 @@
<section class="split-section {{ instance.get_extra_classes }}" id="{{ instance.html_id }}"> <section class="split-section {{ instance.get_extra_classes }}" id="{{ instance.html_id }}">
<div class="container"> <div class="container">
{% if children_to_side|length or children_calculator|length %} {% if children_to_side|length %}
<div class="row"> <div class="row">
<div class="col-sm-6 {% if instance.text_direction == 'right' %}col-sm-push-6{% endif %} split-text"> <div class="col-sm-6 {% if instance.text_direction == 'right' %}col-sm-push-6{% endif %} split-text">
{% include "datacenterlight/cms/includes/_section_split_content.html" %} {% include "datacenterlight/cms/includes/_section_split_content.html" %}
</div> </div>
<div class="col-sm-6 {% if instance.text_direction == 'right' %}col-sm-pull-6{% endif %} split-figure"> <div class="col-sm-6 {% if instance.text_direction == 'right' %}col-sm-pull-6{% endif %} split-figure">
{% if children_calculator|length %}
{% for plugin in children_calculator %}
{% render_plugin plugin %}
{% endfor %}
{% endif %}
{% if children_to_side %}
<div class="section-figure"> <div class="section-figure">
{% for plugin in children_to_side %} {% for plugin in children_to_side %}
{% render_plugin plugin %} {% render_plugin plugin %}
{% endfor %} {% endfor %}
</div> </div>
{% endif %}
</div> </div>
</div> </div>
{% else %} {% else %}

View file

@ -14,7 +14,7 @@
<table style="width: 100%; border-spacing: 0; border-collapse: collapse; max-width: 560px;"> <table style="width: 100%; border-spacing: 0; border-collapse: collapse; max-width: 560px;">
<tr> <tr>
<td> <td>
<img src="{{ base_url }}{% static 'datacenterlight/img/datacenterlight.png' %}" style="max-width: 200px;"> <img src="{{ base_url }}{% static 'datacenterlight/img/logo_black.png' %}" style="width: 200px; height: 50px;">
</td> </td>
</tr> </tr>
<tr> <tr>

View file

@ -14,7 +14,7 @@
<table style="width: 100%; border-spacing: 0; border-collapse: collapse; max-width: 560px;"> <table style="width: 100%; border-spacing: 0; border-collapse: collapse; max-width: 560px;">
<tr> <tr>
<td> <td>
<img src="{{ base_url }}{% static 'datacenterlight/img/datacenterlight.png' %}" style="max-width: 200px;"> <img src="{{ base_url }}{% static 'datacenterlight/img/logo_black.png' %}" style="width: 200px; height: 50px;">
</td> </td>
</tr> </tr>
<tr> <tr>

View file

@ -1,35 +1,14 @@
{% load staticfiles i18n%} {% load staticfiles i18n%}
<form id="order_form" method="POST" action="{% url 'datacenterlight:index' %}" data-toggle="validator" role="form">
{% if vm_pricing %}
<script type="application/javascript">
window.vat_inclusive = {% if vm_pricing.vat_inclusive %}true{% else %}false{% endif%};
window.vat_percentage = {{vm_pricing.vat_percentage|default:0}};
window.coresUnitPrice = {{vm_pricing.cores_unit_price|default:0}};
window.ramUnitPrice = {{vm_pricing.ram_unit_price|default:0}};
window.ssdUnitPrice = {{vm_pricing.ssd_unit_price|default:0}};
window.hddUnitPrice = {{vm_pricing.hdd_unit_price|default:0}};
window.discountAmount = {{vm_pricing.discount_amount|default:0}};
window.minRam = {{min_ram}};
window.minRamErr = '{% blocktrans with min_ram=min_ram %}Please enter a value in range {{min_ram}} - 200.{% endblocktrans %}';
</script>
{% endif %}
<form id="order_form" method="POST" action="{{calculator_form_url}}" data-toggle="validator" role="form">
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="pid" value="{{instance.id}}">
<div class="title"> <div class="title">
<h3>{% trans "VM hosting" %} </h3> <h3>{% trans "VM hosting" %} </h3>
</div> </div>
<div class="price"> <div class="price">
<span id="total"></span> <span id="total">15</span>
<span>CHF/{% trans "month" %}</span> <span>CHF/{% trans "month" %}</span>
<div class="price-text"> <div class="price-text">
<p> <p>{% trans "VAT included" %}</p>
{% if vm_pricing.vat_inclusive %}{% trans "VAT included" %} <br>{% endif %}
{% if vm_pricing.discount_amount %}
{% trans "You save" %} {{ vm_pricing.discount_amount }} CHF
{% endif %}
</p>
</div> </div>
</div> </div>
<div class="descriptions"> <div class="descriptions">
@ -57,8 +36,8 @@
<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-circle left" data-minus="ram" aria-hidden="true"></i>
<input id="ramValue" class="input-price select-number" type="number" min="{% if min_ram == 0.5 %}0{% else %}1{% endif %}" max="200" name="ram" <input id="ramValue" class="input-price select-number" type="number" min="1" max="200" name="ram"
data-error="{% blocktrans with min_ram=min_ram %}Please enter a value in range {{min_ram}} - 200.{% endblocktrans %}" required step="1"> data-error="{% trans 'Please enter a value in range 1 - 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-circle right" data-plus="ram" aria-hidden="true"></i>
</div> </div>
@ -94,12 +73,10 @@
<label for="config">OS</label> <label for="config">OS</label>
<select name="config"> <select name="config">
{% for template in templates %} {% for template in templates %}
<option value="{{template.opennebula_vm_template_id}}">{{template.name}}</option>
<option value="{{template.opennebula_vm_template_id}}" {% if template.name|lower == instance.default_selected_template|lower %}selected="selected"{% endif %}>{{template.name}}</option>
{% endfor %} {% endfor %}
</select> </select>
</div> </div>
</div> </div>
<input type="hidden" name="pricing_name" value="{% if vm_pricing.name %}{{vm_pricing.name}}{% else %}unknown{% endif%}"></input>
<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

@ -4,15 +4,15 @@
<div class="container"> <div class="container">
<ul class="list-inline"> <ul class="list-inline">
<li> <li>
<a class="url-init" href="https://{{MULTISITE_CMS_FALLBACK}}">{% trans "Home" %}</a> <a class="url-init" href="{% url 'datacenterlight:index' %}">{% trans "Home" %}</a>
</li> </li>
<li class="footer-menu-divider">&sdot;</li> <li class="footer-menu-divider">&sdot;</li>
<li> <li>
<a class="url-init" href="https://{{MULTISITE_CMS_FALLBACK}}#contact">{% trans "Contact" %}</a> <a class="url-init" href="{% url 'datacenterlight:index' %}#contact">{% trans "Contact" %}</a>
</li> </li>
<li class="footer-menu-divider">&sdot;</li> <li class="footer-menu-divider">&sdot;</li>
<li> <li>
<a class="url-init" href="https://{{MULTISITE_CMS_FALLBACK}}/cms/terms-of-service">{% trans "Terms of Service" %}</a> <a class="url-init" href="/cms/terms-of-service">{% trans "Terms of Service" %}</a>
</li> </li>
</ul> </ul>

View file

@ -67,18 +67,6 @@
</div> </div>
<div class="dcl-payment-box"> <div class="dcl-payment-box">
<div class="dcl-payment-section"> <div class="dcl-payment-section">
{% if generic_payment_form %}
<h3>{%trans "Make a payment" %}</h3>
<hr class="top-hr">
<form role="form" id="generic-payment-form" method="post" action="" novalidate>
{% csrf_token %}
<input type="hidden" name="product" value="1" />
{% for field in generic_payment_form %}
{% bootstrap_field field type='fields'%}
{% endfor %}
<p class="text-danger">{{generic_payment_form.non_field_errors|striptags}}</p>
</form>
{% else %}
<h3>{%trans "Your Order" %}</h3> <h3>{%trans "Your Order" %}</h3>
<hr class="top-hr"> <hr class="top-hr">
<div class="dcl-payment-order"> <div class="dcl-payment-order">
@ -90,79 +78,99 @@
<hr> <hr>
<p>{% trans "Configuration"%} <strong class="pull-right">{{request.session.template.name}}</strong></p> <p>{% trans "Configuration"%} <strong class="pull-right">{{request.session.template.name}}</strong></p>
<hr> <hr>
<p> <p class="last-p"><strong>{%trans "Total" %}</strong>&nbsp;&nbsp;<small>({%trans "including VAT" %})</small> <strong class="pull-right">{{request.session.specs.price|intcomma}} CHF/{% trans "Month" %}</strong></p>
<strong>{%trans "Total" %}</strong>&nbsp;&nbsp;
<small>
({% if vm_pricing.vat_inclusive %}{%trans "including VAT" %}{% else %}{%trans "excluding VAT" %}{% endif %})
</small>
<strong class="pull-right">{{request.session.specs.price|intcomma}} CHF/{% trans "Month" %}</strong>
</p>
<hr>
{% if vm_pricing.discount_amount %}
<p class="mb-0">
{%trans "Discount" as discount_name %}
<strong>{{ vm_pricing.discount_name|default:discount_name }}</strong>&nbsp;&nbsp;
<strong class="pull-right text-primary">- {{ vm_pricing.discount_amount }} CHF/{% trans "Month" %}</strong>
</p>
<p>
({% trans "Will be applied at checkout" %})
</p>
{% endif %}
</div> </div>
{% endif %}
</div> </div>
</div> </div>
<div class="dcl-payment-box"> <div class="dcl-payment-box">
<div class="dcl-payment-section"> <div class="dcl-payment-section">
{% with card_list_len=cards_list|length %}
<h3><b>{%trans "Credit Card"%}</b></h3> <h3><b>{%trans "Credit Card"%}</b></h3>
<hr class="top-hr"> <hr class="top-hr">
<p> <p>
{% if card_list_len > 0 %}
{% blocktrans %}Please select one of the cards that you used before or fill in your credit card information below. We are using <a href="https://stripe.com" target="_blank">Stripe</a> for payment and do not store your information in our database.{% endblocktrans %}
{% else %}
{% blocktrans %}Please fill in your credit card information below. We are using <a href="https://stripe.com" target="_blank">Stripe</a> for payment and do not store your information in our database.{% endblocktrans %} {% blocktrans %}Please fill in your credit card information below. We are using <a href="https://stripe.com" target="_blank">Stripe</a> for payment and do not store your information in our database.{% endblocktrans %}
{% endif %}
</p> </p>
<div> <div>
{% for card in cards_list %} {% if credit_card_data.last4 %}
<div class="credit-card-info"> <form role="form" id="payment-form-with-creditcard" novalidate>
<div class="col-xs-6 no-padding"> <h5 class="billing-head">Credit Card</h5>
<h5 class="billing-head">{% trans "Credit Card" %}</h5> <h5 class="membership-lead">Last 4: *****{{credit_card_data.last4}}</h5>
<h5 class="membership-lead">{% trans "Last" %} 4: ***** {{card.last4}}</h5> <h5 class="membership-lead">Type: {{credit_card_data.cc_brand}}</h5>
<h5 class="membership-lead">{% trans "Type" %}: {{card.brand}}</h5> <input type="hidden" name="credit_card_needed" value="false"/>
</div> </form>
<div class="col-xs-6 text-right align-bottom"> {% if not messages and not form.non_field_errors %}
<a class="btn choice-btn choice-btn-faded" href="#" data-id_card="{{card.id}}">{% trans "SELECT" %}</a> <p class="card-warning-content card-warning-addtional-margin">
</div> {% trans "You are not making any payment yet. After submitting your card information, you will be taken to the Confirm Order Page." %}
</div> </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 %} {% endfor %}
{% if card_list_len > 0 %} {% for error in form.non_field_errors %}
<div class="new-card-head"> <p class="card-warning-content card-warning-error">
{{ error|escape }}
</p>
{% endfor %}
</div>
<div class="text-right">
<button id="payment_button_with_creditcard" class="btn btn-vm-contact" type="submit">{%trans "SUBMIT" %}</button>
</div>
{% else %}
<form action="" id="payment-form-new" method="POST">
<input type="hidden" name="token"/>
<div class="group">
<div class="credit-card-goup">
<div class="card-element card-number-element">
<label>{%trans "Card Number" %}</label>
<div id="card-number-element" class="field my-input"></div>
</div>
<div class="row"> <div class="row">
<div class="col-xs-6"> <div class="col-xs-5 card-element card-expiry-element">
<h4>{% trans "Add a new credit card" %}</h4> <label>{%trans "Expiry Date" %}</label>
<div id="card-expiry-element" class="field my-input"></div>
</div> </div>
<div class="col-xs-6 text-right new-card-button-margin"> <div class="col-xs-3 col-xs-offset-4 card-element card-cvc-element">
<button data-toggle="collapse" data-target="#newcard" class="btn choice-btn"> <label>{%trans "CVC" %}</label>
<span class="fa fa-plus"></span>&nbsp;&nbsp;{% trans "NEW CARD" %} <div id="card-cvc-element" class="field my-input"></div>
</button> </div>
</div>
<div class="card-element brand">
<label>{%trans "Card Type" %}</label>
<i class="pf pf-credit-card" id="brand-icon"></i>
</div> </div>
</div> </div>
</div> </div>
<div id="newcard" class="collapse"> <div id="card-errors"></div>
<hr class="thick-hr"> {% if not messages and not form.non_field_errors %}
<div class="card-details-box"> <p class="card-warning-content">
<h3>{%trans "New Credit Card" %}</h3> {% trans "You are not making any payment yet. After placing your order, you will be taken to the Submit Payment Page." %}
<hr> </p>
{% include "hosting/includes/_card_input.html" %} {% endif %}
<div id='payment_error'>
{% for message in messages %}
{% if 'failed_payment' in message.tags or 'make_charge_error' in message.tags or 'error' in message.tags %}
<ul class="list-unstyled">
<li><p class="card-warning-content card-warning-error">{{ message|safe }}</p></li>
</ul>
{% endif %}
{% endfor %}
</div> </div>
<div class="text-right">
<button class="btn btn-vm-contact btn-wide" type="submit">{%trans "SUBMIT" %}</button>
</div> </div>
{% else%}
{% include "hosting/includes/_card_input.html" %} <div style="display:none;">
<p class="payment-errors"></p>
</div>
</form>
{% endif %} {% endif %}
</div> </div>
{% endwith %}
</div> </div>
</div> </div>
</div> </div>
@ -182,4 +190,13 @@
})(); })();
</script> </script>
{%endif%} {%endif%}
{% if credit_card_data.last4 and credit_card_data.cc_brand %}
<script type="text/javascript">
(function () {
window.hasCreditcard = true;
})();
</script>
{%endif%}
{%endblock%} {%endblock%}

View file

@ -47,32 +47,6 @@
<hr> <hr>
<div> <div>
<h4>{% trans "Order summary" %}</h4> <h4>{% trans "Order summary" %}</h4>
{% if generic_payment_details %}
<p>
<strong>{% trans "Product" %}:</strong>&nbsp;
{{ generic_payment_details.product_name }}
</p>
<div class="row">
<div class="col-sm-6">
<p>
<span>{% trans "Amount" %}: </span>
<strong class="pull-right">CHF {{generic_payment_details.amount|floatformat:2|intcomma}}</strong>
</p>
{% if generic_payment_details.description %}
<p>
<span>{% trans "Description" %}: </span>
<strong class="pull-right">{{generic_payment_details.description}}</strong>
</p>
{% endif %}
{% if generic_payment_details.recurring %}
<p>
<span>{% trans "Recurring" %}: </span>
<strong class="pull-right">Yes</strong>
</p>
{% endif %}
</div>
</div>
{% else %}
<p> <p>
<strong>{% trans "Product" %}:</strong>&nbsp; <strong>{% trans "Product" %}:</strong>&nbsp;
{{ request.session.template.name }} {{ request.session.template.name }}
@ -81,70 +55,30 @@
<div class="col-sm-6"> <div class="col-sm-6">
<p> <p>
<span>{% trans "Cores" %}: </span> <span>{% trans "Cores" %}: </span>
<strong class="pull-right">{{vm.cpu|floatformat}}</strong> <span class="pull-right">{{vm.cpu|floatformat}}</span>
</p> </p>
<p> <p>
<span>{% trans "Memory" %}: </span> <span>{% trans "Memory" %}: </span>
<strong class="pull-right">{{vm.memory|intcomma}} GB</strong> <span class="pull-right">{{vm.memory|intcomma}} GB</span>
</p> </p>
<p> <p>
<span>{% trans "Disk space" %}: </span> <span>{% trans "Disk space" %}: </span>
<strong class="pull-right">{{vm.disk_size|intcomma}} GB</strong> <span class="pull-right">{{vm.disk_size|intcomma}} GB</span>
</p>
</div>
<div class="col-sm-12">
<hr class="thin-hr">
</div>
{% if vm.vat > 0 or vm.discount.amount > 0 %}
<div class="col-sm-6">
<div class="subtotal-price">
{% if vm.vat > 0 %}
<p>
<strong class="text-lg">{% trans "Subtotal" %} </strong>
<strong class="pull-right">{{vm.price|floatformat:2|intcomma}} CHF</strong>
</p> </p>
<p> <p>
<small>{% trans "VAT" %} ({{ vm.vat_percent|floatformat:2|intcomma }}%) </small> <span>{% trans "Total" %}</span>
<strong class="pull-right">{{vm.vat|floatformat:2|intcomma}} CHF</strong> <span class="pull-right">{{vm.price|intcomma}} CHF</span>
</p>
{% endif %}
{% if vm.discount.amount > 0 %}
<p class="text-primary">
{%trans "Discount" as discount_name %}
<strong>{{ vm.discount.name|default:discount_name }} </strong>
<strong class="pull-right">- {{ vm.discount.amount }} CHF</strong>
</p>
{% endif %}
</div>
</div>
<div class="col-sm-12">
<hr class="thin-hr">
</div>
{% endif %}
<div class="col-sm-6">
<p class="total-price">
<strong>{% trans "Total" %} </strong>
<strong class="pull-right">{{vm.total_price|floatformat:2|intcomma}} CHF</strong>
</p> </p>
</div> </div>
</div> </div>
{% endif %}
</div> </div>
<hr class="thin-hr"> <hr>
</div> </div>
<form id="virtual_machine_create_form" action="" method="POST"> <form id="virtual_machine_create_form" action="" method="POST">
{% csrf_token %} {% csrf_token %}
<div class="row"> <div class="row">
<div class="col-sm-8"> <div class="col-sm-8">
{% if generic_payment_details %} <div class="dcl-place-order-text">{% blocktrans with vm_price=request.session.specs.price %}By clicking "Place order" this plan will charge your credit card account with the fee of {{ vm_price }}CHF/month{% endblocktrans %}.</div>
{% if generic_payment_details.recurring %}
<div class="dcl-place-order-text">{% blocktrans with total_price=generic_payment_details.amount|floatformat:2|intcomma %}By clicking "Place order" this plan will charge your credit card account with {{total_price}} CHF/month{% endblocktrans %}.</div>
{% else %}
<div class="dcl-place-order-text">{% blocktrans with total_price=generic_payment_details.amount|floatformat:2|intcomma %}By clicking "Place order" this payment will charge your credit card account with a one time amount of {{total_price}} CHF{% endblocktrans %}.</div>
{% endif %}
{% else %}
<div class="dcl-place-order-text">{% blocktrans with vm_total_price=vm.total_price|floatformat:2|intcomma %}By clicking "Place order" this plan will charge your credit card account with {{vm_total_price}} CHF/month{% endblocktrans %}.</div>
{% endif %}
</div> </div>
<div class="col-sm-4 order-confirm-btn text-right"> <div class="col-sm-4 order-confirm-btn text-right">
<button class="btn choice-btn" id="btn-create-vm" data-toggle="modal" data-target="#createvm-modal"> <button class="btn choice-btn" id="btn-create-vm" data-toggle="modal" data-target="#createvm-modal">
@ -186,5 +120,16 @@
<script type="text/javascript"> <script type="text/javascript">
{% trans "Some problem encountered. Please try again later." as err_msg %} {% trans "Some problem encountered. Please try again later." as err_msg %}
var create_vm_error_message = '{{err_msg|safe}}'; var create_vm_error_message = '{{err_msg|safe}}';
window.onload = function () {
var locale_dates = document.getElementsByClassName("locale_date");
var formats = ['YYYY-MM-DD hh:mm a']
var i;
for (i = 0; i < locale_dates.length; i++) {
var oldDate = moment.utc(locale_dates[i].textContent, formats);
var outputFormat = locale_dates[i].getAttribute('data-format') || oldDate._f;
locale_dates[i].innerHTML = oldDate.local().format(outputFormat);
locale_dates[i].className += ' done';
}
};
</script> </script>
{%endblock%} {%endblock%}

View file

@ -12,11 +12,9 @@ from unittest import skipIf
from datacenterlight.models import VMTemplate from datacenterlight.models import VMTemplate
from datacenterlight.tasks import create_vm_task from datacenterlight.tasks import create_vm_task
from hosting.models import HostingOrder
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.hosting_utils import get_vm_price
from utils.models import BillingAddress
from utils.stripe_utils import StripeUtils from utils.stripe_utils import StripeUtils
@ -83,13 +81,11 @@ class CeleryTaskTestCase(TestCase):
stripe_customer = StripeCustomer.get_or_create( stripe_customer = StripeCustomer.get_or_create(
email=self.customer_email, email=self.customer_email,
token=self.token token=self.token)
)
card_details = self.stripe_utils.get_card_details( card_details = self.stripe_utils.get_card_details(
stripe_customer.stripe_id stripe_customer.stripe_id,
) self.token)
card_details_dict = card_details.get('error') card_details_dict = card_details.get('response_object')
self.assertEquals(card_details_dict, None)
billing_address_data = {'cardholder_name': self.customer_name, billing_address_data = {'cardholder_name': self.customer_name,
'postal_code': '1231', 'postal_code': '1231',
'country': 'CH', 'country': 'CH',
@ -105,8 +101,7 @@ class CeleryTaskTestCase(TestCase):
disk_size=disk_size) disk_size=disk_size)
plan_name = StripeUtils.get_stripe_plan_name(cpu=cpu, plan_name = StripeUtils.get_stripe_plan_name(cpu=cpu,
memory=memory, memory=memory,
disk_size=disk_size, disk_size=disk_size)
price=amount_to_be_charged)
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,
@ -127,24 +122,10 @@ class CeleryTaskTestCase(TestCase):
msg = subscription_result.get('error') msg = subscription_result.get('error')
raise Exception("Creating subscription failed: {}".format(msg)) raise Exception("Creating subscription failed: {}".format(msg))
billing_address = BillingAddress(
cardholder_name=billing_address_data['cardholder_name'],
street_address=billing_address_data['street_address'],
city=billing_address_data['city'],
postal_code=billing_address_data['postal_code'],
country=billing_address_data['country']
)
billing_address.save()
order = HostingOrder.create(
price=specs['price'],
vm_id=0,
customer=stripe_customer,
billing_address=billing_address
)
async_task = create_vm_task.delay( async_task = create_vm_task.delay(
vm_template_id, self.user, specs, template_data, order.id vm_template_id, self.user, specs, template_data,
stripe_customer.id, billing_address_data,
stripe_subscription_obj.id, card_details_dict
) )
new_vm_id = 0 new_vm_id = 0
res = None res = None

View file

@ -13,7 +13,7 @@ urlpatterns = [
url(r'^g/$', IndexView.as_view(), name='index_g'), url(r'^g/$', IndexView.as_view(), name='index_g'),
url(r'^f/$', IndexView.as_view(), name='index_f'), url(r'^f/$', IndexView.as_view(), name='index_f'),
url(r'^l/$', IndexView.as_view(), name='index_l'), url(r'^l/$', IndexView.as_view(), name='index_l'),
url(r'^new/$', RedirectView.as_view(url='/cms/'), url(r'^new/$', RedirectView.as_view(url='/cms/datacenterlight/'),
name='cms_index'), name='cms_index'),
url(r'^whydatacenterlight/?$', WhyDataCenterLightView.as_view(), url(r'^whydatacenterlight/?$', WhyDataCenterLightView.as_view(),
name='whydatacenterlight'), name='whydatacenterlight'),

View file

@ -1,15 +1,6 @@
import logging
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from datacenterlight.tasks import create_vm_task
from hosting.models import HostingOrder, HostingBill, OrderDetail
from membership.models import StripeCustomer
from utils.forms import UserBillingAddressForm
from utils.models import BillingAddress
from .cms_models import CMSIntegration from .cms_models import CMSIntegration
from .models import VMPricing, VMTemplate
logger = logging.getLogger(__name__)
def get_cms_integration(name): def get_cms_integration(name):
@ -21,82 +12,3 @@ def get_cms_integration(name):
except CMSIntegration.DoesNotExist: except CMSIntegration.DoesNotExist:
cms_integration = CMSIntegration.objects.get(name=name, domain=None) cms_integration = CMSIntegration.objects.get(name=name, domain=None)
return cms_integration return cms_integration
def create_vm(billing_address_data, stripe_customer_id, specs,
stripe_subscription_obj, card_details_dict, request,
vm_template_id, template, user):
billing_address = BillingAddress(
cardholder_name=billing_address_data['cardholder_name'],
street_address=billing_address_data['street_address'],
city=billing_address_data['city'],
postal_code=billing_address_data['postal_code'],
country=billing_address_data['country']
)
billing_address.save()
customer = StripeCustomer.objects.filter(id=stripe_customer_id).first()
vm_pricing = (
VMPricing.get_vm_pricing_by_name(name=specs['pricing_name'])
if 'pricing_name' in specs else
VMPricing.get_default_pricing()
)
final_price = (
specs.get('total_price')
if 'total_price' in specs
else specs.get('price')
)
# Create a Hosting Order with vm_id = 0, we shall set it later in
# celery task once the VM instance is up and running
order = HostingOrder.create(
price=final_price,
customer=customer,
billing_address=billing_address,
vm_pricing=vm_pricing
)
order_detail_obj, obj_created = OrderDetail.objects.get_or_create(
vm_template=VMTemplate.objects.get(
opennebula_vm_template_id=vm_template_id
),
cores=specs['cpu'], memory=specs['memory'], ssd_size=specs['disk_size']
)
order.order_detail = order_detail_obj
order.save()
# 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 the given stripe subscription with the order
order.set_subscription_id(
stripe_subscription_obj.id, card_details_dict
)
# Set order status approved
order.set_approved()
create_vm_task.delay(vm_template_id, user, specs, template, order.id)
clear_all_session_vars(request)
def clear_all_session_vars(request):
if request.session is not None:
for session_var in ['specs', 'template', 'billing_address',
'billing_address_data', 'card_id',
'token', 'customer', 'generic_payment_type',
'generic_payment_details', 'product_id']:
if session_var in request.session:
del request.session[session_var]

View file

@ -1,3 +1,4 @@
import json
import logging import logging
from django import forms from django import forms
@ -6,31 +7,24 @@ from django.contrib import messages
from django.contrib.auth import login, authenticate from django.contrib.auth import login, authenticate
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, JsonResponse, Http404 from django.http import HttpResponseRedirect, HttpResponse
from django.shortcuts import render from django.shortcuts import render
from django.utils.translation import get_language, ugettext_lazy as _ from django.utils.translation import get_language, ugettext_lazy as _
from django.views.decorators.cache import cache_control from django.views.decorators.cache import cache_control
from django.views.generic import FormView, CreateView, DetailView from django.views.generic import FormView, CreateView, DetailView
from hosting.forms import ( from datacenterlight.tasks import create_vm_task
HostingUserLoginForm, GenericPaymentForm, ProductPaymentForm from hosting.forms import HostingUserLoginForm
) from hosting.models import HostingOrder
from hosting.models import (
HostingBill, HostingOrder, UserCardDetail, GenericProduct
)
from membership.models import CustomUser, StripeCustomer from membership.models import CustomUser, StripeCustomer
from opennebula_api.serializers import VMTemplateSerializer from opennebula_api.serializers import VMTemplateSerializer
from utils.forms import ( from utils.forms import BillingAddressForm, BillingAddressFormSignup
BillingAddressForm, BillingAddressFormSignup, UserBillingAddressForm, from utils.hosting_utils import get_vm_price
BillingAddress
)
from utils.hosting_utils import get_vm_price_with_vat
from utils.stripe_utils import StripeUtils from utils.stripe_utils import StripeUtils
from utils.tasks import send_plain_email_task from utils.tasks import send_plain_email_task
from .cms_models import DCLCalculatorPluginModel
from .forms import ContactForm from .forms import ContactForm
from .models import VMTemplate, VMPricing from .models import VMTemplate
from .utils import get_cms_integration, create_vm, clear_all_session_vars from .utils import get_cms_integration
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -64,7 +58,7 @@ class ContactUsView(FormView):
sender=form.cleaned_data.get('email') sender=form.cleaned_data.get('email')
), ),
'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS,
'to': [from_emails.get(from_page, 'support@ungleich.ch')], 'to': [from_emails.get(from_page, 'info@ungleich.ch')],
'body': "\n".join( 'body': "\n".join(
["%s=%s" % (k, v) for (k, v) in form.cleaned_data.items()]), ["%s=%s" % (k, v) for (k, v) in form.cleaned_data.items()]),
'reply_to': [form.cleaned_data.get('email')], 'reply_to': [form.cleaned_data.get('email')],
@ -90,29 +84,7 @@ class IndexView(CreateView):
raise ValidationError(_('Invalid number of cores')) raise ValidationError(_('Invalid number of cores'))
def validate_memory(self, value): def validate_memory(self, value):
if 'pid' in self.request.POST: if (value > 200) or (value < 1):
try:
plugin = DCLCalculatorPluginModel.objects.get(
id=self.request.POST['pid']
)
except DCLCalculatorPluginModel.DoesNotExist as dne:
logger.error(
str(dne) + " plugin_id: " + self.request.POST['pid']
)
raise ValidationError(_('Invalid calculator properties'))
if plugin.enable_512mb_ram:
if value % 1 == 0 or value == 0.5:
logger.debug(
"Given ram {value} is either 0.5 or a"
" whole number".format(value=value)
)
if (value > 200) or (value < 0.5):
raise ValidationError(_('Invalid RAM size'))
else:
raise ValidationError(_('Invalid RAM size'))
elif (value > 200) or (value < 1) or (value % 1 != 0):
raise ValidationError(_('Invalid RAM size'))
else:
raise ValidationError(_('Invalid RAM size')) raise ValidationError(_('Invalid RAM size'))
def validate_storage(self, value): def validate_storage(self, value):
@ -121,41 +93,25 @@ class IndexView(CreateView):
@cache_control(no_cache=True, must_revalidate=True, no_store=True) @cache_control(no_cache=True, must_revalidate=True, no_store=True)
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
clear_all_session_vars(request) for session_var in ['specs', 'user', 'billing_address_data']:
if session_var in request.session:
del request.session[session_var]
return HttpResponseRedirect(reverse('datacenterlight:cms_index')) return HttpResponseRedirect(reverse('datacenterlight:cms_index'))
def post(self, request): def post(self, request):
cores = request.POST.get('cpu') cores = request.POST.get('cpu')
cores_field = forms.IntegerField(validators=[self.validate_cores]) cores_field = forms.IntegerField(validators=[self.validate_cores])
memory = request.POST.get('ram') memory = request.POST.get('ram')
memory_field = forms.FloatField(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])
template_id = int(request.POST.get('config')) template_id = int(request.POST.get('config'))
pricing_name = request.POST.get('pricing_name')
vm_pricing = VMPricing.get_vm_pricing_by_name(pricing_name)
template = VMTemplate.objects.filter( template = VMTemplate.objects.filter(
opennebula_vm_template_id=template_id opennebula_vm_template_id=template_id
).first() ).first()
template_data = VMTemplateSerializer(template).data template_data = VMTemplateSerializer(template).data
referer_url = request.META['HTTP_REFERER'] referer_url = request.META['HTTP_REFERER']
if vm_pricing is None:
vm_pricing_name_msg = _(
"Incorrect pricing name. Please contact support"
"{support_email}".format(
support_email=settings.DCL_SUPPORT_FROM_ADDRESS
)
)
messages.add_message(
self.request, messages.ERROR, vm_pricing_name_msg,
extra_tags='pricing'
)
return HttpResponseRedirect(referer_url + "#order_form")
else:
vm_pricing_name = vm_pricing.name
try: try:
cores = cores_field.clean(cores) cores = cores_field.clean(cores)
except ValidationError as err: except ValidationError as err:
@ -183,22 +139,14 @@ class IndexView(CreateView):
) )
return HttpResponseRedirect(referer_url + "#order_form") return HttpResponseRedirect(referer_url + "#order_form")
price, vat, vat_percent, discount = get_vm_price_with_vat( amount_to_be_charged = get_vm_price(
cpu=cores, cpu=cores, memory=memory, disk_size=storage
memory=memory,
ssd_size=storage,
pricing_name=vm_pricing_name
) )
specs = { specs = {
'cpu': cores, 'cpu': cores,
'memory': memory, 'memory': memory,
'disk_size': storage, 'disk_size': storage,
'price': price, 'price': amount_to_be_charged
'vat': vat,
'vat_percent': vat_percent,
'discount': discount,
'total_price': round(price + vat - discount['amount'], 2),
'pricing_name': vm_pricing_name
} }
request.session['specs'] = specs request.session['specs'] = specs
request.session['template'] = template_data request.session['template'] = template_data
@ -249,15 +197,19 @@ class PaymentOrderView(FormView):
billing_address_form = BillingAddressForm( billing_address_form = BillingAddressForm(
instance=self.request.user.billing_addresses.first() instance=self.request.user.billing_addresses.first()
) )
user = self.request.user # Get user last order
if hasattr(user, 'stripecustomer'): last_hosting_order = HostingOrder.objects.filter(
stripe_customer = user.stripecustomer customer__user=self.request.user
).last()
# If user has already an hosting order, get the credit card
# data from it
if last_hosting_order:
credit_card_data = last_hosting_order.get_cc_data()
if credit_card_data:
context['credit_card_data'] = credit_card_data
else: else:
stripe_customer = None context['credit_card_data'] = None
cards_list = UserCardDetail.get_all_cards_list(
stripe_customer=stripe_customer
)
context.update({'cards_list': cards_list})
else: else:
billing_address_form = BillingAddressFormSignup( billing_address_form = BillingAddressFormSignup(
initial=billing_address_data initial=billing_address_data
@ -268,94 +220,17 @@ class PaymentOrderView(FormView):
'site_url': reverse('datacenterlight:index'), 'site_url': reverse('datacenterlight:index'),
'login_form': HostingUserLoginForm(prefix='login_form'), 'login_form': HostingUserLoginForm(prefix='login_form'),
'billing_address_form': billing_address_form, 'billing_address_form': billing_address_form,
'cms_integration': get_cms_integration('default'), 'cms_integration': get_cms_integration('default')
}) })
if ('generic_payment_type' in self.request.session and
self.request.session['generic_payment_type'] == 'generic'):
if 'product_id' in self.request.session:
product = GenericProduct.objects.get(
id=self.request.session['product_id']
)
context.update({'generic_payment_form': ProductPaymentForm(
prefix='generic_payment_form',
initial={'product_name': product.product_name,
'amount': float(product.get_actual_price()),
'recurring': product.product_is_subscription,
'description': product.product_description,
},
product_id=product.id
), })
else:
context.update({'generic_payment_form': GenericPaymentForm(
prefix='generic_payment_form',
), })
else:
context.update({
'vm_pricing': VMPricing.get_vm_pricing_by_name(
self.request.session['specs']['pricing_name']
)
})
return context return context
@cache_control(no_cache=True, must_revalidate=True, no_store=True) @cache_control(no_cache=True, must_revalidate=True, no_store=True)
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
if (('type' in request.GET and request.GET['type'] == 'generic') if 'specs' not in request.session:
or 'product_slug' in kwargs):
request.session['generic_payment_type'] = 'generic'
if 'generic_payment_details' in request.session:
request.session.pop('generic_payment_details')
request.session.pop('product_id')
if 'product_slug' in kwargs:
logger.debug("Product slug is " + kwargs['product_slug'])
try:
product = GenericProduct.objects.get(
product_slug=kwargs['product_slug']
)
except GenericProduct.DoesNotExist as dne:
logger.error(
"Product '{}' does "
"not exist".format(kwargs['product_slug'])
)
raise Http404()
request.session['product_id'] = product.id
elif 'specs' not in request.session:
return HttpResponseRedirect(reverse('datacenterlight:index')) return HttpResponseRedirect(reverse('datacenterlight:index'))
return self.render_to_response(self.get_context_data()) return self.render_to_response(self.get_context_data())
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
if 'product' in request.POST:
# query for the supplied product
product = None
try:
product = GenericProduct.objects.get(
id=request.POST['generic_payment_form-product_name']
)
except GenericProduct.DoesNotExist as dne:
logger.error(
"The requested product '{}' does not exist".format(
request.POST['generic_payment_form-product_name']
)
)
except GenericProduct.MultipleObjectsReturned as mpe:
logger.error(
"There seem to be more than one product with "
"the name {}".format(
request.POST['generic_payment_form-product_name']
)
)
product = GenericProduct.objects.all(
product_name=request.
POST['generic_payment_form-product_name']
).first()
if product is None:
return JsonResponse({})
else:
return JsonResponse({
'amount': product.get_actual_price(),
'isSubscription': product.product_is_subscription
})
if 'login_form' in request.POST: if 'login_form' in request.POST:
login_form = HostingUserLoginForm( login_form = HostingUserLoginForm(
data=request.POST, prefix='login_form' data=request.POST, prefix='login_form'
@ -366,13 +241,6 @@ class PaymentOrderView(FormView):
auth_user = authenticate(email=email, password=password) auth_user = authenticate(email=email, password=password)
if auth_user: if auth_user:
login(self.request, auth_user) login(self.request, auth_user)
if 'product_slug' in kwargs:
return HttpResponseRedirect(
reverse('show_product',
kwargs={
'product_slug': kwargs['product_slug']}
)
)
return HttpResponseRedirect( return HttpResponseRedirect(
reverse('datacenterlight:payment') reverse('datacenterlight:payment')
) )
@ -389,87 +257,15 @@ class PaymentOrderView(FormView):
data=request.POST, data=request.POST,
) )
if address_form.is_valid(): if address_form.is_valid():
# Check if we are in a generic payment case and handle the generic
# payment details form before we go on to verify payment
if ('generic_payment_type' in request.session and
self.request.session['generic_payment_type'] == 'generic'):
if 'product_id' in request.session:
generic_payment_form = ProductPaymentForm(
data=request.POST, prefix='generic_payment_form',
product_id=request.session['product_id']
)
else:
generic_payment_form = GenericPaymentForm(
data=request.POST, prefix='generic_payment_form'
)
if generic_payment_form.is_valid():
logger.debug("Generic payment form is valid.")
if 'product_id' in request.session:
product = generic_payment_form.product
else:
product = generic_payment_form.cleaned_data.get(
'product_name'
)
gp_details = {
"product_name": product.product_name,
"amount": generic_payment_form.cleaned_data.get(
'amount'
),
"recurring": generic_payment_form.cleaned_data.get(
'recurring'
),
"description": generic_payment_form.cleaned_data.get(
'description'
),
"product_id": product.id,
"product_slug": product.product_slug
}
request.session["generic_payment_details"] = (
gp_details
)
else:
logger.debug("Generic payment form invalid")
context = self.get_context_data()
context['generic_payment_form'] = generic_payment_form
context['billing_address_form'] = address_form
return self.render_to_response(context)
token = address_form.cleaned_data.get('token') token = address_form.cleaned_data.get('token')
if token is '':
card_id = address_form.cleaned_data.get('card')
try:
user_card_detail = UserCardDetail.objects.get(id=card_id)
if not request.user.has_perm(
'view_usercarddetail', user_card_detail
):
raise UserCardDetail.DoesNotExist(
_("{user} does not have permission to access the "
"card").format(user=request.user.email)
)
except UserCardDetail.DoesNotExist as e:
ex = str(e)
logger.error("Card Id: {card_id}, Exception: {ex}".format(
card_id=card_id, ex=ex
)
)
msg = _("An error occurred. Details: {}".format(ex))
messages.add_message(
self.request, messages.ERROR, msg,
extra_tags='make_charge_error'
)
return HttpResponseRedirect(
reverse('datacenterlight:payment') + '#payment_error'
)
request.session['card_id'] = user_card_detail.id
else:
request.session['token'] = token
if request.user.is_authenticated(): if request.user.is_authenticated():
this_user = { this_user = {
'email': request.user.email, 'email': request.user.email,
'name': request.user.name 'name': request.user.name
} }
customer = StripeCustomer.get_or_create( customer = StripeCustomer.get_or_create(
email=this_user.get('email'), token=token email=this_user.get('email'),
) token=token)
else: else:
user_email = address_form.cleaned_data.get('email') user_email = address_form.cleaned_data.get('email')
user_name = address_form.cleaned_data.get('name') user_name = address_form.cleaned_data.get('name')
@ -517,6 +313,7 @@ class PaymentOrderView(FormView):
billing_address_form=address_form billing_address_form=address_form
) )
) )
request.session['token'] = token
if type(customer) is StripeCustomer: if type(customer) is StripeCustomer:
request.session['customer'] = customer.stripe_id request.session['customer'] = customer.stripe_id
else: else:
@ -537,137 +334,48 @@ class OrderConfirmationView(DetailView):
@cache_control(no_cache=True, must_revalidate=True, no_store=True) @cache_control(no_cache=True, must_revalidate=True, no_store=True)
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
context = {} if 'specs' not in request.session or 'user' not in request.session:
if (('specs' not in request.session or 'user' not in request.session)
and 'generic_payment_type' not in request.session):
return HttpResponseRedirect(reverse('datacenterlight:index')) return HttpResponseRedirect(reverse('datacenterlight:index'))
if 'token' in self.request.session: if 'token' not in request.session:
token = self.request.session['token'] return HttpResponseRedirect(reverse('datacenterlight:payment'))
stripe_utils = StripeUtils()
card_details = stripe_utils.get_cards_details_from_token(
token
)
if not card_details.get('response_object'):
return HttpResponseRedirect(reverse('hosting:payment'))
card_details_response = card_details['response_object']
context['cc_last4'] = card_details_response['last4']
context['cc_brand'] = card_details_response['brand']
else:
card_id = self.request.session.get('card_id')
card_detail = UserCardDetail.objects.get(id=card_id)
context['cc_last4'] = card_detail.last4
context['cc_brand'] = card_detail.brand
if ('generic_payment_type' in request.session and
self.request.session['generic_payment_type'] == 'generic'):
context.update({
'generic_payment_details':
request.session['generic_payment_details'],
})
else:
context.update({
'vm': request.session.get('specs'),
})
context.update({
'site_url': reverse('datacenterlight:index'),
'page_header_text': _('Confirm Order'),
'billing_address_data': (
request.session.get('billing_address_data')
),
'cms_integration': get_cms_integration('default'),
})
return render(request, self.template_name, context)
def post(self, request, *args, **kwargs):
user = request.session.get('user')
stripe_api_cus_id = request.session.get('customer') stripe_api_cus_id = request.session.get('customer')
stripe_utils = StripeUtils() stripe_utils = StripeUtils()
card_details = stripe_utils.get_card_details(stripe_api_cus_id,
if 'token' in request.session: request.session.get(
card_details = stripe_utils.get_cards_details_from_token( 'token'))
request.session.get('token')
)
if not card_details.get('response_object'): if not card_details.get('response_object'):
msg = card_details.get('error') msg = card_details.get('error')
messages.add_message(self.request, messages.ERROR, msg, messages.add_message(self.request, messages.ERROR, msg,
extra_tags='failed_payment') extra_tags='failed_payment')
response = { return HttpResponseRedirect(
'status': False, reverse('datacenterlight:payment') + '#payment_error')
'redirect': "{url}#{section}".format( context = {
url=(reverse( 'site_url': reverse('datacenterlight:index'),
'show_product', 'cc_last4': card_details.get('response_object').get('last4'),
kwargs={'product_slug': 'cc_brand': card_details.get('response_object').get('brand'),
request.session['generic_payment_details'] 'vm': request.session.get('specs'),
['product_slug']} 'page_header_text': _('Confirm Order'),
) if 'generic_payment_details' in request.session else 'billing_address_data': (
reverse('datacenterlight:payment') request.session.get('billing_address_data')
), ),
section='payment_error'), 'cms_integration': get_cms_integration('default')
'msg_title': str(_('Error.')),
'msg_body': str(
_('There was a payment related error.'
' On close of this popup, you will be'
' redirected back to the payment page.')
)
} }
return JsonResponse(response) return render(request, self.template_name, context)
card_details_response = card_details['response_object']
card_details_dict = { def post(self, request, *args, **kwargs):
'last4': card_details_response['last4'], template = request.session.get('template')
'brand': card_details_response['brand'], specs = request.session.get('specs')
'card_id': card_details_response['card_id'] user = request.session.get('user')
} stripe_api_cus_id = request.session.get('customer')
stripe_customer_obj = StripeCustomer.objects.filter( vm_template_id = template.get('id', 1)
stripe_id=stripe_api_cus_id).first() stripe_utils = StripeUtils()
if stripe_customer_obj: card_details = stripe_utils.get_card_details(stripe_api_cus_id,
ucd = UserCardDetail.get_user_card_details( request.session.get(
stripe_customer_obj, card_details_response 'token'))
) if not card_details.get('response_object'):
if not ucd: msg = card_details.get('error')
acc_result = stripe_utils.associate_customer_card(
stripe_api_cus_id, request.session['token'],
set_as_default=True
)
if acc_result['response_object'] is None:
msg = _(
'An error occurred while associating the card.'
' Details: {details}'.format(
details=acc_result['error']
)
)
messages.add_message(self.request, messages.ERROR, msg, messages.add_message(self.request, messages.ERROR, msg,
extra_tags='failed_payment') extra_tags='failed_payment')
response = {
'status': False,
'redirect': "{url}#{section}".format(
url=(reverse(
'show_product',
kwargs={'product_slug':
request.session
['generic_payment_details']
['product_slug']}
) if 'generic_payment_details' in
request.session else
reverse('datacenterlight:payment')
),
section='payment_error'),
'msg_title': str(_('Error.')),
'msg_body': str(
_('There was a payment related error.'
' On close of this popup, you will be redirected'
' back to the payment page.')
)
}
return JsonResponse(response)
elif 'card_id' in request.session:
card_id = request.session.get('card_id')
user_card_detail = UserCardDetail.objects.get(id=card_id)
card_details_dict = {
'last4': user_card_detail.last4,
'brand': user_card_detail.brand,
'card_id': user_card_detail.card_id
}
else:
response = { response = {
'status': False, 'status': False,
'redirect': "{url}#{section}".format( 'redirect': "{url}#{section}".format(
@ -679,83 +387,21 @@ class OrderConfirmationView(DetailView):
' On close of this popup, you will be redirected back to' ' On close of this popup, you will be redirected back to'
' the payment page.')) ' the payment page.'))
} }
return JsonResponse(response) return HttpResponse(json.dumps(response),
content_type="application/json")
if ('generic_payment_type' in request.session and card_details_dict = card_details.get('response_object')
self.request.session['generic_payment_type'] == 'generic'):
gp_details = self.request.session['generic_payment_details']
if gp_details['recurring']:
# generic recurring payment
logger.debug("Commencing a generic recurring payment")
else:
# generic one time payment
logger.debug("Commencing a one time payment")
charge_response = stripe_utils.make_charge(
amount=gp_details['amount'],
customer=stripe_api_cus_id
)
stripe_onetime_charge = charge_response.get('response_object')
# Check if the payment was approved
if not stripe_onetime_charge:
msg = charge_response.get('error')
messages.add_message(self.request, messages.ERROR, msg,
extra_tags='failed_payment')
response = {
'status': False,
'redirect': "{url}#{section}".format(
url=(reverse('show_product', kwargs={
'product_slug': gp_details['product_slug']}
) if 'generic_payment_details' in
request.session else
reverse('datacenterlight:payment')
),
section='payment_error'),
'msg_title': str(_('Error.')),
'msg_body': str(
_('There was a payment related error.'
' On close of this popup, you will be redirected'
' back to the payment page.'))
}
return JsonResponse(response)
if ('generic_payment_type' not in request.session or
(request.session['generic_payment_details']['recurring'])):
if 'generic_payment_details' in request.session:
amount_to_be_charged = (
round(
request.session['generic_payment_details']['amount'],
2
)
)
plan_name = "generic-{0}-{1:.2f}".format(
request.session['generic_payment_details']['product_id'],
amount_to_be_charged
)
stripe_plan_id = plan_name
else:
template = request.session.get('template')
specs = request.session.get('specs')
vm_template_id = template.get('id', 1)
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 = specs.get('total_price') amount_to_be_charged = specs.get('price')
plan_name = StripeUtils.get_stripe_plan_name( plan_name = StripeUtils.get_stripe_plan_name(cpu=cpu,
cpu=cpu,
memory=memory, memory=memory,
disk_size=disk_size, disk_size=disk_size)
price=amount_to_be_charged 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,
version=1, version=1,
app='dcl', app='dcl')
price=amount_to_be_charged
)
stripe_plan = stripe_utils.get_or_create_stripe_plan( stripe_plan = stripe_utils.get_or_create_stripe_plan(
amount=amount_to_be_charged, amount=amount_to_be_charged,
name=plan_name, name=plan_name,
@ -768,35 +414,22 @@ class OrderConfirmationView(DetailView):
# Check if the subscription was approved and is active # Check if the subscription was approved and is active
if (stripe_subscription_obj is None if (stripe_subscription_obj is None
or stripe_subscription_obj.status != 'active'): or stripe_subscription_obj.status != 'active'):
# At this point, we have created a Stripe API card and
# associated it with the customer; but the transaction failed
# due to some reason. So, we would want to dissociate this card
# here.
# ...
msg = subscription_result.get('error') msg = subscription_result.get('error')
messages.add_message(self.request, messages.ERROR, msg, messages.add_message(self.request, messages.ERROR, msg,
extra_tags='failed_payment') extra_tags='failed_payment')
response = { response = {
'status': False, 'status': False,
'redirect': "{url}#{section}".format( 'redirect': "{url}#{section}".format(
url=(reverse( url=reverse('datacenterlight:payment'),
'show_product', section='payment_error'),
kwargs={'product_slug':
request.session['generic_payment_details']
['product_slug']}
) if 'generic_payment_details' in request.session else
reverse('datacenterlight:payment')
),
section='payment_error'
),
'msg_title': str(_('Error.')), 'msg_title': str(_('Error.')),
'msg_body': str( 'msg_body': str(
_('There was a payment related error.' _('There was a payment related error.'
' On close of this popup, you will be redirected back to' ' On close of this popup, you will be redirected back to'
' the payment page.')) ' the payment page.'))
} }
return JsonResponse(response) return HttpResponse(json.dumps(response),
content_type="application/json")
# Create user if the user is not logged in and if he is not already # Create user if the user is not logged in and if he is not already
# registered # registered
@ -836,148 +469,12 @@ class OrderConfirmationView(DetailView):
stripe_customer_id = request.user.stripecustomer.id stripe_customer_id = request.user.stripecustomer.id
custom_user = request.user custom_user = request.user
if 'token' in request.session:
ucd = UserCardDetail.get_or_create_user_card_detail(
stripe_customer=self.request.user.stripecustomer,
card_details=card_details_response
)
UserCardDetail.save_default_card_local(
self.request.user.stripecustomer.stripe_id,
ucd.card_id
)
else:
card_id = request.session.get('card_id')
user_card_detail = UserCardDetail.objects.get(id=card_id)
card_details_dict = {
'last4': user_card_detail.last4,
'brand': user_card_detail.brand,
'card_id': user_card_detail.card_id
}
if not user_card_detail.preferred:
UserCardDetail.set_default_card(
stripe_api_cus_id=stripe_api_cus_id,
stripe_source_id=user_card_detail.card_id
)
# Save billing address # Save billing address
billing_address_data = request.session.get('billing_address_data') billing_address_data = request.session.get('billing_address_data')
logger.debug('billing_address_data is {}'.format(billing_address_data)) logger.debug('billing_address_data is {}'.format(billing_address_data))
billing_address_data.update({ billing_address_data.update({
'user': custom_user.id 'user': custom_user.id
}) })
if 'generic_payment_type' in request.session:
stripe_cus = StripeCustomer.objects.filter(
stripe_id=stripe_api_cus_id
).first()
billing_address = BillingAddress(
cardholder_name=billing_address_data['cardholder_name'],
street_address=billing_address_data['street_address'],
city=billing_address_data['city'],
postal_code=billing_address_data['postal_code'],
country=billing_address_data['country']
)
billing_address.save()
order = HostingOrder.create(
price=self.request
.session['generic_payment_details']['amount'],
customer=stripe_cus,
billing_address=billing_address,
vm_pricing=VMPricing.get_default_pricing()
)
# Create a Hosting Bill
HostingBill.create(customer=stripe_cus,
billing_address=billing_address)
# Create Billing Address for User if he does not have one
if not stripe_cus.user.billing_addresses.count():
billing_address_data.update({
'user': stripe_cus.user.id
})
billing_address_user_form = UserBillingAddressForm(
billing_address_data
)
billing_address_user_form.is_valid()
billing_address_user_form.save()
if self.request.session['generic_payment_details']['recurring']:
# Associate the given stripe subscription with the order
order.set_subscription_id(
stripe_subscription_obj.id, card_details_dict
)
else:
# Associate the given stripe charge id with the order
order.set_stripe_charge(stripe_onetime_charge)
# Set order status approved
order.set_approved()
order.generic_payment_description = gp_details["description"]
order.generic_product_id = gp_details["product_id"]
order.save()
# send emails
context = {
'name': user.get('name'),
'email': user.get('email'),
'amount': gp_details['amount'],
'description': gp_details['description'],
'recurring': gp_details['recurring'],
'product_name': gp_details['product_name'],
'product_id': gp_details['product_id'],
'order_id': order.id
}
email_data = {
'subject': (settings.DCL_TEXT +
" Payment received 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']],
}
send_plain_email_task.delay(email_data)
email_data = {
'subject': _("Confirmation of your payment"),
'from_email': settings.DCL_SUPPORT_FROM_ADDRESS,
'to': [user.get('email')],
'body': _("Hi {name},\n\n"
"thank you for your order!\n"
"We have just received a payment of CHF {amount:.2f}"
" from you.{recurring}\n\n"
"Cheers,\nYour Data Center Light team".format(
name=user.get('name'),
amount=gp_details['amount'],
recurring=(
_(' This is a monthly recurring plan.')
if gp_details['recurring'] else ''
)
)
),
'reply_to': ['info@ungleich.ch'],
}
send_plain_email_task.delay(email_data)
response = {
'status': True,
'redirect': (
reverse('hosting:orders')
if request.user.is_authenticated()
else reverse('datacenterlight:index')
),
'msg_title': str(_('Thank you for the payment.')),
'msg_body': str(
_('You will soon receive a confirmation email of the '
'payment. You can always contact us at '
'info@ungleich.ch for any question that you may have.')
)
}
clear_all_session_vars(request)
return JsonResponse(response)
user = { user = {
'name': custom_user.name, 'name': custom_user.name,
'email': custom_user.email, 'email': custom_user.email,
@ -987,11 +484,14 @@ class OrderConfirmationView(DetailView):
'language': get_language(), 'language': get_language(),
} }
create_vm( create_vm_task.delay(vm_template_id, user, specs, template,
billing_address_data, stripe_customer_id, specs, stripe_customer_id, billing_address_data,
stripe_subscription_obj, card_details_dict, request, stripe_subscription_obj.id, card_details_dict)
vm_template_id, template, user for session_var in ['specs', 'template', 'billing_address',
) 'billing_address_data',
'token', 'customer']:
if session_var in request.session:
del request.session[session_var]
response = { response = {
'status': True, 'status': True,
@ -1007,4 +507,5 @@ class OrderConfirmationView(DetailView):
' it is ready.')) ' it is ready.'))
} }
return JsonResponse(response) return HttpResponse(json.dumps(response),
content_type="application/json")

View file

@ -1,25 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2018-08-24 07:39
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('digitalglarus', '0025_membershiporder_stripe_subscription_id'),
]
operations = [
migrations.AlterField(
model_name='bookingorder',
name='cc_brand',
field=models.CharField(blank=True, max_length=128),
),
migrations.AlterField(
model_name='membershiporder',
name='cc_brand',
field=models.CharField(blank=True, max_length=128),
),
]

View file

@ -39,7 +39,7 @@ class Ordereable(models.Model):
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
approved = models.BooleanField(default=False) approved = models.BooleanField(default=False)
last4 = models.CharField(max_length=4, blank=True) last4 = models.CharField(max_length=4, blank=True)
cc_brand = models.CharField(max_length=128, blank=True) cc_brand = models.CharField(max_length=10, blank=True)
stripe_charge_id = models.CharField(max_length=100, null=True) stripe_charge_id = models.CharField(max_length=100, null=True)
class Meta: class Meta:

View file

@ -70,7 +70,7 @@ hr.small {
} }
.navbar-custom .navbar-brand { .navbar-custom .navbar-brand {
color: white; color: white;
padding: 5px 20px; padding: 20px;
} }
.navbar-custom .navbar-brand:hover, .navbar-custom .navbar-brand:hover,
.navbar-custom .navbar-brand:focus { .navbar-custom .navbar-brand:focus {

View file

@ -57,7 +57,7 @@
ga('send', 'pageview'); ga('send', 'pageview');
</script> </script>
<link rel="shortcut icon" href="{% static 'digitalglarus/img/favicon.ico' %}" type="image/x-icon"> <link rel="shortcut icon" href="img/favicon.ico" type="image/x-icon">
<style id="igtranslator-color" type="text/css"></style> <style id="igtranslator-color" type="text/css"></style>
<style type="text/css"> <style type="text/css">

View file

@ -2,7 +2,7 @@ import logging
from django.conf import settings from django.conf import settings
from django.shortcuts import render from django.shortcuts import render
from django.http import HttpResponseRedirect, Http404 from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse_lazy, reverse from django.core.urlresolvers import reverse_lazy, reverse
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.views.generic import TemplateView, UpdateView from django.views.generic import TemplateView, UpdateView
@ -492,18 +492,6 @@ class MembershipPaymentView(LoginRequiredMixin, IsNotMemberMixin, FormView):
'membership_dates': membership.type.first_month_formated_range 'membership_dates': membership.type.first_month_formated_range
}) })
email_to_admin_data = {
'subject': "New Digital Glarus subscription: {user}".format(
user=self.request.user.email
),
'from_email': 'info@digitalglarus.ch',
'to': ['info@ungleich.ch'],
'body': "\n".join(
["%s=%s" % (k, v) for (k, v) in
order_data.items()]),
}
send_plain_email_task.delay(email_to_admin_data)
context = { context = {
'membership': membership, 'membership': membership,
'order': membership_order, 'order': membership_order,
@ -846,9 +834,8 @@ class ContactView(FormView):
def blog(request): def blog(request):
tags = ["digitalglarus"] tags = ["digitalglarus"]
posts = (Post.objects posts = Post.objects.filter(tags__name__in=tags, publish=True).translated(get_language())
.filter(tags__name__in=tags, publish=True) # posts = Post.objects.filter_by_language(get_language()).filter(tags__name__in=tags, publish=True)
.translated(get_language()))
context = { context = {
'post_list': posts, 'post_list': posts,
} }
@ -856,9 +843,9 @@ def blog(request):
def blog_detail(request, slug): def blog_detail(request, slug):
# post = Post.objects.filter_by_language(get_language()).filter(slug=slug).first()
post = Post.objects.translated(get_language(), slug=slug).first() post = Post.objects.translated(get_language(), slug=slug).first()
if post is None:
raise Http404()
context = { context = {
'post': post, 'post': post,
} }

View file

@ -2,15 +2,16 @@
Copyright 2015 ungleich. Copyright 2015 ungleich.
""" """
import json
import logging
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Build paths inside the project like this: os.path.join(BASE_DIR, ...) # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os import os
import json
from django.utils.translation import ugettext_lazy as _
# dotenv # dotenv
import dotenv import dotenv
from django.utils.translation import ugettext_lazy as _ import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -55,7 +56,6 @@ PROJECT_DIR = os.path.abspath(
dotenv.read_dotenv("{0}/.env".format(PROJECT_DIR)) dotenv.read_dotenv("{0}/.env".format(PROJECT_DIR))
from multisite import SiteID from multisite import SiteID
SITE_ID = SiteID(default=1) SITE_ID = SiteID(default=1)
APP_ROOT_ENDPOINT = "/" APP_ROOT_ENDPOINT = "/"
@ -179,7 +179,9 @@ ROOT_URLCONF = 'dynamicweb.urls'
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', 'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(PROJECT_DIR, 'membership'), 'DIRS': [os.path.join(PROJECT_DIR, 'cms_templates/'),
os.path.join(PROJECT_DIR, 'cms_templates/djangocms_blog/'),
os.path.join(PROJECT_DIR, 'membership'),
os.path.join(PROJECT_DIR, 'hosting/templates/'), os.path.join(PROJECT_DIR, 'hosting/templates/'),
os.path.join(PROJECT_DIR, 'nosystemd/templates/'), os.path.join(PROJECT_DIR, 'nosystemd/templates/'),
os.path.join(PROJECT_DIR, os.path.join(PROJECT_DIR,
@ -190,8 +192,6 @@ TEMPLATES = [
os.path.join(PROJECT_DIR, os.path.join(PROJECT_DIR,
'ungleich_page/templates/ungleich_page'), 'ungleich_page/templates/ungleich_page'),
os.path.join(PROJECT_DIR, 'templates/analytics'), os.path.join(PROJECT_DIR, 'templates/analytics'),
os.path.join(PROJECT_DIR, 'cms_templates/'),
os.path.join(PROJECT_DIR, 'cms_templates/djangocms_blog/'),
], ],
'APP_DIRS': True, 'APP_DIRS': True,
'OPTIONS': { 'OPTIONS': {
@ -267,10 +267,6 @@ LANGUAGES = (
LANGUAGE_CODE = 'en-us' LANGUAGE_CODE = 'en-us'
LOCALE_PATHS = [
os.path.join(PROJECT_DIR, 'digitalglarus/locale'),
]
CMS_PLACEHOLDER_CONF = { CMS_PLACEHOLDER_CONF = {
'logo_image': { 'logo_image': {
'name': 'Logo Image', 'name': 'Logo Image',
@ -356,18 +352,6 @@ CMS_PLACEHOLDER_CONF = {
}, },
] ]
}, },
'datacenterlight_calculator': {
'name': _('Datacenterlight Calculator'),
'plugins': ['DCLCalculatorPlugin'],
'default_plugins': [
{
'plugin_type': 'DCLCalculatorPlugin',
'values': {
'pricing_id': 1
},
},
]
},
} }
CMS_PERMISSION = True CMS_PERMISSION = True
@ -532,7 +516,7 @@ META_INCLUDE_KEYWORDS = ["ungleich", "hosting", "switzerland",
"Schweiz", "Swiss", "cdist"] "Schweiz", "Swiss", "cdist"]
META_USE_SITES = True META_USE_SITES = True
PARLER_LANGUAGES = {SITE_ID: ({'code': 'en-us'}, {'code': 'de'},)} PARLER_LANGUAGES = {1: ({'code': 'en-us'}, {'code': 'de'},)}
AUTH_USER_MODEL = 'membership.CustomUser' AUTH_USER_MODEL = 'membership.CustomUser'
# PAYMENT # PAYMENT
@ -580,6 +564,7 @@ MULTISITE_FALLBACK_KWARGS = {
FILER_ENABLE_PERMISSIONS = True FILER_ENABLE_PERMISSIONS = True
############################################# #############################################
# configurations for opennebula-integration # # configurations for opennebula-integration #
############################################# #############################################
@ -629,7 +614,6 @@ GOOGLE_ANALYTICS_PROPERTY_IDS = {
'ipv6onlyhosting.ch': 'UA-62285904-10', 'ipv6onlyhosting.ch': 'UA-62285904-10',
'ipv6onlyhosting.net': 'UA-62285904-10', 'ipv6onlyhosting.net': 'UA-62285904-10',
'ipv6onlyhosting.com': 'UA-62285904-10', 'ipv6onlyhosting.com': 'UA-62285904-10',
'comic.ungleich.ch': 'UA-62285904-13',
'127.0.0.1:8000': 'localhost', '127.0.0.1:8000': 'localhost',
'dynamicweb-development.ungleich.ch': 'development', 'dynamicweb-development.ungleich.ch': 'development',
'dynamicweb-staging.ungleich.ch': 'staging' 'dynamicweb-staging.ungleich.ch': 'staging'
@ -701,12 +685,6 @@ if ENABLE_LOGGING:
TEST_MANAGE_SSH_KEY_PUBKEY = env('TEST_MANAGE_SSH_KEY_PUBKEY') TEST_MANAGE_SSH_KEY_PUBKEY = env('TEST_MANAGE_SSH_KEY_PUBKEY')
TEST_MANAGE_SSH_KEY_HOST = env('TEST_MANAGE_SSH_KEY_HOST') TEST_MANAGE_SSH_KEY_HOST = env('TEST_MANAGE_SSH_KEY_HOST')
X_FRAME_OPTIONS_ALLOW_FROM_URI = env('X_FRAME_OPTIONS_ALLOW_FROM_URI')
X_FRAME_OPTIONS = ('SAMEORIGIN' if X_FRAME_OPTIONS_ALLOW_FROM_URI is None else
'ALLOW-FROM {}'.format(
X_FRAME_OPTIONS_ALLOW_FROM_URI.strip()
))
DEBUG = bool_env('DEBUG') DEBUG = bool_env('DEBUG')
if DEBUG: if DEBUG:

View file

@ -10,7 +10,6 @@ from django.conf import settings
from hosting.views import ( from hosting.views import (
RailsHostingView, DjangoHostingView, NodeJSHostingView RailsHostingView, DjangoHostingView, NodeJSHostingView
) )
from datacenterlight.views import PaymentOrderView
from membership import urls as membership_urls from membership import urls as membership_urls
from ungleich_page.views import LandingView from ungleich_page.views import LandingView
from django.views.generic import RedirectView from django.views.generic import RedirectView
@ -19,8 +18,8 @@ import debug_toolbar
urlpatterns = [ urlpatterns = [
url(r'^index.html$', LandingView.as_view()), url(r'^index.html$', LandingView.as_view()),
url(r'^open_api/', url(r'^open_api/', include('opennebula_api.urls',
include('opennebula_api.urls', namespace='opennebula_api')), namespace='opennebula_api')),
url(r'^railshosting/', RailsHostingView.as_view(), url(r'^railshosting/', RailsHostingView.as_view(),
name="rails.hosting"), name="rails.hosting"),
url(r'^nodehosting/', NodeJSHostingView.as_view(), url(r'^nodehosting/', NodeJSHostingView.as_view(),
@ -29,10 +28,8 @@ urlpatterns = [
name="django.hosting"), name="django.hosting"),
url(r'^nosystemd/', include('nosystemd.urls', namespace="nosystemd")), url(r'^nosystemd/', include('nosystemd.urls', namespace="nosystemd")),
url(r'^taggit_autosuggest/', include('taggit_autosuggest.urls')), url(r'^taggit_autosuggest/', include('taggit_autosuggest.urls')),
url(r'^jsi18n/(?P<packages>\S+?)/$', i18n.javascript_catalog), url(r'^jsi18n/(?P<packages>\S+?)/$',
url(r'^product/(?P<product_slug>[\w-]+)/$', i18n.javascript_catalog),
PaymentOrderView.as_view(),
name='show_product'),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += i18n_patterns( urlpatterns += i18n_patterns(
@ -48,23 +45,28 @@ urlpatterns += i18n_patterns(
url(r'^admin/', include(admin.site.urls)), url(r'^admin/', include(admin.site.urls)),
url(r'^datacenterlight/', url(r'^datacenterlight/',
include('datacenterlight.urls', namespace="datacenterlight")), include('datacenterlight.urls', namespace="datacenterlight")),
url(r'^hosting/', RedirectView.as_view(url=reverse_lazy('hosting:login')), url(r'^hosting/', RedirectView.as_view(
name='redirect_hosting_login'), url=reverse_lazy('hosting:login')), name='redirect_hosting_login'),
url(r'^alplora/', include('alplora.urls', namespace="alplora")), url(r'^alplora/', include('alplora.urls', namespace="alplora")),
url(r'^membership/', include(membership_urls)), url(r'^membership/', include(membership_urls)),
url(r'^digitalglarus/', url(r'^digitalglarus/', include('digitalglarus.urls',
include('digitalglarus.urls', namespace="digitalglarus")), namespace="digitalglarus")),
url(r'^cms/blog/', include('ungleich.urls', namespace='ungleich')), url(r'^cms/blog/',
url(r'^blog/(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/(?P<slug>\w[-\w]*)/$', include('ungleich.urls', namespace='ungleich')),
url(
r'^blog/(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/(?P<slug>\w[-\w]*)/$',
RedirectView.as_view(pattern_name='ungleich:post-detail')), RedirectView.as_view(pattern_name='ungleich:post-detail')),
url(r'^blog/$', url(r'^blog/$', RedirectView.as_view(
RedirectView.as_view(url=reverse_lazy('ungleich:post-list')), url=reverse_lazy('ungleich:post-list')
name='blog_list_view'), ), name='blog_list_view'
),
url(r'^cms/', include('cms.urls')), url(r'^cms/', include('cms.urls')),
url(r'^blog/', include('djangocms_blog.urls', namespace='djangocms_blog')),
url(r'^$', RedirectView.as_view(url='/cms') if REDIRECT_TO_CMS url(r'^$', RedirectView.as_view(url='/cms') if REDIRECT_TO_CMS
else LandingView.as_view()), else LandingView.as_view()),
url(r'^', include('ungleich_page.urls', namespace='ungleich_page')), url(r'^',
include('ungleich_page.urls',
namespace='ungleich_page'),
name='ungleich_page'),
) )
urlpatterns += [ urlpatterns += [

View file

@ -3,12 +3,10 @@ from django.conf.urls import include, url
from django.conf.urls.i18n import i18n_patterns from django.conf.urls.i18n import i18n_patterns
from django.contrib import admin from django.contrib import admin
from django.views import static as static_view from django.views import static as static_view
from django.views.generic import RedirectView
urlpatterns = i18n_patterns( urlpatterns = i18n_patterns(
url(r'^admin/', include(admin.site.urls)), url(r'^admin/', include(admin.site.urls)),
url(r'^cms/', include('cms.urls')), url(r'^cms/', include('cms.urls')),
url(r'^$', RedirectView.as_view(url='/cms')),
) )
urlpatterns += [ urlpatterns += [

View file

@ -1,8 +1,8 @@
from django.contrib import admin from django.contrib import admin
from .models import HostingOrder, HostingBill, HostingPlan, GenericProduct from .models import HostingOrder, HostingBill, HostingPlan
admin.site.register(HostingOrder) admin.site.register(HostingOrder)
admin.site.register(HostingBill) admin.site.register(HostingBill)
admin.site.register(HostingPlan) admin.site.register(HostingPlan)
admin.site.register(GenericProduct)

View file

@ -10,7 +10,7 @@ from django.utils.translation import ugettext_lazy as _
from membership.models import CustomUser from membership.models import CustomUser
from utils.hosting_utils import get_all_public_keys from utils.hosting_utils import get_all_public_keys
from .models import UserHostingKey, GenericProduct from .models import UserHostingKey
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -52,93 +52,6 @@ class HostingUserLoginForm(forms.Form):
raise forms.ValidationError(_("User does not exist")) raise forms.ValidationError(_("User does not exist"))
class ProductModelChoiceField(forms.ModelChoiceField):
def label_from_instance(self, obj):
return obj.product_name
class GenericPaymentForm(forms.Form):
product_name = ProductModelChoiceField(
queryset=GenericProduct.objects.all().order_by('product_name'),
empty_label=_("Choose a product"),
)
amount = forms.FloatField(
widget=forms.TextInput(
attrs={'placeholder': _('Amount in CHF'),
'readonly': 'readonly', }
),
max_value=999999,
min_value=1,
label=_('Amount in CHF')
)
recurring = forms.BooleanField(required=False,
label=_("Recurring monthly"), )
description = forms.CharField(
widget=forms.Textarea(attrs={'style': "height: 60px;"}),
required=False
)
class Meta:
model = GenericProduct
fields = ['product_name', 'amount', 'recurring', 'description']
def clean_amount(self):
amount = self.cleaned_data.get('amount')
if (float(self.cleaned_data.get('product_name').get_actual_price()) !=
amount):
raise forms.ValidationError(_("Amount field does not match"))
return amount
def clean_recurring(self):
recurring = self.cleaned_data.get('recurring')
if (self.cleaned_data.get('product_name').product_is_subscription !=
(True if recurring else False)):
raise forms.ValidationError(_("Recurring field does not match"))
return recurring
class ProductPaymentForm(GenericPaymentForm):
def __init__(self, *args, **kwargs):
product_id = kwargs.pop('product_id', None)
if product_id is not None:
self.product = GenericProduct.objects.get(id=product_id)
super(ProductPaymentForm, self).__init__(*args, **kwargs)
self.fields['product_name'] = forms.CharField(
widget=forms.TextInput(
attrs={'placeholder': _('Product name'),
'readonly': 'readonly'}
)
)
if self.product.product_is_subscription:
self.fields['amount'].label = "{amt} ({payment_type})".format(
amt=_('Amount in CHF'),
payment_type=_('Monthly subscription')
)
else:
self.fields['amount'].label = "{amt} ({payment_type})".format(
amt=_('Amount in CHF'),
payment_type=_('One time payment')
)
self.fields['recurring'].widget = forms.HiddenInput()
self.fields['product_name'].widget.attrs['class'] = 'input-no-border'
self.fields['amount'].widget.attrs['class'] = 'input-no-border'
self.fields['description'].widget.attrs['class'] = 'input-no-border'
def clean_amount(self):
amount = self.cleaned_data.get('amount')
if (self.product is None or
float(self.product.get_actual_price()) != amount):
raise forms.ValidationError(_("Amount field does not match"))
return amount
def clean_recurring(self):
recurring = self.cleaned_data.get('recurring')
if (self.product.product_is_subscription !=
(True if recurring else False)):
raise forms.ValidationError(_("Recurring field does not match"))
return recurring
class HostingUserSignupForm(forms.ModelForm): class HostingUserSignupForm(forms.ModelForm):
confirm_password = forms.CharField(label=_("Confirm Password"), confirm_password = forms.CharField(label=_("Confirm Password"),
widget=forms.PasswordInput()) widget=forms.PasswordInput())

View file

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-09-08 08:45+0000\n" "POT-Creation-Date: 2017-12-21 00:23+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"
@ -209,7 +209,7 @@ msgstr "Du hast eine neue virtuelle Maschine bestellt!"
#, python-format #, python-format
msgid "Your order of <strong>%(vm_name)s</strong> has been charged." msgid "Your order of <strong>%(vm_name)s</strong> has been charged."
msgstr "Deine Bestellung von <strong>%(vm_name)s</strong> wurde entgegengenommen." msgstr "Deine Bestellung von <strong>%(vm_name)s</strong> wurde erhoben."
msgid "You can view your VM detail by clicking the button below." msgid "You can view your VM detail by clicking the button below."
msgstr "Um die Rechnung zu sehen, klicke auf den Button unten." msgstr "Um die Rechnung zu sehen, klicke auf den Button unten."
@ -222,7 +222,7 @@ msgstr "Dein Data Center Light Team"
#, python-format #, python-format
msgid "Your order of %(vm_name)s has been charged." msgid "Your order of %(vm_name)s has been charged."
msgstr "Deine Bestellung von %(vm_name)s wurde entgegengenommen." msgstr "Deine Bestellung von %(vm_name)s wurde erhoben."
msgid "You can view your VM detail by following the link below." msgid "You can view your VM detail by following the link below."
msgstr "Um die Rechnung zu sehen, klicke auf den Link unten." msgstr "Um die Rechnung zu sehen, klicke auf den Link unten."
@ -249,7 +249,7 @@ msgstr "VM Kündigung"
#, python-format #, python-format
msgid "" msgid ""
"You are receiving this email because your virtual machine <strong>" "You are receiving this email because your virutal machine <strong>"
"%(vm_name)s</strong> has been cancelled." "%(vm_name)s</strong> has been cancelled."
msgstr "" msgstr ""
"Du erhälst diese E-Mail, da deine virtuelle Maschine <strong>%(vm_name)s</" "Du erhälst diese E-Mail, da deine virtuelle Maschine <strong>%(vm_name)s</"
@ -265,7 +265,7 @@ msgstr "NEUE VM"
#, python-format #, python-format
msgid "" msgid ""
"You are receiving this email because your virtual machine %(vm_name)s has " "You are receiving this email because your virutal machine %(vm_name)s has "
"been cancelled." "been cancelled."
msgstr "" msgstr ""
"Du erhälst diese E-Mail, da deine virtuelle Maschine %(vm_name)s gekündigt " "Du erhälst diese E-Mail, da deine virtuelle Maschine %(vm_name)s gekündigt "
@ -274,28 +274,6 @@ msgstr ""
msgid "You can always order a new VM by following the link below." msgid "You can always order a new VM by following the link below."
msgstr "" msgstr ""
msgid "Card Number"
msgstr "Kreditkartennummer"
msgid "Expiry Date"
msgstr "Ablaufdatum"
msgid "CVC"
msgstr ""
msgid "Card Type"
msgstr "Kartentyp"
msgid ""
"You are not making any payment yet. After placing your order, you will be "
"taken to the Submit Payment Page."
msgstr ""
"Es wird noch keine Bezahlung vorgenommen. Die Bezahlung wird erst ausgelöst, "
"nachdem Du die Bestellung auf der nächsten Seite bestätigt hast."
msgid "SUBMIT"
msgstr "ABSENDEN"
msgid "Toggle navigation" msgid "Toggle navigation"
msgstr "Umschalten" msgstr "Umschalten"
@ -387,24 +365,15 @@ msgstr "Arbeitsspeicher"
msgid "Disk space" msgid "Disk space"
msgstr "Festplattenkapazität" msgstr "Festplattenkapazität"
msgid "Subtotal"
msgstr "Zwischensumme"
msgid "VAT"
msgstr "Mehrwertsteuer"
msgid "Discount"
msgstr "Rabatt"
msgid "Total" msgid "Total"
msgstr "Gesamt" msgstr "Gesamt"
#, python-format #, python-format
msgid "" msgid ""
"By clicking \"Place order\" this plan will charge your credit card account " "By clicking \"Place order\" this plan will charge your credit card account "
"with %(vm_price)s CHF/month" "with the fee of %(vm_price)sCHF/month"
msgstr "" msgstr ""
"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit %(vm_price)s CHF " "Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit %(vm_price)sCHF "
"pro Monat belastet" "pro Monat belastet"
msgid "Place order" msgid "Place order"
@ -452,26 +421,9 @@ msgstr "Konfiguration"
msgid "including VAT" msgid "including VAT"
msgstr "inkl. Mehrwertsteuer" msgstr "inkl. Mehrwertsteuer"
msgid "excluding VAT"
msgstr "exkl. Mehrwertsteuer"
msgid "Will be applied at checkout"
msgstr "wird an der Kasse angewendet"
msgid "Billing Address" msgid "Billing Address"
msgstr "Rechnungsadresse" msgstr "Rechnungsadresse"
msgid ""
"Please select one of the cards that you used before or fill in your credit "
"card information below. We are using <a href=\"https://stripe.com\" target="
"\"_blank\">Stripe</a> for payment and do not store your information in our "
"database."
msgstr ""
"Bitte wähle eine der zuvor genutzten Kreditkarten oder gib Deine "
"Kreditkartendetails unten an. Die Bezahlung wird über <a href=\"https://"
"stripe.com\" target=\"_blank\">Stripe</a> abgewickelt. Wir speichern Deine "
"Kreditkartendetails nicht in unserer Datenbank."
msgid "" msgid ""
"Please fill in your credit card information below. We are using <a href=" "Please fill in your credit card information below. We are using <a href="
"\"https://stripe.com\" target=\"_blank\">Stripe</a> for payment and do not " "\"https://stripe.com\" target=\"_blank\">Stripe</a> for payment and do not "
@ -481,24 +433,28 @@ msgstr ""
"\"https://stripe.com\" target=\"_blank\">Stripe</a> für die Bezahlung und " "\"https://stripe.com\" target=\"_blank\">Stripe</a> für die Bezahlung und "
"speichern keine Informationen in unserer Datenbank." "speichern keine Informationen in unserer Datenbank."
msgid "Last" msgid ""
msgstr "Letzten" "You are not making any payment yet. After submitting your card information, "
"you will be taken to the Confirm Order Page."
msgstr ""
"Es wird noch keine Bezahlung vorgenommen. Die Bezahlung wird erst ausgelöst, "
"nachdem Du die Bestellung auf der nächsten Seite bestätigt hast."
msgid "Type" msgid "SUBMIT"
msgstr "ABSENDEN"
msgid "Card Number"
msgstr "Kreditkartennummer"
msgid "Expiry Date"
msgstr "Ablaufdatum"
msgid "CVC"
msgstr ""
msgid "Card Type"
msgstr "Kartentyp" msgstr "Kartentyp"
msgid "SELECT"
msgstr "AUSWÄHLEN"
msgid "Add a new credit card"
msgstr "Eine neue Kreditkarte hinzufügen"
msgid "NEW CARD"
msgstr "BEARBEITEN"
msgid "New Credit Card"
msgstr "Neue Kreditkarte"
msgid "Processing" msgid "Processing"
msgstr "Weiter" msgstr "Weiter"
@ -512,22 +468,13 @@ msgid "Password reset"
msgstr "Passwort zurücksetzen" msgstr "Passwort zurücksetzen"
msgid "UPDATE" msgid "UPDATE"
msgstr "AKTUALISIEREN" msgstr ""
msgid "REMOVE CARD" msgid "Last"
msgstr "KARTE ENTFERNEN" msgstr ""
msgid "Remove Card" msgid "Type"
msgstr "Karte entfernen" msgstr "Kartentyp"
msgid "Do you want to remove this associated card?"
msgstr "Möchtest Du den Schlüssel löschen?"
msgid "Delete"
msgstr "Löschen"
msgid "DEFAULT"
msgstr "STANDARD"
msgid "No Credit Cards Added" msgid "No Credit Cards Added"
msgstr "Es wurde keine Kreditkarte hinzugefügt" msgstr "Es wurde keine Kreditkarte hinzugefügt"
@ -572,7 +519,10 @@ msgid "Public Key"
msgstr "" msgstr ""
msgid "Private Key" msgid "Private Key"
msgstr "Privater Schlüssel" msgstr ""
msgid "Delete"
msgstr "Löschen"
msgid "Delete SSH Key" msgid "Delete SSH Key"
msgstr "SSH Key löschen" msgstr "SSH Key löschen"
@ -630,12 +580,6 @@ msgstr ""
"Bitte entschuldige, es scheint ein unerwarteter Fehler aufgetreten zu sein. " "Bitte entschuldige, es scheint ein unerwarteter Fehler aufgetreten zu sein. "
"Versuche es doch bitte noch einmal." "Versuche es doch bitte noch einmal."
msgid "Attention:"
msgstr "Achtung:"
msgid "terminating VM can not be reverted."
msgstr "Das Beenden kann nicht rückgängig gemacht werden."
msgid "Something doesn't work?" msgid "Something doesn't work?"
msgstr "Etwas funktioniert nicht?" msgstr "Etwas funktioniert nicht?"
@ -648,12 +592,8 @@ msgstr "KONTAKT"
msgid "Terminate your Virtual Machine" msgid "Terminate your Virtual Machine"
msgstr "Deine Virtuelle Maschine beenden" msgstr "Deine Virtuelle Maschine beenden"
msgid "" msgid "Do you want to cancel your Virtual Machine"
"Terminated VMs can not be revived and will not be refunded. Do you want to " msgstr "Bist Du sicher, dass Du Deine virtuelle Maschine beenden willst"
"terminate your VM?"
msgstr ""
"Beendete VMs können nicht wiederhergestellt oder erstattet werden. Möchtest "
"du die VM beenden?"
#, python-format #, python-format
msgid "" msgid ""
@ -715,36 +655,6 @@ msgstr "Dein Passwort konnte nicht zurückgesetzt werden."
msgid "The reset password link is no longer valid." msgid "The reset password link is no longer valid."
msgstr "Der Link zum Zurücksetzen Deines Passwortes ist nicht mehr gültig." msgstr "Der Link zum Zurücksetzen Deines Passwortes ist nicht mehr gültig."
msgid "Card deassociation successful"
msgstr "Die Verbindung mit der Karte wurde erfolgreich aufgehoben"
msgid "You are not permitted to do this operation"
msgstr "Du hast keine Erlaubnis um diese Operation durchzuführen"
msgid "The selected card does not exist"
msgstr "Die ausgewählte Karte existiert nicht"
msgid "Billing address updated successfully"
msgstr "Die Rechnungsadresse wurde erfolgreich aktualisiert"
msgid "You seem to have already added this card"
msgstr "Es scheint, als hättest du diese Karte bereits hinzugefügt"
#, python-brace-format
msgid "An error occurred while associating the card. Details: {details}"
msgstr ""
"Beim Verbinden der Karte ist ein Fehler aufgetreten. Details: {details}"
msgid "Successfully associated the card with your account"
msgstr "Die Karte wurde erfolgreich mit deinem Konto verbunden"
#, python-brace-format
msgid "{user} does not have permission to access the card"
msgstr "{user} hat keine Erlaubnis auf diese Karte zuzugreifen"
msgid "An error occurred. Details: {}"
msgstr "Ein Fehler ist aufgetreten. Details: {}"
msgid "Invalid credit card" msgid "Invalid credit card"
msgstr "Ungültige Kreditkarte" msgstr "Ungültige Kreditkarte"
@ -789,10 +699,6 @@ msgstr "Ungültige RAM-Grösse"
msgid "Invalid storage size" msgid "Invalid storage size"
msgstr "Ungültige Speicher-Grösse" msgstr "Ungültige Speicher-Grösse"
#, python-brace-format
msgid "Incorrect pricing name. Please contact support{support_email}"
msgstr ""
msgid "" msgid ""
"We could not find the requested VM. Please " "We could not find the requested VM. Please "
"contact Data Center Light Support." "contact Data Center Light Support."
@ -807,11 +713,6 @@ msgstr ""
msgid "Error terminating VM" msgid "Error terminating VM"
msgstr "Fehler beenden VM" msgstr "Fehler beenden VM"
msgid ""
"VM terminate action timed out. Please contact support@datacenterlight.ch for "
"further information."
msgstr ""
#, python-format #, python-format
msgid "Virtual Machine %(vm_name)s Cancelled" msgid "Virtual Machine %(vm_name)s Cancelled"
msgstr "Virtuelle Maschine %(vm_name)s Kündigung" msgstr "Virtuelle Maschine %(vm_name)s Kündigung"
@ -821,9 +722,6 @@ msgstr ""
"Es gab einen Fehler bei der Bearbeitung Deine Anfrage. Bitte versuche es " "Es gab einen Fehler bei der Bearbeitung Deine Anfrage. Bitte versuche es "
"noch einmal." "noch einmal."
#~ msgid "Do you want to cancel your Virtual Machine"
#~ msgstr "Bist Du sicher, dass Du Deine virtuelle Maschine beenden willst"
#~ msgid "Reset your password" #~ msgid "Reset your password"
#~ msgstr "Passwort zurücksetzen" #~ msgstr "Passwort zurücksetzen"
@ -890,6 +788,15 @@ msgstr ""
#~ msgid "Notifications " #~ msgid "Notifications "
#~ msgstr "Benachrichtigungen" #~ msgstr "Benachrichtigungen"
#~ msgid "REMOVE CARD"
#~ msgstr "KARTE ENTFERNEN"
#~ msgid "EDIT CARD"
#~ msgstr "BEARBEITEN"
#~ msgid "Add a new Card."
#~ msgstr "Neue Kreditkarte hinzufügen."
#~ msgid "You are not making any payment here." #~ msgid "You are not making any payment here."
#~ msgstr "Es wird noch keine Bezahlung vorgenommen" #~ msgstr "Es wird noch keine Bezahlung vorgenommen"

View file

@ -1,45 +0,0 @@
from django.core.management.base import BaseCommand
from hosting.models import UserCardDetail
from membership.models import CustomUser
from utils.stripe_utils import StripeUtils
class Command(BaseCommand):
help = '''Imports the usercard details of all customers. Created just for
multiple card support.'''
def handle(self, *args, **options):
try:
stripe_utils = StripeUtils()
for user in CustomUser.objects.all():
if hasattr(user, 'stripecustomer'):
if user.stripecustomer:
card_details_resp = stripe_utils.get_card_details(
user.stripecustomer.stripe_id
)
card_details = card_details_resp['response_object']
if card_details:
ucd = UserCardDetail.get_or_create_user_card_detail(
stripe_customer=user.stripecustomer,
card_details=card_details
)
UserCardDetail.save_default_card_local(
user.stripecustomer.stripe_id,
ucd.card_id
)
print("Saved user card details for {}".format(
user.email
))
else:
print(" --- Could not get card details for "
"{}".format(user.email))
print(" --- Error: {}".format(
card_details_resp['error']
))
else:
print(" === {} does not have a StripeCustomer object".format(
user.email
))
except Exception as e:
print(" *** Error occurred. Details {}".format(str(e)))

View file

@ -1,23 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2018-04-16 00:22
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('datacenterlight', '0019_auto_20180415_2236'),
('hosting', '0043_vmdetail'),
]
operations = [
migrations.AddField(
model_name='hostingorder',
name='vm_pricing',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='datacenterlight.VMPricing'),
preserve_default=False,
),
]

View file

@ -1,35 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2018-07-01 20:28
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import utils.mixins
class Migration(migrations.Migration):
dependencies = [
('datacenterlight', '0024_dclcalculatorpluginmodel_vm_templates_to_show'),
('hosting', '0044_hostingorder_vm_pricing'),
]
operations = [
migrations.CreateModel(
name='OrderDetail',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('cores', models.IntegerField(default=0)),
('memory', models.IntegerField(default=0)),
('hdd_size', models.IntegerField(default=0)),
('ssd_size', models.IntegerField(default=0)),
('vm_template', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='datacenterlight.VMTemplate')),
],
bases=(utils.mixins.AssignPermissionsMixin, models.Model),
),
migrations.AddField(
model_name='hostingorder',
name='order_detail',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='hosting.OrderDetail'),
),
]

View file

@ -1,36 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2018-07-03 20:32
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import utils.mixins
class Migration(migrations.Migration):
dependencies = [
('membership', '0007_auto_20180213_0128'),
('hosting', '0045_auto_20180701_2028'),
]
operations = [
migrations.CreateModel(
name='UserCardDetail',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('last4', models.CharField(max_length=4)),
('brand', models.CharField(max_length=10)),
('card_id', models.CharField(blank=True, default='', max_length=100)),
('fingerprint', models.CharField(max_length=100)),
('exp_month', models.IntegerField()),
('exp_year', models.IntegerField()),
('preferred', models.BooleanField(default=False)),
('stripe_customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='membership.StripeCustomer')),
],
options={
'permissions': (('view_usercarddetail', 'View User Card'),),
},
bases=(utils.mixins.AssignPermissionsMixin, models.Model),
),
]

View file

@ -1,25 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2018-08-21 12:40
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hosting', '0046_usercarddetail'),
]
operations = [
migrations.AlterField(
model_name='hostingorder',
name='cc_brand',
field=models.CharField(max_length=128),
),
migrations.AlterField(
model_name='usercarddetail',
name='brand',
field=models.CharField(max_length=128),
),
]

View file

@ -1,41 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2018-10-03 07:57
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import utils.mixins
class Migration(migrations.Migration):
dependencies = [
('hosting', '0047_auto_20180821_1240'),
]
operations = [
migrations.CreateModel(
name='GenericProduct',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('product_name', models.CharField(default='', max_length=128)),
('product_slug', models.SlugField(help_text='An optional html id for the Section. Required to set as target of a link on page', unique=True)),
('product_description', models.CharField(default='', max_length=500)),
('created_at', models.DateTimeField(auto_now_add=True)),
('product_price', models.DecimalField(decimal_places=2, max_digits=6)),
('product_vat', models.DecimalField(decimal_places=4, default=0, max_digits=6)),
('product_is_subscription', models.BooleanField(default=True)),
],
bases=(utils.mixins.AssignPermissionsMixin, models.Model),
),
migrations.AddField(
model_name='hostingorder',
name='generic_payment_description',
field=models.CharField(max_length=500, null=True),
),
migrations.AddField(
model_name='hostingorder',
name='generic_product',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='hosting.GenericProduct'),
),
]

View file

@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2018-10-05 07:36
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hosting', '0048_auto_20181003_0757'),
]
operations = [
migrations.AlterField(
model_name='genericproduct',
name='product_slug',
field=models.SlugField(help_text='An mandatory unique slug for the product', unique=True),
),
]

View file

@ -1,5 +1,4 @@
from django.shortcuts import redirect from django.shortcuts import redirect
from django.conf import settings
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from opennebula_api.serializers import VirtualMachineTemplateSerializer from opennebula_api.serializers import VirtualMachineTemplateSerializer
@ -25,10 +24,3 @@ class ProcessVMSelectionMixin(object):
request.session['next'] = reverse('hosting:payment') request.session['next'] = reverse('hosting:payment')
return redirect(reverse('hosting:login')) return redirect(reverse('hosting:login'))
return redirect(reverse('hosting:payment')) return redirect(reverse('hosting:payment'))
class HostingContextMixin(object):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['MULTISITE_CMS_FALLBACK'] = settings.MULTISITE_CMS_FALLBACK
return context

View file

@ -1,17 +1,14 @@
import logging
import os import os
import logging
from Crypto.PublicKey import RSA
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from django.db import models from django.db import models
from django.utils import timezone from django.utils import timezone
from django.utils.functional import cached_property from django.utils.functional import cached_property
from Crypto.PublicKey import RSA
from datacenterlight.models import VMPricing, VMTemplate
from membership.models import StripeCustomer, CustomUser from membership.models import StripeCustomer, CustomUser
from utils.mixins import AssignPermissionsMixin
from utils.models import BillingAddress from utils.models import BillingAddress
from utils.stripe_utils import StripeUtils from utils.mixins import AssignPermissionsMixin
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -42,49 +39,6 @@ class HostingPlan(models.Model):
return price return price
class OrderDetail(AssignPermissionsMixin, models.Model):
vm_template = models.ForeignKey(
VMTemplate, blank=True, null=True, default=None,
on_delete=models.SET_NULL
)
cores = models.IntegerField(default=0)
memory = models.IntegerField(default=0)
hdd_size = models.IntegerField(default=0)
ssd_size = models.IntegerField(default=0)
def __str__(self):
return "Not available" if self.vm_template is None else (
"%s - %s, %s cores, %s GB RAM, %s GB SSD" % (
self.vm_template.name, self.vm_template.vm_type, self.cores,
self.memory, self.ssd_size
)
)
class GenericProduct(AssignPermissionsMixin, models.Model):
permissions = ('view_genericproduct',)
product_name = models.CharField(max_length=128, default="")
product_slug = models.SlugField(
unique=True,
help_text=(
'An mandatory unique slug for the product'
)
)
product_description = models.CharField(max_length=500, default="")
created_at = models.DateTimeField(auto_now_add=True)
product_price = models.DecimalField(max_digits=6, decimal_places=2)
product_vat = models.DecimalField(max_digits=6, decimal_places=4, default=0)
product_is_subscription = models.BooleanField(default=True)
def __str__(self):
return self.product_name
def get_actual_price(self):
return round(
self.product_price + (self.product_price * self.product_vat), 2
)
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'
@ -95,22 +49,11 @@ class HostingOrder(AssignPermissionsMixin, models.Model):
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
approved = models.BooleanField(default=False) approved = models.BooleanField(default=False)
last4 = models.CharField(max_length=4) last4 = models.CharField(max_length=4)
cc_brand = models.CharField(max_length=128) cc_brand = models.CharField(max_length=10)
stripe_charge_id = models.CharField(max_length=100, null=True) stripe_charge_id = models.CharField(max_length=100, null=True)
price = models.FloatField() price = models.FloatField()
subscription_id = models.CharField(max_length=100, null=True) subscription_id = models.CharField(max_length=100, null=True)
vm_pricing = models.ForeignKey(VMPricing)
order_detail = models.ForeignKey(
OrderDetail, null=True, blank=True, default=None,
on_delete=models.SET_NULL
)
generic_product = models.ForeignKey(
GenericProduct, null=True, blank=True, default=None,
on_delete=models.SET_NULL
)
generic_payment_description = models.CharField(
max_length=500, null=True
)
permissions = ('view_hostingorder',) permissions = ('view_hostingorder',)
class Meta: class Meta:
@ -119,32 +62,20 @@ class HostingOrder(AssignPermissionsMixin, models.Model):
) )
def __str__(self): def __str__(self):
hosting_order_str = ("Order Nr: #{} - VM_ID: {} - {} - {} - " return "%s" % (self.id)
"Specs: {} - Price: {}").format(
self.id, self.vm_id, self.customer.user.email, self.created_at,
self.order_detail, self.price
)
if self.generic_product_id is not None:
hosting_order_str += " - Generic Payment"
if self.stripe_charge_id is not None:
hosting_order_str += " - One time charge"
else:
hosting_order_str += " - Recurring"
return hosting_order_str
@cached_property @cached_property
def status(self): def status(self):
return self.ORDER_APPROVED_STATUS if self.approved else self.ORDER_DECLINED_STATUS return self.ORDER_APPROVED_STATUS if self.approved else self.ORDER_DECLINED_STATUS
@classmethod @classmethod
def create(cls, price=None, vm_id=0, customer=None, def create(cls, price=None, vm_id=None, customer=None,
billing_address=None, vm_pricing=None): billing_address=None):
instance = cls.objects.create( instance = cls.objects.create(
price=price, price=price,
vm_id=vm_id, vm_id=vm_id,
customer=customer, customer=customer,
billing_address=billing_address, billing_address=billing_address
vm_pricing=vm_pricing
) )
instance.assign_permissions(customer.user) instance.assign_permissions(customer.user)
return instance return instance
@ -249,156 +180,3 @@ class VMDetail(models.Model):
months = relativedelta(end_date, self.created_at).months or 1 months = relativedelta(end_date, self.created_at).months or 1
end_date = self.created_at + relativedelta(months=months, days=-1) end_date = self.created_at + relativedelta(months=months, days=-1)
return end_date return end_date
class UserCardDetail(AssignPermissionsMixin, models.Model):
permissions = ('view_usercarddetail',)
stripe_customer = models.ForeignKey(StripeCustomer)
last4 = models.CharField(max_length=4)
brand = models.CharField(max_length=128)
card_id = models.CharField(max_length=100, blank=True, default='')
fingerprint = models.CharField(max_length=100)
exp_month = models.IntegerField(null=False)
exp_year = models.IntegerField(null=False)
preferred = models.BooleanField(default=False)
class Meta:
permissions = (
('view_usercarddetail', 'View User Card'),
)
@classmethod
def create(cls, stripe_customer=None, last4=None, brand=None,
fingerprint=None, exp_month=None, exp_year=None, card_id=None,
preferred=False):
instance = cls.objects.create(
stripe_customer=stripe_customer, last4=last4, brand=brand,
fingerprint=fingerprint, exp_month=exp_month, exp_year=exp_year,
card_id=card_id, preferred=preferred
)
instance.assign_permissions(stripe_customer.user)
return instance
@classmethod
def get_all_cards_list(cls, stripe_customer):
"""
Get all the cards of the given customer as a list
:param stripe_customer: The StripeCustomer object
:return: A list of all cards; an empty list if the customer object is
None
"""
cards_list = []
if stripe_customer is None:
return cards_list
user_card_details = UserCardDetail.objects.filter(
stripe_customer_id=stripe_customer.id
).order_by('-preferred', 'id')
for card in user_card_details:
cards_list.append({
'last4': card.last4, 'brand': card.brand, 'id': card.id,
'preferred': card.preferred
})
return cards_list
@classmethod
def get_or_create_user_card_detail(cls, stripe_customer, card_details):
"""
A method that checks if a UserCardDetail object exists already
based upon the given card_details and creates it for the given
customer if it does not exist. It returns the UserCardDetail object
matching the given card_details if it exists.
:param stripe_customer: The given StripeCustomer object to whom the
card object should belong to
:param card_details: A dictionary identifying a given card
:return: UserCardDetail object
"""
try:
if ('fingerprint' in card_details and 'exp_month' in card_details
and 'exp_year' in card_details):
card_detail = UserCardDetail.objects.get(
stripe_customer=stripe_customer,
fingerprint=card_details['fingerprint'],
exp_month=card_details['exp_month'],
exp_year=card_details['exp_year']
)
else:
raise UserCardDetail.DoesNotExist()
except UserCardDetail.DoesNotExist:
preferred = False
if 'preferred' in card_details:
preferred = card_details['preferred']
card_detail = UserCardDetail.create(
stripe_customer=stripe_customer,
last4=card_details['last4'],
brand=card_details['brand'],
fingerprint=card_details['fingerprint'],
exp_month=card_details['exp_month'],
exp_year=card_details['exp_year'],
card_id=card_details['card_id'],
preferred=preferred
)
return card_detail
@staticmethod
def set_default_card(stripe_api_cus_id, stripe_source_id):
"""
Sets the given stripe source as the default source for the given
Stripe customer
:param stripe_api_cus_id: Stripe customer id
:param stripe_source_id: The Stripe source id
:return:
"""
stripe_utils = StripeUtils()
cus_response = stripe_utils.get_customer(stripe_api_cus_id)
cu = cus_response['response_object']
cu.default_source = stripe_source_id
cu.save()
UserCardDetail.save_default_card_local(
stripe_api_cus_id, stripe_source_id
)
@staticmethod
def set_default_card_from_stripe(stripe_api_cus_id):
stripe_utils = StripeUtils()
cus_response = stripe_utils.get_customer(stripe_api_cus_id)
cu = cus_response['response_object']
default_source = cu.default_source
if default_source is not None:
UserCardDetail.save_default_card_local(
stripe_api_cus_id, default_source
)
@staticmethod
def save_default_card_local(stripe_api_cus_id, card_id):
stripe_cust = StripeCustomer.objects.get(stripe_id=stripe_api_cus_id)
user_card_detail = UserCardDetail.objects.get(
stripe_customer=stripe_cust, card_id=card_id
)
for card in stripe_cust.usercarddetail_set.all():
card.preferred = False
card.save()
user_card_detail.preferred = True
user_card_detail.save()
@staticmethod
def get_user_card_details(stripe_customer, card_details):
"""
A utility function to check whether a StripeCustomer is already
associated with the card having given details
:param stripe_customer:
:param card_details:
:return: The UserCardDetails object if it exists, None otherwise
"""
try:
ucd = UserCardDetail.objects.get(
stripe_customer=stripe_customer,
fingerprint=card_details['fingerprint'],
exp_month=card_details['exp_month'],
exp_year=card_details['exp_year']
)
return ucd
except UserCardDetail.DoesNotExist:
return None

View file

@ -23,13 +23,6 @@
margin: 0 auto; margin: 0 auto;
max-width: 1120px; max-width: 1120px;
} }
.container-table{
margin-top: 35px;
overflow-y: hidden;
}
.container-table table{
overflow-y: auto;
}
.borderless td { .borderless td {
border: none !important; border: none !important;
} }
@ -42,19 +35,6 @@
color: transparent; color: transparent;
} }
.inline-headers h3, .inline-headers h4 {
display: inline-block;
vertical-align: baseline;
}
.space-above {
margin-top: 4%;
}
.space-above-big {
margin-top: 20%;
}
.table>tbody>tr>td{ .table>tbody>tr>td{
vertical-align: middle; vertical-align: middle;
} }
@ -294,26 +274,6 @@
font-size: 16px; font-size: 16px;
} }
.payment-container .credit-card-info {
padding-bottom: 15px;
border-bottom: 1px solid #eee;
}
.credit-card-info {
display: flex;
}
.credit-card-info .align-bottom{
align-self: flex-end;
padding-right: 0 !important;
}
.another-card-text {
padding: 20px 0;
font-size: 18px;
font-weight: 700;
}
.credit-card-form { .credit-card-form {
max-width: 360px; max-width: 360px;
} }
@ -333,15 +293,8 @@
text-decoration: none; text-decoration: none;
} }
.settings-container .new-card-head { .settings-container .credit-card-details-opt {
margin-top: 40px; padding-top: 15px;
margin-bottom: 30px;
}
.settings-container .new-card-head h4 {
font-size: 15px;
margin-top: 8px;
font-weight: 600;
} }
.caps-link .svg-img { .caps-link .svg-img {
@ -360,11 +313,7 @@
.settings-container .btn-vm-contact { .settings-container .btn-vm-contact {
font-weight: 600; font-weight: 600;
font-size: 13px; font-size: 13px;
} /* padding: 4px 15px; */
.settings-container .choice-btn {
letter-spacing: 2px;
min-width: 127px;
} }
.btn-wide { .btn-wide {
@ -406,15 +355,6 @@
fill: #999; fill: #999;
} }
.card-details-box {
border: 1px solid #eee;
padding: 5px 25px 25px;
}
.thick-hr {
border-top: 5px solid #eee;
}
.locale_date { .locale_date {
opacity: 0; opacity: 0;
} }
@ -422,21 +362,3 @@
.locale_date.done{ .locale_date.done{
opacity: 1; opacity: 1;
} }
.mb-0 {
margin-bottom: 0;
}
.thin-hr {
margin-top: 10px;
margin-bottom: 10px;
}
.new-card-head {
margin-top: 10px;
}
.new-card-button-margin button{
margin-top: 5px;
margin-bottom: 5px;
}

View file

@ -449,6 +449,230 @@ a.unlink:hover {
color: inherit; color: inherit;
} }
/***** DCL payment page **********/
.dcl-order-container {
font-weight: 300;
}
.dcl-order-table-header {
border-bottom: 1px solid #eee;
padding-top: 15px;
padding-bottom: 15px;
font-size: 16px;
color: #333;
text-align: center;
font-weight: 300;
}
.dcl-order-table-content {
border-bottom: 1px solid #eee;
padding-top: 15px;
padding-bottom: 15px;
font-size: 18px;
font-weight: 600;
text-align: center;
}
.tbl-content {
}
.dcl-order-table-total {
border-bottom: 4px solid #eee;
padding-top: 15px;
padding-bottom: 20px;
font-size: 20px;
font-weight: 600;
color: #999;
}
.dcl-order-table-total span {
font-size: 13px;
color: #999;
font-weight: 400;
padding-left: 5px;
}
.dcl-place-order-text{
color: #808080;
}
.dcl-order-table-total .tbl-total {
text-align: center;
color: #000;
padding-left: 44px;
}
.tbl-total .dcl-price-month {
font-size: 16px;
text-transform: capitalize;
color: #000;
}
.tbl-no-padding {
padding: 0px;
}
.dcl-billing-sec {
margin-top: 50px;
}
.dcl-order-sec {
padding: 0 30px;
}
.card-warning-content {
font-weight: 300;
border: 1px solid #a1a1a1;
border-radius: 3px;
padding: 5px;
margin-bottom: 15px;
}
.card-warning-error {
border: 1px solid #EB4D5C;
color: #EB4D5C;
}
.card-warning-addtional-margin {
margin-top: 15px;
}
.stripe-payment-btn {
outline: none;
width: auto;
float: right;
font-style: normal;
font-weight: 300;
position: absolute;
padding-left: 30px;
padding-right: 30px;
right: 0;
}
.card-cvc-element label {
padding-left: 10px;
}
.card-element {
margin-bottom: 10px;
}
.card-element label{
width:100%;
margin-bottom:0px;
}
.my-input {
border-bottom: 1px solid #ccc;
}
.card-cvc-element .my-input {
padding-left: 10px;
}
#card-errors {
clear: both;
padding: 0 0 10px;
color: #eb4d5c;
}
.credit-card-goup{
padding: 0;
}
@media (max-width: 767px) {
.dcl-order-table-total span {
padding-left: 3px;
}
.dcl-order-sec {
padding: 10px 20px 30px 20px;
border-bottom: 4px solid #eee;
}
.tbl-header {
border-bottom: 1px solid #eee;
padding: 10px 0;
}
.tbl-content {
border-bottom: 1px solid #eee;
padding: 10px 0;
}
.dcl-order-table-header {
border-bottom: 0px solid #eee;
padding: 10px 0;
text-align: left;
}
.dcl-order-table-content {
border-bottom: 0px solid #eee;
padding: 10px 0;
text-align: right;
font-size: 16px;
}
.dcl-order-table-total {
font-size: 18px;
color: #000;
padding: 10px 0;
border-bottom: 0px solid #eee;
}
.dcl-order-table-total .tbl-total {
padding: 0px;
text-align: right;
}
.dcl-billing-sec {
margin-top: 30px;
margin-bottom: 30px;
}
.card-expiry-element {
padding-right: 10px;
}
.card-cvc-element {
padding-left: 10px;
}
#billing-form .form-control {
box-shadow: none !important;
font-weight: 400;
}
}
@media (min-width: 1200px) {
.dcl-order-container {
width: 990px;
padding-right: 15px;
padding-left: 15px;
margin-right: auto;
margin-left: auto;
}
}
@media (min-width: 768px) {
.dcl-billing {
padding-right: 65px;
border-right: 1px solid #eee;
}
.dcl-creditcard {
padding-left: 65px;
}
.tbl-tot {
padding-left: 17px;
}
.content-dashboard {
/*width: auto !important;*/
}
}
@media only screen and (max-width: 1040px) and (min-width: 768px) { @media only screen and (max-width: 1040px) and (min-width: 768px) {
.content-dashboard { .content-dashboard {
width: 96% !important; width: 96% !important;

View file

@ -3,7 +3,6 @@
margin: 100px auto 40px; margin: 100px auto 40px;
border: 1px solid #ccc; border: 1px solid #ccc;
padding: 15px; padding: 15px;
color: #595959;
} }
@media(min-width: 768px) { @media(min-width: 768px) {
@ -49,6 +48,10 @@
margin-bottom: 15px; margin-bottom: 15px;
} }
.order-detail-container .order-details strong {
color: #595959;
}
.order-detail-container h4 { .order-detail-container h4 {
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;
@ -57,28 +60,13 @@
.order-detail-container p { .order-detail-container p {
margin-bottom: 5px; margin-bottom: 5px;
color: #595959;
} }
.order-detail-container hr { .order-detail-container hr {
margin: 15px 0; margin: 15px 0;
} }
.order-detail-container .thin-hr {
margin: 10px 0;
}
.order-detail-container .subtotal-price {
font-size: 16px;
}
.order-detail-container .subtotal-price .text-primary {
font-size: 17px;
}
.order-detail-container .total-price {
font-size: 18px;
}
@media (max-width: 767px) { @media (max-width: 767px) {
.order-confirm-btn { .order-confirm-btn {
text-align: center; text-align: center;
@ -109,7 +97,3 @@
#virtual_machine_create_form { #virtual_machine_create_form {
padding: 15px 0; padding: 15px 0;
} }
.dcl-place-order-text {
color: #808080;
}

View file

@ -1,35 +1,19 @@
.payment-container {
padding-top: 70px;
padding-bottom: 11%;
}
.creditcard-box .panel-title {
display: inline;
font-weight: bold;
font-size: 17px;
}
.creditcard-box .checkbox.pull-right {
margin: 0;
}
.creditcard-box .pl-ziro {
padding-left: 0px;
}
.payment-container {padding-top:70px; padding-bottom: 11%;}
.creditcard-box .panel-title {display: inline;font-weight: bold; font-size:17px;}
.creditcard-box .checkbox.pull-right { margin: 0; }
.creditcard-box .pl-ziro { padding-left: 0px; }
.creditcard-box .form-control.error { .creditcard-box .form-control.error {
border-color: red; border-color: red;
outline: 0; outline: 0;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(255, 0, 0, 0.6); box-shadow: inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(255,0,0,0.6);
} }
.creditcard-box label.error { .creditcard-box label.error {
font-weight: bold; font-weight: bold;
color: red; color: red;
padding: 2px 8px; padding: 2px 8px;
margin-top: 2px; margin-top: 2px;
} }
.creditcard-box .payment-errors { .creditcard-box .payment-errors {
font-weight: bold; font-weight: bold;
color: red; color: red;
@ -37,221 +21,96 @@
margin-top: 2px; margin-top: 2px;
} }
.dcl-order-sec { /* landing page payment new style */
padding: 0 30px; .last-p {
margin-bottom: 0;
} }
.dcl-payment-section {
.dcl-billing-sec { max-width: 391px;
margin-top: 50px; margin: 0 auto 30px;
padding: 0 10px 30px;
border-bottom: 1px solid #edebeb;
height: 100%;
} }
.dcl-payment-section hr{
.dcl-order-container { margin-top: 15px;
font-weight: 300;
}
.dcl-order-table-header {
border-bottom: 1px solid #eee;
padding: 15px 10px;
font-size: 16px;
color: #333;
font-weight: 300;
}
.dcl-order-table-content {
border-bottom: 1px solid #eee;
padding: 15px 10px;
font-size: 18px;
font-weight: 600;
}
.dcl-order-table-total {
border-bottom: 4px solid #eee;
padding-top: 15px;
padding-bottom: 20px;
font-size: 20px;
font-weight: 600;
}
.dcl-order-table-total span {
font-size: 13px;
color: #999;
font-weight: 400;
}
.dcl-order-table-total .tbl-total {
text-align: right;
color: #000;
}
.tbl-no-padding {
padding: 0px;
}
.card-warning-content {
font-weight: 300;
border: 1px solid #a1a1a1;
border-radius: 3px;
padding: 5px;
margin-bottom: 15px; margin-bottom: 15px;
} }
.dcl-payment-section .top-hr {
.card-warning-error { margin-left: -10px;
border: 1px solid #EB4D5C;
color: #EB4D5C;
} }
.dcl-payment-section h3 {
.card-warning-addtional-margin { font-weight: 600;
margin-top: 15px;
} }
.dcl-payment-section p {
.stripe-payment-btn { /*padding: 0 5px;*/
outline: none; font-weight: 400;
width: auto; }
float: right; .dcl-payment-section .card-warning-content {
font-style: normal; padding: 8px 10px;
font-weight: 300; font-weight: 300;
position: absolute;
padding-left: 30px;
padding-right: 30px;
right: 0;
} }
.dcl-payment-order strong{
.card-cvc-element label { font-size: 17px;
padding-left: 10px;
} }
.dcl-payment-order p {
.card-element { font-weight: 300;
}
.dcl-payment-section .form-group {
margin-bottom: 10px; margin-bottom: 10px;
} }
.dcl-payment-section .form-control {
.card-element label { box-shadow: none;
width: 100%; padding: 6px 12px;
margin-bottom: 0px; height: 32px;
}
.dcl-payment-user {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
} }
.my-input { .dcl-payment-user h4 {
border-bottom: 1px solid #ccc; font-weight: 600;
} font-size: 17px;
.card-cvc-element .my-input {
padding-left: 10px;
}
#card-errors {
clear: both;
padding: 0 0 10px;
color: #eb4d5c;
}
.credit-card-goup {
padding: 0;
}
@media (max-width: 767px) {
.dcl-order-sec {
padding: 10px 5px 30px;
border-bottom: 4px solid #eee;
}
.dcl-billing-sec {
margin-top: 30px;
margin-bottom: 30px;
padding: 5px;
}
.dcl-billing {
margin-top: 20px;
margin-bottom: 40px;
}
.tbl-header {
border-bottom: 1px solid #eee;
padding-top: 10px;
padding-bottom: 10px;
margin-right: -15px;
}
.dcl-order-table-total .tbl-total {
margin-left: -15px;
}
.dcl-order-table-total .tbl-tot {
margin-right: -15px;
}
.tbl-content {
border-bottom: 1px solid #eee;
padding-top: 10px;
padding-bottom: 10px;
margin-left: -15px;
}
.dcl-order-table-header {
border-bottom: 0px solid #eee;
padding: 10px 0;
text-align: left;
}
.dcl-order-table-content {
border-bottom: 0px solid #eee;
padding: 10px 0;
text-align: right;
font-size: 16px;
}
.dcl-order-table-total {
font-size: 18px;
color: #000;
padding: 10px 0;
border-bottom: 0px solid #eee;
}
.card-expiry-element {
padding-right: 10px;
}
.card-cvc-element {
padding-left: 10px;
}
#billing-form .form-control {
box-shadow: none !important;
font-weight: 400;
}
} }
@media (min-width: 768px) { @media (min-width: 768px) {
.dcl-billing { .dcl-payment-grid {
padding-right: 65px; display: flex;
border-right: 1px solid #eee; align-items: stretch;
flex-wrap: wrap;
} }
.dcl-payment-box {
.dcl-creditcard { width: 50%;
padding-left: 65px; position: relative;
padding: 0 30px;
} }
.dcl-payment-box:nth-child(2) {
.dcl-order-table-total .tbl-total, order: 1;
.dcl-order-table-total .tbl-tot {
padding: 0 10px;
} }
.dcl-payment-box:nth-child(4) {
.tbl-header-center, order: 2;
.tbl-content-center {
text-align: center;
} }
.dcl-payment-section {
.tbl-header-right, padding: 15px 10px;
.tbl-content-right { margin-bottom: 0;
text-align: right; border-bottom-width: 5px;
} }
} .dcl-payment-box:nth-child(2n) .dcl-payment-section {
border-bottom: none;
@media (min-width: 1200px) { }
.dcl-order-container { .dcl-payment-box:nth-child(1):after,
width: 990px; .dcl-payment-box:nth-child(2):after {
padding-right: 15px; content: ' ';
padding-left: 15px; display: block;
margin-right: auto; background: #eee;
margin-left: auto; width: 1px;
position: absolute;
right: 0;
z-index: 2;
top: 20px;
bottom: 20px;
} }
} }

View file

@ -1,9 +1,7 @@
/* Create VM calculator */ /* Create VM calculator */
.price-calc-section { .price-calc-section {
padding: 20px 0 !important; padding: 80px 40px !important;
font-weight: 300;
font-size: 18px;
} }
@media (max-width: 768px) { @media (max-width: 768px) {
@ -42,19 +40,19 @@
} }
.price-calc-section .card { .price-calc-section .card {
border-radius: 7px; width: 50%;
margin: 0 auto; margin: 0 auto;
background: #fff; background: #fff;
box-shadow: 1px 3px 6px 2px rgba(0, 0, 0, 0.2); box-shadow: 1px 3px 6px 2px rgba(0, 0, 0, 0.2);
padding-bottom: 30px; padding-bottom: 30px;
text-align: center; text-align: center;
max-width: 4000px; max-width: 320px;
position: relative; position: relative;
} }
@media (min-width: 768px) { @media (min-width: 768px) {
.price-calc-section .card { .price-calc-section .card {
/* margin-left: 0; */ margin-left: 0;
} }
} }
@ -87,7 +85,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,7 +93,7 @@
} }
.price-calc-section .card .description span { .price-calc-section .card .description span {
font-size: 16px; font-size: 14px;
margin-left: 5px; margin-left: 5px;
/* margin-left: 0px; */ /* margin-left: 0px; */
/* justify-self: start; */ /* justify-self: start; */
@ -106,18 +104,17 @@
} }
.price-calc-section .card .description .select-number{ .price-calc-section .card .description .select-number{
font-size: 18px; font-size: 16px;
text-align: center; text-align: center;
width: 85px; width: 85px;
padding: 5px 10px;
} }
.price-calc-section .card .description i { .price-calc-section .card .description i {
color: #29427a; color: #29427a;
cursor: pointer; cursor: pointer;
font-size: 20px; font-size: 20px;
/* border: 1px solid #ccc; */ border: 1px solid #ccc;
/* padding: 5px 6px 3px; */ padding: 5px 6px 3px;
border-radius: 5px; border-radius: 5px;
} }
@ -196,7 +193,7 @@
.price-calc-section .help-block.with-errors { .price-calc-section .help-block.with-errors {
text-align: center; text-align: center;
margin: 0 0; margin: 0 0;
padding: 0 0; padding: 0 0 5px;
} }
.price-calc-section .help-block.with-errors ul { .price-calc-section .help-block.with-errors ul {
margin-bottom: 0; margin-bottom: 0;
@ -212,10 +209,10 @@
display: block; display: block;
position: absolute; position: absolute;
bottom: 0; bottom: 0;
left: 0; left: 18%;
z-index: 20; z-index: 20;
height: 1px; height: 1px;
width: 100%; width: 65%;
background: rgba(128, 128, 128, 0.2); background: rgba(128, 128, 128, 0.2);
} }

View file

@ -146,13 +146,9 @@
text-align: center; text-align: center;
} }
.vm-vmid-with-warning {
padding: 50px 0 33px !important;
}
.vm-vmid .alert { .vm-vmid .alert {
margin-top: 15px; margin-top: 15px;
margin-bottom: -25px; margin-bottom: -60px;
} }
.vm-item-lg { .vm-item-lg {
@ -187,13 +183,6 @@
margin-top: 25px; margin-top: 25px;
} }
.vm-terminate-warning {
letter-spacing: 0.6px;
font-size: 12px;
font-weight: 400;
color: #373636;
}
.vm-contact-us { .vm-contact-us {
margin: 25px 0 30px; margin: 25px 0 30px;
/* text-align: center; */ /* text-align: center; */
@ -280,7 +269,7 @@
border: 2px solid #A3C0E2; border: 2px solid #A3C0E2;
padding: 5px 25px; padding: 5px 25px;
font-size: 12px; font-size: 12px;
letter-spacing: 2px; letter-spacing: 1.3px;
} }
.btn-vm-contact:hover, .btn-vm-contact:focus { .btn-vm-contact:hover, .btn-vm-contact:focus {
background: #fff; background: #fff;

View file

@ -153,125 +153,4 @@ $( document ).ready(function() {
$('.navbar-fixed-top.topnav').css('padding-right', topnavPadding-scrollbarWidth); $('.navbar-fixed-top.topnav').css('padding-right', topnavPadding-scrollbarWidth);
} }
}); });
/* ---------------------------------------------
Scripts initialization
--------------------------------------------- */
var minRam = 1;
if(window.minRam){
minRam = window.minRam;
}
var cardPricing = {
'cpu': {
'id': 'coreValue',
'value': 1,
'min': 1,
'max': 48,
'interval': 1
},
'ram': {
'id': 'ramValue',
'value': 2,
'min': minRam,
'max': 200,
'interval': 1
},
'storage': {
'id': 'storageValue',
'value': 10,
'min': 10,
'max': 2000,
'interval': 10
}
};
function _initPricing() {
_fetchPricing();
$('.fa-minus-circle.left').click(function(event) {
var data = $(this).data('minus');
if (cardPricing[data].value > cardPricing[data].min) {
if(data === 'ram' && String(cardPricing[data].value) === "1" && minRam === 0.5){
cardPricing[data].value = 0.5;
$('#ramValue').val('0.5');
$("#ramValue").attr('step', 0.5);
} else {
cardPricing[data].value = Number(cardPricing[data].value) - cardPricing[data].interval;
}
}
_fetchPricing();
$('#ramValue').data('old-value', $('#ramValue').val());
});
$('.fa-plus-circle.right').click(function(event) {
var data = $(this).data('plus');
if (cardPricing[data].value < cardPricing[data].max) {
if(data === 'ram' && String(cardPricing[data].value) === "0.5" && minRam === 0.5){
cardPricing[data].value = 1;
$('#ramValue').val('1');
$("#ramValue").attr('step', 1);
} else {
cardPricing[data].value = Number(cardPricing[data].value) + cardPricing[data].interval;
}
}
_fetchPricing();
$('#ramValue').data('old-value', $('#ramValue').val());
});
$('.input-price').change(function() {
var data = $(this).attr("name");
var input = $('input[name=' + data + ']');
var inputValue = input.val();
if(data === 'ram') {
var ramInput = $('#ramValue');
if ($('#ramValue').data('old-value') < $('#ramValue').val()) {
if($('#ramValue').val() === '1' && minRam === 0.5) {
$("#ramValue").attr('step', 1);
$('#ramValue').val('1');
}
} else {
if($('#ramValue').val() === '0' && minRam === 0.5) {
$("#ramValue").attr('step', 0.5);
$('#ramValue').val('0.5');
}
}
inputValue = $('#ramValue').val();
$('#ramValue').data('old-value', $('#ramValue').val());
}
cardPricing[data].value = inputValue;
_fetchPricing();
});
}
function _fetchPricing() {
Object.keys(cardPricing).map(function(element) {
$('input[name=' + element + ']').val(cardPricing[element].value);
});
_calcPricing();
}
function _calcPricing() {
if(typeof window.coresUnitPrice === 'undefined'){
window.coresUnitPrice = 5;
}
if(typeof window.ramUnitPrice === 'undefined'){
window.ramUnitPrice = 2;
}
if(typeof window.ssdUnitPrice === 'undefined'){
window.ssdUnitPrice = 0.6;
}
if(typeof window.discountAmount === 'undefined'){
window.discountAmount = 0;
}
var total = (cardPricing['cpu'].value * window.coresUnitPrice) +
(cardPricing['ram'].value * window.ramUnitPrice) +
(cardPricing['storage'].value * window.ssdUnitPrice) -
window.discountAmount;
total = parseFloat(total.toFixed(2));
$("#total").text(total);
}
_initPricing();
$('#ramValue').data('old-value', $('#ramValue').val());
}); });

View file

@ -22,39 +22,6 @@ function setBrandIcon(brand) {
$(document).ready(function () { $(document).ready(function () {
$(function () {
$("select#id_generic_payment_form-product_name").change(function () {
var gp_form = $('#generic-payment-form');
$.ajax({
url: gp_form.attr('action'),
type: 'POST',
data: gp_form.serialize(),
init: function () {
console.log("init")
},
success: function (data) {
if (data.amount !== undefined) {
$("#id_generic_payment_form-amount").val(data.amount);
if (data.isSubscription) {
$('#id_generic_payment_form-recurring').prop('checked', true);
} else {
$('#id_generic_payment_form-recurring').prop('checked', false);
}
} else {
$("#id_generic_payment_form-amount").val('');
$('#id_generic_payment_form-recurring').prop('checked', false);
console.log("No product found")
}
},
error: function (xmlhttprequest, textstatus, message) {
$("#id_generic_payment_form-amount").val('');
$('#id_generic_payment_form-recurring').prop('checked', false);
console.log("Error fetching product")
}
});
})
});
$.ajaxSetup({ $.ajaxSetup({
beforeSend: function (xhr, settings) { beforeSend: function (xhr, settings) {
function getCookie(name) { function getCookie(name) {
@ -157,35 +124,17 @@ $(document).ready(function () {
$('#billing-form').submit(); $('#billing-form').submit();
} }
function getCookie(name) {
var value = "; " + document.cookie;
var parts = value.split("; " + name + "=");
if (parts.length === 2) return parts.pop().split(";").shift();
}
function submitBillingForm() {
var billing_form = $('#billing-form');
var recurring_input = $('#id_generic_payment_form-recurring');
billing_form.append('<input type="hidden" name="generic_payment_form-product_name" value="' + $('#id_generic_payment_form-product_name').val() + '" />');
billing_form.append('<input type="hidden" name="generic_payment_form-amount" value="' + $('#id_generic_payment_form-amount').val() + '" />');
if (recurring_input.attr('type') === 'hidden') {
billing_form.append('<input type="hidden" name="generic_payment_form-recurring" value="' + (recurring_input.val() === 'True' ? 'on' : '') + '" />');
} else {
billing_form.append('<input type="hidden" name="generic_payment_form-recurring" value="' + (recurring_input.prop('checked') ? 'on' : '') + '" />');
}
billing_form.append('<input type="hidden" name="generic_payment_form-description" value="' + $('#id_generic_payment_form-description').val() + '" />');
billing_form.submit();
}
var $form_new = $('#payment-form-new'); var $form_new = $('#payment-form-new');
$form_new.submit(payWithStripe_new); $form_new.submit(payWithStripe_new);
function payWithStripe_new(e) { function payWithStripe_new(e) {
e.preventDefault(); e.preventDefault();
function stripeTokenHandler(token) { function stripeTokenHandler(token) {
// Insert the token ID into the form so it gets submitted to the server // Insert the token ID into the form so it gets submitted to the server
$('#id_token').val(token.id); $('#id_token').val(token.id);
submitBillingForm(); $('#billing-form').submit();
} }
@ -246,11 +195,5 @@ $(document).ready(function () {
$(element).closest('.form-group').append(error); $(element).closest('.form-group').append(error);
} }
}); });
$('.credit-card-info .btn.choice-btn').click(function () {
var id = this.dataset['id_card'];
$('#id_card').val(id);
submitBillingForm();
});
}); });

View file

@ -134,15 +134,3 @@ $(document).ready(function() {
$(this).find('.modal-footer .btn').addClass('hide'); $(this).find('.modal-footer .btn').addClass('hide');
}) })
}); });
window.onload = function () {
var locale_dates = document.getElementsByClassName("locale_date");
var formats = ['YYYY-MM-DD hh:mm a'];
var i;
for (i = 0; i < locale_dates.length; i++) {
var oldDate = moment.utc(locale_dates[i].textContent, formats);
var outputFormat = locale_dates[i].getAttribute('data-format') || oldDate._f;
locale_dates[i].innerHTML = oldDate.local().format(outputFormat);
locale_dates[i].className += ' done';
}
};

View file

@ -9,7 +9,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content=""> <meta name="description" content="">
<meta name="author" content="ungleich glarus ag"> <meta name="author" content="">
<title>{{ domain }} - {{ hosting }} hosting as easy as possible</title> <title>{{ domain }} - {{ hosting }} hosting as easy as possible</title>

View file

@ -1,5 +1,5 @@
{% load staticfiles i18n cms_tags sekizai_tags %} {% load staticfiles bootstrap3%}
{% load i18n %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
@ -29,9 +29,6 @@
{% block css_extra %} {% block css_extra %}
{% endblock css_extra %} {% endblock css_extra %}
{% render_block "css" postprocessor "compressor.contrib.sekizai.compress" %}
{% render_block "js" postprocessor "compressor.contrib.sekizai.compress" %}
<!-- Custom Fonts --> <!-- Custom Fonts -->
<link href='//fonts.googleapis.com/css?family=Raleway' rel='stylesheet' type='text/css'> <link href='//fonts.googleapis.com/css?family=Raleway' rel='stylesheet' type='text/css'>
<link href="{% static 'datacenterlight/font-awesome/css/font-awesome.min.css' %}" rel="stylesheet" type="text/css"> <link href="{% static 'datacenterlight/font-awesome/css/font-awesome.min.css' %}" rel="stylesheet" type="text/css">
@ -51,7 +48,7 @@
</head> </head>
<body> <body>
{% cms_toolbar %}
{% block navbar %} {% block navbar %}
{% include "hosting/includes/_navbar_user.html" %} {% include "hosting/includes/_navbar_user.html" %}

View file

@ -1,9 +1,7 @@
{% extends "hosting/base_short.html" %} {% extends "hosting/base_short.html" %}
{% load staticfiles bootstrap3 i18n cms_tags %} {% load staticfiles bootstrap3 i18n %}
{% block content %} {% block content %}
<div class="dashboard-container create-vm-container"> <div class="dashboard-container create-vm-container">
<div class="row"> <div class="row">
<div class="col-sm-5"> <div class="col-sm-5">
@ -19,8 +17,14 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
<div class="col-sm-6 hosting-calculator"> <div class="col-sm-6">
{% render_placeholder cms_integration.calculator_placeholder %} <div class="price-calc-section no-padding">
<div class="landing card">
<div class="caption">
{% include "hosting/calculator_form.html" %}
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -14,7 +14,7 @@
<table style="width: 100%; border-spacing: 0; border-collapse: collapse; max-width: 560px;"> <table style="width: 100%; border-spacing: 0; border-collapse: collapse; max-width: 560px;">
<tr> <tr>
<td> <td>
<img src="{{ base_url }}{% static 'datacenterlight/img/datacenterlight.png' %}" style="max-width: 200px;"> <img src="{{ base_url }}{% static 'datacenterlight/img/logo_black.png' %}" style="width: 200px; height: 50px;">
</td> </td>
</tr> </tr>
<tr> <tr>

View file

@ -14,7 +14,7 @@
<table style="width: 100%; border-spacing: 0; border-collapse: collapse; max-width: 560px;"> <table style="width: 100%; border-spacing: 0; border-collapse: collapse; max-width: 560px;">
<tr> <tr>
<td> <td>
<img src="{{ base_url }}{% static 'datacenterlight/img/datacenterlight.png' %}" style="max-width: 200px;"> <img src="{{base_url}}{% static 'datacenterlight/img/logo_black.png' %}" style="width: 200px; height: 50px;">
</td> </td>
</tr> </tr>
<tr> <tr>

View file

@ -14,7 +14,7 @@
<table style="width: 100%; border-spacing: 0; border-collapse: collapse; max-width: 560px;"> <table style="width: 100%; border-spacing: 0; border-collapse: collapse; max-width: 560px;">
<tr> <tr>
<td> <td>
<img src="{{ base_url }}{% static 'datacenterlight/img/datacenterlight.png' %}" style="max-width: 200px;"> <img src="{{ base_url }}{% static 'datacenterlight/img/logo_black.png' %}" style="width: 200px; height: 50px;">
</td> </td>
</tr> </tr>
<tr> <tr>
@ -25,7 +25,7 @@
<tr> <tr>
<td style="padding-top: 25px; font-size: 16px;"> <td style="padding-top: 25px; font-size: 16px;">
<p style="line-height: 1.75; font-family: Lato, Arial, sans-serif; font-weight: 300; margin: 0;"> <p style="line-height: 1.75; font-family: Lato, Arial, sans-serif; font-weight: 300; margin: 0;">
{% blocktrans %}You are receiving this email because your virtual machine <strong>{{ vm_name }}</strong> has been cancelled.{% endblocktrans %} {% blocktrans %}You are receiving this email because your virutal machine <strong>{{ vm_name }}</strong> has been cancelled.{% endblocktrans %}
</p> </p>
<p style="line-height: 1.75; font-family: Lato, Arial, sans-serif; font-weight: 300; margin: 0;"> <p style="line-height: 1.75; font-family: Lato, Arial, sans-serif; font-weight: 300; margin: 0;">
{% blocktrans %}You can always order a new VM by clicking the button below.{% endblocktrans %} {% blocktrans %}You can always order a new VM by clicking the button below.{% endblocktrans %}

View file

@ -2,7 +2,7 @@
{% trans "Virtual Machine Cancellation" %} {% trans "Virtual Machine Cancellation" %}
{% blocktrans %}You are receiving this email because your virtual machine {{vm_name}} has been cancelled.{% endblocktrans %} {% blocktrans %}You are receiving this email because your virutal machine {{vm_name}} has been cancelled.{% endblocktrans %}
{% blocktrans %}You can always order a new VM by following the link below.{% endblocktrans %} {% blocktrans %}You can always order a new VM by following the link below.{% endblocktrans %}
{{ base_url }}{% url 'hosting:create_virtual_machine' %} {{ base_url }}{% url 'hosting:create_virtual_machine' %}

View file

@ -74,7 +74,7 @@
<center style="font-family: 'Oxygen', 'Helvetica Neue', 'Arial', 'sans-serif' !important;"> <center style="font-family: 'Oxygen', 'Helvetica Neue', 'Arial', 'sans-serif' !important;">
<table cellpadding="0" cellspacing="0" width="600" class="w320" style="border-collapse: collapse !important; font-family: 'Oxygen', 'Helvetica Neue', 'Arial', 'sans-serif' !important;"><tr style="font-family: 'Oxygen', 'Helvetica Neue', 'Arial', 'sans-serif' !important;"> <table cellpadding="0" cellspacing="0" width="600" class="w320" style="border-collapse: collapse !important; font-family: 'Oxygen', 'Helvetica Neue', 'Arial', 'sans-serif' !important;"><tr style="font-family: 'Oxygen', 'Helvetica Neue', 'Arial', 'sans-serif' !important;">
<td class="pull-left mobile-header-padding-left" style="vertical-align: middle; border-collapse: collapse; font-family: 'Oxygen', 'Helvetica Neue', 'Arial', 'sans-serif' !important; font-size: 14px; color: #777777; text-align: left; line-height: 21px; width: 290px; padding-left: 10px;" align="left" valign="middle"> <td class="pull-left mobile-header-padding-left" style="vertical-align: middle; border-collapse: collapse; font-family: 'Oxygen', 'Helvetica Neue', 'Arial', 'sans-serif' !important; font-size: 14px; color: #777777; text-align: left; line-height: 21px; width: 290px; padding-left: 10px;" align="left" valign="middle">
<a href="{{base_url}}" style="font-family: 'Oxygen', 'Helvetica Neue', 'Arial', 'sans-serif' !important; color: #676767; text-decoration: none !important;"><img width="137" src="{{base_url}}{% static 'hosting/img/datacenterlight.png' %}" alt="logo" style="max-width: 600px; outline: none; text-decoration: none; -ms-interpolation-mode: bicubic; font-family: 'Oxygen', 'Helvetica Neue', 'Arial', 'sans-serif' !important; border: none;"></a> <a href="{{base_url}}" style="font-family: 'Oxygen', 'Helvetica Neue', 'Arial', 'sans-serif' !important; color: #676767; text-decoration: none !important;"><img width="137" src="{{base_url}}{% static "hosting/img/logo_black.png" %}" alt="logo" style="max-width: 600px; outline: none; text-decoration: none; -ms-interpolation-mode: bicubic; font-family: 'Oxygen', 'Helvetica Neue', 'Arial', 'sans-serif' !important; border: none;"></a>
</td> </td>
<td class="pull-right mobile-header-padding-right" style="color: #4d4d4d; border-collapse: collapse; font-family: 'Oxygen', 'Helvetica Neue', 'Arial', 'sans-serif' !important; font-size: 14px; text-align: right; line-height: 21px; width: 290px; padding-left: 10px;" align="right"> <td class="pull-right mobile-header-padding-right" style="color: #4d4d4d; border-collapse: collapse; font-family: 'Oxygen', 'Helvetica Neue', 'Arial', 'sans-serif' !important; font-size: 14px; text-align: right; line-height: 21px; width: 290px; padding-left: 10px;" align="right">
</td> </td>

View file

@ -1,50 +0,0 @@
{% load i18n %}
<form action="" id="payment-form-new" method="POST">
<input type="hidden" name="token"/>
<input type="hidden" name="id_card" id="id_card" value=""/>
<div class="group">
<div class="credit-card-goup">
<div class="card-element card-number-element">
<label>{%trans "Card Number" %}</label>
<div id="card-number-element" class="field my-input"></div>
</div>
<div class="row">
<div class="col-xs-5 card-element card-expiry-element">
<label>{%trans "Expiry Date" %}</label>
<div id="card-expiry-element" class="field my-input"></div>
</div>
<div class="col-xs-3 col-xs-offset-4 card-element card-cvc-element">
<label>{%trans "CVC" %}</label>
<div id="card-cvc-element" class="field my-input"></div>
</div>
</div>
<div class="card-element brand">
<label>{%trans "Card Type" %}</label>
<i class="pf pf-credit-card" id="brand-icon"></i>
</div>
</div>
</div>
<div id="card-errors"></div>
{% if not messages and not form.non_field_errors %}
<p class="card-warning-content">
{% trans "You are not making any payment yet. After placing your order, you will be taken to the Submit Payment Page." %}
</p>
{% endif %}
<div id='payment_error'>
{% for message in messages %}
{% if 'failed_payment' in message.tags or 'make_charge_error' in message.tags or 'error' in message.tags %}
<ul class="list-unstyled">
<li><p class="card-warning-content card-warning-error">{{ message|safe }}</p></li>
</ul>
{% endif %}
{% endfor %}
</div>
<div class="text-right">
<button class="btn btn-vm-contact btn-wide" type="submit" name="payment-form">{%trans "SUBMIT" %}</button>
</div>
<div style="display:none;">
<p class="payment-errors"></p>
</div>
</form>

View file

@ -39,7 +39,7 @@
{% endif %} {% endif %}
</span> </span>
</p> </p>
{% if order and vm %} {% if order %}
<p> <p>
<strong>{% trans "Status" %}: </strong> <strong>{% trans "Status" %}: </strong>
<strong> <strong>
@ -93,7 +93,6 @@
<hr> <hr>
<div> <div>
<h4>{% trans "Order summary" %}</h4> <h4>{% trans "Order summary" %}</h4>
{% if vm %}
<p> <p>
<strong>{% trans "Product" %}:</strong>&nbsp; <strong>{% trans "Product" %}:</strong>&nbsp;
{% if vm.name %} {% if vm.name %}
@ -115,84 +114,27 @@
<p> <p>
<span>{% trans "Cores" %}: </span> <span>{% trans "Cores" %}: </span>
{% if vm.cores %} {% if vm.cores %}
<strong class="pull-right">{{vm.cores|floatformat}}</strong> <span class="pull-right">{{vm.cores|floatformat}}</span>
{% else %} {% else %}
<strong class="pull-right">{{vm.cpu|floatformat}}</strong> <span class="pull-right">{{vm.cpu|floatformat}}</span>
{% endif %} {% endif %}
</p> </p>
<p> <p>
<span>{% trans "Memory" %}: </span> <span>{% trans "Memory" %}: </span>
<strong class="pull-right">{{vm.memory}} GB</strong> <span class="pull-right">{{vm.memory}} GB</span>
</p> </p>
<p> <p>
<span>{% trans "Disk space" %}: </span> <span>{% trans "Disk space" %}: </span>
<strong class="pull-right">{{vm.disk_size}} GB</strong> <span class="pull-right">{{vm.disk_size}} GB</span>
</p>
</div>
<div class="col-sm-12">
<hr class="thin-hr">
</div>
{% if vm.vat > 0 or vm.discount.amount > 0 %}
<div class="col-sm-6">
<div class="subtotal-price">
{% if vm.vat > 0 %}
<p>
<strong>{% trans "Subtotal" %} </strong>
<strong class="pull-right">{{vm.price|floatformat:2|intcomma}} CHF</strong>
</p> </p>
<p> <p>
<small>{% trans "VAT" %} ({{ vm.vat_percent|floatformat:2|intcomma }}%) </small> <span>{% trans "Total" %}</span>
<strong class="pull-right">{{vm.vat|floatformat:2|intcomma}} CHF</strong> <span class="pull-right">{{vm.price|intcomma}} CHF</span>
</p>
{% endif %}
{% if vm.discount.amount > 0 %}
<p class="text-primary">
{%trans "Discount" as discount_name %}
<strong>{{ vm.discount.name|default:discount_name }} </strong>
<strong class="pull-right">- {{ vm.discount.amount }} CHF</strong>
</p>
{% endif %}
</div>
</div>
<div class="col-sm-12">
<hr class="thin-hr">
</div>
{% endif %}
<div class="col-sm-6">
<p class="total-price">
<strong>{% trans "Total" %} </strong>
<strong class="pull-right">{% if vm.total_price %}{{vm.total_price|floatformat:2|intcomma}}{% else %}{{vm.price|floatformat:2|intcomma}}{% endif %} CHF</strong>
</p> </p>
</div> </div>
</div> </div>
{% else %}
<p>
<strong>{% trans "Product" %}:</strong>&nbsp;
{{ product_name }}
</p>
<div class="row">
<div class="col-sm-6">
<p>
<span>{% trans "Amount" %}: </span>
<strong class="pull-right">{{order.price|floatformat:2|intcomma}} CHF</strong>
</p>
{% if order.generic_payment_description %}
<p>
<span>{% trans "Description" %}: </span>
<strong class="pull-right">{{order.generic_payment_description}}</strong>
</p>
{% endif %}
{% if order.subscription_id %}
<p>
<span>{% trans "Recurring" %}: </span>
<strong class="pull-right">{{order.created_at|date:'d'|ordinal}} {% trans "of every month" %}</strong>
</p>
{% endif %}
</div> </div>
</div> <hr>
{% endif %}
</div>
<hr class="thin-hr">
</div> </div>
{% if not order %} {% if not order %}
{% block submit_btn %} {% block submit_btn %}
@ -200,7 +142,7 @@
{% csrf_token %} {% csrf_token %}
<div class="row"> <div class="row">
<div class="col-sm-8"> <div class="col-sm-8">
<div class="dcl-place-order-text">{% blocktrans with vm_price=vm.total_price|floatformat:2|intcomma %}By clicking "Place order" this plan will charge your credit card account with {{ vm_price }} CHF/month{% endblocktrans %}.</div> <div class="dcl-place-order-text">{% blocktrans with vm_price=request.session.specs.price %}By clicking "Place order" this plan will charge your credit card account with the fee of {{ vm_price|intcomma }}CHF/month{% endblocktrans %}.</div>
</div> </div>
<div class="col-sm-4 order-confirm-btn text-right"> <div class="col-sm-4 order-confirm-btn text-right">
<button class="btn choice-btn" id="btn-create-vm" data-href="{% url 'hosting:order-confirmation' %}" data-toggle="modal" data-target="#createvm-modal"> <button class="btn choice-btn" id="btn-create-vm" data-href="{% url 'hosting:order-confirmation' %}" data-toggle="modal" data-target="#createvm-modal">
@ -256,6 +198,17 @@
<script type="text/javascript"> <script type="text/javascript">
{% trans "Some problem encountered. Please try again later." as err_msg %} {% trans "Some problem encountered. Please try again later." as err_msg %}
var create_vm_error_message = '{{err_msg|safe}}'; var create_vm_error_message = '{{err_msg|safe}}';
window.onload = function () {
var locale_dates = document.getElementsByClassName("locale_date");
var formats = ['YYYY-MM-DD hh:mm a']
var i;
for (i = 0; i < locale_dates.length; i++) {
var oldDate = moment.utc(locale_dates[i].textContent, formats);
var outputFormat = locale_dates[i].getAttribute('data-format') || oldDate._f;
locale_dates[i].innerHTML = oldDate.local().format(outputFormat);
locale_dates[i].className += ' done';
}
};
</script> </script>
{%endblock%} {%endblock%}

View file

@ -28,8 +28,8 @@
{% for order in orders %} {% for order in orders %}
<tr> <tr>
<td class="xs-td-inline" data-header="{% trans 'Order Nr.' %}">{{ order.id }}</td> <td class="xs-td-inline" data-header="{% trans 'Order Nr.' %}">{{ order.id }}</td>
<td class="xs-td-bighalf locale_date" data-header="{% trans 'Date' %}">{{ order.created_at | date:'Y-m-d h:i a' }}</td> <td class="xs-td-bighalf" data-header="{% trans 'Date' %}">{{ order.created_at | date:"M d, Y H:i" }}</td>
<td class="xs-td-smallhalf" data-header="{% trans 'Amount' %}">{{ order.price|floatformat:2|intcomma }}</td> <td class="xs-td-smallhalf" data-header="{% trans 'Amount' %}">{{ order.price|intcomma }}</td>
<td class="text-right last-td"> <td class="text-right last-td">
<a class="btn btn-order-detail" href="{% url 'hosting:orders' order.pk %}">{% trans 'See Invoice' %}</a> <a class="btn btn-order-detail" href="{% url 'hosting:orders' order.pk %}">{% trans 'See Invoice' %}</a>
</td> </td>

View file

@ -9,161 +9,161 @@
<!-- Credit card form --> <!-- Credit card form -->
<div class="dcl-order-container"> <div class="dcl-order-container">
<div class="payment-container"> <div class="payment-container">
<div class="dcl-order-sec"> <div class="row">
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12 dcl-order-sec">
<h3><strong>{%trans "Your Order" %}</strong></h3> <h3><strong>{%trans "Your Order" %}</strong></h3>
<div class="row"> <div class="col-xs-6 col-sm-12 col-md-12 col-lg-12 dcl-order-table-header">
<div class="col-xs-6 col-sm-12"> <div class="col-xs-12 col-sm-2 col-md-1 col-lg-1 tbl-header">
<div class="dcl-order-table-header">
<div class="row">
<div class="col-sm-2">
<div class="tbl-header">
{%trans "Cores" %} {%trans "Cores" %}
</div> </div>
</div> <div class="col-xs-12 col-sm-3 col-md-4 col-lg-4 tbl-header">
<div class="col-sm-4">
<div class="tbl-header tbl-header-center">
{%trans "Memory" %} {%trans "Memory" %}
</div> </div>
</div> <div class="col-xs-12 col-sm-3 col-md-3 col-lg-3 tbl-header">
<div class="col-sm-3">
<div class="tbl-header tbl-header-center">
{%trans "Disk space" %} {%trans "Disk space" %}
</div> </div>
</div> <div class="col-xs-12 col-sm-4 col-md-4 col-lg-4 tbl-header">
<div class="col-sm-3">
<div class="tbl-header tbl-header-right">
{%trans "Configuration" %} {%trans "Configuration" %}
</div> </div>
</div> </div>
</div> <div class="col-xs-6 col-sm-12 col-md-12 col-lg-12 dcl-order-table-content">
</div> <div class="col-xs-12 col-sm-2 col-md-1 col-lg-1 tbl-content">
</div>
<div class="col-xs-6 col-sm-12">
<div class="dcl-order-table-content">
<div class="row">
<div class="col-sm-2">
<div class="tbl-content">
{{request.session.specs.cpu|floatformat}} {{request.session.specs.cpu|floatformat}}
</div> </div>
</div> <div class="col-xs-12 col-sm-3 col-md-4 col-lg-4 tbl-content">
<div class="col-sm-4">
<div class="tbl-content tbl-content-center">
{{request.session.specs.memory|floatformat}} GB {{request.session.specs.memory|floatformat}} GB
</div> </div>
</div> <div class="col-xs-12 col-sm-3 col-md-3 col-lg-3 tbl-content">
<div class="col-sm-3">
<div class="tbl-content tbl-content-center">
{{request.session.specs.disk_size|floatformat|intcomma}} GB {{request.session.specs.disk_size|floatformat|intcomma}} GB
</div> </div>
</div> <div class="col-xs-12 col-sm-4 col-md-4 col-lg-4 tbl-content">
<div class="col-sm-3">
<div class="tbl-content tbl-content-right">
{{request.session.template.name}} {{request.session.template.name}}
</div> </div>
</div> </div>
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12 dcl-order-table-total">
<div class="col-xs-6 col-sm-6 col-md-6 col-lg-6 tbl-tot tbl-no-padding">
{%trans "Total" %} <span>{%trans "including VAT" %}</span>
</div>
<div class="col-xs-6 col-sm-6 col-md-6 col-lg-6 tbl-no-padding">
<div class="col-xs-12 col-sm-4 col-md-4 col-lg-4"></div>
<div class="col-xs-12 col-sm-6 col-md-6 col-lg-6 tbl-total">{{request.session.specs.price|intcomma}}
CHF<span class="dcl-price-month">/{% trans "Month" %}</span>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="dcl-order-table-total">
<div class="row"> <div class="row">
<div class="col-xs-6"> <div class="col-xs-12 col-sm-12 col-md-12 col-lg-12 dcl-billing-sec">
<div class="tbl-tot"> <div class="col-xs-12 col-sm-5 col-md-6 billing dcl-billing">
{%trans "Total" %}&nbsp;
<span>{% if vm_pricing.vat_inclusive %}{%trans "including VAT" %}{% else %}{%trans "excluding VAT" %}{% endif %}</span>
</div>
</div>
<div class="col-xs-6">
<div class="tbl-total">
{{request.session.specs.price|intcomma}} CHF/{% trans "Month" %}
</div>
</div>
</div>
{% if vm_pricing.discount_amount %}
<hr class="thin-hr">
<div class="row">
<div class="col-xs-6">
<div class="tbl-tot">
{%trans "Discount" as discount_name %}
{{ vm_pricing.discount_name|default:discount_name }}&nbsp;&nbsp;<br>
<span>({% trans "Will be applied at checkout" %})</span>
</div>
</div>
<div class="col-xs-6 text-right">
<div class="tbl-total">
<div class="text-primary">- {{ vm_pricing.discount_amount }} CHF/{% trans "Month" %}</div>
</div>
</div>
</div>
{% endif %}
</div>
</div>
<div class="dcl-billing-sec">
<div class="row">
<div class="col-sm-5 col-md-6">
<div class="billing dcl-billing">
<h3><b>{%trans "Billing Address"%}</b></h3> <h3><b>{%trans "Billing Address"%}</b></h3>
<hr> <hr>
<form role="form" id="billing-form" method="post" action="" novalidate> <form role="form" id="billing-form" method="post" action="" novalidate>
{% csrf_token %}
{% for field in form %} {% for field in form %}
{% csrf_token %}
{% bootstrap_field field show_label=False type='fields'%} {% bootstrap_field field show_label=False type='fields'%}
{% endfor %} {% endfor %}
</form> </form>
</div> </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">
{% with card_list_len=cards_list|length %}
<h3><b>{%trans "Credit Card"%}</b></h3> <h3><b>{%trans "Credit Card"%}</b></h3>
<hr> <hr>
<div> <div>
<p> <p>
{% if card_list_len > 0 %}
{% blocktrans %}Please select one of the cards that you used before or fill in your credit card information below. We are using <a href="https://stripe.com" target="_blank">Stripe</a> for payment and do not store your information in our database.{% endblocktrans %}
{% else %}
{% blocktrans %}Please fill in your credit card information below. We are using <a href="https://stripe.com" target="_blank">Stripe</a> for payment and do not store your information in our database.{% endblocktrans %} {% blocktrans %}Please fill in your credit card information below. We are using <a href="https://stripe.com" target="_blank">Stripe</a> for payment and do not store your information in our database.{% endblocktrans %}
{% endif %}
</p> </p>
<div> <div>
{% for card in cards_list %} {% if credit_card_data.last4 %}
<div class="credit-card-info"> <form role="form" id="payment-form-with-creditcard" novalidate>
<div class="col-xs-6 no-padding"> <h5 class="billing-head">Credit Card</h5>
<h5 class="billing-head">{% trans "Credit Card" %}</h5> <h5 class="membership-lead">Last 4: *****{{credit_card_data.last4}}</h5>
<h5 class="membership-lead">{% trans "Last" %} 4: ***** {{card.last4}}</h5> <h5 class="membership-lead">Type: {{credit_card_data.cc_brand}}</h5>
<h5 class="membership-lead">{% trans "Type" %}: {{card.brand}}</h5> <input type="hidden" name="credit_card_needed" value="false"/>
</div> </form>
<div class="col-xs-6 text-right align-bottom"> {% if not messages and not form.non_field_errors %}
<a class="btn choice-btn choice-btn-faded" href="#" data-id_card="{{card.id}}">{% trans "SELECT" %}</a> <p class="card-warning-content card-warning-addtional-margin">
</div> {% trans "You are not making any payment yet. After submitting your card information, you will be taken to the Confirm Order Page." %}
</div> </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 %} {% endfor %}
{% if card_list_len > 0 %} {% for error in form.non_field_errors %}
<div class="new-card-head"> <p class="card-warning-content card-warning-error">
{{ error|escape }}
</p>
{% endfor %}
</div>
<div class="text-right">
<button id="payment_button_with_creditcard" class="btn btn-vm-contact" type="submit">{%trans "SUBMIT" %}</button>
</div>
{% else %}
<form action="" id="payment-form-new" method="POST">
<input type="hidden" name="token"/>
<div class="group">
<div class="credit-card-goup">
<div class="card-element card-number-element">
<label>{%trans "Card Number" %}</label>
<div id="card-number-element" class="field my-input"></div>
</div>
<div class="row"> <div class="row">
<div class="col-xs-6"> <div class="col-xs-5 card-element card-expiry-element">
<h4>{% trans "Add a new credit card" %}</h4> <label>{%trans "Expiry Date" %}</label>
<div id="card-expiry-element" class="field my-input"></div>
</div> </div>
<div class="col-xs-6 text-right"> <div class="col-xs-3 col-xs-offset-4 card-element card-cvc-element">
<button data-toggle="collapse" data-target="#newcard" class="btn choice-btn"> <label>{%trans "CVC" %}</label>
<span class="fa fa-plus"></span>&nbsp;&nbsp;{% trans "NEW CARD" %} <div id="card-cvc-element" class="field my-input"></div>
</button> </div>
</div>
<div class="card-element brand">
<label>{%trans "Card Type" %}</label>
<i class="pf pf-credit-card" id="brand-icon"></i>
</div> </div>
</div> </div>
</div> </div>
<div id="newcard" class="collapse"> <div id="card-errors"></div>
<hr class="thick-hr"> {% if not messages and not form.non_field_errors %}
<div class="card-details-box"> <p class="card-warning-content">
<h3>{%trans "New Credit Card" %}</h3> {% trans "You are not making any payment yet. After submitting your card information, you will be taken to the Confirm Order Page." %}
<hr> </p>
{% include "hosting/includes/_card_input.html" %} {% 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 class="text-right">
<button class="btn btn-vm-contact btn-wide" type="submit">{%trans "SUBMIT" %}</button>
</div> </div>
</div> </div>
{% else%}
{% include "hosting/includes/_card_input.html" %} <div style="display:none;">
<p class="payment-errors"></p>
</div>
</form>
{% endif %} {% endif %}
</div> </div>
</div> </div>
{% endwith %}
</div> </div>
</div> </div>
</div> </div>
@ -183,7 +183,7 @@
})(); })();
</script> </script>
{%endif%} {%endif%}
{% comment "Looks as if no more used. To test..." %}
{% if credit_card_data.last4 and credit_card_data.cc_brand %} {% if credit_card_data.last4 and credit_card_data.cc_brand %}
<script type="text/javascript"> <script type="text/javascript">
(function () { (function () {
@ -191,5 +191,5 @@
})(); })();
</script> </script>
{%endif%} {%endif%}
{% endcomment %}
{%endblock%} {%endblock%}

View file

@ -7,7 +7,6 @@
{% block content %} {% block content %}
<div class="dashboard-container wide"> <div class="dashboard-container wide">
{% include 'hosting/includes/_messages.html' %}
<div class="dashboard-container-head"> <div class="dashboard-container-head">
<h1 class="dashboard-title-thin"><img src="{% static 'hosting/img/dashboard_settings.svg' %}" class="un-icon wide"> {% trans "My Settings" %}</h1> <h1 class="dashboard-title-thin"><img src="{% static 'hosting/img/dashboard_settings.svg' %}" class="un-icon wide"> {% trans "My Settings" %}</h1>
</div> </div>
@ -15,105 +14,116 @@
<div class="settings-container"> <div class="settings-container">
<div class="row"> <div class="row">
<div class="col-sm-5 col-md-6 billing dcl-billing"> <div class="col-sm-5 col-md-6 billing dcl-billing">
<h3>{%trans "Billing Address" %}</h3> <h3>{%trans "Billing Address"%}</h3>
<hr> <hr>
<form role="form" id="billing-form" method="post" action="" novalidate> <form role="form" id="billing-form" method="post" action="" novalidate>
{% csrf_token %}
{% for field in form %} {% for field in form %}
{% csrf_token %}
{% bootstrap_field field show_label=False type='fields' bound_css_class='' %} {% bootstrap_field field show_label=False type='fields' bound_css_class='' %}
{% endfor %} {% endfor %}
<div class="form-group text-right"> <div class="form-group text-right">
<button type="submit" class="btn btn-vm-contact btn-wide" name="billing-form">{% trans "UPDATE" %}</button> <button type="submit" class="btn btn-vm-contact btn-wide">{% trans "UPDATE" %}</button>
</div> </div>
</form> </form>
</div> </div>
<div class="col-sm-7 col-md-6 creditcard-box dcl-creditcard"> <div class="col-sm-7 col-md-6 creditcard-box dcl-creditcard">
<h3>{%trans "Credit Card" %}</h3> <h3>{%trans "Credit Card"%}</h3>
<hr> <hr>
<div> <div>
{% with card_list_len=cards_list|length %} {% if credit_card_data.last4 %}
{% for card in cards_list %}
<div class="credit-card-details"> <div class="credit-card-details">
<h5 class="billing-head">{% trans "Credit Card" %}</h5> <h5 class="billing-head">{% trans "Credit Card" %}</h5>
<h5 class="membership-lead">{% trans "Last" %} 4: ***** {{card.last4}}</h5> <h5 class="membership-lead">{% trans "Last" %} 4: *****{{credit_card_data.last4}}</h5>
<h5 class="membership-lead">{% trans "Type" %}: {{card.brand}}</h5> <h5 class="membership-lead">{% trans "Type" %}: {{credit_card_data.cc_brand}}</h5>
{% comment %}
<div class="credit-card-details-opt"> <div class="credit-card-details-opt">
<div class="row"> <div class="row">
{% if card_list_len > 1 %}
<div class="col-xs-6"> <div class="col-xs-6">
<a class="caps-link" href="" data-toggle="modal" data-target="#Modal{{ card.id }}"><img src="{% static 'hosting/img/delete.svg' %}" class="svg-img">{% trans "REMOVE CARD" %}</a> <a class="caps-link" href=""><img src="{% static 'hosting/img/delete.svg' %}" class="svg-img">{% trans "REMOVE CARD" %}</a>
<div class="modal fade" id="Modal{{card.id }}" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Confirm"><span aria-hidden="true">&times;</span></button>
</div> </div>
<div class="modal-body">
<div class="modal-icon"><i class="fa fa-trash" aria-hidden="true"></i></div>
<h4 class="modal-title" id="ModalLabel">{% trans "Remove Card"%}</h4>
<div class="modal-text">
<p>{% trans "Do you want to remove this associated card?"%}</p>
</div>
<form method="post" action="{% url 'hosting:delete_card' card.id %}">
{% csrf_token %}
<div class="modal-footer">
<button type="submit" class="btn btn-danger btn-wide" name="delete_card">{% trans "Delete"%}</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endif %}
<div class="col-xs-6 text-right"> <div class="col-xs-6 text-right">
{% if card.preferred %} <a class="btn btn-vm-contact" href="">{% trans "EDIT CARD" %}</a>
{% trans "DEFAULT" %} </div>
</div>
</div>
{% endcomment %}
</div>
{% else %} {% else %}
<form method="post" action="">
{% csrf_token %}
<input type="hidden" name="card" value="{{card.id}}">
<a class="btn choice-btn choice-btn-faded" href="#" onclick="$(this).closest('form').submit()">{% trans "SELECT" %}</a>
</form>
{% endif %}
</div>
</div>
</div>
</div>
{% empty %}
<div class="no-cards"> <div class="no-cards">
<h4>{% trans "No Credit Cards Added" %}</h4> <h4>{% trans "No Credit Cards Added" %}</h4>
<p>{% blocktrans %}We are using <a href="https://stripe.com">Stripe</a> for payment and do not store your information in our database.{% endblocktrans %}</p> <p>{% blocktrans %}We are using <a href="https://stripe.com">Stripe</a> for payment and do not store your information in our database.{% endblocktrans %}</p>
</div> </div>
{% endfor %}
{% endwith %}
<div class="new-card-head"> {% comment %}
<div class="row"> <h4>{% trans "Add a new Card." %}</h4>
<div class="col-xs-6"> <p style="margin-bottom: 15px;">
<h4>{% trans "Add a new credit card" %}</h4> {% blocktrans %}Please fill in your credit card information below. We are using <a href="https://stripe.com" target="_blank">Stripe</a> for payment and do not store your information in our database.{% endblocktrans %}
</p>
<form action="" id="payment-form-new" class="credit-card-form" method="POST">
<input type="hidden" name="token"/>
<div class="credit-card-goup">
<div class="card-element card-number-element">
<label>{%trans "Card Number" %}</label>
<div id="card-number-element" class="field my-input"></div>
</div> </div>
<div class="col-xs-6 text-right"> <div class="row">
<button data-toggle="collapse" data-target="#newcard" class="btn choice-btn"> <div class="col-xs-6 col-sm-4 card-element card-expiry-element">
<span class="fa fa-plus"></span>&nbsp;&nbsp;{% trans "NEW CARD" %} <label>{%trans "Expiry Date" %}</label>
<div id="card-expiry-element" class="field my-input"></div>
</div>
<div class="col-xs-6 col-sm-4 col-sm-offset-4 card-element card-cvc-element">
<label>{%trans "CVC" %}</label>
<div id="card-cvc-element" class="field my-input"></div>
</div>
</div>
<div class="card-element brand">
<label>{%trans "Card Type" %}</label>
<i class="pf pf-credit-card" id="brand-icon"></i>
</div>
</div>
<div id="card-errors" role="alert"></div>
<div>
{% if not messages and not form.non_field_errors %}
<p class="card-warning-content">
{% blocktrans %}You are not making any payment here.{% endblocktrans %}
</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 class="row">
<div class="col-xs-6 col-xs-offset-6 text-right">
<button class="btn btn-success stripe-payment-btn" type="submit">{%trans "Submit" %}
</button> </button>
</div> </div>
</div> </div>
</div> </div>
<div id="newcard" class="collapse">
<hr class="thick-hr"> <div style="display:none;">
<div class="card-details-box"> <p class="payment-errors"></p>
<h3>{%trans "New Credit Card" %}</h3>
<hr>
{% include "hosting/includes/_card_input.html" %}
</div>
</div> </div>
</form>
{% endcomment %}
{% endif %}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{% comment %}
<!-- stripe key data --> <!-- stripe key data -->
{% if stripe_key %} {% if stripe_key %}
{% get_current_language as LANGUAGE_CODE %} {% get_current_language as LANGUAGE_CODE %}
@ -127,4 +137,13 @@
})(); })();
</script> </script>
{%endif%} {%endif%}
{% if credit_card_data.last4 and credit_card_data.cc_brand %}
<script type="text/javascript">
(function () {
window.hasCreditcard = true;
})();
</script>
{%endif%}
{% endcomment %}
{%endblock%} {%endblock%}

View file

@ -45,13 +45,13 @@
<h2 class="vm-detail-title">{% trans "Billing" %} <img src="{% static 'hosting/img/billing.svg' %}" class="un-icon"></h2> <h2 class="vm-detail-title">{% trans "Billing" %} <img src="{% static 'hosting/img/billing.svg' %}" class="un-icon"></h2>
<div class="vm-vmid"> <div class="vm-vmid">
<div class="vm-item-subtitle">{% trans "Current Pricing" %}</div> <div class="vm-item-subtitle">{% trans "Current Pricing" %}</div>
<div class="vm-item-lg">{{order.price|floatformat:2|intcomma}} CHF/{% trans "Month" %}</div> <div class="vm-item-lg">{{virtual_machine.price|floatformat|intcomma}} CHF/{% trans "Month" %}</div>
<a class="btn btn-vm-invoice" href="{% url 'hosting:orders' order.pk %}">{% trans "See Invoice" %}</a> <a class="btn btn-vm-invoice" href="{% url 'hosting:orders' order.pk %}">{% trans "See Invoice" %}</a>
</div> </div>
</div> </div>
<div class="vm-detail-item"> <div class="vm-detail-item">
<h2 class="vm-detail-title">{% trans "Status" %} <img src="{% static 'hosting/img/connected.svg' %}" class="un-icon"></h2> <h2 class="vm-detail-title">{% trans "Status" %} <img src="{% static 'hosting/img/connected.svg' %}" class="un-icon"></h2>
<div class="vm-vmid vm-vmid-with-warning"> <div class="vm-vmid">
<div class="vm-item-subtitle">{% trans "Your VM is" %}</div> <div class="vm-item-subtitle">{% trans "Your VM is" %}</div>
<div id="terminate-VM" data-alt="{% trans 'Terminating' %}"> <div id="terminate-VM" data-alt="{% trans 'Terminating' %}">
{% if virtual_machine.state == 'PENDING' %} {% if virtual_machine.state == 'PENDING' %}
@ -74,10 +74,6 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
<div class="vm-terminate-warning text-center">
<p>{% trans "Attention:" %}</p>
<p>{% trans "terminating VM can not be reverted." %}</p>
</div>
</div> </div>
</div> </div>
<div class="vm-contact-us"> <div class="vm-contact-us">
@ -109,7 +105,7 @@
<div class="modal-icon"><i class="fa fa-ban" aria-hidden="true"></i></div> <div class="modal-icon"><i class="fa fa-ban" aria-hidden="true"></i></div>
<h4 class="modal-title" id="ModalLabel">{% trans "Terminate your Virtual Machine" %}</h4> <h4 class="modal-title" id="ModalLabel">{% trans "Terminate your Virtual Machine" %}</h4>
<div class="modal-text"> <div class="modal-text">
<p>{% trans "Terminated VMs can not be revived and will not be refunded. Do you want to terminate your VM?" %}</p> <p>{% trans "Do you want to cancel your Virtual Machine" %} ?</p>
<p><strong>{{virtual_machine.name}}</strong></p> <p><strong>{{virtual_machine.name}}</strong></p>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">

View file

@ -43,8 +43,6 @@ urlpatterns = [
name='choice_ssh_keys'), name='choice_ssh_keys'),
url(r'delete_ssh_key/(?P<pk>\d+)/?$', SSHKeyDeleteView.as_view(), url(r'delete_ssh_key/(?P<pk>\d+)/?$', SSHKeyDeleteView.as_view(),
name='delete_ssh_key'), name='delete_ssh_key'),
url(r'delete_card/(?P<pk>\d+)/?$', SettingsView.as_view(),
name='delete_card'),
url(r'create_ssh_key/?$', SSHKeyCreateView.as_view(), url(r'create_ssh_key/?$', SSHKeyCreateView.as_view(),
name='create_ssh_key'), name='create_ssh_key'),
url(r'^notifications/$', NotificationsView.as_view(), url(r'^notifications/$', NotificationsView.as_view(),

View file

@ -1,3 +1,4 @@
import json
import logging import logging
import uuid import uuid
from datetime import datetime from datetime import datetime
@ -11,16 +12,13 @@ from django.contrib.auth.tokens import default_token_generator
from django.core.exceptions import ValidationError 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 ( from django.http import Http404, HttpResponseRedirect, HttpResponse
Http404, HttpResponseRedirect, HttpResponse, JsonResponse
)
from django.shortcuts import redirect, render from django.shortcuts import redirect, render
from django.utils.decorators import method_decorator
from django.utils.html import escape
from django.utils.http import urlsafe_base64_decode from django.utils.http import urlsafe_base64_decode
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import get_language, ugettext_lazy as _ from django.utils.translation import get_language, ugettext_lazy as _
from django.utils.translation import ugettext from django.utils.translation import ugettext
from django.utils.decorators import method_decorator
from django.views.decorators.cache import never_cache from django.views.decorators.cache import never_cache
from django.views.generic import ( from django.views.generic import (
View, CreateView, FormView, ListView, DetailView, DeleteView, View, CreateView, FormView, ListView, DetailView, DeleteView,
@ -32,10 +30,8 @@ from stored_messages.api import mark_read
from stored_messages.models import Message from stored_messages.models import Message
from stored_messages.settings import stored_messages_settings from stored_messages.settings import stored_messages_settings
from datacenterlight.cms_models import DCLCalculatorPluginModel from datacenterlight.models import VMTemplate
from datacenterlight.models import VMTemplate, VMPricing from datacenterlight.tasks import create_vm_task
from datacenterlight.utils import create_vm, get_cms_integration
from hosting.models import UserCardDetail
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 ( from opennebula_api.serializers import (
@ -46,7 +42,7 @@ from utils.forms import (
BillingAddressForm, PasswordResetRequestForm, UserBillingAddressForm, BillingAddressForm, PasswordResetRequestForm, UserBillingAddressForm,
ResendActivationEmailForm ResendActivationEmailForm
) )
from utils.hosting_utils import get_vm_price_with_vat, HostingUtils 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
@ -58,10 +54,9 @@ from .forms import (
HostingUserSignupForm, HostingUserLoginForm, UserHostingKeyForm, HostingUserSignupForm, HostingUserLoginForm, UserHostingKeyForm,
generate_ssh_key_name generate_ssh_key_name
) )
from .mixins import ProcessVMSelectionMixin, HostingContextMixin from .mixins import ProcessVMSelectionMixin
from .models import ( from .models import (
HostingOrder, HostingBill, HostingPlan, UserHostingKey, VMDetail, HostingOrder, HostingBill, HostingPlan, UserHostingKey, VMDetail
GenericProduct
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -218,13 +213,13 @@ class IndexView(View):
return render(request, self.template_name, context) return render(request, self.template_name, context)
class LoginView(HostingContextMixin, LoginViewMixin): class LoginView(LoginViewMixin):
template_name = "hosting/login.html" template_name = "hosting/login.html"
form_class = HostingUserLoginForm form_class = HostingUserLoginForm
success_url = reverse_lazy('hosting:dashboard') success_url = reverse_lazy('hosting:dashboard')
class SignupView(HostingContextMixin, CreateView): class SignupView(CreateView):
template_name = 'hosting/signup.html' template_name = 'hosting/signup.html'
form_class = HostingUserSignupForm form_class = HostingUserSignupForm
model = CustomUser model = CustomUser
@ -253,7 +248,7 @@ class SignupView(HostingContextMixin, CreateView):
return super(SignupView, self).get(request, *args, **kwargs) return super(SignupView, self).get(request, *args, **kwargs)
class SignupValidateView(HostingContextMixin, TemplateView): class SignupValidateView(TemplateView):
template_name = "hosting/signup_validate.html" template_name = "hosting/signup_validate.html"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
@ -277,7 +272,7 @@ class SignupValidateView(HostingContextMixin, TemplateView):
return context return context
class SignupValidatedView(SignupValidateView, HostingContextMixin): class SignupValidatedView(SignupValidateView):
template_name = "hosting/signup_validate.html" template_name = "hosting/signup_validate.html"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
@ -312,7 +307,7 @@ class SignupValidatedView(SignupValidateView, HostingContextMixin):
email.send() email.send()
else: else:
home_url = '<a href="' + \ home_url = '<a href="' + \
reverse('datacenterlight:cms_index') + \ reverse('datacenterlight:index') + \
'">Data Center Light</a>' '">Data Center Light</a>'
message = '{sorry_message} <br />{go_back_to} {hurl}'.format( message = '{sorry_message} <br />{go_back_to} {hurl}'.format(
sorry_message=_("Sorry. Your request is invalid."), sorry_message=_("Sorry. Your request is invalid."),
@ -330,8 +325,7 @@ class SignupValidatedView(SignupValidateView, HostingContextMixin):
return super(SignupValidatedView, self).get(request, *args, **kwargs) return super(SignupValidatedView, self).get(request, *args, **kwargs)
class ResendActivationEmailView(HostingContextMixin, class ResendActivationEmailView(ResendActivationLinkViewMixin):
ResendActivationLinkViewMixin):
template_name = 'hosting/resend_activation_link.html' template_name = 'hosting/resend_activation_link.html'
form_class = ResendActivationEmailForm form_class = ResendActivationEmailForm
success_url = reverse_lazy('hosting:login') success_url = reverse_lazy('hosting:login')
@ -339,7 +333,7 @@ class ResendActivationEmailView(HostingContextMixin,
email_template_name = 'user_activation' email_template_name = 'user_activation'
class PasswordResetView(HostingContextMixin, PasswordResetViewMixin): class PasswordResetView(PasswordResetViewMixin):
site = 'dcl' site = 'dcl'
template_name = 'hosting/reset_password.html' template_name = 'hosting/reset_password.html'
form_class = PasswordResetRequestForm form_class = PasswordResetRequestForm
@ -347,8 +341,7 @@ class PasswordResetView(HostingContextMixin, PasswordResetViewMixin):
template_email_path = 'hosting/emails/' template_email_path = 'hosting/emails/'
class PasswordResetConfirmView(HostingContextMixin, class PasswordResetConfirmView(PasswordResetConfirmViewMixin):
PasswordResetConfirmViewMixin):
template_name = 'hosting/confirm_reset_password.html' template_name = 'hosting/confirm_reset_password.html'
success_url = reverse_lazy('hosting:login') success_url = reverse_lazy('hosting:login')
@ -569,7 +562,6 @@ class SettingsView(LoginRequiredMixin, FormView):
template_name = "hosting/settings.html" template_name = "hosting/settings.html"
login_url = reverse_lazy('hosting:login') login_url = reverse_lazy('hosting:login')
form_class = BillingAddressForm form_class = BillingAddressForm
permission_required = ['view_usercarddetail']
def get_form(self, form_class): def get_form(self, form_class):
""" """
@ -584,68 +576,25 @@ class SettingsView(LoginRequiredMixin, FormView):
context = super(SettingsView, self).get_context_data(**kwargs) context = super(SettingsView, self).get_context_data(**kwargs)
# Get user # Get user
user = self.request.user user = self.request.user
stripe_customer = None # Get user last order
if hasattr(user, 'stripecustomer'): last_hosting_order = HostingOrder.objects.filter(
stripe_customer = user.stripecustomer customer__user=user).last()
cards_list = UserCardDetail.get_all_cards_list( # If user has already an hosting order, get the credit card data from
stripe_customer=stripe_customer # it
) if last_hosting_order:
credit_card_data = last_hosting_order.get_cc_data()
context.update({
'credit_card_data': credit_card_data if credit_card_data else None,
})
context.update({ context.update({
'cards_list': cards_list,
'stripe_key': settings.STRIPE_API_PUBLIC_KEY 'stripe_key': settings.STRIPE_API_PUBLIC_KEY
}) })
return context return context
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
if 'card' in request.POST and request.POST['card'] is not '':
card_id = escape(request.POST['card'])
user_card_detail = UserCardDetail.objects.get(id=card_id)
UserCardDetail.set_default_card(
stripe_api_cus_id=request.user.stripecustomer.stripe_id,
stripe_source_id=user_card_detail.card_id
)
msg = _(
("Your {brand} card ending in {last4} set as "
"default card").format(
brand=user_card_detail.brand,
last4=user_card_detail.last4
)
)
messages.add_message(request, messages.SUCCESS, msg)
return HttpResponseRedirect(reverse_lazy('hosting:settings'))
if 'delete_card' in request.POST:
try:
card = UserCardDetail.objects.get(pk=self.kwargs.get('pk'))
if (request.user.has_perm(self.permission_required[0], card)
and
request.user
.stripecustomer
.usercarddetail_set
.count() > 1):
if card.card_id is not None:
stripe_utils = StripeUtils()
stripe_utils.dissociate_customer_card(
request.user.stripecustomer.stripe_id,
card.card_id
)
if card.preferred:
UserCardDetail.set_default_card_from_stripe(
request.user.stripecustomer.stripe_id
)
card.delete()
msg = _("Card deassociation successful")
messages.add_message(request, messages.SUCCESS, msg)
else:
msg = _("You are not permitted to do this operation")
messages.add_message(request, messages.ERROR, msg)
except UserCardDetail.DoesNotExist:
msg = _("The selected card does not exist")
messages.add_message(request, messages.ERROR, msg)
return HttpResponseRedirect(reverse_lazy('hosting:settings'))
form = self.get_form() form = self.get_form()
if form.is_valid(): if form.is_valid():
if 'billing-form' in request.POST:
billing_address_data = form.cleaned_data billing_address_data = form.cleaned_data
billing_address_data.update({ billing_address_data.update({
'user': self.request.user.id 'user': self.request.user.id
@ -654,54 +603,6 @@ class SettingsView(LoginRequiredMixin, FormView):
instance=self.request.user.billing_addresses.first(), instance=self.request.user.billing_addresses.first(),
data=billing_address_data) data=billing_address_data)
billing_address_user_form.save() billing_address_user_form.save()
msg = _("Billing address updated successfully")
messages.add_message(request, messages.SUCCESS, msg)
else:
token = form.cleaned_data.get('token')
stripe_utils = StripeUtils()
card_details = stripe_utils.get_cards_details_from_token(
token
)
if not card_details.get('response_object'):
form.add_error("__all__", card_details.get('error'))
return self.render_to_response(self.get_context_data())
stripe_customer = StripeCustomer.get_or_create(
email=request.user.email, token=token
)
card = card_details['response_object']
if UserCardDetail.get_user_card_details(stripe_customer, card):
msg = _('You seem to have already added this card')
messages.add_message(request, messages.ERROR, msg)
else:
acc_result = stripe_utils.associate_customer_card(
request.user.stripecustomer.stripe_id, token
)
if acc_result['response_object'] is None:
msg = _(
'An error occurred while associating the card.'
' Details: {details}'.format(
details=acc_result['error']
)
)
messages.add_message(request, messages.ERROR, msg)
return self.render_to_response(self.get_context_data())
preferred = False
if stripe_customer.usercarddetail_set.count() == 0:
preferred = True
UserCardDetail.create(
stripe_customer=stripe_customer,
last4=card['last4'],
brand=card['brand'],
fingerprint=card['fingerprint'],
exp_month=card['exp_month'],
exp_year=card['exp_year'],
card_id=card['card_id'],
preferred=preferred
)
msg = _(
"Successfully associated the card with your account"
)
messages.add_message(request, messages.SUCCESS, msg)
return self.render_to_response(self.get_context_data()) return self.render_to_response(self.get_context_data())
else: else:
billing_address_data = form.cleaned_data billing_address_data = form.cleaned_data
@ -734,19 +635,21 @@ class PaymentVMView(LoginRequiredMixin, FormView):
context = super(PaymentVMView, self).get_context_data(**kwargs) context = super(PaymentVMView, self).get_context_data(**kwargs)
# Get user # Get user
user = self.request.user user = self.request.user
if hasattr(user, 'stripecustomer'):
stripe_customer = user.stripecustomer # Get user last order
else: last_hosting_order = HostingOrder.objects.filter(
stripe_customer = None customer__user=user).last()
cards_list = UserCardDetail.get_all_cards_list(
stripe_customer=stripe_customer # If user has already an hosting order, get the credit card data from
) # it
if last_hosting_order:
credit_card_data = last_hosting_order.get_cc_data()
context.update({ context.update({
'stripe_key': settings.STRIPE_API_PUBLIC_KEY, 'credit_card_data': credit_card_data if credit_card_data else None,
'vm_pricing': VMPricing.get_vm_pricing_by_name( })
self.request.session.get('specs', {}).get('pricing_name')
), context.update({
'cards_list': cards_list, 'stripe_key': settings.STRIPE_API_PUBLIC_KEY
}) })
return context return context
@ -755,10 +658,6 @@ class PaymentVMView(LoginRequiredMixin, FormView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
if 'next' in request.session: if 'next' in request.session:
del request.session['next'] del request.session['next']
HostingUtils.clear_items_from_list(
request.session,
['token', 'card_id', 'customer', 'user']
)
return self.render_to_response(self.get_context_data()) return self.render_to_response(self.get_context_data())
@method_decorator(decorators) @method_decorator(decorators)
@ -769,38 +668,9 @@ class PaymentVMView(LoginRequiredMixin, FormView):
billing_address_data = form.cleaned_data billing_address_data = form.cleaned_data
token = form.cleaned_data.get('token') token = form.cleaned_data.get('token')
owner = self.request.user owner = self.request.user
if token is '':
card_id = form.cleaned_data.get('card')
customer = owner.stripecustomer
try:
user_card_detail = UserCardDetail.objects.get(id=card_id)
if not request.user.has_perm(
'view_usercarddetail', user_card_detail
):
raise UserCardDetail.DoesNotExist(
_("{user} does not have permission to access the "
"card").format(user=request.user.email)
)
except UserCardDetail.DoesNotExist as e:
ex = str(e)
logger.error("Card Id: {card_id}, Exception: {ex}".format(
card_id=card_id, ex=ex
)
)
msg = _("An error occurred. Details: {}".format(ex))
messages.add_message(
self.request, messages.ERROR, msg,
extra_tags='make_charge_error'
)
return HttpResponseRedirect(
reverse('hosting:payment') + '#payment_error'
)
request.session['card_id'] = user_card_detail.id
else:
# Get or create stripe customer # Get or create stripe customer
customer = StripeCustomer.get_or_create( customer = StripeCustomer.get_or_create(email=owner.email,
email=owner.email, token=token token=token)
)
if not customer: if not customer:
msg = _("Invalid credit card") msg = _("Invalid credit card")
messages.add_message( messages.add_message(
@ -808,12 +678,13 @@ class PaymentVMView(LoginRequiredMixin, FormView):
extra_tags='make_charge_error') extra_tags='make_charge_error')
return HttpResponseRedirect( return HttpResponseRedirect(
reverse('hosting:payment') + '#payment_error') reverse('hosting:payment') + '#payment_error')
request.session['token'] = token
request.session['billing_address_data'] = billing_address_data request.session['billing_address_data'] = billing_address_data
request.session['token'] = token
request.session['customer'] = customer.stripe_id
return HttpResponseRedirect("{url}?{query_params}".format( return HttpResponseRedirect("{url}?{query_params}".format(
url=reverse('hosting:order-confirmation'), url=reverse('hosting:order-confirmation'),
query_params='page=payment') query_params='page=payment'))
)
else: else:
return self.form_invalid(form) return self.form_invalid(form)
@ -846,6 +717,12 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
).get_context_data(**kwargs) ).get_context_data(**kwargs)
obj = self.get_object() obj = self.get_object()
owner = self.request.user owner = self.request.user
stripe_api_cus_id = self.request.session.get('customer')
stripe_utils = StripeUtils()
card_details = stripe_utils.get_card_details(
stripe_api_cus_id,
self.request.session.get('token')
)
if self.request.GET.get('page') == 'payment': if self.request.GET.get('page') == 'payment':
context['page_header_text'] = _('Confirm Order') context['page_header_text'] = _('Confirm Order')
@ -864,32 +741,17 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
raise Http404 raise Http404
if obj is not None: if obj is not None:
if obj.generic_product_id is not None:
# generic payment case
logger.debug("Generic payment case")
context['product_name'] = GenericProduct.objects.get(
id=obj.generic_product_id
).product_name
else:
# invoice for previous order # invoice for previous order
logger.debug("Invoice of VM order")
try: try:
vm_detail = VMDetail.objects.get(vm_id=obj.vm_id) vm_detail = VMDetail.objects.get(vm_id=obj.vm_id)
context['vm'] = vm_detail.__dict__ context['vm'] = vm_detail.__dict__
context['vm']['name'] = '{}-{}'.format( context['vm']['name'] = '{}-{}'.format(
context['vm']['configuration'], context['vm']['vm_id']) context['vm']['configuration'], context['vm']['vm_id'])
price, vat, vat_percent, discount = get_vm_price_with_vat( context['vm']['price'] = get_vm_price(
cpu=context['vm']['cores'], cpu=context['vm']['cores'],
ssd_size=context['vm']['disk_size'], disk_size=context['vm']['disk_size'],
memory=context['vm']['memory'], memory=context['vm']['memory']
pricing_name=(obj.vm_pricing.name
if obj.vm_pricing else 'default')
) )
context['vm']['vat'] = vat
context['vm']['price'] = price
context['vm']['discount'] = discount
context['vm']['vat_percent'] = vat_percent
context['vm']['total_price'] = price + vat - discount['amount']
context['subscription_end_date'] = vm_detail.end_date() context['subscription_end_date'] = vm_detail.end_date()
except VMDetail.DoesNotExist: except VMDetail.DoesNotExist:
try: try:
@ -898,20 +760,6 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
) )
vm = manager.get_vm(obj.vm_id) vm = manager.get_vm(obj.vm_id)
context['vm'] = VirtualMachineSerializer(vm).data context['vm'] = VirtualMachineSerializer(vm).data
price, vat, vat_percent, discount = get_vm_price_with_vat(
cpu=context['vm']['cores'],
ssd_size=context['vm']['disk_size'],
memory=context['vm']['memory'],
pricing_name=(obj.vm_pricing.name
if obj.vm_pricing else 'default')
)
context['vm']['vat'] = vat
context['vm']['price'] = price
context['vm']['discount'] = discount
context['vm']['vat_percent'] = vat_percent
context['vm']['total_price'] = (
price + vat - discount['amount']
)
except WrongIdError: except WrongIdError:
messages.error( messages.error(
self.request, self.request,
@ -926,25 +774,17 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
_('In order to create a VM, you need to create/upload ' _('In order to create a VM, you need to create/upload '
'your SSH KEY first.') 'your SSH KEY first.')
) )
elif not card_details.get('response_object'):
# new order, failed to get card details
context['failed_payment'] = True
context['card_details'] = card_details
else: else:
# new order, confirm payment # new order, confirm payment
if 'token' in self.request.session:
token = self.request.session['token']
stripe_utils = StripeUtils()
card_details = stripe_utils.get_cards_details_from_token(
token
)
if not card_details.get('response_object'):
return HttpResponseRedirect(reverse('hosting:payment'))
card_details_response = card_details['response_object']
context['cc_last4'] = card_details_response['last4']
context['cc_brand'] = card_details_response['brand']
else:
card_id = self.request.session.get('card_id')
card_detail = UserCardDetail.objects.get(id=card_id)
context['cc_last4'] = card_detail.last4
context['cc_brand'] = card_detail.brand
context['site_url'] = reverse('hosting:create_virtual_machine') context['site_url'] = reverse('hosting:create_virtual_machine')
context['cc_last4'] = card_details.get('response_object').get(
'last4')
context['cc_brand'] = card_details.get('response_object').get(
'cc_brand')
context['vm'] = self.request.session.get('specs') context['vm'] = self.request.session.get('specs')
return context return context
@ -955,9 +795,7 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
return HttpResponseRedirect( return HttpResponseRedirect(
reverse('hosting:create_virtual_machine') reverse('hosting:create_virtual_machine')
) )
if 'token' not in self.request.session:
if ('token' not in self.request.session and
'card_id' not in self.request.session):
return HttpResponseRedirect(reverse('hosting:payment')) return HttpResponseRedirect(reverse('hosting:payment'))
self.object = self.get_object() self.object = self.get_object()
context = self.get_context_data(object=self.object) context = self.get_context_data(object=self.object)
@ -976,86 +814,36 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
def post(self, request): def post(self, request):
template = request.session.get('template') template = request.session.get('template')
specs = request.session.get('specs') specs = request.session.get('specs')
stripe_utils = StripeUtils()
# We assume that if the user is here, his/her StripeCustomer # We assume that if the user is here, his/her StripeCustomer
# object already exists # object already exists
stripe_customer_id = request.user.stripecustomer.id stripe_customer_id = request.user.stripecustomer.id
billing_address_data = request.session.get('billing_address_data') billing_address_data = request.session.get('billing_address_data')
vm_template_id = template.get('id', 1) vm_template_id = template.get('id', 1)
stripe_api_cus_id = request.user.stripecustomer.stripe_id stripe_api_cus_id = self.request.session.get('customer')
if 'token' in self.request.session: # Make stripe charge to a customer
card_details = stripe_utils.get_cards_details_from_token( stripe_utils = StripeUtils()
request.session['token'] card_details = stripe_utils.get_card_details(stripe_api_cus_id,
) request.session.get(
'token'))
if not card_details.get('response_object'): if not card_details.get('response_object'):
return HttpResponseRedirect(reverse('hosting:payment')) msg = card_details.get('error')
card_details_response = card_details['response_object']
card_details_dict = {
'last4': card_details_response['last4'],
'brand': card_details_response['brand'],
'card_id': card_details_response['card_id']
}
ucd = UserCardDetail.get_user_card_details(
request.user.stripecustomer, card_details_response
)
if not ucd:
acc_result = stripe_utils.associate_customer_card(
stripe_api_cus_id, request.session['token'],
set_as_default=True
)
if acc_result['response_object'] is None:
msg = _(
'An error occurred while associating the card.'
' Details: {details}'.format(
details=acc_result['error']
)
)
messages.add_message(self.request, messages.ERROR, msg, messages.add_message(self.request, messages.ERROR, msg,
extra_tags='failed_payment') extra_tags='failed_payment')
response = { return HttpResponseRedirect(
'status': False, reverse('datacenterlight:payment') + '#payment_error')
'redirect': "{url}#{section}".format( card_details_dict = card_details.get('response_object')
url=reverse('hosting:payment'),
section='payment_error'),
'msg_title': str(_('Error.')),
'msg_body': str(
_('There was a payment related error.'
' On close of this popup, you will be redirected'
' back to the payment page.')
)
}
return JsonResponse(response)
else:
card_id = request.session.get('card_id')
user_card_detail = UserCardDetail.objects.get(id=card_id)
card_details_dict = {
'last4': user_card_detail.last4,
'brand': user_card_detail.brand,
'card_id': user_card_detail.card_id
}
if not user_card_detail.preferred:
UserCardDetail.set_default_card(
stripe_api_cus_id=stripe_api_cus_id,
stripe_source_id=user_card_detail.card_id
)
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 = specs.get('total_price') amount_to_be_charged = specs.get('price')
plan_name = StripeUtils.get_stripe_plan_name( plan_name = StripeUtils.get_stripe_plan_name(cpu=cpu,
cpu=cpu,
memory=memory, memory=memory,
disk_size=disk_size, disk_size=disk_size)
price=amount_to_be_charged 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,
version=1, version=1,
app='dcl', app='dcl')
price=amount_to_be_charged
)
stripe_plan = stripe_utils.get_or_create_stripe_plan( stripe_plan = stripe_utils.get_or_create_stripe_plan(
amount=amount_to_be_charged, amount=amount_to_be_charged,
name=plan_name, name=plan_name,
@ -1068,12 +856,6 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
# Check if the subscription was approved and is active # Check if the subscription was approved and is active
if (stripe_subscription_obj is None or if (stripe_subscription_obj is None or
stripe_subscription_obj.status != 'active'): stripe_subscription_obj.status != 'active'):
# At this point, we have created a Stripe API card and
# associated it with the customer; but the transaction failed
# due to some reason. So, we would want to dissociate this card
# here.
# ...
msg = subscription_result.get('error') msg = subscription_result.get('error')
messages.add_message(self.request, messages.ERROR, msg, messages.add_message(self.request, messages.ERROR, msg,
extra_tags='failed_payment') extra_tags='failed_payment')
@ -1086,20 +868,10 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
'msg_body': str( 'msg_body': str(
_('There was a payment related error.' _('There was a payment related error.'
' On close of this popup, you will be redirected back to' ' On close of this popup, you will be redirected back to'
' the payment page.') ' the payment page.'))
)
} }
return JsonResponse(response) return HttpResponse(json.dumps(response),
content_type="application/json")
if 'token' in request.session:
ucd = UserCardDetail.get_or_create_user_card_detail(
stripe_customer=self.request.user.stripecustomer,
card_details=card_details_response
)
UserCardDetail.save_default_card_local(
self.request.user.stripecustomer.stripe_id,
ucd.card_id
)
user = { user = {
'name': self.request.user.name, 'name': self.request.user.name,
'email': self.request.user.email, 'email': self.request.user.email,
@ -1108,12 +880,15 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
'request_host': request.get_host(), 'request_host': request.get_host(),
'language': get_language(), 'language': get_language(),
} }
create_vm_task.delay(vm_template_id, user, specs, template,
stripe_customer_id, billing_address_data,
stripe_subscription_obj.id, card_details_dict)
create_vm( for session_var in ['specs', 'template', 'billing_address',
billing_address_data, stripe_customer_id, specs, 'billing_address_data',
stripe_subscription_obj, card_details_dict, request, 'token', 'customer']:
vm_template_id, template, user if session_var in request.session:
) del request.session[session_var]
response = { response = {
'status': True, 'status': True,
@ -1125,7 +900,8 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
' it is ready.')) ' it is ready.'))
} }
return JsonResponse(response) return HttpResponse(json.dumps(response),
content_type="application/json")
class OrdersHostingListView(LoginRequiredMixin, ListView): class OrdersHostingListView(LoginRequiredMixin, ListView):
@ -1199,29 +975,7 @@ class CreateVirtualMachinesView(LoginRequiredMixin, View):
raise ValidationError(_('Invalid number of cores')) raise ValidationError(_('Invalid number of cores'))
def validate_memory(self, value): def validate_memory(self, value):
if 'pid' in self.request.POST: if (value > 200) or (value < 1):
try:
plugin = DCLCalculatorPluginModel.objects.get(
id=self.request.POST['pid']
)
except DCLCalculatorPluginModel.DoesNotExist as dne:
logger.error(
str(dne) + " plugin_id: " + self.request.POST['pid']
)
raise ValidationError(_('Invalid calculator properties'))
if plugin.enable_512mb_ram:
if value % 1 == 0 or value == 0.5:
logger.debug(
"Given ram {value} is either 0.5 or a"
" whole number".format(value=value)
)
if (value > 200) or (value < 0.5):
raise ValidationError(_('Invalid RAM size'))
else:
raise ValidationError(_('Invalid RAM size'))
elif (value > 200) or (value < 1) or (value % 1 != 0):
raise ValidationError(_('Invalid RAM size'))
else:
raise ValidationError(_('Invalid RAM size')) raise ValidationError(_('Invalid RAM size'))
def validate_storage(self, value): def validate_storage(self, value):
@ -1230,10 +984,7 @@ class CreateVirtualMachinesView(LoginRequiredMixin, View):
@method_decorator(decorators) @method_decorator(decorators)
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
context = { context = {'templates': VMTemplate.objects.all()}
'templates': VMTemplate.objects.all(),
'cms_integration': get_cms_integration('default'),
}
return render(request, self.template_name, context) return render(request, self.template_name, context)
@method_decorator(decorators) @method_decorator(decorators)
@ -1241,38 +992,22 @@ class CreateVirtualMachinesView(LoginRequiredMixin, View):
cores = request.POST.get('cpu') cores = request.POST.get('cpu')
cores_field = forms.IntegerField(validators=[self.validate_cores]) cores_field = forms.IntegerField(validators=[self.validate_cores])
memory = request.POST.get('ram') memory = request.POST.get('ram')
memory_field = forms.FloatField(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])
template_id = int(request.POST.get('config')) template_id = int(request.POST.get('config'))
pricing_name = request.POST.get('pricing_name')
vm_pricing = VMPricing.get_vm_pricing_by_name(pricing_name)
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
if vm_pricing is None:
vm_pricing_name_msg = _(
"Incorrect pricing name. Please contact support"
"{support_email}".format(
support_email=settings.DCL_SUPPORT_FROM_ADDRESS
)
)
messages.add_message(
self.request, messages.ERROR, vm_pricing_name_msg,
extra_tags='pricing'
)
return redirect(CreateVirtualMachinesView.as_view())
else:
vm_pricing_name = vm_pricing.name
try: try:
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(self.request, messages.ERROR, msg, messages.add_message(self.request, messages.ERROR, msg,
extra_tags='cores') extra_tags='cores')
return redirect(CreateVirtualMachinesView.as_view()) return HttpResponseRedirect(
reverse('datacenterlight:index') + "#order_form")
try: try:
memory = memory_field.clean(memory) memory = memory_field.clean(memory)
@ -1280,7 +1015,8 @@ class CreateVirtualMachinesView(LoginRequiredMixin, View):
msg = '{} : {}.'.format(memory, str(err)) msg = '{} : {}.'.format(memory, str(err))
messages.add_message(self.request, messages.ERROR, msg, messages.add_message(self.request, messages.ERROR, msg,
extra_tags='memory') extra_tags='memory')
return redirect(CreateVirtualMachinesView.as_view()) return HttpResponseRedirect(
reverse('datacenterlight:index') + "#order_form")
try: try:
storage = storage_field.clean(storage) storage = storage_field.clean(storage)
@ -1288,25 +1024,15 @@ class CreateVirtualMachinesView(LoginRequiredMixin, View):
msg = '{} : {}.'.format(storage, str(err)) msg = '{} : {}.'.format(storage, str(err))
messages.add_message(self.request, messages.ERROR, msg, messages.add_message(self.request, messages.ERROR, msg,
extra_tags='storage') extra_tags='storage')
return redirect(CreateVirtualMachinesView.as_view()) return HttpResponseRedirect(
reverse('datacenterlight:index') + "#order_form")
price, vat, vat_percent, discount = get_vm_price_with_vat( price = get_vm_price(cpu=cores, memory=memory,
cpu=cores, disk_size=storage)
memory=memory,
ssd_size=storage,
pricing_name=vm_pricing_name
)
specs = { specs = {
'cpu': cores, 'cpu': cores,
'memory': memory, 'memory': memory,
'disk_size': storage, 'disk_size': storage,
'discount': discount, 'price': price
'price': price,
'vat': vat,
'vat_percent': vat_percent,
'total_price': round(price + vat - discount['amount'], 2),
'pricing_name': vm_pricing_name
} }
request.session['specs'] = specs request.session['specs'] = specs
@ -1358,7 +1084,10 @@ class VirtualMachineView(LoginRequiredMixin, View):
for m in storage: for m in storage:
pass pass
storage.used = True storage.used = True
return JsonResponse({'text': ugettext('Terminated')}) return HttpResponse(
json.dumps({'text': ugettext('Terminated')}),
content_type="application/json"
)
else: else:
return redirect(reverse('hosting:virtual_machines')) return redirect(reverse('hosting:virtual_machines'))
elif self.request.is_ajax(): elif self.request.is_ajax():
@ -1369,8 +1098,7 @@ class VirtualMachineView(LoginRequiredMixin, View):
context = { context = {
'virtual_machine': serializer.data, 'virtual_machine': serializer.data,
'order': HostingOrder.objects.get( 'order': HostingOrder.objects.get(
vm_id=serializer.data['vm_id'] vm_id=serializer.data['vm_id'])
)
} }
except Exception as ex: except Exception as ex:
logger.debug("Exception generated {}".format(str(ex))) logger.debug("Exception generated {}".format(str(ex)))
@ -1432,7 +1160,7 @@ class VirtualMachineView(LoginRequiredMixin, View):
terminated = manager.delete_vm(vm.id) terminated = manager.delete_vm(vm.id)
if not terminated: if not terminated:
logger.error( logger.debug(
"manager.delete_vm returned False. Hence, error making " "manager.delete_vm returned False. Hence, error making "
"xml-rpc call to delete vm failed." "xml-rpc call to delete vm failed."
) )
@ -1442,9 +1170,6 @@ class VirtualMachineView(LoginRequiredMixin, View):
try: try:
manager.get_vm(vm.id) manager.get_vm(vm.id)
except WrongIdError: except WrongIdError:
logger.error(
"VM {} not found. So, its terminated.".format(vm.id)
)
response['status'] = True response['status'] = True
response['text'] = ugettext('Terminated') response['text'] = ugettext('Terminated')
vm_detail_obj = VMDetail.objects.filter( vm_detail_obj = VMDetail.objects.filter(
@ -1462,10 +1187,6 @@ class VirtualMachineView(LoginRequiredMixin, View):
break break
else: else:
sleep(2) sleep(2)
if not response['status']:
response['text'] = _("VM terminate action timed out. Please "
"contact support@datacenterlight.ch for "
"further information.")
context = { context = {
'vm_name': vm_name, 'vm_name': vm_name,
'base_url': "{0}://{1}".format( 'base_url': "{0}://{1}".format(
@ -1486,20 +1207,21 @@ class VirtualMachineView(LoginRequiredMixin, View):
email = BaseEmail(**email_data) email = BaseEmail(**email_data)
email.send() email.send()
admin_email_body.update(response) admin_email_body.update(response)
admin_msg_sub = "VM and Subscription for VM {} and user: {}".format(
vm.id,
owner.email
)
email_to_admin_data = { email_to_admin_data = {
'subject': ("Deleted " if response['status'] 'subject': "Deleted VM and Subscription for VM {vm_id} and "
else "ERROR deleting ") + admin_msg_sub, "user: {user}".format(
vm_id=vm.id, user=owner.email
),
'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS,
'to': ['info@ungleich.ch'], 'to': ['info@ungleich.ch'],
'body': "\n".join( 'body': "\n".join(
["%s=%s" % (k, v) for (k, v) in admin_email_body.items()]), ["%s=%s" % (k, v) for (k, v) in admin_email_body.items()]),
} }
send_plain_email_task.delay(email_to_admin_data) send_plain_email_task.delay(email_to_admin_data)
return JsonResponse(response) return HttpResponse(
json.dumps(response),
content_type="application/json"
)
class HostingBillListView(PermissionRequiredMixin, LoginRequiredMixin, class HostingBillListView(PermissionRequiredMixin, LoginRequiredMixin,

View file

@ -222,28 +222,21 @@ class StripeCustomer(models.Model):
# check if user is not in stripe but in database # check if user is not in stripe but in database
customer = stripe_utils.check_customer(stripe_customer.stripe_id, customer = stripe_utils.check_customer(stripe_customer.stripe_id,
stripe_customer.user, token) stripe_customer.user, token)
if "deleted" in customer and customer["deleted"]:
raise StripeCustomer.DoesNotExist() if not customer.sources.data:
stripe_utils.update_customer_token(customer, token)
return stripe_customer return stripe_customer
except StripeCustomer.DoesNotExist: except StripeCustomer.DoesNotExist:
user = CustomUser.objects.get(email=email) user = CustomUser.objects.get(email=email)
stripe_utils = StripeUtils() stripe_utils = StripeUtils()
stripe_data = stripe_utils.create_customer(token, email, user.name) stripe_data = stripe_utils.create_customer(token, email, user.name)
if stripe_data.get('response_object'): if stripe_data.get('response_object'):
stripe_cus_id = stripe_data.get('response_object').get('id') stripe_cus_id = stripe_data.get('response_object').get('id')
if hasattr(user, 'stripecustomer'):
# User already had a Stripe account and we are here stripe_customer = StripeCustomer.objects. \
# because the account was deleted in dashboard create(user=user, stripe_id=stripe_cus_id)
# So, we simply update the stripe_id
user.stripecustomer.stripe_id = stripe_cus_id
user.stripecustomer.save()
stripe_customer = user.stripecustomer
else:
# The user never had an associated Stripe account
# So, create one
stripe_customer = StripeCustomer.objects.create(
user=user, stripe_id=stripe_cus_id
)
return stripe_customer return stripe_customer
else: else:
return None return None

View file

@ -53,18 +53,27 @@ class OpenNebulaManager():
ConnectionError: If the connection to the opennebula server can't be ConnectionError: If the connection to the opennebula server can't be
established established
""" """
return self._get_opennebula_client(user.email, user.password) return oca.Client("{0}:{1}".format(
user.email,
def _get_opennebula_client(self, username, password): user.password),
return oca.Client(
"{0}:{1}".format(username, password),
"{protocol}://{domain}:{port}{endpoint}".format( "{protocol}://{domain}:{port}{endpoint}".format(
protocol=settings.OPENNEBULA_PROTOCOL, protocol=settings.OPENNEBULA_PROTOCOL,
domain=settings.OPENNEBULA_DOMAIN, domain=settings.OPENNEBULA_DOMAIN,
port=settings.OPENNEBULA_PORT, port=settings.OPENNEBULA_PORT,
endpoint=settings.OPENNEBULA_ENDPOINT endpoint=settings.OPENNEBULA_ENDPOINT
) ))
)
def _get_opennebula_client(self, username, password):
return oca.Client("{0}:{1}".format(
username,
password),
"{protocol}://{domain}:{port}{endpoint}".format(
protocol=settings.OPENNEBULA_PROTOCOL,
domain=settings.OPENNEBULA_DOMAIN,
port=settings.OPENNEBULA_PORT,
endpoint=settings.OPENNEBULA_ENDPOINT
))
def _get_user(self, user): def _get_user(self, user):
"""Get the corresponding opennebula user for a CustomUser object """Get the corresponding opennebula user for a CustomUser object
@ -110,7 +119,7 @@ class OpenNebulaManager():
raise UserExistsError() raise UserExistsError()
except OpenNebulaException as err: except OpenNebulaException as err:
logger.error('OpenNebulaException error: {0}'.format(err)) logger.error('OpenNebulaException error: {0}'.format(err))
logger.error('User exists but password is wrong') logger.debug('User exists but password is wrong')
raise UserCredentialError() raise UserCredentialError()
except WrongNameError: except WrongNameError:
@ -148,7 +157,7 @@ class OpenNebulaManager():
) )
return opennebula_user return opennebula_user
except ConnectionRefusedError: except ConnectionRefusedError:
logger.error( logger.info(
'Could not connect to host: {host} via protocol {protocol}'.format( 'Could not connect to host: {host} via protocol {protocol}'.format(
host=settings.OPENNEBULA_DOMAIN, host=settings.OPENNEBULA_DOMAIN,
protocol=settings.OPENNEBULA_PROTOCOL) protocol=settings.OPENNEBULA_PROTOCOL)
@ -160,7 +169,7 @@ class OpenNebulaManager():
user_pool = oca.UserPool(self.oneadmin_client) user_pool = oca.UserPool(self.oneadmin_client)
user_pool.info() user_pool.info()
except ConnectionRefusedError: except ConnectionRefusedError:
logger.error( logger.info(
'Could not connect to host: {host} via protocol {protocol}'.format( 'Could not connect to host: {host} via protocol {protocol}'.format(
host=settings.OPENNEBULA_DOMAIN, host=settings.OPENNEBULA_DOMAIN,
protocol=settings.OPENNEBULA_PROTOCOL) protocol=settings.OPENNEBULA_PROTOCOL)
@ -174,7 +183,7 @@ class OpenNebulaManager():
vm_pool.info() vm_pool.info()
return vm_pool return vm_pool
except AttributeError: except AttributeError:
logger.error('Could not connect via client, using oneadmin instead') logger.info('Could not connect via client, using oneadmin instead')
try: try:
vm_pool = oca.VirtualMachinePool(self.oneadmin_client) vm_pool = oca.VirtualMachinePool(self.oneadmin_client)
vm_pool.info(filter=-2) vm_pool.info(filter=-2)
@ -183,7 +192,7 @@ class OpenNebulaManager():
raise ConnectionRefusedError raise ConnectionRefusedError
except ConnectionRefusedError: except ConnectionRefusedError:
logger.error( logger.info(
'Could not connect to host: {host} via protocol {protocol}'.format( 'Could not connect to host: {host} via protocol {protocol}'.format(
host=settings.OPENNEBULA_DOMAIN, host=settings.OPENNEBULA_DOMAIN,
protocol=settings.OPENNEBULA_PROTOCOL) protocol=settings.OPENNEBULA_PROTOCOL)
@ -209,31 +218,32 @@ class OpenNebulaManager():
except: except:
raise ConnectionRefusedError raise ConnectionRefusedError
def get_ipv6(self, vm_id): def get_primary_ipv4(self, vm_id):
""" """
Returns the first IPv6 of the given vm. Returns the primary IPv4 of the given vm.
To be changed later.
:return: An IPv6 address string, if it exists else returns None :return: An IP address string, if it exists else returns None
""" """
ipv6_list = self.get_all_ipv6_addresses(vm_id) all_ipv4s = self.get_vm_ipv4_addresses(vm_id)
if len(ipv6_list) > 0: if len(all_ipv4s) > 0:
return ipv6_list[0] return all_ipv4s[0]
else: else:
return None return None
def get_all_ipv6_addresses(self, vm_id): def get_vm_ipv4_addresses(self, vm_id):
""" """
Returns a list of IPv6 addresses of the given vm Returns a list of IPv4 addresses of the given vm
:param vm_id: The ID of the vm :param vm_id: The ID of the vm
:return: :return:
""" """
ipv6_list = [] ipv4s = []
vm = self.get_vm(vm_id) vm = self.get_vm(vm_id)
for nic in vm.template.nics: for nic in vm.template.nics:
if hasattr(nic, 'ip6_global'): if hasattr(nic, 'ip'):
ipv6_list.append(nic.ip6_global) ipv4s.append(nic.ip)
return ipv6_list return ipv4s
def create_vm(self, template_id, specs, ssh_key=None, vm_name=None): def create_vm(self, template_id, specs, ssh_key=None, vm_name=None):
@ -249,8 +259,8 @@ class OpenNebulaManager():
vm_specs = vm_specs_formatter.format( vm_specs = vm_specs_formatter.format(
vcpu=int(specs['cpu']), vcpu=int(specs['cpu']),
cpu=0.1 * int(specs['cpu']), cpu=0.1 * int(specs['cpu']),
memory=(512 if specs['memory'] == 0.5 else memory=1024 * int(specs['memory']),
1024 * int(specs['memory'])),
) )
vm_specs += """<DISK> vm_specs += """<DISK>
<TYPE>fs</TYPE> <TYPE>fs</TYPE>
@ -269,8 +279,8 @@ class OpenNebulaManager():
vm_specs = vm_specs_formatter.format( vm_specs = vm_specs_formatter.format(
vcpu=int(specs['cpu']), vcpu=int(specs['cpu']),
cpu=0.1 * int(specs['cpu']), cpu=0.1 * int(specs['cpu']),
memory=(512 if specs['memory'] == 0.5 else memory=1024 * int(specs['memory']),
1024 * int(specs['memory'])),
) )
vm_specs += """<DISK> vm_specs += """<DISK>
<TYPE>fs</TYPE> <TYPE>fs</TYPE>
@ -315,7 +325,7 @@ class OpenNebulaManager():
return vm_id return vm_id
def delete_vm(self, vm_id): def delete_vm(self, vm_id):
TERMINATE_ACTION = 'terminate-hard' TERMINATE_ACTION = 'terminate'
vm_terminated = False vm_terminated = False
try: try:
self.oneadmin_client.call( self.oneadmin_client.call(
@ -325,14 +335,14 @@ class OpenNebulaManager():
) )
vm_terminated = True vm_terminated = True
except socket.timeout as socket_err: except socket.timeout as socket_err:
logger.error("Socket timeout error: {0}".format(socket_err)) logger.info("Socket timeout error: {0}".format(socket_err))
except OpenNebulaException as opennebula_err: except OpenNebulaException as opennebula_err:
logger.error( logger.info(
"OpenNebulaException error: {0}".format(opennebula_err)) "OpenNebulaException error: {0}".format(opennebula_err))
except OSError as os_err: except OSError as os_err:
logger.error("OSError : {0}".format(os_err)) logger.info("OSError : {0}".format(os_err))
except ValueError as value_err: except ValueError as value_err:
logger.error("ValueError : {0}".format(value_err)) logger.info("ValueError : {0}".format(value_err))
return vm_terminated return vm_terminated
@ -342,7 +352,7 @@ class OpenNebulaManager():
template_pool.info() template_pool.info()
return template_pool return template_pool
except ConnectionRefusedError: except ConnectionRefusedError:
logger.error( logger.info(
"""Could not connect to host: {host} via protocol """Could not connect to host: {host} via protocol
{protocol}""".format( {protocol}""".format(
host=settings.OPENNEBULA_DOMAIN, host=settings.OPENNEBULA_DOMAIN,
@ -352,12 +362,12 @@ class OpenNebulaManager():
except: except:
raise ConnectionRefusedError raise ConnectionRefusedError
def get_templates(self, prefix='public-'): def get_templates(self):
try: try:
public_templates = [ public_templates = [
template template
for template in self._get_template_pool() for template in self._get_template_pool()
if template.name.startswith(prefix) if template.name.startswith('public-')
] ]
return public_templates return public_templates
except ConnectionRefusedError: except ConnectionRefusedError:
@ -428,9 +438,8 @@ class OpenNebulaManager():
return template_id return template_id
def delete_template(self, template_id): def delete_template(self, template_id):
self.oneadmin_client.call( self.oneadmin_client.call(oca.VmTemplate.METHODS[
oca.VmTemplate.METHODS['delete'], template_id, False 'delete'], template_id, False)
)
def change_user_password(self, passwd_hash): def change_user_password(self, passwd_hash):
self.oneadmin_client.call( self.oneadmin_client.call(
@ -538,7 +547,7 @@ class OpenNebulaManager():
'value': 'sha-.....', # public key as string 'value': 'sha-.....', # public key as string
'state': True # whether key is to be added or 'state': True # whether key is to be added or
} # removed } # removed
:param hosts: A list of hosts IPv6 addresses :param hosts: A list of hosts IP addresses
:param countdown: Parameter to be passed to celery apply_async :param countdown: Parameter to be passed to celery apply_async
Allows to delay a task by `countdown` number of seconds Allows to delay a task by `countdown` number of seconds
:return: :return:
@ -551,14 +560,12 @@ class OpenNebulaManager():
link_error=save_ssh_key_error_handler.s()) link_error=save_ssh_key_error_handler.s())
else: else:
logger.debug( logger.debug(
"Keys and/or hosts are empty, so not managing any keys" "Keys and/or hosts are empty, so not managing any keys")
)
def get_all_hosts(self): def get_all_hosts(self):
""" """
A utility function to obtain all hosts of this owner A utility function to obtain all hosts of this owner
:return: A list of IPv6 addresses of all the hosts of this customer or :return: A list of hosts IP addresses, empty if none exist
an empty list if none exist
""" """
owner = CustomUser.objects.filter( owner = CustomUser.objects.filter(
email=self.email).first() email=self.email).first()
@ -569,8 +576,10 @@ class OpenNebulaManager():
"the ssh keys.".format(self.email)) "the ssh keys.".format(self.email))
for order in all_orders: for order in all_orders:
try: try:
ip = self.get_ipv6(order.vm_id) vm = self.get_vm(order.vm_id)
hosts.append(ip) for nic in vm.template.nics:
if hasattr(nic, 'ip'):
hosts.append(nic.ip)
except WrongIdError: except WrongIdError:
logger.debug( logger.debug(
"VM with ID {} does not exist".format(order.vm_id)) "VM with ID {} does not exist".format(order.vm_id))

View file

@ -36,10 +36,7 @@ class VirtualMachineTemplateSerializer(serializers.Serializer):
return int(obj.template.memory) / 1024 return int(obj.template.memory) / 1024
def get_name(self, obj): def get_name(self, obj):
if obj.name.startswith('public-'):
return obj.name.lstrip('public-') return obj.name.lstrip('public-')
else:
return obj.name
class VirtualMachineSerializer(serializers.Serializer): class VirtualMachineSerializer(serializers.Serializer):
@ -136,10 +133,7 @@ class VirtualMachineSerializer(serializers.Serializer):
def get_configuration(self, obj): def get_configuration(self, obj):
template_id = obj.template.template_id template_id = obj.template.template_id
template = OpenNebulaManager().get_template(template_id) template = OpenNebulaManager().get_template(template_id)
if template.name.startswith('public-'):
return template.name.lstrip('public-') return template.name.lstrip('public-')
else:
return template.name
def get_ipv4(self, obj): def get_ipv4(self, obj):
""" """
@ -168,10 +162,7 @@ class VirtualMachineSerializer(serializers.Serializer):
return '-' return '-'
def get_name(self, obj): def get_name(self, obj):
if obj.name.startswith('public-'):
return obj.name.lstrip('public-') return obj.name.lstrip('public-')
else:
return obj.name
class VMTemplateSerializer(serializers.Serializer): class VMTemplateSerializer(serializers.Serializer):

View file

@ -34,6 +34,7 @@ django-meta==1.2
django-meta-mixin==0.3.0 django-meta-mixin==0.3.0
django-model-utils==2.5 django-model-utils==2.5
django-mptt==0.8.4 django-mptt==0.8.4
django-multisite==1.4.1
django-parler==1.6.3 django-parler==1.6.3
django-phonenumber-field==1.1.0 django-phonenumber-field==1.1.0
django-polymorphic==0.9.2 django-polymorphic==0.9.2
@ -68,7 +69,7 @@ model-mommy==1.2.6
phonenumbers==7.4.0 phonenumbers==7.4.0
phonenumberslite==7.4.0 phonenumberslite==7.4.0
psycopg2==2.7.3.2 psycopg2==2.7.3.2
pycryptodome==3.6.6 pycryptodome==3.4
pylibmc==1.5.1 pylibmc==1.5.1
python-dateutil==2.5.3 python-dateutil==2.5.3
python-slugify==1.2.0 python-slugify==1.2.0

View file

@ -30,14 +30,6 @@
</span> </span>
</a> </a>
</li> </li>
<li>
<a href="https://blog.ungleich.ch/en-us/cms/blog/feed/">
<span class="fa-stack fa-lg">
<i class="fa fa-circle fa-stack-2x"></i>
<i class="fa fa-rss fa-stack-1x fa-inverse"></i>
</span>
</a>
</li>
</ul> </ul>
<p class="copyright"> <p class="copyright">
Copyright © ungleich GmbH {% now "Y" %} Copyright © ungleich GmbH {% now "Y" %}

View file

@ -18,10 +18,6 @@
<!-- Collect the nav links, forms, and other content for toggling --> <!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav navbar-right"> <ul class="nav navbar-nav navbar-right">
<li>
<a href="{% url 'djangocms_blog:posts-latest' %}">Ungleich Blog</a>
</li>
{% comment %}
{% for child in children %} {% for child in children %}
<li class="child{% if child.selected %} selected{% endif %}{% if child.ancestor %} ancestor{% endif %}{% if child.sibling %} sibling{% endif %}{% if child.descendant %} descendant{% endif %}"> <li class="child{% if child.selected %} selected{% endif %}{% if child.ancestor %} ancestor{% endif %}{% if child.sibling %} sibling{% endif %}{% if child.descendant %} descendant{% endif %}">
<a href="{{ child.attr.redirect_url|default:child.get_absolute_url }}">{{ child.get_menu_title }}</a> <a href="{{ child.attr.redirect_url|default:child.get_absolute_url }}">{{ child.get_menu_title }}</a>
@ -32,7 +28,6 @@
{% endif %} {% endif %}
</li> </li>
{% endfor %} {% endfor %}
{% endcomment %}
</ul> </ul>
</div> </div>
<!-- /.navbar-collapse --> <!-- /.navbar-collapse -->

View file

@ -35,8 +35,6 @@
{% render_block "external-css" %} {% render_block "external-css" %}
{% render_block "css" postprocessor "compressor.contrib.sekizai.compress" %} {% render_block "css" postprocessor "compressor.contrib.sekizai.compress" %}
<link rel="shortcut icon" href="{% static 'ungleich_page/img/favicon.ico' %}"/>
<!-- Google analytics --> <!-- Google analytics -->
{% include "google_analytics.html" %} {% include "google_analytics.html" %}
<!-- End Google Analytics --> <!-- End Google Analytics -->
@ -55,7 +53,7 @@
<div class="row"> <div class="row">
<div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1"> <div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
{% block base_content %} {% block base_content %}
{% placeholder "base_ungleich_content" %} {% placeholder "default" %}
{% endblock %} {% endblock %}
</div> </div>
</div> </div>

View file

@ -1,7 +1,5 @@
{% extends "base_ungleich.html" %} {% extends "base_ungleich.html" %}
{% load cms_tags %}
{% block base_content %} {% block base_content %}
{% placeholder "default" %}
{% block content %} {% block content %}
{% endblock %} {% endblock %}
{% endblock %} {% endblock %}

View file

@ -7,7 +7,6 @@ from djangocms_blog.models import Post
from djangocms_blog.views import PostListView from djangocms_blog.views import PostListView
from djangocms_blog.settings import get_setting from djangocms_blog.settings import get_setting
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from djangocms_blog.models import BlogCategory
def blog(request): def blog(request):
@ -21,7 +20,6 @@ def blog(request):
class PostListViewUngleich(PostListView): class PostListViewUngleich(PostListView):
category = None
model = Post model = Post
context_object_name = 'post_list' context_object_name = 'post_list'
base_template_name = 'post_list_ungleich.html' base_template_name = 'post_list_ungleich.html'
@ -40,26 +38,7 @@ class PostListViewUngleich(PostListView):
def get_queryset(self): def get_queryset(self):
language = get_language() language = get_language()
if self.category: queryset = self.model.objects.filter(publish=True).translated(language)
blog_category = (
BlogCategory
._default_manager
.language(language)
.filter(
translations__language_code=language,
translations__slug=self.category
)
)
queryset = (self.model
.objects
.filter(categories=blog_category, publish=True)
.translated(language))
else:
queryset = (self.model
.objects
.filter(publish=True)
.translated(language))
setattr(self.request, get_setting('CURRENT_NAMESPACE'), self.config) setattr(self.request, get_setting('CURRENT_NAMESPACE'), self.config)
return queryset return queryset

View file

@ -1,11 +1,9 @@
#logoWhite, .topnav img {
.navbar-transparent #logoBlack { height: 100%;
display: none;
} }
#logoBlack,
.navbar-transparent #logoWhite { .navbar-transparent #logoWhite {
display: block; width: 140px;
} }
.navbar-default { .navbar-default {
@ -14,15 +12,6 @@
padding: 5px; padding: 5px;
} }
@media (max-width: 767px) {
.navbar-transparent #logoBlack {
display: block;
}
.navbar-transparent #logoWhite {
display: none;
}
}
@media (min-width: 768px) { @media (min-width: 768px) {
.navbar-transparent { .navbar-transparent {
padding: 20px; padding: 20px;

View file

@ -195,7 +195,7 @@
flex: 1; flex: 1;
} }
.header_slider > .carousel .item .container-fluid { .header_slider > .carousel .item .container {
overflow: auto; overflow: auto;
padding: 50px 20px 60px; padding: 50px 20px 60px;
height: 100%; height: 100%;
@ -236,7 +236,7 @@
.header_slider .carousel-control .fa { .header_slider .carousel-control .fa {
font-size: 4em; font-size: 4em;
} }
.header_slider > .carousel .item .container-fluid { .header_slider > .carousel .item .container {
overflow: auto; overflow: auto;
padding: 75px 50px; padding: 75px 50px;
} }

View file

@ -23,7 +23,7 @@
<!-- External Fonts --> <!-- External Fonts -->
<link href="//fonts.googleapis.com/css?family=Lato:300,400,600,700" rel="stylesheet" type="text/css"> <link href="//fonts.googleapis.com/css?family=Lato:300,400,600,700" rel="stylesheet" type="text/css">
<link rel="shortcut icon" href="{% static 'ungleich_page/img/favicon.ico' %}" type="image/x-icon" /> <link rel="shortcut icon" href="img/favicon.ico" type="image/x-icon" />
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries --> <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// --> <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->

Some files were not shown because too many files have changed in this diff Show more