Lists do not match when sorting and filtering. Django

42 Views Asked by At

when I filter - results and list(all_result_ids) match. when I sort - results and list(all_result_ids) match. when I filter, and sort by any column except "phone" and "emaill" - results and list(all_result_ids) do not match. when I filter and sort by "phone" and "emaill" - results and list(all_result_ids) match.

"email", "phone" are the only columns where the fields always have unique values.

What I need to do is: when I filter and sort by any column - results and list(all_result_ids) match in FilesViewSet

Views.py:

@method_decorator(never_cache, name='dispatch')
class FilesViewSet(ListModelMixin, GenericViewSet):
    __slots__ = ()

    queryset = Upload.objects.all().select_related('source')
    serializer_class = FileSerializer
    filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
    search_fields = ["name"]
    ordering_fields = "__all__"
    filterset_class = FilesFilter

    def list(self, request, *args, **kwargs):
        filters = request.session.get('filters', None)
        if filters is not None:
            for field, value in filters.items():
                request.query_params._mutable = True
                request.query_params[field] = value
        else:
            request.query_params._mutable = True
            request.query_params['ordering'] = '-id'            

        # Check if the query was already sorted
        query_already_sorted = request.session.get('query_already_sorted', False)

        if not query_already_sorted:
            # Get the IDs directly as a QuerySet, not a list
            all_result_ids = self.filter_queryset(self.get_queryset()).values_list('id', flat=True)

            # Store the sorted IDs in the session instead of the SortedID model
            request.session['sorted_ids'] = list(all_result_ids)

            # Set a flag to remember we've sorted this query
            if not inspect.stack()[1].function == 'post_filter_data':
                request.session['query_already_sorted'] = True
                
        limit = int(request.query_params.get('limit', settings.REST_FRAMEWORK['PAGE_SIZE']))
        offset = int(request.query_params.get('offset', 0))
        page_number = offset // limit + 1

        response = super().list(request, *args, **kwargs)
        results = response.data['results']
        result_ids = [result['id'] for result in results]

        selected_ids = set(SelectedID.objects.filter(id__in=result_ids).values_list('id', flat=True))
        for i, result in enumerate(results, start=offset):
            result['check'] = 1 if result['id'] in selected_ids else 0
            result['index'] = i + 1

        has_next = response.data.get('next') is not None

        response.data.update({
            'results': results,
            'total': self.paginator.count,
            'page_size': settings.REST_FRAMEWORK['PAGE_SIZE'],
            'page_number': page_number,
            'has_next': has_next,
        })

        return response

    @action(methods=["POST"], detail=False, url_path="filter_data")
    def post_filter_data(self, request, *args, **kwargs):
        filters = request.data
        request.session['filters'] = filters
        request.session['query_already_sorted'] = False
        request.session.save()  # manually save the session

        return self.list(request, *args, **kwargs)
    
    @action(methods=["GET"], detail=False, url_path="filter_data")
    def filter_data(self, request, *args, **kwargs):
        return Response({
            'source': self.queryset.order_by('source').values_list('source__name', flat=True).distinct(),
            'country': self.queryset.order_by('country').values_list('country', flat=True).distinct(),
            'brand': self.queryset.order_by('brand').values_list('brand', flat=True).distinct(),
            'src': self.queryset.order_by('src').values_list('src', flat=True).distinct(),
            'grouptype': self.queryset.order_by('grouptype').values_list('grouptype', flat=True).distinct(),
        })

filters.py:

from functools import reduce
from operator import or_
from django.db.models import Q

import django_filters
from .models import Upload


class CommaSeparatedCharFilter(django_filters.BaseInFilter, django_filters.CharFilter):
    def sanitize(self, value_list):
        value_list = [v.strip() for v in value_list[0].split(',')]
        return [v for v in value_list if v != '']


class FilesFilter(django_filters.rest_framework.FilterSet):
    from_registr_date = django_filters.DateTimeFilter(
        field_name='date_registr', lookup_expr='gte')
    to_registr_date = django_filters.DateTimeFilter(
        field_name='date_registr', lookup_expr='lte')
    from_upload_date = django_filters.DateTimeFilter(
        field_name='date_upload', lookup_expr='gte')
    to_upload_date = django_filters.DateTimeFilter(
        field_name='date_upload', lookup_expr='lte')
    mpc1_gte = django_filters.NumberFilter(
        field_name='mpc1', lookup_expr='gte')
    mpc1_lte = django_filters.NumberFilter(
        field_name='mpc1', lookup_expr='lte')
    mpc2_gte = django_filters.NumberFilter(
        field_name='mpc2', lookup_expr='gte')
    mpc2_lte = django_filters.NumberFilter(
        field_name='mpc2', lookup_expr='lte')
    mpc3_gte = django_filters.NumberFilter(
        field_name='mpc3', lookup_expr='gte')
    mpc3_lte = django_filters.NumberFilter(
        field_name='mpc3', lookup_expr='lte')
    mpc4_gte = django_filters.NumberFilter(
        field_name='mpc4', lookup_expr='gte')
    mpc4_lte = django_filters.NumberFilter(
        field_name='mpc4', lookup_expr='lte')
    download_count = django_filters.NumberFilter(
        field_name='download_count', lookup_expr='exact')
    search_data = django_filters.CharFilter(
        method='search_filter', label="Search")
    source = CommaSeparatedCharFilter(
        field_name='source__name', lookup_expr='in')
    country = CommaSeparatedCharFilter(field_name='country', lookup_expr='in')
    brand = CommaSeparatedCharFilter(field_name='brand', lookup_expr='in')
    src = CommaSeparatedCharFilter(field_name='src', lookup_expr='in')
    grouptype = CommaSeparatedCharFilter(field_name='src', lookup_expr='in')

    class Meta:
        model = Upload
        fields = [
            'from_registr_date',
            'to_registr_date',
            'from_upload_date',
            'to_upload_date',
            'mpc1_gte',
            'mpc1_lte',
            'mpc2_gte',
            'mpc2_lte',
            'mpc3_gte',
            'mpc3_lte',
            'mpc4_gte',
            'mpc4_lte',
            'source',
            'download_count',
            'search_data',
            'country',
            'brand',
            'src',
            'grouptype',
        ]

    def search_filter(self, queryset, name, value):
        """
        This filter will allow you to search all the text fields in your model for matches.
        """
        search_fields = ['first_name', 'last_name', 'phone', 'brand',
                         'country', 'download_person', 'source__name', 'src', 'email', 'grouptype']
        queries = [Q(**{f"{f}__icontains": value}) for f in search_fields]
        return queryset.filter(reduce(or_, queries))

models.py:

from django.db import models
from django.utils.translation import gettext_lazy as _


class Source(models.Model):
    name = models.CharField(max_length=255)

    def __str__(self):
        return self.name


class Upload(models.Model):
    source = models.ForeignKey(
        Source, on_delete=models.CASCADE, null=True, blank=True)
    first_name = models.CharField(max_length=255, null=True, blank=True)
    last_name = models.CharField(max_length=255, null=True, blank=True)
    country = models.CharField(max_length=150, null=True, blank=True)
    phone = models.CharField(max_length=30, null=True, blank=True)
    email = models.EmailField(null=True, blank=True)
    date_registr = models.DateTimeField(auto_now_add=False, null=True, blank=True)
    date_upload = models.DateTimeField(auto_now_add=True, blank=True)
    brand = models.CharField(max_length=500, null=True, blank=True)
    src = models.CharField(max_length=500, null=True, blank=True)
    grouptype = models.CharField(max_length=500, null=True, blank=True)
    mpc1 = models.DecimalField(
        max_digits=10, decimal_places=2, null=True, blank=True)
    mpc2 = models.DecimalField(
        max_digits=10, decimal_places=2, null=True, blank=True)
    mpc3 = models.DecimalField(
        max_digits=10, decimal_places=2, null=True, blank=True)
    mpc4 = models.DecimalField(
        max_digits=10, decimal_places=2, null=True, blank=True)
    download_count = models.PositiveIntegerField(default=0, blank=True)
    download_person = models.CharField(
        max_length=100000, blank=True, null=True)
    data_history = models.ForeignKey(
        'DataHistoryM', on_delete=models.CASCADE, related_name='uploads', null=True)

    class Meta:
        indexes = [
            models.Index(fields=['source',]),
            models.Index(fields=['first_name',]),
            models.Index(fields=['last_name',]),
            models.Index(fields=['country',]),
            models.Index(fields=['phone',]),
            models.Index(fields=['email',]),
            models.Index(fields=['date_registr',]),
            models.Index(fields=['date_upload',]),
            models.Index(fields=['brand',]),
            models.Index(fields=['src',]),
            models.Index(fields=['grouptype',]),
            models.Index(fields=['mpc1',]),
            models.Index(fields=['mpc2',]),
            models.Index(fields=['mpc3',]),
            models.Index(fields=['mpc4',]),
            models.Index(fields=['download_count',]),
            models.Index(fields=['download_person',]),
        ]

I tried to use base instead of request.session but it's faster to get it into another view that way.

0

There are 0 best solutions below