취미삼아 배우는 프로그래밍

혼자서 만드는 마감정산 시스템(Django) 본문

코드 자가리뷰(장고)

혼자서 만드는 마감정산 시스템(Django)

Nadure 2020. 8. 7. 21:53

장고 코딩하다 막혀서 풀어보는

#리뷰

자가 코드 리뷰

 

(영감을 얻어 가셨으면 합니다.)

1. 모델(전체)

from django.db import models
from jsonfield import JSONField
from django.db.models import F, Sum, Count, Case, When
from django.db.models.signals import pre_save
from django.dispatch import receiver
from .helper import areaHelper
from django.utils import timezone

# Create your models here.

class Month(models.TextChoices):
    january     = '1월'
    febuary     = '2월'
    march       = '3월'
    april       = '4월'
    may         = '5월'
    june        = '6월'
    july        = '7월'
    augus       = '8월'
    september   = '9월'
    october     = ' 10월'
    november    = ' 11월'
    december    = ' 12월'


class CoatCompanyModel(models.Model):
    objects = models.Manager()
    coated_company      = models.CharField(max_length=25, verbose_name='코팅업체')
    def __str__(self):
        return self.coated_company


class ProductList(models.Model):
    objects = models.Manager()
    product = models.CharField(max_length=30, verbose_name='품명')
    
    def __str__(self):
        return self.product
        

class DataOneRow(models.Model):
    # TODO > Validation 추가해야함
    objects = models.Manager()
    product_name = models.ForeignKey(ProductList, on_delete=models.CASCADE,)
    w1_value = models.IntegerField(default=0, verbose_name='w1')
    w2_value = models.IntegerField(default=0, verbose_name='w2')
    w3_value = models.IntegerField(default=0, verbose_name='w3')
    h_value = models.IntegerField(default=0, verbose_name='h')
    
    A_value_quantity = models.IntegerField(default=0, verbose_name='A 수량')
    B_value_quantity = models.IntegerField(default=0, verbose_name='B 수량')
    C_value_quantity = models.IntegerField(default=0, verbose_name='C 수량')

    A_Area = models.DecimalField(verbose_name='A 면적', max_digits=6, decimal_places=2, default=0)
    B_Area = models.DecimalField(verbose_name='B 면적', max_digits=6, decimal_places=2, default=0)
    C_Area = models.DecimalField(verbose_name='C 면적', max_digits=6, decimal_places=2, default=0)

    def __str__(self):
        return f'{self.product_name}/ {self.w1_value}+{self.w2_value}+{self.w3_value} x {self.h_value}'
    

class DetailedData(models.Model):
    objects = models.Manager()
    data_rows = models.ManyToManyField(DataOneRow)
    first_writed = models.DateTimeField(verbose_name='최초작성시간', auto_now_add=True)
    last_modified = models.DateTimeField(verbose_name='마지막변경시간', auto_now=True)
    changes_log = models.CharField(verbose_name='변경내역', max_length=50)

    def __str__(self):
        return str(self.last_modified) + ' / ' + self.changes_log
    
    def sumHelper(self, field):
        val = self.data_rows.values(field).aggregate(Sum(field))
        return val[field+'__sum']

    @property
    def all_rows(self):
        return ', '.join([x.name for x in self.data_rows.all()])

    @property
    def a_value_quantity_sum(self):
        return self.sumHelper('A_value_quantity')

    @property    
    def b_value_quantity_sum(self):
        return self.sumHelper('B_value_quantity')

    @property
    def c_value_quantity_sum(self):
        return self.sumHelper('C_value_quantity')
    
    @property
    def A_Area_sum(self):
        return round(self.sumHelper('A_Area'), 2)
    
    @property
    def B_Area_sum(self):
        return round(self.sumHelper('B_Area'), 2)
    
    @property
    def C_Area_sum(self):
        return round(self.sumHelper('C_Area'), 2)
    
    @property
    def AandC_Area_sum(self):
        return round(self.sumHelper('A_Area') + self.sumHelper('C_Area'), 2)


class TheOneDistrict(models.Model):
    objects = models.Manager()
    # basedata            = models.ForeignKey(BaseData, on_delete=models.CASCADE, )
    category = models.CharField(max_length=20, verbose_name="카테고리", default="기본값")
    company = models.CharField(max_length=20, verbose_name="업체명")
    location_name = models.CharField(max_length=40, verbose_name="현장명")
    district = models.CharField(max_length=20, verbose_name="동")
    detailData = models.ForeignKey(DetailedData, on_delete=models.CASCADE, null=True, blank=True)

    def __str__(self):
        return f'({self.company} . {self.location_name} . {self.district})'


class BaseData(models.Model):
    objects = models.Manager()
    coated_company = models.ForeignKey(CoatCompanyModel, on_delete=models.CASCADE)
    one_district   = models.OneToOneField(TheOneDistrict, on_delete=models.CASCADE, )
    finishing_date = models.DateField(verbose_name='작업시기', default=timezone.now)

    def __str__(self):
        return (self.coated_company.coated_company + '/' 
            + str(self.finishing_date.year) + '년/' + str(self.finishing_date.month) + '월' + ' ' 
            + str(self.finishing_date.day)+ '일/' + str(self.one_district))

    @property
    def worked_date(self):
        return self.finishing_date.day
    
    @property
    def date_strftime(self):
        return f'{self.finishing_date.year}년 {self.finishing_date.month}월 {self.finishing_date.day}일'

    
