utamaro’s blog

誰かの役に立つ情報を発信するブログ

61.DjangoBasicAppsのCommentsを読んでみる

urls.pyには目新しいものがなかったのでスルーします。

urlを使ってbooksと同じように作成されていたのでスルーします。

1つだけわかったことは、viewの引数にはviews.py内のメソッドを入れるということ。

views.pyを確認する

コメント時の日付をバリデーションする方法を見つけました。

get_object_or_404というショートカットが用意されていることにも気が付きました。

このショートカットは便利そうですね。request.userは認証していない場合にNoneになるはずですから、404になる。

djangorestframeworkを使う場合は、serializerを使ったり、IsAuthenticatedを使うことで対応ができると思います。

認証に関しても

DELTA = datetime.datetime.now() - datetime.timedelta(
    minutes=getattr(settings, 'COMMENT_ALTERATION_TIME_LIMIT', 15)
)


def comment_edit(request, object_id, template_name='comments/edit.html'):
    comment = get_object_or_404(Comment, pk=object_id, user=request.user)

    if DELTA > comment.submit_date:
         return comment_error(request)

フォームを使ったバリデーションです。

if request.method == 'POST':
    form = CommentForm(request.POST, instance=comment)
    if form.is_valid():
        form.save()
        return redirect(request, comment.content_object)

CommentFormは↓のようになっています

class CommentForm(ModelForm):
    class Meta:
        model = Comment
        exclude = ('content_type', 'object_pk', 'site', 'user', 'is_public',
            'user_name', 'user_email', 'user_url', 'submit_date', 'ip_address',
            'is_removed',)

個人的には、formは最終的に見た目をカスタマイズすることになるので、Formからタグは作成しないようにしています。

バリデーション目的のみ使うのが良いと思います。

バリデーションのみ行う場合、↓cerberusというライブラリがあるので参考にしてみてください。

http://docs.python-cerberus.org/en/stable/

load i18nという翻訳tag

template内に↓のような記述がありました。

{% load i18n %}

このタグを使うと、翻訳タグを使うことができます。

{% trans "Text" %}

実際に使う際は↓のドキュメントを参考にするとよい

https://docs.djangoproject.com/ja/2.1/topics/i18n/translation/#how-django-discovers-language-preference

60.DjangoBasicAppsという数年前のプロジェクトのBooksを読んでみる

DjangoBasicAppsのリポジトリは↓です。

https://github.com/nathanborror/django-basic-apps

その中にあるBooksを読んで、Djangoの書き方を確認したいと思いました。

https://github.com/nathanborror/django-basic-apps/tree/master/basic/books

views.pyが見当たらない

見当たらないです。

views.pyが見つからないとか…なんででしょうか。

views.pyがないので、urls.pyを確認してみる

object_detailというのがどこにあるのかみつかりませんでした。

url(r'^genres/(?P<slug>[-\w]+)/$',
    view='object_detail',
    kwargs=genre_list,
    name='book_genre_detail',
),

genre_list自体は↓のように作成されていた。

genre_list = {
    'queryset': Genre.objects.all(),
}

この書き方はあまり使わないですね。

これだとページング処理が入らないし、データを固定にするならtemplateにそのまま書いてもよいです。

views.pyがないので拡張しづらいですね。

それから、as_viewがなくてなぜ大丈夫なのかわかりません。

templateを確認する

templateの書き方を見れるのは嬉しいですね。

これまでどうやって書けばよいのか手探りだったのでわかった気がします。

baseとなるtemplateは↓です。

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
   "http://www.w3.org/TR/html4/strict.dtd">

<html lang="en">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <title>{% block title %}{% endblock %}</title>
</head>
<body id="{% block body_id %}{% endblock %}" class="{% block body_class %}{% endblock %}">
  <div id="body">
    {% block body %}
        <div class="content_title">
            {% block content_title %}{% endblock %}
        </div>
        <div class="content">
            {% block content %}{% endblock %}
        </div>
    {% endblock %}
  </div>
</body>
</html>

blockは4つ定義されています。

  • title
  • body
    • content_title
    • content

bookのbaseは↓です

{% extends "base.html" %}
{% block body_class %}books{% endblock %}

book_list.htmlは↓です

{% extends "books/base_books.html" %}


{% block title %}Books{% endblock %}


{% block content_title %}
  <h2>Books</h2>
  {% include "books/_nav.html" %}
{% endblock %}


