Skip to content
This repository has been archived by the owner on Jul 9, 2020. It is now read-only.

[feature] Added API to get context of device #118 #138

Merged
merged 1 commit into from
Jan 3, 2020
Merged

[feature] Added API to get context of device #118 #138

merged 1 commit into from
Jan 3, 2020

Conversation

strang1ato
Copy link
Contributor

Fixes #118

@coveralls
Copy link

coveralls commented Dec 28, 2019

Coverage Status

Coverage increased (+0.001%) to 99.907% when pulling 4abc78f on oltarzewskik:issues/118 into a738f2b on openwisp:master.

Copy link
Member

@nemesifier nemesifier left a 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

django_netjsonconfig/base/admin.py Outdated Show resolved Hide resolved
django_netjsonconfig/base/admin.py Outdated Show resolved Hide resolved
django_netjsonconfig/base/admin.py Outdated Show resolved Hide resolved
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"}')
Copy link
Member

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

Copy link
Member

@nemesifier nemesifier left a 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/admin.py Outdated Show resolved Hide resolved
django_netjsonconfig/base/admin.py Show resolved Hide resolved
django_netjsonconfig/base/admin.py Outdated Show resolved Hide resolved
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)
Copy link
Member

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.

Copy link
Contributor Author

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?

context = json.dumps(device.config.get_context())
else:
device_config = Config(device=device)
context = json.dumps(device_config.get_context())
Copy link
Member

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
Copy link
Member

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')
Copy link
Member

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"')
Copy link
Member

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"')
Copy link
Member

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.

Copy link
Member

@nemesifier nemesifier left a 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.

@@ -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
Copy link
Member

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.

@@ -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):
Copy link
Member

@nemesifier nemesifier Dec 31, 2019

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.

@@ -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)
Copy link
Member

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

device_config = device.config
else:
device_config = Config(device=device)
context = json.dumps(device_config.get_context())
Copy link
Member

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())

if hasattr(device, 'config'):
device_config = device.config
else:
device_config = Config(device=device)
Copy link
Member

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.

Copy link
Member

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()

Copy link
Member

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

Copy link
Contributor Author

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):
Copy link
Contributor Author

@strang1ato strang1ato Dec 31, 2019

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?)

Copy link
Member

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

@@ -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'):
Copy link
Member

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):
Copy link
Member

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())
Copy link
Contributor Author

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):
Copy link
Contributor Author

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

Copy link
Member

@nemesifier nemesifier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work! 👍

@nemesifier nemesifier merged commit ce0a694 into openwisp:master Jan 3, 2020
@strang1ato strang1ato deleted the issues/118 branch January 3, 2020 17:19
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[API] Add API to get context
4 participants