Project

General

Profile

Patch #44274 » example-journalized-news.patch

Florian Walchshofer, 2026-10-18 21:29

View differences:

app/controllers/news_controller.rb
31 31

  
32 32
  helper :watchers
33 33
  helper :attachments
34
  helper :issues
35
  helper :custom_fields
34 36

  
35 37
  def index
36 38
    case params[:format]
......
68 70
  end
69 71

  
70 72
  def show
73
    @journals = @news.journals.visible.preload(:user, :details).reorder(:id).to_a
71 74
    @comments = @news.comments.preload(:commented).to_a
72 75
    @comments.reverse! if User.current.wants_comments_in_reverse_order?
73 76

  
......
105 108
  end
106 109

  
107 110
  def update
111
    @news.init_journal(User.current)
108 112
    @news.safe_attributes = params[:news]
109 113
    @news.save_attachments(params[:attachments] || (params[:news] && params[:news][:uploads]))
110 114
    if @news.save
app/models/news.rb
30 30
  validates_length_of :summary, :maximum => 255
31 31

  
32 32
  acts_as_attachable :edit_permission => :manage_news,
33
                     :delete_permission => :manage_news
33
                     :delete_permission => :manage_news,
34
                     :after_add => :attachment_added,
35
                     :after_remove => :attachment_removed
34 36
  acts_as_searchable :columns => ['title', 'summary', "#{table_name}.description"],
35 37
                     :preload => :project
36 38
  acts_as_event :url => Proc.new {|o| {:controller => 'news', :action => 'show', :id => o.id}}
......
38 40
                            :author_key => :author_id
39 41
  acts_as_watchable
40 42
  acts_as_webhookable
43
  acts_as_journalized :activity_type => 'news',
44
                      :attributes => %w(title summary description),
45
                      :permissions => {
46
                        :edit_notes => :manage_news,
47
                        :add_watchers => :manage_news
48
                      }
41 49
  include News::Webhookable
42 50

  
43 51
  after_create :add_author_as_watcher
44 52
  after_create_commit :send_notification
53
  after_save :create_journal
45 54

  
46 55
  scope :visible, (lambda do |*args|
47 56
    joins(:project).
......
54 63
    !user.nil? && user.allowed_to?(:view_news, project)
55 64
  end
56 65

  
66
  def init_journal(user, notes = '')
67
    @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
68
  end
69

  
70
  attr_reader :current_journal
71

  
72
  # Callback on file attachment
73
  def attachment_added(attachment)
74
    if current_journal && !attachment.new_record?
75
      current_journal.journalize_attachment(attachment, :added)
76
    end
77
  end
78

  
79
  # Callback on attachment deletion
80
  def attachment_removed(attachment)
81
    if current_journal && !attachment.new_record?
82
      current_journal.journalize_attachment(attachment, :removed)
83
      current_journal.save
84
    end
85
  end
86

  
87
  def journal_event_title(journal)
88
    "#{l(:label_news)}: #{title}"
89
  end
90

  
91
  def journal_event_type(journal)
92
    'news'
93
  end
94

  
57 95
  # Returns true if the news can be commented by user
58 96
  def commentable?(user=User.current)
59 97
    user.allowed_to?(:comment_news, project)
......
91 129

  
92 130
  private
93 131

  
132
  def create_journal
133
    current_journal&.save
134
    @current_journal = nil
135
  end
136

  
94 137
  def add_author_as_watcher
95 138
    Watcher.create(:watchable => self, :user => author)
96 139
  end
app/views/news/show.html.erb
35 35
</div>
36 36
<br />
37 37

  
38
<% if @journals.any? %>
39
<div id="history" class="journals">
40
<h3><%= l(:label_history) %></h3>
41
<% @journals.each do |journal| %>
42
  <div id="change-<%= journal.id %>" class="<%= journal.css_classes %>">
43
    <h4 class="journal-header">
44
      <span class="journal-info"><%= avatar(journal.user) %> <%= authoring journal.created_on, journal.user, :label => :label_updated_time_by %></span>
45
    </h4>
46
    <ul class="details">
47
      <% journal.visible_details.each do |detail| %>
48
        <% if detail.property == 'attr' && detail.prop_key == 'description' %>
49
          <%# The diff of JournalsController is specific to issues, show it inline %>
50
          <li>
51
            <%= l(:text_journal_changed_no_detail, :label => content_tag(:strong, l(:field_description))).html_safe %>
52
            <details>
53
              <summary><%= l(:label_diff) %></summary>
54
              <pre class="text-diff"><%= Redmine::Helpers::Diff.new(detail.value, detail.old_value).to_html %></pre>
55
            </details>
56
          </li>
57
        <% else %>
58
          <% details_to_strings([detail]).each do |string| %>
59
            <li><%= string %></li>
60
          <% end %>
61
        <% end %>
62
      <% end %>
63
    </ul>
64
    <%= textilizable(journal, :notes) if journal.notes.present? %>
65
  </div>
66
<% end %>
67
</div>
68
<% end %>
69

  
38 70
<div id="comments" class="journals">
39 71
<h3 class="comments"><%= l(:label_comment_plural) %></h3>
40 72
<% if @news.commentable? && @comments.size >= 3 %>
lib/redmine/preparation.rb
27 27

  
28 28
      # Journalized types, registered before their classes are loaded
29 29
      Redmine::Acts::Journalized.register 'Issue'
30
      Redmine::Acts::Journalized.register 'News'
30 31

  
31 32
      Scm::Base.add "Subversion"
32 33
      Scm::Base.add "Mercurial"
......
409 410
      Activity.map do |activity|
410 411
        activity.register :issues, :class_name => %w(Issue Journal)
411 412
        activity.register :changesets
412
        activity.register :news
413
        activity.register :news, :class_name => %w(News Journal)
413 414
        activity.register :documents, :class_name => %w(Document Attachment)
414 415
        activity.register :files, :class_name => 'Attachment'
415 416
        activity.register :wiki_edits, :class_name => 'WikiContentVersion', :default => false
(3-3/4)