Added date_hierarchy indexes and improvements on billing

This commit is contained in:
Marc Aymerich 2015-07-09 10:19:30 +00:00
parent a364f88452
commit 09025102bc
17 changed files with 175 additions and 32 deletions

View File

@ -450,4 +450,4 @@ def comma(value):
return value return value
# Close, send + download admin action for bills (with confirmation) # db_index on date_hierarchy

View File

@ -107,7 +107,7 @@ class ChangeViewActionsMixin(object):
verbose_name = verbose_name(obj) verbose_name = verbose_name(obj)
view.verbose_name = verbose_name view.verbose_name = verbose_name
view.css_class = getattr(action, 'css_class', 'historylink') view.css_class = getattr(action, 'css_class', 'historylink')
view.description = getattr(action, 'description', '') view.help_text = getattr(action, 'help_text', '')
views.append(view) views.append(view)
return views return views

View File

@ -14,6 +14,7 @@ from django.utils.safestring import mark_safe
from django.utils.translation import ungettext, ugettext_lazy as _ from django.utils.translation import ungettext, ugettext_lazy as _
from orchestra.admin.forms import adminmodelformset_factory from orchestra.admin.forms import adminmodelformset_factory
from orchestra.admin.decorators import action_with_confirmation
from orchestra.admin.utils import get_object_from_url, change_url from orchestra.admin.utils import get_object_from_url, change_url
from orchestra.utils.html import html_to_pdf from orchestra.utils.html import html_to_pdf
@ -23,24 +24,6 @@ from .helpers import validate_contact
from .models import Bill, BillLine from .models import Bill, BillLine
def download_bills(modeladmin, request, queryset):
if queryset.count() > 1:
bytesio = io.BytesIO()
archive = zipfile.ZipFile(bytesio, 'w')
for bill in queryset:
pdf = bill.as_pdf()
archive.writestr('%s.pdf' % bill.number, pdf)
archive.close()
response = HttpResponse(bytesio.getvalue(), content_type='application/pdf')
response['Content-Disposition'] = 'attachment; filename="orchestra-bills.zip"'
return response
bill = queryset.get()
pdf = bill.as_pdf()
return HttpResponse(pdf, content_type='application/pdf')
download_bills.verbose_name = _("Download")
download_bills.url_name = 'download'
def view_bill(modeladmin, request, queryset): def view_bill(modeladmin, request, queryset):
bill = queryset.get() bill = queryset.get()
if not validate_contact(request, bill): if not validate_contact(request, bill):
@ -111,6 +94,7 @@ close_bills.verbose_name = _("Close")
close_bills.url_name = 'close' close_bills.url_name = 'close'
@action_with_confirmation()
def send_bills(modeladmin, request, queryset): def send_bills(modeladmin, request, queryset):
for bill in queryset: for bill in queryset:
if not validate_contact(request, bill): if not validate_contact(request, bill):
@ -128,12 +112,41 @@ send_bills.verbose_name = lambda bill: _("Resend" if getattr(bill, 'is_sent', Fa
send_bills.url_name = 'send' send_bills.url_name = 'send'
def download_bills(modeladmin, request, queryset):
if queryset.count() > 1:
bytesio = io.BytesIO()
archive = zipfile.ZipFile(bytesio, 'w')
for bill in queryset:
pdf = bill.as_pdf()
archive.writestr('%s.pdf' % bill.number, pdf)
archive.close()
response = HttpResponse(bytesio.getvalue(), content_type='application/pdf')
response['Content-Disposition'] = 'attachment; filename="orchestra-bills.zip"'
return response
bill = queryset.get()
pdf = bill.as_pdf()
return HttpResponse(pdf, content_type='application/pdf')
download_bills.verbose_name = _("Download")
download_bills.url_name = 'download'
def close_send_download_bills(modeladmin, request, queryset):
close_bills(modeladmin, request, queryset)
if request.POST.get('post') == 'generic_confirmation':
send_bills(modeladmin, request, queryset)
return download_bills(modeladmin, request, queryset)
close_send_download_bills.verbose_name = _("C.S.D.")
close_send_download_bills.url_name = 'close-send-download'
close_send_download_bills.help_text = _("Close, send and download bills in one shot.")
def manage_lines(modeladmin, request, queryset): def manage_lines(modeladmin, request, queryset):
url = reverse('admin:bills_bill_manage_lines') url = reverse('admin:bills_bill_manage_lines')
url += '?ids=%s' % ','.join(map(str, queryset.values_list('id', flat=True))) url += '?ids=%s' % ','.join(map(str, queryset.values_list('id', flat=True)))
return redirect(url) return redirect(url)
@action_with_confirmation()
def undo_billing(modeladmin, request, queryset): def undo_billing(modeladmin, request, queryset):
group = {} group = {}
for line in queryset.select_related('order'): for line in queryset.select_related('order'):
@ -214,6 +227,7 @@ def copy_lines(modeladmin, request, queryset):
return move_lines(modeladmin, request, queryset) return move_lines(modeladmin, request, queryset)
@action_with_confirmation()
def amend_bills(modeladmin, request, queryset): def amend_bills(modeladmin, request, queryset):
if queryset.filter(is_open=True).exists(): if queryset.filter(is_open=True).exists():
messages.warning(request, _("Selected bills should be in closed state")) messages.warning(request, _("Selected bills should be in closed state"))

View File

@ -207,11 +207,11 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
search_fields = ('number', 'account__username', 'comments') search_fields = ('number', 'account__username', 'comments')
change_view_actions = [ change_view_actions = [
actions.manage_lines, actions.view_bill, actions.download_bills, actions.send_bills, actions.manage_lines, actions.view_bill, actions.download_bills, actions.send_bills,
actions.close_bills, actions.amend_bills, actions.close_bills, actions.amend_bills, actions.close_send_download_bills,
] ]
actions = [ actions = [
actions.manage_lines, actions.download_bills, actions.close_bills, actions.send_bills, actions.manage_lines, actions.download_bills, actions.close_bills, actions.send_bills,
actions.amend_bills, actions.report actions.amend_bills, actions.report, actions.close_send_download_bills,
] ]
change_readonly_fields = ('account_link', 'type', 'is_open', 'amend_of_link', 'amend_links') change_readonly_fields = ('account_link', 'type', 'is_open', 'amend_of_link', 'amend_links')
readonly_fields = ('number', 'display_total', 'is_sent', 'display_payment_state') readonly_fields = ('number', 'display_total', 'is_sent', 'display_payment_state')
@ -303,7 +303,7 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
exclude = [] exclude = []
if obj: if obj:
if not obj.is_open: if not obj.is_open:
exclude.append('close_bills') exclude += ['close_bills', 'close_send_download_bills']
return [action for action in actions if action.__name__ not in exclude] return [action for action in actions if action.__name__ not in exclude]
def get_inline_instances(self, request, obj=None): def get_inline_instances(self, request, obj=None):

File diff suppressed because one or more lines are too long

View File

@ -102,7 +102,7 @@ class Bill(models.Model):
related_name='amends') related_name='amends')
type = models.CharField(_("type"), max_length=16, choices=TYPES) type = models.CharField(_("type"), max_length=16, choices=TYPES)
created_on = models.DateField(_("created on"), auto_now_add=True) created_on = models.DateField(_("created on"), auto_now_add=True)
closed_on = models.DateField(_("closed on"), blank=True, null=True) closed_on = models.DateField(_("closed on"), blank=True, null=True, db_index=True)
is_open = models.BooleanField(_("open"), default=True) is_open = models.BooleanField(_("open"), default=True)
is_sent = models.BooleanField(_("sent"), default=False) is_sent = models.BooleanField(_("sent"), default=False)
due_on = models.DateField(_("due on"), null=True, blank=True) due_on = models.DateField(_("due on"), null=True, blank=True)

View File

@ -49,7 +49,7 @@ def change_ticket_state_factory(action, final_state):
change_ticket_state.url_name = action change_ticket_state.url_name = action
change_ticket_state.verbose_name = action change_ticket_state.verbose_name = action
change_ticket_state.short_description = _('%s selected tickets') % action.capitalize() change_ticket_state.short_description = _('%s selected tickets') % action.capitalize()
change_ticket_state.description = _('Mark ticket as %s.') % final_state.lower() change_ticket_state.help_text = _('Mark ticket as %s.') % final_state.lower()
change_ticket_state.__name__ = action change_ticket_state.__name__ = action
return change_ticket_state return change_ticket_state
@ -90,7 +90,7 @@ def take_tickets(modeladmin, request, queryset):
modeladmin.message_user(request, msg) modeladmin.message_user(request, msg)
take_tickets.url_name = 'take' take_tickets.url_name = 'take'
take_tickets.short_description = _("Take selected tickets") take_tickets.short_description = _("Take selected tickets")
take_tickets.description = _("Make yourself owner of the ticket.") take_tickets.help_text = _("Make yourself owner of the ticket.")
@transaction.atomic @transaction.atomic

View File

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('issues', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='ticket',
name='created_at',
field=models.DateTimeField(db_index=True, auto_now_add=True, verbose_name='created'),
),
]

View File

@ -68,7 +68,7 @@ class Ticket(models.Model):
priority = models.CharField(_("priority"), max_length=32, choices=PRIORITIES, priority = models.CharField(_("priority"), max_length=32, choices=PRIORITIES,
default=MEDIUM) default=MEDIUM)
state = models.CharField(_("state"), max_length=32, choices=STATES, default=NEW) state = models.CharField(_("state"), max_length=32, choices=STATES, default=NEW)
created_at = models.DateTimeField(_("created"), auto_now_add=True) created_at = models.DateTimeField(_("created"), auto_now_add=True, db_index=True)
updated_at = models.DateTimeField(_("modified"), auto_now=True) updated_at = models.DateTimeField(_("modified"), auto_now=True)
cc = models.TextField("CC", help_text=_("emails to send a carbon copy to"), cc = models.TextField("CC", help_text=_("emails to send a carbon copy to"),
blank=True) blank=True)

View File

@ -118,7 +118,7 @@ class BackendLogAdmin(admin.ModelAdmin):
'display_created', 'execution_time', 'display_created', 'execution_time',
) )
list_display_links = ('id', 'backend') list_display_links = ('id', 'backend')
list_filter = ('state', 'backend') list_filter = ('state', 'backend', 'server')
date_hierarchy = 'created_at' date_hierarchy = 'created_at'
inlines = (BackendOperationInline,) inlines = (BackendOperationInline,)
fields = ( fields = (

View File

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import orchestra.models.fields
class Migration(migrations.Migration):
dependencies = [
('orchestration', '0004_route_async_actions'),
]
operations = [
migrations.AlterField(
model_name='backendlog',
name='created_at',
field=models.DateTimeField(db_index=True, verbose_name='created', auto_now_add=True),
),
migrations.AlterField(
model_name='route',
name='async_actions',
field=orchestra.models.fields.MultiSelectField(blank=True, help_text='Specify individual actions to be executed asynchronoulsy.', max_length=256),
),
]

View File

@ -79,7 +79,7 @@ class BackendLog(models.Model):
exit_code = models.IntegerField(_("exit code"), null=True) exit_code = models.IntegerField(_("exit code"), null=True)
task_id = models.CharField(_("task ID"), max_length=36, unique=True, null=True, task_id = models.CharField(_("task ID"), max_length=36, unique=True, null=True,
help_text="Celery task ID when used as execution backend") help_text="Celery task ID when used as execution backend")
created_at = models.DateTimeField(_("created"), auto_now_add=True) created_at = models.DateTimeField(_("created"), auto_now_add=True, db_index=True)
updated_at = models.DateTimeField(_("updated"), auto_now=True) updated_at = models.DateTimeField(_("updated"), auto_now=True)
class Meta: class Meta:

View File

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('orders', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='order',
name='registered_on',
field=models.DateField(db_index=True, default=django.utils.timezone.now, verbose_name='registered'),
),
]

View File

@ -112,7 +112,7 @@ class Order(models.Model):
object_id = models.PositiveIntegerField(null=True) object_id = models.PositiveIntegerField(null=True)
service = models.ForeignKey(settings.ORDERS_SERVICE_MODEL, verbose_name=_("service"), service = models.ForeignKey(settings.ORDERS_SERVICE_MODEL, verbose_name=_("service"),
related_name='orders') related_name='orders')
registered_on = models.DateField(_("registered"), default=timezone.now) registered_on = models.DateField(_("registered"), default=timezone.now, db_index=True)
cancelled_on = models.DateField(_("cancelled"), null=True, blank=True) cancelled_on = models.DateField(_("cancelled"), null=True, blank=True)
billed_on = models.DateField(_("billed"), null=True, blank=True) billed_on = models.DateField(_("billed"), null=True, blank=True)
billed_until = models.DateField(_("billed until"), null=True, blank=True) billed_until = models.DateField(_("billed until"), null=True, blank=True)

View File

@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.db.models.deletion
import orchestra.models.fields
class Migration(migrations.Migration):
dependencies = [
('payments', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='paymentsource',
name='method',
field=models.CharField(max_length=32, verbose_name='method', choices=[('SEPADirectDebit', 'SEPA Direct Debit')]),
),
migrations.AlterField(
model_name='transaction',
name='process',
field=models.ForeignKey(verbose_name='process', on_delete=django.db.models.deletion.SET_NULL, blank=True, related_name='transactions', to='payments.TransactionProcess', null=True),
),
migrations.AlterField(
model_name='transactionprocess',
name='created_at',
field=models.DateTimeField(db_index=True, verbose_name='created', auto_now_add=True),
),
migrations.AlterField(
model_name='transactionprocess',
name='file',
field=orchestra.models.fields.PrivateFileField(blank=True, verbose_name='file', upload_to=''),
),
]

View File

@ -166,7 +166,7 @@ class TransactionProcess(models.Model):
data = JSONField(_("data"), blank=True) data = JSONField(_("data"), blank=True)
file = PrivateFileField(_("file"), blank=True) file = PrivateFileField(_("file"), blank=True)
state = models.CharField(_("state"), max_length=16, choices=STATES, default=CREATED) state = models.CharField(_("state"), max_length=16, choices=STATES, default=CREATED)
created_at = models.DateTimeField(_("created"), auto_now_add=True) created_at = models.DateTimeField(_("created"), auto_now_add=True, db_index=True)
updated_at = models.DateTimeField(_("updated"), auto_now=True) updated_at = models.DateTimeField(_("updated"), auto_now=True)
class Meta: class Meta:

View File

@ -4,7 +4,7 @@
{% block object-tools-items %} {% block object-tools-items %}
{% for item in object_tools_items %} {% for item in object_tools_items %}
<li><a href="{{ item.url_name }}/" class="{{ item.css_class }}" title="{{ item.description }}">{{ item.verbose_name }}</a></li> <li><a href="{{ item.url_name }}/" class="{{ item.css_class }}" title="{{ item.help_text }}">{{ item.verbose_name }}</a></li>
{% endfor %} {% endfor %}
{{ block.super }} {{ block.super }}
{% endblock %} {% endblock %}