diff --git a/hosting/managers.py b/hosting/managers.py new file mode 100644 index 00000000..961b2c6f --- /dev/null +++ b/hosting/managers.py @@ -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) diff --git a/hosting/migrations/0012_auto_20160501_1850.py b/hosting/migrations/0012_auto_20160501_1850.py new file mode 100644 index 00000000..3ef15a46 --- /dev/null +++ b/hosting/migrations/0012_auto_20160501_1850.py @@ -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, + ), + ] diff --git a/hosting/models.py b/hosting/models.py index f3e2a178..edee580d 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -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() diff --git a/hosting/static/hosting/css/commons.css b/hosting/static/hosting/css/commons.css new file mode 100644 index 00000000..e77ddfa2 --- /dev/null +++ b/hosting/static/hosting/css/commons.css @@ -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; +} diff --git a/hosting/static/hosting/css/order.css b/hosting/static/hosting/css/order.css new file mode 100644 index 00000000..8e0c5919 --- /dev/null +++ b/hosting/static/hosting/css/order.css @@ -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; +} diff --git a/hosting/static/hosting/css/orders.css b/hosting/static/hosting/css/orders.css new file mode 100644 index 00000000..64e95328 --- /dev/null +++ b/hosting/static/hosting/css/orders.css @@ -0,0 +1,5 @@ +.orders-container {padding-top:5%; padding-bottom: 11%;} + +.orders-container .table > tbody > tr > td { + vertical-align: middle; +} \ No newline at end of file diff --git a/hosting/templates/hosting/base_short.html b/hosting/templates/hosting/base_short.html index fabfa030..266ee484 100644 --- a/hosting/templates/hosting/base_short.html +++ b/hosting/templates/hosting/base_short.html @@ -18,6 +18,9 @@ + + + @@ -49,30 +52,51 @@ - + diff --git a/hosting/templates/hosting/invoice.html b/hosting/templates/hosting/order_detail.html similarity index 51% rename from hosting/templates/hosting/invoice.html rename to hosting/templates/hosting/order_detail.html index 302028fe..4d5dcc18 100644 --- a/hosting/templates/hosting/invoice.html +++ b/hosting/templates/hosting/order_detail.html @@ -2,55 +2,37 @@ {% load staticfiles bootstrap3 %} {% block content %} - - -
+
-

Invoice

Order # {{order.id}}

+

Invoice

Order # {{object.id}}


Billed To:

- John Smith
- 1234 Main
- Apt. 4B
- Springfield, ST 54321 + {{user.name}}
+ {{object.billing_address.street_address}},{{order.billing_address.postal_code}}
+ {{object.billing_address.city}}, {{object.billing_address.country}}.
Order Date:
- {{order.created_at}}

+ {{object.created_at}}

+ Status:
+ {{object.status}}

+
Payment Method:
- {{brand}} ending **** {{last4}}
+ {{object.cc_brand}} ending **** {{object.last4}}
{{user.email}}
@@ -63,15 +45,15 @@

Order summary


-

Type {{request.session.vm_specs.hosting_company_name}}

+

Type {{object.VMPlan.hosting_company_name}}


-

Cores {{request.session.vm_specs.cores}}

+

Cores {{object.VMPlan.cores}}


-

Memory {{request.session.vm_specs.memory}} GiB

+

Memory {{object.VMPlan.memory}} GiB


-

Disk space {{request.session.vm_specs.disk_size}} GiB

+

Disk space {{object.VMPlan.disk_size}} GiB


-

Total

{{request.session.vm_specs.final_price}} CHF

+

Total

{{object.VMPlan.price}} CHF

diff --git a/hosting/templates/hosting/orders.html b/hosting/templates/hosting/orders.html index df5e6216..819dec4d 100644 --- a/hosting/templates/hosting/orders.html +++ b/hosting/templates/hosting/orders.html @@ -1,31 +1,75 @@ {% extends "hosting/base_short.html" %} {% load staticfiles bootstrap3 %} {% block content %} + + +
-
+
- - +
My orders.
+

My Orders

+
+ {% for order in orders %} - + - - - + + + + {% endfor %}
# Date Amount Status
{{order.id}}{{order.id}} {{order.created_at}}{{order.VMPlan.price}}{{order.approved}}
{{order.VMPlan.price}} CHF{% if order.approved %} + Approved + {% else%} + Declined + {% endif%} + + +
+ + {% if is_paginated %} + + {% endif %} +
diff --git a/hosting/templates/hosting/virtual_machines.html b/hosting/templates/hosting/virtual_machines.html new file mode 100644 index 00000000..47eb23b1 --- /dev/null +++ b/hosting/templates/hosting/virtual_machines.html @@ -0,0 +1,56 @@ +{% extends "hosting/base_short.html" %} +{% load staticfiles bootstrap3 %} +{% block content %} +
+
+
+
+ +

Virtual Machines

+
+ + + + + + + + + + {% for vm in vms %} + + + + + + + {% endfor %} + +
IDTypeAmount
{{vm.id}}{{vm.hosting_company_name}}{{vm.price}} CHF + +
+ + {% if is_paginated %} + + {% endif %} + +
+ +
+
+ +
+ +{%endblock%} \ No newline at end of file diff --git a/hosting/urls.py b/hosting/urls.py index 9f18e836..31a3eaea 100644 --- a/hosting/urls.py +++ b/hosting/urls.py @@ -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\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') ] diff --git a/hosting/views.py b/hosting/views.py index 65bbbcf3..bfdb5d13 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -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()