Merge branch 'feature/vm_pricing' into develop

This commit is contained in:
Levi 2016-05-05 01:04:02 -05:00
commit 9415041f64
21 changed files with 661 additions and 170 deletions

View file

@ -446,8 +446,8 @@ AUTH_USER_MODEL = 'membership.CustomUser'
# PAYMENT # PAYMENT
STRIPE_API_PUBLIC_KEY = 'pk_test_ZRg6P8g5ybiHE6l2RW5pSaYV' # used in frontend to call from user browser STRIPE_API_PUBLIC_KEY = 'pk_test_QqBZ50Am8KOxaAlOxbcm9Psl' # used in frontend to call from user browser
STRIPE_API_PRIVATE_KEY = 'sk_test_uIPMdgXoRGydrcD7fkwcn7dj' # used in backend payment STRIPE_API_PRIVATE_KEY = 'sk_test_dqAmbKAij12QCGfkYZ3poGt2' # used in backend payment
STRIPE_DESCRIPTION_ON_PAYMENT = "Payment for ungleich GmbH services" STRIPE_DESCRIPTION_ON_PAYMENT = "Payment for ungleich GmbH services"
# EMAIL MESSAGES # EMAIL MESSAGES

9
hosting/managers.py Normal file
View file

@ -0,0 +1,9 @@
from django.db import models
class VMPlansManager(models.Manager):
def active(self, user, **kwargs):
return self.prefetch_related('hosting_orders__customer__user').\
filter(hosting_orders__customer__user=user, hosting_orders__approved=True, **kwargs)\
.distinct()

View file

@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2016-05-01 18:50
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hosting', '0011_auto_20160426_0555'),
]
operations = [
migrations.AddField(
model_name='hostingorder',
name='cc_brand',
field=models.CharField(default='Visa', max_length=10),
preserve_default=False,
),
migrations.AddField(
model_name='hostingorder',
name='last4',
field=models.CharField(default=1111, max_length=4),
preserve_default=False,
),
]

View file

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2016-05-05 03:02
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('hosting', '0012_auto_20160501_1850'),
]
operations = [
migrations.AlterField(
model_name='hostingorder',
name='VMPlan',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hosting.VirtualMachinePlan'),
),
]

View file

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2016-05-05 05:41
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('hosting', '0013_auto_20160505_0302'),
]
operations = [
migrations.AlterField(
model_name='hostingorder',
name='VMPlan',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='hosting_orders', to='hosting.VirtualMachinePlan'),
),
]

View file

