2020년을 마치며

탈진한 상반기

상반기는 탈진하도록 몸과 마음을 썼다. 2분기에 들어서는 시점에 탈진 징조를 느꼈고, 6월 중순부터 회복과 충전하는 시간을 가졌다.

  • 내게 익숙하고 편한 몸무게로 감량
  • 치과 치료
  • 요통 치료

한창 크로스핏 할 땐 도무지 도달하지 못했던 몸무게인 67~68kg에 건강하지 못한 방식으로 도달하자 자전거 오래 타다 땅을 딛는 것처럼 몸이 불편했다. 그래서 1분기부터 부담 가지 않는 선에서 1일 1식 하며 체중 감량을 하여 약 4~6개월 만에 4~5kg를 빼고 유지하고 있다. 정형외과에서는 좀 더 경과를 보며 치료를 하되 운동을 재개해도 된다고 하여 내년엔 필라테스를 해볼 생각이다.

마음도 어딘가 고장 난 것 같기도 하고 혹시 성인 ADHD인가 싶어 종합 심리 평가를 받아봤다. 종합 심리 평가에서 성인 ADHD 징후라도 언급해주길 기대했지만 그렇진 않아 괜히 했나 생각했는데, 결과 보고서를 보니 해보길 잘했다. 내가 모르던 강점이나 가능성이 나와서 흥미롭기도 했고, 익히 알고 있던 부분을 재확인하기도 했다. 현재 나의 상태(state)를 살펴본다는(inspect) 점에서 가끔 해볼 만 한 것 같다. 참고로 보고서에 나온 내 강점을 단순무식하게 곧이곧대로 받아들인다면, 나는야 작가 꿈나무.

1일 1식

몸무게 감량하려고 2월부터 1일 1식을 시작했다. 처음엔 상당히 힘들었지만 석 달쯤 되자 몸이 적응했다. 원래 아침을 안 먹기 때문에 아침 공복이 괴롭지 않는 편이고, 아주 엄격하게 1일 1식을 지키진 않아 예상보다 빨리 적응한 것 같다.

  • 일주일에 하루는 먹고 싶은 대로 먹기
  • 여행 가면 1일 1식 안 함
  • 초기엔 저녁에 샐러드 위주로 먹고 1일 1식은 드문드문 함

1일 1식에 몸이 맞춰지자 하루 시작이 가벼워서 좋다. 1일 2식을 할 땐 몰랐고 1일 1식을 유지할 때에도 몰랐는데, 저녁을 푸짐하게 먹거나 야식을 하면 다음 날 몸이 불쾌하게 무거운 걸 확연히 느꼈다. 조금 더 활력이 있긴 한데 크게 체감할 정도는 아니며 불쾌한 무게감은 더 크다. 하지만 하루 한 끼(점심)에 충분한 열량과 영양소를 섭취하지 못하면 다음 날 힘들긴 해서, 점심을 예전에 먹던 한 끼보다 1.2~1.5배 정도 더 먹고 잘 먹어야 한다.

또 다른 변화는 커피와 술에 더 예민해진 것이다. 체력이 떨어져서 예민해졌다 생각했는데, 아무래도 1일 1식으로 체질이 변해서 그런 것 같다. 이제 커피는 하루에 한 잔으로 엄격하게 제한해야 하며, 맥주 500cc만 마셔도 술에 취해서 불편하다. 더 마시면 다음 날 머리가 아프고 땀에 흠뻑 젖은 내복을 입고 있는 것처럼 내 몸이 불편하다.

계획과 다른 하반기 활동

하반기 계획은 크게 세 가지 활동을 계획했다.

  1. 몸과 마음 회복/충전
  2. 목표 두지 않고 부담 없이 아무거나 공부
  3. 지출을 줄이고 느슨한 관계로 시간 많이 안 쓰는 일 하며 2020년 마무리하기

먹고 살아야 하니 3번을 꼼꼼하게 계획했다. 그래서 이노베이션 아카데미에 비상근 개발 멘토로 지원했고, 미뤄둔 집필도 재개했다. 어딘가에 자문하거나 감수하기도 했다. 그런데 코비드19로 이노베이션 아카데미의 비상근 멘토 운영 계획에 차질이 생겨고, 이 활동에서 기대하는 수입은 사실상 사라졌다. 그래서 시간을 들여야 하는 일을 치렀다.

한 프로젝트는 React를 공부하여 React와 React Native로 웹과 앱을 만드는 일이었다. React로 처음 개발하는 데다 Back-end도 포함해서 혼자 구현하다 보니 프로젝트 규모에 비해 시간을 많이 썼지만, 좋은 팀과 협업하여 스트레스 받지 않고 편하게 일했다.

한 프로젝트는 Vue.js로 Front-end 개발하는 일이었다. 분명 시작은 Vue.js 개발이었는데, 어느 시점부터 Front-end이든 Back-end이든 문제를 해결하는 방향으로 일하는 방식이 바뀌었다. 그래서 Ruby on Rails 코드도 보게 됐는데, 내 성향이나 취향에 안 맞는 부분은 여전히 있지만 참 여러모로 예쁘고 우아한 언어와 도구라는 생각을 했다.

모른다고 말하고 틀렸다고 인정하기

예전엔 몰랐는데, 아는 게 깊어질수록 그 분야에 모르는 게 있거나 내가 틀린 걸 인정하는 게 그리 부담스럽지 않아서 좋다. 깊이가 얕을수록 내가 모르는 걸 들키기 싫어하고 틀린 걸 어떡해서든 방어하려 했다. 좀 더 알게 될수록 아는 것만 말하는 게 불편하지 않다. 아는 것만 말해도 손해보지 않거나 불리해지지 않는 상황과 입장이 된 면도 있고.

얕고 넓게 아는 걸 좋아하는 성향과 깊게 잘 알려는 태도 사이에서 아직은 흔들리는데, 깊게 파고드는 매력 중 하나를 이제나마 깨달아서 다행이고 지난 세월은 아쉽다.

손그림

꼬맹이가 미술에 관심과 집중력을 보여서 공통 취미나 관심사를 하나 정도 만들고자 손그림을 취미 생활 중 하나로 시작했다. 힘들고 어렵지만 재밌다. 내년엔 총 네 개 주제를 그리는 걸 목표로 삼았다.


django-filter용 NULLS LAST 정렬 필터.

이 글 내용은 PostgreSQL을 기반으로 하며, 다른 RDBMS 에서는 확인 안 해봤다.

Django ORM으로 NULLS LAST 정렬

Hannal이라는 모델이 있고 name 필드는 null을 허용한다.

class Hannal(models.Model):
    name = models.CharField(max_length=32, null=True, blank=True)
    birthday = models.DateField(null=True, blank=True)

여러 데이터의 name 필드의 값이 다음과 같이 들어갔다고 가정하자.

  • 'abc'
  • 'zyx'
  • None
  • 'lmn'

이제 name 필드로 오름차순(Ascending) 정렬해보자.

Hannal.objects.order_by('name')
  • None
  • 'abc'
  • 'lmn'
  • 'zyx'

결과는 null이 먼저 나온다. 만약 null이 아닌 데이터를 먼저 정렬하고 그 이후에 null인 데이터를 나열하려면 NULLS LAST로 정렬해야 한다.

from django.db.models import F

Hannal.objects.order_by(F('name').desc(nulls_last=True))
  • 'abc'
  • 'lmn'
  • 'zyx'
  • None

우리가 원하는 대로 정렬됐다.

django-filter의 OrderingFilter

이번엔 이를 django-filter에 적용해보자. django-filter에 정렬 필터인 OrderingFilter를 사용하면 간편하게 정렬 필터를 적용할 수 있다.

import django_filters as filters


class HannalFilter(filters.FilterSet):
    ordering = filters.OrderingFilter(
      fields=(
          ('name', 'name', ),
          ('birthday', 'saengil', ),
      ),
    )

자세한 내용은 공식 문서에 나와있지만, 영문 싫어하는 사람도 많고 공식 문서가 썩 친절하진 않으니 설명하겠다.

