aboutsummaryrefslogtreecommitdiffstats
path: root/97suifangqa/apps/recommend
diff options
context:
space:
mode:
Diffstat (limited to '97suifangqa/apps/recommend')
-rw-r--r--97suifangqa/apps/recommend/models.py431
-rw-r--r--97suifangqa/apps/recommend/static/css/add_edit_blog_info.css107
-rw-r--r--97suifangqa/apps/recommend/static/css/recommend_index.css21
-rw-r--r--97suifangqa/apps/recommend/static/javascripts/add_edit_blog_info.js330
-rw-r--r--97suifangqa/apps/recommend/templates/recommend/add_edit_blog_info.html87
-rw-r--r--97suifangqa/apps/recommend/templates/recommend/add_edit_blog_info_error.html66
-rw-r--r--97suifangqa/apps/recommend/templates/recommend/recommend_index.html27
-rw-r--r--97suifangqa/apps/recommend/tools.py81
-rw-r--r--97suifangqa/apps/recommend/urls.py9
-rw-r--r--97suifangqa/apps/recommend/views.py229
10 files changed, 1381 insertions, 7 deletions
diff --git a/97suifangqa/apps/recommend/models.py b/97suifangqa/apps/recommend/models.py
index 98ae64d..0c24c67 100644
--- a/97suifangqa/apps/recommend/models.py
+++ b/97suifangqa/apps/recommend/models.py
@@ -8,8 +8,12 @@ models for apps/recommend
from django.db import models
from django.contrib import admin
+from utils.tools import format_float
-class TreatRespnse(models.Model): # {{{
+import re
+
+
+class TreatResponse(models.Model): # {{{
"""
治疗反应/结果的描述,以及结果的价值/权重
"""
@@ -25,11 +29,11 @@ class TreatRespnse(models.Model): # {{{
verbose_name_plural = u"治疗反应"
def __unicode__(self):
- return u"< TreatRespnse: %s >" % self.name
+ return u"< TreatResponse: %s >" % self.name
def save(self, **kwargs):
if self.is_valid():
- super(TreatRespnse, self).save(**kwargs)
+ super(TreatResponse, self).save(**kwargs)
else:
return self
@@ -37,14 +41,433 @@ class TreatRespnse(models.Model): # {{{
# check weight range
if (self.weight < 0.0) or (self.weight > 10.0):
print u"Error: weight < 0.0 / weight > 10.0"
+ raise ValueError(u"Error: weight<0.0 / weight>10.0")
return False
#
return True
+
+ def dump(self, **kwargs):
+ dump_data = {
+ 'id': self.id,
+ 'name': self.name,
+ 'description': self.description,
+ 'weight': self.weight,
+ }
+ return dump_data
+# }}}
+
+
+class ResearchIndicator(models.Model): # {{{
+ """
+ model to record the indicators which researched in the paper
+ """
+ MEAN_TYPE = 'MEAN'
+ SCOPE_TYPE = 'SCOP'
+ KIND_TYPE = 'KIND'
+ DATA_TYPES = (
+ (MEAN_TYPE, u'平均值'),
+ (SCOPE_TYPE, u'范围'),
+ (KIND_TYPE, u'种类'),
+ )
+ blog = models.ForeignKey("sciblog.SciBlog",
+ related_name="research_indicators",
+ verbose_name=u"待关联文章")
+ indicator = models.ForeignKey("indicator.Indicator",
+ related_name="research_indicators",
+ verbose_name=u"待关联指标")
+ dataType = models.CharField(u"指标值类型", max_length=4,
+ choices=DATA_TYPES)
+ # datetime
+ created_at = models.DateTimeField(u"创建时间", auto_now_add=True)
+ updated_at = models.DateTimeField(u"更新时间",
+ auto_now_add=True, auto_now=True)
+
+ class Meta:
+ verbose_name_plural = u"研究指标信息"
+ ordering = ['blog__id', 'indicator__id']
+
+ def __unicode__(self):
+ # cut down the length of title
+ blog_title = self.blog.title[:10]
+ return u"< ResearchIndicator: %s -> %s (type %s) >" % (
+ blog_title, self.indicator.name, self.dataType)
+
+ def save(self, **kwargs):
+ if self.is_valid():
+ super(ResearchIndicator, self).save(**kwargs)
+ else:
+ return self
+
+ def is_valid(self, **kwargs): # {{{
+ # check dataType, which should be consistent with the
+ # dataType of the related indicator
+ ind_obj = self.indicator
+ ind_dataType = ind_obj.dataType
+ if self.dataType == self.MEAN_TYPE:
+ if ind_dataType not in [ind_obj.FLOAT_TYPE, ind_obj.FLOAT_RANGE_TYPE]:
+ raise ValueError(u"Error: dataType与Indicator不符")
+ return False
+ elif self.dataType == self.SCOPE_TYPE:
+ if ind_dataType not in [ind_obj.RANGE_TYPE, ind_obj.FLOAT_RANGE_TYPE]:
+ raise ValueError(u"Error: dataType与Indicator不符")
+ return False
+ elif self.dataType == self.KIND_TYPE:
+ if ind_dataType in [ind_obj.FLOAT_TYPE,
+ ind_obj.RANGE_TYPE, ind_obj.FLOAT_RANGE_TYPE]:
+ raise ValueError(u"Error: dataType与Indicator不符")
+ return False
+ else:
+ raise ValueError(u"Error: unknown dataType")
+ return False
+ #
+ return True
+ # }}}
+
+ def dump(self, **kwargs):
+ dump_data = {
+ 'id': self.id,
+ 'blog_id': self.blog.id,
+ 'indicator_id': self.indicator.id,
+ 'indicator_name': self.indicator.name,
+ 'dataType': self.dataType,
+ }
+ return dump_data
+# }}}
+
+
+# class ResearchCombination(models.Model): # {{{
+# """
+# 记录文章研究的有效的指标组合
+# """
+# blog = models.ForeignKey("sciblog.SciBlog",
+# related_name="research_combinations",
+# verbose_name=u"待关联文章")
+# combination = models.ManyToManyField("indicator.Indicator",
+# related_name="research_combinations",
+# verbose_name=u"研究指标组合")
+# # datetime
+# created_at = models.DateTimeField(u"创建时间", auto_now_add=True)
+# updated_at = models.DateTimeField(u"更新时间",
+# auto_now_add=True, auto_now=True)
+#
+# class Meta:
+# verbose_name_plural = u"研究指标有效组合"
+# ordering = ['blog__id']
+#
+# def __unicode__(self):
+# # cut down the length of title
+# blog_title = self.blog.title[:10]
+# combination = u''
+# for ind in self.combination:
+# combination = combination + ind.name + ", "
+# combination = re.sub(r',\ $', '', combination)
+# return u"< ResearchCombination: %s -> (%s) >" % (
+# blog_title, combination)
+#
+# def save(self, **kwargs):
+# if self.is_valid():
+# super(ResearchCombination, self).save(**kwargs)
+# else:
+# return self
+#
+# def is_valid(self):
+# """
+# These M2M indicators must have related to the blog
+# """
+# related_indicators = [ri.indicator
+# for ri in self.blog.research_indicators.all()]
+# for ind in self.combination:
+# if ind not in related_indicators:
+# raise ValueError(u"Error: 选择了未关联到该文章的指标")
+# return False
+# return True
+# # }}}
+
+
+# ResearchAtom {{{
+class ResearchAtom(models.Model):
+ """
+ ???any good name???
+ 用于记录某篇文章中对某个指标所分的每一个小类的具体信息
+ """
+ blog = models.ForeignKey("sciblog.SciBlog",
+ related_name="research_atoms",
+ verbose_name=u"待关联文章")
+ researchIndicator = models.ForeignKey("ResearchIndicator",
+ related_name="research_atoms",
+ verbose_name=u"文章研究指标")
+ ## value
+ # unit (XXX: only standard unit supported at the moment)
+ unit = models.ForeignKey("indicator.Unit",
+ null=True, blank=True,
+ related_name="research_atoms", verbose_name=u"单位")
+ # dataType: MEAN
+ mean = models.FloatField(u"平均值", null=True, blank=True)
+ sd = models.FloatField(u"标准值", null=True, blank=True)
+ # dataType: SCOP
+ scope_min = models.FloatField(u"范围下限", null=True, blank=True)
+ scope_max = models.FloatField(u"范围上限", null=True, blank=True)
+ # dataType: KIND
+ kind = models.ForeignKey("ValueKind",
+ null=True, blank=True,
+ related_name="research_atoms", verbose_name=u"种类")
+ ## datetime
+ created_at = models.DateTimeField(u"创建时间", auto_now_add=True)
+ updated_at = models.DateTimeField(u"更新时间",
+ auto_now_add=True, auto_now=True)
+
+ class Meta:
+ verbose_name_plural = u"研究指标原子类"
+ ordering = ['blog__id', 'researchIndicator__id']
+
+ def __unicode__(self):
+ return u'< ResearchAtom: #%s %s >' % (self.id, self.display())
+
+ def save(self, **kwargs):
+ if self.is_valid():
+ super(ResearchAtom, self).save(**kwargs)
+ else:
+ return self
+
+ def is_valid(self, **kwargs): # {{{
+ """
+ The blog here must be consistent with the blog related to
+ the researchIndicator
+ """
+ ri_obj = self.researchIndicator
+ dataType = ri_obj.dataType
+ # confine
+ ind_obj = ri_obj.indicator
+ if ind_obj.check_confine():
+ confine = ind_obj.get_confine()
+ else:
+ raise ValueError(u"Error: 该指标未指定InnateConfine")
+ return False
+ # check blog id first
+ if self.blog.id != ri_obj.blog.id:
+ raise ValueError(u"Error: 关联blog错误")
+ return False
+ # check dataType and confine
+ if dataType == ri_obj.MEAN_TYPE:
+ if not (self.unit and self.unit.standard):
+ raise ValueError(u"Error: 单位未填写/不是标准单位")
+ return False
+ if ((self.mean is None) or (self.sd is None)):
+ raise ValueError(u"Error: 平均值/标准差未填写")
+ return False
+ # check with confine
+ # XXX: only check 'mean' at the moment; 'sd' may also needed
+ if (self.mean < confine['math_min']) or (
+ self.mean > confine['math_max']):
+ raise ValueError(u"Error: 平均值超出允许范围")
+ return False
+ elif dataType == ri_obj.SCOPE_TYPE:
+ if not (self.unit and self.unit.standard):
+ raise ValueError(u"Error: 单位未填写/不是标准单位")
+ return False
+ if ((self.scope_min is None) or (self.scope_max is None)):
+ raise ValueError(u"Error: 范围下限/上限未填写")
+ return False
+ if (self.scope_min >= self.scope_max):
+ raise ValueError(u"Error: scope_min>=scope_max")
+ return False
+ # check confine
+ if (self.scope_min < confine['math_min']) or (
+ self.scope_max > confine['math_max']):
+ raise ValueError(u"Error: scope_min/scope_max 超出允许范围")
+ return False
+ elif dataType == ri_obj.KIND_TYPE:
+ if not self.kind:
+ raise ValueError(u"Error: 未选择种类")
+ return False
+ else:
+ raise ValueError(u"Error: unknown dataType")
+ return False
+ #
+ return True
+ # }}}
+
+ def get_value(self, **kwargs): # {{{
+ ri_obj = self.researchIndicator
+ dataType = ri_obj.dataType
+ value = {
+ 'id': self.id,
+ 'dataType': dataType,
+ 'blog_id': self.blog.id,
+ }
+ if dataType == ri_obj.MEAN_TYPE:
+ value['mean'] = self.mean
+ value['sd'] = self.sd
+ value['unit'] = self.unit.dump()
+ elif dataType == ri_obj.SCOPE_TYPE:
+ value['scope_min'] = self.scope_min
+ value['scope_max'] = self.scope_max
+ value['unit'] = self.unit.dump()
+ elif dataType == ri_obj.KIND_TYPE:
+ value['kind'] = self.kind.dump()
+ else:
+ value['error'] = True
+ return value
+ # }}}
+
+ def display(self, **kwargs):
+ """
+ generate the display string for front page
+ """
+ ri_obj = self.researchIndicator
+ ind_name = ri_obj.indicator.name
+ dataType = ri_obj.dataType
+ if dataType == ri_obj.MEAN_TYPE:
+ value = '%sSD%s' % (format_float(self.mean), self.sd)
+ elif dataType == ri_obj.SCOPE_TYPE:
+ value = '%s~%s' % (format_float(self.scope_min),
+ format_float(self.scope_max))
+ elif dataType == ri_obj.KIND_TYPE:
+ value = '%s' % (self.kind.name)
+ else:
+ value = 'UNKNOWN'
+ disp_str = u'%s(%s|%s)' % (ind_name, dataType, value)
+ return disp_str
+# }}}
+
+
+# ResearchConfig {{{
+class ResearchConfig(models.Model):
+ """
+ 记录某篇文章所研究的某一个具体的组合(哪几个指标的具体值)
+ 的治疗结果等信息
+ """
+ blog = models.ForeignKey("sciblog.SciBlog",
+ related_name="research_configs",
+ verbose_name=u"待关联文章")
+ researchAtoms = models.ManyToManyField("ResearchAtom",
+ related_name="research_configs",
+ verbose_name=u"研究指标值组合")
+ treatResponse = models.ForeignKey("TreatResponse",
+ related_name="research_configs",
+ verbose_name=u"治疗反应")
+ weight = models.FloatField(u"权重", help_text=u"范围:0-10")
+ # datetime
+ created_at = models.DateTimeField(u"创建时间", auto_now_add=True)
+ updated_at = models.DateTimeField(u"更新时间",
+ auto_now_add=True, auto_now=True)
+
+ class Meta:
+ verbose_name_plural = u"研究指标值组合信息"
+ ordering = ['blog__id']
+
+ def __unicode__(self):
+ # XXX
+ info = ''
+ for atom in self.researchAtoms.all():
+ info = '%s%s,' % (info, atom.id)
+ info = re.sub(r',\s*$', '', info)
+ return u'< ResearchConfig: #%s (Atoms: %s) -> %s >' % (
+ self.id, info, self.blog.title[:10])
+
+ def save(self, **kwargs):
+ if self.is_valid():
+ super(ResearchConfig, self).save(**kwargs)
+ else:
+ return self
+
+ def is_valid(self):
+ """
+ The blog here must be consistent with the blog related to
+ the researchAtoms
+ """
+ # check weight range
+ if (self.weight < 0.0) or (self.weight > 10.0):
+ raise ValueError(u"Error: weight<0.0 / weight>10.0")
+ return False
+ #### check blog ####
+ ## Error: needs to have a value for field "researchconfig" before this many-to-many relationship can be used.
+ #for atom in self.researchAtoms.all():
+ # if atom.blog.id != self.blog.id:
+ # raise ValueError(u"Error: 关联blog错误")
+ # return False
+ #### end ####
+ return True
+
+ def is_ok(self, **kwargs):
+ """
+ check this config whether ok or not?
+ i.e.: whether the data fields are valid?
+ """
+ # check atoms
+ if not self.researchAtoms.all():
+ return False
+ # check blog id
+ for atom in self.researchAtoms.all():
+ if atom.blog.id != self.blog.id:
+ raise ValueError(u"Error: 关联blog错误")
+ return False
+ #
+ return True
+
+ def dump(self, **kwargs):
+ dump_data = {
+ 'id': self.id,
+ 'blog_id': self.blog.id,
+ 'researchAtoms_id': [atom.id
+ for atom in self.researchAtoms.all()],
+ 'treatResponse_id': self.treatResponse.id,
+ 'weight': self.weight,
+ }
+ return dump_data
+# }}}
+
+
+class ValueKind(models.Model): # {{{
+ """
+ 记录系统所有可能使用到的"种类"值
+ 并为需要使用"种类"值的地方提供可选范围(e.g.: recommend.ResearchAtom)
+ 使用统一的符号(symbol)来寻找及匹配
+ """
+ name = models.CharField(u"名称", max_length=30)
+ symbol = models.CharField(u"符号", max_length=30,
+ help_text=u"仅能使用字母、数字和下划线,最长30字符")
+ description = models.TextField(u"描述", blank=True)
+
+ class Meta:
+ verbose_name_plural = u"可选种类"
+
+ def __unicode__(self):
+ return u'< ValueKind: %s (%s) >' % (self.name, self.symbol)
+
+ def save(self, **kwargs):
+ if self.is_valid():
+ super(ValueKind, self).save(**kwargs)
+ else:
+ return self
+
+ def is_valid(self):
+ # check symbol
+ sym_regex = re.compile(r'^[_0-9a-zA-Z]+$')
+ if sym_regex.search(self.symbol):
+ return True
+ else:
+ raise ValueError(u"仅能使用字母、数字和下划线,最长30字符")
+ return False
+
+ def dump(self, **kwargs):
+ dump_data = {
+ 'id': self.id,
+ 'name': self.name,
+ 'symbol': self.symbol,
+ 'description': self.description,
+ }
+ return dump_data
# }}}
# admin
admin.site.register([
- TreatRespnse,
+ TreatResponse,
+ ResearchIndicator,
+ #ResearchCombination,
+ ResearchAtom,
+ ResearchConfig,
+ ValueKind,
])
diff --git a/97suifangqa/apps/recommend/static/css/add_edit_blog_info.css b/97suifangqa/apps/recommend/static/css/add_edit_blog_info.css
new file mode 100644
index 0000000..6d2137e
--- /dev/null
+++ b/97suifangqa/apps/recommend/static/css/add_edit_blog_info.css
@@ -0,0 +1,107 @@
+/*
+ * css for 'add_edit_blog_info' page
+ *
+ * 2013/10/07
+ */
+
+section {
+ margin: 0.8em;
+}
+
+h4 {
+ margin: 0.5em;
+}
+
+p {
+ margin: 0.3em;
+}
+
+section.categories {
+ border: 1px solid #4A4A4A;
+ border-radius: 2px;
+ padding: 0.8em;
+}
+
+section.combinations {
+ border: 1px solid #4A4A4A;
+ border-radius: 2px;
+ padding: 0.8em;
+}
+
+section.configs {
+ border: 1px solid #99CC33;
+ border-radius: 2px;
+ padding: 0.8em;
+}
+
+input[type="button"], input[type="submit"] {
+ background-color: #99CC33;
+ border: 1px solid #F3F2F0;
+ border-radius: 3px;
+ box-shadow: 0 0 2px #FFFFFF;
+ color: #FFFFFF;
+ cursor: pointer;
+ height: 2.1em;
+ padding-left: 0.8em;
+ padding-right: 0.8em;
+ vertical-align: middle;
+}
+
+input.selected[type="button"] {
+ background-color: #99CC33;
+ color: #FFFFFF;
+ border-color: #F3F2F0;
+}
+
+input.unselected[type="button"] {
+ background-color: #FFFFFF;
+ color: #7E7E7E;
+ border-color: #4A4A4A;
+}
+
+input[type="text"] {
+ width: 50px;
+ border-radius: 1px;
+ border: 1px solid #9C9C9C;
+}
+input.weight.valid {
+ border: 2px solid green;
+}
+input.weight.invalid {
+ border: 2px solid red;
+}
+
+table {
+ width: 100%;
+}
+table th.name {
+ width: 70%;
+}
+table th.response {
+ width: 20%;
+}
+table th.weight {
+ width: 10%;
+}
+th, td {
+ /* border: 1px solid black; */
+ padding: 0.2em;
+}
+table tr.odd {
+ background-color: #E0F0C2;
+}
+table tr.invalid {
+ background-color: #FFCCCC;
+}
+
+select.treat_response {
+ border: 1px solid #9C9C9C;
+ background-color: #FFFFFF;
+ color: #000000;
+}
+
+span.error {
+ background-color: #FFB3B3;
+ padding: 2px;
+}
+
diff --git a/97suifangqa/apps/recommend/static/css/recommend_index.css b/97suifangqa/apps/recommend/static/css/recommend_index.css
new file mode 100644
index 0000000..0039a2c
--- /dev/null
+++ b/97suifangqa/apps/recommend/static/css/recommend_index.css
@@ -0,0 +1,21 @@
+/*
+ * css for 'recommend_index' page
+ *
+ * 2013/10/07
+ */
+
+
+ul li {
+ padding: 4px
+}
+
+span.has_info, .has_info a {
+ background-color: #B8DB70;
+ padding: 2px;
+}
+
+span.error {
+ background-color: #FFB3B3;
+ padding: 2px;
+}
+
diff --git a/97suifangqa/apps/recommend/static/javascripts/add_edit_blog_info.js b/97suifangqa/apps/recommend/static/javascripts/add_edit_blog_info.js
new file mode 100644
index 0000000..1d994e5
--- /dev/null
+++ b/97suifangqa/apps/recommend/static/javascripts/add_edit_blog_info.js
@@ -0,0 +1,330 @@
+//
+// js for 'add_edit_blog_info' page
+//
+// 2013/10/08
+//
+
+// a list contains all the configs data objects
+var research_configs_list = new Array();
+
+$(document).ready(function() {
+ // make a configs list from 'research_configs {{{
+ var obj_keys = Object.keys(research_configs);
+ for (var i=0; i<obj_keys.length; i++) {
+ var key = obj_keys[i];
+ var configs = research_configs[key].configs;
+ research_configs_list = research_configs_list.concat(configs);
+ }
+ // }}}
+
+ // categories buttons {{{
+ var cate_btns_html = '';
+ for (var i=1; i<=rind_num; i++) {
+ var btn_id = 'btn_cate_'+i;
+ var btn_value = i+'个指标';
+ cate_btns_html += '<input type="button" class="unselected" id="'+btn_id + '" value="'+btn_value + '" /> &ensp; ';
+ };
+ $("#cate_btns").html(cate_btns_html);
+ // button actions
+ $('#cate_btns input[type="button"]').on("click", document, function() {
+ // unselect all buttons
+ //console.log(this);
+ $('#cate_btns input[type="button"]').removeClass("selected");
+ $('#cate_btns input[type="button"]').addClass("unselected");
+ $(this).removeClass("unselected");
+ $(this).addClass("selected");
+ // unselect buttons of combinations
+ $('#comb_divs input[type="button"]').removeClass("selected");
+ $('#comb_divs input[type="button"]').addClass("unselected");
+ // hide configs div's
+ $('#conf_divs .conf_comb').hide();
+ // display category of combinations
+ var cate_id = $(this).attr('id').replace('btn_cate_', '');
+ $('#comb_divs .comb').hide();
+ $('#div_comb_'+cate_id).show();
+ });
+ // }}}
+
+ // indicator combinations div's and buttons {{{
+ var comb_divs_html = '';
+ for (var i=1; i<=rind_num; i++) {
+ var id = 'div_comb_'+i;
+ comb_divs_html += '<div class="comb" id="'+id + '" style="display: none;">\n';
+ // combinations buttons
+ var btn_comb_html = '';
+ var combs = rind_categories['N'+i];
+ for (var j=0; j<combs.length; j++) {
+ var btn_id = combs[j].tag;
+ // get value for button
+ var btn_value = get_comb_btn_value(combs[j].data);
+ btn_comb_html += '<input type="button" class="unselected" id="'+btn_id + '" value="'+btn_value + '" /> &ensp; ';
+ };
+ comb_divs_html += btn_comb_html + '\n</div>\n';
+ };
+ $("#comb_divs").html(comb_divs_html);
+ // button actions
+ $('#comb_divs input[type="button"]').on("click", document, function() {
+ // unselect all buttons
+ $('#comb_divs input[type="button"]').removeClass("selected");
+ $('#comb_divs input[type="button"]').addClass("unselected");
+ $(this).removeClass("unselected");
+ $(this).addClass("selected");
+ // display configs of the combination
+ var comb_id = $(this).attr('id');
+ $('#conf_divs .conf_comb').hide();
+ $('#div_conf_'+comb_id).show();
+ });
+ // }}}
+
+ // config div's & info input {{{
+ var conf_divs_html = '';
+ for (var i=0; i<rind_combs.length; i++) {
+ var comb = rind_combs[i];
+ var id = 'div_conf_'+comb.tag;
+ conf_divs_html += '<div class="conf_comb" id="'+id + '" style="display: none;">\n';
+ // configs input
+ var conf_input_html = '<table class="conf">\n';
+ // table head
+ conf_input_html += '<thead style="display: none;">\n<tr class="head">';
+ conf_input_html += '<th class="name"></th> <th class="response"></th> <th class="weight"></th>';
+ conf_input_html += '</tr></thead>\n';
+ // table body
+ conf_input_html += '<tbody>\n';
+ var configs = research_configs[comb.tag].configs;
+ //console.log(configs);
+ for (var j=0; j<configs.length; j++) {
+ // odd or even (for table tr style)
+ if (j%2 == 0) {
+ var odd_even = 'odd';
+ }
+ else {
+ var odd_even = 'even';
+ }
+ var conf = configs[j];
+ var conf_id = conf.tag;
+ var conf_tr_html = '<tr class="conf '+odd_even + '" id="'+conf_id + '">';
+ // display name column
+ conf_tr_html += '<td class="name">' + conf.display + '</td>';
+ // treat response column (prompt & select input)
+ conf_tr_html += '<td class="response">' + treat_responses_objs.name + ': <select class="treat_response"></select></td>';
+ // weight column
+ conf_tr_html += '<td class="weight">权重: <input type="text" class="weight" value="" /></td>';
+ //
+ conf_tr_html += '</tr>\n';
+ conf_input_html += conf_tr_html;
+ };
+ conf_input_html += '</tbody>\n</table>';
+ //console.log(conf_input_html);
+ conf_divs_html += conf_input_html + '\n</div>\n';
+ };
+ $("#conf_divs").html(conf_divs_html);
+ // }}}
+
+ // treat response select {{{
+ $("select.treat_response").each(function() {
+ // add options for 'select'
+ var select_html = '';
+ // add empty value
+ select_html += '<option value="" selected="selected">----</option>\n';
+ for (var i=0; i<treat_responses_list.length; i++) {
+ var tr = treat_responses_list[i];
+ select_html += '<option class="response tr'+tr.id+'" value="id'+tr.id + '">' + tr.name + '</option>\n';
+ }
+ $(this).html(select_html);
+ });
+ // }}}
+
+ // validate weight {{{
+ $("input.weight").on("validate", null, function(e) {
+ e.stopPropagation();
+ var value = $(this).val();
+ var number = parseFloat(value);
+ if (value == "") {
+ $(this).removeClass("valid invalid");
+ }
+ else if (isNaN(number)) {
+ $(this).removeClass("valid");
+ $(this).addClass("invalid");
+ }
+ else if (number<0.0 || number>10.0) {
+ $(this).removeClass("valid");
+ $(this).addClass("invalid");
+ }
+ else {
+ $(this).removeClass("invalid");
+ $(this).addClass("valid");
+ }
+ });
+ $("input.weight").on("change", null, function() {
+ $(this).trigger("validate");
+ });
+ // }}}
+
+ // validate tr conf {{{
+ // if only one of the 'treat_response' or 'weight' has data
+ // then 'invalid'
+ $("tr.conf").on("validate", null, function(e) {
+ e.stopPropagation();
+ var weight_jq = $(this).find("input.weight");
+ var response_jq = $(this).find("select.treat_response");
+ //console.log(weight_jq);
+ // NOTES:
+ // only trigger 'validate' of '.weight' element, when
+ // the event originated on any element apart from '.weight'
+ // REF: http://stackoverflow.com/questions/5967923/jquery-trigger-click-gives-too-much-recursion
+ if (! $(e.target).is(".weight")) {
+ weight_jq.trigger("validate");
+ }
+ if (weight_jq.hasClass("invalid")) {
+ $(this).addClass("invalid");
+ }
+ // only one of the 'treat_response' or 'weight' has data
+ if (weight_jq.val() !== '' && response_jq.val() === '') {
+ $(this).addClass("invalid");
+ }
+ else if (weight_jq.val() === '' && response_jq.val() !== '') {
+ $(this).addClass("invalid");
+ }
+ });
+ // }}}
+
+ // fill provided configs data {{{
+ $("tr.conf").each(function() {
+ var conf_id = $(this).attr('id');
+ var conf_obj = get_config_obj(conf_id);
+ if (conf_obj.hasOwnProperty('id')) {
+ // this config already in database
+ var tr_id = conf_obj.treatResponse_id;
+ $(this).find('select>option.tr'+tr_id).prop('selected', true);
+ $(this).find('.weight').val(conf_obj.weight);
+ // validate tr config
+ $(this).trigger('validate');
+ }
+ });
+ // }}}
+
+
+ // back_to_list button {{{
+ $("#back_to_list").bind("click", function() {
+ var msg = '注意:当前页面未保存的信息将会丢失。是否继续?';
+ var ans = confirm(msg);
+ if (ans) {
+ window.location.href = recommend_index_url;
+ }
+ else {
+ return false;
+ }
+ });
+ // }}}
+
+ // submit info button {{{
+ $("#submit_info").on("click", null, function() {
+ // validate tr conf data
+ $("tr.conf").trigger("validate");
+ if ($("tr.conf.invalid").length) {
+ alert('存在有错误数据的行,请更正后再提交');
+ return false;
+ }
+ // collect conf data
+ var configs_list = new Array();
+ $("tr.conf").each(function() {
+ var conf_id = $(this).attr('id');
+ var conf_obj = get_config_obj(conf_id);
+ // get config data
+ var weight_jq = $(this).find("input.weight");
+ var response_jq = $(this).find("select.treat_response");
+ var weight = weight_jq.val();
+ var tr_id = response_jq.val().replace('id', '');
+ //
+ if (conf_obj.hasOwnProperty('id')) {
+ if (weight === '' && tr_id === '') {
+ // delete config
+ conf_obj['action'] = 'delete';
+ conf_obj['weight'] = null;
+ conf_obj['treatResponse_id'] = null;
+ }
+ else {
+ // edit config
+ conf_obj['action'] = 'edit';
+ conf_obj['weight'] = parseFloat(weight);
+ conf_obj['treatResponse_id'] = parseInt(tr_id);
+ }
+ }
+ else {
+ if (weight !== '' && tr_id !== '') {
+ // add config
+ conf_obj['action'] = 'add';
+ conf_obj['weight'] = parseFloat(weight);
+ conf_obj['treatResponse_id'] = parseInt(tr_id);
+ }
+ else {
+ // null config
+ conf_obj['action'] = null;
+ conf_obj['weight'] = null;
+ conf_obj['treatResponse_id'] = null;
+ }
+ }
+ // push config data
+ configs_list.push(conf_obj);
+ });
+ //console.log(configs_list);
+
+ // ajax post configs data {{{
+ var time = moment().valueOf();
+ $.ajax({
+ type: 'post',
+ url: recommend_url + 'ajax/add_edit_configs/',
+ data: {
+ csrfmiddlewaretoken: document.getElementsByName('csrfmiddlewaretoken')[0].value,
+ configs_list: JSON.stringify(configs_list),
+ blog_id: blog_id,
+ time: time
+ },
+ dataType: 'json',
+ success: function(dataJson) {
+ if (dataJson.failed == true) {
+ // submit failed
+ alert('Error: submit failed');
+ return false;
+ }
+ else {
+ alert('submit successful');
+ // reload page (do not use cache)
+ location.reload(true);
+ }
+ }
+ });
+ // }}}
+ });
+ // }}}
+});
+
+
+// generate value for combination button
+// sort id list by magnitude
+function get_comb_btn_value(rid_list) {
+ // sort id list numerically (smallest first)
+ rid_list.sort(function(a, b) { return a-b });
+ var value = '';
+ for (var i=0; i<rid_list.length; i++) {
+ var key = 'id'+rid_list[i];
+ value += rind_objs[key].indicator_name + ' | ';
+ }
+ value = value.replace(/\s*\|\s*$/, '');
+ return value;
+};
+
+// return the config js obj (copy)
+// by searching the given 'tag' in 'research_configs_list'
+function get_config_obj(tag) {
+ var result = $.grep(research_configs_list,
+ function(e) { return e.tag == tag });
+ if (result.length == 1) {
+ return $.extend({}, result[0]);
+ }
+ else {
+ return null;
+ }
+};
+
+// vim: set ts=8 sw=4 tw=0 fenc= ft=javascript: //
diff --git a/97suifangqa/apps/recommend/templates/recommend/add_edit_blog_info.html b/97suifangqa/apps/recommend/templates/recommend/add_edit_blog_info.html
new file mode 100644
index 0000000..e646abf
--- /dev/null
+++ b/97suifangqa/apps/recommend/templates/recommend/add_edit_blog_info.html
@@ -0,0 +1,87 @@
+{% extends "base.html" %}
+{% load static from staticfiles %}
+{% load dict_get %}
+
+{% block title %}
+添加文章信息 | admin | 97 随访
+{% endblock %}
+
+{% block css %}
+ <link rel="stylesheet" type="text/css" href="{% static "css/add_edit_blog_info.css" %}"/>
+{% endblock %}
+
+{% block scripts %}
+ <script type="text/javascript" src="{% static "plugins/moment/moment.min.js" %}"></script>
+ <script type="text/javascript" src="{% static "plugins/moment/lang/zh-cn.js" %}"></script>
+ <script type="text/javascript" src="{% static "javascripts/add_edit_blog_info.js" %}"></script>
+
+ <script type="text/javascript">
+ // urls
+ var static_url = "{{ STATIC_URL }}";
+ var recommend_url = "/recommend/";
+
+ // moment.js: default the language to English
+ moment.lang('en');
+ // default date format
+ var mm_date_fmt = "YYYY-MM-DD";
+
+ // index
+ var recommend_index_url = '{% url recommend_index %}';
+ // blog id
+ var blog_id = {{ blog.id }};
+ // number of research indicators
+ var rind_num = {{ rind_num }};
+ // all combinations of research indicators
+ var rind_combs = $.parseJSON('{{ rind_combs_json|safe }}');
+ // research indicator categories (by number)
+ var rind_categories = $.parseJSON('{{ rind_categories_json|safe }}');
+ // research indicator objs dump
+ var rind_objs = $.parseJSON('{{ rind_objs_json|safe }}');
+ // research configs
+ var research_configs = $.parseJSON('{{ research_configs_json|safe }}');
+ // treat response data
+ var treat_responses_list = $.parseJSON('{{ treat_responses_list_json|safe }}');
+ var treat_responses_objs = $.parseJSON('{{ treat_responses_objs_json|safe }}');
+
+ </script>
+{% endblock %}
+
+{% block page %}
+ {% csrf_token %}
+
+ <h2>添加文章信息</h2>
+
+ <section class="blog">
+ id: {{ blog.id }}
+ <br />
+ title: {{ blog.title }}
+ </section>
+
+ <section class="buttons">
+ <input type="button" id="submit_info" value="提交信息" />
+ &ensp; | &ensp;
+ <input type="button" id="back_to_list" value="返回文章列表" />
+ </section>
+
+ <section class="categories">
+ <!-- buttons -->
+ <div id="cate_btns"></div>
+ </section>
+
+ <section class="combinations">
+ <!-- div's & buttons -->
+ <div id="comb_divs"></div>
+ </section>
+
+ <section class="configs">
+ <!-- div's & info input -->
+ <div id="conf_divs"></div>
+ </section>
+
+ <section class="notes">
+ <h4>说明</h4>
+ <p>权重: 浮点数,范围 0-10</p>
+ </section>
+{% endblock page %}
+
+{# vim: set ts=2 sw=2 tw=0 fenc=utf-8 ft=htmldjango.html: #}
diff --git a/97suifangqa/apps/recommend/templates/recommend/add_edit_blog_info_error.html b/97suifangqa/apps/recommend/templates/recommend/add_edit_blog_info_error.html
new file mode 100644
index 0000000..e1a6434
--- /dev/null
+++ b/97suifangqa/apps/recommend/templates/recommend/add_edit_blog_info_error.html
@@ -0,0 +1,66 @@
+{% extends "base.html" %}
+{% load static from staticfiles %}
+{% load dict_get %}
+
+{% block title %}
+添加文章信息 | admin | 97 随访
+{% endblock %}
+
+{% block css %}
+ <link rel="stylesheet" type="text/css" href="{% static "css/add_edit_blog_info.css" %}"/>
+{% endblock %}
+
+{% block scripts %}
+ <script type="text/javascript">
+ // index
+ var recommend_index_url = '{% url recommend_index %}';
+ // back_to_list button
+ $("#back_to_list").bind("click", function() {
+ window.location.href = recommend_index_url;
+ });
+ </script>
+{% endblock %}
+
+{% block page %}
+ <h2>错误 | 添加文章信息</h2>
+
+ <section class="blog">
+ id: {{ blog.id }}
+ <br />
+ title: {{ blog.title }}
+ </section>
+
+ <section class="no_indicator" style="display: {% if no_indicator %}block{% else %}none{% endif %};">
+ <span class="error">
+ 该文章未添加关联的研究指标"ResearchIndicator"
+ ({{ ResearchIndicatorName }})
+ </span>
+ </section>
+
+ <section class="error no_indicator_atom" style="display: {% if no_indicator_atom %}block{% else %}none{% endif %};">
+ <span class="error">
+ 该文章关联的以下研究指标"ResearchIndicator"
+ 没有添加原子分类"ResearchAtom"({{ ResearchAtomName }})
+ </span>
+ {% if no_indicator_atom %}
+ <ul class="research_indicator">
+ {% for ri in no_atom_ri %}
+ <li>{{ ri|dict_get:"display" }} (id={{ ri|dict_get:"id" }})</li>
+ {% endfor %}
+ </ul>
+ {% endif %}
+ </section>
+
+ <section class="error no_treat_response" style="display: {% if no_treat_response %}block{% else %}none{% endif %};">
+ <span class="error">
+ 未添加全局的治疗反应"TreatResponse"({{ TreatResponseName }})
+ </span>
+ </section>
+
+ <section class="button">
+ <input type="button" id="back_to_list" value="返回文章列表" />
+ </section>
+
+{% endblock page %}
+
+{# vim: set ts=2 sw=2 tw=0 fenc=utf-8 ft=htmldjango.html: #}
diff --git a/97suifangqa/apps/recommend/templates/recommend/recommend_index.html b/97suifangqa/apps/recommend/templates/recommend/recommend_index.html
new file mode 100644
index 0000000..fd05bf3
--- /dev/null
+++ b/97suifangqa/apps/recommend/templates/recommend/recommend_index.html
@@ -0,0 +1,27 @@
+{% extends "base.html" %}
+{% load static from staticfiles %}
+{% load dict_get %}
+
+{% block title %}
+文章列表 | admin | 97 随访
+{% endblock %}
+
+{% block css %}
+ <link rel="stylesheet" type="text/css" href="{% static "css/recommend_index.css" %}"/>
+{% endblock %}
+
+{% block page %}
+ <h2>文章列表 | 添加文章信息</h2>
+
+ <p>图例:<span class="has_info">已添加信息</span></p>
+
+ <ul class="blogs">
+ {% for blog in blog_list %}
+ <li class="{% if blog|dict_get:"has_info" %}has_info{% endif %}">
+ <a href="{% url add_edit_blog_info blog|dict_get:"id" %}">{{ blog|dict_get:"title" }}</a>
+ </li>
+ {% endfor %}
+ </ul>
+{% endblock page %}
+
+{# vim: set ts=2 sw=2 tw=0 fenc=utf-8 ft=htmldjango.html: #}
diff --git a/97suifangqa/apps/recommend/tools.py b/97suifangqa/apps/recommend/tools.py
new file mode 100644
index 0000000..3b98c1f
--- /dev/null
+++ b/97suifangqa/apps/recommend/tools.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+#
+# tools for apps/recommend
+#
+
+from django.db.models import Count
+
+from recommend import models as rm
+
+import re
+
+
+def make_tag(ids=[], tag='tag', sep='_'): # {{{
+ """
+ make tag by using given list of ids
+
+ if 'ids' is a list of integers, then sort them by magnitude
+ """
+ if isinstance(ids, tuple):
+ ids = list(ids)
+ # check list element type
+ all_ints = all(isinstance(item, int) for item in ids)
+ if all_ints:
+ # sort ints by magnitude
+ ids.sort()
+ #
+ tag_name = tag
+ for id in ids:
+ tag_name = '%s%s%s' % (tag_name, sep, id)
+ return tag_name
+# }}}
+
+
+def get_research_config(atoms=[]): # {{{
+ """
+ return the found ResearchConfig object
+ by filtering on the given atoms list
+ """
+ if not atoms:
+ return False
+ # convert id list to obj list
+ if isinstance(atoms[0], int):
+ atoms = [rm.ResearchAtom.objects.get(id=id) for id in atoms]
+ qs = rm.ResearchConfig.objects.annotate(c=Count('researchAtoms'))\
+ .filter(c=len(atoms))
+ for atom in atoms:
+ qs = qs.filter(researchAtoms=atom)
+ if not qs:
+ return None
+ elif len(qs) == 1:
+ return qs[0]
+ else:
+ return False
+# }}}
+
+
+def make_config_display(atoms=[]): # {{{
+ """
+ make a display string for the given config
+ """
+ disp_str = u''
+ if not atoms:
+ return disp_str
+ # convert to list
+ if isinstance(atoms, tuple):
+ atoms = list(atoms)
+ # check list element type
+ all_ints = all(isinstance(item, int) for item in atoms)
+ if all_ints:
+ # sort ints by magnitude and convert to objects list
+ atoms.sort()
+ atoms = [rm.ResearchAtom.objects.get(id=id) for id in atoms]
+ #
+ for atom in atoms:
+ disp_str = '%s%s | ' % (disp_str, atom.display())
+ disp_str = re.sub(r'(^\s*\|\s*)|(\s*\|\s*$)', '', disp_str)
+ #
+ return disp_str
+# }}}
+
+
diff --git a/97suifangqa/apps/recommend/urls.py b/97suifangqa/apps/recommend/urls.py
index 09dfed4..ad540d8 100644
--- a/97suifangqa/apps/recommend/urls.py
+++ b/97suifangqa/apps/recommend/urls.py
@@ -14,5 +14,14 @@ urlpatterns = patterns('recommend.views',
url(r'^$',
'recommend_index',
name='recommend_index'),
+ # add/edit blog info
+ url(r'add_edit/blog/(?P<blog_id>[1-9][0-9]*)/$',
+ 'add_edit_blog_info',
+ name='add_edit_blog_info'),
+ ## ajax
+ # add/edit configs
+ url(r'ajax/add_edit_configs/',
+ 'ajax_add_edit_configs',
+ name='ajax_add_edit_configs'),
)
diff --git a/97suifangqa/apps/recommend/views.py b/97suifangqa/apps/recommend/views.py
index 02d5616..d3294a2 100644
--- a/97suifangqa/apps/recommend/views.py
+++ b/97suifangqa/apps/recommend/views.py
@@ -4,14 +4,237 @@
views for apps/recommend
"""
-from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, Http404
+import itertools
+try:
+ import json
+except ImportError:
+ from django.utils import simplejson as json
+
+from django.http import (
+ HttpResponse, HttpResponseRedirect,
+ HttpResponseForbidden, Http404
+)
+from django.views.defaults import permission_denied
from django.shortcuts import render, get_object_or_404
+from django.contrib.auth.decorators import login_required
+
+from recommend import models as rm
+from sciblog.models import SciBlog
+from tools import make_tag, get_research_config, make_config_display
-# index
+# index {{{
def recommend_index(request):
"""
index view for apps/recommend
"""
- return HttpResponse("recommend index")
+ template_name = 'recommend/recommend_index.html'
+ blogs = SciBlog.objects.all().order_by('id')
+ blog_list = []
+ for blog in blogs:
+ if blog.research_configs.all():
+ has_info = True
+ else:
+ has_info = False
+ b = {
+ 'id': blog.id,
+ 'title': blog.title,
+ 'has_info': has_info,
+ }
+ blog_list.append(b)
+ #
+ context = {
+ 'blog_list': blog_list,
+ }
+ return render(request, template_name, context)
+# }}}
+
+
+# add_edit_blog_info {{{
+@login_required
+def add_edit_blog_info(request, blog_id=None):
+ """
+ add/edit the infomation (used to recommend blog for user)
+ of the given blog
+
+ ONLY *STAFF* ALLOWED
+ """
+ template_name = 'recommend/add_edit_blog_info.html'
+ template_name_error = 'recommend/add_edit_blog_info_error.html'
+ context_error = {
+ 'no_indicator': False,
+ 'no_indicator_atom': False,
+ 'no_treat_response': False,
+ 'ResearchIndicatorName': rm.ResearchIndicator._meta.verbose_name_plural,
+ 'ResearchAtomName': rm.ResearchAtom._meta.verbose_name_plural,
+ 'TreatResponseName': rm.TreatResponse._meta.verbose_name_plural,
+ }
+ # check user type
+ if not request.user.is_staff:
+ #return permission_denied(request)
+ html = """
+ <h1>403 Forbidden</h1>
+ <h2>ONLY *STAFF* ALLOWED</h2>
+ """
+ return HttpResponseForbidden(html)
+ ## blog object
+ try:
+ blog_id = int(blog_id)
+ blog_obj = get_object_or_404(SciBlog, id=blog_id)
+ except ValueError:
+ raise ValueError(u"Error: blog_id='%s'错误" % blog_id)
+ except SciBlog.DoesNotExist:
+ raise ValueError(u"Error: SciBlog id='%s'不存在" % blog_id)
+ context_error['blog'] = blog_obj
+ ## research indicators & check
+ r_indicators = blog_obj.research_indicators.all()
+ r_indicators_id = [ri.id for ri in r_indicators]
+ rind_num = len(r_indicators)
+ # check indicator
+ if rind_num == 0:
+ context_error['no_indicator'] = True
+ return render(request, template_name_error, context_error)
+ # check indicator research atoms
+ no_atom_ri = []
+ for ri in r_indicators:
+ if not ri.research_atoms.all():
+ context_error['no_indicator_atom'] = True
+ no_atom_ri.append({
+ 'id': ri.id,
+ 'display': ri.__unicode__(),
+ })
+ #
+ if context_error['no_indicator_atom']:
+ context_error['no_atom_ri'] = no_atom_ri
+ return render(request, template_name_error, context_error)
+ # treat response & check
+ treat_responses = rm.TreatResponse.objects.all()
+ if not treat_responses:
+ context_error['no_treat_response'] = True
+ return render(request, template_name_error, context_error)
+ else:
+ treat_responses_list = [tr.dump() for tr in treat_responses]
+ treat_responses_objs = {'name': rm.TreatResponse._meta.verbose_name_plural}
+ for tr in treat_responses:
+ id = tr.id
+ treat_responses_objs['id%s'%id] = tr.dump()
+ ## research indicator numbers (categories by number)
+ rind_categories = {}
+ for i in range(1, rind_num+1):
+ comb = list(itertools.combinations(r_indicators_id, i))
+ # tag all combinations
+ comb_tagged = []
+ for c in comb:
+ tag = make_tag(ids=c, tag='comb')
+ comb_tagged.append({'tag': tag, 'data': c})
+ rind_categories['N%s'%i] = comb_tagged
+ # dump used research indicators
+ rind_objs = {}
+ for id in r_indicators_id:
+ ri_obj = get_object_or_404(rm.ResearchIndicator, id=id)
+ ri_data = ri_obj.dump()
+ # atoms
+ ri_data['atoms_id'] = [atom.id
+ for atom in ri_obj.research_atoms.all()]
+ rind_objs['id%s'%id] = ri_data
+ ## research configs
+ rind_combs = []
+ for ric in rind_categories.values():
+ rind_combs = rind_combs + ric
+ #
+ research_configs = {}
+ for ric in rind_combs:
+ key = ric['tag']
+ atoms_id_list = []
+ for id in ric['data']:
+ atoms_id_list.append(rind_objs['id%s'%id]['atoms_id'])
+ # itertools to generate combinations
+ configs = list(itertools.product(*atoms_id_list))
+ configs_tagged = []
+ # generate config data for front page
+ for conf in configs:
+ config_obj = get_research_config(atoms=conf)
+ if config_obj:
+ config_data = config_obj.dump()
+ else:
+ config_data = {}
+ tag = make_tag(ids=conf, tag='conf')
+ display = make_config_display(atoms=conf)
+ config_data.update({
+ 'tag': tag,
+ 'data': conf,
+ 'display': display,
+ })
+ configs_tagged.append(config_data)
+ # TODO
+ data = {
+ 'rind_ids': list(ric['data']),
+ 'configs': configs_tagged,
+ }
+ research_configs[key] = data
+ ## context
+ context = {
+ 'blog': blog_obj,
+ 'rind_num': rind_num,
+ 'rind_objs_json': json.dumps(rind_objs),
+ 'rind_combs_json': json.dumps(rind_combs),
+ 'rind_categories_json': json.dumps(rind_categories),
+ 'research_configs_json': json.dumps(research_configs),
+ 'treat_responses_list_json': json.dumps(treat_responses_list),
+ 'treat_responses_objs_json': json.dumps(treat_responses_objs),
+ }
+ return render(request, template_name, context)
+# }}}
+
+
+# ajax add_edit_configs {{{
+@login_required
+def ajax_add_edit_configs(request):
+ """
+ response to the ajax post configs data
+ """
+ data = {'failed': True}
+ if request.is_ajax() and request.method == 'POST':
+ #print request.POST.dict()
+ configs_list = json.loads(request.POST.get('configs_list'))
+ blog_id = int(request.POST.get('blog_id'))
+ blog_obj = get_object_or_404(SciBlog, id=blog_id)
+ #print configs_list
+ for conf in configs_list:
+ if conf['action'] == 'add':
+ # add config
+ tr_obj = get_object_or_404(rm.TreatResponse,
+ id=conf['treatResponse_id'])
+ new_conf_obj = rm.ResearchConfig.objects.create(
+ blog=blog_obj, treatResponse=tr_obj,
+ weight=conf['weight'])
+ new_conf_obj.save()
+ # add m2m researchAtoms
+ for atom_id in conf['data']:
+ atom_obj = get_object_or_404(rm.ResearchAtom,
+ id=atom_id)
+ new_conf_obj.researchAtoms.add(atom_obj)
+ new_conf_obj.save()
+ elif conf['action'] == 'delete':
+ # delete config
+ conf_obj = get_object_or_404(rm.ResearchConfig,
+ id=conf['id'])
+ conf_obj.delete()
+ elif conf['action'] == 'edit':
+ # edit config
+ conf_obj = get_object_or_404(rm.ResearchConfig,
+ id=conf['id'])
+ conf_obj.weight = conf['weight']
+ tr_obj = get_object_or_404(rm.TreatResponse,
+ id=conf['treatResponse_id'])
+ conf_obj.treatResponse = tr_obj
+ conf_obj.save()
+ else:
+ # action==None / unknown action
+ pass
+ data = {'failed': False}
+ #
+ return HttpResponse(json.dumps(data), mimetype='application/json')
+# }}}
+