def get_yearList():
    vals = BaseData.objects.all().values('finishing_date')
    a_list = [ a.get('finishing_date', None).year for a in list(vals) ]
    return list(set(a_list))


# DataOneRow를 저장하기 전 면적을 계산하고 값을 넣음
@receiver(pre_save, sender=DataOneRow)
def post_delete(sender, instance, **kwargs):  
    instance.A_Area = areaHelper(instance) * instance.A_value_quantity
    instance.B_Area = areaHelper(instance) * instance.B_value_quantity
    instance.C_Area = areaHelper(instance) * instance.C_value_quantity


 

모델은 나름 직관적으로 보이기 위해 위에서부터 아래로 쭉 내려오게끔 했다.(깔끔하진 않은것 같다.)

(Month 클래스는 사실 언제고 쓸 일이 있을까 싶어서 냅뒀다.)

 

> 코팅업체, 제품 모델이 가장 상위에(가장 독립적이니) 배치했고,

> 이 둘을 통해 제품 데이터의 한 줄을 만든다(DataOneRow).

> 그리고 이 한 줄을 모두 모아서(ManyToMany) DetailedData를 만든다.

> 그리고 이 DetailedData에 이름표인 Company, Location, District 를 붙여서 한 동(TheOneDistrict)를 구성한다.

> 헌데, 이에대한 또다른 꼬리표가 필요했다.

> Queryset을 넘겼을 시, 데이터를 분류해서 템플릿단으로 풀어내기가 빡빡했던 것,

> 그래서 이어 붙이는 김에 BaseData를 만들고 TheOneDistrict와 OneToOne 필드로 구성했다.

> OneToOne필드는 특이하게도 일대일이라는게 명확히 정해진 것이기 때문에, 역으로 돌아가는것도 가능하다.

> TheOneDistrict.objects.basedata.objects.all() 이런식으로,

> 그리고 계산되는 면적은 애초에 사용자가 넣는 값이 아니니, 저장시 post_save를 통해 저장 직전 계산해서 끼워넣는 방식으로 해두었다.(이거 되게 꿀인것 같다.)

> 그치만, 이렇게 저장된 면적들의 합을 구했어야 했다.

> 따로 필드로 만들자니 어차피 한 번 쓰는 값이고 또 빈번히 바뀌는 값들에 대해서 매번 저장을 해주게 될 것 같았다.

> 그래서 property로 구성해서 일회용 값을 불러오게끔 했다.

def sumHelper(self, field):
        val = self.data_rows.values(field).aggregate(Sum(field))
        return val[field+'__sum']

이 sumHelper는 data_rows라는 ManyToMany 필드 안에서의 명시된 field 값을 모두 더해주는 함수다. 이렇게 되면 example.AandC_Area_Sum 만으로 A와 C의 면적 합들에 대한 값이 상수로 튀어나온다. 이거좀 잘짠거같다.

> 상당히 Property가 편한게, 여러모로 쓰임새가 많다. 예를 들면,

class BaseData(models.Model):
    objects = models.Manager()
	# ...
    finishing_date = models.DateField(verbose_name='작업시기', default=timezone.now)

    @property
    def worked_date(self):
        return self.finishing_date.day
    
    @property
    def date_strftime(self):
        return f'{self.finishing_date.year}년 {self.finishing_date.month}월 {self.finishing_date.day}일'

BaseData 모델 부분에선, 몇 일 작업분인지 뷰단에 표시하려고

@property

def worked_date(self):

    return self.finishing_date.day

를 넣었다.

 

템플릿단에서 막 어렵게 고민할 것 없었다.

어차피 모든 데이터는 모델에서 나오는거다. 그러니 모델에다가 property 를 붙여주고 땡 여깃소 하면 턱하고 나온다.

여깃소

 

 

 

2. View

뷰쪽에서는 딱히 특별한건 별로 없다.

 

딱 하나 있는데,

데이터들이 너무 아랫쪽으로 확 하고 모이는 구조이기도 하고,

따로 날자별로 묶자니 그만큼 큰 의미가 있는거는 아니어서 재정렬을 시도했다.

어떻게냐면

6일에 두 개를 넣는게 그렇게 힘들었다.

이런식으로 나오는건데,

아무래도 일자별로 묶지 않으면, 위와같은 형태로 템플릿단에 넘기기가 쉽지않다.

그래서 아예 틀자체를 새로 재정렬했다.

 

#쿼리 재정렬

class qs_helper(object):
    def __init__(self):
        self.values = {}
    
    def add_values(self, dat):
        res = self.values.get(dat.finishing_date.day, [])
        res.append(dat)
        self.values[dat.finishing_date.day] = res

이놈이다.

 

어떻게 쓴고하면,

query_set = BaseData.objects.filter(
            finishing_date__range=[f"{year}-{month}-01",
                                   f"{year}-{month}-{end_day}"])

        data_obj = qs_helper()
        for qs in query_set:
            data_obj.add_values(qs)

        context = {
            'month':month,
            'year':year,
            'coated_company':coat_company,
            'data_obj':data_obj.values,
        }

쿼리셋들을 루프를 돌리면서 넣는데, 이를

data_obj ={
    2:[qs1, qs2]
}

위와 같은 형태의 dict형태로 만들어버린다. 그렇게 해서 이걸 템플릿단에서 사용해 루프를 돌린다.

 

 

Comments