I am implementing csv export on django. In particular I have a link on my template to export actual query and for this I am trying to handle all on the same class based view.
Here is my code
# views.py
class MyView(ListView):
template_name = 'my_template.html'
model = Archivio
def get_queryset(self):
if self.request.GET.get('q'):
dateC = '01/01/'+self.request.GET.get('q')
queryset = Archivio.objects.filter(~Q(quoteiscrizione__anno_quota__exact=self.request.GET.get('q'))
return queryset
# my custom method
#staticmethod
def csv_output():
qs = MyView.get_queryset(): # i want to access to the queryset variable from get_queryset() method of the class
# here i have other code to produce csv output
But the method csv_output() is wrong.. I get this TypeError: csv_output() takes 0 positional arguments but 1 was given
I have also tried with a classmethod decorator but without success.
My question is: How can i access queryset variable from another method of the same class?
Note the self in get_queryset(self). It means the first argument of this method is always the instance of the class (in your case, MyView). So when you want to call it in another method, you should provide this instance.
The solution is to replace your #staticmethod decorator with the #classmethod one:
class MyView(ListView):
template_name = 'my_template.html'
model = Archivio
def get_queryset(self):
if self.request.GET.get('q'):
dateC = '01/01/' + self.request.GET.get('q')
queryset = Archivio.objects.filter(~Q(quoteiscrizione__anno_quota__exact=self.request.GET.get('q'))
return queryset
#classmethod
def csv_output(cls):
qs = cls.get_queryset(cls)
# Class methods have one required positional argument
# which is the class that contains them
# They are called like this:
# MyClass.class_method()
# So in your case, you can call
# get_queryset() by doing MyView.get_queryset()
# But as you are in a class method, you do
# cls.get_queryset()
# As get_queryset() needs one positional argument *self*,
# which is the instance of your class, you do
# cls.get_queryset(cls)
# and it will work as expected :-)
I solved my Issue saving my queryset on the request and then using it somewhere on my views like this:
views.py
class SomeClass(ListView):
def get_queryset(self):
.....
queryset = MyModel.objects.filter(*somefilterlist)
self.request.session['search_queryset'] = serialize('json', queryset) # in order to save something on session in must be JSON serialized
class Output(SomeClass):
#classmethod
def cvsOutput(cls):
deserialized = list(deserialize('json', request.session.get('search_queryset')))
pk_list = []
for arch in deserialized:
pk_list.append(arch.object.pk) # List of pk
queryset = Archivio.objects.filter(pk__in=pk_list) # Query the list ok pk's
In this way I was able to make available the list ok pk of the queryset of my ListView, then I make again the same query based on that list of pk's..
Related
I am able to render class based view generic ListView template using parameter hard coded in views.py.
class ResourceSearchView(generic.ListView):
model = creations
context_object_name = 'reviews'
template_name = 'reviews.html'
query = 'theory'
# def get(self, request):
# if request.GET.get('q'):
# query = request.GET.get('q')
# print(query)
queryset = creations.objects.filter(narrative__contains=query).order_by('-post_date')
However, when parameter is sent via form by GET method (below),
class ResourceSearchView(generic.ListView):
model = creations
context_object_name = 'reviews'
template_name = 'reviews.html'
query = 'theory'
def get(self, request):
if request.GET.get('q'):
query = request.GET.get('q')
print(query)
queryset = creations.objects.filter(narrative__contains=query).order_by('-post_date')
I receive this error
The view creations.views.ResourceSearchView didn't return an
HttpResponse object. It returned None instead.
Note that the parameter name q and associated value is being retrieved successfully (confirmed using print(query)).
So with CBV in Django, you have to return some kind of valid response that the interpreter can use to perform an actual HTTP action. Your GET method isn't returning anything and that's what is making Django angry. You can render a template or redirect the user to a view that renders a template but you must do something. One common pattern in CBV is to do something like:
return super().get(request, *args, **kwargs)
...which continues up the chain of method calls that ultimately renders a template or otherwise processes the response. You could also call render_to_response() directly yourself or if you're moving on from that view, redirect the user to get_success_url or similar.
Have a look here (http://ccbv.co.uk) for an easy-to-read layout of all the current Django CBVs and which methods / variables they support.
Thanks for the responses. Here is one solution.
class ResourceSearchView(generic.ListView):
model = creations
context_object_name = 'reviews'
template_name = 'reviews.html'
def get_queryset(self):
query = self.request.GET.get('q')
queryset = creations.objects.filter(narrative__contains=query).order_by('-post_date')
return queryset
this part of my code fills the queryset with [category_object].subcats.all(). let subcats be a method of category object:
serializer:
class CatSrlz(serializers.ModelSerializer):
class Meta:
model = Category
fields = ('id', 'label', )
View:
class CatsViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Category.objects.filter(parent=None)
serializer_class = CatSrlz
def retrieve(self, request, *args, **kwargs):
# return Response({'res': self.kwargs})
queryset = Category.objects.get(pk=str(self.kwargs['pk'])).subCats.all()
dt = CatSrlz(queryset, many=True)
return Response(dt.data)
and url:
router.register(r'cats', views.CatsViewSet)
it works but i'm pretty sure that there must be a more correct way of doing so
Is there one?
thanks
When retrieving a single object, you can use the get_object method in your view, which look like this in DRF without modifications :
def get_object(self):
"""
Returns the object the view is displaying.
You may want to override this if you need to provide non-standard
queryset lookups. Eg if objects are referenced using multiple
keyword arguments in the url conf.
"""
queryset = self.filter_queryset(self.get_queryset())
# Perform the lookup filtering.
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
assert lookup_url_kwarg in self.kwargs, (
'Expected view %s to be called with a URL keyword argument '
'named "%s". Fix your URL conf, or set the `.lookup_field` '
'attribute on the view correctly.' %
(self.__class__.__name__, lookup_url_kwarg)
)
filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
obj = get_object_or_404(queryset, **filter_kwargs)
# May raise a permission denied
self.check_object_permissions(self.request, obj)
return obj
So you could adapt the part where you get your object :
filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
obj = get_object_or_404(queryset, **filter_kwargs)
and add your subcat logic there.
By the way, I don't get why you are using
dt = CatSrlz(queryset, many=True)
Shouldn't "retrieve" return a single object?
I basically need something like /?status=[active,processed] or /?status=active,processed
My current setting is: 'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.DjangoFilterBackend',) and it's only filtering one value correctly (/?status=active)
I think there is no inbuilt functionality for that. But you can implement a custom filter to do that. This custom filter you can use in your filterset.
import django_filters as df
class InListFilter(df.Filter):
"""
Expects a comma separated list
filters values in list
"""
def filter(self, qs, value):
if value:
return qs.filter(**{self.name+'__in': value.split(',')})
return qs
class MyFilterSet(df.FilterSet):
status = InListFilter(name='status')
You can use 'field_in' when using Class.object.filter method.
class FileterdListAPIView(generics.ListAPIView):
serializer_class = FooSerializer
def get_queryset(self):
user_profile = self.kwargs['pk']
if user_profile is not None:
workers = Worker.objects.filter(user_profile = user_profile)
queryset = MyModel.objects.filter(worker_in=(workers))
else:
return ''
return queryset
I'm trying to create a FilterSet with filters set with a MethodFilter expecting multiple values like that :
Filters.py
class MyFilter(django_filters.FilterSet):
first_filter = django_filters.MethodFilter()
class Meta:
model = myModel
fields = ['first_filter']
def filter_first_filter(self, queryset, value):
# I expect value to setup with an array of values
myquery = Q()
return queryset.filter(myquery)
Views.py
class MyView(RetrieveAPIView):
def get(self, request, format=None, **kwargs):
filter = MyFilter(request.query_params, queryset=myModel.objects.all())
# Other things go there using the filter instanciated
So when I request the view with this kind of URL /my_view?first_filter=thing1&first_filter=thing2, Only thing2 is passed in the value of the method filter_first_filter instead of ['thing1', 'thing2].
How to change this behaviour ?
I just figured out why that was not working.
The fact to instanciate the FilterSet with request.query_params was wrong because query_params is a QueryDict and the get function of the QueryDict returns only the last element. So to solve the problem, I should do :
class MyView(RetrieveAPIView):
def get(self, request, format=None, **kwargs):
dict_params = dict(request.query_params.iterlists())
filter = MyFilter(dict_params, queryset=myModel.objects.all())
So, I have a Django generic view:
class Foobaz(models.Model):
name = models.CharField(max_length=140)
organisation = models.ForeignKey(Organisation)
class FoobazForm(forms.ModelForm):
class Meta:
model = Foobaz
fields = ('name')
class FoobazCreate(CreateView):
form_class = FoobazForm
#login_required
def dispatch(self, *args, **kwargs):
return super(FoobazCreate, self).dispatch(*args, **kwargs)
What I'm trying to do is to take the organisation id from the URL:
/organisation/1/foobaz/create/
And add it back to the created object. I realise I can do this in CreateView.form_valid(), but from what I understand this is then completely unvalidated.
I've tried adding it to get_form_kwargs() but this does not expect the organisation kwarg as it is not in the included fields.
Ideally what I'd like to do is to add it to the instance of the form to validate it with the rest - ensuring it is a valid organisation, and that the user in question has the correct permissions to add a new foobaz to it.
I'm happy to just roll my own view if that is the best way of doing this, but I may just be simply missing a trick.
Thanks!
I thnik it would be better to include the organisation field and define it as hidden and readonly, this way django will validate it for you.
You can then override get_queryset method like this:
def get_queryset(self):
return Foobaz.objects.filter(
organisation__id=self.kwargs['organisation_id'])
where organisation_id is a keyword in url pattern.
You can do it overriding the View's get_kwargs() method and the Form's save() method. In the get_kwargs() I "inject" the organization_id into the initial data of the form, and in save() I retrieve the missing info with the supplied initial data:
In urls.py:
urlpatterns('',
#... Capture the organization_id
url(r'^/organisation/(?P<organization_id>\d+)/foobaz/create/',
FoobazCreate.as_view()),
#...
)
In views.py:
class FoobazCreate(CreateView):
# Override get_kwargs() so you can pass
# extra info to the form (via 'initial')
# ...(all your other code remains the same)
def get_form_kwargs(self):
# get CreateView kwargs
kw = super(CreateComment, self).get_form_kwargs()
# Add any kwargs you need:
kw['initial']['organiztion_id'] = self.kwargs['organization_id']
# Or, altenatively, pass any View kwarg to the Form:
# kw['initial'].update(self.kwargs)
return kw
In forms.py:
class FoobazForm(forms.ModelForm):
# Override save() so that you can add any
# missing field in the form to the model
# ...(Idem)
def save(self, commit=True):
org_id = self.initial['organization_id']
self.instance.organization = Organization.objects.get(pk=org_id)
return super(FoobazForm, self).save(commit=commit)