Created a base template for login / signup, Fixed some html design issues on login / signup pages, Added Billing Address Form to payment page, Handle Billing Address Form validation, Created VirtualMachinePlan model in order to store user purchased VM, Create method in order to create a stripe plan , Investigated about stripe payment workflows

This commit is contained in:
Levi 2016-04-23 02:22:44 -05:00
commit 4777484245
17 changed files with 573 additions and 415 deletions

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

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

@ -59,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 {
@ -201,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

@ -1,4 +1,5 @@
.creditcard-box {padding-top:17%; padding-bottom: 11%;}
.payment-container {padding-top:5%; padding-bottom: 11%;}
.creditcard-box .panel-title {display: inline;font-weight: bold; font-size:17px;} .creditcard-box .panel-title {display: inline;font-weight: bold; font-size:17px;}
.creditcard-box .checkbox.pull-right { margin: 0; } .creditcard-box .checkbox.pull-right { margin: 0; }
.creditcard-box .pl-ziro { padding-left: 0px; } .creditcard-box .pl-ziro { padding-left: 0px; }
@ -20,12 +21,6 @@
margin-top: 2px; margin-top: 2px;
} }
.summary-box {
padding-top:17%;
padding-bottom: 11%;
}
.summary-box .content { .summary-box .content {
padding-top: 15px; padding-top: 15px;

View file

@ -1,5 +1,30 @@
$( document ).ready(function() { $( 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'); var $form = $('#payment-form');
$form.submit(payWithStripe); $form.submit(payWithStripe);
@ -13,7 +38,6 @@ $( document ).ready(function() {
var PublishableKey = 'pk_test_6pRNASCoBOKtIshFeQd4XMUh'; // Replace with your API publishable key var PublishableKey = 'pk_test_6pRNASCoBOKtIshFeQd4XMUh'; // Replace with your API publishable key
Stripe.setPublishableKey(PublishableKey); Stripe.setPublishableKey(PublishableKey);
Stripe.card.createToken($form, function stripeResponseHandler(status, response) { Stripe.card.createToken($form, function stripeResponseHandler(status, response) {
console.log
if (response.error) { if (response.error) {
/* Visual feedback */ /* Visual feedback */
$form.find('[type=submit]').html('Try again'); $form.find('[type=submit]').html('Try again');
@ -30,19 +54,25 @@ $( document ).ready(function() {
var token = response.id; var token = response.id;
console.log(token); console.log(token);
// AJAX // AJAX
$.post('/account/stripe_card_token', {
token: token //set token on a hidden input
}) $('#id_token').val(token);
// Assign handlers immediately after making the request, $('#billing-form').submit();
.done(function(data, textStatus, jqXHR) {
$form.find('[type=submit]').html('Payment successful <i class="fa fa-check"></i>').prop('disabled', true); // $.post('/hosting/payment/', {
}) // token: token,
.fail(function(jqXHR, textStatus, errorThrown) { // })
$form.find('[type=submit]').html('There was a problem').removeClass('success').addClass('error'); // // Assign handlers immediately after making the request,
/* Show Stripe errors on the form */ // .done(function(data, textStatus, jqXHR) {
$form.find('.payment-errors').text('Try refreshing the page and trying again.');
$form.find('.payment-errors').closest('.row').show(); // $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();
// });
} }
}); });
} }

View file

@ -10,6 +10,7 @@ $( document ).ready(function() {
var ID_SELECTOR = "#"; var ID_SELECTOR = "#";
var CURRENCY = "CHF"; 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

@ -129,7 +129,6 @@
<!-- Proccess payment lib --> <!-- Proccess payment lib -->
<script type="text/javascript" src="{% static 'hosting/js/payment.js' %}"></script> <script type="text/javascript" src="{% static 'hosting/js/payment.js' %}"></script>
</body> </body>
</html> </html>

View file

@ -17,67 +17,6 @@
<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" method="POST" action="{{request.path}}"> <form class="form-inline" method="POST" action="{{request.path}}">
@ -128,7 +67,7 @@
</div> </div>
</li> </li>
<li> <li>
<input id="{{vm.hosting_company}}-final-price" type="hidden" name="final_price" value="{{vm.default_price|floatformat}}"> <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> <h3 id="{{vm.hosting_company}}-final-price">{{vm.default_price|floatformat}}CHF</h3>
<span>per month</span> <span>per month</span>
</li> </li>

View file

@ -1,86 +1,12 @@
{% 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">
<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>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 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="container">
<div class="col-md-4 col-md-offset-4"> <div class="col-md-4 col-md-offset-4">
<div class="intro-message"> <div class="intro-message">
<h2>Login</h2> <h2 class="section-heading">Login</h2>
<form action="{% url 'hosting:login' %}" method="post" class="form" novalidate> <form action="{% url 'hosting:login' %}" method="post" class="form" novalidate>
{% csrf_token %} {% csrf_token %}
{% for field in form %} {% for field in form %}
@ -93,6 +19,8 @@
</button> </button>
{% endbuttons %} {% endbuttons %}
</form> </form>
<span>Doesn't have an account ? <a class="unlink" href="{% url 'hosting:signup' %}">Sign up</a></span>
<ul class="list-inline intro-social-buttons"> <ul class="list-inline intro-social-buttons">
</ul> </ul>
@ -101,48 +29,5 @@
</div> </div>
<!-- /.container --> <!-- /.container -->
</div> </div>
<!-- /.intro-header --> {% 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="js/jquery.js"></script>
<!-- Bootstrap Core JavaScript -->
<script src="js/bootstrap.min.js"></script>
</body>
</html>

View file

@ -3,13 +3,26 @@
{%block content %} {%block content %}
<!-- Credit card form --> <!-- Credit card form -->
<div> <div>
<div class="container"> <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="row">
<div class="col-xs-12 col-md-4 col-md-offset-2 creditcard-box"> <div class="col-xs-12 col-md-4 col-md-offset-2 creditcard-box">
<h3><b>Payment Details</b></h3> <h3><b>Payment Details</b></h3>
<hr> <hr>
<div> <div>
<div class="panel-body"> <div>
<form role="form" id="payment-form" novalidate> <form role="form" id="payment-form" novalidate>
<div class="row"> <div class="row">
<div class="col-xs-12"> <div class="col-xs-12">
@ -68,7 +81,7 @@
<hr> <hr>
<p><b>Memory</b> <span class="pull-right">{{request.session.vm_specs.memory}} GiB</span></p> <p><b>Memory</b> <span class="pull-right">{{request.session.vm_specs.memory}} GiB</span></p>
<hr> <hr>
<p><b>Disk space</b> <span class="pull-right">{{request.session.vm_specs.memory}} GiB</span></p> <p><b>Disk space</b> <span class="pull-right">{{request.session.vm_specs.disk_size}} GiB</span></p>
<hr> <hr>
<h4>Total<p class="pull-right"><b>{{request.session.vm_specs.final_price}} CHF</b></p></h4> <h4>Total<p class="pull-right"><b>{{request.session.vm_specs.final_price}} CHF</b></p></h4>
</div> </div>

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,87 +1,13 @@
{% 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="col-md-4">
<div class="intro-message"> <div class="intro-message">
<h2>Sign up</h2> <h2 class="section-heading">Sign up</h2>
<form action="{% url 'hosting:signup' %}" method="post" class="form" novalidate> <form action="{% url 'hosting:signup' %}" method="post" class="form" novalidate>
{% csrf_token %} {% csrf_token %}
{% for field in form %} {% for field in form %}
@ -89,61 +15,15 @@
{% endfor %} {% endfor %}
{% buttons %} {% buttons %}
<button type="submit" class="btn btn-default"> <button type="submit" class="btn btn-default">
Signup Sign up
</button> </button>
{% endbuttons %} {% endbuttons %}
</form> </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 class="list-inline intro-social-buttons">
</ul> </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

@ -8,11 +8,10 @@ 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 django.conf import settings
from membership.forms import PaymentForm 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 from .mixins import ProcessVMSelectionMixin
@ -32,7 +31,9 @@ class DjangoHostingView(ProcessVMSelectionMixin, View):
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)
@ -70,7 +71,9 @@ class NodeJSHostingView(ProcessVMSelectionMixin, View):
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)
@ -89,23 +92,28 @@ class IndexView(View):
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())
@ -118,18 +126,21 @@ 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): class PaymentVMView(FormView):
template_name = 'hosting/payment.html' template_name = 'hosting/payment.html'
form_class = PaymentForm form_class = BillingAddressForm
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(PaymentVMView, self).get_context_data(**kwargs) context = super(PaymentVMView, self).get_context_data(**kwargs)
@ -138,39 +149,29 @@ class PaymentVMView(FormView):
}) })
return context return context
# moodel = CustomUser def post(self, request, *args, **kwargs):
form = self.get_form()
# def get(self, request, *args, **kwargs): if form.is_valid():
# return render(request, self.template_name, self.context) specifications = request.session.get('vm_specs')
vm_type = specifications.get('hosting_company')
vm = VirtualMachineType.objects.get(hosting_company=vm_type)
# class RailsBetaUserForm(ModelForm): plan_data = {
# required_css_class = 'form-control' 'vm_type': vm,
# class Meta: 'cores': specifications.get('cores'),
# model = RailsBetaUser 'memory': specifications.get('memory'),
# fields = [ 'email' ] 'disk_size': specifications.get('disk_size'),
'price': vm.calculate_price(specifications)
}
# def hosting(request, context): # Stripe payment goes here
# email = RailsBetaUser(received_date=datetime.datetime.now())
# if request.method == 'POST': # Billing Address should be store here
# 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) VirtualMachinePlan.create(plan_data, request.user)
# return HttpResponseRedirect(reverse("hosting:beta")) return HttpResponseRedirect(reverse('hosting:payment'))
# else: else:
# context['form'] = RailsBetaUserForm() return self.form_invalid(form)
# context['error_message'] = "a problem"
# page = "hosting/%s.html" % context['hosting']
# return render(request, page, context)
# def beta(request):
# return render(request, 'hosting/beta.html')

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."