Search code examples
pythondjangocookiecutter-django

Error in shell: doesn't declare an explicit app_label and isn't in an application in INSTALLED_APPS


My first try on Django. Read the book "Two scoops of Django 1.11". Great read. With only scripting experience and new with Python, I'm using best efforts to follow the books standards.

Initiated project with "cookiecutter-django" Made a simple Address app with a TimeStampedModel to experiment.

Works great with makemigrations, migrate, admin and runserver. No issues and it works like a charm.

However trying the book example on a csv import and bind to a form, I wanted to run it from the shell to test.

The following may be overly verbose, but I'm not sure how to proceed.

python manage.py shell --settings=config.settings.local

The error occurs when required code is imported(any other form or model gets the same results):

In [1]: from address.forms import add_csv_postarea
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
<ipython-input-1-f253686866ed> in <module>()
----> 1 from address.forms import add_csv_postarea

~/projects/myproject/myproject/address/forms.py in <module>()
      5 from django import forms
      6 
----> 7 from .models import PostArea, Address
      8 
      9 

~/projects/myproject/myproject/address/models.py in <module>()
      7 
      8 
----> 9 class Country(TimeStampedModel):
     10     """ ISO 3166 Country codes
     11         https://en.wikipedia.org/wiki/ISO_3166-1

~/.virtualenvs/myproject/lib/python3.6/site-packages/django/db/models/base.py in __new__(cls, name, bases, attrs)
    116                         "Model class %s.%s doesn't declare an explicit "
    117                         "app_label and isn't in an application in "
--> 118                         "INSTALLED_APPS." % (module, name)
    119                     )
    120 

RuntimeError: Model class address.models.Country doesn't declare an explicit app_label and isn't in an application in INSTALLED_APPS.

The model mentioned above isn't implemented yet, but if I comment the model out, the next model PostArea will have the same issue.

Here is my relevant settings:

config/settings/base.py

...
# Apps specific for this project go here.
LOCAL_APPS = [
    # custom users app
    'myproject.users.apps.UsersConfig',

    # Your stuff: custom apps go here
    'myproject.core', # Containing the abstract class TimeStampedModel
    'myproject.address', # including the address and PostArea models
]
...

config/urls.py

urlpatterns = [
    ...
    # Your stuff: custom urls includes go here
    url(r'^address/', include('myproject.address.urls', namespace='address')),

myproject/address/forms.py

import csv

from django.utils.six import StringIO

from django import forms

from .models import PostArea, Address


class AddressForm(forms.ModelForm):

    class Meta:

        model = Address
        fields = ['line1', 'line2', 'post_area']


class PostAreaForm(forms.ModelForm):

    class Meta:

        model = PostArea
        fields = ['code', 'name']


def add_csv_postarea(rows):
    """ Two Scoops of Django 1.11 - page 167
        Importing Postal addresses from a CSV file

        Data source: https://data.norge.no/data/posten-norge/postnummer-i-norge
    """

    rows = StringIO(rows)

    records_added = 0
    errors = []

    # Generate a dict per row, overriding the first CSV row missing keys
    for row in csv.DictReader(rows, fieldnames=('code', 'name')):

        # Bind the row to PostAreaForm
        form = PostAreaForm(row)
        # Check to see if the row is valid
        if form.is_valid():
            # Row data is valid so save the record.
            form.save()
            records_added += 1
        else:
            errors.append(form.errors)

    return records_added, errors

myproject/address/models.py

import uuid as uuid_lib

from django.db import models
from django.urls import reverse

from core.models import TimeStampedModel


class Country(TimeStampedModel):
    """ ISO 3166 Country codes
        https://en.wikipedia.org/wiki/ISO_3166-1
    """
    iso_2 = models.CharField(max_length=2, null=False)
    iso_3 = models.CharField(max_length=3, null=False)
    iso_numeric = models.CharField(max_length=3, null=False)
    name_gb = models.CharField(max_length=30)
    name_no = models.CharField(max_length=30)

    def __str__(self):
        return self.iso_2

    class Meta:
        verbose_name = "Country"
        verbose_name_plural = "Countries"


class PostArea(TimeStampedModel):
    """ Norwegian PostArea code and are
        Foreign keys linking connected municipals
    """

    code = models.CharField(max_length=12)
    name = models.CharField(max_length=200)

    def __str__(self):
        return self.code

    class Meta:
        verbose_name = "Postal area"
        verbose_name_plural = "Postal Areas"


class Address(TimeStampedModel):
    """ Reusable Entities Addresses with relations to
            - PostArea
            - Country
    """

    uuid = models.UUIDField(
        primary_key=True,
        default=uuid_lib.uuid4(),
        editable=False
    )
    line1 = models.CharField(max_length=100)
    line2 = models.CharField(max_length=100, blank=True)
    post_area = models.ForeignKey(
        PostArea,
        on_delete=models.PROTECT,
        null=True,
    )

    def __str__(self):
        return self.line1

    def post_name_callable(self):
        return self.post_area.name

    def get_absolute_url(self):
        return reverse('address:detail', kwargs={'pk': self.uuid})

    class Meta:
        verbose_name = "Address"
        verbose_name_plural = "Addresses"

Solution

  • In your INSTALLED_APPS you have included myproject.address. Therefore you should use the myproject.address instead of address for imports.

    For example, change,

    from address.forms import add_csv_postarea
    

    to

    from myproject.address.forms import add_csv_postarea
    

    I’m not sure why your project layout allows you to import the same module as myproject and myproject.address. In the early days of Django, it was easy to do this which led to weird bugs, but the default project layout was changed to avoid this way back in Django 1.4.