##// END OF EJS Templates
Subprojects are now grouped by projects in the 'Projects' top navigation drop-down menu....
Jean-Philippe Lang -
r692:a19a0d7b9278
parent child
Show More
@@ -0,0 +1,12
1 <div id="menuAllProjects" class="menu" onmouseover="menuMouseover(event)">
2 <%= link_to l(:label_project_all), {:controller => 'projects' }, :class => "menuItem" %>
3
4 <% user_projects_by_root = User.current.projects.find(:all, :include => :parent, :limit => 20).group_by(&:root) %>
5 <% user_projects_by_root.keys.sort.each do |root| %>
6 <%= link_to root.name, {:controller => 'projects', :action => 'show', :id => root}, :class => "menuItem" %>
7 <% user_projects_by_root[root].sort.each do |project| %>
8 <% next if project == root %>
9 <%= link_to(('&#187; ' + project.name), {:controller => 'projects', :action => 'show', :id => project}, :class => "menuItem") %>
10 <% end %>
11 <% end %>
12 </div>
@@ -1,125 +1,129
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class Project < ActiveRecord::Base
18 class Project < ActiveRecord::Base
19 # Project statuses
19 # Project statuses
20 STATUS_ACTIVE = 1
20 STATUS_ACTIVE = 1
21 STATUS_ARCHIVED = 9
21 STATUS_ARCHIVED = 9
22
22
23 has_many :members, :dependent => :delete_all, :include => :user, :conditions => "#{User.table_name}.status=#{User::STATUS_ACTIVE}"
23 has_many :members, :dependent => :delete_all, :include => :user, :conditions => "#{User.table_name}.status=#{User::STATUS_ACTIVE}"
24 has_many :users, :through => :members
24 has_many :users, :through => :members
25 has_many :custom_values, :dependent => :delete_all, :as => :customized
25 has_many :custom_values, :dependent => :delete_all, :as => :customized
26 has_many :issues, :dependent => :destroy, :order => "#{Issue.table_name}.created_on DESC", :include => [:status, :tracker]
26 has_many :issues, :dependent => :destroy, :order => "#{Issue.table_name}.created_on DESC", :include => [:status, :tracker]
27 has_many :issue_changes, :through => :issues, :source => :journals
27 has_many :issue_changes, :through => :issues, :source => :journals
28 has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC"
28 has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC"
29 has_many :time_entries, :dependent => :delete_all
29 has_many :time_entries, :dependent => :delete_all
30 has_many :queries, :dependent => :delete_all
30 has_many :queries, :dependent => :delete_all
31 has_many :documents, :dependent => :destroy
31 has_many :documents, :dependent => :destroy
32 has_many :news, :dependent => :delete_all, :include => :author
32 has_many :news, :dependent => :delete_all, :include => :author
33 has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name"
33 has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name"
34 has_many :boards, :order => "position ASC"
34 has_many :boards, :order => "position ASC"
35 has_one :repository, :dependent => :destroy
35 has_one :repository, :dependent => :destroy
36 has_one :wiki, :dependent => :destroy
36 has_one :wiki, :dependent => :destroy
37 has_and_belongs_to_many :custom_fields, :class_name => 'IssueCustomField', :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}", :association_foreign_key => 'custom_field_id'
37 has_and_belongs_to_many :custom_fields, :class_name => 'IssueCustomField', :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}", :association_foreign_key => 'custom_field_id'
38 acts_as_tree :order => "name", :counter_cache => true
38 acts_as_tree :order => "name", :counter_cache => true
39
39
40 attr_protected :status
40 attr_protected :status
41
41
42 validates_presence_of :name, :description, :identifier
42 validates_presence_of :name, :description, :identifier
43 validates_uniqueness_of :name, :identifier
43 validates_uniqueness_of :name, :identifier
44 validates_associated :custom_values, :on => :update
44 validates_associated :custom_values, :on => :update
45 validates_associated :repository, :wiki
45 validates_associated :repository, :wiki
46 validates_length_of :name, :maximum => 30
46 validates_length_of :name, :maximum => 30
47 validates_format_of :name, :with => /^[\w\s\'\-]*$/i
47 validates_format_of :name, :with => /^[\w\s\'\-]*$/i
48 validates_length_of :description, :maximum => 255
48 validates_length_of :description, :maximum => 255
49 validates_length_of :homepage, :maximum => 30
49 validates_length_of :homepage, :maximum => 30
50 validates_length_of :identifier, :in => 3..12
50 validates_length_of :identifier, :in => 3..12
51 validates_format_of :identifier, :with => /^[a-z0-9\-]*$/
51 validates_format_of :identifier, :with => /^[a-z0-9\-]*$/
52
52
53 def identifier=(identifier)
53 def identifier=(identifier)
54 super unless identifier_frozen?
54 super unless identifier_frozen?
55 end
55 end
56
56
57 def identifier_frozen?
57 def identifier_frozen?
58 errors[:identifier].nil? && !(new_record? || identifier.blank?)
58 errors[:identifier].nil? && !(new_record? || identifier.blank?)
59 end
59 end
60
60
61 def issues_with_subprojects(include_subprojects=false)
61 def issues_with_subprojects(include_subprojects=false)
62 conditions = nil
62 conditions = nil
63 if include_subprojects && !active_children.empty?
63 if include_subprojects && !active_children.empty?
64 ids = [id] + active_children.collect {|c| c.id}
64 ids = [id] + active_children.collect {|c| c.id}
65 conditions = ["#{Issue.table_name}.project_id IN (#{ids.join(',')})"]
65 conditions = ["#{Issue.table_name}.project_id IN (#{ids.join(',')})"]
66 end
66 end
67 conditions ||= ["#{Issue.table_name}.project_id = ?", id]
67 conditions ||= ["#{Issue.table_name}.project_id = ?", id]
68 Issue.with_scope :find => { :conditions => conditions } do
68 Issue.with_scope :find => { :conditions => conditions } do
69 yield
69 yield
70 end
70 end
71 end
71 end
72
72
73 # returns latest created projects
73 # returns latest created projects
74 # non public projects will be returned only if user is a member of those
74 # non public projects will be returned only if user is a member of those
75 def self.latest(user=nil, count=5)
75 def self.latest(user=nil, count=5)
76 find(:all, :limit => count, :conditions => visible_by(user), :order => "created_on DESC")
76 find(:all, :limit => count, :conditions => visible_by(user), :order => "created_on DESC")
77 end
77 end
78
78
79 def self.visible_by(user=nil)
79 def self.visible_by(user=nil)
80 if user && user.admin?
80 if user && user.admin?
81 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
81 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
82 elsif user && user.memberships.any?
82 elsif user && user.memberships.any?
83 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND (#{Project.table_name}.is_public = #{connection.quoted_true} or #{Project.table_name}.id IN (#{user.memberships.collect{|m| m.project_id}.join(',')}))"
83 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND (#{Project.table_name}.is_public = #{connection.quoted_true} or #{Project.table_name}.id IN (#{user.memberships.collect{|m| m.project_id}.join(',')}))"
84 else
84 else
85 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND #{Project.table_name}.is_public = #{connection.quoted_true}"
85 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND #{Project.table_name}.is_public = #{connection.quoted_true}"
86 end
86 end
87 end
87 end
88
88
89 def active?
89 def active?
90 self.status == STATUS_ACTIVE
90 self.status == STATUS_ACTIVE
91 end
91 end
92
92
93 def archive
93 def archive
94 # Archive subprojects if any
94 # Archive subprojects if any
95 children.each do |subproject|
95 children.each do |subproject|
96 subproject.archive
96 subproject.archive
97 end
97 end
98 update_attribute :status, STATUS_ARCHIVED
98 update_attribute :status, STATUS_ARCHIVED
99 end
99 end
100
100
101 def unarchive
101 def unarchive
102 return false if parent && !parent.active?
102 return false if parent && !parent.active?
103 update_attribute :status, STATUS_ACTIVE
103 update_attribute :status, STATUS_ACTIVE
104 end
104 end
105
105
106 def active_children
106 def active_children
107 children.select {|child| child.active?}
107 children.select {|child| child.active?}
108 end
108 end
109
109
110 # Returns an array of all custom fields enabled for project issues
110 # Returns an array of all custom fields enabled for project issues
111 # (explictly associated custom fields and custom fields enabled for all projects)
111 # (explictly associated custom fields and custom fields enabled for all projects)
112 def custom_fields_for_issues(tracker)
112 def custom_fields_for_issues(tracker)
113 all_custom_fields.select {|c| tracker.custom_fields.include? c }
113 all_custom_fields.select {|c| tracker.custom_fields.include? c }
114 end
114 end
115
115
116 def all_custom_fields
116 def all_custom_fields
117 @all_custom_fields ||= (IssueCustomField.for_all + custom_fields).uniq
117 @all_custom_fields ||= (IssueCustomField.for_all + custom_fields).uniq
118 end
118 end
119
119
120 def <=>(project)
121 name <=> project.name
122 end
123
120 protected
124 protected
121 def validate
125 def validate
122 errors.add(parent_id, " must be a root project") if parent and parent.parent
126 errors.add(parent_id, " must be a root project") if parent and parent.parent
123 errors.add_to_base("A project with subprojects can't be a subproject") if parent and children.size > 0
127 errors.add_to_base("A project with subprojects can't be a subproject") if parent and children.size > 0
124 end
128 end
125 end
129 end
@@ -1,111 +1,101
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
2 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
2 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
3 <head>
3 <head>
4 <title><%= Setting.app_title + (@html_title ? ": #{@html_title}" : "") %></title>
4 <title><%= Setting.app_title + (@html_title ? ": #{@html_title}" : "") %></title>
5 <meta http-equiv="content-type" content="text/html; charset=utf-8" />
5 <meta http-equiv="content-type" content="text/html; charset=utf-8" />
6 <meta name="description" content="<%= Redmine::Info.app_name %>" />
6 <meta name="description" content="<%= Redmine::Info.app_name %>" />
7 <meta name="keywords" content="issue,bug,tracker" />
7 <meta name="keywords" content="issue,bug,tracker" />
8 <!--[if IE]>
8 <!--[if IE]>
9 <style type="text/css">
9 <style type="text/css">
10 body {behavior: url(<%= stylesheet_path "csshover.htc" %>);}
10 body {behavior: url(<%= stylesheet_path "csshover.htc" %>);}
11 </style>
11 </style>
12 <![endif]-->
12 <![endif]-->
13 <%= stylesheet_link_tag "application" %>
13 <%= stylesheet_link_tag "application" %>
14 <%= stylesheet_link_tag "print", :media => "print" %>
14 <%= stylesheet_link_tag "print", :media => "print" %>
15 <%= javascript_include_tag :defaults %>
15 <%= javascript_include_tag :defaults %>
16 <%= javascript_include_tag 'menu' %>
16 <%= javascript_include_tag 'menu' %>
17 <%= stylesheet_link_tag 'jstoolbar' %>
17 <%= stylesheet_link_tag 'jstoolbar' %>
18 <!-- page specific tags --><%= yield :header_tags %>
18 <!-- page specific tags --><%= yield :header_tags %>
19 </head>
19 </head>
20
20
21 <body>
21 <body>
22 <div id="container" >
22 <div id="container" >
23
23
24 <div id="header">
24 <div id="header">
25 <div style="float: left;">
25 <div style="float: left;">
26 <h1><%= Setting.app_title %></h1>
26 <h1><%= Setting.app_title %></h1>
27 <h2><%= Setting.app_subtitle %></h2>
27 <h2><%= Setting.app_subtitle %></h2>
28 </div>
28 </div>
29 <div style="float: right; padding-right: 1em; padding-top: 0.2em;">
29 <div style="float: right; padding-right: 1em; padding-top: 0.2em;">
30 <% if User.current.logged? %><small><%=l(:label_logged_as)%> <strong><%= User.current.login %></strong> -</small><% end %>
30 <% if User.current.logged? %><small><%=l(:label_logged_as)%> <strong><%= User.current.login %></strong> -</small><% end %>
31 <small><%= toggle_link l(:label_search), 'quick-search-form', :focus => 'quick-search-input' %></small>
31 <small><%= toggle_link l(:label_search), 'quick-search-form', :focus => 'quick-search-input' %></small>
32 <% form_tag({:controller => 'search', :action => 'index', :id => @project}, :method => :get, :id => 'quick-search-form', :style => "display:none;" ) do %>
32 <% form_tag({:controller => 'search', :action => 'index', :id => @project}, :method => :get, :id => 'quick-search-form', :style => "display:none;" ) do %>
33 <%= text_field_tag 'q', @question, :size => 15, :class => 'small', :id => 'quick-search-input' %>
33 <%= text_field_tag 'q', @question, :size => 15, :class => 'small', :id => 'quick-search-input' %>
34 <% end %>
34 <% end %>
35 </div>
35 </div>
36 </div>
36 </div>
37
37
38 <div id="navigation">
38 <div id="navigation">
39 <ul>
39 <ul>
40 <li><%= link_to l(:label_home), { :controller => 'welcome' }, :class => "icon icon-home" %></li>
40 <li><%= link_to l(:label_home), { :controller => 'welcome' }, :class => "icon icon-home" %></li>
41 <li><%= link_to l(:label_my_page), { :controller => 'my', :action => 'page'}, :class => "icon icon-mypage" %></li>
41 <li><%= link_to l(:label_my_page), { :controller => 'my', :action => 'page'}, :class => "icon icon-mypage" %></li>
42
42
43 <% if User.current.memberships.any? %>
43 <% if User.current.memberships.any? %>
44 <li class="submenu"><%= link_to l(:label_project_plural), { :controller => 'projects' }, :class => "icon icon-projects", :onmouseover => "buttonMouseover(event, 'menuAllProjects');" %></li>
44 <li class="submenu"><%= link_to l(:label_project_plural), { :controller => 'projects' }, :class => "icon icon-projects", :onmouseover => "buttonMouseover(event, 'menuAllProjects');" %></li>
45 <% else %>
45 <% else %>
46 <li><%= link_to l(:label_project_plural), { :controller => 'projects' }, :class => "icon icon-projects" %></li>
46 <li><%= link_to l(:label_project_plural), { :controller => 'projects' }, :class => "icon icon-projects" %></li>
47 <% end %>
47 <% end %>
48
48
49 <% if User.current.logged? %>
49 <% if User.current.logged? %>
50 <li><%= link_to l(:label_my_account), { :controller => 'my', :action => 'account' }, :class => "icon icon-user" %></li>
50 <li><%= link_to l(:label_my_account), { :controller => 'my', :action => 'account' }, :class => "icon icon-user" %></li>
51 <% end %>
51 <% end %>
52
52
53 <% if User.current.admin? %>
53 <% if User.current.admin? %>
54 <li class="submenu"><%= link_to l(:label_administration), { :controller => 'admin' }, :class => "icon icon-admin", :onmouseover => "buttonMouseover(event, 'menuAdmin');" %></li>
54 <li class="submenu"><%= link_to l(:label_administration), { :controller => 'admin' }, :class => "icon icon-admin", :onmouseover => "buttonMouseover(event, 'menuAdmin');" %></li>
55 <% end %>
55 <% end %>
56
56
57 <li class="right"><%= link_to l(:label_help), { :controller => 'help', :ctrl => params[:controller], :page => params[:action] }, :onclick => "window.open(this.href); return false;", :class => "icon icon-help" %></li>
57 <li class="right"><%= link_to l(:label_help), { :controller => 'help', :ctrl => params[:controller], :page => params[:action] }, :onclick => "window.open(this.href); return false;", :class => "icon icon-help" %></li>
58
58
59 <% if User.current.logged? %>
59 <% if User.current.logged? %>
60 <li class="right"><%= link_to l(:label_logout), { :controller => 'account', :action => 'logout' }, :class => "icon icon-user" %></li>
60 <li class="right"><%= link_to l(:label_logout), { :controller => 'account', :action => 'logout' }, :class => "icon icon-user" %></li>
61 <% else %>
61 <% else %>
62 <li class="right"><%= link_to l(:label_login), { :controller => 'account', :action => 'login' }, :class => "icon icon-user" %></li>
62 <li class="right"><%= link_to l(:label_login), { :controller => 'account', :action => 'login' }, :class => "icon icon-user" %></li>
63 <% end %>
63 <% end %>
64 </ul>
64 </ul>
65 </div>
65 </div>
66
66
67 <% if User.current.admin? %>
67 <%= render(:partial => 'admin/menu') if User.current.admin? %>
68 <%= render :partial => 'admin/menu' %>
68 <%= render(:partial => 'layouts/projects_menu') if User.current.memberships.any? %>
69 <% end %>
70
71 <% if User.current.memberships.any? %>
72 <div id="menuAllProjects" class="menu" onmouseover="menuMouseover(event)">
73 <%= link_to l(:label_project_all), {:controller => 'projects' }, :class => "menuItem" %>
74 <% User.current.memberships.find(:all, :limit => 20).each do |membership| %>
75 <%= link_to membership.project.name, {:controller => 'projects',:action => 'show', :id => membership.project }, :class => "menuItem" %>
76 <% end %>
77 </div>
78 <% end %>
79
69
80 <div id="subcontent">
70 <div id="subcontent">
81 <% if @project && !@project.new_record? %>
71 <% if @project && !@project.new_record? %>
82 <h2><%= @project.name %></h2>
72 <h2><%= @project.name %></h2>
83 <ul class="menublock">
73 <ul class="menublock">
84 <% Redmine::MenuManager.allowed_items(:project_menu, current_role).each do |item| %>
74 <% Redmine::MenuManager.allowed_items(:project_menu, current_role).each do |item| %>
85 <% unless item.condition && !item.condition.call(@project) %>
75 <% unless item.condition && !item.condition.call(@project) %>
86 <li><%= link_to l(item.name), {item.param => @project}.merge(item.url) %></li>
76 <li><%= link_to l(item.name), {item.param => @project}.merge(item.url) %></li>
87 <% end %>
77 <% end %>
88 <% end %>
78 <% end %>
89 </ul>
79 </ul>
90 <% end %>
80 <% end %>
91 </div>
81 </div>
92
82
93 <div id="content">
83 <div id="content">
94 <div id="flash">
84 <div id="flash">
95 <%= content_tag('div', flash[:error], :class => 'error') if flash[:error] %>
85 <%= content_tag('div', flash[:error], :class => 'error') if flash[:error] %>
96 <%= content_tag('div', flash[:notice], :class => 'notice') if flash[:notice] %>
86 <%= content_tag('div', flash[:notice], :class => 'notice') if flash[:notice] %>
97 </div>
87 </div>
98 <%= yield %>
88 <%= yield %>
99 </div>
89 </div>
100
90
101 <div id="ajax-indicator" style="display:none;">
91 <div id="ajax-indicator" style="display:none;">
102 <span><%= l(:label_loading) %></span>
92 <span><%= l(:label_loading) %></span>
103 </div>
93 </div>
104
94
105 <div id="footer">
95 <div id="footer">
106 <p><%= link_to Redmine::Info.app_name, Redmine::Info.url %> <small><%= Redmine::VERSION %> &copy 2006-2007 Jean-Philippe Lang</small></p>
96 <p><%= link_to Redmine::Info.app_name, Redmine::Info.url %> <small><%= Redmine::VERSION %> &copy 2006-2007 Jean-Philippe Lang</small></p>
107 </div>
97 </div>
108
98
109 </div>
99 </div>
110 </body>
100 </body>
111 </html>
101 </html>
General Comments 0
You need to be logged in to leave comments. Login now