diff --git a/TODO.md b/TODO.md index a32ec73c..39218e4e 100644 --- a/TODO.md +++ b/TODO.md @@ -65,3 +65,5 @@ Remember that, as always with QuerySets, any subsequent chained methods which im * Be consistent with dates: name_on, created ? + +* backend logs with hal logo diff --git a/orchestra/admin/decorators.py b/orchestra/admin/decorators.py index 898da675..141fccf8 100644 --- a/orchestra/admin/decorators.py +++ b/orchestra/admin/decorators.py @@ -1,4 +1,4 @@ -from functools import wraps +from functools import wraps, partial from django.contrib import messages from django.contrib.admin import helpers @@ -7,6 +7,22 @@ from django.utils.decorators import available_attrs from django.utils.encoding import force_text +def admin_field(method): + def admin_field_wrapper(*args, **kwargs): + """ utility function for creating admin links """ + kwargs['field'] = args[0] if args else '' + kwargs['order'] = kwargs.get('order', kwargs['field']) + kwargs['popup'] = kwargs.get('popup', False) + kwargs['description'] = kwargs.get('description', + kwargs['field'].split('__')[-1].replace('_', ' ').capitalize()) + admin_method = partial(method, **kwargs) + admin_method.short_description = kwargs['description'] + admin_method.allow_tags = True + admin_method.admin_order_field = kwargs['order'] + return admin_method + return admin_field_wrapper + + def action_with_confirmation(action_name, extra_context={}, template='admin/orchestra/generic_confirmation.html'): """ @@ -14,7 +30,6 @@ def action_with_confirmation(action_name, extra_context={}, If custom template is provided the form must contain: """ - def decorator(func, extra_context=extra_context, template=template): @wraps(func, assigned=available_attrs(func)) def inner(modeladmin, request, queryset): @@ -23,16 +38,16 @@ def action_with_confirmation(action_name, extra_context={}, stay = func(modeladmin, request, queryset) if not stay: return - + opts = modeladmin.model._meta app_label = opts.app_label action_value = func.__name__ - + if len(queryset) == 1: objects_name = force_text(opts.verbose_name) else: objects_name = force_text(opts.verbose_name_plural) - + context = { "title": "Are you sure?", "content_message": "Are you sure you want to %s the selected %s?" % @@ -45,12 +60,11 @@ def action_with_confirmation(action_name, extra_context={}, "app_label": app_label, 'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME, } - + context.update(extra_context) - + # Display the confirmation page return TemplateResponse(request, template, context, current_app=modeladmin.admin_site.name) return inner return decorator - diff --git a/orchestra/admin/menu.py b/orchestra/admin/menu.py index c3e7b4f1..cb9c1927 100644 --- a/orchestra/admin/menu.py +++ b/orchestra/admin/menu.py @@ -32,7 +32,8 @@ def get_services(): for model, options in services.get().iteritems(): if options.get('menu', True): opts = model._meta - url = reverse('admin:%s_%s_changelist' % (opts.app_label, opts.model_name)) + url = reverse('admin:{}_{}_changelist'.format( + opts.app_label, opts.model_name)) name = capfirst(options.get('verbose_name_plural')) result.append(items.MenuItem(name, url)) return sorted(result, key=lambda i: i.title) @@ -40,24 +41,27 @@ def get_services(): def get_account_items(): childrens = [ - items.MenuItem(_("Accounts"), reverse('admin:accounts_account_changelist')) + items.MenuItem(_("Accounts"), + reverse('admin:accounts_account_changelist')) ] if isinstalled('orchestra.apps.contacts'): url = reverse('admin:contacts_contact_changelist') childrens.append(items.MenuItem(_("Contacts"), url)) if isinstalled('orchestra.apps.users'): url = reverse('admin:users_user_changelist') - users = [items.MenuItem(_("Users"), url)] - if isinstalled('rest_framework.authtoken'): - tokens = reverse('admin:authtoken_token_changelist') - users.append(items.MenuItem(_("Tokens"), tokens)) - childrens.append(items.MenuItem(_("Users"), url, children=users)) + childrens.append(items.MenuItem(_("Users"), url)) if isinstalled('orchestra.apps.prices'): url = reverse('admin:prices_pack_changelist') childrens.append(items.MenuItem(_("Packs"), url)) if isinstalled('orchestra.apps.orders'): url = reverse('admin:orders_order_changelist') childrens.append(items.MenuItem(_("Orders"), url)) + if isinstalled('orchestra.apps.bills'): + url = reverse('admin:bills_bill_changelist') + childrens.append(items.MenuItem(_("Bills"), url)) + if isinstalled('orchestra.apps.payments'): + url = reverse('admin:payments_transaction_changelist') + childrens.append(items.MenuItem(_("Transactions"), url)) if isinstalled('orchestra.apps.issues'): url = reverse('admin:issues_ticket_changelist') childrens.append(items.MenuItem(_("Tickets"), url)) @@ -92,7 +96,7 @@ def get_administration_items(): childrens.append(items.MenuItem(_("Miscellaneous"), url)) if isinstalled('orchestra.apps.issues'): url = reverse('admin:issues_queue_changelist') - childrens.append(items.MenuItem(_("Issue queues"), url)) + childrens.append(items.MenuItem(_("Ticket queues"), url)) if isinstalled('djcelery'): task = reverse('admin:djcelery_taskstate_changelist') periodic = reverse('admin:djcelery_periodictask_changelist') diff --git a/orchestra/admin/utils.py b/orchestra/admin/utils.py index 70a0c77d..2de55a03 100644 --- a/orchestra/admin/utils.py +++ b/orchestra/admin/utils.py @@ -12,6 +12,8 @@ from django.utils.translation import ugettext_lazy as _ from orchestra.models.utils import get_field_value from orchestra.utils.humanize import naturaldate +from .decorators import admin_field + def get_modeladmin(model, import_module=True): """ returns the modeladmin registred for model """ @@ -44,7 +46,9 @@ def insertattr(model, name, value, weight=0): weights = {} if hasattr(modeladmin, 'weights') and name in modeladmin.weights: weights = modeladmin.weights.get(name) - inserted_attrs[name] = [ (attr, weights.get(attr, 0)) for attr in getattr(modeladmin, name) ] + inserted_attrs[name] = [ + (attr, weights.get(attr, 0)) for attr in getattr(modeladmin, name) + ] inserted_attrs[name].append((value, weight)) inserted_attrs[name].sort(key=lambda a: a[1]) @@ -70,85 +74,40 @@ def set_default_filter(queryarg, request, value): request.META['QUERY_STRING'] = request.GET.urlencode() +@admin_field def admin_link(*args, **kwargs): - """ utility function for creating admin links """ - field = args[0] if args else '' - order = kwargs.pop('order', field) - popup = kwargs.pop('popup', False) - - def display_link(*args): - instance = args[-1] - obj = getattr(instance, field, instance) - if not getattr(obj, 'pk', None): - return '---' - opts = obj._meta - view_name = 'admin:%s_%s_change' % (opts.app_label, opts.model_name) - url = reverse(view_name, args=(obj.pk,)) - extra = '' - if popup: - extra = 'onclick="return showAddAnotherPopup(this);"' - return '%s' % (url, extra, obj) - display_link.allow_tags = True - display_link.short_description = _(field.replace('_', ' ')) - display_link.admin_order_field = order - return display_link + instance = args[-1] + obj = get_field_value(instance, kwargs['field']) + if not getattr(obj, 'pk', None): + return '---' + opts = obj._meta + view_name = 'admin:%s_%s_change' % (opts.app_label, opts.model_name) + url = reverse(view_name, args=(obj.pk,)) + extra = '' + if kwargs['popup']: + extra = 'onclick="return showAddAnotherPopup(this);"' + return '%s' % (url, extra, obj) -def colored(field_name, colours, description='', verbose=False, bold=True): - """ returns a method that will render obj with colored html """ - def colored_field(obj, field=field_name, colors=colours, verbose=verbose): - value = escape(get_field_value(obj, field)) - color = colors.get(value, "black") - if verbose: - # Get the human-readable value of a choice field - value = getattr(obj, 'get_%s_display' % field)() - colored_value = '%s' % (color, value) - if bold: - colored_value = '%s' % colored_value - return mark_safe(colored_value) - if not description: - description = field_name.split('__').pop().replace('_', ' ').capitalize() - colored_field.short_description = description - colored_field.allow_tags = True - colored_field.admin_order_field = field_name - return colored_field +@admin_field +def admin_colored(*args, **kwargs): + instance = args[-1] + field = kwargs['field'] + value = escape(get_field_value(instance, field)) + color = kwargs.get('colors', {}).get(value, 'black') + value = getattr(instance, 'get_%s_display' % field)().upper() + colored_value = '%s' % (color, value) + if kwargs.get('bold', True): + colored_value = '%s' % colored_value + return mark_safe(colored_value) -#def display_timesince(date, double=False): -# """ -# Format date for messages create_on: show a relative time -# with contextual helper to show fulltime format. -# """ -# if not date: -# return 'Never' -# date_rel = timesince(date) -# if not double: -# date_rel = date_rel.split(',')[0] -# date_rel += ' ago' -# date_abs = date.strftime("%Y-%m-%d %H:%M:%S %Z") -# return mark_safe("%s" % (date_abs, date_rel)) - - -def admin_date(field, **kwargs): - """ utility function for creating admin dates """ - default = kwargs.pop('default', '') - order = kwargs.pop('order', field) - - def display_date(*args): - instance = args[-1] - value = get_field_value(instance, field) - if not value: - return default - return '{1}'.format( - escape(str(value)), escape(naturaldate(value)), - ) - display_date.short_description = _(field.replace('_', ' ')) - display_date.admin_order_field = order - display_date.allow_tags = True - return display_date - - -#def display_timeuntil(date): -# date_rel = timeuntil(date) + ' left' -# date_abs = date.strftime("%Y-%m-%d %H:%M:%S %Z") -# return mark_safe("%s" % (date_abs, date_rel)) +@admin_field +def admin_date(*args, **kwargs): + instance = args[-1] + value = get_field_value(instance, kwargs['field']) + if not value: + return kwargs.get('default', '') + return '{1}'.format( + escape(str(value)), escape(naturaldate(value)), + ) diff --git a/orchestra/apps/issues/admin.py b/orchestra/apps/issues/admin.py index d0c43ea8..0f4896ed 100644 --- a/orchestra/apps/issues/admin.py +++ b/orchestra/apps/issues/admin.py @@ -12,7 +12,8 @@ from django.utils.translation import ugettext_lazy as _ from markdown import markdown from orchestra.admin import ChangeListDefaultFilter, ExtendedModelAdmin#, ChangeViewActions -from orchestra.admin.utils import admin_link, colored, wrap_admin_view, admin_date +from orchestra.admin.utils import (admin_link, admin_colored, wrap_admin_view, + admin_date) from orchestra.apps.contacts import settings as contacts_settings from .actions import (reject_tickets, resolve_tickets, take_tickets, close_tickets, @@ -111,19 +112,13 @@ class TicketInline(admin.TabularInline): owner_link = admin_link('owner') created = admin_link('created_on') last_modified = admin_link('last_modified_on') + colored_state = admin_colored('state', colors=STATE_COLORS, bold=False) + colored_priority = admin_colored('priority', colors=PRIORITY_COLORS, bold=False) def ticket_id(self, instance): return '%s' % admin_link()(instance) ticket_id.short_description = '#' ticket_id.allow_tags = True - - def colored_state(self, instance): - return colored('state', STATE_COLORS, bold=False)(instance) - colored_state.short_description = _("State") - - def colored_priority(self, instance): - return colored('priority', PRIORITY_COLORS, bold=False)(instance) - colored_priority.short_description = _("Priority") class TicketAdmin(ChangeListDefaultFilter, ExtendedModelAdmin): #TODO ChangeViewActions, @@ -198,6 +193,8 @@ class TicketAdmin(ChangeListDefaultFilter, ExtendedModelAdmin): #TODO ChangeView display_queue = admin_link('queue') display_owner = admin_link('owner') last_modified = admin_date('last_modified_on') + display_state = admin_colored('state', colors=STATE_COLORS, bold=False) + display_priority = admin_colored('priority', colors=PRIORITY_COLORS, bold=False) def display_summary(self, ticket): context = { @@ -216,18 +213,6 @@ class TicketAdmin(ChangeListDefaultFilter, ExtendedModelAdmin): #TODO ChangeView display_summary.short_description = 'Summary' display_summary.allow_tags = True - def display_priority(self, ticket): - """ State colored for change_form """ - return colored('priority', PRIORITY_COLORS, bold=False, verbose=True)(ticket) - display_priority.short_description = _("Priority") - display_priority.admin_order_field = 'priority' - - def display_state(self, ticket): - """ State colored for change_form """ - return colored('state', STATE_COLORS, bold=False, verbose=True)(ticket) - display_state.short_description = _("State") - display_state.admin_order_field = 'state' - def unbold_id(self, ticket): """ Unbold id if ticket is read """ if ticket.is_read_by(self.user): diff --git a/orchestra/apps/orchestration/admin.py b/orchestra/apps/orchestration/admin.py index 6cdd0059..cff38be3 100644 --- a/orchestra/apps/orchestration/admin.py +++ b/orchestra/apps/orchestration/admin.py @@ -4,7 +4,7 @@ from django.utils.html import escape from django.utils.translation import ugettext_lazy as _ from orchestra.admin.html import monospace_format -from orchestra.admin.utils import admin_link, admin_date, colored +from orchestra.admin.utils import admin_link, admin_date, admin_colored from .models import Server, Route, BackendLog, BackendOperation @@ -90,7 +90,7 @@ class BackendLogAdmin(admin.ModelAdmin): server_link = admin_link('server') display_last_update = admin_date('last_update') display_created = admin_date('created') - display_state = colored('state', STATE_COLORS) + display_state = admin_colored('state', colors=STATE_COLORS) def mono_script(self, log): return monospace_format(escape(log.script)) diff --git a/orchestra/apps/payments/admin.py b/orchestra/apps/payments/admin.py index 4c150b72..6a7122a5 100644 --- a/orchestra/apps/payments/admin.py +++ b/orchestra/apps/payments/admin.py @@ -1,7 +1,30 @@ from django.contrib import admin +from orchestra.admin.utils import admin_colored, admin_link + from .models import PaymentSource, Transaction +STATE_COLORS = { + Transaction.WAITTING_PROCESSING: 'darkorange', + Transaction.WAITTING_CONFIRMATION: 'orange', + Transaction.CONFIRMED: 'green', + Transaction.REJECTED: 'red', + Transaction.LOCKED: 'magenta', + Transaction.DISCARTED: 'blue', +} + + +class TransactionAdmin(admin.ModelAdmin): + list_display = ( + 'id', 'bill_link', 'account_link', 'method', 'display_state', 'amount' + ) + list_filter = ('method', 'state') + + bill_link = admin_link('bill') + account_link = admin_link('bill__account') + display_state = admin_colored('state', colors=STATE_COLORS) + + admin.site.register(PaymentSource) -admin.site.register(Transaction) +admin.site.register(Transaction, TransactionAdmin) diff --git a/orchestra/apps/payments/models.py b/orchestra/apps/payments/models.py index a2f7a79c..b225f6c5 100644 --- a/orchestra/apps/payments/models.py +++ b/orchestra/apps/payments/models.py @@ -12,6 +12,7 @@ class PaymentSource(models.Model): method = models.CharField(_("method"), max_length=32, choices=PaymentMethod.get_plugin_choices()) data = JSONField(_("data")) + is_active = models.BooleanField(_("is active"), default=True) class Transaction(models.Model): @@ -22,14 +23,15 @@ class Transaction(models.Model): LOCKED = 'LOCKED' DISCARTED = 'DISCARTED' STATES = ( - (WAITTING_PROCESSING, _("Waitting for processing")), - (WAITTING_CONFIRMATION, _("Waitting for confirmation")), + (WAITTING_PROCESSING, _("Waitting processing")), + (WAITTING_CONFIRMATION, _("Waitting confirmation")), (CONFIRMED, _("Confirmed")), (REJECTED, _("Rejected")), (LOCKED, _("Locked")), (DISCARTED, _("Discarted")), ) + # TODO account fk? bill = models.ForeignKey('bills.bill', verbose_name=_("bill"), related_name='transactions') method = models.CharField(_("payment method"), max_length=32, @@ -42,3 +44,6 @@ class Transaction(models.Model): created_on = models.DateTimeField(auto_now_add=True) modified_on = models.DateTimeField(auto_now=True) related = models.ForeignKey('self', null=True, blank=True) + + def __unicode__(self): + return "Transaction {}".format(self.id) diff --git a/orchestra/apps/resources/admin.py b/orchestra/apps/resources/admin.py index eefb22e6..023dccda 100644 --- a/orchestra/apps/resources/admin.py +++ b/orchestra/apps/resources/admin.py @@ -15,8 +15,8 @@ from .models import Resource, ResourceData, MonitorData class ResourceAdmin(ExtendedModelAdmin): list_display = ( - 'id', 'name', 'verbose_name', 'content_type', 'period', 'ondemand', - 'default_allocation', 'disable_trigger', 'crontab', + 'id', 'verbose_name', 'content_type', 'period', 'ondemand', + 'default_allocation', 'unit', 'disable_trigger', 'crontab', ) list_filter = (UsedContentTypeFilter, 'period', 'ondemand', 'disable_trigger') fieldsets = ( diff --git a/orchestra/conf/base_settings.py b/orchestra/conf/base_settings.py index 213deac4..57b1f41f 100644 --- a/orchestra/conf/base_settings.py +++ b/orchestra/conf/base_settings.py @@ -179,7 +179,7 @@ FLUENT_DASHBOARD_APP_ICONS = { 'miscellaneous/miscellaneous': 'applications-other.png', # Accounts 'accounts/account': 'Face-monkey.png', - 'contacts/contact': 'contact.png', + 'contacts/contact': 'contact_book.png', 'orders/order': 'basket.png', 'orders/service': 'price.png', 'prices/pack': 'Pack.png', diff --git a/orchestra/static/orchestra/icons/Pack.png b/orchestra/static/orchestra/icons/Pack.png index 24d48ced..e7d2a8d4 100644 Binary files a/orchestra/static/orchestra/icons/Pack.png and b/orchestra/static/orchestra/icons/Pack.png differ diff --git a/orchestra/static/orchestra/icons/Pack.svg b/orchestra/static/orchestra/icons/Pack.svg index 7afdb383..5675e5c5 100644 --- a/orchestra/static/orchestra/icons/Pack.svg +++ b/orchestra/static/orchestra/icons/Pack.svg @@ -15,8 +15,8 @@ width="48" version="1.0" inkscape:version="0.48.3.1 r9886" - sodipodi:docname="TuxBox.svg" - inkscape:export-filename="/home/glic3rinu/orchestra/django-orchestra/orchestra/static/orchestra/icons/Pack.png" + sodipodi:docname="Pack.svg" + inkscape:export-filename="/home/glic3/orchestra/django-orchestra/orchestra/static/orchestra/icons/Pack.png" inkscape:export-xdpi="90" inkscape:export-ydpi="90"> - - - + transform="matrix(0.67441404,0,0,0.67441404,-1.7640493,12.799498)"> diff --git a/orchestra/static/orchestra/icons/contact_alt.png b/orchestra/static/orchestra/icons/contact_alt.png new file mode 100644 index 00000000..ff123948 Binary files /dev/null and b/orchestra/static/orchestra/icons/contact_alt.png differ diff --git a/orchestra/static/orchestra/icons/contact_book.png b/orchestra/static/orchestra/icons/contact_book.png new file mode 100644 index 00000000..7d3dce3d Binary files /dev/null and b/orchestra/static/orchestra/icons/contact_book.png differ