Implementing Many to Many

Peewee provides a field for representing many-to-many relationships, much like Django does. This feature was added due to many requests from users, but I strongly advocate against using it, since it conflates the idea of a field with a junction table and hidden joins. It’s just a nasty hack to provide convenient accessors.

To implement many-to-many correctly with peewee, you will therefore create the intermediary table yourself and query through it:

  1. class Student(Model):
  2. name = CharField()
  3. class Course(Model):
  4. name = CharField()
  5. class StudentCourse(Model):
  6. student = ForeignKeyField(Student)
  7. course = ForeignKeyField(Course)

To query, let’s say we want to find students who are enrolled in math class:

  1. query = (Student
  2. .select()
  3. .join(StudentCourse)
  4. .join(Course)
  5. .where(Course.name == 'math'))
  6. for student in query:
  7. print(student.name)

To query what classes a given student is enrolled in:

  1. courses = (Course
  2. .select()
  3. .join(StudentCourse)
  4. .join(Student)
  5. .where(Student.name == 'da vinci'))
  6. for course in courses:
  7. print(course.name)

To efficiently iterate over a many-to-many relation, i.e., list all students and their respective courses, we will query the through model StudentCourse and precompute the Student and Course:

  1. query = (StudentCourse
  2. .select(StudentCourse, Student, Course)
  3. .join(Course)
  4. .switch(StudentCourse)
  5. .join(Student)
  6. .order_by(Student.name))

To print a list of students and their courses you might do the following:

  1. for student_course in query:
  2. print(student_course.student.name, '->', student_course.course.name)

Since we selected all fields from Student and Course in the select clause of the query, these foreign key traversals are “free” and we’ve done the whole iteration with just 1 query.

ManyToManyField

The ManyToManyField provides a field-like API over many-to-many fields. For all but the simplest many-to-many situations, you’re better off using the standard peewee APIs. But, if your models are very simple and your querying needs are not very complex, ManyToManyField may work.

Modeling students and courses using ManyToManyField:

  1. from peewee import *
  2. db = SqliteDatabase('school.db')
  3. class BaseModel(Model):
  4. class Meta:
  5. database = db
  6. class Student(BaseModel):
  7. name = CharField()
  8. class Course(BaseModel):
  9. name = CharField()
  10. students = ManyToManyField(Student, backref='courses')
  11. StudentCourse = Course.students.get_through_model()
  12. db.create_tables([
  13. Student,
  14. Course,
  15. StudentCourse])
  16. # Get all classes that "huey" is enrolled in:
  17. huey = Student.get(Student.name == 'Huey')
  18. for course in huey.courses.order_by(Course.name):
  19. print(course.name)
  20. # Get all students in "English 101":
  21. engl_101 = Course.get(Course.name == 'English 101')
  22. for student in engl_101.students:
  23. print(student.name)
  24. # When adding objects to a many-to-many relationship, we can pass
  25. # in either a single model instance, a list of models, or even a
  26. # query of models:
  27. huey.courses.add(Course.select().where(Course.name.contains('English')))
  28. engl_101.students.add(Student.get(Student.name == 'Mickey'))
  29. engl_101.students.add([
  30. Student.get(Student.name == 'Charlie'),
  31. Student.get(Student.name == 'Zaizee')])
  32. # The same rules apply for removing items from a many-to-many:
  33. huey.courses.remove(Course.select().where(Course.name.startswith('CS')))
  34. engl_101.students.remove(huey)
  35. # Calling .clear() will remove all associated objects:
  36. cs_150.students.clear()

Attention

Before many-to-many relationships can be added, the objects being referenced will need to be saved first. In order to create relationships in the many-to-many through table, Peewee needs to know the primary keys of the models being referenced.

Warning

It is strongly recommended that you do not attempt to subclass models containing ManyToManyField instances.

A ManyToManyField, despite its name, is not a field in the usual sense. Instead of being a column on a table, the many-to-many field covers the fact that behind-the-scenes there’s actually a separate table with two foreign-key pointers (the through table).

Therefore, when a subclass is created that inherits a many-to-many field, what actually needs to be inherited is the through table. Because of the potential for subtle bugs, Peewee does not attempt to automatically subclass the through model and modify its foreign-key pointers. As a result, many-to-many fields typically will not work with inheritance.

For more examples, see: