Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django admin foreign key dropdown with custom value

I have 3 Django models:

class Test(models.Model):
    pass

class Page(models.Model):
    test = models.ForeignKey(Test)

class Question(model.Model):
    page = models.ForeignKey(Page)

If I register the Question model to the admin, I get a dropdown with the desired Page. Now, what do I have to modify to display the desired Page plus that page's corresponding Test?

Say, if I have three pages created, the dropdown will contain these values: Page1, Page2, Page3. I would like to see: Page1 - Test1, Page2 - Test1, Page3 - Test1

like image 286
linkyndy Avatar asked Oct 24 '13 09:10

linkyndy


2 Answers

For some reason Option B in the accepted answer didn't work for me so I figured I'd update this page with what worked well for me.

You can overload the __str__ function for the Page model to get what you're wanting. So, something along the lines of this

class Page(models.Model):
   test = models.ForeignKey(Test)
   def __str__(self):
      return f'Page{self.pk} - Test{self.test.pk}'
like image 117
Tanner Lyman Avatar answered Sep 17 '22 21:09

Tanner Lyman


2 Options.

Option 1:

Create a new field, copy forms.ModelChoiceField and override label_from_instance.

# From the source
class PageModelChoiceField(forms.ModelChoiceField():
    def label_from_instance(self, obj):
        """
        This method is used to convert objects into strings; it's used to
        generate the labels for the choices presented by this object. Subclasses
        can override this method to customize the display of the choices.
        """
        # Then return what you'd like to display
        return "Page{0} - Test{1}".format(obj.pk, obj.test.pk)

This will only change the text for that particular dropdown field. As you are accessing the Test object for each item in the list, you may want to ensure the queryset you pass to the PageModelChoiceField has select_related('test'), otherwise it will make a DB hit for each item on the list.

I've not tested this exact code but the logic is there. Will try it later when I can

class QuestionForm(forms.ModelForm):

    page = PageModelChoiceField(
        queryset=Page.objects.select_related('test').all()
    )

    class Meta:
        model = Page


class QuestionAdmin(ModelAdmin):
    class Meta:
        model = Question
        form = QuestionForm

Option B.

Change the unicode() representation of Page.

class Page(models.Model):
    test = models.ForeignKey(Test)

    def __unicode__(self):
        return "Page{0} - Test{1}".format(obj.pk, obj.test.pk)

This will change how Pages are displayed everywhere you print a page object, print(page_object), {{ page_object }}.


Personally I prefer Option 1

like image 36
rockingskier Avatar answered Sep 19 '22 21:09

rockingskier