A reusable Django app to make integrations with the DataTables javascript library easy.
Project description
About
Listable is a Django package to make the integration of your Django models with Datatables.js easy.
Django-listable was motivated by my repeated need to generate sortable and filterable tables from my Django models for CRUD apps.
The idea is that you should easily be able to go from a model like this:
class Staff(models.Model): first_name = models.CharField(max_length=255, help_text=_("Enter the name of the staff being rounded")) last_name = models.CharField(max_length=255, help_text=_("Enter the name of the staff being rounded")) active = models.CharField(max_length=10, choices = ACTIVE_CHOICES) position = models.ForeignKey(Position) department = models.ForeignKey(Department) limit = models.Q(app_label='staff', model='genericmodela') | models.Q(app_label='staff', model='genericmodelb') content_type = models.ForeignKey(ContentType, limit_choices_to=limit) object_id = models.PositiveIntegerField() generic_object = generic.GenericForeignKey("content_type", "object_id")
to a filterable/orderable table in a template like this with as little code as possible:
There are a couple of other similar projects worth checking out to see if they fit your needs better:
Installation
$ pip install django-listable
Settings
Listable currently has 4 settings you can configure to be used as default values for your table (they can be overriden in the listable template tag).
LISTABLE_DOM
Default datatables sDOM parameter to use. By default listable uses the Bootstrap 3 dom below.:
# bootstrap 2 # LISTABLE_DOM = '<"row-fluid"<"span6"ir><"span6"p>>rt<"row-fluid"<"span12"lp>>' #boostrap 3 LISTABLE_DOM = '<"row"<"col-sm-6"i><"col-sm-6"rp>>rt<"row"<"col-sm-12"lp>>'
LISTABLE_PAGINATION_TYPE
# pagination types -> bootstrap2, bootstrap3, two_button, full_numbers LISTABLE_PAGINATION_TYPE = "full_numbers"
LISTABLE_STATE_SAVE
Enable sticky filters by default.:
LISTABLE_STATE_SAVE = True
LISTABLE_PAGINATE_BY
Default page size.:
LISTABLE_PAGINATE_BY = 10
Usage
There’s four steps to using django-listable
Including listable in your settings.INSTALLED_APPS
Create a view by subclassing listable.views.BaseListableView
Connect the view to a url pattern in your apps urls.py
Include the listable template tag in a template
These steps will demonstrated below assuming we have a Django application called staff and we want to create a page on our site with a list of staff and the department and business they belong to.
with the following models defined:
class Business(models.Model): name = models.CharField(max_length=255) class Department(models.Model): name = models.CharField(max_length=255) business = models.ForeignKey(Business) class Staff(models.Model): first_name = models.CharField(max_length=255, help_text=_("Enter the name of the staff being rounded")) last_name = models.CharField(max_length=255, help_text=_("Enter the name of the staff being rounded")) active = models.CharField(max_length=10, choices = ACTIVE_CHOICES) department = models.ForeignKey(Department) def name(self): return "%s, %s" % (self.last_name, self.first_name) def status(self): return self.get_active_display()
A full functional example can be found in the demo app included with django-listable.
Adding listable to settings.INSTALLED_APPS
To start using django-listable add listable to your INSTALLED_APPS:
INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.messages', 'django.contrib.staticfiles', 'django.contrib.admin', 'staff', 'listable', ... )
Defining a Listable view
To define a listable view, sublcass listable.views.BaseListableView and set the model that is to be used as the source of data:
from listable.views import BaseListableView from models import Staff class StaffList(BaseListableView): model = models.Staff ...
Defining Columns for your table
Every listable view must define one or more fields to be displayed as columns in the table. listable fields are defined in a manner similar to ModelForms:
class StaffList(BaseListableView): model = models.Staff fields = (...) widgets = {...} # optional search_fields = {...} # optional order_fields = {...} # optional headers = {...} # optional select_related = (...) # optional prefetch_related = (...) # optional
fields
Fields defines an iterable of the columns that you want to display in the table, these fields can either be fields on your model, foreign key lookups, the name of a callable on your view, the name of a callable on your model or the result of an extra query.
widgets
Widgets is a dictionary mapping a field to a search widget type. Currently you can use either text (default) or select inputs. For example:
from listable.views import BaseListableView, SELECT from . import models class StaffList(BaseListableView): model = models.Staff fields = ("id", "name", "active", "department__name",) widgets = { "department__name": SELECT, "active": SELECT, }
The choices available in a select widget are currently automatically populated although this will change to allow manual configuration of choices in the future. The choices are populated based on either the choices option for a model field or in the case of a foreign key all the values of the foreign key lookup. (I hope to make this more flexible in the future)
search_fields (optional)
Search fields are a mapping of field names to the django filter syntax that should be used for searching the table. This can either be a string, an iterable of strings or a falsy value to disable searching on that field. For example:
search_fields = { "name": ("first_name__icontains", "last_name__icontains",), "last_name": "last_name__exact", "genericname": "genericname__icontains", "department__name": False, }
if a field is not declared in search_field’s it a filter using icontains is assumed.
loose_text_search
If set to True, will split search terms. E.g. “Sm ti” will return an object with field value of “Small Ticket”. This is very similar to how Django’s admin backend does its searches. Be default, the value is False for backward compatibility.
order_fields (optional)
Order fields allows you to define how a column should be ordered (similar to Django’s ordering or order_by). For example:
order_fields = { "name": ("last_name", "first_name",), }
headers (optional)
Headers is a mapping of field names to the column name to be displayed. For example by default a field name of department__business__name would be converted to “Department Business Name” but that could be overriden like so:
headers = { "department__business__name": _("Business"), }
select_related
Allows you to use Django’s queryset select_related option for reducing database queries. e.g:
select_related = ("department", "position", "department__business",)
prefetch_related
Allows you to use Django’s queryset prefetch_related option for reducing database queries. e.g:
prefetch_related = ("some_fk__some_field",)
get_extra
Due to a bug with pagination, using an extra query will result in your entire table being loaded into memory before being paginated :(
You may define a callable get_extra method on your view that should return a dictionary suitable for use in the Django queryset’s extra method. For example:
def get_extra(self): return {select: {'is_recent': "pub_date > '2006-01-01'"}}
A more complex example is given in the “Complete Example” sample below.
Formatting fields
The order in which listable tries to find a method for formatting a field for display is as follows:
A method on the actual view:
class StaffList(BaseListableView): model = models.Staff fields = (..., "name",...) def name(self, staff): return staff.name()
A get_{field}_display callable on the model.
A callable on the model:
class Staff(Model): ... def staff_name(self): return "{0} {1}".format(self.first_name, self.last_name) class StaffList(BaseListableView): model = models.Staff fields = (..., "staff_name",...)
A field on the model.
A listable column is defined using the listable.views.Column data structure. A Column is essentially a namedtuple with the following fields (detailed descriptions below):
Including the listable template tag in a template
To include listable in your templates you need to load the listable template tags and include the listable_css, a placeholder for the listable table and the listable tag which tells the template the name of the view to wire the table to.:
{% extends 'base.html' %} {% load listable %} {% block extra_css %} {% listable_css %} {% endblock extra_css %} {% block content %} {{listable_table}} {% endblock %} {% block extra_js %} {% listable 'staff-list'%} {% endblock extra_js %}
with the example above requiring a url something like:
urlpatterns = patterns('', url('staff-list/$', views.StaffList.as_view(), name="staff-list"), )
Arguments to the listable tag
The listable tag currently has 1 required argument and five optional keyword args. A full example of the listable template tag looks like:
{% listable 'staff-list' dom="", save_state=False, pagination_type="", css_table_class="", css_input_class="" %}
dom
Overrides the default Datatables sDOM parameter to use.
{% listable 'staff-list' dom='<"row-fluid"<"span6"ir><"span6"p>>rt<"row-fluid"<"span12"lp>>' %}
pagination_type
Overrides the default Datatables sDOM parameter to use.
{% listable 'staff-list' pagination_type='bootstrap3' %}
save_state
Save state enables/disables sticky filters in DataTables.:
{% listable 'staff-list' save_state=False %}
css_table_class
Add a css class to your datatables table e.g.:
{% listable 'staff-list' css_table_class="striped compact" %}
css_input_class
Add a css class to the datatables column filter inputs e.g.:
{% listable 'staff-list' css_table_class="input-sm" %}
A Complete Example
This is a complete example of a django-listable table. It is included as a demo app under the django-listable/listable-demo/
models.py
ACTIVE = 'active' INACTIVE = 'inactive' TERMINATED = 'terminated' ACTIVE_CHOICES = ( (ACTIVE, "Active"), (INACTIVE, "Inactive"), (TERMINATED, "Terminated"), ) ACTIVE_CHOICES_DISPLAY = dict(ACTIVE_CHOICES) class Business(models.Model): name = models.CharField(max_length=255) business_type = models.IntegerField(choices=zip(range(5), range(5)), default=1) class Meta: verbose_name_plural = "Businesses" def __unicode__(self): return self.name class Department(models.Model): name = models.CharField(max_length=255) business = models.ForeignKey(Business) def __unicode__(self): return self.name class Position(models.Model): name = models.CharField(max_length=255) def __unicode__(self): return self.name class AbstractGeneric(models.Model): name = models.CharField(max_length=255) description = models.TextField() staff = generic.GenericRelation( "Staff", content_type_field="content_type", object_id_field="object_id", ) class Meta: abstract = True class GenericModelA(AbstractGeneric): class Meta: verbose_name_plural = "Generic Model A's" def __unicode__(self): return self.name class GenericModelB(AbstractGeneric): class Meta: verbose_name_plural = "Generic Model B's" def __unicode__(self): return self.name class Staff(models.Model): first_name = models.CharField(max_length=255, help_text=_("Enter the name of the staff being rounded")) last_name = models.CharField(max_length=255, help_text=_("Enter the name of the staff being rounded")) active = models.CharField(max_length=10, choices=ACTIVE_CHOICES) position = models.ForeignKey(Position) department = models.ForeignKey(Department) limit = models.Q(app_label='staff', model='genericmodela') | models.Q(app_label='staff', model='genericmodelb') content_type = models.ForeignKey(ContentType, limit_choices_to=limit) object_id = models.PositiveIntegerField() generic_object = generic.GenericForeignKey("content_type", "object_id") class Meta: verbose_name_plural = "staff" ordering = ("last_name", "first_name",) def name(self): return "%s, %s" % (self.last_name, self.first_name) def status(self): return ACTIVE_CHOICES_DISPLAY[self.active] def __unicode__(self): return self.name()
views.py
class StaffList(BaseListableView): model = models.Staff fields = ( "id", "name", "active", "department__name", "position__name", "department__business__name", "department__business__business_type", "genericname", ) widgets = { "department__business__name": SELECT, "department__business__business_type": SELECT, "position__name": SELECT, "choices": SELECT, "active": SELECT, } search_fields = { "name": ("first_name__icontains", "last_name__icontains",), "last_name": "last_name__exact", "genericname": "genericname__icontains", "department__name": "department__name__icontains", } order_fields = { "name": ("last_name", "first_name",), } headers = { "position__name": _("Position"), "department__business__name": _("Business"), "department__business__business_type": _("Business Type"), } select_related = ("department", "position", "department__business",) def generic(self, obj): return obj.generic_object.name def name(self, staff): return staff.name() def get_extra(self): cta = ContentType.objects.get_for_model(models.GenericModelA) ctb = ContentType.objects.get_for_model(models.GenericModelB) extraq = """ CASE WHEN content_type_id = {0} THEN (SELECT name from staff_genericmodela WHERE object_id = staff_genericmodela.id) WHEN content_type_id = {1} THEN (SELECT name from staff_genericmodelb WHERE object_id = staff_genericmodelb.id) END """.format(cta.pk, ctb.pk) return {"select": {'genericname': extraq}}
staff_list.html
{% extends 'base.html' %} {% load listable %} {% block extra_css %} {% listable_css %} {% endblock extra_css %} {% block content %} {{listable_table}} {% endblock %} {% block extra_js %} {% listable 'staff-list' save_state=True %} {% endblock extra_js %}
History
0.8.3 (2024-07-03)
Add support for “Live Filters”.
0.8.2 (2023-12-22)
Django 4.x compatibility
0.8.1 (2023-05-25)
In order to allow | characters in searches, the search term separator for multi selects has been updated to use `|` which is a 3 character sequence unlikely to apply in normal searches.
0.8.0 (2023-04-18)
Added a loose_text_search setting to views. Set loose_text_search = True on your view to enable partial matching in your text searches. For example “Fo Ba” will match “Foo Bar”.
0.7.0 (2023-02-24)
Listable for Django 3.2
0.6.0 (2021-10-07)
Add field names to column headers as data attributes
Add columnSearch to Listable context object
0.5.2 (2021-08-20)
Fix issue with encoding of search filters
0.5.1 (2021-06-15)
wrap datatables css/scripts in function so static is not called at import
0.5.0 (2021-02-03)
Fixed a same site cookie issue
Fixed a bug where select dropdowns were being truncated by bottom of page
Added a get_fields method to set fields dynamically
Fix an issue with incorrect timezones
Add support for Django 2-
0.4.3 (2017-05-11)
Fix values_to_dt to allow unicode
0.4.1 (2016-10-14)
Add fix for when using FORCE_SCRIPT_NAME setting
0.4.0 (2016-10-02)
Update to support Django 1.8-1.10 and Python 2.7-3.5
0.3.10 (2016-11-08)
Cast search term to lower case if case insensitive search requested to allow easier filtering with extra queries.
0.3.9 (2016-09-27)
Fix formatting bug introduced by 0.3.8
0.3.8 (2016-09-27)
Fix unicode encoding error
0.3.7 (2016-08-25)
Add date range picker
0.3.6 (2016-06-29)
Add multi select and date select widgets (thanks to @ryanbottema)
0.3.5 (2016-06-22)
Fix filtering and count queries for django-mssql
0.3.3 (2015-04-12)
Fix filtering of None values for SELECT fields
0.3.1 (2015-02-25)
Fix issue with boolean field filtering
0.2.10 (2014-12-16)
Fix issue with pagination type
0.2.9 (2014-12-15)
Fix issue with namespaced urls
0.2.6 (2014-10-30)
add view args & kwargs to context to allow full reverse
0.2.5 (2014-10-30)
fix order_by
0.2.0 (2014-10-29)
Complete overhaul of api
0.1.2 (2014-07-09)
Fix saveState bug
0.1.0 (2013-08-15)
First release on PyPI.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Hashes for django_listable-0.8.3-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 1b2503ddb2c3443cb65a1509147ed0398d5fd0124d585211f60b0a24e24c743b |
|
MD5 | 2ea2f3fe1026dab63f3eece9c1000f4e |
|
BLAKE2b-256 | cf58b468200507e06832ec09b8be1277f132b4ce7ef7df9b788e527e3afb8525 |