User groups feature initial commit.

git-svn-id: http://redmine.rubyforge.org/svn/branches/work@1373 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
Jean-Philippe Lang
2008-04-28 15:10:04 +00:00
parent 4783d3d7c5
commit ced3cab7bb
41 changed files with 812 additions and 65 deletions

View File

@@ -1,5 +1,5 @@
# redMine - project management software
# Copyright (C) 2006 Jean-Philippe Lang
# Copyright (C) 2006-2008 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -32,16 +32,18 @@ class CustomFieldsController < ApplicationController
def new
case params[:type]
when "IssueCustomField"
@custom_field = IssueCustomField.new(params[:custom_field])
@custom_field.trackers = Tracker.find(params[:tracker_ids]) if params[:tracker_ids]
when "UserCustomField"
@custom_field = UserCustomField.new(params[:custom_field])
when "ProjectCustomField"
@custom_field = ProjectCustomField.new(params[:custom_field])
else
redirect_to :action => 'list'
return
when "IssueCustomField"
@custom_field = IssueCustomField.new(params[:custom_field])
@custom_field.trackers = Tracker.find(params[:tracker_ids]) if params[:tracker_ids]
when "UserCustomField"
@custom_field = UserCustomField.new(params[:custom_field])
when "ProjectCustomField"
@custom_field = ProjectCustomField.new(params[:custom_field])
when "GroupCustomField"
@custom_field = GroupCustomField.new(params[:custom_field])
else
render_404
return
end
if request.post? and @custom_field.save
flash[:notice] = l(:notice_successful_create)

View File

@@ -0,0 +1,114 @@
# redMine - project management software
# Copyright (C) 2008 FreeCode
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class GroupsController < ApplicationController
layout 'base'
before_filter :require_admin
helper :custom_fields
# GET /groups
# GET /groups.xml
def index
@groups = Group.find(:all, :order => 'name')
@group = Group.new
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @groups }
end
end
# GET /groups/1
# GET /groups/1.xml
def show
@group = Group.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => @group }
end
end
# GET /groups/new
# GET /groups/new.xml
def new
@group = Group.new
@custom_values = GroupCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @group) }
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => @group }
end
end
# GET /groups/1/edit
def edit
@group = Group.find(params[:id])
@custom_values = GroupCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| @group.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x) }
end
# POST /groups
# POST /groups.xml
def create
@group = Group.new(params[:group])
@custom_values = GroupCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @group, :value => (params[:custom_fields] ? params["custom_fields"][x.id.to_s] : nil)) }
@group.custom_values = @custom_values
respond_to do |format|
if @group.save
flash[:notice] = l(:notice_successful_create)
format.html { redirect_to(groups_path) }
format.xml { render :xml => @group, :status => :created, :location => @group }
else
format.html { render :action => "new" }
format.xml { render :xml => @group.errors, :status => :unprocessable_entity }
end
end
end
# PUT /groups/1
# PUT /groups/1.xml
def update
@group = Group.find(params[:id])
@custom_values = GroupCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @group, :value => (params[:custom_fields] ? params["custom_fields"][x.id.to_s] : nil)) }
@group.custom_values = @custom_values
respond_to do |format|
if @group.update_attributes(params[:group])
flash[:notice] = l(:notice_successful_update)
format.html { redirect_to(groups_path) }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => @group.errors, :status => :unprocessable_entity }
end
end
end
# DELETE /groups/1
# DELETE /groups/1.xml
def destroy
@group = Group.find(params[:id])
@group.destroy
respond_to do |format|
format.html { redirect_to(groups_url) }
format.xml { head :ok }
end
end
end

View File

@@ -20,11 +20,17 @@ class MembersController < ApplicationController
before_filter :find_member, :except => :new
before_filter :find_project, :only => :new
before_filter :authorize
helper :projects
def new
@project.members << Member.new(params[:member]) if request.post?
member = Member.new(params[:member])
if params[:principal].to_s =~ %r{^(group|user)_(\d+)$}
member.principal_type, member.principal_id = $1.camelize, $2.to_i
end
@project.members << member if request.post?
respond_to do |format|
format.html { redirect_to :action => 'settings', :tab => 'members', :id => @project }
format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
format.js { render(:update) {|page| page.replace_html "tab-content-members", :partial => 'projects/settings/members'} }
end
end
@@ -54,7 +60,7 @@ private
end
def find_member
@member = Member.find(params[:id])
@member = Member.find(params[:id], :conditions => 'inherited_from IS NULL')
@project = @member.project
rescue ActiveRecord::RecordNotFound
render_404

View File

