tags:

views:

20

answers:

1

Hi,

We have a Django app which contains a list of newspaper articles. Each article has a m2m relationship with both a "spokesperson", as well as a "firm" (company mentioned in the article).

At the moment, the Add Article page for creating new Articles is quite close to what we want - it's just the stock Django Admin, and we're using filter_horizontal for setting the two m2m relationships.

The next step was to add a "rating" field as an intermediary field on each m2m relationship.

So, an example of our models.py

class Article(models.Model):
    title = models.CharField(max_length=100)
    publication_date = models.DateField()
    entry_date = models.DateField(auto_now_add=True)
    abstract = models.TextField() # Can we restrict this to 450 characters?
    category = models.ForeignKey(Category)
    subject = models.ForeignKey(Subject)
    weekly_summary = models.BooleanField(help_text = 'Should this article be included in the weekly summary?')
    source_publication = models.ForeignKey(Publication)
    page_number = models.CharField(max_length=30)
    article_softcopy = models.FileField(upload_to='article_scans', null=True, blank=True, help_text='Optionally upload a soft-copy (scan) of the article.')
    url = models.URLField(null=True, blank=True, help_text = 'Enter a URL for the article. Include the protocl (e.g. http)')
    firm = models.ManyToManyField(Firm, null=True, blank=True, through='FirmRating')
    spokesperson = models.ManyToManyField(Spokeperson, null=True, blank=True, through='SpokespersonRating')

    def __unicode__(self):
        return self.title

class Firm(models.Model):
    name = models.CharField(max_length=50, unique=True)
    homepage = models.URLField(verify_exists=False, help_text='Enter the homepage of the firm. Include the protocol (e.g. http)')

    def __unicode__(self):
        return self.name

    class Meta:
        ordering = ['name']

class Spokeperson(models.Model):
    title = models.CharField(max_length=100)
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)

    def __unicode__(self):
        return self.first_name + ' ' + self.last_name

    class Meta:
        ordering = ['last_name', 'first_name']

class FirmRating(models.Model):
    firm = models.ForeignKey(Firm)
    article = models.ForeignKey(Article)
    rating = models.IntegerField()

class SpokespersonRating(models.Model):
    firm = models.ForeignKey(Spokesperson)
    article = models.ForeignKey(Article)
    rating = models.IntegerField()

The issue here is that once we change our Firm and Spokesperson field to "through" and use intermediaries, our Add Article page no longer has a filter_horizontal control to add Firms/Spokeperson relationships to the Article - they completely disappear. You can't see them at all. I have no idea why this is.

I was hoping for there to be some way to keep using the cool filter_horizontal widget to set the relationship, and somehow just embed another field below that for setting the rating. However, I'm not sure how to do this whilst still leveraging off the Django admin.

I saw a writeup here about overriding a single widget in Django admin:

http://www.fictitiousnonsense.com/archives/22

However, I'm not sure if that method is still valid, and I'm not sure about applying it to here, with a FK to a intermediary model (it's basically an inline then?).

Surely there's an easy way of doing all this?

Cheers, Victor

A: 

The problem is that the admin's method formfield_for_manytomany in django.contrib.admin.options doesn't return a form field for manytomany fields with an intermediary model! http://code.djangoproject.com/browser/django/trunk/django/contrib/admin/options.py#L157

You would have to override this method in your ModelAdmin:

def formfield_for_manytomany(self, db_field, request=None, **kwargs):
    """
    Get a form Field for a ManyToManyField.
    """
    # If it uses an intermediary model that isn't auto created, don't show
    # a field in admin.
    if not db_field.rel.through._meta.auto_created:
        return None    # return something suitable for your needs here!
    db = kwargs.get('using')

    if db_field.name in self.raw_id_fields:
        kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel, using=db)
        kwargs['help_text'] = ''
    elif db_field.name in (list(self.filter_vertical) + list(self.filter_horizontal)):
        kwargs['widget'] = widgets.FilteredSelectMultiple(db_field.verbose_name, (db_field.name in self.filter_vertical))
lazerscience
Hmm, so I need to override this method somehow, in a generalised way that doesn't break anything? Urgh, my knowledge of Django is a bit lacking here. How do people normally edit intermediary models with the Django admin?
victorhooi
You only have to override it for your field, you can put something like `if db_field.name == 'my_field` there!
lazerscience