Skip to content

Commit

Permalink
Merge pull request openwisp#22 from openwisp/gis
Browse files Browse the repository at this point in the history
[geo] Added GIS module (imported django-loci)
  • Loading branch information
nemesifier committed Dec 2, 2017
2 parents c481b03 + 3eda380 commit 6a5e8e6
Show file tree
Hide file tree
Showing 28 changed files with 834 additions and 24 deletions.
58 changes: 58 additions & 0 deletions .jslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
{
"predef": [
"document",
"navigator",
"console",
"window",
"django",
"gettext",
"windowname_to_id",
"jQuery",
"WebSocket",
"$",
"dismissRelatedLookupPopup",
"dismissAddAnotherPopup",
"FileReader",
"Image",
"L"
],

"adsafe": false,
"safe": false,

"bitwise": false,
"cap": false,
"confusion": true,
"continue": true,
"css": true,
"debug": false,
"eqeq": false,
"es5": true,
"evil": false,
"forin": false,
"fragment": true,
"newcap": false,
"nomen": false,
"on": false,
"plusplus": false,
"regexp": true,
"sloppy": false,
"sub": false,
"undef": false,
"unparam": true,
"vars": true,
"white": false,
"validthis": true,
"strict_mode":true,

"browser": false,
"devel": false,
"node": false,
"rhino": false,
"widget": false,
"windows": false,
"indent": 4,
"maxerr": 50,
"maxlen": 120,
"passfail": false
}
10 changes: 10 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ language: python
sudo: false
cache: pip

addons:
apt:
packages:
- sqlite3
- gdal-bin
- libproj-dev
- libgeos-dev
- libspatialite-dev

python:
- "3.4"
- "2.7"
Expand All @@ -18,6 +27,7 @@ before_install:

install:
- python setup.py -q develop
- if [[ $TRAVIS_PYTHON_VERSION == "2.7" ]]; then pip install git+git://github.com/tinio/pysqlite.git@extension-enabled#egg=pysqlite; fi

script:
- coverage run --source=openwisp_controller runtests.py
Expand Down
44 changes: 25 additions & 19 deletions openwisp_controller/config/tests/test_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,27 +25,33 @@ class TestAdmin(CreateConfigTemplateMixin, TestAdminMixin,
{'codename__endswith': 'template'},
{'codename__endswith': 'vpn'},
]
_device_params = {
'name': 'test-device',
'mac_address': CreateConfigTemplateMixin.TEST_MAC_ADDRESS,
'key': CreateConfigTemplateMixin.TEST_KEY,
'model': '',
'os': '',
'notes': '',
'config-0-id': '',
'config-0-device': '',
'config-0-backend': 'netjsonconfig.OpenWrt',
'config-0-templates': '',
'config-0-config': json.dumps({}),
'config-TOTAL_FORMS': 1,
'config-INITIAL_FORMS': 0,
'config-MIN_NUM_FORMS': 0,
'config-MAX_NUM_FORMS': 1,
}
# WARNING - WATCHOUT
# this class attribute is changed dinamically
# by other apps which add inlines to DeviceAdmin
_additional_params = {}

def _get_device_params(self, org):
return {
'name': 'test-device',
'organization': org.pk,
'mac_address': self.TEST_MAC_ADDRESS,
'key': self.TEST_KEY,
'model': '',
'os': '',
'notes': '',
'config-0-id': '',
'config-0-device': '',
'config-0-backend': 'netjsonconfig.OpenWrt',
'config-0-organization': org.pk,
'config-0-templates': '',
'config-0-config': json.dumps({}),
'config-TOTAL_FORMS': 1,
'config-INITIAL_FORMS': 0,
'config-MIN_NUM_FORMS': 0,
'config-MAX_NUM_FORMS': 1,
}
p = self._device_params.copy()
p.update(self._additional_params)
p['organization'] = org.pk
return p

def test_device_and_template_different_organization(self):
org1 = self._create_org()
Expand Down
1 change: 1 addition & 0 deletions openwisp_controller/geo/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
default_app_config = 'openwisp_controller.geo.apps.GeoConfig'
81 changes: 81 additions & 0 deletions openwisp_controller/geo/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from django.contrib import admin
from django_loci.base.admin import (AbstractFloorPlanAdmin, AbstractFloorPlanForm, AbstractFloorPlanInline,
AbstractLocationAdmin, AbstractLocationForm, AbstractObjectLocationForm,
ObjectLocationMixin)

from openwisp_utils.admin import MultitenantOrgFilter

from ..admin import MultitenantAdminMixin
from ..config.admin import DeviceAdmin as BaseDeviceAdmin
from ..config.admin import ConfigInline
from ..config.models import Device
from .models import DeviceLocation, FloorPlan, Location


class FloorPlanForm(AbstractFloorPlanForm):
class Meta(AbstractFloorPlanForm.Meta):
model = FloorPlan
exclude = ('organization',) # automatically managed


class FloorPlanAdmin(MultitenantAdminMixin, AbstractFloorPlanAdmin):
form = FloorPlanForm
list_filter = [('organization', MultitenantOrgFilter),
'created']


FloorPlanAdmin.list_display.insert(1, 'organization')


class LocationForm(AbstractLocationForm):
class Meta(AbstractLocationForm.Meta):
model = Location


