Two foreign keys and a value in django template
I am a newbie to django, so the question might be dumb, but please feel free to teach me the right way if you know it. I tried googling the issue, but I am still at loss. Here's my problem:
I have a class in my model that has two foreign keys:
class X(models.Model):
name = models.CharField(max_length=30)
def __unicode__(self):
return name
class Y(models.Model):
name = models.CharField(max_length=30)
def __unicode__(self):
return name
class Z(models.Model):
name = mod开发者_如何学Pythonels.CharField(max_length=30)
x = models.ForeignKey(X)
y = models.ForeignKey(Y)
def __unicode__(self):
return name
In my view I get a partial list of X objects and a partial list of Y objects like so:
def MyView(x_pattern, y_pattern):
x_list = X.objects.filter(name__contains=x_pattern)
y_list = Y.objects.filter(name__contains=y_pattern)
z_list = Z.objects.all()
return render_to_response({'x_list': x_list, 'y_list': y_list, 'z_list': z_list})
In my template I'd like to be able to display a table like so:
<table>
<tr>
<td>Y</td>
{% for x in x_list %}
<td>{{ x }}</td>
{% endfor %}
</tr>
{% for y in y_list %}
<tr>
<td>{{ y }}</td>
{% for x in x_list %}
<td>
<!-- here I need help:
I need to display z[x, y] if it exists, or "N/A" otherwise.
-->
</td>
{% endfor %}
</tr>
{% endfor %}
How do I do this properly in django?
Thank you very much,
As @DZPM suggested, you should think of keeping the logic in your view. [sheepish] I once invented my own "Table" data structure to do something very similar. The table was had rows corresponding to X
, columns corresponding to Y
and cells corresponding to Z[X, Y]
. I then wrote get_row
and get_cell
filters to do the trick in the template. [/sheepish]
That said, what you want can be accomplished using a pair of custom filters. This solution is rather verbose.
@register.filter
def x_is(value, x):
return value.x == x
@register.filter
def y_is(value, y):
return value.y == y
You can use these filters in the template as shown below:
{% if z|x_is:x and z|y_is:y %}
{{ z }}
{% else %}
N/A
{% endif %}
You should keep the logic in your view. Why don't you just filter the z_list there?
z_list = Z.objects.filter(x__name__contains=x_pattern, y__name__contains=y_pattern)
Another approach would be to create a generator in your view, and then send it to your template context:
# nested inside your view function
def x_and_z_list(y):
for x in x_list:
z_obj = x.z_set.filter(y=y)
z_name = z_obj or 'N/A'
yield {'x': x, 'z': z_name}
return render_to_response('mytemplate', {'list_generator': x_and_z_list}
Then your template could look like this:
{% for y in y_list %}
<tr>
<td>{{ y }}</td>
{% for pair in list_generator.y %} {# pair is the dict you yielded before #}
<td>{{ pair.x.name }}: {{ pair.z }}</td>
{% endfor %}
</tr>
{% endfor %}
I combined the concepts of custom filter and functions as first class objects making a template filter into a functor (function-object).
Here is what I ended up doing:
def z_filter(x, y):
z_list = list(Z.objects.filter(x, y))
return z_list.pop().name or 'N/A'
register.filter(z_filter)
In template:
{% load z_filter %}
<table>
<tr>
<td>Y</td>
{% for x in x_list %}
<td>{{ x }}</td>
{% endfor %}
</tr>
{% for y in y_list %}
<tr>
<td>{{ y }}</td>
{% for x in x_list %}
<td>{{ x|z_filter:y }}</td>
{% endfor %}
</tr>
{% endfor %}
</table>
Thanks everybody for your help!
精彩评论