장고 페어코딩 후

(문서 아래 쪽에 django annoying 관련 사항이 추가되었습니다. 2016.11.30)


어제 회사의 장고 전문가와 페어코딩을 했습니다. 어제 배운 것 중에 모델 매니저 관련 사항을 공유합니다.

  1. 장고에서 ORM 으로부터 레코드 하나를 가져오는 방법에는 get()과 filter().first()가 있습니다. 저는 아이디를 주고 레코드를 하나 가져오는 경우에는 get()을 부르는 것이 맞다고 생각해서 이런 식으로 코딩을 했습니다.

    class SomeView(View):
    def def(self, req, **kwargs):
        try:
            obj = MyObject.objects.get(key=key)
        except:
            obj = None
    

안타깝게도 get()은 해당키의 레코드가 없으면 exception을 올려주시네요. 코딩을 하다보니 try-except 코드가 반복되길래 모델 쪽에 추상 클래스를 하나 만들어드렸습니다.

class SimpleAbstractModel(models.Model):
    class Meta:
        abstract = True
    
    @classmethod
    def get_or_none(cls, **kwargs):
        """
        Same with `obj = Model.objects.filter(xx_id=id).first()` but mode clear.
        """
        try:
            return cls.objects.get(**kwargs)
        except (cls.DoesNotExist, ValueError):
            return None

이제는 이렇게 간단하게 쓸 수 있습니다.

class SomeView(View):
    def get(self, req, **kwargs):
        obj = MyObject.get_or_none(key=key)

하지만, 뭔가 찜찜한 기분이 있었고, 전문가를 만나면 잘했나 못했나 물어봐야겠다고 생각하고 있었습니다.

  1. 어제 페어코딩하면서

여쭤보니, 장고의 모델은 데이터를 담고있는 인스턴스이고, DB와의 통신은 매니저를 쓰는 것이 관례라고 하시더군요. 도움을 받아서 이렇게 고쳤습니다.

아래가 모델 매니저 코드입니다.

class SimpleModelManager(models.Manager):
    def get_or_none(self, **kwargs):
        try:
            qs = self.get_queryset()
            obj = qs.filter(**kwargs).first()
        except (cls.DoesNotExist, ValueError):
            return None
        return obj

아래는 모델 매니저를 적용했을 때의 코드입니다.

class MyModel(models.Model):
    # ...
    objects = SimpleModelManager()

class SomeView(View):
    def get(self, req, **kwargs):
        obj = MyModel.objects.get_or_none(key=key)

어쩐지 장고 스타일에 맞는 느낌이라, 깔끔하고 좋아보입니다.

라인 수에서는 그다지 이득이 없습니다만, 마음속에 있던 찜찜함은 사라졌네요.

  1. 장고 스타일

장고 튜토리얼이나 개론서를 읽을 때, 매니저는 별 쓸데없는 것으로 생각하고 무시했는데, 아마도 모델 클래스를 가볍게 가져가고 모델을 생성하는 코드를 매니저로 독립시킨 것 같습니다. 나름 이유가 있겠지요 :)

더불어 해주신 말씀은

  • 목적에 맞는 명확한 이름의 메서드들을 매니저에 구현하면
  • 뷰에서는 호출하는 메서드들을 주욱 훑어보기만해도
  • 무슨 일을 하는 코드인지 알기 쉽게 된다.

라는 조언을 해주셨습니다.

장고에서는 모델에 무언가를 추가할 때 어디에 코드를 넣을까, 고민한 분들께 도움되기를 바랍니다.

— 2016. 11. 30 추가 —

페이스북 파이썬 그룹에서 주신 답변을 보면 장고 애노잉에서 이미 구현한 사항이라는 말씀이 있습니다. 확인해보니 그쪽의 내부구현에 참조할만한 사항이 있어, 아래에 사용법과 내부코드를 공유합니다.

아래는 구현부입니다.

인자로 클래스를 받고 거기에서 queryset 을 가져오므로 모델매니저를 쓰지 않습니다. _get_queryset 이 클래스를 인자로 받는군요.

from django.shortcuts import _get_queryset
def get_object_or_None(klass, *args, **kwargs):
    queryset = _get_queryset(klass)
    try:
        return queryset.get(*args, **kwargs)
    except queryset.model.DoesNotExist:
        return None

아래는 사용하는 방법입니다. 일반적으로 장고에서 보이던 Model.objects.xxx() 과는 다른 방식으로 호출하게 됩니다.

from annoying.functions import get_object_or_None

def get_user(req, user_id):
    user = get_object_or_None(User, id=user_id)
    if not user:
        ...

이쪽이 마음에 드시는 분들은 참고가 될듯합니다.


댓글

allieus : qureyset.first() 에서는 DoesNotExist 예외가 발생하지 않습니다. None 이 리턴됩니다. https://github.com/django/django/blob/master/django/db/models/query.py#L556 그러므로 get_or_none 메소드를 따로 만드실 필요가 없습니다. :-) - AskDjango ( http://facebook.com/groups/askdjango ) (2016-11-29 17:02:29)

jinto : 앗 이쪽에도 댓글을 달아주셨군요. 페북에도 간단하게 달았습니다만, 다른 조치가 필요없는 상황에서는 first()를 호출하는 것이 저도 맞다고 생각합니다. 감사합니다. (2016-11-29 23:27:26)