Browse Source

HUE-950 [core] Added UI for tags

Enrico Berti 12 years ago
parent
commit
d259c98971

+ 19 - 11
desktop/core/src/desktop/models.py

@@ -64,19 +64,24 @@ class DocumentTag(models.Model):
   def get_trash_tag(cls, user):
   def get_trash_tag(cls, user):
     tag, created = DocumentTag.objects.get_or_create(owner=user, tag=DocumentTag.TRASH)
     tag, created = DocumentTag.objects.get_or_create(owner=user, tag=DocumentTag.TRASH)
     return tag
     return tag
-  
+
   @classmethod
   @classmethod
-  def add_or_create_tag(cls, owner, doc_id, tag_name, tag_id=None):
-    tag = DocumentTag.objects.get(id=tag_id, owner=owner)
-    if tag.id == DocumentTag.get_trash_tag(owner):
-      raise Exception(_("Can't add trash tag. Please trash the document from instead."))
-        
-    if tag_id is not None:
-      tag = DocumentTag.objects.create(tag=tag_name, owner=owner)  
-      
-    doc = Document.objects.get_doc(doc_id, owner=owner)
-    doc.add(tag)
+  def create_tag(cls, owner, tag_name):
+    tag = DocumentTag.objects.create(tag=tag_name, owner=owner)
+    return tag
 
 
+  @classmethod
+  def add_or_create_tag(cls, owner, doc_id, tag_name, tag_id=None):
+    try:
+      tag = DocumentTag.objects.get(id=tag_id, owner=owner)
+      if tag.id == DocumentTag.get_trash_tag(owner):
+        raise Exception(_("Can't add trash tag. Please trash the document from instead."))
+    except:
+      tag = DocumentTag.objects.create(tag=tag_name, owner=owner)
+
+    doc = Document.objects.get_doc(doc_id, owner)
+    doc.add_tag(tag)
+    return tag
 
 
   @classmethod
   @classmethod
   def remove_tag(cls, tag_id, owner, doc_id):
   def remove_tag(cls, tag_id, owner, doc_id):
@@ -204,6 +209,9 @@ class Document(models.Model):
   def can_edit_or_exception(self, user, exception_class=PopupException):
   def can_edit_or_exception(self, user, exception_class=PopupException):
     """Deprecated by can_write_or_exception"""
     """Deprecated by can_write_or_exception"""
     return self.can_write_or_exception(user, exception_class)
     return self.can_write_or_exception(user, exception_class)
+
+  def add_tag(self, tag):
+    self.tags.add(tag)
       
       
   def send_to_trash(self):
   def send_to_trash(self):
     tag = DocumentTag.get_trash_tag(user=self.owner)
     tag = DocumentTag.get_trash_tag(user=self.owner)

+ 337 - 131
desktop/core/src/desktop/templates/home.mako

@@ -44,9 +44,10 @@ ${ commonheader(_('Welcome Home'), "home", user) | n,unicode }
     line-height: 15px !important;
     line-height: 15px !important;
   }
   }
 
 
-  .toggleTag {
+  .toggleTag, .documentTagsModalCheckbox, .tagsModalCheckbox {
     cursor: pointer;
     cursor: pointer;
   }
   }
+
 </style>
 </style>
 
 
 <div class="navbar navbar-inverse navbar-fixed-top nokids">
 <div class="navbar navbar-inverse navbar-fixed-top nokids">
@@ -89,7 +90,7 @@ ${ commonheader(_('Welcome Home'), "home", user) | n,unicode }
               </ul>
               </ul>
            </li>
            </li>
            <li class="viewTrash"><a href="javascript:void(0)"><i class="icon-trash"></i> ${_('View Trash')} <span id="trashCounter" class="badge pull-right">0</span></a></li>
            <li class="viewTrash"><a href="javascript:void(0)"><i class="icon-trash"></i> ${_('View Trash')} <span id="trashCounter" class="badge pull-right">0</span></a></li>
