@@ -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