From b88689864fdcf25efc60dcaae114f5e2dd77736d Mon Sep 17 00:00:00 2001 From: Marc Date: Thu, 24 Jul 2014 15:43:23 +0000 Subject: [PATCH] Added preliminar implementation of admin billing --- TODO.md | 7 +- orchestra/admin/decorators.py | 1 + orchestra/admin/forms.py | 18 ++++ orchestra/apps/orders/actions.py | 90 +++++++++++++++++++ orchestra/apps/orders/admin.py | 2 + orchestra/apps/orders/forms.py | 43 +++++++++ .../orders/order/bill_selected_options.html | 36 ++++++++ 7 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 orchestra/admin/forms.py create mode 100644 orchestra/apps/orders/actions.py create mode 100644 orchestra/apps/orders/forms.py create mode 100644 orchestra/apps/orders/templates/admin/orders/order/bill_selected_options.html diff --git a/TODO.md b/TODO.md index 39218e4e..0cd52e1a 100644 --- a/TODO.md +++ b/TODO.md @@ -64,6 +64,11 @@ Remember that, as always with QuerySets, any subsequent chained methods which im dependency collector with max_recursion that matches the number of dots on service.match and service.metric -* Be consistent with dates: name_on, created ? +* Be consistent with dates: + * created_on date + * created_at datetime + +at + clock time, midnight, noon- At 3:30 p.m., At 4:01, At noon + * backend logs with hal logo diff --git a/orchestra/admin/decorators.py b/orchestra/admin/decorators.py index 141fccf8..aabbeb87 100644 --- a/orchestra/admin/decorators.py +++ b/orchestra/admin/decorators.py @@ -8,6 +8,7 @@ from django.utils.encoding import force_text def admin_field(method): + """ Wraps a function to be used as a ModelAdmin method field """ def admin_field_wrapper(*args, **kwargs): """ utility function for creating admin links """ kwargs['field'] = args[0] if args else '' diff --git a/orchestra/admin/forms.py b/orchestra/admin/forms.py new file mode 100644 index 00000000..65ae2b33 --- /dev/null +++ b/orchestra/admin/forms.py @@ -0,0 +1,18 @@ +from django.template import Template, Context +from django.contrib.admin.helpers import AdminForm + + +class AdminFormMixin(object): + """ Provides a method for rendering a form just like in Django Admin """ + def as_admin(self): + prepopulated_fields = {} + fieldsets = [ + (None, {'fields': self.fields.keys()}) + ] + adminform = AdminForm(self, fieldsets, prepopulated_fields) + template = Template( + '{% for fieldset in adminform %}' + '{% include "admin/includes/fieldset.html" %}' + '{% endfor %}' + ) + return template.render(Context({'adminform': adminform})) diff --git a/orchestra/apps/orders/actions.py b/orchestra/apps/orders/actions.py new file mode 100644 index 00000000..8c858df9 --- /dev/null +++ b/orchestra/apps/orders/actions.py @@ -0,0 +1,90 @@ +from django.contrib import admin, messages +from django.core.urlresolvers import reverse +from django.utils.safestring import mark_safe +from django.utils.translation import ugettext_lazy as _ +from django.shortcuts import render + +from .forms import (BillSelectedOptionsForm, BillSelectConfirmationForm, + BillSelectRelatedForm) + + +class BillSelectedOrders(object): + """ Form wizard for billing orders admin action """ + short_description = _("Bill selected orders") + template = 'admin/orders/order/bill_selected_options.html' + __name__ = 'bill_selected_orders' + + def __call__(self, modeladmin, request, queryset): + """ make this monster behave like a function """ + self.modeladmin = modeladmin + self.queryset = queryset + opts = modeladmin.model._meta + app_label = opts.app_label + self.context = { + 'opts': opts, + 'app_label': app_label, + 'queryset': queryset, + 'action_checkbox_name': admin.helpers.ACTION_CHECKBOX_NAME, + } + return self.set_options(request) + + def set_options(self, request): + form = BillSelectedOptionsForm() + if request.POST.get('step'): + form = BillSelectedOptionsForm(request.POST) + if form.is_valid(): + self.options = dict( + billing_point=form.cleaned_data['billing_point'], + fixed_point=form.cleaned_data['fixed_point'], + create_new_open=form.cleaned_data['create_new_open'], + ) + return self.select_related(request) + self.context.update({ + 'title': _("Options for billing selected orders, step 1 / 3"), + 'step': 'one', + 'form': form, + }) + return render(request, self.template, self.context) + + def select_related(self, request): + self.options['related_queryset'] = self.queryset.all() #get_related(**options) + form = BillSelectRelatedForm(initial=self.options) + if request.POST.get('step') == 'two': + form = BillSelectRelatedForm(request.POST, initial=self.options) + if form.is_valid(): + select_related = form.cleaned_data['selected_related'] + self.options['selected_related'] = select_related + return self.confirmation(request) + self.context.update({ + 'title': _("Select related order for billing, step 2 / 3"), + 'step': 'two', + 'form': form, + }) + return render(request, self.template, self.context) + + def confirmation(self, request): + form = BillSelectConfirmationForm(initial=self.options) + if request.POST: + bills = Order.bill(queryset, commit=True, **self.options) + if not bills: + msg = _("Selected orders do not have pending billing") + self.modeladmin.message_user(request, msg, messages.WARNING) + else: + ids = ','.join([bill.id for bill in bills]) + url = reverse('admin:bills_bill_changelist') + context = { + 'url': url + '?id=%s' % ids, + 'num': len(bills), + 'bills': _("bills"), + 'msg': _("have been generated"), + } + msg = '%(num)s %(bills)s %(msg)s' % context + msg = mark_safe(msg) + self.modeladmin.message_user(request, msg, messages.INFO) + return + self.context.update({ + 'title': _("Confirmation for billing selected orders"), + 'step': 'three', + 'form': form, + }) + return render(request, self.template, self.context) diff --git a/orchestra/apps/orders/admin.py b/orchestra/apps/orders/admin.py index b79badc7..5b2a8141 100644 --- a/orchestra/apps/orders/admin.py +++ b/orchestra/apps/orders/admin.py @@ -11,6 +11,7 @@ from orchestra.admin.utils import admin_link, admin_date from orchestra.apps.accounts.admin import AccountAdminMixin from orchestra.core import services +from .actions import BillSelectedOrders from .filters import ActiveOrderListFilter from .models import Service, Order, MetricStorage @@ -81,6 +82,7 @@ class OrderAdmin(AccountAdminMixin, ChangeListDefaultFilter, admin.ModelAdmin): ) list_display_link = ('id', 'service') list_filter = (ActiveOrderListFilter, 'service',) + actions = (BillSelectedOrders(),) date_hierarchy = 'registered_on' default_changelist_filters = ( ('is_active', 'True'), diff --git a/orchestra/apps/orders/forms.py b/orchestra/apps/orders/forms.py new file mode 100644 index 00000000..3a5c9268 --- /dev/null +++ b/orchestra/apps/orders/forms.py @@ -0,0 +1,43 @@ +from django import forms +from django.utils import timezone +from django.utils.translation import ugettext_lazy as _ + +from orchestra.admin.forms import AdminFormMixin + +from .models import Order + + +class BillSelectedOptionsForm(AdminFormMixin, forms.Form): + billing_point = forms.DateField(initial=timezone.now, + label=_("Billing point"), + help_text=_("Date you want to bill selected orders")) + fixed_point = forms.BooleanField(initial=False, required=False, + label=_("fixed point"), + help_text=_("Deisgnates whether you want the billing point to be an " + "exact date, or adapt it to the billing period.")) + create_new_open = forms.BooleanField(initial=False, required=False, + label=_("Create a new open bill"), + help_text=_("Deisgnates whether you want to put this orders on a new " + "open bill, or allow to reuse an existing one.")) + + +class BillSelectRelatedForm(AdminFormMixin, forms.Form): + selected_related = forms.ModelMultipleChoiceField(queryset=Order.objects.none(), + required=False) + billing_point = forms.DateField(widget=forms.HiddenInput()) + fixed_point = forms.BooleanField(widget=forms.HiddenInput(), required=False) + create_new_open = forms.BooleanField(widget=forms.HiddenInput(), required=False) + + def __init__(self, *args, **kwargs): + super(BillSelectRelatedForm, self).__init__(*args, **kwargs) + queryset = kwargs['initial'].get('related_queryset', None) + if queryset: + self.fields['selected_related'].queryset = queryset + + +class BillSelectConfirmationForm(forms.Form): + selected_related = forms.ModelMultipleChoiceField(queryset=Order.objects.none(), + widget=forms.HiddenInput(), required=False) + billing_point = forms.DateField(widget=forms.HiddenInput()) + fixed_point = forms.BooleanField(widget=forms.HiddenInput(), required=False) + create_new_open = forms.BooleanField(widget=forms.HiddenInput(), required=False) diff --git a/orchestra/apps/orders/templates/admin/orders/order/bill_selected_options.html b/orchestra/apps/orders/templates/admin/orders/order/bill_selected_options.html new file mode 100644 index 00000000..03aa0626 --- /dev/null +++ b/orchestra/apps/orders/templates/admin/orders/order/bill_selected_options.html @@ -0,0 +1,36 @@ +{% extends "admin/base_site.html" %} +{% load i18n l10n staticfiles admin_urls %} + +{% block extrastyle %} +{{ block.super }} + +{% endblock %} + +{% block breadcrumbs %} + +{% endblock %} + + +{% block content %} +
{% csrf_token %} +
+ +
+ {{ form.as_admin }} +
+ {% for obj in queryset %} + + {% endfor %} + + + +
+
+{% endblock %} + +