본문 바로가기

SK네트웍스 Family AI캠프 10기/Daily 회고

76일차. Django

더보기

 

76일 차 회고.

 

 밤에 잠에 일찍 안 들다 보니까 하루 종일 피곤해서 오늘은 일찍 자야 할 것 같다. 특강을 다 들으면 9시 반이 되니까 12시 전에는 자도록 노력해봐야 할 것 같다.

 

 

 

 

1. Django

 

 

1-1. Login - Class

 

가상환경 생성

uv venv .venv -p 3.13
.\.venv\Scripts\activate

uv pip install django mysqlclient

 

Django 프로젝트 생성

django-admin startproject config .

 

Django 앱 생성

python manage.py startapp user
python manage.py startapp todolist

 

teamplates 생성

mkdir templates
mkdir .\templates\base
mkdir .\templates\user
mkdir .\templates\todolist

 

static 생성

mkdir static
mkdir .\static\css

 

코드 작성

  • TEMPLATE & STATIC 기존 코드 사용
  • MODEL
# user/models.py

from django.db import models
# AbstractBaseUser: A base class for creating custom user models.
# BaseUserManager: A helper class for creating user managers for custom user models.
# PermissionsMixin: A mixin that provides the permission-related fields and methods.
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin

class CustomUserManager(BaseUserManager):
    use_in_migrations = True

    # Create user
    def create_user(self, username, email=None, password=None):
        if not email:
            raise ValueError('The Email field must be set')

        user = self.model(
            username=username,
            email=self.normalize_email(email),
        )
        user.set_password(password)
        user.save(using=self._db)

        return user
    
    # Create superuser
    def create_superuser(self, username, email=None, password=None):
        user = self.create_user(
            username=username,
            email=email,
            password=password,
        )
        user.is_admin = True
        user.is_superuser=True
        user.save(using=self._db)

        return user
    
class CustomUser(AbstractBaseUser, PermissionsMixin):
    objects = CustomUserManager()

    username = models.CharField(max_length=30, unique=True, null=False)
    email = models.EmailField(max_length=200, unique=True, null=False)
    is_superuser = models.BooleanField(default=False)
    is_admin = models.BooleanField(default=False)
    is_staff = models.BooleanField(default=False)
    is_active = models.BooleanField(default=True)
    created_dt = models.DateTimeField(auto_now_add=True)
    updated_dt = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = 'custom_user'
    
    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = ['email']
# todolist/models.py

from django.db import models
from user.models import CustomUser

class TodoList(models.Model):
    user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
    title = models.CharField(max_length=200)
    status = models.BooleanField(default=False)
    created_dt = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.title
    
    class Meta:
        db_table = 'todo_list'
        ordering = ['status', '-created_dt']
  • VIEW
# user/views.py

from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, login, logout
from django.contrib import messages
from .models import CustomUser

def user_register(request):
    if request.user.is_authenticated:
        return redirect(to='todolist')
    elif request.method == 'POST':
        username = request.POST.get('username').strip()
        email = request.POST.get('email').strip()
        password = request.POST.get('password').strip()

        if CustomUser.objects.filter(username=username).exists():
            messages.error(request, 'Username already exists')
            return redirect(to='user-register')
        elif not username or not email or not password:
            messages.error(request, 'Email and password are required')
            return redirect(to='user-register')
        
        new_user = CustomUser.objects.create_user(
            username=username,
            email=email,
            password=password
        )
        new_user.save()
        
        return redirect(to='user-login')

    return render(request, 'user/register.html')

def user_login(request):
    if request.user.is_authenticated:
        return redirect(to='todolist')
    elif request.method == 'POST':
        username = request.POST.get('username').strip()
        password = request.POST.get('password').strip()

        auth_user = authenticate(username=username, password=password)
        if not auth_user:
            messages.error(request, 'Invalid username or password')
            return redirect(to='user-login')
        
        login(request, auth_user)
        return redirect(to='todolist')

    return render(request, 'user/login.html')

def user_logout(request):
    logout(request)
    return redirect(to='user-login')
# todolist/views.py

from django.shortcuts import render, redirect
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from .models import TodoList

@login_required(login_url='user-login')
def todolist(request):
    if request.method == 'POST':
        task = request.POST.get('task').strip()

        if TodoList.objects.filter(title=task, user=request.user).exists():
            messages.error(request=request, message='Task already exists')
            return redirect(to='todolist')
        elif not len(task):
            messages.error(request=request, message='Task cannot be empty')
            return redirect(to='todolist')

        new_task = TodoList(title=task, user=request.user)
        new_task.save()

    content = {
        "todo_list": TodoList.objects.filter(user=request.user).order_by('status'),
    }
    return render(request, 'todolist/todolist.html', content)

@login_required(login_url='user-login')
def update_task(request, task_id):
    task = TodoList.objects.get(id=task_id, user=request.user)
    task.status = True
    task.save()

    return redirect(to='todolist')