fields로 모델의 필드와 HTTP Query String에서 넘겨 받을 필드를 짝지어야 한다. 뒤(namesaengil)는 Query string으로 받을 값, 앞(namebirthday)은 모델의 필드/필드표현식을 뜻한다. 보통은 모델 필드명과 동일하게 하는 게 알아보기 좋지만, 다르게 해야 할 경우도 많다.

우선 요청자(client)측에서 다른 이름을 쓰고 싶은 경우이다. Python 관례에 따르면 snake case 표기를 쓰겠지만, Front-end쪽에서는 camel case 표기를 대개 쓴다. 예를 들어 모델 필드명은 birth_day인데 요청자는 굳이 birthDaysaengil을 쓰고 싶은 경우이다.

다른 경우가 더 흔한 경우인데, 모델 관계(model relationship)를 필드 표현식으로 다뤄야 하는 경우이다. 예를 들어 Kay라는 모델이 있고 Hannal 모델과 1:N 관계를 맺고 있다고 하자.

class Kay(models.Model):
    name = models.CharField(max_length=32, null=True, blank=True)


class Hannal(models.Model):
    name = models.CharField(max_length=32, null=True, blank=True)
    birthday = models.DateField(null=True, blank=True)
    related = models.ForeignKey('Kay', on_delete=models.CASCADE)

그리고 Kay 모델의 name 필드를 기준으로 정렬한 Hannal 모델의 데이터를 가져오려면 다음과 같이 한다.

Hannal.objects.order_by('related__name')

여기서 related__name을 django-filter 에서도 사용할 수 있다.

class HannalFilter(filters.FilterSet):
    ordering = filters.OrderingFilter(
      fields=(
          ('name', 'name', ),
          ('birthday', 'saengil', ),
          ('related__name', 'kayname', ),
      ),
    )

이렇게 fields를 지정하면 ordering키의 값으로 name, saengil, kayname을 사용할 수 있다. Django REST framework(이하 DRF)에 적용하면 URL을 ?ordering=name로 하여 name 필드에 대해 오름차순 정렬하거나 ?ordering=-name로 하여 내림차순 정렬한다. 이 뿐만 아니라 여러 개 필드를 정렬할 수도 있다. 예를 들어 name 필드는 오름차순, birthday 필드는 내림차순으로 정렬하고자 한다면 HTTP Query String 으로 ?ordering=name,-birthday라고 하면 된다.

OrderingFilter에 NULLS LAST 적용

편하다. 근데 django-filter 2.4.0 버전 기준까지는 NULLS LASTNULLS FIRST를 지원하지 않는다. 그러므로 따로 구현해야 한다. OrderingFilter가 제공하는 기능은 그대로 쓰되 QuerySet 만들 때 null 정렬만 추가하면 되니 이 클래스를 상속받아 쓴다.

from django.db.models import F
import django_filters as filters


class NullsLastOrderingFilter(filters.OrderingFilter):
    def filter(self, qs, value):
        if not value:
            return qs
        for _v in value:
            is_desc = _v.startswith('-')
            field = self.param_map.get(_v[1:] if is_desc else _v)
            if not field:
                continue
            if is_desc:
                qs = qs.order_by(F(field).desc(nulls_last=True))
            else:
                qs = qs.order_by(F(field).asc(nulls_last=True))
        return qs


class HannalFilter(filters.FilterSet):
    ordering = NullsLastOrderingFilter(
      fields=(
          ('name', 'name', ),
          ('birthday', 'saengil', ),
          ('related__name', 'kayname', ),
      ),
    )

    class Meta:
        model = Hannal
        fields = ('name', 'birthday', )

위에서 설명한 내용을 모두 반영한 코드이다. filter() 메서드는 django-filter 가 넘겨받은 QuerySet 객체이다. DRF에 연동해 사용한다면 DRF가 이런 저런 조치를 취한 QuerySet일테고, 사용자가 만든 filter 들을 거친 QuerySet이기도 하다.

NULLS FIRST를 적용하려면 nulls_last 대신 nulls_firstTrue로 지정하면 된다.