1

This appears to be a common error, and I have checked all the solutions I could find (there are only about 4 and almost all of them involve misconfigurations). I am NOT using heroku, but I AM using docker. I am using the docker images python:3.9.7 and postgis/postgis:10-3.1-alpine. My Dockerfile contains the following line:

ARG BUILD_ENV=production
# ...
FROM python:3.9.7 as apiserver
#...
RUN apt-get update && \
    apt-get install --no-install-recommends -y apt-utils build-essential sudo git wget curl ca-certificates nginx openssl libpq-dev expect libmagic-dev graphviz python3-gdal python3-pandas postgis binutils libproj-dev gdal-bin python3-djangorestframework-gis
# ...
RUN pip install --upgrade pip && pip install -r requirements/${BUILD_ENV}.txt
# ...

This should make sure all the dependencies I need to build the postgis libs for django are present, so when I run pip install, it will have everything it needs. It is more verbose than it needs to be because the GeoDjango docs say to install postgis binutils libproj-dev gdal-bin, and the drf-gis one has all the GeoDjango libs as dependencies. I plan on using the drf-gis package, but am not currently using it.

requirements/production.txt

django-cors-headers==3.5.0
django-extensions==3.0.9
django-filter==2.4.0
# ...
Django==3.1.13
# ...
djangorestframework==3.12.4
djangorestframework-gis==0.17
# ...

I have enabled the correct app in INSTALLED_APPS:

INSTALLED_APPS = [
    'django.contrib.contenttypes',
    'django.contrib.admin',
    'django.contrib.admindocs',
    'django.contrib.auth',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.gis',
    'django_extensions',
    'rest_framework',
    'rest_framework.authtoken',
    # 'rest_framework_gis',
    'django.contrib.sites',
    'django_filters',
    'apiserver',  # api v1 app doesn't use geodjango
    'apiserver_v2', # api v2 uses geodjango
    # ...
]

I also set the following as my DB config:

DATABASES = {
    'default': {
        'ENGINE': 'django.contrib.gis.db.backends.postgis',
        'HOST': os.environ.get('PGHOST'),
        'PORT': 5432,
        'NAME': os.environ.get('PGDATABASE'),        
        'USER': os.environ.get('PGUSER'),
        'PASSWORD': os.environ.get('PGPASSWORD'),
        'CONN_MAX_AGE': os.environ.get('DB_CONN_MAX_AGE', 500),
    },
}

I also have the following migration as the first migration in my app. I didn't need this in my testing, with SpatiaLite, but I added it here to be complete with the documentation.

0000_postgis.py

from django.contrib.postgres.operations import CreateExtension
from django.db import migrations


class Migration(migrations.Migration):

    operations = [
        CreateExtension('postgis'),
        CreateExtension('postgis_raster'),
    ]

This migration works as expected, and the extension appears to be installed:

apiserver_db_name=# \dx
                                    List of installed extensions
      Name      | Version |   Schema   |                        Description                         
----------------+---------+------------+------------------------------------------------------------
 plpgsql        | 1.0     | pg_catalog | PL/pgSQL procedural language
 postgis        | 3.1.4   | public     | PostGIS geometry and geography spatial types and functions
 postgis_raster | 3.1.4   | public     | PostGIS raster types and functions
(3 rows)

Now it's all setup according to the documentation. However, when I run my migrations, I get the following error:

Operations to perform:
  Apply all migrations: admin, apiserver, apiserver_v2, auth, authtoken, contenttypes, enter, guardian, sessions, sites
