Skip to content

Commit

Permalink
feat(Stats): new TotalStats model (#475)
Browse files Browse the repository at this point in the history
  • Loading branch information
raphodn committed Sep 28, 2024
1 parent b02c480 commit 034025b
Show file tree
Hide file tree
Showing 8 changed files with 242 additions and 1 deletion.
2 changes: 2 additions & 0 deletions config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"django_filters", # django-filter
"drf_spectacular", # drf-spectacular
"django_q", # django-q2
"solo", # django-solo
# "debug_toolbar", # django-debug-toolbar (see below)
"django_extensions", # django-extensions
]
Expand All @@ -50,6 +51,7 @@
"open_prices.proofs",
"open_prices.prices",
"open_prices.users",
"open_prices.stats",
"open_prices.api",
"open_prices.www",
]
Expand Down
Empty file added open_prices/stats/__init__.py
Empty file.
44 changes: 44 additions & 0 deletions open_prices/stats/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Generated by Django 5.1 on 2024-09-28 07:30

import django.utils.timezone
from django.db import migrations, models


class Migration(migrations.Migration):
initial = True

dependencies = []

operations = [
migrations.CreateModel(
name="TotalStats",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("price_count", models.PositiveIntegerField(default=0)),
("price_barcode_count", models.PositiveIntegerField(default=0)),
("price_category_count", models.PositiveIntegerField(default=0)),
("product_count", models.PositiveIntegerField(default=0)),
("product_with_price_count", models.PositiveIntegerField(default=0)),
("location_count", models.PositiveIntegerField(default=0)),
("location_with_price_count", models.PositiveIntegerField(default=0)),
("proof_count", models.PositiveIntegerField(default=0)),
("proof_with_price_count", models.PositiveIntegerField(default=0)),
("user_count", models.PositiveIntegerField(default=0)),
("user_with_price_count", models.PositiveIntegerField(default=0)),
("created", models.DateTimeField(default=django.utils.timezone.now)),
("updated", models.DateTimeField(auto_now=True)),
],
options={
"verbose_name": "Total Stats",
"verbose_name_plural": "Total Stats",
},
),
]
Empty file.
79 changes: 79 additions & 0 deletions open_prices/stats/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
from django.db import models
from django.utils import timezone
from solo.models import SingletonModel


class TotalStats(SingletonModel):
PRICE_COUNT_FIELDS = ["price_count", "price_barcode_count", "price_category_count"]
PRODUCT_COUNT_FIELDS = ["product_count", "product_with_price_count"]
LOCATION_COUNT_FIELDS = ["location_count", "location_with_price_count"]
PROOF_COUNT_FIELDS = ["proof_count", "proof_with_price_count"]
USER_COUNT_FIELDS = ["user_count", "user_with_price_count"]

price_count = models.PositiveIntegerField(default=0)
price_barcode_count = models.PositiveIntegerField(default=0)
price_category_count = models.PositiveIntegerField(default=0)
product_count = models.PositiveIntegerField(default=0)
product_with_price_count = models.PositiveIntegerField(default=0)
location_count = models.PositiveIntegerField(default=0)
location_with_price_count = models.PositiveIntegerField(default=0)
proof_count = models.PositiveIntegerField(default=0)
proof_with_price_count = models.PositiveIntegerField(default=0)
user_count = models.PositiveIntegerField(default=0)
user_with_price_count = models.PositiveIntegerField(default=0)

created = models.DateTimeField(default=timezone.now)
updated = models.DateTimeField(auto_now=True)

class Meta:
verbose_name = "Total Stats"
verbose_name_plural = "Total Stats"

def update_price_stats(self):
from open_prices.prices.models import Price

self.price_count = Price.objects.count()
self.price_barcode_count = Price.objects.filter(
product_code__isnull=False
).count()
self.price_category_count = Price.objects.filter(
category_tag__isnull=False
).count()
self.save(
update_fields=self.PRICE_COUNT_FIELDS
+ [
"updated",
]
)

def update_product_stats(self):
from open_prices.products.models import Product

self.product_count = Product.objects.count()
self.product_with_price_count = Product.objects.has_prices().count()
# self.product_with_price_count = User.objects.values_list("product_id", flat=True).distinct().count() # noqa
self.save(update_fields=self.PRODUCT_COUNT_FIELDS + ["updated"])

def update_location_stats(self):
from open_prices.locations.models import Location

self.location_count = Location.objects.count()
self.location_with_price_count = Location.objects.has_prices().count()
# self.location_with_price_count = User.objects.values_list("location_id", flat=True).distinct().count() # noqa
self.save(update_fields=self.LOCATION_COUNT_FIELDS + ["updated"])

def update_proof_stats(self):
from open_prices.proofs.models import Proof

