Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

neither contains or icontains is working #88

Closed
ericntd-legacy opened this issue May 3, 2016 · 13 comments
Closed

neither contains or icontains is working #88

ericntd-legacy opened this issue May 3, 2016 · 13 comments

Comments

@ericntd-legacy
Copy link

ericntd-legacy commented May 3, 2016

My /api/articles/ returns 100 results.
Both /api/articles/title__contains=Hot and /api/articles/title__icontains return 100 results.
Negation is working.
Exact match is working.
My ViewSet is as follows:

import rest_framework_filters as rf_filters
from rest_framework import filters

class ArticleFilterRf(rf_filters.FilterSet):
    category = rf_filters.CharFilter(name="cat__slug")
    category_id = rf_filters.CharFilter(name="cat_id")

    class Meta:
        model = Article
        fields = ['id', 'status', 'category', 'category_id', 'deleted', 'author', 'featured', 'tags', 'title']
class ArticleViewSet(viewsets.ModelViewSet):
    """
    A viewset for viewing and editing user instances.
    """
    permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
    serializer_class = ArticleSerializer
    queryset = Article.objects.all()
    filter_backends = (filters.SearchFilter, filters.DjangoFilterBackend, filters.OrderingFilter,)
    search_fields = ('title', 'tags', 'slug', 'status', 'cat__title', 'cat__slug', 'author')
    filter_class = ArticleFilterRf
    ordering_fields = ('title', 'updated')
    ordering = ('updated',)
@rpkilby
Copy link
Collaborator

rpkilby commented May 3, 2016

Hi @ericntd. There is both a list syntax and dict syntax for Meta.fields. The list syntax only creates filters for the exact lookup. You should be using the dict syntax instead.

Also, make sure to use the rest_framework_filters.backends.DjangoFilterBackend backend instead of the default backend.

@rpkilby rpkilby closed this as completed May 3, 2016
@jsmedmar
Copy link

does the icontains filter works for a related (ForeingKey) relationship? Wondering why it isnt working for me...

@rpkilby
Copy link
Collaborator

rpkilby commented Jul 21, 2016

icontains does not work on a foreign key value, although it will work across relationships on a foreign field.

icontains isn't a registered lookup for FKs:
https://github.com/django/django/blob/master/django/db/models/fields/related_lookups.py

@jsmedmar
Copy link

jsmedmar commented Jul 21, 2016

I didn't get this part though:

although it will work across relationships on a foreign field

You are saying that it will work if I'm querying the primary key of the foreign object?

@rpkilby
Copy link
Collaborator

rpkilby commented Jul 21, 2016

For example:

class AuthorFilter(FilterSet):
    class Meta:
        model = Author
        fields = {'username': ['icontains']}

class BookFilter(FilterSet):
    author = RelatedFilter(AuthorFilter)

   class Meta:
        model = Book

You could do /books?author__username__icontains=bob. You can traverse the author FK and do an icontains on a related field.

@jsmedmar
Copy link

jsmedmar commented Jul 21, 2016

Is there a way to do a symlink to a field of a RelatedFilter? something like:

class AuthorFilter(FilterSet):
    class Meta:
        model = Author
        fields = {'username': ['icontains']}

class BookFilter(FilterSet):
    username = RelatedFilter(AuthorFilter, name="username")

   class Meta:
        model = Book

I just don't want the client to know that they are actually querying a related model, that is not relevant to them.

@rpkilby
Copy link
Collaborator

rpkilby commented Jul 21, 2016

I'm not sure if it will work, but you might be able to do...

class BookFilter(FilterSet):
    author = RelatedFilter(AuthorFilter, name="author__username")

@jsmedmar
Copy link

jsmedmar commented Jul 21, 2016

Ok, it didn't work... and I just realized that this solution does not work... You cannot traverse the author FK and do an icontains on a related field.

@rpkilby
Copy link
Collaborator

rpkilby commented Jul 21, 2016

You should be able to filter on related model fields. See this as an example - it's the same idea.

The name aliasing - not so sure about that. I'll try to look into it further.

@jsmedmar
Copy link

jsmedmar commented Jul 22, 2016

I just get empty querysets if I don't put the exact value, for example:

>> len(list(NoteFilter({"author__username__icontains": "u"}, queryset=Note.objects.all())))
0

>>len(list(NoteFilter({"author__username__icontains": "user1"}, queryset=Note.objects.all())))
2

This is my exact setting:

settings.py

REST_FRAMEWORK = {

    'PAGE_SIZE': 10,

    'DEFAULT_PAGINATION_CLASS':
        'rest_framework.pagination.LimitOffsetPagination',

    'DEFAULT_FILTER_BACKENDS': (
        'rest_framework_filters.backends.DjangoFilterBackend',
        'rest_framework.filters.SearchFilter',
         ),

    }

filters.py

class ExtractionFilter(FilterSet):

    """Extraction Filter Class."""

    #: ``analyte`` filter field.
    analyte = AllLookupsFilter(name="analyte")

    #: ``ext_id`` filter field.
    ext_id = AllLookupsFilter(name="ext_id")

    class Meta:
        model = Extraction


class WorkflowFilter(FilterSet):

    """Workflow Filter Class."""

    #: Nested filter for ``extraction`` ForeignKey relation.
    extraction = RelatedFilter(ExtractionFilter, name="extraction")

    class Meta:
        model = Workflow
>> len(list(WorkflowFilter({"extraction__analyte__icontains": "R"}, queryset=Workflow.objects.all())))
0

>> len(list(WorkflowFilter({"extraction__analyte__icontains": "RNA"}, queryset=Workflow.objects.all())))
100

@jsmedmar
Copy link

jsmedmar commented Jul 22, 2016

Ok I think I found the problem...

icontains does not work with fields that have choices (analyte) but it does work on fields without choices (ext_id):

class Extraction(Model):

    analyte = models.CharField(
        verbose_name=_("Biological Material"),
        max_length=100,
        choices=CHOICES,
        null=True,
        )

    ext_id = models.CharField(
        verbose_name=_("Extraction External ID"),
        max_length=100,
        blank=True,
        default=None,
        )

Is this an expected behavior?

@rpkilby
Copy link
Collaborator

rpkilby commented Jul 22, 2016

does not work with fields that have choices

Great catch! This if statement has been nagging me for a very long time but decided to just leave it be. The problem is that the value being filtered won't match any of the choices, so it won't validate and returns 0 results. I've submitted a PR (carltongibson/django-filter#447) to only create choice filters for exact lookups (which was probably the original intent anyway).

The following should get around this:

class FilterSet(filters.FilterSet):

    @classmethod
    def filter_for_lookup(cls, f, lookup_type):
        # copy field and remove choices to bypass default behavior
        # See: https://github.com/carltongibson/django-filter/issues/447
        if f.choices and lookup_type != 'exact':
            f = deepcopy(f)
            f.choices = None

        return super(FilterSet, cls).filter_for_lookup(f, lookup_type)

@jsmedmar
Copy link

@rpkilby hey! thank you very much, so I saw that django-filters merged your pull request. Thats great. I think I'll create a new "issue" for the field aliasing, I think that would be a nice feature for django-rest-framework-filters

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants