Empresas
Empleos
  • Sobre nosotros
  • Soluciones
    • Publicación de vacantes
      Publica tu vacante y recibe candidatos calificados en 48h.
    • Evaluación de candidatos
      500+ pruebas técnicas y psicológicas, más anti-fraude.
    • Headhunting
      Búsqueda ejecutiva a la medida de principio a fin.
    • Nómina + EOR
      Dispersión de nómina y EOR en más de 15 países de LATAM.
  • Precios
  • Empleos

0

334
Vistas
Django: lógica duplicada entre propiedades y anotaciones de conjuntos de consultas

Cuando quiero definir la lógica de mi negocio, me cuesta encontrar la manera correcta de hacerlo, porque a menudo necesito una propiedad Y un conjunto de consultas personalizado para obtener la misma información. Al final, la lógica se duplica.

Dejame explicar...

Primero, después de definir mi clase, naturalmente empiezo a escribir una propiedad simple para los datos que necesito:

 class PickupTimeSlot(models.Model): @property def nb_bookings(self) -> int: """ How many times this time slot is booked? """ return self.order_set.validated().count()

Luego, rápidamente me doy cuenta de que llamar a esta propiedad mientras se manejan muchos objetos en un conjunto de consultas conducirá a consultas duplicadas y matará el rendimiento (incluso si uso la captación previa, porque el filtrado se vuelve a llamar). Así que resuelvo el problema escribiendo un conjunto de consultas personalizado con anotación:

 class PickupTimeSlotQuerySet(query.QuerySet): def add_nb_bookings_data(self): return self.annotate(db_nb_bookings=Count('order', filter=Q(order__status=Order.VALIDATED)))

La cuestión

Y entonces, termino con 2 problemas:

  • Tengo la misma lógica comercial (" cómo encontrar el número de reservas ") escrita dos veces, lo que podría generar errores funcionales.
  • Necesito encontrar dos nombres de atributos diferentes para evitar conflictos, porque obviamente, establecer nb_bookings tanto para la propiedad como para la anotación no funciona. Esto me obliga, cuando uso mi objeto, a pensar en cómo se generan los datos, a llamar al nombre de atributo correcto (digamos pickup_slot.nb_bookings (propiedad) o pickup_slot.db_nb_bookings (anotación))

Esto me parece mal diseñado, y estoy bastante seguro de que hay una manera de hacerlo mejor. Necesitaría una forma de escribir siempre pickup_slot.nb_bookings y tener una respuesta eficaz, siempre usando la misma lógica comercial.

Tengo una idea, pero no estoy seguro...

Estaba pensando en eliminar por completo la propiedad y mantener solo el conjunto de consultas personalizado. Luego, para objetos individuales, envuélvalos en conjuntos de consultas solo para poder llamar y agregar datos de anotación en él. Algo como:

pickup_slot = PickupTimeSlot.objects.add_nb_bookings_data().get(pk=pickup_slot.pk)

Me parece bastante raro y poco natural. ¿Qué piensas?

over 4 years ago · Santiago Trujillo
4 Respuestas
Responde la pregunta

0

Para evitar cualquier duplicación, una opción podría ser:

  • eliminar la propiedad en el Modelo
  • usar un administrador personalizado
  • anular su método get_queryset():
 class PickupTimeSlotManager(models.Manager): def get_queryset(self): return super().get_queryset().annotate( db_nb_bookings=Count( 'order', filter=Q(order__status=Order.VALIDATED) ) )
 from django.db import models from .managers import PickupTimeSlotManager class PickupTimeSlot(models.Model): ... # Add custom manager objects = PickupTimeSlotManager()

ventaja : las propiedades calculadas se agregan de forma transparente a cualquier conjunto de consultas; no se requiere ninguna otra acción para usarlo

desventaja : la sobrecarga computacional ocurre incluso cuando no se usa la propiedad calculada

over 4 years ago · Santiago Trujillo Denunciar

0

Deja que esta sea la forma alternativa de archivar lo que quieras:

Dado que generalmente agrego el prefetch_related cada vez que escribo un conjunto de consultas. Entonces, cuando enfrente este problema, usaré Python para resolverlo.

Voy a usar Python para hacer un bucle y contar los datos por mí en lugar de hacerlo de forma SQL.

 class PickupTimeSlot(models.Model): @property def nb_bookings(self) -> int: """ How many times this time slot is booked? """ orders = self.order_set.all() # this won't hit the database if you already did the prefetch_related validated_orders = filter(lambda x: x.status == Order.VALIDATED, orders) return len(validated_orders)

Y lo más importante, prefetch_related :

 time_slots = PickupTimeSlot.objects.prefetch_related('order_set').all()

Es posible que tenga una pregunta sobre por qué no prefetch_related con el conjunto de consultas filtrado para que Python no necesite filtrar nuevamente como:

 time_slots = PickupTimeSlot.objects.prefetch_related( Prefetch('order_set', queryset=Order.objects.filter(status=Order.VALIDATED)) ).all()

La respuesta es que a veces también necesitamos otra información de los orders . Hacer la primera forma no costará nada más si vamos a buscarlo de todos modos.

Espero que esto más o menos te ayude. ¡Que tenga un lindo día!

over 4 years ago · Santiago Trujillo Denunciar

0

No creo que haya una bala de plata aquí. Pero uso este patrón en mis proyectos para tales casos.

 class PickupTimeSlotAnnotatedManager(models.Manager): def with_nb_bookings(self): return self.annotate( _nb_bookings=Count( 'order', filter=Q(order__status=Order.VALIDATED) ) ) class PickupTimeSlot(models.Model): ... annotated = PickupTimeSlotAnnotatedManager() @property def nb_bookings(self) -> int: """ How many times this time slot is booked? """ if hasattr(self, '_nb_bookings'): return self._nb_bookings return self.order_set.validated().count()

En codigo

 qs = PickupTimeSlot.annotated.with_nb_bookings() for item in qs: print(item.nb_bookings)

De esta manera, siempre puedo usar la propiedad, si es parte del conjunto de consultas anotado, usará el valor anotado, si no, lo calculará. Este enfoque garantiza que tendré el control total de cuándo hacer queryset sea "más pesado" al anotarlo con los valores requeridos. Si no necesito esto, solo uso PickupTimeSlot.objects. ...

Además, si hay muchas propiedades de este tipo, podría escribir un decorador que envuelva la propiedad y simplifique el código. Funcionará como decorador cached_property , pero en su lugar usará un valor anotado si está presente.

over 4 years ago · Santiago Trujillo Denunciar

0

Según sus diferentes buenas respuestas, decidí seguir con las anotaciones y las propiedades. Creé un mecanismo de caché para que sea transparente sobre el nombre. La principal ventaja es mantener la lógica empresarial en un solo lugar. El único inconveniente que veo es que se podría llamar a un objeto desde la base de datos por segunda vez para anotarlo. El impacto en el rendimiento sigue siendo menor en mi opinión.

Aquí hay un ejemplo completo con 3 atributos diferentes que necesito en mi modelo. Siéntase libre de comentar para mejorar esto.

modelos.py

 class PickupTimeSlotQuerySet(query.QuerySet): def add_booking_data(self): return self \ .prefetch_related('order_set') \ .annotate(_nb_bookings=Count('order', filter=Q(order__status=Order.VALIDATED))) \ .annotate(_nb_available_bookings=F('nb_max_bookings') - F('_nb_bookings')) \ .annotate(_is_bookable=Case(When(_nb_bookings__lt=F('nb_max_bookings'), then=Value(True)), default=Value(False), output_field=BooleanField()) ) \ .order_by('start') class PickupTimeSlot(models.Model): objects = SafeDeleteManager.from_queryset(PickupTimeSlotQuerySet)() nb_max_bookings = models.PositiveSmallIntegerField() @annotate_to_property('add_booking_data', 'nb_bookings') def nb_bookings(self): pass @annotate_to_property('add_booking_data', 'nb_available_bookings') def nb_available_bookings(self): pass @annotate_to_property('add_booking_data', 'is_bookable') def is_bookable(self): pass

decoradores.py

 def annotate_to_property(queryset_method_name, key_name): """ allow an annotated attribute to be used as property. """ from django.apps import apps def decorator(func): def inner(self): attr = "_" + key_name if not hasattr(self, attr): klass = apps.get_model(self._meta.app_label, self._meta.object_name) to_eval = f"klass.objects.{queryset_method_name}().get(pk={self.pk}).{attr}" value = eval(to_eval, {'klass': klass}) setattr(self, attr, value) return getattr(self, attr) return property(inner) return decorator
over 4 years ago · Santiago Trujillo Denunciar
Responde la pregunta
Encuentra empleos remotos

¡Descubre la nueva forma de encontrar empleo!

Top de empleos
Top categorías de empleo
Empresas
Publicar vacante Precios Comercial
Legal
Términos y condiciones Política de privacidad
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomiéndame algunas ofertas
Necesito ayuda