Skip to content

Commit d543c55

Browse files
authored
Merge pull request #10 from lucaskampi/feat/price-parameter
Feat/price parameter
2 parents 383a177 + bf88404 commit d543c55

5 files changed

Lines changed: 102 additions & 2 deletions

File tree

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 6.0.1 on 2026-01-24 03:43
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('entities', '0001_initial'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='entity',
15+
name='price_cents',
16+
field=models.IntegerField(default=0),
17+
),
18+
]
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from decimal import Decimal
2+
from django.db import migrations, models
3+
4+
5+
def forwards(apps, schema_editor):
6+
Entity = apps.get_model('entities', 'Entity')
7+
for obj in Entity.objects.all():
8+
# convert existing price_cents (int) to unit_price Decimal
9+
cents = getattr(obj, 'price_cents', None)
10+
if cents is not None:
11+
obj.unit_price = Decimal(cents) / Decimal('100')
12+
obj.save(update_fields=['unit_price'])
13+
14+
15+
def backwards(apps, schema_editor):
16+
Entity = apps.get_model('entities', 'Entity')
17+
for obj in Entity.objects.all():
18+
val = getattr(obj, 'unit_price', None)
19+
if val is not None:
20+
obj.price_cents = int((Decimal(val) * Decimal('100')).to_integral_value())
21+
obj.save(update_fields=['price_cents'])
22+
23+
24+
class Migration(migrations.Migration):
25+
26+
dependencies = [
27+
('entities', '0002_entity_price_cents'),
28+
]
29+
30+
operations = [
31+
migrations.AddField(
32+
model_name='entity',
33+
name='unit_price',
34+
field=models.DecimalField(decimal_places=4, default=Decimal('0.0000'), max_digits=12),
35+
),
36+
migrations.RunPython(forwards, backwards),
37+
migrations.RemoveField(
38+
model_name='entity',
39+
name='price_cents',
40+
),
41+
]

entities/models.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,23 @@
11
from django.db import models
2+
from decimal import Decimal, ROUND_DOWN
3+
24

35
class Entity(models.Model):
46
type = models.CharField(max_length=100)
57
name = models.CharField(max_length=255, blank=True, null=True)
68
description = models.TextField(blank=True, null=True)
9+
# store unit price as Decimal with 4 decimal places to preserve precision from measurements
10+
unit_price = models.DecimalField(max_digits=12, decimal_places=4, default=Decimal('0.0000'))
711
created_at = models.DateTimeField(auto_now_add=True)
812

913
def __str__(self):
10-
return f"{self.type}: {self.name or self.pk}"
14+
return f"{self.type}: {self.name or self.pk}"
15+
16+
def unit_price_float(self) -> float:
17+
"""Return unit price as float for convenience (not used for storage)."""
18+
return float(self.unit_price or Decimal('0'))
19+
20+
def price_cents_truncated(self) -> int:
21+
"""Return price in cents after truncating to 2 decimal places (no rounding)."""
22+
val = (Decimal(self.unit_price or Decimal('0'))).quantize(Decimal('0.01'), rounding=ROUND_DOWN)
23+
return int((val * 100).to_integral_value())

entities/schemas.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
from pydantic import BaseModel, constr
22
from typing import Optional
33
from datetime import datetime
4+
from decimal import Decimal
45

56
class EntityBase(BaseModel):
67
type: constr(max_length=100)
78
name: Optional[constr(max_length=255)] = None
89
description: Optional[str] = None
10+
# store and accept unit_price with Decimal precision; API accepts float/str and will be parsed
11+
unit_price: Optional[Decimal] = None
912

1013
model_config = {"from_attributes": True}
1114

@@ -16,9 +19,12 @@ class EntityUpdate(BaseModel):
1619
type: Optional[constr(max_length=100)] = None
1720
name: Optional[constr(max_length=255)] = None
1821
description: Optional[str] = None
22+
unit_price: Optional[Decimal] = None
1923

2024
model_config = {"from_attributes": True}
2125

2226
class EntityOut(EntityBase):
2327
id: int
24-
created_at: datetime
28+
created_at: datetime
29+
# unit_price returned as Decimal (serialized by Pydantic)
30+
unit_price: Decimal

entities/services.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from typing import Dict, Any
22
from django.db import transaction
33
from .models import Entity
4+
from decimal import Decimal, InvalidOperation, ROUND_DOWN
45

56
def list_entities(
67
name: str = None,
@@ -35,11 +36,32 @@ def get_entity(entity_id: int) -> Entity:
3536
return Entity.objects.get(pk=entity_id)
3637

3738
def create_entity(data: Dict[str, Any]) -> Entity:
39+
# accept `unit_price` (e.g. 50.99) and store as Decimal with 4 decimal places
40+
unit = data.pop('unit_price', None)
41+
if unit is not None:
42+
# accept both 50.99 and '50,99' by normalizing comma to dot
43+
unit_str = str(unit).replace(',', '.')
44+
try:
45+
d = Decimal(unit_str)
46+
except (InvalidOperation, ValueError):
47+
d = Decimal(float(unit_str))
48+
# store with 4 decimal places to preserve measurement precision, truncate (ROUND_DOWN)
49+
data['unit_price'] = d.quantize(Decimal('0.0001'), rounding=ROUND_DOWN)
3850
with transaction.atomic():
3951
return Entity.objects.create(**data)
4052

4153
def update_entity(entity_id: int, data: Dict[str, Any]) -> Entity:
4254
with transaction.atomic():
55+
# support updating via `unit_price` as well
56+
unit = data.pop('unit_price', None)
57+
if unit is not None:
58+
unit_str = str(unit).replace(',', '.')
59+
try:
60+
d = Decimal(unit_str)
61+
except (InvalidOperation, ValueError):
62+
d = Decimal(float(unit_str))
63+
data['unit_price'] = d.quantize(Decimal('0.0001'), rounding=ROUND_DOWN)
64+
4365
obj = Entity.objects.get(pk=entity_id)
4466
for k, v in data.items():
4567
setattr(obj, k, v)

0 commit comments

Comments
 (0)