{% block content %}
    <table class="book_table">
        <tr>
            <th>Title</th>
            <th>Authors</th>
            <th>Progress</th>
        </tr>
    {% for book in object_list %}
        <tr>
            <td class="title"><a href="{{ book.get_absolute_url }}">{{ book.title }}{% if book.prefix %}, {{ book.prefix }}{% endif %}</a></td>
            <td class="authors">{% for author in book.authors.all %}<a href="{{ author.get_absolute_url }}">{{ author.full_name }}</a>{% if not forloop.last %}, {% endif %}{% endfor %}</td>
            <td class="publisher"><a href="{{ book.publisher.get_absolute_url }}">{{ book.publisher.full_title }}</a></td>
        </tr>
    {% endfor %}
    </table>
{% endblock %}

content_titleの中にincludeがあります。

includeが使えるのは初めて知りました。

jinja2でもincludeを使えるので当たり前といえば当たり前なんですが。。。

_nav.htmlは↓のようになっていました。

<div id="section_nav">
    <ul>
        <li class="music"><a href="{% url music_index %}">Music</a></li>
        <li class="movies"><a href="{% url movie_list %}">Movies</a></li>
        <li class="books"><a href="{% url book_list %}">Books</a>
            <ul>
                <li class="genres"><a href="{% url book_genre_list %}">Genres</a></li>
                <li class="publishers"><a href="{% url book_publisher_list %}">Publishers</a></li>
            </ul>
        </li>
    </ul>
</div>

階層構造でtemplateを表現するとこのようになります。

  • base.html
    • book_list.html
      • _nav.html

こうやって見るとずいぶんと効率的に書けるとわかります。

ただし、htmlのデザインを書いて、共通化できる箇所を考え、コーディングする技術が必要です。

頻繁にデザインが変わる際は共通化せずに実装し、最終的にリファクタリングする段階で共通化するのが良いでしょう。

headやfooter部分、ナビゲーション部分を共通化するのが良いと思いました。

59.DjangoのでTwythonを使ったOAuthのサンプルを読んでみた。

参考にしたプロジェクトは↓です。

https://github.com/ryanmcgrath/twython-django

Twythonを使ってTwitterAPIを利用することができるようです。

An example Django application to showcase how to use OAuth with Twitter in Django using Twython.

と書かれていたので、OAuthの実装がわかるかな?と思って読んでみようと思いました。

それから、自分で実装できるようになると応用が効くと考えたからです。googleの認証にも使えそうですし。

requirements.txtがリポジトリ内に見当たらなかったので不満です。

なんで用意されていないのでしょうか。

setup.pyを見てみると↓のように書かれていました。

install_requires=['twython>=3.1.0', 'django'],

小さなプロジェクトというこのがわかります。

ちなみに、twythonのリンクは↓です。

https://twython.readthedocs.io/en/latest/usage/install.html

views.py

クラスベースの書き方ではないのですが、どうなのでしょうか。

良いか悪いかわかりませんが、こういうやり方もあるっていうのはドキュメントを読んで理解しているので良しとします。

https://github.com/ryanmcgrath/twython-django/blob/master/twython_django_oauth/views.py

OAuthの開始時に必要なcallbackや、認証後のurlを設定している箇所です。

    # Request an authorization url to send the user to...
    callback_url = request.build_absolute_uri(reverse('twython_django_oauth.views.thanks'))
    auth_props = twitter.get_authentication_tokens(callback_url)

    # Then send them over there, durh.
    request.session['request_token'] = auth_props

    request.session['next_url'] = request.GET.get('next',None)
    
    return HttpResponseRedirect(auth_props['auth_url'])

認証後にTwitterApiを使うところまで

コードを読んだ感じだと↓の順番で処理をすると良いみたいです。

  1. sessionからoauth_token, oauth_token_secretを取得する。
  2. twythonを使ってTwitterAPIを利用するための準備をする
  3. authorized_tokenをtwitterAPIを使って取得する
    oauth_token = request.session['request_token']['oauth_token']
    oauth_token_secret = request.session['request_token']['oauth_token_secret']
    twitter = Twython(settings.TWITTER_KEY, settings.TWITTER_SECRET,
                      oauth_token, oauth_token_secret)

    # Retrieve the tokens we want...
    authorized_tokens = twitter.get_authorized_tokens(request.GET['oauth_verifier'])
  1. authorized_tokenからscreen_nameを取得して、テーブル上にユーザーが登録されているか確認する
    1. 登録されている場合はuserオブジェクトを取得する
    2. 登録されていないなら、データを追加する
try:
    user = User.objects.get(username=authorized_tokens['screen_name'])
except User.DoesNotExist:
    # We mock a creation here; no email, password is just the token, etc.
    user = User.objects.create_user(authorized_tokens['screen_name'], "fjdsfn@jfndjfn.com", authorized_tokens['oauth_token_secret'])

DBに保存するのはoauth_token_secretで良いみたいです。

social-auth-app-djangoを使ったときは以下のデータがDB上に保存されます。

(一部マスキングしています)

{
    "auth_time": 1542545735,
    "id": 97379597xxxxxxxx620, 
    "access_token": {
        "oauth_token": "97379597xxxxxxxx620-Kn5iKEyyyyyyybzfc1QuFmyKt",
        "oauth_token_secret": "0TBySf6hjiSKqHnog4Qm6xZyyyyyyyyykmDaaos3CPxkLcn", 
        "user_id": "kdkdiem9597xxxxxxxx620", 
        "screen_name": "xxxxxxxxx"
    }
}

サンプルではoauth_tokenは保存していなかったので、どちらが正なのか判断できません。

Twythonのインスタンスを作成する際にoauth_tokenが必要かなと思うのですが、、、

ログイン時のsessionに含まれていると考えると必須ではないのかもしれません。

もしかすると、↓のコードでsaveとあるので、oauth_tokenが保存されているのかもしれません。(動かしていないのでわからないです)

profile = TwitterProfile()
profile.user = user
profile.oauth_token = authorized_tokens['oauth_token']
profile.oauth_secret = authorized_tokens['oauth_token_secret']
profile.save()

タイムラインを表示する

タイムラインを表示します。

def user_timeline(request):
    """An example view with Twython/OAuth hooks/calls to fetch data about the user in question."""
    user = request.user.twitterprofile
    twitter = Twython(settings.TWITTER_KEY, settings.TWITTER_SECRET,
                      user.oauth_token, user.oauth_secret)
    user_tweets = twitter.get_home_timeline()
    return render_to_response('tweets.html', {'tweets': user_tweets})

でもこれって、ログインしてない場合どうなるのでしょうか。

request.user.twitterprofileがNoneになるとエラーになるのですが、、、

ちなみに、htmlはこのようになっていました。

{% for tweet in tweets %}
    {{ tweet.text }}
{% endfor %}

Djangoのサンプルを覗いて使い方を勉強してみたときのメモ

django-realworld-example-appを参考にする

このプロジェクトは一番最初に参考にしたものです。

Djangoのバージョンが1.10.0と古い(記事を書いている段階での最新は2.1.5が最新)です。

https://github.com/gothinkster/django-realworld-example-app

このプロジェクトで学べること

  • ForeignKeyフィールドの作り方
author = models.ForeignKey(
    'profiles.Profile', on_delete=models.CASCADE, related_name='articles'
)
  • serializerクラスの使い方
    • method_nameというのを知りました。
    • SerializerMethodFieldを使ったget_xxxの方法を知りました。
updatedAt = serializers.SerializerMethodField(method_name='get_updated_at')
favorited = serializers.SerializerMethodField()

def get_favorited(self, instance):
    request = self.context.get('request', None)

    if request is None:
        return False

    if not request.user.is_authenticated():
        return False

    return request.user.profile.has_favorited(instance)

# ↓が実行されます
# def has_favorited(self, article):
    # """Returns True if we have favorited `article`; else False."""
    # return self.favorites.filter(pk=article.pk).exists()
  • models.pyの作り方
    • 多対多のリレーションの書き方. xxx_by
    • orderingを指定したデフォルトのソード方法
favorites = models.ManyToManyField(
    'articles.Article',
    related_name='favorited_by'
)
ordering = ['-created_at', '-updated_at']
  • urls.pyの階層構造
url(r'^api/', include('conduit.apps.profiles.urls', namespace='profiles')),
  • jsonレスポンスの拡張方法
    • ページネーションを想定したレスポンス
class ConduitJSONRenderer(JSONRenderer):
    charset = 'utf-8'
    object_label = 'object'
    pagination_object_label = 'objects'
    pagination_object_count = 'count'

    def render(self, data, media_type=None, renderer_context=None):
        if data.get('results', None) is not None:
            return json.dumps({
                self.pagination_object_label: data['results'],
                self.pagination_count_label: data['count']
            })

        # If the view throws an error (such as the user can't be authenticated
        # or something similar), `data` will contain an `errors` key. We want
        # the default JSONRenderer to handle rendering errors, so we need to
        # check for this case.
        elif data.get('errors', None) is not None:
            return super(ConduitJSONRenderer, self).render(data)

        else:
            return json.dumps({
                self.object_label: data
            })

