Django ORM 기능 및 데이터베이스 마이그레이션 오류 해결 방법

모듈 임포트 후 메서드 자동 완성 안 되는 문제 해결

모델 클래스를 임포트한 후 objects와 같은 메서드가 자동 완성되지 않는 경우, 모델 클래스 내부에 objects = models.Manager()를 추가하면 해결됩니다.

단일 테이블 데이터 삽입

데이터 삽입은 세 가지 방식으로 가능합니다: save(), create(), bulk_create(). 두 번째 방식인 create()는 저장과 동시에 객체를 반환하며, 반환된 객체는 삽입된 필드 값을 포함합니다.

from my_app.models import Book

def orm_add(request):
    # 방법 1: 인스턴스 생성 후 save()
    new_book = Book(title="go 책", price=100, pub_date="2021-01-03", publish="인민출판사")
    new_book.validate_unique()  # 중복 여부 검사
    print(new_book.title)
    new_book.save()

    # 방법 2: create() 사용 (반환값 존재)
    book_obj = Book.objects.create(title="자바", price=300, pub_date="2021-04-03", publish="난징출판사")
    print(book_obj.title)

    return HttpResponse("데이터 추가 완료")

bulk_create()는 대량 삽입에 유리합니다. 그러나 중복 키 오류 발생 시 예외 처리가 필요합니다.

book_list = []
for i in range(1, 100):
    bk = models.Test.objects.create(name=f"파이썬-{i}", price=i)
    book_list.append(bk)

try:
    models.Test.objects.bulk_create(book_list)
except Exception:
    pass  # 중복 시 무시

사용자 정의 삽입 로직 구현

이미지 필드(ImageField)의 경로를 동적으로 설정하고, 이를 바탕으로 다른 필드(licence_path)를 자동 생성하는 경우, save() 메서드를 오버라이드해야 합니다.

# model.py
def user_directory_path(instance, filename):
    return os.path.join("upload", instance.company.username, filename)

class CompanyAuth(models.Model):
    company = models.ForeignKey("Company", on_delete=models.CASCADE, to_field="id")
    title = models.CharField(max_length=64, verbose_name="기업명")
    uniques_id = models.CharField(max_length=64, verbose_name="사업자등록번호")
    leader = models.CharField(max_length=10, verbose_name="대표자명")
    licence_path = models.CharField(max_length=64, verbose_name="영업허가서 경로")
    leader_identify = models.CharField(max_length=10, verbose_name="직위")
    avatar_img = models.ImageField(upload_to=user_directory_path, default="default_upload/test.jpg", blank=True, null=True)

    def save(self, *args, **kwargs):
        # 이미지 경로를 기반으로 라이선스 경로 생성
        path = user_directory_path(self, self.avatar_img.name)
        self.licence_path = os.path.join(settings.MEDIA_URL, path)
        super().save(*args, **kwargs)

중복 파일 처리 전략

django.core.files.storage.default_storage는 중복 파일을 자동으로 식별하고 이름을 무작위로 재설정하여 저장합니다. 주요 메서드는 다음과 같습니다:

  • save(name, content): 파일 저장. 중복 시 자동 이름 변경.
  • get_available_name(name): 중복 여부 확인 후 고유 이름 반환.
  • url(name): 공개 접근용 URL 생성.
  • exists(name): 파일 존재 여부 확인.
  • delete(name): 파일 삭제.
from django.core.files.storage import default_storage
from django.core.files.base import ContentFile

content = b"Hello, World!"
file = ContentFile(content)
saved_path = default_storage.save("test.txt", file)  # 중복 시 자동 이름 변경
file_url = default_storage.url(saved_path)  # http://host/media/test_1.txt

단일 테이블 조회

쿼리 결과는 항상 QuerySet 형태이며, 리스트처럼 반복 가능합니다. 출력을 명확히 하기 위해 __str__ 메서드를 정의하는 것이 좋습니다.

values()values_list()의 차이점:

  • values(): 딕셔너리 형태로 반환.
  • values_list(): 튜플 형태로 반환.

조건 기반 조회

  • contains: 포함 여부 확인. 예: Student.objects.filter(name__contains='화').
  • startswith, endswith: 시작/끝 문자열 일치. i 접두사로 대소문자 무시 가능.
  • isnull: NULL 값 여부 확인.
  • in, range: 범위 내 포함 여부. 예: age__range=(20, 30).
  • gt, gte, lt, lte: 비교 연산.
  • year, month, day 등: 날짜/시간 필드 분리.

F 쿼리와 Q 쿼리

F 쿼리

데이터베이스 내 두 컬럼 간 비교에 사용됩니다.

from django.db.models import F
# 생성 시간과 수정 시간이 동일하지 않은 항목 추출
students = Student.objects.exclude(created_time=F('updated_time'))

# 필드 값 증가
models.Book.objects.update(comment=F('comment') + 1)

# 별칭 지정
ret = models.NaviBar.objects.filter(rooter=pk).annotate(menu_title=F('rooter__title')).values('title', 'name', 'is_menu', 'menu_title')

Q 쿼리

논리 조합을 위한 도구입니다. & (AND), | (OR), ~ (NOT).

from django.db.models import Q
# 나이가 19 미만 또는 20 초과
student_list = Student.objects.filter(Q(age__lt=19) | Q(age__gt=20))

# NOT 조건
Student.objects.filter(~Q(pk=30))

집계 및 그룹화 쿼리

관련 내용은 여기에서 확인하세요.

원시 SQL 실행

ORM을 우회하여 직접 SQL을 실행할 수 있습니다. 보안을 위해 파라미터화된 방식을 사용해야 합니다.

# raw() 사용
res = models.Author.objects.raw('SELECT * FROM app01_author WHERE nid > %s', [1])

# connections 사용
from django.db import connections
with connections['default'].cursor() as cursor:
    cursor.execute("UPDATE TbEmp SET sal=sal+10 WHERE dno=30")
    cursor.execute("SELECT ename, job FROM TbEmp WHERE dno=10")
    rows = cursor.fetchall()

보안 주의사항

SQL 인젝션 방지를 위해 문자열 결합은 피하고, 파라미터화된 쿼리를 사용하세요.

sql = "SELECT id, name FROM student WHERE id = %s"
cursor.execute(sql, [user_id])
result = cursor.fetchall()

단일 테이블 삭제

  • obj.delete(): 단일 객체 삭제.
  • queryset.delete(): 여러 객체 삭제. 반환값은 (삭제 건수, 테이블 정보) 형식.
Book.objects.filter(title="go").delete()

단일 테이블 수정

update()는 효율적이며, save()는 모든 필드를 다시 저장하므로 권장하지 않습니다.

Book.objects.filter(price=100).update(title="전체 수정")

데이터베이스 마이그레이션 오류 해결

테이블에 컬럼 없음

오류: Unknown column 'name' in 'django_content_type'
해결: 수동으로 컬럼 추가

ALTER TABLE django_content_type ADD COLUMN name VARCHAR(10);

테이블 중복 생성

오류: Table 'echart_show_category' already exists
해결: 초기화 스킵

python manage.py migrate --fake-initial

태그: Django ORM database migration F query Q query

5월 29일 09:52에 게시됨