개발 일기

Django FileField upload_to Custom 야매 적용기

친구들안녕 2021. 7. 12. 13:06

서론

필자의 개인 프로젝트 간 사용했던 내용을 저장 및 정리 용도로 쓰는 페이지입니다.

찾으시는 내용이 없을 수 있습니다.

Crawler -> Crwaler 오타가 있습니다.

 

FileField upload_to Custom

먼저 filefield의 upload_to 페이지를 보자.

기본적으로 upload_to는 아래와 같이 model 딴에서 간단하게 사용이 가능하다.

class MyModel(models.Model):
    # file will be uploaded to MEDIA_ROOT/uploads
    upload = models.FileField(upload_to='uploads/')
    # or...
    # file will be saved to MEDIA_ROOT/uploads/2015/01/30
    upload = models.FileField(upload_to='uploads/%Y/%m/%d/')

 

 

그러나 필자의 상황으로는 이렇다.

각 회사는 Crawler Model 생성 시 Crwaler Model의 왜래키를 가지고 나머지는 빈 값으로 자동 생성된다.

각 회사의 사진들을 하나의 모델에서 받기 위해 한 개의 Cralwer Photos에 각 회사의 외래키를 때려 박았다.

각 회사의 사진을 저장하기 위해서는 다르게 적용되는 upload path가 필요하다.

 

 

이해를 돕기 위한 Model 코드 더보기(Custom이 완성된 코드입니다)

더보기
class CrwalingModel(TimeModel):
    enter_name = models.CharField(max_length=50)

    def __str__(self):
        return self.enter_name

    class Meta:
        ordering = ['created_at']


class CrwalingBaseModel(models.Model):
    enter = models.OneToOneField(CrwalingModel, on_delete=models.CASCADE)
    company_code = models.CharField(max_length=1000, blank=True)
    location = models.CharField(max_length=50, blank=True)
    url = models.SlugField(max_length=1000, blank=True)
    upload_to_path = models.CharField(max_length=2000, blank=True)


    def __str__(self):
        return self.enter.enter_name

    class Meta:
        abstract = True


@receiver(post_save, sender=CrwalingModel)
def create_Post(sender, instance, created, **kwargs):
    if created:
        if instance not in ['SaraminInfo', 'JobKoreaInfo', 'JobPlanetInfo', 'KreditJobInfo']:
            SaraminInfo.objects.create(enter=instance)
            JobKoreaInfo.objects.create(enter=instance)
            JobPlanetInfo.objects.create(enter=instance)
            KreditJobInfo.objects.create(enter=instance)


class SaraminInfo(CrwalingBaseModel):
    # name = models.CharField(max_length=7, default='saramin')
    pass


class JobKoreaInfo(CrwalingBaseModel):
    # name = models.CharField(max_length=8, default='jobkorea')
    pass


class JobPlanetInfo(CrwalingBaseModel):
    # name = models.CharField(max_length=9, default='jobplanet')
    pass


class KreditJobInfo(CrwalingBaseModel):
    # name = models.CharField(max_length=9, default='kreditjob')
    pass

# 경로: 회사이름 -> 크롤링회사 -> 코드 -> 날짜
# 이미지이름: 회사이름_회사코드_기타내용.png
def crwaling_photo_path(instance, filename):
    if instance.jobkorea_info:
        return '{0}/{1}'.format(instance.jobkorea_info.upload_to_path, filename)
    elif instance.saramin_info:
        return '{0}/{1}'.format(instance.saramin_info.upload_to_path, filename)
    elif instance.jobplanet_info:
        return '{0}/{1}'.format(instance.jobplanet_info.upload_to_path, filename)
    elif instance.kreditjob_info:
        return '{0}/{1}'.format(instance.kreditjob_info.upload_to_path, filename)

class CrwalingPhotos(TimeModel):
    photo = models.ImageField(upload_to=crwaling_photo_path, max_length=1000)
    saramin_info = models.ForeignKey(SaraminInfo, on_delete=models.RESTRICT, blank=True, null=True)
    jobkorea_info = models.ForeignKey(JobKoreaInfo, on_delete=models.RESTRICT, blank=True, null=True)
    jobplanet_info = models.ForeignKey(JobPlanetInfo, on_delete=models.RESTRICT, blank=True, null=True)
    kreditjob_info = models.ForeignKey(KreditJobInfo, on_delete=models.RESTRICT, blank=True, null=True)

 

 

