hikaru’s diary

Django Engineer

【Django】モデルについていろいろ書く#4

前回:【Django】モデルについていろいろ書く#3 - hikaru’s diary

最終回です。

アグリゲーション

アグリゲーション | Django ドキュメント | Django

チートシートです。

# ドキュメントから引用
# Total number of books.
>>> Book.objects.count()
2452

# Total number of books with publisher=BaloneyPress
>>> Book.objects.filter(publisher__name='BaloneyPress').count()
73

# Average price across all books.
>>> from django.db.models import Avg
>>> Book.objects.all().aggregate(Avg('price'))
{'price__avg': 34.35}

# Max price across all books.
>>> from django.db.models import Max
>>> Book.objects.all().aggregate(Max('price'))
{'price__max': Decimal('81.20')}

# Difference between the highest priced book and the average price of all books.
>>> from django.db.models import FloatField
>>> Book.objects.aggregate(
...     price_diff=Max('price', output_field=FloatField()) - Avg('price'))
{'price_diff': 46.85}

# All the following queries involve traversing the Book<->Publisher
# foreign key relationship backwards.

# Each publisher, each with a count of books as a "num_books" attribute.
>>> from django.db.models import Count
>>> pubs = Publisher.objects.annotate(num_books=Count('book'))
>>> pubs
<QuerySet [<Publisher: BaloneyPress>, <Publisher: SalamiPress>, ...]>
>>> pubs[0].num_books
73

# Each publisher, with a separate count of books with a rating above and below 5
>>> from django.db.models import Q
>>> above_5 = Count('book', filter=Q(book__rating__gt=5))
>>> below_5 = Count('book', filter=Q(book__rating__lte=5))
>>> pubs = Publisher.objects.annotate(below_5=below_5).annotate(above_5=above_5)
>>> pubs[0].above_5
23
>>> pubs[0].below_5
12

# The top 5 publishers, in order by number of books.
>>> pubs = Publisher.objects.annotate(num_books=Count('book')).order_by('-num_books')[:5]
>>> pubs[0].num_books
1323


私は高度な集計だと.query()を使いSQLで書くことが多いです。
もしSQLを書きたくない場合は以下のようにmodelsに定義するといいと思います。

