Casey Schneider
Casey Schneider

Reputation: 1002

How to make graphql query that can find "latest" or "min" or "max"

I'm trying to use graphql with python to retrieve min and max temperatures within a time range.

The temperature table has 2 columns: timestamp and value.

My models.py is:

from django.db import models

class Temperature(models.Model):
    timestamp = models.DateTimeField(auto_now=True)
    value = models.FloatField()

    def __str__(self):
        return f"the value:time is {self.value}:{self.timestamp}"

and schema.py is:

class Query(graphene.ObjectType):
    temperature_statistics = graphene.Field(Temperature)

    def resolve_temperature_statistics(self, info, **kwargs):
        before = kwargs.get("before")
        after = kwargs.get("after")
    
        return models.Temperature.objects.filter(created_at__gte=after, created_at__lte=before)

The query that returns the min and max temperatures over a timespan, should be like this:

query {
    temperatureStatistics(after: "2020-12-06T12:00:00+00:00", before: "2020-12-07T12:00:00+00:00") {
        min
        max
    }
}

I cannot figure out how to implement the min and max parts of this.

Since the min and max are used in the graphql query I cannot just return min and max from a resolver, for example if the query were written like this,

query {
    temperatureMax(after: "2020-12-06T12:00:00+00:00", before: "2020-12-07T12:00:00+00:00")
}

I have to somehow figure out how to enable graphql to do that work.

In response to @Ahtisham 's comment. Here is how I implemented currentTemperature without Types.

schema.py

import graphene
from graphene_django import DjangoObjectType

from temperature import models

class Temperature(DjangoObjectType):
    class Meta:
        model = models.Temperature

class Query(graphene.ObjectType):
    current_temperature = graphene.Field(Temperature)

    def resolve_current_temperature(self, info, **kwargs):
        return models.Temperature.objects.last()


schema = graphene.Schema(query=Query)

models.py

from django.db import models

class Temperature(models.Model):
    timestamp = models.DateTimeField(auto_now=True)
    value = models.FloatField()

This worked when queried with this graphql query:

query {
    currentTemperature {
        timestamp
        value
    }
}

UPDATE 2

I have updated my schema.py according to @Ahtisham 's answer and now have this as my schema.py:

import graphene
from graphene_django import DjangoObjectType
from django.db.models import Max, Min


from temperature import models

class Temperature(DjangoObjectType):
    class Meta:
        model = models.Temperature

class RangeType(graphene.ObjectType):
    min = graphene.String()
    max = graphene.String()

class Query(graphene.ObjectType):
    current_temperature = graphene.Field(Temperature, id=graphene.Int())
    temperature_statistics = graphene.Field(Temperature, before=graphene.String(), after=graphene.String())

    def resolve_current_temperature(self, info, **kwargs):
        return models.Temperature.objects.latest('timestamp')
        

    def resolve_temperature_statistics(self, info, before=None, after=None):
        range_type = RangeType()

        if before and after is not None:
            range_type.min_ = models.Temperature.objects.filter(created_at__gte=after, created_at__lte=before).aggregate(Min('temperature'))
            range_type.max_ = models.Temperature.objects.filter(created_at__gte=after, created_at__lte=before).aggregate(Max('temperature'))
            return range_type
            
        elif before is None and after is not None:
            range_type.min_ = models.Temperature.objects.filter(created_at__gte=after).aggregate(Min('temperature'))
            range_type.max_ = models.Temperature.objects.filter(created_at__gte=after).aggregate(Max('temperature'))
            return range_type

        elif after is None and before is not None:
            range_type.min_ = models.Temperature.objects.filter(created_at__lte=before).aggregate(Min('temperature'))
            range_type.max_ = models.Temperature.objects.filter(created_at__lte=before).aggregate(Max('temperature'))
            return range_type

        else:
            range_type.min_ = models.Temperature.objects.aggregate(Min('temperature'))
            range_type.max_ = models.Temperature.objects.aggregate(Max('temperature'))
            return range_type

    
schema = graphene.Schema(query=Query)

Graphql gives me this error

{
  "errors": [
    {
      "message": "Cannot query field \"min_\" on type \"Temperature\".",
      "locations": [
        {
          "line": 3,
          "column": 7
        }
      ]
    },
    {
      "message": "Cannot query field \"max_\" on type \"Temperature\".",
      "locations": [
        {
          "line": 4,
          "column": 7
        }
      ]
    }
  ]
}

when executing this query

query {
    temperatureStatistics {
                min
                max
        }
}

UPDATE 3

After changing

    temperature_statistics = graphene.Field(Temperature, before=graphene.String(), after=graphene.String())

to

    temperature_statistics = graphene.Field(RangeType, before=graphene.String(), after=graphene.String())

Graphql now gives me this error

{
  "errors": [
    {
      "message": "Cannot resolve keyword 'temperature' into field. Choices are: id, timestamp, value",
      "locations": [
        {
          "line": 2,
          "column": 5
        }
      ],
      "path": [
        "temperatureStatistics"
      ]
    }
  ],
  "data": {
    "temperatureStatistics": null
  }
}

when executing this query

query {
    temperatureStatistics {
                min
                max
        }
}

Also:

Graphql gives me this error

{
  "errors": [
    {
      "message": "Cannot resolve keyword 'created_at' into field. Choices are: id, timestamp, value",
      "locations": [
        {
          "line": 2,
          "column": 5
        }
      ],
      "path": [
        "temperatureStatistics"
      ]
    }
  ],
  "data": {
    "temperatureStatistics": null
  }
}

when executing this query

query {
    temperatureStatistics(after: "2020-12-06T12:00:00+00:00", before: "2020-12-07T12:00:00+00:00")) {
                min
                max
        }
}

UPDATE 4

The last error was because I was using 'temperature' instead of 'value' in my django query. It works now.

Upvotes: 1

Views: 2031

Answers (1)

Ahtisham
Ahtisham

Reputation: 10126

You can define a new Type with min and max then add it to your Query like this:

class RangeType(graphene.ObjectType):
    min = graphene.String()
    max = graphene.String()
       

class Query(graphene.ObjectType):
    temperature_statistics = graphene.Field(
       RangeType, before=graphene.String(), after=graphene.String()
    )

    def resolve_temperature_statistics(self, info, before=None, after=None):
        range_type = RangeType()
        range_type.min = # query to get min value
        range_type.max = # query to get max value
        return range_type

Calling the query should be the same.

Upvotes: 1

Related Questions