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は自動コミットをしますが、いつコミットするか知るために一読したほうが良いかもしれません。



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