Refactor admin_fields

This commit is contained in:
Marc 2014-07-24 09:53:34 +00:00
parent 8c13e75d5d
commit 06db4cd346
14 changed files with 119 additions and 140 deletions

View File

@ -65,3 +65,5 @@ Remember that, as always with QuerySets, any subsequent chained methods which im
* Be consistent with dates: name_on, created ? * Be consistent with dates: name_on, created ?
* backend logs with hal logo

View File

@ -1,4 +1,4 @@
from functools import wraps from functools import wraps, partial
from django.contrib import messages from django.contrib import messages
from django.contrib.admin import helpers from django.contrib.admin import helpers
@ -7,6 +7,22 @@ from django.utils.decorators import available_attrs
from django.utils.encoding import force_text 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={}, def action_with_confirmation(action_name, extra_context={},
template='admin/orchestra/generic_confirmation.html'): 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: If custom template is provided the form must contain:
<input type="hidden" name="post" value="generic_confirmation" /> <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):
@wraps(func, assigned=available_attrs(func)) @wraps(func, assigned=available_attrs(func))
def inner(modeladmin, request, queryset): def inner(modeladmin, request, queryset):
@ -23,16 +38,16 @@ def action_with_confirmation(action_name, extra_context={},
stay = func(modeladmin, request, queryset) stay = func(modeladmin, request, queryset)
if not stay: if not stay:
return return
opts = modeladmin.model._meta opts = modeladmin.model._meta
app_label = opts.app_label app_label = opts.app_label
action_value = func.__name__ action_value = func.__name__
if len(queryset) == 1: if len(queryset) == 1:
objects_name = force_text(opts.verbose_name) objects_name = force_text(opts.verbose_name)
else: else:
objects_name = force_text(opts.verbose_name_plural) objects_name = force_text(opts.verbose_name_plural)
context = { context = {
"title": "Are you sure?", "title": "Are you sure?",
"content_message": "Are you sure you want to %s the selected %s?" % "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, "app_label": app_label,
'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME, 'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME,
} }
context.update(extra_context) context.update(extra_context)
# Display the confirmation page # Display the confirmation page
return TemplateResponse(request, template, return TemplateResponse(request, template,
context, current_app=modeladmin.admin_site.name) context, current_app=modeladmin.admin_site.name)
return inner return inner
return decorator return decorator

View File

@ -32,7 +32,8 @@ def get_services():
for model, options in services.get().iteritems(): for model, options in services.get().iteritems():
if options.get('menu', True): if options.get('menu', True):
opts = model._meta 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')) name = capfirst(options.get('verbose_name_plural'))
result.append(items.MenuItem(name, url)) result.append(items.MenuItem(name, url))
return sorted(result, key=lambda i: i.title) return sorted(result, key=lambda i: i.title)
@ -40,24 +41,27 @@ def get_services():
def get_account_items(): def get_account_items():
childrens = [ childrens = [
items.MenuItem(_("Accounts"), reverse('admin:accounts_account_changelist')) items.MenuItem(_("Accounts"),
reverse('admin:accounts_account_changelist'))
] ]
if isinstalled('orchestra.apps.contacts'): if isinstalled('orchestra.apps.contacts'):
url = reverse('admin:contacts_contact_changelist') url = reverse('admin:contacts_contact_changelist')
childrens.append(items.MenuItem(_("Contacts"), url)) childrens.append(items.MenuItem(_("Contacts"), url))
if isinstalled('orchestra.apps.users'): if isinstalled('orchestra.apps.users'):
url = reverse('admin:users_user_changelist') url = reverse('admin:users_user_changelist')
users = [items.MenuItem(_("Users"), url)] childrens.append(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))
if isinstalled('orchestra.apps.prices'): if isinstalled('orchestra.apps.prices'):
url = reverse('admin:prices_pack_changelist') url = reverse('admin:prices_pack_changelist')
childrens.append(items.MenuItem(_("Packs"), url)) childrens.append(items.MenuItem(_("Packs"), url))
if isinstalled('orchestra.apps.orders'): if isinstalled('orchestra.apps.orders'):
url = reverse('admin:orders_order_changelist') url = reverse('admin:orders_order_changelist')
childrens.append(items.MenuItem(_("Orders"), url)) 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'): if isinstalled('orchestra.apps.issues'):
url = reverse('admin:issues_ticket_changelist') url = reverse('admin:issues_ticket_changelist')
childrens.append(items.MenuItem(_("Tickets"), url)) childrens.append(items.MenuItem(_("Tickets"), url))
@ -92,7 +96,7 @@ def get_administration_items():
childrens.append(items.MenuItem(_("Miscellaneous"), url)) childrens.append(items.MenuItem(_("Miscellaneous"), url))
if isinstalled('orchestra.apps.issues'): if isinstalled('orchestra.apps.issues'):
url = reverse('admin:issues_queue_changelist') url = reverse('admin:issues_queue_changelist')
childrens.append(items.MenuItem(_("Issue queues"), url)) childrens.append(items.MenuItem(_("Ticket queues"), url))
if isinstalled('djcelery'): if isinstalled('djcelery'):
task = reverse('admin:djcelery_taskstate_changelist') task = reverse('admin:djcelery_taskstate_changelist')
periodic = reverse('admin:djcelery_periodictask_changelist') periodic = reverse('admin:djcelery_periodictask_changelist')

