Skip to main content
CI build status https://coveralls.io/repos/openwisp/django-loci/badge.svg Dependency monitoring https://img.shields.io/gitter/room/nwjs/nw.js.svg?style=flat-square https://badge.fury.io/py/django-loci.svg downloads code style: black

Reusable django-app for storing GIS and indoor coordinates of objects.

Indoor coordinates Map coordinates Mobile coordinates

Dependencies

Compatibility Table

django-loci

Python version

0.2

2.7 or >=3.4

0.3 - 0.4

>=3.6

1.0

>=3.7

1.1

>=3.8

dev

>=3.10

Install stable version from pypi

Install from pypi:

pip install django-loci

Install development version

First of all, install the dependencies of GeoDjango:

Install tarball:

pip install https://github.com/openwisp/django-loci/tarball/master

Alternatively you can install via pip using git:

pip install -e git+git://github.com/openwisp/django-loci#egg=django_loci

If you want to contribute, install your cloned fork:

git clone git@github.com:<your_fork>/django-loci.git
cd django_loci
python setup.py develop

Setup (integrate in an existing django project)

First of all, set up your database engine to one of the spatial databases suppported by GeoDjango.

Add django_loci and its dependencies to INSTALLED_APPS in the following order:

INSTALLED_APPS = [
    # ...
    "django.contrib.gis",
    "django_loci",
    "django.contrib.admin",
    "leaflet",
    "channels",
    # ...
]

Configure CHANNEL_LAYERS according to your needs, a sample configuration can be:

ASGI_APPLICATION = "django_loci.channels.asgi.channel_routing"
CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels_redis.core.RedisChannelLayer",
        "CONFIG": {
            "hosts": [("127.0.0.1", 6379)],
        },
    },
}

Now run migrations:

./manage.py migrate

Troubleshooting

Common issues and solutions when installing GeoDjango.

Unable to load the SpatiaLite library extension

If you get the following exception:

django.core.exceptions.ImproperlyConfigured: Unable to load the SpatiaLite library extension

You need to specify the SPATIALITE_LIBRARY_PATH in your settings.py as explained in the django documentation regarding how to install and configure spatialte.

Issues with other geospatial libraries

Please refer to the geodjango documentation on troubleshooting issues related to geospatial libraries.

Settings

LOCI_FLOORPLAN_STORAGE

type:

str

default:

django_loci.storage.OverwriteStorage

The django file storage class used for uploading floorplan images.

The filestorage can be changed to a different one as long as it has an upload_to class method which will be passed to FloorPlan.image.upload_to.

To understand the details of this statement, take a look at the code of django_loci.storage.OverwriteStorage.

DJANGO_LOCI_GEOCODER

type:

str

default:

ArcGIS

Service used for geocoding and reverse geocoding.

Supported geolocation services:

  • ArcGIS

  • Nominatim

  • GoogleV3 (Google Maps v3)

DJANGO_LOCI_GEOCODE_FAILURE_DELAY

type:

int

default:

1

Amount of seconds between geocoding retry API calls when geocoding requests fail.

DJANGO_LOCI_GEOCODE_RETRIES

type:

int

default:

3

Amount of retry API calls when geocoding requests fail.

DJANGO_LOCI_GEOCODE_API_KEY

type:

str

default:

None

API key if required (eg: Google Maps).

System Checks

geocoding

Use to check if geocoding is working as expected or not.

Run this checks with:

./manage.py check --deploy --tag geocoding

Extending django-loci

django-loci provides a set of models and admin classes which can be imported, extended and reused by third party apps.

To extend django-loci, you MUST NOT add it to settings.INSTALLED_APPS, but you must create your own app (which goes into settings.INSTALLED_APPS), import the base classes of django-loci and add your customizations.

Extending models

This example provides an example of how to extend the base models of django-loci by adding a relation to another django model named Organization.

# models.py of your app
from django.db import models
from django_loci.base.models import (
    AbstractFloorPlan,
    AbstractLocation,
    AbstractObjectLocation,
)

# the model ``organizations.Organization`` is omitted for brevity
# if you are curious to see a real implementation, check out django-organizations


class OrganizationMixin(models.Model):
    organization = models.ForeignKey("organizations.Organization")

    class Meta:
        abstract = True


class Location(OrganizationMixin, AbstractLocation):
    class Meta(AbstractLocation.Meta):
        abstract = False

    def clean(self):
        # your own validation logic here...
        pass


class FloorPlan(OrganizationMixin, AbstractFloorPlan):
    location = models.ForeignKey(Location)

    class Meta(AbstractFloorPlan.Meta):
        abstract = False

    def clean(self):
        # your own validation logic here...
        pass


class ObjectLocation(OrganizationMixin, AbstractObjectLocation):
    location = models.ForeignKey(Location, models.PROTECT, blank=True, null=True)
    floorplan = models.ForeignKey(FloorPlan, models.PROTECT, blank=True, null=True)

    class Meta(AbstractObjectLocation.Meta):
        abstract = False

    def clean(self):
        # your own validation logic here...
        pass

Extending the admin

Following the previous Organization example, you can avoid duplicating the admin code by importing the base admin classes and registering your models with them.

But first you have to change a few settings in your settings.py, these are needed in order to load the admin templates and static files of django-loci even if it’s not listed in settings.INSTALLED_APPS.

Add django.forms to INSTALLED_APPS, now it should look like the following:

INSTALLED_APPS = [
    # ...
    "django.contrib.gis",
    "django_loci",
    "django.contrib.admin",
    #      ↓
    "django.forms",  # <-- add this
    #      ↑
    "leaflet",
    "channels",
    # ...
]

Now add EXTENDED_APPS after INSTALLED_APPS:

INSTALLED_APPS = [
    # ...
]

EXTENDED_APPS = ("django_loci",)

Add openwisp_utils.staticfiles.DependencyFinder to STATICFILES_FINDERS:

STATICFILES_FINDERS = [
    "django.contrib.staticfiles.finders.FileSystemFinder",
    "django.contrib.staticfiles.finders.AppDirectoriesFinder",
    "openwisp_utils.staticfiles.DependencyFinder",
]

Add openwisp_utils.loaders.DependencyLoader to TEMPLATES:

TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [],
        "OPTIONS": {
            "loaders": [
                "django.template.loaders.filesystem.Loader",
                "django.template.loaders.app_directories.Loader",
                # add the following line
                "openwisp_utils.loaders.DependencyLoader",
            ],
            "context_processors": [
                "django.template.context_processors.debug",
                "django.template.context_processors.request",
                "django.contrib.auth.context_processors.auth",
                "django.contrib.messages.context_processors.messages",
            ],
        },
    }
]

Last step, add FORM_RENDERER:

FORM_RENDERER = "django.forms.renderers.TemplatesSetting"

Then you can go ahead and create your admin.py file following the example below:

# admin.py of your app
from django.contrib import admin

from django_loci.base.admin import (
    AbstractFloorPlanAdmin,
    AbstractFloorPlanForm,
    AbstractFloorPlanInline,
    AbstractLocationAdmin,
    AbstractLocationForm,
    AbstractObjectLocationForm,
    AbstractObjectLocationInline,
)
from django_loci.models import FloorPlan, Location, ObjectLocation


class FloorPlanForm(AbstractFloorPlanForm):
    class Meta(AbstractFloorPlanForm.Meta):
        model = FloorPlan


class FloorPlanAdmin(AbstractFloorPlanAdmin):
    form = FloorPlanForm


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


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


class LocationAdmin(AbstractLocationAdmin):
    form = LocationForm
    inlines = [FloorPlanInline]


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


class ObjectLocationInline(AbstractObjectLocationInline):
    model = ObjectLocation
    form = ObjectLocationForm


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

Extending channel consumers

Extend the channel consumer of django-loci in this way:

from django_loci.channels.base import BaseLocationBroadcast
from ..models import Location  # your own location model


class LocationBroadcast(BaseLocationBroadcast):
    model = Location

Extend the broadcast consumer for all locations:

from django_loci.channels.base import BaseCommonLocationBroadcast
from ..models import Location  # your own location model


class CommonLocationBroadcast(BaseCommonLocationBroadcast):
    model = Location

Extending AppConfig

You may want to reuse the AppConfig class of django-loci too:

from django_loci.apps import LociConfig


class MyConfig(LociConfig):
    name = "myapp"
    verbose_name = _("My custom app")

    def __setmodels__(self):
        from .models import Location

        self.location_model = Location

Installing for development

Install sqlite:

sudo apt-get install sqlite3 libsqlite3-dev libsqlite3-mod-spatialite gdal-bin

Install your forked repo:

git clone git://github.com/<your_fork>/django-loci
cd django-loci/
python setup.py develop

Install test requirements:

pip install -r requirements-test.txt

Launch Redis:

docker compose up -d redis

Create database:

cd tests/
./manage.py migrate
./manage.py createsuperuser

Launch development server and SMTP debugging server:

./manage.py runserver

You can access the admin interface at http://127.0.0.1:8000/admin/.

Run tests with (make sure you have the selenium dependencies installed locally first):

./runtests

Contributing

Please refer to the OpenWISP Contribution Guidelines.

Questions

See Github Discussions.

Changelog

See CHANGES.

License

See LICENSE.

Release files for django-loci 1.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-loci 1.3
File Size Uploaded
django_loci-1.3.tar.gz 51.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for django-loci 1.3
File Interpreter ABI Platform
django_loci-1.3-py2.py3-none-any.whl Python 3, Python 2 none any Details

Total release size: 109.8 kB

Release files / django_loci-1.3.tar.gz

Download URL django_loci-1.3.tar.gz
Size 51.7 kB
Tags Source
SHA-256 checksum
How to use checksums
1092800b9402a63f5c706526346f430bfc919f8b6b0e891b6e5fac7ca8f743d3
BLAKE2b-256 checksum
How to use checksums
ce06fbf81d0e91bd5ce8dba03fedeb2612adf99650531b0a18bb4383968b2c20
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 1, 2026.

Transparency log

Release files / django_loci-1.3-py2.py3-none-any.whl

Download URL django_loci-1.3-py2.py3-none-any.whl
Size 58.1 kB
Tags Python 2 Python 3
SHA-256 checksum
How to use checksums
bff53d4ee1ade5b496a1a948db68ff9836d2c24317461f0b394e6a4ffddf493d
BLAKE2b-256 checksum
How to use checksums
c9f7fe757423a02cfad6f1e9282585194b2407bc7c17ecdd47119d9f0affa467
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 1, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.3 This release

2 release files

1.2.2

2 release files

1.2.1

2 release files

1.2

2 release files

1.1.4

2 release files

1.1.3

2 release files

1.1.2

2 release files

1.1.1

2 release files

1.1

2 release files

1.0.1

2 release files

1.0

2 release files

0.4.3

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.4

2 release files

0.3.4

2 release files

0.3.3

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3

2 release files

0.2.1

2 release files

0.2

2 release files

0.1.1

2 release files

0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page