Implementing Many to Many

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

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

  1. class Student(Model):
  2. name = CharField()
  3.  
  4. class Course(Model):
  5. name = CharField()
  6.  
  7. class StudentCourse(Model):
  8. student = ForeignKeyField(Student)
  9. 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.  
  7. for course in courses:
  8. print(course.name)

To efficiently iterate over a many-to-many relation, i.e., list all studentsand their respective courses, we will query the through modelStudentCourse 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 thewhole iteration with just 1 query.

ManyToManyField

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

Modeling students and courses using ManyToManyField:

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

Attention

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

Warning

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

A ManyToManyField, despite its name, is not a field in theusual sense. Instead of being a column on a table, the many-to-many fieldcovers the fact that behind-the-scenes there’s actually a separate tablewith 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 thepotential for subtle bugs, Peewee does not attempt to automaticallysubclass the through model and modify its foreign-key pointers. As aresult, many-to-many fields typically will not work with inheritance.

For more examples, see: