如何在OTree中延迟展示结果,将各应用结果页存为静态HTML?
实现oTree多应用结果统一展示的简单方案
嘿,我完全懂你的需求——不想搞一堆participant变量来存每个结果,而是直接把每个应用的结果页面“固化”成带真实数据的静态HTML,最后统一展示,这个思路真的很省心!作为老派开发者,咱就搞简单直接的方案,不用复杂的Bakery模块(那玩意儿更适合整站静态化,不太适配oTree这种动态会话场景),下面给你两个实用方向:
方案一:直接把渲染好的HTML存在Participant Vars里(最省事)
这个方案完全不用碰文件读写,直接把每个应用的结果页面渲染成字符串存在participant的变量里,最后统一拿出来展示,简单到爆:
步骤1:在每个应用的结果页面添加渲染逻辑
打开每个应用的pages.py,找到最后那个结果页面(比如ResultsPage),添加before_next_page方法:
from django.template.loader import render_to_string from otree.api import Page class ResultsPage(Page): def before_next_page(self): # 自动提取当前应用的名称(比如从模块名里拆出来) app_name = self.__class__.__module__.split('.')[0] # 用Django的模板渲染器把页面转成带真实值的HTML字符串 rendered_html = render_to_string( f"{app_name}/results_page.html", # 你的结果页面模板路径 context=self.get_context_data() # 这里包含了模板里所有需要的变量:player、group、participant等 ) # 把渲染好的HTML存到participant vars里,用应用名当键区分 self.participant.vars[f"app_results_{app_name}"] = rendered_html
这里的render_to_string会自动把模板里的{{player.payoff}}、{{group.total}}这些变量替换成实际数值,生成完整的HTML字符串。
步骤2:创建最终结果应用展示所有内容
新建一个叫FinalResults的应用(或者随便起个名字),在它的pages.py里写:
from otree.api import Page from django.utils.safestring import mark_safe class FinalResultsPage(Page): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) # 从participant vars里把所有应用的结果HTML捞出来 results_htmls = [] for key, html_str in self.participant.vars.items(): if key.startswith('app_results_'): # 标记这是安全的HTML,避免Django把标签转义成文本 results_htmls.append(mark_safe(html_str)) context['results_htmls'] = results_htmls return context
然后在这个应用的模板FinalResultsPage.html里循环展示所有结果:
{% extends "global/Page.html" %} {% load otree static %} {% block title %}所有实验结果汇总{% endblock %} {% block content %} <h2>各应用结果详情</h2> {% for html in results_htmls %} <div class="app-result-container"> {{ html }} </div> <hr style="margin: 2rem 0; border-color: #eee;"> {% endfor %} {% endblock %}
方案二:把渲染好的HTML存成静态文件(适合需要备份的场景)
如果你需要把每个参与者的结果页面存成独立文件留底,就用这个方案:
步骤1:每个应用结果页面保存HTML文件
还是在每个应用的ResultsPage里加逻辑:
from django.template.loader import render_to_string import os from otree.api import Page class ResultsPage(Page): def before_next_page(self): app_name = self.__class__.__module__.split('.')[0] rendered_html = render_to_string( f"{app_name}/results_page.html", context=self.get_context_data() ) # 定义保存路径(比如项目根目录下的static/app_results文件夹) save_dir = os.path.join(os.getcwd(), 'static', 'app_results') # 确保文件夹存在,不存在就创建 os.makedirs(save_dir, exist_ok=True) # 生成唯一文件名,避免不同参与者/会话的文件互相覆盖 file_name = f"result_{self.participant.unique_id}_{self.session.code}_{app_name}.html" file_path = os.path.join(save_dir, file_name) # 写入文件 with open(file_path, 'w', encoding='utf-8') as f: f.write(rendered_html) # 把文件名存到participant vars里,方便最后找到这个文件 self.participant.vars[f"result_file_{app_name}"] = file_name
步骤2:最终结果应用读取并展示文件
在FinalResults的pages.py里:
from otree.api import Page import os from django.utils.safestring import mark_safe class FinalResultsPage(Page): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) results_htmls = [] save_dir = os.path.join(os.getcwd(), 'static', 'app_results') # 遍历participant vars里的文件名,读取每个文件的内容 for key, file_name in self.participant.vars.items(): if key.startswith('result_file_'): file_path = os.path.join(save_dir, file_name) with open(file_path, 'r', encoding='utf-8') as f: html_content = f.read() results_htmls.append(mark_safe(html_content)) context['results_htmls'] = results_htmls return context
模板和方案一的一样就行。
一些注意事项
- 关于模板里的静态资源:如果你的
results_page.html里引用了CSS、图片之类的静态资源,渲染后的HTML路径还是有效的,不用额外调整。 - 文件清理(方案二):如果用文件存储,时间久了会积累很多文件,可以写个简单的脚本定期清理,或者在会话结束时自动删除(比如在
session_config.py里加finish方法)。 - 权限问题(方案二):部署到服务器时,要确保oTree运行的用户有
static/app_results文件夹的读写权限。
这两个方案都不用深入Django,用的都是基础的模板渲染和变量/文件操作,完全符合你的需求~
内容的提问来源于stack exchange,提问作者Blackers
相关产品推荐
相关产品推荐

