diff --git a/digitalglarus/urls.py b/digitalglarus/urls.py index b5329bab..4270fe2b 100644 --- a/digitalglarus/urls.py +++ b/digitalglarus/urls.py @@ -6,8 +6,8 @@ from .views import ContactView, IndexView, AboutView urlpatterns = [ # url(r'^$', IndexView.as_view(), name='home'), -# url(_(r'home/?$'), IndexView.as_view(), name='home'), -# url(_(r'about/?$'), AboutView.as_view(), name='about'), + url(_(r'home/?$'), IndexView.as_view(), name='home'), + url(_(r'about/?$'), AboutView.as_view(), name='about'), url(_(r'contact/?$'), ContactView.as_view(), name='contact'), url(_(r'supporters/?$'), views.supporters, name='supporters'), url(r'calendar_api/(?P\d+)/(?P\d+)?$',views.CalendarApi.as_view()), diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index 6daec940..9d64dc60 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 @@ -445,8 +446,8 @@ AUTH_USER_MODEL = 'membership.CustomUser' # PAYMENT -STRIPE_API_PUBLIC_KEY = 'pk_test_uvWyHNJgVL2IB8kjfgJkGjg4' # used in frontend to call from user browser -STRIPE_API_PRIVATE_KEY = 'sk_test_uIPMdgXoRGydrcD7fkwcn7dj' # used in backend payment +STRIPE_API_PUBLIC_KEY = 'pk_test_QqBZ50Am8KOxaAlOxbcm9Psl' # used in frontend to call from user browser +STRIPE_API_PRIVATE_KEY = 'sk_test_dqAmbKAij12QCGfkYZ3poGt2' # used in backend payment STRIPE_DESCRIPTION_ON_PAYMENT = "Payment for ungleich GmbH services" # EMAIL MESSAGES diff --git a/dynamicweb/urls.py b/dynamicweb/urls.py index 8cb54776..d813ab31 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'), @@ -18,16 +20,15 @@ urlpatterns = [ # note the django CMS URLs included via i18n_patterns urlpatterns += i18n_patterns('', - # url(r'^$',include('ungleich.urls')), url(r'^admin/', include(admin.site.urls)), url(r'^digitalglarus/login/', include(membership_urls)), url(r'^digitalglarus/', include('digitalglarus.urls', namespace="digitalglarus"), - name='digitalglarus'), url(r'^blog/', include('ungleich.urls', namespace='ungleich')), url(r'^ungleich_page/', include('ungleich_page.urls', namespace='ungleich_page'), name='ungleich_page'), + url(r'^blog/',include('ungleich.urls',namespace='ungleich')), url(r'^', include('cms.urls')), ) 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/migrations/0009_auto_20160426_0444.py b/hosting/migrations/0009_auto_20160426_0444.py new file mode 100644 index 00000000..fdc51b6e --- /dev/null +++ b/hosting/migrations/0009_auto_20160426_0444.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2016-04-26 04:44 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('utils', '0002_billingaddress'), + ('membership', '0004_stripecustomer'), + ('hosting', '0008_virtualmachineplan'), + ] + + operations = [ + migrations.CreateModel( + name='HostingOrder', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ], + ), + migrations.RemoveField( + model_name='virtualmachineplan', + name='client', + ), + migrations.AddField( + model_name='hostingorder', + name='VMPlan', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hosting.VirtualMachinePlan'), + ), + migrations.AddField( + model_name='hostingorder', + name='billing_address', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='utils.BillingAddress'), + ), + migrations.AddField( + model_name='hostingorder', + name='customer', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='membership.StripeCustomer'), + ), + ] diff --git a/hosting/migrations/0010_auto_20160426_0530.py b/hosting/migrations/0010_auto_20160426_0530.py new file mode 100644 index 00000000..756c1446 --- /dev/null +++ b/hosting/migrations/0010_auto_20160426_0530.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2016-04-26 05:30 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hosting', '0009_auto_20160426_0444'), + ] + + operations = [ + migrations.AddField( + model_name='hostingorder', + name='approved', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='hostingorder', + name='stripe_charge_id', + field=models.CharField(max_length=100, null=True), + ), + ] diff --git a/hosting/migrations/0011_auto_20160426_0555.py b/hosting/migrations/0011_auto_20160426_0555.py new file mode 100644 index 00000000..2eedbcc9 --- /dev/null +++ b/hosting/migrations/0011_auto_20160426_0555.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2016-04-26 05:55 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('hosting', '0010_auto_20160426_0530'), + ] + + operations = [ + migrations.AlterField( + model_name='hostingorder', + name='VMPlan', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='hosting.VirtualMachinePlan'), + ), + ] 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..f3e2a178 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -1,7 +1,10 @@ +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 StripeCustomer +from utils.models import BillingAddress class RailsBetaUser(models.Model): @@ -42,7 +45,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 +72,51 @@ 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) + price = models.FloatField() + + @classmethod + def create(cls, data, user): + instance = cls.objects.create(**data) + return instance + + +class HostingOrder(models.Model): + VMPlan = models.OneToOneField(VirtualMachinePlan) + customer = models.ForeignKey(StripeCustomer) + billing_address = models.ForeignKey(BillingAddress) + created_at = models.DateTimeField(auto_now_add=True) + approved = models.BooleanField(default=False) + stripe_charge_id = models.CharField(max_length=100, null=True) + + @classmethod + def create(cls, VMPlan=None, customer=None, billing_address=None): + instance = cls.objects.create(VMPlan=VMPlan, customer=customer, + billing_address=billing_address) + return instance + + def set_approved(self): + self.approved = True + self.save() + + def set_stripe_charge(self, stripe_charge): + self.stripe_charge_id = stripe_charge.id + self.save() + + + + + + + + + + + + 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..dd6c64d4 --- /dev/null +++ b/hosting/static/hosting/js/payment.js @@ -0,0 +1,124 @@ +$( 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 = window.stripeKey; + 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; + // AJAX + + //set token on a hidden input + $('#id_token').val(token); + $('#billing-form').submit(); + } + }); + } + + /* 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 %} - +