Running migrations:
  Applying apiserver_v2.0000_postgis... OK
  Applying apiserver_v2.0001_initial...Traceback (most recent call last):
  File "/api-server/manage.py", line 15, in <module>
    execute_from_command_line(sys.argv)
  File "/usr/local/lib/python3.9/site-packages/django/core/management/__init__.py", line 401, in execute_from_command_line
    utility.execute()
  File "/usr/local/lib/python3.9/site-packages/django/core/management/__init__.py", line 395, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/usr/local/lib/python3.9/site-packages/django/core/management/base.py", line 330, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/usr/local/lib/python3.9/site-packages/django/core/management/base.py", line 371, in execute
    output = self.handle(*args, **options)
  File "/usr/local/lib/python3.9/site-packages/django/core/management/base.py", line 85, in wrapped
    res = handle_func(*args, **kwargs)
  File "/usr/local/lib/python3.9/site-packages/django/core/management/commands/migrate.py", line 243, in handle
    post_migrate_state = executor.migrate(
  File "/usr/local/lib/python3.9/site-packages/django/db/migrations/executor.py", line 117, in migrate
    state = self._migrate_all_forwards(state, plan, full_plan, fake=fake, fake_initial=fake_initial)
  File "/usr/local/lib/python3.9/site-packages/django/db/migrations/executor.py", line 147, in _migrate_all_forwards
    state = self.apply_migration(state, migration, fake=fake, fake_initial=fake_initial)
  File "/usr/local/lib/python3.9/site-packages/django/db/migrations/executor.py", line 227, in apply_migration
    state = migration.apply(state, schema_editor)
  File "/usr/local/lib/python3.9/site-packages/django/db/migrations/migration.py", line 124, in apply
    operation.database_forwards(self.app_label, schema_editor, old_state, project_state)
  File "/usr/local/lib/python3.9/site-packages/django/db/migrations/operations/models.py", line 92, in database_forwards
    schema_editor.create_model(model)
  File "/usr/local/lib/python3.9/site-packages/django/db/backends/base/schema.py", line 322, in create_model
    sql, params = self.table_sql(model)
  File "/usr/local/lib/python3.9/site-packages/django/db/backends/base/schema.py", line 159, in table_sql
    definition, extra_params = self.column_sql(model, field)
  File "/usr/local/lib/python3.9/site-packages/django/db/backends/base/schema.py", line 212, in column_sql
    db_params = field.db_parameters(connection=self.connection)
  File "/usr/local/lib/python3.9/site-packages/django/db/models/fields/__init__.py", line 717, in db_parameters
    type_string = self.db_type(connection)
  File "/usr/local/lib/python3.9/site-packages/django/contrib/gis/db/models/fields.py", line 105, in db_type
    return connection.ops.geo_db_type(self)
AttributeError: 'DatabaseOperations' object has no attribute 'geo_db_type'

SPECIAL NOTE: I have looked at all the issues I could find that generate this error, but i cannot find any explanation as to what the error actually MEANS. Why is it looking for this attribute and how can I verify it has this attribute? What package is supposed to put it there and how? If I could find that out, diagnosing and fixing this problem would be much easier.

UPDATE: I tried this with a clean postgis database and got the same errors. I also tried with pg_dumped and pg_restored database. No luck.

Skaman Sam
  • 628
  • 4
  • 13
  • I've tried it out and was able to make it work without issues even with Docker (https://github.com/DusanMadar/geodjango-tutorial-kickoff). WRT `geo_db_type`, check https://stackoverflow.com/q/48895257/4183498. I would verify that Django connects to the DB you expect by `python manage.py dbshell`. Also, can you share your models to see what runs in apiserver_v2.0001_initial? – Dušan Maďar Nov 05 '21 at 21:09
  • Could this be because I switched from using `postgres/postgres:10` to postgis? – Skaman Sam Nov 05 '21 at 22:34
  • Possibly. https://github.com/django/django/blob/main/django/contrib/gis/db/backends/base/operations.py#L62-L67 indicates the DB backend is not able to identify the type of geo-data column. I get `django.db.utils.OperationalError: could not open extension control file "/usr/local/share/postgresql/extension/postgis.control": No such file or directory` on running migrations when I switch to `postgres:11.1-alpine`. – Dušan Maďar Nov 06 '21 at 08:38
  • I've managed to reproduce the same error you get while using postgis DB but with `django.db.backends.postgresql` as DB engine (in `DATABASES["default"]`). Don't you have multiple settings.py files which override DATABASES settings for different environments or something? – Dušan Maďar Nov 06 '21 at 08:56

1 Answers1

1

Solved! This was due to another configuration overriding the DB settings. I was very adamant that it was not a configuration issue, and I'm sorry. I verified this by running the admin shell in the running django app and checking settings.DATABASES. (The other override was django-prometheus, if you're curious.)

Skaman Sam
  • 628
  • 4
  • 13