Merge pull request #41 from levivm/feature/vm_pricing

Billing Address Form added, Fixes on login / signup, Stripe test.
This commit is contained in:
tmslav 2016-04-23 19:01:16 +02:00
commit 81281e9cd7
21 changed files with 998 additions and 431 deletions

View file

@ -30,6 +30,7 @@ dotenv.read_dotenv("{0}/.env".format(PROJECT_DIR))
SITE_ID = 1 SITE_ID = 1
APP_ROOT_ENDPOINT = "/" APP_ROOT_ENDPOINT = "/"
APPEND_SLASH=True
LOGIN_URL = None LOGIN_URL = None
LOGOUT_URL = None LOGOUT_URL = None

View file

@ -5,12 +5,14 @@ from django.conf.urls.i18n import i18n_patterns
from django.conf.urls.static import static from django.conf.urls.static import static
from django.conf import settings from django.conf import settings
from hosting.views import RailsHostingView from hosting.views import RailsHostingView, DjangoHostingView, NodeJSHostingView
from membership import urls as membership_urls from membership import urls as membership_urls
urlpatterns = [ urlpatterns = [
url(r'^hosting/', include('hosting.urls', namespace="hosting")), url(r'^hosting/', include('hosting.urls', namespace="hosting")),
url(r'^railshosting/', RailsHostingView.as_view(), name="rails.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'^taggit_autosuggest/', include('taggit_autosuggest.urls')),
url(r'^jsi18n/(?P<packages>\S+?)/$', url(r'^jsi18n/(?P<packages>\S+?)/$',
'django.views.i18n.javascript_catalog'), 'django.views.i18n.javascript_catalog'),

View file

@ -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')),
],
),
]

20
hosting/mixins.py Normal file
View file

@ -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'))

View file

@ -1,7 +1,9 @@
import json
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.core import serializers from django.core import serializers
import json from membership.models import CustomUser
class RailsBetaUser(models.Model): class RailsBetaUser(models.Model):
@ -42,7 +44,13 @@ class VirtualMachineType(models.Model):
def get_serialized_vm_types(cls): def get_serialized_vm_types(cls):
return [vm.get_serialized_data() return [vm.get_serialized_data()
for vm in cls.objects.all()] 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): def defeault_price(self):
price = self.base_price price = self.base_price
@ -63,3 +71,28 @@ class VirtualMachineType(models.Model):
'default_price': self.defeault_price(), 'default_price': self.defeault_price(),
'id': self.id, '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)

View file