Djangoの管理画面でBootStrap臭がひどいので消すことにした。

管理画面のBootStrap臭がひどいので消すことにしました。

アドミン機能はとても便利です。

DB内のデータをモデルを使ってCRUD操作できるので、編集が簡単です。

でも、個人的にはDB内のデータを操作する際は、専用のクライアントツールを使いたい派です。

postgresqlならPSequelを使うし、mysqlならworkbenchを使っています。

このどちらも管理画面を使用するよりも便利なので、あまり管理画面を使用しないのです。

あと、リリースしたあとに/adminを開いたときの残念感が嫌いです。

(urls.pyを修正すれば分からなくできるかもしれませんが、、、)

Bootstrap臭がすごいですよね、あのページ。あれはいらないです。

初期設定から管理画面に関する箇所を消す

settings.pyを編集します。

このページを見てみると、django.contrib.adminが管理画面に関するappというのがわかります。

https://docs.djangoproject.com/es/2.0/ref/contrib/admin/

また、管理画面は4つほど依存性を持っているようです。

この4つは管理画面で使用されているぐらいなので、アプリケーション開発に役立つ機能があると予想できます。

(実際使えます。)

というわけで、↓が修正するpythonファイルです。

INSTALLED_APPS = [
    # 'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
# from django.contrib import admin
from django.urls import path

urlpatterns = [
    # path('admin/', admin.site.urls),
]

migrateを実行します。

django.contrib.adminをコメントアウトしてmigrateを実行した結果です。

                       List of relations
 Schema |               Name                |   Type   | Owner
--------+-----------------------------------+----------+--------
 public | auth_group                        | table    | xxxxxx
 public | auth_group_id_seq                 | sequence | xxxxxx
 public | auth_group_permissions            | table    | xxxxxx
 public | auth_group_permissions_id_seq     | sequence | xxxxxx
 public | auth_permission                   | table    | xxxxxx
 public | auth_permission_id_seq            | sequence | xxxxxx
 public | auth_user                         | table    | xxxxxx
 public | auth_user_groups                  | table    | xxxxxx
 public | auth_user_groups_id_seq           | sequence | xxxxxx
 public | auth_user_id_seq                  | sequence | xxxxxx
 public | auth_user_user_permissions        | table    | xxxxxx
 public | auth_user_user_permissions_id_seq | sequence | xxxxxx
 public | django_content_type               | table    | xxxxxx
 public | django_content_type_id_seq        | sequence | xxxxxx
 public | django_migrations                 | table    | xxxxxx
 public | django_migrations_id_seq          | sequence | xxxxxx
 public | django_session                    | table    | xxxxxx

django.contrib.adminをコメントアウトしないでmigrateした結果です。

django_admin_logが消えているのが確認できます。

というかこれぐらいしか消えません。

                       List of relations
 Schema |               Name                |   Type   | Owner
--------+-----------------------------------+----------+--------
 public | auth_group                        | table    | xxxxxx
 public | auth_group_id_seq                 | sequence | xxxxxx
 public | auth_group_permissions            | table    | xxxxxx
 public | auth_group_permissions_id_seq     | sequence | xxxxxx
 public | auth_permission                   | table    | xxxxxx
 public | auth_permission_id_seq            | sequence | xxxxxx
 public | auth_user                         | table    | xxxxxx
 public | auth_user_groups                  | table    | xxxxxx
 public | auth_user_groups_id_seq           | sequence | xxxxxx
 public | auth_user_id_seq                  | sequence | xxxxxx
 public | auth_user_user_permissions        | table    | xxxxxx
 public | auth_user_user_permissions_id_seq | sequence | xxxxxx
 public | django_admin_log                  | table    | xxxxxx
 public | django_admin_log_id_seq           | sequence | xxxxxx
 public | django_content_type               | table    | xxxxxx
 public | django_content_type_id_seq        | sequence | xxxxxx
 public | django_migrations                 | table    | xxxxxx
 public | django_migrations_id_seq          | sequence | xxxxxx
 public | django_session                    | table    | xxxxxx

admin以外はすべて便利に使えるものなので、残すのが良いと思いました。

Javaの個人的読みやすいコードフォーマット

個人的読みやすいコードフォーマット【Java版】

このサイトがわかりやすく、見やすいかと思いました。

future-architect.github.io

↑のページにかかれていること以外で、自分なりに気をつけていることをまとめます。

変数の定義時には型を省略しない

int val1 = 1,
    val2 = 2;

ではなく、型を付けて宣言する。

int val1 = 1;
int val2 = 2;

ifの括弧と中括弧について

if(val==1)
{
    //
}
else
{
    //
}

ではなく、↓のように書く

if (val == 1) {
    // 
} else {
    //
}

for

for(int i=0; i<2; i++)
{
    //
}

ではなく、↓のように書く。(=,<等の前後にスペースを入れています)

for (int i = 0; i < 2; i++) {
    //
}

builder

SampleModel model = new SampleModel.builder()
    .val1(1)
    .val2(2)
    .build();

SampleModel model = new SampleModel.builder()でbuilderを使ってインスタンスを生成することを表す。

↓の例のように、builder()で改行すると、SampleModel model = new SampleModelで何をするのか分かりづらい。 (次の行を読んで、builderと初めて分かるため)

SampleModel model = new SampleModel
    .builder()
    .val1(1)
    .build();

コメントの内容について

コメントは、その処理で何をやっているかではなく、どのような処理をしているのかを書く。

if (val == 1) {
    // valが1の場合の処理
}

ではなく、↓のように書く。

if (val == 1) {
    // valが1の場合、〇〇の処理を行う
}

if (!result) {
    // 前処理で〇〇かXXの処理が失敗している場合
}

このようにすれば、コメント部分をヒントに後続処理を読むことができる。

1行に書きすぎない

sampleModel.setVal(Integer.parseInt(sameModel.getVal()));
Integer sameModelVal = Integer.parseInt(sameModel.getVal());
sampleModel.setVal(sameModelVal);

デバッグする場合、2つの処理に分けたほうがやりやすいと考えているからです。

空行はインデントを入れる

if (true) {
    int a = 1;
    // このような空行にはインデントを入れる.
    int b = 2;
}
class Sample() {
    public void method1() {
        //
    }
    // こことか
    public void method2() {
        //
    }
}

インデントを入れなかった場合は、何か処理を入れようとした場合にタブを打つことになる。

とても面倒というわけでも無いけど、コーディング時に気をつけれるところなので気をつける。

タブはスペース4にする

タブでもいいんじゃない?と思う人がいると思うけど、環境によってタブがスペースに変えられたり、タブ1がスペース8で表示されることがある。

そうすると、とても読みづらい。

かなり読みづらい。

gitのcommit時に無駄なdiffが発生したりもする。

スペース2にすると、インデントでの区切りが分かりづらく、どこからどこまでがforのブロックなのかなど分かりづらい。

個人的にはスペース4が良いと考えています。

同じ行にコメントを書く場合

int val = 1;  // hoge

スペース2つ開けて//を入れる。

そのあとにスペース1つ入れて、コメント本文を書く。

これはpythonのコメント方法を参考にしました。

省略名を使う場合は必ずコメントを入れる

int mkdir = "mkdir";  // make directory

/** name converter. nをXXに変換する  
 * @param nm 
 *  n: name. 名前
*/
public void nmConv(String n) {
    //
}

省略しなければならない場合は無いと思いますが、省略する場合は必ずコメントを入れる。

後で見たときや、別の人が読んだときに必ずわからなくなるから。

どのように利用するフィールドなのかコメントする

例えば↓のようなModelクラスでidnumberというフィールドがあるとします。

class SampleModel() {
    private String id;
    private String number;
}

このクラスだけではidとnumberがどのように使われるのかわかりません。

つまり、このクラスを使用してgetId()と書くのか、getNumber()と書くのかわからないのです。

ですが、それぞれにコメントがついていれば判断ができるというわけです。

youtubeのパルクールオススメ動画まとめ

パルクールについて簡単に

パルクールについてはこちらのページを参考にするとよいと思います。

詳しく書かれているので分かりやすいかと思います。

パルクールとは | 日本パルクール協会 | Japan Parkour Association

簡単にまとめると、↓です。

  • パルクールとは、走る・跳ぶ・登るといった移動に重点を置く動作を通じて、心身を鍛えるスポーツ
  • 機能性、体力、バランス、空間認識力、敏捷性、コーディネーション能力、正確さ、コントロール、創造的視点などを鍛える
  • パルクールの実践を続けていくことで、実践者はどのような環境においても自由に、かつ機能的に動くことのできる心身を得る

YouTubeで公開されているパルクールの動画をまとめました。

www.youtube.com

www.youtube.com

www.youtube.com

www.youtube.com

www.youtube.com

www.youtube.com

www.youtube.com

ヴィークルエンドという電撃文庫の小説があります。

この小説はパルクールを題材にしている小説で個人的にオススメの小説でもあります。

a.r10.to