utamaro’s blog

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

bulmaのpanelのヘッダーにボタンを付ける

bulmaというcssフレームワークでpanelというものがあります。

このpanelではpanel-headingというのがあるのですが、このヘッダーに編集ボタンは用意されていません。

この記事ではbulmaのpanelのヘッダーに編集ボタンを付ける方法を紹介します。

panelについてのドキュメントはこちらです。

https://bulma.io/documentation/components/panel/

最終的にこのようなものができます。

f:id:miyaji-y26:20181030075329p:plain

html

panelのテンプレートに編集を追加しています。

<nav class="panel">
    <p class="panel-heading is-size-6 sp-panel-heading">
        管理
        <a class="button is-small sp-right" href="edit">
            <span>編集</span>
            <span class="icon is-small">
                <i class="fas fa-cog"></i>
            </span>
        </a>
    </p>
    <a class="panel-block" href="">
        <span class="panel-icon">
            <i class="fas fa-flag" aria-hidden="true"></i>
        </span>
        <span class="is-size-7">お気に入り</span>
    </a>
</nav>

追加している部分はこの部分です。

<a class="button is-small sp-right" href="edit">
    <span>編集</span>
    <span class="icon is-small">
        <i class="fas fa-cog"></i>
    </span>
</a>

css

cssは殆どをbulmaで補っているので、2つ追加するだけです。

.sp-panel-heading {
    display: flex;
    align-items: center;
}

.sp-panel-heading .sp-right {
    margin-left: auto;
}

bulmaのbutton is-smallでも、高さが合わずに管理という文字がずれます。

そのため、flexの指定とalign-items: centerで調整しています。

ボタンを右寄せするためにmargin-left: auto;をいれています。

注意

margin-left: auto;を入れていますが、ヘッダーに入れる文字によってはレイアウトが崩れる恐れがあります。

その場合はflex: 1;などで調整するのが良いでしょう。

画像の上に文字列を表示する方法

htmlとcssを使って、画像にマウスを置くと文字が出てくる部品の作り方を解説します。

今回javascriptは使用しません。また、scssも使いません。もちろんwebpackとかビルドは必要ないです。

最終的に↓のようなものを作れます。 f:id:miyaji-y26:20181027202015g:plain

htmlのコード

htmlのコードはこのようになっています。

<div>
    <div class="sp-image-wrap">
        <img src="https://bulma.io/images/bulma-banner.png" alt="">
        <span class="sp-description">Bulma is an open source CSS framework based on Flexbox and built with Sass. It's 100% responsive, fully modular, and available for free.</span>
    </div>
    <span>Bulma: a modern CSS framework based on Flexbox</span>
</div>

imgのsrcにはサンプルとしてbulmaというcssフレームワークの画像を使っています。

今回の実装には関係ないですが、とても使いやすく、便利なコンポーネントが揃っています。

htmlでイメージ部分と、画像の下に出す文字列を分けています。

sp-descriptionをimgの上に表示されているように見せます。

cssのコード

cssの書き方はたくさんあると思いますが、ひとまずコードを載せます。

.sp-image-wrap {
    position: relative;
}

.sp-image-wrap:before {
    position: absolute;
    content: '';
    background-color: #c3c3c3a3;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    visibility: hidden;
    z-index: -1;
}

.sp-image-wrap .sp-description {
    position: absolute;
    top: 0;
    bottom: 0;
    height: 100%;
    width: 100%;
    padding: 16px;
    overflow-y: scroll;
    visibility: hidden;
    z-index: -1;
    transform: translateY(-10px);
    transition: transform .3s ease;
}

.sp-image-wrap:hover img {
    filter: blur(5px);
}
.sp-image-wrap:hover:before {
    visibility: visible;
    z-index: 10;
}
.sp-image-wrap:hover .sp-description {
    visibility: visible;
    z-index: 11;
    transform: translateY(0px);
}

機能を実装する上で工夫した点です。

  • sp-image-wraprelativeにして、その子要素のsp-descriptionabsoluteにした
  • hover時に上から降りてくるようにtransformtransitionを設定した
  • sp-image-wrapbeforeにオーバーレイを設定し、.sp-image-wrap:hover:beforeとすることでhover時にbeforeを表示するようにした
  • そのままオーバーレイを表示すると、背景画像が残ってしまって見えづらいためfilter:blurを使ってモザイク処理した
  • display:noneを使うのではなく、アニメーションすることも考えてvisibilityを仕様した

stackoverflowのapiをpythonで使ってみた

stackoverflowのoauthを試してみたので、そのときにやったことを紹介します。

基本的なステップについてはすべてドキュメントにかかれていたので、試す際に書いたコードを載せます。

ドキュメントはこちらです。

https://api.stackexchange.com/docs/authentication#scope

import requests
import json

