Add the ability to add refunds
This commit is contained in:
parent
343bc1fd84
commit
b8e7309ee2
11 changed files with 130 additions and 21 deletions
|
@ -1,5 +1,5 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from refunding.forms import RefundForm
|
from refunding.forms import RefundForm, RefundFormAdmin
|
||||||
from refunding.models import Refund, Payment
|
from refunding.models import Refund, Payment
|
||||||
|
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ class RefundAdmin(admin.ModelAdmin):
|
||||||
list_display_links = ('title',)
|
list_display_links = ('title',)
|
||||||
search_fields = ('title',)
|
search_fields = ('title',)
|
||||||
date_hierarchy = 'date'
|
date_hierarchy = 'date'
|
||||||
form = RefundForm
|
form = RefundFormAdmin
|
||||||
readonly_fields = ('eur_value',)
|
readonly_fields = ('eur_value',)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
|
from bootstrap3_datetime.widgets import DateTimePicker
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.db.models import Q
|
||||||
from refunding.models import Refund, Payment
|
from refunding.models import Refund, Payment
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,18 +9,39 @@ class RefundForm(forms.ModelForm):
|
||||||
model = Refund
|
model = Refund
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
payments = forms.ModelMultipleChoiceField(queryset=Payment.objects.all())
|
payments = forms.ModelMultipleChoiceField(queryset=Payment.objects.none())
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(RefundForm, self).__init__(*args, **kwargs)
|
super(RefundForm, self).__init__(*args, **kwargs)
|
||||||
if self.instance:
|
if self.instance:
|
||||||
self.fields['payments'].initial = self.instance.payment_set.all()
|
self.fields['payments'].initial = self.instance.payment_set.all()
|
||||||
|
self.fields['payments'].queryset = Payment.objects.filter(Q(refund=None) | Q(refund=self.instance))
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
# Save the refund
|
# Save the refund
|
||||||
instance = super(RefundForm, self).save(commit=False)
|
instance = super(RefundForm, self).save()
|
||||||
# Remove the refund from payments it was previously assigned to
|
# Remove the refund from payments it was previously assigned to
|
||||||
self.fields['payments'].initial.update(refund=None)
|
self.fields['payments'].initial.update(refund=None)
|
||||||
# Add the refund to the selected payments
|
# Add the refund to the selected payments
|
||||||
self.cleaned_data['payments'].update(refund=instance)
|
self.cleaned_data['payments'].update(refund=instance)
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
class RefundFormAdmin(RefundForm):
|
||||||
|
class Meta:
|
||||||
|
model = Refund
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class RefundFormPublic(RefundForm):
|
||||||
|
class Meta:
|
||||||
|
model = Refund
|
||||||
|
exclude = ('user',)
|
||||||
|
|
||||||
|
date = forms.DateField(
|
||||||
|
widget=DateTimePicker(
|
||||||
|
options={
|
||||||
|
'format': 'YYYY-MM-DD'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
22
refunding/migrations/0002_auto_20160604_0140.py
Normal file
22
refunding/migrations/0002_auto_20160604_0140.py
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.6 on 2016-06-04 01:40
|
||||||
|
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 = [
|
||||||
|
('refunding', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='refund',
|
||||||
|
name='user',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
]
|
|
@ -12,11 +12,16 @@ class Refund(models.Model):
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
user = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
AUTH_USER_MODEL,
|
AUTH_USER_MODEL,
|
||||||
on_delete=models.PROTECT
|
on_delete=models.PROTECT,
|
||||||
|
null=True
|
||||||
)
|
)
|
||||||
|
|
||||||
def eur_value(self) -> float:
|
def eur_value(self) -> float:
|
||||||
return self.payment_set.all().aggregate(Sum('value')).get('value__sum') / 100
|
value_sum = self.payment_set.all().aggregate(Sum('value')).get('value__sum')
|
||||||
|
if value_sum:
|
||||||
|
return value_sum / 100
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return "{0} on {1} for {2}".format(self.title, self.date, self.eur_value())
|
return "{0} on {1} for {2}".format(self.title, self.date, self.eur_value())
|
||||||
|
|
27
refunding/templates/refunding/new_refund.html
Normal file
27
refunding/templates/refunding/new_refund.html
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load crispy_forms_filters %}
|
||||||
|
{% load crispy_forms_field %}
|
||||||
|
|
||||||
|
{% block javascript %}
|
||||||
|
{{ block.super }}
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.13.0/moment.min.js"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.37/js/bootstrap-datetimepicker.min.js"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block style %}
|
||||||
|
{{ block.super }}
|
||||||
|
<link rel="stylesheet"
|
||||||
|
href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.37/css/bootstrap-datetimepicker.min.css">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>{% block title %}New refund{% endblock %}</h1>
|
||||||
|
<form action="{% url 'new_refund' %}" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form.media }}
|
||||||
|
{{ form|crispy }}
|
||||||
|
<input class="btn btn-primary" type="submit" value="Submit"/>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
|
@ -2,6 +2,15 @@
|
||||||
{% load l10n %}
|
{% load l10n %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>{% block title %}Latest refunds{% endblock %}</h1>
|
<h1>
|
||||||
|
{% block title %}Latest refunds{% endblock %}
|
||||||
|
{% if user.is_authenticated %}
|
||||||
|
<div class="btn-group pull-right">
|
||||||
|
<a class="btn btn-success" href="{% url 'new_refund' %}">
|
||||||
|
<span class="glyphicon glyphicon-plus"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</h1>
|
||||||
{% include 'refunding/elements_list.html' with elements=refunds %}
|
{% include 'refunding/elements_list.html' with elements=refunds %}
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -1,7 +1,8 @@
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
from refunding.views import not_refunded_payments, latest_refunds
|
from refunding import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^payments/$', not_refunded_payments, name='not_refunded_payments'),
|
url(r'^payments/$', views.not_refunded_payments, name='not_refunded_payments'),
|
||||||
url(r'^refunds/$', latest_refunds, name='latest_refunds'),
|
url(r'^refunds/$', views.latest_refunds, name='latest_refunds'),
|
||||||
|
url(r'^refunds/new/$', views.new_refund, name='new_refund'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.db.models import Sum
|
from django.db.models import Sum
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render, redirect
|
||||||
|
from refunding.forms import RefundForm, RefundFormPublic
|
||||||
from refunding.models import Payment, Refund
|
from refunding.models import Payment, Refund
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,3 +25,21 @@ def latest_refunds(request):
|
||||||
'default_nothing': 'No refund to show.'
|
'default_nothing': 'No refund to show.'
|
||||||
}
|
}
|
||||||
return render(request, "refunding/refunds.html", context)
|
return render(request, "refunding/refunds.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def new_refund(request):
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = RefundFormPublic(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
refund = form.save()
|
||||||
|
refund.user = request.user
|
||||||
|
refund.save()
|
||||||
|
return redirect('latest_refunds')
|
||||||
|
else:
|
||||||
|
form = RefundFormPublic()
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'form': form
|
||||||
|
}
|
||||||
|
return render(request, "refunding/new_refund.html", context)
|
|
@ -50,6 +50,7 @@ X_FRAME_OPTIONS = 'DENY'
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
'refunding',
|
'refunding',
|
||||||
'authentication',
|
'authentication',
|
||||||
|
'bootstrap3_datetime',
|
||||||
'crispy_forms',
|
'crispy_forms',
|
||||||
'django.contrib.admin',
|
'django.contrib.admin',
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
dj-database-url==0.4.1
|
dj-database-url==0.4.1
|
||||||
Django==1.9.6
|
Django==1.9.6
|
||||||
|
django-bootstrap3-datetimepicker-2==2.4.2
|
||||||
django-crispy-forms==1.6.0
|
django-crispy-forms==1.6.0
|
||||||
django-dotenv==1.4.1
|
django-dotenv==1.4.1
|
||||||
gunicorn==19.6.0
|
gunicorn==19.6.0
|
||||||
|
|
|
@ -7,9 +7,19 @@
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
{% block javascript %}
|
||||||
|
<script src="https://code.jquery.com/jquery-2.2.3.min.js"
|
||||||
|
integrity="sha256-a23g1Nt4dtEYOj7bR+vTu7+T8VP13humZFBJNIYoEJo="
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"
|
||||||
|
integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
{% endblock javascript %}
|
||||||
|
|
||||||
{% block style %}
|
{% block style %}
|
||||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
|
||||||
<link rel="stylesheet" type="text/css" href="//fonts.googleapis.com/css?family=Open+Sans:400italic,700italic,400,700">
|
<link rel="stylesheet" type="text/css"
|
||||||
|
href="//fonts.googleapis.com/css?family=Open+Sans:400italic,700italic,400,700">
|
||||||
<link rel="stylesheet" href="{% static 'default_style.css' %}">
|
<link rel="stylesheet" href="{% static 'default_style.css' %}">
|
||||||
{% endblock style %}
|
{% endblock style %}
|
||||||
</head>
|
</head>
|
||||||
|
@ -20,14 +30,5 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% block javascript %}
|
|
||||||
<script src="https://code.jquery.com/jquery-2.2.3.min.js"
|
|
||||||
integrity="sha256-a23g1Nt4dtEYOj7bR+vTu7+T8VP13humZFBJNIYoEJo="
|
|
||||||
crossorigin="anonymous"></script>
|
|
||||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"
|
|
||||||
integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS"
|
|
||||||
crossorigin="anonymous"></script>
|
|
||||||
{% endblock javascript %}
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
Loading…
Reference in a new issue