LoginSignup
5
7

More than 5 years have passed since last update.

Django Tips ーDjangoでランキングサイトを作るー

Last updated at Posted at 2017-07-01

はじめに

初めてDjangoでアプリを作ったので、その時にあった課題や解決法の備忘録です。
今回はAtCoderのランキングサイトを作りました。
MVCモデル(DjangoだとMTV?)を全体像を理解するのに苦労しました。
参考(http://mimumimu.net/blog/2011/11/21/python-django-%E3%81%97%E3%81%A6%E3%81%BF%E3%81%9F%E3%80%82-mtv-model-%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6/)

0. Webアプリの構想を練る

そもそもどんなサービスを作りたいか
->
* Model - どんなModelを作るか、どんなModelのリレーションがあるか
* View - どんなページを作るか、どのModelを使うか
...

1. Djangoでアプリを作成

$ django-admin startproject project-name
$ cd project-name
$ python manage.py startapp app-name

2. Templateをダウンロードする

HTML,CSS,JavaScriptを書くのがめんどくさかったので、Bootstrapのテンプレートをダウンロードする。

3. テンプレートを運用する

Djangoでは、HTMLを/project/app/templatesフォルダに入れて、CSS,JavaScriptを/project/static/appフォルダに入れる。

HTMLファイルの処理:

-BaseとなるHTMLファイル-
ナビゲーションバーや、読み込むファイルを{% static 'project/app/file' %} として書き込む。
<title>の中身に{% block title %} {% endblock %}、
<body>の中身に{% block body %} {% endblock %}を書く。

-ExtendするHTMLファイル-
HTMLファイルの<title>の中身を{% block title %} {% endblock %}、
HTMLファイルの<body>の中身を{% block body %} {% endblock %}の中に書く。

4. model.pyを編集

0.で考えたModelを実装する。

今回の場合は、デフォルトではなく、自分のUserでログインできるようにしたことがポイント。
models.py (一部抜粋)

from django.contrib.auth.models import (BaseUserManager, AbstractBaseUser)

class UserManager(BaseUserManager):
    def create_user(self, username, email, password, **extra_fields):
        now = timezone.now()
        if not email:
            raise ValueError('Users must have an email address.')
        email = UserManager.normalize_email(email)
        user = self.model(
            username=username,
            email=email,
            is_active=True,
            last_login=now,
            date_joined=now,
            **extra_fields
        )
        user.set_password(password)
        user.save()
        return user

    def create_superuser(self, username, email, password, **extra_fields):
        user = self.create_user(username, email, password)
        user.is_active = True
        user.is_staff = True
        user.is_admin = True
        user.is_superuser = True
        user.save(using=self._db)
        return user


class User(AbstractBaseUser):
    username = models.CharField(_('username'), max_length=30, unique=True,)
    arc_user_name = models.CharField(_('arc name'), max_length=15, blank=True)
    email = models.EmailField(verbose_name='email address', max_length=255, unique=True)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)
    is_admin = models.BooleanField(default=False)
    is_superuser = models.BooleanField(default=False)
    date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
    delete = models.BooleanField(default=0)
    score = models.IntegerField(default=0)
    main_language = models.CharField(max_length=15, default='')
    objects = UserManager()

    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = ['email']

    def email_user(self, subject, message, from_email=None):
        send_mail(subject, message, from_email, [self.email])

    def has_perm(self, perm, obj=None):
        return True

    def has_module_perms(self, app_label):
        return self.is_admin

    def get_short_name(self):
        "Returns the short name for the user."
        return self.arc_user_name

    def __str__(self):
        return self.username

5. views.pyを編集

  • 基本的にクラスベース汎用ビューを用いて書いた。
  • Userの登録や、結果の投稿はCreateView, 他はTemplateView。
  • LoginRequiredMixinを用いることで、ログインしているUserのみが閲覧できるようにする。
  • 外部のライブラリは一括でatcoder_ranking.commons.librariesで管理すると、コードが綺麗になる。
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import TemplateView, CreateView
from atcoder_ranking.commons.libraries import *
from .models import *


