본문 바로가기
Dev Log/Django

[Django] 오픈 소스 기반 웹 소스 코드 'Django Girls Website' 분석기 - 1

by 삽질하는큐 2017. 2. 18.

Django를 책으로도 공부해보고 유투브도 보았고, Toy 프로그램도 만들어보고 있다. 하지만 내가 Django way로 개발하고 있는지에 대해서 의문이 들기 시작해서 다른 이들은 어떻게 코드를 짜고 있는지 뜯어보기로 했다. 생각끝에 찾아낸 것은 장고 걸스(Django Girls, https://djangogirls.org/ ) 공식 홈페이지를 분석하는 것이다. Github에서 오픈 소스로 개발 되고 있고, 사람들이 많이 참여하고 있는 프로젝트여서 보고 배울 만한 것이 많다고 생각했다. Github에는 Fork 기능으로 내 Repository에 복사를 할 수 있기 때문에 해당 소스를 다운 받아서 하나씩 뜯어보기로 했다.



--소스 코드 분석 순서--

먼저 root 폴더가 어딘지를 확인(settings.py가 있는 곳)하고 settings.py를 기준으로 urls.py에 따라서 메인페이지 부터 차근차근 따라가 본다. 일단은 url에서 root에 해당하는 부분 부터 html의 nav에 해당하는 부분(app)을 기준으로 하나씩 뜯어본다.





1. RedirectView

https://docs.djangoproject.com/en/1.10/ref/class-based-views/base/#redirectview


core/urls.py

url(r'^pages/in-your-city/$',
        RedirectView.as_view(url='/organize/', permanent=True)),

url을 설정할 때 곧장 redirect를 하고 싶을 때 RedirectView를 쓰면 된다. 예전에 정해놓았던 url에 대한 설정이 바뀌었는데 이미 URL이 공유가 많이 되었다거나 할 때 404 에러를 줄이기 위해서 쓰는 모양이다. 다른 프로젝트 소스에서도 보통 여러 urls.py 중에 root에 위치하는 것을 볼 수 있었다.

url은 리다이렉트될 url이 전달되고, permanent는 True일 때 Http Status Code가 301, False일 때 302가 된다.





2. {% load staticfiles %} VS {% load static %}

https://docs.djangoproject.com/en/1.10/ref/templates/builtins/#static


templates/core/index.html

{% load staticfiles rev %}
...

결론 부터 말하면 1.10부터는 static만 쓰면 된다. static은 STATIC_ROOT에 설정되어 있는 static file을 읽어오고 staticfiles는 STATICFILES_STORAGE에 설정되어 있는 파일들을 읽어온다. 1.10부터는 이게 간소화되었다.


{% load rev %}에 해당하는 것은 gulp-rev의 장고 버전이다. 

https://github.com/olasitarska/django-gulp-rev

gulp-rev는 static file에 랜덤으로 해시값을 넣어주는 라이브러리다.

예 ) unicorn.css → unicorn-d41d8cd98f.css





3. {% include ... with  %}

https://docs.djangoproject.com/en/1.10/ref/templates/builtins/#include


templates/core/index.html

<div class="upcoming-events">
  <div class="row-container">
    <div class="row">
      {% for event in future_events %}
        {% include 'includes/_event.html' with event=event %}
        {% if forloop.counter|divisibleby:3 %}</div><div class="row">{% endif %}
      {% endfor %}
    </div>
  </div>
  <div class="show-more"><a href="javascript:;">Show all {{ future_events_count }} upcoming events »</a></div>
</div>

template에서 extend와 block의 경우는 상속과 재정의의 개념이라면 아예 다른 html 파일을 포함시키는 것이 include이다. 여기에 with를 같이 쓰면 _event.html에 event를 context로 주입시켜서 해당 html에서도 사용할 수 있다.


templates/include/_event.html

<a href="/{{ event.page_url }}">
    <div class="col-md-4 event"{% if event.photo %} style="background-image: url({{ event.photo.url }})"{% endif %}>
        <div class="overlay">
            <p class="city">{{ event.city }}</p>
            <span class="date">{{ event.date }}</span>
            {% if event.photo_credit %}<p class="credit" rel="{{ event.photo_link }}">Photo credit: {{ event.photo_credit }} (<span rel="https://creativecommons.org/licenses/by-nc-sa/2.0/">CC</span>)</p>{% endif %}
        </div>
    </div>
</a>





4. Model Manager에서 query를 method로 만들기

https://docs.djangoproject.com/en/1.10/topics/db/managers/#adding-extra-manager-methods


core/views.py

def index(request):

    return render(request, 'core/index.html', {
        'future_events': Event.objects.future(),
        'stories': Story.objects.filter(is_story=True).order_by('-created')[:2],
        'blogposts': Story.objects.filter(is_story=False).order_by('-created')[:3],
        'patreon_stats': FundraisingStatus.objects.all().first(),
        'organizers_count': User.objects.all().count(),
        'cities_count': Event.objects.values('city').distinct().count(),
        'country_count': Event.objects.values('country').distinct().count(),
    })

위의 Event.objects.future()를 보면, future()는 장고 프레임워크 모델에서 존재하는 함수가 아니라 새로 정의한 함수인데, 이는 해당 model의 Manager에서 정의할 수 있다.


core/models.py

class EventManager(models.Manager):

    def get_queryset(self):
        return (super(EventManager, self).get_queryset()
                                         .filter(is_deleted=False))

    def public(self):
        """
        Only include events that are on the homepage.
        """
        return self.get_queryset().filter(is_on_homepage=True)

    def future(self):
        return self.public().filter(
            date__gte=datetime.now().strftime("%Y-%m-%d")
        ).order_by("date")

    def past(self):
        return self.public().filter(
            date__isnull=False,
            date__lt=datetime.now().strftime("%Y-%m-%d")
        ).order_by("-date")

class Event(models.Model):
    """중략"""
    objects = EventManager()
    all_objects = models.Manager()  # This includes deleted objects
    """중략"""

future() 함수는 is_on_home_page=True 이면서 date가 미래에 있는 것들을 가져오는 함수라고 보면 된다. Manager를 등록하기 위해서는 objects = EventManager() 라고 써주면 되고, 여기에 해당하지 않는 쿼리를 쓸 때를 대비해서 all_objects = models.Manager()를 따로 명시한 것으로 보인다.





5. @property decorator

이것은 장고라기보단 파이썬 문법이라고 보면 된다. 

patreonmanager/models.py

class FundraisingStatus(models.Model):
    GOAL = 1500

    date_updated = models.DateTimeField(auto_now_add=True)
    number_of_patrons = models.IntegerField()
    amount_raised = models.IntegerField()

    class Meta:
        ordering = ('-date_updated',)

    @property
    def percentage_of_goal(self):
        return int(float(self.amount_raised) / float(self.GOAL) * 100.0)


templates/core/index.html

<div class="support-bar">
  <div class="pledge" style="width: {{ patreon_stats.percentage_of_goal }}%">{{ patreon_stats.percentage_of_goal }}%</div>
</div>

percentage_of_goal 을 보면 된다. 프로퍼티 처럼 쓰고 싶지만, model에 따로 정의하기에는 DB에 필요하지 않은 값이고, view에서 일일이 정리하기에는 호출 횟수가 많을 것이라고 예상되면 @property로 정의하면 쉽게 해결된다.