Naturalmente el modelo de base de datos de nuestras aplicaciones suele contener múltiples tablas que se relacionan entre ellas. Estas relaciones no siempre son sencillas de definir, a veces un modelo puede relacionarse de forma opcional con otro.
Por ejemplo supongamos que estamos desarrollando una aplicación para realizar Giveaways y sortear entre los participante de cada sorteo distintos tipos de premios como smartphones, gaming laptops, viajes, estadías en hoteles.
Lógicamente los atributos para describir un smartphone son muy distintos a los atributos de un viaje o una estadía en un hotel, por lo que tendríamos un modelo para cada uno de estos, pero a su vez todos son premios y un giveaway puede sortear distintos tipos de premios a la vez.
Para solucionar este problema Django cuenta con GenericForeignKey, para establecer relaciones genéricas entre los modelos de nuestra aplicación.
Veamos cómo funciona continuando con el ejemplo de nuestra aplicación para giveaways.
Luego puedes ver todo el código de este tutorial en este repositorio.
Primero definimos los modelos para los Participantes, los distintos tipos de premios y los Giveaway.
class Competitor(models.Model):
ig_username = models.CharField(max_length=50, unique=True)
name = models.CharField(max_length=50)
email = models.EmailField(max_length=254)
class Meta:
verbose_name = 'Competitor'
verbose_name_plural = 'Competitors'
def __str__(self):
return f'{self.ig_username}'
class Article(models.Model):
name = models.CharField(max_length=50)
model = models.CharField(max_length=50)
description = models.TextField()
value = models.FloatField()
class Meta:
verbose_name = 'Article'
verbose_name_plural = 'Articles'
def __str__(self):
return f'{self.id} - {self.name}'
class DayPassResort(models.Model):
name = models.CharField(max_length=100)
resort = models.CharField(max_length=100)
description = models.TextField()
address = models.TextField()
date = models.DateField()
class Meta:
verbose_name = 'Day Pass Resort'
verbose_name_plural = 'Day Pass Resort'
def __str__(self):
return f'{self.id} - {self.resort}'
class GiveAway(models.Model):
name = models.CharField(max_length=100, unique=True)
description = models.TextField()
date = models.DateField()
number_of_winners = models.PositiveIntegerField()
class Meta:
verbose_name = 'GiveAway'
verbose_name_plural = 'GiveAway'
def __str__(self):
return f'{self.name}'
Ahora para almacenar los ganadores de un giveaway necesitamos otro modelo que almacene la relación entre un participante, un giveaway y un premio.
Ya que existen distintos tipos de premios necesitamos una relación genérica para ellos en el modelo. Para definir esta relación recomiendo utilizar django-gfklookupwidget, un paquete que nos ayuda a gestionar de forma sencilla este tipo de relaciones desde el admin de Django.
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey
import gfklookupwidget.fields as gfklookup_fields
class GiveAwayWinner(models.Model):
competitor = models.ForeignKey(Competitor, on_delete=models.CASCADE)
giveaway = models.ForeignKey(GiveAway, on_delete=models.CASCADE)
limit = models.Q(app_label='giveaway', model='article') | \
models.Q(app_label='giveaway', model='daypassresort')
content_type = models.ForeignKey(ContentType, limit_choices_to=limit, on_delete=models.CASCADE)
object_id = gfklookup_fields.GfkLookupField('content_type')
content_object = GenericForeignKey('content_type', 'object_id')
class Meta:
verbose_name = 'GiveAway Winner'
verbose_name_plural = 'GiveAway Winner'
def __str__(self):
return f'{self.id} - {self.competitor.name} - {self.content_type} - {self.id}'
El campo content_type, crea una relación con el modelo ContentType de Django que almacena información sobre el resto de modelos que definimos en nuestro proyecto. Por lo general django.contrib.contenttypes se encuentra dentro de INSTALLED_APPS en los settings.
Limit nos permite definir los modelos que vamos a permitir para la relación regenerica. Article y DayPassResort en este caso.
object_id almacena el id del objeto relacionado y content_object crea la relación entre el content_type y el object_id.
Una vez definidos nuestros modelos generamos y corremos las migraciones, para definir el admin que nos permita gestionar los objetos necesarios para organizar un Giveaway.
Ahora accedemos al admin y creamos un par de premios, uno de tipo Article y otro que sea un DayPassResort.
Luego creamos un par de participantes, porque necesitamos gente ganadora!
Creamos un Giveaway.
Por último registramos un Giveawaywinner, al seleccionar el content type y hacer clic sobre el icono de buscar aparecerá un pop-up para seleccionar un objeto del tipo seleccionado.
Esto es gracias a django-gfklookupwidget, si no usamos este paquete tendrias ingresar directamente id del objeto. Otra alternativa que nos ayuda a hacer esto es django-grappelli pero en lo personal no me gusta porque nos obliga a modificar la interfaz del admin de nuestro proyecto.