Skip to content

Commit b7e4d02

Browse files
committed
[change] Reduce sortedm2m queries for templates #204
To remove queries generated by sortedm2m, 'formfield_for_manytomany' is overriden such that on page loads, we can get templates using 'get_relevant_templates'. Added device/group id to identify selected templates. Changes are made in JS to ensure templates are viewed and changes are saved as per initial behaviour. Ordering is not maintained. Closes #204 Signed-off-by: DragnEmperor <[email protected]>
1 parent ccf380c commit b7e4d02

File tree

3 files changed

+60
-2
lines changed

3 files changed

+60
-2
lines changed

openwisp_controller/config/admin.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,13 @@ def get_fields(self, request, obj):
462462
fields = super().get_fields(request, obj)
463463
return self._error_reason_field_conditional(obj, fields)
464464

465+
def formfield_for_manytomany(self, db_field, request, **kwargs):
466+
# setting queryset none for all requests except POST as queryset
467+
# is required for the form to be valid
468+
if db_field.name == 'templates' and request.method != 'POST':
469+
kwargs['queryset'] = Template.objects.none()
470+
return super().formfield_for_manytomany(db_field, request, **kwargs)
471+
465472

466473
class ChangeDeviceGroupForm(forms.Form):
467474
device_group = forms.ModelChoiceField(
@@ -1317,6 +1324,13 @@ def get_extra_context(self, pk=None):
13171324
}
13181325
return ctx
13191326

1327+
def formfield_for_manytomany(self, db_field, request, **kwargs):
1328+
# setting queryset none for all requests except POST as queryset
1329+
# is required for the form to be valid
1330+
if db_field.name == 'templates' and request.method != 'POST':
1331+
kwargs['queryset'] = Template.objects.none()
1332+
return super().formfield_for_manytomany(db_field, request, **kwargs)
1333+
13201334

13211335
admin.site.register(Device, DeviceAdminExportable)
13221336
admin.site.register(Template, TemplateAdmin)

openwisp_controller/config/static/config/js/relevant_templates.js

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ django.jQuery(function ($) {
3333
if (templateConfig.required) {
3434
inputField.prop("disabled", true);
3535
}
36-
if (isSelected || templateConfig.required) {
36+
// mark the template as selected if it is required or if it is enabled for the current device or group
37+
if (isSelected || templateConfig.required || templateConfig.selected) {
3738
inputField.prop("checked", true);
3839
}
3940
return element;
@@ -115,6 +116,14 @@ django.jQuery(function ($) {
115116
var url = window._relevantTemplateUrl.replace("org_id", orgID);
116117
// Get relevant templates of selected org and backend
117118
url = url + "?backend=" + backend;
119+
if (!isDeviceGroup()) {
120+
var deviceID = $('input[name="config-0-device"]').val();
121+
url = url + "&device=" + deviceID;
122+
} else {
123+
// Get the group id from the URL
124+
var pathParts = window.location.pathname.split("/");
125+
url = url + "&group=" + pathParts[pathParts.length - 3];
126+
}
118127
$.get(url).done(function (data) {
119128
resetTemplateOptions();
120129
var enabledTemplates = [],
@@ -156,7 +165,8 @@ django.jQuery(function ($) {
156165
// Adds "li" elements for templates that are not selected
157166
// in the database.
158167
var counter =
159-
selectedTemplates !== undefined ? selectedTemplates.length : 0;
168+
selectedTemplates !== undefined ? selectedTemplates.length : 0,
169+
deviceTemplates = [];
160170
Object.keys(data).forEach(function (templateId, index) {
161171
// corner case in which backend of template does not match
162172
if (!data[templateId]) {
@@ -186,6 +196,9 @@ django.jQuery(function ($) {
186196
if (isSelected === true) {
187197
enabledTemplates.push(templateId);
188198
}
199+
if (element.children().children("input").prop("checked") === true) {
200+
deviceTemplates.push(templateId);
201+
}
189202
sortedm2mUl.append(element);
190203
if (!isDeviceGroup()) {
191204
sortedm2mPrefixUl.append(prefixElement);
@@ -194,6 +207,23 @@ django.jQuery(function ($) {
194207
if (firstRun === true && selectedTemplates !== undefined) {
195208
updateTemplateSelection(selectedTemplates);
196209
}
210+
// this runs on first load and sets the name of hidden input field which tracks
211+
// the selected templates.
212+
if (selectedTemplates === undefined) {
213+
if (!isDeviceGroup()) {
214+
$(
215+
`.has_original .field-templates .sortedm2m-container input[type="hidden"]`,
216+
).attr("name", templatesFieldName());
217+
// set the initial value of the hidden input field
218+
// to the selected templates
219+
django._owcInitialValues[templatesFieldName()] =
220+
deviceTemplates.join(",");
221+
} else {
222+
$(
223+
`.field-templates .sortedm2m-container input[type="hidden"]`,
224+
).attr("name", templatesFieldName());
225+
}
226+
}
197227
updateTemplateHelpText();
198228
updateConfigTemplateField(enabledTemplates);
199229
});

openwisp_controller/config/views.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
Organization = load_model('openwisp_users', 'Organization')
1717
Template = load_model('config', 'Template')
18+
Config = load_model('config', 'Config')
1819
DeviceGroup = load_model('config', 'DeviceGroup')
1920
OrganizationConfigSettings = load_model('config', 'OrganizationConfigSettings')
2021

@@ -24,6 +25,8 @@ def get_relevant_templates(request, organization_id):
2425
returns default templates of specified organization
2526
"""
2627
backend = request.GET.get("backend", None)
28+
device_id = request.GET.get("device", None)
29+
group_id = request.GET.get("group", None)
2730
user = request.user
2831
if not user.is_superuser and not user.is_manager(organization_id):
2932
return HttpResponse(status=403)
@@ -38,13 +41,24 @@ def get_relevant_templates(request, organization_id):
3841
.filter(Q(organization_id=org.pk) | Q(organization_id=None))
3942
.only('id', 'name', 'backend', 'default', 'required')
4043
)
44+
# for checking which templates are enabled for given device or group
45+
selected_templates = []
46+
if device_id:
47+
selected_templates = Config.objects.filter(device_id=device_id).values_list(
48+
'templates__id', flat=True
49+
)
50+
if group_id:
51+
selected_templates = DeviceGroup.objects.filter(
52+
pk=group_id, organization_id=organization_id
53+
).values_list('templates__id', flat=True)
4154
relevant_templates = {}
4255
for template in queryset:
4356
relevant_templates[str(template.pk)] = dict(
4457
name=template.name,
4558
backend=template.get_backend_display(),
4659
default=template.default,
4760
required=template.required,
61+
selected=template.pk in selected_templates,
4862
)
4963
return JsonResponse(relevant_templates)
5064

0 commit comments

Comments
 (0)