-
Notifications
You must be signed in to change notification settings - Fork 60
[feature] Added API to get context of device #118 #138
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good! See my comments
self._create_config(device=device) | ||
url = reverse('admin:django_netjsonconfig_device_context', args=[device.pk]) | ||
response = self.client.get(url) | ||
self.assertEqual(str(response.content.decode()), '{"error": "login required"}') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
once you do these changes, the error you get here may be different
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Progressing well, see my comment
django_netjsonconfig/base/config.py
Outdated
if app_settings.HARDWARE_ID_ENABLED and self._has_device(): | ||
c.update({'hardware_id': self.device.hardware_id}) | ||
if self.context: | ||
c.update(self.context) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ups, I didn't notice this, I forgot we had this feature, sorry for my bad advice, we need to rollback to how it was in the previous round of review, and we need to make sure the current config instance is being used, unless we're dealing with a new instance, in which case we should create a new one on the fly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nemesisdesign so I have to revert changes made in /base/device.py and /base/config.py?
django_netjsonconfig/base/admin.py
Outdated
context = json.dumps(device.config.get_context()) | ||
else: | ||
device_config = Config(device=device) | ||
context = json.dumps(device_config.get_context()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please do not duplicate code.
Use device_config
to store the right config instance and then write only one json.dumps(device_config.get_context())
call.
device = self._create_device() | ||
url = reverse('admin:django_netjsonconfig_device_context', args=[device.pk]) | ||
response = self.client.get(url) | ||
expected_url = reverse('admin:login') + '?next=' + url |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
write this as:
'{}?next={}'.format(reverse('admin:login') , url)
expected_url = reverse('admin:login') + '?next=' + url | ||
self.assertRedirects(response, expected_url) | ||
# log in again for next tests | ||
self.client.login(username='admin', password='tester') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is not needed, the setUp
method of this test class handles that.
device = self._create_device(name='sample-device') | ||
url = reverse('admin:django_netjsonconfig_device_context', args=[device.pk]) | ||
response = self.client.get(url) | ||
self.assertContains(response, '"name": "sample-device"') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this way of testing your code is not good enough.
Use response.json() and compare it to the result returned by get_context(), they must be equal.
Eg:
self.assertEqual(response.json(), Config(device=device).get_context())
self._create_config(device=device) | ||
url = reverse('admin:django_netjsonconfig_device_context', args=[device.pk]) | ||
response = self.client.get(url) | ||
self.assertContains(response, '"name": "sample-device"') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here, the code will be slightly different.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Further review and testing is showing problems.
I left my findings below, let me know if all is clear.
django_netjsonconfig/base/admin.py
Outdated
@@ -11,6 +12,7 @@ | |||
from django.template.response import TemplateResponse | |||
from django.urls import reverse | |||
from django.utils.translation import ugettext_lazy as _ | |||
from django_netjsonconfig.models import Config |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just realized you cannot do this. You would break extensibility because you are importing a concrete model into base classes. See the next comment on how to fix this, remove this import please.
django_netjsonconfig/base/admin.py
Outdated
@@ -200,6 +205,15 @@ def download_view(self, request, pk): | |||
return send_file(filename='{0}.tar.gz'.format(config.name), | |||
contents=config_archive.getvalue()) | |||
|
|||
def device_context_view(self, request, pk): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
after reviewing the code again, I realized we're editing a base class that is then inherited by the Template admin and VPN admin, this means the context API will generate different views and we're testing only one:
(show_urls is a django-extensions command)
./manage.py show_urls | grep context
/admin/django_netjsonconfig/device/<pk>/context\.json django_netjsonconfig.base.admin.device_context_view admin:django_netjsonconfig_device_context
/admin/django_netjsonconfig/template/<pk>/context\.json django_netjsonconfig.base.admin.device_context_view admin:django_netjsonconfig_template_context
/admin/django_netjsonconfig/vpn/<pk>/context\.json django_netjsonconfig.base.admin.device_context_view admin:django_netjsonconfig_vpn_context
Those views are now raising an exception and are not being tested, we should test the template and vpn views as well.
device_context_view
should be renamed to context_view
.
django_netjsonconfig/base/admin.py
Outdated
@@ -200,6 +205,15 @@ def download_view(self, request, pk): | |||
return send_file(filename='{0}.tar.gz'.format(config.name), | |||
contents=config_archive.getvalue()) | |||
|
|||
def device_context_view(self, request, pk): | |||
device = get_object_or_404(self.model, pk=pk) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
device
should be renamed instance
as in the beginning, you were right on that one
django_netjsonconfig/base/admin.py
Outdated
device_config = device.config | ||
else: | ||
device_config = Config(device=device) | ||
context = json.dumps(device_config.get_context()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
then you just have to do:
context = json.dumps(instance.get_context())
django_netjsonconfig/base/admin.py
Outdated
if hasattr(device, 'config'): | ||
device_config = device.config | ||
else: | ||
device_config = Config(device=device) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this logic has to be moved into a get_context
method on AbstractDevice
, with the difference that in order to get the Config
model, you will have to use the get_config_model
method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the get_context
method on the device then will have to simply return config.get_context()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please also write a unit test for this new method on the device model in test_models
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just found out that AbstractTemplate
class doesn't have get_context
function, I should make function there too, right?
@@ -100,5 +100,12 @@ def clean(self, *args, **kwargs): | |||
if self.type == 'vpn' and not self.config: | |||
self.config = self.vpn.auto_client(auto_cert=self.auto_cert) | |||
|
|||
def get_context(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This, function will probably need changes, (I am not sure what this function have to output, can you tell please?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
right, let's take advantage of this situation to do some cleanup.
This module has a setting, NETJSONCONFIG_CONTEXT
which contents are added to the Config
context (see the line containing c.update(app_settings.CONTEXT)
), but not in the result returned by Vpn.get_context()
, nor this new method.
So the best thing and most consistent thing to do, is to add a new get_context()
method on django_netjsonconfig.base.base.BaseConfig
, which just does something like:
def get_context(self):
return app_settings.CONTEXT
Then all the other get_context
methods on the classes inheriting from BaseConfig
must do something like:
def get_context(self):
c = super(ModelName, self).get_context()
# add more data to c ...
return c
django_netjsonconfig/base/device.py
Outdated
@@ -79,6 +79,13 @@ def _get_config_attr(self, attr): | |||
attr = getattr(self.config, attr) | |||
return attr() if callable(attr) else attr | |||
|
|||
def get_context(self): | |||
if hasattr(self, 'config'): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there's a method for this: _has_config
@@ -100,5 +100,12 @@ def clean(self, *args, **kwargs): | |||
if self.type == 'vpn' and not self.config: | |||
self.config = self.vpn.auto_client(auto_cert=self.auto_cert) | |||
|
|||
def get_context(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
right, let's take advantage of this situation to do some cleanup.
This module has a setting, NETJSONCONFIG_CONTEXT
which contents are added to the Config
context (see the line containing c.update(app_settings.CONTEXT)
), but not in the result returned by Vpn.get_context()
, nor this new method.
So the best thing and most consistent thing to do, is to add a new get_context()
method on django_netjsonconfig.base.base.BaseConfig
, which just does something like:
def get_context(self):
return app_settings.CONTEXT
Then all the other get_context
methods on the classes inheriting from BaseConfig
must do something like:
def get_context(self):
c = super(ModelName, self).get_context()
# add more data to c ...
return c
'id': str(self.id), | ||
'name': self.name, | ||
} | ||
c.update(super(AbstractTemplate, self).get_context()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added this line in end instead of beginning because if we change this dict to json we want app_settings.CONTEXT
on end
@@ -223,9 +225,9 @@ def test_dh(self): | |||
self.assertTrue(v.dh.startswith('-----BEGIN DH PARAMETERS-----')) | |||
self.assertTrue(v.dh.endswith('-----END DH PARAMETERS-----\n')) | |||
|
|||
def test_context_empty(self): | |||
def test_get_context_empty_vpn(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed name of this function, because old name would lead to misunderstanding, it now clearly say that we are testing empty vpn object
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work! 👍
Fixes #118