Networks/Django

SK networks AI Camp - Django & user 실습

코딩하는 Español되기 2024. 8. 8. 18:00

[기본 프로젝트 틀 만들기]

* 폴더명 : dev/django/django_user

2024.08.06 - [Networks/Django] - SK networks AI Camp - Django와 MySQL 연결

 

SK networks AI Camp - Django와 MySQL 연결

○ DB생성CMD(| Powershell)에 아래 명령어 입력* mysql 폴더에 아래 파일 있어야함cd ../../dev/mysql ○ 명령어로 docker-compose.yml 실행docker-compose up -d ○ DBeaver에 연결localhost에서 urstory 계정 생성 → test conn

joowon582.tistory.com

 

(+ 추가)

더보기

todolist

 1. model.py

from django.db import models
from user.models import Custom 

# Create your models here.
class Task(models.Model):
    custom = models.ForeignKey(Custom, on_delete=models.CASCADE)
    title = models.CharField(max_length=200)
    description = models.TextField(null=True, blank=True)
    complete = models.BooleanField(default=False)
    created = models.DateTimeField(auto_now_add=True)

    def __str__(self) -> str:
        return self.title 
    
    class Meta:
        ordering = ['complete', '-created']

 2. urls.py

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

urlpatterns = [
    path("", todo_list, name="todo-list"),
    path("delete-task/<str:task>", delete_task, name="delete-task"),
    path("update-task/<str:task>", update_task, name="update-task"),
]

 3. views.py

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

from .models import Task
from .forms import TaskForm

# Create your views here.
@login_required
def task_list(request):
    template_name = 'todoList/task_list.html' 
    context = {}

    search_input = request.GET.get('search-area') or ''
    if search_input:
        context['taskList'] = Task.objects.filter(custom=request.user, title__startswith=search_input)
    else:
        context['taskList'] = Task.objects.filter(custom=request.user)

    context['search_input'] = search_input
    context['cnt'] = Task.objects.filter(custom=request.user, complete=False).count()

    return render(request, template_name, context)

@login_required
def task_detail(request, pk):
    template_name = 'todoList/task_detail.html'
    get_task = Task.objects.get(id=pk, custom=request.user)
    context={
        'form':TaskForm(instance=get_task),
        'task':get_task
    }

    return render(request, template_name, context) 

@login_required
def task_create(request):
    if request.method == "POST":
        form = TaskForm(request.POST)
        if form.is_valid():
            task = form.save(commit=False)
            task.custom = request.user 
            task.save()
            return redirect('task-list')
    
    # GET method
    template_name = 'todoList/task_form.html'
    context={'form':TaskForm()}
    return render(request, template_name, context) 

@login_required
def task_update(request, pk):
    get_task = Task.objects.get(id=pk, custom=request.user)

    if request.method == "POST":
        form = TaskForm(request.POST, instance=get_task)
        if form.is_valid():
            task = form.save(commit=False)
            task.id = pk
            task.save()
            return redirect('task-list')
    
    # GET method
    template_name = 'todoList/task_form.html'
    context={
        'form':TaskForm(instance=get_task),
        'task':get_task
    }
    return render(request, template_name, context) 

@login_required
def task_delete(request, pk):
    get_task = Task.objects.get(id=pk, custom=request.user)
    if request.method == "POST":
        get_task.delete()
        return redirect('task-list')
    
    # GET method
    template_name = 'todoList/task_delete.html'
    context={
        'task':get_task
    }
    return render(request, template_name, context)

 

templates/todolist

 1. task_list.html

<head>
    <script src="//d.bablic.com/snippet/6288d4c3c4c5800001a91242.js?version=3.9"></script>
</head>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.min.js" integrity="sha384-QJHtvGhmr9XOIpI6YVutG+2QOK9T+ZnN4kzFN1RtK3zEFEIsxhlmWl5/YESvpZ13" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script>

<section class="vh-100" style="background-color: #eee;">
    <div class="container py-5 h-100">
        <div class="row d-flex justify-content-center align-items-center h-100">
        <div class="col col-lg-9 col-xl-7">
            <div class="card rounded-3">
            <div class="card-body p-4">
                {% if messages %}
                {% for message in messages %}
                    <h4 style="color: #b22222;">{{message}}</h4>
                {% endfor %}
                {% endif %}

                <h4 class="text-center my-3 pb-3">To Do App</h4>
                <form method="POST" class="row row-cols-lg-auto g-3 justify-content-center align-items-center mb-4 pb-2">
                {% csrf_token %}
                    <div class="col-12">
                        <div class="form-outline">
                        <input type="text" id="form1" class="form-control" name="task" placeholder="Enter a task here"/>
                        </div>
                    </div>

                    <div class="col-12">
                        <button type="submit" class="btn btn-primary">Add Task</button>
                    </div>
                </form>
                <table class="table mb-4">
                    <thead>
                        <tr>
                        <th scope="col">Todo item</th>
                        <th scope="col">Status</th>
                        <th scope="col">Actions</th>
                        </tr>
                    </thead>
                    <tbody>
                        {% for i in todos %}
                        <tr>
                        <td>{{i.todo_name}}</td>

                        {% if i.status == True %}
                            <td>Completed</td>
                        {% else %}
                            <td>In progress</td>
                        {% endif %}

                        <td>
                            <a href="{% url 'delete-task' i.todo_name %}"><button type="submit" class="btn btn-danger">Delete</button></a>
                            <a href="{% url 'update-task' i.todo_name %}"><button type="submit" class="btn btn-success ms-1">Finished</button></a>
                        </td>
                        </tr>
                        {% endfor %}
                    </tbody>
                </table>
            </div>
            </div>
        </div>
        </div>
    </div>
</section>

 2. task_form.html

{% extends 'base/index.html' %}
{% block content %}

<div class="header-bar">
    <a href="{% url 'task-list' %}">&#8592; Back</a>
</div>


<div class="card-body">
    <form method="POST" action="">
        {% csrf_token %}
        {{form.as_p}}
        <input class="button" type="submit" value="Submit">
    </form>
</div>

{% endblock content %}

 3. task_detail.html

{% extends 'base/index.html' %} 
{% block content %}
{% load static %}
<script type="text/javascript" src="{% static 'js/task_detail.js' %}"></script>

<div class="header-bar">
    <a href="{% url 'task-list' %}">&#8592; Back</a>
</div>

<div class="card-body">
    <form method="POST" action="">
        {% csrf_token %}
        {{form.as_p}}
    </form>
    <div class="button-wrapper">
        <a class="button txt-center" href="{% url 'task-update' task.id %}">Update</a>
        <a class="button txt-center" href="{% url 'task-delete' task.id %}">Delete</a>
    </div>
</div>

{% endblock content %}

 4. task_delete.html

{% extends 'base/index.html' %}
{% block content %}

<div class="header-bar">
    <a href="{% url 'task-list' %}">&#8592; Go Back</a>
</div>

<div class="card-body">
    <form method="POST">
        {% csrf_token %}
        <p>Are your sure you want to delete this task? <b>"{{task}}"</b> </p>
        <input class="button" type="submit" value="Delete" />
    </form>
</div>

{% endblock content %}

 templates/base/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="description"
        content="This is to do list implemented using Django by Dennis Ivy who is a full stack web developer.">

    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>To Do Items</title>
    <meta name="description" content="">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <link rel="preconnect" href="https://fonts.gstatic.com">
    <link href="https://fonts.googleapis.com/css2?family=Nunito:wght@200&display=swap" rel="stylesheet">
    {% load static %}
    <link rel="stylesheet" href="{% static 'css/index.css' %}">

</head>
<body>
    <div class="container">
        {% block content %} {% endblock %}
    </div>
</body>
</html>

static/css

 - index.css

body {
    background-color: #FAFAFA;
    font-family: 'Nunito', sans-serif;
    padding-top: 50px;
}

h1,
h2,
h3,
h4,
h5,
h6 {
    font-family: 'Raleway', sans-serif;
}

a,
p {
    color: #4b5156
}

a {
    font-weight: 600;
}

.container {
    max-width: 550px;
    margin: auto;
    background-color: #fff;
    -webkit-box-shadow: 2px 2px 13px -4px rgba(0, 0, 0, 0.21);
    box-shadow: 2px 2px 13px -4px rgba(0, 0, 0, 0.21);
}

input {
    outline: none;
    border: none;
}

.header-bar {
    display: flex;
    justify-content: space-between;
    color: #fff;
    padding: 10px;
    border-radius: 5px 5px 0 0;
    background: linear-gradient(90deg, #EEA390 0%, #EB796F 43%, #EB796F 100%);
}

.header-bar a {
    color: rgb(247, 247, 247);
    text-decoration: none;
}

.task-wrapper {
    display: flex;
    align-items: center;
    justify-content: space-between;
    background-color: #fff;
    border-top: 1px solid #dfe4ea;
    overflow: hidden;
}

.task-title {
    display: flex;
    padding: 20px;
}

.task-title a {
    text-decoration: none;
    color: #4b5156;
    margin-left: 10px;
}

.task-complete-icon {
    height: 20px;
    width: 20px;
    background-color: rgb(105, 192, 105);
    border-radius: 50%;
}

.task-incomplete-icon {
    height: 20px;
    width: 20px;
    background-color: rgb(218, 218, 218);
    border-radius: 50%;
}

.delete-link {
    text-decoration: none;
    font-weight: 900;
    color: #cf4949;
    font-size: 22px;
    line-height: 0;
    padding: 20px 0px;
}

/*Handle Classes*/

.handle {
    display: inline-block;
    padding: 20px 16px;
    cursor: grab;
    user-select: none;
}

.handle:after,
.handle:before {
    display: block;
    content: "";
}

.handle:active,
.handle:active:before,
.handle:active:after {
    cursor: grabbing;
}

.dropArea {
    background-color: #f1f2f6;
    color: black;
    border: #ced6e0 1px solid;
}

.selectedTask {
    opacity: 0.6;
}


#add-link {
    color: #EB796F;
    text-decoration: none;
    font-size: 42px;
    text-shadow: 1px 1px #81413b;
}

#search-add-wrapper {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 20px;
}

@media screen and (max-width:320px) {
    #search-add-wrapper {
        padding: 10px;
    }
}

input[type=text],
input[type=password],
input[type=email],
textarea {
    border: 1px solid #757575;
    border-radius: 5px;
    padding: 10px;
    width: 90%;
}

label {
    padding-top: 10px !important;
    display: block;
}

::placeholder {
    font-weight: 300;
    opacity: 0.5;
}

.button {
    border: 1px solid #757575;
    background-color: #FFF;
    color: #EB796F;
    padding: 10px;
    font-size: 14px;
    border-radius: 5px;
    cursor: pointer;
    text-decoration: none;
}

.card-body {
    padding: 20px;
}

.errorMessage {
    color: blueviolet;
}

.button-wrapper {
    display : flex; 
    justify-content: space-evenly;
}

.txt-center {
    width: 100px;
    text-align: center;
}

 - task_detail.js

window.onload = function() {
    let f = document.getElementsByTagName("form")[0];
    for(let i=0,fLen=f.length;i<fLen;i++){
        f.elements[i].readOnly = true; 
        if(f.elements[i].type == "checkbox") {
            f.elements[i].onclick =  function checkClickFunc() {
                return false;
            }
        }
    }
}

 

 

 config

 1. settings.py

     - TEMPLATES, INSTALLED_APPS에 추가

     - static 경로

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.mysql",
        "NAME": "examplesdb", # 데이터베이스 이름 
        "USER": "urstory", 
        "PASSWORD": "u1234",
        "HOST": "localhost",
        "PORT": "3306"
    }
}

     - mysql 부분 수정

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.mysql",
        "NAME": "examplesdb", # 데이터베이스 이름 
        "USER": "urstory", 
        "PASSWORD": "u1234",
        "HOST": "localhost",
        "PORT": "3306"
    }
}

 

 2. urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("", include("todolist.urls")),
]

user app 생성 및 templates/user 생성

python manage.py startapp user
mkdir -p .\templates\user
더보기

1. login.html

{% extends 'base/index.html' %}
{% block content %}

<div class="header-bar">
    <h1>Login</h1>
</div>

<div class="card-body">
    {% if messages %}
        {% for message in messages %}
            <p style="color: #b22222;">{{message}}</p>
        {% endfor %}
    {% endif %}
    <form method="POST">
        {% csrf_token %}
        {{form.as_p}}
        <input class="button" type="submit" value="Login">
    </form>
    <p>Don't have an account? <a href="{% url 'register' %}">Register</a></p>
</div>

{% endblock %}

2. register.html

{% extends 'base/index.html' %}
{% block content %}

<div class="header-bar">
    <h1>Register</h1>
</div>

<div class="card-body">
    {% if messages %}
        {% for message in messages %}
            <p style="color: #b22222;">{{message}}</p>
        {% endfor %}
    {% endif %}
    <form method="POST">
        {% csrf_token %}
        {% for field in form %} 
            <label>{{field.label}}</label>
            {{field}}
            {% if field.errors %}
            <ul>
                {% for error in field.errors %} 
                <li class="errorMessage">{{ error }}</li>
                {% endfor %}
            </ul>
            {% endif %}
        {% endfor %}
        <input style="margin-top:10px ;" class="button" type="submit" value="Register">
    </form>
    <p>Already have an account? <a href="{% url 'login' %}">Login</a></p>
</div>

{% endblock content %}

추가해줬으니 수정해야하는 것은?

urls.py, views.py, models.py, config/urls.py, config/settings.py

 

더보기

urls.py

from django.urls import path
from django.contrib.auth.views import LogoutView

from .views import user_login, user_register

urlpatterns = [
    path('login/', user_login, name='login'),
    path('register/', user_register, name='register'),
    path('logout/', LogoutView.as_view(next_page='login'), name='logout'),
]

 views.py

from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, login
from django.contrib import messages
from .forms import CustomForm, CustomCreationForm

