brunch

매거진 백엔드

You can make anything
by writing

C.S.Lewis

by 내가 사는 세상 Dec 20. 2023

Django (DRF) - Model

dbeaver

목차

1. Quick Start, 기본 예제

    1.1. 멤버 변수

    1.2. 멤버 함수

    1.3. class Meta


2. 멤버변수 -> 테이블 간의 관계

    2.1. 1:1 - OneToOneField

    2.2. 1:N - ForeignKey

    2.3. M:N - ManyToManyField


3. Model - QuerySet, SQL

    3.1. QuerySet API - 기본 api

    3.2. QuerySet API - lookup 필드 #__

    3.3. QuerySet API - Q개체 #좀 더 깐깐하게

    3.4. QuerySet API -> SQL 


4. DBeaver로 데이터베이스 접속하기




1. Quick Start, 기본 예제


웹에는 DB가 있다. 장고는 이를 쉽게 다루고 관리할 수 있게 해주는 Model이 있다. Model은 클래스의 일종(멤버변수, 멤버함수를 가짐)이며, 기본적인 형태는 다음과 같다.


from django.db import models


class Author(models.Model):

    name = models.CharField(max_length=255)

    class Meta:

        verbose_name_plural = "Authors"

    def __str__(self):

        return self.name


class Book(models.Model):

    title = models.CharField(max_length=255)

    genre = models.CharField(max_length=255)

    published_date = models.DateField()

    author = models.ForeignKey(Author, on_delete=models.CASCADE)


    class Meta:

        ordering = ['-published_date']

        verbose_name_plural = "Books"


    def __str__(self):

        return f"{self.title} by {self.author.name}"


하나의 모델은 하나의 데이터베이스 테이블을 나타낸다. 위의 Book 모델은 다음과 같은 DB 테이블로 나타낼 수 있다.

테이블 명은 app이름_class이름 형태인 소문자로 등록된다.

예제에서 models.py가 들어있는 app이름이 library라면

library_book이라는 이름의 테이블이 되는 것이다.



1.1. 멤버변수


from django.db import models


class MyModel(models.Model):

    name = models.CharField(max_length=100)

    user = models.ForeignKey(User, on_delete=)

    created_at = models.DateTimeField(auto_now_add=True)

    updated_at = models.DateTimeField(auto_now=True)



    

1.1.1. CharField

max_length 옵션 필수지정

https://docs.djangoproject.com/en/5.0/ref/models/fields/#charfield


선택하는 것도 가능

from django.utils.translation import gettext_lazy as _


class RestaurantTable(models.Model):

    class Weekday(models.TextChoices):

        MONDAY = 'MON', _('월요일')

        TUESDAY = 'TUE', _('화요일')

        WEDNESDAY = 'WED', _('수요일')

        THURSDAY = 'THU', _('목요일')

        FRIDAY = 'FRI', _('금요일')

        SATURDAY = 'SAT', _('토요일')

        SUNDAY = 'SUN', _('일요일')        


    weekday = models.CharField(max_length=3, choices=Weekday.choices, default=Weekday.MONDAY)




1.1.2. TextField

1.1.3. DateTimeField

1.1.4. BooleanField

1.1.5. ImageField

1.1.6. IntegerField

1.1.7. FloatField


1.1.8. DecimalField

max_digits : 필수옵션

decimal_places : 필수옵션

price = models.DecimalField(max_digits=2, decimal_places=2)


1.1.9. ForeignKey

to 모델 :  필수지정

on_delete : 필수지정

realted_name : 반대방향 모델 인스턴스 호출 명




주요 옵션

db_index = True : 해당 필드를 인덱스 열로 설정

B-Tree 자료구조로 DB 인덱싱




cf) 옵션들의 기본값

# django\db\models\fields\__init__.py


