1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
|
from django.db import models
from django.utils.encoding import force_str
from django.utils.html import conditional_escape
from django.utils.safestring import mark_safe
from .base import Column, LinkTransform, library
@library.register
class ManyToManyColumn(Column):
"""
Display the list of objects from a `ManyRelatedManager`
Ordering is disabled for this column.
Arguments:
transform: callable to transform each item to text, it gets an item as argument
and must return a string-like representation of the item.
By default, it calls `~django.utils.force_str` on each item.
filter: callable to filter, limit or order the QuerySet, it gets the
`ManyRelatedManager` as first argument and must return a filtered QuerySet.
By default, it returns `all()`
separator: separator string to join the items with. default: ``", "``
linkify_item: callable, arguments to reverse() or `True` to wrap items in a ``<a>`` tag.
For a detailed explanation, see ``linkify`` argument to ``Column``.
For example, when displaying a list of friends with their full name::
# models.py
class Person(models.Model):
first_name = models.CharField(max_length=200)
last_name = models.CharField(max_length=200)
friends = models.ManyToManyField(Person)
is_active = models.BooleanField(default=True)
@property
def name(self):
return f"{self.first_name} {self.last_name}"
# tables.py
class PersonTable(tables.Table):
name = tables.Column(order_by=("last_name", "first_name"))
friends = tables.ManyToManyColumn(transform=lambda user: user.name)
If only the active friends should be displayed, you can use the `filter` argument::
friends = tables.ManyToManyColumn(filter=lambda qs: qs.filter(is_active=True))
"""
def __init__(
self, transform=None, filter=None, separator=", ", linkify_item=None, *args, **kwargs
):
kwargs.setdefault("orderable", False)
super().__init__(*args, **kwargs)
if transform is not None:
self.transform = transform
if filter is not None:
self.filter = filter
self.separator = separator
link_kwargs = None
if callable(linkify_item):
link_kwargs = dict(url=linkify_item)
elif isinstance(linkify_item, (dict, tuple)):
link_kwargs = dict(reverse_args=linkify_item)
elif linkify_item is True:
link_kwargs = dict()
if link_kwargs is not None:
self.linkify_item = LinkTransform(attrs=self.attrs.get("a", {}), **link_kwargs)
def transform(self, obj):
"""
Transform is applied to each item of the list of objects from the ManyToMany relation.
"""
return force_str(obj)
def filter(self, qs):
"""
Filter is called on the ManyRelatedManager to allow ordering, filtering or limiting
on the set of related objects.
"""
return qs.all()
def render(self, value):
items = []
for item in self.filter(value):
content = conditional_escape(self.transform(item))
if hasattr(self, "linkify_item"):
content = self.linkify_item(content=content, record=item)
items.append(content)
return mark_safe(conditional_escape(self.separator).join(items))
@classmethod
def from_field(cls, field, **kwargs):
if isinstance(field, models.ManyToManyField):
return cls(**kwargs)
|