Django ListView self.kwargs - django

I'm using Django==1.11. As I understand from class based views, in this case ListView, you can access url params in get_queryset with self.kwargs, as answered here and here. And I have no problem when I use get_context_data and self.kwargs.
But I can't get it to work in get_queryset. ¿What I'm doing wrong or missing? I've been trying so many alternatives but I can't get the right one.
My code:
urls
urlpatterns = [
url(r'^escuelas/(?P<level>(inicial|primario|secundario))/$', SchoolListView.as_view(), name='school-by-level-index'),
#...
view
class SchoolListView(ListView):
model = School
template_name = 'edu/adminlte/school_index.html'
def get_queryset(self):
queryset = super(SchoolListView, self).get_queryset()
"""
Here below self.kwargs['level'] does not return anything as I would expect
"""
level = self.kwargs['level']
if level is 'inicial':
queryset = School.objects.filter(level='I')
return queryset
return queryset
Thanks.

I knew it was something simple:
double equals vs is in python
My code was:
if level is 'inicial':
But it had to be
if level == 'inicial':

Related

Rest Framework pass additional variables to ModelViewSet

Might seem like a dumb question but trying to pass additional filter variables to a ModelViewSet but request.data is empty.
class ObjViewSet(viewsets.ModelViewSet):
def get_queryset(self):
if self.request.get('orderBy', None):
return Obj.objects.all().order_by(self.request.get('orderBy'))
else:
return Obj.objects.all()
What is the correct way to do this? I don't want to screw up the /view/<id>/ routing but I also wish to pass a couple more variables via /view/?orderBy=id&var2=val2
Using DefaultRouter
router.register('objs', views.ObjViewSet, basename="obj")
You should change the self.request.get('orderBy') into self.request.GET.get('orderBy')
class ObjViewSet(viewsets.ModelViewSet):
queryset = Obj.objects.all()
def get_queryset(self):
order_by = self.request.GET.get('orderBy')
if order_by is not None:
return self.queryset.order_by(order_by)
return self.queryset

Customize queryset in django-filter ModelChoiceFilter (select) and ModelMultipleChoiceFilter (multi-select) menus based on request

I'm using django-filter in 2 places: My Django Rest Framework API, and in my FilterViews (Django Filter's Generic ListViews.) In the case of my FilterViews I'm showing both select boxes (ModelChoiceFilter) and multi-select boxes (ModelMultipleChoiceFilter) to be filtered on.
I need to be able to limit what's in those select and multi-select inputs based on a field inside the request.
It's relatively simple to change what's listed as a kwarg in the relevant field in the FilterSet. For example, here's my FilterSet where the queryset is set as a kwarg:
class FieldFilter(django_filters.FilterSet):
"""Filter for the field list in the API"""
dataset = ModelChoiceFilter(queryset=Dataset.objects.all())
class Meta(object):
"""Meta options for the filter"""
model = Field
fields = ['dataset']
And it's relatively straightforward to limit what the result is in DRF inside the get_queryset() method. For example, here's my DRF ViewSet:
class FieldViewSet(viewsets.ReadOnlyModelViewSet):
"""A ViewSet for viewing dataset fields"""
queryset = Field.objects.all()
serializer_class = FieldSerializer
filter_class = FieldFilter
def get_queryset(self):
"""Get the queryset"""
queryset = super(FieldViewSet, self).get_queryset()
queryset = queryset.filter(
dataset__organization=self.request.organization)
return queryset
I just can't find anywhere to edit the Dataset field in the filter_class when the view is being displayed.
This is super straightforward in Django FormView generic views, but it doesn't appear that FieldViewSet follows the same get_form() structure as generic views. It's also relatively straightforward to do in the admin, but DRF/Django-Filter don't seem to follow that structure either.
Is there any way to customize the queryset in those inputs on a per-request basis? Preferably both on FilterViews and in the HTML API browser, but just in FilterViews would be fine if it's too complicated for the HTML API browser.
After hours of search, I found the solution in the official documentation here!
The queryset argument for ModelChoiceFilter and ModelMultipleChoiceFilter supports callable behavior. If a callable is passed, it will be invoked with the request as its only argument.
import django_filters as filters
from django.utils.translation import gettext as _
def ourBranches(request):
if request is None:
return Branch.objects.none()
company = request.user.profile.company
return Branch.objects.filter(company=company)
class UnitFilter(filters.FilterSet):
branch = filters.ModelChoiceFilter(
queryset=ourBranches, empty_label=_("All Branches"))
class Meta:
model = Unit
fields = ('branch', )
and in the view, I made sure to pass the request as well
qs = Unit.objects.all()
filter = UnitFilter(self.request.GET, request=self.request, queryset=qs)
table = UnitTable(filter.qs)
I also had problems finding a resolution to this.
I solved it (I think) via the following:
views.py
table_filter = ExampleFilter(request.GET, kwarg_I_want_to_pass=request.user, queryset=qs)
filters.py
class ExampleFilter(django_filters.FilterSet):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('kwarg_I_want_to_pass', None)
super(ExampleFilter, self).__init__(*args, **kwargs)
self.filters['field_to_filter'].extra.update({
'queryset': Supplier.objects.filter(related_user=self.user),
'empty_label': '',
'help_text': False
})
class Meta:
model = ExampleModel
fields = ['related_user', 'field_to_filter', ... other fields]

