Merge pull request #56 from levivm/feature/vm_pricing

Added pagination to orders view, Created, Virtual machines booked view
This commit is contained in:
Levi Velázquez 2016-05-03 01:03:17 -05:00
commit ec5cecb7c3
12 changed files with 289 additions and 116 deletions

8
hosting/managers.py Normal file
View file

@ -0,0 +1,8 @@
from django.db import models
class VMPlansManager(models.Manager):
def active(self, user, **kwargs):
return self.select_related('hostingorder__customer__user').\
filter(hostingorder__customer__user=user, hostingorder__approved=True, **kwargs)

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

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

View file

@ -0,0 +1,15 @@
.dashboard-container {
padding-top:5%; padding-bottom: 11%;
}
.borderless td {
border: none !important;
}
.borderless thead {
}
.borderless tbody:before {
content: "-";
display: block;
color: transparent;
}

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

@ -18,6 +18,9 @@
<!-- Custom CSS -->
<link href="{% static 'hosting/css/landing-page.css' %}" rel="stylesheet">
<link href="{% static 'hosting/css/payment.css' %}" rel="stylesheet">
<link href="{% static 'hosting/css/orders.css' %}" rel="stylesheet">
<link href="{% static 'hosting/css/orders.css' %}" rel="stylesheet">
<link href="{% static 'hosting/css/commons.css' %}" rel="stylesheet">
<!-- Custom Fonts -->
<link href='http://fonts.googleapis.com/css?family=Raleway' rel='stylesheet' type='text/css'>
@ -49,30 +52,51 @@
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</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>
<!-- 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="{{ request.META.HTTP_REFERER }}#how">How it works</a>
</li>
<li>
<a href="{{ request.META.HTTP_REFERER }}#your">Your infrastructure</a>
</li>
<li>
<a href="{{ request.META.HTTP_REFERER }}#our">Our inftrastructure</a>
</li>
<li>
<a href="{{ request.META.HTTP_REFERER }}#price">Pricing</a>
</li>
<li>
<a href="{{ request.META.HTTP_REFERER }}#contact">Contact</a>
</li>
{% if request.user.is_authenticated %}
<li>
<a href="{% url 'hosting:logout' %}"> Logout</a>
</li>
<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>
<a href="{{ request.META.HTTP_REFERER }}#how">How it works</a>
</li>
<li>
<a href="{{ request.META.HTTP_REFERER }}#your">Your infrastructure</a>
</li>
<li>
<a href="{{ request.META.HTTP_REFERER }}#our">Our inftrastructure</a>
</li>
<li>
<a href="{{ request.META.HTTP_REFERER }}#price">Pricing</a>
</li>
<li>
<a href="{{ request.META.HTTP_REFERER }}#contact">Contact</a>
</li>
<li>
<a href="{% url 'hosting:login' %}?next={{request.current_path}}">Login</a>
</li>
{% endif %}
</ul>
</div>

View file

