Skip to content

Ticket Comments System

Version: Arctyk ITSM v0.6.0+
Last Updated: January 2026

Overview

The Ticket Comments System in Arctyk ITSM provides a comprehensive two-tier communication system for tickets, enabling both public discussions and internal notes. The system features real-time AJAX updates, rich text editing with TinyMCE, inline editing, and complete audit trails.

Key Features:

  • 🔓 Public Comments - Visible to all users with ticket access
  • 🔒 Internal Notes - Restricted to staff and assignees only
  • ✏️ Inline Editing - Edit comments in place with full revision history
  • 🗑️ Soft Deletion - Comments are marked as deleted, not removed
  • 📝 Rich Text - Full TinyMCE editor with formatting, lists, links, tables
  • 🚀 AJAX Operations - No page reloads for create/edit/delete
  • 🔍 Full Audit Trail - Automatic change tracking via ChangeLog

Architecture

Models

Comment Model

class Comment(models.Model):
    ticket = ForeignKey(Ticket, on_delete=CASCADE, related_name='comments')
    author = ForeignKey(User, on_delete=CASCADE, related_name='ticket_comments')
    body = TextField()  # Rich HTML content from TinyMCE
    comment_type = CharField(max_length=10, choices=[
        ('public', 'Public Comment'),
        ('internal', 'Internal Note'),
    ], default='public')

    # Timestamps
    created_at = DateTimeField(auto_now_add=True)
    updated_at = DateTimeField(auto_now=True)
    edited_at = DateTimeField(null=True, blank=True)

    # Deletion tracking
    is_deleted = BooleanField(default=False)
    deleted_at = DateTimeField(null=True, blank=True)
    deleted_by = ForeignKey(User, on_delete=SET_NULL, null=True, related_name='deleted_comments')

    # Editing tracking
    edited_by = ForeignKey(User, on_delete=SET_NULL, null=True, related_name='edited_comments')

    # Convenience properties
    @property
    def is_internal(self):
        return self.comment_type == 'internal'

    @property
    def is_edited(self):
        return self.edited_at is not None

CommentEditHistory Model

Tracks all edits to comments for complete audit trail:

class CommentEditHistory(models.Model):
    comment = ForeignKey(Comment, on_delete=CASCADE, related_name='edit_history')
    edited_by = ForeignKey(User, on_delete=SET_NULL, null=True)
    previous_body = TextField()  # Content before edit
    edited_at = DateTimeField(auto_now_add=True)

API Endpoints

All endpoints use AJAX with JSON responses:

POST /tickets/<ticket_id>/comments/add/

Creates a new comment.

Request:

{
    "body": "Comment text (HTML allowed)",
    "comment_type": "public" | "internal"
}

Response (201 Created):

{
  "success": true,
  "comment": {
    "id": 123,
    "body": "...",
    "comment_type": "public",
    "author": "John Doe",
    "created_at": "Jan 02, 2026 15:30",
    "is_internal": false
  }
}

PATCH /tickets/comments/<comment_id>/edit/

Updates an existing comment (creates edit history).

Request:

{
  "body": "Updated comment text",
  "comment_type": "public"
}

DELETE /tickets/comments/<comment_id>/delete/

Soft-deletes a comment (marks as deleted, doesn't remove).

Response (200 OK):

{
  "success": true,
  "message": "Comment deleted successfully"
}

GET /tickets/<ticket_id>/comments/

Retrieves all comments for a ticket (with permission filtering).


Frontend Implementation

JavaScript Class: TicketComments

The TicketComments class handles all client-side operations:

class TicketComments {
  constructor(ticketId) {
    this.ticketId = ticketId;
    this.init();
  }

  init() {
    this.initEventListeners();
    this.initTinyMCE();
  }

  // Key methods:
  // - submitComment()
  // - editComment(commentId)
  // - deleteComment(commentId)
  // - addCommentToUI(commentData)
}

Features:

  • Prevents default form submission
  • AJAX POST with CSRF token
  • Dynamic UI updates without page reload
  • TinyMCE initialization and management
  • Loading states with spinner
  • Error handling with user feedback
  • Internal note warning toggle

Templates

comment_form.html

<form id="commentForm" method="POST" data-ticket-id="{{ ticket.id }}">
  {% csrf_token %}

  {# Comment Type Toggle #}
  <div class="btn-group">
    <input type="radio" name="comment_type" id="typePublic" value="public" checked>
    <label for="typePublic">Public Comment</label>

    <input type="radio" name="comment_type" id="typeInternal" value="internal">
    <label for="typeInternal">Internal Note</label>
  </div>

  {# Internal Warning (hidden by default) #}
  <div class="internal-note-warning d-none">
    Warning: This note will not be visible to the requester.
  </div>

  {# TinyMCE Editor #}
  <textarea id="commentBody" name="body"></textarea>

  <button type="submit">Add Comment</button>
</form>

comment_list.html

Displays all comments with badges, timestamps, and edit/delete actions:

<div class="comments-list">
  {% for comment in comments %}
    <div class="comment-item {% if comment.is_internal %}comment-internal{% endif %}">
      <div class="comment-header">
        <strong>{{ comment.author.get_full_name }}</strong>
        {% if comment.is_internal %}
          <span class="badge bg-warning">Internal</span>
        {% endif %}
        <span class="text-muted">{{ comment.created_at }}</span>
      </div>

      <div class="comment-body">{{ comment.body|safe }}</div>

      <div class="comment-actions">
        <button class="edit-comment" data-comment-id="{{ comment.id }}">Edit</button>
        <button class="delete-comment" data-comment-id="{{ comment.id }}">Delete</button>
      </div>
    </div>
  {% endfor %}
</div>

Permissions

View Permissions

  • Public Comments: Visible to anyone with ticket access
  • Internal Notes: Only visible to:
  • Staff users (is_staff=True)
  • Ticket assignee
  • Ticket assigned group members

Action Permissions

  • Create Public Comment: Any authenticated user with ticket access
  • Create Internal Note: Staff, assignee, or assigned group only
  • Edit Comment: Author or staff
  • Delete Comment: Author or staff

Permission helper function:

def can_view_internal_notes(user):
    return user.is_staff or user.groups.filter(name__in=['Agents', 'Administrators']).exists()

Styling

The comment system uses a dedicated SCSS file with matching aesthetics to the overall design:

Key CSS Classes:

  • .comments-container - Main container
  • .comment-item - Individual comment card
  • .comment-internal - Internal note styling (yellow background)
  • .comment-form-container - Form wrapper with background
  • .internal-note-warning - Warning message styling
  • .comment-actions - Edit/delete buttons (visible on hover)

Design Features:

  • Responsive design with mobile support
  • Hover effects on comment cards
  • Color-coded internal notes (yellow)
  • Visual badges for internal notes
  • Clean timeline layout
  • Smooth transitions and animations

Security Features

  1. Authentication: All endpoints require login (@login_required)
  2. Permission Checks:
  3. Internal note visibility restricted
  4. Edit/delete restricted to author or staff
  5. XSS Protection: HTML sanitization in admin
  6. CSRF Protection: Token validation on all POST requests
  7. Audit Trail: All changes logged via changelog app
  8. Data Validation:
  9. Non-empty comment bodies
  10. Valid comment types
  11. User permission verification

Usage Examples

Creating a Public Comment

  1. Navigate to ticket detail page
  2. Click "Comments" tab
  3. Ensure "Public Comment" is selected
  4. Type comment in TinyMCE editor
  5. Click "Add Comment"
  6. Comment appears immediately without page reload

Creating an Internal Note

  1. Click "Internal Note" toggle
  2. Yellow warning appears
  3. Type internal note
  4. Click "Add Comment"
  5. Note marked with yellow badge

Editing a Comment

  1. Hover over your comment
  2. Click "Edit" button
  3. Modify text in editor
  4. Save changes
  5. "Edited" indicator appears with timestamp

Testing

Comprehensive test suite in src/tickets/tests/test_comments.py:

  • Model Tests: Comment creation, properties, relationships
  • Permission Tests: Public/internal visibility rules
  • API Tests: AJAX endpoints, response formats
  • Edit History Tests: Revision tracking
  • Deletion Tests: Soft delete functionality
  • Filter Tests: Comment list filtering by type

Run tests:

pytest src/tickets/tests/test_comments.py

Configuration

TinyMCE Settings

tinymce.init({
  selector: "#commentBody",
  plugins: "lists link image code table",
  toolbar:
    "undo redo | formatselect | bold italic | bullist numlist | link image | code table",
  menubar: false,
  statusbar: false,
  height: 200,
  content_css: "/static/css/app.css",
});

Admin Configuration

Comments are manageable in Django admin with:

  • List filters: ticket, comment type, author, date
  • Search fields: body, author name
  • Color-coded badges for internal notes
  • Edit history inline
  • Change log integration

Future Enhancements

Potential improvements for future versions:

  • Comment threading/replies
  • @mentions with notifications
  • Comment reactions (emoji)
  • Comment attachments
  • Rich notifications (email/push)
  • Comment templates
  • Scheduled comments
  • Comment export


Summary

The Ticket Comments System provides a production-ready, two-tier communication platform with rich text editing, real-time updates, comprehensive permissions, and full audit trails. It's designed for both internal collaboration and external customer communication within the Arctyk ITSM platform.