Relacionamento Muitos-para-Muitos (Many-to-Many) no Django
O relacionamento Muitos-para-Muitos ocorre quando um registro de uma entidade pode estar associado a múltiplos registros de outra entidade e vice-versa. Um exemplo clássico é a relação entre estudantes e cursos, onde um estudante pode se inscrever em vários cursos, e um curso pode ter vários estudantes matriculados.
No Django, esse tipo de relação é representado pelo campo ManyToManyField
from django.db import models
class Course(models.Model):
name = models.CharField(max_length=30)
class Student(models.Model):
name = models.CharField(max_length=30)
courses = models.ManyToManyField(Course)
O campo ManyToManyField
Student
Course
Após a migração, um banco SQLite criaria as seguintes tabelas:
CREATE TABLE "hello_course" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"name" varchar(30) NOT NULL
);
CREATE TABLE "hello_student" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"name" varchar(30) NOT NULL
);
CREATE TABLE "hello_student_courses" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"student_id" bigint NOT NULL REFERENCES "hello_student" ("id") DEFERRABLE INITIALLY DEFERRED,
"course_id" bigint NOT NULL REFERENCES "hello_course" ("id") DEFERRABLE INITIALLY DEFERRED
);
A tabela hello_student_courses
Student
Course
Operações com Models
O campo courses
Student
# Criando um estudante
tom = Student.objects.create(name="Tom")
# Criando um curso e associando-o ao estudante
tom.courses.create(name="Algebra")
# Obtendo todos os cursos de um estudante
courses = Student.objects.get(name="Tom").courses.all()
# Obtendo todos os estudantes matriculados em um curso específico
students = Student.objects.filter(courses__name="Algebra")
No último exemplo, o filtro usa courses__name
courses
ManyToManyField
name
Course
campo__atributo
Se for necessário obter todos os estudantes matriculados em um curso, o Django oferece o related manager_set
# Criando um curso
python = Course.objects.create(name="Python")
# Criando um estudante e associando-o ao curso
python.student_set.create(name="Bob")
# Criando um estudante separadamente e associando-o ao curso
sam = Student.objects.create(name="Sam")
python.student_set.add(sam)
# Obtendo todos os estudantes do curso
students = python.student_set.all()
# Contando os estudantes de um curso
number = python.student_set.count()
# Removendo um estudante do curso
python.student_set.remove(sam)
# Removendo todos os estudantes do curso
python.student_set.clear()
O related manager _set
Definição de uma Model Intermediária
A estrutura acima funciona bem para casos simples, mas pode ser necessário armazenar dados adicionais na relação, como a data de inscrição ou uma nota. O Django permite definir uma model intermediária personalizada para representar essa relação com atributos adicionais.
from django.db import models
class Course(models.Model):
name = models.CharField(max_length=30)
class Student(models.Model):
name = models.CharField(max_length=30)
courses = models.ManyToManyField(Course, through="Enrollment")
class Enrollment(models.Model):
student = models.ForeignKey(Student, on_delete=models.CASCADE)
course = models.ForeignKey(Course, on_delete=models.CASCADE)
date = models.DateField() # Data de inscrição
mark = models.IntegerField() # Nota obtida
Agora, a relação entre Student
Course
Enrollment
date
mark
Após a migração, um banco SQLite criaria as seguintes tabelas:
CREATE TABLE "hello_course" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"name" varchar(30) NOT NULL
);
CREATE TABLE "hello_student" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"name" varchar(30) NOT NULL
);
CREATE TABLE "hello_enrollment" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"date" date NOT NULL,
"mark" integer NOT NULL,
"course_id" bigint NOT NULL REFERENCES "hello_course" ("id") DEFERRABLE INITIALLY DEFERRED,
"student_id" bigint NOT NULL REFERENCES "hello_student" ("id") DEFERRABLE INITIALLY DEFERRED
);
Agora vamos utilizar a model intermediária para criar registros e acessar informações sobre os estudantes e cursos:
from datetime import date
# Criando um curso
python = Course.objects.create(name="Python")
# Criando dois estudantes
tom = Student.objects.create(name="Tom")
sam = Student.objects.create(name="Sam")
# Criando registros na model intermediária
Enrollment.objects.create(student=tom, course=python, date=date.today(), mark=5)
Enrollment.objects.create(student=sam, course=python, date=date.today(), mark=4)
# Obtendo todos os cursos de Tom
tom_courses = tom.courses.all()
print(tom_courses[0].name) # Python
# Obtendo todos os estudantes matriculados no curso Python
python_students = python.student_set.all()
print(python_students[0].name) # Tom
Métodos de Relacionamento
O Django permite utilizar os métodos add()
create()
set()
through_defaults
# Criando cursos
django = Course.objects.create(name="Django")
python = Course.objects.create(name="Python")
java = Course.objects.create(name="Java")
# Criando um estudante
bob = Student.objects.create(name="Bob")
# Associando um curso ao estudante com dados adicionais
bob.courses.add(django, through_defaults={"date": date.today(), "mark": 5})
# Criando um curso e associando-o ao estudante
bob.courses.create(name="C++", through_defaults={"date": date.today(), "mark": 4})
# Obtendo todos os cursos de Bob
print(bob.courses.all().values_list())
Os métodos remove()
clear()
# Obtendo estudantes do curso Python com nota 4 ou menor
students = Student.objects.filter(
courses__name="Python",
enrollment__mark__lte=4
)
print(students.values_list())