strawberry-graphql / strawberry-django

Strawberry GraphQL Django extension
MIT License
391 stars 115 forks source link

Docs for reference for DjMoney type #505

Open moritz89 opened 3 months ago

moritz89 commented 3 months ago

Would and if yes an example of a moneyed / django-money be useful to be added to the documentation somewhere? It is a non-trivial implementation with a few pitfalls including optimizing DB queries. Follows the Shopify GQL API.

from decimal import Decimal
from enum import Enum

from moneyed import CURRENCIES_BY_ISO
import strawberry
from decimal import Decimal
from strawberry import relay
import strawberry_django

CurrencyCode = strawberry.enum(
    Enum("CurrencyCode", {i.code: i.code for i in CURRENCIES_BY_ISO.values()})
)

@strawberry.type(name="Money")
class MoneyType:
    amount: Decimal
    currency_code: CurrencyCode

@strawberry_django.type(MyDjangoModel)
class MyGQLNode(relay.Node):
    @strawberry_django.field(only=["plan_price", "plan_price_currency"])
    def plan_price(self, root: FSSubscription) -> MoneyType:
        return MoneyType(
            amount=root.plan_price.amount, currency_code=root.plan_price_currency
        )
from moneyed import EUR, Money
from django.db import models
from djmoney.models.fields import MoneyField

class MyDjangoModel(models.Model):
    plan_price = MoneyField(
        max_digits=19,
        decimal_places=4,
        default_currency=EUR,
        help_text="The current price of the plan",
    )

Upvote & Fund

Fund with Polar

bellini666 commented 3 months ago

Hey @moritz89 ,

Yeah, more documentation is never bad :)

Probably add that under a new section called "Examples" or "Recipes"?

Btw, regarding the example itself, I think it is even easier if you register your type in the field_type_map, then you can just let auto do what you expect. e.g.

from strawberry_django.fields.types import field_type_map

@strawberry.type(name="Money")
class MoneyType:
    @strawberry.field
    def amount(self, root: Money) -> Decimal:
        return root.amount

    @strawberry.field
    def currency_code(self, root: Money) -> CurrencyCode:
        return root.currency

field_type_map[MoneyField] = MoneyType

Not sure how that type works (never used if before), but if what the field returns contains everything that is needed, this should work.

Just a suggestion :)

moritz89 commented 3 months ago

That looks neat with the field_type_map. However, where would you insert the only keyword? Guess that hidden requirement breaks it when using the optimized queries?

bellini666 commented 3 months ago

@moritz89 that would probably require an adjustment...

Wondering if we should have an integration for that lib? We do have optional integrations for some, such as django-guardian, django-choices-field, etc

moritz89 commented 3 months ago

AFAIK, django-money is the most popular library to save money fields, so it would be useful for a 'broad' user base. There are a few unknowns so I'd first just add the docs and have a look at the integration support when time permits / more interest arises.

moritz89 commented 2 months ago

I've had to change the class to the following to work with mutation outputs by adding a custom constructor and field methods. Is this a good approach or is there a better one? Without the constructor it only worked with GQL queries (see above).

# utils.py ---------------------------------------------------------
from moneyed import CURRENCIES_BY_ISO, Money

CurrencyCode = strawberry.enum(
    Enum("CurrencyCode", {i.code: i.code for i in CURRENCIES_BY_ISO.values()})
)

@strawberry.type(name="Money")
class MoneyType:
    def __init__(self, money: Money):
        self.money = money

    @strawberry.field
    def amount(self) -> Decimal:
        return self.money.amount

    @strawberry.field
    def currency_code(self) -> CurrencyCode:
        return self.money.currency.code

# mutations.py ---------------------------------------------------------
from moneyed import Money
from utils import MoneyType

@strawberry.type
class OpenSubscriptionSession:
    secure_data: str
    secure_key: str
    total_price: MoneyType

@strawberry.type
class Mutation:
    @input_mutation
    def open_subscription_session(self, info: Info, plan: str) -> OpenSubscriptionSession:
        ...
        total_price: Money = prices.total_price()
        session = OpenSubscriptionSession(
            secure_data=json.dumps(store_data),
            secure_key="",
            total_price=MoneyType(total_price),
        )
        return session