Project

General

Profile

Patch #1470 ยป MyPage_Projects_Block.patch

Chris Peterson, 2008-06-16 21:09

View differences:

app/controllers/my_controller.rb (working copy)
5 5
# modify it under the terms of the GNU General Public License
6 6
# as published by the Free Software Foundation; either version 2
7 7
# of the License, or (at your option) any later version.
8
#
8
#
9 9
# This program is distributed in the hope that it will be useful,
10 10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 12
# GNU General Public License for more details.
13
#
13
#
14 14
# You should have received a copy of the GNU General Public License
15 15
# along with this program; if not, write to the Free Software
16 16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17 17
class MyController < ApplicationController
18 18
  helper :issues
19

  
19

  
20 20
  layout 'base'
21 21
  before_filter :require_login
......
27 27
             'news' => :label_news_latest,
28 28
             'calendar' => :label_calendar,
29 29
             'documents' => :label_document_plural,
30
             'timelog' => :label_spent_time
30
             'timelog' => :label_spent_time,
31
             'projects' => :label_project_plural
31 32
           }.freeze
32
  DEFAULT_LAYOUT = {  'left' => ['issuesassignedtome'],
33
                      'right' => ['issuesreportedbyme']
33
  DEFAULT_LAYOUT = {  'left' => ['issuesassignedtome'],
34
                      'right' => ['issuesreportedbyme']
34 35
                   }.freeze
35 36
  verify :xhr => true,
......
72 73
    # Only users that belong to more than 1 project can select projects for which they are notified
73 74
    # Note that @user.membership.size would fail since AR ignores :include association option when doing a count
74 75
    @notification_options.insert 1, [l(:label_user_mail_option_selected), 'selected'] if @user.memberships.length > 1
75
    @notification_option = @user.mail_notification? ? 'all' : (@user.notified_projects_ids.empty? ? 'none' : 'selected')
76
    @notification_option = @user.mail_notification? ? 'all' : (@user.notified_projects_ids.empty? ? 'none' : 'selected')
76 77
  end
77 78
  # Manage user's password
......
91 92
      end
92 93
    end
93 94
  end
94

  
95

  
95 96
  # Create a new feeds key
96 97
  def reset_rss_key
97 98
    if request.post? && User.current.rss_token
......
110 111
    @block_options = []
111 112
    BLOCKS.each {|k, v| @block_options << [l(v), k]}
112 113
  end
113

  
114

  
114 115
  # Add a block to user's page
115 116
  # The block is added on top of the page
116 117
  # params[:block] : id of the block to add
......
124 125
    session[:page_layout]['top'].unshift block
125 126
    render :partial => "block", :locals => {:user => @user, :block_name => block}
126 127
  end
127

  
128

  
128 129
  # Remove a block to user's page
129 130
  # params[:block] : id of the block to remove
130 131
  def remove_block
......
145 146
      %w(top left right).each {|f|
146 147
        session[:page_layout][f] = (session[:page_layout][f] || []) - group_items
147 148
      }
148
      session[:page_layout][group] = group_items
149
      session[:page_layout][group] = group_items
149 150
    end
150 151
    render :nothing => true
151 152
  end
152

  
153
  # Save user's page layout
153

  
154
  # Save user's page layout
154 155
  def page_layout_save
155 156
    @user = User.current
156 157
    @user.pref[:my_page_layout] = session[:page_layout] if session[:page_layout]
app/models/project.rb (working copy)
5 5
# modify it under the terms of the GNU General Public License
6 6
# as published by the Free Software Foundation; either version 2
7 7
# of the License, or (at your option) any later version.
8
#
8
#
9 9
# This program is distributed in the hope that it will be useful,
10 10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 12
# GNU General Public License for more details.
13
#
13
#
14 14
# You should have received a copy of the GNU General Public License
15 15
# along with this program; if not, write to the Free Software
16 16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
......
19 19
  # Project statuses
20 20
  STATUS_ACTIVE     = 1
21 21
  STATUS_ARCHIVED   = 9
22

  
22

  
23 23
  has_many :members, :include => :user, :conditions => "#{User.table_name}.status=#{User::STATUS_ACTIVE}"
24 24
  has_many :users, :through => :members
25 25
  has_many :custom_values, :dependent => :delete_all, :as => :customized
......
38 38
  has_many :changesets, :through => :repository
39 39
  has_one :wiki, :dependent => :destroy
40 40
  # Custom field for the project issues
41
  has_and_belongs_to_many :custom_fields,
41
  has_and_belongs_to_many :custom_fields,
42 42
                          :class_name => 'IssueCustomField',
43 43
                          :order => "#{CustomField.table_name}.position",
44 44
                          :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
45 45
                          :association_foreign_key => 'custom_field_id'
46

  
46

  
47 47
  acts_as_tree :order => "name", :counter_cache => true
48 48
  acts_as_searchable :columns => ['name', 'description'], :project_key => 'id', :permission => nil
......
51 51
                :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o.id}}
