[기본 프로젝트 틀 만들기]
* 폴더명 : 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' %}">← 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' %}">← 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' %}">← 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']
나중에 로직, 구조, 사용 이유를 알아봐야 할듯
'Networks > Django' 카테고리의 다른 글
SK networks AI Camp - 인증&인가 (0) | 2024.08.07 |
---|---|
SK networks AI Camp - Django 실습 (0) | 2024.08.06 |
SK networks AI Camp - Django와 MySQL 연결 (0) | 2024.08.06 |
SK networks AI Camp - Django(admin) (0) | 2024.08.06 |
SK networks AI Camp - Django 설치 (0) | 2024.08.05 |