Atualizado: 18/01/2024

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 define a relação entre Student e Course. Internamente, o Django cria uma tabela intermediária para armazenar as associações entre as duas entidades.

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 é a tabela intermediária que estabelece a relação entre Student e Course.

Operações com Models

O campo courses na model Student permite acessar e gerenciar os cursos associados a um estudante.

# 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, onde courses representa o campo ManyToManyField, e name é um atributo da model Course. A sintaxe campo__atributo com dois sublinhados permite buscar registros com base em atributos da entidade relacionada.

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 permite adicionar, modificar e remover relações entre as entidades diretamente a partir da model principal.

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 e Course é mantida na model Enrollment, que também contém os atributos date e 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() e set() para definir relações com models intermediárias. No entanto, quando há atributos adicionais, é necessário fornecer valores para os campos da model intermediária através do parâmetro 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() e clear() podem ser usados para remover relações entre as entidades:

# 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())
Política de Privacidade

Copyright © www.programicio.com Todos os direitos reservados

É proibida a reprodução do conteúdo desta página sem autorização prévia do autor.

Contato: programicio@gmail.com