Added DonatorStatus model in order to know if an donator has canceled or not his monthly donation, Now we create a DonatorStatus for the user after receiving his first donation. Added DonatorStatus view. Added donator_status.html in order to allow an user view his donation status , Added action to allow user to cancel his monthly donations. Now the user can logout using navbar. added Donation model to admin.Added command make_donations_charges in order to create stripe current monthly donations from all donators

This commit is contained in:
Levi 2016-07-27 00:08:45 -05:00
parent cb520f6b58
commit 4580a75f89
22 changed files with 611 additions and 232 deletions

View file

@ -1,21 +0,0 @@
from .base import *
REGISTRATION_MESSAGE['message'] = REGISTRATION_MESSAGE['message'].format(host='dynamicweb-development.ungleich.ch',slug='{slug}')
ALLOWED_HOSTS = [
"*"
]
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'unique-snowflake'
}
}
MIDDLEWARE_CLASSES+=("debug_toolbar.middleware.DebugToolbarMiddleware",)
INSTALLED_APPS+=(
'django_extensions',
'debug_toolbar'
)

View file

@ -3,7 +3,6 @@ from django.utils.html import format_html
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from utils.mailer import BaseEmail from utils.mailer import BaseEmail
from utils.stripe_utils import StripeUtils
from .forms import HostingOrderAdminForm from .forms import HostingOrderAdminForm
from .models import VirtualMachineType, VirtualMachinePlan, HostingOrder from .models import VirtualMachineType, VirtualMachinePlan, HostingOrder

View file

@ -56,7 +56,7 @@ $( document ).ready(function() {
//set token on a hidden input //set token on a hidden input
$('#id_token').val(token); $('#id_token').val(token);
$('#billing-form').submit(); $('#donation-form').submit();
} }
}); });
} }

View file

@ -1,3 +1,22 @@
from django.contrib import admin from django.contrib import admin
from django.utils.html import format_html
from django.core.urlresolvers import reverse
from .models import Donation
# Register your models here. # Register your models here.
class DonationAdmin(admin.ModelAdmin):
list_display = ('id', 'donation', 'donator')
search_fields = ['id', 'donator__user__email']
def user(self, obj):
email = obj.customer.user.email
user_url = reverse("admin:membership_customuser_change", args=[obj.customer.user.id])
return format_html("<a href='{url}'>{email}</a>", url=user_url, email=email)
admin.site.register(Donation, DonationAdmin)

View file

@ -1,7 +1,11 @@
from django import forms from django import forms
from django.utils.translation import ugettext_lazy as _
from utils.forms import LoginFormMixin, SignupFormMixin from utils.forms import LoginFormMixin, SignupFormMixin, BillingAddressForm
from utils.models import BillingAddress
from .models import Donation, DonatorStatus
class LoginForm(LoginFormMixin): class LoginForm(LoginFormMixin):
@ -10,5 +14,38 @@ class LoginForm(LoginFormMixin):
class SignupForm(SignupFormMixin): class SignupForm(SignupFormMixin):
confirm_password = forms.CharField(widget=forms.EmailInput()) confirm_password = forms.CharField(widget=forms.PasswordInput())
password = forms.CharField(widget=forms.PasswordInput()) password = forms.CharField(widget=forms.PasswordInput())
class DonationForm(forms.ModelForm):
class Meta:
model = Donation
fields = ['donation', 'donator', 'billing_address',
'last4', 'cc_brand', 'stripe_charge_id']
def save(self, commit=True):
instance = super(DonationForm, self).save(commit=False)
if commit:
DonatorStatus.create(self.cleaned_data['donator'].user)
instance.save()
return instance
class DonationBillingForm(BillingAddressForm):
token = forms.CharField(widget=forms.HiddenInput())
donation_amount = forms.FloatField(widget=forms.TextInput(attrs={'placeholder': 'Amount'}))
class Meta:
model = BillingAddress
fields = ['donation_amount', 'street_address', 'city', 'postal_code', 'country']
labels = {
'amount': _('Amount'),
'street_address': _('Street Address'),
'city': _('City'),
'postal_code': _('Postal Code'),
'Country': _('Country'),
}

View file

View file

@ -0,0 +1,51 @@
from django.core.management.base import BaseCommand
from nosystem.models import DonatorStatus, Donation
from datetime import datetime
from utils.stripe_utils import StripeUtils
from .forms import DonationForm
class Command(BaseCommand):
help = 'Make the monthly stripe charge to all donators'
def handle(self, *args, **options):
donators = DonatorStatus.objects.filter(status=DonatorStatus.ACTIVE)
current_month = datetime.now().month
current_year = datetime.now().year
for donator in donators:
current_month_donation = Donation.objects.get(created_at__month=current_month,
created_at__year=current_year)
if not current_month_donation:
last_donation = Donation.objects.filter(donator=donator).last()
donation_amount = last_donation.donation
# Make stripe charge to a customer
stripe_utils = StripeUtils()
stripe_utils.CURRENCY = 'usd'
charge_response = stripe_utils.make_charge(amount=donation_amount,
customer=donator.stripe_id)
charge = charge_response.get('response_object')
# Check if the payment was approved
if not charge:
# TODO save error
context = {
'paymentError': charge_response.get('error'),
}
print(context)
# Create a donation
charge = charge_response.get('response_object')
donation_data = {
'cc_brand': charge.source.brand,
'stripe_charge_id': charge.id,
'last4': charge.source.last4,
'billing_address': last_donation.billing_address.id,
'donator': donator.id,
'donation': donation_amount
}
donation_form = DonationForm(donation_data)
if donation_form.is_valid():
donation = donation_form.save()
print(donation)

View file

@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2016-07-22 06:51
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):
initial = True
dependencies = [
('utils', '0002_billingaddress'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Donation',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('donation', models.FloatField()),
('created_at', models.DateTimeField(auto_now_add=True)),
('last4', models.CharField(max_length=4)),
('cc_brand', models.CharField(max_length=10)),
('stripe_charge_id', models.CharField(max_length=100, null=True)),
('billing_address', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='utils.BillingAddress')),
('donator', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View file

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2016-07-23 18:48
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('nosystemd', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='donation',
name='donator',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='membership.StripeCustomer'),
),
]

