Reputation: 11193
i'm having trouble with returning data from many to many relationship between two models. i wan't to show all Currency? objects with their newest
Tickerobject related to that currency based on the
created_at` field. i'm wondering how i can create such an output in json?
desired output
[
{
"id": 1,
"name": "Bitcoin",
"symbol": "BTC",
"img": "/media/static/img/currencies/bitcoin_uzSOQkH.png",
"tickers":
{
"rank": 1,
"price_dkk": "111239.222648",
"market_cap_dkk": 1861795518438,
"percent_change_1h": "0.03",
"percent_change_24h": "2.44",
"percent_change_7d": "46.80",
"created_at": "2017-12-12T20:11:49.995851Z"
}
}
]
view
class CurrencyGetView(ProtectedResourceView):
def get(self, request):
currencies = CurrencySerializer(Currency.objects.all(), many=True)
return JsonResponse(currencies.data, safe=False)
serializers
class TickerSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Ticker
fields = ('rank', 'price_dkk', 'market_cap_dkk', 'percent_change_1h', 'percent_change_24h', 'percent_change_7d', 'created_at', )
class CurrencySerializer(serializers.HyperlinkedModelSerializer):
tickers = TickerSerializer(many=True)
class Meta:
model = Currency
fields = ('id', 'name','symbol', 'img', 'tickers',)
models
class Ticker(models.Model):
rank = models.IntegerField()
price_dkk = models.DecimalField(max_digits=20, decimal_places=6)
market_cap_dkk = models.BigIntegerField()
percent_change_1h = models.DecimalField(max_digits=4, decimal_places=2)
percent_change_24h = models.DecimalField(max_digits=4, decimal_places=2)
percent_change_7d = models.DecimalField(max_digits=4, decimal_places=2)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name_plural = _('Ticker')
def __str__(self):
return self.id
class Currency(models.Model):
symbol = models.CharField(max_length=4, default='BTC', unique=True)
name = models.CharField(max_length=20, default='Bitcoin', unique=True)
img = models.ImageField(upload_to = 'static/img/currencies', blank=True)
is_active = models.BooleanField(default=False)
tickers = models.ManyToManyField(Ticker)
class Meta:
verbose_name_plural = 'currencies'
def __str__(self):
return self.name
Upvotes: 3
Views: 215
Reputation: 3038
I feel that this should be solved on the query level, ideally with prefetch_related
. But Django can't slice Prefetch objects. Bummer. And Subquery is painstakingly slow.
The best solution I can think of, is creating a SerializerMethodField, which calls the TickerSerializer
with a sliced dataset. The drawback is that DRF will fire an additional SQL query for each Currency
object in the list.
from rest_framework.fields import SerializerMethodField
class CurrencySerializer(serializers.HyperlinkedModelSerializer):
ticker = SerializerMethodField()
def get_ticker(self, obj):
return TickerSerializer(
instance=obj.tickers.order_by('-created_at')[:1],
many=True
).data
class Meta:
model = Currency
fields = ('id', 'name','symbol', 'img', 'tickers', 'ticker')
Upvotes: 3
Reputation: 1612
Think something like this would work, the problem with the current implementation is that the many=True
would get all the related tickers and return those objects in a list, which is the correct behaviour, but since you want to only display the latest ticker for each currency you'll need to serialize only the latest object.
class CurrencySerializer(serializers.HyperlinkedModelSerializer):
tickers = serializers.SerializerMethodField()
class Meta:
model = Currency
fields = ('id', 'name','symbol', 'img', 'tickers')
def get_tickers(self, obj):
data = {}
try:
latest_ticker = obj.tickers.latest('-created_at')
data = TickerSerializer(instance=latest_ticker).data
except Ticker.DoesNotExist:
pass
return data
Upvotes: 1