# DataTables Server Side Rendering The `allianceauth.framework.datatables.DataTablesView` module provides a simple class based view to implement simple server side filtering ordering and searching of DataTables. This is intended to make the life of our community apps developer a little easier, so they don't have to reinvent the wheel. ## Usage To use this view is as easy as defining your stub templates, and fields and adding the view to the `urls.py` Given the `EveCharacter` Model as our model of choice we would define our stubs like so ## Add our Templates ### template/appname/stubs/icon.html ```django {% load evelinks %} ``` ### template/appname/stubs/name.html ```django {{ row.character_name }} ({{ row.character_ownership.user.username }}) ``` ### template/appname/stubs/corp.html ```django {{ row.corporation_name }} ``` ### template/appname/list.html ```django {% extends "allianceauth/base-bs5.html" %} {% load i18n %} {% load aa_i18n %} {% block page_title %} {% translate "App Name" %} {% endblock page_title %} {% block content %}
{% translate "Name" %} {% translate "Corporation" %} {% translate "Alliance" %}
{% endblock content %} {% block extra_css %} {% include "bundles/datatables-2-css-bs5.html" %} {% comment %} If you don't use the ColumnControl Extension, remove the next line {% endcomment %} {% include "bundles/datatables-2-columncontrol-css-bs5.html" %} {% endblock %} {% block extra_javascript %} {% get_datatables_language_static LANGUAGE_CODE as DT_LANG_PATH %} {% include "bundles/datatables-2-js-bs5.html" %} {% comment %} If you don't use the ColumnControl Extension, remove the next line {% endcomment %} {% include "bundles/datatables-2-columncontrol-js-bs5.html" %} {% endblock extra_javascript %} ``` ## Add our Views Then we can setup out view in our `appname/views.py` file. ### Columns definition The `columns` must be defined as a 2 part tuple - Part 1 is the database field that will be used for filtering and ordering. If this is a foreign key you need to point to a field that is compatible with `__icontains` like `charField` or `textField`. It can be `None`/`False`/`""` if no ordering for filtering is required for this row. - Examples for the EveCharacter Model: - `character_name` - `character_ownership__user__username` - `character_ownership__user__profile__main_character__character_name` - Part 2 is a string that is used to the render the column for each row. This can be a html stub or a string containing django style template language. - Examples for the EveCharacter Model - `{{ row.character_name }}` - `{{ row.character_ownership.user.username }}` - `{{ row.character_ownership.user.profile.main_character.character_name }}` - `appname/stubs/character_img.html` ### appname/views.py ```python from django.shortcuts import render # Alliance Auth from allianceauth.framework.datatables import DataTablesView from allianceauth.eveonline.models import EveCharacter ## Datatables server side view class EveCharacterTableData(DataTablesView): model = EveCharacter # Define the columns as a tuple. # String for field name for filtering and ordering # String for the render template # Templates can be a html file or template language directly in the list below columns = [ # ("field_for_queries_or_sort", template: str) ("", "appname/stubs/icon.html"), ("character_name", "appname/stubs/name.html"), ("corporation_name", "appname/stubs/corp.html"), ("alliance_name", "{{ row.alliance_name }} {{ row.alliance_id }}"), ] # if you need to do some prefetch or pre-filtering you can override this function def get_model_qs(self, request: HttpRequest): qs = self.model.objects if not request.user.is_superuser: # eg only show unlinked characters to non-superusers # just an example # filtering here will prevent people searching things that may not be visible to them qs = qs.filter(character_ownership__isnull=True) # maybe some character ownership select related for performance? return qs.select_related("character_ownership", "character_ownership__user") ## Main Page View def eve_characters_table(request): return render(request, "appname/list.html") ``` ## Add our Urls ### appname/urls.py ```python from django.urls import path from . import views app_name = 'appname' urlpatterns = [ path("eve_characters/", views.eve_characters_table, name='eve_characters_table_view'), path("eve_characters/data/", views.EveCharacterTableData.as_view(), name='eve_character_table_data'), ] ``` and you are done. ## Dropdown Filters If you want to add dropdown filters to your server-side DataTable, you have to add a bit of code to the above. ### Modify template/appname/list.html Right above the table add the following code to add dropdown filters for characters, corporations and alliances. You can of course add more or less filters as you see fit. ```django {% load i18n %}

{% translate "Filter by" %}:

``` Now you need to add the necessary JavaScript code to make the filters work. Add the following code to the JavaScript block in the same template. ```javascript // Get the filter elements const custom_dt_filter = { character: $('#filter-character'), corporation: $('#filter-corporation'), alliance: $('#filter-alliance') }; ``` Then, extend the `ajax` configuration of your DataTable to include the filter values in the request data. You can do this by adding a `data` callback function to it. This will take the current values of the filters and add them to the data sent to the server, prefixed with `filter_` to distinguish them from other parameters. ```javascript ajax: { url: '{% url "appname:eve_characters_table_data" %}', data: (data) => { const mappedFilters = Object.fromEntries( Object.entries(custom_dt_filter).map(([key, $el]) => [`filter_${key}`, $el.val()]) ); return {...data, ...mappedFilters}; }, error: (xhr, error) => console.error('Error fetching data:', xhr, error) }, ``` To reload the DataTable data when the filter values change, add event listeners to the filter elements that trigger a redraw of the DataTable. You can do this in the `initComplete` callback of the DataTable initialization. ```javascript initComplete: () => { // Redraw table when filter values change Object.values(custom_dt_filter).forEach(($el) => { $el.on('change', () => dt.draw()); }); } ``` ### Modify appname/views.py Finally, you need to modify the `get_model_qs` method of your DataTablesView to apply the filters to the queryset based on the request parameters. ```python def get_model_qs(self, request: HttpRequest): qs = self.model.objects if not request.user.is_superuser: # eg only show unlinked characters to non-superusers # just an example # filtering here will prevent people searching things that may not be visible to them qs = qs.filter(character_ownership__isnull=True) # Custom filters get_params = request.GET.dict() filter_character = get_params.get("filter_character", None) filter_corporation = get_params.get("filter_corporation", None) filter_alliance = get_params.get("filter_alliance", None) if filter_character: qs = qs.filter(character_id=filter_character) if filter_corporation: qs = qs.filter(corporation_id=filter_corporation) if filter_alliance qs = qs.filter(alliance_id=filter_alliance): # maybe some character ownership select related for performance? return qs.select_related("character_ownership", "character_ownership__user") ``` ### Populating the Filter Options To populate the filter options in the dropdowns, you need to pass the necessary data to the template context in your main page view. You can do this by querying the relevant models and adding them to the context. ```python def eve_characters_table(request): qs = EveCharacter.objects if not request.user.is_superuser: # eg only show unlinked characters to non-superusers # just an example # filtering here will prevent people searching things that may not be visible to them qs = qs.filter(character_ownership__isnull=True) characters = qs.values("id", "character_name").distinct().order_by("character_name") corporations = qs.values("corporation_id", "corporation_name").distinct().order_by("corporation_name") alliances = qs.values("alliance_id", "alliance_name").distinct().order_by("alliance_name") context = { "characters": characters, "corporations": corporations, "alliances": alliances } return render(request, "appname/list.html", context) ```