Relaciones many to many: conceptos y fundamentos.
Giovani Fouz
Posted on May 17, 2024
Al crear una aplicación con Django u otro framework, a menudo te encuentras con situaciones en las que necesitas establecer relaciones entre modelos. Un tipo común de relación es la relación de muchos a muchos
(M2M).
En este artículo, exploraremos qué son las relaciones M2M, cuándo usarlas y cómo implementarlas en
Django y Django Ninja.
¿Qué son las relaciones de muchos a muchos?
En una relación tradicional uno a uno (1:1) o uno a muchos (1:N), cada registro de una tabla corresponde
exactamente a un registro de otra tabla. Por el contrario, una relación de muchos a muchos (M2M) es un
tipo de relación en la que un registro de una tabla se puede asociar con varios registros de otra tabla y
viceversa. A esto se le suele denominar tabla de "puente" o "unión".
Cuándo utilizar relaciones de muchos a muchos
Hay varios escenarios en los que es posible que desee utilizar relaciones M:N
Etiquetado o categorización : cuando tienes un modelo que se puede asociar con múltiples categorías o
etiquetas, como una publicación de blog que se puede etiquetar con múltiples temas.Roles o permisos de usuario : cuando necesita asignar múltiples roles o permisos a un usuario, como
administrador, moderador o colaborador.Asociaciones de muchos a muchos : cuando necesita establecer relaciones entre múltiples modelos,
como un libro que se puede asociar con múltiples autores y múltiples géneros.
Cómo implementar relaciones de muchos a muchos utilizando Django?
Para implementar una relación M:N en Django, necesitarás crear dos modelos y luego usar la clase
ManyToManyField en el archivo de modelos.
He aquí un ejemplo:
# models.py
from django.db import models
from django.utils.text import slugify
from django.contrib.auth.models import User
class Category(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
# return self.name
return self.name
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
slug = models.SlugField(blank=True)
image_src = models.SlugField(blank=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
categories = models.ManyToManyField(Category, blank=True)
def save(self, *args, **kwargs):
words = self.title.split() # Split the title into words
first_two_words = " ".join(words[:2]) # Select the first two words
formatted_title = first_two_words.replace(" ", "_") # Replace spaces with
self.slug = slugify(self.title.replace(" ", "-"))
self.image_src = slugify(formatted_title)
super().save(*args, **kwargs)
def __str__(self):
return self.title
En este caso, Django creará automáticamente una tabla intermedia que se encargará de almacenar las
relaciones entre los usuarios y sus roles. Podemos entonces acceder a las relaciones utilizando el
método categories en el modelo Post.
Al utilizar la clase ManyToManyField evita la creación de una tabla intermedia adicional, lo que puede mejorar el rendimiento y la escalabilidad.
Proporciona una forma más fácil y concisa de definir relaciones muchos a muchos.
Permite acceder a las relaciones utilizando métodos naturales o (built-in) en los modelos.
En resumen, la elección entre utilizar el mecanismo automático de Django o crear una tabla intermedia
manualmente depende del proyecto y las necesidades específicas. Es importante considerar las ventajas y
desventajas de cada opción antes de tomar una decisión.
Siempre que sea posible es más simple utilizar ManyToManyField en lugar de una tabla intermedia cuando se trata de una relación muchos a muchos en Django.
https://docs.djangoproject.com/en/3.2/ref/models/fields/#manytomanyfield
Ejemplo de creación de una tabla intermedia:
# models.py
from django.db import models
class IntermediaryModel(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
is_released = models.BooleanField()
#...
Ya con los modelos creados podemos utilizarlos para crear registros
en nuestra base de datos e implementar una API en este caso con Django
Ninja veamos un ejemplo:
Primero creamos los esquemas en un archivo que convencionalmente se llama
schemas.py
from ninja import ModelSchema, Schema
from posts.models import Post, Category
from django.contrib.auth.models import User
from datetime import datetime
class ErrorMessage(Schema):
message: str
class UserSchema(ModelSchema):
class Meta:
model = User
fields = ["id", "username"]
# exclude = ["last_login", "user_permissions"]
class CategorySchema(ModelSchema):
class Meta:
model = Category
fields = ["id", "name"]
class PostSchema(ModelSchema):
author: UserSchema
categories: CategorySchema
class Meta:
model = Post
fields = ["id", "title", "slug", "content", "categories"]
class CategoryFullSchema(Schema):
id: int
name: str
class PostFullSchema(Schema):
id: int
title: str
slug: str
image_src: str
content: str
author: UserSchema
categories: list[CategoryFullSchema]
created_at: datetime = datetime.now()
class PostUpdateSchema(Schema):
title: str
content: str
class PostCreateSchema(Schema):
title: str
content: str
author_id: int = None
categories: list[str]
Luego de crear los esquemas entonces creamos otro archivo que suele llamarse
api.py
from ninja import Router, NinjaAPI
from ninja.responses import Response
from django.shortcuts import get_object_or_404
from django.contrib.auth.models import User
from posts.models import Post, Category
from ninja.security import HttpBearer
from django.conf import settings
from posts.schemas import PostSchema, PostUpdateSchema, UserSchema
from posts.schemas import PostFullSchema, PostCreateSchema
from posts.schemas import CategorySchema, CategoryFullSchema
from posts.schemas import ErrorMessage
@router.get("/get/single/post/{post_id}", response={200: PostFullSchema})
def get_post(request, post_id: int):
try:
post = Post.objects.get(id=post_id)
return post
except Post.DoesNotExist:
raise HttpError(404, "Not found!.")
@router.get("/postlist/", response={200: list[PostFullSchema]})
def get_all_posts(request):
print(Post.objects.all())
return Post.objects.all()
@router.post("/create/post-category", response={200: PostFullSchema, 400: ErrorMessage})
def create_post_and_category(request, payload: PostCreateSchema):
categories = []
try:
if Post.objects.filter(title=payload.title).exists():
return 400, {"message": "title already exists"}
except:
raise HttpError(400, {"message": "title already exists"})
for category_name in payload.categories:
if not Category.objects.filter(name=category_name).exists():
Category.objects.create(name=category_name)
else:
category = Category.objects.get(name=category_name)
categories.append(category)
post = Post.objects.create(
title=payload.title, content=payload.content, author=User(pk=payload.author_id)
)
post.categories.add(*categories)
return post
Puede tomar como punto de partida el ejemplo anterior y ajustarlo
a sus necesidades.
En el mundo de la programación, las relaciones entre objetos son fundamentales para representar y
manipular datos. En este sentido, las relaciones muchos a muchos (M:N) son especialmente útiles cuando se
necesitan representar conexiones entre objetos que pueden tener múltiples asociaciones
En conclusión, las relaciones de muchos a muchos son una herramienta poderosa que permite establecer relaciones complejas entre modelos. Si comprende cuándo utilizarlos y cómo implementarlos, podrá crear aplicaciones más sólidas y escalables.
Espero que este artículo haya sido útil. Si tiene alguna pregunta o desea obtener más información sobre este tema, ¡no dude en preguntar!
Posted on May 17, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.