client_id = '12345'
client_secret = 'xxxxxxxxxxxxxxx'

scope = 'read_inbox'
redirect_uri = 'http://localhost'

def step1():
    step1 = requests.get(
        'https://stackoverflow.com/oauth',
        {
            'client_id': client_id,
            'scope': scope,
            'redirect_uri': redirect_uri,
        }
    )
    print(step1)
    print(step1.url)

def step2(code):
    step2 = requests.post(
        'https://stackoverflow.com/oauth/access_token/json',
        {
            'client_id': client_id,
            'client_secret': client_secret,
            'code': code,
            'redirect_uri': redirect_uri,
        }
    )
    print(step2)
    obj = json.loads(step2.text)
    access_token = obj.get('access_token')
    expires = obj.get('expires')
    print(access_token, expires)
    '{"access_token":"xxxxxxxxxxxxxxx","expires":86400}'

if __name__ == '__main__':
    step1()
    # step2()

step1について

step1ではcodeというものを取得します。

requests.getで取得した文字列の中に、↓のようなurlが含まれているので、そのurlをブラウザで開きます。

https://stackoverflow.com/users/login?xxxxxxxxxxxxxxxxxx

Django等で作成している場合は、urlにリダイレクトする感じで良いはずです。

ブラウザで開くと、stackoverflowのログイン画面が開きます。

ユーザーがアプリケーションのアクセスを許可すると、リダイレクトurlへリクエストが来ます。

サンプルのコードは、サーバーサイドの実装はしていないので、ブラウザのurlを見ます。

ここで取得したcodeをコピーして、step2を実行します。

step2

step2は簡単です。

step1で取得したcodeをpostで送ると、access_tokenとexpireを取得できます。

このaccess_tokenを使って、ユーザーが持っている情報を取得できます。

DjangoではSocialAuth用のテーブルにaccess_tokenを保存していたので、DBに保存しても良いでしょう。

そして、有効期限が切れたらもう一度tokenを発行してもらう形で対応できると思います。

おまけ

認証が必要ないapiもあります。

stackoverflowでログインが不要な処理はできます。

ドキュメントはこちらです。

https://api.stackexchange.com/docs

例えば、回答一覧を取得するなら↓で実行できます。

def get_answers():
    url = 'https://api.stackexchange.com/2.2/answers?order=desc&sort=activity&site=stackoverflow'
    r = requests.get(url)
    print(r.text)

Djangoのテンプレート内でcloudinaryのディレクトリを指定して画像を表示する

Djangoを使って、cloudinaryで保存している画像を表示する場合、{% cloudinary %}を使用します。

その際に、ファイル名を指定することができるのですが、ファイル名のみだとディレクトリ分けしていた場合表示されません。

そこでなんとかディレクトリを指定して、画像を表示したいと思いました。

そこで取った方法が以下の書き方です。

{% cloudinary public_id crop="fill/dir" class="is-rounded" %}

これで出力されるurlは以下のようになります。

http://res.cloudinary.com/hqarhdoek/image/upload/c_fill/dir/image_name

ポイントはcrop部分です。

もともとはcropの値はfillだけで、その場合のurlは以下のようになります。

http://res.cloudinary.com/hqarhdoek/image/upload/c_fill/imange_name

ちなみに、folder="dir"としてもだめでした。

問題点

widthやheightを指定する場合、以下のようなurlになります。

'https://res.cloudinary.com/demo/image/upload/c_fill,h_150,w_100/sample.jpg' 

すると、fill/dirのようになってもうまく動きません。

あくまでもファイル名の前の区切りがフォルダとしなければなりません。

その場合は、おとなしくサーバーサイドでファイル名をつくりましょう。

herokuに上げて動作を確認してると、imageが404になったりしたので、この方法は間違っているのかもしれません。

その検証はしてなくて、他のバグを直してるときについでに解決していたので未検証です。

ただ、ローカルでは動いていたのでいまいち原因がわかってないです。

Djangoでログイン後に特定の処理を挟む方法

表題の通りですが、Djangoでログイン後に一回だけ処理を入れたいときの方法を考えて見ました。

一つしか思いつかなかったのですが、紹介したいと思います。

やることをは簡単で、ログイン後のリダイレクトに一つだけviewを挟む形です。

まずは、settings.pyに以下の項目を追加します。

LOGIN_REDIRECT_URL = 'before-home'

これを追加すると、ログイン後にbefore-homeにリダイレクトされます。

before-homeというのはurls.pyにnameで設定した名前を入れます。

次にview.pyを修正します。

class GoToHomeView(RedirectView):
    pattern_name = 'home'

    def get_redirect_url(self, *args, **kwargs):
        request = self.request
        if request.user.is_authenticated:
            # ログインしている場合は、なにか処理する
            # is_authenticatedを入れなくても良いですが、ログインしてるとは限らない
            self.execute(request.user)
        return super().get_redirect_url(*args, **kwargs)

    def execute(self, user):
        # なにか処理

