changeset 341:85fa60bc06d4

Tracker: import and display history (including comments)
author Sylvain Beucler <beuc@beuc.net>
date Sat, 28 Aug 2010 12:54:52 +0200
parents bd527a3a0c68
children a13981e03bf4
files migrate_old_savane.sql savane/svmain/templatetags/svmarkup.py savane/tracker/models.py static_media/savane/css/tracker.css templates/tracker/item_form.html
diffstat 5 files changed, 191 insertions(+), 29 deletions(-) [+]
line wrap: on
line diff
--- a/migrate_old_savane.sql
+++ b/migrate_old_savane.sql
@@ -669,3 +669,30 @@
   FROM savane_old.trackers_msgid JOIN tracker_item
       ON (savane_old.trackers_msgid.item_id = tracker_item.public_bugs_id
           AND savane_old.trackers_msgid.artifact = tracker_item.tracker_id);
+
+-- Comments
+TRUNCATE tracker_comment;
+INSERT INTO tracker_comment
+    (item_id, message, posted_by_id, date, comment_type, spamscore, ip)
+  SELECT internal_id, old_value, mod_by, FROM_UNIXTIME(savane_old.bugs_history.date),
+    type, savane_old.bugs_history.spamscore, savane_old.bugs_history.ip
+  FROM savane_old.bugs_history JOIN tracker_item
+      ON (savane_old.bugs_history.bug_id = tracker_item.public_bugs_id)
+  WHERE field_name='details';
+-- TODO: import 'patch'
+-- TODO: import 'support'
+-- TODO: import 'task'
+
+-- Field history
+TRUNCATE tracker_itemhistory;
+INSERT INTO tracker_itemhistory
+    (item_id, date, mod_by_id, field, old_value, new_value)
+  SELECT internal_id, FROM_UNIXTIME(savane_old.bugs_history.date),
+    mod_by, field_name, old_value, new_value
+  FROM savane_old.bugs_history JOIN tracker_item
+      ON (savane_old.bugs_history.bug_id = tracker_item.public_bugs_id)
+  WHERE field_name <> 'details';
+-- TODO: import 'patch'
+-- TODO: import 'support'
+-- TODO: import 'task'
+
--- a/savane/svmain/templatetags/svmarkup.py
+++ b/savane/svmain/templatetags/svmarkup.py
@@ -26,3 +26,11 @@
 @register.filter
 def svmarkup_full(text):
     return mark_safe(markup.full(escape(text)))
+
+@register.filter
+def svmarkup_rich(text):
+    return mark_safe(markup.rich(escape(text)))
+
+@register.filter
+def svmarkup_basic(text):
+    return mark_safe(markup.basic(escape(text)))
--- a/savane/tracker/models.py
+++ b/savane/tracker/models.py
@@ -618,14 +618,34 @@
     item = models.ForeignKey('Item')
     msg_id = models.CharField(max_length=255)
 
+class Comment(models.Model):
+    """
+    Item comments (field='details')
+    """
+    class Meta:
+        ordering = ('item','date',)
+    item = models.ForeignKey('Item')
+    date = models.DateTimeField(default=datetime.date.today)
+    posted_by = models.ForeignKey(auth_models.User)
+    message = models.TextField(blank=True, null=True)
+    comment_type = models.IntegerField(_("comment type"), blank=True, null=True)
+      # Should be:
+      # type = models.ForeignKey('FieldChoice', to_field='value_id')
+      #        + constraint(same group or NULL) + constraint(field='comment_type_id')
+      # The purpose is to add <strong>[$comment_type]</strong> when
+      # displaying an item comment.
+    spamscore = models.IntegerField(_("total spamscore for this comment"))
+    ip = models.IPAddressField(blank=True, null=True)
 
 class ItemHistory(models.Model):
     """
-    This stores 2 kinds of values:
-    - item comments (field='details')
-    - value changes, for fields that have history tracking enabled
+    Value changes, for fields that have history tracking enabled
     """
+    class Meta:
+        ordering = ('item','date','id',)
     item = models.ForeignKey('Item')
+    date = models.DateTimeField(default=datetime.date.today)
+    mod_by = models.ForeignKey(auth_models.User)
     field = models.CharField(max_length=32)
        # Should be: field = models.ForeignKey('Field', to_field='name')
        #            + constraint (item.tracker=field.tracker)