52 52
  attr_protected :status, :enabled_module_names
53

  
53

  
54 54
  validates_presence_of :name, :identifier
55 55
  validates_uniqueness_of :name, :identifier
56 56
  validates_associated :custom_values, :on => :update
......
60 60
  validates_length_of :homepage, :maximum => 255
61 61
  validates_length_of :identifier, :in => 3..20
62 62
  validates_format_of :identifier, :with => /^[a-z0-9\-]*$/
63

  
63

  
64 64
  before_destroy :delete_all_members
65

  
65

  
66 66
  def identifier=(identifier)
67 67
    super unless identifier_frozen?
68 68
  end
69

  
69

  
70 70
  def identifier_frozen?
71 71
    errors[:identifier].nil? && !(new_record? || identifier.blank?)
72 72
  end
73

  
73

  
74 74
  def issues_with_subprojects(include_subprojects=false)
75 75
    conditions = nil
76 76
    if include_subprojects
......
79 79
    end
80 80
    conditions ||= ["#{Project.table_name}.id = ?", id]
81 81
    # Quick and dirty fix for Rails 2 compatibility
82
    Issue.send(:with_scope, :find => { :conditions => conditions }) do
82
    Issue.send(:with_scope, :find => { :conditions => conditions }) do
83 83
      Version.send(:with_scope, :find => { :conditions => conditions }) do
84 84
        yield
85 85
      end
86
    end
86
    end
87 87
  end
88 88
  # returns latest created projects
89 89
  # non public projects will be returned only if user is a member of those
90 90
  def self.latest(user=nil, count=5)
91
    find(:all, :limit => count, :conditions => visible_by(user), :order => "created_on DESC")
92
  end
91
    find(:all, :limit => count, :conditions => visible_by(user), :order => "created_on DESC")
92
  end
93 93
  def self.visible_by(user=nil)
94 94
    user ||= User.current
......
102 102
      return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND #{Project.table_name}.is_public = #{connection.quoted_true}"
103 103
    end
104 104
  end
105

  
105

  
106
  def self.members(user=nil)
107
    user ||= User.current
108
    if user && user.memberships.any?
109
      return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND #{Project.table_name}.id IN (#{user.memberships.collect{|m| m.project_id}.join(',')})"
110
    end
111
  end
112

  
113

  
106 114
  def self.allowed_to_condition(user, permission, options={})
107 115
    statements = []
108 116
    base_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
......
119 127
      statements << "#{Project.table_name}.id IN (#{allowed_project_ids.join(',')})" if allowed_project_ids.any?
120 128
    elsif Role.anonymous.allowed_to?(permission)
121 129
      # anonymous user allowed on public project
122
      statements << "#{Project.table_name}.is_public = #{connection.quoted_true}"
130
      statements << "#{Project.table_name}.is_public = #{connection.quoted_true}"
123 131
    else
124 132
      # anonymous user is not authorized
125 133
      statements << "1=0"
126 134
    end
127 135
    statements.empty? ? base_statement : "((#{base_statement}) AND (#{statements.join(' OR ')}))"
128 136
  end
129

  
137

  
130 138
  def project_condition(with_subprojects)
131 139
    cond = "#{Project.table_name}.id = #{id}"
132 140
    cond = "(#{cond} OR #{Project.table_name}.parent_id = #{id})" if with_subprojects
133 141
    cond
134 142
  end
135

  
143

  
136 144
  def self.find(*args)
137 145
    if args.first && args.first.is_a?(String) && !args.first.match(/^\d*$/)
138 146
      project = find_by_identifier(*args)
......
142 150
      super
143 151
    end
144 152
  end
145

  
153

  
146 154
  def to_param
147 155
    # id is used for projects with a numeric identifier (compatibility)
148 156
    @to_param ||= (identifier.to_s =~ %r{^\d*$} ? id : identifier)
149 157
  end
150

  
158

  
151 159
  def active?
152 160
    self.status == STATUS_ACTIVE
153 161
  end
154

  
162

  
155 163
  def archive
156 164
    # Archive subprojects if any
157 165
    children.each do |subproject|
......
159 167
    end
160 168
    update_attribute :status, STATUS_ARCHIVED
161 169
  end
162

  
170

  
163 171
  def unarchive
164 172
    return false if parent && !parent.active?
165 173
    update_attribute :status, STATUS_ACTIVE
166 174
  end
167

  
175

  
168 176
  def active_children
169 177
    children.select {|child| child.active?}
170 178
  end
171

  
179

  
172 180
  # Returns an array of the trackers used by the project and its sub projects
173 181
  def rolled_up_trackers
174 182
    @rolled_up_trackers ||=
......
177 185
                         :conditions => ["#{Project.table_name}.id = ? OR #{Project.table_name}.parent_id = ?", id, id],
178 186
                         :order => "#{Tracker.table_name}.position")
179 187
  end
180

  
188

  
181 189
  # Deletes all project's members
182 190
  def delete_all_members
183 191
    Member.delete_all(['project_id = ?', id])
184 192
  end
185

  
193

  
186 194
  # Users issues can be assigned to
187 195
  def assignable_users
188 196
    members.select {|m| m.role.assignable?}.collect {|m| m.user}.sort
189 197
  end
190

  
198

  
191 199
  # Returns the mail adresses of users that should be always notified on project events
192 200
  def recipients
193 201
    members.select {|m| m.mail_notification? || m.user.mail_notification?}.collect {|m| m.user.mail}
194 202
  end
195

  
203

  
196 204
  # Returns an array of all custom fields enabled for project issues
197 205
  # (explictly associated custom fields and custom fields enabled for all projects)
198 206
  def custom_fields_for_issues(tracker)
199 207
    all_custom_fields.select {|c| tracker.custom_fields.include? c }
200 208
  end
201

  
209

  
202 210
  def all_custom_fields
203 211
    @all_custom_fields ||= (IssueCustomField.for_all + custom_fields).uniq
204 212
  end
205

  
213

  
206 214
  def project
207 215
    self
208 216
  end
209

  
217

  
210 218
  def <=>(project)
211 219
    name.downcase <=> project.name.downcase
212 220
  end
213

  
221

  
214 222
  def to_s
215 223
    name
216 224
  end
217

  
225

  
218 226
  # Returns a short description of the projects (first lines)
219 227
  def short_description(length = 255)