View file

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2016-07-26 01:53
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),
('nosystemd', '0002_auto_20160723_1848'),
]
operations = [
migrations.CreateModel(
name='DonatorStatus',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('status', models.CharField(choices=[('active', 'Active'), ('canceled', 'Canceled')], max_length=10)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View file

@ -1,15 +1,28 @@
from django.db import models from django.db import models
from membership.models import CustomUser from membership.models import StripeCustomer, CustomUser
from utils.models import BillingAddress from utils.models import BillingAddress
# Create your models here. class DonatorStatus(models.Model):
ACTIVE = 'active'
CANCELED = 'canceled'
STATUS_CHOICES = (
(ACTIVE, 'Active'),
(CANCELED, 'Canceled')
)
user = models.OneToOneField(CustomUser)
status = models.CharField(choices=STATUS_CHOICES, max_length=10, default=ACTIVE)
@classmethod
def create(cls, user):
cls.objects.get_or_create(user=user)
class Donation(models.Model): class Donation(models.Model):
donation = models.FloatField() donation = models.FloatField()
donator = models.ForeignKey(CustomUser) donator = models.ForeignKey(StripeCustomer)
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
billing_address = models.ForeignKey(BillingAddress) billing_address = models.ForeignKey(BillingAddress)
last4 = models.CharField(max_length=4) last4 = models.CharField(max_length=4)
@ -20,3 +33,9 @@ class Donation(models.Model):
def create(cls, data): def create(cls, data):
obj = cls.objects.create(**data) obj = cls.objects.create(**data)
return obj return obj
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,124 @@
$( document ).ready(function() {
$.ajaxSetup({
beforeSend: function(xhr, settings) {
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie != '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) == (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) {
// Only send the token to relative URLs i.e. locally.
xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
}
}
});
var $form = $('#payment-form');
$form.submit(payWithStripe);
/* If you're using Stripe for payments */
function payWithStripe(e) {
e.preventDefault();
/* Visual feedback */
$form.find('[type=submit]').html('Validating <i class="fa fa-spinner fa-pulse"></i>');
var PublishableKey = window.stripeKey;
Stripe.setPublishableKey(PublishableKey);
Stripe.card.createToken($form, function stripeResponseHandler(status, response) {
if (response.error) {
/* Visual feedback */
$form.find('[type=submit]').html('Try again');
/* Show Stripe errors on the form */
$form.find('.payment-errors').text(response.error.message);
$form.find('.payment-errors').closest('.row').show();
} else {
/* Visual feedback */
$form.find('[type=submit]').html('Processing <i class="fa fa-spinner fa-pulse"></i>');
/* Hide Stripe errors on the form */
$form.find('.payment-errors').closest('.row').hide();
$form.find('.payment-errors').text("");
// response contains id and card, which contains additional card details
var token = response.id;
// AJAX
//set token on a hidden input
$('#id_token').val(token);
$('#donation-form').submit();
}
});
}
/* Form validation */
$.validator.addMethod("month", function(value, element) {
return this.optional(element) || /^(01|02|03|04|05|06|07|08|09|10|11|12)$/.test(value);
}, "Please specify a valid 2-digit month.");
$.validator.addMethod("year", function(value, element) {
return this.optional(element) || /^[0-9]{2}$/.test(value);
}, "Please specify a valid 2-digit year.");
validator = $form.validate({
rules: {
cardNumber: {
required: true,
creditcard: true,
digits: true
},
expMonth: {
required: true,
month: true
},
expYear: {
required: true,
year: true
},
cvCode: {
required: true,
digits: true
}
},
highlight: function(element) {
$(element).closest('.form-control').removeClass('success').addClass('error');
},
unhighlight: function(element) {
$(element).closest('.form-control').removeClass('error').addClass('success');
},
errorPlacement: function(error, element) {
$(element).closest('.form-group').append(error);
}
});
paymentFormReady = function() {
if ($form.find('[name=cardNumber]').hasClass("success") &&
$form.find('[name=expMonth]').hasClass("success") &&
$form.find('[name=expYear]').hasClass("success") &&
$form.find('[name=cvCode]').val().length > 1) {
return true;
} else {
return false;
}
}
$form.find('[type=submit]').prop('disabled', true);
var readyInterval = setInterval(function() {
if (paymentFormReady()) {
$form.find('[type=submit]').prop('disabled', false);
clearInterval(readyInterval);
}
}, 250);
});

View file

@ -1,4 +1,4 @@
{% load static bootstrap3 %} {% load static bootstrap3 i18n %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
@ -59,9 +59,20 @@
<li> <li>
<a class="page-scroll" href="#contact">Contact</a> <a class="page-scroll" href="#contact">Contact</a>
</li> </li>
<li> {% if request.user.is_authenticated %}
<a class="page-scroll" href="{% url 'nosystemd:login' %}">Login</a> <li class="dropdown">
</li> <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 'nosystemd:logout' %}"><i class="glyphicon glyphicon-lock"></i> {% trans "Logout"%} </a></li>
<li><a href="{% url 'nosystemd:donator_status' %}"><i class="fa fa-heart-o" aria-hidden="true"></i> {% trans "Donation"%} </a></li>
</ul>
</li>
{% else %}
<li>
<a class="page-scroll" href="{% url 'nosystemd:login' %}">Login</a>
</li>
{% endif %}
</ul> </ul>
</div> </div>
<!-- /.navbar-collapse --> <!-- /.navbar-collapse -->
@ -90,7 +101,7 @@
<script type="text/javascript" src="//js.stripe.com/v2/"></script> <script type="text/javascript" src="//js.stripe.com/v2/"></script>
<!-- Proccess payment lib --> <!-- Proccess payment lib -->
<script type="text/javascript" src="{% static 'hosting/js/payment.js' %}"></script> <script type="text/javascript" src="{% static 'nosystemd/js/donation.js' %}"></script>
</body> </body>

View file

@ -8,36 +8,41 @@
<div class="header-content-inner"> <div class="header-content-inner">
<div class="container payment-container"> <div class="container payment-container">
<div class="row"> <form role="form" id="donation-form" name="donation-form" method="post" action="{% url 'nosystemd:donations' %}" novalidate>
<div class="col-xs-12 col-md-4 col-md-offset-3" > <div class="row">
<h3><b>Monthly Donation</b></h3> <div class="col-xs-12 col-md-4 col-md-offset-3" >
<hr> <h3><b>Monthly Donation</b></h3>
<form role="form" novalidate> <hr>
<div class="row"> <!-- <form role="form" novalidate> -->
<div class="col-xs-9 col-md-4 col-md-offset-4"> <div class="row">
<div class="form-group"> <div class="col-xs-9 col-md-4 col-md-offset-4">
<div class="input-group"> <div class="form-group">
<input type="number" class="form-control" placeholder="Amount to donate" name="donation_amount" /> <div class="input-group">
{% bootstrap_field form.donation_amount show_label=False type='fields'%}
<!-- <input type="number" class="form-control" placeholder="Amount to donate" name="donation" /> -->
</div>
</div> </div>
</div> </div>
</div> </div>
</div> <!-- </form> -->
</form> </div>
</div> </div>
</div> <div class="row">
<div class="row"> <div class="col-xs-12 col-md-4 col-md-offset-3 billing">
<div class="col-xs-12 col-md-4 col-md-offset-3 billing"> <h3><b>Billing Address</b></h3>
<h3><b>Billing Address</b></h3> <hr>
<hr>
<form role="form" id="billing-form" method="post" action="{% url 'hosting:payment' %}" novalidate>
{% for field in form %} {% for field in form %}
{% csrf_token %} {% csrf_token %}
{% bootstrap_field field show_label=False type='fields'%}
{% if not field.name == 'donation_amount' %}
{% bootstrap_field field show_label=False type='fields'%}
{% endif %}
{% endfor %} {% endfor %}
{% bootstrap_form_errors form type='non_fields'%} {% bootstrap_form_errors form type='non_fields'%}
</form>
</div>
</div> </div>
</div> </form>
<div class="row"> <div class="row">
<div class="col-xs-12 col-sm-6 col-md-4 col-md-offset-3 creditcard-box"> <div class="col-xs-12 col-sm-6 col-md-4 col-md-offset-3 creditcard-box">
<h3><b>Payment Details</b></h3> <h3><b>Payment Details</b></h3>

View file

@ -0,0 +1,74 @@
{% extends "nosystemd/base.html" %}
{% load staticfiles bootstrap3 %}
{% load i18n %}
{% 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>{% trans "Invoice"%}</h2><h3 class="pull-right">{% trans "Donation #"%} {{donation.id}}</h3>
</div>
<div class="row">
<div class="col-xs-6">
<address>
<h3><b>{% trans "Billing Address:"%}</b></h3>
{{user.name}}<br>
{{donation.billing_address.street_address}},{{donation.billing_address.postal_code}}<br>
{{donation.billing_address.city}}, {{donation.billing_address.country}}.
</address>
</div>
<div class="col-xs-6 text-right">
<address>
<strong>{% trans "Date:"%}</strong><br>
{{donation.created_at}}<br><br>
<br><br>
</address>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<address>
<strong>{% trans "Payment Method:"%}</strong><br>
{{donation.cc_brand}} ending **** {{donation.last4}}<br>
{{user.email}}
</address>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h3><b>{% trans "Donation summary"%}</b></h3>
<hr>
<div class="content">
<p><b>{% trans "Donation"%}-{{donation.created_at|date:"M Y"}}</b> <span class="pull-right">{{donation.donation}} USD</span></p>
<hr>
<h4>{% trans "Total"%}<p class="pull-right"><b>{{donation.donation}} USD</b></p></h4>
</div>
<br/>
{% url 'hosting:payment' as payment_url %}
{% if payment_url in request.META.HTTP_REFERER %}
<div class=" content pull-right">
<a href="{% url 'hosting:virtual_machine_key' order.vm_plan.id %}" ><button class="btn btn-info">{% trans "Finish Configuration"%}</button></a>
</div>
{% endif %}
</div>
</div>
<div class="row">
<div class="col-md-8 col-md-offset-2">
<hr>
{% trans "Thanks for you donation, you can cancel your monthly donation at any time going to profile > cancel danation"%}
</div>
</div>
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class=" content pull-right">
<a href="{% url 'nosystemd:landing' %}" ><button class="btn btn-info">{% trans "Return to home"%}</button></a>
</div>
</div>
</div>
</div>
{%endblock%}

View file

@ -0,0 +1,40 @@
{% extends "nosystemd/base.html" %}
{% load staticfiles bootstrap3 i18n %}
{% block content %}
<header>
<div class="header-content">
<div class="header-content-inner">
<div class="col-md-4 col-md-offset-4">
{% if messages %}
<ul class="list-unstyled">
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
<h1 id="homeHeading">Thanks you</h1>
<hr>
<p>Your monthly donation status is {{donator_status.status}}</p>
<form action="{% url 'nosystemd:change_donator_status' donator_status.id %}" method="post" class="form" novalidate>
{% csrf_token %}
<button type="submit" class="btn btn-primary btn-xl page-scroll">
{% if donator_status.status == 'active'%}
{% trans "Cancel Donation"%}
{% else %}
{% trans "Reanude Donation"%}
{% endif %}
</button>
</form>
<br/>
</div>
</div>
</div>
</header>
{% endblock %}

View file

@ -1,79 +1,6 @@
{% load static bootstrap3 %} {% extends "nosystemd/base.html" %}
<!DOCTYPE html> {% load staticfiles bootstrap3 i18n %}
<html lang="en"> {% block content %}
<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>Creative - Start Bootstrap Theme</title>
<!-- Bootstrap Core CSS -->
<link href="{% static 'nosystemd/vendor/bootstrap/css/bootstrap.min.css' %}" rel="stylesheet">
<!-- Custom Fonts -->
<link href="{% static 'nosystemd/vendor/font-awesome/css/font-awesome.min.css' %}" rel="stylesheet" type="text/css">
<link href='https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/css?family=Merriweather:400,300,300italic,400italic,700,700italic,900,900italic' rel='stylesheet' type='text/css'>
<!-- Plugin CSS -->
<link href="{% static 'nosystemd/vendor/magnific-popup/magnific-popup.css' %}" rel="stylesheet">
<!-- Theme CSS -->
<link href="{% static 'nosystemd/css/creative.css' %}" rel="stylesheet">
<!-- 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 id="page-top">
<nav id="mainNav" class="navbar navbar-default navbar-fixed-top">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
<span class="sr-only">Toggle navigation</span> Menu <i class="fa fa-bars"></i>
</button>
<a class="navbar-brand page-scroll" href="#page-top">nosystemd</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 class="page-scroll" href="#about">About</a>
</li>
<li>
<a class="page-scroll" href="#services">Services</a>
</li>
<li>
<a class="page-scroll" href="#contact">Contact</a>
</li>
{% if not request.user.is_authenticated %}
<li>
<a class="page-scroll" href="{% url 'nosystemd:login' %}">Login</a>
</li>
{% else %}
<li>
<a class="page-scroll" href=" {% url 'nosystemd:logout' %}">Logout</a>
</li>
{% endif %}
</ul>
</div>
<!-- /.navbar-collapse -->
</div>
<!-- /.container-fluid -->
</nav>
<header> <header>
<div class="header-content"> <div class="header-content">
@ -81,7 +8,7 @@
<h1 id="homeHeading">No more SYSTEMD</h1> <h1 id="homeHeading">No more SYSTEMD</h1>
<hr> <hr>
<p>We want to remove systemd from the famous linux distros and create a good replacement</p> <p>We want to remove systemd from the famous linux distros and create a good replacement</p>
<a href="{% url 'nosystemd:donation' %}" class="btn btn-primary btn-xl page-scroll">DONATE NOW</a> <a href="{% url 'nosystemd:donations' %}" class="btn btn-primary btn-xl page-scroll">DONATE NOW</a>
</div> </div>
</div> </div>
</header> </header>
@ -170,21 +97,5 @@
</div> </div>
</div> </div>
</section> </section>
{% endblock %}
<!-- jQuery -->
<script src="{% static 'nosystemd/vendor/jquery/jquery.min.js' %}"></script>
<!-- Bootstrap Core JavaScript -->
<script src="{% static 'nosystemd/vendor/bootstrap/js/bootstrap.min.js' %}"></script>
<!-- Plugin JavaScript -->
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery-easing/1.3/jquery.easing.min.js"></script>
<script src="{% static 'nosystemd/vendor/scrollreveal/scrollreveal.min.js' %}"></script>
<script src="{% static 'nosystemd/vendor/magnific-popup/jquery.magnific-popup.min.js' %}"></script>
<!-- Theme JavaScript -->
<script src="{% static 'nosystemd/js/creative.min.js' %}"></script>
</body>
</html>

View file

@ -1,7 +1,8 @@
from django.conf.urls import url from django.conf.urls import url
from .views import LandingView, LoginView, SignupView, PasswordResetView,\ from .views import LandingView, LoginView, SignupView, PasswordResetView,\
PasswordResetConfirmView, DonationView PasswordResetConfirmView, DonationView, DonationDetailView, ChangeDonatorStatusDetailView,\
DonatorStatusDetailView
urlpatterns = [ urlpatterns = [
url(r'^$', LandingView.as_view(), name='landing'), url(r'^$', LandingView.as_view(), name='landing'),
@ -13,6 +14,12 @@ urlpatterns = [
url(r'reset-password-confirm/(?P<uidb64>[0-9A-Za-z]+)-(?P<token>.+)/$', url(r'reset-password-confirm/(?P<uidb64>[0-9A-Za-z]+)-(?P<token>.+)/$',
PasswordResetConfirmView.as_view(), name='reset_password_confirm'), PasswordResetConfirmView.as_view(), name='reset_password_confirm'),
url(r'^donation/?$', DonationView.as_view(), name='donation'), url(r'^donations/?$', DonationView.as_view(), name='donations'),
url(r'donations/(?P<pk>\d+)/?$', DonationDetailView.as_view(), name='donations'),
url(r'donations/status/?$', DonatorStatusDetailView.as_view(),
name='donator_status'),
url(r'donations/status/(?P<pk>\d+)/?$', ChangeDonatorStatusDetailView.as_view(),
name='change_donator_status'),
# url(r'^donation/invoice?$', DonationView.as_view(), name='donation_detail'),
] ]

View file

@ -1,15 +1,20 @@
from django.views.generic import TemplateView, CreateView, FormView from django.views.generic import TemplateView, CreateView, FormView, DetailView, UpdateView
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.core.urlresolvers import reverse_lazy, reverse from django.core.urlresolvers import reverse_lazy, reverse
from django.contrib.auth import authenticate, login from django.contrib.auth import authenticate, login
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.conf import settings from django.conf import settings
from django.contrib import messages
from membership.models import CustomUser
from membership.models import CustomUser, StripeCustomer
from utils.stripe_utils import StripeUtils
from utils.views import PasswordResetViewMixin, PasswordResetConfirmViewMixin from utils.views import PasswordResetViewMixin, PasswordResetConfirmViewMixin
from utils.forms import PasswordResetRequestForm, BillingAddressForm from utils.forms import PasswordResetRequestForm
from .forms import LoginForm, SignupForm from .forms import LoginForm, SignupForm, DonationForm, DonationBillingForm
from .models import Donation, DonatorStatus
class LandingView(TemplateView): class LandingView(TemplateView):
@ -74,7 +79,8 @@ class PasswordResetConfirmView(PasswordResetConfirmViewMixin):
class DonationView(LoginRequiredMixin, FormView): class DonationView(LoginRequiredMixin, FormView):
template_name = 'nosystemd/donation.html' template_name = 'nosystemd/donation.html'
login_url = reverse_lazy('nosystemd:login') login_url = reverse_lazy('nosystemd:login')
form_class = BillingAddressForm form_class = DonationBillingForm
success_url = reverse_lazy('nosystemd:donations')
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(DonationView, self).get_context_data(**kwargs) context = super(DonationView, self).get_context_data(**kwargs)
@ -84,86 +90,103 @@ class DonationView(LoginRequiredMixin, FormView):
return context return context
# def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
# form = self.get_form() form = self.get_form()
# if form.is_valid(): if form.is_valid():
# context = self.get_context_data() context = self.get_context_data()
# specifications = request.session.get('vm_specs') token = form.cleaned_data.get('token')
# vm_type = specifications.get('hosting_company') donation_amount = form.cleaned_data.get('donation_amount')
# vm = VirtualMachineType.objects.get(hosting_company=vm_type)
# final_price = vm.calculate_price(specifications)
# plan_data = { # Get or create stripe customer
# 'vm_type': vm, customer = StripeCustomer.get_or_create(email=self.request.user.email,
# 'cores': specifications.get('cores'), token=token)
# 'memory': specifications.get('memory'), if not customer:
# 'disk_size': specifications.get('disk_size'), form.add_error("__all__", "Invalid credit card")
# 'configuration': specifications.get('configuration'), return self.render_to_response(self.get_context_data(form=form))
# 'price': final_price
# }
# token = form.cleaned_data.get('token')
# # Get or create stripe customer # Create Billing Address
# customer = StripeCustomer.get_or_create(email=self.request.user.email, billing_address = form.save()
# token=token)
# if not customer:
# form.add_error("__all__", "Invalid credit card")
# return self.render_to_response(self.get_context_data(form=form))
# # Create Virtual Machine Plan # Make stripe charge to a customer
# plan = VirtualMachinePlan.create(plan_data, request.user) stripe_utils = StripeUtils()
stripe_utils.CURRENCY = 'usd'
charge_response = stripe_utils.make_charge(amount=donation_amount,
customer=customer.stripe_id)
charge = charge_response.get('response_object')
# # Create Billing Address # Check if the payment was approved
# billing_address = form.save() if not charge:
context.update({
'paymentError': charge_response.get('error'),
'form': form
})
return render(request, self.template_name, context)
# # Create a Hosting Order # Create a donation
# order = HostingOrder.create(vm_plan=plan, customer=customer, charge = charge_response.get('response_object')
# billing_address=billing_address) donation_data = request.POST.copy()
donation_data.update({
'cc_brand': charge.source.brand,
'stripe_charge_id': charge.id,
'last4': charge.source.last4,
'billing_address': billing_address.id,
'donator': customer.id,
'donation': donation_amount
})
donation_form = DonationForm(donation_data)
if donation_form.is_valid():
donation = donation_form.save()
return HttpResponseRedirect(reverse('nosystemd:donations',
kwargs={'pk': donation.id}))
else:
self.form_invalid(donation_form)
# # Make stripe charge to a customer else:
# stripe_utils = StripeUtils() return self.form_invalid(form)
# charge_response = stripe_utils.make_charge(amount=final_price,
# customer=customer.stripe_id)
# charge = charge_response.get('response_object')
# # Check if the payment was approved
# if not charge:
# context.update({
# 'paymentError': charge_response.get('error'),
# 'form': form
# })
# return render(request, self.template_name, context)
# charge = charge_response.get('response_object')
# # Associate an order with a stripe payment
# order.set_stripe_charge(charge)
# # If the Stripe payment was successed, set order status approved
# order.set_approved()
# # Send notification to ungleich as soon as VM has been booked
# # TODO send email using celery
# context = {
# 'vm': plan,
# 'order': order,
# 'base_url': "{0}://{1}".format(request.scheme, request.get_host())
# }
# email_data = {
# 'subject': 'New VM request',
# 'to': request.user.email,
# 'context': context,
# 'template_name': 'new_booked_vm',
# 'template_path': 'hosting/emails/'
# }
# email = BaseEmail(**email_data)
# email.send()
# return HttpResponseRedirect(reverse('hosting:orders', kwargs={'pk': order.id}))
# else:
# return self.form_invalid(form)
class DonationDetailView(LoginRequiredMixin, DetailView):
template_name = "nosystemd/donation_detail.html"
context_object_name = "donation"
login_url = reverse_lazy('nosystemd:login')
model = Donation
class DonatorStatusDetailView(LoginRequiredMixin, TemplateView):
template_name = "nosystemd/donator_status.html"
login_url = reverse_lazy('nosystemd:login')
model = DonatorStatus
def get_context_data(self, **kwargs):
context = super(DonatorStatusDetailView, self).get_context_data(**kwargs)
context.update({
'donator_status': self.request.user.donatorstatus
if self.request.user.donatorstatus else None
})
return context
def get(self, request, *args, **kwargs):
if not request.user.donatorstatus:
HttpResponseRedirect('nosystemd:landing')
return super(DonatorStatusDetailView, self).get(request, *args, **kwargs)
class ChangeDonatorStatusDetailView(LoginRequiredMixin, UpdateView):
template_name = "nosystemd/donator_status.html"
context_object_name = "donator_status"
login_url = reverse_lazy('nosystemd:login')
model = DonatorStatus
def get_object(self, queryset=None):
return self.request.user.donatorstatus
def post(self, *args, **kwargs):
donator_status = self.get_object()
donator_status.status = DonatorStatus.ACTIVE \
if donator_status.status == DonatorStatus.CANCELED else DonatorStatus.CANCELED
donator_status.save()
messages.success(self.request, 'Your monthly donation status has been changed.')
return HttpResponseRedirect(reverse_lazy('nosystemd:donator_status'))

View file

@ -11,6 +11,9 @@ class BillingAddress(models.Model):
postal_code = models.CharField(max_length=50) postal_code = models.CharField(max_length=50)
country = CountryField() country = CountryField()
def __str__(self):
return self.street_address
class ContactMessage(models.Model): class ContactMessage(models.Model):
name = models.CharField(max_length=200) name = models.CharField(max_length=200)

View file

@ -11,7 +11,6 @@ def handleStripeError(f):
'error': None 'error': None
} }
common_message = "Currently its not possible to make payments." common_message = "Currently its not possible to make payments."
try: try:
response_object = f(*args, **kwargs) response_object = f(*args, **kwargs)
response = { response = {
@ -85,7 +84,6 @@ class StripeUtils(object):
@handleStripeError @handleStripeError
def make_charge(self, amount=None, customer=None): def make_charge(self, amount=None, customer=None):
amount = int(amount * 100) # stripe amount unit, in cents amount = int(amount * 100) # stripe amount unit, in cents
charge = self.stripe.Charge.create( charge = self.stripe.Charge.create(
amount=amount, # in cents amount=amount, # in cents
currency=self.CURRENCY, currency=self.CURRENCY,
@ -93,7 +91,6 @@ class StripeUtils(object):
) )
return charge return charge
@handleStripeError @handleStripeError
def create_plan(self, amount, name, id): def create_plan(self, amount, name, id):
self.stripe.Plan.create( self.stripe.Plan.create(