@@ -633,31 +653,19 @@
        # But as it's a history field, adding constraints might be just bad.
     old_value= models.TextField(blank=True, null=True)
     new_value= models.TextField()
-    mod_by = models.ForeignKey(auth_models.User)
-    date = models.DateTimeField(default=datetime.date.today)
-    ip = models.IPAddressField(blank=True, null=True)
-
-    # Specific (bad!) field for 'details'
-    # I guess 'details' could be stored separately.
-    type = models.IntegerField(_("comment type"), blank=True, null=True)
-      # Should be:
-      # type = models.ForeignKey('FieldChoice', to_field='value_id')
-      #        + constraint(same group or 100) + constraint(field='comment_type_id')
-      # The purpose is to add <strong>[$comment_type]</strong> when
-      # displaying an item comment.
-    spamscore = models.IntegerField(_("total spamscore for this comment"))
 
 class ItemCc(models.Model):
     """
     Item carbon copies for mail notifications
     """
     item = models.ForeignKey('Item')
-    email = models.EmailField(max_length=255)
+    contact = models.CharField(max_length=255)
     added_by = models.ForeignKey(auth_models.User)
     comment = models.TextField()
     date = models.DateTimeField(default=datetime.date.today)
 
 #class ItemDependencies:
+# TODO: import
 # => cf. Item.dependencies
 
 class ItemFile(models.Model):
@@ -673,17 +681,17 @@
     filetype = models.TextField()
     # /!\ `file` longblob NOT NULL - if not savane-cleanup
 
-class ItemSpamScore(models.Model):
-    """
-    Spam reports
-
-    Score is summed in ItemHistory.spamscore.
-    """
-    score = models.IntegerField(default=1)
-    affected_user = models.ForeignKey(auth_models.User, related_name='itemspamscore_affected_set')
-    reporter_user = models.ForeignKey(auth_models.User, related_name='itemspamscore_reported_set')
-    item = models.ForeignKey('Item')
-    comment_id = models.ForeignKey('ItemHistory', null=True)
+#class CommentSpamScore(models.Model):
+#    """
+#    Spam reports
+#
+#    Score is summed in ItemHistory.spamscore.
+#    """
+#    score = models.IntegerField(default=1)
+#    affected = models.ForeignKey(auth_models.User, related_name='commentspamscore_affected_set')
+#    reporter = models.ForeignKey(auth_models.User, related_name='commentspamscore_reported_set')
+#    item = models.ForeignKey('Item')
+#    comment_id = models.ForeignKey('ItemComment', null=True)
 
 
 # TODO:
--- a/static_media/savane/css/tracker.css
+++ b/static_media/savane/css/tracker.css
@@ -47,6 +47,30 @@
 	border-left: thin #b5b5b5 solid;
 }
 
+table.comments {
+    width: 98%;
+    margin-left: 1%;
+    margin-right: 1%;
+    margin-bottom: 15px;
+    vertical-align: top;
+    border-spacing: 1px;
+    border: 0;
+}
+table.comments p {
+    padding-left:0.2em;
+    padding-right:0.2em;
+}
+table.comments td {
+    vertical-align: top;
+}
+
+table.history td {
+  text-align: center;
+}
+table.history td.old_value {
+  text-align: right;
+}
+
 /**********************************************************************
  *
  * Priorities
--- a/templates/tracker/item_form.html
+++ b/templates/tracker/item_form.html
@@ -1,5 +1,6 @@
 {% extends "base.html" %}
 {% load i18n %}
+{% load svmarkup %}
 
 {% block title %}
 {{object.group.name}} - {{object.get_tracker_name}}{% trans ": " %}
@@ -67,7 +68,7 @@
 <p class="preinput">{% trans "Add a new comment" %}{% trans ": " %}<br />
   <textarea cols="65" rows="16" name="comment"></textarea>
 </p>
-<p class="preinput">{% trans "Comment type & canned response" %}{% trans ": " %}<br />
+<p class="preinput">{% filter force_escape %}{% trans "Comment type & canned response" %}{% endfilter %}{% trans ": " %}<br />
   <select name="comment_type_id">
     {% for choice in object.get_field_defs.comment_type_id.choices %}
     <option value="{{choice.value_id}}">{{choice.value}}</option>
@@ -89,6 +90,58 @@
 
 <h2>{% trans "Discussion" %}</h2>
 
+<table class="comments">
+{% for comment in object.comment_set.all reversed %}
+<tr class="{% cycle 'boxitem' 'boxitemalt' as rowcolor %}">
+  <td>
+    <a name="comment{{forloop.counter}}" href="#comment{{forloop.count}}"
+      class="preinput">
+      {{comment.date}},
+      {% blocktrans with forloop.counter as number %}comment #{{number}}{% endblocktrans %}{% trans ": " %}
+    </a><br />
+    {{ comment.message|svmarkup_rich }}
+  </td>
+  <td class="boxitemextra">
+    {% if comment.posted_by_id %}
+      {% if comment.posted_by %}
+      <a href="{% url savane:svmain:user_detail comment.posted_by.username %}"
+      >{{comment.posted_by.get_full_name}}
+        &lt;{{comment.posted_by.username}}&gt;</a>
+      {% else %}
+        <strong>{% trans "Invalid user ID" %}</strong>
+      {% endif %}
+    {% else %}
+    {% trans "Anonymous" %}
+    {% endif %}
+    <!-- TODO: display poster privs -->
+  </td>
+</tr>
+{% endfor %}
+<tr class="{% cycle rowcolor %}">
+  <td>
+    <a name="comment0" href="#comment0"
+      class="preinput">
+      {{object.date}}, <strong>{% trans "original submission" %}{% trans ": " %}</strong>
+    </a><br />
+    {{ object.details|svmarkup_full }}
+  </td>
+  <td class="boxitemextra">
+    {% if object.submitted_by_id %}
+      {% if object.submitted_by %}
+      <a href="{% url savane:svmain:user_detail object.submitted_by.username %}"
+      >{{object.submitted_by.get_full_name}}
+        &lt;{{object.submitted_by.username}}&gt;</a>
+      {% else %}
+        <strong>{% trans "Invalid user ID" %}</strong>
+      {% endif %}
+    {% else %}
+    {% trans "Anonymous" %}
+    {% endif %}
+    <!-- TODO: display poster privs -->
+  </td>
+</tr>
+</table>
+
 <h2>{% trans "Attached files" %}</h2>
 
 <h2>{% trans "Dependencies" %}</h2>
@@ -102,6 +155,48 @@
 
 <h2>{% trans "History" %}</h2>
 
+<p>
+{% blocktrans count object.itemhistory_set.count as count %}Follows {{count}} latest change.{% plural %}Follow {{count}} latest changes.{% endblocktrans %}
+</p>
+
+<table class="history smaller box">
+<tr>
+  <th>Date</th>
+  <th>Changed by</th>
+  <th>Updated field</th>
+  <th>Previous value</th>
+  <th width="1">=&gt;</th>
+  <th>Replaced By</th>
+</tr>
+{% for history in object.itemhistory_set.all %}
+{% ifchanged history.date history.mod_by %}
+<tr class="{% cycle 'boxitem' 'boxitemalt' as rowcolor %}">
+  <td>{{history.date}}</td>
+  <td>
+    {% if history.mod_by_id %}
+      {% if history.mod_by %}
+      <a href="{% url savane:svmain:user_detail history.mod_by.username %}"
+      >{{history.mod_by.username}}</a>
+      {% else %}
+        <strong>{% trans "Invalid user ID" %}</strong>
+      {% endif %}
+    {% else %}
+    {% trans "Anonymous" %}
+    {% endif %}
+  </td>
+{% else %}
+<!-- skip {% cycle rowcolor %} once -->
+<tr class="{% cycle rowcolor %}">
+  <td></td><td></td>
+{% endifchanged %}
+  <td>{{history.field}}</td><!-- TODO: field definition['label'] -->
+  <td class="old_value">{{history.old_value}}</td><!-- TODO: if SB, use FieldChoice -->
+  <td><img width="24" src="{{STATIC_MEDIA_URL}}savane/images/common/arrows1/next.orig.png" border="0" alt="=&gt;" /></td>
+  <td>{{history.new_value}}</td>
+</tr>
+{% endfor %}
+</table>
+
 {% endblock %}
 {% comment %}
 Local Variables: **