-          <li class="nav-header">${_('Tags')}</li>
+          <li class="nav-header tag-header">${_('Tags')} <div id="editTags" style="display: inline;cursor: pointer;margin-left: 6px"><i class="icon-edit"></i></div> </li>
           % for tag in tags:
           % for tag in tags:
             %if tag.tag != "trash":
             %if tag.tag != "trash":
             <li class="toggleTag white" data-tag="${ tag.tag }"><div class="hueCheckbox pull-left"></div>${ tag.tag }</li>
             <li class="toggleTag white" data-tag="${ tag.tag }"><div class="hueCheckbox pull-left"></div>${ tag.tag }</li>
@@ -128,167 +129,372 @@ ${ commonheader(_('Welcome Home'), "home", user) | n,unicode }
   </div>
   </div>
 </div>
 </div>
 
 
+<div id="documentTagsModal" class="modal hide fade">
+  <div class="modal-header">
+    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+    <h3>${_('Tags for ')}<span id="documentTagsModalName"></span></h3>
+  </div>
+  <div class="modal-body">
+    <p>
+      <div id="documentTagsModalList"></div>
+      <div class="clearfix"></div>
+      <div style="margin-top: 20px">
+        <div class="input-append">
+          <input id="documentTagsNew" type="text">
+          <button id="documentTagsNewBtn" class="btn" type="button"><i class="icon-plus-sign"></i> ${_('Add')}</button>
+        </div>
+      </div>
+    </p>
+  </div>
+  <div class="modal-footer">
+    <a href="#" data-dismiss="modal" class="btn">${_('Cancel')}</a>
+    <a id="saveDocumentTags" href="#" class="btn btn-primary">${_('Save tags')}</a>
+  </div>
+</div>
+
+<div id="tagsModal" class="modal hide fade">
+  <div class="modal-header">
+    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+    <h3>${_('Manage tags')}</h3>
+  </div>
+  <div class="modal-body">
+    <p>
+      <div id="tagsModalList"></div>
+      <div class="clearfix"></div>
+      <div style="margin-top: 20px">
+        <div class="input-append">
+          <input id="tagsNew" type="text">
+          <button id="tagsNewBtn" class="btn" type="button"><i class="icon-plus-sign"></i> ${_('Add')}</button>
+        </div>
+      </div>
+    </p>
+  </div>
+  <div class="modal-footer">
+    <a href="#" data-dismiss="modal" class="btn">${_('Cancel')}</a>
+    <a id="removeTags" href="#" class="btn btn-danger">${_('Remove selected')}</a>
+  </div>
+</div>
+
+
 <script src="/static/ext/js/datatables-paging-0.1.js" type="text/javascript" charset="utf-8"></script>
 <script src="/static/ext/js/datatables-paging-0.1.js" type="text/javascript" charset="utf-8"></script>
 <script type="text/javascript" charset="utf-8">
 <script type="text/javascript" charset="utf-8">
 
 
-  var JSON_DOCS = ${json_documents|n};
-  var documentsTable;
-
-  $(document).ready(function () {
-    documentsTable = $(".datatables").dataTable({
-      "sPaginationType": "bootstrap",
-      "iDisplayLength": 50,
-      "bLengthChange": false,
-      "sDom": "<'row'r>t<'row-fluid'<'dt-pages'p><'dt-records'i>>",
-      "aoColumns": [
-        { "bSortable": false, "sWidth": "26px" },
-        null,
-        null,
-        null,
-        null,
-        { "sSortDataType": "dom-sort-value", "sType": "numeric", "sWidth": "100px" }
-      ],
-      "aaSorting": [
-        [ 1, "desc" ]
-      ],
-      "oLanguage": {
-        "sEmptyTable": "${_('No data available')}",
-        "sInfo": "${_('Showing _START_ to _END_ of _TOTAL_ entries')}",
-        "sInfoEmpty": "${_('Showing 0 to 0 of 0 entries')}",
-        "sInfoFiltered": "${_('(filtered from _MAX_ total entries)')}",
-        "sZeroRecords": "${_('No matching records')}",
-        "oPaginate": {
-          "sFirst": "${_('First')}",
-          "sLast": "${_('Last')}",
-          "sNext": "${_('Next')}",
-          "sPrevious": "${_('Previous')}"
-        }
-      },
-      "fnDrawCallback": function (oSettings) {
-        $("a[data-row-selector='true']").jHueRowSelector();
+var JSON_DOCS = ${json_documents|n};
+var JSON_TAGS = ${json_tags|n};
+var documentsTable;
+
+$(document).ready(function () {
+  documentsTable = $(".datatables").dataTable({
+    "sPaginationType": "bootstrap",
+    "iDisplayLength": 50,
+    "bLengthChange": false,
+    "sDom": "<'row'r>t<'row-fluid'<'dt-pages'p><'dt-records'i>>",
+    "aoColumns": [
+      { "bSortable": false, "sWidth": "26px" },
+      null,
+      null,
+      { "sClass": "row-selector-exclude"},
+      null,
+      { "sSortDataType": "dom-sort-value", "sType": "numeric", "sWidth": "100px" }
+    ],
+    "aaSorting": [
+      [ 1, "desc" ]
+    ],
+    "oLanguage": {
+      "sEmptyTable": "${_('No data available')}",
+      "sInfo": "${_('Showing _START_ to _END_ of _TOTAL_ entries')}",
+      "sInfoEmpty": "${_('Showing 0 to 0 of 0 entries')}",
+      "sInfoFiltered": "${_('(filtered from _MAX_ total entries)')}",
+      "sZeroRecords": "${_('No matching records')}",
+      "oPaginate": {
+        "sFirst": "${_('First')}",
+        "sLast": "${_('Last')}",
+        "sNext": "${_('Next')}",
+        "sPrevious": "${_('Previous')}"
       }
       }
+    },
+    "fnDrawCallback": function (oSettings) {
+      $("a[data-row-selector='true']").jHueRowSelector();
+    }
+  });
+
+  $("#filterInput").keydown(function (e) {
+    if (e.which == 13) {
+      e.preventDefault();
+      return false;
+    }
+  });
+
+  $("#filterInput").keyup(function () {
+    documentsTable.fnFilter($(this).val());
+    documentsTable.fnDraw();
+  });
+
+  populateTable();
+
+  $(".viewTrash").on("click", function () {
+    $(".hueCheckbox").removeClass("icon-ok");
+    var _this = $(this);
+    if (_this.hasClass("active")) {
+      populateTable();
+      _this.removeClass("active");
+    }
+    else {
+      populateTable("trash");
+      _this.addClass("active");
+    }
+  });
+
+  $(document).on("click", ".toggleTag", function (e) {
+    $(".viewTrash").removeClass("active");
+    var _this = $(this);
+    _this.blur();
+    if (_this.find(".hueCheckbox").hasClass("icon-ok")) {
+      _this.find(".hueCheckbox").removeClass("icon-ok");
+    }
+    else {
+      _this.find(".hueCheckbox").addClass("icon-ok");
+    }
+    var _tags = [];
+    $(".hueCheckbox.icon-ok").each(function () {
+      _tags.push($(this).parent().data("tag"));
     });
     });
+    populateTable(_tags.join(","));
+  });
+
+  var _trashCounter = 0;
+  $(JSON_DOCS).each(function (cnt, doc) {
+    if (isInTags(doc, "trash")) {
+      _trashCounter++;
+    }
+  });
+  $("#trashCounter").text(_trashCounter);
 
 
-    $("#filterInput").keydown(function (e) {
-      if (e.which == 13) {
-        e.preventDefault();
-        return false;
+  $(document).on("click", ".documentTags", function () {
+    $("#documentTagsModal").data("document-id", $(this).data("document-id"));
+    renderDocumentTagsModal();
+  });
+
+  function renderTags() {
+    $(".toggleTag").remove();
+    for (var i = JSON_TAGS.length - 1; i >= 0; i--) {
+      if (!JSON_TAGS[i].isTrash) {
+        var _t = $("<li>").addClass("toggleTag").addClass("white");
+        _t.data("tag", JSON_TAGS[i].name);
+        _t.html('<div class="hueCheckbox pull-left"></div>' + JSON_TAGS[i].name);
+        _t.insertAfter(".tag-header");
       }
       }
+    }
+  }
+
+  function renderTagsModal() {
+    var _tags = "";
+    for (var i = 0; i < JSON_TAGS.length; i++) {
+      if (!JSON_TAGS[i].isTrash) {
+        _tags += '<div style="margin-right:10px;margin-bottom: 6px;float:left;"><span class="tagsModalCheckbox badge" data-value="' + JSON_TAGS[i].id + '"><i class="icon-trash hide"></i> ' + JSON_TAGS[i].name + '</span></div>';
+      }
+    }
+    $("#tagsModalList").html(_tags);
+  }
+
+  function renderDocumentTagsModal() {
+    var _doc = getDocById($("#documentTagsModal").data("document-id"));
+    if (_doc != null) {
+      $("#documentTagsModalList").empty();
+      $("#documentTagsModalName").text(_doc.name);
+      var _tags = "";
+      for (var i = 0; i < JSON_TAGS.length; i++) {
+        if (!JSON_TAGS[i].isTrash) {
+          var _inTags = isInTags(_doc, JSON_TAGS[i].name);
+          _tags += '<div style="margin-right:10px;margin-bottom: 6px;float:left;"><span class="documentTagsModalCheckbox badge' + (_inTags ? ' badge-info selected' : '') + '" data-value="' + JSON_TAGS[i].id + '"><i class="icon-ok-sign' + (_inTags ? '' : ' hide') + '"></i> ' + JSON_TAGS[i].name + '</span></div>';
+        }
+      }
+      $("#documentTagsModalList").html(_tags);
+      $("#documentTagsModal").modal("show");
+    }
+  }
+
+  $("#documentTagsModal").modal({
+    show: false
+  });
+
+  $("#tagsModal").modal({
+    show: false
+  });
+
+  $("#documentTagsNewBtn").on("click", function () {
+    addTag($("#documentTagsNew").val(), function () {
+      $("#documentTagsNew").val("");
+      renderDocumentTagsModal();
     });
     });
+  });
 
 
-    $("#filterInput").keyup(function () {
-      documentsTable.fnFilter($(this).val());
-      documentsTable.fnDraw();
+  $("#tagsNewBtn").on("click", function () {
+    addTag($("#tagsNew").val(), function () {
+      $("#tagsNew").val("");
+      renderTagsModal();
     });
     });
+  });
 
 
-    populateTable();
 
 
-    $(".viewTrash").on("click", function () {
-      $(".hueCheckbox").removeClass("icon-ok");
-      var _this = $(this);
-      if (_this.hasClass("active")) {
-        populateTable();
-        _this.removeClass("active");
-      }
-      else {
-        populateTable("trash");
-        _this.addClass("active");
-      }
+  function addTag(value, callback) {
+    $.post("/tag/add_tag", {
+      name: value
+    }, function (data) {
+      $("#documentTagsNew").val("");
+      $.getJSON("/tag/list_tags", function (data) {
+        JSON_TAGS = data;
+        renderTags();
+        callback();
+      })
     });
     });
+  }
 
 
-    $(".toggleTag").on("click", function (e) {
-      $(".viewTrash").removeClass("active");
+  $(document).on("click", ".documentTagsModalCheckbox", function () {
+    var _this = $(this);
+    if (_this.hasClass("selected")) {
+      _this.removeClass("selected").removeClass("badge-info");
+      _this.find(".icon-ok-sign").addClass("hide");
+    }
+    else {
+      _this.addClass("selected").addClass("badge-info");
+      _this.find(".icon-ok-sign").removeClass("hide");
+    }
+  });
+
+
+  $("#editTags").on("click", function () {
+    renderTagsModal();
+    $("#tagsModal").modal("show");
+  });
+
+  $(document).on("click", ".tagsModalCheckbox", function () {
+    var _this = $(this);
+    if (_this.hasClass("selected")) {
+      _this.removeClass("selected").removeClass("badge-important");
+      _this.find(".icon-trash").addClass("hide");
+    }
+    else {
+      _this.addClass("selected").addClass("badge-important");
+      _this.find(".icon-trash").removeClass("hide");
+    }
+  });
+
+  $("#saveDocumentTags").on("click", function () {
+    var _tags = [];
+    $(".documentTagsModalCheckbox.selected").each(function () {
       var _this = $(this);
       var _this = $(this);
-      _this.blur();
-      if (_this.find(".hueCheckbox").hasClass("icon-ok")) {
-        _this.find(".hueCheckbox").removeClass("icon-ok");
-      }
-      else {
-        _this.find(".hueCheckbox").addClass("icon-ok");
-      }
-      var _tags = [];
-      $(".hueCheckbox.icon-ok").each(function () {
-        _tags.push($(this).parent().data("tag"));
-      });
-      populateTable(_tags.join(","));
+      _tags.push(_this.data("value"));
     });
     });
+    // TODO: $.post to remote to save the tags of a document
+    console.log("Post add tags to document " + _tags);
+  });
 
 
-    var _trashCounter = 0;
-    $(JSON_DOCS).each(function (cnt, doc) {
-      if (doc.tags.indexOf("trash") > -1) {
-        _trashCounter++;
-      }
+  $("#removeTags").on("click", function () {
+    var _tags = [];
+    $(".tagsModalCheckbox.selected").each(function () {
+      var _this = $(this);
+      _tags.push(_this.data("value"));
     });
     });
-    $("#trashCounter").text(_trashCounter);
+    // TODO: $.post to remote to remove tags globally
+    console.log("Post remove tags " + _tags);
   });
   });
 
 
-  function populateTable(tags) {
-    documentsTable.fnClearTable();
-    documentsTable.fnDraw();
-    if (tags == null || tags == "") {
-      $(JSON_DOCS).each(function (cnt, doc) {
-        if (doc.tags.indexOf("trash") == -1) {
-          addRow(doc);
-        }
-      });
+});
+
+function isInTags(doc, tag) {
+  if (doc.tags == null) {
+    return false;
+  }
+  var _inTags = false;
+  for (var i = 0; i < doc.tags.length; i++) {
+    if (doc.tags[i].name == tag) {
+      _inTags = true;
     }
     }
-    else {
-      var _tags = tags.split(",");
-      $(JSON_DOCS).each(function (cnt, doc) {
-        var _add = false;
-        $(_tags).each(function (cnt, tag) {
-          console.log(tag);
-          if (doc.tags.indexOf(tag) > -1) {
-            _add = true;
-          }
-        });
-        if (_add) {
-          addRow(doc);
+  }
+  return _inTags;
+}
+
+function populateTable(tags) {
+  documentsTable.fnClearTable();
+  documentsTable.fnDraw();
+  if (tags == null || tags == "") {
+    $(JSON_DOCS).each(function (cnt, doc) {
+      if (!isInTags(doc, "trash")) {
+        addRow(doc);
+      }
+    });
+  }
+  else {
+    var _tags = tags.split(",");
+    $(JSON_DOCS).each(function (cnt, doc) {
+      var _add = false;
+      $(_tags).each(function (cnt, tag) {
+        if (isInTags(doc, tag)) {
+          _add = true;
         }
         }
       });
       });
-    }
-    documentsTable.fnDraw();
+      if (_add) {
+        addRow(doc);
+      }
+    });
   }
   }
+  documentsTable.fnDraw();
+}
 
 
-  function addRow(doc) {
-    try {
-      documentsTable.fnAddData([
-        getIcon(doc.contentType),
-        '<a href="' + doc.url + '" data-row-selector="true">' + doc.name + '</a>',
-        emptyStringIfNull(doc.description),
-        emptyStringIfNull(doc.tags.join("-")),
-        emptyStringIfNull(doc.owner),
-        emptyStringIfNull(doc.lastModified)
-      ], false);
-    }
-    catch (error) {
-      $(document).trigger("error", error);
+function addRow(doc) {
+  try {
+    var _tags = "";
+    for (var i = 0; i < doc.tags.length; i++) {
+      _tags += '<span class="badge">' + doc.tags[i].name + '</span> ';
     }
     }
+    documentsTable.fnAddData([
+      getIcon(doc.contentType),
+      '<a href="' + doc.url + '" data-row-selector="true">' + doc.name + '</a>',
+      emptyStringIfNull(doc.description),
+      '<div class="documentTags" data-document-id="' + doc.id + '">' + _tags + '</div>',
+      emptyStringIfNull(doc.owner),
+      emptyStringIfNull(doc.lastModified)
+    ], false);
+  }
+  catch (error) {
+    $(document).trigger("error", error);
   }
   }
+}
 
 
-  function getIcon(contentType) {
-    var _code = '<img src="';
-    switch (contentType) {
-      case "workflow":
-        _code += '/oozie/static/art/icon_oozie_24_workflow.png';
-        break;
-      case "coordinator":
-        _code += '/oozie/static/art/icon_oozie_24_coordinator.png';
-        break;
-      case "bundle":
-        _code += '/oozie/static/art/icon_oozie_24_bundle.png';
-        break;
+function getDocById(id) {
+  var _doc = null;
+  $(JSON_DOCS).each(function (cnt, doc) {
+    if (doc.id == id) {
+      _doc = doc;
     }
     }
-    _code += '" />';
-    return _code;
+  });
+  return _doc;
+}
+
+function getIcon(contentType) {
+  var _code = '<img src="';
+  switch (contentType) {
+    case "workflow":
+      _code += '/oozie/static/art/icon_oozie_24_workflow.png';
+      break;
+    case "coordinator":
+      _code += '/oozie/static/art/icon_oozie_24_coordinator.png';
+      break;
+    case "bundle":
+      _code += '/oozie/static/art/icon_oozie_24_bundle.png';
+      break;
   }
   }
+  _code += '" />';
+  return _code;
+}
 
 
-  function emptyStringIfNull(obj) {
-    if (obj != null && typeof obj != "undefined") {
-      return obj;
-    }
-    return "";
+function emptyStringIfNull(obj) {
+  if (obj != null && typeof obj != "undefined") {
+    return obj;
   }
   }
+  return "";
+}
 </script>
 </script>
 
 
 ${ commonfooter(messages) | n,unicode }
 ${ commonfooter(messages) | n,unicode }

