인프런 커뮤니티 질문&답변

ajdsasld님의 프로필 이미지
ajdsasld

작성한 질문수

파이썬/장고 웹서비스 개발 완벽 가이드 with 리액트

migrate 오류 질문

작성

·

771

0

안녕하세요, 강사님.

제가 개인적으로 진행하고 있는 프로젝트의 models.py를 일부 수정하고 migration 이후 migrate를 진행할 때 오류가 나고 있어 질문드립니다.

from django.db import models
from django.contrib.auth.models import User
from urllib3 import encode_multipart_formdata


# Create your models here.


# Customer 모델
class Customer(models.Model):
user = models.OneToOneField(User, null=False, blank=False, on_delete=models.CASCADE, default=0) # User모델과 1대1 관계 / null=True는 비워도 괜찮다는 것. / on delete를 사용해서 회원이 지워지면 고객도 지워지게 한다.
name = models.CharField(max_length=100, null=False, default=0) # 디폴트 값을 미지정으로 수정
email = models.CharField(max_length=100)

def __str__(self):
return f'user: {self.user} | name: {self.name} | email: {self.email}'


# Products 모델
class Post(models.Model):
product_name = models.CharField(max_length=100)
brand = models.TextField()
price = models.PositiveIntegerField(default=0) # 가격이니까 실수가 아닌 정수 필드로 수정
image = models.ImageField(null=True, blank=True) # 이미지이니까 Image 필드 선택
created_at = models.DateTimeField(auto_now_add=True) # 상품 db에 추가할 때 날짜 설정
updated_at = models.DateTimeField(auto_now=True) # 수정될 떄마다 날짜 설정

def __str__(self):
return f'product_name: {self.product_name} | brand: {self.brand} | price: {self.price} | image: {self.image} |created_at: {self.created_at}'

@property
def imageURL(self):
try:
url = self.image.url
except:
url = 'posts/'
return url


# Order 모델
class Order(models.Model):
customer = models.ForeignKey(Customer, on_delete=models.SET_NULL, blank=True, null=True) # Customer가 회원탈퇴 하더라도 주문 내역을 유지하기
date_ordered = models.DateTimeField(auto_now_add=True)
complete = models.BooleanField(default=True, null=True, blank=True)
transaction_id = models.CharField(max_length=200, null=True)

def __str__(self):
return f'id: {self.id} | customer: {self.customer} | date_ordered: {self.date_ordered} | complete: {self.complete} | transaction_id: {self.transaction_id}'


@property
# 장바구니에 담은 모든 상품들의 총 금액을 계산하는 코드
def get_cart_total(self):
orderitems = self.orderitem_set.all()
total = sum([item.get_total for item in orderitems])
return total

# 장바구니에 담은 모든 상품들의 수를 계산하는 코드
def get_cart_items(self):
orderitems = self.orderitem_set.all()
total = sum([item.quantity for item in orderitems])
return total




# Order item 모델
class OrderItem(models.Model):
product = models.ForeignKey(Post, on_delete=models.SET_NULL, blank=True, null=True)
order = models.ForeignKey(Order, on_delete=models.SET_NULL, blank=True, null=True)
quantity = models.IntegerField(default=0, null=False, blank=False)
date_added = models.DateTimeField(auto_now_add=True)

def __str__(self):
return f'id: {self.id} | product: {self.product} | order: {self.order} | quantity: {self.quantity} | date_added: {self.date_added}'


# 장바구니에서 총 합계를 볼 수 있도록 property operator를 설정
@property
def get_total(self):
total = self.product.price * self.quantity
return total



# Shipping Address 모델
class ShippingAddress(models.Model):
customer = models.ForeignKey(Customer, on_delete=models.SET_NULL, blank=True, null=True)
order = models.ForeignKey(Order, on_delete=models.SET_NULL, blank=True, null=True)
address = models.CharField(max_length=200, null=True)
city = models.CharField(max_length=200, null=True)
zipcode = models.CharField(max_length=200, null=True)
date_added = models.DateTimeField(auto_now_add=True)

def __str__(self):
return f'{self.address}'



위의 코드들은 models.py인데, 제가 여기서 Customer 모델 클래스의 user와 name 필드의 속성을 수정했습니다. 기존에는 null과 blank가 True로 되어있어 False로 수정을 하고 migration을 진행했는데, name과 user 필드 모두 non-nullable로 바꾸는데 default를 주지 않았다고 나왔습니다.

그래서, 

name = models.CharField(max_length=100, null=False, default='미지정')  

 

user = models.OneToOneField(User, null=False, blank=False, on_delete=models.CASCADE, default='미지정')  

 이렇게 수정하고 migrations를 진행하니까 넘어가고 migrate를 진행하니까

 

(env) kimsangbaek@gimsangbaeg-ui-MacBookAir impactmuseum % python manage.py makemigrations posts

Migrations for 'posts':

  posts/migrations/0004_auto_20220206_0259.py

    - Alter field name on customer

    - Alter field user on customer

(env) kimsangbaek@gimsangbaeg-ui-MacBookAir impactmuseum % python manage.py migrate posts

Operations to perform:

  Apply all migrations: posts

Running migrations:

  Applying posts.0003_auto_20220206_0249...Traceback (most recent call last):

  File "/Users/kimsangbaek/Desktop/likelion/env/lib/python3.9/site-packages/django/db/models/fields/__init__.py", line 1823, in get_prep_value

    return int(value)

ValueError: invalid literal for int() with base 10: '미지정'

 

The above exception was the direct cause of the following exception:

 

Traceback (most recent call last):

  File "/Users/kimsangbaek/Desktop/likelion/env/impactmuseum/manage.py", line 22, in <module>

    main()

  File "/Users/kimsangbaek/Desktop/likelion/env/impactmuseum/manage.py", line 18, in main

    execute_from_command_line(sys.argv)

  File "/Users/kimsangbaek/Desktop/likelion/env/lib/python3.9/site-packages/django/core/management/__init__.py", line 419, in execute_from_command_line

    utility.execute()

  File "/Users/kimsangbaek/Desktop/likelion/env/lib/python3.9/site-packages/django/core/management/__init__.py", line 413, in execute

    self.fetch_command(subcommand).run_from_argv(self.argv)

  File "/Users/kimsangbaek/Desktop/likelion/env/lib/python3.9/site-packages/django/core/management/base.py", line 354, in run_from_argv

    self.execute(*args, **cmd_options)

  File "/Users/kimsangbaek/Desktop/likelion/env/lib/python3.9/site-packages/django/core/management/base.py", line 398, in execute

    output = self.handle(*args, **options)

  File "/Users/kimsangbaek/Desktop/likelion/env/lib/python3.9/site-packages/django/core/management/base.py", line 89, in wrapped

    res = handle_func(*args, **kwargs)

  File "/Users/kimsangbaek/Desktop/likelion/env/lib/python3.9/site-packages/django/core/management/commands/migrate.py", line 244, in handle

    post_migrate_state = executor.migrate(

  File "/Users/kimsangbaek/Desktop/likelion/env/lib/python3.9/site-packages/django/db/migrations/executor.py", line 117, in migrate

    state = self._migrate_all_forwards(state, plan, full_plan, fake=fake, fake_initial=fake_initial)

  File "/Users/kimsangbaek/Desktop/likelion/env/lib/python3.9/site-packages/django/db/migrations/executor.py", line 147, in _migrate_all_forwards

    state = self.apply_migration(state, migration, fake=fake, fake_initial=fake_initial)

  File "/Users/kimsangbaek/Desktop/likelion/env/lib/python3.9/site-packages/django/db/migrations/executor.py", line 227, in apply_migration

    state = migration.apply(state, schema_editor)

  File "/Users/kimsangbaek/Desktop/likelion/env/lib/python3.9/site-packages/django/db/migrations/migration.py", line 126, in apply

    operation.database_forwards(self.app_label, schema_editor, old_state, project_state)

  File "/Users/kimsangbaek/Desktop/likelion/env/lib/python3.9/site-packages/django/db/migrations/operations/fields.py", line 244, in database_forwards

    schema_editor.alter_field(from_model, from_field, to_field)

  File "/Users/kimsangbaek/Desktop/likelion/env/lib/python3.9/site-packages/django/db/backends/sqlite3/schema.py", line 140, in alter_field

    super().alter_field(model, old_field, new_field, strict=strict)

  File "/Users/kimsangbaek/Desktop/likelion/env/lib/python3.9/site-packages/django/db/backends/base/schema.py", line 608, in alter_field

    self._alter_field(model, old_field, new_field, old_type, new_type,

  File "/Users/kimsangbaek/Desktop/likelion/env/lib/python3.9/site-packages/django/db/backends/sqlite3/schema.py", line 362, in _alter_field

    self._remake_table(model, alter_field=(old_field, new_field))

  File "/Users/kimsangbaek/Desktop/likelion/env/lib/python3.9/site-packages/django/db/backends/sqlite3/schema.py", line 202, in _remake_table

    'default': self.quote_value(self.effective_default(new_field))

  File "/Users/kimsangbaek/Desktop/likelion/env/lib/python3.9/site-packages/django/db/backends/base/schema.py", line 324, in effective_default

    return field.get_db_prep_save(self._effective_default(field), self.connection)

  File "/Users/kimsangbaek/Desktop/likelion/env/lib/python3.9/site-packages/django/db/models/fields/related.py", line 971, in get_db_prep_save

    return self.target_field.get_db_prep_save(value, connection=connection)

  File "/Users/kimsangbaek/Desktop/likelion/env/lib/python3.9/site-packages/django/db/models/fields/__init__.py", line 842, in get_db_prep_save

    return self.get_db_prep_value(value, connection=connection, prepared=False)

  File "/Users/kimsangbaek/Desktop/likelion/env/lib/python3.9/site-packages/django/db/models/fields/__init__.py", line 2486, in get_db_prep_value

    value = self.get_prep_value(value)

  File "/Users/kimsangbaek/Desktop/likelion/env/lib/python3.9/site-packages/django/db/models/fields/__init__.py", line 1825, in get_prep_value

    raise e.__class__(

ValueError: Field 'id' expected a number but got '미지정'.

 

 

다음과 같이 id 필드에 숫자가 필요하다고 나오더라구요.. 그래서 최종적으로 위의 코드와 같이 user와 name의 default=0 이렇게 설정하고 다시 migration, migrate를 진행해도 위와 같이 오류가 해결되지 않고 있습니다.

 

 

구글링 해봤을 때 아예 migrations 파일을 지우라고 하는 경우도 있던데 확실하지가 않아 이렇게 질문을 드리게 되었습니다. 천천히 답변주시면 감사하겠습니다!

 

답변 2

2

이진석님의 프로필 이미지
이진석
지식공유자

안녕하세요.

장고에서 지원하는 관계필드(models.ForengiKey, models.OneToOneField, models.ManyToManyField)들은 데이터베이스에서 기본키(primary key)/외래키(foreign key) 관계로서 표현이 됩니다.

장고 모델에서는 필드 정의 시에 primary_key=True 속성 지정을 통해 기본키 필드를 지정하지 않는 한, 디폴트로 id = models.AutoField 로서 정의가 됩니다. 이는 1부터 1씩 자동증가(auto increment)하는 필드입니다.

그러니, 관계필드에 대해서 값을 지정하실 때에는 1 이상의 자연수를 지정하셔야 하며, 지정된 값은 외래키로서 활용되기에, 외래키에 해당되는 Row가 필히 데이터베이스에 테이블에 있어야 합니다.

Customer 모델에 user 필드를 추가했을 때 값으로 0을 지정했다면, User 모델에 필히 pk가 0인 Row가 있어야 합니다. 그렇지 않다면, 외래키 제약사항에 걸려 마이그레이션에 실패하게 됩니다. pk는 1부터 시작하기에 절대 pk=0인 데이터는 존재할 수 없습니다.

Customer 모델에 user 필드를 추가했을 때 값으로 "미지정" 문자열을 지정했다면, User 모델의 pk은 자연수이기에 자연수 변환을 시도하게 되는 데, "미지정" 문자열은 int("미지정") 로직에서 ValueError가 발생하게 되어, 마이그레이션에 실패하게 됩니다.

모델 필드의 디폴트 값이나 makemigrations 시의 값 지정에서는 그 타입에 맞게 적절히 값을 잘 지정해주셔야 합니다. 마이그레이션 툴은 손쉬운 마이그레이션을 도와주지만, 어떤 값을 지정하는 지에 대해서는 개발자가 필드타입 및 데이터베이스 데이터 상황을 잘 파악하여 적절히 지정해주셔야만 합니다.

아마 아래의 모델 필드 정의를 하신 후에, makemigrations을 하셨기에 관계필드에 "미지정" 값을 지정하는 마이그레이션 파일이 생성이 되었을 것입니다. 그러니 migrate 시에 잘못된 "외래키" 값으로 인해 마이그레이션이 실패하게 되셨을 것이구요.

user = models.OneToOneField(User, null=False, blank=False, on_delete=models.CASCADE, default='미지정')  

user 필드의 디폴트 값을 0으로 변경하셔서, 다시 makemigrations을 하셔서 새로운 마이그레이션 파일을 생성하셨더라도, 앞서 생성된 "미지정" 값이 지정된 마이그레이션 파일이 있기에 - 그럼 현재 2개의 마이그레이션 파일이 생성된 상황 - migrate 명령이 실패하게 됩니다.

python manage.py showmigrations 명령을 통해 마이그레이션 적용 현황을 확인하실 수 있습니다. 미적용된 마이그레이션 파일은 삭제하시고, 모델 내역은 그대로 있으니 다시 makemigrations 명령을 수행해보실 수 있습니다. 절대 적용된 마이그레이션은 삭제하시면 안 됩니다.

1

ajdsasld님의 프로필 이미지
ajdsasld
질문자

강사님 덕분에 오류 해결하고 정상적으로 migrations - migrate 완료했습니다, 정말 감사합니다!

단순히 포기하지 않고 구글링 하면 해결할 수 있을것이라 생각했는데, django의 기본원리와 데이터베이스 구성을 먼저 아는 것이 중요하다는 것을 느낄 수 있었습니다ㅎㅎ

강사님 강의 듣고 제가 맘대로 만들고 있는 프로젝트에 연습을 하고 있었는데, 얼른 데이터베이스 강의를 다 듣고 다시 해봐야겠습니다.

정말 감사합니다:)

ajdsasld님의 프로필 이미지
ajdsasld

작성한 질문수

질문하기