Russell Keith-Magee wrote:
...
> That said, 'm2m with intermediate' is a relatively common use case, so
> if you have any neat ideas on how to represent such a structure, feel
> free to suggest them. The idea has been discussed before, but no
> obvious solution has emerged (the real sticking point is querying the
> intermediate table - check the archives for previous discussions).

Here's some brainstorming. This post is long, and it's all daydreaming.
I suspect someone else may have already come up with the same answer
and it doesn't work for some reason... Checked the archives, found some
relevant discussions, but not sure I found the right ones :-/

The problem is, how to add some attributes to a ManyToMany relation-
how to define them and use them. Django currently hides the
intermediate table, and that's good. It can stay hidden for the usual
use case, where all you want is a many-to-many relationship. I think,
when someone wants to add "fields" to the relationship, then it should
become visible, and also synthesize models with both relationship
attributes and table behavior.

How would it look?

Starting with defining half, the inner Meta class seems like a good
place to go:

class Reporter(models.Model):
    first_name = models.CharField(maxlength=30)
    last_name = models.CharField(maxlength=30)

class WritingRole(models.Model):
    responsibility = models.CharField(maxlength=30)
    description = models.CharField(maxlength=30)
    pay_scale = models.ForeignKey(PayScale)

class Article(models.Model):
    headline = models.CharField(maxlength=100)
    pub_date = models.DateField()
    writers = models.ManyToManyField(Reporter)

    # Here's a stab at 'm2m with intermediate' defining syntax
    class Meta:
      writers.actual_pay=models.SmallIntegerField() # We don't pay
much!
      writers.writing_role=models.ForeignKey(WritingRole)

That's it. Looks pretty clean, is easy to understand.

Maybe the "writers.<fields>' don't even go in the Meta subclass, maybe
they stay in the main class.

Recursion requires a little thought.
  # 'self' should always mean 'this model',Article in this case
  writers.similar_articles=models.ForiegnKey('self')
  # so spell out when you really want to self-relate
  writers.collusion_group=models.ForeignKey('Article.writers')
  # And someone will find a need for ManyToMany recursion!

How to use?

The old API doesn't change, things work as always
  art = Article.objects.get(pk=3)
  writers = art.writers.all() # as usual, gives all reporters
  writer1 = reps[1] # get a single reporter from the list
  print writer1.last_name # "Smith"
  written_things = writer1.article_set.all() # find all articles for
the reporter
  print written_things[0].headline # "Django Gets Useful Model
Enhancements"

Maybe you're wondering why I didn't call the variables "reporter" and
"article". I'm thinking that when you define "writers"- when you define
a relationship with attributes- you're saying that the model on the
other side has something extra, when viewed from this side. So, you get
a subclass of the foreign model.* It does all the things that the
related class does, plus lets you retreive the attributes for that
particular relation:

  writer1.actual_pay  # 0 if working for free!
  writer1.writing_role.responsibility  # "Muse"...
  # describe the role writer1 had when writing this article
  written_things[0].writing_role.description

Given a plain old Reporter object "reporter1":

  reporter1.writers_set.all() # gets all Article-plus objects for
writer
  reporter1.writers_set.get(headline='Foo') # may have the syntax a bit
off here- find Article-plus object with headline 'Foo'
  reporter1.writers_set.filter(writingrole__responsibility='Author') #
find Article-pluses for which reporter1 was the author.

* I realize this abstraction my be contentious- if you ask for a
Reporter you should get a Reporter and not some subclass Django creates
for you. That's my reaction too. It does seem cleaner to make a
'Writers' object of its own that behaves like a model with two foreign
key fields, and the extra attributes. Indeed Django should, so one can
make requests like:


Writers.objects.filter(reporter__last_name='Smith',actual_pay=0).count()
# how often do people named Smith work for free?

But then how would that abstraction work for other common cases?

  reps = art.writers.all() # really gives Reporters this time
  rep1 = reps[1] # get a single reporter from the list
  # We've lost the information we got traversing the relation!
  rep1.actual_pay  # gives an attribute error
  art.writers.actual_pay # what writer are we talking about?
  art.writers[1].actual pay # huh? Even if that syntax works to select
a single writer, 'art.writers' returns Reporters, which don't have
"actual_pay"
  Writers.objects.get(article=art,reporter=rep1).actual_pay # This
works. I think it hits the database again.

So, you can get something working with an auto-created class named
after the M2M relation (with an override to let user give it a
different name, perhaps). I do think that traversing such a
"relationship-with-extras" should give a subclass of the related
object: something that has behaviors and data from the related class,
and also the relationsip-with-attribute-class. It makes the app writers
job easier, and it doesn't break existing code, since existing code
doesn't have "relationship-with-extras" yet.

("relationship-with-extras" is fun!)


--~--~---------~--~----~------------~-------~--~----~
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 [EMAIL PROTECTED]
For more options, visit this group at 
http://groups.google.com/group/django-users
-~----------~----~----~----~------~----~------~--~---

Reply via email to