220 228
    description.gsub(/^(.{#{length}}[^\n]*).*$/m, '\1').strip if description
221 229
  end
222

  
230

  
223 231
  def allows_to?(action)
224 232
    if action.is_a? Hash
225 233
      allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
......
227 235
      allowed_permissions.include? action
228 236
    end
229 237
  end
230

  
238

  
231 239
  def module_enabled?(module_name)
232 240
    module_name = module_name.to_s
233 241
    enabled_modules.detect {|m| m.name == module_name}
234 242
  end
235

  
243

  
236 244
  def enabled_module_names=(module_names)
237 245
    enabled_modules.clear
238 246
    module_names = [] unless module_names && module_names.is_a?(Array)
......
247 255
    errors.add_to_base("A project with subprojects can't be a subproject") if parent and children.size > 0
248 256
    errors.add(:identifier, :activerecord_error_invalid) if !identifier.blank? && identifier.match(/^\d*$/)
249 257
  end
250

  
258

  
251 259
private
252 260
  def allowed_permissions
253 261
    @allowed_permissions ||= begin
app/views/my/blocks/_projects.rhtml (revision 0)
1
<h3><%=l(:label_project_plural)%></h3>
2

  
3
<% myprojects = Project.find(:all,
4
                           :conditions => ["#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"]) %>
5
<% form_tag({}) do %>
6
    <table class="list projects">
7
        <thead><tr>
8
            <th align="left"><%=l(:field_project)%></th>
9
            <th align="center"><%=l(:label_assigned_issues)%></th>
10
            <th align="center"><%=l(:label_reported_issues)%></th>
11
            <th align="center"><%=l(:label_all_time)%></th>
12
        </tr></thead>
13
        <tbody>
14
            <% for project in myprojects %>
15
                <% if User.current.member_of?(project) %>
16
                    <tr class="<%= cycle('odd', 'even') %>" >
17
                    <td>
18
                    <%= link_to h(project.name), :controller => 'projects', :action => 'show', :id => project %>
19
                    </td><td align="center">
20
                    <% num_assigned = 0 %>
21
                    <% num_reported = 0 %>
22
                    <% spent_time = 0 %>
23
                    <% for issue in project.issues %>
24
                        <% if issue.assigned_to == User.current && !issue.status.is_closed %>
25
                            <% num_assigned = num_assigned + 1 %>
26
                        <% end %>
27
                        <% if issue.author == User.current && !issue.status.is_closed %>
28
                            <% num_reported = num_reported + 1 %>
29
                        <% end %>
30
                        <% for timeentry in issue.time_entries %>
31
                            <% if timeentry.user_id == User.current.id %>
32
                                <% spent_time = spent_time + timeentry.hours %>
33
                            <% end %>
34
                        <% end %>
35
                    <% end %>
36
                    <% if num_assigned > 0 %>
37
                        <%= link_to num_assigned, :controller => 'projects/' << project.identifier, :action => 'issues', :set_filter => 1, :assigned_to_id => 'me' %>
38
                    <% end %>
39
                    </td><td align="center">
40
                    <% if num_reported > 0 %>
41
                        <%= link_to num_reported, :controller => 'projects/' << project.identifier, :action => 'issues', :set_filter => 1, :author_id => 'me' %>
42
                    <% end %>
43
                    </td><td align="center">
44
                    <% if spent_time > 0 %>
45
                        <%= link_to spent_time, :controller => 'projects/' << project.identifier << '/timelog', :action => 'details' %>
46
                    <% end %>
47
                    </td></tr>
48
                <% end %>
49
            <% end %>
50
        </tbody>
51
    </table>
52
<% end %>
53

  
54
<% if myprojects.length > 0 %>
55
<p class="small"><%= link_to l(:label_project_all), :controller => 'projects', :action => 'index' %></p>
56
<% end %>
lang/en.yml (working copy)
278 278
label_login: Sign in
279 279
label_logout: Sign out
280 280
label_help: Help
281
label_reported_issues: Reported issues
281
label_reported_issues: Reported
282
label_assigned_issues: Assigned
282 283
label_assigned_to_me_issues: Issues assigned to me
283 284
label_last_login: Last connection
284 285
label_last_updates: Last updated
......
373 374
label_in_more_than: in more than
374 375
label_in: in
375 376
label_today: today
376
label_all_time: all time
377
label_all_time: Time
377 378
label_yesterday: yesterday
378 379
label_this_week: this week
379 380
label_last_week: last week
......
409 410
label_sort_lower: Move down
410 411
label_sort_lowest: Move to bottom
411 412
label_roadmap: Roadmap
412
label_roadmap_due_in: Due in
413
label_roadmap_due_in: Due in
413 414
label_roadmap_overdue: %s late
414 415
label_roadmap_no_issues: No issues for this version
415 416
label_search: Search
public/stylesheets/application.css (working copy)
57 57
#content { width: 80%; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; z-index: 10; height:600px; min-height: 600px;}
58 58
* html #content{ width: 80%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;}
59
html>body #content { height: auto; min-height: 600px; overflow: auto; }
59
html>body #content { height: auto; min-height: 600px; overflow: auto; }
60 60
#main.nosidebar #sidebar{ display: none; }
61 61
#main.nosidebar #content{ width: auto; border-right: 0; }
......
78 78
a.issue.closed, .issue.closed a { text-decoration: line-through; }
79 79
/***** Tables *****/
80
table.list { border: 1px solid #e4e4e4;  border-collapse: collapse; width: 100%; margin-bottom: 4px; }
80
table.list { border: 1px solid #e4e4e4;  border-collapse: collapse; width: 100%; margin-bottom: 4px;}
81 81
table.list th {  background-color:#EEEEEE; padding: 4px; white-space:nowrap; }
82 82
table.list td { vertical-align: top; }
83 83
table.list td.id { width: 2%; text-align: center;}
84 84
table.list td.checkbox { width: 15px; padding: 0px;}
85
table.list.issues { margin-top: 10px; }
86
table.projects { margin-top: 10px; }
85
table.list.issues { margin-top: 10px; }
86 87
tr.issue { text-align: center; white-space: nowrap; }
87 88
tr.issue td.subject, tr.issue td.category { white-space: normal; }
88 89
tr.issue td.subject { text-align: left; }
......
159 160
li p {margin-top: 0;}
160 161
div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;}
161 162
p.breadcrumb { font-size: 0.9em; margin: 4px 0 4px 0;}
162
p.subtitle { font-size: 0.9em; margin: -6px 0 12px 0; font-style: italic; }
163
p.subtitle { font-size: 0.9em; margin: -6px 0 12px 0; font-style: italic; }
163 164
fieldset#filters { padding: 0.7em; }
164 165
fieldset#filters p { margin: 1.2em 0 0.8em 2px; }
......
236 237
float: left;
237 238
text-align: right;
238 239
margin-left: -180px; /*width of left column*/
239
width: 175px; /*width of labels. Should be smaller than left column to create some right
240
width: 175px; /*width of labels. Should be smaller than left column to create some right
240 241
margin*/
241 242
}
......
272 273
#errorExplanation, div.flash, .nodata, .warning {
273 274
    padding: 4px 4px 4px 30px;
274 275
    margin-bottom: 12px;
275
   font-size: 1.1em;
276
   border: 2px solid;
276
    font-size: 1.1em;
277
    border: 2px solid;
277 278
}
278 279
div.flash {margin-top: 8px;}
279 280
div.flash.error, #errorExplanation {
280 281
    background: url(../images/false.png) 8px 5px no-repeat;
281
   background-color: #ffe3e3;
282
   border-color: #dd0000;
283
   color: #550000;
282
    background-color: #ffe3e3;
283
    border-color: #dd0000;
284
    color: #550000;
284 285
}
285 286
div.flash.notice {
286 287
    background: url(../images/true.png) 8px 5px no-repeat;
287
   background-color: #dfffdf;
288
   border-color: #9fcf9f;
289
   color: #005f00;
288
    background-color: #dfffdf;
289
    border-color: #9fcf9f;
290
    color: #005f00;
290 291
}
291 292
.nodata, .warning {
292 293
    text-align: center;
293
   background-color: #FFEBC1;
294
   border-color: #FDBF3B;
295
   color: #A6750C;
294
    background-color: #FFEBC1;
295
    border-color: #FDBF3B;
296
    color: #A6750C;
296 297
}
297 298
#errorExplanation ul { font-size: 0.9em;}
......
528 529
}
529 530
.task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
530
.task_done { background:#66f url(../images/task_done.png); border: 1px solid #66f; }
531
.task_done { background:#66f url(../images/task_done.png); border: 1px solid #66f; }
531 532
.task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
532 533
.milestone { background-image:url(../images/milestone.png); background-repeat: no-repeat; border: 0; }
    (1-1/1)