@ -31,8 +31,7 @@ h6 {
} }
.intro-header { .intro-header {
height: 85%; 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-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-bottom: 50px; padding-bottom: 50px;
text-align: center; text-align: center;
color: #f8f8f8; color: #f8f8f8;
@ -48,8 +47,7 @@ h6 {
background-size: cover; background-size: cover;
} }
.intro-header-2 { .intro-header-2 {
height: 85%; 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-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-bottom: 50px; padding-bottom: 50px;
text-align: center; text-align: center;
color: #f8f8f8; color: #f8f8f8;
@ -61,10 +59,23 @@ h6 {
padding-top: 20%; padding-top: 20%;
padding-bottom: 20%; padding-bottom: 20%;
} }
.intro-signup {
.intro-auth {
text-align: center;
color: #f8f8f8;
position: relative; position: relative;
padding-top: 20%; padding-bottom: 25%;
padding-bottom: 20%; 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 { .intro-message > h1 {
@ -203,3 +214,11 @@ a#forgotpassword {
font-weight: 700; font-weight: 700;
color: #6db97c; color: #6db97c;
} }
a.unlink {
color: inherit;
}
a.unlink:hover {
color: inherit;
}

View file

@ -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;
}

View file

@ -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 <i class="fa fa-spinner fa-pulse"></i>');
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 <i class="fa fa-spinner fa-pulse"></i>');
/* 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 <i class="fa fa-check"></i>').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);
});

View file

@ -8,8 +8,9 @@ $( document ).ready(function() {
function calculate_price(vm_type){ function calculate_price(vm_type){
var ID_SELECTOR = "#"; var ID_SELECTOR = "#";
var CURRENCY = "$"; var CURRENCY = "CHF";
var final_price_selector = ID_SELECTOR.concat(vm_type.concat('-final-price')); 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 core_selector = ID_SELECTOR.concat(vm_type.concat('-cores'));
var memory_selector = ID_SELECTOR.concat(vm_type.concat('-memory')); var memory_selector = ID_SELECTOR.concat(vm_type.concat('-memory'));
var disk_size_selector = ID_SELECTOR.concat(vm_type.concat('-disk_space')); 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.memory_price*memory;
price += company_prices.disk_size_price*disk_size; 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_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); $('.disk-space-selector').on('change',change_attribute);
console.log("mirame",window.VMTypesData);
}); });

View file

@ -0,0 +1,134 @@
{% load staticfiles bootstrap3%}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<title>Payment</title>
<!-- Bootstrap Core CSS -->
<link href="{% static 'hosting/css/bootstrap.min.css' %}" rel="stylesheet">
<!-- Custom CSS -->
<link href="{% static 'hosting/css/landing-page.css' %}" rel="stylesheet">
<link href="{% static 'hosting/css/payment.css' %}" rel="stylesheet">
<!-- Custom Fonts -->
<link href='http://fonts.googleapis.com/css?family=Raleway' rel='stylesheet' type='text/css'>
<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">
<link href="http://fonts.googleapis.com/css?family=Lato:300,400,700,300italic,400italic,700italic" rel="stylesheet" type="text/css">
<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 -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<!-- Navigation -->
<nav class="navbar navbar-default navbar-fixed-top topnav" role="navigation">
<div class="container topnav">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand topnav" href="#"><img src="img/logo_black.svg"></a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav navbar-right">
<li>
<a href="#how">How it works</a>
</li>
<li>
<a href="#your">Your infrastructure</a>
</li>
<li>
<a href="#our">Our inftrastructure</a>
</li>
<li>
<a href="#price">Pricing</a>
</li>
<li>
<a href="#contact">Contact</a>
</li>
</ul>
</div>
<!-- /.navbar-collapse -->
</div>
<!-- /.container -->
</nav>
<!-- Header -->
<a name="about"></a>
{% block content %}
{% endblock %}
<!-- Footer -->
<footer>
<div class="container">
<div class="row">
<div class="col-lg-12">
<ul class="list-inline">
<li>
<a href="#">Home</a>
</li>
<li class="footer-menu-divider">&sdot;</li>
<li>
<a href="#about">How it works</a></li>
<li class="footer-menu-divider">&sdot;</li>
<li>
<a href="#about">Your infrastructure</a></li>
<li>&sdot;</li>
<li>
<a href="#about">Our infrastructure</a></li>
<li class="footer-menu-divider">&sdot;</li>
<li>
<a href="#services">Pricing</a>
</li>
<li class="footer-menu-divider">&sdot;</li>
<li>
<a href="#contact">Contact</a>
</li>
</ul>
<p class="copyright text-muted small">Copyright &copy; ungleich GmbH {% now "Y" %}. All Rights Reserved</p>
</div>
</div>
</div>
</footer>
<!-- jQuery -->
<script src="{% static 'hosting/js/jquery.js' %}"></script>
<script type="text/javascript" src="http://cdn.jsdelivr.net/jquery.validation/1.13.1/jquery.validate.min.js"></script>
<!-- Bootstrap Core JavaScript -->
<script src="{% static 'hosting/js/bootstrap.min.js' %}"></script>
<!-- Stripe Lib -->
<script type="text/javascript" src="https://js.stripe.com/v2/"></script>
<!-- Proccess payment lib -->
<script type="text/javascript" src="{% static 'hosting/js/payment.js' %}"></script>
</body>
</html>

View file

@ -1,5 +1,4 @@
{% load staticfiles %} {% load staticfiles %}
<a name="about"></a> <a name="about"></a>
<div class="intro-header"> <div class="intro-header">
<div class="container"> <div class="container">

View file

@ -17,70 +17,12 @@
<div class="row text-center"> <div class="row text-center">
<div class="block"> <div class="block">
{% for vm in vm_types %}
<div class="row well pricing">
<form class="form-inline p-green" role="form">
<div class="btn-group col-md-3">
<div class="form-group">
<big>
{{vm.hosting_company_name}}
</big>
<p>
{{vm.description}}
</p>
</div>
</div>
<div class="btn-group col-md-2">
<div class="form-group">
<label for="cores">Cores:</label>
<select class="form-control" id="cores">
{% with ''|center:10 as range %}
{% for _ in range %}
<option>{{ forloop.counter }}</option>
{% endfor %}
{% endwith %}
</select>
</div>
</div>
<div class="btn-group col-md-2">
<label for="memory">Memory: </label>
<div class="form-group">
<select class="form-control short-input" id="memory">
{% with ''|center:50 as range %}
{% for _ in range %}
<option>{{ forloop.counter }}</option>
{% endfor %}
{% endwith %}
</select>
<span>GiB</span>
</div>
</div>
<div class="form-group col-md-2">
<label for="Disk Size">Disk Size: </label>
<input class="form-control short-input" type="number" id="disk_space" min="0" value="0"/>
<span>GiB</span>
</div>
<div class="col-md-2">
<h3>$199</h3>
</div>
<div class="col-md-1">
<button type="submit" class="btn btn-default">Buy it</button>
</div>
<!-- <button type="submit" class="btn btn-default">Submit</button> -->
</form>
</div>
{% endfor %}
{% for vm in vm_types %} {% for vm in vm_types %}
<div class="col-xs-12 col-sm-6 col-md-3"> <div class="col-xs-12 col-sm-6 col-md-3">
<form class="form-inline"> <form class="form-inline" method="POST" action="{{request.path}}">
{% csrf_token %}
<input type="hidden" name="hosting_company" value="{{vm.hosting_company}}">
<input type="hidden" name="hosting_company_name" value="{{vm.hosting_company_name}}">
<ul class="pricing {% cycle 'p-green' 'p-yel' 'p-red' 'p-blue' %}"> <ul class="pricing {% cycle 'p-green' 'p-yel' 'p-red' 'p-blue' %}">
<li style="height:200px;"> <li style="height:200px;">
<!-- <img src="http://bread.pp.ua/n/settings_g.svg" alt=""> --> <!-- <img src="http://bread.pp.ua/n/settings_g.svg" alt=""> -->
@ -91,7 +33,7 @@
<div class="btn-group"> <div class="btn-group">
<div class="form-group"> <div class="form-group">
<label for="cores">Cores: </label> <label for="cores">Cores: </label>
<select class="form-control cores-selector" id="{{vm.hosting_company}}-cores" data-vm-type="{{vm.hosting_company}}"> <select class="form-control cores-selector" name="cores" id="{{vm.hosting_company}}-cores" data-vm-type="{{vm.hosting_company}}">
{% with ''|center:10 as range %} {% with ''|center:10 as range %}
{% for _ in range %} {% for _ in range %}
<option>{{ forloop.counter }}</option> <option>{{ forloop.counter }}</option>
@ -106,7 +48,7 @@
<div class="btn-group"> <div class="btn-group">
<div class="form-group"> <div class="form-group">
<label for="memory">Memory: </label> <label for="memory">Memory: </label>
<select class="form-control memory-selector" id="{{vm.hosting_company}}-memory" data-vm-type="{{vm.hosting_company}}"> <select class="form-control memory-selector" name="memory" id="{{vm.hosting_company}}-memory" data-vm-type="{{vm.hosting_company}}">
{% with ''|center:50 as range %} {% with ''|center:50 as range %}
{% for _ in range %} {% for _ in range %}
<option>{{ forloop.counter }}</option> <option>{{ forloop.counter }}</option>
@ -120,12 +62,13 @@
<li class="row"> <li class="row">
<div class="form-group"> <div class="form-group">
<label for="Disk Size">Disk Size: </label> <label for="Disk Size">Disk Size: </label>
<input class="form-control short-input disk-space-selector" type="number" id="{{vm.hosting_company}}-disk_space" min="10" value="10" data-vm-type="{{vm.hosting_company}}"/> <input class="form-control short-input disk-space-selector" name="disk_space" type="number" id="{{vm.hosting_company}}-disk_space" min="10" value="10" data-vm-type="{{vm.hosting_company}}"/>
<span>GiB</span> <span>GiB</span>
</div> </div>
</li> </li>
<li> <li>
<h3 id="{{vm.hosting_company}}-final-price">{{vm.default_price|floatformat}}$</h3> <input id="{{vm.hosting_company}}-final-price-input" type="hidden" name="final_price" value="{{vm.default_price|floatformat}}">
<h3 id="{{vm.hosting_company}}-final-price">{{vm.default_price|floatformat}}CHF</h3>
<span>per month</span> <span>per month</span>
</li> </li>
<li> <li>

View file

@ -1,148 +1,33 @@
{% extends "hosting/base_short.html" %}
{% load staticfiles bootstrap3%} {% load staticfiles bootstrap3%}
<!DOCTYPE html> {% block content %}
<html lang="en">
<head> <div class="intro-auth intro-login">
<div class="container">
<div class="col-md-4 col-md-offset-4">
<div class="intro-message">
<h2 class="section-heading">Login</h2>
<form action="{% url 'hosting:login' %}" method="post" class="form" novalidate>
{% csrf_token %}
{% for field in form %}
{% bootstrap_field field show_label=False type='fields'%}
{% endfor %}
{% bootstrap_form_errors form type='non_fields'%}
{% buttons %}
<button type="submit" class="btn btn-default">
Login
</button>
{% endbuttons %}
</form>
<span>Doesn't have an account ? <a class="unlink" href="{% url 'hosting:signup' %}">Sign up</a></span>
<meta charset="utf-8"> <ul class="list-inline intro-social-buttons">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<title>Rails Hosting.ch - Ruby on Rails as easy as possible</title>
<!-- Bootstrap Core CSS -->
<link href="{% static 'hosting/css/bootstrap.min.css' %}" rel="stylesheet">
<!-- Custom CSS -->
<link href="{% static 'hosting/css/landing-page.css' %}" rel="stylesheet">
<!-- Custom Fonts -->
<link href='http://fonts.googleapis.com/css?family=Raleway' rel='stylesheet' type='text/css'>
<link href="font-awesome/css/font-awesome.min.css" rel="stylesheet" type="text/css">
<link href="http://fonts.googleapis.com/css?family=Lato:300,400,700,300italic,400italic,700italic" rel="stylesheet" type="text/css">
<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 -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<!-- Navigation -->
<nav class="navbar navbar-default navbar-fixed-top topnav" role="navigation">
<div class="container topnav">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand topnav" href="#"><img src="img/logo_black.svg"></a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav navbar-right">
<li>
<a href="#how">How it works</a>
</li>
<li>
<a href="#your">Your infrastructure</a>
</li>
<li>
<a href="#our">Our inftrastructure</a>
</li>
<li>
<a href="#price">Pricing</a>
</li>
<li>
<a href="#contact">Contact</a>
</li>
</ul>
</div>
<!-- /.navbar-collapse -->
</div>
<!-- /.container -->
</nav>
<!-- Header -->
<a name="about"></a>
<div class="intro-header">
<div class="container">
<div class="col-md-4">&nbsp;</div><div class="col-md-4">
<div class="intro-message">
<h2>Login</h2>
<form action="{% url 'hosting:login' %}" method="post" class="form" novalidate>
{% csrf_token %}
{% for field in form %}
{% bootstrap_field field show_label=False type='fields'%}
{% endfor %}
{% bootstrap_form_errors form type='non_fields'%}
{% buttons %}
<button type="submit" class="btn btn-default">
Login
</button>
{% endbuttons %}
</form>
<ul class="list-inline intro-social-buttons">
</ul>
</div>
</div>
</div>
<!-- /.container -->
</div>
<!-- /.intro-header -->
<!-- Footer -->
<footer>
<div class="container">
<div class="row">
<div class="col-lg-12">
<ul class="list-inline">
<li>
<a href="#">Home</a>
</li>
<li class="footer-menu-divider">&sdot;</li>
<li>
<a href="#about">How it works</a></li>
<li class="footer-menu-divider">&sdot;</li>
<li>
<a href="#about">Your infrastructure</a></li>
<li>&sdot;</li>
<li>
<a href="#about">Our infrastructure</a></li>
<li class="footer-menu-divider">&sdot;</li>
<li>
<a href="#services">Pricing</a>
</li>
<li class="footer-menu-divider">&sdot;</li>
<li>
<a href="#contact">Contact</a>
</li>
</ul> </ul>
<p class="copyright text-muted small">Copyright &copy; ungleich GmbH {% now "Y" %}. All Rights Reserved</p>
</div> </div>
</div>
</div> </div>
</footer> </div>
<!-- /.container -->
<!-- jQuery --> </div>
<script src="js/jquery.js"></script> {% endblock %}
<!-- Bootstrap Core JavaScript -->
<script src="js/bootstrap.min.js"></script>
</body>
</html>

View file

@ -0,0 +1,103 @@
{% extends "hosting/base_short.html" %}
{% load staticfiles bootstrap3 %}
{%block content %}
<!-- Credit card form -->
<div>
<div class="container payment-container">
<div class="row">
<div class="col-xs-12 col-md-4 col-md-offset-2 billing">
<h3><b>Billing Address</b></h3>
<hr>
<form role="form" id="billing-form" method="post" action="{% url 'hosting:payment' %}" novalidate>
{% for field in form %}
{% csrf_token %}
{% bootstrap_field field show_label=False type='fields'%}
{% endfor %}
{% bootstrap_form_errors form type='non_fields'%}
</form>
</div>
</div>
<div class="row">
<div class="col-xs-12 col-md-4 col-md-offset-2 creditcard-box">
<h3><b>Payment Details</b></h3>
<hr>
<div>
<div>
<form role="form" id="payment-form" novalidate>
<div class="row">
<div class="col-xs-12">
<div class="form-group">
<label class="control-label" for="cardNumber">CARD NUMBER</label>
<div class="input-group">
<input type="text" class="form-control" name="cardNumber" placeholder="Valid Card Number" required autofocus data-stripe="number" />
<span class="input-group-addon"><i class="fa fa-credit-card"></i></span>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-7 col-md-7">
<div class="form-group">
<label for="expMonth">EXPIRATION DATE</label>
<div class="col-xs-6 col-lg-6 pl-ziro">
<input type="text" class="form-control" name="expMonth" placeholder="MM" required data-stripe="exp_month" />
</div>
<div class="col-xs-6 col-lg-6 pl-ziro">
<input type="text" class="form-control" name="expYear" placeholder="YY" required data-stripe="exp_year" />
</div>
</div>
</div>
<div class="col-xs-5 col-md-5 pull-right">
<div class="form-group">
<label for="cvCode">CV CODE</label>
<input type="password" class="form-control" name="cvCode" placeholder="CV" required data-stripe="cvc" />
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<button class="btn btn-success btn-lg btn-block" type="submit">Submit Payment</button>
</div>
</div>
<div class="row" style="display:none;">
<div class="col-xs-12">
<p class="payment-errors"></p>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="col-xs-12 col-md-3 col-md-offset-1 summary-box">
<form role="form" novalidate>
<div class="row">
<div class="col-xs-12">
<h3><b>Billing Amount</b></h3>
<hr>
<div class="content">
<p><b>Type</b> <span class="pull-right">{{request.session.vm_specs.hosting_company_name}}</span></p>
<hr>
<p><b>Cores</b> <span class="pull-right">{{request.session.vm_specs.cores}}</span></p>
<hr>
<p><b>Memory</b> <span class="pull-right">{{request.session.vm_specs.memory}} GiB</span></p>
<hr>
<p><b>Disk space</b> <span class="pull-right">{{request.session.vm_specs.disk_size}} GiB</span></p>
<hr>
<h4>Total<p class="pull-right"><b>{{request.session.vm_specs.final_price}} CHF</b></p></h4>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
{%endblock%}

View file

@ -5,7 +5,7 @@
<li><i class="fa-li fa fa-check-square-o fa-lg"></i> <li><i class="fa-li fa fa-check-square-o fa-lg"></i>
<p class="lead">Ubuntu 14.04 as the operating system, full root access!</p> <p class="lead">Ubuntu 14.04 as the operating system, full root access!</p>
</li> </li>
<li><i class="fa-li fa fa-check-square-o fa-lg"></i><p class="lead">rbenv to let you decide which Ruby version you want to use</p></li>s <li><i class="fa-li fa fa-check-square-o fa-lg"></i><p class="lead">rbenv to let you decide which Ruby version you want to use</p></li>
<li><i class="fa-li fa fa-check-square-o fa-lg"></i><p class="lead">nginx as the frontend Server (optional with SSL Support)</p></li> <li><i class="fa-li fa fa-check-square-o fa-lg"></i><p class="lead">nginx as the frontend Server (optional with SSL Support)</p></li>
<li><i class="fa-li fa fa-check-square-o fa-lg"></i><p class="lead">uwsgi to have your application talk to nginx and vice versa <li><i class="fa-li fa fa-check-square-o fa-lg"></i><p class="lead">uwsgi to have your application talk to nginx and vice versa
<li><i class="fa-li fa fa-check-square-o fa-lg"></i><p class="lead">PostgreSQL as the database</p> <li><i class="fa-li fa fa-check-square-o fa-lg"></i><p class="lead">PostgreSQL as the database</p>

View file

@ -1,149 +1,29 @@
{% extends "hosting/base_short.html" %}
{% load staticfiles bootstrap3%} {% load staticfiles bootstrap3%}
{% block content %}
<!DOCTYPE html> <div class="intro-auth intro-signup">
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<title>Signup</title>
<!-- Bootstrap Core CSS -->
<link href="{% static 'hosting/css/bootstrap.min.css' %}" rel="stylesheet">
<!-- Custom CSS -->
<link href="{% static 'hosting/css/landing-page.css' %}" rel="stylesheet">
<!-- Custom Fonts -->
<link href='http://fonts.googleapis.com/css?family=Raleway' rel='stylesheet' type='text/css'>
<link href="font-awesome/css/font-awesome.min.css" rel="stylesheet" type="text/css">
<link href="http://fonts.googleapis.com/css?family=Lato:300,400,700,300italic,400italic,700italic" rel="stylesheet" type="text/css">
<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 -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<!-- Navigation -->
<nav class="navbar navbar-default navbar-fixed-top topnav" role="navigation">
<div class="container topnav">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand topnav" href="#"><img src="{% static 'hosting/img/logo_black.svg' %}"></a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav navbar-right">
<li>
<a href="#how">How it works</a>
</li>
<li>
<a href="#your">Your infrastructure</a>
</li>
<li>
<a href="#our">Our inftrastructure</a>
</li>
<li>
<a href="#price">Pricing</a>
</li>
<li>
<a href="#contact">Contact</a>
</li>
</ul>
</div>
<!-- /.navbar-collapse -->
</div>
<!-- /.container -->
</nav>
<!-- Header -->
<a name="about"></a>
<div class="intro-header-2">
<div class="container"> <div class="container">
<div class="col-md-4">&nbsp;</div><div class="col-md-4"> <div class="col-md-4">&nbsp;</div>
<div class="intro-message"> <div class="col-md-4">
<h2>Sign up</h2> <div class="intro-message">
<form action="{% url 'hosting:signup' %}" method="post" class="form" novalidate> <h2 class="section-heading">Sign up</h2>
{% csrf_token %}
{% for field in form %}
{% bootstrap_field field show_label=False %}
{% endfor %}
{% buttons %}
<button type="submit" class="btn btn-default">
Signup
</button>
{% endbuttons %}
</form>
<ul class="list-inline intro-social-buttons">
</ul> <form action="{% url 'hosting:signup' %}" method="post" class="form" novalidate>
{% csrf_token %}
{% for field in form %}
{% bootstrap_field field show_label=False %}
{% endfor %}
{% buttons %}
<button type="submit" class="btn btn-default">
Sign up
</button>
{% endbuttons %}
</form>
<span>Already have an account ? <a class="unlink" href="{% url 'hosting:login' %}">Log in</a></span>
<ul class="list-inline intro-social-buttons">
</ul>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- /.container --> {% endblock %}
</div>
<!-- /.intro-header -->
<!-- Footer -->
<footer>
<div class="container">
<div class="row">
<div class="col-lg-12">
<ul class="list-inline">
<li>
<a href="#">Home</a>
</li>
<li class="footer-menu-divider">&sdot;</li>
<li>
<a href="#about">How it works</a></li>
<li class="footer-menu-divider">&sdot;</li>
<li>
<a href="#about">Your infrastructure</a></li>
<li>&sdot;</li>
<li>
<a href="#about">Our infrastructure</a></li>
<li class="footer-menu-divider">&sdot;</li>
<li>
<a href="#services">Pricing</a>
</li>
<li class="footer-menu-divider">&sdot;</li>
<li>
<a href="#contact">Contact</a>
</li>
</ul>
<p class="copyright text-muted small">Copyright &copy; ungleich GmbH {% now "Y" %}. All Rights Reserved</p>
</div>
</div>
</div>
</footer>
<!-- jQuery -->
<script src="js/jquery.js"></script>
<!-- Bootstrap Core JavaScript -->
<script src="js/bootstrap.min.js"></script>
</body>
</html>

View file

@ -1,14 +1,14 @@
from django.conf.urls import url from django.conf.urls import url
from .views import VMPricingView, DjangoHostingView, RailsHostingView, \ from .views import DjangoHostingView, RailsHostingView, PaymentVMView, \
NodeJSHostingView, LoginView, SignupView, IndexView NodeJSHostingView, LoginView, SignupView, IndexView
urlpatterns = [ urlpatterns = [
url(r'index/?$', IndexView.as_view(), name='index'), url(r'index/?$', IndexView.as_view(), name='index'),
url(r'pricing/?$', VMPricingView.as_view(), name='pricing'),
url(r'django/?$', DjangoHostingView.as_view(), name='djangohosting'), url(r'django/?$', DjangoHostingView.as_view(), name='djangohosting'),
url(r'nodejs/?$', NodeJSHostingView.as_view(), name='nodejshosting'), url(r'nodejs/?$', NodeJSHostingView.as_view(), name='nodejshosting'),
url(r'rails/?$', RailsHostingView.as_view(), name='railshosting'), url(r'rails/?$', RailsHostingView.as_view(), name='railshosting'),
url(r'login/?$', LoginView.as_view(), name='login'), url(r'login/?$', LoginView.as_view(), name='login'),
url(r'signup/?$', SignupView.as_view(), name='signup'), url(r'signup/?$', SignupView.as_view(), name='signup'),
url(r'payment/?$', PaymentVMView.as_view(), name='payment'),
] ]

View file

@ -3,50 +3,52 @@ from django.shortcuts import get_object_or_404, render
from django.core.urlresolvers import reverse_lazy, reverse from django.core.urlresolvers import reverse_lazy, reverse
from django.views.generic import View, CreateView, FormView from django.views.generic import View, CreateView, FormView
from django.shortcuts import redirect
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.contrib.auth import authenticate, login from django.contrib.auth import authenticate, login
from django.conf import settings
from membership.forms import PaymentForm
from membership.models import CustomUser from membership.models import CustomUser
from .models import RailsBetaUser, VirtualMachineType from utils.forms import BillingAddressForm
from .models import VirtualMachineType, VirtualMachinePlan
from .forms import HostingUserSignupForm, HostingUserLoginForm from .forms import HostingUserSignupForm, HostingUserLoginForm
from .mixins import ProcessVMSelectionMixin
class VMPricingView(View): class DjangoHostingView(ProcessVMSelectionMixin, View):
template_name = "hosting/pricing.html"
def get(self, request, *args, **kwargs):
return render(request, self.template_name, request)
class DjangoHostingView(View):
template_name = "hosting/django.html" template_name = "hosting/django.html"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = {} context = {
context["hosting"] = "django" 'hosting': "django",
context["hosting_long"] = "Django" 'hosting_long': "Django",
context["domain"] = "django-hosting.ch" 'domain': "django-hosting.ch",
context["google_analytics"] = "UA-62285904-6" 'google_analytics': "UA-62285904-6",
context["email"] = "info@django-hosting.ch" 'email': "info@django-hosting.ch",
context["vm_types"] = VirtualMachineType.get_serialized_vm_types() 'vm_types': VirtualMachineType.get_serialized_vm_types(),
}
return context return context
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
context = self.get_context_data() context = self.get_context_data()
return render(request, self.template_name, context) return render(request, self.template_name, context)
class RailsHostingView(View): class RailsHostingView(ProcessVMSelectionMixin, View):
template_name = "hosting/rails.html" template_name = "hosting/rails.html"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = {} context = {
context["hosting"] = "rails" 'hosting': "rails",
context["hosting_long"] = "Ruby On Rails" 'hosting_long': "Ruby On Rails",
context["domain"] = "rails-hosting.ch" 'domain': "rails-hosting.ch",
context["google_analytics"] = "UA-62285904-5" 'google_analytics': "UA-62285904-5",
context["email"] = "info@rails-hosting.ch" 'email': "info@rails-hosting.ch",
context["vm_types"] = VirtualMachineType.get_serialized_vm_types() 'vm_types': VirtualMachineType.get_serialized_vm_types(),
}
return context return context
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
@ -54,21 +56,24 @@ class RailsHostingView(View):
return render(request, self.template_name, context) return render(request, self.template_name, context)
class NodeJSHostingView(View): class NodeJSHostingView(ProcessVMSelectionMixin, View):
template_name = "hosting/nodejs.html" template_name = "hosting/nodejs.html"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = {} context = {
context["hosting"] = "nodejs" 'hosting': "nodejs",
context["hosting_long"] = "NodeJS" 'hosting_long': "NodeJS",
context["domain"] = "node-hosting.ch" 'domain': "node-hosting.ch",
context["google_analytics"] = "UA-62285904-7" 'google_analytics': "UA-62285904-7",
context["email"] = "info@node-hosting.ch" 'email': "info@node-hosting.ch",
context["vm_types"] = VirtualMachineType.get_serialized_vm_types() 'vm_types': VirtualMachineType.get_serialized_vm_types(),
}
return context return context
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
context = self.get_context_data() context = self.get_context_data()
return render(request, self.template_name, context) return render(request, self.template_name, context)
@ -76,33 +81,39 @@ class IndexView(View):
template_name = "hosting/index.html" template_name = "hosting/index.html"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = {} context = {
context["hosting"] = "nodejs" 'hosting': "nodejs",
context["hosting_long"] = "NodeJS" 'hosting_long': "NodeJS",
context["domain"] = "node-hosting.ch" 'domain': "node-hosting.ch",
context["google_analytics"] = "UA-62285904-7" 'google_analytics': "UA-62285904-7",
context["email"] = "info@node-hosting.ch" 'email': "info@node-hosting.ch",
context["vm_types"] = VirtualMachineType.get_serialized_vm_types() 'vm_types': VirtualMachineType.get_serialized_vm_types(),
}
return context return context
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
context = self.get_context_data() context = self.get_context_data()
return render(request, self.template_name, context) return render(request, self.template_name, context)
class LoginView(FormView): class LoginView(FormView):
template_name = 'hosting/login.html' template_name = 'hosting/login.html'
success_url = reverse_lazy('hosting:login')
form_class = HostingUserLoginForm form_class = HostingUserLoginForm
moodel = CustomUser moodel = CustomUser
success_url = reverse_lazy('hosting:login')
def form_valid(self, form): def form_valid(self, form):
email = form.cleaned_data.get('email') email = form.cleaned_data.get('email')
password = form.cleaned_data.get('password') password = form.cleaned_data.get('password')
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)
return HttpResponseRedirect(self.get_success_url()) return HttpResponseRedirect(self.get_success_url())
return HttpResponseRedirect(self.get_success_url()) return HttpResponseRedirect(self.get_success_url())
@ -115,43 +126,52 @@ class SignupView(CreateView):
return reverse_lazy('hosting:signup') return reverse_lazy('hosting:signup')
def form_valid(self, form): def form_valid(self, form):
name = form.cleaned_data.get('name') name = form.cleaned_data.get('name')
email = form.cleaned_data.get('email') email = form.cleaned_data.get('email')
password = form.cleaned_data.get('password') password = form.cleaned_data.get('password')
CustomUser.register(name, password, email) CustomUser.register(name, password, email)
auth_user = authenticate(email=email, password=password) auth_user = authenticate(email=email, password=password)
login(self.request, auth_user) login(self.request, auth_user)
return HttpResponseRedirect(self.get_success_url()) return HttpResponseRedirect(self.get_success_url())
class PaymentVMView(FormView):
template_name = 'hosting/payment.html'
form_class = BillingAddressForm
# class RailsBetaUserForm(ModelForm): def get_context_data(self, **kwargs):
# required_css_class = 'form-control' context = super(PaymentVMView, self).get_context_data(**kwargs)
# class Meta: context.update({
# model = RailsBetaUser 'stripe_key': settings.STRIPE_API_PUBLIC_KEY
# fields = [ 'email' ] })
return context
# def hosting(request, context): def post(self, request, *args, **kwargs):
# email = RailsBetaUser(received_date=datetime.datetime.now()) form = self.get_form()
# if request.method == 'POST': if form.is_valid():
# context['form'] = RailsBetaUserForm(request.POST, instance=email)
# if context['form'].is_valid():
# context['form'].save()
# email = context['form'].cleaned_data['email']
# subject = "%shosting request" % context['hosting']
# message = "Request for beta by: %s" % email
# mail_managers(subject, message) specifications = request.session.get('vm_specs')
vm_type = specifications.get('hosting_company')
vm = VirtualMachineType.objects.get(hosting_company=vm_type)
# return HttpResponseRedirect(reverse("hosting:beta")) plan_data = {
# else: 'vm_type': vm,
# context['form'] = RailsBetaUserForm() 'cores': specifications.get('cores'),
# context['error_message'] = "a problem" 'memory': specifications.get('memory'),
'disk_size': specifications.get('disk_size'),
'price': vm.calculate_price(specifications)
}
# page = "hosting/%s.html" % context['hosting'] # Stripe payment goes here
# return render(request, page, context) # Billing Address should be store here
# def beta(request): VirtualMachinePlan.create(plan_data, request.user)
# return render(request, 'hosting/beta.html')
return HttpResponseRedirect(reverse('hosting:payment'))
else:
return self.form_invalid(form)

256
utils/fields.py Normal file
View file

@ -0,0 +1,256 @@
from django.utils.translation import ugettext as _
from django import forms
# http://xml.coverpages.org/country3166.html
COUNTRIES = (
('AD', _('Andorra')),
('AE', _('United Arab Emirates')),
('AF', _('Afghanistan')),
('AG', _('Antigua & Barbuda')),
('AI', _('Anguilla')),
('AL', _('Albania')),
('AM', _('Armenia')),
('AN', _('Netherlands Antilles')),
('AO', _('Angola')),
('AQ', _('Antarctica')),
('AR', _('Argentina')),
('AS', _('American Samoa')),
('AT', _('Austria')),
('AU', _('Australia')),
('AW', _('Aruba')),
('AZ', _('Azerbaijan')),
('BA', _('Bosnia and Herzegovina')),
('BB', _('Barbados')),
('BD', _('Bangladesh')),
('BE', _('Belgium')),
('BF', _('Burkina Faso')),
('BG', _('Bulgaria')),
('BH', _('Bahrain')),
('BI', _('Burundi')),
('BJ', _('Benin')),
('BM', _('Bermuda')),
('BN', _('Brunei Darussalam')),
('BO', _('Bolivia')),
('BR', _('Brazil')),
('BS', _('Bahama')),
('BT', _('Bhutan')),
('BV', _('Bouvet Island')),
('BW', _('Botswana')),
('BY', _('Belarus')),
('BZ', _('Belize')),
('CA', _('Canada')),
('CC', _('Cocos (Keeling) Islands')),
('CF', _('Central African Republic')),
('CG', _('Congo')),
('CH', _('Switzerland')),
('CI', _('Ivory Coast')),
('CK', _('Cook Iislands')),
('CL', _('Chile')),
('CM', _('Cameroon')),
('CN', _('China')),
('CO', _('Colombia')),
('CR', _('Costa Rica')),
('CU', _('Cuba')),
('CV', _('Cape Verde')),
('CX', _('Christmas Island')),
('CY', _('Cyprus')),
('CZ', _('Czech Republic')),
('DE', _('Germany')),
('DJ', _('Djibouti')),
('DK', _('Denmark')),
('DM', _('Dominica')),
('DO', _('Dominican Republic')),
('DZ', _('Algeria')),
('EC', _('Ecuador')),
('EE', _('Estonia')),
('EG', _('Egypt')),
('EH', _('Western Sahara')),
('ER', _('Eritrea')),
('ES', _('Spain')),
('ET', _('Ethiopia')),
('FI', _('Finland')),
('FJ', _('Fiji')),
('FK', _('Falkland Islands (Malvinas)')),
('FM', _('Micronesia')),
('FO', _('Faroe Islands')),
('FR', _('France')),
('FX', _('France, Metropolitan')),
('GA', _('Gabon')),
('GB', _('United Kingdom (Great Britain)')),
('GD', _('Grenada')),
('GE', _('Georgia')),
('GF', _('French Guiana')),
('GH', _('Ghana')),
('GI', _('Gibraltar')),
('GL', _('Greenland')),
('GM', _('Gambia')),
('GN', _('Guinea')),
('GP', _('Guadeloupe')),
('GQ', _('Equatorial Guinea')),
('GR', _('Greece')),
('GS', _('South Georgia and the South Sandwich Islands')),
('GT', _('Guatemala')),
('GU', _('Guam')),
('GW', _('Guinea-Bissau')),
('GY', _('Guyana')),
('HK', _('Hong Kong')),
('HM', _('Heard & McDonald Islands')),
('HN', _('Honduras')),
('HR', _('Croatia')),
('HT', _('Haiti')),
('HU', _('Hungary')),
('ID', _('Indonesia')),
('IE', _('Ireland')),
('IL', _('Israel')),
('IN', _('India')),
('IO', _('British Indian Ocean Territory')),
('IQ', _('Iraq')),
('IR', _('Islamic Republic of Iran')),
('IS', _('Iceland')),
('IT', _('Italy')),
('JM', _('Jamaica')),
('JO', _('Jordan')),
('JP', _('Japan')),
('KE', _('Kenya')),
('KG', _('Kyrgyzstan')),
('KH', _('Cambodia')),
('KI', _('Kiribati')),
('KM', _('Comoros')),
('KN', _('St. Kitts and Nevis')),
('KP', _('Korea, Democratic People\'s Republic of')),
('KR', _('Korea, Republic of')),
('KW', _('Kuwait')),
('KY', _('Cayman Islands')),
('KZ', _('Kazakhstan')),
('LA', _('Lao People\'s Democratic Republic')),
('LB', _('Lebanon')),
('LC', _('Saint Lucia')),
('LI', _('Liechtenstein')),
('LK', _('Sri Lanka')),
('LR', _('Liberia')),
('LS', _('Lesotho')),
('LT', _('Lithuania')),
('LU', _('Luxembourg')),
('LV', _('Latvia')),
('LY', _('Libyan Arab Jamahiriya')),
('MA', _('Morocco')),
('MC', _('Monaco')),
('MD', _('Moldova, Republic of')),
('MG', _('Madagascar')),
('MH', _('Marshall Islands')),
('ML', _('Mali')),
('MN', _('Mongolia')),
('MM', _('Myanmar')),
('MO', _('Macau')),
('MP', _('Northern Mariana Islands')),
('MQ', _('Martinique')),
('MR', _('Mauritania')),
('MS', _('Monserrat')),
('MT', _('Malta')),
('MU', _('Mauritius')),
('MV', _('Maldives')),
('MW', _('Malawi')),
('MX', _('Mexico')),
('MY', _('Malaysia')),
('MZ', _('Mozambique')),
('NA', _('Namibia')),
('NC', _('New Caledonia')),
('NE', _('Niger')),
('NF', _('Norfolk Island')),
('NG', _('Nigeria')),
('NI', _('Nicaragua')),
('NL', _('Netherlands')),
('NO', _('Norway')),
('NP', _('Nepal')),
('NR', _('Nauru')),
('NU', _('Niue')),
('NZ', _('New Zealand')),
('OM', _('Oman')),
('PA', _('Panama')),
('PE', _('Peru')),
('PF', _('French Polynesia')),
('PG', _('Papua New Guinea')),
('PH', _('Philippines')),
('PK', _('Pakistan')),
('PL', _('Poland')),
('PM', _('St. Pierre & Miquelon')),
('PN', _('Pitcairn')),
('PR', _('Puerto Rico')),
('PT', _('Portugal')),
('PW', _('Palau')),
('PY', _('Paraguay')),
('QA', _('Qatar')),
('RE', _('Reunion')),
('RO', _('Romania')),
('RU', _('Russian Federation')),
('RW', _('Rwanda')),
('SA', _('Saudi Arabia')),
('SB', _('Solomon Islands')),
('SC', _('Seychelles')),
('SD', _('Sudan')),
('SE', _('Sweden')),
('SG', _('Singapore')),
('SH', _('St. Helena')),
('SI', _('Slovenia')),
('SJ', _('Svalbard & Jan Mayen Islands')),
('SK', _('Slovakia')),
('SL', _('Sierra Leone')),
('SM', _('San Marino')),
('SN', _('Senegal')),
('SO', _('Somalia')),
('SR', _('Suriname')),
('ST', _('Sao Tome & Principe')),
('SV', _('El Salvador')),
('SY', _('Syrian Arab Republic')),
('SZ', _('Swaziland')),
('TC', _('Turks & Caicos Islands')),
('TD', _('Chad')),
('TF', _('French Southern Territories')),
('TG', _('Togo')),
('TH', _('Thailand')),
('TJ', _('Tajikistan')),
('TK', _('Tokelau')),
('TM', _('Turkmenistan')),
('TN', _('Tunisia')),
('TO', _('Tonga')),
('TP', _('East Timor')),
('TR', _('Turkey')),
('TT', _('Trinidad & Tobago')),
('TV', _('Tuvalu')),
('TW', _('Taiwan, Province of China')),
('TZ', _('Tanzania, United Republic of')),
('UA', _('Ukraine')),
('UG', _('Uganda')),
('UM', _('United States Minor Outlying Islands')),
('US', _('United States of America')),
('UY', _('Uruguay')),
('UZ', _('Uzbekistan')),
('VA', _('Vatican City State (Holy See)')),
('VC', _('St. Vincent & the Grenadines')),
('VE', _('Venezuela')),
('VG', _('British Virgin Islands')),
('VI', _('United States Virgin Islands')),
('VN', _('Viet Nam')),
('VU', _('Vanuatu')),
('WF', _('Wallis & Futuna Islands')),
('WS', _('Samoa')),
('YE', _('Yemen')),
('YT', _('Mayotte')),
('YU', _('Yugoslavia')),
('ZA', _('South Africa')),
('ZM', _('Zambia')),
('ZR', _('Zaire')),
('ZW', _('Zimbabwe')),
('ZZ', _('Unknown or unspecified country')),
)
class CountryField(forms.ChoiceField):
def __init__(self, *args, **kwargs):
kwargs.setdefault('choices', COUNTRIES)
kwargs.setdefault('initial', 'CH')
super(CountryField, self).__init__(*args, **kwargs)
def get_internal_type(self):
return "CharField"

View file

@ -3,6 +3,23 @@ from .models import ContactMessage
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.core.mail import EmailMultiAlternatives from django.core.mail import EmailMultiAlternatives
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from utils.fields import CountryField
class BillingAddressForm(forms.Form):
street_address = forms.CharField()
city = forms.CharField()
postal_code = forms.CharField()
country = CountryField()
token = forms.CharField(widget=forms.HiddenInput())
class Meta:
labels = {
'street_address': _('Street Address'),
'city': _('City'),
'postal_code': _('Postal Code'),
'Country': _('Country'),
}
class ContactUsForm(forms.ModelForm): class ContactUsForm(forms.ModelForm):

57
utils/stripe_utils.py Normal file
View file

@ -0,0 +1,57 @@
import stripe
from django.conf import settings
class StripeUtils(object):
CURRENCY = 'chf'
INTERVAL = 'month'
SUCCEEDED_STATUS = 'succeeded'
def __init__(self):
self.stripe = stripe
self.stripe.api_key = settings.STRIPE_API_PRIVATE_KEY
def create_plan(self, amount, name, id):
self.stripe.Plan.create(
amount=amount,
interval=self.INTERVAL,
name=name,
currency=self.CURRENCY,
id=id)
def make_payment(self, user, amount, token, time):
try:
# Use Stripe's library to make requests...
charge = self.stripe.Charge.create(
amount=amount,
currency=self.CURRENCY,
source=token,
description=settings.STRIPE_DESCRIPTION_ON_PAYMENT
)
if charge.get('status') == self.SUCCEEDED_STATUS:
# do something
pass
return charge['status']
except self.stripe.error.CardError as e:
# Since it's a decline, stripe.error.CardError will be caught
body = e.json_body
err = body['error']
return err['message']
except self.stripe.error.RateLimitError as e:
return "Too many requests made to the API too quickly"
except self.stripe.error.InvalidRequestError as e:
return "Invalid parameters"
except self.stripe.error.AuthenticationError as e:
# Authentication with Stripe's API failed
# (maybe you changed API keys recently)
pass
except self.stripe.error.APIConnectionError as e:
return "Currently its not possible to make payments."
except self.stripe.error.StripeError as e:
# maybe send email
return "Currently its not possible to make payments."
except Exception as e:
# maybe send email
return "Currently its not possible to make payments."