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"
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.