Compare commits

..

5 Commits

Author SHA1 Message Date
Aaron Kable
4799e0beb1 more tests 2026-01-09 10:40:00 +08:00
Aaron Kable
a4cce84f67 py3.8... 2026-01-09 10:26:27 +08:00
Aaron Kable
edf5b7bb8c colum control is & 2026-01-09 09:36:51 +08:00
Aaron Kable
7e338d09a2 Add Colum Control 2026-01-09 09:25:28 +08:00
Aaron Kable
775db62c7a add basic column control fitlering 2026-01-09 08:55:58 +08:00
3 changed files with 178 additions and 53 deletions

View File

@@ -43,7 +43,7 @@ def defaultdict_to_dict(d):
class DataTablesView(View):
model: Model = None
columns: list[tuple] = []
columns: List[tuple] = []
def get_model_qs(self, request: HttpRequest, *args, **kwargs):
return self.model.objects
@@ -53,22 +53,25 @@ class DataTablesView(View):
filter_qs = Q()
for id, c in table_conf["columns"].items():
_c = self.columns[int(id)][0]
if c["searchable"] and len(_c) > 0:
if c["columnControl"]:
if c.get("searchable", False) and len(_c) > 0:
if c.get("columnControl", False):
_sv = str(c["columnControl"]["search"]["value"])
"""contains, equal, ends, starts, empty"""
_logic = str(c["columnControl"]["search"]["logic"])
"""text, date, num"""
_type = str(c["columnControl"]["search"]["type"])
if len(_sv) > 0:
if _logic == "empty":
filter_qs &= Q(**{f'{_c}': ""})
elif len(_sv) > 0:
if _logic == "contains":
filter_qs |= Q(**{f'{_c}__icontains': _sv})
filter_qs &= Q(**{f'{_c}__icontains': _sv})
elif _logic == "starts":
filter_qs |= Q(**{f'{_c}__istartswith': _sv})
filter_qs &= Q(**{f'{_c}__istartswith': _sv})
elif _logic == "ends":
filter_qs |= Q(**{f'{_c}__iendswith': _sv})
filter_qs &= Q(**{f'{_c}__iendswith': _sv})
elif _logic == "equal":
filter_qs |= Q(**{f'{_c}': _sv})
elif _logic == "empty":
filter_qs |= Q(**{f'{_c}': ""})
filter_qs &= Q(**{f'{_c}': _sv})
else:
_sv = str(c["search"]["value"])
if len(_sv) > 0:
@@ -81,6 +84,28 @@ class DataTablesView(View):
filter_qs |= Q(**{f'{_c}__icontains': _gsv})
return filter_qs
def except_qs(self, table_conf: dict):
# Search
except_qs = Q()
for id, c in table_conf["columns"].items():
_c = self.columns[int(id)][0]
if c.get("searchable", False) and len(_c) > 0:
if c.get("columnControl", False):
_sv = str(c["columnControl"]["search"]["value"])
"""notContains, notEqual, notEmpty"""
_logic = str(c["columnControl"]["search"]["logic"])
"""text, date, num"""
_type = str(c["columnControl"]["search"]["type"])
if _logic == "notEmpty":
except_qs |= Q(**{f'{_c}': ""})
elif len(_sv) > 0:
if _logic == "notContains":
except_qs |= Q(**{f'{_c}__icontains': _sv})
elif _logic == "notEqual":
except_qs |= Q(**{f'{_c}': _sv})
return except_qs
def get_table_config(self, get: dict):
_cols = nested_param_dict()
for c, v in get.items():
@@ -134,6 +159,8 @@ class DataTablesView(View):
**kwargs
).filter(
self.filter_qs(table_conf)
).exclude(
self.except_qs(table_conf)
).order_by(
*self.get_order(table_conf)
)

View File

@@ -74,10 +74,18 @@ class TestDataTables(TestCase):
cls.user.save()
EveCharacter.objects.all().delete()
for i in range(1,21):
for i in range(1,16):
EveCharacter.objects.create(
character_id=1000+i,
character_name=f"{1000+i} - Test Character",
character_name=f"{1000+i} - Test Character - {1000+i}",
corporation_id=2000+i,
corporation_name=f"{2000+i} - Test Corporation",
)
for i in range(16,21):
EveCharacter.objects.create(
character_id=1000+i,
character_name=f"{1000+i} - Test Character - {1000+i}",
corporation_id=2000+i,
corporation_name=f"{2000+i} - Test Corporation",
alliance_id=3000+i,
@@ -104,6 +112,17 @@ class TestDataTables(TestCase):
self.assertEqual(data[0][0], "1020")
self.assertEqual(data[9][0], "1011")
def test_view_no_sort(self):
self.get_params.pop("order[0][column]")
self.get_params.pop("order[0][dir]")
self.client.force_login(self.user)
request = self.factory.get('/fake-url/', data=self.get_params)
response = TestView()
response.setup(request)
data = json.loads(response.get(request).content)["data"]
self.assertEqual(data[0][0], "1001")
self.assertEqual(data[9][0], "1010")
def test_view_non_sortable_sort(self):
self.get_params["order[0][dir]"] = "desc"
self.get_params["order[0][column]"] = "0"
@@ -147,9 +166,104 @@ class TestDataTables(TestCase):
def test_view_col_1_search_empty(self):
self.get_params["columns[1][search][value]"] = "zzz"
self.client.force_login(self.user)
request = self.factory.get('/fake-url/', data=self.get_params)
response = TestView()
response.setup(request)
data = json.loads(response.get(request).content)["data"]
self.assertEqual(len(data), 0)
def test_view_cc_3_search_empty(self):
self.get_params["columns[3][columnControl][search][value]"] = ""
self.get_params["columns[3][columnControl][search][logic]"] = "empty"
self.get_params["columns[3][columnControl][search][type]"] = "text"
self.get_params["length"] = "20"
self.client.force_login(self.user)
request = self.factory.get('/fake-url/', data=self.get_params)
response = TestView()
response.setup(request)
data = json.loads(response.get(request).content)["data"]
self.assertEqual(len(data), 15)
def test_view_cc_3_search_not_empty(self):
self.get_params["columns[3][columnControl][search][value]"] = ""
self.get_params["columns[3][columnControl][search][logic]"] = "notEmpty"
self.get_params["columns[3][columnControl][search][type]"] = "text"
self.client.force_login(self.user)
request = self.factory.get('/fake-url/', data=self.get_params)
response = TestView()
response.setup(request)
data = json.loads(response.get(request).content)["data"]
self.assertEqual(len(data), 5)
def test_view_cc_1_search_ends_with(self):
self.get_params["columns[1][columnControl][search][value]"] = "9"
self.get_params["columns[1][columnControl][search][logic]"] = "ends"
self.get_params["columns[1][columnControl][search][type]"] = "text"
self.client.force_login(self.user)
request = self.factory.get('/fake-url/', data=self.get_params)
response = TestView()
response.setup(request)
data = json.loads(response.get(request).content)["data"]
self.assertEqual(len(data), 2)
def test_view_cc_1_search_starts_with(self):
self.get_params["columns[1][columnControl][search][value]"] = "1009"
self.get_params["columns[1][columnControl][search][logic]"] = "starts"
self.get_params["columns[1][columnControl][search][type]"] = "text"
self.client.force_login(self.user)
request = self.factory.get('/fake-url/', data=self.get_params)
response = TestView()
response.setup(request)
data = json.loads(response.get(request).content)["data"]
self.assertEqual(len(data), 1)
def test_view_cc_1_search_not_contains(self):
self.get_params["columns[1][columnControl][search][value]"] = "100"
self.get_params["columns[1][columnControl][search][logic]"] = "notContains"
self.get_params["columns[1][columnControl][search][type]"] = "text"
self.get_params["length"] = "20"
self.client.force_login(self.user)
request = self.factory.get('/fake-url/', data=self.get_params)
response = TestView()
response.setup(request)
data = json.loads(response.get(request).content)["data"]
self.assertEqual(len(data), 11)
def test_view_cc_1_search_contains(self):
self.get_params["columns[1][columnControl][search][value]"] = "100"
self.get_params["columns[1][columnControl][search][logic]"] = "contains"
self.get_params["columns[1][columnControl][search][type]"] = "text"
self.get_params["length"] = "20"
self.client.force_login(self.user)
request = self.factory.get('/fake-url/', data=self.get_params)
response = TestView()
response.setup(request)
data = json.loads(response.get(request).content)["data"]
self.assertEqual(len(data), 9)
def test_view_cc_1_search_equal(self):
self.get_params["columns[1][columnControl][search][value]"] = "1001 - Test Character - 1001"
self.get_params["columns[1][columnControl][search][logic]"] = "equal"
self.get_params["columns[1][columnControl][search][type]"] = "text"
self.get_params["length"] = "20"
self.client.force_login(self.user)
request = self.factory.get('/fake-url/', data=self.get_params)
response = TestView()
response.setup(request)
data = json.loads(response.get(request).content)["data"]
self.assertEqual(len(data), 1)
def test_view_cc_1_search_not_equal(self):
self.get_params["columns[1][columnControl][search][value]"] = "1001 - Test Character - 1001"
self.get_params["columns[1][columnControl][search][logic]"] = "notEqual"
self.get_params["columns[1][columnControl][search][type]"] = "text"
self.get_params["length"] = "20"
self.client.force_login(self.user)
request = self.factory.get('/fake-url/', data=self.get_params)
response = TestView()
response.setup(request)
data = json.loads(response.get(request).content)["data"]
self.assertEqual(len(data), 19)

View File

@@ -52,58 +52,42 @@ Given the `EveCharacter` Model as our model of choice we would define our stubs
<th>{% translate "Alliance" %}</th>
</tr>
</thead>
<!-- Put the footer in the header so it doesn't trigger the sorting on searching -->
<!-- only footers with text will get given an input -->
<!-- This is just an option, you do it however you like -->
<tfoot style="display: table-header-group;">
<tr>
<th></th>
<th>{% translate "Name" %}</th>
<th>{% translate "Corporation" %}</th>
<th>{% translate "Alliance" %}</th>
</tr>
</tfoot>
<!-- Empty tbody -->
<tbody>
</tbody>
</table>
{% endblock content %}
{% block extra_css %}
{% include 'bundles/datatables-css-bs5.html' %}
<link rel="stylesheet" href="https://cdn.datatables.net/2.3.6/css/dataTables.bootstrap5.css" />
<link href="https://cdn.datatables.net/columncontrol/1.2.0/css/columnControl.bootstrap5.min.css" rel="stylesheet">
{% endblock %}
{% block extra_javascript %}
{% include 'bundles/datatables-js-bs5.html' %}
<script src="https://cdn.datatables.net/2.3.6/js/dataTables.js"></script>
<script src="https://cdn.datatables.net/2.3.6/js/dataTables.bootstrap5.js"></script>
<script src="https://cdn.datatables.net/columncontrol/1.2.0/js/dataTables.columnControl.min.js"></script>
<script>
$(document).ready(function() {
$('#table').DataTable({
serverSide: true,
ajax: '{% url "appname:table" %}',
// This is for the column searching
initComplete: function () {
this.api()
.columns()
.every(function () {
let column = this;
let title = column.footer().textContent;
if (title != ""){
// Create input element
let input = document.createElement('input');
input.classList.add("w-100")
input.placeholder = title;
column.footer().replaceChildren(input);
// Event listener for user input
input.addEventListener('keyup', () => {
if (column.search() !== this.value) {
column.search(input.value).draw();
}
});
}
});
},
columnDefs: [
{ "searchable": false, "targets": [0] },
{ "sortable": false, "targets": [0] },
{
targets: [0],
columnControl: [],
sortable: false,
searchable: false
},
{
targets: [1,2,3],
columnControl: [
{
target: 0,
content: []
},
{
target: 1,
content: ['search']
}
],
}
],
order: [
[1, "asc"]