Skip to content

Commit

Permalink
Merge branch 'feature/changelist-data-attributes' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
abidibo committed Dec 4, 2020
2 parents 5d4a411 + db770e8 commit 1b69831
Show file tree
Hide file tree
Showing 13 changed files with 189 additions and 8 deletions.
60 changes: 59 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Login with user `demo` and password `demo`
- [Js Utilities](#js-utilities)
- [List Filters](#list-filters)
- [Changelist Includes](#changelist-includes)
- [Changelist Row Attributes](#changelist-row-attributes)
- [Form Tabs](#form-tabs)
- [Form Includes](#form-includes)
- [Collapsable stacked inlines entries](#collapsable-stackedinline)
Expand Down Expand Up @@ -67,10 +68,11 @@ The following packages are required to manage the Google Analytics index:
- google-api-python-client
- requests

At the moment __baton__ defines only 4 custom templates:
At the moment __baton__ defines only 5 custom templates:

- `admin/base_site.html`, needed to inject the JS application (which includes css and images, compiled with [webpack](https://webpack.github.io/));
- `admin/change_form.html`, needed to inject the `baton_form_includes` stuff. In any case, the template extends the default one and just adds some stuff at the end of the content block, so it's still full compatible with the django one;
- `admin/change_list.html`, needed to inject the `baton_cl_includes` and `baton_cl_rows_attributes` stuff. In any case, the template extends the default one and just adds some stuff at the end of the content block, so it's still full compatible with the django one;
- `admin/delete_confirmation.html`, needed to wrap contents;
- `admin/delete_selected_confirmation.html`, same as above.

Expand Down Expand Up @@ -438,6 +440,8 @@ class MyModelAdmin(admin.ModelAdmin):

## <a name="changelist-includes"></a>Changelist Includes

> In order for this feature to work, the user browser must support html template tags.
Baton lets you include templates directly inside the change list page, in any position you desire. It's as simple as specifying the template path and the position of the template:

```python
Expand Down Expand Up @@ -465,6 +469,58 @@ You can specify the following positions:

And, of course, you can access the all the changelist view context variables inside your template.

## <a name="changelist-row-attributes"></a>Changelist Row Attributes

> In order for this feature to work, the user browser must support html template tags.
With Baton you can add every kind of html attribute (including css classes) to any element in the changelist table (cell, rows, ...)

![Baton changelist row attributes](docs/images/baton-cl-row-attributes.png)

It's a bit tricky, let's see how:

1. Add a `baton_cl_rows_attributes` function to your `ModelAdmin` class, which takes `request` as a parameter.
2. Return a json dictionary where the keys are used to match an element and the values specifies the attributes and other rules to select the element.

Better to see an example:

``` javascript
class NewsModelAdmin(admin.ModelAdmin):
# ...

def get_category(self, instance):
return mark_safe('<span class="span-category-id-%d">%s</span>' % (instance.id, str(instance.category)))
get_category.short_description = 'category'

def baton_cl_rows_attributes(self, request):
data = {}
for news in News.objects.filter(category__id=2):
data[news.id] = {
'class': 'table-info',
}
data[news.id] = {
'class': 'table-success',
'data-lol': 'lol',
'title': 'A fantasctic tooltip!',
'selector': '.span-category-id-%d' % 1,
'getParent': 'td',
}
return json.dumps(data)
```

In such case we're returning a dictionary with possibly many keys (each key is an id of a news instance).

The first kind of dictionary elements will add a `table-info` class to the `tr` (rows) containing the news respecting the rule `category__id=2`

The second kind of element instead uses some more options to customize the element selection: you can specify a css selector, and you can specify if Baton should then take one of its parents, and in such case you can give a parent selector also.
In the example provided Baton will add the class `table-success`, `data-attribute` and the `title` attribute to the cell which contains the element `.span-category-id-1`.

So these are the rules:

- the default `selector` is `#result_list tr input[name=_selected_action][value=' + key + ']`, meaning that it can work only if the model is editable (you have the checkox inputs for selecting a row), and selects the row of the instance identified by `key`. If you use a custom selector the dictionary `key` is unuseful.
- the default `getParent` is `tr`. You can change it at you will, or set it to `False`, in such case the element to which apply the given attributes will be the one specified by `selector`.
- Every other key different from `selector` and `getParent` will be considered an attribute and added to the element.

## <a name="form-tabs"></a>Form tabs

How much I loved django-suit form tabs? Too much. So, this was a feature I couldn't live without.
Expand Down Expand Up @@ -533,6 +589,8 @@ Other features:

## [Form Includes](#form-includes)

> In order for this feature to work, the user browser must support html template tags.
Baton lets you include templates directly inside the change form page, in any position you desire. It's as simple as specifying the template path, the field name used as anchor and the position of the template:

```python
Expand Down
2 changes: 1 addition & 1 deletion baton/static/baton/app/dist/baton.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion baton/static/baton/app/dist/baton.min.js.map

Large diffs are not rendered by default.

30 changes: 29 additions & 1 deletion baton/static/baton/app/src/core/ChangeList.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ let ChangeList = {
bottom: 'append',
}

$('template').each(function (index, template) {
$('template[data-type=include]').each(function (index, template) {
let position = positionMap[$(template).attr('data-position')]
if (position !== undefined) {
let el = $('#changelist-form')
Expand All @@ -87,6 +87,34 @@ let ChangeList = {
console.error('Baton: wrong changelist include position detected')
}
})

$('template[data-type=attributes]').each(function (index, template) {
try {
let data = JSON.parse($(template).html())

for (let key in data) {
if (data.hasOwnProperty(key)) {
let selector
let getParent = 'tr'
if (data[key]['selector']) {
selector = data[key]['selector']
delete data[key]['selector']
} else {
selector = '#result_list tr input[name=_selected_action][value=' + key + ']'
}
if (data[key]['getParent'] !== undefined) {
getParent = data[key]['getParent']
delete data[key]['getParent']
}

let el = getParent ? $(selector).parents(getParent) : $(selector)
el.attr(data[key])
}
}
} catch (e) {
console.error(e)
}
})
}
}

Expand Down
10 changes: 9 additions & 1 deletion baton/templates/admin/change_list.html
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
{% extends 'admin/change_list.html' %}
{% load baton_tags %}
{% block content %}{{ block.super }}

{% for template, position in cl.model_admin.baton_cl_includes %}
<template data-position="{{ position }}">
<template data-type="include" data-position="{{ position }}">
<div class="baton-cl-include baton-cl-include-{{ position }}">
{% include template %}
</div>
</template>
{% endfor %}

{% call_model_admin_method model_admin=cl.model_admin method='baton_cl_rows_attributes' as data %}
{% if data %}
<template data-type="attributes">
{{ data }}
</template>
{% endif %}
{% endblock %}
10 changes: 10 additions & 0 deletions baton/templatetags/baton_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,13 @@ def footer(context):
'copyright': get_config('COPYRIGHT'),
'powered_by': get_config('POWERED_BY'),
}


@register.simple_tag(takes_context=True)
def call_model_admin_method(context, **kwargs):
try:
model_admin = kwargs.pop('model_admin')
method = kwargs.pop('method')
return getattr(model_admin, method)(context['request'], **kwargs)
except Exception as e:
return None
6 changes: 4 additions & 2 deletions docs/changelist_includes.rst
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
Form includes
=========
Changelist includes
===================

.. image:: images/baton-cl-includes.png

.. important:: In order for this feature to work, the user browser must support html template tags.

Baton lets you include templates directly inside the change list page, in any position you desire. It's as simple as specifying the template path and the position of the template::

@admin.register(News)
Expand Down
50 changes: 50 additions & 0 deletions docs/changelist_row_attributes.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
Changelist Row Attributes
=========================

.. image:: images/baton-cl-row-attributes.png
.. important:: In order for this feature to work, the user browser must support html template tags.

With Baton you can add every kind of html attribute (including css classes) to any element in the changelist table (cell, rows, ...)


It's a bit tricky, let's see how:

1. Add a ``baton_cl_rows_attributes`` function to your ``ModelAdmin`` class, which takes ``request`` as a parameter.
2. Return a json dictionary where the keys are used to match an element and the values specifies the attributes and other rules to select the element.

Better to see an example: ::

class NewsModelAdmin(admin.ModelAdmin):
# ...

def get_category(self, instance):
return mark_safe('<span class="span-category-id-%d">%s</span>' % (instance.id, str(instance.category)))
get_category.short_description = 'category'

def baton_cl_rows_attributes(self, request):
data = {}
for news in News.objects.filter(category__id=2):
data[news.id] = {
'class': 'table-info',
}
data[news.id] = {
'class': 'table-success',
'data-lol': 'lol',
'title': 'A fantasctic tooltip!',
'selector': '.span-category-id-%d' % 1,
'getParent': 'td',
}
return json.dumps(data)

In such case we're returning a dictionary with possibly many keys (each key is an id of a news instance).

The first kind of dictionary elements will add a ``table-info`` class to the ``tr`` (rows) containing the news respecting the rule ``category__id=2``

The second kind of element instead uses some more options to customize the element selection: you can specify a css selector, and you can specify if Baton should then take one of its parents, and in such case you can give a parent selector also.
In the example provided Baton will add the class ``table-success``, ``data-attribute`` and the ``title`` attribute to the cell which contains the element ``.span-category-id-1``.

So these are the rules:

- the default ``selector`` is ``#result_list tr input[name=_selected_action][value=' + key + ']``, meaning that it can work only if the model is editable (you have the checkox inputs for selecting a row), and selects the row of the instance identified by ``key``. If you use a custom selector the dictionary ``key`` is unuseful.
- the default ``getParent`` is ``tr``. You can change it at you will, or set it to `False`, in such case the element to which apply the given attributes will be the one specified by ``selector``.
- Every other key different from ``selector`` and ``getParent`` will be considered an attribute and added to the element.
2 changes: 2 additions & 0 deletions docs/form_includes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ Form includes

.. image:: images/baton-form-includes.png

.. important:: In order for this feature to work, the user browser must support html template tags.

Baton lets you include templates directly inside the change form page, in any position you desire. It's as simple as specifying the template path, the field name used as anchor and the position of the template::

@admin.register(News)
Expand Down
Binary file added docs/images/baton-cl-row-attributes.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Getting started
js_utilities
list_filters
changelist_includes
changelist_row_attributes
form_tabs
form_includes
collapsable_stackedinline
Expand Down
Binary file modified testapp/app/db.sqlite3
Binary file not shown.
24 changes: 23 additions & 1 deletion testapp/app/news/admin.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import json

from django.contrib import admin
from django.utils.safestring import mark_safe
from baton.admin import InputFilter, RelatedDropdownFilter
from .models import News, Category, Attachment, Video

Expand Down Expand Up @@ -36,7 +39,7 @@ class NewsAdmin(admin.ModelAdmin):
list_display = (
'title',
'date',
'category',
'get_category',
'published',
)
list_filter = (
Expand Down Expand Up @@ -93,3 +96,22 @@ class NewsAdmin(admin.ModelAdmin):
baton_cl_includes = [
('news/admin_cl_top_include.html', 'top', ),
]

def get_category(self, instance):
return mark_safe('<span class="span-category-id-%d">%s</span>' % (instance.id, str(instance.category)))
get_category.short_description = 'category'

def baton_cl_rows_attributes(self, request, **kwargs):
data = {}
for news in News.objects.filter(category__id=2):
data[news.id] = {
'class': 'table-info',
# 'selector': '#result_list tr input[name=_selected_action][value=%d]' % news.id,
}
data[news.id] = {
'class': 'table-success',
'selector': '.span-category-id-%d' % 1,
'getParent': 'td',
'title': 'A fantasctic tooltip!'
}
return json.dumps(data)

0 comments on commit 1b69831

Please sign in to comment.