2016년 5월 14일 토요일

django ImageField upload_to 설정하기

django에서 파일 또는 이미지 업로드 기능을 사용하려면 장고 모델의 ImageField, FileField에서 사용하는 upload_to argument를 설정해야 합니다.
upload_to에 설정한 값은 DEFAULT_FILE_STORAGE에 설정된 Storage 클래스에 있는 save() 메소드로 전달되어 파일을 저장할 때 사용되는데, Storage 클래스에 관한 내용은 staticfiles 에 대한 내용을 포스팅할 때 자세하게 다루어 보겠습니다.

upload_to를 설정하기 전에

upload_to에 값을 설정하기 전, settings.py에 정의되어야 하는 값이 두가지가 있습니다.

  1. MEDIA_ROOT - 업로드할 파일을 저장하는 root 폴더
  2. DEFAULT_FILE_STORAGE - 업로드할 파일을 저장하는 방식을 정의하는 Storage 클래스

DEFAULT_FILE_STORAGE 값은 기본값으로 FileSystemStorage 클래스를 사용하기 때문에, settings.py 에 명시하지 않아도 됩니다.

upload_to 설정 방법

upload_to argument는 두가지 방법으로 설정이 가능합니다. 상황에 맞게 사용하시면 되지만, 개인적으로 2번 방법으로 사용하는 것을 추천합니다.

  1. string으로 설정

    class MyModel(models.Model):
        upload = models.FileField(upload_to='uploads/')
        upload_with_date = models.FileField(upload_to='uploads/%Y/%m/%d/')

    첫번째는, upload_to에 string 값을 설정하는 방법입니다. 이 방법을 사용하면, 업로드하는 파일은 {MEDIA_ROOT}/{upload_to}/{업로드 하는 파일명}의 형태로 저장됩니다.
    또한, datetime 모듈을 import하지 않고 %Y%m%d의 형태로 날짜 또는 시간을 입력할 수 있습니다. 이렇게 저장되는 이유는 위에 간단하게 말씀드렸다시피 upload_to의 값이 Storage 클래스로 전달된 후 Storage 클래스가 string을 처리하기 때문입니다.

  2. callable로 설정

    def user_directory_path(instance, filename):
        return 'user_{}/{}'.format(instance.name, filename)
    
    class MyModel(models.Model):
        name = models.CharField(max_length=20, blank=True, null=True)
        upload = models.FileField(upload_to=user_directory_path)

    두번째는, callable을 설정하는 방법인데, 메소드를 정의한 후에 그 메소드를 설정하는 방법입니다. 이 방법을 사용하면 uuid, random, datetime 모듈 등을 활용하여 업로드하는 파일의 경로와 파일명의 customizing을 간편하게 할 수 있습니다.

upload_to를 설정하는 두가지 방법을 알아보았는데, callable이 좋은 이유는 파일이 저장되는 경로를 변경하는 경우에 데이터베이스 migration이 필요하지 않다는 점입니다. 모델 필드에 직접 string값을 입력하게되면, upload_to값을 변경할 때마다 migration을 해야하지만, callable방식을 사용하면 코드가 수정되었을 때 migration을 하지 않아도 수정된 경로가 반영됩니다.

파일명에 instance id, instance pk 추가하기

upload_to를 사용할 때, 해당 인스턴스의 id 또는 pk를 추가하려고 하는 경우가 있습니다. 이 때, upload_to 메소드를 아래와 같이 정의한다면,

def user_directory_path(instance, filename):
    return 'user_{}/{}'.format(instance.id, filename)

업로드한 파일은 user_None/{filename}의 형태로 instance.id 값이 None으로 저장되게 됩니다. 이렇게 저장되는 이유는 이미지를 저장하는 시점에 instance가 생성되지 않아 id 값이 존재하지 않기 때문입니다. 따라서, instance id를 파일명에 사용하려면 모델의 save() 메소드를 아래와 같이 오버라이딩 해야합니다.

def save(self, *args, **kwargs):
      if self.id is None:
          temp_image = self.upload
          self.upload = None
          super().save(*args, **kwargs)
          self.upload = temp_image
      super().save(*args, **kwargs)

instance가 아직 생성되지 않아 id를 가지고 올 수 없을 때에는, 업로드하려는 파일을 임시로 저장한 후 save() 메소드를 호출하여 instance를 생성시키고, 생성된 id를 이용하여 파일을 업로드하는 로직입니다.

upload_to 에 대하여 더 자세하게 알고싶으신 분들은 장고 공식문서 https://docs.djangoproject.com/en/1.9/ref/models/fields/#filefield를 참고하시면 됩니다.

감사합니다.