DB index, improve Pokemon performance (#194)

* DB index, improve Pokemon performance

* fix test

* blep
This commit is contained in:
Paul Hallett 2016-05-30 14:58:03 +01:00
parent 8537999858
commit 4919c0674d
6 changed files with 585 additions and 113 deletions

View file

@ -1,6 +1,8 @@
from __future__ import unicode_literals
from rest_framework import viewsets
from rest_framework import status, viewsets
from rest_framework.response import Response
from rest_framework.views import APIView
from django.shortcuts import get_object_or_404
from .models import * # NOQA
from .serializers import * # NOQA
@ -396,3 +398,78 @@ class VersionGroupResource(PokeapiCommonViewset):
queryset = VersionGroup.objects.all()
serializer_class = VersionGroupDetailSerializer
list_serializer_class = VersionGroupSummarySerializer
class PokemonEncounterView(APIView):
"""
Handles Pokemon Encounters as a sub-resource.
"""
def get(self, request, pokemon_id):
self.context = dict(request=request)
try:
pokemon = Pokemon.objects.get(pk=pokemon_id)
except Pokemon.DoesNotExist:
# No pokemon exists!
return Response({}, status=status.HTTP_404_NOT_FOUND)
# get versions for later use
version_objects = Version.objects.all()
version_data = VersionSummarySerializer(
version_objects, many=True, context=self.context).data
# all encounters associated with location area
all_encounters = Encounter.objects.filter(
pokemon=pokemon).order_by(
'location_area').values_list('location_area', flat=True)[:200]
encounters_list = []
# break encounters into pokemon groupings
location_area_objects = LocationArea.objects.filter(pk__in=all_encounters)
for area in all_encounters:
location_area_detail = OrderedDict()
location_area_detail['location_area'] = LocationAreaSummarySerializer(
location_area_objects.get(pk=area), context=self.context).data
location_area_detail['version_details'] = []
area_encounters = Encounter.objects.filter(
pk__in=all_encounters,
location_area=location_area_objects.get(pk=area)
).order_by('version').distinct()
# each pokemon has multiple versions it could be encountered in
for ver in area_encounters:
version_detail = OrderedDict()
version_detail['max_chance'] = 0
version_detail['encounter_details'] = []
version_detail['version'] = version_data[ver.version.id - 1]
area_data = EncounterDetailSerializer(
area_encounters.filter(version=ver),
many=True, context=self.context).data
# each version has multiple ways a pokemon can be encountered
for encounter in area_data:
slot = EncounterSlot.objects.get(pk=encounter['encounter_slot'])
slot_data = EncounterSlotSerializer(slot, context=self.context).data
del encounter['pokemon']
del encounter['encounter_slot']
del encounter['location_area']
del encounter['version']
encounter['chance'] = slot_data['chance']
version_detail['max_chance'] += slot_data['chance']
encounter['method'] = slot_data['encounter_method']
version_detail['encounter_details'].append(encounter)
location_area_detail['version_details'].append(version_detail)
encounters_list.append(location_area_detail)
return Response(encounters_list)

View file

@ -0,0 +1,500 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('pokemon_v2', '0002_itemsprites_pokemonformsprites_pokemonsprites'),
]
operations = [
migrations.AlterField(
model_name='ability',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='abilityname',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='berry',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='berryfirmness',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='berryfirmnessname',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='berryflavor',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='berryflavorname',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='contesttype',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='contesttypename',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='egggroup',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='egggroupname',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='encountercondition',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='encounterconditionname',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='encounterconditionvalue',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='encounterconditionvaluename',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='encountermethod',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='encountermethodname',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='evolutiontrigger',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='evolutiontriggername',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='gender',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='generation',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='generationname',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='growthrate',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='item',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='itemattribute',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='itemattributename',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='itemcategory',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='itemcategoryname',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='itemflingeffect',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='itemname',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='itempocket',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='itempocketname',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='language',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='languagename',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='location',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='locationarea',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='locationareaname',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='locationname',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='move',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='moveattribute',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='moveattributename',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='movebattlestyle',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='movebattlestylename',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='movedamageclass',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='movedamageclassname',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='movelearnmethod',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='movelearnmethodname',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='movemetaailment',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='movemetaailmentname',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='movemetacategory',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='movename',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='movetarget',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='movetargetname',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='nature',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='naturename',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='palparkarea',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='palparkareaname',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='pokeathlonstat',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='pokeathlonstatname',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='pokedex',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='pokedexname',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='pokemon',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='pokemoncolor',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='pokemoncolorname',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='pokemonform',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='pokemonformname',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='pokemonhabitat',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='pokemonhabitatname',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='pokemonshape',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='pokemonshapename',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='pokemonspecies',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='pokemonspeciesname',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='region',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='regionname',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='stat',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='statname',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='type',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='typename',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='version',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='versiongroup',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='versionname',
name='name',
field=models.CharField(max_length=100, db_index=True),
preserve_default=True,
),
]

View file

@ -275,7 +275,7 @@ class HasMoveTarget(models.Model):
class HasName(models.Model):
name = models.CharField(max_length=100)
name = models.CharField(max_length=100, db_index=True)
class Meta:
abstract = True

View file

@ -1,5 +1,6 @@
from __future__ import unicode_literals
from django.core.urlresolvers import reverse
from rest_framework import serializers
from collections import OrderedDict
import json
@ -2538,60 +2539,7 @@ class PokemonDetailSerializer(serializers.ModelSerializer):
def get_encounters(self, obj):
# get versions for later use
version_objects = Version.objects.all()
version_data = VersionSummarySerializer(
version_objects, many=True, context=self.context).data
# all encounters associated with location area
all_encounters = Encounter.objects.filter(pokemon=obj).order_by('location_area')
encounters_list = []
# break encounters into pokemon groupings
for area in all_encounters.values('location_area').distinct():
location_area_object = LocationArea.objects.get(pk=area['location_area'])
location_area_detail = OrderedDict()
location_area_detail['location_area'] = LocationAreaSummarySerializer(
location_area_object, context=self.context).data
location_area_detail['version_details'] = []
area_encounters = all_encounters.filter(
location_area=area['location_area']).order_by('version')
# each pokemon has multiple versions it could be encountered in
for ver in area_encounters.values('version').distinct():
version_detail = OrderedDict()
version_detail['max_chance'] = 0
version_detail['encounter_details'] = []
version_detail['version'] = version_data[ver['version'] - 1]
area_data = EncounterDetailSerializer(
area_encounters.filter(version=ver['version']),
many=True, context=self.context).data
# each version has multiple ways a pokemon can be encountered
for encounter in area_data:
slot = EncounterSlot.objects.get(pk=encounter['encounter_slot'])
slot_data = EncounterSlotSerializer(slot, context=self.context).data
del encounter['pokemon']
del encounter['encounter_slot']
del encounter['location_area']
del encounter['version']
encounter['chance'] = slot_data['chance']
version_detail['max_chance'] += slot_data['chance']
encounter['method'] = slot_data['encounter_method']
version_detail['encounter_details'].append(encounter)
location_area_detail['version_details'].append(version_detail)
encounters_list.append(location_area_detail)
return encounters_list
return reverse('pokemon_encounters', kwargs={'pokemon_id': obj.pk})
#################################

View file

@ -4089,12 +4089,12 @@ class APITests(APIData, APITestCase):
encounter_method = self.setup_encounter_method_data(name='encntr mthd for lctn area')
location_area1 = self.setup_location_area_data(name='lctn1 area for base pkmn')
encounter_slot1 = self.setup_encounter_slot_data(encounter_method, slot=1, rarity=30)
encounter1 = self.setup_encounter_data(
self.setup_encounter_data(
location_area=location_area1, pokemon=pokemon,
encounter_slot=encounter_slot1, min_level=30, max_level=35)
location_area2 = self.setup_location_area_data(name='lctn2 area for base pkmn')
encounter_slot2 = self.setup_encounter_slot_data(encounter_method, slot=2, rarity=40)
encounter2 = self.setup_encounter_data(
self.setup_encounter_data(
location_area=location_area2, pokemon=pokemon,
encounter_slot=encounter_slot2, min_level=32, max_level=36)
response = self.client.get(
@ -4216,61 +4216,6 @@ class APITests(APIData, APITestCase):
self.assertEqual(
response.data['forms'][0]['url'],
'{}{}/pokemon-form/{}/'.format(test_host, api_v2, pokemon_form.pk))
# encounter params
self.assertEqual(
response.data['location_area_encounters'][0]['location_area']['name'],
location_area1.name)
self.assertEqual(
response.data['location_area_encounters'][0]['location_area']['url'],
'{}{}/location-area/{}/'.format(test_host, api_v2, location_area1.pk))
self.assertEqual(
response.data['location_area_encounters'][0]['version_details'][0]['max_chance'],
encounter_slot1.rarity)
self.assertEqual(
response.data['location_area_encounters'][0]['version_details'][0]['version']['name'],
encounter1.version.name)
self.assertEqual(
response.data['location_area_encounters'][0]['version_details'][0]['version']['url'],
'{}{}/version/{}/'.format(test_host, api_v2, encounter1.version.pk))
self.assertEqual(
response.data['location_area_encounters'][0].get(
'version_details')[0]['encounter_details'][0]['chance'],
encounter_slot1.rarity)
self.assertEqual(
response.data['location_area_encounters'][0].get(
'version_details')[0]['encounter_details'][0]['method']['name'],
encounter_method.name)
self.assertEqual(
response.data['location_area_encounters'][0].get(
'version_details')[0]['encounter_details'][0]['method']['url'],
'{}{}/encounter-method/{}/'.format(test_host, api_v2, encounter_method.pk))
self.assertEqual(
response.data['location_area_encounters'][1]['location_area']['name'],
location_area2.name)
self.assertEqual(
response.data['location_area_encounters'][1]['location_area']['url'],
'{}{}/location-area/{}/'.format(test_host, api_v2, location_area2.pk))
self.assertEqual(
response.data['location_area_encounters'][1]['version_details'][0]['max_chance'],
encounter_slot2.rarity)
self.assertEqual(
response.data['location_area_encounters'][1]['version_details'][0]['version']['name'],
encounter2.version.name)
self.assertEqual(
response.data['location_area_encounters'][1]['version_details'][0]['version']['url'],
'{}{}/version/{}/'.format(test_host, api_v2, encounter2.version.pk))
self.assertEqual(
response.data['location_area_encounters'][1].get(
'version_details')[0]['encounter_details'][0]['chance'], encounter_slot2.rarity)
self.assertEqual(
response.data['location_area_encounters'][1].get(
'version_details')[0]['encounter_details'][0]['method']['name'],
encounter_method.name)
self.assertEqual(
response.data['location_area_encounters'][1].get(
'version_details')[0]['encounter_details'][0]['method']['url'],
'{}{}/encounter-method/{}/'.format(test_host, api_v2, encounter_method.pk))
# sprite params
self.assertEqual(
response.data['sprites']['front_default'],

View file

@ -71,4 +71,6 @@ router.register(r"version-group", VersionGroupResource)
urlpatterns = [
url(r'^api/v2/', include(router.urls)),
url(r'^api/v2/pokemon/(?P<pokemon_id>\d+)/encounters',
PokemonEncounterView.as_view(), name='pokemon_encounters')
]