diff options
author | Alvin Li <liweitianux@gmail.com> | 2013-08-18 00:52:06 +0800 |
---|---|---|
committer | Alvin Li <liweitianux@gmail.com> | 2013-08-18 00:52:06 +0800 |
commit | 9d07e8a26657542c98535abb7812d83a98839918 (patch) | |
tree | 6d5b039e7c01082a280fcefb7cc4fe3ca806e521 /97suifangqa/apps/indicator | |
parent | 0cdba05f09201b6db129ebaa787e960be9ecb851 (diff) | |
download | 97dev-9d07e8a26657542c98535abb7812d83a98839918.tar.bz2 |
* improved 'indicator.views.follow_indicator()',
renamed to 'indicator_fanduf()' due to name conflict
* finished 'search' function for page indicator NewDeleteIndex.html
* added 'apps/utils/search_tools.py': some tools to process search results
* renamed 'sciblog/templatetags/tools.py' to 'get_range.py'
* added 'format_data()' to indicator.tools': to format data of
record/confine, make it properly display in html page;
* 'indicator/javascripts/new_delete_index.js':
added global 'var added_indexes_id' to track the already added indexes
* added 'indicator/templatetags/divisible_by.py' filter to check
if a number can be extactly divide by another number;
* fixed the 'index_card_fir/index_card_sec' problem of
'act_card_container' of page 'SheetDefault.html'
* indicator/static/css/new_delete_index.css:
added 'index_search_error' and 'index_error'
* fixed 'unit.dump' problems in 'indicator.models'
* 'indicator.tools':
o added 'RI_TYPES', 'RI_WEIGHTS'
o added 'type' in the return value of 'calc_indicator_weight()'
and 'recommend_indicator()'
* added field 'followedHistories' in 'indicator.models.UserIndicator'
* moved 'follow_indicator()' and 'unfollow_indicator()' from
'indicator.views' to 'indicator.tools'
TODO:
* highcharts: draw chart for indicator records;
* add/edit data, and submit (page format design)
* followed indicator submit buttion
Diffstat (limited to '97suifangqa/apps/indicator')
16 files changed, 968 insertions, 474 deletions
diff --git a/97suifangqa/apps/indicator/fixtures/initial_data.json b/97suifangqa/apps/indicator/fixtures/initial_data.json index 25d6c73..07aeaaa 100644 --- a/97suifangqa/apps/indicator/fixtures/initial_data.json +++ b/97suifangqa/apps/indicator/fixtures/initial_data.json @@ -1,24 +1,79 @@ [ { - "pk": 1, + "pk": 4, + "model": "indicator.indicatorcategory", + "fields": { + "pinyin": "gan-chuan-ci", + "englishName": "Liver Puncture", + "addByUser": 1, + "name": "\u809d\u7a7f\u523a", + "description": "\u809d\u7a7f\u523a" + } + }, + { + "pk": 3, + "model": "indicator.indicatorcategory", + "fields": { + "pinyin": "gan-gong-neng", + "englishName": "Liver Function", + "addByUser": 1, + "name": "\u809d\u529f\u80fd", + "description": "\u809d\u529f\u80fd" + } + }, + { + "pk": 5, + "model": "indicator.indicatorcategory", + "fields": { + "pinyin": "lei-bie-5", + "englishName": "category5", + "addByUser": 1, + "name": "\u7c7b\u522b5", + "description": "\u7c7b\u522b5\r\n\u6d4b\u8bd5" + } + }, + { + "pk": 6, "model": "indicator.indicatorcategory", "fields": { - "pinyin": "lei-bie-1", - "englishName": "category1", + "pinyin": "lei-bie-6", + "englishName": "category6", "addByUser": 1, - "name": "\u7c7b\u522b1", - "description": "\u6307\u6807\u7c7b\u522b1\r\n\r\n\u4fee\u65391\uff08\u6d4b\u8bd5\uff09" + "name": "\u7c7b\u522b6", + "description": "\u7c7b\u522b6\r\n\r\n\u6d4b\u8bd5" + } + }, + { + "pk": 7, + "model": "indicator.indicatorcategory", + "fields": { + "pinyin": "lei-bie-7", + "englishName": "category7", + "addByUser": 1, + "name": "\u7c7b\u522b7", + "description": "\u7c7b\u522b7\r\n\r\n\u6d4b\u8bd5" } }, { "pk": 2, "model": "indicator.indicatorcategory", "fields": { - "pinyin": "lei-bie-2", - "englishName": "category2", + "pinyin": "liang-dui-ban", + "englishName": "TODO", "addByUser": 1, - "name": "\u7c7b\u522b2", - "description": "\u7c7b\u522b2\r\n\r\nadd_edit_category() \u6dfb\u52a0" + "name": "\u4e24\u5bf9\u534a", + "description": "\u4e59\u809d\u4e24\u5bf9\u534a" + } + }, + { + "pk": 1, + "model": "indicator.indicatorcategory", + "fields": { + "pinyin": "xie-chang-gui", + "englishName": "Blood Routine", + "addByUser": 1, + "name": "\u8840\u5e38\u89c4", + "description": "\u8840\u5e38\u89c4" } }, { @@ -27,14 +82,30 @@ "fields": { "addByUser": 1, "name": "\u6d4b\u8bd51", - "dataType": "FL", + "dataType": "PM", "pinyin": "ce-shi-1", "helpText": "\u5e2e\u52a9 help", "englishName": "test1", "categories": [ + 2 + ], + "description": "forms \u6d4b\u8bd51\r\npm type" + } + }, + { + "pk": 1, + "model": "indicator.indicator", + "fields": { + "addByUser": 1, + "name": "\u5b9a1", + "dataType": "FL", + "pinyin": "ding-1", + "helpText": "\u6d6e\u70b9\u578b", + "englishName": "indicator1", + "categories": [ 1 ], - "description": "forms \u6d4b\u8bd51" + "description": "\u5b9a\u503c1\r\n\r\n\u6d6e\u70b9\u578b\u6570\u636e" } }, { @@ -42,12 +113,13 @@ "model": "indicator.indicator", "fields": { "addByUser": 1, - "name": "\u5b9a\u503c1", + "name": "\u5b9a\u503c2", "dataType": "FL", - "pinyin": "ding-zhi-1", + "pinyin": "ding-zhi-2", "helpText": "\u6d6e\u70b9\u5b9a\u503c", - "englishName": "float1", + "englishName": "float2", "categories": [ + 2, 1 ], "description": "float type" @@ -71,28 +143,24 @@ }, { "pk": 1, - "model": "indicator.indicator", + "model": "indicator.userindicator", "fields": { - "addByUser": 1, - "name": "\u6307\u68071", - "dataType": "FL", - "pinyin": "zhi-biao-1", - "helpText": "\u6d6e\u70b9\u578b", - "englishName": "indicator1", - "categories": [ - 1 + "followedIndicators": [ + 3, + 1, + 2 ], - "description": "\u6307\u68071\r\n\r\n\u6d6e\u70b9\u578b\u6570\u636e" + "followedHistories": [], + "user": 1 } }, { - "pk": 1, + "pk": 2, "model": "indicator.userindicator", "fields": { - "followedIndicators": [ - 4 - ], - "user": 1 + "followedIndicators": [], + "followedHistories": [], + "user": 2 } }, { @@ -101,8 +169,8 @@ "fields": { "indicator": 1, "notes": "\u6307\u68071\r\n\u7b2c1\u6761\u8bb0\u5f55", - "created_at": "2013-08-05T15:48:00.035", - "updated_at": "2013-08-05T15:50:00.326", + "created_at": "2013-08-05T15:48:00.035Z", + "updated_at": "2013-08-05T15:50:00.326Z", "value": "250", "val_min": null, "user": 1, @@ -116,23 +184,39 @@ "model": "indicator.indicatorrecord", "fields": { "indicator": 1, - "notes": "test\r\n\u8bb0\u5f55", - "created_at": "2013-08-09T10:53:15.927", - "updated_at": "2013-08-10T00:30:09.336", + "notes": "\r\n\u8bb0\u5f55", + "created_at": "2013-08-09T10:53:15.927Z", + "updated_at": "2013-08-16T16:23:33.798Z", "value": "50", "val_min": null, - "user": 2, + "user": 1, "date": "2013-08-09", "val_max": null, "unit": 1 } }, { + "pk": 3, + "model": "indicator.indicatorrecord", + "fields": { + "indicator": 3, + "notes": "pm type\r\nrecord 1", + "created_at": "2013-08-16T16:07:00.547Z", + "updated_at": "2013-08-16T16:07:00.547Z", + "value": "+", + "val_min": null, + "user": 1, + "date": "2013-08-17", + "val_max": null, + "unit": null + } + }, + { "pk": 1, "model": "indicator.recordhistory", "fields": { "val_min_bak": null, - "created_at": "2013-08-05T16:07:01.832", + "created_at": "2013-08-05T16:07:01.832Z", "indicatorRecord": 1, "reason": "\u6d4b\u8bd5\r\nadmin\u754c\u9762\u76f4\u63a5\u4fee\u6539", "unit_bak": 1, @@ -147,7 +231,7 @@ "model": "indicator.recordhistory", "fields": { "val_min_bak": null, - "created_at": "2013-08-10T11:40:23.170", + "created_at": "2013-08-10T11:40:23.170Z", "indicatorRecord": 1, "reason": "\u6d4b\u8bd5\u4fee\u6539", "unit_bak": 1, @@ -228,15 +312,30 @@ "pk": 2, "model": "indicator.innateconfine", "fields": { - "math_max": 800.0, + "math_max": 80000.0, "indicator": 2, - "human_max": 500.0, + "human_max": 50000.0, "description": "\u6307\u6807\r\n\r\n\u6570\u636e\u7c7b\u578b\uff1a\u8303\u56f4\u578b\r\n\r\n\u6570\u636e\u8303\u56f4", "val_norm": "", "addByUser": 1, - "human_min": 50.0, + "human_min": 10000.0, "unit": 3, - "math_min": 0.0 + "math_min": 5000.0 + } + }, + { + "pk": 3, + "model": "indicator.innateconfine", + "fields": { + "math_max": null, + "indicator": 3, + "human_max": null, + "description": "pm type", + "val_norm": "+", + "addByUser": 1, + "human_min": null, + "unit": null, + "math_min": null } }, { @@ -245,8 +344,8 @@ "fields": { "indicator": 1, "weight": 5.9, - "created_at": "2013-08-10T22:40:00.035", - "updated_at": "2013-08-10T22:40:00.326", + "created_at": "2013-08-10T22:40:00.035Z", + "updated_at": "2013-08-10T22:40:00.326Z", "blog": null, "annotation": 2, "objectType": "AN" @@ -258,8 +357,8 @@ "fields": { "indicator": 1, "weight": 8.0, - "created_at": "2013-08-11T00:56:08.080", - "updated_at": "2013-08-11T00:56:08.080", + "created_at": "2013-08-11T00:56:08.080Z", + "updated_at": "2013-08-11T00:56:08.080Z", "blog": null, "annotation": 1, "objectType": "AN" @@ -271,8 +370,8 @@ "fields": { "indicator": 2, "weight": 8.3, - "created_at": "2013-08-10T22:50:00.035", - "updated_at": "2013-08-10T22:50:00.326", + "created_at": "2013-08-10T22:50:00.035Z", + "updated_at": "2013-08-10T22:50:00.326Z", "blog": 3, "annotation": null, "objectType": "BL" @@ -284,8 +383,8 @@ "fields": { "indicator": 1, "weight": 4.0, - "created_at": "2013-08-11T00:56:49.463", - "updated_at": "2013-08-11T00:56:49.463", + "created_at": "2013-08-11T00:56:49.463Z", + "updated_at": "2013-08-11T00:56:49.463Z", "blog": 1, "annotation": null, "objectType": "BL" @@ -297,11 +396,11 @@ "fields": { "indicator": 1, "weight": 6.0, - "created_at": "2013-08-11T00:57:23.067", - "updated_at": "2013-08-11T00:57:23.067", + "created_at": "2013-08-11T00:57:23.067Z", + "updated_at": "2013-08-11T00:57:23.067Z", "blog": 3, "annotation": null, "objectType": "BL" } } -]
\ No newline at end of file +] diff --git a/97suifangqa/apps/indicator/models.py b/97suifangqa/apps/indicator/models.py index 615398f..18c699c 100644 --- a/97suifangqa/apps/indicator/models.py +++ b/97suifangqa/apps/indicator/models.py @@ -247,7 +247,11 @@ class UserIndicator(models.Model): # {{{ related_name="user_indicator") followedIndicators = models.ManyToManyField(Indicator, verbose_name=u"关注的指标", - related_name="user_indicators", + related_name="followed_indicators", + null=True, blank=True) + followedHistories = models.ManyToManyField(Indicator, + verbose_name=u"历史关注指标", + related_name="followed_histories", null=True, blank=True) class Meta: @@ -658,6 +662,12 @@ class IndicatorRecord(models.Model): # {{{ # }}} def dump(self, **kwargs): + # check if the indicator needs unit + if self.unit: + unit_id = self.unit.id + else: + unit_id = None + # dump dump_data = { 'id': self.id, 'indicator_id': self.indicator.id, @@ -665,7 +675,7 @@ class IndicatorRecord(models.Model): # {{{ 'created_at': self.created_at.isoformat(), 'updated_at': self.updated_at.isoformat(), 'date': self.date.isoformat(), - 'unit_id': self.unit.id, + 'unit_id': unit_id, 'value': self.value, 'val_max': self.val_max, 'val_min': self.val_min, @@ -724,13 +734,19 @@ class RecordHistory(models.Model): # {{{ super(RecordHistory, self).save(**kwargs) def dump(self, **kwargs): + # check 'unit_bak' + if self.unit_bak: + unit_bak_id = self.unit_bak.id + else: + unit_bak_id = None + # dump dump_data = { 'id': self.id, 'indicatorRecord_id': self.indicatorRecord.id, 'created_at': self.created_at.isoformat(), 'reason': self.reason, 'date_bak': self.date_bak.isoformat(), - 'unit_bak_id': self.unit_bak.id, + 'unit_bak_id': unit_bak_id, 'value_bak': self.value_bak, 'val_max_bak': self.val_max_bak, 'val_min_bak': self.val_min_bak, @@ -930,10 +946,16 @@ class InnateConfine(models.Model): # {{{ # }}} def dump(self, **kwargs): + # check unit, if the indicator not need unit, then return {} + if self.unit: + unit_dump = self.unit.dump() + else: + unit_dump = {} + # dump dump_data = { 'id': self.id, 'indicator_id': self.indicator.id, - 'unit': self.unit.dump(), + 'unit': unit_dump, 'val_norm': self.val_norm, 'human_max': self.human_max, 'human_min': self.human_min, diff --git a/97suifangqa/apps/indicator/search_indexes.py b/97suifangqa/apps/indicator/search_indexes.py index 67d5723..f225b2c 100644 --- a/97suifangqa/apps/indicator/search_indexes.py +++ b/97suifangqa/apps/indicator/search_indexes.py @@ -12,6 +12,8 @@ class IndicatorCategoryIndex(indexes.SearchIndex, indexes.Indexable): search index for 'Indicator' """ text = indexes.CharField(document=True, use_template=True) + # pinyin: for ordering the search result + pinyin = indexes.CharField(model_attr='pinyin') addByUser = indexes.CharField(model_attr='addByUser') def get_model(self): @@ -31,6 +33,8 @@ class IndicatorIndex(indexes.SearchIndex, indexes.Indexable): search index for 'Indicator' """ text = indexes.CharField(document=True, use_template=True) + # pinyin: for ordering the search result + pinyin = indexes.CharField(model_attr='pinyin') addByUser = indexes.CharField(model_attr='addByUser') dataType = indexes.CharField(model_attr='dataType') categories_id = indexes.MultiValueField() diff --git a/97suifangqa/apps/indicator/static/css/new_delete_index.css b/97suifangqa/apps/indicator/static/css/new_delete_index.css index 0eb41d1..b5b161a 100644 --- a/97suifangqa/apps/indicator/static/css/new_delete_index.css +++ b/97suifangqa/apps/indicator/static/css/new_delete_index.css @@ -160,6 +160,18 @@ width: 185px; } +.index_search_error { + margin-top: 3px; +} +.index_search_error .index_error { + float: left; + font-size: 14px; + height: 20px; + line-height: 20px; + color: #4A4A4A; + font-weight: bold; +} + .all_condition .index_all_title { font-weight: bold; font-size: 19px; diff --git a/97suifangqa/apps/indicator/static/css/sheet_default.css b/97suifangqa/apps/indicator/static/css/sheet_default.css index aca021c..15d1a98 100644 --- a/97suifangqa/apps/indicator/static/css/sheet_default.css +++ b/97suifangqa/apps/indicator/static/css/sheet_default.css @@ -153,6 +153,9 @@ .index_card .refer_range .refer_text { margin-right: 5px; } +.index_card .refer_range .refer_value { + margin-right: 5px; +} .index_card .edit_data, .index_card .editing_data { height: 25px; margin-top: 7px; @@ -216,6 +219,10 @@ margin-top: 1px; cursor: pointer; } +.index_card .editing_data .data_unit { + float: left; + margin-right: 8px; +} .index_card .editing_data .cancel_edit_icon { float: right; background:url(../images/cancel_edit.png) no-repeat; @@ -356,4 +363,4 @@ } .move_div_2_left { float: left; -}
\ No newline at end of file +} diff --git a/97suifangqa/apps/indicator/static/javascripts/delete_card_tip.js b/97suifangqa/apps/indicator/static/javascripts/delete_card_tip.js index 099a216..9365d46 100644 --- a/97suifangqa/apps/indicator/static/javascripts/delete_card_tip.js +++ b/97suifangqa/apps/indicator/static/javascripts/delete_card_tip.js @@ -1,4 +1,7 @@ +// global js var: card_2_delete_id (type: string) + $(document).ready(function(){ +// console.log(parent.card_2_delete_id); //要取消关注的 卡片id 的获取方法 //点大叉、继续关注按钮,关闭弹层页面 $(".delete_card_tip_close, .action_confirm_ignore").bind("click", function(){ parent.TB_remove(); @@ -7,12 +10,23 @@ $(document).ready(function(){ //取消关注,关闭弹层页面 $(".action_confirm_cancel").bind("click", function(){ - //TODO - //底层数据层取消关注(ajax) - //console.log(parent.card_2_delete_id); //要取消关注的 卡片id 的获取方法 + // ajax process to unfollow the indicator + // indicator_id -> parseInt(parent.card_2_delete_id) + // 底层数据层取消关注(ajax) + var date = new Date(); + var time = date.getTime(); + $.ajax({ + type: 'get', + url: parent.indicator_url + 'ajax/unfollow_indicator', + data: 'indicator_id='+parent.card_2_delete_id+'&time='+time, + success: function(data) { + if (data == 'success') { + parent.delete_card(); + parent.TB_remove(); + } + }, + }); - parent.delete_card(); - parent.TB_remove(); return false; }); -});
\ No newline at end of file +}); diff --git a/97suifangqa/apps/indicator/static/javascripts/load_card.js b/97suifangqa/apps/indicator/static/javascripts/load_card.js index 4db48a4..160273b 100644 --- a/97suifangqa/apps/indicator/static/javascripts/load_card.js +++ b/97suifangqa/apps/indicator/static/javascripts/load_card.js @@ -1,26 +1,61 @@ var detail_chart; -$(document).ready(function(){ - var startDate = '2013-07-13'; - startDate = new Date(startDate.replace(/-/g,"/")); - var start_date_UTC_time = startDate.getTime() - startDate.getTimezoneOffset() * 60 * 1000; - var chart3 = new Highcharts.Chart({ + +// set global options for hightcharts {{{ +$(function() { + Highcharts.setOptions ({ chart: { - renderTo: 'chart_3', type: 'area', marginLeft: 15, height: 223, spacingTop: 10, - spacingBottom: 0, - overflow: false, - zIndex: 5 + spacingBottom: 0 + //overflow: false, + //zIndex: 5 }, + colors: ['#31B6AD'], credits: { enabled: false }, + legend: { + enabled: false + }, + plotOptions: { + series: { + fillOpacity: 0.12, + lineWidth: 1, + marker: { + enabled: true, //false false的时候就不会突出显示点 + lineColor: '#31B6AD', + lineWidth: 1, + radius: 3, // 点的大小 + fillColor: '#FFFFFF' // 设置点中间填充的颜色 + }, + shadow: false + //threshold: null + } + }, + //series: [{ + // data: [6.0, 5.9, 5.5, 4.5, 6.2, 6.5, 5.2, 6.0, + // 5.9, 5.5, 4.5, 6.2, 6.5, 5.2, 6.0, 5.9, + // 5.5, 4.5, 6.2, 6.5], + // pointStart: start_date_UTC_time, + // pointInterval: 1 * 24 * 3600 * 1000 // one day + //}], title: { - text: ' ' + text: null + }, + tooltip: { + //formatter: function() { + // return '<span style="color:#969696;font-weight:bold;">' + Highcharts.dateFormat('%b %e', this.x) + '</span>' +'<br />' + '<span style="color:#464646;font-weight:bold;">' + this.y + 'mmol/L' + '</span>' + '<br />' + 'click for more info'; + //}, + // positioner: function (boxWidth, boxHeight, point) { + // return { x: point.plotX+80, y: point.plotY-20 }; + // }, + style: { + padding: '7px' + }, + borderColor: '#EAEAEA' }, - colors: ['#31B6AD'], xAxis: { type: 'datetime', dateTimeLabelFormats: { @@ -34,13 +69,13 @@ $(document).ready(function(){ step: 2, maxStaggerLines: 1 }, - tickInterval: (4 * 24 * 3600 * 1000), + tickInterval: (4 * 24 * 3600 * 1000), // 4 days tickColor: '#FFFFFF' }, yAxis: { - title: { - text: '' - }, + //title: { + // text: '' + //}, allowDecimals: false, endOnTick: false, tickInterval: 1, @@ -50,36 +85,31 @@ $(document).ready(function(){ gridLineWidth: 1, minPadding: 0.3, maxPadding: 1.2 + } + }); +}); +// }}} + +$(document).ready(function(){ + var startDate = '2013-07-13'; + startDate = new Date(startDate.replace(/-/g,"/")); + var start_date_UTC_time = startDate.getTime() - startDate.getTimezoneOffset() * 60 * 1000; + var chart3 = new Highcharts.Chart({ + chart: { + renderTo: 'chart_3' }, - legend: { - enabled: false + yAxis: { + title: { + text: '' + } }, tooltip: { formatter: function() { return '<span style="color:#969696;font-weight:bold;">' + Highcharts.dateFormat('%b %e', this.x) + '</span>' +'<br />' + '<span style="color:#464646;font-weight:bold;">' + this.y + 'mmol/L' + '</span>' + '<br />' + 'click for more info'; - }, + } // positioner: function (boxWidth, boxHeight, point) { // return { x: point.plotX+80, y: point.plotY-20 }; // }, - style: { - padding: '7px' - }, - borderColor: '#EAEAEA' - }, - plotOptions: { - series: { - marker: { - enabled: true, //false false的时候就不会突出显示点 - lineColor: '#31B6AD', - lineWidth: 1, - radius: 3, // 点的大小 - fillColor: '#FFFFFF' // 设置点中间填充的颜色 - }, - fillOpacity: 0.12, - lineWidth: 1, - threshold: null, - shadow: false - } }, series: [{ data: [6.0, 5.9, 5.5, 4.5, 6.2, 6.5, 5.2, 6.0, 5.9, 5.5, 4.5, 6.2, 6.5, 5.2, 6.0, 5.9, 5.5, 4.5, 6.2, 6.5], @@ -254,7 +284,8 @@ function redraw_chart(detail_chart, start, end){ url: indicator_url + 'ajax/get_card_data_chart', data: 'card_detail_id='+card_detail_id+'&start='+start+'&end='+end+'&time='+time, dataType: 'json', - success: function(dataJson) { //每一天都要有数据,否则x轴刻度时间对不上 + success: function(dataJson) { + //每一天都要有数据,否则x轴刻度时间对不上 var startDateLogFormat = new Date(start.replace(/-/g,"/")); var start_date_log_UTC_time = startDateLogFormat.getTime() - startDateLogFormat.getTimezoneOffset() * 60 * 1000; var pointStart = start_date_log_UTC_time; diff --git a/97suifangqa/apps/indicator/static/javascripts/new_delete_index.js b/97suifangqa/apps/indicator/static/javascripts/new_delete_index.js index 2c05d7f..8beed99 100644 --- a/97suifangqa/apps/indicator/static/javascripts/new_delete_index.js +++ b/97suifangqa/apps/indicator/static/javascripts/new_delete_index.js @@ -1,3 +1,6 @@ +// track the indexes already added(/followed) +var added_indexes_id = new Array(); + $(document).ready(function(){ $("#search_btn").bind("click", function(){ var kw = $("#search_kw").val(); @@ -10,6 +13,11 @@ $(document).ready(function(){ $(".right>.index_line").each(function(){ classHover($(this), "minus"); }); + // save the "index_id's of added (type: string) + $(".right>.index_line").each(function(){ + var index_id = $(this).attr("index_id"); + added_indexes_id.push(index_id); + }); $(".add>.icon").live("click", function(){ var add_icon = $(this); var index_id = add_icon.closest(".index_line").attr("index_id"); @@ -21,12 +29,16 @@ $(document).ready(function(){ data: 'index_id='+index_id+'&act=add'+'&time='+time, success: function(data){ if(data == 'success'){ - var obj = add_icon.parent(); - var objClone = obj.clone(); - objClone.removeClass("add") - objClone.children(".index_category").remove(); - $(".right").append(objClone); - classHover(objClone, "minus"); + // check if the index exists? + if (added_indexes_id.indexOf(index_id) == -1) { + var obj = add_icon.parent(); + var objClone = obj.clone(); + objClone.removeClass("add") + objClone.children(".index_category").remove(); + $(".right").append(objClone); + classHover(objClone, "minus"); + added_indexes_id.push(index_id); + } } } }); @@ -45,6 +57,8 @@ $(document).ready(function(){ success: function(data){ var obj = minus_icon.parent(); obj.remove(); + rm_index = added_indexes_id.indexOf(index_id); + added_indexes_id.splice(rm_index, 1); } }); diff --git a/97suifangqa/apps/indicator/templates/indicator/NewDeleteIndex.html b/97suifangqa/apps/indicator/templates/indicator/NewDeleteIndex.html index c2a219a..f4e9a18 100644 --- a/97suifangqa/apps/indicator/templates/indicator/NewDeleteIndex.html +++ b/97suifangqa/apps/indicator/templates/indicator/NewDeleteIndex.html @@ -52,19 +52,8 @@ <div style="clear: both;"></div> </div> <div class="index_navigation"> - <!-- 根据url的参数tab,给对应div的class加 selected 样式 --> - <!-- - <div class="index_type selected" id="index_all"><a href="?tab=all">所有指标</a></div> - <div class="index_type"><a href="?tab=1">血常规</a></div> - <div class="index_type"><a href="?tab=2">两对半</a></div> - <div class="index_type"><a href="?tab=3">肝功能</a></div> - <div class="index_type"><a href="?tab=4">肝穿刺</a></div> - <div class="index_type"><a href="?tab=5">血常规</a></div> - <div class="index_type"><a href="?tab=6">两对半</a></div> - <div class="index_type"><a href="?tab=7">肝功能</a></div> - --> <!-- 所有指标 --> - <div class="index_type {% if selected_catid == "all" %}selected{% endif %}"> + <div class="index_type {% if page_condition == "all" %}selected{% endif %}"> <a href="?tab=all">所有指标</a> </div> <!-- 指标类别,页面只能容纳 7 个 --> @@ -146,6 +135,7 @@ <div style="clear:both;"></div> </div> <!-- list of indicators of the category --> + {% if page_condition == "category" %} <div class="index_lines left"> {% for ind in indicators %} <div class="index_line" index_id="{{ ind.id }}"> @@ -156,6 +146,7 @@ </div> {% endfor %} </div> <!-- end: index_lines left --> + {% endif %} {# end: page_condition == "category" #} </div> <!-- end: category_condition --> <!-- 搜索后左边的展示 --> @@ -164,36 +155,42 @@ <div class="search_condition" style="display: {% if page_condition == "search" %}block{% else %}none{% endif %};"> <div class="index_title_container"> <div class="index_title">搜索指标</div> - <div class="example_sheet">仿真化验单</div> + <div class="example_sheet"></div> <div style="clear:both;"></div> </div> <!-- list of indicators returned by search --> + {% if page_condition == "search" %} <div class="index_lines left"> {# 'indicators': list made up by the dumps of indicator #} - {% for ind in indicators %} - <!-- TODO --> - <div class="index_line" index_id="{{ ind|dict_get:"id" }}"> - <div class="index_name">{{ ind|dict_get:"name" }}</div> - <div class="index_category"> - <a href="?tab={{ ind|dict_get:"categories_id"|first }}">{{ ind|dict_get:"categories_name"|first }}</a> + {# check if search keyword empty & if search result empty #} + {% if search_kw_empty %} + {# search keyword empty #} + <div class="index_search_error"> + <div class="index_error">您未输入搜索关键词</div> + <div class="icon"></div> + <div style="clear:both"></div> </div> - <div class="icon"></div> - <div style="clear:both"></div> - </div> - {% endfor %} - <div class="index_line" index_id="2"> - <div class="index_name">乙肝病毒S蛋白定量</div> - <div class="index_category">血常规</div> - <div class="icon"></div> - <div style="clear:both"></div> - </div> - <div class="index_line" index_id="3"> - <div class="index_name">乙肝病毒表面抗体</div> - <div class="index_category">血常规</div> - <div class="icon"></div> - <div style="clear:both"></div> - </div> + {% elif search_result_empty %} + {# search result empty #} + <div class="index_search_error"> + <div class="index_error">未搜索到符合的结果</div> + <div class="icon"></div> + <div style="clear:both"></div> + </div> + {% else %} + {% for ind in indicators %} + <div class="index_line" index_id="{{ ind|dict_get:"id" }}"> + <div class="index_name">{{ ind|dict_get:"name" }}</div> + <div class="index_category"> + <a href="?tab={{ ind|dict_get:"categories_id"|first }}">{{ ind|dict_get:"categories_name"|first }}</a> + </div> + <div class="icon"></div> + <div style="clear:both"></div> + </div> + {% endfor %} + {% endif %} </div> <!-- end: index_lines left --> + {% endif %} {# end: page_condition == "search" #} </div> <!-- end: search_condition --> </div> <!-- end: index_container --> @@ -219,49 +216,6 @@ <div style="clear:both"></div> </div> {% endfor %} - - {% comment %}{# vim: {{{ #} - <div class="index_line" index_id="1"> - <div class="index_name">V-谷丙氨酰转氨酶</div> - <div class="icon"></div> - <div style="clear:both"></div> - </div> - <div class="index_line" index_id="2"> - <div class="index_name">总胆红素</div> - <div class="icon"></div> - <div style="clear:both"></div> - </div> - <div class="index_line" index_id="3"> - <div class="index_name">乙肝病毒表面抗原</div> - <div class="icon"></div> - <div style="clear:both"></div> - </div> - <div class="index_line" index_id="4"> - <div class="index_name">乙肝病毒表面抗体</div> - <div class="icon"></div> - <div style="clear:both"></div> - </div> - <div class="index_line" index_id="5"> - <div class="index_name">乙肝病毒e抗体</div> - <div class="icon"></div> - <div style="clear:both"></div> - </div> - <div class="index_line" index_id="6"> - <div class="index_name">乙肝病毒核算定量(PCR)</div> - <div class="icon"></div> - <div style="clear:both"></div> - </div> - <div class="index_line" index_id="7"> - <div class="index_name">乙肝病毒基因型C型</div> - <div class="icon"></div> - <div style="clear:both"></div> - </div> - <div class="index_line" index_id="8"> - <div class="index_name">乙肝病毒型混合型</div> - <div class="icon"></div> - <div style="clear:both"></div> - </div> - {% endcomment %}{# vim: }}} #} </div> <!-- end: index_lines right --> </div> diff --git a/97suifangqa/apps/indicator/templates/indicator/SheetDefault.html b/97suifangqa/apps/indicator/templates/indicator/SheetDefault.html index 56d0e6a..88a8d82 100644 --- a/97suifangqa/apps/indicator/templates/indicator/SheetDefault.html +++ b/97suifangqa/apps/indicator/templates/indicator/SheetDefault.html @@ -1,5 +1,7 @@ {% extends "base.html" %} {% load static from staticfiles %} +{% load dict_get %} +{% load divisible_by %} {% block title %} 指标状态 | 随访工具 | 97 随访 @@ -33,200 +35,183 @@ var static_url = "{{ STATIC_URL }}"; var indicator_url = "/indicator/"; </script> + + <!-- Highcharts related, draw records chart --> + <script> + {% for ind in indicators %} + {% if not ind|dict_get:"record_empty" %} {# indicator has records #} + var chart_{{ ind|dict_get:"id" }}; + {% endif %} + {% endfor %} + </script> {% endblock %} {% block page %} - <!-- - <iframe align="left" width="420" height="720" src="SideBar.html" style="position:fixed;left:0;top:0" frameborder="no" border="0" marginwidth="0" marginheight="0" scrolling="no"></iframe> - --> <iframe align="left" width="420" height="720" src="{% url indicator_sidebar %}" style="position:fixed;left:0;top:0" frameborder="no" border="0" marginwidth="0" marginheight="0" scrolling="no"></iframe> - <div id="right_container"> - <div id="index_status_container"> - <div class="index_title">指标状态</div> - <!-- 这里需要后端读取cookie,来判断用户是否已经点击大叉。若否,则显示;若是,则隐藏 --> - <div class="index_sub_title"> - <div class="content">以下可能是您感兴趣的指标,您可以点击卡片右上角“X”,取消关注。</div> - <div class="close_icon" id="index_title_closed_icon"></div> - </div> - - <!-- 左边的卡片加一个class "index_card_fir", 右边的卡片加一个class "index_card_sec" --> - <!-- 卡片div的id为 "index_card_卡片id",方便后续操作 --> - <div class="index_card index_card_fir" id="index_card_1"> - <div class="card_title">乙肝病毒核算定量 (PCR)1</div> - <div class="refer_range"> - <span class="refer_text">参考范围</span> - <span>100 x 10^4 拷贝 /mL</span> - </div> - <div class="edit_data"> - <div class="last_edit_data" style="display:none;"><span class="data_fir">0</span> x 10^<span class="data_sec">0</span> 拷贝 /mL</div> - <img src="{% static "images/pen.png" %}" class="small_edit_icon"> - <img class="explain_icon" src="{% static "images/nodata.png" %}"> - </div> - <div class="editing_data"> - <div class="input_container"> - <input class="edit_input_main" type="text" value="98" /> x 10^ - <input class="edit_input_sub" type="text" value="4" /> - </div> - <div class="add_minus_icon"> - <div class="add_icon"></div> - <div class="minus_icon"></div> - </div> - 拷贝 /mL - <div class="confirm_edit_icon"></div> - <div class="cancel_edit_icon"></div> - </div> - <div class="edit_icon_container"> - <div class="edit_icon"></div> - <div class="curve_icon"></div> - <div style="clear:both;"></div> - </div> - <div class="edit_text">点击右上角的按钮可以编辑数据</div> - <div class="card_bottom"> - <!-- - <div class="understand_index"><a class="thickbox" href="popup/IndexDesc.html?TB_iframe=true&no1_title&transfer_params&height=351&width=630&card_id=1">了解该指标</a></div> - --> - <div class="understand_index"><a class="thickbox" href="{% url indicator_indexdesc %}?TB_iframe=true&no1_title&transfer_params&height=351&width=630&card_id=1">了解该指标</a></div> - <div class="simulation_sheet"><a href="{% static "images/demo_sheet.png" %}" class="thickbox">仿真化验单</a></div> - <div class="detail_history"><a href="javascript:void(0)">详细历史记录</a></div> - <div style="clear:both;"></div> - </div> - <!-- thickbox插件,方便父级页面与子级弹出层的MVC隔离,方便子级弹出层的复杂需求,如:搜索、分页… --> - <!-- height参数为弹出层页面高度+2,width参数为弹出层页面宽度+2,card_id参数为卡片id --> - <!-- - <a class="card_delete_icon card_delete thickbox" href="popup/DeleteCardTip.html?TB_iframe=true&no1_title&transfer_params&height=166&width=630&card_id=1"></a> - --> - <a class="card_delete_icon card_delete thickbox" href="{% url indicator_deletecardtip %}?TB_iframe=true&no1_title&transfer_params&height=166&width=630&card_id=1"></a> - </div> - - <!-- 右边的卡片 --> - <div class="index_card index_card_sec" id="index_card_2"> - <div class="card_title">乙肝病毒核算定量 (PCR)2</div> - <div class="refer_range"> - <span class="refer_text">参考范围</span> - <span>100 x 10^4 拷贝 /mL</span> - </div> - <div class="edit_data"> - <div class="last_edit_data" style="display:none;"><span class="data_fir">0</span> x 10^<span class="data_sec">0</span> 拷贝 /mL</div> - <img src="{% static "images/pen.png" %}" class="small_edit_icon"> - <img class="explain_icon" src="{% static "images/nodata.png" %}"> - </div> - <div class="editing_data"> - <div class="input_container"> - <input class="edit_input_main" type="text" value="98" /> x 10^ - <input class="edit_input_sub" type="text" value="4" /> - </div> - <div class="add_minus_icon"> - <div class="add_icon"></div> - <div class="minus_icon"></div> - </div> - 拷贝 /mL - <div class="confirm_edit_icon"></div> - <div class="cancel_edit_icon"></div> - </div> - <div class="edit_icon_container"> - <div class="edit_icon"></div> - <div class="curve_icon"></div> - <div style="clear:both;"></div> - </div> - <div class="edit_text">点击右上角的按钮可以编辑数据</div> - <div class="card_bottom"> - <!-- - <div class="understand_index"><a class="thickbox" href="popup/IndexDesc.html?TB_iframe=true&no1_title&transfer_params&height=351&width=630&card_id=2">了解该指标</a></div> - --> - <div class="understand_index"><a class="thickbox" href="{% url indicator_indexdesc %}?TB_iframe=true&no1_title&transfer_params&height=351&width=630&card_id=2">了解该指标</a></div> - <div class="simulation_sheet"><a href="{% static "images/demo_sheet.png" %}" class="thickbox">仿真化验单</a></div> - <div class="detail_history"><a href="javascript:void(0)">详细历史记录</a></div> - <div style="clear:both;"></div> - </div> - <!-- - <a class="card_delete_icon card_delete thickbox" href="popup/DeleteCardTip.html?TB_iframe=true&no1_title&transfer_params&height=166&width=630&card_id=2"></a> - --> - <a class="card_delete_icon card_delete thickbox" href="{% url indicator_deletecardtip %}?TB_iframe=true&no1_title&transfer_params&height=166&width=630&card_id=2"></a> - </div> - - <div class="index_card index_card_fir" id="index_card_3"> - <div class="card_title">乙肝病毒核算定量 (PCR)3</div> - <div class="refer_range"> - <span class="refer_text">参考范围</span> - <span>100 x 10^4 拷贝 /mL</span> - </div> - <div class="edit_data"> - <div class="last_edit_data"><span class="data_fir">98</span> x 10^<span class="data_sec">4</span> 拷贝 /mL</div> - <img src="{% static "images/pen.png" %}" class="small_edit_icon"> - <img src="{% static "images/last_edit_data.png" %}" class="explain_icon"> - </div> - <div class="editing_data"> - <div class="input_container"> - <input class="edit_input_main" type="text" value="" autocomplete="off" /> x 10^ - <input class="edit_input_sub" type="text" value="" autocomplete="off" /> - </div> - <div class="add_minus_icon"> - <div class="add_icon"></div> - <div class="minus_icon"></div> - </div> - 拷贝 /mL - <div class="confirm_edit_icon"></div> - <div class="cancel_edit_icon"></div> - </div> - <div class="refresh_data"> - <div class="refresh_text">这是2013-07-01 化验单上的记录</div> - <div class="refresh_icon"></div> - <div style="clear:both;"></div> - </div> - <div class="select_date"> - <input class="datepicker" type="text" autocomplete="off" /> - </div> - <div id="chart_3" class="chart"></div> - <div class="card_bottom"> - <!-- - <div class="understand_index"><a class="thickbox" href="popup/IndexDesc.html?TB_iframe=true&no1_title&transfer_params&height=351&width=630&card_id=3">了解该指标</a></div> - --> - <div class="understand_index"><a class="thickbox" href="{% url indicator_indexdesc %}?TB_iframe=true&no1_title&transfer_params&height=351&width=630&card_id=3">了解该指标</a></div> - <div class="simulation_sheet"><a href="{% static "images/demo_sheet.png" %}" class="thickbox">仿真化验单</a></div> - <div class="detail_history"><a href="javascript:void(0)">详细历史记录</a></div> - <div style="clear:both;"></div> - </div> - <!-- - <a class="card_delete_icon card_delete thickbox" href="popup/DeleteCardTip.html?TB_iframe=true&no1_title&transfer_params&height=166&width=630&card_id=3"></a> - --> - <a class="card_delete_icon card_delete thickbox" href="{% url indicator_deletecardtip %}?TB_iframe=true&no1_title&transfer_params&height=166&width=630&card_id=3"></a> - </div> - - <div class="detail_card_info"> - <div class="card_title">乙肝病毒核算定量 (PCR)4<input class="collapse_btn" type="button" value="收 起" /></div> - <div class="search_data_div"> - <input class="recent_one_week shift_week" start_date="2013-08-04" end_date="2013-08-10" type="button" value="最近1周" /> - <input class="recent_two_week shift_week unselected" start_date="2013-07-28" end_date="2013-08-10" type="button" value="最近2周" /> - <div class="datepicker_container end_date_container"> - <label>截至日期</label> - <input class="datepicker end_date" id="search_end_date" type="text" value="" /> - </div> - <div class="datepicker_container"> - <label class="start_label">起始日期</label> - <input class="datepicker start_date" id="search_start_date" type="text" value="" /> - </div> - <div style="clear:both;"></div> - </div> - <div id="detail_chart"></div> - <div class="table_div"> - <table width="100%"> - <tr class="first_line"> - <td width="122px">日期</td> - <td width="124px">时间</td> - <td width="312px">记录</td> - </tr> - </table> - </div> - <div class="see_more"><input class="see_more_btn" type="button" value="浏览更多记录" /></div> - </div> - - <div class="act_card_container index_card_sec"> + + <div id="right_container"> + <div id="index_status_container"> + <div class="index_title">指标状态</div> + + <!-- 这里需要后端读取cookie,来判断用户是否已经点击大叉。 + 若否,则显示;若是,则隐藏 + --> + <div class="index_sub_title"> + <div class="content">以下可能是您感兴趣的指标,您可以点击卡片右上角“X”,取消关注。</div> + <div class="close_icon" id="index_title_closed_icon"></div> + </div> + + <!-- 左边的卡片加一个class "index_card_fir", + 右边的卡片加一个class "index_card_sec" + --> + <!-- 卡片div的id为 "index_card_卡片id",方便后续操作 --> + {% for ind in indicators %} + <div class="index_card {% cycle 'index_card_fir' 'index_card_sec' %}" id="index_card_{{ ind|dict_get:"id" }}"> + <div class="card_title">{{ ind|dict_get:"name" }}</div> + <div class="refer_range"> + <span class="refer_text">{{ ind|dict_get:"ref_text" }}</span> + <span class="refer_value">{{ ind|dict_get:"ref_value"|safe }}</span> + <span class="data_unit">{{ ind|dict_get:"std_unit_symbol" }}</span> + </div> + {% if ind|dict_get:"record_empty" %} {# vim: {{{ #} + {# if no record, then hide 'last_edit_data' #} + <!-- "record_empty": True --> + <div class="edit_data"> + <div class="last_edit_data" style="display: none;"> + <span class="last_data">Null</span> + <span class="data_unit">{{ ind|dict_get:"std_unit_symbol" }}</span> + </div> + <img class="small_edit_icon" src="{% static "images/pen.png" %}" /> + <img class="explain_icon" src="{% static "images/nodata.png" %}" /> + </div> <!-- end: edit_data --> + <div class="editing_data"> + <div class="input_container"> + <input class="edit_input_main" type="text" value="0" /> + x 10^ + <input class="edit_input_sub" type="text" value="0" /> + </div> + <div class="add_minus_icon"> + <div class="add_icon"></div> + <div class="minus_icon"></div> + </div> + <div class="data_unit">{{ ind|dict_get:"std_unit_symbol" }}</div> + <div class="confirm_edit_icon"></div> + <div class="cancel_edit_icon"></div> + </div> <!-- end: editing_data --> + <!-- hints to get started, display when has no record --> + <div class="edit_icon_container"> + <div class="edit_icon"></div> + <div class="curve_icon"></div> + <div style="clear:both;"></div> + </div> + <div class="edit_text">点击右上角的按钮开始添加数据</div> + {# vim: }}} #} + {% else %} {# record_empty == False; vim: {{{ #} + {# indicator has records #} + <!-- "record_empty": False --> + <div class="edit_data"> + <div class="last_edit_data" style="display: block;"> + <span class="last_data">{{ ind|dict_get:"last_record"|dict_get:"value_str" }}</span> + <span class="data_unit">{{ ind|dict_get:"std_unit_symbol" }}</span> + </div> + <img class="small_edit_icon" src="{% static "images/pen.png" %}" /> + <img class="explain_icon" src="{% static "images/last_edit_data.png" %}" /> + </div> <!-- end: edit_data --> + <div class="editing_data"> + <div class="input_container"> + <input class="edit_input_main" type="text" value="" autocomplete="off" /> + x 10^ + <input class="edit_input_sub" type="text" value="" autocomplete="off" /> + </div> + <div class="add_minus_icon"> + <div class="add_icon"></div> + <div class="minus_icon"></div> + </div> + <div class="data_unit">{{ ind|dict_get:"std_unit_symbol" }}</div> + <div class="confirm_edit_icon"></div> + <div class="cancel_edit_icon"></div> + </div> <!-- end: editing_data --> + <div class="refresh_data"> + <div class="refresh_text"> + 记录日期: {{ ind|dict_get:"last_record"|dict_get:"date" }} + </div> + <div class="refresh_icon"></div> + <div style="clear:both;"></div> + </div> + <div class="select_date"> + <input class="datepicker" type="text" autocomplete="off" /> + </div> + <!-- chart rendered by Highcharts --> + <div id="chart_{{ ind|dict_get:"id" }}" class="chart"></div> + {% endif %} {# end: record_empty; vim: }}} #} + + <div class="card_bottom"> + <div class="understand_index"><a class="thickbox" href="{% url indicator_indexdesc %}?TB_iframe=true&no1_title&transfer_params&height=351&width=630&card_id={{ ind|dict_get:"id" }}">了解该指标</a></div> + <!-- TODO --> + <div class="simulation_sheet"><a href="{% static "images/demo_sheet.png" %}" class="thickbox">仿真化验单</a></div> + <div class="detail_history"> + <a href="javascript:void(0)">详细历史记录</a> + </div> + <div style="clear:both;"></div> + </div> + + {% comment %} + thickbox插件: + 方便父级页面与子级弹出层的MVC隔离, + 方便子级弹出层的复杂需求,如:搜索、分页… + height参数为弹出层页面高度+2, + width参数为弹出层页面宽度+2, + card_id参数为 "卡片id" + {% endcomment %} + <a class="card_delete_icon card_delete thickbox" href="{% url indicator_deletecardtip %}?TB_iframe=true&no1_title&transfer_params&height=166&width=630&card_id={{ ind|dict_get:"id" }}"></a> + </div> <!-- end: index_card --> + {% endfor %} {# end: indicators #} + + <!-- detail card info --> + <div class="detail_card_info"> + <div class="card_title">乙肝病毒核算定量 (PCR)4<input class="collapse_btn" type="button" value="收 起" /></div> + <div class="search_data_div"> + <input class="recent_one_week shift_week" start_date="2013-08-04" end_date="2013-08-10" type="button" value="最近1周" /> + <input class="recent_two_week shift_week unselected" start_date="2013-07-28" end_date="2013-08-10" type="button" value="最近2周" /> + <div class="datepicker_container end_date_container"> + <label>截止日期</label> + <input class="datepicker end_date" id="search_end_date" type="text" value="" /> + </div> + <div class="datepicker_container"> + <label class="start_label">起始日期</label> + <input class="datepicker start_date" id="search_start_date" type="text" value="" /> + </div> + <div style="clear:both;"></div> + </div> + <div id="detail_chart"></div> + <div class="table_div"> + <table width="100%"> + <tr class="first_line"> + <td width="122px">日期</td> + <td width="124px">时间</td> + <td width="312px">记录</td> + </tr> + </table> + </div> + <div class="see_more"> + <input class="see_more_btn" type="button" value="浏览更多记录" /> + </div> + </div> <!-- end: detail card info --> + + <!-- goto follow/unfollow indicator --> + {% if indicators|length|divisible_by:"2" %} + {# number of followed indicators: even #} + <div class="act_card_container index_card_fir"> + {% else %} + <div class="act_card_container index_card_sec"> + {% endif %} <div class="act_card"> - <a href="{% url follow_indicator %}">添加或删除关注指标</a> + <a href="{% url indicator_fanduf %}">添加或删除关注指标</a> </div> - </div> - </div> - </div> + </div> <!-- end: follow/unfollow indicator --> + + </div> + </div> {% endblock page %} {# vim: set ts=2 sw=2 tw=0 fenc=utf-8 ft=htmldjango.html: #} diff --git a/97suifangqa/apps/indicator/templates/indicator/index.html b/97suifangqa/apps/indicator/templates/indicator/index.html index 7c280f7..408ce67 100644 --- a/97suifangqa/apps/indicator/templates/indicator/index.html +++ b/97suifangqa/apps/indicator/templates/indicator/index.html @@ -8,7 +8,7 @@ {% block body %} <ul> <li><a href="{% url indicator_status %}">指标状态</a></li> - <li><a href="{% url follow_indicator %}">关注指标</a></li> + <li><a href="{% url indicator_fanduf %}">关注指标</a></li> </ul> {% endblock %} diff --git a/97suifangqa/apps/indicator/templates/indicator/popup/DeleteCardTip.html b/97suifangqa/apps/indicator/templates/indicator/popup/DeleteCardTip.html index 7424930..c978f8a 100644 --- a/97suifangqa/apps/indicator/templates/indicator/popup/DeleteCardTip.html +++ b/97suifangqa/apps/indicator/templates/indicator/popup/DeleteCardTip.html @@ -24,7 +24,9 @@ <div class="delete_card_tip_title">提示</div> <div class="delete_card_tip_close"></div> </div> - <div class="delete_card_tip_content">取消关注指标后,该指标卡片将从本页面消失,您确定需要取消关注?</div> + <div class="delete_card_tip_content"> + 取消关注指标后,该指标卡片将从本页面消失,您确定需要取消关注? + </div> <div class="delete_card_tip_action"> <a class="action_confirm_cancel">取消关注</a> <a class="action_confirm_ignore">继续关注</a> diff --git a/97suifangqa/apps/indicator/templatetags/divisible_by.py b/97suifangqa/apps/indicator/templatetags/divisible_by.py new file mode 100644 index 0000000..96638e7 --- /dev/null +++ b/97suifangqa/apps/indicator/templatetags/divisible_by.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- + +from django import template + +register = template.Library() + +@register.filter +def divisible_by(dividend, divisor): + """ + if 'dividend' can be *exactly* divided by 'divisor', + return True; + else, return False. + + input parameters: + dividend: <type 'int'> + divisor: <class 'django.utils.safestring.SafeUnicode'> + """ + if not isinstance(dividend, int): + raise ValueError(u'Error: dividend="%s" not int type' % dividend) + try: + divisor = int(divisor) + except ValueError, TypeError: + raise ValueError(u'Error: divisor="%s" cannot convert to int' + % divisor) + # + if dividend % divisor == 0: + return True + else: + return False + diff --git a/97suifangqa/apps/indicator/tools.py b/97suifangqa/apps/indicator/tools.py index 663ec4f..a472db5 100644 --- a/97suifangqa/apps/indicator/tools.py +++ b/97suifangqa/apps/indicator/tools.py @@ -5,13 +5,52 @@ utils for apps/indicator """ from django.contrib.auth.models import User +from django.shortcuts import get_object_or_404 from indicator import models as im from sciblog import models as sciblogm +import re import datetime +# follow_indicator {{{ +def follow_indicator(user_id, indicator_id): + """ + 用户关注指标 + """ + try: + user = get_object_or_404(User, id=user_id) + indicator = im.Indicator.objects.get(id=indicator_id) + ui, created = im.UserIndicator.objects.get_or_create(user=user) + ui.followedIndicators.add(indicator) + # to remove the indicator from 'followedHistories' if exists + if indicator in ui.followedHistories.all(): + ui.followedHistories.remove(indicator) + return True + except: + return False +# }}} + + +# unfollow_indicator {{{ +def unfollow_indicator(user_id, indicator_id): + """ + 用户取消关注指标 + """ + try: + user = get_object_or_404(User, id=user_id) + indicator = im.Indicator.objects.get(id=indicator_id) + ui, created = im.UserIndicator.objects.get_or_create(user=user) + ui.followedIndicators.remove(indicator) + # add indicator to 'followedHistories' + ui.followedHistories.add(indicator) + return True + except: + return False +# }}} + + # get_indicator {{{ def get_indicator(category_id="all", startswith="all"): """ @@ -102,7 +141,8 @@ def get_unfollowed_indicator(user_id, category_id="all", startswith="all"): u = User.objects.get(id=user_id) ui, created = im.UserIndicator.objects.get_or_create(user=u) _idict = {} - iqueryset = im.Indicator.objects.exclude(user_indicators=ui) + # XXX: if 'exclude(followed_indicators=ui)' OK?? + iqueryset = im.Indicator.objects.exclude(followed_indicators=ui) if not category_id == 'all': try: cid = int(category_id) @@ -192,25 +232,41 @@ def get_record_std(**kwargs): # }}} +# types of recommended indicators, and weights {{{ +RI_TYPES = { + 'ANNOTATION_COLLECTED': u'ANN_CL', + 'BLOG_CATCHED': u'BLG_CT', + 'BLOG_COLLECTED': u'BLG_CL', + 'OTHER': u'OTHER', + 'ERROR': u'ERROR', # no 'RelatedIndicator' data +} +RI_WEIGHTS = { + RI_TYPES['ANNOTATION_COLLECTED']: 4.0, + RI_TYPES['BLOG_CATCHED']: 3.0, + RI_TYPES['BLOG_COLLECTED']: 2.0, + RI_TYPES['OTHER']: 1.0, + RI_TYPES['ERROR']: 0.0, +} +# }}} + + # calc_indicator_weight {{{ def calc_indicator_weight(user_id, indicator_id): """ calculate the weight of given indicator used by 'recommend_indicator' + + return format: + {'weight': w, 'type': t} """ - ### XXX: weight_type: how to store the weights into database ### - weight_annotation = 4.0 - weight_blog_catched = 3.0 - weight_blog_collected = 2.0 - weight_other = 1.0 - ################################################################ # weight = weight_type * relatedindicator.weight user = User.objects.get(id=user_id) ri_qs = im.RelatedIndicator.objects.filter(indicator__id=indicator_id) if not ri_qs: - # queryset empty + # queryset empty: no 'RelatedIndicator' for this indicator + type = RI_TYPES['ERROR'] w = 0.0 - return w + return {'weight': w, 'type': type} # queryset not empty annotation_ri_qs = ri_qs.filter(annotation__collected_by=user) blogcatch_ri_qs = ri_qs.filter(blog__catched_by=user) @@ -218,26 +274,32 @@ def calc_indicator_weight(user_id, indicator_id): weights = [] if annotation_ri_qs: # related to annotations collected by user + type = RI_TYPES['ANNOTATION_COLLECTED'] for ri in annotation_ri_qs: - w = weight_annotation * ri.weight - weights.append(w) + w = RI_WEIGHTS[type] * ri.weight + weights.append({'weight': w, 'type': type}) elif blogcatch_ri_qs: # related to blogs catched by user + type = RI_TYPES['BLOG_CATCHED'] for ri in blogcatch_ri_qs: - w = weight_blog_catched * ri.weight - weights.append(w) + w = RI_WEIGHTS[type] * ri.weight + weights.append({'weight': w, 'type': type}) elif blogcollect_ri_qs: - # related to blogs catched by user + # related to blogs collected by user + type = RI_TYPES['BLOG_COLLECTED'] for ri in blogcollect_ri_qs: - w = weight_blog_collected * ri.weight - weights.append(w) + w = RI_WEIGHTS[type] * ri.weight + weights.append({'weight': w, 'type': type}) else: # other type, use 'ri_qs' here + type = RI_TYPES['OTHER'] for ri in ri_qs: - w = weight_other * ri.weight - weights.append(w) + w = RI_WEIGHTS[type] * ri.weight + weights.append({'weight': w, 'type': type}) + # sort results + weights_sorted = sorted(weights, key = lambda item: item['weight']) # return final result - return max(weights) + return weights_sorted[-1] # }}} @@ -247,22 +309,25 @@ def recommend_indicator(user_id, number): recommend unfollowed indicator for user, based on his/her readings and collections. - return a list with the id's of recommended indicators - - TODO: - performance test + return a list of recommended indicators in format: + [ {'id': id, 'weight': w, 'type': t}, ... ] """ user_id = int(user_id) number = int(number) # get unfollowed indicators u = User.objects.get(id=user_id) ui, created = im.UserIndicator.objects.get_or_create(user=u) - uf_ind_qs = im.Indicator.objects.exclude(user_indicators=ui) + # XXX: if 'exclude(followed_indicators=ui)' OK?? + uf_ind_qs = im.Indicator.objects.exclude(followed_indicators=ui) # calc weight for each unfollowed indicator weights = [] for ind in uf_ind_qs: - w = calc_indicator_weight(user_id, ind.id) - weights.append({'id': ind.id, 'weight': w}) + wdict = calc_indicator_weight(user_id, ind.id) + weights.append({ + 'id': ind.id, + 'weight': wdict.get('weight'), + 'type': wdict.get('type'), + }) # sort 'weights' dict list by key 'weight' weights_sorted = sorted(weights, key=lambda item: item['weight']) weights_sorted.reverse() @@ -271,3 +336,90 @@ def recommend_indicator(user_id, number): # }}} +# format_data {{{ +def format_data(indicator_obj, value=None, val_max=None, val_min=None): + """ + format given data according to the dataType of given Indicator, + make it proper for django templates + e.g.: + if number very big, then display in 'exponent notation' + + used in '.views.indicator_status()' + """ + # threshold to show a float number in scientific notation + float_threshold = 9999.9 + # float display format: fixed point, exponent notation + fix_fmt = '{:,.1f}' # comma as a thousands separator + exp_fmt = '{:.2e}' + # regex to process exponent notation + rep = re.compile(r'^(?P<sign>[-+]?)(?P<num>\d\.\d+)[eE]\+?(?P<expminus>-?)0*(?P<exp>[1-9]+)$') + # range symbol (range: low $symbol$ high) + range_sym = '∼' + # default return value + value_str = u'' + + # check given 'indicator_obj' + ind = indicator_obj + if not isinstance(ind, im.Indicator): + print u'Error: given indicator_obj NOT a instance of Indicator' + raise ValueError(u'Given indicator_obj NOT a instance of Indicator') + # get dataType + dataType = ind.dataType + + if value is not None: + # a) record float data; b) record integer/pm data; + # c) confine integer/pm data. + if dataType == ind.INTEGER_TYPE: + # TODO: process 'integer type' data + value_str = u'INTEGER: %s' % value + elif dataType == ind.PM_TYPE: + if value == u'+': + value_str = u'阳性(+)' + else: + value_str = u'阳性(-)' + elif dataType in [ind.FLOAT_TYPE, ind.FLOAT_RANGE_TYPE]: + # process float number + value = float(value) + if value <= float_threshold: + value_str = fix_fmt.format(value) + else: + value_expstr = exp_fmt.format(value) + # convert to html exponent format + m = rep.match(value_expstr) + value_str = "%s%s×10<sup>%s%s</sup>" % ( + m.group('sign'), m.group('num'), + m.group('expminus'), m.group('exp')) + else: + # unknown XXX + pass + elif (val_max is not None) and (val_min is not None): + # a) record range data; b) confine range. + # val_max + if val_max <= float_threshold: + val_max_str = fix_fmt.format(val_max) + else: + val_max_expstr = exp_fmt.format(val_max) + # convert to html exponent format + m = rep.match(val_max_expstr) + val_max_str = "%s%s×10<sup>%s%s</sup>" % ( + m.group('sign'), m.group('num'), + m.group('expminus'), m.group('exp')) + # val_min + if val_min <= float_threshold: + val_min_str = fix_fmt.format(val_min) + else: + val_min_expstr = exp_fmt.format(val_min) + # convert to html exponent format + m = rep.match(val_min_expstr) + val_min_str = "%s%s×10<sup>%s%s</sup>" % ( + m.group('sign'), m.group('num'), + m.group('expminus'), m.group('exp')) + # value_str + value_str = u'%s %s %s' % (val_min_str, range_sym, val_max_str) + else: + # other type?? + pass + + return value_str +# }}} + diff --git a/97suifangqa/apps/indicator/urls.py b/97suifangqa/apps/indicator/urls.py index 5818616..93f3c7b 100644 --- a/97suifangqa/apps/indicator/urls.py +++ b/97suifangqa/apps/indicator/urls.py @@ -51,10 +51,10 @@ urlpatterns += patterns('indicator.views', url(r'^status/$', 'indicator_status', name='indicator_status'), - # follow_indicator, 关注指标 - url(r'^follow/$', - 'follow_indicator', - name='follow_indicator'), + # indicator_fanduf, 关注指标 + url(r'^follow_and_unfollow/$', + 'indicator_fanduf', + name='indicator_fanduf'), ## indicator: popup # DeleteCardTip url(r'^popup/deletecardtip/$', @@ -94,6 +94,10 @@ urlpatterns += patterns('indicator.views', url(r'^ajax/get_card_data_table/$', 'ajax_get_card_data_table', name='indicator_ajax_getcarddatatable'), + # unfollow_indicator + url(r'^ajax/unfollow_indicator/$', + 'ajax_unfollow_indicator', + name='indicator_ajax_unfollowindicator'), ) urlpatterns += patterns('indicator.views', diff --git a/97suifangqa/apps/indicator/views.py b/97suifangqa/apps/indicator/views.py index f222d10..dc7b902 100644 --- a/97suifangqa/apps/indicator/views.py +++ b/97suifangqa/apps/indicator/views.py @@ -11,10 +11,16 @@ from django.shortcuts import render, get_object_or_404 # CRSF from django.template import RequestContext +# haystack search +from haystack.query import SearchQuerySet + from indicator import models as im from indicator.forms import * from indicator.tools import * +# apps/utils +from utils.search_tools import objects_of_sqs + import re import datetime @@ -51,40 +57,6 @@ def recommend_indicator_view(request, **kwargs): return HttpResponse("%s" % ilist) -# follow_indicator {{{ -@login_required -def follow_indicator(request, indicator_id): - """ - 用户关注指标 - """ - try: - indicator = im.Indicator.objects.get(pk=int(indicator_id)) - ui, created = im.UserIndicator.objects.get_or_create( - user=request.user) - ui.followedIndicators.add(indicator) - return { 'success': True } - except: - return { 'success': False } -# }}} - - -# unfollow_indicator {{{ -@login_required -def unfollow_indicator(request, indicator_id): - """ - 用户取消关注指标 - """ - try: - indicator = im.Indicator.objects.get(pk=int(indicator_id)) - ui, created = im.UserIndicator.objects.get_or_create( - user=request.user) - ui.followedIndicators.remove(indicator) - return { 'success': True } - except: - return { 'success': False } -# }}} - - # get_record_view {{{ @login_required def get_record_view(request, indicator_id, date_range, **kwargs): @@ -445,15 +417,126 @@ def indicator_status(request): """ status page for indicator add/edit/view indicator data + + TODO: + * when to recommend indicators + * how to deal with non-standard units """ template = 'indicator/SheetDefault.html' - return render(request, template) + letters = map(chr, range(ord('a'), ord('z')+1)) + # indicators + indicators = [] + followed_indicators = [] + r_indicators = [] + + # get followed indicator, P[inyin] dict format + followed_indicators_pdict = get_followed_indicator(request.user.id) + # convert to list + for l in letters: + followed_indicators += followed_indicators_pdict[l] + + ## TODO: when to recommend indicators for user ?? + if not followed_indicators: + # if no followed indicators yet, then recommend 2 indicators + r_indicators_unsort = [ + im.Indicator.objects.get(id=ri['id']).dump() + for ri in recommend_indicator(request.user.id, 2) + ] + r_indicators= sorted(r_indicators_unsort, + key = lambda item: item['pinyin']) + + # recommended indicators behind followed ones + indicators = followed_indicators + r_indicators + + # process 'indicators' list, to add following keys: # {{{ + # ref_text: + # ref_value: + # std_unit_symbol: + # record_empty: bool, if has no record, then True + for ind in indicators: + ind_obj = get_object_or_404(im.Indicator, id=ind['id']) + # check if 'indicator.is_ready()' + if not ind_obj.is_ready(): + raise ValueError(u"Indicator id=%s is NOT ready yet!" + % ind_obj.id) + # the indicator is ready + dataType = ind_obj.dataType + confine = ind_obj.get_confine() + ## set 'ref_text', 'ref_value', 'std_unit_*' + if dataType in [ind_obj.FLOAT_TYPE, ind_obj.RANGE_TYPE, + ind_obj.FLOAT_RANGE_TYPE]: + ind['ref_text'] = u"参考范围" + # ref_value + human_max = confine.get('human_max') + human_min = confine.get('human_min') + ind['ref_value'] = format_data(ind_obj, + val_max=human_max, val_min=human_min) + # set 'std_unit_*' + ind['std_unit_name'] = confine.get('unit').get('name') + ind['std_unit_symbol'] = confine.get('unit').get('symbol') + elif dataType in [ind_obj.INTEGER_TYPE, ind_obj.PM_TYPE]: + ind['ref_text'] = u"参考值" + # ref_value + val_norm = confine.get('val_norm') + ind['ref_value'] = format_data(ind_obj, value=val_norm) + # std_unit + ind['std_unit_name'] = u"" + ind['std_unit_symbol'] = u"" + else: + ind['ref_text'] = u"参考" + ind['ref_value'] = None + ind['std_unit_name'] = None + ind['std_unit_symbol'] = None + ## check record of indicator + records = ind_obj.indicator_records.filter(user=request.user).\ + order_by('-date', '-updated_at') + if records: + ind['record_empty'] = False + # last record of the indicator + last_record = records[0] + if dataType in [ind_obj.INTEGER_TYPE, ind_obj.PM_TYPE, + ind_obj.FLOAT_TYPE]: + value_str = format_data(ind_obj, value=last_record.value) + elif dataType == ind_obj.RANGE_TYPE: + value_str = format_data(ind_obj, + val_max=last_record.val_max, + val_min=last_record.val_min) + elif dataType == ind_obj.FLOAT_RANGE_TYPE: + value = last_record.value + val_max = last_record.val_max + val_min = last_record.val_min + if value is not None: + value_str = format_data(ind_obj, value=value) + elif (val_max is not None) and (val_min is not None): + value_str = format_data(ind_obj, + val_max=val_max, val_min=val_min) + else: + value_str = u'' + else: + # unknow + value_str = u'' + # save to dict + ind['last_record'] = { + 'date': last_record.date.isoformat(), + 'value_str': value_str, + } + else: + ind['record_empty'] = True + ind['last_record'] = {} + # }}} + + data = { + 'indicators': indicators, + } + # render template + #raise ValueError + return render(request, template, data) # }}} # indicator/NewDeleteIndex.html {{{ @login_required -def follow_indicator(request): +def indicator_fanduf(request): """ follow/unfollow indicator @@ -471,23 +554,23 @@ def follow_indicator(request): # set default value for 'selected_cat*' selected_catid = None selected_category = None - # get indicators, P[inyin] dict format - indicators_pdict = get_indicator() - # get followed indicator, P[inyin] dict format - followed_indicators_pdict = get_followed_indicator(request.user.id) - # convert to list - followed_indicators = [] - for l in letters: - followed_indicators += followed_indicators_pdict[l] + # default parameters + indicators = None + search_kw_empty = False + search_result_empty = False # get/post views if request.method == 'GET': - if request.GET.get('tab'): + # default page_condition: "all" + selected_catid = "all" + page_condition = "all" + # page_condition: "category" + # select category / search indicator + if 'tab' in request.GET: # tab: selected category, default "all" selected_catid = request.GET.get('tab') - if selected_catid == "all": + if selected_catid == "all" or selected_catid == "": page_condition = "all" - indicators = indicators_pdict else: selected_catid = int(selected_catid) selected_category = get_object_or_404( @@ -496,25 +579,50 @@ def follow_indicator(request): # get indicators of the category indicators = selected_category.indicators.\ all().order_by('pinyin') - elif request.GET.get('kw'): + # page_condition: "search" + # can override the above 'category' if 'tab' & 'kw' both exist + if 'kw' in request.GET: # kw: search keyword to find indicator search_kw = request.GET.get('kw') page_condition = "search" - # TODO - indicators = [] - else: - # default page_condition: "all" - selected_catid = "all" - page_condition = "all" - indicators = indicators_pdict + selected_catid = None + # check search keyword + if search_kw == "": + search_kw_empty = True + else: + # search + # TODO: howto order_by() by 'pinyin' + sqs = SearchQuerySet().models(im.Indicator).\ + filter(content=search_kw) + if sqs: + # search result not empty + inds_unsort = [ind.dump() + for ind in objects_of_sqs(sqs)] + indicators = sorted(inds_unsort, + key = lambda item: item['pinyin']) + else: + search_result_empty = True elif request.method == 'POST': - # do post process + # posted data of followed indicators # TODO - pass + post = request.POST + raise ValueError(u"TODO") else: # XXX raise Http404 + # all indicators + if page_condition == "all": + # get indicators, P[inyin] dict format + indicators = get_indicator() + + # get followed indicator, P[inyin] dict format + followed_indicators_pdict = get_followed_indicator(request.user.id) + # convert to list + followed_indicators = [] + for l in letters: + followed_indicators += followed_indicators_pdict[l] + data = { 'page_condition': page_condition, 'categories': categories, @@ -523,6 +631,8 @@ def follow_indicator(request): 'letters': letters, 'indicators': indicators, 'followed_indicators': followed_indicators, + 'search_kw_empty': search_kw_empty, + 'search_result_empty': search_result_empty, } # render page return render(request, template, data) @@ -537,7 +647,7 @@ def indicator_deletecardtip(request): prompted tip for deleting a card """ template = 'indicator/popup/DeleteCardTip.html' - return render_to_response(template) + return render(request, template) # }}} @@ -548,7 +658,7 @@ def indicator_edithistorydata(request): popup page to edit history data for an indicator """ template = 'indicator/popup/EditHistoryData.html' - return render_to_response(template) + return render(request, template) # }}} @@ -559,30 +669,52 @@ def indicator_indexdesc(request): description for an indicator """ template = 'indicator/popup/IndexDesc.html' - return render_to_response(template) + return render(request, template) # }}} ########################################################### ###### ajax ###### +# ajax_act_index {{{ @login_required def ajax_act_index(request): """ index action (add/minus) follow/unfollow indicator - - TODO: - * howto relate 'index_id' to 'indicator_id'? - * howto implement follow/unfollow indicator function? """ - if request.is_ajax(): - result = 'success' - else: - result = 'fail' - #raise Http404 + # default 'fail' + result = 'fail' + #if request.is_ajax(): + if True: + # check index_id -> indicator_id + if request.GET.get('index_id') is not None: + index_id = request.GET.get('index_id') + try: + indicator_id = int(index_id) + except ValueError: + print u'Error: Given index_id="%s" cannot convert to integer' % indicator_id + result = 'fail' + return HttpResponse(result) + # check 'act': add/minus -> action: follow/unfollow + if request.GET.get('act') is not None: + action = request.GET.get('act') + if action == "add": + # follow + if follow_indicator(request.user.id, indicator_id): + result = 'success' + elif action == "minus": + # unfollow + if unfollow_indicator(request.user.id, indicator_id): + result = 'success' + else: + raise ValueError(u'Error: Given act="%s" unknown' % action) + result = 'fail' + return HttpResponse(result) +# }}} +# ajax_close_sub_title {{{ def ajax_close_sub_title(request): """ close the small prompt banner above the indicator cards @@ -595,8 +727,10 @@ def ajax_close_sub_title(request): result = 'fail' #raise Http404 return HttpResponse(result) +# }}} +# ajax_edit_history_data {{{ @login_required def ajax_edit_history_data(request): """ @@ -609,8 +743,10 @@ def ajax_edit_history_data(request): result = 'fail' #raise Http404 return HttpResponse(result) +# }}} +# ajax_get_card_data_chart {{{ @login_required def ajax_get_card_data_chart(request): """ @@ -634,8 +770,10 @@ def ajax_get_card_data_chart(request): #raise Http404 return HttpResponse(json.dumps(result), mimetype='application/json') +# }}} +# ajax_get_card_data_table {{{ @login_required def ajax_get_card_data_table(request): """ @@ -665,6 +803,32 @@ def ajax_get_card_data_table(request): result = '' #raise Http404 return HttpResponse(result) +# }}} + + +# ajax_unfollow_indicator {{{ +@login_required +def ajax_unfollow_indicator(request): + """ + respone to the ajax request from 'delete_card_tip.js' + unfollow the specified indicator: GET.indicator_id + """ + # default 'fail' + result = 'fail' + if request.is_ajax(): + if request.GET.get('indicator_id') is not None: + indicator_id = request.GET.get('indicator_id') + try: + indicator_id = int(indicator_id) + except ValueError: + print u'Error: Given indicator_id="%s" cannot convert to integer' % indicator_id + result = 'fail' + if unfollow_indicator(request.user.id, indicator_id): + result = 'success' + + # return result + return HttpResponse(result) +# }}} ########################################################### |