-
Notifications
You must be signed in to change notification settings - Fork 429
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
Issue #687 Added ability to specify query and fragment elements of url via linkify. #711
base: master
Are you sure you want to change the base?
Changes from all commits
a62227b
af13e95
53916f9
dafcdbd
9b78f2e
adc0b17
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -1,5 +1,6 @@ | ||||||||||
from collections import OrderedDict | ||||||||||
from itertools import islice | ||||||||||
from urllib.parse import urlencode | ||||||||||
|
||||||||||
from django.core.exceptions import ImproperlyConfigured | ||||||||||
from django.urls import reverse | ||||||||||
|
@@ -65,7 +66,9 @@ class LinkTransform: | |||||||||
accessor = None | ||||||||||
attrs = None | ||||||||||
|
||||||||||
def __init__(self, url=None, accessor=None, attrs=None, reverse_args=None): | ||||||||||
def __init__( | ||||||||||
self, url=None, accessor=None, attrs=None, reverse_args=None, query=None, fragment=None | ||||||||||
): | ||||||||||
""" | ||||||||||
arguments: | ||||||||||
url (callable): If supplied, the result of this callable will be used as ``href`` attribute. | ||||||||||
|
@@ -75,10 +78,14 @@ def __init__(self, url=None, accessor=None, attrs=None, reverse_args=None): | |||||||||
reverse_args (dict, tuple): Arguments to ``django.urls.reverse()``. If dict, the arguments | ||||||||||
are assumed to be keyword arguments to ``reverse()``, if tuple, a ``(viewname, args)`` | ||||||||||
or ``(viewname, kwargs)`` | ||||||||||
query (dict): If supplied, field-value pairs to be formatted into a query string. | ||||||||||
fragment (str): If supplied, value of URL fragment identifier (hash). | ||||||||||
""" | ||||||||||
self.url = url | ||||||||||
self.attrs = attrs | ||||||||||
self.accessor = accessor | ||||||||||
self.query = query | ||||||||||
self.fragment = fragment | ||||||||||
|
||||||||||
if isinstance(reverse_args, (list, tuple)): | ||||||||||
viewname, args = reverse_args | ||||||||||
|
@@ -95,23 +102,37 @@ def compose_url(self, **kwargs): | |||||||||
record = kwargs["record"] | ||||||||||
|
||||||||||
if self.reverse_args.get("viewname", None) is not None: | ||||||||||
return self.call_reverse(record=record) | ||||||||||
|
||||||||||
if bound_column is None and self.accessor is None: | ||||||||||
accessor = Accessor("") | ||||||||||
url = self.call_reverse(record=record) | ||||||||||
else: | ||||||||||
accessor = Accessor(self.accessor if self.accessor is not None else bound_column.name) | ||||||||||
context = accessor.resolve(record) | ||||||||||
if not hasattr(context, "get_absolute_url"): | ||||||||||
if hasattr(record, "get_absolute_url"): | ||||||||||
context = record | ||||||||||
if bound_column is None and self.accessor is None: | ||||||||||
accessor = Accessor("") | ||||||||||
else: | ||||||||||
raise TypeError( | ||||||||||
"for linkify=True, '{}' must have a method get_absolute_url".format( | ||||||||||
str(context) | ||||||||||
) | ||||||||||
accessor = Accessor( | ||||||||||
self.accessor if self.accessor is not None else bound_column.name | ||||||||||
) | ||||||||||
return context.get_absolute_url() | ||||||||||
context = accessor.resolve(record) | ||||||||||
if not hasattr(context, "get_absolute_url"): | ||||||||||
if hasattr(record, "get_absolute_url"): | ||||||||||
context = record | ||||||||||
else: | ||||||||||
raise TypeError( | ||||||||||
"for linkify=True, '{}' must have a method get_absolute_url".format( | ||||||||||
str(context) | ||||||||||
) | ||||||||||
) | ||||||||||
url = context.get_absolute_url() | ||||||||||
|
||||||||||
if self.query: | ||||||||||
url += "?" + urlencode( | ||||||||||
{ | ||||||||||
a: v.resolve(record) if isinstance(v, Accessor) else v | ||||||||||
for a, v in self.query.items() | ||||||||||
} | ||||||||||
) | ||||||||||
if self.fragment: | ||||||||||
url += "#" + self.fragment | ||||||||||
|
||||||||||
return url | ||||||||||
|
||||||||||
def call_reverse(self, record): | ||||||||||
""" | ||||||||||
|
@@ -150,6 +171,20 @@ def __call__(self, content, **kwargs): | |||||||||
|
||||||||||
return format_html("<a {}>{}</a>", attrs.as_html(), content) | ||||||||||
|
||||||||||
@classmethod | ||||||||||
def get_callback(cls, *args, **kwargs): | ||||||||||
""" | ||||||||||
This method constructs a LinkTransform and returns a callback function suitable as the linkify | ||||||||||
parameter of ``Column.__init__()`` This may be used if you wish to subclass LinkTransform or to access | ||||||||||
(future) features of LinkTransform which may not be exposed via ``linkify``. | ||||||||||
""" | ||||||||||
link_transform = cls(*args, **kwargs) | ||||||||||
|
||||||||||
def callback(record, value, **kwargs): | ||||||||||
return link_transform.compose_url(record=record, value=value, **kwargs) | ||||||||||
|
||||||||||
return callback | ||||||||||
|
||||||||||
|
||||||||||
@library.register | ||||||||||
class Column: | ||||||||||
|
@@ -207,9 +242,11 @@ class Column: | |||||||||
``a`` tag. The different ways to define the ``href`` attribute: | ||||||||||
|
||||||||||
- If `True`, the ``record.get_absolute_url()`` or the related model's | ||||||||||
`get_absolute_url()` is used. | ||||||||||
- If a callable is passed, the returned value is used, if it's not ``None``. | ||||||||||
- If a `dict` is passed, it's passed on to ``~django.urls.reverse``. | ||||||||||
``get_absolute_url()`` is used. | ||||||||||
- If a callable is passed, the returned value is used, if it's not `None`. | ||||||||||
- If a `dict` is passed, optional items named `query` (dict), and `fragment` (str), | ||||||||||
if present, specify the query string and fragment (hash) elements of the URL. | ||||||||||
The remaining items are passed on to ``~django.urls.reverse`` as kwargs. | ||||||||||
- If a `tuple` is passed, it must be either a (viewname, args) or (viewname, kwargs) | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. List is also supported per your change in line 326, so can you add it to the docs here too? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks. Will do, but I guess this is all going to take some time.. because we are on Debian Jessie here w/ Python 3.4. So I can run flake8, but can't run black (required python3.6) and tables2 tests fail due to too-old version of django_filters. We are in the process of a upgrading but it will take some time. Probably I'll just have to create a debian stretch VM for tables2 development. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you can just push however many times you like to let the CI do it, the logs can be inspected here: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, but if I have to write tests I need to be able to run them locally.. so I set up the environment. I've got several other tables2 features I want to offer to the project that I am using here, some implemented using monkeypatches that I have had in place for years, and I just recently found a problem with pagination and order_by that I want to fix (in the meantime, I coded around it by eliminating my use of RequestConfig and overriding SingleTableMixin.get_table()), so I might as well set things up correctly. So now I've been able to run black and see what it did not like about my formatting, so that's fine now. But I've got two questions:
jazzband/django-oauth-toolkit#752 So the tables2 tests all fail. So there's legitimate breakage between tables2 HEAD with Django 3.1 and I can fix that, but that should probably be a separate pr... meanwhile I am stuck on the tests, so any help understanding why tox doesn't use the right django is appreciated. This is my first time using tox so maybe I'm just running it wrong... I more or less followed what you did here: https://gist.github.com/jieter/e0c2f577fe3c685edd04 Thanks! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For 1: I had to look this up, so it is probably not consistent:
http://www.sphinx-doc.org/en/master/usage/restructuredtext/roles.html If we are going to adopt a rule, it would be some work to bring the existing docstrings into alignment with it, so I'd suggest to not worry about it too much for this PR. For 2: Tox installs different versions of Django for different entries in the test matrix: Lines 33 to 36 in 4941e4f
In addition to Django, it also installs a couple of other dependencies: from requirements/common.pip |
||||||||||
tuple, which is also passed to ``~django.urls.reverse``. | ||||||||||
|
||||||||||
|
@@ -295,8 +332,14 @@ def __init__( | |||||||||
link_kwargs = None | ||||||||||
if callable(linkify) or hasattr(self, "get_url"): | ||||||||||
link_kwargs = dict(url=linkify if callable(linkify) else self.get_url) | ||||||||||
elif isinstance(linkify, (dict, tuple)): | ||||||||||
elif isinstance(linkify, (list, tuple)): | ||||||||||
link_kwargs = dict(reverse_args=linkify) | ||||||||||
elif isinstance(linkify, dict): | ||||||||||
# specific keys in linkify are understood to be link_kwargs, and the rest must be reverse_args | ||||||||||
link_kwargs = { | ||||||||||
name: linkify.pop(name) for name in ("query", "fragment") if name in linkify | ||||||||||
} | ||||||||||
link_kwargs["reverse_args"] = linkify | ||||||||||
elif linkify is True: | ||||||||||
link_kwargs = dict(accessor=self.accessor) | ||||||||||
|
||||||||||
|
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 this does not work for
QueryDict
s, which is needed to support multiple values for a single key (i.e.id=1&id=2
). I'm not sure if thats worth it to add support for.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.
Hi, I am back! Are you still interested in this patch?
If the only think holding it back is support for QueryDict I can make that change.
Or.. I should ask.. is it actual QueryDict support you would like (direct usage of QueryDict class? Or just equivalent functionality, eg. a list would be interpreted as multiple values:
Result: ?a1&b=2&b=3&b=4&b=5
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.
Welcome back ;)
Yes, when looking at it now, multiple values is a must-have.