diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index 39902025..9b4c2f75 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -30,6 +30,7 @@ dotenv.read_dotenv("{0}/.env".format(PROJECT_DIR)) SITE_ID = 1 APP_ROOT_ENDPOINT = "/" +APPEND_SLASH=True LOGIN_URL = None LOGOUT_URL = None diff --git a/dynamicweb/urls.py b/dynamicweb/urls.py index 78c06077..9be4a91e 100644 --- a/dynamicweb/urls.py +++ b/dynamicweb/urls.py @@ -5,12 +5,14 @@ from django.conf.urls.i18n import i18n_patterns from django.conf.urls.static import static from django.conf import settings -from hosting.views import RailsHostingView +from hosting.views import RailsHostingView, DjangoHostingView, NodeJSHostingView from membership import urls as membership_urls urlpatterns = [ url(r'^hosting/', include('hosting.urls', namespace="hosting")), url(r'^railshosting/', RailsHostingView.as_view(), name="rails.hosting"), + url(r'^nodehosting/', NodeJSHostingView.as_view(), name="node.hosting"), + url(r'^djangohosting/', DjangoHostingView.as_view(), name="django.hosting"), url(r'^taggit_autosuggest/', include('taggit_autosuggest.urls')), url(r'^jsi18n/(?P\S+?)/$', 'django.views.i18n.javascript_catalog'), diff --git a/hosting/migrations/0008_virtualmachineplan.py b/hosting/migrations/0008_virtualmachineplan.py new file mode 100644 index 00000000..a06d76b2 --- /dev/null +++ b/hosting/migrations/0008_virtualmachineplan.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2016-04-23 07:10 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('hosting', '0007_auto_20160418_0103'), + ] + + operations = [ + migrations.CreateModel( + name='VirtualMachinePlan', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('cores', models.IntegerField()), + ('memory', models.IntegerField()), + ('disk_size', models.IntegerField()), + ('price', models.FloatField()), + ('client', models.ManyToManyField(to=settings.AUTH_USER_MODEL)), + ('vm_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hosting.VirtualMachineType')), + ], + ), + ] diff --git a/hosting/mixins.py b/hosting/mixins.py new file mode 100644 index 00000000..7104e157 --- /dev/null +++ b/hosting/mixins.py @@ -0,0 +1,20 @@ +from django.shortcuts import redirect +from django.core.urlresolvers import reverse + + +class ProcessVMSelectionMixin(object): + + def post(self, request, *args, **kwargs): + vm_specs = { + 'cores': request.POST.get('cores'), + 'memory': request.POST.get('memory'), + 'disk_size': request.POST.get('disk_space'), + 'hosting_company': request.POST.get('hosting_company'), + 'hosting_company_name': request.POST.get('hosting_company_name'), + 'final_price': request.POST.get('final_price') + } + request.session['vm_specs'] = vm_specs + if not request.user.is_authenticated(): + request.session['vm_specs'] = vm_specs + return redirect(reverse('hosting:login')) + return redirect(reverse('hosting:payment')) diff --git a/hosting/models.py b/hosting/models.py index 59ba8521..42e66e34 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -1,7 +1,9 @@ +import json + from django.db import models from django.utils.translation import ugettext_lazy as _ from django.core import serializers -import json +from membership.models import CustomUser class RailsBetaUser(models.Model): @@ -42,7 +44,13 @@ class VirtualMachineType(models.Model): def get_serialized_vm_types(cls): return [vm.get_serialized_data() for vm in cls.objects.all()] - # return serializers.serialize("json",) + + def calculate_price(self, specifications): + price = float(specifications['cores']) * self.core_price + price += float(specifications['memory']) * self.memory_price + price += float(specifications['disk_size']) * self.disk_size_price + price += self.base_price + return price def defeault_price(self): price = self.base_price @@ -63,3 +71,28 @@ class VirtualMachineType(models.Model): 'default_price': self.defeault_price(), 'id': self.id, } + + +class VirtualMachinePlan(models.Model): + cores = models.IntegerField() + memory = models.IntegerField() + disk_size = models.IntegerField() + vm_type = models.ForeignKey(VirtualMachineType) + client = models.ManyToManyField(CustomUser) + price = models.FloatField() + + @classmethod + def create(cls, data, user): + instance = cls.objects.create(**data) + instance.client.add(user) + + + + + + + + + + + diff --git a/hosting/static/hosting/css/landing-page.css b/hosting/static/hosting/css/landing-page.css index 24be9dec..cbbc5898 100644 --- a/hosting/static/hosting/css/landing-page.css +++ b/hosting/static/hosting/css/landing-page.css @@ -31,8 +31,7 @@ h6 { } .intro-header { - height: 85%; - padding-top: 10%; /* If you're making other pages, make sure there is 50px of padding to make sure the navbar doesn't overlap content! */ + padding-top: 50px; /* If you're making other pages, make sure there is 50px of padding to make sure the navbar doesn't overlap content! */ padding-bottom: 50px; text-align: center; color: #f8f8f8; @@ -48,8 +47,7 @@ h6 { background-size: cover; } .intro-header-2 { - height: 85%; - padding-top: 100px; /* If you're making other pages, make sure there is 50px of padding to make sure the navbar doesn't overlap content! */ + padding-top: 50px; /* If you're making other pages, make sure there is 50px of padding to make sure the navbar doesn't overlap content! */ padding-bottom: 50px; text-align: center; color: #f8f8f8; @@ -61,10 +59,23 @@ h6 { padding-top: 20%; padding-bottom: 20%; } -.intro-signup { + +.intro-auth { + text-align: center; + color: #f8f8f8; position: relative; - padding-top: 20%; - padding-bottom: 20%; + padding-bottom: 25%; + padding-top: 10%; +} + +.intro-login { + background: url(../img/intro-bg.jpg) no-repeat center center; + background-size: cover; +} + +.intro-signup { + background: url(../img/configure.jpg) no-repeat center center; + background-size: cover; } .intro-message > h1 { @@ -202,4 +213,12 @@ a#forgotpassword { line-height: 1; font-weight: 700; color: #6db97c; +} + +a.unlink { + color: inherit; +} + +a.unlink:hover { + color: inherit; } \ No newline at end of file diff --git a/hosting/static/hosting/css/payment.css b/hosting/static/hosting/css/payment.css new file mode 100644 index 00000000..57e830f4 --- /dev/null +++ b/hosting/static/hosting/css/payment.css @@ -0,0 +1,28 @@ + +.payment-container {padding-top:5%; 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 { + border-color: red; + outline: 0; + 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 { + font-weight: bold; + color: red; + padding: 2px 8px; + margin-top: 2px; +} +.creditcard-box .payment-errors { + font-weight: bold; + color: red; + padding: 2px 8px; + margin-top: 2px; +} + +.summary-box .content { + + padding-top: 15px; + +} \ No newline at end of file diff --git a/hosting/static/hosting/js/payment.js b/hosting/static/hosting/js/payment.js new file mode 100644 index 00000000..05704cb9 --- /dev/null +++ b/hosting/static/hosting/js/payment.js @@ -0,0 +1,140 @@ +$( document ).ready(function() { + + $.ajaxSetup({ + beforeSend: function(xhr, settings) { + function getCookie(name) { + var cookieValue = null; + if (document.cookie && document.cookie != '') { + var cookies = document.cookie.split(';'); + for (var i = 0; i < cookies.length; i++) { + var cookie = jQuery.trim(cookies[i]); + // Does this cookie string begin with the name we want? + if (cookie.substring(0, name.length + 1) == (name + '=')) { + cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); + break; + } + } + } + return cookieValue; + } + if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) { + // Only send the token to relative URLs i.e. locally. + xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken')); + } + } + }); + + + var $form = $('#payment-form'); + $form.submit(payWithStripe); + + /* If you're using Stripe for payments */ + function payWithStripe(e) { + e.preventDefault(); + + /* Visual feedback */ + $form.find('[type=submit]').html('Validating '); + + var PublishableKey = 'pk_test_6pRNASCoBOKtIshFeQd4XMUh'; // Replace with your API publishable key + Stripe.setPublishableKey(PublishableKey); + Stripe.card.createToken($form, function stripeResponseHandler(status, response) { + if (response.error) { + /* Visual feedback */ + $form.find('[type=submit]').html('Try again'); + /* Show Stripe errors on the form */ + $form.find('.payment-errors').text(response.error.message); + $form.find('.payment-errors').closest('.row').show(); + } else { + /* Visual feedback */ + $form.find('[type=submit]').html('Processing '); + /* Hide Stripe errors on the form */ + $form.find('.payment-errors').closest('.row').hide(); + $form.find('.payment-errors').text(""); + // response contains id and card, which contains additional card details + var token = response.id; + console.log(token); + // AJAX + + //set token on a hidden input + $('#id_token').val(token); + $('#billing-form').submit(); + + // $.post('/hosting/payment/', { + // token: token, + // }) + // // Assign handlers immediately after making the request, + // .done(function(data, textStatus, jqXHR) { + + // $form.find('[type=submit]').html('Payment successful ').prop('disabled', true); + // }) + // .fail(function(jqXHR, textStatus, errorThrown) { + // $form.find('[type=submit]').html('There was a problem').removeClass('success').addClass('error'); + // /* Show Stripe errors on the form */ + // $form.find('.payment-errors').text('Try refreshing the page and trying again.'); + // $form.find('.payment-errors').closest('.row').show(); + // }); + } + }); + } + + /* Form validation */ + $.validator.addMethod("month", function(value, element) { + return this.optional(element) || /^(01|02|03|04|05|06|07|08|09|10|11|12)$/.test(value); + }, "Please specify a valid 2-digit month."); + + $.validator.addMethod("year", function(value, element) { + return this.optional(element) || /^[0-9]{2}$/.test(value); + }, "Please specify a valid 2-digit year."); + + validator = $form.validate({ + rules: { + cardNumber: { + required: true, + creditcard: true, + digits: true + }, + expMonth: { + required: true, + month: true + }, + expYear: { + required: true, + year: true + }, + cvCode: { + required: true, + digits: true + } + }, + highlight: function(element) { + $(element).closest('.form-control').removeClass('success').addClass('error'); + }, + unhighlight: function(element) { + $(element).closest('.form-control').removeClass('error').addClass('success'); + }, + errorPlacement: function(error, element) { + $(element).closest('.form-group').append(error); + } + }); + + paymentFormReady = function() { + if ($form.find('[name=cardNumber]').hasClass("success") && + $form.find('[name=expMonth]').hasClass("success") && + $form.find('[name=expYear]').hasClass("success") && + $form.find('[name=cvCode]').val().length > 1) { + return true; + } else { + return false; + } + } + + $form.find('[type=submit]').prop('disabled', true); + var readyInterval = setInterval(function() { + if (paymentFormReady()) { + $form.find('[type=submit]').prop('disabled', false); + clearInterval(readyInterval); + } + }, 250); + +}); + diff --git a/hosting/static/hosting/js/pricing.js b/hosting/static/hosting/js/pricing.js index 64e80638..814b4aa9 100644 --- a/hosting/static/hosting/js/pricing.js +++ b/hosting/static/hosting/js/pricing.js @@ -8,8 +8,9 @@ $( document ).ready(function() { function calculate_price(vm_type){ var ID_SELECTOR = "#"; - var CURRENCY = "$"; + var CURRENCY = "CHF"; var final_price_selector = ID_SELECTOR.concat(vm_type.concat('-final-price')); + var final_price_input_selector = final_price_selector.concat('-input'); var core_selector = ID_SELECTOR.concat(vm_type.concat('-cores')); var memory_selector = ID_SELECTOR.concat(vm_type.concat('-memory')); var disk_size_selector = ID_SELECTOR.concat(vm_type.concat('-disk_space')); @@ -27,8 +28,9 @@ $( document ).ready(function() { price += company_prices.memory_price*memory; price += company_prices.disk_size_price*disk_size; - console.log(final_price_selector); + console.log(final_price_input_selector); $(final_price_selector).text(price.toString().concat(CURRENCY)); + $(final_price_input_selector).attr('value', price); } @@ -47,7 +49,5 @@ $( document ).ready(function() { $('.disk-space-selector').on('change',change_attribute); - console.log("mirame",window.VMTypesData); - }); \ No newline at end of file diff --git a/hosting/templates/hosting/base_short.html b/hosting/templates/hosting/base_short.html new file mode 100644 index 00000000..3ffca138 --- /dev/null +++ b/hosting/templates/hosting/base_short.html @@ -0,0 +1,134 @@ +{% load staticfiles bootstrap3%} + + + + + + + + + + + + Payment + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {% block content %} + {% endblock %} + + + + + + + + + + + + + + + + + + + + + diff --git a/hosting/templates/hosting/includes/_header.html b/hosting/templates/hosting/includes/_header.html index 03e3e5c2..a75e797c 100644 --- a/hosting/templates/hosting/includes/_header.html +++ b/hosting/templates/hosting/includes/_header.html @@ -1,5 +1,4 @@ {% load staticfiles %} -
diff --git a/hosting/templates/hosting/includes/_pricing.html b/hosting/templates/hosting/includes/_pricing.html index 628a65f8..e782ce47 100644 --- a/hosting/templates/hosting/includes/_pricing.html +++ b/hosting/templates/hosting/includes/_pricing.html @@ -17,70 +17,12 @@
- - {% for vm in vm_types %} -
-
-
-
- - - {{vm.hosting_company_name}} - - -