class Kinmu(models.Model):
    name = models.ForeignKey(User, to_field='username',
                             on_delete=models.CASCADE)
    hiduke = models.DateField(verbose_name="日付", default=timezone.now)
    syukkin = models.TimeField(verbose_name="出勤時間", blank=True, null=True)
    taikin = models.TimeField(verbose_name="退勤時間", blank=True, null=True)
    kyukei = models.TimeField(verbose_name="休憩時間", blank=True, null=True)
    bikou = models.CharField(verbose_name="備考", max_length=100, blank=True, default="")
    km = models.FloatField(verbose_name="走行距離", default=0)

    def __str__(self):
        return str(self.name) + str(self.hiduke)

    @property
    def sum(self):  # ここ
        a = datetime.timedelta(hours=self.taikin.hour, minutes=self.taikin.minute) - \
            datetime.timedelta(hours=self.syukkin.hour, minutes=self.syukkin.minute) - \
            datetime.timedelta(hours=self.kyukei.hour)
        a = round(a.total_seconds())
        b = str(a % 3600 // 60) if len(str(a % 3600 // 60)) == 2 else str(a % 3600 // 60) + "0"
        c = str(a // 3600) + ":" + b if len(b) == 2 else b + "0"
        return c
>>> from diary.models import Kinmu
>>> Kinmu.objects.all()[0].sum
'8:00'
>>> 
検索

完全一致

# ドキュメントから引用
# SELECT ... WHERE headline LIKE '%Lennon%';と同じ
>>> Entry.objects.get(headline__icontains='Lennon')


PostgreSQL独自の検索機能だとicontainsよりかは強力で楽に実装できるそうです。
Full text search | Django ドキュメント | Django

マネージャ

ORMを使うときはモデル名.objects....と書きますが、objectsという変数名は変更することができます。
変更するとobjectsをフィールド名として扱うことができます。下記はobjectsをManagerにする例です。

from django.db import models

class Person(models.Model):
    people = models.Manager()


マネージャをカスタマイズするにはQuerySetを定義します。

from django.db import models

class PersonQuerySet(models.QuerySet):
    def authors(self):
        return self.filter(role='A')

    def editors(self):
        return self.filter(role='E')

class Person(models.Model):
    people = PersonQuerySet.as_manager()  # objectsでも可
データベースのトランザクション

データベースのトランザクション | Django ドキュメント | Django

Djangoは自動コミットをしますが、いつコミットするか知るために一読したほうが良いかもしれません。



以上、お疲れさまでした。

【Django】モデルについていろいろ書く#3

前回:【Django】モデルについていろいろ書く#2 - hikaru’s diary


クエリ作成を見ていきます。
クエリを作成する | Django ドキュメント | Django


下記のようなモデルを作成し、migrateまで実行したとします。

# ドキュメントから引用
from datetime import date

from django.db import models

class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()

    def __str__(self):
        return self.name

class Author(models.Model):
    name = models.CharField(max_length=200)
    email = models.EmailField()

    def __str__(self):
        return self.name

class Entry(models.Model):
    blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
    headline = models.CharField(max_length=255)
    body_text = models.TextField()
    pub_date = models.DateField()
    mod_date = models.DateField(default=date.today)
    authors = models.ManyToManyField(Author)
    number_of_comments = models.IntegerField(default=0)
    number_of_pingbacks = models.IntegerField(default=0)
    rating = models.IntegerField(default=5)

    def __str__(self):
        return self.headline
CRUD

shellでCRUDします。

>>> from blog.models import Blog
>>> b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.')
>>> b.save()  # create
>>> b = Blog.objects.get(id=1)  # read
>>> all = Blog.objects.all()  # 全件取得 
>>> b.tagline = "The today Beatles news"
>>> b.save()  # update
>>> b.delete()  # delete


外部キーで参照列を取り出したい時は繋げて書く(?)と良いです。
templateでも出来ます。

>>> from blog.models import Entry
>>> tagline = Entry.objects.get(blog_id=1).blog.tagline


参照列がunique=Trueであれば指定することが出来ます
https://docs.djangoproject.com/ja/4.0/ref/models/fields/#django.db.models.ForeignKey.to_field:~:text=special%20syntax.-,ForeignKey.to_field,-%C2%B6


リストのように取り出せます。どのデータでもいいなら一番簡単だと思います。

>>> Entry.objects.all()[0]
>>> Entry.objects.all()[:5]
その他
# ドキュメントから引用
Entry.objects.filter(pub_date__year=2006)


filterの代わりにgetを使うと例外を出してくれるのでけっこう便利です。
https://docs.djangoproject.com/ja/4.0/topics/db/queries/#:~:text=%E3%81%A6%E3%81%8F%E3%81%A0%E3%81%95%E3%81%84%E3%80%82-,get()%20%E3%82%92%E7%94%A8%E3%81%84%E3%81%A61%E3%81%A4%E3%81%AE%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B,-%C2%B6


.updateで一括更新する。

# ドキュメントから引用
Entry.objects.filter(pub_date__year=2007).update(headline='Everything is the same')


sql文を発行する。

Entry.objects.all().query

素のsql文を実行する。
ORMは集計などの複雑な処理が苦手なのでsqlで書いたほうが良い時もあります。

Person.objects.raw('SELECT * FROM myapp_person')


以上、お疲れさまでした。

【Django】モデルについていろいろ書く#2

前回: 【Django】モデルについていろいろ書く#1 - hikaru’s diary

リレーション
# ドキュメントから引用
リレーショナルデータベースの強力さがテーブル同士の関係によって決まることは疑いがありません。Dango では、最も一般的な 3 つのデータベースリレーションシップを定義しています: 多対 1、多対多、1 対 1 です。

多対1にはForeignKeyを使用し、多対多にはManyToManyFieldを使用するそうです。
ForeignKeyはORMを使うと

<p>名前:{{ p.number.name }}</p>
a = Person.objects.filter(number.name='name')

って感じで楽に書けます。

Metaオプション

メタデータを設定することができます。

# ドキュメントから引用
from django.db import models

class Ox(models.Model):
    horn_length = models.IntegerField()

    class Meta:
        ordering = ["horn_length"]
        verbose_name_plural = "oxen"

そのほかのオプションについてはこちらにあります。
Model Meta options | Django ドキュメント | Django

継承

modelクラスを継承します。

# ドキュメントから引用
from django.db import models

class CommonInfo(models.Model):  # テーブルは作成されない
    name = models.CharField(max_length=100)
    age = models.PositiveIntegerField()

    class Meta:
        abstract = True

class Student(CommonInfo):  # CommonInfoから継承
    home_group = models.CharField(max_length=5)

他にconstraints,verbose_name,verbose_name_plural辺りは必須。
init.pyを作ってfrom .models import * と書くと複数ファイルに分けることが出来ます。


次回はクエリの書き方を書きます。お疲れ様でした。

【Django】モデルについていろいろ書く#1

はじめに

最近書けていなかったのでたまには更新しようかなと思います。
モデルのページを順に読んで気になったところはメモしていくっていう形で書いています。
モデルとデータベース | Django ドキュメント | Django

基本
# ドキュメントから引用
from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

こんな感じで適当に書いたらmaemigrationsコマンドを実行
アプリ/migratinosにファイルが生成されるのでmigrateでデータベースに入力される
python manage.py sqlmigrate app 0001でSQL文の発行ができる

フィールドオプション

null,blank,choices,default,help_text,primary_key,uniqueの7つです
これらはどのフィールドでも利用できるそうです。

DateFieldのdefaultには設定方法は複数はあります。

from django.utils import timezone

class DateFieldSample(models.Model):
    a = models.DateField(auto_now=True)  # 更新日時などに使う
    b = models.DateField(auto_now_add=True)  # 作成日時などに使う
    c = models.DateField(default=timezone.now)  # cと同じ、datetimeはダメ


a,bでは手動で日時を変えられないことに注意。
私はadminサイトでトラブルに合いました。

uniqueは複数フィールドでの設定もできます。

    class Meta:
        db_table = 'kinmu'
        constraints = [
            models.UniqueConstraint(
                fields=['name', 'hiduke'],
                name="name_hiduke_unique"
            )
        ]




今日はここまでにします。お疲れ様でした。

【PostgreSQL】ログの文字化けを治す

postgresql.confを編集する。
私の環境ではC:\Program Files\PostgreSQL\14\dataにありました。

# lc_messages = 'Japanese_Japan.932'
lc_messages = 'en_US'

再起動

pg_ctl -D "C:\Program Files\PostgreSQL\14\data" restart

net start postgresql-x64-14  # 上ができない場合

確認

LOG:  statement: select 1;  # logの中身

以上、お疲れさまでした。

md-to-pdfでマークダウンからPDFにする

github.com


公式サイトからnode.jsをインストール
https://nodejs.org/ja/download/

LTS版の8.11.0をWindowsで使うと「npm WARN config global `–global`, `–local` are deprecated. Use `–location=global` instead.」と出てきて面倒なので私は最新版にしてます。

npm -v  # 確認
8.15.0

npm i -g md-to-pdf  # md-to-pdfをインストール
added 163 packages, changed 1 package, and audited 165 packages in 21s

14 packages are looking for funding
  run `npm fund` for details

md-to-pdf README.md  # README.mdからREDME.pdfを作成
√ generating PDF from README.md


me-to-pdfのREADME.mdをpdfにしました。

以上、お疲れ様でした。

【Django】セッションの有効期限を設定する

ソースコード

settings.py

from datetime import timedelta

***省略***

SESSIOM_COOKIE_AGE = timedelta(days=365).total_seconds()
説明

djangoはデフォルトだと2週間でセッションが切れるので1年に変更しました。
SESSION_COOKIE_AGE = 31536000とするのは分かりづらいためtimedeltaを使います。

viewsからセッションを使うこともできます。

views.py

print(request.session.get_expiry_age())  # 31536000.0


以上、お疲れさまでした。