django復習(tables2)


とりあえずdjangoの復習用に適当にテーブルを作成。
投入データは下記から取得。
日本政府が出してるっぽいデータ

from django.db import models
from datetime import datetime


# 人口テーブル
class Population(models.Model):
    prefectures_code = models.CharField(max_length=10,verbose_name="都道府県コード", null=False) 
    prefectures = models.CharField(max_length=10,verbose_name="都道府県", null=False) 
    era =  models.CharField(max_length=500, verbose_name="元号", null=False)
    jp_calendar =  models.CharField(max_length=500, verbose_name="和暦", null=False)
    year =  models.CharField(max_length=500, verbose_name="西暦", null=False)
    population =  models.CharField(max_length=500, verbose_name="人口", null=False)
    man =  models.CharField(max_length=500, verbose_name="男", null=False)
    woman =  models.CharField(max_length=500, verbose_name="女", null=False)
    create_date = models.DateTimeField(default=datetime.now, null=True, verbose_name="登録日")

    def __str__(self):
        return self.prefectures

適当すぎて人口の数値部分も文字列で作成。
問題ないかと思いそのまま続行した結果、色々被害続出。

・tables2の復習で最新年度の人口の表を作成したが、
 ソート機能が文字列(辞書順)でソートしてしまうため、正しくソートできない。
 例:
  123000,200,500があった場合、辞書順なので1文字目を取って
  123000 < 200 < 500 になる。

・matplotlibの復習で特定の県について年ごとの人口の推移を見ようとしたが、
 人口が文字列のため、グラフがちゃんとできない。
 例:
  1年間で100万人→120万人に増えても1メモリ分しか増加しない。
  1年間で100万人→200万人に増えても1メモリ分しか増加しない。

matplotlibの方は特にハマらなかったのでtables2についてメモ。
(model直してDB作り直すのが一番ですが、勉強も兼ねてmodelはそのままで続行)

table.pyのソース

from population.models import Population
import django_tables2 as tables
from django_tables2.utils import A
from population.models import Population

class ButtonLinkColumn(tables.LinkColumn):
    """
    リンク表示用カラム(ボタン)
    """
    def render(self, record, value):
        if record.count > 0:
            # ステータス:完了
            return "データ表示"
        else:
            # ステータス:完了以外
            return ""

class PopulationTable(tables.Table):
    """
    人口画面用テーブル
    Population
    """
    prefectures = tables.Column(verbose_name="都道府県")
    # リンク用
    plot = tables.LinkColumn("population:pop_plot", args=[A("prefectures_code")], 
                    verbose_name = "",text="人口グラフ", 
                    attrs={"a": {"class": "btn btn-success text-nowrap"}} 
                    )
    population = tables.Column(verbose_name="総人口")
    man = tables.Column(verbose_name="人口(男性)")
    woman = tables.Column(verbose_name="人口(女性)")

    class Meta:
        model = Population
        template_name = 'django_tables2/bootstrap4.html'
        # 表示する列column
        fields = ('prefectures_code',
                'prefectures',
                'plot',
                'population',
                'man',
                'woman',

        )    
        attrs = {"class": "table table-striped"}

django-tables2の参考サイト
参考サイトを見るとorder_カラム名で対象のカラムのソート条件をいじれるらしい。

from django.db.models.functions import Length

~以下中略~

    def order_name(self, queryset, is_descending):
        queryset = queryset.annotate(
            length=Length("first_name")
        ).order_by(("-" if is_descending else "") + "length")
        return (queryset, True)

真似してmanでやってみると、桁数でのソートはできた。

    def order_man(self, queryset, is_descending):
        queryset = queryset.annotate(
            length=Length("man")
        ).order_by(("-" if is_descending else "") + "length")
        return (queryset, True)

じゃぁ次は数値でソートしたいと思ったが、ここからが問題。
理解度が低いせいかLength部分に何が入るか分からず、そこを調べるのに時間がかかった。

色々調べたが、googleでは上手く見つけられずdjango.db.models.functionsを調べることで解決した。

from .comparison import Cast, Coalesce, Greatest, Least, NullIf
from .datetime import (
    Extract, ExtractDay, ExtractHour, ExtractIsoWeekDay, ExtractIsoYear,
    ExtractMinute, ExtractMonth, ExtractQuarter, ExtractSecond, ExtractWeek,
    ExtractWeekDay, ExtractYear, Now, Trunc, TruncDate, TruncDay, TruncHour,
    TruncMinute, TruncMonth, TruncQuarter, TruncSecond, TruncTime, TruncWeek,
    TruncYear,
)
from .math import (
    Abs, ACos, ASin, ATan, ATan2, Ceil, Cos, Cot, Degrees, Exp, Floor, Ln, Log,
    Mod, Pi, Power, Radians, Round, Sign, Sin, Sqrt, Tan,
)
from .text import (
    MD5, SHA1, SHA224, SHA256, SHA384, SHA512, Chr, Concat, ConcatPair, Left,
    Length, Lower, LPad, LTrim, Ord, Repeat, Replace, Reverse, Right, RPad,
    RTrim, StrIndex, Substr, Trim, Upper,
)
from .window import (
    CumeDist, DenseRank, FirstValue, Lag, LastValue, Lead, NthValue, Ntile,
    PercentRank, Rank, RowNumber,
)

__all__ = [
    # comparison and conversion
    'Cast', 'Coalesce', 'Greatest', 'Least', 'NullIf',
    # datetime
    'Extract', 'ExtractDay', 'ExtractHour', 'ExtractMinute', 'ExtractMonth',
    'ExtractQuarter', 'ExtractSecond', 'ExtractWeek', 'ExtractIsoWeekDay',
    'ExtractWeekDay', 'ExtractIsoYear', 'ExtractYear', 'Now', 'Trunc',
    'TruncDate', 'TruncDay', 'TruncHour', 'TruncMinute', 'TruncMonth',
    'TruncQuarter', 'TruncSecond', 'TruncTime', 'TruncWeek', 'TruncYear',
    # math
    'Abs', 'ACos', 'ASin', 'ATan', 'ATan2', 'Ceil', 'Cos', 'Cot', 'Degrees',
    'Exp', 'Floor', 'Ln', 'Log', 'Mod', 'Pi', 'Power', 'Radians', 'Round',
    'Sign', 'Sin', 'Sqrt', 'Tan',
    # text
    'MD5', 'SHA1', 'SHA224', 'SHA256', 'SHA384', 'SHA512', 'Chr', 'Concat',
    'ConcatPair', 'Left', 'Length', 'Lower', 'LPad', 'LTrim', 'Ord', 'Repeat',
    'Replace', 'Reverse', 'Right', 'RPad', 'RTrim', 'StrIndex', 'Substr',
    'Trim', 'Upper',
    # window
    'CumeDist', 'DenseRank', 'FirstValue', 'Lag', 'LastValue', 'Lead',
    'NthValue', 'Ntile', 'PercentRank', 'Rank', 'RowNumber',
]

上記にあるメソッドは大体使えるっぽいのでAbs(絶対値)を使って解決した。

    def order_population(self, queryset, is_descending):
        queryset = queryset.annotate(
            abs=Abs("population")
        ).order_by(("-" if is_descending else "") + "abs")
        return (queryset, True)

    def order_man(self, queryset, is_descending):
        queryset = queryset.annotate(
            abs=Abs("man")
        ).order_by(("-" if is_descending else "") + "abs")
        return (queryset, True)

    def order_woman(self, queryset, is_descending):
        queryset = queryset.annotate(
            abs=Abs("woman")
        ).order_by(("-" if is_descending else "") + "abs")
        return (queryset, True)