diff --git a/app/controllers/plans_controller.rb b/app/controllers/plans_controller.rb index 526f76a..d431436 100644 --- a/app/controllers/plans_controller.rb +++ b/app/controllers/plans_controller.rb @@ -16,14 +16,14 @@ def new @plan = Plan.new authorize @plan - + # Get all of the available funders and non-funder orgs @funders = Org.funders.sort{|x,y| x.name <=> y.name } @orgs = (Org.institutions + Org.managing_orgs).flatten.uniq.sort{|x,y| x.name <=> y.name } - + # Get the current user's org @default_org = current_user.org if @orgs.include?(current_user.org) - + respond_to :html end @@ -32,42 +32,42 @@ def create @plan = Plan.new authorize @plan - + @plan.principal_investigator = current_user.surname.blank? ? nil : "#{current_user.firstname} #{current_user.surname}" @plan.data_contact = current_user.email @plan.funder_name = plan_params[:funder_name] - + # If a template hasn't been identified look for the available templates if plan_params[:template_id].blank? template_options(plan_params[:org_id], plan_params[:funder_id]) # Return the 'Select a template' section respond_to do |format| - format.js {} + format.js {} end - + # Otherwise create the plan else @plan.template = Template.find(plan_params[:template_id]) - + if plan_params[:title].blank? - @plan.title = current_user.firstname.blank? ? _('My Plan') + '(' + @plan.template.title + ')' : + @plan.title = current_user.firstname.blank? ? _('My Plan') + '(' + @plan.template.title + ')' : current_user.firstname + "'s" + _(" Plan") else @plan.title = plan_params[:title] end - + if @plan.save @plan.assign_creator(current_user) - + default = Template.find_by(is_default: true) - + msg = "#{_('Plan was successfully created.')} " - + if !default.nil? && default == @plan.template # We used the generic/default template msg += _('This plan is based on the default template.') - + elsif !@plan.template.customization_of.nil? # We used a customized version of the the funder template msg += "#{_('This plan is based on the')} #{plan_params[:funder_name]} #{_('template with customisations by the')} #{plan_params[:org_name]}" @@ -76,9 +76,9 @@ # We used the specified org's or funder's template msg += "#{_('This plan is based on the')} #{@plan.template.org.name} template." end - + flash[:notice] = msg - + respond_to do |format| format.js { render js: "window.location='#{plan_url(@plan)}?editing=true'" } end @@ -87,7 +87,7 @@ # Something went wrong so report the issue to the user flash[:notice] = failed_create_error(@plan, 'Plan') respond_to do |format| - format.js {} + format.js {} end end end @@ -99,6 +99,7 @@ def show @plan = Plan.eager_load(params[:id]) authorize @plan + @editing = (!params[:editing].nil? && @plan.administerable_by?(current_user.id)) # Get all Guidance Groups applicable for the plan and group them by org @@ -109,8 +110,8 @@ @important_ggs = [] @important_ggs << [current_user.org, @all_ggs_grouped_by_org.delete(current_user.org)] @all_ggs_grouped_by_org.each do |org, ggs| - if org.organisation? - @important_ggs << [org,ggs] + if org.organisation? + @important_ggs << [org,ggs] @all_ggs_grouped_by_org.delete(org) end end @@ -377,11 +378,26 @@ end end + def duplicate + plan = Plan.find(params[:id]) + authorize plan + @plan = Plan.deep_copy(plan) + respond_to do |format| + if @plan.save + @plan.assign_creator(current_user) + format.html { redirect_to @plan, notice: _('Plan was successfully duplicated.') } + format.json { head :no_content } + else + flash[:notice] = failed_update_error(@plan, _('plan')) + format.html { render action: "edit" } + end + end + end private - def plan_params + def plan_params params.require(:plan).permit(:org_id, :org_name, :funder_id, :funder_name, :template_id, :title) end @@ -452,7 +468,7 @@ # -------------------------------------------------------------------------- def template_options(org_id, funder_id) @templates = [] - + if !org_id.blank? || !funder_id.blank? if funder_id.blank? # Load the org's template(s) @@ -461,15 +477,15 @@ @templates = Template.where(published: true, org: org, customization_of: nil).to_a @msg = _("We found multiple DMP templates corresponding to the research organisation.") if @templates.count > 1 end - + else funder = Org.find(funder_id) # Load the funder's template(s) @templates = Template.where(published: true, org: funder).to_a - + unless org_id.blank? org = Org.find(org_id) - + # Swap out any organisational cusotmizations of a funder template @templates.each do |tmplt| customization = Template.find_by(published: true, org: org, customization_of: tmplt.dmptemplate_id) @@ -479,17 +495,17 @@ end end end - + msg = _("We found multiple DMP templates corresponding to the funder.") if @templates.count > 1 end end - + # If no templates were available use the generic templates if @templates.empty? @msg = _("Using the generic Data Management Plan") @templates << Template.find_by(is_default: true) end - + @templates = @templates.sort{|x,y| x.title <=> y.title } if @templates.count > 1 end diff --git a/app/models/answer.rb b/app/models/answer.rb index 54ac937..04b26e5 100644 --- a/app/models/answer.rb +++ b/app/models/answer.rb @@ -1,5 +1,5 @@ class Answer < ActiveRecord::Base - + ## # Associations belongs_to :question @@ -13,27 +13,40 @@ ## # Possibly needed for active_admin # -relies on protected_attributes gem as syntax depricated in rails 4.2 - attr_accessible :text, :plan_id, :lock_version, :question_id, :user_id, :question_option_ids, + attr_accessible :text, :plan_id, :lock_version, :question_id, :user_id, :question_option_ids, :question, :user, :plan, :question_options, :notes, :note_ids, :id, :as => [:default, :admin] ## # Validations # validates :user, :plan, :question, presence: true -# +# # # Make sure there is only one answer per question! -# validates :question, uniqueness: {scope: [:plan], +# validates :question, uniqueness: {scope: [:plan], # message: I18n.t('helpers.answer.only_one_per_question')} -# +# # # The answer MUST have a text value if the question is NOT option based or a question_option if -# # it is option based. -# validates :text, presence: true, if: Proc.new{|a| +# # it is option based. +# validates :text, presence: true, if: Proc.new{|a| # (a.question.nil? ? false : !a.question.question_format.option_based?) # } -# validates :question_options, presence: true, if: Proc.new{|a| +# validates :question_options, presence: true, if: Proc.new{|a| # (a.question.nil? ? false : a.question.question_format.option_based?) # } -# +# # # Make sure the plan and question are associated with the same template! # validates :plan, :question, answer_for_correct_template: true + + + + ## + # deep copy the given answer + # + # @params [Answer] question_option to be deep copied + # @return [Answer] the saved, copied answer + def self.deep_copy(answer) + answer_copy = answer.dup + answer_copy.save! + return answer_copy + end end diff --git a/app/models/guidance.rb b/app/models/guidance.rb index 8ba321b..ca9ab48 100644 --- a/app/models/guidance.rb +++ b/app/models/guidance.rb @@ -129,5 +129,4 @@ # pass the list of viewable guidances to the view return all_viewable_guidances.flatten end - end diff --git a/app/models/guidance_group.rb b/app/models/guidance_group.rb index 892a47e..79f4ec4 100644 --- a/app/models/guidance_group.rb +++ b/app/models/guidance_group.rb @@ -7,7 +7,7 @@ has_and_belongs_to_many :plans, join_table: :plans_guidance_groups # depricated but needed for migration "single_group_for_guidance" # has_and_belongs_to_many :guidances, join_table: "guidance_in_group" - + ## # Possibly needed for active_admin @@ -25,7 +25,7 @@ - + ## # Converts the current guidance group to a string containing the display name. @@ -49,7 +49,7 @@ # @return [Array] a list of guidance groups def self.guidance_groups_excluding(excluded_orgs) excluded_org_ids = Array.new - + if excluded_orgs.is_a?(Array) excluded_orgs.each do |org| excluded_org_ids << org.id @@ -57,7 +57,7 @@ else excluded_org_ids << excluded_orgs end - + return_orgs = GuidanceGroup.where("org_id NOT IN (?)", excluded_org_ids) return return_orgs end diff --git a/app/models/org.rb b/app/models/org.rb index e11d5e1..22c5f98 100644 --- a/app/models/org.rb +++ b/app/models/org.rb @@ -11,7 +11,7 @@ has_many :templates has_many :users has_many :suggested_answers - + has_and_belongs_to_many :token_permission_types, join_table: "org_token_permissions", unique: true ## @@ -20,7 +20,7 @@ # attr_accessible :abbreviation, :banner_text, :logo, :remove_logo, # :logo_file_name, :name, :target_url, # :organisation_type_id, :wayfless_entity, :parent_id, :sort_name, -# :token_permission_type_ids, :language_id, :contact_email, +# :token_permission_type_ids, :language_id, :contact_email, # :language, :org_type, :region, :token_permission_types ## @@ -57,7 +57,7 @@ # What do they do? do they do it efficiently, and do we need them? # Determines the locale set for the organisation - # @return String or nil + # @return String or nil def get_locale if !self.language.nil? return self.language.abbreviation @@ -128,7 +128,7 @@ end return children end - + ## # returns a list of all guidance groups belonging to other organisations # @@ -163,7 +163,7 @@ end return organisations_list end - + ## # returns a list of all sections of a given version from this organisation and it's parents # @@ -180,7 +180,7 @@ return sections.where("version_id = ? ", version_id).all + parent.all_sections(version_id) end end - + ## # returns the guidance groups of this organisation and all of it's children # @@ -192,7 +192,7 @@ end return ggs end - + ## # returns the highest parent organisation in the tree # @@ -205,7 +205,7 @@ end end =end - + ## # returns all published templates belonging to the organisation # @@ -222,7 +222,7 @@ end end end - + private ## # checks size of logo and resizes if necessary @@ -233,5 +233,5 @@ self.logo = logo.thumb('x100') # resize height and maintain aspect ratio end end - end + end end diff --git a/app/models/plan.rb b/app/models/plan.rb index 8c8d3c7..324c1af 100644 --- a/app/models/plan.rb +++ b/app/models/plan.rb @@ -28,7 +28,7 @@ ## # Possibly needed for active_admin # -relies on protected_attributes gem as syntax depricated in rails 4.2 - attr_accessible :locked, :project_id, :version_id, :version, :plan_sections, + attr_accessible :locked, :project_id, :version_id, :version, :plan_sections, :exported_plans, :project, :title, :template, :grant_number, :identifier, :principal_investigator, :principal_investigator_identifier, :description, :data_contact, :funder_name, :visibility, :exported_plans, @@ -38,7 +38,7 @@ # public is a Ruby keyword so using publicly enum visibility: [:organisationally_visible, :publicly_visible, :is_test, :privately_visible] - #TODO: work out why this messes up plan creation : + #TODO: work out why this messes up plan creation : # briley: Removed reliance on :users, its really on :roles (shouldn't have a plan without at least a creator right?) It should be ok like this though now # validates :template, :title, presence: true @@ -208,7 +208,7 @@ end end end - + # Get guidance by theme from any guidance groups currently selected self.guidance_groups.each do |group| group.guidances.each do |guidance| @@ -367,7 +367,7 @@ format = rec.qformat answer = nil - if qa_map.has_key?(qid) + if qa_map.has_key?(qid) answer = qa_map[qid] end @@ -421,7 +421,7 @@ opt_hash[aid] << optid end - status["questions"].each_key do |questionid| + status["questions"].each_key do |questionid| answerid = status["questions"][questionid]["answer_id"] status["questions"][questionid]["answer_option_ids"] = opt_hash[answerid] end @@ -634,10 +634,10 @@ user_id = user_id.id if user_id.is_a?(User) add_user(user_id, true, true, true) end - -# TODO: commenting these out because they are overriden by private methods below, so this + +# TODO: commenting these out because they are overriden by private methods below, so this # is unreachable =begin ## @@ -694,7 +694,7 @@ end =end -# TODO: What are these used for? Should just be using self.org and self.org.funder? +# TODO: What are these used for? Should just be using self.org and self.org.funder? =begin ## # sets a new funder for the project @@ -948,7 +948,7 @@ self.dmptemplate.try(:organisation).try(:abbreviation) end =end - + # the following two methods are for eager loading. One gets used for the plan/show @@ -979,6 +979,26 @@ ]).find(id) end + # deep copy the given plan and all of it's associations + # + # @params [Plan] plan to be deep copied + # @return [Plan] saved copied plan + def self.deep_copy(plan) + plan_copy = plan.dup + plan_copy.title = "Copy of " + plan.title + plan_copy.save! + plan.answers.each do |answer| + answer_copy = Answer.deep_copy(answer) + answer_copy.plan_id = plan_copy.id + answer_copy.save! + end + plan.guidance_groups.each do |guidance_group| + if guidance_group.present? + plan_copy.guidance_groups << GuidanceGroup.where(id: guidance_group.id).first + end + end + return plan_copy + end private @@ -1026,12 +1046,12 @@ end role.save - - # This is necessary because we're creating the associated record but not assigning it + + # This is necessary because we're creating the associated record but not assigning it # to roles. Auto-saving like this may be confusing when coding upstream in a controller, - # view or api. Should probably change this to: + # view or api. Should probably change this to: # self.roles << role - # and then let the save be called manually via: + # and then let the save be called manually via: # plan.save! #self.reload end @@ -1111,7 +1131,7 @@ # -------------------------------------------------------- def set_creation_defaults # Only run this before_validation because rails fires this before save/create - if self.id.nil? + if self.id.nil? self.title = "My plan (#{self.template.title})" if self.title.nil? && !self.template.nil? self.visibility = 1 end diff --git a/app/policies/plan_policy.rb b/app/policies/plan_policy.rb index d6c8703..173b284 100644 --- a/app/policies/plan_policy.rb +++ b/app/policies/plan_policy.rb @@ -7,7 +7,7 @@ @user = user @plan = plan end - + def show? @plan.readable_by?(@user.id) end @@ -39,15 +39,19 @@ def destroy? @plan.editable_by?(@user.id) end - + def status? @plan.readable_by?(@user.id) end - + def possible_templates? @plan.id.nil? end + def duplicate? + @plan.editable_by?(@user.id) + end + # TODO: These routes are no lonmger used =begin def section_answers? diff --git a/app/views/plans/_plan_list_item.html.erb b/app/views/plans/_plan_list_item.html.erb index ad1dff2..64a6e99 100644 --- a/app/views/plans/_plan_list_item.html.erb +++ b/app/views/plans/_plan_list_item.html.erb @@ -12,6 +12,8 @@ <%= link_to _('Export'), show_export_plan_path(plan), :class => "dmp_table_link"%> + <%= link_to _('Duplicate'), duplicate_plan_path(plan), method: :post, :class => "dmp_table_link"%> + <% if plan.administerable_by?(current_user.id) then %> <%= link_to _('Delete'), plan_path(plan), :class => "dmp_table_link", :method => :delete, :data => { diff --git a/config/routes.rb b/config/routes.rb index 1f4d9a1..5f82f75 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -31,26 +31,26 @@ end devise_for :users, controllers: { - registrations: "registrations", - passwords: 'passwords', - sessions: 'sessions', + registrations: "registrations", + passwords: 'passwords', + sessions: 'sessions', omniauth_callbacks: 'users/omniauth_callbacks'} do - + get "/users/sign_out", :to => "devise/sessions#destroy" end - + # WAYFless access point - use query param idp #get 'auth/shibboleth' => 'users/omniauth_shibboleth_request#redirect', :as => 'user_omniauth_shibboleth' #get 'auth/shibboleth/assoc' => 'users/omniauth_shibboleth_request#associate', :as => 'user_shibboleth_assoc' #post '/auth/:provider/callback' => 'sessions#oauth_create' - + # fix for activeadmin signout bug devise_scope :user do get '/users/sign_out' => 'devise/sessions#destroy' end delete '/users/identifiers/:id', to: 'user_identifiers#destroy', as: 'destroy_user_identifier' - + #ActiveAdmin.routes(self) #organisation admin area @@ -77,10 +77,10 @@ get "public_plans" => 'static_pages#public_plans' get "public_export/:id" => 'static_pages#public_export', as: 'public_export' get "existing_users" => 'existing_users#index' - + #post 'contact_form' => 'contacts', as: 'localized_contact_creation' #get 'contact_form' => 'contacts#new', as: 'localized_contact_form' - + resources :orgs, :path => 'org/admin', only: [] do member do get 'children' @@ -200,6 +200,7 @@ get 'section_answers' get 'share' get 'show_export' + post 'duplicate' get 'export' post 'invite' end