class IndexView(LoginRequiredMixin, TemplateView):
    template_name = 'index.html'

    def get(self, _, *args, **kwargs):
        users = User.objects.all()
        results = Result.objects.all().select_related('user')
        for user in users:
            user.score = len(results.filter(user=user)) * 100
            language_list = [result.result_language for result in results.filter(user=user)]
            if language_list != []:
                # もっとも数が多い言語を主言語として選択
                user.main_language = Counter(language_list).most_common(1)[0][0]
            user.save()

        context = super(IndexView, self).get_context_data(**kwargs)
        context['users'] = User.objects.all().order_by('-score')[: 3]

        return render(self.request, self.template_name, context)

6. url.pyを編集

作ったviews.pyに対して作る。

from django.conf.urls import url
from django.contrib import admin

import ranking.views as ranking_view

urlpatterns = [
    # admin
    url(r'^admin/?', admin.site.urls),

    # top page
    url(r'^$', ranking_view.TopView.as_view()),

    # ranking
    url(r'^ranking/?$', ranking_view.IndexView.as_view()),
    url(r'^ranking/result.png$', ranking_view.plotResults),
    url(r'^ranking/create/$', ranking_view.CreateUserView.as_view()),
    url(r'^ranking/problems/$', ranking_view.AtCoderProblemsView.as_view()),
    url(r'^ranking/get_problems/$', ranking_view.GetProblemsView.as_view()),
    url(r'^ranking/posts/$', ranking_view.PostsView.as_view()),
    url(r'^ranking/posts/(?P<posts_id>[0-9]+)/$', ranking_view.PostsDetailView.as_view()),
    url(r'^ranking/create_posts/$', ranking_view.CreatePostsView.as_view()),
    url(r'^ranking/login/$', ranking_view.LoginView.as_view(), name='login'),
    url(r'^ranking/logout/$', ranking_view.logout, name='logout')
]

7. test.pyを編集

そのまま書くもよし、Seleniumを使うもよし。
factory_boyを使ってModelオブジェクトを生成するテストを作った。

factory.py

from atcoder_ranking.commons.libraries import *

from ranking.models import *


class UserFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = User
    username = factory.Sequence(lambda n: 'testing_user{}'.format(n))
    arc_user_name = factory.Faker('name')
    email = factory.Sequence(lambda n: 'testuser{}@gmail.com'.format(n))
    password = factory.PostGenerationMethodCall(
        'set_password', 'ranking_password')

test.py

class IndexViewTest(TestCase):
    @classmethod
    def setUpTestData(cls):
        cls.user = UserFactory.create()

    def setUp(self):
        self.client = ServiceTestTool.login(Client(), IndexViewTest.user, 'ranking_password')

    def test_index_ranking(self):
        # create 10 testusers
        users = [UserFactory.create() for i in range(10)]
        # create posts for testusers:
        result1 = [ResultFactory.create(user=users[1]) for i in range(30)]
        result2 = [ResultFactory.create(user=users[2]) for i in range(20)]
        result3 = [ResultFactory.create(user=users[3]) for i in range(40)]
        result4 = [ResultFactory.create(user=users[4]) for i in range(10)]
        result5 = [ResultFactory.create(user=users[5]) for i in range(5)]
        result6 = [ResultFactory.create(user=users[6]) for i in range(50)]

        response = self.client.get('/ranking/')

        self.assertContains(response, users[1].username)
        self.assertContains(response, users[3].username)
        self.assertContains(response, users[6].username)
        self.assertNotContains(response, users[2].username)
        self.assertNotContains(response, users[4].username)
        self.assertNotContains(response, users[5].username)
        self.assertNotContains(response, users[0].username)

8. testを動かしてみる

$ python manage.py test

9. ページを動かす

$ python manage.py runserver

スクリーンショット 2017-07-01 14.44.58.png

こんな感じで表示されれば完成!

5
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
7