Django Pagination: switch between paginated/non-paginated ListView - python

I'm trying to elaborate a smart way to switch between a paginated template and a non-paginated one.
I already have a working paginator and I was thinking of adding a button next to it that read "Show all results" that linked to a non-paginated list, from there then there would be another button to go back to the paginated list.
1) Easy Solution
Use 2 ListViews with different assignations of the attribute paginate_by (django default to set pagination), but since I have many lists in my project it wouldn't be convenient (not much smart either).
2) Solution I'm stuck on
Write a Mixin (that will later be extended by my ListViews) to set the variable paginate_by based on a condition and then add some useful variables to the context :
class PaginationMixin:
no_pagination = False
no_pagination_url = ''
def get_paginate_by(self, queryset):
# overwrite django method
if self.no_pagination:
return None
else:
return super().get_paginate_by(queryset)
def get_no_pagination_url(self):
return self.no_pagination_url
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['no_pagination'] = self.no_pagination
context['no_pagination_url'] = self.get_no_pagination_url()
return context
class MyListView(PaginationMixin, ListView):
#...
def get_no_pagination_url(self):
return reverse('mylist_urlname')
PROBLEM: I don't know how to set the no_pagination variable from the template. Is there some way to do this?
Thanks for the help.
UPDATED SOLUTION (edited from #hi-lan solution):
This way it will show all results and also keep urlparams (from filters or other) if present.
class PaginationMixin:
toggle_pagination = False
toggle_pagination_url = ''
no_pagination = False
view_name = ''
urlparams_dict = {}
def get(self, request, page=None, *args, **kwargs):
#store current GET params and pop 'page' key
self.urlparams_dict = request.GET
self.urlparams_dict.pop('page', None)
page = page or request.GET.get('page', '1')
if page == 'all':
page = self.paginate_by = None
self.no_pagination = True
return super().get(request, page=page, *args, **kwargs)
def get_paginate_by(self, queryset):
if self.no_pagination:
return None
else:
return super().get_paginate_by(queryset)
def get_toggle_pagination_url(self):
# variables to set in view to toggle this mixin
if self.toggle_pagination and self.view_name:
if not self.no_pagination:
extra = {'page': 'all'}
self.urlparams_dict.update(extra)
else:
self.urlparams_dict.pop('page', None)
# url keeps track of urlparams adds page=all if toggled
self.toggle_pagination_url = reverse(self.view_name) + '?' + urlencode(self.urlparams_dict)
return self.toggle_pagination_url
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['toggle_pagination_url'] = self.get_toggle_pagination_url()
context['toggle_pagination'] = self.toggle_pagination
return context

The problem is at data flow back from user to indicate non pagination. The only way I can think of is to use special page number. There are two options, depends on which way you config urls.py.
In case of path('objects/page<int:page>/',
PaginatedView.as_view()), special number is 0 (as normal page number
is started from 1).
In case of /objects/?page=3, special number can be all.
In either case, we need to override get method as it is where we can retrieve user's selection.
class PaginationMixin:
no_pagination = False
view_name = ''
def get(self, request, page=None, *args, **kwargs):
page = page or request.GET.get('page', '1')
if page in ['0', 'all']:
page = self.paginate_by = None
else: pass
return super().get(request, page=page, *args, **kwargs)
def get_paginate_by(self, queryset):
# overwrite django method
if self.no_pagination:
return None
else:
return super().get_paginate_by(queryset)
def get_no_pagination_url(self):
# For using path
extra = {'page': '0'}
no_pagination_url = reverse(self.view_name, kwargs=extra)
# For using query params
extra = {'page': 'all'}
no_pagination_url = reverse(self.view_name) + '?' + urlencode(extra)
return no_pagination_url
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['no_pagination'] = self.no_pagination
context['no_pagination_url'] = self.get_no_pagination_url()
return context
class MyListView(PaginationMixin, ListView):
view_name = 'mylist_urlname'
#...

I'm trying the UPDATED SOLUTION of gccallie with this view :
class StageTempList(PaginationMixin, LoginRequiredMixin, SingleTableMixin, FilterView):
view_name = 'stagetemp-list'
table_class = StageTempTable
model = StageTemp
filterset_class = StageTempFilter
template_name = 'stage/stagetemp_list.html'
paginate_by = 30
strict = False
But when get_paginate_by return None, I get 25 rows.
Django version 2.1.2
UPDATE : the PaginationMixin Class I use
class PaginationMixin:
no_pagination = False
view_name = ''
def get(self, request, page=None, *args, **kwargs):
page = page or request.GET.get('page', '1')
if page in ['0', 'all']:
page = self.paginate_by = None
self.no_pagination = True
else: pass
return super().get(request, page=page, *args, **kwargs)
def get_paginate_by(self, queryset):
# overwrite django method
if self.no_pagination:
return None
else:
return super().get_paginate_by(queryset)
def get_no_pagination_url(self):
extra = {'page': 'all'}
no_pagination_url = reverse(self.view_name) + '?' + urlencode(extra)
return no_pagination_url
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['no_pagination'] = self.no_pagination
context['no_pagination_url'] = self.get_no_pagination_url()
return context

Related

How can I pass a model id from the url to a class based view?

I have a class-based view:
class Create(View):
note_id = None
http_method_names = ['post', 'patch']
default_title = "You fool! This was left empty"
default_body = "Why did you leave this blank :("
def dispatch(self, *args, **kwargs):
method = self.request.POST.get('_method', '').lower()
print('method = ', method)
if method == 'patch':
return self.patch(*args, **kwargs)
elif method == 'post':
self.post(*args, **kwargs)
return super(Create, self).dispatch(*args, **kwargs)
def post(self, note_id):
date = datetime.date.today()
title = self.request.POST.get('title', '')
body = self.request.POST.get('note', '')
# check for blank attributes
if title == "":
title = Create.default_title
if body == "":
body = Create.default_body
note = Note(note_title=title, note_body=body, publish_date=date, edit_date=None)
note.save()
return HttpResponseRedirect(reverse('notes:note'))
def patch(self, note_id):
note = Note.objects.get(id=note_id)
title = self.request.POST.get('title', '')
body = self.request.POST.get('note', '')
# if something changed
if title != note.note_title or body != note.note_body:
# check for blank attributes
if title == "":
title = Create.default_title
if body == "":
body = Create.default_body
note.note_title = title
note.note_body = body
note.edit_date = datetime.date.today()
note.save()
return HttpResponseRedirect(reverse('notes:note'))
and in url.py I have
urlpatterns = [
path('<int:note_id>/create/', views.Create.as_view(), name='create'),
path('<int:note_id>/edit/', views.Create.as_view(), name='edit')
]
Previously, with function-based views the note_id would just be passed to the function automatically. I can not figure out the equivalent of this in class based views. I've tried explictiely passing note_id to each function, but that did not work. I tried included Create.as_view(note_id=note_id) in my url.py, but to no avail.
Currently, running this code I get:
post() got multiple values for argument 'note_id'
As mentioned in the comment above, you need to access these values through self.kwargs. e.g:
class Create(View):
note_id = None # Delete this line
...
# delete the dispatch method - no need to overwrite this.
# Don't include note_id as an argument, but request should be an argument
def post(self, request):
note_id = self.kwargs['note_id']
....
def put(self, request): # Likewise here
note_id = self.kwargs['note_id']
....
It doesn't look like you need to write a custom dispatch method. If all you are doing is calling the appropriate method, then the dispatch method that comes for free with View is just fine :)

How to move some code from post method to a separate method in Django views

I have a class that take some info from a form, make some changes to it. And than saves it into database
At the moment all the logic is in the post method. And I want to make the code more structured and I want to put some part of it to a separate method. Is it possible? If so, how can I do it?
here is my code:
class AddSiteView(View):
form_class = AddSiteForm
template_name = 'home.html'
def get(self, request, *args, **kwargs):
form = self.form_class()
return render(request, self.template_name, { 'form': form })
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
site_instanse = form.save()
url = request.POST.get('url', '')
if url.endswith('/'):
url = url + "robots.txt"
else:
url = url + "/robots.txt"
robot_link = Robot(
site = site_instanse,
link = url,
)
robot_link.save()
pk = Robot.objects.get(site=site_instanse)
return redirect('checks:robots', pk.id)
return render(request, self.template_name, { 'form': form })
I want to make 2 changes to it:
The 1st thing I want to do is to move this part of code to a separate method
if url.endswith('/'):
url = url + "robots.txt"
else:
url = url + "/robots.txt"
And the 2nd thing I want to do is to move this part of code also in a separate method
robot_link = Robot(
site = site_instanse,
link = url,
)
robot_link.save()
pk = Robot.objects.get(site=site_instanse)
return redirect('checks:robots', pk.id)
The reason is that I will be adding more functions here. And I don't want to have it all in post method. If it is possible, please, help me. I've already tried several ways of solving this problem, but they didn't work
Thank you
There is nothing special about Django preventing you from using plain python functions. So, if you know how to define methods and functions, you should take the same approach. For example, the first part can be the function
def get_robots_url(url):
if url.endswith('/'):
url = url + "robots.txt"
else:
url = url + "/robots.txt"
return url
Then you call the extracted function in the same place
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
site_instance = form.save()
url = request.POST.get('url', '')
url = get_robots_url(url)
....
You can also define a function inside the class - a method, to group the code. For the 2nd part:
class AddSiteView(View):
...
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
site_instanse = form.save()
url = request.POST.get('url', '')
url = get_robots_url(url)
return self.create_robot(site_instanse, url)
return render(request, self.template_name, { 'form': form })
def create_robot(self, site_instance, url):
robot_link = Robot(
site = site_instanse,
link = url,
)
robot_link.save()
pk = Robot.objects.get(site=site_instance)
return redirect('checks:robots', pk.id)

How to change the form instance dynamically from the data entered at the previous step

I would like from the data entered in the form to check if the person exists in the database if yes we skip the registration form to another form. Alas my code does not work. Someone can help me.
Forms :
FORMS = [
("stage1-1", RegisterIdentification),
("stage1-2", RecRegister),
("stage2-1", BirthIdentification),
("stage2-2", RecBirth),
("stage3-1", FatherIdentification),
("stage3-2", RecFather),
("stage4-1", MotherIdentification),
("stage4-2", RecMother),
]
Templates
TEMPLATES = {
"stage1-1": "fr/public/births-wizard/stage1-1.html",
"stage1-2": "fr/public/births-wizard/stage1-2.html",
"stage2-1": "fr/public/births-wizard/stage2-1.html",
"stage2-2": "fr/public/births-wizard/stage2-2.html",
"stage3-1": "fr/public/births-wizard/stage3-1.html",
"stage3-2": "fr/public/births-wizard/stage3-2.html",
"stage4-1": "fr/public/births-wizard/stage4-1.html",
"stage4-2": "fr/public/births-wizard/stage4-2.html",
}
Views :
class BirthsWizard(SessionWizardView):
instance = None
def get_form(self, step=None, data=None, files=None):
"""
change the form or the step instance dynamically from the data we entered
at the previous step
"""
# get the default form
form = super(BirthsWizard, self).get_form(step, data, files)
if step is None:
step = self.steps.current
if step == 'stage1-2':
# get prev datas and chek if they are in Database
step1_data = self.get_cleaned_data_for_step('stage1-1')
register_doctype = step1_data.get('registerDoctype')
register_nationality = step1_data.get('registerNationality')
register_idNum = step1_data.get('registeridNum')
if register_doctype and register_nationality and register_idNum:
check_register = BirthsDeclarer.objects.filter(doctype=register_doctype,
nationality=register_nationality,
idNum=register_idNum)
if check_register:
# if prev datas are in Database jump to a specific step
form_name = "stage2-1"
form = form_name
return form
def get_template_names(self):
return TEMPLATES[self.steps.current]
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(BirthsWizard, self).dispatch(*args, **kwargs)
def done(self, form_list, **kwargs):
# i will process data here
return render(self.request, 'fr/public/births-wizard/done.html', {'form_data': [ form.cleaned_data for form in form_list ] })
Result: the page displays without the form
RESULT: THE PAGE DISPLAYS WITHOUT THE FORM

Pass context to pagination class in Django Rest Framework

I have my custom pagination class.
class BasicPagination(PageNumberPagination):
page_size = 3
page_size_query_param = 'page_size'
max_page_size = 20
def get_paginated_response(self, data):
has_next, has_previous = False, False
if self.get_next_link():
has_next = True
if self.get_previous_link():
has_previous = True
meta = collections.OrderedDict([
('page', self.page.number),
('has_next', has_next),
('has_previous', has_previous),
])
ret = collections.OrderedDict(meta=meta)
ret["results"] = data
return Response(ret)
Also I have a generics.ListCreateAPIView class, which has custom queryset method and pagination_class = BasicPagination. I wanna pass self.kwargs.get("obj_type") to pagination class so that it displays obj_type not results. Here is my class view. How can I pass self.kwargs to pagination class?
class Translation(ListCreateAPIView):
pagination_class = BasicPagination
serializer_class = TranslationStepSerializer
def get_queryset(self):
api_controller = ApiController.load()
obj_type = self.kwargs.get("obj_type")
pk = self.kwargs.get("pk")
data = api_controller.get_translation(obj_type, pk)
return data if not None else None
I am assuming that by this -
it displays obj_type not results
you mean that you want the key in your response to be obj_type instead of "results". That is obj_type is a string in your code.
I had a similar requirement where I wanted to modify the response based on certain conditions. As a workaround, I added all the required parameters to data itself, through which I customised the paginated response.
def get_paginated_response(self, data):
if self.get_next_link():
next_page = data["page_no"] + 1
else:
next_page = 0
response = {
"next": next_page,
'count': self.page.paginator.count,
'cards': data["cards"],
'companies': data["companies"],
'positions': data["positions"],
'cities':data["cities"]
}
tags = data.get('tags', None)
if tags is not None:
response['tags'] = tags
return Response(data=response)
In your case you can do something like:
ret[data['obj_type']] = data['results']
And prior to this, in the queryset:
data = {'results': api_controller.get_translation(obj_type, pk), 'obj_type': obj_type}
It's late I know. But in your view-set you can override get_paginated_response and list methods:
class Translation(ListCreateAPIView):
pagination_class = BasicPagination
serializer_class = TranslationStepSerializer
def get_queryset(self):
api_controller = ApiController.load()
obj_type = self.kwargs.get("obj_type")
pk = self.kwargs.get("pk")
data = api_controller.get_translation(obj_type, pk)
return data if not None else None
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data, extra_keys={'obj_type': self.kwargs.get("obj_type")})
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
def get_paginated_response(self, data, extra_keys):
"""
Return a paginated style `Response` object for the given output data.
"""
assert self.paginator is not None
return self.paginator.get_paginated_response(data, extra_keys)
and in your pagination class you can get extra_keys as argument:
class BasicPagination(PageNumberPagination):
page_size = 3
page_size_query_param = 'page_size'
max_page_size = 20
def get_paginated_response(self, data, extra_keys):
# here you can get your passed keys
# for example for obj_type
obj_type = extra_keys['obj_type']
has_next, has_previous = False, False
if self.get_next_link():
has_next = True
if self.get_previous_link():
has_previous = True
meta = collections.OrderedDict([
('page', self.page.number),
('has_next', has_next),
('has_previous', has_previous),
])
ret = collections.OrderedDict(meta=meta)
ret["results"] = data
return Response(ret)
Note: I used dictinary to pass your variable because if there be another variables to pass this is neater. but you can simply just pass your desired variable.