@@ -42,6 +42,7 @@ class UsersController < ApplicationController
per_page_option,
params['page']
@users = User.find :all,:order => sort_clause,
:include => :group,
:conditions => conditions,
:limit => @user_pages.items_per_page,
:offset => @user_pages.current.offset
@@ -58,6 +59,7 @@ class UsersController < ApplicationController
@user.admin = params[:user][:admin] || false
@user.login = params[:user][:login]
@user.password, @user.password_confirmation = params[:password], params[:password_confirmation] unless @user.auth_source_id
@user.group_id = params[:user][:group_id]
@custom_values = UserCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @user, :value => (params[:custom_fields] ? params["custom_fields"][x.id.to_s] : nil)) }
@user.custom_values = @custom_values
if @user.save
@@ -67,6 +69,7 @@ class UsersController < ApplicationController
end
end
@auth_sources = AuthSource.find(:all)
@groups = Group.find(:all, :order => 'name')
end
def edit
@@ -77,6 +80,7 @@ class UsersController < ApplicationController
@user.admin = params[:user][:admin] if params[:user][:admin]
@user.login = params[:user][:login] if params[:user][:login]
@user.password, @user.password_confirmation = params[:password], params[:password_confirmation] unless params[:password].nil? or params[:password].empty? or @user.auth_source_id
@user.group_id = params[:user][:group_id]
if params[:custom_fields]
@custom_values = UserCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @user, :value => params["custom_fields"][x.id.to_s]) }
@user.custom_values = @custom_values
@@ -88,6 +92,7 @@ class UsersController < ApplicationController
end
end
@auth_sources = AuthSource.find(:all)
@groups = Group.find(:all, :order => 'name')
@roles = Role.find_all_givable
@projects = Project.find(:all, :order => 'name', :conditions => "status=#{Project::STATUS_ACTIVE}") - @user.projects
@membership ||= Member.new
@@ -95,7 +100,7 @@ class UsersController < ApplicationController
def edit_membership
@user = User.find(params[:id])
@membership = params[:membership_id] ? Member.find(params[:membership_id]) : Member.new(:user => @user)
@membership = params[:membership_id] ? Member.find(params[:membership_id], :conditions => 'inherited_from IS NULL') : Member.new(:principal => @user)
@membership.attributes = params[:membership]
if request.post? and @membership.save
flash[:notice] = l(:notice_successful_update)
@@ -105,7 +110,7 @@ class UsersController < ApplicationController
def destroy_membership
@user = User.find(params[:id])
if request.post? and Member.find(params[:membership_id]).destroy
if request.post? and Member.find(params[:membership_id], :conditions => 'inherited_from IS NULL').destroy
flash[:notice] = l(:notice_successful_update)
end
redirect_to :action => 'edit', :id => @user and return

View File

@@ -20,7 +20,8 @@ module CustomFieldsHelper
def custom_fields_tabs
tabs = [{:name => 'IssueCustomField', :label => :label_issue_plural},
{:name => 'ProjectCustomField', :label => :label_project_plural},
{:name => 'UserCustomField', :label => :label_user_plural}
{:name => 'UserCustomField', :label => :label_user_plural},
{:name => 'GroupCustomField', :label => :label_group_plural}
]
end

View File

@@ -0,0 +1,19 @@
# redMine - project management software
# Copyright (C) 2008 FreeCode
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module GroupsHelper
end

View File

@@ -42,6 +42,19 @@ module ProjectsHelper
tabs.select {|tab| User.current.allowed_to?(tab[:action], @project)}
end
def principal_select_tag(groups, users)
options = ''
options << "<optgroup label=\"#{l(:label_group_plural)}\" class=\"groups\">" +
options_for_select(groups.collect {|g| [g.name, "group_#{g.id}"]}) +
"</optgroup>" unless groups.empty?
options << "<optgroup label=\"#{l(:label_user_plural)}\" class=\"users\">" +
options_for_select(users.collect {|u| [u.name, "user_#{u.id}"]}) +
"</optgroup>" unless users.empty?
select_tag 'principal', options
end
# Generates a gantt image
# Only defined if RMagick is avalaible
def gantt_image(events, date_from, months, zoom)

View File

@@ -0,0 +1,39 @@
# redMine - project management software
# Copyright (C) 2008 FreeCode
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class Group < ActiveRecord::Base
has_many :users, :dependent => :nullify
has_many :memberships, :class_name => 'Member', :as => :principal, :dependent => :destroy
has_many :members, :as => :principal,
:include => [ :project, :role ],
:conditions => "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}",
:order => "#{Project.table_name}.name"
has_many :custom_values, :dependent => :delete_all, :as => :customized
validates_presence_of :name
validates_uniqueness_of :name
validates_length_of :name, :maximum => 30
def <=>(group)
name <=> group.name
end
def to_s
name
end
end

View File

@@ -0,0 +1,22 @@
# redMine - project management software
# Copyright (C) 2008 FreeCode
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class GroupCustomField < CustomField
def type_name
:label_group_plural
end
end

View File

@@ -16,27 +16,52 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class Member < ActiveRecord::Base
belongs_to :user
belongs_to :role
belongs_to :project
belongs_to :role
belongs_to :principal, :polymorphic => true
belongs_to :user, :foreign_key => 'principal_id'
validates_presence_of :role, :user, :project
validates_uniqueness_of :user_id, :scope => :project_id
attr_protected :inherited_from
validates_presence_of :project, :role, :principal
validates_uniqueness_of :principal_id, :scope => [:project_id, :principal_type, :inherited_from]
def validate
errors.add :role_id, :activerecord_error_invalid if role && !role.member?
end
def name
self.user.name
principal.name
end
# Groups sorted by role then users sorted by role
def <=>(member)
role == member.role ? (user <=> member.user) : (role <=> member.role)
principal_type == member.principal_type ?
(role == member.role ? principal <=> member.principal : role <=> member.role) :
(principal_type <=> member.principal_type)
end
def to_s
principal.to_s
end
def after_save
# Update memberships based on group inheritance
if principal.is_a? Group
Member.delete_all "inherited_from = #{id}"
principal.users.each do |user|
Member.create! :project => project, :role => role, :principal => user, :inherited_from => id
end
end
end
def before_destroy
# remove category based auto assignments for this member
IssueCategory.update_all "assigned_to_id = NULL", ["project_id = ? AND assigned_to_id = ?", project.id, user.id]
# Remove inherited memberships
Member.delete_all "inherited_from = #{id}"
# Remove category based auto assignments for this member
if principal.is_a? User
IssueCategory.update_all "assigned_to_id = NULL", ["project_id = ? AND assigned_to_id = ?", project_id, principal_id]
end
end
end

View File

@@ -20,8 +20,9 @@ class Project < ActiveRecord::Base
STATUS_ACTIVE = 1
STATUS_ARCHIVED = 9
has_many :members, :include => :user, :conditions => "#{User.table_name}.status=#{User::STATUS_ACTIVE}"
has_many :users, :through => :members
has_many :members, :include => :user, :conditions => "#{Member.table_name}.principal_type='User' AND #{User.table_name}.status=#{User::STATUS_ACTIVE}"
has_many :memberships, :class_name => 'Member'
has_many :users, :through => :members, :uniq => true
has_many :custom_values, :dependent => :delete_all, :as => :customized
has_many :enabled_modules, :dependent => :delete_all
has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position"

View File

@@ -35,18 +35,25 @@ class User < ActiveRecord::Base
:username => '#{login}'
}
has_many :memberships, :class_name => 'Member', :include => [ :project, :role ], :conditions => "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}", :order => "#{Project.table_name}.name", :dependent => :delete_all
has_many :memberships, :class_name => 'Member',
:as => :principal,
:include => [ :project, :role ],
:conditions => "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}",
:order => "#{Project.table_name}.name, inherited_from ASC",
:dependent => :delete_all
has_many :projects, :through => :memberships
has_many :custom_values, :dependent => :delete_all, :as => :customized
has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
belongs_to :auth_source
belongs_to :group
attr_accessor :password, :password_confirmation
attr_accessor :last_before_login_on
# Prevents unauthorized assignments
attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
attr_protected :login, :admin, :password, :password_confirmation, :hashed_password, :group_id
validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }
@@ -71,6 +78,23 @@ class User < ActiveRecord::Base
# update hashed_password if password was set
self.hashed_password = User.hash_password(self.password) if self.password
end
def after_save
if @group_changed
# Update inherited memberships
Member.delete_all "principal_type = 'User' AND principal_id = #{id} AND inherited_from IS NOT NULL"
unless group.nil?
group.memberships.each do |m|
Member.create! :project => m.project, :role => m.role, :principal => self, :inherited_from => m.id
end
end
end
end
def group_id=(gid)
@group_changed = true unless gid == group_id
write_attribute(:group_id, gid)
end
def self.active
with_scope :find => { :conditions => [ "status = ?", STATUS_ACTIVE ] } do
@@ -167,8 +191,8 @@ class User < ActiveRecord::Base
end
def notified_project_ids=(ids)
Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
Member.update_all("mail_notification = #{connection.quoted_false}", ["principal_type = 'User' AND principal_id = ?", id])
Member.update_all("mail_notification = #{connection.quoted_true}", ["principal_type = 'User' AND principal_id = ? AND project_id IN (?)", id, ids]) if ids && !ids.empty?
@notified_projects_ids = nil
notified_projects_ids
end

View File

@@ -8,6 +8,11 @@
</p>
<p class="icon22 icon22-users">
<%= link_to l(:label_group_plural), :controller => 'groups' %> |
<%= link_to l(:label_new), :controller => 'groups', :action => 'new' %>
</p>
<p class="icon22 icon22-user">
<%= link_to l(:label_user_plural), :controller => 'users' %> |
<%= link_to l(:label_new), :controller => 'users', :action => 'add' %>
</p>

View File

@@ -99,12 +99,9 @@ when "IssueCustomField" %>
<p><%= f.check_box :is_filter %></p>
<p><%= f.check_box :searchable %></p>
<% when "UserCustomField" %>
<% else %>
<p><%= f.check_box :is_required %></p>
<% when "ProjectCustomField" %>
<p><%= f.check_box :is_required %></p>
<% end %>
</div>
<%= javascript_tag "toggle_custom_field_format();" %>

View File

@@ -0,0 +1,10 @@
<div class="box tabular">
<p><%= f.text_field :name %></p>
<% for @custom_value in @custom_values %>
<p><%= custom_field_tag_with_label @custom_value %></p>
<% end %>
<% unless @group.users.empty? %>
<p><label><%= l(:label_user_plural) %> (<%= @group.users.size %>)</label><%= @group.users.collect {|u| link_to_user u }.join(', ') %></p>
<% end %>
</div>

View File

@@ -0,0 +1,8 @@
<h2><%= l(:label_group) %></h2>
<%= error_messages_for :group %>
<% form_for(@group, :builder => TabularFormBuilder, :lang => current_language) do |f| %>
<%= render :partial => 'form', :locals => { :f => f } %>
<p><%= f.submit l(:button_save) %></p>
<% end %>

View File

@@ -0,0 +1,25 @@
<h2><%= l(:label_group_plural) %></h2>
<% if @groups.any? %>
<table class="list groups">
<thead><tr>
<th><%=l(:label_group)%></th>
<th><%=l(:label_user_plural)%></th>
<th></th>
</tr></thead>
<tbody>
<% @groups.each do |group| %>
<tr class="group <%= cycle 'odd', 'even' %>">
<td><%= link_to h(group.name), :action => 'edit', :id => group %></td>
<td align="center"><%= group.users.size %></td>
<td align="right"><%= link_to l(:button_delete), group, :confirm => l(:text_are_you_sure), :method => :delete, :class => 'icon icon-del' %></td>
</tr>
<% end %>
</table>
<% else %>
<p class="nodata"><%= l(:label_no_data) %></p>
<% end %>
<% form_for(@group) do |f| %>
<p><label><%= l(:label_group_new) %>: <%= f.text_field :name %> <%= f.submit l(:button_create) %></p>
<% end %>

View File

@@ -0,0 +1,8 @@
<h2><%= l(:label_group_new) %></h2>
<%= error_messages_for :group %>
<% form_for(@group, :builder => TabularFormBuilder, :lang => current_language) do |f| %>
<%= render :partial => 'form', :locals => { :f => f } %>
<p><%= f.submit l(:button_create) %></p>
<% end %>

View File

@@ -0,0 +1,7 @@
<h2><%=h @group %></h2>
<ul>
<% @group.users.each do |user| %>
<li><%=h user %></li>
<% end %>
</ul>

View File