@ -2,10 +2,13 @@ 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.utils.functional import cached_property
from django.core import serializers from django.core import serializers
from membership.models import StripeCustomer from membership.models import StripeCustomer
from utils.models import BillingAddress from utils.models import BillingAddress
from .managers import VMPlansManager
class RailsBetaUser(models.Model): class RailsBetaUser(models.Model):
email = models.EmailField(unique=True) email = models.EmailField(unique=True)
@ -81,6 +84,17 @@ class VirtualMachinePlan(models.Model):
vm_type = models.ForeignKey(VirtualMachineType) vm_type = models.ForeignKey(VirtualMachineType)
price = models.FloatField() price = models.FloatField()
objects = VMPlansManager()
@cached_property
def hosting_company_name(self):
return self.vm_type.get_hosting_company_display()
@cached_property
def name(self):
name = 'vm-%s' % self.id
return name
@classmethod @classmethod
def create(cls, data, user): def create(cls, data, user):
instance = cls.objects.create(**data) instance = cls.objects.create(**data)
@ -88,13 +102,23 @@ class VirtualMachinePlan(models.Model):
class HostingOrder(models.Model): class HostingOrder(models.Model):
VMPlan = models.OneToOneField(VirtualMachinePlan)
ORDER_APPROVED_STATUS = 'Approved'
ORDER_DECLINED_STATUS = 'Declined'
VMPlan = models.ForeignKey(VirtualMachinePlan, related_name='hosting_orders')
customer = models.ForeignKey(StripeCustomer) customer = models.ForeignKey(StripeCustomer)
billing_address = models.ForeignKey(BillingAddress) billing_address = models.ForeignKey(BillingAddress)
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
approved = models.BooleanField(default=False) approved = models.BooleanField(default=False)
last4 = models.CharField(max_length=4)
cc_brand = models.CharField(max_length=10)
stripe_charge_id = models.CharField(max_length=100, null=True) stripe_charge_id = models.CharField(max_length=100, null=True)
@cached_property
def status(self):
return self.ORDER_APPROVED_STATUS if self.approved else self.ORDER_DECLINED_STATUS
@classmethod @classmethod
def create(cls, VMPlan=None, customer=None, billing_address=None): def create(cls, VMPlan=None, customer=None, billing_address=None):
instance = cls.objects.create(VMPlan=VMPlan, customer=customer, instance = cls.objects.create(VMPlan=VMPlan, customer=customer,
@ -107,6 +131,8 @@ class HostingOrder(models.Model):
def set_stripe_charge(self, stripe_charge): def set_stripe_charge(self, stripe_charge):
self.stripe_charge_id = stripe_charge.id self.stripe_charge_id = stripe_charge.id
self.last4 = stripe_charge.source.last4
self.cc_brand = stripe_charge.source.brand
self.save() self.save()

View file

@ -0,0 +1,20 @@
.dashboard-container {
padding-top:5%; padding-bottom: 11%;
}
.borderless td {
border: none !important;
}
.borderless thead {
}
.borderless tbody:before {
content: "-";
display: block;
color: transparent;
}
.inline-headers h3, .inline-headers h4 {
display: inline-block;
vertical-align: baseline;
}

View file

@ -0,0 +1,17 @@
.order-detail-container {padding-top:5%; padding-bottom: 11%;}
.order-detail-container .invoice-title h2, .invoice-title h3 {
display: inline-block;
}
.order-detail-container .table > tbody > tr > .no-line {
border-top: none;
}
.order-detail-container .table > thead > tr > .no-line {
border-bottom: none;
}
.order-detail-container .table > tbody > tr > .thick-line {
border-top: 2px solid;
}

View file

@ -0,0 +1,5 @@
.orders-container {padding-top:5%; padding-bottom: 11%;}
.orders-container .table > tbody > tr > td {
vertical-align: middle;
}

View file

@ -0,0 +1,42 @@
.virtual-machine-container .tabs-left, .virtual-machine-container .tabs-right {
border-bottom: none;
padding-top: 2px;
}
.virtual-machine-container .tabs-left {
border-right: 1px solid #ddd;
}
.virtual-machine-container .tabs-right {
border-left: 1px solid #ddd;
}
.virtual-machine-container .tabs-left>li, .virtual-machine-container .tabs-right>li {
float: none;
margin-bottom: 2px;
}
.virtual-machine-container .tabs-left>li {
margin-right: -1px;
}
.virtual-machine-container .tabs-right>li {
margin-left: -1px;
}
.virtual-machine-container .tabs-left>li.active>a,
.virtual-machine-container .tabs-left>li.active>a:hover,
.virtual-machine-container .tabs-left>li.active>a:focus {
border-bottom-color: #ddd;
border-right-color: transparent;
}
.virtual-machine-container .tabs-right>li.active>a,
.virtual-machine-container .tabs-right>li.active>a:hover,
.virtual-machine-container .tabs-right>li.active>a:focus {
border-bottom: 1px solid #ddd;
border-left-color: transparent;
}
.virtual-machine-container .tabs-left>li>a {
border-radius: 4px 0 0 4px;
margin-right: 0;
display:block;
}
.virtual-machine-container .tabs-right>li>a {
border-radius: 0 4px 4px 0;
margin-right: 0;
}

View file

@ -18,6 +18,10 @@
<!-- Custom CSS --> <!-- Custom CSS -->
<link href="{% static 'hosting/css/landing-page.css' %}" rel="stylesheet"> <link href="{% static 'hosting/css/landing-page.css' %}" rel="stylesheet">
<link href="{% static 'hosting/css/payment.css' %}" rel="stylesheet"> <link href="{% static 'hosting/css/payment.css' %}" rel="stylesheet">
<link href="{% static 'hosting/css/order.css' %}" rel="stylesheet">
<link href="{% static 'hosting/css/orders.css' %}" rel="stylesheet">
<link href="{% static 'hosting/css/commons.css' %}" rel="stylesheet">
<link href="{% static 'hosting/css/virtual-machine.css' %}" rel="stylesheet">
<!-- Custom Fonts --> <!-- Custom Fonts -->
<link href='http://fonts.googleapis.com/css?family=Raleway' rel='stylesheet' type='text/css'> <link href='http://fonts.googleapis.com/css?family=Raleway' rel='stylesheet' type='text/css'>
@ -49,11 +53,33 @@
<span class="icon-bar"></span> <span class="icon-bar"></span>
<span class="icon-bar"></span> <span class="icon-bar"></span>
</button> </button>
<a class="navbar-brand topnav" href="#"><img src="img/logo_black.svg"></a> <a class="navbar-brand topnav" href="#"><img src="{% static 'hosting/img/logo_black.svg' %}"></a>
</div> </div>
<!-- Collect the nav links, forms, and other content for toggling --> <!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav navbar-right"> <ul class="nav navbar-nav navbar-right">
{% if request.user.is_authenticated %}
<li>
<a href="{% url 'hosting:virtual_machines' %}">
<i class="fa fa-server" aria-hidden="true"></i> My Virtual Machines
</a>
</li>
<li>
<a href="{% url 'hosting:orders' %}">
<i class="fa fa-credit-card"></i> My Orders
</a>
</li>
<li class="dropdown">
<a class="dropdown-toggle" role="button" data-toggle="dropdown" href="#">
<i class="glyphicon glyphicon-user"></i> {{request.user.name}} <span class="caret"></span></a>
<ul id="g-account-menu" class="dropdown-menu" role="menu">
<li><a href="{% url 'hosting:logout' %}"><i class="glyphicon glyphicon-lock"></i> Logout</a></li>
</ul>
</li>
{% else %}
<li> <li>
<a href="{{ request.META.HTTP_REFERER }}#how">How it works</a> <a href="{{ request.META.HTTP_REFERER }}#how">How it works</a>
</li> </li>
@ -69,9 +95,8 @@
<li> <li>
<a href="{{ request.META.HTTP_REFERER }}#contact">Contact</a> <a href="{{ request.META.HTTP_REFERER }}#contact">Contact</a>
</li> </li>
{% if request.user.is_authenticated %}
<li> <li>
<a href="{% url 'hosting:logout' %}"> Logout</a> <a href="{% url 'hosting:login' %}?next={{request.current_path}}">Login</a>
</li> </li>
{% endif %} {% endif %}
</ul> </ul>

View file

@ -1,79 +0,0 @@
{% extends "hosting/base_short.html" %}
{% load staticfiles bootstrap3 %}
{% block content %}
<style type="text/css">
.invoice-title h2, .invoice-title h3 {
display: inline-block;
}
.table > tbody > tr > .no-line {
border-top: none;
}
.table > thead > tr > .no-line {
border-bottom: none;
}
.table > tbody > tr > .thick-line {
border-top: 2px solid;
}
</style>
<div class="container payment-container">
<div class="row">
<div class="col-xs-8 col-xs-offset-2">
<div class="invoice-title">
<h2>Invoice</h2><h3 class="pull-right">Order # {{order.id}}</h3>
</div>
<hr>
<div class="row">
<div class="col-xs-6">
<address>
<h3><b>Billed To:</b></h3>
John Smith<br>
1234 Main<br>
Apt. 4B<br>
Springfield, ST 54321
</address>
</div>
<div class="col-xs-6 text-right">
<address>
<strong>Order Date:</strong><br>
{{order.created_at}}<br><br>
</address>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<address>
<strong>Payment Method:</strong><br>
{{brand}} ending **** {{last4}}<br>
{{user.email}}
</address>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h3><b>Order summary</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>
</div>
{%endblock%}

View file

@ -0,0 +1,64 @@
{% extends "hosting/base_short.html" %}
{% load staticfiles bootstrap3 %}
{% block content %}
<div class="container order-detail-container">
<div class="row">
<div class="col-xs-8 col-xs-offset-2">
<div class="invoice-title">
<h2>Invoice</h2><h3 class="pull-right">Order # {{object.id}}</h3>
</div>
<hr>
<div class="row">
<div class="col-xs-6">
<address>
<h3><b>Billed To:</b></h3>
{{user.name}}<br>
{{object.billing_address.street_address}},{{order.billing_address.postal_code}}<br>
{{object.billing_address.city}}, {{object.billing_address.country}}.
</address>
</div>
<div class="col-xs-6 text-right">
<address>
<strong>Order Date:</strong><br>
{{object.created_at}}<br><br>
<strong>Status:</strong><br>
<strong class="{% if object.status == 'Approved' %}text-success
{%else%} text-danger
{% endif %}">{{object.status}}</strong>
<br><br>
</address>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<address>
<strong>Payment Method:</strong><br>
{{object.cc_brand}} ending **** {{object.last4}}<br>
{{user.email}}
</address>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h3><b>Order summary</b></h3>
<hr>
<div class="content">
<p><b>Type</b> <span class="pull-right">{{object.VMPlan.hosting_company_name}}</span></p>
<hr>
<p><b>Cores</b> <span class="pull-right">{{object.VMPlan.cores}}</span></p>
<hr>
<p><b>Memory</b> <span class="pull-right">{{object.VMPlan.memory}} GiB</span></p>
<hr>
<p><b>Disk space</b> <span class="pull-right">{{object.VMPlan.disk_size}} GiB</span></p>
<hr>
<h4>Total<p class="pull-right"><b>{{object.VMPlan.price}} CHF</b></p></h4>
</div>
</div>
</div>
</div>
{%endblock%}

View file

@ -1,31 +1,59 @@
{% extends "hosting/base_short.html" %} {% extends "hosting/base_short.html" %}
{% load staticfiles bootstrap3 %} {% load staticfiles bootstrap3 %}
{% block content %} {% block content %}
<div> <div>
<div class="container payment-container"> <div class="container orders-container">
<div class="row"> <div class="row">
<div class="col-md-8 col-md-offset-2"> <div class="col-md-8 col-md-offset-2">
<table class="table"> <table class="table borderless table-hover">
<caption>My orders.</caption> <h3><i class="fa fa-credit-card"></i> My Orders</h3>
<br/>
<thead> <thead>
<tr> <tr>
<th>#</th> <th>#</th>
<th>Date</th> <th>Date</th>
<th>Amount</th> <th>Amount</th>
<th>Status</th> <th>Status</th>
<th></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for order in orders %} {% for order in orders %}
<tr> <tr>
<th scope="row">{{order.id}}</th> <td scope="row">{{order.id}}</td>
<td>{{order.created_at}}</td> <td>{{order.created_at}}</td>
<td>{{order.VMPlan.price}}</td> <td>{{order.VMPlan.price}} CHF</td>
<td>{{order.approved}}</td> <td>{% if order.approved %}
<span class="text-success strong">Approved</span>
{% else%}
<span class="text-danger strong">Declined</span>
{% endif%}
</td>
<td>
<button type="button" class="btn btn-default"><a href="{% url 'hosting:orders' order.id %}">View Detail</a></button>
</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
{% if is_paginated %}
<div class="pagination">
<span class="page-links">
{% if page_obj.has_previous %}
<a href="{{request.path}}?page={{ page_obj.previous_page_number }}">previous</a>
{% endif %}
<span class="page-current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
<a href="{{request.path}}?page={{ page_obj.next_page_number }}">next</a>
{% endif %}
</span>
</div>
{% endif %}
</div> </div>
</div> </div>

View file

@ -0,0 +1,155 @@
{% extends "hosting/base_short.html" %}
{% load staticfiles bootstrap3 %}
{% block content %}
<div>
<div class="container virtual-machine-container dashboard-container ">
<div class="row">
<div class="col-md-9 col-md-offset-2">
<div class="col-sm-12">
<h3><i class="fa fa-cloud" aria-hidden="true"></i> {{virtual_machine.name}}</h3>
<hr/>
<div class="col-md-3"> <!-- required for floating -->
<!-- Nav tabs -->
<ul class="nav nav-tabs tabs-left sideways">
<li class="active">
<a href="#settings-v" data-toggle="tab">
<i class="fa fa-cogs" aria-hidden="true"></i>
Settings
</a>
</li>
<li>
<a href="#billing-v" data-toggle="tab">
<i class="fa fa-money" aria-hidden="true"></i>
Billing
</a>
</li>
<li>
<a href="#orders-v" data-toggle="tab">
<i class="fa fa-credit-card"></i>
Orders
</a>
</li>
<li>
<a href="#status-v" data-toggle="tab">
<i class="fa fa-signal" aria-hidden="true"></i> Status
</a>
</li>
</ul>
</div>
<div class="col-md-9">
<!-- Tab panes -->
<div class="tab-content">
<div class="tab-pane active" id="settings-v">
<div class="row">
<div class="col-md-12">
<h3>{{virtual_machine.hosting_company_name}}</h3>
<hr>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="row">
<div class="col-md-3">
<div class="well text-center">
<i class="fa fa-cubes" aria-hidden="true"></i> Cores <br/>
<span class="label label-success">{{virtual_machine.cores}}</span>
</div>
</div>
<div class="col-md-3">
<div class="well text-center">
<i class="fa fa-tachometer" aria-hidden="true"></i> Memory <br/>
<span class="label label-success">{{virtual_machine.memory}} GiB</span>
</div>
</div>
<div class="col-md-3">
<div class="well text-center">
<i class="fa fa-hdd-o" aria-hidden="true"></i> Disk <br/>
<span class="label label-success">{{virtual_machine.disk_size}} GiB</span>
</div>
</div>
</div><!--/row-->
</div><!--/col-12-->
</div><!--/row-->
</div>
<div class="tab-pane" id="billing-v">
<div class="row ">
<div class="col-md-12 inline-headers">
<h3>Current pricing</h3>
<span class="h3 pull-right"><strong>{{virtual_machine.price|floatformat}} CHF</strong>/mo</span>
<hr>
</div>
</div>
</div>
<div class="tab-pane" id="orders-v">
<div class="row">
<div class="col-md-12">
<table class="table borderless table-hover">
<h3>Orders</h3>
<br/>
<thead>
<tr>
<th>#</th>
<th>Date</th>
<th>Amount</th>
<th>Status</th>
<th></th>
</tr>
</thead>
<tbody>
{% for order in virtual_machine.hosting_orders.all %}
<tr>
<td scope="row">{{order.id}}</td>
<td>{{order.created_at}}</td>
<td>{{order.VMPlan.price}} CHF</td>
<td>{% if order.approved %}
<span class="text-success strong">Approved</span>
{% else%}
<span class="text-danger strong">Declined</span>
{% endif%}
</td>
<td>
<button type="button" class="btn btn-default"><a href="{% url 'hosting:orders' order.id %}">View Detail</a></button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div><!--/col-12-->
</div><!--/row-->
</div>
<div class="tab-pane" id="status-v">
<div class="row ">
<div class="col-md-12 inline-headers">
<h3>Current status</h3>
<span class="h3 pull-right label label-success"><strong>Online</strong></span>
<hr>
</div>
</div>
</div>
</div>
</div>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
</div>
{%endblock%}

View file

@ -0,0 +1,56 @@
{% extends "hosting/base_short.html" %}
{% load staticfiles bootstrap3 %}
{% block content %}
<div>
<div class="container dashboard-container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<table class="table borderless table-hover">
<h3><i class="fa fa-server" aria-hidden="true"></i> Virtual Machines</h3>
<br/>
<thead>
<tr>
<th>ID</th>
<th>Type</th>
<th>Amount</th>
<th></th>
</tr>
</thead>
<tbody>
{% for vm in vms %}
<tr>
<td scope="row">{{vm.name}}</td>
<td>{{vm.hosting_company_name}}</td>
<td>{{vm.price}} CHF</td>
<td>
<button type="button" class="btn btn-default"><a href="{% url 'hosting:virtual_machines' vm.id %}">View Detail</a></button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if is_paginated %}
<div class="pagination">
<span class="page-links">
{% if page_obj.has_previous %}
<a href="{{request.path}}?page={{ page_obj.previous_page_number }}">previous</a>
{% endif %}
<span class="page-current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
<a href="{{request.path}}?page={{ page_obj.next_page_number }}">next</a>
{% endif %}
</span>
</div>
{% endif %}
</div>
</div>
</div>
</div>
{%endblock%}

