'Graphene GraphQL on return return records where calculated field is greater than zero

I have a GrpahQL endpoint that returns all the Magic the Gathering cards for a particular MtG set. I have created a field that returns a list of calculated fields which is the delta is card price between two days. I would like to filter all the cards where the delta percentage is greater than 0.

so something along the lines of return magic_sets_cards_pricing.objects.filter(MagicSetsGainsPricing.delta_percent>0)

Code:

class MagicSetsGainsPricing(graphene.ObjectType):
    value = graphene.String()
    delta_percent = graphene.String()
    delta_value = graphene.String()

    class Meta:
        model = magic_sets_cards_pricing

    def resolve_gains(self, info, days=None, **kwargs):
        us = auth_user_settings.objects.values('region', 'currency').get()
        if us['region'] == 'Europe':
            region_nf = 'eur'
            match us['currency']:
                case 'Pound':
                    symbol = '£'
                    rate = currency_exchanges.objects.filter(c_from='EUR').filter(c_to='GBP').values('rate').get()['rate']
                case 'Dollar':
                    symbol = '$'
                    rate = currency_exchanges.objects.filter(c_from='EUR').filter(c_to='USD').values('rate').get()['rate']
                case _:
                    symbol = '€'
                    rate = 1
        else:
            region_nf = 'usd'
            match us['currency']:
                case 'Pound':
                    symbol = '£'
                    rate = currency_exchanges.objects.filter(c_from='USD').filter(c_to='GBP').values('rate').get()['rate']
                case 'Euro':
                    symbol = '€'
                    rate = currency_exchanges.objects.filter(c_from='USD').filter(c_to='EUR').values('rate').get()['rate']
                case _:
                    symbol = '$'
                    rate = 1

        price_today = magic_sets_cards_pricing.objects.filter(card_id=self).values('date', region_nf).order_by('-date')[0:1][0][region_nf] or 0
        calc_price_today = round((price_today * rate), 2)

        price_previous = magic_sets_cards_pricing.objects.filter(card_id=self).values('date', region_nf).order_by('-date')[days:days+1][0][region_nf] or 0
        calc_price_previous = round((price_previous * rate), 2)

        calc_delta_value = str(calc_price_today - calc_price_previous)
        calc_delta_percent = str(round(((calc_price_today - calc_price_previous)  / (calc_price_previous or 1)) * 100, 2))

        return MagicSetsGainsPricing(
            value=symbol + str(calc_price_today),
            delta_value=symbol + calc_delta_value,
            delta_percent=calc_delta_percent + '%',
        )


class MagicSetsGains(DjangoObjectType):
    pricing = graphene.Field(MagicSetsGainsPricing, days=graphene.Int(), resolver=MagicSetsGainsPricing.resolve_gains)

    class Meta:
        model = magic_sets_cards

    def resolve_pricing(self, info, **kwargs):
        return magic_sets_cards_pricing.objects.filter(card_id=self)


class MagicSetsGainsQuery(ObjectType):
    magic_sets_gains = graphene.List(MagicSetsGains, code=graphene.String())

    def resolve_magic_sets_gains(self, info, code=None, **kwargs):
        sql_number_to_int = "CAST((REGEXP_MATCH(number, '\d+'))[1] as INTEGER)"
        excluded_sides = ['b', 'c', 'd', 'e']
        return magic_sets_cards.objects.filter(set_id__code=code).exclude(side__in=excluded_sides).extra(select={'int': sql_number_to_int}).order_by('int', 'number').all()

Response:

{
  "data": {
    "magicSetsGains": [
      {
        "number": "1",
        "name": "Adeline, Resplendent Cathar",
        "pricing": {
          "value": "£2.23",
          "deltaValue": "£0.52",
          "deltaPercent": "30.41%"
        }
      },
      {
        "number": "2",
        "name": "Ambitious Farmhand // Seasoned Cathar",
        "pricing": {
          "value": "£0.07",
          "deltaValue": "£-0.04",
          "deltaPercent": "-36.36%"
        }
      },
      ...
    ]
  }
}


Solution 1:[1]

So you cant use the model.objects.filter() because that filters the objects on the database level, and the database doesn't know about the "delta_percent" field that you've calculated at the schema level. However you can filter it in python using the filter function.

What I would suggest you do is this. Firstly, the code inside resolve_gains needs to move outside of the schema into a utility file and you can name it something like get_pricing_for_card(card, days=None) that returns a dict with {"value": blah, "delta_percent": blah, "delta_price": blah}.

Then your resolve_gains becomes:


def resolve_gains(self, info, days=None, **kwargs):
    pricing = get_pricing_for_card(self, days)
    return MagicSetsGainsPricing(**pricing)

Then MagicSetsGainsQuery becomes this:

class MagicSetsGainsQuery(ObjectType):
    magic_sets_gains = graphene.List(MagicSetsGains, code=graphene.String())

    def resolve_magic_sets_gains(self, info, code=None, **kwargs):
        sql_number_to_int = "CAST((REGEXP_MATCH(number, '\d+'))[1] as INTEGER)"
        excluded_sides = ['b', 'c', 'd', 'e']
        cards_to_return = magic_sets_cards.objects.filter(set_id__code=code).exclude(side__in=excluded_sides).extra(select={'int': sql_number_to_int}).order_by('int', 'number').all()
        return filter(lambda card: get_pricing_for_card(card)["delta_percent"] > 0, cards_to_return) 

Alternatively you can replace the return line with:

return [card for card in cards_to_return if get_pricing_for_card(card)["delta_percent"] > 0]

I know this isn't great because essentially you end up running get_pricing_for_card twice. This is a little annoying thing about the graphene schema where you can't really pass around values calculated in the resolve function. The larger refactor to this would be that you save the pricing in the pricing table with the delta percent field and so forth, when you create a card. then you can do pricing.objects.filter(delta_percent__gte=0)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1