# Create your views here.
def user_login(request):
    if request.user.is_authenticated:
        return redirect("task-list")
    elif request.method == 'POST':
        name = request.POST.get("name")
        pwd = request.POST.get("password")
        validate_user = authenticate(name=name, password=pwd)
        if validate_user is not None:
            login(request, validate_user)
            return redirect('task-list')
        else:
            messages.info(request, 'Try again! username or password is incorrect')
            return redirect("login")

    template_name = 'user/login.html' 
    context={'form':CustomForm()}
    return render(request, template_name, context)  

def user_register(request):
    if request.user.is_authenticated:
        return redirect("task-list")
    elif request.method == "POST":
        form = CustomCreationForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect('login') 
        else:
            # messages.info(request, 'Try again!')
            messages.error(request, form.errors)
            return redirect("register")
    
    template_name = 'user/register.html'
    context={'form':CustomCreationForm()}
    return render(request, template_name, context)

 

models.py

from django.contrib.auth.models import AbstractBaseUser,BaseUserManager,PermissionsMixin
from django.db import models 

# https://hckcksrl.medium.com/django-%EC%BB%A4%EC%8A%A4%ED%85%80-%EC%9C%A0%EC%A0%80-%EB%AA%A8%EB%8D%B8-custom-user-model-b8487c0d150
# https://www.coninggu.com/8
# https://blaize.tistory.com/89

# 유저를 생성할 때, 사용하는 클래스
class CustomManager(BaseUserManager):    
    
    use_in_migrations = True    
    
    def create_user(self, name, password=None, email=None):        
        
        if not email :            
            raise ValueError('must have user email')        
        user = self.model(            
            email = self.normalize_email(email),            
            name = name        
        )        
        user.set_password(password)        
        user.save(using=self._db)        
        return user     
    def create_superuser(self, name, password, email=None ):        
    
        user = self.create_user(            
            email = self.normalize_email(email),            
            name = name,            
            password=password        
        )        
        user.is_admin = True        
        user.is_superuser = True        
        user.save(using=self._db)        
        return user 


# 유저 모델(테이블, 객체)
class Custom(AbstractBaseUser,PermissionsMixin):  
    objects = CustomManager()

    email = models.EmailField(
        max_length=255, 
        unique=True, null=False
    )
    name = models.CharField(
        max_length=20, 
        unique=True, null=False
    ) 
    is_superuser = models.BooleanField(default=False)    
    s_admin = models.BooleanField(default=False)
    is_active = models.BooleanField(default=True)
    created_on = models.DateTimeField(auto_now_add=True)
    updated_on = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = 'custom'

    USERNAME_FIELD = 'name'
    REQUIRED_FIELDS = ['email']

 

config/urls.py

"""
URL configuration for config project.

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/4.2/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("", include("todolist.urls")),
    path("user/", include("user.urls")),
]

 

config/settings.py

"""
Django settings for config project.

Generated by 'django-admin startproject' using Django 4.2.1.

For more information on this file, see
https://docs.djangoproject.com/en/4.2/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.2/ref/settings/
"""

import os
from pathlib import Path
import pymysql 
pymysql.install_as_MySQLdb()

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = "django-insecure-j7c89zdrgqacr)mqh8abo3@ylez68^9)$q_rr4(!&hcvkes5*s"

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "todolist",
    "user"
]

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",
]

ROOT_URLCONF = "config.urls"

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

WSGI_APPLICATION = "config.wsgi.application"


# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.mysql",
        "NAME": "django_db", # 데이터베이스 이름 
        "USER": "django_root", # 유저 아이디
        "PASSWORD": "django_root1!", # 유저 비번
        "HOST": "localhost", # host 주소
        "PORT": "3306" # port 번호 
    }
}


# Password validation
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
    },
    {
        "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
    },
    {
        "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
    },
    {
        "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
    },
]


# Internationalization
# https://docs.djangoproject.com/en/4.2/topics/i18n/

LANGUAGE_CODE = "en-us"

TIME_ZONE = "UTC"

USE_I18N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.2/howto/static-files/

STATIC_URL = "static/"
STATIC_PATH = os.path.join(
    BASE_DIR, "static"
)  # concatena a pasta static a variavel instanciada base_dir que aponta para a raiz do projeto

STATICFILES_DIRS = (STATIC_PATH,)

# Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

user/forms.py 추가

from django import forms
from django.contrib.auth.forms import UserCreationForm

from .models import Custom 

class CustomForm(forms.ModelForm):
    class Meta:
        model = Custom  # 사용할 모델
        fields = ['name', 'password']
        widgets = {
            'password': forms.PasswordInput(),
        }

class CustomCreationForm(UserCreationForm):
    email=forms.EmailField()

    class Meta:
        model = Custom
        fields = ('name', 'email', 'password1', 'password2')

 

todolist/forms.py 추가

from django import forms
from .models import Task


class TaskForm(forms.ModelForm):
    class Meta:
        model = Task  # 사용할 모델
        fields = ['title', 'description', 'complete']

 

나중에 로직, 구조, 사용 이유를 알아봐야 할듯