72
hosting/test_views.py Normal file
View file

@ -0,0 +1,72 @@
from django.test import TestCase
from django.core.urlresolvers import reverse
from django.core.urlresolvers import resolve
from .models import VirtualMachineType
from .views import DjangoHostingView, RailsHostingView, NodeJSHostingView
class ProcessVMSelectionTestMixin(object):
def url_resolve_to_view_correctly(self):
found = resolve(self.url)
self.assertEqual(found.func.__name__, self.view.__name__)
def test_get(self):
response = self.client.get(self.url)
self.assertEqual(response.status_code, 200)
self.assertEqual(self.view.get_context_data(), self.expected_context)
self.assertEqual(response.context['hosting'], self.expected_context['hosting'])
self.assertTemplateUsed(response, self.expected_template)
def test_anonymous_post(self):
response = self.client.post(self.url)
self.assertRedirects(response, expected_url=reverse('hosting:login'),
status_code=302, target_status_code=200)
class DjangoHostingViewTest(TestCase, ProcessVMSelectionTestMixin):
def setUp(self):
self.url = reverse('django.hosting')
self.view = DjangoHostingView()
self.expected_template = 'hosting/django.html'
self.expected_context = {
'hosting': "django",
'hosting_long': "Django",
'domain': "django-hosting.ch",
'google_analytics': "UA-62285904-6",
'email': "info@django-hosting.ch",
'vm_types': VirtualMachineType.get_serialized_vm_types(),
}
class RailsHostingViewTest(TestCase, ProcessVMSelectionTestMixin):
def setUp(self):
self.url = reverse('rails.hosting')
self.view = RailsHostingView()
self.expected_template = 'hosting/rails.html'
self.expected_context = {
'hosting': "rails",
'hosting_long': "Ruby On Rails",
'domain': "rails-hosting.ch",
'google_analytics': "UA-62285904-5",
'email': "info@rails-hosting.ch",
'vm_types': VirtualMachineType.get_serialized_vm_types(),
}
class NodeJSHostingViewTest(TestCase, ProcessVMSelectionTestMixin):
def setUp(self):
self.url = reverse('node.hosting')
self.view = NodeJSHostingView()
self.expected_template = 'hosting/nodejs.html'
self.expected_context = {
'hosting': "nodejs",
'hosting_long': "NodeJS",
'domain': "node-hosting.ch",
'google_analytics': "UA-62285904-7",
'email': "info@node-hosting.ch",
'vm_types': VirtualMachineType.get_serialized_vm_types(),
}

View file

@ -2,7 +2,8 @@ from django.conf.urls import url
from .views import DjangoHostingView, RailsHostingView, PaymentVMView, \ from .views import DjangoHostingView, RailsHostingView, PaymentVMView, \
NodeJSHostingView, LoginView, SignupView, IndexView, \ NodeJSHostingView, LoginView, SignupView, IndexView, \
InvoiceVMView, OrdersHostingView OrdersHostingListView, OrdersHostingDetailView, VirtualMachinesPlanListView,\
VirtualMachineDetailListView
urlpatterns = [ urlpatterns = [
# url(r'pricing/?$', VMPricingView.as_view(), name='pricing'), # url(r'pricing/?$', VMPricingView.as_view(), name='pricing'),
@ -11,8 +12,11 @@ urlpatterns = [
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'payment/?$', PaymentVMView.as_view(), name='payment'), url(r'payment/?$', PaymentVMView.as_view(), name='payment'),
url(r'invoice/?$', InvoiceVMView.as_view(), name='invoice'), url(r'orders/?$', OrdersHostingListView.as_view(), name='orders'),
url(r'orders/?$', OrdersHostingView.as_view(), name='orders'), url(r'orders/(?P<pk>\d+)/?$', OrdersHostingDetailView.as_view(), name='orders'),
url(r'my-virtual-machines/?$', VirtualMachinesPlanListView.as_view(), name='virtual_machines'),
url(r'my-virtual-machines/(?P<pk>\d+)/?$', VirtualMachineDetailListView.as_view(),
name='virtual_machines'),
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'^logout/?$', 'django.contrib.auth.views.logout', url(r'^logout/?$', 'django.contrib.auth.views.logout',

View file

@ -2,16 +2,12 @@
from django.shortcuts import get_object_or_404, render 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.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views.generic import View, CreateView, FormView from django.views.generic import View, CreateView, FormView, ListView, DetailView
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 django.conf import settings
from membership.forms import PaymentForm
from membership.models import CustomUser, StripeCustomer from membership.models import CustomUser, StripeCustomer
from utils.stripe_utils import StripeUtils from utils.stripe_utils import StripeUtils
from utils.forms import BillingAddressForm from utils.forms import BillingAddressForm
@ -21,7 +17,6 @@ from .forms import HostingUserSignupForm, HostingUserLoginForm
from .mixins import ProcessVMSelectionMixin from .mixins import ProcessVMSelectionMixin
class DjangoHostingView(ProcessVMSelectionMixin, View): class DjangoHostingView(ProcessVMSelectionMixin, View):
template_name = "hosting/django.html" template_name = "hosting/django.html"
@ -106,7 +101,7 @@ class IndexView(View):
class LoginView(FormView): class LoginView(FormView):
template_name = 'hosting/login.html' template_name = 'hosting/login.html'
success_url = reverse_lazy('hosting:login') success_url = reverse_lazy('hosting:orders')
form_class = HostingUserLoginForm form_class = HostingUserLoginForm
moodel = CustomUser moodel = CustomUser
@ -217,60 +212,45 @@ class PaymentVMView(FormView):
'order': order.id, 'order': order.id,
'billing_address': billing_address.id 'billing_address': billing_address.id
}) })
return HttpResponseRedirect(reverse('hosting:invoice')) return HttpResponseRedirect(reverse('hosting:orders', kwargs={'pk': order.id}))
else: else:
return self.form_invalid(form) return self.form_invalid(form)
class InvoiceVMView(LoginRequiredMixin, View): class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
template_name = "hosting/invoice.html" template_name = "hosting/order_detail.html"
login_url = reverse_lazy('hosting:login') login_url = reverse_lazy('hosting:login')
model = HostingOrder
def get_context_data(self, **kwargs):
charge = self.request.session.get('charge')
order_id = self.request.session.get('order')
billing_address_id = self.request.session.get('billing_address')
last4 = charge.get('source').get('last4')
brand = charge.get('source').get('brand')
order = get_object_or_404(HostingOrder, pk=order_id)
billing_address = get_object_or_404(BillingAddress, pk=billing_address_id)
if not charge:
return
context = {
'last4': last4,
'brand': brand,
'order': order,
'billing_address': billing_address,
}
return context
def get(self, request, *args, **kwargs):
context = self.get_context_data()
return render(request, self.template_name, context)
class OrdersHostingView(LoginRequiredMixin, View): class OrdersHostingListView(LoginRequiredMixin, ListView):
template_name = "hosting/orders.html" template_name = "hosting/orders.html"
login_url = reverse_lazy('hosting:login') login_url = reverse_lazy('hosting:login')
context_object_name = "orders"
model = HostingOrder
paginate_by = 10
def get_context_data(self, **kwargs): def get_queryset(self):
user = self.request.user user = self.request.user
orders = HostingOrder.objects.filter(customer__user=user) self.queryset = HostingOrder.objects.filter(customer__user=user)
context = { return super(OrdersHostingListView, self).get_queryset()
'orders':orders
}
return context
def get(self, request, *args, **kwargs):
context = self.get_context_data()
return render(request, self.template_name, context)
class VirtualMachinesPlanListView(LoginRequiredMixin, ListView):
template_name = "hosting/virtual_machines.html"
login_url = reverse_lazy('hosting:login')
context_object_name = "vms"
model = VirtualMachinePlan
paginate_by = 10
def get_queryset(self):
user = self.request.user
self.queryset = VirtualMachinePlan.objects.active(user)
return super(VirtualMachinesPlanListView, self).get_queryset()
class VirtualMachineDetailListView(LoginRequiredMixin, DetailView):
template_name = "hosting/virtual_machine_detail.html"
login_url = reverse_lazy('hosting:login')
model = VirtualMachinePlan
context_object_name = "virtual_machine"

View file

@ -131,7 +131,7 @@ class StripeCustomer(models.Model):
Check if there is a registered stripe customer with that email Check if there is a registered stripe customer with that email
or create a new one or create a new one
""" """
stripe_customer = None
try: try:
stripe_utils = StripeUtils() stripe_utils = StripeUtils()
stripe_customer = cls.objects.get(user__email=email) stripe_customer = cls.objects.get(user__email=email)

View file

@ -52,8 +52,6 @@ def handleStripeError(f):
return handleProblems return handleProblems
class StripeUtils(object): class StripeUtils(object):
CURRENCY = 'chf' CURRENCY = 'chf'
INTERVAL = 'month' INTERVAL = 'month'
@ -71,7 +69,7 @@ class StripeUtils(object):
customer = stripe.Customer.retrieve(id) customer = stripe.Customer.retrieve(id)
except stripe.InvalidRequestError: except stripe.InvalidRequestError:
customer = self.create_customer(token, user.email) customer = self.create_customer(token, user.email)
user.stripecustomer.stripe_id = customer.get('id') user.stripecustomer.stripe_id = customer.get('response_object').get('id')
user.stripecustomer.save() user.stripecustomer.save()
return customer return customer