+ 2 - 0
desktop/core/src/desktop/urls.py

@@ -69,6 +69,8 @@ dynamic_patterns += patterns('desktop.views',
   (r'^log_frontend_event$', 'log_frontend_event'),
   (r'^log_frontend_event$', 'log_frontend_event'),
 
 
   # Doc model
   # Doc model
+  (r'^tag/add_tag', 'add_tag'),
+  (r'^tag/list_tags', 'list_tags'),
   (r'^doc/add_or_create_tag$', 'add_or_create_tag'),
   (r'^doc/add_or_create_tag$', 'add_or_create_tag'),
   (r'^doc/remove_tag$', 'remove_tag'),
   (r'^doc/remove_tag$', 'remove_tag'),
   (r'^doc/add_or_update_permission$', 'add_or_update_permission'),
   (r'^doc/add_or_update_permission$', 'add_or_update_permission'),

+ 41 - 6
desktop/core/src/desktop/views.py

@@ -55,10 +55,12 @@ LOG = logging.getLogger(__name__)
 @access_log_level(logging.WARN)
 @access_log_level(logging.WARN)
 def home(request):
 def home(request):
   docs = Document.objects.get_docs(request.user).order_by('-last_modified')[:100]
   docs = Document.objects.get_docs(request.user).order_by('-last_modified')[:100]
+  tags = DocumentTag.objects.filter(owner=request.user)
   return render('home.mako', request, {
   return render('home.mako', request, {
     'documents': docs,
     'documents': docs,
     'json_documents': json.dumps(massaged_documents_for_json(docs)),
     'json_documents': json.dumps(massaged_documents_for_json(docs)),
-    'tags': DocumentTag.objects.filter(owner=request.user),
+    'tags': tags,
+    'json_tags': json.dumps(massaged_tags_for_json(tags, request.user))
   })
   })
 
 
 def massaged_documents_for_json(documents):
 def massaged_documents_for_json(documents):
@@ -70,7 +72,7 @@ def massaged_documents_for_json(documents):
       'name': doc.name,
       'name': doc.name,
       'url': doc.content_object.get_absolute_url(),
       'url': doc.content_object.get_absolute_url(),
       'description': doc.description,
       'description': doc.description,
-      'tags': ['%s' % (tag.tag) for tag in doc.tags.all()],
+      'tags': [{'id': tag.id, 'name': tag.tag} for tag in doc.tags.all()],
       'owner': doc.owner.username,
       'owner': doc.owner.username,
       'lastModified': doc.last_modified.strftime("%x %X"),
       'lastModified': doc.last_modified.strftime("%x %X"),
       'lastModifiedInMillis': time.mktime(doc.last_modified.timetuple())
       'lastModifiedInMillis': time.mktime(doc.last_modified.timetuple())
@@ -80,13 +82,46 @@ def massaged_documents_for_json(documents):
   return docs
   return docs
 
 
 
 
+def massaged_tags_for_json(tags, user):
+  ts = []
+  trash = DocumentTag.get_trash_tag(user)
+  for tag in tags:
+    massaged_tag = {
+      'id': tag.id,
+      'name': tag.tag,
+      'isTrash': tag.id == trash.id
+    }
+    ts.append(massaged_tag)
+
+  return ts
+
+def add_tag(request):
+  response = {'status': -1, 'message': ''}
+
+  if request.method == 'POST':
+    try:
+      tag = DocumentTag.create_tag(request.user, request.POST['name'])
+      response['tag_id'] = tag.id
+    except Exception, e:
+      response['message'] = force_unicode(e)
+  else:
+    response['message'] = _('POST request only')
+
+  return HttpResponse(json.dumps(response), mimetype="application/json")
+
+def list_tags(request):
+  tags = DocumentTag.objects.filter(owner=request.user)
+  return HttpResponse(json.dumps(massaged_tags_for_json(tags, request.user)), mimetype="application/json")
+
+
 def add_or_create_tag(request):
 def add_or_create_tag(request):
   response = {'status': -1, 'message': ''}
   response = {'status': -1, 'message': ''}
   
   
-  if request.action == 'POST':
-    json = {'tag_id': 1, 'tag': 'hue project', 'doc_id': 1}   # instead ... json.load(request.POST) 
+  if request.method == 'POST':
+    request_json = json.loads(request.POST['data'])
     try:
     try:
-      DocumentTag.add_or_create_tag(request.user, json['doc_id'], json['tag'], json.get('tag_id'))
+      tag = DocumentTag.add_or_create_tag(request.user, request_json['doc_id'], request_json['tag'], request_json['tag_id'])
+      response['tag_id'] = tag.id
     except Exception, e:
     except Exception, e:
       response['message'] = force_unicode(e)
       response['message'] = force_unicode(e)
   else:
   else:
@@ -98,7 +133,7 @@ def add_or_create_tag(request):
 def remove_tag(request):
 def remove_tag(request):
   response = {'status': -1, 'message': _('Error')}
   response = {'status': -1, 'message': _('Error')}
   
   
-  if request.action == 'POST':
+  if request.method == 'POST':
     json = {'tag_id': 1, 'tag': 'hue project', 'doc_id': 1}  # instead ... json.load(request.POST)
     json = {'tag_id': 1, 'tag': 'hue project', 'doc_id': 1}  # instead ... json.load(request.POST)
     try:
     try:
       DocumentTag.remove_tag(id=json['tag_id'], owner=request.user, doc_id=json['doc_id'])
       DocumentTag.remove_tag(id=json['tag_id'], owner=request.user, doc_id=json['doc_id'])

+ 1 - 1
desktop/core/static/js/jquery.rowselector.js

@@ -39,7 +39,7 @@
     Plugin.prototype.init = function () {
     Plugin.prototype.init = function () {
         var _this = this;
         var _this = this;
         $(_this.element).closest("tr").click(function (e) {
         $(_this.element).closest("tr").click(function (e) {
-            if ($(e.target).data("row-selector-exclude")) {
+            if ($(e.target).data("row-selector-exclude") || $(e.target).closest("td").hasClass("row-selector-exclude")) {
                 return;
                 return;
             }
             }
             if (!$(e.target).is("a")) {
             if (!$(e.target).is("a")) {