Django Rest, OrderingFilter using ForeignKey field?

I'm using Django Rest's OrderingFilter to order my API endpoint results (http://www.django-rest-framework.org/api-guide/filtering#orderingfilter)
like so:
/endpoint?ordering=-id
Is it possible to give it a foreign key field to order by?
like:
/endpoint?ordering=myfk__id
Trying the above doesn't seem to work for me
Just overwrite list method of ListModelMixin, then you can use all ORM ordering.
class ViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
params = request.GET.get('ordering', '')
if params:
ordering = [param.strip() for param in params.split(',')]
queryset = queryset.order_by(*ordering)
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
Specify the ordering_fields in your view. So your field should look like:
ordering_fields = ('myfk__id',)
EDIT:
Looks like this is not currently allowed in DRF. See here. That being said, that site includes a snippet of some code you can use to implement it. Basically, use the filter code posted to subclass the OrderingFilter and use that as your filter instead.

Django: 1.5: Overriding generic.DetailView returns 404

I have a generic.DetailView which I'm trying to override the function get_queryset(self) I'm doing the following:
class MyView(generic.DetailView):
model = MyModel
slug_field='guid'
template_name = 'myhtml.html'
def get_queryset(self):
guid = uuid.UUID(self.kwargs.get('slug'))
x = MyModel.objects.all().filter(guid=guid)
pprint(x)
return MyModel.objects.all().filter(guid=guid)
Here is the urls.py:
urlpatterns = patterns('',
url (r'^(?P<slug>[A-Fa-f0-9]{30,32})$', view.MyView.as_view(), name='myview'),
)
When I run the page, I keep getting a 404 error. However, I know MyModel returns something because pprint returns:
[<MyModel: mydata>]
PS If I do this same modification w/ PK: I still get a 404:
def get_queryset(self):
return MyModel.objects.all().filter(id=pk)
...and I know MyModel.objects.all().filter(id=pk) returns data
What am I missing?
Thanks
You're getting 404 because get_object() is run right after get_queryset(), which filters the queryset again (using slug url kwarg), giving you no results.
You should instead override get_object() method and return a single object.
Something like this should work:
def get_object(self, queryset=None):
slug = self.kwargs.get(self.slug_url_kwarg, None)
guid = uuid.UUID(slug)
return queryset.get(guid=guid)
NOTE:
This is just a quick example, you should definitely look into original get_object() method and see how to handle all the edge cases and exceptions.

Special handling of multiple urls to one view with class based generic views

I am converting a WordPress site to a django one. I need to preserve the url structure for old posts, but have a different structure for new posts. I've done this by creating the 2 urls, setting a date in settings.py, then setting the absolute url like so:
urls.py
url(r'^reviews/archives/(?P<pk>\d+)$', PostDetail.as_view(), name="oldpost_view"),
posts/urls.py
url(r'^(?P<slug>[-\w]+)$', PostDetail.as_view(), name="post_view"),
posts/models.py
#property
def is_old_post(self):
wp_date = settings.WP_ARCHIVE_DATE
if self.post_date.date() < wp_date:
return True
# return False
#models.permalink
def get_abs_url(self):
if self.is_old_post:
return ('oldpost_view', (), {
'pk': self.id,
}
)
else:
return ('post_view', [str(self.url_slug)])
I am using one view for the 2 urls:
class PostDetail(DetailView):
model = Post
slug_field = 'url_slug'
template_name = "posts/detail.html"
This all works great. Now, what I need to is prevent new posts from being rendered by the oldpost_view url and vice versa. I know I can override the "get" and use reverse for this but how can I tell which url the request came from? What is the most efficient and DRY way to do this?
If you don't follow my advice with the '301' status code above, here is how I would do it:
Override the get method on the DetailView
If the date is before CUTOFF_DATE, and request.path[:10] != "reviews/arc" --> Redirect (301)
elseif date is after CUTOFF_DATE and request.path[:10] == "reviews/arc" --> redirect
Something roughly like that.
Based on Issac Kelly's feedback I was able to solve my problem. Here's the updated views:
class PostDetail(DetailView):
model = Post
slug_field = 'post_name'
template_name = "posts/detail.html"
def OldPostView(request, pk):
post_name = get_object_or_404(Post, pk=pk).post_name
return redirect('post_view', slug=post_name, permanent=True)
I also updated my models to utilize the "post_name" field that WordPress has, then simplified my permalink:
#models.permalink
def get_abs_url(self):
return ('post_view', [str(self.post_name)])
Thanks Issac!