diff --git a/README.rst b/README.rst
index cddb102a0..c899f0d8c 100644
--- a/README.rst
+++ b/README.rst
@@ -53,8 +53,8 @@ Other popular building blocks that are part of the OpenWISP ecosystem are:
provides device status monitoring, collection of metrics, charts,
alerts, possibility to define custom checks
- `openwisp-firmware-upgrader
- `_: automated firmware
- upgrades (single devices or mass network upgrades)
+ `_: automated
+ firmware upgrades (single devices or mass network upgrades)
- `openwisp-radius `_:
based on FreeRADIUS, allows to implement network access authentication
systems like 802.1x WPA2 Enterprise, captive portal authentication,
@@ -65,10 +65,11 @@ Other popular building blocks that are part of the OpenWISP ecosystem are:
daemons or other network software (e.g.: OpenVPN); it can be used in
conjunction with openwisp-monitoring to get a better idea of the state
of the network
-- `openwisp-ipam `_: allows to manage
- the assignment of IP addresses used in the network
-- `openwisp-notifications `_:
- allows users to be aware of important events happening in the network.
+- `openwisp-ipam `_: allows to
+ manage the assignment of IP addresses used in the network
+- `openwisp-notifications
+ `_: allows users to be
+ aware of important events happening in the network.
**For a more complete overview of the OpenWISP modules and architecture**,
see the `OpenWISP Architecture Overview
diff --git a/openwisp_controller/__init__.py b/openwisp_controller/__init__.py
index 6e2bfeb2a..d35f51f21 100644
--- a/openwisp_controller/__init__.py
+++ b/openwisp_controller/__init__.py
@@ -1,4 +1,4 @@
-VERSION = (1, 1, 0, 'final')
+VERSION = (1, 2, 0, 'alpha')
__version__ = VERSION # alias
diff --git a/openwisp_controller/config/base/template.py b/openwisp_controller/config/base/template.py
index 1ac764882..25c6a7d00 100644
--- a/openwisp_controller/config/base/template.py
+++ b/openwisp_controller/config/base/template.py
@@ -6,6 +6,7 @@
from django.db import models, transaction
from django.utils.translation import gettext_lazy as _
from jsonfield import JSONField
+from netjsonconfig.exceptions import ValidationError as NetjsonconfigValidationError
from swapper import get_model_name
from taggit.managers import TaggableManager
@@ -115,7 +116,14 @@ def save(self, *args, **kwargs):
if hasattr(self, 'backend_instance'):
del self.backend_instance
current = self.__class__.objects.get(pk=self.pk)
- update_related_config_status = self.checksum != current.checksum
+ try:
+ current_checksum = current.checksum
+ except NetjsonconfigValidationError:
+ # If the Netjsonconfig library upgrade changes the schema,
+ # the old configuration may become invalid, raising an exception.
+ # Setting the checksum to None forces related configurations to update.
+ current_checksum = None
+ update_related_config_status = self.checksum != current_checksum
# save current changes
super().save(*args, **kwargs)
# update relations
diff --git a/openwisp_controller/config/tests/test_template.py b/openwisp_controller/config/tests/test_template.py
index 914499e7f..53540c6ea 100644
--- a/openwisp_controller/config/tests/test_template.py
+++ b/openwisp_controller/config/tests/test_template.py
@@ -7,6 +7,7 @@
from django.db import transaction
from django.test import TestCase, TransactionTestCase
from netjsonconfig import OpenWrt
+from netjsonconfig.exceptions import ValidationError as NetjsonconfigValidationError
from swapper import load_model
from openwisp_utils.tests import catch_signal
@@ -500,6 +501,20 @@ def test_required_vpn_template_corner_case(self):
# {'__all__': ['VPN client with this Config and Vpn already exists.']}
self.assertIsNotNone(vpn_client)
+ def test_regression_preventing_from_fixing_invalid_conf(self):
+ template = self._create_template()
+ # create a template with an invalid configuration
+ Template.objects.update(config={'interfaces': [{'name': 'eth0', 'type': ''}]})
+ # ensure the configuration raises ValidationError
+ with self.assertRaises(NetjsonconfigValidationError):
+ template.refresh_from_db()
+ del template.backend_instance
+ template.checksum
+ del template.backend_instance
+ template.config = {'interfaces': [{'name': 'eth0', 'type': 'ethernet'}]}
+ template.full_clean()
+ template.save()
+
class TestTemplateTransaction(
TransactionTestMixin,