From d029eae1f7002aced1a6b002062bf19e4b515afb Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 25 Mar 2016 16:27:31 +0530 Subject: [PATCH] [enhancement] added feature to track user seen, fixes #1648 --- frappe/core/doctype/doctype/doctype.json | 72 +++++++++++++++++-- frappe/core/doctype/user/user.js | 2 +- frappe/core/doctype/user/user.py | 1 - frappe/data/Framework.sql | 1 + frappe/desk/doctype/event/event.json | 35 ++++++++- frappe/desk/form/load.py | 2 + frappe/model/__init__.py | 2 +- frappe/model/db_query.py | 8 ++- frappe/model/db_schema.py | 7 ++ frappe/model/document.py | 22 ++++++ frappe/public/css/list.css | 3 + .../js/frappe/list/list_item_subject.html | 2 +- frappe/public/js/frappe/list/listview.js | 8 +++ frappe/public/less/list.less | 4 ++ frappe/tests/test_seen.py | 43 +++++++++++ frappe/website/doctype/web_form/web_form.py | 1 + 16 files changed, 201 insertions(+), 12 deletions(-) create mode 100644 frappe/tests/test_seen.py diff --git a/frappe/core/doctype/doctype/doctype.json b/frappe/core/doctype/doctype/doctype.json index 04377ec510..0bfa848a46 100644 --- a/frappe/core/doctype/doctype/doctype.json +++ b/frappe/core/doctype/doctype/doctype.json @@ -18,6 +18,7 @@ "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "", @@ -42,6 +43,7 @@ "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, "label": "Module", @@ -69,6 +71,7 @@ "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Is Child Table", @@ -95,6 +98,7 @@ "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Is Single", @@ -120,6 +124,7 @@ "fieldtype": "Column Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "length": 0, @@ -142,6 +147,7 @@ "fieldtype": "Select", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Document Type", @@ -168,6 +174,7 @@ "fieldtype": "Data", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Icon", @@ -191,6 +198,7 @@ "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Custom?", @@ -214,6 +222,7 @@ "fieldtype": "Data", "hidden": 1, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Plugin", @@ -237,6 +246,7 @@ "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Fields", @@ -261,6 +271,7 @@ "fieldtype": "Table", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Fields", @@ -287,6 +298,7 @@ "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Naming", @@ -306,11 +318,12 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "description": "\\\n
  • field:[fieldname] - By Field\\\n
  • naming_series: - By Naming Series (field called naming_series must be present\\\n
  • Prompt - Prompt user for a name\\\n
  • [series] - Series by prefix (separated by a dot); for example PRE.#####\\\n')\">Naming Options", + "description": "Naming Options", "fieldname": "autoname", "fieldtype": "Data", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Auto Name", @@ -336,6 +349,7 @@ "fieldtype": "Select", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Name Case", @@ -362,6 +376,7 @@ "fieldtype": "Small Text", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Description", @@ -388,6 +403,7 @@ "fieldtype": "Column Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "length": 0, @@ -412,6 +428,7 @@ "fieldtype": "Data", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Title Field", @@ -437,6 +454,7 @@ "fieldtype": "Data", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Timeline Field", @@ -462,6 +480,7 @@ "fieldtype": "Data", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Search Fields", @@ -490,6 +509,7 @@ "fieldtype": "Data", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Sort Field", @@ -515,6 +535,7 @@ "fieldtype": "Select", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Sort Order", @@ -540,6 +561,7 @@ "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Permission Rules", @@ -564,6 +586,7 @@ "fieldtype": "Table", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Permissions", @@ -591,6 +614,7 @@ "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "length": 0, @@ -613,6 +637,7 @@ "fieldtype": "Column Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Permissions Settings", @@ -636,6 +661,7 @@ "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "User Cannot Create", @@ -661,6 +687,7 @@ "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "User Cannot Search", @@ -686,6 +713,7 @@ "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Is Submittable", @@ -710,6 +738,7 @@ "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Allow Import", @@ -733,6 +762,7 @@ "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Allow Rename", @@ -758,6 +788,7 @@ "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "In Dialog", @@ -783,6 +814,7 @@ "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Show Print First", @@ -808,6 +840,7 @@ "fieldtype": "Int", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Max Attachments", @@ -833,9 +866,10 @@ "fieldtype": "Column Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "Hide Actions", + "label": "Other Settings", "length": 0, "no_copy": 0, "permlevel": 0, @@ -856,6 +890,7 @@ "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Hide Heading", @@ -881,6 +916,7 @@ "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Hide Toolbar", @@ -906,6 +942,7 @@ "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Hide Copy", @@ -923,6 +960,31 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "track_seen", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Track Seen", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -931,6 +993,7 @@ "fieldtype": "Data", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Default Print Format", @@ -957,7 +1020,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2016-02-01 07:55:35.810722", + "modified": "2016-03-25 05:25:12.265558", "modified_by": "Administrator", "module": "Core", "name": "DocType", @@ -1008,5 +1071,6 @@ "read_only_onload": 0, "search_fields": "module", "sort_field": "modified", - "sort_order": "DESC" + "sort_order": "DESC", + "track_seen": 0 } \ No newline at end of file diff --git a/frappe/core/doctype/user/user.js b/frappe/core/doctype/user/user.js index 9bc055836e..707c6f4b32 100644 --- a/frappe/core/doctype/user/user.js +++ b/frappe/core/doctype/user/user.js @@ -20,7 +20,7 @@ frappe.ui.form.on('User', { }, onload: function(frm) { - if(has_common(user_roles, ["Administrator", "System Manager"])) { + if(has_common(user_roles, ["Administrator", "System Manager"]) && !frm.doc.__islocal) { if(!frm.roles_editor) { var role_area = $('
    ') .appendTo(frm.fields_dict.roles_html.wrapper); diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index 0358194691..f1e99f27b7 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -452,7 +452,6 @@ def user_query(doctype, txt, searchfield, start, page_len, filters): where enabled=1 and docstatus < 2 and name not in ({standard_users}) - and user_type != 'Website User' and ({key} like %s or concat_ws(' ', first_name, middle_name, last_name) like %s) {mcond} diff --git a/frappe/data/Framework.sql b/frappe/data/Framework.sql index 98e039b3a0..3e541bcd43 100644 --- a/frappe/data/Framework.sql +++ b/frappe/data/Framework.sql @@ -132,6 +132,7 @@ CREATE TABLE `tabDocType` ( `allow_import` int(1) NOT NULL DEFAULT 0, `hide_toolbar` int(1) NOT NULL DEFAULT 0, `hide_heading` int(1) NOT NULL DEFAULT 0, + `track_seen` int(1) NOT NULL DEFAULT 0, `max_attachments` int(11) NOT NULL DEFAULT 0, `print_outline` varchar(255) DEFAULT NULL, `read_only_onload` int(1) NOT NULL DEFAULT 0, diff --git a/frappe/desk/doctype/event/event.json b/frappe/desk/doctype/event/event.json index d846a16ef5..c58b463c78 100644 --- a/frappe/desk/doctype/event/event.json +++ b/frappe/desk/doctype/event/event.json @@ -7,6 +7,7 @@ "custom": 0, "docstatus": 0, "doctype": "DocType", + "document_type": "Document", "fields": [ { "allow_on_submit": 0, @@ -16,6 +17,7 @@ "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "", @@ -40,6 +42,7 @@ "fieldtype": "Data", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, "label": "Subject", @@ -63,6 +66,7 @@ "fieldtype": "Select", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, "label": "Event Type", @@ -90,6 +94,7 @@ "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Send an email reminder in the morning", @@ -113,6 +118,7 @@ "fieldtype": "Column Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "length": 0, @@ -135,6 +141,7 @@ "fieldtype": "Datetime", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Starts on", @@ -158,6 +165,7 @@ "fieldtype": "Datetime", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Ends on", @@ -181,6 +189,7 @@ "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "All Day", @@ -204,6 +213,7 @@ "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "length": 0, @@ -226,6 +236,7 @@ "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Repeat this Event", @@ -250,6 +261,7 @@ "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "length": 0, @@ -273,6 +285,7 @@ "fieldtype": "Select", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Repeat On", @@ -299,6 +312,7 @@ "fieldtype": "Date", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Repeat Till", @@ -322,6 +336,7 @@ "fieldtype": "Column Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "length": 0, @@ -345,6 +360,7 @@ "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Monday", @@ -369,6 +385,7 @@ "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Tuesday", @@ -393,6 +410,7 @@ "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Wednesday", @@ -417,6 +435,7 @@ "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Thursday", @@ -441,6 +460,7 @@ "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Friday", @@ -465,6 +485,7 @@ "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Saturday", @@ -489,6 +510,7 @@ "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Sunday", @@ -512,6 +534,7 @@ "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "length": 0, @@ -534,6 +557,7 @@ "fieldtype": "Text Editor", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Description", @@ -561,6 +585,7 @@ "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Participants", @@ -585,6 +610,7 @@ "fieldtype": "Column Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Groups", @@ -611,6 +637,7 @@ "fieldtype": "Table", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Roles", @@ -637,6 +664,7 @@ "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Ref Type", @@ -663,6 +691,7 @@ "fieldtype": "Dynamic Link", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Ref Name", @@ -692,7 +721,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2016-01-08 04:50:37.240223", + "modified": "2016-03-25 06:09:03.205236", "modified_by": "Administrator", "module": "Desk", "name": "Event", @@ -741,5 +770,7 @@ ], "read_only": 1, "read_only_onload": 0, - "title_field": "subject" + "sort_order": "DESC", + "title_field": "subject", + "track_seen": 1 } \ No newline at end of file diff --git a/frappe/desk/form/load.py b/frappe/desk/form/load.py index a9c58cff9b..f306a2cc81 100644 --- a/frappe/desk/form/load.py +++ b/frappe/desk/form/load.py @@ -45,6 +45,8 @@ def getdoc(doctype, name, user=None): if doc and not name.startswith('_'): frappe.get_user().update_recent(doctype, name) + doc.add_seen() + frappe.response.docs.append(doc) @frappe.whitelist() diff --git a/frappe/model/__init__.py b/frappe/model/__init__.py index 64b40502e9..a2903c0013 100644 --- a/frappe/model/__init__.py +++ b/frappe/model/__init__.py @@ -14,7 +14,7 @@ default_fields = ('doctype','name','owner','creation','modified','modified_by', integer_docfield_properties = ("reqd", "search_index", "in_list_view", "permlevel", "hidden", "read_only", "ignore_user_permissions", "allow_on_submit", "report_hide", "in_filter", "no_copy", "print_hide", "unique") -optional_fields = ("_user_tags", "_comments", "_assign", "_liked_by") +optional_fields = ("_user_tags", "_comments", "_assign", "_liked_by", "_seen") def rename(doctype, old, new, debug=False): import frappe.model.rename_doc diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index 7582f0d720..0b08a05e7d 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -85,7 +85,7 @@ class DatabaseQuery(object): def prepare_args(self): self.parse_args() self.extract_tables() - self.remove_user_tags() + self.set_optional_columns() self.build_conditions() args = frappe._dict() @@ -176,7 +176,7 @@ class DatabaseQuery(object): if (not self.flags.ignore_permissions) and (not frappe.has_permission(doctype)): raise frappe.PermissionError, doctype - def remove_user_tags(self): + def set_optional_columns(self): """Removes optional columns like `_user_tags`, `_comments` etc. if not in table""" columns = frappe.db.get_table_columns(self.doctype) @@ -206,6 +206,10 @@ class DatabaseQuery(object): else: self.filters.remove(each) + # add _seen if track_seen is set + if frappe.get_meta(self.doctype).track_seen: + self.fields.append('_seen') + def build_conditions(self): self.conditions = [] self.grouped_or_conditions = [] diff --git a/frappe/model/db_schema.py b/frappe/model/db_schema.py index 6a902d992c..7d773ca3c7 100644 --- a/frappe/model/db_schema.py +++ b/frappe/model/db_schema.py @@ -212,6 +212,13 @@ class DbTable: "fieldtype": "Text" }) + # add _seen column if track_seen + if self.meta.track_seen: + fl.append({ + 'fieldname': '_seen', + 'fieldtype': 'Text' + }) + if not frappe.flags.in_install_db and frappe.flags.in_install != "frappe": custom_fl = frappe.db.sql("""\ SELECT * FROM `tabCustom Field` diff --git a/frappe/model/document.py b/frappe/model/document.py index 6183913a4e..6c42e5d972 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -609,6 +609,7 @@ class Document(BaseDocument): Will also update title_field if set""" self.set_title_field() + self.reset_seen() if self.flags.ignore_validate: return @@ -675,6 +676,11 @@ class Document(BaseDocument): for d in self.get_all_children(): _clear_cache(d) + def reset_seen(self): + '''Clear _seen property and set current user as seen''' + if self.meta.track_seen: + self._seen = json.dumps([frappe.session.user]) + def notify_update(self): """Publish realtime that the current document is modified""" frappe.publish_realtime("doc_update", {"modified": self.modified, "doctype": self.doctype, "name": self.name}, @@ -811,6 +817,22 @@ class Document(BaseDocument): }).insert(ignore_permissions=True) return comment + def add_seen(self, user=None): + '''add the given/current user to list of users who have seen this document (_seen)''' + if not user: + user = frappe.session.user + + if self.meta.track_seen: + if self._seen: + _seen = json.loads(self._seen) + else: + _seen = [] + + if user not in _seen: + _seen.append(user) + self.db_set('_seen', json.dumps(_seen)) + frappe.local.flags.commit = True + def get_signature(self): """Returns signature (hash) for private URL.""" return hashlib.sha224(get_datetime_str(self.creation)).hexdigest() diff --git a/frappe/public/css/list.css b/frappe/public/css/list.css index 2ed7e89401..a5f3a27158 100644 --- a/frappe/public/css/list.css +++ b/frappe/public/css/list.css @@ -153,6 +153,9 @@ .list-id { font-weight: bold; } +.list-id.seen { + font-weight: normal; +} .list-col { height: 20px; } diff --git a/frappe/public/js/frappe/list/list_item_subject.html b/frappe/public/js/frappe/list/list_item_subject.html index 1a00e96db7..23c2b98ba3 100644 --- a/frappe/public/js/frappe/list/list_item_subject.html +++ b/frappe/public/js/frappe/list/list_item_subject.html @@ -12,7 +12,7 @@ -{{ _title }} diff --git a/frappe/public/js/frappe/list/listview.js b/frappe/public/js/frappe/list/listview.js index 27a2dbc0c5..9a63082481 100644 --- a/frappe/public/js/frappe/list/listview.js +++ b/frappe/public/js/frappe/list/listview.js @@ -259,6 +259,14 @@ frappe.views.ListView = Class.extend({ get_avatar_and_id: function(data, without_workflow) { data._without_workflow = without_workflow; + + var seen = JSON.parse(data._seen); + + data.css_seen = ''; + if(seen && seen.indexOf(frappe.session.user) !== -1) { + data.css_seen = 'seen' + } + return frappe.render_template("list_item_subject", data); }, diff --git a/frappe/public/less/list.less b/frappe/public/less/list.less index 880543a549..39b2c86116 100644 --- a/frappe/public/less/list.less +++ b/frappe/public/less/list.less @@ -197,6 +197,10 @@ font-weight: bold; } +.list-id.seen { + font-weight: normal; +} + .list-col { height: 20px; } diff --git a/frappe/tests/test_seen.py b/frappe/tests/test_seen.py new file mode 100644 index 0000000000..d50e8a73ad --- /dev/null +++ b/frappe/tests/test_seen.py @@ -0,0 +1,43 @@ +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt +from __future__ import unicode_literals + +import frappe, unittest, json + +class TestSeen(unittest.TestCase): + def test_if_user_is_added(self): + ev = frappe.get_doc({ + 'doctype': 'Event', + 'subject': 'test event for seen', + 'starts_on': '2016-01-01 10:10:00', + 'event_type': 'Public' + }).insert() + + frappe.set_user('test@example.com') + + from frappe.desk.form.load import getdoc + + # load the form + getdoc('Event', ev.name) + + # reload the event + ev = frappe.get_doc('Event', ev.name) + + self.assertTrue('test@example.com' in json.loads(ev._seen)) + + # test another user + frappe.set_user('test1@example.com') + + # load the form + getdoc('Event', ev.name) + + # reload the event + ev = frappe.get_doc('Event', ev.name) + + self.assertTrue('test@example.com' in json.loads(ev._seen)) + self.assertTrue('test1@example.com' in json.loads(ev._seen)) + + ev.save() + + self.assertFalse('test@example.com' in json.loads(ev._seen)) + self.assertTrue('test1@example.com' in json.loads(ev._seen)) diff --git a/frappe/website/doctype/web_form/web_form.py b/frappe/website/doctype/web_form/web_form.py index 2d64539035..f194c2b13d 100644 --- a/frappe/website/doctype/web_form/web_form.py +++ b/frappe/website/doctype/web_form/web_form.py @@ -102,6 +102,7 @@ class WebForm(WebsiteGenerator): if frappe.form_dict.name: context.doc = frappe.get_doc(self.doc_type, frappe.form_dict.name) context.title = context.doc.get(context.doc.meta.get_title_field()) + context.doc.add_seen() context.reference_doctype = context.doc.doctype context.reference_name = context.doc.name