def __init__(
    self,
    verbose_name=None,
    name=None,
    primary_key=False,
    max_length=None,
    unique=False,
    blank=False,
    null=False,
    db_index=False,
    rel=None,
    default=NOT_PROVIDED,
    editable=True,
    serialize=True,
    unique_for_date=None,
    unique_for_month=None,
    unique_for_year=None,
    choices=None,
    help_text="",
    db_column=None,
    db_tablespace=None,
    auto_created=False,
    validators=(),
    error_messages=None,
    db_comment=None,
    db_default=NOT_PROVIDED,
):




1.2. 멤버함수


from django.db import models


class YourModel(models.Model):

    def get_absolute_url(self):

        return reverse('app_name:url_name', args=[str(self.id)])


    def __str__(self):

        return f'{self.some_field}'



def get_absolute_url(self):


def __str__(self) 

: 모델을 문자열로 표현할 때 사용할 값을 지정

: 대표적으로 print() 함수를 사용할 때 사용

: 모델 인스턴스를 template에서 표현할 때 나타나는 값






1.3. class Meta:


모델 class의 전반적인 명세에 대해 설명할 때 사용한다.


class Meta:
    unique_together = ['product_name', 'price']


여기선 2개의 멤버변수(필드) 조합이 고유해야 한다는 것이다.

product_name  |      price

          제품A       |    1000원   (O)

          제품A       |     2000원   (X)   bcz. product_name 중복

          제품B       |     1000원   (X)   bcz. price 중복

          제품B       |      3000원   (O)




2. 테이블 간의 관계


하나의 데이터베이스에는 여러 테이블이 있다. 이때 테이블 간의 관계는 중요하다. 그 관계는 3종류가 있는데 하나씩 알아보자.


2.1. OneToOneField - 1:1


from django.db import models


class Person(models.Model):

    name = models.CharField(max_length=100)


class Profile(models.Model):

    person = models.OneToOneField(Person, on_delete=models.CASCADE)

    age = models.IntegerField()



해당 테이블을 합치면 다음과 같다.

장고 ORM 명령으로는

persons = Person.objects.select_related('profile').all()


이는 아래의 SQL문과 동일하다.

SELECT * FROM "yourapp_person"

LEFT JOIN "yourapp_profile" ON "yourapp_person"."id" = "yourapp_profile"."person_id";





2.2. ForeignKey - 1:N


작가 1명이 N(여러)개의 책을 쓸 수 있다. N측에 ForeignKey로 나타내면 된다.


from django.db import models


class Author(models.Model):

    name = models.CharField(max_length=100)


class Book(models.Model):

    title = models.CharField(max_length=200)

    author = models.ForeignKey(Author, on_delete=models.CASCADE)



2.2.1. 설정하기(핵심 옵션)

ForeignKey(to, on_delete)

to 

: 1:N의 관계에서 '1의 모델(부모 모델)'을 적은다. N(자식 모델들)이 어디를 바라보는지 쓰는 것이다.


on_delete 

: 관련 record를 삭제 할때 어떻게 할지 설정. 

: 부모가 사라지면 자식들도 폭포수에 쓸려 내려가듯 같이 사라지면 CASCADE(주로 사용)

: 부모가 사라져도 자식은 남아있어야 한다면 SET_NULL을 사용


from django.db import models


class Author(models.Model):

    name = models.CharField(max_length=100)


class Book(models.Model):

    title = models.CharField(max_length=200)

    author = models.ForeignKey(Author, on_delete=models.CASCADE)




2.2.2. 조회하기 #reverse_name #related_name


2.2.2.1. 순방향 조회


Book.objects.filter(author_id=1) : Book 테이블의 author_id 필드를 활용해 조회

Book.objects.filter(author__name='김철수') : Author 테이블까지 타고 올라가서, 부모 모델(Author)의  name 필드가 '김철수'인 것을 조회



2.2.2.2. 역방향 조회


 이런 기본적인 조회 뿐만 아니라 역방향 조회도 가능하다. 부모 모델에선 '자식 모델명_set'(해당 예제에선 book_set)으로 조회도 가능한 것이다. Book 클래스를 다시 살펴보자.


class Book(models.Model):

    title = models.CharField(max_length=200)

    author = models.ForeignKey(Author, on_delete=models.CASCADE)


 다음과 같이 author.book_set.all()로 조회가능하다.





 그런데 같은 클래스 내 다른 멤버변수가 같은 부모를 바라볼 때도 있다. 이땐 서로 그 값이 곂칠 수도 있다. 이를 방지하기 위해 개발자가 related_name 옵션으로 커스텀이 가능하다.


class Book(models.Model):

    ...

    author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='myapp_book_set')



다음과 같이 조회가능하다.

author_instance = Author.objects.get(pk=1)

books_by_author = author_instance.myapp_book_set.all()





 이젠 두 테이블을 합쳐보자.



Django ORM문

from your_app.models import Author, Book


# Author와 Book을 Author의 ID와 Book의 Author_id를 기준으로 Left Join

result = Author.objects \

              .filter(id=Book.objects.values('author_id')) \

             .values('id', 'name', 'book__id', 'book__title')


SQL문

SELECT Author.id, Author.name, Book.id AS book_id, Book.title

FROM Author

LEFT JOIN Book ON Author.id = Book.author_id;





2.3. ManyToManyField - M:N


from django.db import models


class Student(models.Model):

    name = models.CharField(max_length=100)


class Course(models.Model):

    title = models.CharField(max_length=200)

    students = models.ManyToManyField(Student)



2.3.1. 설정하기(핵심 옵션)

ManyToManyField(to, blank)


to에 해당하는 클래스가 밑에 선언된다면 '클래스명'으로 선언한다. 위의 예제에서 MTM 관계를 Student 클래스에서 선언하고자 하면 다음과 같이 적으면 된다.


class Student(models.Model):

    students_set = models.ManyToManyField('Student')


2.3.2. 중간 테이블


중간 테이블이 생성되는데 그 이름은 appname_table1_table2_set이다.

위의 경우 app이름이 xxx라면

xxx_student_course_set으로 생긴다.


이와 관련해서 추가적인 내용을 보충하고자 하면 through 옵션을 사용하면 된다.




3. Model - QuerySet, SQL


3.1. QuerySet API - 기본 api


DB에서 데이터를 가져올 때 사용하는 기본 쿼리를 살펴보자.

https://docs.djangoproject.com/ko/5.0/ref/models/querysets/


3.1.1. filter()


쿼리셋(개체들의 모음(list)) 반환 O




3.1.2. get_or_create()


쿼리셋(개체들의 모음(list)) 반환 X, 개체 반환 O

default를 제외하고 전달


obj, created = MyModel.objects.get_or_create(

    field_name='조건값',

    defaults={'additional_field': '기본값'}

)


https://docs.djangoproject.com/ko/5.0/ref/models/querysets/#get-or-create




3.1.3. get_object_or_404()


쿼리셋(개체들의 모음(list)) 반환 X, 개체 반환 O


from django.shortcuts import get_object_or_404


obj = get_object_or_404(YourModel, pk=object_id)




from yourapp.models import YourModel


# 모든 레코드 조회

all_records = YourModel.objects.all()


# 특정 조건에 맞는 레코드 조회 (예: id가 1인 레코드)

specific_record = YourModel.objects.get(id=1)


# 특정 조건에 맞는 레코드들 조회 (예: name이 'John'인 레코드들)

filtered_records = YourModel.objects.filter(name='John')


# 특정 조건에 맞는 레코드가 있는지 확인

exists = YourModel.objects.filter(name='Jane').exists()


# 정렬된 상태로 조회 (예: 나이를 기준으로 오름차순)

ordered_records = YourModel.objects.order_by('age')


# 특정 필드만 조회 (예: 이름 필드만 조회)

name_values = YourModel.objects.values_list('name', flat=True)


# 조건을 만족하는 레코드 개수 조회

record_count = YourModel.objects.filter(age__gte=18).count()


# 중복제거

queryset.distinct().all()




3.2. QuerySet API - lookup 필드


Book.objects.filter(published_date__gte=today)


lookup field를 사용하여 쿼리를 요청하는 예를 보자. 원하는 필드에 언더바 2개(__)를 붙이고 사용하면 된다.


# models.py

class RestaurantTable(models.Model):

    restaurant = models.ForeignKey(

        Restaurant, on_delete=models.CASCADE,

    )

    time = models.TimeField()

    available = models.IntegerField()


# views.py에서 쿼리에 조건 넣어 조회

query_sets = Restaurant.objects.filter(visible=True).order_by('-created_at')

relation_conditions = relation_conditions & Q(restauranttable__time__gte=start_time)




3.3. QuerySet API - Q개체 


Q : ORM에서 filter() 사용할 때 논리 조건 사용가능하게

ex1) Q(조건1) | Q(조건2) 

ex2) Q(조건1) & Q(조건2)


from django.db.models import Q

from yourapp.models import YourModel


# Q 객체를 사용한 OR 연산 예제

query = Q(name='John') | Q(name='Jane')

or_records = YourModel.objects.filter(query)


# AND 연산

and_query = Q(age__gte=18) & Q(city='New York')

and_records = YourModel.objects.filter(and_query)


# NOT 연산 

not_query = ~Q(name='Bob')

not_records = YourModel.objects.filter(not_query)


# 복잡한 조합 ( (A AND B) OR (C AND D) )

complex_query = (Q(age__gte=21) & Q(city='Los Angeles')) | (Q(age__gte=18) & Q(city='San Francisco'))

result_records = YourModel.objects.filter(complex_query)


장고는 ORM(Object-Relational Mapping : 데이터베이스와 상호작용하기 위한 파이썬 객체를 제공하는 도구)이다. ORM은 개발자가 SQL 쿼리를 직접 작성하지 않고도 데이터베이스와 상호 작용할 수 있게 해준다. 하지만 ORM에서의 DB 관련 명령어가 내부적으로는 어떤 SQL을 날리는지 알 필요는 있다. 프로젝트의 앱이름이 yourapp이라면


Book.objects.all()

SELECT * FROM yourapp_book;




3.4. QuerySet API -> SQL 


3.4.1. SQL 조회


query_set = Post.objects.filter(id=1)

str(query_set.query)


3.4.2. ORM - SQL 예시


Book.objects.filter(published_date__gte=today)

SELECT * FROM yourapp_book WHERE published_date >= '현재날짜';



Book.objects.filter(

    models.Q(genre='Fantasy') | models.Q(genre='Mystery')

)

SELECT * FROM yourapp_book WHERE genre = 'Fantasy' OR genre = 'Mystery';



Book.objects.order_by('-published_date')

SELECT * FROM yourapp_book ORDER BY published_date DESC;



Book.objects.values('title')

SELECT title FROM yourapp_book;



Book.objects.values('genre').annotate(count=Count('id'))

SELECT genre, COUNT(id) AS count FROM yourapp_book GROUP BY genre;



Author.objects.get(name='J.K. Rowling').book_set.all()

SELECT * FROM yourapp_book WHERE author_id = (SELECT id FROM yourapp_author WHERE name = 'J.K. Rowling');




4. DBeaver로 데이터베이스 접속하기


배포 서버 내역이 아래와 같고, 

IP Address : xxx


DB 컨테이너의 환경변수가 아래와 같으면

POSTGRES_HOST=xxx

POSTGRES_PORT=5432

POSTGRES_DB=xxx

POSTGRES_USER=xxx

POSTGRES_PASSWORD=xxx

DATABASE_URL=xxx



배포하는 서버의 내역이 다음과 같다면, SSH 탭에서 다음과 같이 설정하면 된다.

IP Address : xxx

Username : xxx

Password : xxx



그러면 다음과 같이 접속이 가능해진다.



매거진의 이전글 Django (DRF) - 개발순서
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari