Enrique Torres
Enrique Torres

Reputation: 289

Filter an object with a many to many relation, checking if it contains at least one of the elements in a list

We're making an app that will be used to sell second-hand items. Inside of this app, we'll have an option to filter products based on a series of criteria: the user's geographical postion and a maximum distance, a minimum price, a maximum price, a minimum score, and a list of tags.

My main problem is filtering the many to many relationship that a product has with a tag. What we want to do is get all the products which contain at least one of the tags selected by the user.

Here's the code for the filter function:

def FilterProduct(request, format=None):
    if request.method != 'POST':
        return Response(status=status.HTTP_400_BAD_REQUEST)
    token = request.POST.get('token', 'nothing')
    if token == 'nothing':
        return Response(status=status.HTTP_400_BAD_REQUEST)
    else:
        try:
            tags = request.POST.get('tags', '')
            tags_pk = []
            for tag_search in tags:
                tag = Tag.objects.filter(nombre=tag_search)
                if tag != None:
                    tags_pk.append(tag.pk)

            user_latitude = request.POST.get('latitud', '')
            user_longitude = request.POST.get('longitud', '')
            max_distance = request.POST.get('distancia_maxima', '')
            min_price = request.POST.get('precio_minimo', '')
            max_price = request.POST.get('precio_maximo', '')
            min_score = request.POST.get('calificacion_minima', '')
            if tags == '' or user_latitude == '' or user_longitude == '' or max_distance == '' or min_price == '' or max_price == '' or min_score == '':
                return Response(status=status.HTTP_400_BAD_REQUEST)
            products = Productos.objects.filter(precio__lte=max_price, precio__gte=min_price, vendido_por__media_valoraciones__gte=min_score, tiene_tags__in=tags_pk)

For some reason, this is not returning all the products that at least contain one item from the list of primary keys.

Here's the models.py:

class Producto(models.Model):
    vendido_por = models.ForeignKey(
        to=Usuario,
        null=False,
        on_delete=models.CASCADE,
        verbose_name='Usuario que ha puesto a la venta el producto',
        related_name='producto_del_usuario')
    latitud = models.DecimalField(
        verbose_name='Latitud del producto',
        max_digits=9,
        decimal_places=6)
    longitud = models.DecimalField(
        verbose_name='Longitud del producto',
        max_digits=9,
        decimal_places=6)
    nombre = models.CharField(
        max_length=50,
        verbose_name='Nombre del producto')
    precio = models.CharField(
        max_length=10,
        verbose_name='Precio del producto')
    estado_producto = models.CharField(
        max_length=50,
        choices=[(tag.name, tag.value) for tag in EleccionEstadoProducto],
        verbose_name='Estado en el que se encuentra el producto: Nuevo, Semi-nuevo, etc')
    estado_venta = models.CharField(
        max_length=50,
        choices=[(tag.name, tag.value) for tag in EleccionEstadoVenta],
        verbose_name='Estado en el que se encuentra la venta')
    num_acciones = models.IntegerField(
        default=0,
        verbose_name='Marca si uno o los dos usuarios han confirmado el estado de venta')
    tipo_envio = models.CharField(
        max_length=50,
        verbose_name='Si el usuario que ha colgado el producto esta dispuestos a enviar a domicilio o no')
    descripcion = models.CharField(
        max_length=1000,
        verbose_name='Descripcion asociada al producto')
    tiene_tags = models.ManyToManyField(
        Tag,
        blank=True,
        editable=True,
        related_name='tiene_tags')
    le_gusta_a = models.ManyToManyField(
        Usuario,
        blank=True,
        editable=True,
        related_name='le_gusta_a')
    num_likes = models.IntegerField(
        default=0,
        verbose_name='Likes acumulados por el producto')

class Tag(models.Model):
    nombre = models.CharField(
        max_length=50,
        verbose_name='Nombre del tag')
    es_predeterminado = models.BooleanField(
        default=False,
        verbose_name='Marca si un tag ha sido creado por los administradores de la aplicacion')

Is there an actual way to filter all the products which contain at least one of the tags in the list sent in the request?

Thanks in advance!

Upvotes: 0

Views: 629

Answers (1)

Artisan
Artisan

Reputation: 2124

I think you can simplify the way your tag filter is happening, e.g.

tags = request.POST.get('tags', '')
tag_queryset = Tag.objects.filter(nombre__in=tags)

Then you can throw the queryset directly into your Product filter e.g.

products = Productos.objects.filter(precio__lte=max_price, precio__gte=min_price, vendido_por__media_valoraciones__gte=min_score, tiene_tags__in=tag_queryset)

Hope that helps

Upvotes: 2

Related Questions