Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

django giving TypeError when using len() on a QuerySet

Tags:

python

django

I am programming a very simple application to store results of soccer matches, and I am stuck with the following problem. When running one of the unit tests, the following code:

listCompetition = Competition.objects.filter(compId=competitionId)
if len(listCompetition) == 0:
   #some code here
else:
   #some code here

gives the following error:

File "C:\Users\admin\workspace\project\src\bla\bla\module.py", line 222, in getMatches
   if len(listCompetition) == 0:
File "C:\Python27\lib\site-packages\django\db\models\query.py", line 82, in __len__
   self._result_cache = list(self.iterator())
File "C:\Python27\lib\site-packages\django\db\models\query.py", line 286, in iterator
   obj = model(*row[index_start:aggregate_start])
TypeError: __init__() takes exactly 3 arguments (4 given)

However, if I substitute the first line of code by:

listCompetition = list(Competition.objects.filter(compId=competitionId))

then it works perfectly fine. Why does it behave in this strange way? How comes that Django is passing 4 parameters, if I have defined only two in the constructor of class Competition? If it helps, here is the model definition for class Competition:

class Competicion(MultiName):
   def __init__(self, canonicalName, compId):
      super(Competition, self).__init__(canonicalName, compId)

class MultiName(models.Model):
   entId = models.CharField(null=True, max_length=25); 
   canonicalName = models.CharField(max_length=50, primary_key=True);

   def __init__(self, canonicalName, entId=None):
      super(MultiName, self).__init__()
      self.canonicalName = canonicalName;
      self.entId = entId;

Thank you very much.

like image 571
joanlofe Avatar asked Jun 10 '11 23:06

joanlofe


3 Answers

Simple: in your first case, you're getting back a queryset object, not a list. Querysets are Iterators. List objects are also Iterators, but Iterators are not lists.

Django does this to optimize memory and performance: the database keeps the set, and Django reads the response one item at a time, each time you request an object from the queryset. Using list() causes Django to read the entire response and pack it into a list object. If the return set is very large, this can be problematic.

To know how big a queryset is, use the Queryset.count() method instead.

like image 162
Elf Sternberg Avatar answered Nov 12 '22 06:11

Elf Sternberg


Instead of

if len(listCompetition) == 0:

use

if listCompetition.count() == 0:

or perhaps

if not listCompetition.exists():
like image 34
Leopd Avatar answered Nov 12 '22 05:11

Leopd


It's broken because you've broken the conventions in use by Django models when you overrode __init__. Why are you implementing __init__ anyway? What you're implementing is… already implemented.

You seem to be making assumptions about how to use Django that are unfounded. I suggest you read the tutorial before proceeding.

To fix your code, change it to the following:

class Competition(MultiName):
    def __init__(self, *args, **kwargs):
        if "compId" in kwargs:
            kwargs["entId"] = kwargs.pop("compId")
        super(Competition, self).__init__(*args, **kwargs)

class MultiName(models.Model):
    entId = models.CharField(null=True, max_length=25); 
    canonicalName = models.CharField(max_length=50, primary_key=True);

There's a lot of great documentation for models that covers this stuff.

like image 30
bradley.ayers Avatar answered Nov 12 '22 07:11

bradley.ayers