Flask, or Bottle. And in fact, there are many others.
After reading the first chapter, I was a little bit disappointed. There were no Django models involved in the shown example. So, I try to find out if it was possible: a single file Django project with at least one application containing one model. I found some interesting links on the topic. But either it was for a very old version of Django, either I was obliged to install another package besides Django. After few days reading others people code, this is what I've done:
#!/usr/bin/env python
import os
import sys
from django.conf import settings
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path[0] = os.path.dirname(BASE_DIR)
# the current folder name will also be our app
APP_LABEL = os.path.basename(BASE_DIR)
settings.configure(
DEBUG=os.environ.get('DEBUG', 'on') == 'on',
SECRET_KEY=os.environ.get('SECRET_KEY', os.urandom(32)),
ALLOWED_HOSTS=os.environ.get('ALLOWED_HOSTS', 'localhost').split(','),
ROOT_URLCONF=__name__,
MIDDLEWARE=[
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.locale.LocaleMiddleware',
],
INSTALLED_APPS=[
APP_LABEL,
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
],
STATIC_URL='/static/',
STATICFILES_DIRS=[
os.path.join(BASE_DIR, "static"),
],
STATIC_ROOT=os.path.join(BASE_DIR, "static_root"),
MEDIA_ROOT=os.path.join(BASE_DIR, "media"),
MEDIA_URL='/media/',
TEMPLATES=[
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, "templates"),],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.i18n',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.template.context_processors.tz',
'django.contrib.messages.context_processors.messages',
],
},
},
],
DATABASES={
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
},
REST_FRAMEWORK={
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAdminUser',
],
'PAGE_SIZE': 10
}
)
import django
django.setup()
from django.db import models
from django.contrib import admin
from django.db import models
# Create your models here.
class Author(models.Model):
name = models.CharField(max_length=200)
class Meta:
app_label = APP_LABEL
class Book(models.Model):
author = models.ForeignKey(Author, related_name='books')
title = models.CharField(max_length=400)
class Meta:
app_label = APP_LABEL
admin.site.register(Book)
admin.site.register(Author)
admin.autodiscover()
from rest_framework import serializers
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'
from rest_framework import viewsets
class BooksViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
from django.conf.urls import url, include
from rest_framework import routers
from django.http import HttpResponse
from django.contrib import admin
router = routers.DefaultRouter()
router.register(r'books', BooksViewSet)
def index(request):
return HttpResponse("Hello")
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^$', index, name='homepage'),
url(r'^api/', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls',\
namespace='rest_framework'))
]
from django.core.wsgi import get_wsgi_application
if __name__ == "__main__":
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)
else:
get_wsgi_application()
Everything related to Django REST Framework is not mandatory. I was looking for interesting links over the internet, and I found this question on StackOverflow. I saw this as an opportunity to kill two birds with one stone.
The above script was saved in a file named single.py
in a folder named lwdj
. But you can do as you feel, as soon as you respect the name of the file and the name of the current folder. The rest is known story:
Make the migrations for our application:
(dj) ~/.../how_to_django/lwdj (master *%)
⤷ python single.py makemigrations lwdj
Migrations for 'lwdj':
migrations/0001_initial.py:
- Create model Author
- Create model Book
(dj) ~/.../how_to_django/lwdj (master *%) ⤷
Run the migrations:
(dj) ~/.../how_to_django/lwdj (master *%)
⤷ python single.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, lwdj, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying lwdj.0001_initial... OK
Applying sessions.0001_initial... OK
(dj) ~/.../how_to_django/lwdj (master *%)
Create a super user and run the server:
(dj) ~/.../how_to_django/lwdj (master *%)
⤷ python single.py createsuperuser
Username (leave blank to use 'nsukami'):
Email address: nsukami@gmail.com
Password:
Password (again):
Superuser created successfully.
(dj) ~/.../how_to_django/lwdj (master *%)
⤷ python single.py runserver
Performing system checks...
System check identified no issues (0 silenced).
January 12, 2017 - 18:52:57
Django version 1.10.4, using settings None
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
Yes, that is not how you are supposed to use Django. But we were able to learn few things.
Somewhere in the Django documentation related to projects and applications, we can read:
There's no restriction that a project package can't also be considered an application and have models, etc. (which would require adding it to INSTALLED_APPS).
We can double check using the tree
command:
(dj) ~/.../how_to_django/lwdj (master *%)
⤷ tree
.
├── activate -> /home/nsukami/envs/dj/bin/activate
├── db.sqlite3
├── Makefile
├── media
├── migrations
│ ├── 0001_initial.py
│ ├── __init__.py
│ └── __pycache__
│ └── __init__.cpython-36.pyc
├── single.py
├── static
│ └── site.css
├── static_root
└── templates
└── home.html
Reading The Fabulous Manual, we learn that, this method configures Django by loading the settings, but also:
When Django starts, django.setup() is responsible for populating the application registry.
Let's start a shell using the command python single.py shell
, and let's check the application registry:
(dj) >>> from django.apps import apps
(dj) >>> apps.ready
True
(dj) >>>
(dj) >>>
(dj) >>> apps.get_app_configs()
odict_values([<AppConfig: lwdj>, <AdminConfig: admin>, <AuthConfig: auth>, <ContentTypesConfig: contenttypes>, <SessionsConfig: sessions>, <MessagesConfig: messages>, <StaticFilesConfig: staticfiles>, <AppConfig: rest_framework>])
(dj) >>>
(dj) >>> c = apps.get_app_config('lwdj')
(dj) >>>
(dj) >>> c.module
<module 'lwdj' (namespace)>
(dj) >>>
(dj) >>> c.path
'/home/nsukami/GITHUB/how_tos/how_to_django/lwdj'
(dj) >>>
(dj) >>> c.verbose_name
'Lwdj'
(dj) >>>
(dj) >>> c.name
'lwdj'
(dj) >>> c.label
'lwdj'
(dj) >>>
(dj) >>> for m in c.models: print(m)
...
author
book
(dj) >>>
Neat!
We also read that:
At this stage, your code shouldn't import any models!
This is exactly why our models, and everything related to them, do not appear before those 2 lines of code.
import django
django.setup()
Doing otherwise, raises the AppRegistryNotReady
exception:
(dj) ~/.../how_to_django/lwdj (master *%)
⤷ python single.py runserver
Traceback (most recent call last):
File "single.py", line 90, in <module>
class Author(models.Model):
File "/home/nsukami/envs/dj/lib/python3.6/site-packages/django/db/models/base.py", line 105, in __new__
app_config = apps.get_containing_app_config(module)
File "/home/nsukami/envs/dj/lib/python3.6/site-packages/django/apps/registry.py", line 237, in get_containing_app_config
self.check_apps_ready()
File "/home/nsukami/envs/dj/lib/python3.6/site-packages/django/apps/registry.py", line 124, in check_apps_ready
raise AppRegistryNotReady("Apps aren't loaded yet.")
django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet.
(dj) ~/.../how_to_django/lwdj (master *%)
I have not yet fully understood this part. APP_LABEL
is actually containing the name of the current directory. By putting APP_LABEL
inside INSTALLED_APPS
, Django was able to find all the Django models existing inside the current directory:
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path[0] = os.path.dirname(BASE_DIR)
APP_LABEL = os.path.basename(BASE_DIR)
{{< /highlight >}}
{{< highlight python >}}
INSTALLED_APPS=[
APP_LABEL,
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
],
All our models were created outside of a models.py
file. They're not even in a real Django application. So we were obliged to tell them which application they belongs to. This is done using the app_label attribute of the internal class Meta
of Django models. {{< highlight python >}} class Author(models.Model): name = models.CharField(max_length=200) class Meta: app_label = APP_LABEL
class Book(models.Model): author = models.ForeignKey(Author, related_name='books') title = models.CharField(max_length=400) class Meta: app_label = APP_LABEL {{< /highlight >}}
Really hope you learnt something. Also, I would not be able to write this post, without following the StackTrace leaved by the people who were there before me. A huge thanks to them: