[Answered ]-Can I define fields on an implicit many-to-many relationship?

1đź‘Ť

As far as I know, you are not able to do that.

The reason for that is that it is nowhere to store the extra fields of that relationship. If I understand you correctly, these “extra fields” are not implicit in the actor-film or productionstudio-film relationships, so even though you say they are implicit, the extra fields themselves are explicit.

You could try to emulate it by creating an explicit direct relationship whenever it is needed. Then you could simulate the extra fields using the model as an abstraction, but I am not sure if this is what you want. If you opt for this kind of solution you can use default values (in your abstraction) for filling in relationships that don’t have a instance yet.

Does that explanation make sense to you?

EDIT:
(I have not double checked that the code works, so be vary)
OK, so you have the original models:

class FilmStudio(models.Model):
    name = models.CharField(max_length=200, unique=True)


class ProductionCompany(models.Model):
    name = models.CharField(max_length=200)
    film_studio = models.ForeignKey(FilmStudio)

    class Meta:
        # Ensure that a given combination of ProductionCompany name and FilmStudio object is unique
        unique_together = ('name', 'film_studio')


class Film(models.Model):
    title = models.CharField(max_length=200)
    production_company = models.ForeignKey(ProductionCompany)


class Actor(models.Model):
    name = models.CharField(max_length=200)
    films = models.ManyToManyField(Film, blank=True)

# The "solution" would be:

class ActorProductionComapny(models.Model):
    production_company = models.ForeignKey(ProductionCompany, related_name='actors')
    actor = models.ForeignKey(Actor, related_name='companies')
    # your extra fields here
    someproperty = models.CharField(max_length=200)

    class Meta:
        # let's say one per actor
        unique_together = ('production_company', 'actor')

This is going to get messy really quickly

We use a F object like this:

FilmStudio.objects.filter(productioncompany__film__actor__name='Samuel L. Jackson', 
                          productioncompany__film__actor=F('actors__actor'),
                          actors__someproperty="Plays poker with CEO").distinct()

The tricky part is going to be handling default values (i.e. when there is no value) This would have to be implemented using a custom Manager, but then I am out of my depth.

I will try to explain as well as I can, but it’s going to be tricky.

If you want to make a filter on the relationship you may have to do something like this:

def filter_prod(pq_query, someproperty, actor_name):
if someproperty == "Default":
    # Great, this means we can ignore the parallel relationship:
    return pq_query.filter(productioncompany__film__actor__name=actor_name)
else:
    # Here comes the hard part
    FilmStudio.objects.filter(productioncompany__film__actor__name=actor_name, 
                              productioncompany__film__actor=F('actors__actor'),
                              actors__someproperty=someproperty).distinct()

The thing I am trying to illustrate here is that there are two kinds of actor-productioncompany relationships, those with custom field values (non-default), and those without.

Now, you can make a custom getter that looks something like this:

class ProductionCompany(models.Model):
    name = models.CharField(max_length=200)
    film_studio = models.ForeignKey(FilmStudio)

    def get_actors(self):
        # This one is not lazy, so be aware
        actors = list(self.actors)
        # Get a list of actor IDs
        actor_ids = [a.actor_id for a in actors]
        for actor in Actor.objects.filter(films__production_company_id=self.id):
            if actor.id not in actor_ids:
                actors.append(ActorProductionComapny(actor=actor, production_company=self)
                actor_ids.append(actor.id)
        return actors

    class Meta:
        # Ensure that a given combination of ProductionCompany name and FilmStudio object is unique
        unique_together = ('name', 'film_studio')

This should not save the relationship to the database until you call .save() on an instance. You can also add a custom save method that ignores/aports .save() calls where all the values are default. Just remember to check if it is a new instance or not, because you don’t want it to cancel a “set back to default” call. You could also make it delete on a “set back to default”, but check if you are allowed to do that within .save().

For even more complex queries (mix of default and non-default) you have Q-objects (further down on the page from F objects)

👤CVi

1đź‘Ť

In short, you need to create an extra model to store this extra relational data between Actor and FilmStudio.

class Actor(models.Model):
     name = models.CharField(max_length=200)
     films = models.ManyToManyField(Film, blank=True)
     film_studios = models.ManyToMany(FilmStudio, through='ActorFilmStudio')

class ActorFilmStudio(models.Model):
    actor = models.ForeignKey(Actor)
    film_studio = models.ForeignKey(FilmStudio)

    # define extra data fields here
    data1 = models.TextField()
    data2 = models.IntegerField()

One way to think about this: the data you’re trying to store belongs to an Actor-FilmStudio relation, and is not related in anyway to Film or ProductionCompany.

Your existing ability to retrieve the a set of Actors for a given FilmStudio (or vice-versa) does not necessarily imply you can store relational data belonging to these two models using the models defined in your example.

Keep in mind that each of the models you defined in your example are backed by a table in your database. In the case of Actor.films field, Django creates an extra table to store the many-to-many relationship data.

Since you’re looking to store relational data between Actor and FilmStudio, you need to consider where the data will be stored in your database. Can you store the data in the Film model? or the ProductionCompany model?

👤Derek Kwok

Leave a comment