1

I'm new to django and I'm trying to concatenate different querysets from different models based on this answer here: https://stackoverflow.com/a/434755/4465450

I keep getting this error when I try to view it in my admin.

'list' object has no attribute 'iterator'

I'm calling it in my forms.py

 from django import forms
 from itertools import chain
 from content.models import Article, Tutorial

 class DashboardChoiceForm(forms.Modelform):
     dashboard_select = forms.ModelChoiceField(queryset=None)

     def __init__(self, *args, **kwargs):
         super(DashboardChoiceForm, self).__init__(*args, **kwargs)
         article_list = Article.objects.all()
         tutorial_list = Tutorial.objects.all()
         self.fields['dashboard_select'].queryset = list(chain(article_list, tutorial_list))

And I'm including it on my admin page like so:

from django.contrib import admin
from .models import *
from .forms import *

class RowSingleAdmin(admin.ModelAdmin):
    model = RowSingle
    form = DashboardChoiceForm

What I'm trying to do is create a dropdown on the RowSingle admin so that a user can select one content item (either article or tutorial) to display in that row.

Community
  • 1
  • 1

1 Answers1

2

This line won't work:

self.fields['dashboard_select'].queryset = list(chain(article_list, tutorial_list))

The problem does not come from the right part, but from the left. The ModelChoiceField expects queryset to be an actual queryset, not a list.

If you want to supply a list, you should use another field type, namely a ChoiceField. Then you will need to assign the choices. They must be a list of 2-tuples, where first item is the actual value and the second item is the displayed text.

It becomes a bit tricky as the actual value must be a string, or something that can be converted to a string. For instance, we could do something like "article_<pk>" for articles and "tutorial_<pk>" for tutorials.

choices = chain(
    ('article_%d' % obj.pk, str(obj)) for obj in article_list),
    ('tutorial_%d' % obj.pk, str(obj)) for obj in tutorial_list),
)
self.fields['dashboard_select'].choices = choices

You may replace the str(obj) with whatever is a sensible way to display the object to the user.

And then when your form is validated you will get the value, which you should parse back:

from django.core.exceptions import ObjectDoesNotExist, ValidationError

# on your form class
def clean_dashboard_select(self):
    data = self.cleaned_data['dashboard_select'] # will be, eg: "article_42"
    try:
        typename, value = data.split('_')
        pk = int(value)
    except ValueError:
        raise ValidationError('cannot understand %s' % data)
    try:
        if typename == 'article':
            return Article.objects.get(pk=pk)
        if typename == 'tutorial':
            return Tutorial.objects.get(pk=pk)
    except ObjectDoesNotExist:
        raise ValidationError('No %s with pk %d' % (typename, pk))
    raise ValidationError('Never heard of type %s' % typename)

Edit (from comment) — to add section groups, change the choices like this:

choices = (
    ('Articles', tuple(('article_%d' % obj.pk, str(obj)) for obj in article_list)),
    ('Tutorials', tuple(('tutorial_%d' % obj.pk, str(obj)) for obj in tutorial_list)),
)
spectras
  • 13,105
  • 2
  • 31
  • 53
  • Thank you so much for taking the time to explain this to me! However, instead of displaying 'article_%d', it shows the unicode that I set up in the models. I'm okay with that, but is there an explanation for that? Also, is it possible to add headers to each section before the choices? Like a heading of 'Article' and then all of the article selections, then a heading of 'Tutorial,' etc. – mashedpotatoes Aug 24 '15 at 18:31
  • Well, you see the `chain` call with the two iterations? On the left of the line, it builds a tuple of 2 items. First item will be 'article_%d', it is not shown to the user, it's the internal value that you will get back for parsing. Second item is what to display. In the example I put `str(obj)`, which will use the model's `__unicode__`/`__str__` but you can replace that with whatever you like. – spectras Aug 24 '15 at 18:44
  • In the end, whatever you set on `choices` must follow the format specified in [the documentation](https://docs.djangoproject.com/en/1.8/ref/models/fields/#django.db.models.Field.choices). Have a look there, it is explained how to have section headings. – spectras Aug 24 '15 at 18:48
  • I'm running into some more trouble. I looked at the documentation and I'm trying to set up headers like this: `choices = chain(('Articles', (('article_%d' % obj.pk, str(obj) for obj in article_list))),)` But I keep getting this error: `('article_%d' % obj.pk, str(obj) for obj in article_list) SyntaxError: invalid syntax` When I add more parentheses to group: `(('article_%d' % obj.pk, str(obj)) for obj in article_list)` Nothing shows up in the choice form. – mashedpotatoes Aug 24 '15 at 19:56
  • I appended something on section titles to the answer above ^^ – spectras Aug 25 '15 at 02:59