<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -188,10 +188,13 @@ class ProjectsController &lt; ApplicationController
 
   def add_file
     if request.post?
-      @version = @project.versions.find_by_id(params[:version_id])
-      attachments = attach_files(@version, params[:attachments])
-      Mailer.deliver_attachments_added(attachments) if !attachments.empty? &amp;&amp; Setting.notified_events.include?('file_added')
+      container = (params[:version_id].blank? ? @project : @project.versions.find_by_id(params[:version_id]))
+      attachments = attach_files(container, params[:attachments])
+      if !attachments.empty? &amp;&amp; Setting.notified_events.include?('file_added')
+        Mailer.deliver_attachments_added(attachments)
+      end
       redirect_to :controller =&gt; 'projects', :action =&gt; 'list_files', :id =&gt; @project
+      return
     end
     @versions = @project.versions.sort
   end
@@ -199,7 +202,8 @@ class ProjectsController &lt; ApplicationController
   def list_files
     sort_init &quot;#{Attachment.table_name}.filename&quot;, &quot;asc&quot;
     sort_update
-    @versions = @project.versions.find(:all, :include =&gt; :attachments, :order =&gt; sort_clause).sort.reverse
+    @containers = [ Project.find(@project.id, :include =&gt; :attachments, :order =&gt; sort_clause)]
+    @containers += @project.versions.find(:all, :include =&gt; :attachments, :order =&gt; sort_clause).sort.reverse
     render :layout =&gt; !request.xhr?
   end
   </diff>
      <filename>app/controllers/projects_controller.rb</filename>
    </modified>
    <modified>
      <diff>@@ -73,6 +73,9 @@ class Mailer &lt; ActionMailer::Base
     added_to = ''
     added_to_url = ''
     case container.class.name
+    when 'Project'
+      added_to_url = url_for(:controller =&gt; 'projects', :action =&gt; 'list_files', :id =&gt; container)
+      added_to = &quot;#{l(:label_project)}: #{container}&quot;
     when 'Version'
       added_to_url = url_for(:controller =&gt; 'projects', :action =&gt; 'list_files', :id =&gt; container.project_id)
       added_to = &quot;#{l(:label_version)}: #{container.name}&quot;</diff>
      <filename>app/models/mailer.rb</filename>
    </modified>
    <modified>
      <diff>@@ -44,6 +44,8 @@ class Project &lt; ActiveRecord::Base
                           :association_foreign_key =&gt; 'custom_field_id'
                           
   acts_as_tree :order =&gt; &quot;name&quot;, :counter_cache =&gt; true
+  acts_as_attachable :view_permission =&gt; :view_files,
+                     :delete_permission =&gt; :manage_files
 
   acts_as_customizable
   acts_as_searchable :columns =&gt; ['name', 'description'], :project_key =&gt; 'id', :permission =&gt; nil</diff>
      <filename>app/models/project.rb</filename>
    </modified>
    <modified>
      <diff>@@ -4,10 +4,13 @@
 &lt;div class=&quot;box&quot;&gt;
 &lt;% form_tag({ :action =&gt; 'add_file', :id =&gt; @project }, :multipart =&gt; true, :class =&gt; &quot;tabular&quot;) do %&gt;
 
-&lt;p&gt;&lt;label for=&quot;version_id&quot;&gt;&lt;%=l(:field_version)%&gt; &lt;span class=&quot;required&quot;&gt;*&lt;/span&gt;&lt;/label&gt;
-&lt;%= select_tag &quot;version_id&quot;, options_from_collection_for_select(@versions, &quot;id&quot;, &quot;name&quot;) %&gt;&lt;/p&gt;
+&lt;% if @versions.any? %&gt;
+&lt;p&gt;&lt;label for=&quot;version_id&quot;&gt;&lt;%=l(:field_version)%&gt;&lt;/label&gt;
+&lt;%= select_tag &quot;version_id&quot;, content_tag('option', '') +
+														 options_from_collection_for_select(@versions, &quot;id&quot;, &quot;name&quot;) %&gt;&lt;/p&gt;
+&lt;% end %&gt;
 
 &lt;p&gt;&lt;label&gt;&lt;%=l(:label_attachment_plural)%&gt;&lt;/label&gt;&lt;%= render :partial =&gt; 'attachments/form' %&gt;&lt;/p&gt;
 &lt;/div&gt;
 &lt;%= submit_tag l(:button_add) %&gt;
-&lt;% end %&gt; 
\ No newline at end of file
+&lt;% end %&gt; </diff>
      <filename>app/views/projects/add_file.rhtml</filename>
    </modified>
    <modified>
      <diff>@@ -8,36 +8,33 @@
 
 &lt;table class=&quot;list&quot;&gt;
   &lt;thead&gt;&lt;tr&gt;