Django success url using kwargs

I am trying to amend my get_success_url so that if any kwargs have been passed to it, I can build the returned url using them.
Heres what I have so far:
class CalcUpdate(SuccessMessageMixin, UpdateView):
model = Calc
template_name = 'calc/cru_template.html'
form_class = CalcForm
def archive_calc(self, object_id):
model_a = Calc.objects.get(id = object_id)
model_b = Calc()
for field in model_a._meta.fields:
setattr(model_b, field.name, getattr(model_a, field.name))
model_b.pk = None
model_b.save()
self.get_success_url(idnumber = model_b.pk)
def form_valid(self, form):
#objects
if self.object.checked == True:
object_id = self.object.id
self.archive_calc(object_id)
#save
def get_success_url(self, **kwargs):
if kwargs != None:
return reverse_lazy('detail', kwargs = {'pk': kwargs['idnumber']})
else:
return reverse_lazy('detail', args = (self.object.id,))
So far this just gives a keyerror detailing 'idnumber'.
I have printed kwargs['idnumber'] and it returns the pk as expected however I just cant seem to see where I am going wrong with this.
Thanks in advance.
form_valid should return a HttpResponseRedirect https://github.com/django/django/blob/master/django/views/generic/edit.py#L57 which in your case, you never do. I dont know if you have any code after #save, but take a look at the comments I made in your code
class CalcUpdate(SuccessMessageMixin, UpdateView):
model = Calc
template_name = 'calc/cru_template.html'
form_class = CalcForm
def archive_calc(self, object_id):
model_a = Calc.objects.get(id = object_id)
model_b = Calc()
for field in model_a._meta.fields:
setattr(model_b, field.name, getattr(model_a, field.name))
model_b.pk = None
model_b.save()
return self.get_success_url(idnumber = model_b.pk) # you never return this value
def form_valid(self, form):
#objects
if self.object.checked == True:
object_id = self.object.id
return HttpResponseRedirect(self.archive_calc(object_id)) # you never return a `HttpResponse`
#save -- If this is where you are saving... you can store the value from archive and return it after saving
def get_success_url(self, **kwargs):
if kwargs != None:
return reverse_lazy('detail', kwargs = {'pk': kwargs['idnumber']})
else:
return reverse_lazy('detail', args = (self.object.id,))
Also you don't need to manually copy the fields, just do (assuming there are no unique constraints because if there were, your version would fail too):
def archive_calc(self, object_id):
c = self.model.objects.get(id = object_id)
c.pk = None
c.save()
return self.get_success_url(idnumber = c.pk)
After playing around with #Ngenator's answer and various other posts on here I have the following working code. However its not very nice to look at :(
def get_success_url(self):
if self.pknumber != None:
return reverse_lazy('pstdetail', args = (self.pknumber,))
else:
return reverse_lazy('pstdetail', args = (self.object.id,))
I have this self.pknumber = model_b.pk in the necessary place within the view and self.pknumber = None else where to enable the if statement to build the required url. Hope this helps anyone and feel free to point out any errors/improvements.
https://ccbv.co.uk/projects/Django/4.0/django.views.generic.edit/UpdateView/
You cannot pass parameters in get_success_url(self) method. You can only refer to self parameter. For example self.kwargs['pk'].

Categories

Resources