rukurxの日記

自分の日々の作業や調べたことのメモ

DjangoのモデルでHaving句を使う

ユーザと趣味モデルを定義して、趣味が3つ以上ある既婚者を取得する例。

from django.db import models

class Hobby(models.Model):
    """
    趣味モデル
    """
    name = models.CharField(max_length=255,  null=False, blank=False)

class User(models.Model):
    """
    ユーザモデル
    """
    name = models.CharField(max_length=255,  null=False, blank=False)
    is_married = models.BooleanField(default=False)
    hobbies = models.ManyToManyField(
        to=Hobby,
    )
# 既婚者で趣味が3つ以上あるユーザ取得
User.objects.annotate(hobby_count=Count('hobbies')).filter(is_married=True, hobby_count__gte=3)

参照

stackoverflow.com

Pythonで電話番号をハイフン区切りに変換する

電話番号をハイフン区切りにできる便利なライブラリがないか探していたところ、GoogleGithubで公開しているlibphonenumberを見つけました! libphonenumberは国際電話番号を解析・変換・バリデーションすることができます。

github.com

だがしかし!Java, C++, JavaScript の実装しかない… 諦めかけたその時、サードパーティが多言語に移植してくれている一覧を発見!C#, Go, Objective-c, PHP, PostgreSQL in-database types, Python, Ruby …!!! Pythonもありました! 名前は phonenumbers になってますね。わからいやすいな。 早速こちらのライブラリを試して見ました。

github.com

まずはライブラリのインストール

$ pip install phonenumbers

Pythonコンソール起動して実行

$ python
>>> import phonenumbers
>>> tel = '0300000000'
>>> tel_hyphen = phonenumbers.parse(tel, 'JP')
>>> phonenumbers.format_number(tel_hyphen, phonenumbers.PhoneNumberFormat.NATIONAL)
u'03-0000-0000'

ちゃんとハイフン区切りに変換されました!素晴らしい… 変換する電話番の形式がおかしかったりすると例外投げるので、入力ミスった値が来た時用に例外処理は入れといた方がよさそう。

libphonenumberの解説をしてくれているスライドあったので気になったら見てみよう。

speakerdeck.com

Djangoのテンプレート内でリレーション関係の子データをソートする

リレーション組んで取得した子データの表示順がバラバラだったのでどうにかできないかと思ったらテンプレートの組み込みタグである dictsort を使って解決できました!

dictsortを指定したキーで昇順にする

<ul>
{% for user in users %}
  {% for hobby in user.hobbies.all|dictsort:"id"%}
      <li>{{ hobby.category.name }}: {{ hobby.name }}</li>
  {% endfor %}
{% endfor %}
</ul>

降順にしたい場合はdictsortreversedを指定する。てっきりorder_byみたいに-つければいいのかと思った...

<ul>
{% for user in users %}
  {% for hobby in user.hobbies.all|dictsortreversed:"id"%}
      <li>{{ hobby.category.name }}: {{ hobby.name }}</li>
  {% endfor %}
{% endfor %}
</ul>

更にリレーション関係のあるキーでソートしたい場合は、カンマ区切りで指定できる。

<ul>
{% for user in users %}
  {% for hobby in user.hobbies.all|dictsort:"category.id"%}
      <li>{{ hobby.category.name }}: {{ hobby.name }}</li>
  {% endfor %}
{% endfor %}
</ul>

参考

docs.djangoproject.com

Pythonで数字を3桁区切りカンマにする

Integer型の場合

f"{num:,d}"

>>> num = 1000000
>>> print(f"{num:,d}")
1,000,000

Float型の場合

f"{num:,.2f}"

>>> num = 1234.5678
>>> print(f"{num:,.2f}")
1,234.57

参考

stackoverflow.com

aliasで設定したコマンドをシェルスクリプト内で実行する

シェルスクリプトを実行したらaliasで追加したコマンドが無いよと怒られた...

./hoge.sh: 行 40: hoge: コマンドが見つかりません

普段コマンドラインで使っているコマンドだったので、???状態。 man bash で見たらインタラクティブモードじゃないとaliasで登録したコマンドが使いえないらしい...

Aliases are not expanded when the shell is not interactive, unless the expand_aliases shell option is set using shopt (see the description of shopt under SHELL BUILTIN COMMANDS below).

そこで、 /etc/bashrc にaliasを登録して、環境変数を渡すことでシェルスクリプト内でもコマンドを実行できるようにした。

$ BASH_ENV=~/.bashrc bash -O expand_aliases  hoge.sh

参考

takafumi-s.hatenablog.com

MySQLのSELECT結果をエクスポートする

MySQLからデータをエクスポートするとtsv形式になる。 オプションでCSVとかの形式も選べるのかと思っていたが、どうやらできなそう。

デフォルトのエクスポートだとtsv形式でファイルに出力される

echo "SELECT * FROM user" | mysql -uroot -p > data.tsv

csv形式にするには出力をsedで変換すれば一応できなくもない

echo "SELECT * FROM user" | mysql -uroot -p | sed 's/"/""/g;s/^/"/;s/$/"/;s/\n//g' > data.csv

ついでにzipにする

echo "SELECT * FROM user" | mysql -uroot -p | gzip -c > data.tsv.zip

csvファイルをzipする

echo "SELECT * FROM user" | mysql -uroot -p | sed 's/"/""/g;s/^/"/;s/$/"/;s/\n//g' | gzip -c > data.csv.zip

参考

obel.hatenablog.jp

privateの静的関数をPHPUnitでテストする

PHPUnitでの private static function のテスト方法がわからなかったのでメモ。

公式のドキュメントを見るとReflectionMethod::invokeArgs に最初の引数にnullを渡してあげればよさそうなのでやってみる。

f:id:rukurx:20181001165403p:plain

PHPUnitをインストールする

$ composer require phpunit/phpunit --dev

privateの静的関数を含んだテスト対象のクラスを作成する

<?php

class Hoge {

  private static function fuga() {
    return 'hello';
  }

}

PHPUnitのテストを書く

<?php
use PHPUnit\Framework\TestCase;

require_once 'Hoge.php';

class HogeTest extends TestCase {

  /**
   * privateの静的関数fugaのテスト
   */
  public function test_fuga() {
    $expected = 'hello';
    $class = new \ReflectionClass(new Hoge());
    $method = $class->getMethod('fuga');

    $method->setAccessible(true);
    // 第一引数にnullを渡すとstaticメソッドが呼べる
    $actual = $method->invokeArgs(null, []);

    $this->assertEquals($expected, $actual);
  }

}

テストを実行する

$ phpunit HogeTest.php
PHPUnit 6.5.5 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 65 ms, Memory: 10.00MB

OK (1 test, 1 assertion)

参考

http://php.net/manual/ja/reflectionmethod.invokeargs.php