What Python beginners got hooked on with Django

Introduction

~~ You said "Django has a lot of experts in the company" !!!! ~~

Future Advent Calendar 2019 This is the article on the 10th day. This year's Advent calendar is scary because there are too many stuff ...

This time, I wrote golang and rails, It will be the first time to build a system with Python ~~ rushing ~~, and I will publish what I was addicted to.

In one line

Django's model doesn't reflect settings such as default in the DB!

Prerequisite environment

The environment assumes that you have completed Introduction to Python Django (1) up to the article (6) in order. It's not an official reference, but it was a good article. I'm glad that the finished product is on https://github.com/kakky/mybook20. (The following verification is performed with this commit. Https://github.com/kakky/mybook20/commit/82e741652bfd7f82f5c0bc601e04b7585632d266)

If you have cloned the above, do the following as a preliminary preparation.

$ python manage.py migrate
$ python manage.py createsuperuser
$ python manage.py runserver

If you stumble here, please refer to the prerequisite article. The above command is written in this article. Introduction to Python Django (3)

Main subject

Django's model doesn't reflect settings such as default in the DB

Preparation

Let's change the class Book of cms / models.py as follows.

cms/models.py


class Book(models.Model):
    """Books"""
    name = models.CharField('Book title', max_length=255, unique=True)
    publisher = models.CharField('the publisher', max_length=255)
    page = models.IntegerField('number of pages', blank=True, default=0)
    on_sale = models.BooleanField('Sale', default=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.name

Add created_at and ʻupdated_at, fill the current time at insert and update respectively, add ʻon_sale and leave the default value set to True.

In addition, ʻunique = True is added to name. By default, all columns have null = False`, which is the behavior of not null constraint.

Since we changed the model, create a migration file as follows. I'm angry to fill in the initial value, so set it appropriately as timezone.now.

$ python manage.py makemigrations
You are trying to add the field 'created_at' with 'auto_now_add=True' to book without a default; the database needs something to populate existing rows.

 1) Provide a one-off default now (will be set on all existing rows)
 2) Quit, and let me add a default in models.py
Select an option: 1
Please enter the default value now, as valid Python
You can accept the default 'timezone.now' by pressing 'Enter' or you can provide another value.
The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now
Type 'exit' to exit this prompt
[default: timezone.now] >>> timezone.now

Apply migration.

python manage.py migrate

Operation check

Go to http://127.0.0.1:8000/admin/ and add your Book. (Details omitted)

success.png

Let's take a look at the contents of the DB.

$ sqlite> select * from cms_book;
id|name|page|created_at|on_sale|updated_at|publisher
1|Devil's darling|100|2019-12-09 13:03:29.102973|1|2019-12-09 13:03:29.132151|Kadokawa Bunko

It contains created_at updated_at and looks good. * ʻOn_sale` is 1 and represents True. Now let's insert directly into the DB.

sqlite> insert into cms_book(name, publisher, page) values('Akuma no Temariuta', 'Shueisha', 200);
Error: NOT NULL constraint failed: cms_book.created_at

For some reason, I get angry that created_at, which should automatically enter the current time, violates the not null constraint. So, fill created_at, ʻupdated_at` with CURRENT_TIMESTAMP.

sqlite> insert into cms_book(
    name,
    publisher,
    page,
    created_at,
    updated_at
)
values(
    'Akuma no Temariuta',
    'Shueisha',
    200,
    CURRENT_TIMESTAMP,
    CURRENT_TIMESTAMP
)
;
Error: NOT NULL constraint failed: cms_book.on_sale

This time, I get angry that on_sale, which should have specified the default value, is null. Since it can't be helped, ʻon_sale` also explicitly specifies the value.

sqlite> insert into cms_book(
    name,
    publisher,
    page,
    on_sale,
    created_at,
    updated_at
)
values(
    'Akuma no Temariuta',
    'Shueisha',
    200,
    0,
    CURRENT_TIMESTAMP,
    CURRENT_TIMESTAMP
)
;
sqlite> select * from cms_book;
id|name|page|created_at|on_sale|updated_at|publisher
1|Devil's darling|100|2019-12-09 13:03:29.102973|1|2019-12-09 13:03:29.132151|Kadokawa Bunko
2|Akuma no Temariuta|200|2019-12-09 13:12:17|0|2019-12-09 13:12:17|Shueisha

This time I was able to insert successfully. __ Why did NOT NULL constraint occur in a column with default, ʻauto_now_add, ʻauto_now? __ I will look at them in order.

sqlite> .schema cms_book
CREATE TABLE IF NOT EXISTS "cms_book"(
    "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
    "name" varchar(255) NOT NULL UNIQUE,
    "page" integer NOT NULL,
    "created_at" datetime NOT NULL,
    "on_sale" bool NOT NULL,
    "updated_at" datetime NOT NULL,
    "publisher" varchar(255) NOT NULL
)
;

At the time of table definition, the default and ʻauto_now` systems have already dropped. Check the migration file and the generated sql.

cms/migrations/0002_auto_20191209_2202.py


    operations = [
        migrations.AddField(
            model_name='book',
            name='created_at',
            field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
            preserve_default=False,
        ),
        migrations.AddField(
            model_name='book',
            name='on_sale',
            field=models.BooleanField(default=True, verbose_name='Sale'),
        ),
        migrations.AddField(
            model_name='book',
            name='updated_at',
            field=models.DateTimeField(auto_now=True),
        ),
        migrations.AlterField(
            model_name='book',
            name='name',
            field=models.CharField(max_length=255, unique=True, verbose_name='Book title'),
        ),
        migrations.AlterField(
            model_name='book',
            name='publisher',
            field=models.CharField(max_length=255, verbose_name='the publisher'),
        ),
    ]
$ python manage.py showmigrations
admin
 [X] 0001_initial
(abridgement)
cms
 [X] 0001_initial
 [X] 0002_auto_20191209_2202
(abridgement)
$ python manage.py sqlmigrate cms 0002_auto_20191209_2202
BEGIN;
(abridgement)
--
-- Alter field publisher on book
--
CREATE TABLE "new__cms_book"(
    "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
    "name" varchar(255) NOT NULL UNIQUE,
    "page" integer NOT NULL,
    "created_at" datetime NOT NULL,
    "on_sale" bool NOT NULL,
    "updated_at" datetime NOT NULL,
    "publisher" varchar(255) NOT NULL
)
;
INSERT INTO "new__cms_book"(
    "id",
    "name",
    "page",
    "created_at",
    "on_sale",
    "updated_at",
    "publisher"
)
SELECT
    "id",
    "name",
    "page",
    "created_at",
    "on_sale",
    "updated_at",
    "publisher"
FROM
    "cms_book"
;
DROP TABLE "cms_book"
;
ALTER TABLE "new__cms_book" RENAME TO "cms_book"
;
COMMIT
;

In the migration file, you can see that the notation remains like default = True. I omitted the generated sql because it is longer (I don't know why you bother to create, drop, alter) Please pay attention to the following parts.

CREATE TABLE "new__cms_book"(
    "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
    "name" varchar(255) NOT NULL UNIQUE,
    "page" integer NOT NULL,
    "created_at" datetime NOT NULL,
    "on_sale" bool NOT NULL,
    "updated_at" datetime NOT NULL,
    "publisher" varchar(255) NOT NULL
)

The default value and ʻauto_now` have fallen brilliantly. By the way, this behavior was the same not only in sqlite but also in postgresql etc.

solution

At first, I couldn't find it easily even if I googled, but this phenomenon was mentioned in this stackoverflow. https://stackoverflow.com/questions/53706125/django-default-at-database

In other words, "model and DB schema are different mons. Not null and unique will be applied, but default is impossible!" ...

I think that there are many cases where one DB is referenced / updated from multiple applications or patched manually, but Django users will not be in trouble ... As a workaround, it seems better to steadily generate DDL (raw SQL) from ERD instead of DDL generated from model.

Finally

I was surprised at how popular FW allows such behavior with problems in practicality. I reaffirmed that it is more important to suppress low-layer parts such as reading raw SQL to solve problems.

Recommended Posts

What Python beginners got hooked on with Django
Django 1.11 started with Python3.6
Do Django with CodeStar (Python3.8, Django2.1.15)
Python3 + Django ~ Mac ~ with Apache
Getting Started with Python Django (1)
Getting Started with Python Django (4)
Getting Started with Python Django (3)
Getting Started with Python Django (6)
Hello World (beginners) on Django
Getting Started with Python Django (5)
[Python] What is a with statement?
python + django + scikit-learn + mecab (1) on heroku
What to do with PYTHON release?
Run python3 Django1.9 with mod_wsgi (deploy)
Notes on using rstrip with python.
Getting started with Python 3.8 on Windows
Python | What you can do with Python
[Memo] Tweet on twitter with python
A note on what you did to use Flycheck with Python
Get started with the Python framework Django on Mac OS X
[Python] Looking back on what I taught programming beginners from functions
INSERT into MySQL with Python [For beginners]
Run servo with Python on ESP32 (Windows)
[Episode 2] Beginners tried Numeron AI with python
[Episode 3] Beginners tried Numeron AI with python
Django + Apache with mod_wsgi on Windows Server 2016
A memo with Python2.7 and Python3 on CentOS
Map rent information on a map with python
Follow active applications on Mac with Python
Notes on doing Japanese OCR with Python
scipy stumbles with pip install on python 2.7.8
CentOS 6.4 with Python 2.7.3 with Apache with mod_wsgi and Django
Download files on the web with Python
What I got from Python Boot Camp
Build Python environment with Anaconda on Mac
[Episode 0] Beginners tried Numeron AI with python
[Episode 1] Beginners tried Numeron AI with python
What I did with a Python array
[Python] Read images with OpenCV (for beginners)
A reminder of what I got stuck when starting Atcoder with python
WebApi creation with Python (CRUD creation) For beginners
Upload what you got in request to S3 with AWS Lambda Python
How Python beginners get started with Python with Progete
Rock-paper-scissors with Python Let's run on a Windows local server for beginners
Installing PIL with Python 3.x on macOS
[For beginners] Try web scraping with Python
Working with GPS on Raspberry Pi 3 Python
What I did when I got stuck in the time limit with lambda python
Getting started with Python with 100 knocks on language processing
Until you CI what you made with Django with Jenkins
Project cannot be created with Python3.5 (Windows) + django1.7.1
Build python environment with pyenv on EC2 (ubuntu)
Build a python environment with ansible on centos6
Vienna with Python + Flask web app on Jenkins
[Final story] Beginners tried Numeron AI with python
Install OpenCV 4.0 and Python 3.7 on Windows 10 with Anaconda
[Python] Build a Django development environment with Docker
~ Tips for Python beginners from Pythonista with love ① ~
Create Nginx + uWSGI + Python (Django) environment with docker
Folium: Visualize data on a map with Python
Try working with Mongo in Python on Mac