그럼 과연 django에는 upload_to를 어떻게 커스텀해서 적용할까?

 

이하 django filefield 예시

def user_directory_path(instance, filename):
    # file will be uploaded to MEDIA_ROOT/user_<id>/<filename>
    return 'user_{0}/{1}'.format(instance.user.id, filename)

위와 같이 instance와 filename가 넘어오는데 instance는 model에서 생성된 instance 내용물들을 말한다

그래서 그 내용물의 필드들, 왜래키의 필드들에 접근할 수 있다.

 

 

그러면 살짝 바꿔서 테스트해보자

def user_directory_path(instance, filename):
    # file will be uploaded to MEDIA_ROOT/user_<id>/<filename>
    return 'user_{0}/%Y/%m/%d/{1}'.format(instance.user.id, filename)

이 코드의 결과로는 날짜가 들어가지 않고 %Y/%m/%d가 문자열로 들어간다.

그러므로 time 라이브러리로 직접 넣어줘야 날짜가 들어간다.

 

 

 

time을 따로 적용해야 하는 걸 알았으니 직접 적용해보자

필자는 path를 따로 만들어 db에 저장 후, db path를 이미지에 적용하도록 했다.

또한 Crawler Model 생성 시 각 회사가 자동 생성되므로 그 회사에 해당하는 코드를 넣었다.

path = os.path.join(BASE_DIR) + '/media/search_job/' + company_name + '/jobkorea/' + time.strftime(
            "/%Y/%m/%d/")
jobkorea = JobKoreaInfo.objects.get(enter=self.enter)
jobkorea.company_code = company_code
jobkorea.upload_to_path = path
jobkorea.save()
CrwalingPhotos.objects.create(jobkorea_info=jobkorea, photo=image)

 

 

회사에 대한 upload_path를 생성 후, CrawlerPhotos의 Custom Upload Path를 만들어보자

def crwaling_photo_path(instance, filename):
    if instance.jobkorea_info:
        return '{0}/{1}'.format(instance.jobkorea_info.upload_to_path, filename)
    elif instance.saramin_info:
        return '{0}/{1}'.format(instance.saramin_info.upload_to_path, filename)
    elif instance.jobplanet_info:
        return '{0}/{1}'.format(instance.jobplanet_info.upload_to_path, filename)
    elif instance.kreditjob_info:
        return '{0}/{1}'.format(instance.kreditjob_info.upload_to_path, filename)

class CrwalingPhotos(TimeModel):
    photo = models.ImageField(upload_to=crwaling_photo_path, max_length=1000)
    saramin_info = models.ForeignKey(SaraminInfo, on_delete=models.RESTRICT, blank=True, null=True)
    jobkorea_info = models.ForeignKey(JobKoreaInfo, on_delete=models.RESTRICT, blank=True, null=True)
    jobplanet_info = models.ForeignKey(JobPlanetInfo, on_delete=models.RESTRICT, blank=True, null=True)
    kreditjob_info = models.ForeignKey(KreditJobInfo, on_delete=models.RESTRICT, blank=True, null=True)

한 번의 Crawlerphoto당 한 개의 회사, 한 개의 photo만 들어갈 것이므로(나머지 회사는 null)

crawling_photo_path에 조건문으로 instance.~~~_info가 생성되었다면 그 데이터에서 저장된 path를 불러와

upload_path와 filename을 적용하도록 만들었다.(참고로 filename은 db 저장하는 곳에서 적용한다.)

 

 

결론

필자의 코드를 바탕으로 설명하느라 좀 길어졌는데

간단하게 줄이자면 upload_to에는 생성된 instance와 filename이 넘어오는데

필자의 경우는 path를 직접 db에 넣어서 그 경로를 불러와서 file 저장 path로 쓰는 방식을 사용했다.

 

다른 경우가 있나 잠깐 찾아봤지만 다른 방법이 안보이길래 이런 야매 방식으로 업로드 경로를 지정했다.