- {{vm.description}} -

-
-
-
-
- - -
-
-
- -
- - - GiB -
-
-
- - - GiB -
-
-

$199

-
-
- -
- - - - - -
-
- {% endfor %} {% for vm in vm_types %}
-
+ + {% csrf_token %} + +
  • @@ -91,7 +33,7 @@
    - {% with ''|center:10 as range %} {% for _ in range %} @@ -106,7 +48,7 @@
    - {% with ''|center:50 as range %} {% for _ in range %} @@ -120,12 +62,13 @@
  • - + GiB
  • -

    {{vm.default_price|floatformat}}$

    + +

    {{vm.default_price|floatformat}}CHF

    per month
  • diff --git a/hosting/templates/hosting/login.html b/hosting/templates/hosting/login.html index 04def39f..4521553c 100644 --- a/hosting/templates/hosting/login.html +++ b/hosting/templates/hosting/login.html @@ -1,148 +1,33 @@ +{% extends "hosting/base_short.html" %} {% load staticfiles bootstrap3%} - - +{% block content %} - +
  • Ubuntu 14.04 as the operating system, full root access!

  • -
  • rbenv to let you decide which Ruby version you want to use

  • s +
  • rbenv to let you decide which Ruby version you want to use

  • nginx as the frontend Server (optional with SSL Support)

  • uwsgi to have your application talk to nginx and vice versa

  • PostgreSQL as the database

    diff --git a/hosting/templates/hosting/signup.html b/hosting/templates/hosting/signup.html index d92957bc..663d67b5 100644 --- a/hosting/templates/hosting/signup.html +++ b/hosting/templates/hosting/signup.html @@ -1,149 +1,29 @@ +{% extends "hosting/base_short.html" %} {% load staticfiles bootstrap3%} - - - - - - - - - - - - - Signup - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    +{% block content %} +