@login_required(login_url='user-login')
def delete_task(request, task_id):
    TodoList.objects.get(id=task_id, user=request.user).delete()

    return redirect(to='todolist')
  • URL
# user/urls.py

from django.urls import path
from .views import user_register, user_login, user_logout

urlpatterns = [
    path('register/', user_register, name='user-register'),
    path('login/', user_login, name='user-login'),
    path('logout/', user_logout, name='user-logout'),
]
# todolist/urls.py

from django.urls import path
from .views import todolist, update_task, delete_task

urlpatterns = [
    path('', todolist, name='todolist'),
    path('todolist-update/<int:task_id>', update_task, name='todolist-update'),
    path('todolist-delete/<int:task_id>', delete_task, name='todolist-delete'),
]
# config/urls.py

from django.urls import path, include

urlpatterns = [
    path('user/', include('user.urls')),
    path('', include('todolist.urls')),
]
  • SETTING
# config/settings.py

import os
from pathlib import Path

...

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'user',
    'todolist',
]

...

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': ['templates'],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

...

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': '',
        'USER': '',
        'PASSWORD': '',
        'HOST': 'localhost',
        'PORT': '3306',
    }
}

...

STATIC_URL = 'static/'
STATIC_PATH = os.path.join(BASE_DIR, 'static')
STATICFILES_DIRS = (STATIC_PATH,)

...

AUTH_USER_MODEL = 'user.CustomUser'

 

서버 실행

python manage.py makemigrations
python manage.py migrate
python manage.py runserver

 

 

1-2. Docker

 

Django

  • 아래의 수정된 코드를 제외하고는 위의 코드를 사용한다.
# django_server/requirements.txt

django
gunicorn
# django_server/config/settings.py

...

SECRET_KEY = os.getenv('SECRET_KEY')

DEBUG = os.getenv('DEBUG')

ALLOWED_HOSTS = ['*']

CSRF_TRUSTED_ORIGINS = [
    'http://localhost',
    'http://127.0.0.1',
    'http://localhost:8000',
    'http://127.0.0.1:8000',
    'http://0.0.0.0',
    'http://0.0.0.0:8000',
]

...

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.sqlite3",
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

...

STATIC_ROOT = '/static/'
STATIC_URL = 'static/'
STATIC_PATH = os.path.join(BASE_DIR, 'static')
STATICFILES_DIRS = (STATIC_PATH,)

...

 

Nginx

  • Configure
# nginx_server/default.conf

upstream django {
    server django_gunicorn:8000;
}

server {
    listen 80;
    location / {
        proxy_pass http://django;
    }
    location /static/ {
        alias /static/;
    }
}
  • Dockerfile
# nginx_server/Dockerfile

FROM nginx

COPY ./default.conf /etc/nginx/conf.d/default.conf

 

Docker

  • Environment
SECRET_KEY=
DEBUG=False

SUPER_USER_NAME=
SUPER_USER_PASSWORD=
SUPER_USER_EMAIL=
  • Dockerfile
# Dockerfile

FROM python:3.13-alpine

COPY ./django_server /app
COPY ./entrypoint.sh /app/entrypoint.sh

WORKDIR /app

RUN pip3 install --upgrade pip && pip3 install -r /app/requirements.txt

ENTRYPOINT [ "sh", "/app/entrypoint.sh" ]
  • Shell
# entrypoint.sh

#!/bin/sh

# models.py
python manage.py makemigrations --no-input
# apply migration
python manage.py migrate --no-input
# collect static file -> Nginx(WEB server)
python manage.py collectstatic --no-input

DJANGO_SUPERUSER_PASSWORD=$SUPER_USER_PASSWORD python manage.py createsuperuser --username $SUPER_USER_NAME --email $SUPER_USER_EMAIL --noinput

# gunicorn(WAS server)
gunicorn config.wsgi:application --bind 0.0.0.0:8000
  • Docker Compose
# docker-compose.yml

version: '3.7'

services:
    django_gunicorn:
        container_name: django_gunicorn
        volumes:
            - static:/static
        env_file:
            - .env
        build:
            context: .
        ports:
            - '8000:8000'
        networks:
            - compose_net

    nginx:
        container_name: nginx
        build: ./nginx_server
        volumes:
            - static:/static
        ports:
            - '80:80'
        depends_on:
            - django_gunicorn
        networks:
            - compose_net

volumes:
    static:

networks:
    compose_net:
        name: django_net
        driver: bridge

 

Docker 실행

docker-compose up -d

 

 

'SK네트웍스 Family AI캠프 10기 > Daily 회고' 카테고리의 다른 글

78일차. AWS - EC2  (1) 2025.05.02
77일차. AWS - IAM & EC2  (0) 2025.04.30
75일차. Django  (0) 2025.04.28
74일차. Django  (0) 2025.04.25
73일차. Django  (0) 2025.04.24