Reputation: 8285
Let's say I have these models:
class A(models.Model):
name = models.CharField(max_length=32)
b = models.ForeignKey("B", null=True)
def __unicode__(self):
return self.name
class B(models.Model):
name = models.CharField(max_length=32)
def __unicode__(self):
return self.name
And this view:
def DisplayIt(request):
html = ""
for a in A.objects.all():
html += "<input type='text' value='" + a.b.name + "' />"
This works fine as long as every A has a reference to a B in field b. But, I have null=True on the b reference field. When b is None, an exception is thrown that NoneType has no function name, which is completely expected.
Obviously, I can do this:
def DisplayIt(request):
somestring = ""
for a in A.objects.all():
if a.b is not None:
somestring += a.b.name
BUT, I don't want to do this if I don't have to. So, is there a way to get b.name or None if b is None, WITHOUT putting an if a.b is not None:
in each loop? My real world example is much more complex, and I am creating a temporary object for display out of actual database fields... suffice it to say I'd prefer not to have all of those if statements, and wasn't sure if there was another built-in function to get there. I could also create a class method to get each of these foreign fields if they exist or blank if they don't, but I'm not sure this is the best way to go, either.
Upvotes: 2
Views: 1827
Reputation: 15206
Make the database do it for you.
Use a cross-relation filter to get all A
with a b
with name
which is not null:
def DisplayIt(request):
somestring = ""
for a in A.objects.filter(b__name__isnull=False):
somestring += a.b.name
To do this such that dummy values are included when a.b
is None, use the following:
def DisplayIt(request):
somestring = ""
for a in A.objects.filter(b__name__isnull=False):
somestring += a.b.name
for a in A.objects.filter(b__name__isnull=True):
somestring += "dummy value for missing b.name"
Also, as a side note, don't do string concatenation that way -- it's horribly inefficient, since Python strings aren't mutable. The following is much better:
def DisplayIt(request):
stringparts = []
for a in A.objects.filter(b__name__isnull=False):
stringparts.append(a.b.name)
for a in A.objects.filter(b__name__isnull=True):
stringparts.append("dummy value for missing b.name")
''.join(stringparts)
Second side note: The code above will perform a query to fetch a.b on each iteration of the for loop. That can be suuuuper slow. Consider using select_related
to grab everything all in one go. Be wary, though - this can be slow too if your indexes aren't set up correctly. After enabling this I'd sniff the queries and spend some quality time with your query planner to make sure you're not doing any nasty non-indexed lookups.
Revised code:
def DisplayIt(request):
stringparts = []
for a in A.objects.filter(b__name__isnull=False).select_related():
stringparts.append(a.b.name)
for a in A.objects.filter(b__name__isnull=True).select_related():
stringparts.append("dummy value for missing b.name")
''.join(stringparts)
Upvotes: 3
Reputation: 48710
Building off your comment that says you want the instances of A that may or may not have a B. You can hack it to work, which is usually a terrible idea. Use the long-winded pythonic way that you want to avoid. But here is the bad way to do what you want:
a.__dict__.get('b',{'name':None}).name
This uses the fact that all attributes are stored in a dict
object on the actual instance of the object. You use a standard dictionary accessor get()
to return either b
or a dictionary that contains an attribute that you're interested in. This is a terrible idea, but there it is.
Also, django/python is NOT old school PHP. I can't believe how many times I've seen this on stackoverflow in the last few weeks, but... DO NOT CONCATENATE STRINGS TO PRODUCE HTML!! That's what the Templating Language is for. Use it. Stop what you're doing RIGHT NOW. STOP IT STOP IT STOP IT. Use templates, do NOT try and write HTML in your views. It's a provable WRONG thing to do, so stop. Just stop.
Upvotes: 3