Python/Django API with Docker and Docker Compose Udemy Course Notes

 https://www.udemy.com/course/django-python-advanced/learn/lecture/32238716#overview


docker-compose run --rm app sh -c "python manage.py collectstatic"


https://www.udemy.com/course/django-python-advanced/learn/lecture/32238736#overview


created requirements and dockfile

FROM python:3.9-alpine3.13   (Alpine is lightweight version of linux)

from hub.docker.com - lots of image tags there for the dockerfile.

ENV PYTHONUNBUFFERED 1

Could have used multiple RUN lines.  but && /  is better

Boyo likes using venv anyway even though using docker

.dockerignore  file creation

docker build .

docker-compose file creation  e.g. sh -c "python manage.py runserver 0.0.0.0:8000"
 -->  can run docker-compose build   (same as docker build . but via docker file)



https://www.udemy.com/course/django-python-advanced/learn/lecture/32238750#overview

linting and testing

flake8 simple to use liniting tool

docker-compose run --rm app sh -c "flake8"

if you get linting error... fix from bottom up, or line numbers are messed up.


docker-compose run --rm app sh -c "python manage.py test"



https://www.udemy.com/course/django-python-advanced/learn/lecture/32238752#overview

dev and deployed config          - DEV=true  in  docker-compose.yml

and then     if [ $DEV = "true" ]; 


.flake8   ensure you save this in \app folder




https://www.udemy.com/course/django-python-advanced/learn/lecture/32238754#overview


create django project

docker-compose run --rm app sh -c "django-admin startproject app ."


it creates the django project in \app  because docker-compose has line  "volumes:  - ./app:/app"




docker-compose up    !!   UP



https://www.udemy.com/course/django-python-advanced/learn/lecture/32238764#overview


Github Actions.

Travis, Gitlab CI/CD, Jenkins


https://www.udemy.com/course/django-python-advanced/learn/lecture/32238768#overview


C:\Source\github\bendecko\recipe-app-api\.github\workflows\checks.yml

need to checkout the code because we want to run test and lint.




https://www.udemy.com/course/django-python-advanced/learn/lecture/32238780#overview


Django Test Framework


Based in unittest library

Django adds features

- test client - dummy webbrowser
- simulate authentication
- temporary database

Django REST framework

- API test client



Where to put tests?

tests.py OR tests/   must be test_   must contain __init_.py


Test classes

- SimpleTestCase

No db integration

- TestCase



https://www.udemy.com/course/django-python-advanced/learn/lecture/32238784#overview


writing test cases


made the calc app and the test



docker-compose run --rm app sh -c "python manage.py test"




https://www.udemy.com/course/django-python-advanced/learn/lecture/32238806#overview

Mocking

- Override or change behavior of dependencies
- Avoid unintended side effect
- Isolate code being tested


Why?

- Avoid relying on external services  e.g. sending email

- Speeds up tests.

How ?


Use unittest.mock
- MagicMock / Mock - replace real object
- Patch, overides code.  


https://www.udemy.com/course/django-python-advanced/learn/lecture/32238810#overview

Django REST framework API Client
- based on Django's TestClient
- Override authentication.



https://www.udemy.com/course/django-python-advanced/learn/lecture/32238812#overview

Common testing problems:

missing __init__.py file
Indention Failure.
missing test_ prefix.
Import Error  probably tests/ folder and tests.py




https://www.udemy.com/course/django-python-advanced/learn/lecture/32238814#overview

Database

in Docker Compose to configure.    

Defined in code, therefore is reusable.    can persist volume. (Basically maps to a local folder on host machine)
  Docker handles network.  Environment variables settings.

 


https://www.udemy.com/course/django-python-advanced/learn/lecture/32238818#overview

Configure docker-compose.yml  (use VScode, for the indendation management)


    environment:
      - DB_HOST=db
      - DB_NAME=devdb
      - DB_USER=devuser
      - DB_PASSWORD=changeme
    depends_on:
      - db

  db:
    image: postgres:13-alpine
    environment:
      POSTGRES_USER: devuser
      POSTGRES_PASSWORD: changeme
      POSTGRES_DB: devdb
    ports:
      - "5432:5432"
    volumes:
      - dev-db-data:/var/lib/postgresql/data

volumes:
  dev-db-data:










https://www.udemy.com/course/django-python-advanced/learn/lecture/32238820#overview


configure django for DB

install db adaptor dependencies

update python requirements


done in settings.py  in Django  (I knew that)


we use environment variables to do this.  Apparently this is the standard way.

os.environ.get("DB_HOST")


Psycopg2 package.  Postgres adaptor.

psycopg2-binary (not recommended for production, see source one below)

psycopg2 - compiles from source (can still be installed from PIP), apparently best to compile as it does it specifically for the machine.  Also need some dependencies to be installed, so can be a pain.


Big boy is going for source installed.

Docker best practice.  He wants to delete the gcc etc after compile.


https://www.udemy.com/course/django-python-advanced/learn/lecture/32238822#overview


in dockerfile add couple apk lines

    apk add --update --no-cache postgresql-client && \
    apk add --update --no-cache --virtual .tmp-build-deps \

and 


    apk del .tmp-build-deps && \

to clean up




https://www.udemy.com/course/django-python-advanced/learn/lecture/32238826#overview

Setup Django settings.py  use the os environment variable from docker compose yml

DATABASES = {
    'default':{
        'ENGINE': 'django.db.backends.postgresql',
        'HOST': os.environ.get('DB_HOST'),
        'NAME': os.environ.get('DB_NAME'),
        'USER': os.environ.get('DB_USER'),
        'PASSWORD': os.environ.get('DB_PASSWORD'),
        'PORT': 5432,
    }
}



