Admin cosmetics

This commit is contained in:
Marc 2014-07-21 15:43:36 +00:00
parent c731a73889
commit ccbda512bf
19 changed files with 194 additions and 105 deletions

View File

@ -62,15 +62,15 @@ def wrap_admin_view(modeladmin, view):
def set_default_filter(queryarg, request, value): def set_default_filter(queryarg, request, value):
""" set default filters for changelist_view """ """ set default filters for changelist_view """
if queryarg not in request.GET: if queryarg not in request.GET:
q = request.GET.copy() request_copy = request.GET.copy()
if callable(value): if callable(value):
value = value(request) value = value(request)
q[queryarg] = value request_copy[queryarg] = value
request.GET = q request.GET = request_copy
request.META['QUERY_STRING'] = request.GET.urlencode() request.META['QUERY_STRING'] = request.GET.urlencode()
def link(*args, **kwargs): def admin_link(*args, **kwargs):
""" utility function for creating admin links """ """ utility function for creating admin links """
field = args[0] if args else '' field = args[0] if args else ''
order = kwargs.pop('order', field) order = kwargs.pop('order', field)
@ -88,7 +88,7 @@ def link(*args, **kwargs):
extra = 'onclick="return showAddAnotherPopup(this);"' extra = 'onclick="return showAddAnotherPopup(this);"'
return '<a href="%s" %s>%s</a>' % (url, extra, obj) return '<a href="%s" %s>%s</a>' % (url, extra, obj)
display_link.allow_tags = True display_link.allow_tags = True
display_link.short_description = _(field) display_link.short_description = _(field.replace('_', ' '))
display_link.admin_order_field = order display_link.admin_order_field = order
return display_link return display_link

View File

@ -94,6 +94,8 @@ class LinkHeaderRouter(DefaultRouter):
for _prefix, viewset, __ in self.registry: for _prefix, viewset, __ in self.registry:
if _prefix == prefix_or_model or viewset.model == prefix_or_model: if _prefix == prefix_or_model or viewset.model == prefix_or_model:
return viewset return viewset
msg = "%s does not have a regiestered viewset" % prefix_or_model
raise KeyError(msg)
def insert(self, prefix_or_model, name, field, **kwargs): def insert(self, prefix_or_model, name, field, **kwargs):
""" Dynamically add new fields to an existing serializer """ """ Dynamically add new fields to an existing serializer """

View File

@ -8,7 +8,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.admin import ExtendedModelAdmin from orchestra.admin import ExtendedModelAdmin
from orchestra.admin.utils import wrap_admin_view, link from orchestra.admin.utils import wrap_admin_view, admin_link
from orchestra.core import services from orchestra.core import services
from .filters import HasMainUserListFilter from .filters import HasMainUserListFilter
@ -42,7 +42,7 @@ class AccountAdmin(ExtendedModelAdmin):
add_form = AccountCreationForm add_form = AccountCreationForm
form = AccountChangeForm form = AccountChangeForm
user_link = link('user', order='user__username') user_link = admin_link('user', order='user__username')
def name(self, account): def name(self, account):
return account.name return account.name

View File

@ -7,7 +7,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.admin import ExtendedModelAdmin from orchestra.admin import ExtendedModelAdmin
from orchestra.admin.utils import link from orchestra.admin.utils import admin_link
from orchestra.apps.accounts.admin import AccountAdminMixin, SelectAccountAdminMixin from orchestra.apps.accounts.admin import AccountAdminMixin, SelectAccountAdminMixin
from .forms import (DatabaseUserChangeForm, DatabaseUserCreationForm, from .forms import (DatabaseUserChangeForm, DatabaseUserCreationForm,
@ -21,7 +21,7 @@ class UserInline(admin.TabularInline):
readonly_fields = ('user_link',) readonly_fields = ('user_link',)
extra = 0 extra = 0
user_link = link('user') user_link = admin_link('user')
def formfield_for_dbfield(self, db_field, **kwargs): def formfield_for_dbfield(self, db_field, **kwargs):
""" Make value input widget bigger """ """ Make value input widget bigger """
@ -38,7 +38,7 @@ class PermissionInline(AccountAdminMixin, admin.TabularInline):
extra = 0 extra = 0
filter_by_account_fields = ['database'] filter_by_account_fields = ['database']
database_link = link('database', popup=True) database_link = admin_link('database', popup=True)
def formfield_for_dbfield(self, db_field, **kwargs): def formfield_for_dbfield(self, db_field, **kwargs):
""" Make value input widget bigger """ """ Make value input widget bigger """

View File

@ -8,7 +8,7 @@ from django.template.response import TemplateResponse
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.admin import ChangeListDefaultFilter, ExtendedModelAdmin from orchestra.admin import ChangeListDefaultFilter, ExtendedModelAdmin
from orchestra.admin.utils import wrap_admin_view, link from orchestra.admin.utils import wrap_admin_view, admin_link
from orchestra.apps.accounts.admin import AccountAdminMixin from orchestra.apps.accounts.admin import AccountAdminMixin
from orchestra.utils import apps from orchestra.utils import apps
@ -41,7 +41,7 @@ class DomainInline(admin.TabularInline):
extra = 0 extra = 0
verbose_name_plural = _("Subdomains") verbose_name_plural = _("Subdomains")
domain_link = link() domain_link = admin_link()
domain_link.short_description = _("Name") domain_link.short_description = _("Name")
def has_add_permission(self, *args, **kwargs): def has_add_permission(self, *args, **kwargs):

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 (link, colored, wrap_admin_view, display_timesince) from orchestra.admin.utils import (admin_link, colored, wrap_admin_view,
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,
@ -107,8 +108,8 @@ class TicketInline(admin.TabularInline):
extra = 0 extra = 0
max_num = 0 max_num = 0
creator_link = link('creator') creator_link = admin_link('creator')
owner_link = link('owner') owner_link = admin_link('owner')
def ticket_id(self, instance): def ticket_id(self, instance):
return '<b>%s</b>' % link()(self, instance) return '<b>%s</b>' % link()(self, instance)
@ -198,9 +199,9 @@ class TicketAdmin(ChangeListDefaultFilter, ExtendedModelAdmin): #TODO ChangeView
'issues/js/ticket-admin.js', 'issues/js/ticket-admin.js',
) )
display_creator = link('creator') display_creator = admin_link('creator')
display_queue = link('queue') display_queue = admin_link('queue')
display_owner = link('owner') display_owner = admin_link('owner')
def display_summary(self, ticket): def display_summary(self, ticket):
context = { context = {
@ -212,7 +213,7 @@ class TicketAdmin(ChangeListDefaultFilter, ExtendedModelAdmin): #TODO ChangeView
if msg: if msg:
context.update({ context.update({
'updated': display_timesince(msg.created_on), 'updated': display_timesince(msg.created_on),
'updater': link('author')(self, msg) if msg.author else msg.author_name, 'updater': admin_link('author')(self, msg) if msg.author else msg.author_name,
}) })
context['updated'] = '. Updated by %(updater)s about %(updated)s' % context context['updated'] = '. Updated by %(updater)s about %(updated)s' % context
return '<h4>Added by %(creator)s about %(created)s%(updated)s</h4>' % context return '<h4>Added by %(creator)s about %(created)s%(updated)s</h4>' % context

View File

@ -1,4 +1,5 @@
from django.contrib.admin import SimpleListFilter from django.contrib.admin import SimpleListFilter
from django.utils.translation import ugettext_lazy as _
from .models import Ticket from .models import Ticket
@ -10,14 +11,20 @@ class MyTicketsListFilter(SimpleListFilter):
def lookups(self, request, model_admin): def lookups(self, request, model_admin):
return ( return (
('True', 'My Tickets'), ('True', _("My Tickets")),
('False', 'All'), ('False', _("All")),
) )
def queryset(self, request, queryset): def queryset(self, request, queryset):
if self.value() == 'True': if self.value() == 'True':
return queryset.involved_by(request.user) return queryset.involved_by(request.user)
def choices(self, cl):
""" Remove default All """
choices = iter(super(MyTicketsListFilter, self).choices(cl))
choices.next()
return choices
class TicketStateListFilter(SimpleListFilter): class TicketStateListFilter(SimpleListFilter):
title = 'State' title = 'State'
@ -25,14 +32,14 @@ class TicketStateListFilter(SimpleListFilter):
def lookups(self, request, model_admin): def lookups(self, request, model_admin):
return ( return (
('OPEN', "Open"), ('OPEN', _("Open")),
(Ticket.NEW, "New"), (Ticket.NEW, _("New")),
(Ticket.IN_PROGRESS, "In Progress"), (Ticket.IN_PROGRESS, _("In Progress")),
(Ticket.RESOLVED, "Resolved"), (Ticket.RESOLVED, _("Resolved")),
(Ticket.FEEDBACK, "Feedback"), (Ticket.FEEDBACK, _("Feedback")),
(Ticket.REJECTED, "Rejected"), (Ticket.REJECTED, _("Rejected")),
(Ticket.CLOSED, "Closed"), (Ticket.CLOSED, _("Closed")),
('False', 'All'), ('False', _("All")),
) )
def queryset(self, request, queryset): def queryset(self, request, queryset):
@ -41,3 +48,10 @@ class TicketStateListFilter(SimpleListFilter):
elif self.value() == 'False': elif self.value() == 'False':
return queryset return queryset
return queryset.filter(state=self.value()) return queryset.filter(state=self.value())
def choices(self, cl):
""" Remove default All """
choices = iter(super(TicketStateListFilter, self).choices(cl))
choices.next()
return choices

View File

@ -4,7 +4,7 @@ from django.contrib.auth.admin import UserAdmin
from django.utils.translation import ugettext, ugettext_lazy as _ from django.utils.translation import ugettext, ugettext_lazy as _
from orchestra.admin import ExtendedModelAdmin from orchestra.admin import ExtendedModelAdmin
from orchestra.admin.utils import link from orchestra.admin.utils import admin_link
from orchestra.apps.accounts.admin import SelectAccountAdminMixin from orchestra.apps.accounts.admin import SelectAccountAdminMixin
from .forms import ListCreationForm, ListChangeForm from .forms import ListCreationForm, ListChangeForm
@ -47,7 +47,7 @@ class ListAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
add_form = ListCreationForm add_form = ListCreationForm
filter_by_account_fields = ['address_domain'] filter_by_account_fields = ['address_domain']
address_domain_link = link('address_domain', order='address_domain__name') address_domain_link = admin_link('address_domain', order='address_domain__name')
def get_urls(self): def get_urls(self):
useradmin = UserAdmin(List, self.admin_site) useradmin = UserAdmin(List, self.admin_site)

View File

@ -5,7 +5,7 @@ from django.utils.translation import ugettext_lazy as _
from djcelery.humanize import naturaldate from djcelery.humanize import naturaldate
from orchestra.admin.html import monospace_format from orchestra.admin.html import monospace_format
from orchestra.admin.utils import link from orchestra.admin.utils import admin_link
from .models import Server, Route, BackendLog, BackendOperation from .models import Server, Route, BackendLog, BackendOperation
@ -60,7 +60,7 @@ class BackendOperationInline(admin.TabularInline):
def instance_link(self, operation): def instance_link(self, operation):
try: try:
return link('instance')(self, operation) return admin_link('instance')(self, operation)
except: except:
return _("deleted {0} {1}").format( return _("deleted {0} {1}").format(
escape(operation.content_type), escape(operation.object_id) escape(operation.content_type), escape(operation.object_id)
@ -88,11 +88,7 @@ class BackendLogAdmin(admin.ModelAdmin):
] ]
readonly_fields = fields readonly_fields = fields
def server_link(self, log): server_link = admin_link('server')
url = reverse('admin:orchestration_server_change', args=(log.server.pk,))
return '<a href="%s">%s</a>' % (url, log.server.name)
server_link.short_description = _("server")
server_link.allow_tags = True
def display_state(self, log): def display_state(self, log):
color = STATE_COLORS.get(log.state, 'grey') color = STATE_COLORS.get(log.state, 'grey')

View File

@ -1,18 +1,29 @@
from django import forms from django import forms
from django.db import models
from django.contrib import admin from django.contrib import admin
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.admin import ChangeListDefaultFilter
from orchestra.admin.filters import UsedContentTypeFilter
from orchestra.admin.utils import admin_link
from orchestra.apps.accounts.admin import AccountAdminMixin from orchestra.apps.accounts.admin import AccountAdminMixin
from orchestra.core import services from orchestra.core import services
from .filters import ActiveOrderListFilter
from .models import Service, Order, MetricStorage from .models import Service, Order, MetricStorage
class ServiceAdmin(admin.ModelAdmin): class ServiceAdmin(admin.ModelAdmin):
list_display = (
'description', 'content_type', 'handler_type', 'num_orders', 'is_active'
)
list_filter = ('is_active', 'handler_type', UsedContentTypeFilter)
fieldsets = ( fieldsets = (
(None, { (None, {
'classes': ('wide',), 'classes': ('wide',),
'fields': ('description', 'content_type', 'match', 'handler', 'is_active') 'fields': ('description', 'content_type', 'match', 'handler_type',
'is_active')
}), }),
(_("Billing options"), { (_("Billing options"), {
'classes': ('wide',), 'classes': ('wide',),
@ -37,12 +48,32 @@ class ServiceAdmin(admin.ModelAdmin):
kwargs['widget'] = forms.TextInput(attrs={'size':'160'}) kwargs['widget'] = forms.TextInput(attrs={'size':'160'})
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):
num = service.orders.count()
url = reverse('admin:orders_order_changelist')
url += '?service=%i' % service.pk
return '<a href="%s">%d</a>' % (url, num)
num_orders.short_description = _("Orders")
num_orders.admin_order_field = 'orders__count'
num_orders.allow_tags = True
class OrderAdmin(AccountAdminMixin, admin.ModelAdmin): def get_queryset(self, request):
list_display = ('id', 'service', 'account_link', 'cancelled_on') qs = super(ServiceAdmin, self).get_queryset(request)
list_filter = ('service',) qs = qs.annotate(models.Count('orders'))
return qs
class OrderAdmin(AccountAdminMixin, ChangeListDefaultFilter, admin.ModelAdmin):
list_display = (
'id', 'service', 'account_link', 'content_object_link', 'cancelled_on'
)
list_filter = (ActiveOrderListFilter, 'service',)
default_changelist_filters = (
('is_active', 'True'),
)
content_object_link = admin_link('content_object')
class MetricStorageAdmin(admin.ModelAdmin): class MetricStorageAdmin(admin.ModelAdmin):
list_display = ('order', 'value', 'created_on', 'updated_on') list_display = ('order', 'value', 'created_on', 'updated_on')
list_filter = ('order__service',) list_filter = ('order__service',)

View File

@ -0,0 +1,28 @@
from django.contrib.admin import SimpleListFilter
from django.utils.translation import ugettext_lazy as _
class ActiveOrderListFilter(SimpleListFilter):
""" Filter tickets by created_by according to request.user """
title = 'Orders'
parameter_name = 'is_active'
def lookups(self, request, model_admin):
return (
('True', _("Active")),
('False', _("Inactive")),
('None', _("All")),
)
def queryset(self, request, queryset):
if self.value() == 'True':
return queryset.active()
elif self.value() == 'False':
return queryset.inactive()
return queryset
def choices(self, cl):
""" Remove default All """
choices = iter(super(ActiveOrderListFilter, self).choices(cl))
choices.next()
return choices

View File

@ -12,13 +12,19 @@ class ServiceHandler(plugins.Plugin):
def __init__(self, service): def __init__(self, service):
self.service = service self.service = service
def __getattr__(self, attr):
return getattr(self.service, attr)
@classmethod @classmethod
def get_plugin_choices(cls): def get_plugin_choices(cls):
choices = super(ServiceHandler, cls).get_plugin_choices() choices = super(ServiceHandler, cls).get_plugin_choices()
return [('', _("Default"))] + choices return [('', _("Default"))] + choices
def __getattr__(self, attr): def get_content_type(self):
return getattr(self.service, attr) if not self.model:
return self.content_type
app_label, model = self.model.split('.')
return ContentType.objects.get_by_natural_key(app_label, model.lower())
def matches(self, instance): def matches(self, instance):
safe_locals = { safe_locals = {
@ -27,13 +33,8 @@ class ServiceHandler(plugins.Plugin):
return eval(self.match, safe_locals) return eval(self.match, safe_locals)
def get_metric(self, instance): def get_metric(self, instance):
safe_locals = { if self.metric:
instance._meta.model_name: instance safe_locals = {
} instance._meta.model_name: instance
return eval(self.metric, safe_locals) }
return eval(self.metric, safe_locals)
def get_content_type(self):
if not self.model:
return self.content_type
app_label, model = self.model.split('.')
return ContentType.objects.get_by_natural_key(app_label, model.lower())

View File

@ -31,7 +31,7 @@ def search_for_related(origin, max_depth=2):
if hasattr(node, 'account') or isinstance(node, Account): if hasattr(node, 'account') or isinstance(node, Account):
return node return node
for related in related_iterator(node): for related in related_iterator(node):
if related not in models: if related and related not in models:
new_models = list(models) new_models = list(models)
new_models.append(related) new_models.append(related)
queue.append(new_models) queue.append(new_models)

View File

@ -45,8 +45,10 @@ class Service(models.Model):
description = models.CharField(_("description"), max_length=256, unique=True) description = models.CharField(_("description"), max_length=256, unique=True)
content_type = models.ForeignKey(ContentType, verbose_name=_("content type")) content_type = models.ForeignKey(ContentType, verbose_name=_("content type"))
match = models.CharField(_("match"), max_length=256, blank=True) match = models.CharField(_("match"), max_length=256, blank=True)
handler = models.CharField(_("handler"), max_length=256, blank=True, handler_type = models.CharField(_("handler"), max_length=256, blank=True,
help_text=_("Handler used to process this Service."), help_text=_("Handler used for processing this Service. A handler "
"enables customized behaviour far beyond what options "
"here allow to."),
choices=ServiceHandler.get_plugin_choices()) choices=ServiceHandler.get_plugin_choices())
is_active = models.BooleanField(_("is active"), default=True) is_active = models.BooleanField(_("is active"), default=True)
# Billing # Billing
@ -172,14 +174,16 @@ class Service(models.Model):
cache.set(ct, services) cache.set(ct, services)
return services return services
# FIXME some times caching is nasty, do we really have to? make get_plugin more efficient?
@cached_property @cached_property
def proxy(self): def handler(self):
if self.handler: """ Accessor of this service handler instance """
return ServiceHandler.get_plugin(self.handler)(self) if self.handler_type:
return ServiceHandler.get_plugin(self.handler_type)(self)
return ServiceHandler(self) return ServiceHandler(self)
def clean(self): def clean(self):
content_type = self.proxy.get_content_type() content_type = self.handler.get_content_type()
if self.content_type != content_type: if self.content_type != content_type:
msg =_("Content type must be equal to '%s'." % str(content_type)) msg =_("Content type must be equal to '%s'." % str(content_type))
raise ValidationError(msg) raise ValidationError(msg)
@ -191,22 +195,30 @@ class Service(models.Model):
except IndexError: except IndexError:
pass pass
else: else:
try: for attr in ['matches', 'get_metric']:
self.proxy.matches(obj) try:
except Exception as e: getattr(self.handler, attr)(obj)
raise ValidationError(_(str(e))) except Exception as exception:
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):
def by_object(self, obj, *args, **kwargs): def by_object(self, obj, **kwargs):
ct = ContentType.objects.get_for_model(obj) ct = ContentType.objects.get_for_model(obj)
return self.filter(object_id=obj.pk, content_type=ct) return self.filter(object_id=obj.pk, content_type=ct, **kwargs)
def active(self, *args, **kwargs): def active(self, **kwargs):
""" return active orders """ """ return active orders """
return self.filter( return self.filter(
Q(cancelled_on__isnull=True) | Q(cancelled_on__gt=timezone.now()) Q(cancelled_on__isnull=True) | Q(cancelled_on__gt=timezone.now())
).filter(*args, **kwargs) ).filter(**kwargs)
def inactive(self, **kwargs):
""" return inactive orders """
return self.filter(cancelled_on__lt=timezone.now(), **kwargs)
class Order(models.Model): class Order(models.Model):
@ -234,10 +246,12 @@ class Order(models.Model):
def update(self): def update(self):
instance = self.content_object instance = self.content_object
if self.service.metric: handler = self.service.handler
metric = self.service.get_metric(instance) if handler.metric:
MetricStorage.store(self, metric) metric = handler.get_metric(instance)
description = "{}: {}".format(self.service.description, str(instance)) if metric is not None:
MetricStorage.store(self, metric)
description = "{}: {}".format(handler.description, str(instance))
if self.description != description: if self.description != description:
self.description = description self.description = description
self.save() self.save()
@ -246,7 +260,7 @@ class Order(models.Model):
def update_orders(cls, instance): def update_orders(cls, instance):
for service in Service.get_services(instance): for service in Service.get_services(instance):
orders = Order.objects.by_object(instance, service=service).active() orders = Order.objects.by_object(instance, service=service).active()
if service.matches(instance): if service.handler.matches(instance):
if not orders: if not orders:
account_id = getattr(instance, 'account_id', instance.pk) account_id = getattr(instance, 'account_id', instance.pk)
order = cls.objects.create(content_object=instance, order = cls.objects.create(content_object=instance,
@ -289,23 +303,20 @@ 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 (not sender in [MetricStorage, LogEntry, Order, Service] and if sender not in [MetricStorage, LogEntry, Order, Service]:
not Service in sender.__mro__): 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")
def update_orders(sender, **kwargs): def update_orders(sender, **kwargs):
if (not sender in [MetricStorage, LogEntry, Order, Service] and if sender not in [MetricStorage, LogEntry, Order, Service]:
not Service in sender.__mro__): instance = kwargs['instance']
instance = kwargs['instance'] if instance.pk:
print kwargs # post_save
if instance.pk: Order.update_orders(instance)
# post_save related = search_for_related(instance)
Order.update_orders(instance) if related:
related = search_for_related(instance) Order.update_orders(related)
if related:
Order.update_orders(related)

View File

@ -7,7 +7,7 @@ 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, link from orchestra.admin.utils import insertattr, get_modeladmin, admin_link
from orchestra.core import services from orchestra.core import services
from orchestra.utils import running_syncdb from orchestra.utils import running_syncdb
@ -71,10 +71,7 @@ class ResourceDataAdmin(admin.ModelAdmin):
list_filter = ('resource',) list_filter = ('resource',)
readonly_fields = ('content_object_link',) readonly_fields = ('content_object_link',)
def content_object_link(self, data): content_object_link = admin_link('content_object')
return link('content_object')(self, data)
content_object_link.allow_tags = True
content_object_link.short_description = _("Content object")
class MonitorDataAdmin(admin.ModelAdmin): class MonitorDataAdmin(admin.ModelAdmin):
@ -82,10 +79,7 @@ class MonitorDataAdmin(admin.ModelAdmin):
list_filter = ('monitor',) list_filter = ('monitor',)
readonly_fields = ('content_object_link',) readonly_fields = ('content_object_link',)
def content_object_link(self, data): content_object_link = admin_link('content_object')
return link('content_object')(self, data)
content_object_link.allow_tags = True
content_object_link.short_description = _("Content object")
admin.site.register(Resource, ResourceAdmin) admin.site.register(Resource, ResourceAdmin)

View File

@ -164,7 +164,15 @@ def create_resource_relation():
class ResourceHandler(object): class ResourceHandler(object):
""" account.resources.web """ """ account.resources.web """
def __getattr__(self, attr): def __getattr__(self, attr):
return self.obj.resource_set.get(resource__name=attr) """ get or create ResourceData """
try:
return self.obj.resource_set.get(resource__name=attr)
except ResourceData.DoesNotExist:
model = self.obj._meta.model_name
resource = Resource.objects.get(content_type__model=model,
name=attr, is_active=True)
return ResourceData.objects.create(content_object=self.obj,
resource=resource)
def __get__(self, obj, cls): def __get__(self, obj, cls):
self.obj = obj self.obj = obj

View File

@ -27,7 +27,10 @@ if not running_syncdb():
# TODO why this is even loaded during syncdb? # TODO why this is even loaded during syncdb?
for resources in Resource.group_by_content_type(): for resources in Resource.group_by_content_type():
model = resources[0].content_type.model_class() model = resources[0].content_type.model_class()
router.insert(model, 'resources', ResourceSerializer, required=False, many=True) try:
router.insert(model, 'resources', ResourceSerializer, required=False, many=True)
except KeyError:
continue
def validate_resources(self, attrs, source, _resources=resources): def validate_resources(self, attrs, source, _resources=resources):
""" Creates missing resources """ """ Creates missing resources """

View File

@ -7,7 +7,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.admin import ExtendedModelAdmin from orchestra.admin import ExtendedModelAdmin
from orchestra.admin.utils import insertattr, link from orchestra.admin.utils import insertattr, admin_link
from orchestra.apps.accounts.admin import SelectAccountAdminMixin from orchestra.apps.accounts.admin import SelectAccountAdminMixin
from orchestra.apps.domains.forms import DomainIterator from orchestra.apps.domains.forms import DomainIterator
from orchestra.apps.users.roles.admin import RoleAdmin from orchestra.apps.users.roles.admin import RoleAdmin
@ -79,7 +79,7 @@ class AddressAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
filter_by_account_fields = ['domain'] filter_by_account_fields = ['domain']
filter_horizontal = ['mailboxes'] filter_horizontal = ['mailboxes']
domain_link = link('domain', order='domain__name') domain_link = admin_link('domain', order='domain__name')
def email_link(self, address): def email_link(self, address):
link = self.domain_link(address) link = self.domain_link(address)

View File

@ -5,7 +5,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.admin import ExtendedModelAdmin from orchestra.admin import ExtendedModelAdmin
from orchestra.admin.utils import link from orchestra.admin.utils import admin_link
from orchestra.apps.accounts.admin import AccountAdminMixin, SelectAccountAdminMixin from orchestra.apps.accounts.admin import AccountAdminMixin, SelectAccountAdminMixin
from orchestra.apps.accounts.widgets import account_related_field_widget_factory from orchestra.apps.accounts.widgets import account_related_field_widget_factory
@ -35,7 +35,7 @@ class ContentInline(AccountAdminMixin, admin.TabularInline):
readonly_fields = ('webapp_link', 'webapp_type') readonly_fields = ('webapp_link', 'webapp_type')
filter_by_account_fields = ['webapp'] filter_by_account_fields = ['webapp']
webapp_link = link('webapp', popup=True) webapp_link = admin_link('webapp', popup=True)
webapp_link.short_description = _("Web App") webapp_link.short_description = _("Web App")
def webapp_type(self, content): def webapp_type(self, content):