-    &lt;th&gt;&lt;%=l(:field_version)%&gt;&lt;/th&gt;
     &lt;%= sort_header_tag(&quot;#{Attachment.table_name}.filename&quot;, :caption =&gt; l(:field_filename)) %&gt;
     &lt;%= sort_header_tag(&quot;#{Attachment.table_name}.created_on&quot;, :caption =&gt; l(:label_date), :default_order =&gt; 'desc') %&gt;
     &lt;%= sort_header_tag(&quot;#{Attachment.table_name}.filesize&quot;, :caption =&gt; l(:field_filesize), :default_order =&gt; 'desc') %&gt;
     &lt;%= sort_header_tag(&quot;#{Attachment.table_name}.downloads&quot;, :caption =&gt; l(:label_downloads_abbr), :default_order =&gt; 'desc') %&gt;
     &lt;th&gt;MD5&lt;/th&gt;
-    &lt;% if delete_allowed %&gt;&lt;th&gt;&lt;/th&gt;&lt;% end %&gt;
+    &lt;th&gt;&lt;/th&gt;
   &lt;/tr&gt;&lt;/thead&gt;
   &lt;tbody&gt;
-&lt;% for version in @versions %&gt;	
-  &lt;% unless version.attachments.empty? %&gt;
-  &lt;tr&gt;&lt;th colspan=&quot;7&quot; align=&quot;left&quot;&gt;&lt;span class=&quot;icon icon-package&quot;&gt;&lt;b&gt;&lt;%= version.name %&gt;&lt;/b&gt;&lt;/span&gt;&lt;/th&gt;&lt;/tr&gt;
-  &lt;% for file in version.attachments %&gt;		
+&lt;% @containers.each do |container| %&gt;	
+  &lt;% next if container.attachments.empty? -%&gt;
+	&lt;% if container.is_a?(Version) -%&gt;
+  &lt;tr&gt;&lt;th colspan=&quot;6&quot; align=&quot;left&quot;&gt;&lt;span class=&quot;icon icon-package&quot;&gt;&lt;b&gt;&lt;%=h container %&gt;&lt;/b&gt;&lt;/span&gt;&lt;/th&gt;&lt;/tr&gt;
+	&lt;% end -%&gt;
+  &lt;% container.attachments.each do |file| %&gt;		
   &lt;tr class=&quot;&lt;%= cycle(&quot;odd&quot;, &quot;even&quot;) %&gt;&quot;&gt;
-    &lt;td&gt;&lt;/td&gt;
     &lt;td&gt;&lt;%= link_to_attachment file, :download =&gt; true, :title =&gt; file.description %&gt;&lt;/td&gt;
     &lt;td align=&quot;center&quot;&gt;&lt;%= format_time(file.created_on) %&gt;&lt;/td&gt;
     &lt;td align=&quot;center&quot;&gt;&lt;%= number_to_human_size(file.filesize) %&gt;&lt;/td&gt;
     &lt;td align=&quot;center&quot;&gt;&lt;%= file.downloads %&gt;&lt;/td&gt;
     &lt;td align=&quot;center&quot;&gt;&lt;small&gt;&lt;%= file.digest %&gt;&lt;/small&gt;&lt;/td&gt;
-    &lt;% if delete_allowed %&gt;
     &lt;td align=&quot;center&quot;&gt;
-        &lt;%= link_to image_tag('delete.png'), {:controller =&gt; 'attachments', :action =&gt; 'destroy', :id =&gt; file},
-																						 :confirm =&gt; l(:text_are_you_sure), :method =&gt; :post %&gt;
+    &lt;%= link_to(image_tag('delete.png'), {:controller =&gt; 'attachments', :action =&gt; 'destroy', :id =&gt; file},
+																				 :confirm =&gt; l(:text_are_you_sure), :method =&gt; :post) if delete_allowed %&gt;
     &lt;/td&gt;
-    &lt;% end %&gt;
   &lt;/tr&gt;		
   &lt;% end
   reset_cycle %&gt;
-  &lt;% end %&gt;
 &lt;% end %&gt;
   &lt;/tbody&gt;
 &lt;/table&gt;</diff>
      <filename>app/views/projects/list_files.rhtml</filename>
    </modified>
    <modified>
      <diff>@@ -85,4 +85,28 @@ attachments_007:
   filename: archive.zip
   author_id: 1
   content_type: application/octet-stream
+attachments_008: 
+  created_on: 2006-07-19 21:07:27 +02:00
+  container_type: Project
+  container_id: 1
+  downloads: 0
+  disk_filename: 060719210727_project_file.zip
+  digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
+  id: 8
+  filesize: 320
+  filename: project_file.zip
+  author_id: 2
+  content_type: application/octet-stream
+attachments_009: 
+  created_on: 2006-07-19 21:07:27 +02:00
+  container_type: Version
+  container_id: 1
+  downloads: 0
+  disk_filename: 060719210727_version_file.zip
+  digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
+  id: 9
+  filesize: 452
+  filename: version_file.zip
+  author_id: 2
+  content_type: application/octet-stream
   
\ No newline at end of file</diff>
      <filename>test/fixtures/attachments.yml</filename>
    </modified>
    <modified>
      <diff>@@ -97,6 +97,23 @@ class AttachmentsControllerTest &lt; Test::Unit::TestCase
     @request.session[:user_id] = 2
     assert_difference 'Attachment.count', -1 do
       post :destroy, :id =&gt; 3
+      assert_response 302
+    end
+  end
+  
+  def test_destroy_project_attachment
+    @request.session[:user_id] = 2
+    assert_difference 'Attachment.count', -1 do
+      post :destroy, :id =&gt; 8
+      assert_response 302
+    end
+  end
+  
+  def test_destroy_version_attachment
+    @request.session[:user_id] = 2
+    assert_difference 'Attachment.count', -1 do
+      post :destroy, :id =&gt; 9
+      assert_response 302
     end
   end
   </diff>
      <filename>test/functional/attachments_controller_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -23,7 +23,8 @@ class ProjectsController; def rescue_action(e) raise e end; end
 
 class ProjectsControllerTest &lt; Test::Unit::TestCase
   fixtures :projects, :versions, :users, :roles, :members, :issues, :journals, :journal_details,
-           :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages
+           :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages,
+           :attachments
 
   def setup
     @controller = ProjectsController.new
@@ -112,12 +113,56 @@ class ProjectsControllerTest &lt; Test::Unit::TestCase
     assert_redirected_to 'admin/projects'
     assert_nil Project.find_by_id(1)
   end
+  
+  def test_add_file
+    set_tmp_attachments_directory
+    @request.session[:user_id] = 2
+    Setting.notified_events &lt;&lt; 'file_added'
+    ActionMailer::Base.deliveries.clear
+    
+    assert_difference 'Attachment.count' do
+      post :add_file, :id =&gt; 1, :version_id =&gt; '',
+           :attachments =&gt; {'1' =&gt; {'file' =&gt; test_uploaded_file('testfile.txt', 'text/plain')}}
+    end
+    assert_redirected_to 'projects/list_files/ecookbook'
+    a = Attachment.find(:first, :order =&gt; 'created_on DESC')
+    assert_equal 'testfile.txt', a.filename
+    assert_equal Project.find(1), a.container
+
+    mail = ActionMailer::Base.deliveries.last
+    assert_kind_of TMail::Mail, mail
+    assert_equal &quot;[eCookbook] New file&quot;, mail.subject
+    assert mail.body.include?('testfile.txt')
+  end
+  
+  def test_add_version_file
+    set_tmp_attachments_directory
+    @request.session[:user_id] = 2
+    Setting.notified_events &lt;&lt; 'file_added'
+    
+    assert_difference 'Attachment.count' do
+      post :add_file, :id =&gt; 1, :version_id =&gt; '2',
+           :attachments =&gt; {'1' =&gt; {'file' =&gt; test_uploaded_file('testfile.txt', 'text/plain')}}
+    end
+    assert_redirected_to 'projects/list_files/ecookbook'
+    a = Attachment.find(:first, :order =&gt; 'created_on DESC')
+    assert_equal 'testfile.txt', a.filename
+    assert_equal Version.find(2), a.container
+  end
   
   def test_list_files
     get :list_files, :id =&gt; 1
     assert_response :success
     assert_template 'list_files'
-    assert_not_nil assigns(:versions)
+    assert_not_nil assigns(:containers)
+    
+    # file attached to the project
+    assert_tag :a, :content =&gt; 'project_file.zip',
+                   :attributes =&gt; { :href =&gt; '/attachments/download/8/project_file.zip' }
+    
+    # file attached to a project's version
+    assert_tag :a, :content =&gt; 'version_file.zip',
+                   :attributes =&gt; { :href =&gt; '/attachments/download/9/version_file.zip' }
   end
 
   def test_changelog</diff>
      <filename>test/functional/projects_controller_test.rb</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>c8fac31f1d4b3417f3fb9abf155e659ac2ed114f</id>
    </parent>
  </parents>
  <author>
    <name>jplang</name>
    <email>jplang@e93f8b46-1217-0410-a6f0-8f06a7374b81</email>
  </author>
  <url>http://github.com/yugui/redmine4ruby-lang/commit/50f5aba64558a69c061b394d02c1972f705cd128</url>
  <id>50f5aba64558a69c061b394d02c1972f705cd128</id>
  <committed-date>2008-12-09T10:00:27-08:00</committed-date>
  <authored-date>2008-12-09T10:00:27-08:00</authored-date>
  <message>Files module: makes version field non required (#1053).

git-svn-id: http://redmine.rubyforge.org/svn/trunk@2117 e93f8b46-1217-0410-a6f0-8f06a7374b81</message>
  <tree>0be90ee0f69c66e70ca32a6635b782eabd1bca20</tree>
  <committer>
    <name>jplang</name>
    <email>jplang@e93f8b46-1217-0410-a6f0-8f06a7374b81</email>
  </committer>
</commit>