View File

@ -12,6 +12,8 @@ from django.utils.translation import ugettext_lazy as _
from orchestra.models.utils import get_field_value from orchestra.models.utils import get_field_value
from orchestra.utils.humanize import naturaldate from orchestra.utils.humanize import naturaldate
from .decorators import admin_field
def get_modeladmin(model, import_module=True): def get_modeladmin(model, import_module=True):
""" returns the modeladmin registred for model """ """ returns the modeladmin registred for model """
@ -44,7 +46,9 @@ def insertattr(model, name, value, weight=0):
weights = {} weights = {}
if hasattr(modeladmin, 'weights') and name in modeladmin.weights: if hasattr(modeladmin, 'weights') and name in modeladmin.weights:
weights = modeladmin.weights.get(name) 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].append((value, weight))
inserted_attrs[name].sort(key=lambda a: a[1]) 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() request.META['QUERY_STRING'] = request.GET.urlencode()
@admin_field
def admin_link(*args, **kwargs): def admin_link(*args, **kwargs):
""" utility function for creating admin links """ instance = args[-1]
field = args[0] if args else '' obj = get_field_value(instance, kwargs['field'])
order = kwargs.pop('order', field) if not getattr(obj, 'pk', None):
popup = kwargs.pop('popup', False) return '---'
opts = obj._meta
def display_link(*args): view_name = 'admin:%s_%s_change' % (opts.app_label, opts.model_name)
instance = args[-1] url = reverse(view_name, args=(obj.pk,))
obj = getattr(instance, field, instance) extra = ''
if not getattr(obj, 'pk', None): if kwargs['popup']:
return '---' extra = 'onclick="return showAddAnotherPopup(this);"'
opts = obj._meta return '<a href="%s" %s>%s</a>' % (url, extra, obj)
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 '<a href="%s" %s>%s</a>' % (url, extra, obj)
display_link.allow_tags = True
display_link.short_description = _(field.replace('_', ' '))
display_link.admin_order_field = order
return display_link
def colored(field_name, colours, description='', verbose=False, bold=True): @admin_field
""" returns a method that will render obj with colored html """ def admin_colored(*args, **kwargs):
def colored_field(obj, field=field_name, colors=colours, verbose=verbose): instance = args[-1]
value = escape(get_field_value(obj, field)) field = kwargs['field']
color = colors.get(value, "black") value = escape(get_field_value(instance, field))
if verbose: color = kwargs.get('colors', {}).get(value, 'black')
# Get the human-readable value of a choice field value = getattr(instance, 'get_%s_display' % field)().upper()
value = getattr(obj, 'get_%s_display' % field)() colored_value = '<span style="color: %s;">%s</span>' % (color, value)
colored_value = '<span style="color: %s;">%s</span>' % (color, value) if kwargs.get('bold', True):
if bold: colored_value = '<b>%s</b>' % colored_value
colored_value = '<b>%s</b>' % colored_value return mark_safe(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
#def display_timesince(date, double=False): @admin_field
# """ def admin_date(*args, **kwargs):
# Format date for messages create_on: show a relative time instance = args[-1]
# with contextual helper to show fulltime format. value = get_field_value(instance, kwargs['field'])
# """ if not value:
# if not date: return kwargs.get('default', '')
# return 'Never' return '<span title="{0}">{1}</span>'.format(
# date_rel = timesince(date) escape(str(value)), escape(naturaldate(value)),
# 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("<span title='%s'>%s</span>" % (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 '<span title="{0}">{1}</span>'.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("<span title='%s'>%s</span>" % (date_abs, date_rel))

View File

@ -12,7 +12,8 @@ from django.utils.translation import ugettext_lazy as _
from markdown import markdown from markdown import markdown
from orchestra.admin import ChangeListDefaultFilter, ExtendedModelAdmin#, ChangeViewActions 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 orchestra.apps.contacts import settings as contacts_settings
from .actions import (reject_tickets, resolve_tickets, take_tickets, close_tickets, from .actions import (reject_tickets, resolve_tickets, take_tickets, close_tickets,
@ -111,19 +112,13 @@ class TicketInline(admin.TabularInline):
owner_link = admin_link('owner') owner_link = admin_link('owner')
created = admin_link('created_on') created = admin_link('created_on')
last_modified = admin_link('last_modified_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): def ticket_id(self, instance):
return '<b>%s</b>' % admin_link()(instance) return '<b>%s</b>' % admin_link()(instance)
ticket_id.short_description = '#' ticket_id.short_description = '#'
ticket_id.allow_tags = True 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, class TicketAdmin(ChangeListDefaultFilter, ExtendedModelAdmin): #TODO ChangeViewActions,
@ -198,6 +193,8 @@ class TicketAdmin(ChangeListDefaultFilter, ExtendedModelAdmin): #TODO ChangeView
display_queue = admin_link('queue') display_queue = admin_link('queue')
display_owner = admin_link('owner') display_owner = admin_link('owner')
last_modified = admin_date('last_modified_on') 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): def display_summary(self, ticket):
context = { context = {
@ -216,18 +213,6 @@ class TicketAdmin(ChangeListDefaultFilter, ExtendedModelAdmin): #TODO ChangeView
display_summary.short_description = 'Summary' display_summary.short_description = 'Summary'
display_summary.allow_tags = True 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): def unbold_id(self, ticket):
""" Unbold id if ticket is read """ """ Unbold id if ticket is read """
if ticket.is_read_by(self.user): if ticket.is_read_by(self.user):

View File

@ -4,7 +4,7 @@ from django.utils.html import escape
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.admin.html import monospace_format 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 from .models import Server, Route, BackendLog, BackendOperation
@ -90,7 +90,7 @@ class BackendLogAdmin(admin.ModelAdmin):
server_link = admin_link('server') server_link = admin_link('server')
display_last_update = admin_date('last_update') display_last_update = admin_date('last_update')
display_created = admin_date('created') display_created = admin_date('created')
display_state = colored('state', STATE_COLORS) display_state = admin_colored('state', colors=STATE_COLORS)
def mono_script(self, log): def mono_script(self, log):
return monospace_format(escape(log.script)) return monospace_format(escape(log.script))

View File

@ -1,7 +1,30 @@
from django.contrib import admin from django.contrib import admin
from orchestra.admin.utils import admin_colored, admin_link
from .models import PaymentSource, Transaction 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(PaymentSource)
admin.site.register(Transaction) admin.site.register(Transaction, TransactionAdmin)

View File

@ -12,6 +12,7 @@ class PaymentSource(models.Model):
method = models.CharField(_("method"), max_length=32, method = models.CharField(_("method"), max_length=32,
choices=PaymentMethod.get_plugin_choices()) choices=PaymentMethod.get_plugin_choices())
data = JSONField(_("data")) data = JSONField(_("data"))
is_active = models.BooleanField(_("is active"), default=True)
class Transaction(models.Model): class Transaction(models.Model):
@ -22,14 +23,15 @@ class Transaction(models.Model):
LOCKED = 'LOCKED' LOCKED = 'LOCKED'
DISCARTED = 'DISCARTED' DISCARTED = 'DISCARTED'
STATES = ( STATES = (
(WAITTING_PROCESSING, _("Waitting for processing")), (WAITTING_PROCESSING, _("Waitting processing")),
(WAITTING_CONFIRMATION, _("Waitting for confirmation")), (WAITTING_CONFIRMATION, _("Waitting confirmation")),
(CONFIRMED, _("Confirmed")), (CONFIRMED, _("Confirmed")),
(REJECTED, _("Rejected")), (REJECTED, _("Rejected")),
(LOCKED, _("Locked")), (LOCKED, _("Locked")),
(DISCARTED, _("Discarted")), (DISCARTED, _("Discarted")),
) )
# TODO account fk?
bill = models.ForeignKey('bills.bill', verbose_name=_("bill"), bill = models.ForeignKey('bills.bill', verbose_name=_("bill"),
related_name='transactions') related_name='transactions')
method = models.CharField(_("payment method"), max_length=32, method = models.CharField(_("payment method"), max_length=32,
@ -42,3 +44,6 @@ class Transaction(models.Model):
created_on = models.DateTimeField(auto_now_add=True) created_on = models.DateTimeField(auto_now_add=True)
modified_on = models.DateTimeField(auto_now=True) modified_on = models.DateTimeField(auto_now=True)
related = models.ForeignKey('self', null=True, blank=True) related = models.ForeignKey('self', null=True, blank=True)
def __unicode__(self):
return "Transaction {}".format(self.id)

View File

@ -15,8 +15,8 @@ from .models import Resource, ResourceData, MonitorData
class ResourceAdmin(ExtendedModelAdmin): class ResourceAdmin(ExtendedModelAdmin):
list_display = ( list_display = (
'id', 'name', 'verbose_name', 'content_type', 'period', 'ondemand', 'id', 'verbose_name', 'content_type', 'period', 'ondemand',
'default_allocation', 'disable_trigger', 'crontab', 'default_allocation', 'unit', 'disable_trigger', 'crontab',
) )
list_filter = (UsedContentTypeFilter, 'period', 'ondemand', 'disable_trigger') list_filter = (UsedContentTypeFilter, 'period', 'ondemand', 'disable_trigger')
fieldsets = ( fieldsets = (

View File

@ -179,7 +179,7 @@ FLUENT_DASHBOARD_APP_ICONS = {
'miscellaneous/miscellaneous': 'applications-other.png', 'miscellaneous/miscellaneous': 'applications-other.png',
# Accounts # Accounts
'accounts/account': 'Face-monkey.png', 'accounts/account': 'Face-monkey.png',
'contacts/contact': 'contact.png', 'contacts/contact': 'contact_book.png',
'orders/order': 'basket.png', 'orders/order': 'basket.png',
'orders/service': 'price.png', 'orders/service': 'price.png',
'prices/pack': 'Pack.png', 'prices/pack': 'Pack.png',

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -15,8 +15,8 @@
width="48" width="48"
version="1.0" version="1.0"
inkscape:version="0.48.3.1 r9886" inkscape:version="0.48.3.1 r9886"
sodipodi:docname="TuxBox.svg" sodipodi:docname="Pack.svg"
inkscape:export-filename="/home/glic3rinu/orchestra/django-orchestra/orchestra/static/orchestra/icons/Pack.png" inkscape:export-filename="/home/glic3/orchestra/django-orchestra/orchestra/static/orchestra/icons/Pack.png"
inkscape:export-xdpi="90" inkscape:export-xdpi="90"
inkscape:export-ydpi="90"> inkscape:export-ydpi="90">
<svg:metadata <svg:metadata
@ -45,7 +45,7 @@
id="namedview50" id="namedview50"
showgrid="false" showgrid="false"
inkscape:zoom="4.9166667" inkscape:zoom="4.9166667"
inkscape:cx="75.979939" inkscape:cx="75.97994"
inkscape:cy="-22.905943" inkscape:cy="-22.905943"
inkscape:window-x="0" inkscape:window-x="0"
inkscape:window-y="27" inkscape:window-y="27"
@ -3780,19 +3780,6 @@
y1="7.9757" y1="7.9757"
x2="15.464" x2="15.464"
y2="45.042" /> y2="45.042" />
<svg:filter
color-interpolation-filters="sRGB"
id="filter3974-4"
height="2.5622675"
y="-0.78113377"
width="1.1420243"
x="-0.071012162"
inkscape:collect="always">
<svg:feGaussianBlur
id="feGaussianBlur3976-3"
stdDeviation="0.97641723"
inkscape:collect="always" />
</svg:filter>
<svg:linearGradient <svg:linearGradient
gradientTransform="translate(0.01860128,-4.0163098)" gradientTransform="translate(0.01860128,-4.0163098)"
gradientUnits="userSpaceOnUse" gradientUnits="userSpaceOnUse"
@ -4294,7 +4281,7 @@
id="path3273" /> id="path3273" />
<svg:g <svg:g
id="g3888" id="g3888"
transform="matrix(0.7674386,0,0,0.7674386,-7.6813433,9.7817236)"> transform="matrix(0.67441404,0,0,0.67441404,-1.7640493,12.799498)">
<svg:g <svg:g
transform="matrix(0.91489252,0,0,0.91489252,30.720532,5.6526667)" transform="matrix(0.91489252,0,0,0.91489252,30.720532,5.6526667)"
id="layer2"> id="layer2">

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB