django pagination with random queryset, ?page=1 content different to root - django

I have a CBV Listview for which I obtain and random queryset and paginate it, but, I see that there seems to a difference in content from somedomain.com/quiz/1 and somedomain.com/quiz/1/?page=1
My Listview looks like so:
class Test_ListView1(ListView):
template_name = "test.html"
paginate_by = 12
context_object_name = "test"
def get_queryset(self):
queryset = list(SomeModel.objects.order_by("-created_at").values('id','question_field','some_json'))
random.shuffle(queryset)
return queryset[:24]
in my urls, I cache my page so that it returns the same value for a certain period (50000sec) of time like so:
path('somedomain.com/quiz/1', cache_page(50000)(Test_ListView1.as_view()), name="test1" ),
but, I still see a difference between somedomain.com/quiz/1 and somedomain.com/quiz/1/?page=1 - they seem to be two different pages..
How do I make them the same?

According to this ticket Django takes into account query params when construct cache key. You can see source here. request.build_absolute_uri() is using here which returns full url with query params. That's why somedomain.com/quiz/1 and somedomain.com/quiz/1/?page=1 are different pages for cache middleware.
So to fix I suppose you can just add redirect if page query parameter not provided, something like this:
def get(self, request):
if not request.GET.get("page"):
return redirect(reverse('view_name') + '?page=1')
return super().get(request)

Related

Can't test django autocomplete light view

Just following the tutorial for django autocomplete and I'm a little worried because I already can't seem to figure out how to see the view for autocomplete despite writing the view and url for it. I'm trying to use autocomplete-light on
admin for a model called KeywordInContext
urls.py:
url(r'^keywordincontext-autocomplete/$', views.KeywordInContextAutocomplete.as_view(), name='keywordincontext-autocomplete',),
views.py:
class KeywordInContextAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
if not self.user.is_authenticated():
return KeywordInContext.objects.none()
qs = KeywordInContext.objects.all()
if self.q:
qs = qs.filter(name__istartswith=self.q)
return qs
So I'm trying to just perform the "test view" part of the tutorial where I can see my autocomplete view and either I didn't configure something correctly or I didn't type in the url.
the url I attempt to use for the test is /admin/gtr_site/keywordincontext/?q=7 the only part of tht url that I actually type in is ?q=7 for a random primary key... but any variant of this just loads the "KeywordsInContext" admin page.
What am I missing?

DjangoRestFramework - How to serialize only certain number of objects when ViewSet GET / LIST is called?

So this is my ViewSet:
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = (IsAuthenticated, IsLikeOrOwnerDeleteOrReadOnly,)
def perform_create(self, serializer):
serializer.save(owner=self.request.user, location=self.request.user.userextended.location)
#detail_route(methods=['post'], permission_classes=[IsFromLoactionOrReadOnly])
def like(self, request, pk=None):
post = self.get_object()
post.usersVoted.add(request.user)
return Response(status=status.HTTP_204_NO_CONTENT)
and this is my URL / router:
router.register(r'posts', views.PostViewSet)
Now, when I go to this URL:
/posts
DRF sends all the posts and serializers (or so I think.. I don't have many posts yet so I'm assuming it sends all of them). What I want to do is I want to be able to limit the number of posts my ViewSet serializes to 10. The 10 objects I want to serialize depends on the page number which I want to force API users to send with the URL. For example, I want to force users to send a number with the URL like so:
/posts/x
and on the backend, I want to serialize posts with the pk x to x+9 (so if we assume x=1, then I want to serialize posts with pk=1, pk=2, pk=3... pk=10.). Is this possible with DRF? I'm guessing I use Pagination because when I read the documentation, it kind of looks like what I need but I can't fully wrap my head around what pagination exactly is and how I can use it to accomplish what I want. Here is the documentation for pagination: http://www.django-rest-framework.org/api-guide/pagination/
Thanks in advance.
You'll want to use the pagination controls that Django Rest Framework provides just like you guessed.
This snippit might help you wrap your head around it:
{
"count": 1023
"next": "https://api.example.org/accounts/?page=5",
"previous": "https://api.example.org/accounts/?page=3",
"results": [
…
]
}
It is showing a response that, in addition to the normal 'results' that you would expect also includes next and previous values. These are links to the end points that can be used to return the results for the previous page worth of data, or the next page worth of data. It is the front end's responsibility to map these links to the appropriate user controls so that the pagination navigation can occur.
You are right. You need Pagination to achieve this. Just include pagination_class like you wrote serializer_class etc. Also, create a class which will have the number, with which you wish to paginate.
from rest_framework.pagination import PageNumberPagination
class StandardResultsSetPagination(PageNumberPagination):
page_size = 10
page_size_query_param = 'page_size'
max_page_size = 1000
and set pagniation_class = StandardResultsSetPagination.

Providing parameters when reverse_lazy-ing a success_url redirect

