diff --git a/orchestra/admin/utils.py b/orchestra/admin/utils.py index 2e7a00b7..d378968d 100644 --- a/orchestra/admin/utils.py +++ b/orchestra/admin/utils.py @@ -62,15 +62,15 @@ def wrap_admin_view(modeladmin, view): def set_default_filter(queryarg, request, value): """ set default filters for changelist_view """ if queryarg not in request.GET: - q = request.GET.copy() + request_copy = request.GET.copy() if callable(value): value = value(request) - q[queryarg] = value - request.GET = q + request_copy[queryarg] = value + request.GET = request_copy request.META['QUERY_STRING'] = request.GET.urlencode() -def link(*args, **kwargs): +def admin_link(*args, **kwargs): """ utility function for creating admin links """ field = args[0] if args else '' order = kwargs.pop('order', field) @@ -88,7 +88,7 @@ def link(*args, **kwargs): extra = 'onclick="return showAddAnotherPopup(this);"' return '%s' % (url, extra, obj) display_link.allow_tags = True - display_link.short_description = _(field) + display_link.short_description = _(field.replace('_', ' ')) display_link.admin_order_field = order return display_link diff --git a/orchestra/api/options.py b/orchestra/api/options.py index a56c3565..c56b8e33 100644 --- a/orchestra/api/options.py +++ b/orchestra/api/options.py @@ -94,6 +94,8 @@ class LinkHeaderRouter(DefaultRouter): for _prefix, viewset, __ in self.registry: if _prefix == prefix_or_model or viewset.model == prefix_or_model: 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): """ Dynamically add new fields to an existing serializer """ diff --git a/orchestra/apps/accounts/admin.py b/orchestra/apps/accounts/admin.py index 18645882..2f8f7024 100644 --- a/orchestra/apps/accounts/admin.py +++ b/orchestra/apps/accounts/admin.py @@ -8,7 +8,7 @@ from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ 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 .filters import HasMainUserListFilter @@ -42,7 +42,7 @@ class AccountAdmin(ExtendedModelAdmin): add_form = AccountCreationForm form = AccountChangeForm - user_link = link('user', order='user__username') + user_link = admin_link('user', order='user__username') def name(self, account): return account.name diff --git a/orchestra/apps/databases/admin.py b/orchestra/apps/databases/admin.py index b3771b59..30c45af4 100644 --- a/orchestra/apps/databases/admin.py +++ b/orchestra/apps/databases/admin.py @@ -7,7 +7,7 @@ from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ 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 .forms import (DatabaseUserChangeForm, DatabaseUserCreationForm, @@ -21,7 +21,7 @@ class UserInline(admin.TabularInline): readonly_fields = ('user_link',) extra = 0 - user_link = link('user') + user_link = admin_link('user') def formfield_for_dbfield(self, db_field, **kwargs): """ Make value input widget bigger """ @@ -38,7 +38,7 @@ class PermissionInline(AccountAdminMixin, admin.TabularInline): extra = 0 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): """ Make value input widget bigger """ diff --git a/orchestra/apps/domains/admin.py b/orchestra/apps/domains/admin.py index e49af15b..233250c1 100644 --- a/orchestra/apps/domains/admin.py +++ b/orchestra/apps/domains/admin.py @@ -8,7 +8,7 @@ from django.template.response import TemplateResponse from django.utils.translation import ugettext_lazy as _ 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.utils import apps @@ -41,7 +41,7 @@ class DomainInline(admin.TabularInline): extra = 0 verbose_name_plural = _("Subdomains") - domain_link = link() + domain_link = admin_link() domain_link.short_description = _("Name") def has_add_permission(self, *args, **kwargs): diff --git a/orchestra/apps/issues/admin.py b/orchestra/apps/issues/admin.py index db5c496c..5e0ef9ff 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 (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 .actions import (reject_tickets, resolve_tickets, take_tickets, close_tickets, @@ -107,8 +108,8 @@ class TicketInline(admin.TabularInline): extra = 0 max_num = 0 - creator_link = link('creator') - owner_link = link('owner') + creator_link = admin_link('creator') + owner_link = admin_link('owner') def ticket_id(self, instance): return '%s' % link()(self, instance) @@ -198,9 +199,9 @@ class TicketAdmin(ChangeListDefaultFilter, ExtendedModelAdmin): #TODO ChangeView 'issues/js/ticket-admin.js', ) - display_creator = link('creator') - display_queue = link('queue') - display_owner = link('owner') + display_creator = admin_link('creator') + display_queue = admin_link('queue') + display_owner = admin_link('owner') def display_summary(self, ticket): context = { @@ -212,7 +213,7 @@ class TicketAdmin(ChangeListDefaultFilter, ExtendedModelAdmin): #TODO ChangeView if msg: context.update({ '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 return '

Added by %(creator)s about %(created)s%(updated)s

' % context diff --git a/orchestra/apps/issues/filters.py b/orchestra/apps/issues/filters.py index 8782574d..982ac7b1 100644 --- a/orchestra/apps/issues/filters.py +++ b/orchestra/apps/issues/filters.py @@ -1,4 +1,5 @@ from django.contrib.admin import SimpleListFilter +from django.utils.translation import ugettext_lazy as _ from .models import Ticket @@ -10,13 +11,19 @@ class MyTicketsListFilter(SimpleListFilter): def lookups(self, request, model_admin): return ( - ('True', 'My Tickets'), - ('False', 'All'), + ('True', _("My Tickets")), + ('False', _("All")), ) def queryset(self, request, queryset): if self.value() == 'True': 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): @@ -25,14 +32,14 @@ class TicketStateListFilter(SimpleListFilter): def lookups(self, request, model_admin): return ( - ('OPEN', "Open"), - (Ticket.NEW, "New"), - (Ticket.IN_PROGRESS, "In Progress"), - (Ticket.RESOLVED, "Resolved"), - (Ticket.FEEDBACK, "Feedback"), - (Ticket.REJECTED, "Rejected"), - (Ticket.CLOSED, "Closed"), - ('False', 'All'), + ('OPEN', _("Open")), + (Ticket.NEW, _("New")), + (Ticket.IN_PROGRESS, _("In Progress")), + (Ticket.RESOLVED, _("Resolved")), + (Ticket.FEEDBACK, _("Feedback")), + (Ticket.REJECTED, _("Rejected")), + (Ticket.CLOSED, _("Closed")), + ('False', _("All")), ) def queryset(self, request, queryset): @@ -41,3 +48,10 @@ class TicketStateListFilter(SimpleListFilter): elif self.value() == 'False': return queryset return queryset.filter(state=self.value()) + + def choices(self, cl): + """ Remove default All """ + choices = iter(super(TicketStateListFilter, self).choices(cl)) + choices.next() + return choices + diff --git a/orchestra/apps/lists/admin.py b/orchestra/apps/lists/admin.py index dfcf2e54..11ed445e 100644 --- a/orchestra/apps/lists/admin.py +++ b/orchestra/apps/lists/admin.py @@ -4,7 +4,7 @@ from django.contrib.auth.admin import UserAdmin from django.utils.translation import ugettext, ugettext_lazy as _ 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 .forms import ListCreationForm, ListChangeForm @@ -47,7 +47,7 @@ class ListAdmin(SelectAccountAdminMixin, ExtendedModelAdmin): add_form = ListCreationForm 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): useradmin = UserAdmin(List, self.admin_site) diff --git a/orchestra/apps/orchestration/admin.py b/orchestra/apps/orchestration/admin.py index cf5e39ad..fa06ebe9 100644 --- a/orchestra/apps/orchestration/admin.py +++ b/orchestra/apps/orchestration/admin.py @@ -5,7 +5,7 @@ from django.utils.translation import ugettext_lazy as _ from djcelery.humanize import naturaldate 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 @@ -60,7 +60,7 @@ class BackendOperationInline(admin.TabularInline): def instance_link(self, operation): try: - return link('instance')(self, operation) + return admin_link('instance')(self, operation) except: return _("deleted {0} {1}").format( escape(operation.content_type), escape(operation.object_id) @@ -88,11 +88,7 @@ class BackendLogAdmin(admin.ModelAdmin): ] readonly_fields = fields - def server_link(self, log): - url = reverse('admin:orchestration_server_change', args=(log.server.pk,)) - return '%s' % (url, log.server.name) - server_link.short_description = _("server") - server_link.allow_tags = True + server_link = admin_link('server') def display_state(self, log): color = STATE_COLORS.get(log.state, 'grey') diff --git a/orchestra/apps/orders/admin.py b/orchestra/apps/orders/admin.py index b51a91c6..d949dd19 100644 --- a/orchestra/apps/orders/admin.py +++ b/orchestra/apps/orders/admin.py @@ -1,18 +1,29 @@ from django import forms +from django.db import models from django.contrib import admin +from django.core.urlresolvers import reverse 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.core import services +from .filters import ActiveOrderListFilter from .models import Service, Order, MetricStorage class ServiceAdmin(admin.ModelAdmin): + list_display = ( + 'description', 'content_type', 'handler_type', 'num_orders', 'is_active' + ) + list_filter = ('is_active', 'handler_type', UsedContentTypeFilter) fieldsets = ( (None, { 'classes': ('wide',), - 'fields': ('description', 'content_type', 'match', 'handler', 'is_active') + 'fields': ('description', 'content_type', 'match', 'handler_type', + 'is_active') }), (_("Billing options"), { 'classes': ('wide',), @@ -36,12 +47,32 @@ class ServiceAdmin(admin.ModelAdmin): if db_field.name in ['match', 'metric']: kwargs['widget'] = forms.TextInput(attrs={'size':'160'}) 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 '%d' % (url, num) + num_orders.short_description = _("Orders") + num_orders.admin_order_field = 'orders__count' + num_orders.allow_tags = True + + def get_queryset(self, request): + qs = super(ServiceAdmin, self).get_queryset(request) + qs = qs.annotate(models.Count('orders')) + return qs -class OrderAdmin(AccountAdminMixin, admin.ModelAdmin): - list_display = ('id', 'service', 'account_link', 'cancelled_on') - list_filter = ('service',) - +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): list_display = ('order', 'value', 'created_on', 'updated_on') diff --git a/orchestra/apps/orders/filters.py b/orchestra/apps/orders/filters.py new file mode 100644 index 00000000..f82e54a6 --- /dev/null +++ b/orchestra/apps/orders/filters.py @@ -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 diff --git a/orchestra/apps/orders/handlers.py b/orchestra/apps/orders/handlers.py index 2de9cbb8..76597327 100644 --- a/orchestra/apps/orders/handlers.py +++ b/orchestra/apps/orders/handlers.py @@ -12,13 +12,19 @@ class ServiceHandler(plugins.Plugin): def __init__(self, service): self.service = service + def __getattr__(self, attr): + return getattr(self.service, attr) + @classmethod def get_plugin_choices(cls): choices = super(ServiceHandler, cls).get_plugin_choices() return [('', _("Default"))] + choices - def __getattr__(self, attr): - return getattr(self.service, attr) + 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()) def matches(self, instance): safe_locals = { @@ -27,13 +33,8 @@ class ServiceHandler(plugins.Plugin): return eval(self.match, safe_locals) def get_metric(self, instance): - safe_locals = { - instance._meta.model_name: instance - } - 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()) + if self.metric: + safe_locals = { + instance._meta.model_name: instance + } + return eval(self.metric, safe_locals) diff --git a/orchestra/apps/orders/helpers.py b/orchestra/apps/orders/helpers.py index 746fdfa4..087c1c4e 100644 --- a/orchestra/apps/orders/helpers.py +++ b/orchestra/apps/orders/helpers.py @@ -31,7 +31,7 @@ def search_for_related(origin, max_depth=2): if hasattr(node, 'account') or isinstance(node, Account): return 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.append(related) queue.append(new_models) diff --git a/orchestra/apps/orders/models.py b/orchestra/apps/orders/models.py index 2b8a014b..a24d24ba 100644 --- a/orchestra/apps/orders/models.py +++ b/orchestra/apps/orders/models.py @@ -45,8 +45,10 @@ class Service(models.Model): description = models.CharField(_("description"), max_length=256, unique=True) content_type = models.ForeignKey(ContentType, verbose_name=_("content type")) match = models.CharField(_("match"), max_length=256, blank=True) - handler = models.CharField(_("handler"), max_length=256, blank=True, - help_text=_("Handler used to process this Service."), + handler_type = models.CharField(_("handler"), max_length=256, blank=True, + 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()) is_active = models.BooleanField(_("is active"), default=True) # Billing @@ -172,14 +174,16 @@ class Service(models.Model): cache.set(ct, services) return services + # FIXME some times caching is nasty, do we really have to? make get_plugin more efficient? @cached_property - def proxy(self): - if self.handler: - return ServiceHandler.get_plugin(self.handler)(self) + def handler(self): + """ Accessor of this service handler instance """ + if self.handler_type: + return ServiceHandler.get_plugin(self.handler_type)(self) return ServiceHandler(self) def clean(self): - content_type = self.proxy.get_content_type() + content_type = self.handler.get_content_type() if self.content_type != content_type: msg =_("Content type must be equal to '%s'." % str(content_type)) raise ValidationError(msg) @@ -191,22 +195,30 @@ class Service(models.Model): except IndexError: pass else: - try: - self.proxy.matches(obj) - except Exception as e: - raise ValidationError(_(str(e))) + for attr in ['matches', 'get_metric']: + try: + getattr(self.handler, attr)(obj) + 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): - def by_object(self, obj, *args, **kwargs): + def by_object(self, obj, **kwargs): 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 self.filter( 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): @@ -234,10 +246,12 @@ class Order(models.Model): def update(self): instance = self.content_object - if self.service.metric: - metric = self.service.get_metric(instance) - MetricStorage.store(self, metric) - description = "{}: {}".format(self.service.description, str(instance)) + handler = self.service.handler + if handler.metric: + metric = handler.get_metric(instance) + if metric is not None: + MetricStorage.store(self, metric) + description = "{}: {}".format(handler.description, str(instance)) if self.description != description: self.description = description self.save() @@ -246,7 +260,7 @@ class Order(models.Model): def update_orders(cls, instance): for service in Service.get_services(instance): orders = Order.objects.by_object(instance, service=service).active() - if service.matches(instance): + if service.handler.matches(instance): if not orders: account_id = getattr(instance, 'account_id', instance.pk) order = cls.objects.create(content_object=instance, @@ -289,23 +303,20 @@ class MetricStorage(models.Model): @receiver(pre_delete, dispatch_uid="orders.cancel_orders") def cancel_orders(sender, **kwargs): - if (not sender in [MetricStorage, LogEntry, Order, Service] and - not Service in sender.__mro__): - instance = kwargs['instance'] - for order in Order.objects.by_object(instance).active(): - order.cancel() + if sender not in [MetricStorage, LogEntry, Order, Service]: + instance = kwargs['instance'] + for order in Order.objects.by_object(instance).active(): + order.cancel() @receiver(post_save, dispatch_uid="orders.update_orders") @receiver(post_delete, dispatch_uid="orders.update_orders") def update_orders(sender, **kwargs): - if (not sender in [MetricStorage, LogEntry, Order, Service] and - not Service in sender.__mro__): - instance = kwargs['instance'] - print kwargs - if instance.pk: - # post_save - Order.update_orders(instance) - related = search_for_related(instance) - if related: - Order.update_orders(related) + if sender not in [MetricStorage, LogEntry, Order, Service]: + instance = kwargs['instance'] + if instance.pk: + # post_save + Order.update_orders(instance) + related = search_for_related(instance) + if related: + Order.update_orders(related) diff --git a/orchestra/apps/resources/admin.py b/orchestra/apps/resources/admin.py index 3f5fba38..7bbb55f7 100644 --- a/orchestra/apps/resources/admin.py +++ b/orchestra/apps/resources/admin.py @@ -7,7 +7,7 @@ from djcelery.humanize import naturaldate from orchestra.admin import ExtendedModelAdmin 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.utils import running_syncdb @@ -71,10 +71,7 @@ class ResourceDataAdmin(admin.ModelAdmin): list_filter = ('resource',) readonly_fields = ('content_object_link',) - def content_object_link(self, data): - return link('content_object')(self, data) - content_object_link.allow_tags = True - content_object_link.short_description = _("Content object") + content_object_link = admin_link('content_object') class MonitorDataAdmin(admin.ModelAdmin): @@ -82,10 +79,7 @@ class MonitorDataAdmin(admin.ModelAdmin): list_filter = ('monitor',) readonly_fields = ('content_object_link',) - def content_object_link(self, data): - return link('content_object')(self, data) - content_object_link.allow_tags = True - content_object_link.short_description = _("Content object") + content_object_link = admin_link('content_object') admin.site.register(Resource, ResourceAdmin) diff --git a/orchestra/apps/resources/models.py b/orchestra/apps/resources/models.py index 1940df8d..14a54027 100644 --- a/orchestra/apps/resources/models.py +++ b/orchestra/apps/resources/models.py @@ -164,7 +164,15 @@ def create_resource_relation(): class ResourceHandler(object): """ account.resources.web """ 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): self.obj = obj diff --git a/orchestra/apps/resources/serializers.py b/orchestra/apps/resources/serializers.py index 1f7808bb..7be4393c 100644 --- a/orchestra/apps/resources/serializers.py +++ b/orchestra/apps/resources/serializers.py @@ -27,7 +27,10 @@ if not running_syncdb(): # TODO why this is even loaded during syncdb? for resources in Resource.group_by_content_type(): 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): """ Creates missing resources """ diff --git a/orchestra/apps/users/roles/mail/admin.py b/orchestra/apps/users/roles/mail/admin.py index 0124b242..72bfdc71 100644 --- a/orchestra/apps/users/roles/mail/admin.py +++ b/orchestra/apps/users/roles/mail/admin.py @@ -7,7 +7,7 @@ from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ 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.domains.forms import DomainIterator from orchestra.apps.users.roles.admin import RoleAdmin @@ -79,7 +79,7 @@ class AddressAdmin(SelectAccountAdminMixin, ExtendedModelAdmin): filter_by_account_fields = ['domain'] filter_horizontal = ['mailboxes'] - domain_link = link('domain', order='domain__name') + domain_link = admin_link('domain', order='domain__name') def email_link(self, address): link = self.domain_link(address) diff --git a/orchestra/apps/websites/admin.py b/orchestra/apps/websites/admin.py index 741f863a..b67ac4ad 100644 --- a/orchestra/apps/websites/admin.py +++ b/orchestra/apps/websites/admin.py @@ -5,7 +5,7 @@ from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ 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.widgets import account_related_field_widget_factory @@ -35,7 +35,7 @@ class ContentInline(AccountAdminMixin, admin.TabularInline): readonly_fields = ('webapp_link', 'webapp_type') filter_by_account_fields = ['webapp'] - webapp_link = link('webapp', popup=True) + webapp_link = admin_link('webapp', popup=True) webapp_link.short_description = _("Web App") def webapp_type(self, content):