django-url-filter
django-url-filter copied to clipboard
usage.rst: Django section fails to explain Django usage
Forgive me. But on this page:
https://github.com/miki725/django-url-filter/blob/master/docs/usage.rst
There is an enticing Django section after a Vanilla section that makes no sense to me. And in it I expect to see how I might use this in Django. But that section helps me naught.
It presents some code snippets out of context and I'm a tad clueless as to to use filters from it.
Where does what sit in urls.py, models.py, views.py, settings.pi etc. in the Django context? What am I missing? How could this page improve to actually elucidate things for a punter like me (building a site with Django, pretty au fait with much of Django but unable to decrypt this so clearly not a pro).
To use URL filter in Django there is nothing to be done in settings, urlconf, etc. You simply use it directly in the view since in Django request.GET
is an instance of QueryDict
so you directly pass that to the filterset.
If you feel the doc can be improved please feel free to suggest improvements or to send a PR.
Thanks for the quick and informative update. Much appreciated.
Alas not sure what a PR is (a Pull Request probably, but if so I need to know how ti works before I can make the PR, which I'm happy to do in fact).
My suggestions would be:
- State what you have stated clearly in the Django section.
- Provide a simple example function and/or class view that implements it and perhaps a URLconf entry that fires it up.
But alas lacking 2) I don't know where to start. The example is rather terse at present and lacks context.
I have a clear use case in mind, namely implementing a view with the "Human-friendly URLs" mentioned under Features in the README:
https://github.com/miki725/django-url-filter/blob/master/README.rst
I'm failing at present to comprehend how to get form one of those URLs to a class based ListView in my case that lists a subset of records based on the URL.
its really up to your view implemention. class based view you can do something like:
class MyListView(ListView):
model = MyModel
queryset = MyModel.objects.all()
template_name = 'my_list_template.html'
def get_queryset(self):
qs = super(MyListView, self).get_queryset()
# note that the following is pretty much identical to the vanilla docs example
# except using diff var names
fs = UserFilterSet(data=self.request.GET, queryset=qs)
return fs.fitler()
something like that should get you started.
Ill definitely try to improve the docs in the future so will reopen the issue. not sure when that will happen since doing renovations in the house now so a bunch of my open source libs are on hold until thats done. feel free to open PR with whatever you feel will make the docs more useful to other people and we can go from there.
thanks
Hey, thanks that look awesomely simple. My reading then is that the filterset reads the full get request and doesn't rely on any tweaks in URLconf.
For example if I use a simple conf like:
url(r'^list/(?P<model>\w+)$', views.MyListView.as_view(), name='list'),
MyListView sees model as a parameter, but UserFilterSet's data argument wants the whole request anyhow so no further pattern matching or distinction at the URLconf is necessary. Though I imagine I'll need to lose the end anchor ($) in the RE and replace with a / anchor (/).
That looks real sweet and simple and yes, worth documenting as such. Thanks very much for the quick leg up. I'll experiment with it tonight if I can, given I now have a clear sense of direction. Morning here now.
In all honest my blinkers if you will, were the assumption that I'd need to be explicit like:
url(r'^list/(?P<model>\w+)/(?P<filter>.+)$', views.MyListView.as_view(), name='list'),
But there is, it seems, no need.
My reading then is that the filterset reads the full get request and doesn't rely on any tweaks in URLconf.
that is correct. it uses querystrings (the thing after ?
in the uri) which are within the domain of the view, not the urlconf.
just a note though that urlfilter is meant for filtering within a particular model. from your quick example you should prolly filter the model
parameter itself in the get_queryset
and then let django url filter filter within that. in other words, use django ORM to filter url kwargs and let urlfilter filter querystrings.
class MyListView(ListView):
def get_queryset(self):
qs = super(MyListView, self).get_queryset()
qs = qs.filter(model=self.kwargs['model'])
fs = UserFilterSet(data=self.request.GET, queryset=qs)
return fs.filter()
that will allow to do things like:
/list/foo/ # all foo results
/list/foo/?created_timestamp__gt=2017-01-01
the idea is that the model regex itself should validate the model
value hence no need for django url filter (e.g. in (?P<model>\w+)
).
Thanks yet again. You have indeed hit on a stumbling block for me. I spent the other night doing the Django REST tutorial and learned a lot there but have found yet another neat abstraction to make code even terser than I currently have it ;-). I may well move to the REST framework some time.
For now though you nailed it. I use one class based view to view any specified model (as passed in through the kwarg). That is easy (with a little trickery only) in the form of:
class MyListView(ListView):
def get_queryset(self, *args, **kwargs):
app = type(self).__module__.split('.')[0]
model = apps.get_model(app, self.kwargs['model'])
self.queryset = self.model.objects.all()
return self.queryset
which is a charmingly generic ListView that will list any model in the current app.
Alas indeed the FilterSet class:
class MyFilterSet(ModelFilterSet):
class Meta(object):
model = MyModel
has a hardcoded model in the Meta class (akin to Djangos Model Meta classes I guess).
I wonder, is there a way to define MyFilterset to take a model as a variable. Seems not in this pro forma as the class has no arguments per se. A Class Factory is not out of the question, with a soluton here:
http://stackoverflow.com/questions/15247075/how-can-i-dynamically-create-derived-classes-from-a-base-class
but it's convoluted with a class within a class. Certainly not tidy. An alternative is to make a filterset explicitly for each model, but that sort of defeats DRY. Seeing no clear work around that though at present, either explicit filterset declarations or a class factory or perhaps the REST framework.
OK, very pleased to say, that with your very kind support and help I've now got it working. I'm happy to update the docs a but and submit a pull request with what I've learned.
Although a tad esoteric, my generic list view now works, by dynamically creating a FilterSet class on the fly with:
def get_queryset(self, *args, **kwargs):
self.app = type(self).__module__.split('.')[0]
self.model = class_from_string(self.app, self.kwargs['model'])
if len(self.request.GET) > 0:
FilterSet = type("FilterSet", (ModelFilterSet,), {
'Meta': type("Meta", (object,), {
'model': self.model
})
})
fs = FilterSet(data=self.request.GET, queryset=self.model.objects.all())
self.queryset = fs.filter()
else:
self.queryset = self.model.objects.all()
A rather elegant version of a class factory, a short in-line nested type builder. Pleased to say it works fine at present, and a deep profound word of thanks for your kind support and direction.
Well I have notice I can't filter on properties alas, only on fields. Just saying. I'll have to get more creative in that space it seams.
Though I am mildly surprised the it doesn't throw a FieldError because model.objects.filter() does.