6

I have just begun to play around with Django admin views, and to start off, I am trying to do something very simple: showing several fields in the listing of objects using list_display as explained here: https://docs.djangoproject.com/en/dev/ref/contrib/admin/

This is my dead simple code:

class ArticleAdmin(admin.ModelAdmin):
     list_display = ('title', 'category')

Unfortunately, the list_display option is causing the columnar view to appear, but only some of the objects (40 out of 85) are now displaying in the listing. I cannot deduce why certain objects are showing over the others - their fields look like they are filled similarly. It's clearly not paginating, because when I tried it on an admin of another model, it showed only 2 objects out of about 70 objects.

What might be going on here?

[UPDATE] Article Model:

class Article(models.Model):
    revision = models.ForeignKey('ArticleRevision', related_name="current_revision")
    category = models.ForeignKey('meta.Category')
    language = models.ForeignKey('meta.Language', default=get_default_language)
    created = models.DateTimeField(auto_now_add=True, editable=False)
    changed = models.DateTimeField(auto_now=True, editable=False)
    title = models.CharField(max_length=256)
    resources = models.ManyToManyField('oer.Resource', blank=True)
    image = models.ManyToManyField('media.Image', blank=True)
    views = models.IntegerField(editable=False, default=0)
    license = models.ForeignKey('license.License', default=get_default_license)
    slug = models.SlugField(max_length=256)
    difficulty = models.PositiveIntegerField(editable=True, default=0)
    published = models.NullBooleanField()
    citation = models.CharField(max_length=1024, blank=True, null=True)

Before adding list_display:

Django amdin before list_display

After adding list_display:

Django amdin after list_display

[UPDATE] This behaviour occurs only when ForeignKey fields are included in list_display tuple. Any of them.

[UPDATE] Category model code:

class Category(models.Model):
    title = models.CharField(max_length=256)
    parent = models.ForeignKey('self')
    project = models.NullBooleanField(default=False)
    created = models.DateTimeField(auto_now_add=True, editable=False)
    slug = models.SlugField(max_length=256, blank=True)

    def __unicode__(self):
        return self.title
Varun Arora
  • 332
  • 4
  • 8
  • 1
    does it show all the objects if you remove the `list_display` option altogether? – karthikr May 03 '13 at 17:15
  • @karthikr: Yes, it does – Varun Arora May 03 '13 at 17:49
  • can you post your models, and screen shots of what it is showing? – dm03514 May 03 '13 at 17:49
  • 2
    Is it possible that some articles don't have category set? – Peter DeGlopper May 03 '13 at 17:57
  • @PeterDeGlopper: I just checked all of them again; they all have it set. – Varun Arora May 03 '13 at 17:58
  • And if you remove category from the list_display tuple it goes back to showing all of them? – melwil May 03 '13 at 18:05
  • @melwil: Thanks for asking me to check that. I actually went a step ahead and realized that this is happening only when ForeignKey fields (with or without __unicode__ methods) are included in the tuple and not otherwise – Varun Arora May 03 '13 at 18:11
  • I'm not satisfied with the result though, the admin listing should have no trouble displaying models.ForeignKey fields. I use it all the time with User, for example. Could we see the Category code as well? – melwil May 03 '13 at 18:18
  • @melwil: I think my problem is a duplicate of http://stackoverflow.com/questions/8941857/django-admin-list-display-foreignkey-empty-change-list?lq=1. The same behavior is happening. Meanwhile, I have added the Category code for you. – Varun Arora May 03 '13 at 18:20
  • The solution to that question was based upon a migration from an older database, but if the suggested fix from there works for you, then I'm glad you got it fixed. – melwil May 03 '13 at 18:24
  • @melwil: Well, I did not migrate from an older database. But using forcefully using `null=True, blank=True` seems more of a hack than a solution. – Varun Arora May 03 '13 at 18:25
  • The fact that the selection_note text ("0 of 2 selected") does not agree with the pagination text (43 articles) but that the usual pagination links aren't shown is odd. What version of Django is this on? – Peter DeGlopper May 03 '13 at 18:26
  • @PeterDeGlopper: Exactly. 1.4.1. – Varun Arora May 03 '13 at 18:28
  • @VarunArora it's either that, or you go through each category and make sure all the fields adhere to their rules. The reason why adding null/blank=True is that it loosens the rules and lets admin display empty fields. – melwil May 03 '13 at 18:29
  • @melwil: I fear saying I just did that thrice right now, and everything seems to adhere (there are only ~10 records). Clearly something else is failing. I am using DDT, but I can't figure out what's happening with the queries – Varun Arora May 03 '13 at 18:43
  • I don't see why this would cause the problem you're seeing, but it caught my eye as I tried to replicate this - you probably do want to allow Category's parent field to be blank and null, unless you have a circular category structure. – Peter DeGlopper May 03 '13 at 18:48
  • @PeterDeGlopper: I have a circular Category structure :( – Varun Arora May 03 '13 at 18:52
  • I think you may have a Category that's missing a parent, then. I've been able to replicate this error by defining three categories, two of which have parents and one of which doesn't. Articles in the category that doesn't have a parent (when the model definition says the field cannot be blank, anyway) drop out of the list. – Peter DeGlopper May 03 '13 at 19:04
  • @PeterDeGlopper: Unfortunately, that is not the case (checked visually through PHPMyAdmin and through shell). One of the category had a parent as itself, but even changing that does not seem to solve the problem. – Varun Arora May 03 '13 at 19:20
  • @PeterDeGlopper: Your self-deleted response to use "list_select_related" did the trick. Although it takes a boolean value, and not a tuple – Varun Arora May 03 '13 at 19:35
  • Ok, that shouldn't work. You're right that it takes a boolean, that's why I deleted it. Setting list_set_related to True should force it to always look up related objects, making the undesired behavior consistent. And setting list_set_related to False still has it look up related objects if you have a FK field in list_display. – Peter DeGlopper May 03 '13 at 19:41
  • @PeterDeGlopper: I set it to True, and now it is consistently showing all objects. Which is what I want. – Varun Arora May 03 '13 at 19:48
  • I advise against dropping your investigation there - that flag should have no effect at all as long as category is in your list_display value. – Peter DeGlopper May 03 '13 at 19:55

1 Answers1

18

This behavior is caused by a foreign key relation somewhere that is not declared as nullable, but nonetheless has a null value in the database. When you have a ManyToOne relationship in list_display, the change list class will always execute the query using select_related. (See the get_query_set method in django.contrib.admin.views.ChangeList).

select_related by default follows all foreign keys on each object, so any broken foreign key found by this query will cause data to drop out when the query is evaluated. This is not specific to the admin; you can interactively test it by comparing the results of Article.objects.all() to Article.objects.all().select_related().

There's no simple way to control which foreign keys the admin will look up - select_related takes some parameters, but the admin doesn't expose a way to pass them through. In theory you could write your own ChangeList class and override get_query_set, but I don't recommend that.

The real fix is to make sure your foreign key model fields accurately reflect the state of your database in their null settings. Personally, I'd probably do this by commenting out all FKs on Article other than Category, seeing if that helps, then turning them back on one by one until things start breaking. The problem doesn't have to be with a FK on an article itself; if a revision, language or category has a broken FK that will still cause the join to miss rows. Or if something they relate to has a broken FK, etc etc.

Peter DeGlopper
  • 36,326
  • 7
  • 90
  • 83