self.proof_count = Proof.objects.count()
self.proof_with_price_count = Proof.objects.has_prices().count()
# self.proof_with_price_count = User.objects.values_list("proof_id", flat=True).distinct().count() # noqa
self.save(update_fields=self.PROOF_COUNT_FIELDS + ["updated"])

def update_user_stats(self):
from open_prices.users.models import User

self.user_count = User.objects.count()
self.user_with_price_count = User.objects.has_prices().count()
# self.user_with_price_count = User.objects.values_list("owner", flat=True).distinct().count() # noqa
self.save(update_fields=self.USER_COUNT_FIELDS + ["updated"])
101 changes: 101 additions & 0 deletions open_prices/stats/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
from django.db import IntegrityError
from django.test import TestCase

from open_prices.locations.factories import LocationFactory
from open_prices.prices.factories import PriceFactory
from open_prices.proofs.factories import ProofFactory
from open_prices.stats.models import TotalStats
from open_prices.users.factories import UserFactory

LOCATION_NODE_652825274 = {
"osm_id": 652825274,
"osm_type": "NODE",
"osm_name": "Monoprix",
}


class TotalStatsSaveTest(TestCase):
@classmethod
def setUpTestData(cls):
cls.total_stats = TotalStats.get_solo()

def test_total_stats_singleton(self):
# cannot create another TotalStats instance
self.assertRaises(IntegrityError, TotalStats.objects.create)


class TotalStatsTest(TestCase):
@classmethod
def setUpTestData(cls):
cls.total_stats = TotalStats.get_solo()
cls.user = UserFactory()
cls.user_2 = UserFactory()
cls.location = LocationFactory(**LOCATION_NODE_652825274)
cls.location_2 = LocationFactory()
cls.proof = ProofFactory(
location_osm_id=cls.location.osm_id,
location_osm_type=cls.location.osm_type,
owner=cls.user.user_id,
)
cls.proof_2 = ProofFactory(
location_osm_id=cls.location_2.osm_id,
location_osm_type=cls.location_2.osm_type,
owner=cls.user_2.user_id,
)
PriceFactory(
product_code="0123456789100",
location_osm_id=cls.location.osm_id,
location_osm_type=cls.location.osm_type,
proof_id=cls.proof.id,
price=1.0,
owner=cls.user.user_id,
)
PriceFactory(
product_code="0123456789101",
location_osm_id=cls.location.osm_id,
location_osm_type=cls.location.osm_type,
price=2.0,
owner=cls.user_2.user_id,
)

def test_update_price_stats(self):
self.assertEqual(self.total_stats.price_count, 0)
self.assertEqual(self.total_stats.price_barcode_count, 0)
self.assertEqual(self.total_stats.price_category_count, 0)
# update_price_stats() will update price_counts
self.total_stats.update_price_stats()
self.assertEqual(self.total_stats.price_count, 2)
self.assertEqual(self.total_stats.price_barcode_count, 2)
self.assertEqual(self.total_stats.price_category_count, 0)

def test_update_product_stats(self):
self.assertEqual(self.total_stats.product_count, 0)
self.assertEqual(self.total_stats.product_with_price_count, 0)
# update_product_stats() will update product_counts
self.total_stats.update_product_stats()
self.assertEqual(self.total_stats.product_count, 2)
self.assertEqual(self.total_stats.product_with_price_count, 2)

def test_update_location_stats(self):
self.assertEqual(self.total_stats.location_count, 0)
self.assertEqual(self.total_stats.location_with_price_count, 0)
# update_location_stats() will update location_counts
self.total_stats.update_location_stats()
self.assertEqual(self.total_stats.location_count, 2)
self.assertEqual(self.total_stats.location_with_price_count, 1)

def test_update_proof_stats(self):
self.assertEqual(self.total_stats.proof_count, 0)
self.assertEqual(self.total_stats.proof_with_price_count, 0)
# update_proof_stats() will update proof_counts
self.total_stats.update_proof_stats()
self.assertEqual(self.total_stats.proof_count, 2)
self.assertEqual(self.total_stats.proof_with_price_count, 1)

def test_update_user_stats(self):
self.assertEqual(self.total_stats.user_count, 0)
self.assertEqual(self.total_stats.user_with_price_count, 0)
# update_user_stats() will update user_counts
self.total_stats.update_user_stats()
self.assertEqual(self.total_stats.user_count, 2)
self.assertEqual(self.total_stats.user_with_price_count, 2)
16 changes: 15 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ ipython = "^8.26.0"
gunicorn = "^22.0.0"
django-cors-headers = "^4.4.0"
sentry-sdk = {extras = ["django"], version = "^2.13.0"}
django-solo = "^2.3.0"

[tool.poetry.group.dev.dependencies]
black = "~23.12.1"
Expand Down

0 comments on commit 034025b

Please sign in to comment.