@@ -1,22 +1,23 @@
<%= error_messages_for 'member' %>
<% roles = Role.find_all_givable %>
<% users = User.find_active(:all).sort - @project.users %>
<% users = User.find_active(:all).sort - @project.users.find(:all, :conditions => 'inherited_from IS NULL') %>
<% groups = Group.find(:all, :order => 'name') - @project.memberships.collect(&:principal) %>
<% # members sorted by role position
members = @project.members.find(:all, :include => [:role, :user]).sort %>
members = @project.memberships.select {|m| m.inherited_from.nil? }.sort #members.find(:all, :include => [:role, :user]).sort %>
<% if members.any? %>
<table class="list">
<table class="list members">
<thead>
<th><%= l(:label_user) %></th>
<th><%= l(:label_member) %></th>
<th><%= l(:label_role) %></th>
<th style="width:15%"></th>
</thead>
<tbody>
<% members.each do |member| %>
<% next if member.new_record? %>
<tr class="<%= cycle 'odd', 'even' %>">
<td><%= member.name %></td>
<td align="center">
<tr class="<%= cycle 'odd', 'even' %> <%= member.principal.class.name.downcase %>">
<td class="name"><%=h member %></td>
<td class="role">
<% if authorize_for('members', 'edit') %>
<% remote_form_for(:member, member, :url => {:controller => 'members', :action => 'edit', :id => member}, :method => :post) do |f| %>
<%= f.select :role_id, roles.collect{|role| [role.name, role.id]}, {}, :class => "small" %>
@@ -38,10 +39,10 @@
<p class="nodata"><%= l(:label_no_data) %></p>
<% end %>
<% if authorize_for('members', 'new') && !users.empty? %>
<% if authorize_for('members', 'new') && !(users.empty? && groups.empty?) %>
<p><%=l(:label_member_new)%></p>
<% remote_form_for(:member, @member, :url => {:controller => 'members', :action => 'new', :id => @project}, :method => :post) do |f| %>
<p><label for="member_user_id"><%=l(:label_member_new)%></label><br />
<%= f.select :user_id, users.collect{|user| [user.name, user.id]} %>
<p><%= principal_select_tag(groups, users) %>
<%= l(:label_role) %>: <%= f.select :role_id, roles.collect{|role| [role.name, role.id]}, :selected => nil %>
<%= submit_tag l(:button_add) %></p>
<% end %>

View File

@@ -7,6 +7,9 @@
<p><%= f.text_field :firstname, :required => true %></p>
<p><%= f.text_field :lastname, :required => true %></p>
<p><%= f.text_field :mail, :required => true %></p>
<% unless @groups.empty? -%>
<p><%= f.select :group_id, @groups.collect {|g| [g.name, g.id]}, { :include_blank => true } %></p>
<% end -%>
<p><%= f.select :language, lang_options_for_select %></p>
<% for @custom_value in @custom_values %>

View File

@@ -1,7 +1,7 @@
<div class="box" style="margin-top: 16px;">
<h3><%= l(:label_project_plural) %></h3>
<% @user.memberships.each do |membership| %>
<% @user.memberships.select {|m| m.inherited_from.nil? }.each do |membership| %>
<% form_tag({ :action => 'edit_membership', :id => @user, :membership_id => membership }, :class => "tabular") do %>
<p style="margin:0;padding-top:0;">
<label><%= membership.project.name %></label>
@@ -13,6 +13,8 @@
</p>
<% end %>
<% end %>
<% unless @projects.empty? || @roles.empty? %>
<hr />
<p>
<label><%=l(:label_project_new)%></label><br/>
@@ -20,10 +22,12 @@
<select name="membership[project_id]">
<%= options_from_collection_for_select @projects, "id", "name", @membership.project_id %>
</select>
<%= l(:label_role) %>:
<select name="membership[role_id]">
<%= options_from_collection_for_select @roles, "id", "name", @membership.role_id %>
</select>
<%= submit_tag l(:button_add) %>
<% end %>
</p>
<% end %>
</div>

View File

@@ -18,6 +18,7 @@
<%= sort_header_tag('firstname', :caption => l(:field_firstname)) %>
<%= sort_header_tag('lastname', :caption => l(:field_lastname)) %>
<%= sort_header_tag('mail', :caption => l(:field_mail)) %>
<%= sort_header_tag("#{Group.table_name}.name", :caption => l(:field_group)) %>
<%= sort_header_tag('admin', :caption => l(:field_admin), :default_order => 'desc') %>
<%= sort_header_tag('created_on', :caption => l(:field_created_on), :default_order => 'desc') %>
<%= sort_header_tag('last_login_on', :caption => l(:field_last_login_on), :default_order => 'desc') %>
@@ -27,9 +28,10 @@
<% for user in @users -%>
<tr class="user <%= cycle("odd", "even") %> <%= %w(anon active registered locked)[user.status] %>">
<td class="username"><%= link_to user.login, :action => 'edit', :id => user %></td>
<td class="firstname"><%= user.firstname %></td>
<td class="lastname"><%= user.lastname %></td>
<td class="email"><%= user.mail %></td>
<td class="firstname"><%=h user.firstname %></td>
<td class="lastname"><%=h user.lastname %></td>
<td class="email"><%=h user.mail %></td>
<td class="group"><%=h user.group %></td>
<td align="center"><%= image_tag('true.png') if user.admin? %></td>
<td class="created_on" align="center"><%= format_time(user.created_on) %></td>
<td class="last_login_on" align="center"><%= format_time(user.last_login_on) unless user.last_login_on.nil? %></td>