Skip to content

Commit

Permalink
[feature] Added admin action to allow moving devices to groups openwi…
Browse files Browse the repository at this point in the history
  • Loading branch information
totallynotvaishnav authored May 12, 2022
1 parent 67cc97f commit 78e039d
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 34 deletions.
43 changes: 42 additions & 1 deletion openwisp_controller/config/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
ObjectDoesNotExist,
ValidationError,
)
from django.http import Http404, HttpResponse, JsonResponse
from django.http import Http404, HttpResponse, HttpResponseRedirect, JsonResponse
from django.shortcuts import get_object_or_404
from django.template.loader import get_template
from django.template.response import TemplateResponse
Expand Down Expand Up @@ -402,6 +402,14 @@ def get_queryset(self, request):
return qs.select_related(*self.change_select_related)


class ChangeDeviceGroupForm(forms.Form):
device_group = forms.ModelChoiceField(
queryset=DeviceGroup.objects.all(),
label=_('Group'),
required=False,
)


class DeviceAdmin(MultitenantAdminMixin, BaseConfigAdmin, UUIDAdmin):
recover_form_template = 'admin/config/device_recover_form.html'
list_display = [
Expand Down Expand Up @@ -451,6 +459,7 @@ class DeviceAdmin(MultitenantAdminMixin, BaseConfigAdmin, UUIDAdmin):
]
inlines = [ConfigInline]
conditional_inlines = []
actions = ['change_group']

org_position = 1 if not app_settings.HARDWARE_ID_ENABLED else 2
list_display.insert(org_position, 'organization')
Expand All @@ -469,6 +478,38 @@ class Media(BaseConfigAdmin.Media):
f'{prefix}js/relevant_templates.js',
]

def change_group(self, request, queryset):
if 'apply' in request.POST:
form = ChangeDeviceGroupForm(request.POST)
if form.is_valid():
group = form.cleaned_data['device_group']
queryset.update(group=group or None)
self.message_user(
request,
_('Successfully changed group of selected devices.'),
messages.SUCCESS,
)
return HttpResponseRedirect(request.get_full_path())

form = ChangeDeviceGroupForm()
context = {
'title': _('Change group'),
'queryset': queryset,
'form': form,
'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME,
'opts': self.model._meta,
'changelist_url': (
f'{request.resolver_match.app_name}:'
f'{request.resolver_match.url_name}'
),
}

return TemplateResponse(
request, 'admin/config/change_device_group.html', context
)

change_group.short_description = _('Change group of selected Devices')

def get_fields(self, request, obj=None):
"""
Do not show readonly fields in add form
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
.admin-actions form select {
margin: 10px;
}
.admin-actions form input[type='submit'] {
background: #ba2121;
border-radius: 4px;
padding: 10px 15px;
color: #fff;
margin: 10px 0 0 0;
}
.admin-actions form input[type='submit']:active,
.admin-actions form input[type='submit']:focus,
.admin-actions form input[type='submit']:hover {
background: #a41515;
}
.admin-actions form .cancel-link {
display: inline-block;
vertical-align: middle;
height: 15px;
line-height: 15px;
background: #ddd;
border-radius: 4px;
padding: 10px 15px;
color: #333;
margin: 10px 0 0 10px;
}
.admin-actions form .cancel-link:active,
.admin-actions form .cancel-link:focus,
.admin-actions form .cancel-link:hover {
background: #ccc;
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{% extends "admin/base_site.html" %}
{% load i18n l10n admin_urls static %}

{% block extrahead %}
<link rel="stylesheet" type="text/css" href="{% static 'config/css/admin-actions-intermediate-page.css' %}">
{% endblock %}

{% block bodyclass %}
change-device-group admin-actions
{% endblock %}

{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
&rsaquo; <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_config.verbose_name }}</a>
&rsaquo; <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>
&rsaquo; {% trans 'Change group' %}
</div>
{% endblock %}

{% block content %}

<p>{% blocktrans %}What group do you want to assign to the selected devices?{% endblocktrans %}</p>
<form method="post">{% csrf_token %}
<div>
{% for obj in queryset %}
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk|unlocalize }}">
{% endfor %}
{{ form }}
<br>
<input type="hidden" name="action" value="change_group">
<input type="submit" name='apply' value="{% trans 'Change group' %}">
<a href="{% url changelist_url %}" class="button cancel-link">{% trans 'Cancel' %}</a>
</div>
</form>
{% endblock %}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
{% load i18n l10n admin_urls static %}

{% block extrahead %}
<link rel="stylesheet" type="text/css" href="{% static 'config/css/clone-template-form.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'config/css/admin-actions-intermediate-page.css' %}">
{% endblock %}

{% block bodyclass %}
organization-selection
organization-selection admin-actions
{% endblock %}

{% block breadcrumbs %}
Expand Down
18 changes: 18 additions & 0 deletions openwisp_controller/config/tests/test_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,24 @@ def test_device_template_filter(self):
hidden=[data['t2'].name, data['t3_inactive'].name],
)

def _get_change_device_post_data(self, device):
return {
'_selected_action': [device.pk],
'action': 'change_group',
'csrfmiddlewaretoken': 'test',
}

def test_change_device_group(self):
path = reverse(f'admin:{self.app_label}_device_changelist')
org = self._get_org(org_name='default')
device = self._create_device(organization=org)
post_data = self._get_change_device_post_data(device)
response = self.client.post(path, post_data, follow=True)
self.assertEqual(response.status_code, 200)
self.assertContains(
response, 'What group do you want to assign to the selected devices?'
)

def test_device_contains_default_templates_js(self):
config = self._create_config(organization=self._get_org())
path = reverse(f'admin:{self.app_label}_device_change', args=[config.device.pk])
Expand Down

0 comments on commit 78e039d

Please sign in to comment.