Commit 362633d4 authored by Jean-Philippe Lang's avatar Jean-Philippe Lang

- new controller "myController"

- account/my_page moved to my/page
- account/my_account mmoved to my/account
- "my page" is now customizable (table user_preferences added)

git-svn-id: http://redmine.rubyforge.org/svn/trunk@62 e93f8b46-1217-0410-a6f0-8f06a7374b81
parent 120e39ba
......@@ -40,7 +40,7 @@ class AccountController < ApplicationController
user = User.try_to_login(params[:login], params[:password])
if user
self.logged_in_user = user
redirect_back_or_default :controller => 'account', :action => 'my_page'
redirect_back_or_default :controller => 'my', :action => 'page'
else
flash.now[:notice] = l(:notice_account_invalid_creditentials)
end
......@@ -52,41 +52,6 @@ class AccountController < ApplicationController
self.logged_in_user = nil
redirect_to :controller => ''
end
# Show logged in user's page
def my_page
@user = self.logged_in_user
@reported_issues = Issue.find(:all, :conditions => ["author_id=?", @user.id], :limit => 10, :include => [ :status, :project, :tracker ], :order => 'issues.updated_on DESC')
@assigned_issues = Issue.find(:all, :conditions => ["assigned_to_id=?", @user.id], :limit => 10, :include => [ :status, :project, :tracker ], :order => 'issues.updated_on DESC')
end
# Edit logged in user's account
def my_account
@user = self.logged_in_user
if request.post? and @user.update_attributes(@params[:user])
set_localization
flash.now[:notice] = l(:notice_account_updated)
self.logged_in_user.reload
end
end
# Change logged in user's password
def change_password
@user = self.logged_in_user
flash[:notice] = l(:notice_can_t_change_password) and redirect_to :action => 'my_account' and return if @user.auth_source_id
if @user.check_password?(@params[:password])
@user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
if @user.save
flash[:notice] = l(:notice_account_password_updated)
else
render :action => 'my_account'
return
end
else
flash[:notice] = l(:notice_account_wrong_password)
end
redirect_to :action => 'my_account'
end
# Enable user to choose a new password
def lost_password
......
# redMine - project management software
# Copyright (C) 2006 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
# 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 MyController < ApplicationController
layout 'base'
before_filter :require_login
BLOCKS = { 'issues_assigned_to_me' => :label_assigned_to_me_issues,
'issues_reported_by_me' => :label_reported_issues,
'latest_news' => :label_news_latest,
'calendar' => :label_calendar,
'documents' => :label_document_plural
}.freeze
verify :xhr => true,
:session => :page_layout,
:only => [:add_block, :remove_block, :order_blocks]
def index
page
render :action => 'page'
end
# Show user's page
def page
@user = self.logged_in_user
@blocks = @user.pref[:my_page_layout] || { 'left' => ['issues_assigned_to_me'], 'right' => ['issues_reported_by_me'] }
end
# Edit user's account
def account
@user = self.logged_in_user
if request.post? and @user.update_attributes(@params[:user])
set_localization
flash.now[:notice] = l(:notice_account_updated)
self.logged_in_user.reload
end
end
# Change user's password
def change_password
@user = self.logged_in_user
flash[:notice] = l(:notice_can_t_change_password) and redirect_to :action => 'account' and return if @user.auth_source_id
if @user.check_password?(@params[:password])
@user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
if @user.save
flash[:notice] = l(:notice_account_password_updated)
else
render :action => 'account'
return
end
else
flash[:notice] = l(:notice_account_wrong_password)
end
redirect_to :action => 'account'
end
# User's page layout configuration
def page_layout
@user = self.logged_in_user
@blocks = @user.pref[:my_page_layout] || { 'left' => ['issues_assigned_to_me'], 'right' => ['issues_reported_by_me'] }
session[:page_layout] = @blocks
%w(top left right).each {|f| session[:page_layout][f] ||= [] }
@block_options = []
BLOCKS.each {|k, v| @block_options << [l(v), k]}
end
# Add a block to user's page
# The block is added on top of the page
# params[:block] : id of the block to add
def add_block
@user = self.logged_in_user
block = params[:block]
# remove if already present in a group
%w(top left right).each {|f| (session[:page_layout][f] ||= []).delete block }
# add it on top
session[:page_layout]['top'].unshift block
render :partial => "block", :locals => {:user => @user, :block_name => block}
end
# Remove a block to user's page
# params[:block] : id of the block to remove
def remove_block
block = params[:block]
# remove block in all groups
%w(top left right).each {|f| (session[:page_layout][f] ||= []).delete block }
render :nothing => true
end
# Change blocks order on user's page
# params[:group] : group to order (top, left or right)
# params[:list-(top|left|right)] : array of block ids of the group
def order_blocks
group = params[:group]
group_items = params["list-#{group}"]
if group_items and group_items.is_a? Array
# remove group blocks if they are presents in other groups
%w(top left right).each {|f|
session[:page_layout][f] = (session[:page_layout][f] || []) - group_items
}
session[:page_layout][group] = group_items
end
render :nothing => true
end
# Save user's page layout
def page_layout_save
@user = self.logged_in_user
@user.pref[:my_page_layout] = session[:page_layout] if session[:page_layout]
@user.pref.save
session[:page_layout] = nil
redirect_to :action => 'page'
end
end
# redMine - project management software
# Copyright (C) 2006 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
# 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 MyHelper
end
......@@ -19,7 +19,9 @@ require "digest/sha1"
class User < ActiveRecord::Base
has_many :memberships, :class_name => 'Member', :include => [ :project, :role ], :dependent => true
has_many :projects, :through => :memberships
has_many :custom_values, :dependent => true, :as => :customized
has_one :preference, :dependent => true, :class_name => 'UserPreference'
belongs_to :auth_source
attr_accessor :password, :password_confirmation
......@@ -114,6 +116,10 @@ class User < ActiveRecord::Base
end
@role_for_projects[project_id]
end
def pref
self.preference ||= UserPreference.new(:user => self)
end
private
# Return password digest
......
# redMine - project management software
# Copyright (C) 2006 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
# 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 UserPreference < ActiveRecord::Base
belongs_to :user
serialize :others, Hash
attr_protected :others
def initialize(attributes = nil)
super
self.others ||= {}
end
def [](attr_name)
if attribute_present? attr_name
super
else
others[attr_name]
end
end
def []=(attr_name, value)
if attribute_present? attr_name
super
else
others.store attr_name, value
end
end
end
<h2><%=l(:label_my_page)%></h2>
<p>
<% unless @user.last_before_login_on.nil? %>
<%=l(:label_last_login)%>: <%= format_time(@user.last_before_login_on) %>
<% end %>
</p>
<div class="splitcontentleft">
<h3><%=l(:label_reported_issues)%></h3>
<%= render :partial => 'issues/list_simple', :locals => { :issues => @reported_issues } %>
<% if @reported_issues.length > 0 %>
<p><%=lwr(:label_last_updates, @reported_issues.length)%></p>
<% end %>
</div>
<div class="splitcontentright">
<h3><%=l(:label_assigned_to_me_issues)%></h3>
<%= render :partial => 'issues/list_simple', :locals => { :issues => @assigned_issues } %>
<% if @assigned_issues.length > 0 %>
<p><%=lwr(:label_last_updates, @assigned_issues.length)%></p>
<% end %>
</div>
\ No newline at end of file
......@@ -70,7 +70,7 @@ var menu_contenu=' \
<div id="navigation">
<ul>
<li class="selected"><%= link_to l(:label_home), { :controller => '' }, :class => "picHome" %></li>
<li><%= link_to l(:label_my_page), { :controller => 'account', :action => 'my_page'}, :class => "picUserPage" %></li>
<li><%= link_to l(:label_my_page), { :controller => 'my', :action => 'page'}, :class => "picUserPage" %></li>
<li><%= link_to l(:label_project_plural), { :controller => 'projects' }, :class => "picProject" %></li>
<% unless @project.nil? || @project.id.nil? %>
......@@ -78,7 +78,7 @@ var menu_contenu=' \
<% end %>
<% if loggedin? %>
<li><%= link_to l(:label_my_account), { :controller => 'account', :action => 'my_account' }, :class => "picUser" %></li>
<li><%= link_to l(:label_my_account), { :controller => 'my', :action => 'account' }, :class => "picUser" %></li>
<% end %>
<% if admin_loggedin? %>
......
<div id="block_<%= block_name %>" class="mypage-box">
<div style="float:right;margin-right:16px;z-index:500;">
<%= link_to_remote "", {
:url => { :action => "remove_block", :block => block_name },
:complete => "removeBlock('block_#{block_name}')",
:loading => "Element.show('indicator')",
:loaded => "Element.hide('indicator')" },
:class => "close-icon"
%>
</div>
<div class="handle">
<%= render :partial => "my/blocks/#{block_name}", :locals => { :user => user } %>
</div>
</div>
\ No newline at end of file
......@@ -9,7 +9,7 @@
<div class="box">
<h3><%=l(:label_information_plural)%></h3>
<%= start_form_tag({:action => 'my_account'}, :class => "tabular") %>
<%= start_form_tag({:action => 'account'}, :class => "tabular") %>
<!--[form:user]-->
<p><label for="user_firstname"><%=l(:field_firstname)%> <span class="required">*</span></label>
......
<h3><%= l(:label_calendar) %></h3>
<%
@date_from = Date.today - (Date.today.cwday-1)
@date_to = Date.today + (7-Date.today.cwday)
@issues = Issue.find :all,
:conditions => ["issues.project_id in (#{@user.projects.collect{|m| m.id}.join(',')}) AND ((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?))", @date_from, @date_to, @date_from, @date_to],
:include => [:project, :tracker] unless @user.projects.empty?
@issues ||= []
%>
<table class="calenderTable">
<tr class="ListHead">
<td></td>
<% 1.upto(7) do |d| %>
<td align="center" width="14%"><%= day_name(d) %></td>
<% end %>
</tr>
<tr height="100">
<% day = @date_from
while day <= @date_to
if day.cwday == 1 %>
<td valign="middle"><%= day.cweek %></td>
<% end %>
<td valign="top" width="14%" class="<%= day.month==@month ? "even" : "odd" %>">
<p align="right"><%= day==Date.today ? "<b>#{day.day}</b>" : day.day %></p>
<% day_issues = []
@issues.each { |i| day_issues << i if i.start_date == day or i.due_date == day }
day_issues.each do |i| %>
<%= if day == i.start_date and day == i.due_date
image_tag('arrow_bw')
elsif day == i.start_date
image_tag('arrow_from')
elsif day == i.due_date
image_tag('arrow_to')
end %>
<small><%= link_to "#{i.tracker.name} ##{i.id}", :controller => 'issues', :action => 'show', :id => i %>: <%= i.subject.sub(/^(.{30}[^\s]*\s).*$/, '\1 (...)') %></small><br />
<% end %>
</td>
<%= '</tr><tr height="100">' if day.cwday >= 7 and day!=@date_to %>
<%
day = day + 1
end %>
</tr>
</table>
\ No newline at end of file
<h3><%=l(:label_document_plural)%></h3>
<ul>
<% for document in Document.find :all,
:limit => 10,
:conditions => "documents.project_id in (#{@user.projects.collect{|m| m.id}.join(',')})",
:include => [:project] %>
<li>
<b><%= link_to document.title, :controller => 'documents', :action => 'show', :id => document %></b>
<br />
<%= truncate document.description, 150 %><br />
<em><%= format_time(document.created_on) %></em><br />&nbsp;
</li>
<% end unless @user.projects.empty? %>
</ul>
\ No newline at end of file
<h3><%=l(:label_assigned_to_me_issues)%></h3>
<% assigned_issues = Issue.find(:all,
:conditions => ["assigned_to_id=?", user.id],
:limit => 10,
:include => [ :status, :project, :tracker ],
:order => 'issues.updated_on DESC') %>
<%= render :partial => 'issues/list_simple', :locals => { :issues => assigned_issues } %>
<% if assigned_issues.length > 0 %>
<p><%=lwr(:label_last_updates, assigned_issues.length)%></p>
<% end %>
<h3><%=l(:label_reported_issues)%></h3>
<% reported_issues = Issue.find(:all,
:conditions => ["author_id=?", user.id],
:limit => 10,
:include => [ :status, :project, :tracker ],
:order => 'issues.updated_on DESC') %>
<%= render :partial => 'issues/list_simple', :locals => { :issues => reported_issues } %>
<% if reported_issues.length > 0 %>
<p><%=lwr(:label_last_updates, reported_issues.length)%></p>
<% end %>
\ No newline at end of file
<h3><%=l(:label_news_latest)%></h3>
<ul>
<% for news in News.find :all,
:limit => 10,
:conditions => "news.project_id in (#{@user.projects.collect{|m| m.id}.join(',')})",
:include => [:project, :author] %>
<li><%= link_to news.title, :controller => 'news', :action => 'show', :id => news %><br />
<% unless news.summary.empty? %><%= news.summary %><br /><% end %>
<em><%= news.author.name %>, <%= format_time(news.created_on) %></em><br />&nbsp;
</li>
<% end unless @user.projects.empty? %>
</ul>
\ No newline at end of file
<h2><%=l(:label_my_page)%></h2>
<div class="topright">
<small><%= link_to l(:label_personalize_page), :action => 'page_layout' %></small>
</div>
<div id="list-top">
<% @blocks['top'].each do |b| %>
<div class="mypage-box">
<%= render :partial => "my/blocks/#{b}", :locals => { :user => @user } %>
</div>
<% end if @blocks['top'] %>
</div>
<div id="list-left" class="splitcontentleft">
<% @blocks['left'].each do |b| %>
<div class="mypage-box">
<%= render :partial => "my/blocks/#{b}", :locals => { :user => @user } %>
</div>
<% end if @blocks['left'] %>
</div>
<div id="list-right" class="splitcontentright">
<% @blocks['right'].each do |b| %>
<div class="mypage-box">
<%= render :partial => "my/blocks/#{b}", :locals => { :user => @user } %>
</div>
<% end if @blocks['right'] %>
</div>
<script language="JavaScript">
function recreateSortables() {
Sortable.destroy('list-top');
Sortable.destroy('list-left');
Sortable.destroy('list-right');
Sortable.create("list-top", {constraint:false, containment:['list-top','list-left','list-right'], dropOnEmpty:true, handle:'handle', onUpdate:function(){new Ajax.Request('/my/order_blocks?group=top', {asynchronous:true, evalScripts:true, onComplete:function(request){new Effect.Highlight("list-top",{});}, onLoaded:function(request){Element.hide('indicator')}, onLoading:function(request){Element.show('indicator')}, parameters:Sortable.serialize("list-top")})}, only:'mypage-box', tag:'div'})
Sortable.create("list-left", {constraint:false, containment:['list-top','list-left','list-right'], dropOnEmpty:true, handle:'handle', onUpdate:function(){new Ajax.Request('/my/order_blocks?group=left', {asynchronous:true, evalScripts:true, onComplete:function(request){new Effect.Highlight("list-left",{});}, onLoaded:function(request){Element.hide('indicator')}, onLoading:function(request){Element.show('indicator')}, parameters:Sortable.serialize("list-left")})}, only:'mypage-box', tag:'div'})
Sortable.create("list-right", {constraint:false, containment:['list-top','list-left','list-right'], dropOnEmpty:true, handle:'handle', onUpdate:function(){new Ajax.Request('/my/order_blocks?group=right', {asynchronous:true, evalScripts:true, onComplete:function(request){new Effect.Highlight("list-right",{});}, onLoaded:function(request){Element.hide('indicator')}, onLoading:function(request){Element.show('indicator')}, parameters:Sortable.serialize("list-right")})}, only:'mypage-box', tag:'div'})
}
function updateSelect() {
s = $('block-select')
for (var i = 0; i < s.options.length; i++) {
if ($('block_' + s.options[i].value)) {
s.options[i].disabled = true;
} else {
s.options[i].disabled = false;
}
}
s.options[0].selected = true;
}
function afterAddBlock() {
recreateSortables();
updateSelect();
}
function removeBlock(block) {
$(block).parentNode.removeChild($(block));
updateSelect();
}
</script>
<div style="float:right;">
<%= start_form_tag({:action => "add_block"}, :id => "block-form") %>
<%= select_tag 'block', "<option></option>" + options_for_select(@block_options), :id => "block-select", :class => "select-small" %>
<small>
<%= link_to_remote l(:button_add),
:url => { :action => "add_block" },
:with => "Form.serialize('block-form')",
:update => "list-top",
:position => :top,
:complete => "afterAddBlock();",
:loading => "Element.show('indicator')",
:loaded => "Element.hide('indicator')"
%>
</small>
<%= end_form_tag %>
<small>|
<%= link_to l(:button_save), :action => 'page_layout_save' %> |
<%= link_to l(:button_cancel), :action => 'page' %>
</small>
</div>
<div style="float:right;margin-right:20px;">
<span id="indicator" style="display:none"><%= image_tag "loading.gif" %></span>
</div>
<h2><%=l(:label_my_page)%></h2>
<div id="list-top" class="block-receiver">
<% @blocks['top'].each do |b| %>
<%= render :partial => 'block', :locals => {:user => @user, :block_name => b} %>
<% end if @blocks['top'] %>
</div>
<div id="list-left" class="splitcontentleft block-receiver">
<% @blocks['left'].each do |b| %>
<%= render :partial => 'block', :locals => {:user => @user, :block_name => b} %>
<% end if @blocks['left'] %>
</div>
<div id="list-right" class="splitcontentright block-receiver">
<% @blocks['right'].each do |b| %>
<%= render :partial => 'block', :locals => {:user => @user, :block_name => b} %>
<% end if @blocks['right'] %>
</div>
<%= sortable_element 'list-top',
:tag => 'div',
:only => 'mypage-box',
:handle => "handle",
:dropOnEmpty => true,
:containment => ['list-top', 'list-left', 'list-right'],
:constraint => false,
:complete => visual_effect(:highlight, 'list-top'),
:url => { :action => "order_blocks", :group => "top" },
:loading => "Element.show('indicator')",
:loaded => "Element.hide('indicator')"
%>
<%= sortable_element 'list-left',
:tag => 'div',
:only => 'mypage-box',
:handle => "handle",
:dropOnEmpty => true,
:containment => ['list-top', 'list-left', 'list-right'],
:constraint => false,
:complete => visual_effect(:highlight, 'list-left'),
:url => { :action => "order_blocks", :group => "left" },
:loading => "Element.show('indicator')",
:loaded => "Element.hide('indicator')" %>
<%= sortable_element 'list-right',
:tag => 'div',
:only => 'mypage-box',
:handle => "handle",
:dropOnEmpty => true,
:containment => ['list-top', 'list-left', 'list-right'],
:constraint => false,
:complete => visual_effect(:highlight, 'list-right'),
:url => { :action => "order_blocks", :group => "right" },
:loading => "Element.show('indicator')",
:loaded => "Element.hide('indicator')" %>
<%= javascript_tag "updateSelect()" %>
\ No newline at end of file
class CreateUserPreferences < ActiveRecord::Migration
def self.up
create_table :user_preferences do |t|
t.column "user_id", :integer, :default => 0, :null => false
t.column "others", :text
end
end
def self.down
drop_table :user_preferences
end
end
......@@ -7,6 +7,7 @@ http://redmine.org/
== xx/xx/2006 v0.x.x
* "my page" is now customizable
* improved issues change history
* new functionality: move an issue to another project or tracker
* new functionality: add a note to an issue
......
......@@ -257,6 +257,7 @@ label_gantt_chart: Gantt Diagramm
label_internal: Intern
label_last_changes: %d änderungen des Letzten
label_change_view_all: Alle änderungen ansehen
label_personalize_page: Diese Seite personifizieren
button_login: Einloggen
button_submit: Einreichen
......@@ -278,6 +279,7 @@ button_list: Aufzulisten
button_view: Siehe
button_move: Bewegen
button_back: Rückkehr
button_cancel: Annullieren
text_select_mail_notifications: Aktionen für die Mailbenachrichtigung aktiviert werden soll.
text_regexp_info: eg. ^[A-Z0-9]+$
......
......@@ -257,6 +257,7 @@ label_gantt_chart: Gantt chart
label_internal: Internal
label_last_changes: last %d changes
label_change_view_all: View all changes
label_personalize_page: Personalize this page
button_login: Login
button_submit: Submit
......@@ -278,6 +279,7 @@ button_list: List
button_view: View
button_move: Move
button_back: Back
button_cancel: Cancel
text_select_mail_notifications: Select actions for which mail notifications should be sent.
text_regexp_info: eg. ^[A-Z0-9]+$
......
......@@ -257,6 +257,7 @@ label_gantt_chart: Diagrama de Gantt
label_internal: Interno
label_last_changes: %d cambios del último
label_change_view_all: Ver todos los cambios
label_personalize_page: Personalizar esta página
button_login: Conexión
button_submit: Someter
......@@ -278,6 +279,7 @@ button_list: Listar
button_view: Ver
button_move: Mover
button_back: Atrás
button_cancel: Cancelar
text_select_mail_notifications: Seleccionar las actividades que necesitan la activación de la notificación por mail.
text_regexp_info: eg. ^[A-Z0-9]+$
......
......@@ -258,10 +258,11 @@ label_gantt_chart: Diagramme de Gantt
label_internal: Interne
label_last_changes: %d derniers changements
label_change_view_all: Voir tous les changements
label_personalize_page: Personnaliser cette page
button_login: Connexion
button_submit: Soumettre
button_save: Valider
button_save: Sauvegarder
button_check_all: Tout cocher
button_uncheck_all: Tout décocher
button_delete: Supprimer
......@@ -279,6 +280,7 @@ button_list: Lister
button_view: Voir
button_move: Déplacer
button_back: Retour
button_cancel: Annuler
text_select_mail_notifications: Sélectionner les actions pour lesquelles la notification par mail doit être activée.
text_regexp_info: ex. ^[A-Z0-9]+$
......
......@@ -379,6 +379,22 @@ color:#505050;
line-height