I am trying to customize the django changelist and I have been having
trouble displaying data across reverse ForeignKey relationships.
Despite searching at length I have been unable to find a better method
to display this data than using _set.all(). My issue with this
solution is that it requires O(n) database queries. Is there any way
to access the data more efficiently?
Example:
Host
name = CharField
Account
name = CharField
host = ForeignKey(Host, related_name='accounts')
What I want to do is list the associated account names for every host
on the changelist. The problem with adding
def get_accounts(self, host):
return host.accounts.all()
to the list_display of the host admin model is that it will then hit
the database O(n) times where n is the number of rows in the list.
My first solution was performing a raw query with ' ...
GROUP_CONCAT(account.id) ... ORDER BY host.id' and then matching the
account id against a cache of account id -> account name. I have since
refined this by creating a custom aggregate to perform the aggregate.
Once the data has been found it, again, uses a cache of account id ->
account name.
So, my questions are as follows:
1. How should I be going about this?
2. Is support for this type of function ever going to be build into
django? (I have noticed that the docs state that doing this from
list_display in admin would require O(n) database queries, and no
mention is made of methods other than _set.all() to retrieve the data,
at least that I was able to find).
Thanks,
Chad
PS: In case anyone else runs into the same problem in the future, here
is the code that I am currently using:
~~~aggregates.py~~~
from django.db.models import Aggregate
from django.db.models.sql.aggregates import Aggregate as SQLAggregate,
AggregateField as SQLAggregateField
string_aggregate_field = SQLAggregateField('TextField')
class GroupConcat(Aggregate):
name = 'GroupConcat'
def add_to_query(self, query, alias, col, source, is_summary):
# Manually override the aggregate class
klass = SQLGroupConcat # getattr(query.aggregates_module,
self.name)
aggregate = klass(col, source=source, is_summary=is_summary,
**self.extra)
query.aggregates[alias] = aggregate
class SQLGroupConcat(SQLAggregate):
sql_function = 'GROUP_CONCAT'
def __init__(self, col, **extra):
super(SQLGroupConcat, self).__init__(col, **extra)
tmp = string_aggregate_field
self.field = tmp
~~~admin.py~~~~
...
from aggregates import GroupConcat
...
class HostAdmin(admin.ModelAdmin):
list_display = ('__unicode__', 'account_names')
def queryset(self, request):
self.account_cache = {}
return super(HostAdmin,
self).queryset(request).annote(GroupConcat('accounts'))
def account_names(self, host):
# Cache the related objects to avoid O(n) queries
if not self.account_cache:
queryset = Account.objects.all()
for obj in queryset:
self.account_cache[obj.id] = obj.name
# Parse the account list and output formatted links
accounts = host.accounts__groupconcat
if not accounts:
return 'none'
accounts = accounts.split(',')
result = ''
num_accounts = len(accounts)
for i in range(0, num_accounts):
account = int(accounts[i])
name = self.account_cache[account]
url =
urlresolvers.reverse('admin:accounts_account_change', args=(account,))
result += '<a href=\'%s\'>%s</a>' % (url, name)
if i < num_accounts:
result += '<br />'
return result
account_names.allow_tags = True
--
You received this message because you are subscribed to the Google Groups "Django users" group.
To post to this group, send email to django-users@googlegroups.com.
To unsubscribe from this group, send email to django-users+unsubscribe@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/django-users?hl=en.
No comments:
Post a Comment