Arquitectura¶
Modelo de base de datos¶
Los modelos fundamentales de Preciosa se definen en la aplicación precios </preciosa/precios/models.py>
. Un diagrama simplificado es el siguiente:
Algoritmos de «precios»¶
Como se observa, el relevamiento de un precio
está asociado a un producto y a una sucursal específica (y, opcionalmente, a un usuario), además de su fecha de relevamiento.
Al ser improbable la existencia de precios para cualquier producto en todas las sucursales, la consulta del precio de un producto para una sucursal en particular se realiza a través del método PrecioManager.mas_probables()
, que realiza un degradado de información:
Si existe un precio relevado para el producto en la sucursal, dentro del rango de máximo de dias, se devuelve ese dato
En caso de que no exista, se buscan precios para el producto en sucursales de la misma cadena o un radio definido.
Si no hay precios en Sucursales, se buscan el precio del producto en la «Sucursal Online» de la Cadena.
Si no hay sucursal online asociada o no existe un precio vigente, no se devuelve un resultado.
def mas_probables(self, producto, sucursal, dias=None, radio=10):
"""
Cuando no hay datos especificos de un
producto para una sucursal (:meth:`PrecioManager.historico`),
debe ofrecerse un precio más probable. Se calcula
- Precio más nuevo para el producto en otras sucursales
de la misma cadena en la ciudad y/o un radio de distancia si es dado
- En su defecto, precio online de la cadena
"""
qs = self.historico(producto, sucursal, dias)
if len(qs) > 0:
return qs
qs = super(PrecioManager, self).get_queryset()
# precios para sucursales de la misma cadena de la ciudad
cercanas_ciudad = sucursal.cercanas(misma_cadena=True).values_list('id',
flat=True)
if radio:
cercanas_radio = sucursal.cercanas(radio=radio,
misma_cadena=True).values_list('id',
flat=True)
else:
cercanas_radio = []
cercanas = set(list(cercanas_ciudad) + list(cercanas_radio))
qs = qs.filter(producto=producto,
sucursal__id__in=cercanas).distinct('precio')
if qs.exists():
return self._registro_precio(qs)
qs = super(PrecioManager, self).get_queryset()
qs = qs.filter(producto=producto,
sucursal__cadena=sucursal.cadena,
sucursal__online=True).distinct('precio')
# precios online
if qs.exists():
return self._registro_precio(qs)
return []
Análogamente se calculan los mejores precios. Dado un producto, una ubicación (o sucursal) y radio de distancia, se obtiene una lista de los mejores precios en la zona para ese producto.
def mejores(self, producto, ciudad=None, punto_o_sucursal=None,
radio=None, dias=None, limite=5):
"""
devuelve una lista de instancias Precio para el producto,
ordenados por menor precio (importe) para
un determinado producto y un radio de distancia o ciudad.
Sólo considera el último precio en cada sucursal.
"""
#si tiene puto.. tenemos que tener el radio
if punto_o_sucursal and not radio:
raise ValueError(
'Si se especifica el punto o sucursal debe proveer el radio')
if radio and not punto_o_sucursal and not ciudad:
raise ValueError(
'Si se especifica el radio debe proveer el punto o sucursal')
#si no tenemos una ciudad o un punto con radio
if not ciudad and not radio:
raise ValueError(
'Debe proveer una ciudad o un radio en kilometros')
qs = super(PrecioManager,
self).get_queryset().filter(producto=producto, activo__isnull=False)
if dias:
desde = timezone.now() - timedelta(days=dias)
qs = qs.filter(created__gte=desde)
if ciudad:
if isinstance(ciudad, City):
ciudad = ciudad.id
qs = qs.filter(sucursal__ciudad__id=ciudad).distinct(
'sucursal')[:limite]
elif radio:
if isinstance(punto_o_sucursal, Sucursal):
punto = punto_o_sucursal.ubicacion
else:
punto = punto_o_sucursal
cercanas = Sucursal.objects.filter(ubicacion__distance_lte=(punto,
D(km=radio)))
cercanas = cercanas.values_list('id', flat=True)
qs = qs.filter(sucursal__id__in=cercanas).distinct(
'sucursal')[:limite]
if qs.exists():
return sorted(qs, key=lambda i: i.precio)
return []