假设我有一个项目的查询集,我想计算每个要在模板中显示的东西.
在我看来,我可以创建一个对象列表,对于每个对象,我可以在该对象上设置一个属性进行计算,然后我可以在模板中显示它.或者我可以创建一个字典列表,只获取我需要在每个字典中显示的字段以及计算字段.对于性能和一般实践来说哪个更好?
一个过于简化的示例,为了清晰起见(我知道我可以从模板中调用getAge(),我真正计算的是更复杂的,对于性能,我想在视图代码中进行计算):
models.py:
class Person(models.Model): first_name = ... last_name = ... date_of_birth = ... . . . def getAge(self): return ... # return the calculated years since date_of_birth
views.py:
def method1_object_property(request): people = Person.objects.all() for p in people: p.age = p.getAge() return render_to_response('template.htm',{'people': people}) def method2_dictionary(request): people = Person.objects.all() data = list() for p in people: row = dict() row['first_name'] = p.first_name row['last_name'] = p.last_name row['age'] = p.getAge() data.append(row) return render_to_response('template.htm',{'people': data})
template.htm:
<ul> {% for p in people %} {{ p.first_name }} {{ p.last_name }} (Age: {{ p.age }}) {% endfor %} </ul>
到目前为止,这两种方法都可以正常工作,我只是好奇首选方法是什么以及为什么.是否存在使用对象点属性方法(object.new_field =’some_detail’)将新字段动态分配给内存中现有对象的性能问题?
更新:
是的,我知道在我的例子中我可以从模板调用getAge(),是的,这是方法的不正确的命名标准,应该是带有下划线的小写.我认为我的例子太简单了,而且让我真正想知道的事情蒙上阴影.
将信息添加到我想要在视图中显示的对象的最佳方法是什么,该视图不是模型层的一部分.假设我得到一个Person对象的QuerySet,并想要计算他们在过去30,60和90天登录我网站的次数.我想动态为每个Person对象创建三个“属性”.我可以在视图中设置它
for p in people: p.last_30 = Login.objects.filter(person=p,login_date__gt=date.today()-timedelta(days=30)) p.last_60 = Login.objects.filter(person=p,login_date__gt=date.today()-timedelta(days=60)) p.last_90 = Login.objects.filter(person=p,login_date__gt=date.today()-timedelta(days=90))
然后在我的模板中,我可以显示那些“属性”.我只是想确保我没有违反某些Python标准或欺骗系统.或者,我可以将这些其他查找存储在字典中,其中对象在一个键/对中,并且各个细节在单独的键中.在视图中这是一个更多的工作,但我很好奇是否更好的性能或标准的合规性这样做?
对不起,如果我的原始问题不够清楚,或者我的例子增加了混乱.
解决方法
方法2毫无意义,您可以直接在模板中迭代查询集,无需在视图中构建中间的“词典列表”.例如,你可以这样做:
def method2_dictionary(request): people = Person.objects.all() return render_to_response('template.htm',{'people': people})
在您的模板中:
{% for p in people %} {{ p.first_name }} etc {% endfor %}
回到方法1 ……
这个:p.age = p.getAge()也没有意义,你可以直接调用模板中的方法{{p.getAge}}(只要你的方法不带参数),请看这里的文档:
https://docs.djangoproject.com/en/dev/topics/templates/#accessing-method-calls
请注意,在Python中,我们通常更喜欢使用“带下划线的小写”作为方法名称,例如def get_age(self)和{{p.get_age}}
(参见http://www.python.org/dev/peps/pep-0008/#function-names官方’PEP8’样式指南)
如果你的get_age方法没有side-effects并且没有参数你可能想让它成为一个属性,这是Python的方法,你可以访问没有括号的getter方法.
在这种情况下,将它命名为年龄是有意义的:
@property def age(self): return ... # return the calculated years since date_of_birth
并在您的模板中:
{% for p in people %} {{ p.first_name }} {{ p.age }} etc {% endfor %}
有关Python属性的更多信息,请参见此处:
http://docs.python.org/2/library/functions.html#property
在这个SO问题中的更多信息:
Real world example about how to use property feature in python?
UPDATE
参考您更新的问题…作为样式问题,我仍然会在模型上创建这些(last_30等)方法,而不是在视图代码中的每个模型实例上添加ad hoc属性.
从性能的角度来看,在大多数现实世界中,方法查找与字典等的内存,处理时间等的差异是微不足道的……到目前为止,这种代码中最大的性能考虑通常是数据库查询的数量.
如果您知道要为查询集中的每个项目执行额外的查询(或三个),那么值得寻找在一个或多个大查询中获取所有内容的方法.
在某些情况下,您可以使用annotate()方法:
https://docs.djangoproject.com/en/dev/ref/models/querysets/#annotate
(我认为这不可能在你的例子中)
在上面的特定代码中,您只需查询90天(最早的间隔),您可以在Python中过滤60天和30天的集,而无需再次查询数据库.
但是,对于人员查询集中的每个项目,仍然会进行一次额外查询.最好为人员的所有(或任何子集)的Login对象执行一个大查询.由于登录时Person有一个外键关系,我们可以在查询Login模型时使用select_related()
在一个大查询中获取Person实例:
def method3(request): logins = Login.objects.filter( person__in=Person.objects.all(),login_date__gt=date.today() - timedelta(days=90) ).order_by('person','login_date').select_related() return render_to_response('template.htm',{'logins': logins})
注意,如果你真的在做Person.objects.all(),你就不需要上面的person__in过滤器,只要你想以某种方式过滤Person集.
现在我们在一个大查询中获得了所有数据,我们可以在python端执行我们需要的操作来显示数据.例如,在模板中我们可以使用regroup
标签:
{% regroup logins by person as people %} {% for person in people %} {% with person.grouper as p %} {{ p.first_name }} {% for login in person.list %} {{ login.login_date }} {% endfor %} {% endwith %} {% endfor %}
你可以更进一步,为登录日期范围写一个自定义标签…我不会在这里详细说明,但在你的模板中它可能看起来像:
{% regroup logins by person as people %} {% for person in people %} {% with person.grouper as p %} {{ p.first_name }} {% logins_since person.list 60 as last_60_days %} {% logins_since person.list 30 as last_30_days %} {% for login in last_30_days %} {{ login.login_date }} {% endfor %} {% for login in last_60_days %} {{ login.login_date }} {% endfor %} {% endwith %} {% endfor %}