-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add organization view UI filters (#10847)
* Add organization view UI filters This filters are used for the new dashboard UI, they are not API filters. These were a challenge to create, as django-filter is really opinionated about the way it should work, and doesn't quite agree with use cases that need to use filtered querysets (such as limiting the field choices to objects the organization is related to). I went through many permutations of this code, trying to find a pattern that was not obtuse or awful. Unfortunately, I don't quite like the patterns here, but because all of the django-filter magic happens at the class level instead of at instantiation time, every direction required hacks to obtain something reasonable. Given the use we have for filters in our UI, I wouldn't mind making these into a more generalized filter solution. I think I'd like to replace some of the existing filters that currently hit the API with frontend code and replace those with proper filters too. These are mostly the project/version listing elements. * Add filter for organization dropdown This replaces an API driven UI element. It's not important that these UI filters are frontend, it was just easier than figuring out how to make django-filter work for this use case at that time. * Fix the team member filter yet again * Use a custom filter field to execute filter set queryset method * Add tests organization filter sets * Update code comments * A few more improvements - Make view code nicer, use django_filters.views.FilterMixin, sort of - Use FilterSet.is_valid() to give empty list on validation errors - Clean up tests with standard fixtures instead * Review updates for tests and arguments * Rename FilterMixin -> FilterContextMixin * Fix lint
- Loading branch information
Showing
5 changed files
with
931 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
"""Extended classes for django-filter.""" | ||
|
||
from django_filters import ModelChoiceFilter, views | ||
|
||
|
||
class FilteredModelChoiceFilter(ModelChoiceFilter): | ||
|
||
""" | ||
A model choice field for customizing choice querysets at initialization. | ||
This extends the model choice field queryset lookup to include executing a | ||
method from the parent filter set. Normally, ModelChoiceFilter will use the | ||
underlying model manager ``all()`` method to populate choices. This of | ||
course is not correct as we need to worry about permissions to organizations | ||
and teams. | ||
Using a method on the parent filterset, the queryset can be filtered using | ||
attributes on the FilterSet instance, which for now is view time parameters | ||
like ``organization``. | ||
Additional parameters from this class: | ||
:param queryset_method: Name of method on parent FilterSet to call to build | ||
queryset for choice population. | ||
:type queryset_method: str | ||
""" | ||
|
||
def __init__(self, *args, **kwargs): | ||
self.queryset_method = kwargs.pop("queryset_method", None) | ||
super().__init__(*args, **kwargs) | ||
|
||
def get_queryset(self, request): | ||
if self.queryset_method: | ||
fn = getattr(self.parent, self.queryset_method, None) | ||
if not callable(fn): | ||
raise ValueError(f"Method {self.queryset_method} is not callable") | ||
return fn() | ||
return super().get_queryset(request) | ||
|
||
|
||
class FilterContextMixin(views.FilterMixin): | ||
|
||
""" | ||
Django-filter filterset mixin class for context data. | ||
Django-filter gives two classes for constructing views: | ||
- :py:class:`~django_filters.views.BaseFilterView` | ||
- :py:class:`~django_filters.views.FilterMixin` | ||
These aren't quite yet usable, as some of our views still support our legacy | ||
dashboard. For now, this class will aim to be an intermediate step. It will | ||
expect these methods to be called from ``get_context_data()``, but will | ||
maintain some level of compatibility with the native mixin/view classes. | ||
""" | ||
|
||
def get_filterset(self, **kwargs): | ||
""" | ||
Construct filterset for view. | ||
This does not automatically execute like it would with BaseFilterView. | ||
Instead, this should be called directly from ``get_context_data()``. | ||
Unlike the parent methods, this method can be used to pass arguments | ||
directly to the ``FilterSet.__init__``. | ||
:param kwargs: Arguments to pass to ``FilterSet.__init__`` | ||
""" | ||
# This method overrides the method from FilterMixin with differing | ||
# arguments. We can switch this later if we ever resturcture the view | ||
# pylint: disable=arguments-differ | ||
if not getattr(self, "filterset", None): | ||
filterset_class = self.get_filterset_class() | ||
all_kwargs = self.get_filterset_kwargs(filterset_class) | ||
all_kwargs.update(kwargs) | ||
self.filterset = filterset_class(**all_kwargs) | ||
return self.filterset | ||
|
||
def get_filtered_queryset(self): | ||
if self.filterset.is_valid() or not self.get_strict(): | ||
return self.filterset.qs | ||
return self.filterset.queryset.none() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.