views:

447

answers:

6

I have something like this in models.py

class ZipCode(models.Model):
zip = models.CharField(max_length=20)
cities = City.objects.filter(zip=self).distinct()

class City(models.Model):
name = models.CharField(max_length=50)
slug = models.CharField(max_length=50)
state = models.ForeignKey(State)
zip = models.ManyToManyField(ZipCode)

When I do this I get:

NameError: name 'City' is not defined

Is this because the order of declaration matters? And if so, how can I do this, because either way I arrange this, it looks like I'm going to get a NameError.

Thanks.

A: 

Order matters in Python. This thread may be relevant to your question. Also for your uses, you may want to use a unique foreign key in the ZIP code class.

Joey Robert
+1  A: 

Yes, order does matter, but your example does not look right to me. I think you should just be using a foreign key for your many-to-one relationship:

cities = models.ForeignKey(City)

This has the details on many-to-one relationships with django models.

Edit:

It was pointed out to me in the comments that cities in Europe might have several cities in the same zip code. If you are looking for a many-to-many relationship here, you should use:

cities = models.ManyToManyField(City)

This is described in Django's documentation. The point is, this is either of these examples are much more clear than what is used in the example.

Shane C. Mason
Hi Shane, your suggestion seems to imply that a zip code can be associated with only one city, but I thought that zip codes can belong to more than one city and one city can have multiple zip codes. Isn't that the case?
rick
No, you can have many-to-one relationships, but I am not familiar with the syntax you have used here. According to the link I provided above: "To define a many-to-one relationship, use ForeignKey()"
Shane C. Mason
Many-to-one and many-to-many are two different relationships, Shane. In this case many-to-many in probably more appropriate as a single zip code may represent multiple cities (thinking internationally). That's besides the point though. Declaring a binary relationship in both directions is both a bad idea, and redundant.
ozan
OK, If he wants a many to many relationship, he should use models.ManyToManyField(City) - I will add that to my answer.
Shane C. Mason
+2  A: 

When you have references to classes defined after, you can use this trick:

attribute = models.ForeignKey('ClassDefinedAfterThis')
Alex. S.
Would this work though?cities = 'City'.objects.filter(zip=self).distinct()
rick
Their position should not matter at that point
phillc
@rick: No, that will not work.
Carl Meyer
+1  A: 

I was once worried about order... because I thought my models below could only reference models above. But then realized that you can just do a

models.ForeignKey('appName.modelName')

and all was fine.

phillc
A: 

Yes order does matter as others have noted.

Though, encountering this issue is almost always going to be an indication that you're doing something wrong.

In this case your declaration:

cities = City.objects.filter(zip=self).distinct()

... is both redundant and bad practice. You can find the cities related to a zip code by referring to that zip code's city_set in your views (ie not in your model!). So if zip is an instance of ZipCode, you would do:

cities = zip.city_set.all()

If you really want to call it 'cities' rather than 'city_set' you can use the related_name parameter in your m2m declaration.

ozan
+3  A: 

Apart from order issues, this is wrong:

cities = City.objects.filter(zip=self).distinct()

It is not inside a method, so "self" will also be undefined. It is executed only once, at class-creation time (i.e. when the module is first imported), so the attribute created would be a class attribute and have the same value for all instances. What you might be looking for is this:

@property
def cities(self):
  return City.objects.filter(zip=self).distinct()

Because this is inside a method, which is not executed until it's accessed, ordering issues will no longer be a problem. As ozan points out, this is a duplication of what Django reverse relations already give you for free:

a_zip_code.city_set.all()

And you can use related_name to call it what you like:

zip = models.ManyToManyField(ZipCode, related_name='cities')
...
a_zip_code.cities.all()

So I don't think the ordering issue you originally asked about is even relevant to your situation. When it is, others have already pointed out using quoted strings in ForeignKey and ManyToManyField declarations to get around it.

Carl Meyer