RedirectViewについては説明は↓に記載されています。

https://docs.djangoproject.com/ja/2.1/ref/class-based-views/base/#redirectview

コードの例では、pattern_nameにhomeを指定していて、リダイレクトするとhomeが呼ばれます。

get_redirect_urlをオーバーライドしており、ログイン後に処理が実行されるようになります。

if request.user.is_authenticated:でセーフコードを入れる理由としては、ログインしなくてもbefore-homeが実行される可能性があるからです。

@require_loginを入れても同じことができると考えたのですが、以下のコードでつまづきました。

    def decorator(view_func):
        @wraps(view_func)
        def _wrapped_view(request, *args, **kwargs):
            if test_func(request.user):

このコードはfrom django.contrib.auth.decorators内のコードで、login_requiredが実行されたときに呼ばれます。

その際にRedirectViewではuser値が取得できないのでエラーになります。

f:id:miyaji-y26:20181023080759p:plain

エラーの内容

うまい解決方法が見つかったら、別の記事に書きたいと思います。

Djangoでtwitter認証をしたあとにツイートする方法

前提として、social-auth-app-djangoを使ってtwitterでの認証が実装できている状態とします。

class TwitterPost(TemplateView):
    post_api = "https://api.twitter.com/1.1/statuses/update.json"

    def post(self, request, *args, **kwargs):
        social_user = UserSocialAuth.objects.filter(user=request.user).first()
        # ここでバリデーションを入れるべきでしょう。
        twitter_oauth = OAuth1Session(
            SOCIAL_AUTH_TWITTER_KEY,
            SOCIAL_AUTH_TWITTER_SECRET,
            social_user.tokens.get('oauth_token'),
            social_user.tokens.get('oauth_token_secret'),
        )
        params = {"status": 'twitter post api test.', "lang": "ja"}
        result = twitter_oauth.post(self.post_api, params)
        return redirect('home')

バリデーションのコードは省いています。

これが実行されると、ログインしているユーザがtwitter post api test.とツイートします。

次にhtmlのテンプレートを(特別なことはやっていませんが)htmlのどこかに入れます。

{% if user.is_authenticated %}
<form action="{% url 'twitter_post' %}" method="post">
    {% csrf_token %}
    <button type="submit">
        <span>twitterで共有</span>
    </button>
</form>
{% endif %}

settings.pyに以下のpathを追加します。

    path('twitter_post', TwitterPost.as_view(), name='twitter_post'),

これでOK

Djangoのtemplate内でクエリパラメータを設定する方法

Djangoのtemplate内で以下のように書いているときに、リンク先をクエリパラメータで渡すときの方法です。

<li><a href="{% url 'home' %}">next</a></li>

渡したいパラメータはpとします。値は整数です。

まず、一番簡単な方法を紹介します。

<li><a href="{% url 'home' %}?p=1">next</a></li>

この方法はシンプルですが、パラメータが増えたときに大変なことになります。

具体的には以下のようにどこからどこまでがパラメータとして設定しているのか分かりづらくなります。

{% url 'home' %}?p=1&s=23&d=201810&f=90

この問題を解決するための少し、面倒な方法を紹介します。

以下のようなディレクトリ構成でtemplate_tags/tags.pyというファイルを作成します。

.
├── manage.py
├── project
│   ├── __init__.py
│   ├── assets
│   ├── django.log
│   ├── settings
│   ├── templates
│   ├── urls.py
│   └── wsgi.py
└── template_tags
    ├── __init__.py
    └── tags.py

tags.pyの内容は以下のように書きます。

from django import template

register = template.Library()


@register.simple_tag
def query_transform(request, **kwargs):
    updated = request.GET.copy()
    for k, v in kwargs.items():
        updated[k] = v

    return updated.urlencode()

これはDjangoのcustom templateという機能で使えるものです。

https://docs.djangoproject.com/en/2.1/howto/custom-template-tags/

次に、settings.pyを修正します

librariesのところから追加します。

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR,  'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
            'libraries': {  # ← これを追加する
                'tags': 'template_tags.tags',
            }
        },
    },
]

最後に、template(html)を修正します。

どこでも良いのですが、以下の記述を書きます。

私の場合は<body>直下に書いています。

<body>
    {% load tags %}
    ...

tagsはlibraries.tagsの名称です。つまり、template_tags.tagsで登録しているタグを読み込むということです。

ではタグを使ってリンクを書き直します。

<li><a href="{% url 'home' %}?{%query_transform request p=1%}">next</a></li>

だいぶスッキリしました。

複数のパラメータを渡したい場合は{%query_transform request p=1 q=2%}となります。