-
-
Notifications
You must be signed in to change notification settings - Fork 32.8k
Fixed #36175 -- Refactor admin pagination to make it reusable across different queryset. #19172
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
53a20fe
to
69216c0
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like a good start.
My biggest concerns are about the missing deprecation if we are going with a changed API.
In order to validate that this is not breaking anything I would also duplicate the tests instead of changing them:
- keep a copy of the old tests as they are + add a check that a deprecation warning is raised and a note to remove those after the deprecation cycle
- add the changed version of the tests to validate the new API.
django/contrib/admin/options.py
Outdated
@@ -843,6 +843,14 @@ def get_changelist(self, request, **kwargs): | |||
|
|||
return ChangeList | |||
|
|||
def get_pagination(self, request, **kwargs): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given this returns a class, is there a reason not to use get_pagination_class
instead of get_pagination
?
See also comment below in the same area
django/contrib/admin/options.py
Outdated
page_obj = paginator.get_page(page_number) | ||
page_range = paginator.get_elided_page_range(page_obj.number) | ||
Pagination = self.get_pagination(request) | ||
pagination = Pagination( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like having both Pagination
and pagination
can be quite confusing, especially since we have get_pagination
that returns the class and not the instance.
This would look better to me if we had:
pagination_class
andget_pagination_class
instead ofPagination
andget_pagination
- maybe also have a new
get_pagination
that would be in charge of callingget_pagination_class
and instantiating the pagination class instead of directly calling thePagination
class.
This would be similar to a pattern we have in other places in Django, like in the FormView
https://ccbv.co.uk/projects/Django/5.0/django.views.generic.edit/FormView/#get_form
@@ -35,36 +36,37 @@ | |||
|
|||
|
|||
@register.simple_tag | |||
def paginator_number(cl, i): | |||
def paginator_number(p, i): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm aware you are replacing the cl
abbreviation that is 2 letters with a similar-type abbreviation for pagination but p
is probably too short. Could this be pagination
instead of just p
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree, better to have descriptive variable names to make the code more readable and self-documenting.
i, | ||
) | ||
|
||
|
||
def pagination(cl): | ||
def pagination(p): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above, one-letter variable names outside of very specific use-cases isn't great
@@ -82,6 +84,23 @@ def pagination_tag(parser, token): | |||
) | |||
|
|||
|
|||
def changelist_pagination(p, cl): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
one-letter variable-name here as well
django/contrib/admin/utils.py
Outdated
new_params = {} | ||
if remove is None: | ||
remove = [] | ||
p = params.copy() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still not found of the one-letter variable names ;-)
p
, k
, r
...
Even though I know you are just moving pre-existing code; if this piece of code is being touched, it's probably a good time to also make it more readable
django/contrib/admin/views/main.py
Outdated
def get_results(self, request): | ||
paginator = self.model_admin.get_paginator( | ||
request, self.queryset, self.list_per_page | ||
pagination = Pagination( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calling Pagination
directly seems less flexible than having a separate get_paginator
method.
If feels like the recommended approach from above (get_pagination_class
+ get_pagination
would be more flexible and extendable)
self.show_full_result_count = self.model_admin.show_full_result_count | ||
# Admin actions are shown if there is at least one entry | ||
# or if entries are not counted because show_full_result_count is disabled | ||
self.show_admin_actions = not self.show_full_result_count or bool( | ||
full_result_count | ||
) | ||
self.full_result_count = full_result_count | ||
self.result_list = result_list | ||
self.can_show_all = can_show_all |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The API of get_results
is being changed here.
If this is the final approach, I don't see an issue with the change itself but we need to deprecate this properly:
ie:
- deprecation warning
- a workaround to populate the old properties while the deprecation is active
Thank you, @nanuxbe! 😃 Since this is my first time handling such a issue, I may not have fully understood all the points you suggested. It might take me some time to complete this work, but I will make progress every day and ensure it’s not delayed too much. Once I believe the work is done, would it be okay to request review again from you? |
Yes, feel free to ping me when you think this is ready for another pass! |
69216c0
to
308139a
Compare
Hello @nanuxbe ! However, the ticket(36175) I am currently working on does not aim to fully expose One thing I'm wondering about is whether the |
9b69465
to
364bdd3
Compare
@Antoliny0919 I am not sure what you are asking about. AFAICT what you are working on here is a wrapper around Paginator for use in the admin so that several things there have the same pagination. I am not sure what benefit there would be to make that API available outside of the admin and make it available outside of the admin, can you elaborate on this? |
@nanuxbe Ah,,, I think I misunderstood. Your understanding is correct. I should focus on this task regardless of ticket(24947). sorry about that. 🥲 I haven't worked on the documentation yet, but if you don't mind, could you review the work I've done so far? |
@Antoliny0919 Sure. My day today is going to get quite busy in about ten minutes though, so I'll probably only be able to give it a good review tomorrow or Wednesday |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you pull out the get_query_string
refactor to a separate commit?
Instead of defining a new utility, we should use querystring
I think we should also consider using querystring
directly in the template, and pass the arguments in the context
"replaced by an method in the Pagination class.", | ||
RemovedInDjango70Warning, | ||
stacklevel=2, | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Antoliny0919 the deprecation warnings look good to me
364bdd3
to
d38d4de
Compare
@Antoliny0919 No pressure but I see there is no more activity here, is this ready for another review round? |
Yes @nanuxbe, there are still parts that need to be worked on after 35529 is merged, and the documentation has not been updated yet(This is the reason why I haven't marked the ticket as ready for review.). However, if you are willing to review it, I would appreciate it. |
Return an instance of the `Pagination` class to be | ||
used for paginating the queryset. | ||
""" | ||
Pagination = self.get_pagination_class(request) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since you are calling get_pagination_class
without passing any kwargs, and get_pagination_class
itself doesn't use the kwargs, I wonder if they should be removed from the method definition
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the late reply. I agree, it looks like kwargs aren’t necessary here.
|
||
context = { | ||
**self.admin_site.each_context(request), | ||
"title": _("Change history: %s") % obj, | ||
"subtitle": None, | ||
"action_list": page_obj, | ||
"page_range": page_range, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These 3 params (page_range
, page_var
and pagination_required
should probably be left in the context with a deprecation warning if accessed.
The use case here is that there are quite a few admin themes around and removing variables from a context will probably break a few of those
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. Suddenly removing them from the context could cause problems for people who were previously using those context variables.
However, I haven’t found a way to show a deprecation warning when those variables are accessed from the context.
Should I implement it myself? I’ll look into it a bit more for now!
django/contrib/admin/paginations.py
Outdated
return format_html("{} ", self.paginator.ELLIPSIS) | ||
elif i == self.page_num: | ||
return format_html('<span class="this-page">{}</span> ', i) | ||
else: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My preference here would be to drop the else
and un-indent the rest of the code. Ie: return early and have the main body of the function as the "default".
It's a just preference though
from django.utils.http import urlencode | ||
from django.utils.timezone import make_aware | ||
from django.utils.translation import gettext | ||
|
||
# Changelist settings | ||
ALL_VAR = "all" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if those should be left there with a deprecation warning. I would expect one of the fellows would have an opinion about this
Thank you for taking an interest in this work and for reviewing it @nanuxbe 🥰. I will reflect on the points you mentioned as soon as I have time. |
c794504
to
f772203
Compare
django/contrib/admin/paginations.py
Outdated
|
||
@property | ||
def pagination_required(self): | ||
return (not self.show_all or not self.can_show_all) and self.multi_page |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return (not self.show_all or not self.can_show_all) and self.multi_page | |
self.multi_page and not (self.show_all and self.can_show_all) |
I think reversing the order here is more readable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. Thank you, cliff!
django/contrib/admin/paginations.py
Outdated
def render_all_pages(self): | ||
""" | ||
Generate the HTML for all page links in the pagination. | ||
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This name doesn't sound property-like. It sounds more like it does something, rather than representing something. Maybe something like:
def render_all_pages(self): | |
""" | |
Generate the HTML for all page links in the pagination. | |
""" | |
def all_rendered_pages(self): | |
"""HTML for all the pages in the pagination.""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since it is provided as a property rather than a method, the name you suggested is more appropriate.
Hmm, but I also think it might be better if it were provided as a method rather than a property 🧐
"paginator_number template tag has been " | ||
"replaced by an method in the Pagination class.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"paginator_number template tag has been " | |
"replaced by an method in the Pagination class.", | |
"paginator_number template tag has been replaced by an method in the Pagination " | |
"class.", |
django/contrib/admin/views/main.py
Outdated
SEARCH_VAR = "q" | ||
ERROR_FLAG = "e" | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
django/contrib/admin/views/main.py
Outdated
self.list_select_related = list_select_related | ||
self.list_per_page = list_per_page | ||
self.list_max_show_all = list_max_show_all | ||
self.list_select_related = list_select_related |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can revert this 👍🏾
django/contrib/admin/paginations.py
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think pagination.py
is a better name here
django/contrib/admin/views/main.py
Outdated
"ChangeList().page_num attribute is deprecated. " | ||
"use pagination.page_num instead.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"ChangeList().page_num attribute is deprecated. " | |
"use pagination.page_num instead.", | |
"ChangeList().page_num attribute is deprecated. Use " | |
pagination.page_num instead.", |
django/contrib/admin/views/main.py
Outdated
"ChangeList().show_all attribute is deprecated. " | ||
"use pagination.show_all instead.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto, as above. Capitalise second sentence
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also in the other warnings
docs/ref/contrib/admin/index.txt
Outdated
all" admin change list page. The admin will display a "Show all" link on the | ||
change list only if the total result count is less than or equal to this | ||
setting. By default, this is set to ``200``. | ||
all" admin page(change list, history). The admin will display a "Show all" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
all" admin page(change list, history). The admin will display a "Show all" | |
all" admin page (change list, history). The admin will display a "Show all" | |
```suggestion |
docs/ref/contrib/admin/index.txt
Outdated
|
||
.. attribute:: ModelAdmin.list_per_page | ||
|
||
Set ``list_per_page`` to control how many items appear on each paginated | ||
admin change list page. By default, this is set to ``100``. | ||
admin page(change list, history). By default, this is set to ``100``. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
admin page(change list, history). By default, this is set to ``100``. | |
admin page (change list, history). By default, this is set to ``100``. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @Antoliny0919 for your dedication to this work ⭐ !
I left some comments (from an incomplete review).
I think you should split your commit so that the deprecations are in a separate first commit and the fix in a different commit. That will make this a bit easier to review.
…different queryset.
f772203
to
50ba1d5
Compare
Thank you for reviewing this work @cliff688 . |
Could you please add the Selenium label? 😎 |
@Antoliny0919 do you mind resolving the conflicts before this goes through another round of review? ⭐ |
Nice!! I'm glad this work can be reviewed! |
Trac ticket number
ticket-36175
Branch description
Provide a concise overview of the issue or rationale behind the proposed changes.
Refactored the pagination in the admin interface by separating the parts that were specifically designed for
ChangeList
, making thePagination
class reusable across different queryset.Checklist
main
branch.