##// END OF EJS Templates
Added several validates_length_of...
Jean-Philippe Lang -
r590:fcefdb22bfc3
parent child
Show More
@@ -1,96 +1,98
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 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 require "digest/md5"
18 require "digest/md5"
19
19
20 class Attachment < ActiveRecord::Base
20 class Attachment < ActiveRecord::Base
21 belongs_to :container, :polymorphic => true
21 belongs_to :container, :polymorphic => true
22 belongs_to :author, :class_name => "User", :foreign_key => "author_id"
22 belongs_to :author, :class_name => "User", :foreign_key => "author_id"
23
23
24 validates_presence_of :container, :filename
24 validates_presence_of :container, :filename
25
25 validates_length_of :filename, :maximum => 255
26 validates_length_of :disk_filename, :maximum => 255
27
26 cattr_accessor :storage_path
28 cattr_accessor :storage_path
27 @@storage_path = "#{RAILS_ROOT}/files"
29 @@storage_path = "#{RAILS_ROOT}/files"
28
30
29 def validate
31 def validate
30 errors.add_to_base :too_long if self.filesize > Setting.attachment_max_size.to_i.kilobytes
32 errors.add_to_base :too_long if self.filesize > Setting.attachment_max_size.to_i.kilobytes
31 end
33 end
32
34
33 def file=(incomming_file)
35 def file=(incomming_file)
34 unless incomming_file.nil?
36 unless incomming_file.nil?
35 @temp_file = incomming_file
37 @temp_file = incomming_file
36 if @temp_file.size > 0
38 if @temp_file.size > 0
37 self.filename = sanitize_filename(@temp_file.original_filename)
39 self.filename = sanitize_filename(@temp_file.original_filename)
38 self.disk_filename = DateTime.now.strftime("%y%m%d%H%M%S") + "_" + self.filename
40 self.disk_filename = DateTime.now.strftime("%y%m%d%H%M%S") + "_" + self.filename
39 self.content_type = @temp_file.content_type
41 self.content_type = @temp_file.content_type
40 self.filesize = @temp_file.size
42 self.filesize = @temp_file.size
41 end
43 end
42 end
44 end
43 end
45 end
44
46
45 def file
47 def file
46 nil
48 nil
47 end
49 end
48
50
49 # Copy temp file to its final location
51 # Copy temp file to its final location
50 def before_save
52 def before_save
51 if @temp_file && (@temp_file.size > 0)
53 if @temp_file && (@temp_file.size > 0)
52 logger.debug("saving '#{self.diskfile}'")
54 logger.debug("saving '#{self.diskfile}'")
53 File.open(diskfile, "wb") do |f|
55 File.open(diskfile, "wb") do |f|
54 f.write(@temp_file.read)
56 f.write(@temp_file.read)
55 end
57 end
56 self.digest = Digest::MD5.hexdigest(File.read(diskfile))
58 self.digest = Digest::MD5.hexdigest(File.read(diskfile))
57 end
59 end
58 end
60 end
59
61
60 # Deletes file on the disk
62 # Deletes file on the disk
61 def after_destroy
63 def after_destroy
62 if self.filename?
64 if self.filename?
63 File.delete(diskfile) if File.exist?(diskfile)
65 File.delete(diskfile) if File.exist?(diskfile)
64 end
66 end
65 end
67 end
66
68
67 # Returns file's location on disk
69 # Returns file's location on disk
68 def diskfile
70 def diskfile
69 "#{@@storage_path}/#{self.disk_filename}"
71 "#{@@storage_path}/#{self.disk_filename}"
70 end
72 end
71
73
72 def increment_download
74 def increment_download
73 increment!(:downloads)
75 increment!(:downloads)
74 end
76 end
75
77
76 # returns last created projects
78 # returns last created projects
77 def self.most_downloaded
79 def self.most_downloaded
78 find(:all, :limit => 5, :order => "downloads DESC")
80 find(:all, :limit => 5, :order => "downloads DESC")
79 end
81 end
80
82
81 def project
83 def project
82 container.is_a?(Project) ? container : container.project
84 container.is_a?(Project) ? container : container.project
83 end
85 end
84
86
85 private
87 private
86 def sanitize_filename(value)
88 def sanitize_filename(value)
87 # get only the filename, not the whole path
89 # get only the filename, not the whole path
88 just_filename = value.gsub(/^.*(\\|\/)/, '')
90 just_filename = value.gsub(/^.*(\\|\/)/, '')
89 # NOTE: File.basename doesn't work right with Windows paths on Unix
91 # NOTE: File.basename doesn't work right with Windows paths on Unix
90 # INCORRECT: just_filename = File.basename(value.gsub('\\\\', '/'))
92 # INCORRECT: just_filename = File.basename(value.gsub('\\\\', '/'))
91
93
92 # Finally, replace all non alphanumeric, underscore or periods with underscore
94 # Finally, replace all non alphanumeric, underscore or periods with underscore
93 @filename = just_filename.gsub(/[^\w\.\-]/,'_')
95 @filename = just_filename.gsub(/[^\w\.\-]/,'_')
94 end
96 end
95
97
96 end
98 end
@@ -1,60 +1,61
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 CustomField < ActiveRecord::Base
18 class CustomField < ActiveRecord::Base
19 has_many :custom_values, :dependent => :delete_all
19 has_many :custom_values, :dependent => :delete_all
20 serialize :possible_values
20 serialize :possible_values
21
21
22 FIELD_FORMATS = { "string" => { :name => :label_string, :order => 1 },
22 FIELD_FORMATS = { "string" => { :name => :label_string, :order => 1 },
23 "text" => { :name => :label_text, :order => 2 },
23 "text" => { :name => :label_text, :order => 2 },
24 "int" => { :name => :label_integer, :order => 3 },
24 "int" => { :name => :label_integer, :order => 3 },
25 "list" => { :name => :label_list, :order => 4 },
25 "list" => { :name => :label_list, :order => 4 },
26 "date" => { :name => :label_date, :order => 5 },
26 "date" => { :name => :label_date, :order => 5 },
27 "bool" => { :name => :label_boolean, :order => 6 }
27 "bool" => { :name => :label_boolean, :order => 6 }
28 }.freeze
28 }.freeze
29
29
30 validates_presence_of :name, :field_format
30 validates_presence_of :name, :field_format
31 validates_uniqueness_of :name
31 validates_uniqueness_of :name
32 validates_length_of :name, :maximum => 30
32 validates_format_of :name, :with => /^[\w\s\'\-]*$/i
33 validates_format_of :name, :with => /^[\w\s\'\-]*$/i
33 validates_inclusion_of :field_format, :in => FIELD_FORMATS.keys
34 validates_inclusion_of :field_format, :in => FIELD_FORMATS.keys
34
35
35 def initialize(attributes = nil)
36 def initialize(attributes = nil)
36 super
37 super
37 self.possible_values ||= []
38 self.possible_values ||= []
38 end
39 end
39
40
40 def before_validation
41 def before_validation
41 # remove empty values
42 # remove empty values
42 self.possible_values = self.possible_values.collect{|v| v unless v.empty?}.compact
43 self.possible_values = self.possible_values.collect{|v| v unless v.empty?}.compact
43 end
44 end
44
45
45 def validate
46 def validate
46 if self.field_format == "list"
47 if self.field_format == "list"
47 errors.add(:possible_values, :activerecord_error_blank) if self.possible_values.nil? || self.possible_values.empty?
48 errors.add(:possible_values, :activerecord_error_blank) if self.possible_values.nil? || self.possible_values.empty?
48 errors.add(:possible_values, :activerecord_error_invalid) unless self.possible_values.is_a? Array
49 errors.add(:possible_values, :activerecord_error_invalid) unless self.possible_values.is_a? Array
49 end
50 end
50 end
51 end
51
52
52 # to move in project_custom_field
53 # to move in project_custom_field
53 def self.for_all
54 def self.for_all
54 find(:all, :conditions => ["is_for_all=?", true])
55 find(:all, :conditions => ["is_for_all=?", true])
55 end
56 end
56
57
57 def type_name
58 def type_name
58 nil
59 nil
59 end
60 end
60 end
61 end
@@ -1,24 +1,25
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 Document < ActiveRecord::Base
18 class Document < ActiveRecord::Base
19 belongs_to :project
19 belongs_to :project
20 belongs_to :category, :class_name => "Enumeration", :foreign_key => "category_id"
20 belongs_to :category, :class_name => "Enumeration", :foreign_key => "category_id"
21 has_many :attachments, :as => :container, :dependent => :destroy
21 has_many :attachments, :as => :container, :dependent => :destroy
22
22
23 validates_presence_of :project, :title, :category
23 validates_presence_of :project, :title, :category
24 validates_length_of :title, :maximum => 60
24 end
25 end
@@ -1,50 +1,51
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 Enumeration < ActiveRecord::Base
18 class Enumeration < ActiveRecord::Base
19 before_destroy :check_integrity
19 before_destroy :check_integrity
20
20
21 validates_presence_of :opt, :name
21 validates_presence_of :opt, :name
22 validates_uniqueness_of :name, :scope => [:opt]
22 validates_uniqueness_of :name, :scope => [:opt]
23 validates_length_of :name, :maximum => 30
23 validates_format_of :name, :with => /^[\w\s\'\-]*$/i
24 validates_format_of :name, :with => /^[\w\s\'\-]*$/i
24
25
25 OPTIONS = {
26 OPTIONS = {
26 "IPRI" => :enumeration_issue_priorities,
27 "IPRI" => :enumeration_issue_priorities,
27 "DCAT" => :enumeration_doc_categories,
28 "DCAT" => :enumeration_doc_categories,
28 "ACTI" => :enumeration_activities
29 "ACTI" => :enumeration_activities
29 }.freeze
30 }.freeze
30
31
31 def self.get_values(option)
32 def self.get_values(option)
32 find(:all, :conditions => ['opt=?', option])
33 find(:all, :conditions => ['opt=?', option])
33 end
34 end
34
35
35 def option_name
36 def option_name
36 OPTIONS[self.opt]
37 OPTIONS[self.opt]
37 end
38 end
38
39
39 private
40 private
40 def check_integrity
41 def check_integrity
41 case self.opt
42 case self.opt
42 when "IPRI"
43 when "IPRI"
43 raise "Can't delete enumeration" if Issue.find(:first, :conditions => ["priority_id=?", self.id])
44 raise "Can't delete enumeration" if Issue.find(:first, :conditions => ["priority_id=?", self.id])
44 when "DCAT"
45 when "DCAT"
45 raise "Can't delete enumeration" if Document.find(:first, :conditions => ["category_id=?", self.id])
46 raise "Can't delete enumeration" if Document.find(:first, :conditions => ["category_id=?", self.id])
46 when "ACTI"
47 when "ACTI"
47 raise "Can't delete enumeration" if TimeEntry.find(:first, :conditions => ["activity_id=?", self.id])
48 raise "Can't delete enumeration" if TimeEntry.find(:first, :conditions => ["activity_id=?", self.id])
48 end
49 end
49 end
50 end
50 end
51 end
@@ -1,131 +1,132
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 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 Issue < ActiveRecord::Base
18 class Issue < ActiveRecord::Base
19 belongs_to :project
19 belongs_to :project
20 belongs_to :tracker
20 belongs_to :tracker
21 belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
21 belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
22 belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
22 belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
23 belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id'
23 belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id'
24 belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id'
24 belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id'
25 belongs_to :priority, :class_name => 'Enumeration', :foreign_key => 'priority_id'
25 belongs_to :priority, :class_name => 'Enumeration', :foreign_key => 'priority_id'
26 belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
26 belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
27
27
28 has_many :journals, :as => :journalized, :dependent => :destroy
28 has_many :journals, :as => :journalized, :dependent => :destroy
29 has_many :attachments, :as => :container, :dependent => :destroy
29 has_many :attachments, :as => :container, :dependent => :destroy
30 has_many :time_entries, :dependent => :nullify
30 has_many :time_entries, :dependent => :nullify
31 has_many :custom_values, :dependent => :delete_all, :as => :customized
31 has_many :custom_values, :dependent => :delete_all, :as => :customized
32 has_many :custom_fields, :through => :custom_values
32 has_many :custom_fields, :through => :custom_values
33 has_and_belongs_to_many :changesets, :order => "revision ASC"
33 has_and_belongs_to_many :changesets, :order => "revision ASC"
34
34
35 has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
35 has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
36 has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
36 has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
37
37
38 acts_as_watchable
38 acts_as_watchable
39
39
40 validates_presence_of :subject, :description, :priority, :tracker, :author, :status
40 validates_presence_of :subject, :description, :priority, :tracker, :author, :status
41 validates_length_of :subject, :maximum => 255
41 validates_inclusion_of :done_ratio, :in => 0..100
42 validates_inclusion_of :done_ratio, :in => 0..100
42 validates_associated :custom_values, :on => :update
43 validates_associated :custom_values, :on => :update
43
44
44 # set default status for new issues
45 # set default status for new issues
45 def before_validation
46 def before_validation
46 self.status = IssueStatus.default if status.nil?
47 self.status = IssueStatus.default if status.nil?
47 end
48 end
48
49
49 def validate
50 def validate
50 if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty?
51 if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty?
51 errors.add :due_date, :activerecord_error_not_a_date
52 errors.add :due_date, :activerecord_error_not_a_date
52 end
53 end
53
54
54 if self.due_date and self.start_date and self.due_date < self.start_date
55 if self.due_date and self.start_date and self.due_date < self.start_date
55 errors.add :due_date, :activerecord_error_greater_than_start_date
56 errors.add :due_date, :activerecord_error_greater_than_start_date
56 end
57 end
57
58
58 if start_date && soonest_start && start_date < soonest_start
59 if start_date && soonest_start && start_date < soonest_start
59 errors.add :start_date, :activerecord_error_invalid
60 errors.add :start_date, :activerecord_error_invalid
60 end
61 end
61 end
62 end
62
63
63 def before_create
64 def before_create
64 # default assignment based on category
65 # default assignment based on category
65 if assigned_to.nil? && category && category.assigned_to
66 if assigned_to.nil? && category && category.assigned_to
66 self.assigned_to = category.assigned_to
67 self.assigned_to = category.assigned_to
67 end
68 end
68 end
69 end
69
70
70 def before_save
71 def before_save
71 if @current_journal
72 if @current_journal
72 # attributes changes
73 # attributes changes
73 (Issue.column_names - %w(id description)).each {|c|
74 (Issue.column_names - %w(id description)).each {|c|
74 @current_journal.details << JournalDetail.new(:property => 'attr',
75 @current_journal.details << JournalDetail.new(:property => 'attr',
75 :prop_key => c,
76 :prop_key => c,
76 :old_value => @issue_before_change.send(c),
77 :old_value => @issue_before_change.send(c),
77 :value => send(c)) unless send(c)==@issue_before_change.send(c)
78 :value => send(c)) unless send(c)==@issue_before_change.send(c)
78 }
79 }
79 # custom fields changes
80 # custom fields changes
80 custom_values.each {|c|
81 custom_values.each {|c|
81 @current_journal.details << JournalDetail.new(:property => 'cf',
82 @current_journal.details << JournalDetail.new(:property => 'cf',
82 :prop_key => c.custom_field_id,
83 :prop_key => c.custom_field_id,
83 :old_value => @custom_values_before_change[c.custom_field_id],
84 :old_value => @custom_values_before_change[c.custom_field_id],
84 :value => c.value) unless @custom_values_before_change[c.custom_field_id]==c.value
85 :value => c.value) unless @custom_values_before_change[c.custom_field_id]==c.value
85 }
86 }
86 @current_journal.save unless @current_journal.details.empty? and @current_journal.notes.empty?
87 @current_journal.save unless @current_journal.details.empty? and @current_journal.notes.empty?
87 end
88 end
88 end
89 end
89
90
90 def after_save
91 def after_save
91 relations_from.each(&:set_issue_to_dates)
92 relations_from.each(&:set_issue_to_dates)
92 end
93 end
93
94
94 def custom_value_for(custom_field)
95 def custom_value_for(custom_field)
95 self.custom_values.each {|v| return v if v.custom_field_id == custom_field.id }
96 self.custom_values.each {|v| return v if v.custom_field_id == custom_field.id }
96 return nil
97 return nil
97 end
98 end
98
99
99 def init_journal(user, notes = "")
100 def init_journal(user, notes = "")
100 @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
101 @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
101 @issue_before_change = self.clone
102 @issue_before_change = self.clone
102 @custom_values_before_change = {}
103 @custom_values_before_change = {}
103 self.custom_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value }
104 self.custom_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value }
104 @current_journal
105 @current_journal
105 end
106 end
106
107
107 def spent_hours
108 def spent_hours
108 @spent_hours ||= time_entries.sum(:hours) || 0
109 @spent_hours ||= time_entries.sum(:hours) || 0
109 end
110 end
110
111
111 def relations
112 def relations
112 (relations_from + relations_to).sort
113 (relations_from + relations_to).sort
113 end
114 end
114
115
115 def all_dependent_issues
116 def all_dependent_issues
116 dependencies = []
117 dependencies = []
117 relations_from.each do |relation|
118 relations_from.each do |relation|
118 dependencies << relation.issue_to
119 dependencies << relation.issue_to
119 dependencies += relation.issue_to.all_dependent_issues
120 dependencies += relation.issue_to.all_dependent_issues
120 end
121 end
121 dependencies
122 dependencies
122 end
123 end
123
124
124 def duration
125 def duration
125 (start_date && due_date) ? due_date - start_date : 0
126 (start_date && due_date) ? due_date - start_date : 0
126 end
127 end
127
128
128 def soonest_start
129 def soonest_start
129 @soonest_start ||= relations_to.collect{|relation| relation.successor_soonest_start}.compact.min
130 @soonest_start ||= relations_to.collect{|relation| relation.successor_soonest_start}.compact.min
130 end
131 end
131 end
132 end
@@ -1,58 +1,59
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 IssueStatus < ActiveRecord::Base
18 class IssueStatus < ActiveRecord::Base
19 before_destroy :check_integrity
19 before_destroy :check_integrity
20 has_many :workflows, :foreign_key => "old_status_id", :dependent => :delete_all
20 has_many :workflows, :foreign_key => "old_status_id", :dependent => :delete_all
21 acts_as_list
21 acts_as_list
22
22
23 validates_presence_of :name
23 validates_presence_of :name
24 validates_uniqueness_of :name
24 validates_uniqueness_of :name
25 validates_length_of :name, :maximum => 30
25 validates_format_of :name, :with => /^[\w\s\'\-]*$/i
26 validates_format_of :name, :with => /^[\w\s\'\-]*$/i
26 validates_length_of :html_color, :is => 6
27 validates_length_of :html_color, :is => 6
27 validates_format_of :html_color, :with => /^[a-f0-9]*$/i
28 validates_format_of :html_color, :with => /^[a-f0-9]*$/i
28
29
29 def before_save
30 def before_save
30 IssueStatus.update_all "is_default=#{connection.quoted_false}" if self.is_default?
31 IssueStatus.update_all "is_default=#{connection.quoted_false}" if self.is_default?
31 end
32 end
32
33
33 # Returns the default status for new issues
34 # Returns the default status for new issues
34 def self.default
35 def self.default
35 find(:first, :conditions =>["is_default=?", true])
36 find(:first, :conditions =>["is_default=?", true])
36 end
37 end
37
38
38 # Returns an array of all statuses the given role can switch to
39 # Returns an array of all statuses the given role can switch to
39 # Uses association cache when called more than one time
40 # Uses association cache when called more than one time
40 def new_statuses_allowed_to(role, tracker)
41 def new_statuses_allowed_to(role, tracker)
41 new_statuses = workflows.select {|w| w.role_id == role.id && w.tracker_id == tracker.id}.collect{|w| w.new_status} if role && tracker
42 new_statuses = workflows.select {|w| w.role_id == role.id && w.tracker_id == tracker.id}.collect{|w| w.new_status} if role && tracker
42 new_statuses ? new_statuses.compact.sort{|x, y| x.position <=> y.position } : []
43 new_statuses ? new_statuses.compact.sort{|x, y| x.position <=> y.position } : []
43 end
44 end
44
45
45 # Same thing as above but uses a database query
46 # Same thing as above but uses a database query
46 # More efficient than the previous method if called just once
47 # More efficient than the previous method if called just once
47 def find_new_statuses_allowed_to(role, tracker)
48 def find_new_statuses_allowed_to(role, tracker)
48 new_statuses = workflows.find(:all,
49 new_statuses = workflows.find(:all,
49 :include => :new_status,
50 :include => :new_status,
50 :conditions => ["role_id=? and tracker_id=?", role.id, tracker.id]).collect{ |w| w.new_status }.compact if role && tracker
51 :conditions => ["role_id=? and tracker_id=?", role.id, tracker.id]).collect{ |w| w.new_status }.compact if role && tracker
51 new_statuses ? new_statuses.sort{|x, y| x.position <=> y.position } : []
52 new_statuses ? new_statuses.sort{|x, y| x.position <=> y.position } : []
52 end
53 end
53
54
54 private
55 private
55 def check_integrity
56 def check_integrity
56 raise "Can't delete status" if Issue.find(:first, :conditions => ["status_id=?", self.id])
57 raise "Can't delete status" if Issue.find(:first, :conditions => ["status_id=?", self.id])
57 end
58 end
58 end
59 end
@@ -1,29 +1,31
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 News < ActiveRecord::Base
18 class News < ActiveRecord::Base
19 belongs_to :project
19 belongs_to :project
20 belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
20 belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
21 has_many :comments, :as => :commented, :dependent => :delete_all, :order => "created_on"
21 has_many :comments, :as => :commented, :dependent => :delete_all, :order => "created_on"
22
22
23 validates_presence_of :title, :description
23 validates_presence_of :title, :description
24 validates_length_of :title, :maximum => 60
25 validates_length_of :summary, :maximum => 255
24
26
25 # returns latest news for projects visible by user
27 # returns latest news for projects visible by user
26 def self.latest(user=nil, count=5)
28 def self.latest(user=nil, count=5)
27 find(:all, :limit => count, :conditions => Project.visible_by(user), :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
29 find(:all, :limit => count, :conditions => Project.visible_by(user), :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
28 end
30 end
29 end
31 end
@@ -1,239 +1,240
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 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 Query < ActiveRecord::Base
18 class Query < ActiveRecord::Base
19 belongs_to :project
19 belongs_to :project
20 belongs_to :user
20 belongs_to :user
21 serialize :filters
21 serialize :filters
22
22
23 attr_protected :project, :user
23 attr_protected :project, :user
24 attr_accessor :executed_by
24 attr_accessor :executed_by
25
25
26 validates_presence_of :name, :on => :save
26 validates_presence_of :name, :on => :save
27 validates_length_of :name, :maximum => 255
27
28
28 @@operators = { "=" => :label_equals,
29 @@operators = { "=" => :label_equals,
29 "!" => :label_not_equals,
30 "!" => :label_not_equals,
30 "o" => :label_open_issues,
31 "o" => :label_open_issues,
31 "c" => :label_closed_issues,
32 "c" => :label_closed_issues,
32 "!*" => :label_none,
33 "!*" => :label_none,
33 "*" => :label_all,
34 "*" => :label_all,
34 "<t+" => :label_in_less_than,
35 "<t+" => :label_in_less_than,
35 ">t+" => :label_in_more_than,
36 ">t+" => :label_in_more_than,
36 "t+" => :label_in,
37 "t+" => :label_in,
37 "t" => :label_today,
38 "t" => :label_today,
38 ">t-" => :label_less_than_ago,
39 ">t-" => :label_less_than_ago,
39 "<t-" => :label_more_than_ago,
40 "<t-" => :label_more_than_ago,
40 "t-" => :label_ago,
41 "t-" => :label_ago,
41 "~" => :label_contains,
42 "~" => :label_contains,
42 "!~" => :label_not_contains }
43 "!~" => :label_not_contains }
43
44
44 cattr_reader :operators
45 cattr_reader :operators
45
46
46 @@operators_by_filter_type = { :list => [ "=", "!" ],
47 @@operators_by_filter_type = { :list => [ "=", "!" ],
47 :list_status => [ "o", "=", "!", "c", "*" ],
48 :list_status => [ "o", "=", "!", "c", "*" ],
48 :list_optional => [ "=", "!", "!*", "*" ],
49 :list_optional => [ "=", "!", "!*", "*" ],
49 :list_one_or_more => [ "*", "=" ],
50 :list_one_or_more => [ "*", "=" ],
50 :date => [ "<t+", ">t+", "t+", "t", ">t-", "<t-", "t-" ],
51 :date => [ "<t+", ">t+", "t+", "t", ">t-", "<t-", "t-" ],
51 :date_past => [ ">t-", "<t-", "t-", "t" ],
52 :date_past => [ ">t-", "<t-", "t-", "t" ],
52 :string => [ "=", "~", "!", "!~" ],
53 :string => [ "=", "~", "!", "!~" ],
53 :text => [ "~", "!~" ] }
54 :text => [ "~", "!~" ] }
54
55
55 cattr_reader :operators_by_filter_type
56 cattr_reader :operators_by_filter_type
56
57
57 def initialize(attributes = nil)
58 def initialize(attributes = nil)
58 super attributes
59 super attributes
59 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
60 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
60 end
61 end
61
62
62 def executed_by=(user)
63 def executed_by=(user)
63 @executed_by = user
64 @executed_by = user
64 set_language_if_valid(user.language) if user
65 set_language_if_valid(user.language) if user
65 end
66 end
66
67
67 def validate
68 def validate
68 filters.each_key do |field|
69 filters.each_key do |field|
69 errors.add label_for(field), :activerecord_error_blank unless
70 errors.add label_for(field), :activerecord_error_blank unless
70 # filter requires one or more values
71 # filter requires one or more values
71 (values_for(field) and !values_for(field).first.empty?) or
72 (values_for(field) and !values_for(field).first.empty?) or
72 # filter doesn't require any value
73 # filter doesn't require any value
73 ["o", "c", "!*", "*", "t"].include? operator_for(field)
74 ["o", "c", "!*", "*", "t"].include? operator_for(field)
74 end if filters
75 end if filters
75 end
76 end
76
77
77 def editable_by?(user)
78 def editable_by?(user)
78 return false unless user
79 return false unless user
79 return true if !is_public && self.user_id == user.id
80 return true if !is_public && self.user_id == user.id
80 is_public && user.authorized_to(project, "projects/add_query")
81 is_public && user.authorized_to(project, "projects/add_query")
81 end
82 end
82
83
83 def available_filters
84 def available_filters
84 return @available_filters if @available_filters
85 return @available_filters if @available_filters
85 @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
86 @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
86 "tracker_id" => { :type => :list, :order => 2, :values => Tracker.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
87 "tracker_id" => { :type => :list, :order => 2, :values => Tracker.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
87 "priority_id" => { :type => :list, :order => 3, :values => Enumeration.find(:all, :conditions => ['opt=?','IPRI']).collect{|s| [s.name, s.id.to_s] } },
88 "priority_id" => { :type => :list, :order => 3, :values => Enumeration.find(:all, :conditions => ['opt=?','IPRI']).collect{|s| [s.name, s.id.to_s] } },
88 "subject" => { :type => :text, :order => 8 },
89 "subject" => { :type => :text, :order => 8 },
89 "created_on" => { :type => :date_past, :order => 9 },
90 "created_on" => { :type => :date_past, :order => 9 },
90 "updated_on" => { :type => :date_past, :order => 10 },
91 "updated_on" => { :type => :date_past, :order => 10 },
91 "start_date" => { :type => :date, :order => 11 },
92 "start_date" => { :type => :date, :order => 11 },
92 "due_date" => { :type => :date, :order => 12 } }
93 "due_date" => { :type => :date, :order => 12 } }
93 unless project.nil?
94 unless project.nil?
94 # project specific filters
95 # project specific filters
95 user_values = []
96 user_values = []
96 user_values << ["<< #{l(:label_me)} >>", "me"] if executed_by
97 user_values << ["<< #{l(:label_me)} >>", "me"] if executed_by
97 user_values += @project.users.collect{|s| [s.name, s.id.to_s] }
98 user_values += @project.users.collect{|s| [s.name, s.id.to_s] }
98
99
99 @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values }
100 @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values }
100 @available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values }
101 @available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values }
101 @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } }
102 @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } }
102 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.versions.sort.collect{|s| [s.name, s.id.to_s] } }
103 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.versions.sort.collect{|s| [s.name, s.id.to_s] } }
103 unless @project.active_children.empty?
104 unless @project.active_children.empty?
104 @available_filters["subproject_id"] = { :type => :list_one_or_more, :order => 13, :values => @project.active_children.collect{|s| [s.name, s.id.to_s] } }
105 @available_filters["subproject_id"] = { :type => :list_one_or_more, :order => 13, :values => @project.active_children.collect{|s| [s.name, s.id.to_s] } }
105 end
106 end
106 @project.all_custom_fields.select(&:is_filter?).each do |field|
107 @project.all_custom_fields.select(&:is_filter?).each do |field|
107 case field.field_format
108 case field.field_format
108 when "string", "int"
109 when "string", "int"
109 options = { :type => :string, :order => 20 }
110 options = { :type => :string, :order => 20 }
110 when "text"
111 when "text"
111 options = { :type => :text, :order => 20 }
112 options = { :type => :text, :order => 20 }
112 when "list"
113 when "list"
113 options = { :type => :list_optional, :values => field.possible_values, :order => 20}
114 options = { :type => :list_optional, :values => field.possible_values, :order => 20}
114 when "date"
115 when "date"
115 options = { :type => :date, :order => 20 }
116 options = { :type => :date, :order => 20 }
116 when "bool"
117 when "bool"
117 options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
118 options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
118 end
119 end
119 @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name })
120 @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name })
120 end
121 end
121 # remove category filter if no category defined
122 # remove category filter if no category defined
122 @available_filters.delete "category_id" if @available_filters["category_id"][:values].empty?
123 @available_filters.delete "category_id" if @available_filters["category_id"][:values].empty?
123 end
124 end
124 @available_filters
125 @available_filters
125 end
126 end
126
127
127 def add_filter(field, operator, values)
128 def add_filter(field, operator, values)
128 # values must be an array
129 # values must be an array
129 return unless values and values.is_a? Array # and !values.first.empty?
130 return unless values and values.is_a? Array # and !values.first.empty?
130 # check if field is defined as an available filter
131 # check if field is defined as an available filter
131 if available_filters.has_key? field
132 if available_filters.has_key? field
132 filter_options = available_filters[field]
133 filter_options = available_filters[field]
133 # check if operator is allowed for that filter
134 # check if operator is allowed for that filter
134 #if @@operators_by_filter_type[filter_options[:type]].include? operator
135 #if @@operators_by_filter_type[filter_options[:type]].include? operator
135 # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
136 # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
136 # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
137 # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
137 #end
138 #end
138 filters[field] = {:operator => operator, :values => values }
139 filters[field] = {:operator => operator, :values => values }
139 end
140 end
140 end
141 end
141
142
142 def add_short_filter(field, expression)
143 def add_short_filter(field, expression)
143 return unless expression
144 return unless expression
144 parms = expression.scan(/^(o|c|\!|\*)?(.*)$/).first
145 parms = expression.scan(/^(o|c|\!|\*)?(.*)$/).first
145 add_filter field, (parms[0] || "="), [parms[1] || ""]
146 add_filter field, (parms[0] || "="), [parms[1] || ""]
146 end
147 end
147
148
148 def has_filter?(field)
149 def has_filter?(field)
149 filters and filters[field]
150 filters and filters[field]
150 end
151 end
151
152
152 def operator_for(field)
153 def operator_for(field)
153 has_filter?(field) ? filters[field][:operator] : nil
154 has_filter?(field) ? filters[field][:operator] : nil
154 end
155 end
155
156
156 def values_for(field)
157 def values_for(field)
157 has_filter?(field) ? filters[field][:values] : nil
158 has_filter?(field) ? filters[field][:values] : nil
158 end
159 end
159
160
160 def label_for(field)
161 def label_for(field)
161 label = @available_filters[field][:name] if @available_filters.has_key?(field)
162 label = @available_filters[field][:name] if @available_filters.has_key?(field)
162 label ||= field.gsub(/\_id$/, "")
163 label ||= field.gsub(/\_id$/, "")
163 end
164 end
164
165
165 def statement
166 def statement
166 sql = "1=1"
167 sql = "1=1"
167 if has_filter?("subproject_id")
168 if has_filter?("subproject_id")
168 subproject_ids = []
169 subproject_ids = []
169 if operator_for("subproject_id") == "="
170 if operator_for("subproject_id") == "="
170 subproject_ids = values_for("subproject_id").each(&:to_i)
171 subproject_ids = values_for("subproject_id").each(&:to_i)
171 else
172 else
172 subproject_ids = project.active_children.collect{|p| p.id}
173 subproject_ids = project.active_children.collect{|p| p.id}
173 end
174 end
174 sql << " AND #{Issue.table_name}.project_id IN (%d,%s)" % [project.id, subproject_ids.join(",")] if project
175 sql << " AND #{Issue.table_name}.project_id IN (%d,%s)" % [project.id, subproject_ids.join(",")] if project
175 else
176 else
176 sql << " AND #{Issue.table_name}.project_id=%d" % project.id if project
177 sql << " AND #{Issue.table_name}.project_id=%d" % project.id if project
177 end
178 end
178 filters.each_key do |field|
179 filters.each_key do |field|
179 next if field == "subproject_id"
180 next if field == "subproject_id"
180 v = values_for(field).clone
181 v = values_for(field).clone
181 next unless v and !v.empty?
182 next unless v and !v.empty?
182
183
183 sql = sql + " AND " unless sql.empty?
184 sql = sql + " AND " unless sql.empty?
184 sql << "("
185 sql << "("
185
186
186 if field =~ /^cf_(\d+)$/
187 if field =~ /^cf_(\d+)$/
187 # custom field
188 # custom field
188 db_table = CustomValue.table_name
189 db_table = CustomValue.table_name
189 db_field = "value"
190 db_field = "value"
190 sql << "#{db_table}.custom_field_id = #{$1} AND "
191 sql << "#{db_table}.custom_field_id = #{$1} AND "
191 else
192 else
192 # regular field
193 # regular field
193 db_table = Issue.table_name
194 db_table = Issue.table_name
194 db_field = field
195 db_field = field
195 end
196 end
196
197
197 # "me" value subsitution
198 # "me" value subsitution
198 if %w(assigned_to_id author_id).include?(field)
199 if %w(assigned_to_id author_id).include?(field)
199 v.push(executed_by ? executed_by.id.to_s : "0") if v.delete("me")
200 v.push(executed_by ? executed_by.id.to_s : "0") if v.delete("me")
200 end
201 end
201
202
202 case operator_for field
203 case operator_for field
203 when "="
204 when "="
204 sql = sql + "#{db_table}.#{db_field} IN (" + v.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
205 sql = sql + "#{db_table}.#{db_field} IN (" + v.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
205 when "!"
206 when "!"
206 sql = sql + "#{db_table}.#{db_field} NOT IN (" + v.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
207 sql = sql + "#{db_table}.#{db_field} NOT IN (" + v.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
207 when "!*"
208 when "!*"
208 sql = sql + "#{db_table}.#{db_field} IS NULL"
209 sql = sql + "#{db_table}.#{db_field} IS NULL"
209 when "*"
210 when "*"
210 sql = sql + "#{db_table}.#{db_field} IS NOT NULL"
211 sql = sql + "#{db_table}.#{db_field} IS NOT NULL"
211 when "o"
212 when "o"
212 sql = sql + "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
213 sql = sql + "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
213 when "c"
214 when "c"
214 sql = sql + "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id"
215 sql = sql + "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id"
215 when ">t-"
216 when ">t-"
216 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date((Date.today - v.first.to_i).to_time), connection.quoted_date((Date.today + 1).to_time)]
217 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date((Date.today - v.first.to_i).to_time), connection.quoted_date((Date.today + 1).to_time)]
217 when "<t-"
218 when "<t-"
218 sql = sql + "#{db_table}.#{db_field} <= '%s'" % connection.quoted_date((Date.today - v.first.to_i).to_time)
219 sql = sql + "#{db_table}.#{db_field} <= '%s'" % connection.quoted_date((Date.today - v.first.to_i).to_time)
219 when "t-"
220 when "t-"
220 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date((Date.today - v.first.to_i).to_time), connection.quoted_date((Date.today - v.first.to_i + 1).to_time)]
221 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date((Date.today - v.first.to_i).to_time), connection.quoted_date((Date.today - v.first.to_i + 1).to_time)]
221 when ">t+"
222 when ">t+"
222 sql = sql + "#{db_table}.#{db_field} >= '%s'" % connection.quoted_date((Date.today + v.first.to_i).to_time)
223 sql = sql + "#{db_table}.#{db_field} >= '%s'" % connection.quoted_date((Date.today + v.first.to_i).to_time)
223 when "<t+"
224 when "<t+"
224 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(Date.today.to_time), connection.quoted_date((Date.today + v.first.to_i + 1).to_time)]
225 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(Date.today.to_time), connection.quoted_date((Date.today + v.first.to_i + 1).to_time)]
225 when "t+"
226 when "t+"
226 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date((Date.today + v.first.to_i).to_time), connection.quoted_date((Date.today + v.first.to_i + 1).to_time)]
227 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date((Date.today + v.first.to_i).to_time), connection.quoted_date((Date.today + v.first.to_i + 1).to_time)]
227 when "t"
228 when "t"
228 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(Date.today.to_time), connection.quoted_date((Date.today+1).to_time)]
229 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(Date.today.to_time), connection.quoted_date((Date.today+1).to_time)]
229 when "~"
230 when "~"
230 sql = sql + "#{db_table}.#{db_field} LIKE '%#{connection.quote_string(v.first)}%'"
231 sql = sql + "#{db_table}.#{db_field} LIKE '%#{connection.quote_string(v.first)}%'"
231 when "!~"
232 when "!~"
232 sql = sql + "#{db_table}.#{db_field} NOT LIKE '%#{connection.quote_string(v.first)}%'"
233 sql = sql + "#{db_table}.#{db_field} NOT LIKE '%#{connection.quote_string(v.first)}%'"
233 end
234 end
234 sql << ")"
235 sql << ")"
235
236
236 end if filters and valid?
237 end if filters and valid?
237 sql
238 sql
238 end
239 end
239 end
240 end
@@ -1,37 +1,38
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 Role < ActiveRecord::Base
18 class Role < ActiveRecord::Base
19 before_destroy :check_integrity
19 before_destroy :check_integrity
20 has_and_belongs_to_many :permissions
20 has_and_belongs_to_many :permissions
21 has_many :workflows, :dependent => :delete_all
21 has_many :workflows, :dependent => :delete_all
22 has_many :members
22 has_many :members
23 acts_as_list
23 acts_as_list
24
24
25 validates_presence_of :name
25 validates_presence_of :name
26 validates_uniqueness_of :name
26 validates_uniqueness_of :name
27 validates_length_of :name, :maximum => 30
27 validates_format_of :name, :with => /^[\w\s\'\-]*$/i
28 validates_format_of :name, :with => /^[\w\s\'\-]*$/i
28
29
29 def <=>(role)
30 def <=>(role)
30 position <=> role.position
31 position <=> role.position
31 end
32 end
32
33
33 private
34 private
34 def check_integrity
35 def check_integrity
35 raise "Can't delete role" if Member.find(:first, :conditions =>["role_id=?", self.id])
36 raise "Can't delete role" if Member.find(:first, :conditions =>["role_id=?", self.id])
36 end
37 end
37 end
38 end
@@ -1,33 +1,34
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 Tracker < ActiveRecord::Base
18 class Tracker < ActiveRecord::Base
19 before_destroy :check_integrity
19 before_destroy :check_integrity
20 has_many :issues
20 has_many :issues
21 has_many :workflows, :dependent => :delete_all
21 has_many :workflows, :dependent => :delete_all
22 has_and_belongs_to_many :custom_fields, :class_name => 'IssueCustomField', :join_table => "#{table_name_prefix}custom_fields_trackers#{table_name_suffix}", :association_foreign_key => 'custom_field_id'
22 has_and_belongs_to_many :custom_fields, :class_name => 'IssueCustomField', :join_table => "#{table_name_prefix}custom_fields_trackers#{table_name_suffix}", :association_foreign_key => 'custom_field_id'
23 acts_as_list
23 acts_as_list
24
24
25 validates_presence_of :name
25 validates_presence_of :name
26 validates_uniqueness_of :name
26 validates_uniqueness_of :name
27 validates_length_of :name, :maximum => 30
27 validates_format_of :name, :with => /^[\w\s\'\-]*$/i
28 validates_format_of :name, :with => /^[\w\s\'\-]*$/i
28
29
29 private
30 private
30 def check_integrity
31 def check_integrity
31 raise "Can't delete tracker" if Issue.find(:first, :conditions => ["tracker_id=?", self.id])
32 raise "Can't delete tracker" if Issue.find(:first, :conditions => ["tracker_id=?", self.id])
32 end
33 end
33 end
34 end
@@ -1,61 +1,62
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 Version < ActiveRecord::Base
18 class Version < ActiveRecord::Base
19 before_destroy :check_integrity
19 before_destroy :check_integrity
20 belongs_to :project
20 belongs_to :project
21 has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id'
21 has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id'
22 has_many :attachments, :as => :container, :dependent => :destroy
22 has_many :attachments, :as => :container, :dependent => :destroy
23
23
24 validates_presence_of :name
24 validates_presence_of :name
25 validates_uniqueness_of :name, :scope => [:project_id]
25 validates_uniqueness_of :name, :scope => [:project_id]
26 validates_length_of :name, :maximum => 30
26 validates_format_of :effective_date, :with => /^\d{4}-\d{2}-\d{2}$/, :message => :activerecord_error_not_a_date, :allow_nil => true
27 validates_format_of :effective_date, :with => /^\d{4}-\d{2}-\d{2}$/, :message => :activerecord_error_not_a_date, :allow_nil => true
27
28
28 def start_date
29 def start_date
29 effective_date
30 effective_date
30 end
31 end
31
32
32 def due_date
33 def due_date
33 effective_date
34 effective_date
34 end
35 end
35
36
36 def completed?
37 def completed?
37 effective_date && effective_date <= Date.today
38 effective_date && effective_date <= Date.today
38 end
39 end
39
40
40 def wiki_page
41 def wiki_page
41 if project.wiki && !wiki_page_title.blank?
42 if project.wiki && !wiki_page_title.blank?
42 @wiki_page ||= project.wiki.find_page(wiki_page_title)
43 @wiki_page ||= project.wiki.find_page(wiki_page_title)
43 end
44 end
44 @wiki_page
45 @wiki_page
45 end
46 end
46
47
47 # Versions are sorted by effective_date
48 # Versions are sorted by effective_date
48 # Those with no effective_date are at the end, sorted by name
49 # Those with no effective_date are at the end, sorted by name
49 def <=>(version)
50 def <=>(version)
50 if self.effective_date
51 if self.effective_date
51 version.effective_date ? (self.effective_date <=> version.effective_date) : -1
52 version.effective_date ? (self.effective_date <=> version.effective_date) : -1
52 else
53 else
53 version.effective_date ? 1 : (self.name <=> version.name)
54 version.effective_date ? 1 : (self.name <=> version.name)
54 end
55 end
55 end
56 end
56
57
57 private
58 private
58 def check_integrity
59 def check_integrity
59 raise "Can't delete version" if self.fixed_issues.find(:first)
60 raise "Can't delete version" if self.fixed_issues.find(:first)
60 end
61 end
61 end
62 end
General Comments 0
You need to be logged in to leave comments. Login now