Skip to content

Commit

Permalink
[fix] Fixed restoring deleted device restores related objects #168
Browse files Browse the repository at this point in the history
Closes #168
  • Loading branch information
pandafy committed Jan 18, 2022
1 parent dbfe46e commit 9df7d2d
Show file tree
Hide file tree
Showing 3 changed files with 147 additions and 0 deletions.
22 changes: 22 additions & 0 deletions openwisp_firmware_upgrader/admin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import logging
from datetime import timedelta

import reversion
import swapper
from django import forms
from django.conf import settings
from django.contrib import admin, messages
Expand All @@ -26,6 +28,9 @@
UpgradeOperation = load_model('UpgradeOperation')
DeviceFirmware = load_model('DeviceFirmware')
FirmwareImage = load_model('FirmwareImage')
Category = load_model('Category')
Build = load_model('Build')
Device = swapper.load_model('config', 'Device')


class BaseAdmin(MultitenantAdminMixin, TimeReadonlyAdminMixin, admin.ModelAdmin):
Expand All @@ -45,6 +50,11 @@ class CategoryAdmin(BaseVersionAdmin):
search_fields = ['name']
ordering = ['-name', '-created']

def reversion_register(self, model, **kwargs):
if model == Category:
kwargs['follow'] = (*kwargs['follow'], 'build_set')
return super().reversion_register(model, **kwargs)


class FirmwareImageInline(TimeReadonlyAdminMixin, admin.StackedInline):
model = FirmwareImage
Expand Down Expand Up @@ -101,6 +111,14 @@ def organization(self, obj):

organization.short_description = _('organization')

def reversion_register(self, model, **kwargs):
if model == FirmwareImage:
kwargs['follow'] = (
*kwargs['follow'],
'build',
)
return super().reversion_register(model, **kwargs)

def upgrade_selected(self, request, queryset):
opts = self.model._meta
app_label = opts.app_label
Expand Down Expand Up @@ -353,3 +371,7 @@ def _get_conditional_queryset(self, request, obj, select_related=False):

# DeviceAdmin.get_inlines = device_admin_get_inlines
DeviceAdmin.conditional_inlines += [DeviceFirmwareInline, DeviceUpgradeOperationInline]

reversion.register(model=DeviceFirmware, follow=['device', 'image'])
reversion.register(model=UpgradeOperation)
DeviceAdmin.add_reversion_following(follow=['devicefirmware', 'upgradeoperation_set'])
124 changes: 124 additions & 0 deletions openwisp_firmware_upgrader/tests/test_selenium.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import swapper
from django.conf import settings
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from django.core.management import call_command
from django.urls.base import reverse
from reversion.models import Version
from selenium import webdriver
from selenium.common.exceptions import TimeoutException, UnexpectedAlertPresentException
from selenium.webdriver.common.by import By
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait

from openwisp_controller.tests.utils import SeleniumTestMixin
from openwisp_firmware_upgrader.hardware import REVERSE_FIRMWARE_IMAGE_MAP
from openwisp_firmware_upgrader.tests.base import TestUpgraderMixin

from ..swapper import load_model

Device = swapper.load_model('config', 'Device')
DeviceConnection = swapper.load_model('connection', 'DeviceConnection')
UpgradeOperation = load_model('UpgradeOperation')
DeviceFirmware = load_model('DeviceFirmware')


class TestDeviceAdmin(TestUpgraderMixin, SeleniumTestMixin, StaticLiveServerTestCase):
config_app_label = 'config'
admin_username = 'admin'
admin_password = 'password'
os = 'OpenWrt 19.07-SNAPSHOT r11061-6ffd4d8a4d'
image_type = REVERSE_FIRMWARE_IMAGE_MAP['YunCore XD3200']

@classmethod
def setUpClass(cls):
super().setUpClass()
chrome_options = webdriver.ChromeOptions()
if getattr(settings, 'SELENIUM_HEADLESS', True):
chrome_options.add_argument('--headless')
chrome_options.add_argument('--window-size=1366,768')
chrome_options.add_argument('--ignore-certificate-errors')
chrome_options.add_argument('--remote-debugging-port=9222')
capabilities = DesiredCapabilities.CHROME
capabilities['goog:loggingPrefs'] = {'browser': 'ALL'}
cls.web_driver = webdriver.Chrome(
options=chrome_options, desired_capabilities=capabilities
)

@classmethod
def tearDownClass(cls):
cls.web_driver.quit()
super().tearDownClass()

def setUp(self):
self.admin = self._create_admin(
username=self.admin_username, password=self.admin_password
)

def tearDown(self):
# Accept unsaved changes alert to allow other tests to run
try:
self.web_driver.refresh()
except UnexpectedAlertPresentException:
self.web_driver.switch_to_alert().accept()
else:
try:
WebDriverWait(self.web_driver, 1).until(EC.alert_is_present())
except TimeoutException:
pass
else:
self.web_driver.switch_to_alert().accept()
self.web_driver.refresh()
WebDriverWait(self.web_driver, 2).until(
EC.visibility_of_element_located((By.XPATH, '//*[@id="site-name"]'))
)

def test_restoring_deleted_device(self):
org = self._get_org()
category = self._get_category(organization=org)
build = self._create_build(category=category, version='0.1', os=self.os)
image = self._create_firmware_image(build=build, type=self.image_type)
self._create_credentials(auto_add=True, organization=org)
device = self._create_device(
os=self.os, model=image.boards[0], organization=org
)
self._create_config(device=device)
self.assertEqual(Device.objects.count(), 1)
self.assertEqual(DeviceConnection.objects.count(), 1)
self.assertEqual(DeviceFirmware.objects.count(), 1)

call_command('createinitialrevisions')

self.login()
# Delete the device
self.open(
reverse(f'admin:{self.config_app_label}_device_delete', args=[device.id])
)
self.web_driver.find_element_by_xpath(
'//*[@id="content"]/form/div/input[2]'
).click()
self.assertEqual(Device.objects.count(), 0)
self.assertEqual(DeviceConnection.objects.count(), 0)
self.assertEqual(DeviceFirmware.objects.count(), 0)

version_obj = Version.objects.get_deleted(model=Device).first()

# Restore deleted device
self.open(
reverse(
f'admin:{self.config_app_label}_device_recover', args=[version_obj.id]
)
)
self.web_driver.find_element_by_xpath(
'//*[@id="device_form"]/div/div[1]/input[1]'
).click()
try:
WebDriverWait(self.web_driver, 5).until(
EC.url_to_be(f'{self.live_server_url}/admin/config/device/')
)
except TimeoutException:
self.fail('Deleted device was not restored')

self.assertEqual(Device.objects.count(), 1)
self.assertEqual(DeviceConnection.objects.count(), 1)
self.assertEqual(DeviceFirmware.objects.count(), 1)
1 change: 1 addition & 0 deletions requirements-test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ redis>=3.4.1,<4.0.0
django-redis>=4.11.0,<5.0
mock-ssh-server~=0.8.0
responses~=0.12.1
selenium~=3.141.0

0 comments on commit 9df7d2d

Please sign in to comment.