https://www.udemy.com/course/django-python-advanced/learn/lecture/32238828#overview

fix the database race condition



https://www.udemy.com/course/django-python-advanced/learn/lecture/32238828#overview


Custom Django command.

Best practice since we don't know how quick postgres starts up




https://www.udemy.com/course/django-python-advanced/learn/lecture/32238842#overview


docker-compose run --rm app sh -c "python manage.py startapp core"







https://www.udemy.com/course/django-python-advanced/learn/lecture/32238844#overview

create a load of standard folders /management etc.

created a wait_for_db.py command.

But he want to do TDD, so we make a test_commands.py file

in there

from unittest.mock import patch

from psycopg2 import OperationalError as Psycopg2Error

from django.core.management import call_command
from django.db.utils import OperationalError
from django.test import SimpleTestCase


to mock the behaviour 

@patch('core.management.commands.wait_for_db.Command.check')
class Command(SimpleTestCase):
   
    def test_wait_for_db_ready(self, patched_check):
        """Test waiting for database if database is available."""
        patched_check.return_value = True

        call_command('wait_for_db')

        patched_check.assert_called_once_with(database=['default'])

    @patch('time.sleep')     #these are applied from the bottom up
    def test_wait_for_db_delay(self, patched_sleep, patched_check):
        """Test waiting for database when getting OperationalError."""
        patched_check.side_effect = [Psycopg2Error] * 2 + \
            [OperationalError] * 3 + [True]

        call_command('wait_for_db')

        self.assertEqual(patched_check.call_count, 6)
        patched_check.assert_called_with(database=['default'])


https://www.udemy.com/course/django-python-advanced/learn/lecture/32238844#overview

Now add actually wait for db command





https://www.udemy.com/course/django-python-advanced/learn/lecture/32238858#overview


docker-compose run --rm app sh -c "python manage.py test && flake8"

This was running the tests, and the linting test





https://www.udemy.com/course/django-python-advanced/learn/lecture/32238864#overview

Database Migrations

Django comes with ORM

And reminds me another reason why something like Django migrations or EF are OK for small scale developments, but once you need to have full control over the DB then you have to fall back to a data schema-first paradigm.  I'm sure there are hooks and patches that you can use on the framework side to "fix" the "auto" handlers, but ultimately when you look at the effort-required to shimy these in... you have to ask yourself... why bother,  just make the SQL/linq commands in the first place.  




They were added in two places because the Udemy course runs your Django app in two completely separate environments: locally via docker-compose.yml, and in the cloud CI pipeline via GitHub Actions. Your local docker-compose.yml defines how the app container starts on your machine, including waiting for the database and running migrations. GitHub Actions, however, does not use that startup command, so the workflow file must repeat the same logic when it runs tests in its own fresh container environment. Since each environment executes containers independently, the wait/migrate commands have to be duplicated so both local development and CI behave consistently.



https://www.udemy.com/course/django-python-advanced/learn/lecture/32238874?start=15#overview


Customise user model


Common Issue.. If you've run migration before setting custom model the you screwed (or it's harder) later.

We added a couple of tests in \core\tests\test_models.py

from django.contrib.auth import get_user_model

get_user_model is a function




https://www.udemy.com/course/django-python-advanced/learn/lecture/32238884#overview

    USERNAME_FIELD = 'email'


Changes the default from "username"


https://www.udemy.com/course/django-python-advanced/learn/lecture/32238892#overview

 raise ValueError('Users must have an email address')


Various unit tests


https://www.udemy.com/course/django-python-advanced/learn/lecture/32238894#overview

        self.assertTrue(user.is_superuser)
        self.assertTrue(user.is_staff)


Contains this

        user.save(using=self._db)

The line user.save(using=self._db) is a future-proofing technique. It ensures that your user creation logic respects the database context it was called in, making your code safe for multi-database architectures.



had a migrations issue here:

docker compose down -v

docker compose up --build

Took it down, deleting the docker volume,  and build took it back up and ran the migrations OK



https://www.udemy.com/course/django-python-advanced/learn/lecture/32238898#overview



docker-compose run --rm app sh -c "python manage.py createsuperuser"







https://www.udemy.com/course/django-python-advanced/learn/lecture/32238904#overview

Django Admin

Apparently the Djando Admin can edit DB data for each model that's enabled in admin.py

Can be customised by basing off ModelAdmin or UserAdmin




https://www.udemy.com/course/django-python-advanced/learn/lecture/32238912#overview

Unit Test for Django Admin

These are actually quite cool.  But this is deploy, not

 reverse('admin:core_user_changelist')

What was that?


  • Query Parameters and Fragments: Starting from Django 5.2, you can append query parameters and fragments:

url = reverse('admin:index', query={'q': 'search'}, fragment='results')
# Generates: '/admin/?q=search#results'




https://www.udemy.com/course/django-python-advanced/learn/lecture/32238914#overview


admin.site.register(models.User, UserAdmin)


https://www.udemy.com/course/django-python-advanced/learn/lecture/32238918#overview

https://www.udemy.com/course/django-python-advanced/learn/lecture/32238922#overview


'classes': ('wide',),


Custom css 



PUT and PATCH...  Very interesting.  Made own article for that.

https://bytebastard.blogspot.com/2025/12/http-api-methodsverbs.html


https://www.udemy.com/course/django-python-advanced/learn/lecture/32237130?start=0#overview


User API

def create_user(**params):

This is flexible to pass anything that we want.

def setUp(self):

Is supplied in unittest framework


Comments

Popular posts from this blog

Django Journey

github start project

Solr worked example with National Trees, from "Ground up" :)