I am relatively new to django and the DRF. I am trying to do an API for a couple games where either a resource is shown or a resource and another word (tagging).
I am trying to get a randomly chosen tag object along with the randomly chosen resource in a view for one of the games. Currently, I am getting a random resource with the list of tags for that resource (as per the SuggestionsSerializer). However, I cannot seem to be able to filter out a randomly chosen tag.
I have tried this both over the approach:
order_by("?").first()
as well as using my method from the controller class that I have also listed below (after filtering out the tags for the particular resource).
What I suspect is that there are simply too many objects in the Tagging table and maybe my get_random_object() method is not efficient enough for the Tagging table. Is this the problem or am I missing something? How else could I solve this?
Thank you in advance.
models.py
class Resource(models.Model):
id = models.PositiveIntegerField(null=False, primary_key=True)
hash_id = models.CharField(max_length=256)
creators = models.ManyToManyField(Creator)
titles = models.ManyToManyField(Title)
created_start = models.DateField(null=True)
created_end = models.DateField(null=True)
location = models.CharField(max_length=512, null=True)
institution_source = models.CharField(max_length=512, blank=True)
institution = models.CharField(max_length=512, blank=True)
origin = models.URLField(max_length=256, null=True)
enabled = models.BooleanField(default=True)
media_type = models.CharField(max_length=256, default='picture')
objects = models.Manager()
def __str__(self):
return self.hash_id or ''
@property
def tags(self):
tags = self.taggings.values('tag').annotate(count=Count('tag'))
return tags.values('tag_id', 'tag__name', 'tag__language', 'count')
class Tagging(models.Model):
user = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, null=True)
gameround = models.ForeignKey(Gameround, on_delete=models.CASCADE, related_name='taggings')
resource = models.ForeignKey(Resource, on_delete=models.CASCADE, related_name='taggings')
tag = models.ForeignKey(Tag, on_delete=models.CASCADE)
created = models.DateTimeField(editable=False)
score = models.PositiveIntegerField(default=0)
origin = models.URLField(max_length=256, blank=True, default='')
objects = models.Manager()
def __str__(self):
return str(self.tag) or ''
serializers.py
class TaggingSerializer(serializers.ModelSerializer):
tag = TagSerializer(read_only=True)
resource = ResourceSerializer(read_only=True)
gameround = GameroundSerializer(read_only=True)
class Meta:
model = Tagging
fields = ('id', 'tag', 'gameround', 'created', 'score', 'resource')
def create(self, validated_data):
return Tagging.objects.create(**validated_data)
def to_representation(self, data):
data = super().to_representation(data)
return data
class SuggestionsSerializer(serializers.ModelSerializer):
creators = CreatorSerializer(many=True)
titles = TitleSerializer(many=True)
suggestions = serializers.SerializerMethodField('get_suggestions')
class Meta:
model = Resource
fields = ['id', 'hash_id', 'titles', 'creators', 'suggestions']
read_only_fields = ['titles', 'creators', 'institution']
def get_suggestions(self, res):
suggestions = res.tags
return suggestions
def to_representation(self, data):
data = super().to_representation(data)
return data
views.py
class GameView(APIView):
"""
API endpoint that retrieves the game
"""
def get(self, request, *args, **kwargs):
controller = GameViewController()
resource_suggestions = Resource.objects.all().filter(id=controller.get_random_object(Resource))
suggestions_serializer = SuggestionsSerializer(resource_suggestions, many=True)
# first approach
tag = Tagging.objects.all().filter(resource=resource_suggestions, id=controller.get_random_object(Tagging))
# second approach
tag = Tagging.objects.all().filter(resource=resource_suggestions).order_by('?').first()
tagging_serializer = TaggingSerializer(tag)
return Response({
'tag': tagging_serializer.data,
'resource and suggestions': suggestions_serializer.data,
})
GameViewController class inside the views.py
class GameViewController:
...
def get_random_object(self, MyModel):
random_object = None
object_count = MyModel.objects.all().count() 1
while not MyModel.objects.all().filter(id=random_object).exists():
for obj in range(object_count):
n = random.randint(1, object_count)
if MyModel.objects.all().filter(id=n).exists():
random_object = n
return random_object
- I get an "The QuerySet value for an exact lookup must be limited to one result using slicing." error with the order_by('?).first() approach and simply no result with the get_random_object() since the query set is probably too large for this method to work efficiently.
CodePudding user response:
The issue is in this line of code
tag=Tagging.objects.all().filter(resource=resource_suggestions).order_by('?').first()
resource_suggestions appears to be a query set, at the very least, according to the error, it is more than one object. If you want to filter your tagging objects by anything within this queryset, you need to do it like this:
.filter(resource__in=resource_suggestions)
The __in operator will filter by anything that is within the queryset resource suggestions.