class FloorPlanInline(AbstractFloorPlanInline):
form = FloorPlanForm
model = FloorPlan


class LocationAdmin(MultitenantAdminMixin, AbstractLocationAdmin):
form = LocationForm
inlines = [FloorPlanInline]
list_select_related = ('organization',)


LocationAdmin.list_display.insert(1, 'organization')
LocationAdmin.list_filter.insert(0, ('organization', MultitenantOrgFilter))


class ObjectLocationForm(AbstractObjectLocationForm):
class Meta(AbstractObjectLocationForm.Meta):
model = DeviceLocation

def _get_location_instance(self):
location = super(ObjectLocationForm, self)._get_location_instance()
location.organization_id = self.data.get('organization')
return location

def _get_floorplan_instance(self):
floorplan = super(ObjectLocationForm, self)._get_floorplan_instance()
floorplan.organization_id = self.data.get('organization')
return floorplan


class DeviceLocationInline(ObjectLocationMixin, admin.StackedInline):
model = DeviceLocation
form = ObjectLocationForm


admin.site.register(FloorPlan, FloorPlanAdmin)
admin.site.register(Location, LocationAdmin)


# Add DeviceLocationInline to config.DeviceAdmin

class GeoDeviceAdmin(BaseDeviceAdmin):
inlines = [DeviceLocationInline, ConfigInline]


admin.site.unregister(Device)
admin.site.register(Device, GeoDeviceAdmin)
Empty file.
9 changes: 9 additions & 0 deletions openwisp_controller/geo/api/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from django.conf.urls import url

from . import views

urlpatterns = [
url(r'^api/device-location/(?P<pk>[^/]+)/$',
views.device_location,
name='api_device_location'),
]
56 changes: 56 additions & 0 deletions openwisp_controller/geo/api/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from django.core.exceptions import ObjectDoesNotExist
from rest_framework import generics
from rest_framework.permissions import BasePermission
from rest_framework_gis import serializers as gis_serializers

from ...config.models import Device
from ..models import DeviceLocation, Location


class DevicePermission(BasePermission):
def has_object_permission(self, request, view, obj):
return request.query_params.get('key') == obj.key


class LocationSerializer(gis_serializers.GeoFeatureModelSerializer):
class Meta:
model = Location
geo_field = 'geometry'
fields = ('name', 'geometry')
read_only_fields = ('name', )


class DeviceLocationView(generics.RetrieveUpdateAPIView):
serializer_class = LocationSerializer
permission_classes = (DevicePermission,)
queryset = Device.objects.select_related('devicelocation',
'devicelocation__location')

def get_location(self, device):
try:
return device.devicelocation.location
except ObjectDoesNotExist:
return None

def get_object(self, *args, **kwargs):
device = super(DeviceLocationView, self).get_object()
location = self.get_location(device)
if location:
return location
# if no location present, automatically create it
return self.create_location(device)

def create_location(self, device):
location = Location(name=device.name,
type='outdoor',
organization=device.organization)
location.full_clean()
location.save()
dl = DeviceLocation(content_object=device,
location=location)
dl.full_clean()
dl.save()
return location


device_location = DeviceLocationView.as_view()
41 changes: 41 additions & 0 deletions openwisp_controller/geo/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from django_loci.apps import LociConfig


class GeoConfig(LociConfig):
name = 'openwisp_controller.geo'
label = 'geo'
verbose_name = _('Geographic Information')

def __setmodels__(self):
from .models import Location
self.location_model = Location

def ready(self):
super(GeoConfig, self).ready()
if getattr(settings, 'TESTING', False):
self._add_params_to_test_config()

def _add_params_to_test_config(self):
"""
this methods adds the management fields of DeviceLocationInline
to the parameters used in config.tests.test_admin.TestAdmin
this hack is needed for the following reasons:
- avoids breaking config.tests.test_admin.TestAdmin
- avoids adding logic of geo app in config, this
way config doesn't know anything about geo, keeping
complexity down to a sane level
"""
from .tests.test_admin_inline import TestAdminInline
from ..config.tests.test_admin import TestAdmin as TestConfigAdmin
params = TestAdminInline._get_params()
delete_keys = []
# delete unnecessary fields
# leave only management fields
for key in params.keys():
if '_FORMS' not in key:
delete_keys.append(key)
for key in delete_keys:
del params[key]
TestConfigAdmin._additional_params.update(params)
Empty file.
17 changes: 17 additions & 0 deletions openwisp_controller/geo/channels/consumers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from django_loci.channels.base import BaseLocationBroadcast

from ..models import Location


class LocationBroadcast(BaseLocationBroadcast):
model = Location

def is_authorized(self, user, location):
result = super(LocationBroadcast, self).is_authorized(user, location)
# non superusers must also be members of the org
if result and not user.is_superuser and (
(location.organization.pk,) not in
user.organizations_pk
):
return False
return result
5 changes: 5 additions & 0 deletions openwisp_controller/geo/channels/routing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django_loci.channels.base import location_broadcast_path

from .consumers import LocationBroadcast

channel_routing = [LocationBroadcast.as_route(path=location_broadcast_path)]
Loading

0 comments on commit 6a5e8e6

Please sign in to comment.