Admin interface improvements
This commit is contained in:
parent
ccbda512bf
commit
e57226b769
|
@ -10,7 +10,7 @@ from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import ugettext_lazy as _
|
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.time import timesince, timeuntil
|
from orchestra.utils.humanize import naturaldate
|
||||||
|
|
||||||
|
|
||||||
def get_modeladmin(model, import_module=True):
|
def get_modeladmin(model, import_module=True):
|
||||||
|
@ -78,7 +78,7 @@ def admin_link(*args, **kwargs):
|
||||||
|
|
||||||
def display_link(self, instance):
|
def display_link(self, instance):
|
||||||
obj = getattr(instance, field, instance)
|
obj = getattr(instance, field, instance)
|
||||||
if not getattr(obj, 'pk', False):
|
if not getattr(obj, 'pk', None):
|
||||||
return '---'
|
return '---'
|
||||||
opts = obj._meta
|
opts = obj._meta
|
||||||
view_name = 'admin:%s_%s_change' % (opts.app_label, opts.model_name)
|
view_name = 'admin:%s_%s_change' % (opts.app_label, opts.model_name)
|
||||||
|
@ -95,7 +95,7 @@ def admin_link(*args, **kwargs):
|
||||||
|
|
||||||
def colored(field_name, colours, description='', verbose=False, bold=True):
|
def colored(field_name, colours, description='', verbose=False, bold=True):
|
||||||
""" returns a method that will render obj with colored html """
|
""" returns a method that will render obj with colored html """
|
||||||
def colored_field(obj, field=field_name, colors=colours, verbose=verbose):
|
def colored_field(modeladmin, obj, field=field_name, colors=colours, verbose=verbose):
|
||||||
value = escape(get_field_value(obj, field))
|
value = escape(get_field_value(obj, field))
|
||||||
color = colors.get(value, "black")
|
color = colors.get(value, "black")
|
||||||
if verbose:
|
if verbose:
|
||||||
|
@ -113,22 +113,40 @@ def colored(field_name, colours, description='', verbose=False, bold=True):
|
||||||
return colored_field
|
return colored_field
|
||||||
|
|
||||||
|
|
||||||
def display_timesince(date, double=False):
|
#def display_timesince(date, double=False):
|
||||||
"""
|
# """
|
||||||
Format date for messages create_on: show a relative time
|
# Format date for messages create_on: show a relative time
|
||||||
with contextual helper to show fulltime format.
|
# with contextual helper to show fulltime format.
|
||||||
"""
|
# """
|
||||||
if not date:
|
# if not date:
|
||||||
return 'Never'
|
# return 'Never'
|
||||||
date_rel = timesince(date)
|
# date_rel = timesince(date)
|
||||||
if not double:
|
# if not double:
|
||||||
date_rel = date_rel.split(',')[0]
|
# date_rel = date_rel.split(',')[0]
|
||||||
date_rel += ' ago'
|
# date_rel += ' ago'
|
||||||
date_abs = date.strftime("%Y-%m-%d %H:%M:%S %Z")
|
# date_abs = date.strftime("%Y-%m-%d %H:%M:%S %Z")
|
||||||
return mark_safe("<span title='%s'>%s</span>" % (date_abs, date_rel))
|
# return mark_safe("<span title='%s'>%s</span>" % (date_abs, date_rel))
|
||||||
|
|
||||||
|
|
||||||
def display_timeuntil(date):
|
def admin_date(field, **kwargs):
|
||||||
date_rel = timeuntil(date) + ' left'
|
""" utility function for creating admin dates """
|
||||||
date_abs = date.strftime("%Y-%m-%d %H:%M:%S %Z")
|
default = kwargs.pop('default', '')
|
||||||
return mark_safe("<span title='%s'>%s</span>" % (date_abs, date_rel))
|
order = kwargs.pop('order', field)
|
||||||
|
|
||||||
|
def display_date(self, instance):
|
||||||
|
value = get_field_value(instance, field)
|
||||||
|
if not value:
|
||||||
|
return default
|
||||||
|
return '<div title="{0}">{1}</div>'.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))
|
||||||
|
|
|
@ -12,8 +12,7 @@ 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,
|
from orchestra.admin.utils import admin_link, colored, wrap_admin_view, admin_date
|
||||||
display_timesince)
|
|
||||||
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,
|
||||||
|
@ -110,6 +109,8 @@ class TicketInline(admin.TabularInline):
|
||||||
|
|
||||||
creator_link = admin_link('creator')
|
creator_link = admin_link('creator')
|
||||||
owner_link = admin_link('owner')
|
owner_link = admin_link('owner')
|
||||||
|
created = admin_link('created_on')
|
||||||
|
last_modified = admin_link('last_modified_on')
|
||||||
|
|
||||||
def ticket_id(self, instance):
|
def ticket_id(self, instance):
|
||||||
return '<b>%s</b>' % link()(self, instance)
|
return '<b>%s</b>' % link()(self, instance)
|
||||||
|
@ -123,12 +124,6 @@ class TicketInline(admin.TabularInline):
|
||||||
def colored_priority(self, instance):
|
def colored_priority(self, instance):
|
||||||
return colored('priority', PRIORITY_COLORS, bold=False)(instance)
|
return colored('priority', PRIORITY_COLORS, bold=False)(instance)
|
||||||
colored_priority.short_description = _("Priority")
|
colored_priority.short_description = _("Priority")
|
||||||
|
|
||||||
def created(self, instance):
|
|
||||||
return display_timesince(instance.created_on)
|
|
||||||
|
|
||||||
def last_modified(self, instance):
|
|
||||||
return display_timesince(instance.last_modified_on)
|
|
||||||
|
|
||||||
|
|
||||||
class TicketAdmin(ChangeListDefaultFilter, ExtendedModelAdmin): #TODO ChangeViewActions,
|
class TicketAdmin(ChangeListDefaultFilter, ExtendedModelAdmin): #TODO ChangeViewActions,
|
||||||
|
@ -327,7 +322,7 @@ class QueueAdmin(admin.ModelAdmin):
|
||||||
}
|
}
|
||||||
|
|
||||||
def num_tickets(self, queue):
|
def num_tickets(self, queue):
|
||||||
num = queue.tickets.count()
|
num = queue.tickets__count
|
||||||
url = reverse('admin:issues_ticket_changelist')
|
url = reverse('admin:issues_ticket_changelist')
|
||||||
url += '?my_tickets=False&queue=%i' % queue.pk
|
url += '?my_tickets=False&queue=%i' % queue.pk
|
||||||
return '<a href="%s">%d</a>' % (url, num)
|
return '<a href="%s">%d</a>' % (url, num)
|
||||||
|
|
|
@ -14,7 +14,7 @@ class MiscServiceAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
def num_instances(self, misc):
|
def num_instances(self, misc):
|
||||||
""" return num slivers as a link to slivers changelist view """
|
""" return num slivers as a link to slivers changelist view """
|
||||||
num = misc.instances.count()
|
num = misc.instances__count
|
||||||
url = reverse('admin:miscellaneous_miscellaneous_changelist')
|
url = reverse('admin:miscellaneous_miscellaneous_changelist')
|
||||||
url += '?service={}'.format(misc.pk)
|
url += '?service={}'.format(misc.pk)
|
||||||
return mark_safe('<a href="{0}">{1}</a>'.format(url, num))
|
return mark_safe('<a href="{0}">{1}</a>'.format(url, num))
|
||||||
|
|
|
@ -2,10 +2,9 @@ from django.contrib import admin
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from djcelery.humanize import naturaldate
|
|
||||||
|
|
||||||
from orchestra.admin.html import monospace_format
|
from orchestra.admin.html import monospace_format
|
||||||
from orchestra.admin.utils import admin_link
|
from orchestra.admin.utils import admin_link, admin_date, colored
|
||||||
|
|
||||||
from .models import Server, Route, BackendLog, BackendOperation
|
from .models import Server, Route, BackendLog, BackendOperation
|
||||||
|
|
||||||
|
@ -89,13 +88,9 @@ class BackendLogAdmin(admin.ModelAdmin):
|
||||||
readonly_fields = fields
|
readonly_fields = fields
|
||||||
|
|
||||||
server_link = admin_link('server')
|
server_link = admin_link('server')
|
||||||
|
display_last_update = admin_date('last_update')
|
||||||
def display_state(self, log):
|
display_created = admin_date('created')
|
||||||
color = STATE_COLORS.get(log.state, 'grey')
|
display_state = colored('state', STATE_COLORS)
|
||||||
return '<span style="color: %s;">%s</span>' % (color, log.state)
|
|
||||||
display_state.short_description = _("state")
|
|
||||||
display_state.allow_tags = True
|
|
||||||
display_state.admin_order_field = 'state'
|
|
||||||
|
|
||||||
def mono_script(self, log):
|
def mono_script(self, log):
|
||||||
return monospace_format(escape(log.script))
|
return monospace_format(escape(log.script))
|
||||||
|
@ -113,20 +108,6 @@ class BackendLogAdmin(admin.ModelAdmin):
|
||||||
return monospace_format(escape(log.traceback))
|
return monospace_format(escape(log.traceback))
|
||||||
mono_traceback.short_description = _("traceback")
|
mono_traceback.short_description = _("traceback")
|
||||||
|
|
||||||
def display_last_update(self, log):
|
|
||||||
return '<div title="{0}">{1}</div>'.format(
|
|
||||||
escape(str(log.last_update)), escape(naturaldate(log.last_update)),
|
|
||||||
)
|
|
||||||
display_last_update.short_description = _("last update")
|
|
||||||
display_last_update.allow_tags = True
|
|
||||||
|
|
||||||
def display_created(self, log):
|
|
||||||
return '<div title="{0}">{1}</div>'.format(
|
|
||||||
escape(str(log.created)), escape(naturaldate(log.created)),
|
|
||||||
)
|
|
||||||
display_created.short_description = _("created")
|
|
||||||
display_created.allow_tags = True
|
|
||||||
|
|
||||||
def get_queryset(self, request):
|
def get_queryset(self, request):
|
||||||
""" Order by structured name and imporve performance """
|
""" Order by structured name and imporve performance """
|
||||||
qs = super(BackendLogAdmin, self).get_queryset(request)
|
qs = super(BackendLogAdmin, self).get_queryset(request)
|
||||||
|
|
|
@ -2,11 +2,12 @@ from django import forms
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.utils import timezone
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.admin import ChangeListDefaultFilter
|
from orchestra.admin import ChangeListDefaultFilter
|
||||||
from orchestra.admin.filters import UsedContentTypeFilter
|
from orchestra.admin.filters import UsedContentTypeFilter
|
||||||
from orchestra.admin.utils import admin_link
|
from orchestra.admin.utils import admin_link, admin_date
|
||||||
from orchestra.apps.accounts.admin import AccountAdminMixin
|
from orchestra.apps.accounts.admin import AccountAdminMixin
|
||||||
from orchestra.core import services
|
from orchestra.core import services
|
||||||
|
|
||||||
|
@ -49,9 +50,9 @@ class ServiceAdmin(admin.ModelAdmin):
|
||||||
return super(ServiceAdmin, self).formfield_for_dbfield(db_field, **kwargs)
|
return super(ServiceAdmin, self).formfield_for_dbfield(db_field, **kwargs)
|
||||||
|
|
||||||
def num_orders(self, service):
|
def num_orders(self, service):
|
||||||
num = service.orders.count()
|
num = service.orders__count
|
||||||
url = reverse('admin:orders_order_changelist')
|
url = reverse('admin:orders_order_changelist')
|
||||||
url += '?service=%i' % service.pk
|
url += '?service=%i&is_active=True' % service.pk
|
||||||
return '<a href="%s">%d</a>' % (url, num)
|
return '<a href="%s">%d</a>' % (url, num)
|
||||||
num_orders.short_description = _("Orders")
|
num_orders.short_description = _("Orders")
|
||||||
num_orders.admin_order_field = 'orders__count'
|
num_orders.admin_order_field = 'orders__count'
|
||||||
|
@ -59,20 +60,36 @@ class ServiceAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
def get_queryset(self, request):
|
def get_queryset(self, request):
|
||||||
qs = super(ServiceAdmin, self).get_queryset(request)
|
qs = super(ServiceAdmin, self).get_queryset(request)
|
||||||
qs = qs.annotate(models.Count('orders'))
|
# Count active orders
|
||||||
|
qs = qs.extra(select={
|
||||||
|
'orders__count': (
|
||||||
|
"SELECT COUNT(*) "
|
||||||
|
"FROM orders_order "
|
||||||
|
"WHERE orders_order.service_id = orders_service.id AND ("
|
||||||
|
" orders_order.cancelled_on IS NULL OR"
|
||||||
|
" orders_order.cancelled_on > '%s' "
|
||||||
|
")" % timezone.now()
|
||||||
|
)
|
||||||
|
})
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
|
||||||
class OrderAdmin(AccountAdminMixin, ChangeListDefaultFilter, admin.ModelAdmin):
|
class OrderAdmin(AccountAdminMixin, ChangeListDefaultFilter, admin.ModelAdmin):
|
||||||
list_display = (
|
list_display = (
|
||||||
'id', 'service', 'account_link', 'content_object_link', 'cancelled_on'
|
'id', 'service', 'account_link', 'content_object_link',
|
||||||
|
'display_registered_on', 'display_cancelled_on'
|
||||||
)
|
)
|
||||||
|
list_display_link = ('id', 'service')
|
||||||
list_filter = (ActiveOrderListFilter, 'service',)
|
list_filter = (ActiveOrderListFilter, 'service',)
|
||||||
|
date_hierarchy = 'registered_on'
|
||||||
default_changelist_filters = (
|
default_changelist_filters = (
|
||||||
('is_active', 'True'),
|
('is_active', 'True'),
|
||||||
)
|
)
|
||||||
|
|
||||||
content_object_link = admin_link('content_object')
|
content_object_link = admin_link('content_object')
|
||||||
|
display_registered_on = admin_date('registered_on')
|
||||||
|
display_cancelled_on = admin_date('cancelled_on')
|
||||||
|
|
||||||
|
|
||||||
class MetricStorageAdmin(admin.ModelAdmin):
|
class MetricStorageAdmin(admin.ModelAdmin):
|
||||||
list_display = ('order', 'value', 'created_on', 'updated_on')
|
list_display = ('order', 'value', 'created_on', 'updated_on')
|
||||||
|
|
|
@ -10,7 +10,7 @@ from django.utils import timezone
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.core import caches
|
from orchestra.core import caches, services
|
||||||
from orchestra.utils.apps import autodiscover
|
from orchestra.utils.apps import autodiscover
|
||||||
|
|
||||||
from . import settings
|
from . import settings
|
||||||
|
@ -195,14 +195,22 @@ class Service(models.Model):
|
||||||
except IndexError:
|
except IndexError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
for attr in ['matches', 'get_metric']:
|
attr = None
|
||||||
try:
|
try:
|
||||||
getattr(self.handler, attr)(obj)
|
bool(self.handler.matches(obj))
|
||||||
except Exception as exception:
|
except Exception as exception:
|
||||||
name = type(exception).__name__
|
attr = "Matches"
|
||||||
message = exception.message
|
try:
|
||||||
msg = "{0} {1}: {2}".format(attr, name, message)
|
metric = self.handler.get_metric(obj)
|
||||||
raise ValidationError(msg)
|
if metric is not None:
|
||||||
|
int(metric)
|
||||||
|
except Exception as exception:
|
||||||
|
attr = "Get metric"
|
||||||
|
if attr is not None:
|
||||||
|
name = type(exception).__name__
|
||||||
|
message = exception.message
|
||||||
|
msg = "{0} {1}: {2}".format(attr, name, message)
|
||||||
|
raise ValidationError(msg)
|
||||||
|
|
||||||
|
|
||||||
class OrderQuerySet(models.QuerySet):
|
class OrderQuerySet(models.QuerySet):
|
||||||
|
@ -222,9 +230,6 @@ class OrderQuerySet(models.QuerySet):
|
||||||
|
|
||||||
|
|
||||||
class Order(models.Model):
|
class Order(models.Model):
|
||||||
SAVE = 'SAVE'
|
|
||||||
DELETE = 'DELETE'
|
|
||||||
|
|
||||||
account = models.ForeignKey('accounts.Account', verbose_name=_("account"),
|
account = models.ForeignKey('accounts.Account', verbose_name=_("account"),
|
||||||
related_name='orders')
|
related_name='orders')
|
||||||
content_type = models.ForeignKey(ContentType)
|
content_type = models.ForeignKey(ContentType)
|
||||||
|
@ -303,14 +308,14 @@ class MetricStorage(models.Model):
|
||||||
|
|
||||||
@receiver(pre_delete, dispatch_uid="orders.cancel_orders")
|
@receiver(pre_delete, dispatch_uid="orders.cancel_orders")
|
||||||
def cancel_orders(sender, **kwargs):
|
def cancel_orders(sender, **kwargs):
|
||||||
if sender not in [MetricStorage, LogEntry, Order, Service]:
|
if sender in services:
|
||||||
instance = kwargs['instance']
|
instance = kwargs['instance']
|
||||||
for order in Order.objects.by_object(instance).active():
|
for order in Order.objects.by_object(instance).active():
|
||||||
order.cancel()
|
order.cancel()
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, dispatch_uid="orders.update_orders")
|
@receiver(post_save, dispatch_uid="orders.update_orders")
|
||||||
@receiver(post_delete, dispatch_uid="orders.update_orders")
|
@receiver(post_delete, dispatch_uid="orders.update_orders_post_delete")
|
||||||
def update_orders(sender, **kwargs):
|
def update_orders(sender, **kwargs):
|
||||||
if sender not in [MetricStorage, LogEntry, Order, Service]:
|
if sender not in [MetricStorage, LogEntry, Order, Service]:
|
||||||
instance = kwargs['instance']
|
instance = kwargs['instance']
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
from django.contrib import admin, messages
|
from django.contrib import admin, messages
|
||||||
from django.contrib.contenttypes import generic
|
from django.contrib.contenttypes import generic
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.html import escape
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from djcelery.humanize import naturaldate
|
|
||||||
|
|
||||||
from orchestra.admin import ExtendedModelAdmin
|
from orchestra.admin import ExtendedModelAdmin
|
||||||
from orchestra.admin.filters import UsedContentTypeFilter
|
from orchestra.admin.filters import UsedContentTypeFilter
|
||||||
from orchestra.admin.utils import insertattr, get_modeladmin, admin_link
|
from orchestra.admin.utils import insertattr, get_modeladmin, admin_link, admin_date
|
||||||
from orchestra.core import services
|
from orchestra.core import services
|
||||||
from orchestra.utils import running_syncdb
|
from orchestra.utils import running_syncdb
|
||||||
|
|
||||||
|
@ -91,13 +89,17 @@ admin.site.register(MonitorData, MonitorDataAdmin)
|
||||||
|
|
||||||
def resource_inline_factory(resources):
|
def resource_inline_factory(resources):
|
||||||
class ResourceInlineFormSet(generic.BaseGenericInlineFormSet):
|
class ResourceInlineFormSet(generic.BaseGenericInlineFormSet):
|
||||||
def total_form_count(self):
|
def total_form_count(self, resources=resources):
|
||||||
return len(resources)
|
return len(resources)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def forms(self):
|
def forms(self, resources=resources):
|
||||||
forms = []
|
forms = []
|
||||||
for i, resource in enumerate(resources):
|
resources_copy = list(resources)
|
||||||
|
for i, data in enumerate(self.queryset):
|
||||||
|
forms.append(self._construct_form(i, resource=data.resource))
|
||||||
|
resources_copy.remove(data.resource)
|
||||||
|
for i, resource in enumerate(resources_copy, len(self.queryset)):
|
||||||
forms.append(self._construct_form(i, resource=resource))
|
forms.append(self._construct_form(i, resource=resource))
|
||||||
return forms
|
return forms
|
||||||
|
|
||||||
|
@ -117,16 +119,11 @@ def resource_inline_factory(resources):
|
||||||
'all': ('orchestra/css/hide-inline-id.css',)
|
'all': ('orchestra/css/hide-inline-id.css',)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
display_last_update = admin_date('last_update', default=_("Never"))
|
||||||
|
|
||||||
def has_add_permission(self, *args, **kwargs):
|
def has_add_permission(self, *args, **kwargs):
|
||||||
""" Hidde add another """
|
""" Hidde add another """
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def display_last_update(self, data):
|
|
||||||
return '<div title="{0}">{1}</div>'.format(
|
|
||||||
escape(str(data.last_update)), escape(naturaldate(data.last_update)),
|
|
||||||
)
|
|
||||||
display_last_update.short_description = _("last update")
|
|
||||||
display_last_update.allow_tags = True
|
|
||||||
|
|
||||||
return ResourceInline
|
return ResourceInline
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
from djcelery.models import PeriodicTask, CrontabSchedule
|
from djcelery.models import PeriodicTask, CrontabSchedule
|
||||||
|
|
||||||
from orchestra.models.fields import MultiSelectField
|
from orchestra.models.fields import MultiSelectField
|
||||||
|
from orchestra.utils.functional import cached
|
||||||
|
|
||||||
from . import helpers
|
from . import helpers
|
||||||
from .backends import ServiceMonitor
|
from .backends import ServiceMonitor
|
||||||
|
@ -164,15 +165,17 @@ def create_resource_relation():
|
||||||
class ResourceHandler(object):
|
class ResourceHandler(object):
|
||||||
""" account.resources.web """
|
""" account.resources.web """
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
""" get or create ResourceData """
|
""" get or build ResourceData """
|
||||||
try:
|
try:
|
||||||
return self.obj.resource_set.get(resource__name=attr)
|
data = self.obj.resource_set.get(resource__name=attr)
|
||||||
except ResourceData.DoesNotExist:
|
except ResourceData.DoesNotExist:
|
||||||
model = self.obj._meta.model_name
|
model = self.obj._meta.model_name
|
||||||
resource = Resource.objects.get(content_type__model=model,
|
resource = Resource.objects.get(content_type__model=model,
|
||||||
name=attr, is_active=True)
|
name=attr, is_active=True)
|
||||||
return ResourceData.objects.create(content_object=self.obj,
|
data = ResourceData(content_object=self.obj, resource=resource)
|
||||||
resource=resource)
|
print data.resource_id, data.content_type_id, data.object_id
|
||||||
|
setattr(self, attr, data)
|
||||||
|
return data
|
||||||
|
|
||||||
def __get__(self, obj, cls):
|
def __get__(self, obj, cls):
|
||||||
self.obj = obj
|
self.obj = obj
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.utils.translation import ungettext, ugettext as _
|
||||||
|
|
||||||
|
|
||||||
|
def pluralize_year(n):
|
||||||
|
return ungettext(_('{num:.1f} year ago'), _('{num:.1f} years ago'), n)
|
||||||
|
|
||||||
|
|
||||||
|
def pluralize_month(n):
|
||||||
|
return ungettext(_('{num:.1f} month ago'), _('{num:.1f} months ago'), n)
|
||||||
|
|
||||||
|
|
||||||
|
def pluralize_week(n):
|
||||||
|
return ungettext(_('{num:.1f} week ago'), _('{num:.1f} weeks ago'), n)
|
||||||
|
|
||||||
|
|
||||||
|
def pluralize_day(n):
|
||||||
|
return ungettext(_('{num:.1f} day ago'), _('{num:.1f} days ago'), n)
|
||||||
|
|
||||||
|
|
||||||
|
OLDER_CHUNKS = (
|
||||||
|
(365.0, pluralize_year),
|
||||||
|
(30.0, pluralize_month),
|
||||||
|
(7.0, pluralize_week),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _un(singular__plural, n=None):
|
||||||
|
singular, plural = singular__plural
|
||||||
|
return ungettext(singular, plural, n)
|
||||||
|
|
||||||
|
|
||||||
|
def naturaldate(date, include_seconds=False):
|
||||||
|
"""Convert datetime into a human natural date string."""
|
||||||
|
if not date:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
right_now = timezone.now()
|
||||||
|
today = datetime(right_now.year, right_now.month,
|
||||||
|
right_now.day, tzinfo=right_now.tzinfo)
|
||||||
|
delta = right_now - date
|
||||||
|
delta_midnight = today - date
|
||||||
|
|
||||||
|
days = delta.days
|
||||||
|
hours = int(round(delta.seconds / 3600, 0))
|
||||||
|
minutes = delta.seconds / 60
|
||||||
|
seconds = delta.seconds
|
||||||
|
|
||||||
|
if days < 0:
|
||||||
|
return _('just now')
|
||||||
|
|
||||||
|
if days == 0:
|
||||||
|
if hours == 0:
|
||||||
|
if minutes > 0:
|
||||||
|
minutes += float(seconds)/60
|
||||||
|
return ungettext(
|
||||||
|
_('{minutes:.1f} minute ago'),
|
||||||
|
_('{minutes:.1f} minutes ago'), minutes
|
||||||
|
).format(minutes=minutes)
|
||||||
|
else:
|
||||||
|
if include_seconds and seconds:
|
||||||
|
return ungettext(
|
||||||
|
_('{seconds} second ago'),
|
||||||
|
_('{seconds} seconds ago'), seconds
|
||||||
|
).format(seconds=seconds)
|
||||||
|
return _('just now')
|
||||||
|
else:
|
||||||
|
hours += float(minutes)/60
|
||||||
|
return ungettext(
|
||||||
|
_('{hours:.1f} hour ago'), _('{hours:.1f} hours ago'), hours
|
||||||
|
).format(hours=hours)
|
||||||
|
|
||||||
|
if delta_midnight.days == 0:
|
||||||
|
return _('yesterday at {time}').format(time=date.strftime('%H:%M'))
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
for chunk, pluralizefun in OLDER_CHUNKS:
|
||||||
|
if days < 7.0:
|
||||||
|
count = days + float(hours)/24
|
||||||
|
fmt = pluralize_day(count)
|
||||||
|
return fmt.format(num=count)
|
||||||
|
if days >= chunk:
|
||||||
|
count = (delta_midnight.days + 1) / chunk
|
||||||
|
fmt = pluralizefun(count)
|
||||||
|
return fmt.format(num=count)
|
Loading…
Reference in New Issue