diff --git a/TODO.md b/TODO.md index 740223b6..5fd1a19a 100644 --- a/TODO.md +++ b/TODO.md @@ -450,4 +450,4 @@ def comma(value): return value -# Close, send + download admin action for bills (with confirmation) +# db_index on date_hierarchy diff --git a/orchestra/admin/options.py b/orchestra/admin/options.py index 692a3da7..9abd71dc 100644 --- a/orchestra/admin/options.py +++ b/orchestra/admin/options.py @@ -107,7 +107,7 @@ class ChangeViewActionsMixin(object): verbose_name = verbose_name(obj) view.verbose_name = verbose_name view.css_class = getattr(action, 'css_class', 'historylink') - view.description = getattr(action, 'description', '') + view.help_text = getattr(action, 'help_text', '') views.append(view) return views diff --git a/orchestra/contrib/bills/actions.py b/orchestra/contrib/bills/actions.py index b8dc3e7d..8e358348 100644 --- a/orchestra/contrib/bills/actions.py +++ b/orchestra/contrib/bills/actions.py @@ -14,6 +14,7 @@ from django.utils.safestring import mark_safe from django.utils.translation import ungettext, ugettext_lazy as _ 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.utils.html import html_to_pdf @@ -23,24 +24,6 @@ from .helpers import validate_contact 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): bill = queryset.get() if not validate_contact(request, bill): @@ -111,6 +94,7 @@ close_bills.verbose_name = _("Close") close_bills.url_name = 'close' +@action_with_confirmation() def send_bills(modeladmin, request, queryset): for bill in queryset: 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' +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): url = reverse('admin:bills_bill_manage_lines') url += '?ids=%s' % ','.join(map(str, queryset.values_list('id', flat=True))) return redirect(url) +@action_with_confirmation() def undo_billing(modeladmin, request, queryset): group = {} for line in queryset.select_related('order'): @@ -214,6 +227,7 @@ def copy_lines(modeladmin, request, queryset): return move_lines(modeladmin, request, queryset) +@action_with_confirmation() def amend_bills(modeladmin, request, queryset): if queryset.filter(is_open=True).exists(): messages.warning(request, _("Selected bills should be in closed state")) diff --git a/orchestra/contrib/bills/admin.py b/orchestra/contrib/bills/admin.py index 67d10c02..a24e298d 100644 --- a/orchestra/contrib/bills/admin.py +++ b/orchestra/contrib/bills/admin.py @@ -207,11 +207,11 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin): search_fields = ('number', 'account__username', 'comments') change_view_actions = [ 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.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') readonly_fields = ('number', 'display_total', 'is_sent', 'display_payment_state') @@ -303,7 +303,7 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin): exclude = [] if obj: 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] def get_inline_instances(self, request, obj=None): diff --git a/orchestra/contrib/bills/migrations/0006_auto_20150709_1016.py b/orchestra/contrib/bills/migrations/0006_auto_20150709_1016.py new file mode 100644 index 00000000..dc6a8be6 --- /dev/null +++ b/orchestra/contrib/bills/migrations/0006_auto_20150709_1016.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('bills', '0005_auto_20150623_1031'), + ] + + operations = [ + migrations.AlterField( + model_name='bill', + name='closed_on', + field=models.DateField(db_index=True, blank=True, null=True, verbose_name='closed on'), + ), + migrations.AlterField( + model_name='billcontact', + name='country', + field=models.CharField(choices=[('BS', 'Bahamas'), ('CX', 'Christmas Island'), ('DK', 'Denmark'), ('AI', 'Anguilla'), ('PF', 'French Polynesia'), ('ZA', 'South Africa'), ('NI', 'Nicaragua'), ('BB', 'Barbados'), ('TD', 'Chad'), ('PR', 'Puerto Rico'), ('AD', 'Andorra'), ('GE', 'Georgia'), ('UY', 'Uruguay'), ('AG', 'Antigua and Barbuda'), ('DM', 'Dominica'), ('RU', 'Russian Federation'), ('SE', 'Sweden'), ('UG', 'Uganda'), ('KN', 'Saint Kitts and Nevis'), ('CF', 'Central African Republic'), ('MU', 'Mauritius'), ('SR', 'Suriname'), ('KH', 'Cambodia'), ('CV', 'Cabo Verde'), ('CD', 'Congo (the Democratic Republic of the)'), ('BV', 'Bouvet Island'), ('PS', 'Palestine, State of'), ('BQ', 'Bonaire, Sint Eustatius and Saba'), ('CK', 'Cook Islands'), ('MD', 'Moldova (the Republic of)'), ('DE', 'Germany'), ('CM', 'Cameroon'), ('BF', 'Burkina Faso'), ('SM', 'San Marino'), ('NL', 'Netherlands'), ('BL', 'Saint Barthélemy'), ('SV', 'El Salvador'), ('AU', 'Australia'), ('GN', 'Guinea'), ('GM', 'Gambia'), ('MK', 'Macedonia (the former Yugoslav Republic of)'), ('MQ', 'Martinique'), ('SC', 'Seychelles'), ('GY', 'Guyana'), ('TF', 'French Southern Territories'), ('NP', 'Nepal'), ('KW', 'Kuwait'), ('AT', 'Austria'), ('AZ', 'Azerbaijan'), ('QA', 'Qatar'), ('JP', 'Japan'), ('HT', 'Haiti'), ('EC', 'Ecuador'), ('LR', 'Liberia'), ('RO', 'Romania'), ('LB', 'Lebanon'), ('TT', 'Trinidad and Tobago'), ('BR', 'Brazil'), ('AW', 'Aruba'), ('BM', 'Bermuda'), ('VU', 'Vanuatu'), ('MR', 'Mauritania'), ('SL', 'Sierra Leone'), ('NE', 'Niger'), ('VC', 'Saint Vincent and the Grenadines'), ('IQ', 'Iraq'), ('NC', 'New Caledonia'), ('GI', 'Gibraltar'), ('NG', 'Nigeria'), ('MX', 'Mexico'), ('NZ', 'New Zealand'), ('KP', "Korea (the Democratic People's Republic of)"), ('ZM', 'Zambia'), ('ZW', 'Zimbabwe'), ('SX', 'Sint Maarten (Dutch part)'), ('WF', 'Wallis and Futuna'), ('CC', 'Cocos (Keeling) Islands'), ('TZ', 'Tanzania, United Republic of'), ('NU', 'Niue'), ('MP', 'Northern Mariana Islands'), ('IN', 'India'), ('LC', 'Saint Lucia'), ('TC', 'Turks and Caicos Islands'), ('PG', 'Papua New Guinea'), ('IL', 'Israel'), ('YE', 'Yemen'), ('LA', "Lao People's Democratic Republic"), ('BH', 'Bahrain'), ('NF', 'Norfolk Island'), ('DZ', 'Algeria'), ('MS', 'Montserrat'), ('JO', 'Jordan'), ('US', 'United States of America'), ('WS', 'Samoa'), ('KZ', 'Kazakhstan'), ('ME', 'Montenegro'), ('ET', 'Ethiopia'), ('UZ', 'Uzbekistan'), ('HR', 'Croatia'), ('PE', 'Peru'), ('LS', 'Lesotho'), ('UM', 'United States Minor Outlying Islands'), ('PL', 'Poland'), ('GF', 'French Guiana'), ('RW', 'Rwanda'), ('TV', 'Tuvalu'), ('FM', 'Micronesia (Federated States of)'), ('TR', 'Turkey'), ('TJ', 'Tajikistan'), ('SO', 'Somalia'), ('GP', 'Guadeloupe'), ('SG', 'Singapore'), ('JE', 'Jersey'), ('IS', 'Iceland'), ('KR', 'Korea (the Republic of)'), ('VN', 'Viet Nam'), ('SB', 'Solomon Islands'), ('CG', 'Congo'), ('NO', 'Norway'), ('VG', 'Virgin Islands (British)'), ('IT', 'Italy'), ('VE', 'Venezuela (Bolivarian Republic of)'), ('IR', 'Iran (Islamic Republic of)'), ('CO', 'Colombia'), ('IM', 'Isle of Man'), ('GQ', 'Equatorial Guinea'), ('UA', 'Ukraine'), ('MN', 'Mongolia'), ('GH', 'Ghana'), ('BO', 'Bolivia (Plurinational State of)'), ('AR', 'Argentina'), ('GB', 'United Kingdom of Great Britain and Northern Ireland'), ('TK', 'Tokelau'), ('PT', 'Portugal'), ('CW', 'Curaçao'), ('BN', 'Brunei Darussalam'), ('AM', 'Armenia'), ('TL', 'Timor-Leste'), ('TO', 'Tonga'), ('MY', 'Malaysia'), ('AX', 'Åland Islands'), ('CY', 'Cyprus'), ('GL', 'Greenland'), ('RS', 'Serbia'), ('AF', 'Afghanistan'), ('LT', 'Lithuania'), ('KY', 'Cayman Islands'), ('SK', 'Slovakia'), ('SI', 'Slovenia'), ('CN', 'China'), ('CL', 'Chile'), ('BA', 'Bosnia and Herzegovina'), ('DO', 'Dominican Republic'), ('CH', 'Switzerland'), ('LV', 'Latvia'), ('HN', 'Honduras'), ('TH', 'Thailand'), ('GT', 'Guatemala'), ('SY', 'Syrian Arab Republic'), ('BT', 'Bhutan'), ('GS', 'South Georgia and the South Sandwich Islands'), ('YT', 'Mayotte'), ('MV', 'Maldives'), ('LY', 'Libya'), ('MG', 'Madagascar'), ('FI', 'Finland'), ('AE', 'United Arab Emirates'), ('ID', 'Indonesia'), ('AS', 'American Samoa'), ('IO', 'British Indian Ocean Territory'), ('RE', 'Réunion'), ('SH', 'Saint Helena, Ascension and Tristan da Cunha'), ('ES', 'Spain'), ('MZ', 'Mozambique'), ('PN', 'Pitcairn'), ('DJ', 'Djibouti'), ('LI', 'Liechtenstein'), ('BZ', 'Belize'), ('EE', 'Estonia'), ('MF', 'Saint Martin (French part)'), ('PW', 'Palau'), ('TM', 'Turkmenistan'), ('AL', 'Albania'), ('MO', 'Macao'), ('AO', 'Angola'), ('VA', 'Holy See'), ('SN', 'Senegal'), ('GG', 'Guernsey'), ('MH', 'Marshall Islands'), ('NR', 'Nauru'), ('KE', 'Kenya'), ('BJ', 'Benin'), ('MA', 'Morocco'), ('EG', 'Egypt'), ('KG', 'Kyrgyzstan'), ('GD', 'Grenada'), ('IE', 'Ireland'), ('BG', 'Bulgaria'), ('HU', 'Hungary'), ('SD', 'Sudan'), ('NA', 'Namibia'), ('CU', 'Cuba'), ('BY', 'Belarus'), ('GR', 'Greece'), ('TW', 'Taiwan (Province of China)'), ('MT', 'Malta'), ('ST', 'Sao Tome and Principe'), ('JM', 'Jamaica'), ('BW', 'Botswana'), ('MM', 'Myanmar'), ('KI', 'Kiribati'), ('SA', 'Saudi Arabia'), ('FK', 'Falkland Islands [Malvinas]'), ('FR', 'France'), ('VI', 'Virgin Islands (U.S.)'), ('GA', 'Gabon'), ('ML', 'Mali'), ('LK', 'Sri Lanka'), ('FO', 'Faroe Islands'), ('CI', "Côte d'Ivoire"), ('LU', 'Luxembourg'), ('TN', 'Tunisia'), ('PA', 'Panama'), ('HK', 'Hong Kong'), ('TG', 'Togo'), ('KM', 'Comoros'), ('PH', 'Philippines'), ('PM', 'Saint Pierre and Miquelon'), ('AQ', 'Antarctica'), ('MW', 'Malawi'), ('MC', 'Monaco'), ('FJ', 'Fiji'), ('BI', 'Burundi'), ('PY', 'Paraguay'), ('SJ', 'Svalbard and Jan Mayen'), ('HM', 'Heard Island and McDonald Islands'), ('SS', 'South Sudan'), ('CZ', 'Czech Republic'), ('GU', 'Guam'), ('SZ', 'Swaziland'), ('GW', 'Guinea-Bissau'), ('CA', 'Canada'), ('EH', 'Western Sahara'), ('OM', 'Oman'), ('ER', 'Eritrea'), ('BE', 'Belgium'), ('CR', 'Costa Rica'), ('PK', 'Pakistan'), ('BD', 'Bangladesh')], default='ES', verbose_name='country', max_length=20), + ), + migrations.AlterField( + model_name='billline', + name='end_on', + field=models.DateField(blank=True, null=True, verbose_name='end'), + ), + ] diff --git a/orchestra/contrib/bills/models.py b/orchestra/contrib/bills/models.py index 84166d81..aabb3305 100644 --- a/orchestra/contrib/bills/models.py +++ b/orchestra/contrib/bills/models.py @@ -102,7 +102,7 @@ class Bill(models.Model): related_name='amends') type = models.CharField(_("type"), max_length=16, choices=TYPES) 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_sent = models.BooleanField(_("sent"), default=False) due_on = models.DateField(_("due on"), null=True, blank=True) diff --git a/orchestra/contrib/issues/actions.py b/orchestra/contrib/issues/actions.py index 9bf73d20..dba77702 100644 --- a/orchestra/contrib/issues/actions.py +++ b/orchestra/contrib/issues/actions.py @@ -49,7 +49,7 @@ def change_ticket_state_factory(action, final_state): change_ticket_state.url_name = action change_ticket_state.verbose_name = action 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 return change_ticket_state @@ -90,7 +90,7 @@ def take_tickets(modeladmin, request, queryset): modeladmin.message_user(request, msg) take_tickets.url_name = 'take' 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 diff --git a/orchestra/contrib/issues/migrations/0002_auto_20150709_1018.py b/orchestra/contrib/issues/migrations/0002_auto_20150709_1018.py new file mode 100644 index 00000000..b0ceac03 --- /dev/null +++ b/orchestra/contrib/issues/migrations/0002_auto_20150709_1018.py @@ -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'), + ), + ] diff --git a/orchestra/contrib/issues/models.py b/orchestra/contrib/issues/models.py index 1e2aab0d..e6ceea4e 100644 --- a/orchestra/contrib/issues/models.py +++ b/orchestra/contrib/issues/models.py @@ -68,7 +68,7 @@ class Ticket(models.Model): priority = models.CharField(_("priority"), max_length=32, choices=PRIORITIES, default=MEDIUM) 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) cc = models.TextField("CC", help_text=_("emails to send a carbon copy to"), blank=True) diff --git a/orchestra/contrib/orchestration/admin.py b/orchestra/contrib/orchestration/admin.py index 42f88fc2..bae2e228 100644 --- a/orchestra/contrib/orchestration/admin.py +++ b/orchestra/contrib/orchestration/admin.py @@ -118,7 +118,7 @@ class BackendLogAdmin(admin.ModelAdmin): 'display_created', 'execution_time', ) list_display_links = ('id', 'backend') - list_filter = ('state', 'backend') + list_filter = ('state', 'backend', 'server') date_hierarchy = 'created_at' inlines = (BackendOperationInline,) fields = ( diff --git a/orchestra/contrib/orchestration/migrations/0005_auto_20150709_1016.py b/orchestra/contrib/orchestration/migrations/0005_auto_20150709_1016.py new file mode 100644 index 00000000..6ac2af8a --- /dev/null +++ b/orchestra/contrib/orchestration/migrations/0005_auto_20150709_1016.py @@ -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), + ), + ] diff --git a/orchestra/contrib/orchestration/models.py b/orchestra/contrib/orchestration/models.py index 350f4965..37cb144e 100644 --- a/orchestra/contrib/orchestration/models.py +++ b/orchestra/contrib/orchestration/models.py @@ -79,7 +79,7 @@ class BackendLog(models.Model): exit_code = models.IntegerField(_("exit code"), 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") - 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) class Meta: diff --git a/orchestra/contrib/orders/migrations/0002_auto_20150709_1018.py b/orchestra/contrib/orders/migrations/0002_auto_20150709_1018.py new file mode 100644 index 00000000..acbe5109 --- /dev/null +++ b/orchestra/contrib/orders/migrations/0002_auto_20150709_1018.py @@ -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'), + ), + ] diff --git a/orchestra/contrib/orders/models.py b/orchestra/contrib/orders/models.py index dcb46c9f..71251bd1 100644 --- a/orchestra/contrib/orders/models.py +++ b/orchestra/contrib/orders/models.py @@ -112,7 +112,7 @@ class Order(models.Model): object_id = models.PositiveIntegerField(null=True) service = models.ForeignKey(settings.ORDERS_SERVICE_MODEL, verbose_name=_("service"), 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) billed_on = models.DateField(_("billed"), null=True, blank=True) billed_until = models.DateField(_("billed until"), null=True, blank=True) diff --git a/orchestra/contrib/payments/migrations/0002_auto_20150709_1018.py b/orchestra/contrib/payments/migrations/0002_auto_20150709_1018.py new file mode 100644 index 00000000..2cf39a6a --- /dev/null +++ b/orchestra/contrib/payments/migrations/0002_auto_20150709_1018.py @@ -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=''), + ), + ] diff --git a/orchestra/contrib/payments/models.py b/orchestra/contrib/payments/models.py index 9a89303d..15e09151 100644 --- a/orchestra/contrib/payments/models.py +++ b/orchestra/contrib/payments/models.py @@ -166,7 +166,7 @@ class TransactionProcess(models.Model): data = JSONField(_("data"), blank=True) file = PrivateFileField(_("file"), blank=True) 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) class Meta: diff --git a/orchestra/templates/orchestra/admin/change_form.html b/orchestra/templates/orchestra/admin/change_form.html index 778d5a73..05b47f98 100644 --- a/orchestra/templates/orchestra/admin/change_form.html +++ b/orchestra/templates/orchestra/admin/change_form.html @@ -4,7 +4,7 @@ {% block object-tools-items %} {% for item in object_tools_items %} -
  • {{ item.verbose_name }}
  • +
  • {{ item.verbose_name }}
  • {% endfor %} {{ block.super }} {% endblock %}