TLDR: I want to be able to provide slug in reverse_lazy('view', kwargs={'slug':'my_page'}) like this: reverse_lazy('view').apply(kwargs={'slug':'my_page'}), after creating the lazy object.
I have the following url pattern that includes a slug to identify a page model instance:
url(r'^(?P<slug>'+settings.SLUG_PATTERN+')/$', views.MyView.as_view(), name='view'),
I have another view for editing the page:
url(r'^(?P<slug>'+settings.SLUG_PATTERN+')/_edit/$',
views.MyEditView.as_view(success_url=reverse_lazy('view')), name='edit'),
Note the addition of success_url so that when I submit the form with the new content I'm redirected to the now-edited page. In case I ever change my view url pattern I don't have to worry about updating the redirect for my edit url.
After the form is validated and saved, the view grabs the success url to be used in a HttpResponseRedirect. However just the name 'view' isn't enough to identify the URL. I also need to know the slug name which is stored in my page model's slug field.
A similar question is here: success_url in UpdateView, based on passed value
The answers suggest writing a custom get_success_url for every view, but there must be better approaches.
In the generic views in django's edit.py there's this:
url = self.success_url.format(**self.object.__dict__)
If success_url were given as a hard coded URL but with a slug identifier such as '{slug}/' this would replace it with the slug field in my model. That's very close to what I want, but I don't want to hard code my URL. This brings me to my question:
How can I pass in parameters to a reverse_lazy object? I would use this in my base view's get_success_url with self.object.__dict__ and it'd just work everywhere.
Moreover if my slug string was stored on separate Slug model I might want the success URL to be '{slug.name}/'. With the above approach I could supply a mapping between the URL parameters and model attributes:
redirect_model_mapping = {'slug': '{slug.name}'}
...
def get_success_url(self):
url = self.success_url
if is_a_lazy_redirect(url):
url = url.somehow_apply_parameters(redirect_model_mapping)
return url.format(**self.object.__dict__)
I would like somehow_apply_parameters to be equivalent to originally calling reverse_lazy('blog:view', kwargs=redirect_model_mapping). However I don't think this should be in urls.py because it shouldn't have to know about the mapping.
This is a hack, but does what I want...
class MyView(FormMixin, ...):
#this is actually set on child classes
redirect_model_mapping = {'slug':'{slug.name}'}
def get_success_url(self):
url = self.success_url
if url is not None:
if hasattr(self.success_url, '_proxy____kw'):
url_parameters = dict((k, v.format(**self.object.__dict__)) for k, v in six.iteritems(self.redirect_model_mapping))
url._proxy____kw = {'kwargs': url_parameters}
url = force_text(url)
else:
url = url.format(**self.object.__dict__)
else:
raise ImproperlyConfigured("No URL to redirect to.")
return url
It replaces the kwards parameter normally passed to reverse_lazy but after it actually has the values it needs. As reverse_lazy also requires the string to match the regex, I had to make the mapping between url parameters and the values in the models first.
I'd quite like an approach that doesn't need to write to _proxy____kw.

Django pagination url parameters

I am trying to use Django's pagination for class based views, as described in the docs.
In my urls.py, I have:
url(r'^clues/page(?P<page>[0-9]+)/$', views.ClueIndexView.as_view())
The docs tell me I should be able to access this with an url like:
/clues/?page=3
But that always fails with a 404.
Instead, /clues/page3/ works....but that isn't what I want...I want to use ?page=3.
What am I doing wrong?
EDIT:
I'm handling it with a class-based view, like so:
class ClueIndexView(ListView):
context_object_name = 'clue_list'
template_name = 'clue_list.html'
queryset = Clue.objects.all()
paginate_by = 10
You should do something like this:
url(r'^clues/$')
def clues(request):
if request.method == 'GET':
page = request.GET.get('page')
...
all GET info passed after '?' like your page '?page=n' stored in request.GET dictionary
My url was bad. I found the docs to be a bit confusing. My url needs to be just
url(r'^clues/$', views.ClueIndexView.as_view()
Works now.

Django: How to return user to correct pagination page after CreateView form_invalid?

I have a paginated comments page. Pagination is provided by django-endless-pagination. When clicking to new paginated pages a get parameter is append to the url. Such as ?page=4.
Each comment on each paginated page displays a 'reply to' comment form containing a captcha field.
My view uses CreateView and I implement form_invalid myself in order to add some data to the context variable. At the end of my form_invalid method I return self.render_to_response(context)
The Problem
If a user attempts to reply to a comment when on page 4, and that user supplies and invalid captcha, then the pagination get parameter (?page=4) is lost during the response.
How can I redirect to the full path (keeping get params) and pass context data along with it?
Thanks
This problem is similar to this SO question, but my scenario is a little different given that I want to maintain my form state (the captcha error mentioned in question) in the redirect.
Thank you #petkostas for pointing out HTTP_REFERER
My resolution to this involved storing the context in cache with a cache key derived from the current timestamp. I redirect to the (same) comments url, but in doing this I append the current comment page as a get parameter and also the timestamp as another get parameter.
Then during get requests the view checks for the existence of the timestamp parameter. If it is provided, then a cache.get call is made to retrieve the needed context data. And finally, the cached item is deleted.
from datetime import datetime
from django.core.cache import cache
from django.shortcuts import redirect
from django.utils.dateformat import format
class MyView(CreateView):
def form_invalid(self, form, **kwargs):
context = {
'form': form,
... other context stuff ...
}
timestamp = format(datetime.now(), u'U')
cache.set('invalid_context_{0}'.format(timestamp), context)
page = 1
full_path = self.request.META.get('HTTP_REFERER')
if '?' in full_path:
query_string = full_path.split('?')[1].split('&')
for q in query_string:
if q.startswith('page='):
page = q.split('=')[1]
response = redirect('my_comments_page')
response['Location'] += '?page={0}&x={1}'.format(page, timestamp)
return response
def get_context_data(self, **kwargs):
context = super(MyView, self).get_context_data(**kwargs)
context.update({
... add context stuff ...
})
if 'x' in self.request.GET:
x = self.request.GET.get('x')
cache_key = 'invalid_context_{0}'.format(x)
invalid_context = cache.get(cache_key)
if invalid_context:
context.update(invalid_context)
cache.delete(cache_key)
return context
I haven't used django-endless-pagination, but Django offers 2 ways to get either the full current request path, or the Refferer of the request:
In your view or template (check documentation on how to use request in templates) for the referring requesting page:
request.META.get('HTTP_REFERER')
Or for the current request full path:
request.full_path()