Developer's Development
3.4.7 [Django] Model-DB 연동 본문
Django 모델의 개념
Django 모델은 데이터베이스 구조(테이블)을 정의하는 역할을 한다.
Python 클래스를 사용하여 정의하며, Django의 ORM(Object-Relational Mapping)을 통해 데이터베이스와 상호작용한다.
- Django ORM의 특징
SQL 쿼리를 작성하지 않아도 객체 지향적으로 데이터베이스 작업이 가능하다.
데이터베이스 독립성을 제공하여 여러 DBMS를 지원한다.
모델을 통해 데이터의 유효성 검사 및 제약 조건을 설정할 수 있다.
- ORM으로 데이터 조작
1. 데이터 추가
1) create() 메서드
- 매개변수: 필드 이름과 값을 키워드 인수로 전달
- 반환값: 생성된 객체
2) 객체 생성 후 저장
- 객체를 생성한 후 .save() 메서드 호출로 데이터베이스에 저장한다.
2. 데이터 조회
1) all() 메서드로 모든 데이터 조회
- 반환값: 쿼리셋(QuerySet), 데이터베이스에서 검색된 객체의 집합
2) filter() 메서드를 이용한 필터링
- 반환값: 조건에 맞는 객체들의 쿼리셋
3) get() 메서드를 이용한 단일 객체 조회
- 반환값: 조건에 맞는 단일 객체 (조건이 충족되지 않으면 예외 발생)
4) order_by() 메서드로 정렬
- 반환값: 정렬된 쿼리셋
3. 데이터 수정
1) 객체를 가져와 수정
- 특정 객체를 조회한 후 필드를 변경하고 .save() 메서드를 호출한다.
4. 데이터 삭제
1) 객체 삭제
- 특정 객체를 조회한 후 .delete() 메서드를 호출해 삭제한다.
👉🏻 프로젝트 및 앱 생성
django-admin startproject _03_django_orm
cd _03_django_orm
django-admin startapp post
pip install mysqlclient
👉🏻 MySQL Workbench
root 계정에서 스키마 생성
create user 'django'@'%' identified by 'django';
create database djangodb character set utf8mb4 collate utf8mb4_unicode_ci;
grant all privileges on djangodb.* to 'django'@'%';
👉🏻 settings.py 수정
앱 등록 및 데이터베이스 설정
# 수정
INSTALLED_APPS = [
'django.contrib.admin',
...
'post' # 방금 만들어준 앱 등록
]
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'djangodb',
'USER': 'django',
'PASSWORD': 'django',
'HOST': '127.0.0.1',
'PORT': '3306',
'OPTIONS': {
'charset': 'utf8mb4'
}
}
}
# 추가
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'class': 'logging.StreamHandler'
}
},
'loggers': {
'django.db.backends': {
'level': 'DEBUG',
'handlers': ['console']
}
}
}
# 수정
LANGUAGE_CODE = 'ko-kr'
TIME_ZONE = 'Asia/Seoul'
👉🏻 모델 정의
post/models.py 수정
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
👉🏻 데이터베이스 반영
마이그레이션 파일 생성 및 데이터베이스에 적용
python manage.py makemigrations
python manage.py migrate
👉🏻 post/migrations/0001_initial.py
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Post',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=100)),
('content', models.TextField()),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
),
]
👉🏻 Django Shell을 활용한 데이터베이스 CRUD 확인
아래 코드 맨 위 코드 두 줄이랑 >>> 부분 shell 명령어 입력
Shell 종료: Ctrl+Z → Enter
cd _03_django_orm
python manage.py shell
7 objects imported automatically (use -v 2 for details).
Ctrl click to launch VS Code Native REPL
Python 3.12.11 | packaged by Anaconda, Inc. | (main, Jun 5 2025, 12:58:53) [MSC v.1929 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from post.models import Post
>>> Post
<class 'post.models.Post'>
>>> Post.objects
<django.db.models.manager.Manager object at 0x0000015C6F68A330>
>>> Post.objects.all()
(0.000)
SELECT VERSION(),
@@sql_mode,
@@default_storage_engine,
@@sql_auto_is_null,
@@lower_case_table_names,
CONVERT_TZ('2001-01-01 01:00:00', 'UTC', 'UTC') IS NOT NULL
; args=None; alias=default
(0.000) SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; args=None; alias=default
(0.000) SELECT `post_post`.`id`, `post_post`.`title`, `post_post`.`content`, `post_post`.`created_at`, `post_post`.`updated_at` FROM `post_post` LIMIT 21; args=(); alias=default
<QuerySet []>
>>> post = Post.objects.create(title='Hello World', content='hello everyone 좋은 아침이에요~')
(0.016) INSERT INTO `post_post` (`title`, `content`, `created_at`, `updated_at`) VALUES ('Hello World', 'hello everyone 좋은 아침이에요~', '2025-10-16 00:21:36.902041', '2025-10-16 00:21:36.902041'); args=['Hello World', 'hello everyone 좋은 아침이에요~', '2025-10-16 00:21:36.902041', '2025-10-16 00:21:36.902041']; alias=default
>>> post = Post.objects.create(title='Hello World', content='hello everyone 좋은 아침이에요~')
(0.016) INSERT INTO `post_post` (`title`, `content`, `created_at`, `updated_at`) VALUES ('Hello World', 'hello everyone 좋은 아침이에요~', '2025-10-16 00:21:36.902041', '2025-10-16 00:21:36.902041'); args=['Hello World', 'hello everyone 좋은 아침이에요~', '2025-10-16 00:21:36.902041', '2025-10-16 00:21:36.902041']; alias=default
>>> post.title
'Hello World'
>>> post.content
'hello everyone 좋은 아침이에요~'
>>> post.created_at
datetime.datetime(2025, 10, 16, 0, 21, 36, 902041, tzinfo=datetime.timezone.utc)
>>> post.updated_at
datetime.datetime(2025, 10, 16, 0, 21, 36, 902041, tzinfo=datetime.timezone.utc)
>>> post.id
1
>>> Post.objects.create(title='this is 10', content='벌써 10월이네요...')
(0.015) INSERT INTO `post_post` (`title`, `content`, `created_at`, `updated_at`) VALUES ('this is 10', '벌써 10월이 네요...', '2025-10-16 00:26:19.990654', '2025-10-16 00:26:19.990654'); args=['this is 10', '벌써 10월이네요...', '2025-10-16 00:26:19.990654', '2025-10-16 00:26:19.990654']; alias=default
<Post: this is 10>
>>> Post.objects.create(title='오늘 점심 뭐 먹지?', content='기가 막힌 점심 메뉴 추천 받습니다🤗')
(0.000) INSERT INTO `post_post` (`title`, `content`, `created_at`, `updated_at`) VALUES ('오늘 점심 뭐 먹지?', '기가 막힌 점심 메뉴 추천 받습니다🤗', '2025-10-16 00:27:13.575977', '2025-10-16 00:27:13.575977'); args=['오늘 점심 뭐 먹지?', '기가 막힌 점심 메뉴 추천 받습니다🤗', '2025-10-16 00:27:13.575977', '2025-10-16 00:27:13.575977']; alias=default
<Post: 오늘 점심 뭐 먹지?>
>>> post2 = Post(title='가을이네요', content='환절기 감기 조심하세요~!!!')
>>> post2.save()
(0.032) INSERT INTO `post_post` (`title`, `content`, `created_at`, `updated_at`) VALUES ('가을이네요', '환절기 감기 조심하세요~!!!', '2025-10-16 00:31:25.929084', '2025-10-16 00:31:25.929084'); args=['가을이네요', '환절기 감기 조심 하세요~!!!', '2025-10-16 00:31:25.929084', '2025-10-16 00:31:25.929084']; alias=default
>>> queryset = Post.objects.all()
>>> queryset
(0.000) SELECT `post_post`.`id`, `post_post`.`title`, `post_post`.`content`, `post_post`.`created_at`, `post_post`.`updated_at` FROM `post_post` LIMIT 21; args=(); alias=default
<QuerySet [<Post: Hello World>, <Post: this is 10>, <Post: 오늘 점심 뭐 먹지?>, <Post: 가을이네요>]>
>>> queryset.query
<django.db.models.sql.query.Query object at 0x0000015C6BB737D0>
>>> str(queryset.query)
'SELECT `post_post`.`id`, `post_post`.`title`, `post_post`.`content`, `post_post`.`created_at`, `post_post`.`updated_at` FROM `post_post`'
>>> import sqlparse
>>> print(sqlparse.format(str(queryset.query), reindent=True))
SELECT `post_post`.`id`,
`post_post`.`title`,
`post_post`.`content`,
`post_post`.`created_at`,
`post_post`.`updated_at`
FROM `post_post`
>>> Post.objects.filter(title='Hello World')
(0.000) SELECT `post_post`.`id`, `post_post`.`title`, `post_post`.`content`, `post_post`.`created_at`, `post_post`.`updated_at` FROM `post_post` WHERE `post_post`.`title` = 'Hello World' LIMIT 21; args=('Hello World',); alias=default
<QuerySet [<Post: Hello World>]>
>>> Post.objects.filter(created_at__year=2025)
(0.000) SELECT `post_post`.`id`, `post_post`.`title`, `post_post`.`content`, `post_post`.`created_at`, `post_post`.`updated_at` FROM `post_post` WHERE `post_post`.`created_at` BETWEEN '2024-12-31 15:00:00' AND '2025-12-31 14:59:59.999999' LIMIT 21; args=('2024-12-31 15:00:00', '2025-12-31 14:59:59.999999'); alias=default
<QuerySet [<Post: Hello World>, <Post: this is 10>, <Post: 오늘 점심 뭐 먹지?>, <Post: 가을이네요>]>
>>> Post.objects.all().order_by('title')
(0.000) SELECT `post_post`.`id`, `post_post`.`title`, `post_post`.`content`, `post_post`.`created_at`, `post_post`.`updated_at` FROM `post_post` ORDER BY `post_post`.`title` ASC LIMIT 21; args=(); alias=default
<QuerySet [<Post: Hello World>, <Post: this is 10>, <Post: 가을이네요>, <Post: 오늘 점심 뭐 먹지?>]>
>>> Post.objects.get(id=1)
(0.000) SELECT `post_post`.`id`, `post_post`.`title`, `post_post`.`content`, `post_post`.`created_at`, `post_post`.`updated_at` FROM `post_post` WHERE `post_post`.`id` = 1 LIMIT 21; args=(1,); alias=default
<Post: Hello World>
>>> Post.objects.get(id=100)
(0.000) SELECT `post_post`.`id`, `post_post`.`title`, `post_post`.`content`, `post_post`.`created_at`, `post_post`.`updated_at` FROM `post_post` WHERE `post_post`.`id` = 100 LIMIT 21; args=(100,); alias=default
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "C:\Users\Playdata\AppData\Local\anaconda3\envs\django_env\Lib\site-packages\django\db\models\manager.py", line 87, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\Playdata\AppData\Local\anaconda3\envs\django_env\Lib\site-packages\django\db\models\query.py", line 633, in get
raise self.model.DoesNotExist(
post.models.Post.DoesNotExist: Post matching query does not exist.
>>> post = Post.objects.get(id=1)
(0.000) SELECT `post_post`.`id`, `post_post`.`title`, `post_post`.`content`, `post_post`.`created_at`, `post_post`.`updated_at` FROM `post_post` WHERE `post_post`.`id` = 1 LIMIT 21; args=(1,); alias=default
>>> Post.objects.get(id=1) == post
(0.000) SELECT `post_post`.`id`, `post_post`.`title`, `post_post`.`content`, `post_post`.`created_at`, `post_post`.`updated_at` FROM `post_post` WHERE `post_post`.`id` = 1 LIMIT 21; args=(1,); alias=default
True
>>> Post.objects.get(id=1) is post
(0.000) SELECT `post_post`.`id`, `post_post`.`title`, `post_post`.`content`, `post_post`.`created_at`, `post_post`.`updated_at` FROM `post_post` WHERE `post_post`.`id` = 1 LIMIT 21; args=(1,); alias=default
False
>>> Post.objects.create(title='Hello World', content='새로운 hello world')
(0.016) INSERT INTO `post_post` (`title`, `content`, `created_at`, `updated_at`) VALUES ('Hello World', '새로운 hello world', '2025-10-16 01:08:43.196605', '2025-10-16 01:08:43.196605'); args=['Hello World', '새로운 hello world', '2025-10-16 01:08:43.196605', '2025-10-16 01:08:43.196605']; alias=default
<Post: Hello World>
>>> Post.objects.get(title='this is 10')
(0.000) SELECT `post_post`.`id`, `post_post`.`title`, `post_post`.`content`, `post_post`.`created_at`, `post_post`.`updated_at` FROM `post_post` WHERE `post_post`.`title` = 'this is 10' LIMIT 21; args=('this is 10',); alias=default
<Post: this is 10>
>>> Post.objects.values('title', 'content')
(0.000) SELECT `post_post`.`title` AS `title`, `post_post`.`content` AS `content` FROM `post_post` LIMIT 21; args=(); alias=default
<QuerySet [{'title': 'Hello World', 'content': 'hello everyone 좋은 아침이에요~'}, {'title': 'this is 10', 'content': '벌써 10월이네요...'}, {'title': '오늘 점심 뭐 먹지?', 'content': '기가 막힌 점심 메뉴 추천 받습니다🤗'}, {'title': '가을이네요', 'content': '환절기 감기 조심하세요~!!!'}, {'title': 'Hello World', 'content': '새로운 hello world'}]>
>>> Post.objects.values()
(0.000) SELECT `post_post`.`id`, `post_post`.`title`, `post_post`.`content`, `post_post`.`created_at`, `post_post`.`updated_at` FROM `post_post` LIMIT 21; args=(); alias=default
<QuerySet [{'id': 1, 'title': 'Hello World', 'content': 'hello everyone 좋은 아침이에요~', 'created_at': datetime.datetime(2025, 10, 16, 0, 21, 36, 902041, tzinfo=datetime.timezone.utc), 'updated_at': datetime.datetime(2025, 10, 16, 0, 21, 36, 902041, tzinfo=datetime.timezone.utc)}, {'id': 2, 'title': 'this is 10', 'content': '벌써 10월이네요...', 'created_at': datetime.datetime(2025, 10, 16, 0, 26, 19, 990654, tzinfo=datetime.timezone.utc), 'updated_at': datetime.datetime(2025, 10, 16, 0, 26, 19, 990654, tzinfo=datetime.timezone.utc)}, {'id': 3, 'title': '오늘 점심 뭐 먹지?', 'content': '기가 막힌 점심 메뉴 추천 받습니다🤗', 'created_at': datetime.datetime(2025, 10, 16, 0, 27, 13, 575977, tzinfo=datetime.timezone.utc), 'updated_at': datetime.datetime(2025, 10, 16, 0, 27, 13, 575977, tzinfo=datetime.timezone.utc)}, {'id': 4, 'title': '가을이네요', 'content': '환절기 감기 조심하세요~!!!', 'created_at': datetime.datetime(2025, 10, 16, 0, 31, 25, 929084, tzinfo=datetime.timezone.utc), 'updated_at': datetime.datetime(2025, 10, 16, 0, 31, 25, 929084, tzinfo=datetime.timezone.utc)}, {'id': 5, 'title': 'Hello World', 'content': '새로운 hello world', 'created_at': datetime.datetime(2025, 10, 16, 1, 8, 43, 196605, tzinfo=datetime.timezone.utc), 'updated_at': datetime.datetime(2025, 10, 16, 1, 8, 43, 196605, tzinfo=datetime.timezone.utc)}]>
>>> Post.objects.values('title', 'content').distinct()
(0.000) SELECT DISTINCT `post_post`.`title` AS `title`, `post_post`.`content` AS `content` FROM `post_post` LIMIT 21; args=(); alias=default
<QuerySet [{'title': 'Hello World', 'content': 'hello everyone 좋은 아침이에요~'}, {'title': 'this is 10', 'content': '벌써 10월이네요...'}, {'title': '오늘 점심 뭐 먹지?', 'content': '기가 막힌 점심 메뉴 추천 받습니다🤗'}, {'title': '가을이네요', 'content': '환절기 감기 조심하세요~!!!'}, {'title': 'Hello World', 'content': '새로운 hello world'}]>
>>> from django.db.models.functions import ExtractYear
>>> from django.db.models import Count
>>> Post.objects.annotate(year=ExtractYear('created_at')).values('year').annotate(count_by_year=Count('year'))
(0.000) SELECT EXTRACT(YEAR FROM CONVERT_TZ(`post_post`.`created_at`, 'UTC', 'Asia/Seoul')) AS `year`, COUNT(EXTRACT(YEAR FROM CONVERT_TZ(`post_post`.`created_at`, 'UTC', 'Asia/Seoul'))) AS `count_by_year` FROM `post_post` GROUP BY 1 ORDER BY NULL LIMIT 21; args=('UTC', 'Asia/Seoul', 'UTC', 'Asia/Seoul'); alias=default
<QuerySet [{'year': None, 'count_by_year': 0}]>
>>> post = Post.objects.get(id=1)
(0.000) SELECT `post_post`.`id`, `post_post`.`title`, `post_post`.`content`, `post_post`.`created_at`, `post_post`.`updated_at` FROM `post_post` WHERE `post_post`.`id` = 1 LIMIT 21; args=(1,); alias=default
>>> post.title
'Hello World'
>>> post.title += ' Morning☀️'
>>> post.title
'Hello World Morning☀️'
>>> post.save()
(0.016) UPDATE `post_post` SET `title` = 'Hello World Morning☀️', `content` = 'hello everyone 좋은 아침이에요~', `crreated_at` = '2025-10-16 00:21:36.902041', `updated_at` = '2025-10-16 01:27:39.052901' WHERE `post_post`.`id` = 1; args=('Hello World Morning☀️', 'hello everyone 좋은 아침이에요~', '2025-10-16 00:21:36.902041', '2025-10-16 01:27:39.0052901', 1); alias=default
>>> post.delete()
(0.000) DELETE FROM `post_post` WHERE `post_post`.`id` IN (1); args=(1,); alias=default
(1, {'post.Post': 1})
ORM 연관 관계 활용
👉🏻 앱 생성
django-admin startapp product
👉🏻 settings.py 수정
product app 추가
INSTALLED_APPS = [
'django.contrib.admin',
...
'post',
'product'
]
👉🏻 모델 정의
product/models.py 수정
from django.db import models
class Product(models.Model):
name = models.CharField(max_length=100)
description = models.TextField(blank=True)
price = models.PositiveIntegerField()
stock = models.PositiveIntegerField()
available = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
# 1:1
class Discount(models.Model):
product = models.OneToOneField(
Product,
on_delete=models.CASCADE,
related_name='discount'
)
discount_percentage = models.DecimalField(
max_digits=5,
decimal_places=2,
help_text='Discount percentage (e.g., 10.00 for 10%)'
)
start_date = models.DateTimeField()
end_date = models.DateTimeField()
def __str__(self):
return f'{self.discount_percentage}% off for {self.product.name}'
# 1:N
class Review(models.Model):
product = models.ForeignKey(
Product,
on_delete=models.CASCADE,
related_name='review'
)
user_id = models.PositiveBigIntegerField(
max_length=50,
blank=True,
null=True
)
rating = models.PositiveIntegerField(
default=1,
help_text='Rating from 1 to 5'
)
comment = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f'Review for {self.product.name} by {self.user_id}'
# N:M
class Category(models.Model):
name = models.CharField(max_length=100, unique=True)
products = models.ManyToManyField(
Product,
related_name='categories',
blank=True
)
def __str__(self):
return self.name
👉🏻 product/migrations/0001_initial.py
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Product',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
('description', models.TextField(blank=True)),
('price', models.PositiveIntegerField()),
('stock', models.PositiveIntegerField()),
('available', models.BooleanField(default=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
),
migrations.CreateModel(
name='Discount',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('discount_percentage', models.DecimalField(decimal_places=2, help_text='Discount percentage (e.g., 10.00 for 10%)', max_digits=5)),
('start_date', models.DateTimeField()),
('end_date', models.DateTimeField()),
('product', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='discount', to='product.product')),
],
),
migrations.CreateModel(
name='Category',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100, unique=True)),
('products', models.ManyToManyField(blank=True, related_name='categories', to='product.product')),
],
),
migrations.CreateModel(
name='Review',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('user_id', models.PositiveBigIntegerField(blank=True, max_length=50, null=True)),
('rating', models.PositiveIntegerField(default=1, help_text='Rating from 1 to 5')),
('comment', models.TextField(blank=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='review', to='product.product')),
],
),
]
👉🏻 product/sample.py 추가 (샘플 데이터)
from product.models import Product, Category, Discount, Review
def create_sample_categories():
# Category 샘플 데이터
category_data = [
{"name": "가전"},
{"name": "스마트폰/태블릿"},
{"name": "가구/인테리어"},
{"name": "패션/의류"},
{"name": "스포츠/레저"},
]
# Category 생성
for data in category_data:
category, created = Category.objects.get_or_create(name=data["name"])
if created:
print(f"Created category: {category.name}")
else:
print(f"Category already exists: {category.name}")
def create_sample_products():
# 실제 데이터를 기반으로 한 Product 샘플 데이터
product_data = [
{
"name": "iPhone 15 Pro",
"description": "애플의 최신 스마트폰, 강력한 A17 Pro 칩셋과 뛰어난 카메라 성능.",
"price": 1490000,
"stock": 20,
"available": True,
},
{
"name": "Galaxy Z Flip5",
"description": "삼성의 폴더블 스마트폰, 컴팩트한 디자인과 혁신적인 힌지.",
"price": 1350000,
"stock": 15,
"available": True,
},
{
"name": "LG 올레드 TV 55인치",
"description": "최상의 화질을 제공하는 LG 올레드 TV. 영화 감상에 최적화.",
"price": 1790000,
"stock": 10,
"available": True,
},
{
"name": "Dyson V12 무선청소기",
"description": "다이슨의 최첨단 무선청소기. 강력한 흡입력과 긴 배터리 수명.",
"price": 899000,
"stock": 25,
"available": True,
},
{
"name": "Nintendo Switch OLED 모델",
"description": "닌텐도의 인기 있는 게임 콘솔, 선명한 OLED 화면.",
"price": 419000,
"stock": 30,
"available": True,
},
{
"name": "MacBook Air 15인치 M2",
"description": "애플의 초경량 노트북, M2 칩셋 탑재로 향상된 성능.",
"price": 1890000,
"stock": 10,
"available": True,
},
{
"name": "Sony WH-1000XM5",
"description": "소니의 프리미엄 노이즈 캔슬링 헤드폰, 뛰어난 음질 제공.",
"price": 499000,
"stock": 40,
"available": True,
},
{
"name": "Canon EOS R6 Mark II",
"description": "캐논의 풀프레임 미러리스 카메라, 프로급 사진 촬영에 적합.",
"price": 2790000,
"stock": 5,
"available": True,
},
{
"name": "Apple Watch Series 9",
"description": "애플의 스마트워치, 더 밝은 디스플레이와 혁신적인 기능.",
"price": 599000,
"stock": 50,
"available": True,
},
{
"name": "Samsung Bespoke 냉장고",
"description": "삼성의 맞춤형 디자인 냉장고, 고급스러운 인테리어 연출.",
"price": 2990000,
"stock": 8,
"available": True,
},
]
# Product 생성
for data in product_data:
product, created = Product.objects.get_or_create(
name=data["name"],
defaults={
"description": data["description"],
"price": data["price"],
"stock": data["stock"],
"available": data["available"],
},
)
if created:
print(f"Created product: {product.name}")
else:
print(f"Product already exists: {product.name}")
from django.db import models
from datetime import datetime
import random
def create_sample_reviews():
# 샘플 Product 데이터 (ID와 매칭)
products = [
(1, "iPhone 15 Pro"),
(2, "Galaxy Z Flip5"),
(3, "LG 올레드 TV 55인치"),
(4, "Dyson V12 무선청소기"),
(5, "Nintendo Switch OLED 모델"),
(6, "MacBook Air 15인치 M2"),
(7, "Sony WH-1000XM5"),
(8, "Canon EOS R6 Mark II"),
(9, "Apple Watch Series 9"),
(10, "Samsung Bespoke 냉장고"),
]
# 샘플 리뷰 데이터 (제품 ID별로 0~4개씩 랜덤 생성)
review_data = {
1: [
(1, 5, "살짝 무겁긴 한데, 간지가 장난 아닙니다."),
(2, 4, "카메라 성능은 최고! 하지만 발열이 좀 있네요."),
(10, 5, "사진 퀄리티가 대단합니다. 프로급 장비 같아요."),
(11, 2, "이번 모델은 아쉽네요. 이전 모델이 더 좋았어요ㅠ"),
],
2: [
(3, 5, "폴더블이라 휴대성 짱! 힌지가 정말 부드럽습니다."),
(4, 3, "디자인은 예쁜데, 배터리가 빨리 닳아요."),
],
3: [
(5, 5, "올레드 화질은 정말 최고입니다. 영화 볼 때 몰입감이 대단하네요."),
],
4: [
(6, 4, "흡입력은 강력하지만 무게가 조금 무겁습니다."),
(7, 5, "다이슨 청소기 중 가장 만족스러운 모델이에요!"),
],
5: [
(8, 5, "OLED 화면 너무 선명하고, 게임 플레이하기에 딱이에요!"),
(9, 4, "휴대 모드가 편리하고 화면도 좋아요."),
(10, 5, "닌텐도 팬으로서 너무 만족스럽습니다!"),
],
6: [
(11, 5, "M2 칩셋 정말 빠릅니다. 작업용으로 최고예요."),
],
7: [
(12, 5, "노이즈 캔슬링이 엄청 강력합니다. 음질도 너무 좋아요."),
(13, 4, "장시간 사용하면 귀가 좀 아프긴 해요."),
],
9: [
(15, 5, "손목에 딱 맞고 기능이 정말 많아요. 운동할 때 최고!"),
],
10: [
(16, 5, "디자인이 집 인테리어랑 너무 잘 어울립니다."),
(17, 4, "수납공간이 넓고 활용도가 좋아요."),
],
}
# Review 생성
for product_id, reviews in review_data.items():
for review in reviews:
user_id, rating, comment = review
Review.objects.create(
product_id=product_id,
user_id=user_id,
rating=rating,
comment=comment,
created_at=datetime.now()
)
print(f"Review created for Product {product_id} by User {user_id}")
def create_sample_discounts():
# 샘플 Product 데이터 (ID와 매칭)
products = [
(1, "iPhone 15 Pro"),
(2, "Galaxy Z Flip5"),
(3, "LG 올레드 TV 55인치"),
(4, "Dyson V12 무선청소기"),
(5, "Nintendo Switch OLED 모델"),
]
# 샘플 할인 데이터
discount_data = [
{"product_id": 3, "discount_percentage": 10.00, "start_date": "2025-01-05", "end_date": "2025-01-15"},
{"product_id": 5, "discount_percentage": 15.00, "start_date": "2025-01-10", "end_date": "2025-01-20"},
{"product_id": 7, "discount_percentage": 20.00, "start_date": "2025-02-01", "end_date": "2025-02-10"},
{"product_id": 8, "discount_percentage": 5.00, "start_date": "2025-01-12", "end_date": "2025-01-18"},
{"product_id": 10, "discount_percentage": 25.00, "start_date": "2025-01-15", "end_date": "2025-01-25"},
]
# Discount 생성
for data in discount_data:
start_date = datetime.strptime(data["start_date"], "%Y-%m-%d")
end_date = datetime.strptime(data["end_date"], "%Y-%m-%d")
Discount.objects.create(
product_id=data["product_id"],
discount_percentage=data["discount_percentage"],
start_date=start_date,
end_date=end_date,
)
print(f"Discount created: {data['discount_percentage']}% off for Product ID {data['product_id']}")
def create_sample_category_products():
# 샘플 Product 데이터 (ID와 매칭)
products = [
(1, "iPhone 15 Pro"),
(2, "Galaxy Z Flip5"),
(3, "LG 올레드 TV 55인치"),
(4, "Dyson V12 무선청소기"),
(5, "Nintendo Switch OLED 모델"),
(6, "MacBook Air 15인치 M2"),
(7, "Sony WH-1000XM5"),
(8, "Canon EOS R6 Mark II"),
(9, "Apple Watch Series 9"),
(10, "Samsung Bespoke 냉장고"),
]
# 샘플 Category 데이터
categories = [
{"name": "가전"},
{"name": "스마트폰/태블릿"},
{"name": "가구/인테리어"},
{"name": "패션/액세서리"},
{"name": "스포츠/레저"},
]
# Category 생성
for category_data in categories:
category, _ = Category.objects.get_or_create(name=category_data["name"])
# Category와 Product 연결 데이터
category_products = [
{"category_name": "스마트폰/태블릿", "product_id": 1},
{"category_name": "스마트폰/태블릿", "product_id": 2},
{"category_name": "가전", "product_id": 3},
{"category_name": "가전", "product_id": 4},
{"category_name": "스포츠/레저", "product_id": 5},
{"category_name": "가전", "product_id": 6},
{"category_name": "패션/액세서리", "product_id": 7},
{"category_name": "스포츠/레저", "product_id": 8},
{"category_name": "패션/액세서리", "product_id": 9},
{"category_name": "가구/인테리어", "product_id": 10},
{"category_name": "가전", "product_id": 7},
{"category_name": "스마트폰/태블릿", "product_id": 9},
{"category_name": "스포츠/레저", "product_id": 3},
{"category_name": "가구/인테리어", "product_id": 8},
{"category_name": "스마트폰/태블릿", "product_id": 6},
]
# Category와 Product 연결
for data in category_products:
category = Category.objects.get(name=data["category_name"])
category.products.add(data["product_id"])
print(f"Linked Product ID {data['product_id']} with Category {category.name}")
👉🏻 project/urls.py 수정
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('product/', include('product.urls'))
]
👉🏻 product/urls.py 추가
from django.urls import path
from product import views
app_name = 'product'
urlpatterns = [
path('n_1/', views.test_n_1, name='test_n_1'),
path('1_1/', views.test_1_1, name='test_1_1'),
path('prefetch/', views.test_prefetch, name='test_prefetch'),
path('n_m/', views.test_n_m, name='test_prefetch'),
]
👉🏻 데이터베이스 반영
마이그레이션 파일 생성 및 데이터베이스에 적용
python manage.py makemigrations
python manage.py migrate
👉🏻 Django Shell
python manage.py shell
>>> from product.sample import *
>>> create_sample_categories()
>>> create_sample_products()
>>> create_sample_reviews()
>>> create_sample_discounts()
>>> create_sample_category_products()
👉🏻 product/views.py 수정
from django.shortcuts import render
from django.http import HttpResponse
from product.models import Product, Category, Discount, Review
from django.db.models import Avg, Count
from datetime import datetime, timedelta
# 1:N = Product:Review
def test_n_1(request):
result = ''
# 1. 특정 제품의 모든 리뷰 select
# (1) Review 테이블 1번 + Product 테이블 4번 = 총 5번
# reviews = Review.objects.filter(product_id=1)
# (2) Product 테이블 1번 + Review 테이블 1번 + Product 테이블 4번 = 총 6번
# product = Product.objects.get(id=1)
# reviews = Review.objects.filter(product=product)
# ★ (3) Product 테이블 1번 + Review 테이블 1번 = 총 2번
product = Product.objects.get(id=1)
reviews = product.review.all()
for review in reviews:
result += str(review.id) + '/' + review.product.name + '/' + str(review.user_id) + '/' + str(review.rating) + '/' + review.comment + '<br>'
# 2. 특정 제품의 평균 평점과 리뷰 개수 select
product = Product.objects.get(id=1)
avg_rating = product.review.aggregate(avg_rating=Avg('rating'))['avg_rating']
review_cnt = product.review.count()
result = f'{product.name}의 리뷰 평균 평점: {avg_rating}({review_cnt}개 리뷰)<br>'
# 3. 평점이 높은 리뷰(4점 이상)만 select
product = Product.objects.get(id=1)
high_rating_reviews = product.review.filter(rating__gte='4')
for review in high_rating_reviews:
result += f'[High Rating] {review.user_id}의 {review.comment} ({review.rating}점)<br>'
# 4. 모든 제품의 평균 평점과 리뷰 개수 select
products_with_review = Product.objects.annotate(
avg_rating=Avg('review__rating'),
review_count = Count('review')
)
result = ''
for product in products_with_review:
result += f'Product {product.name} | 평균 평점 {product.avg_rating} : 리뷰 개수 {product.review_count}<br>'
# 5. 특정 기간(한달전~오늘)동안 작성된 리뷰 select
start_date = datetime.now() - timedelta(weeks=4)
end_date = datetime.now()
reviews_by_date = Review.objects.filter(created_at__range=(start_date, end_date))
for review in reviews_by_date:
result += str(review.id) + '/' + review.product.name + '/' + str(review.user_id) + '/' + str(review.rating) + '/' + review.comment + '<br>'
return HttpResponse(result)
# 1:1 = Product:Discount
def test_1_1(request):
result = ''
now = datetime.now()
# 1. 특정 제품의 할인 정보 select
product_id = 1
# (1) Discount.objects.get()
# (2) 할인 정보가 있는 제품이라면 -> Product 제품명 | Discount 할인율
# (3) 할인 정보가 없는 제품이라면 -> product_id는 할인 안함!
try:
discount = Discount.objects.get(product_id=product_id)
result += f'{discount.product.name} | {discount.discount_percentage}% 할인<br>'
except Discount.DoesNotExist:
result += f'product_id {product_id}는 할인 안함!<br>'
# 2. 할인 중인 모든 제품 select
# - 현재 시점에 할인 중인 제품 == 조건
on_sale_discounts = Discount.objects.filter(start_date__lte=now, end_date__gte=now)
for discount in on_sale_discounts:
result += f'{discount.product.name} ({discount.discount_percentage}%)<br>'
# 3. 특정 할인율(20%) 이상인 제품 select
# - [파격세일!!!] {제품명} ({할인율}%)
high_discounts = Discount.objects.filter(discount_percentage__gte=20)
for discount in high_discounts:
result += f'[파격세일!!!] {discount.product.name} ({discount.discount_percentage}%)<br>'
# 4. 할인 정보와 함께 모든 제품 정보 select
# - 할인 정보가 있으면 -> {제품명} ({할인율}% 세일)
# - 할인 정보가 없으면 -> 할인 안하는 {제품명}
all_products = Product.objects.all()
result += '<br>'
for product in all_products:
if hasattr(product, 'discount'):
result += f'{product.name} ({product.discount.discount_percentage}% 세일)<br>'
else:
result += f'할인 안하는 {product.name}<br>'
# 5. 할인 기간이 지난 제품 select
# - [할인 종료!!!] {제품명} ({할인율}%)
expired_discounts = Discount.objects.filter(end_date__lt=now)
result += '<br>'
for discount in expired_discounts:
result += f'[할인 종료!!!] {discount.product.name} ({discount.discount_percentage}%)<br>'
return HttpResponse(result)
# prefetch
# 💧 위 4번의 많은 쿼리 실행 문제롤 해결
# product 테이블 전체 조회 -> product id를 가지고 discount가 있는지 다 조회
def test_prefetch(request):
result = ''
products = Product.objects.prefetch_related('discount')
for product in products:
if hasattr(product, 'discount'):
result += f'{product.name} ({product.discount.discount_percentage}% 세일)<br>'
else:
result += f'할인 안하는 {product.name}<br>'
return HttpResponse(result)
# N:M = Product:Category
def test_n_m(request):
result = ''
# 1. 특정 제품이 속한 모든 카테고리 select
product_id = 9
# (1) 출력 예시
# Product {제품명}의 category
# - {카테고리 1}
# - {카테고리 2}
# - ...
product = Product.objects.get(id=product_id)
result += f'{product.name}의 category<br>'
for category in product.categories.all():
result += f'- {category.name}<br>'
# 2. 특정 카테고리에 속한 모든 제품 정보(이름, 가격, 재고량) select
category_name = '스마트폰/태블릿'
# (1) 출력 예시
# Category {카테고리명}의 제품
# - {제품명} ({가격}원 / 수량: {재고량}개)
# - {제품명} ({가격}원 / 수량: {재고량}개)
# - ...
category = Category.objects.get(name=category_name)
result += '<br>'
result += f'{category.name}의 제품<br>'
for product in category.products.all():
result += f'- {product.name} ({product.price}원 / 수량: {product.stock}개)<br>'
# 3. 카테고리가 없는 제품 select
# (1) category가 null인 product 조회
# (2) 출력 예시
# Category 미포함 제품
# - {제품명} ({가격}원 / 수량: {재고량}개)
# - {제품명} ({가격}원 / 수량: {재고량}개)
# - ...
products_no_category = Product.objects.filter(categories__isnull=True)
result += '<br>'
result += 'Category 미포함 제품<br>'
for product in products_no_category:
result += f'- {product.name} ({product.price}원 / 수량: {product.stock}개)<br>'
# 4. 🔥특정 제품에 새 카테고리 추가
product_id = 9
new_category_name = 'Seasonal'
# (1) 힌트: get_or_create()와 add()
# (2) 출력 예시: {제품명} ({카테고리명1}, {카테고리명2}, ...)
product = Product.objects.get(id=product_id)
new_category, created = Category.objects.get_or_create(name=new_category_name)
product.categories.add(new_category)
categories = product.categories.all()
category_names = ', '.join([cat.name for cat in categories])
result += '<br>'
result += f'{product.name} ({category_names})<br>'
# 5. 모든 카테고리와 각 카테고리의 제품 개수 select
# (1) 출력 예시
# - Category {카테고리명}에는 {제품 개수}개의 제품이!
# - Category {카테고리명}에는 {제품 개수}개의 제품이!
# - Category {카테고리명}에는 {제품 개수}개의 제품이!
# - ...
categories_with_product_count = Category.objects.annotate(product_count=Count('products'))
result += '<br>'
for category in categories_with_product_count:
result += f'- Category {category.name}에는 {category.product_count}개의 제품이!<br>'
# 6. 여러 카테고리에 속한 제품 select
# (1) 출력 예시
# 여러 카테고리에 속한 제품 목록
# - {제품명} (Category 개수: {카테고리 개수})
# - {제품명} (Category 개수: {카테고리 개수})
# - {제품명} (Category 개수: {카테고리 개수})
# - ...
products_in_multiple_categories = Product.objects.annotate(category_count=Count('categories')).filter(category_count__gt=1)
result += '<br>'
for product in products_in_multiple_categories:
result += f'- {product.name} (Category 개수: {product.category_count})<br>'
return HttpResponse(result)
👉🏻 서버 실행 및 결과 확인
ex)
http://127.0.0.1:8000/product/n_1
python manage.py runserver
관리자 페이지 활용 (Django Admin)
- 관리자 페이지 활성화
1. 관리자 계정 생성
python manage.py createsuperuser
# 사용자이름 입력
# 이메일 주소 입력
# password 입력 (2번)
2. 브라우저에서 관리자 페이지 접속
http://127.0.0.1:8000/admin
3. DB에서 insert된 data 확인
SELECT * FROM auth_user;
- 관리자 페이지 커스터마이징
admin.py에 모델 등록 및 리스트 필드 표시 설정
from django.contrib import admin
from qna.entity.models import Question, Answer
# 모델 등록
# admin.site.register(Question)
# admin.site.register(Answer)
# 리스트 필드 표시 설정
@admin.register(Question)
class QuestionAdmin(admin.ModelAdmin):
list_display = ('id', 'subject', 'created_at')
search_fields = ('subject',)
@admin.register(Answer)
class AnswerAdmin(admin.ModelAdmin):
pass
👉🏻 결과
리스트에 필드 표시, 검색 및 필터링 기능 제공
'AI 활용 애플리케이션 개발 > Django Framework' 카테고리의 다른 글
| 3.4.10 [Django] Fast API (RESTful API) (0) | 2025.10.28 |
|---|---|
| 3.4.9 [Django] Class-based View(CBV) (0) | 2025.10.19 |
| 3.4.8 [Django] Form 처리 (0) | 2025.10.19 |
| 3.4.6 [Django] View-Template 연동 (0) | 2025.10.16 |