@ -2,55 +2,37 @@
{% 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="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 # {{order.id}}</h3>
<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>
John Smith<br>
1234 Main<br>
Apt. 4B<br>
Springfield, ST 54321
{{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>
{{order.created_at}}<br><br>
{{object.created_at}}<br><br>
<strong>Status:</strong><br>
<strong class="text-danger">{{object.status}}</strong><br><br>
</address>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<address>
<strong>Payment Method:</strong><br>
{{brand}} ending **** {{last4}}<br>
{{object.cc_brand}} ending **** {{object.last4}}<br>
{{user.email}}
</address>
</div>
@ -63,15 +45,15 @@
<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>
<p><b>Type</b> <span class="pull-right">{{object.VMPlan.hosting_company_name}}</span></p>
<hr>
<p><b>Cores</b> <span class="pull-right">{{request.session.vm_specs.cores}}</span></p>
<p><b>Cores</b> <span class="pull-right">{{object.VMPlan.cores}}</span></p>
<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">{{object.VMPlan.memory}} GiB</span></p>
<hr>
<p><b>Disk space</b> <span class="pull-right">{{request.session.vm_specs.disk_size}} GiB</span></p>
<p><b>Disk space</b> <span class="pull-right">{{object.VMPlan.disk_size}} GiB</span></p>
<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>{{object.VMPlan.price}} CHF</b></p></h4>
</div>
</div>
</div>

View file

@ -1,31 +1,75 @@
{% extends "hosting/base_short.html" %}
{% load staticfiles bootstrap3 %}
{% block content %}
<style type="text/css">
.borderless td {
border: none !important;
}
.borderless thead {
}
.borderless tbody:before {
content: "-";
display: block;
color: transparent;
}
</style>
<div>
<div class="container payment-container">
<div class="container orders-container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<table class="table">
<caption>My orders.</caption>
<table class="table borderless table-hover">
<h3><i class="fa fa-credit-card"></i> My Orders</h3>
<br/>
<thead>
<tr>
<th>#</th>
<th>Date</th>
<th>Amount</th>
<th>Status</th>
<th></th>
</tr>
</thead>
<tbody>
{% for order in orders %}
<tr>
<th scope="row">{{order.id}}</th>
<td scope="row">{{order.id}}</td>
<td>{{order.created_at}}</td>
<td>{{order.VMPlan.price}}</td>
<td>{{order.approved}}</td>
</tr>
<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>
{% 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>

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.id}}</td>
<td>{{vm.hosting_company_name}}</td>
<td>{{vm.price}} CHF</td>
<td>
<button type="button" class="btn btn-default"><a href="">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%}

View file

@ -1,19 +1,20 @@
from django.conf.urls import url
from .views import DjangoHostingView, RailsHostingView, PaymentVMView, \
NodeJSHostingView, LoginView, SignupView, IndexView, \
InvoiceVMView, OrdersHostingView
NodeJSHostingView, LoginView, SignupView, IndexView, \
OrdersHostingListView, OrdersHostingDetailView, VirtualMachinesPlanListView
urlpatterns = [
url(r'index/?$', IndexView.as_view(), name='index'),
url(r'django/?$', DjangoHostingView.as_view(), name='djangohosting'),
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'invoice/?$', InvoiceVMView.as_view(), name='invoice'),
url(r'orders/?$', OrdersHostingView.as_view(), name='orders'),
url(r'login/?$', LoginView.as_view(), name='login'),
url(r'orders/?$', OrdersHostingListView.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'login/?$', LoginView.as_view(), name='login'),
url(r'signup/?$', SignupView.as_view(), name='signup'),
url(r'^logout/?$', 'django.contrib.auth.views.logout',
{'next_page': '/ungleich_page'}, name='logout')
{'next_page': '/ungleich_page'}, name='logout')
]

View file

@ -2,16 +2,12 @@
from django.shortcuts import get_object_or_404, render
from django.core.urlresolvers import reverse_lazy, reverse
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.shortcuts import redirect
from django.views.generic import View, CreateView, FormView, ListView, DetailView
from django.http import HttpResponseRedirect
from django.contrib.auth import authenticate, login
from django.conf import settings
from membership.forms import PaymentForm
from membership.models import CustomUser, StripeCustomer
from utils.stripe_utils import StripeUtils
from utils.forms import BillingAddressForm
@ -21,7 +17,6 @@ from .forms import HostingUserSignupForm, HostingUserLoginForm
from .mixins import ProcessVMSelectionMixin
class DjangoHostingView(ProcessVMSelectionMixin, View):
template_name = "hosting/django.html"
@ -217,60 +212,38 @@ class PaymentVMView(FormView):
'order': order.id,
'billing_address': billing_address.id
})
return HttpResponseRedirect(reverse('hosting:invoice'))
return HttpResponseRedirect(reverse('hosting:orders', kwargs={'pk': order.id}))
else:
return self.form_invalid(form)
class InvoiceVMView(LoginRequiredMixin, View):
template_name = "hosting/invoice.html"
class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
template_name = "hosting/order_detail.html"
login_url = reverse_lazy('hosting:login')
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)
model = HostingOrder
class OrdersHostingView(LoginRequiredMixin, View):
class OrdersHostingListView(LoginRequiredMixin, ListView):
template_name = "hosting/orders.html"
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
orders = HostingOrder.objects.filter(customer__user=user)
context = {
'orders':orders
}
return context
def get(self, request, *args, **kwargs):
context = self.get_context_data()
return render(request, self.template_name, context)
self.queryset = HostingOrder.objects.filter(customer__user=user)
return super(OrdersHostingListView, self).get_queryset()
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()