Improvements on transactions
This commit is contained in:
parent
2b91495657
commit
821463eb33
|
@ -24,16 +24,16 @@ def admin_field(method):
|
|||
return admin_field_wrapper
|
||||
|
||||
|
||||
def action_with_confirmation(action_name, extra_context={},
|
||||
def action_with_confirmation(action_name=None, extra_context={},
|
||||
template='admin/orchestra/generic_confirmation.html'):
|
||||
"""
|
||||
Generic pattern for actions that needs confirmation step
|
||||
If custom template is provided the form must contain:
|
||||
<input type="hidden" name="post" value="generic_confirmation" />
|
||||
"""
|
||||
def decorator(func, extra_context=extra_context, template=template):
|
||||
def decorator(func, extra_context=extra_context, template=template, action_name=action_name):
|
||||
@wraps(func, assigned=available_attrs(func))
|
||||
def inner(modeladmin, request, queryset):
|
||||
def inner(modeladmin, request, queryset, action_name=action_name):
|
||||
# The user has already confirmed the action.
|
||||
if request.POST.get('post') == "generic_confirmation":
|
||||
stay = func(modeladmin, request, queryset)
|
||||
|
@ -48,7 +48,8 @@ def action_with_confirmation(action_name, extra_context={},
|
|||
objects_name = force_text(opts.verbose_name)
|
||||
else:
|
||||
objects_name = force_text(opts.verbose_name_plural)
|
||||
|
||||
if not action_name:
|
||||
action_name = func.__name__
|
||||
context = {
|
||||
"title": "Are you sure?",
|
||||
"content_message": "Are you sure you want to %s the selected %s?" %
|
||||
|
|
|
@ -62,7 +62,8 @@ class ChangeViewActionsMixin(object):
|
|||
action.url_name)))
|
||||
return new_urls + urls
|
||||
|
||||
def get_change_view_actions(self):
|
||||
def get_change_view_actions(self, obj=None):
|
||||
""" allow customization on modelamdin """
|
||||
views = []
|
||||
for action in self.change_view_actions:
|
||||
if isinstance(action, basestring):
|
||||
|
@ -79,8 +80,9 @@ class ChangeViewActionsMixin(object):
|
|||
def change_view(self, request, object_id, **kwargs):
|
||||
if not 'extra_context' in kwargs:
|
||||
kwargs['extra_context'] = {}
|
||||
obj = self.get_object(request, unquote(object_id))
|
||||
kwargs['extra_context']['object_tools_items'] = [
|
||||
action.__dict__ for action in self.get_change_view_actions()
|
||||
action.__dict__ for action in self.get_change_view_actions(obj=obj)
|
||||
]
|
||||
return super(ChangeViewActionsMixin, self).change_view(request, object_id, **kwargs)
|
||||
|
||||
|
|
|
@ -46,8 +46,7 @@ def close_bills(modeladmin, request, queryset):
|
|||
if not queryset:
|
||||
messages.warning(request, _("Selected bills should be in open state"))
|
||||
return
|
||||
SelectSourceFormSet = adminmodelformset_factory(modeladmin, SelectSourceForm,
|
||||
extra=0)
|
||||
SelectSourceFormSet = adminmodelformset_factory(modeladmin, SelectSourceForm, extra=0)
|
||||
formset = SelectSourceFormSet(queryset=queryset)
|
||||
if request.POST.get('post') == 'generic_confirmation':
|
||||
formset = SelectSourceFormSet(request.POST, request.FILES, queryset=queryset)
|
||||
|
@ -55,6 +54,8 @@ def close_bills(modeladmin, request, queryset):
|
|||
for form in formset.forms:
|
||||
source = form.cleaned_data['source']
|
||||
form.instance.close(payment=source)
|
||||
for bill in queryset:
|
||||
modeladmin.log_change(request, bill, 'Closed')
|
||||
messages.success(request, _("Selected bills have been closed"))
|
||||
return
|
||||
opts = modeladmin.model._meta
|
||||
|
@ -80,5 +81,6 @@ close_bills.url_name = 'close'
|
|||
def send_bills(modeladmin, request, queryset):
|
||||
for bill in queryset:
|
||||
bill.send()
|
||||
modeladmin.log_change(request, bill, 'Sent')
|
||||
send_bills.verbose_name = _("Send")
|
||||
send_bills.url_name = 'send'
|
||||
|
|
|
@ -17,7 +17,7 @@ def change_ticket_state_factory(action, final_state):
|
|||
'form': ChangeReasonForm()
|
||||
}
|
||||
@transaction.atomic
|
||||
@action_with_confirmation(action, extra_context=context)
|
||||
@action_with_confirmation(action_name=action, extra_context=context)
|
||||
def change_ticket_state(modeladmin, request, queryset, action=action, final_state=final_state):
|
||||
form = ChangeReasonForm(request.POST)
|
||||
if form.is_valid():
|
||||
|
@ -81,6 +81,7 @@ def take_tickets(modeladmin, request, queryset):
|
|||
ticket.messages.create(content=content, author=request.user)
|
||||
if is_read and not ticket.is_read_by(request.user):
|
||||
ticket.mark_as_read_by(request.user)
|
||||
modeladmin.log_change(request, ticket, 'Taken')
|
||||
context = {
|
||||
'count': queryset.count(),
|
||||
'user': request.user
|
||||
|
@ -97,6 +98,7 @@ def mark_as_unread(modeladmin, request, queryset):
|
|||
""" Mark a tickets as unread """
|
||||
for ticket in queryset:
|
||||
ticket.mark_as_unread_by(request.user)
|
||||
modeladmin.log_change(request, ticket, 'Marked as unread')
|
||||
msg = _("%s selected tickets have been marked as unread.") % queryset.count()
|
||||
modeladmin.message_user(request, msg)
|
||||
|
||||
|
@ -106,6 +108,7 @@ def mark_as_read(modeladmin, request, queryset):
|
|||
""" Mark a tickets as unread """
|
||||
for ticket in queryset:
|
||||
ticket.mark_as_read_by(request.user)
|
||||
modeladmin.log_change(request, ticket, 'Marked as read')
|
||||
msg = _("%s selected tickets have been marked as read.") % queryset.count()
|
||||
modeladmin.message_user(request, msg)
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from django.contrib import admin, messages
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import transaction
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ungettext
|
||||
|
@ -71,10 +72,13 @@ class BillSelectedOrders(object):
|
|||
})
|
||||
return render(request, self.template, self.context)
|
||||
|
||||
@transaction.atomic
|
||||
def confirmation(self, request):
|
||||
form = BillSelectConfirmationForm(initial=self.options)
|
||||
if int(request.POST.get('step')) >= 3:
|
||||
bills = self.queryset.bill(commit=True, **self.options)
|
||||
for order in self.queryset:
|
||||
modeladmin.log_change(request, order, 'Billed')
|
||||
if not bills:
|
||||
msg = _("Selected orders do not have pending billing")
|
||||
self.modeladmin.message_user(request, msg, messages.WARNING)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import sys
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.db.migrations.recorder import MigrationRecorder
|
||||
from django.db.models import F, Q
|
||||
|
@ -39,6 +40,11 @@ class ContractedPlan(models.Model):
|
|||
|
||||
def __unicode__(self):
|
||||
return str(self.plan)
|
||||
|
||||
def clean(self):
|
||||
if not self.pk and not self.plan.allow_multipls:
|
||||
if ContractedPlan.objects.filter(plan=self.plan, account=self.account).exists():
|
||||
raise ValidationError("A contracted plan for this account already exists")
|
||||
|
||||
|
||||
class RateQuerySet(models.QuerySet):
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
from django.contrib import messages
|
||||
from django.db import transaction
|
||||
from django.shortcuts import render
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orchestra.admin.decorators import action_with_confirmation
|
||||
|
||||
from .methods import PaymentMethod
|
||||
from .models import Transaction
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
def process_transactions(modeladmin, request, queryset):
|
||||
processes = []
|
||||
if queryset.exclude(state=Transaction.WAITTING_PROCESSING).exists():
|
||||
|
@ -16,6 +21,8 @@ def process_transactions(modeladmin, request, queryset):
|
|||
method = PaymentMethod.get_plugin(method)
|
||||
procs = method.process(transactions)
|
||||
processes += procs
|
||||
for transaction in transactions:
|
||||
modeladmin.log_change(request, transaction, 'Processed')
|
||||
if not processes:
|
||||
return
|
||||
opts = modeladmin.model._meta
|
||||
|
@ -27,3 +34,42 @@ def process_transactions(modeladmin, request, queryset):
|
|||
'app_label': opts.app_label,
|
||||
}
|
||||
return render(request, 'admin/payments/transaction/get_processes.html', context)
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
@action_with_confirmation()
|
||||
def mark_as_executed(modeladmin, request, queryset):
|
||||
""" Mark a tickets as unread """
|
||||
for transaction in queryset:
|
||||
transaction.mark_as_executed()
|
||||
modeladmin.log_change(request, transaction, 'Executed')
|
||||
msg = _("%s selected transactions have been marked as executed.") % queryset.count()
|
||||
modeladmin.message_user(request, msg)
|
||||
mark_as_executed.url_name = 'execute'
|
||||
mark_as_executed.verbose_name = _("Mark as executed")
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
@action_with_confirmation()
|
||||
def mark_as_secured(modeladmin, request, queryset):
|
||||
""" Mark a tickets as unread """
|
||||
for transaction in queryset:
|
||||
transaction.mark_as_secured()
|
||||
modeladmin.log_change(request, transaction, 'Secured')
|
||||
msg = _("%s selected transactions have been marked as secured.") % queryset.count()
|
||||
modeladmin.message_user(request, msg)
|
||||
mark_as_secured.url_name = 'secure'
|
||||
mark_as_secured.verbose_name = _("Mark as secured")
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
@action_with_confirmation()
|
||||
def mark_as_rejected(modeladmin, request, queryset):
|
||||
""" Mark a tickets as unread """
|
||||
for transaction in queryset:
|
||||
transaction.mark_as_rejected()
|
||||
modeladmin.log_change(request, transaction, 'Rejected')
|
||||
msg = _("%s selected transactions have been marked as rejected.") % queryset.count()
|
||||
modeladmin.message_user(request, msg)
|
||||
mark_as_rejected.url_name = 'reject'
|
||||
mark_as_rejected.verbose_name = _("Mark as rejected")
|
||||
|
|
|
@ -5,10 +5,11 @@ from django.core.urlresolvers import reverse
|
|||
from django.shortcuts import render, redirect
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orchestra.admin import ChangeViewActionsMixin
|
||||
from orchestra.admin.utils import admin_colored, admin_link, wrap_admin_view
|
||||
from orchestra.apps.accounts.admin import AccountAdminMixin
|
||||
|
||||
from .actions import process_transactions
|
||||
from . import actions
|
||||
from .methods import PaymentMethod
|
||||
from .models import PaymentSource, Transaction, TransactionProcess
|
||||
|
||||
|
@ -16,10 +17,9 @@ from .models import PaymentSource, Transaction, TransactionProcess
|
|||
STATE_COLORS = {
|
||||
Transaction.WAITTING_PROCESSING: 'darkorange',
|
||||
Transaction.WAITTING_CONFIRMATION: 'magenta',
|
||||
Transaction.CONFIRMED: 'olive',
|
||||
Transaction.EXECUTED: 'olive',
|
||||
Transaction.SECURED: 'green',
|
||||
Transaction.REJECTED: 'red',
|
||||
Transaction.DISCARTED: 'blue',
|
||||
}
|
||||
|
||||
|
||||
|
@ -27,7 +27,10 @@ class TransactionInline(admin.TabularInline):
|
|||
model = Transaction
|
||||
can_delete = False
|
||||
extra = 0
|
||||
fields = ('transaction_link', 'bill_link', 'source_link', 'display_state', 'amount', 'currency')
|
||||
fields = (
|
||||
'transaction_link', 'bill_link', 'source_link', 'display_state',
|
||||
'amount', 'currency'
|
||||
)
|
||||
readonly_fields = fields
|
||||
|
||||
transaction_link = admin_link('__unicode__', short_description=_("ID"))
|
||||
|
@ -44,14 +47,19 @@ class TransactionInline(admin.TabularInline):
|
|||
return False
|
||||
|
||||
|
||||
class TransactionAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
class TransactionAdmin(ChangeViewActionsMixin, AccountAdminMixin, admin.ModelAdmin):
|
||||
list_display = (
|
||||
'id', 'bill_link', 'account_link', 'source_link', 'display_state', 'amount', 'process_link'
|
||||
'id', 'bill_link', 'account_link', 'source_link', 'display_state',
|
||||
'amount', 'process_link'
|
||||
)
|
||||
list_filter = ('source__method', 'state')
|
||||
actions = (process_transactions,)
|
||||
actions = (
|
||||
actions.process_transactions, actions.mark_as_executed,
|
||||
actions.mark_as_secured, actions.mark_as_rejected
|
||||
)
|
||||
change_view_actions = actions
|
||||
filter_by_account_fields = ['source']
|
||||
readonly_fields = ('process_link', 'account_link')
|
||||
readonly_fields = ('bill_link', 'display_state', 'process_link', 'account_link')
|
||||
|
||||
bill_link = admin_link('bill')
|
||||
source_link = admin_link('source')
|
||||
|
@ -62,6 +70,20 @@ class TransactionAdmin(AccountAdminMixin, admin.ModelAdmin):
|
|||
def get_queryset(self, request):
|
||||
qs = super(TransactionAdmin, self).get_queryset(request)
|
||||
return qs.select_related('source', 'bill__account__user')
|
||||
|
||||
def get_change_view_actions(self, obj=None):
|
||||
actions = super(TransactionAdmin, self).get_change_view_actions()
|
||||
discard = []
|
||||
if obj:
|
||||
if obj.state == Transaction.EXECUTED:
|
||||
discard = ['mark_as_executed']
|
||||
elif obj.state == Transaction.REJECTED:
|
||||
discard = ['mark_as_rejected']
|
||||
elif obj.state == Transaction.SECURED:
|
||||
discard = ['mark_as_secured']
|
||||
if not discard:
|
||||
return actions
|
||||
return [action for action in actions if action.__name__ not in discard]
|
||||
|
||||
|
||||
class PaymentSourceAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
|
@ -89,10 +111,14 @@ class PaymentSourceAdmin(AccountAdminMixin, admin.ModelAdmin):
|
|||
return select_urls + urls
|
||||
|
||||
def select_method_view(self, request):
|
||||
opts = self.model._meta
|
||||
context = {
|
||||
'opts': opts,
|
||||
'app_label': opts.app_label,
|
||||
'methods': PaymentMethod.get_plugin_choices(),
|
||||
}
|
||||
return render(request, 'admin/payments/payment_source/select_method.html', context)
|
||||
template = 'admin/payments/payment_source/select_method.html'
|
||||
return render(request, template, context)
|
||||
|
||||
def add_view(self, request, form_url='', extra_context=None):
|
||||
""" Redirects to select account view if required """
|
||||
|
|
|
@ -67,17 +67,15 @@ class TransactionQuerySet(models.QuerySet):
|
|||
class Transaction(models.Model):
|
||||
WAITTING_PROCESSING = 'WAITTING_PROCESSING' # CREATED
|
||||
WAITTING_CONFIRMATION = 'WAITTING_CONFIRMATION' # PROCESSED
|
||||
CONFIRMED = 'CONFIRMED'
|
||||
REJECTED = 'REJECTED'
|
||||
DISCARTED = 'DISCARTED'
|
||||
EXECUTED = 'EXECUTED'
|
||||
SECURED = 'SECURED'
|
||||
REJECTED = 'REJECTED'
|
||||
STATES = (
|
||||
(WAITTING_PROCESSING, _("Waitting processing")),
|
||||
(WAITTING_CONFIRMATION, _("Waitting confirmation")),
|
||||
(CONFIRMED, _("Confirmed")),
|
||||
(REJECTED, _("Rejected")),
|
||||
(EXECUTED, _("Executed")),
|
||||
(SECURED, _("Secured")),
|
||||
(DISCARTED, _("Discarted")),
|
||||
(REJECTED, _("Rejected")),
|
||||
)
|
||||
|
||||
objects = TransactionQuerySet.as_manager()
|
||||
|
@ -101,6 +99,21 @@ class Transaction(models.Model):
|
|||
@property
|
||||
def account(self):
|
||||
return self.bill.account
|
||||
|
||||
def mark_as_executed(self):
|
||||
self.state = self.EXECUTED
|
||||
self.save()
|
||||
|
||||
def mark_as_secured(self):
|
||||
self.state = self.SECURED
|
||||
# TODO think carefully about bill feedback
|
||||
self.bill.mark_as_paid()
|
||||
self.save()
|
||||
|
||||
def mark_as_rejected(self):
|
||||
self.state = self.REJECTED
|
||||
# TODO bill feedback
|
||||
self.save()
|
||||
|
||||
|
||||
class TransactionProcess(models.Model):
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
{% extends "admin/orchestra/generic_confirmation.html" %}
|
||||
{% load i18n l10n staticfiles admin_urls %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<h1>Select a method for the new payment source</h1>
|
||||
<form action="" method="post">{% csrf_token %}
|
||||
<div>
|
||||
<div style="margin:20px;">
|
||||
<ul>
|
||||
{% for name, verbose in methods %}
|
||||
<li><a href="../?method={{ name }}&{{ request.META.QUERY_STRING }}">{{ verbose }}</<a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
<p>{{ content_message | safe }}</p>
|
||||
<ul>
|
||||
{% for display_object in display_objects %}
|
||||
<li> <a href="{% url 'admin:nodes_node_change' deletable_object.id %}">{{ deletable_object }} </a></li>
|
||||
<li> <a href="{% url opts|admin_urlname:'change' display_object.pk %}">{{ display_object }} </a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<form action="" method="post">{% csrf_token %}
|
||||
|
|
Loading…
Reference in a new issue