Skip to content

Commit 33944dd

Browse files
committed
feat: remove stored pdf path and allow move to archive
- refactor to allow different file roots - remove need for portfolio evidence attribute, while still working with existing values
1 parent b45bdf6 commit 33944dd

20 files changed

+403
-103
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Doubtfire requires multiple environment variables that help define settings abou
3232
| `DF_AUTH_METHOD` | The authentication method you would like Doubtfire to use. Possible values are `database` for standard authentication with the database, `ldap` | `database` |
3333
| | for [LDAP](https://www.freebsd.org/doc/en/articles/ldap-auth/), `aaf` for [AAF Rapid Connect](https://rapid.aaf.edu.au/), or `SAML2` for [SAML2.0 auth](https://en.wikipedia.org/wiki/SAML_2.0). | |
3434
| `DF_STUDENT_WORK_DIR` | The directory to store uploaded student work for processing. | `student_work` |
35+
| `DF_ARCHIVE_DIR` | The directory to move archived unit files to, and access from. | `DF_STUDENT_WORK_DIR/archive` |
3536
| `DF_INSTITUTION_NAME` | The name of your institution running Doubtfire. | _University of Foo_ |
3637
| `DF_INSTITUTION_EMAIL_DOMAIN` | The email domain from which emails are sent to and from in your institution. | `doubtfire.com` |
3738
| `DF_INSTITUTION_HOST` | The host running the Doubtfire instance. | `localhost:3000` |

app/api/submission/portfolio_evidence_api.rb

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ def self.logger
7676
optional :as_attachment, type: Boolean, desc: 'Whether or not to download file as attachment. Default is false.'
7777
end
7878
get '/projects/:id/task_def_id/:task_definition_id/submission' do
79-
project = Project.find(params[:id])
80-
task_definition = project.unit.task_definitions.find(params[:task_definition_id])
79+
project = Project.eager_load(:unit).find(params[:id])
80+
task_definition = project.unit.task_definitions.select(:id, :name, :abbreviation).find(params[:task_definition_id])
8181

8282
# check the user can put this task
8383
unless authorise? current_user, project, :get_submission
@@ -86,14 +86,12 @@ def self.logger
8686

8787
task = project.task_for_task_definition(task_definition)
8888

89-
evidence_loc = task.portfolio_evidence_path
90-
student = task.project.student
91-
unit = task.project.unit
89+
evidence_loc = task.final_pdf_path
9290

9391
if task.processing_pdf?
9492
evidence_loc = Rails.root.join('public/resources/AwaitingProcessing.pdf')
9593
filename = 'AwaitingProcessing.pdf'
96-
elsif evidence_loc.nil?
94+
elsif evidence_loc.nil? || !File.exist?(evidence_loc)
9795
evidence_loc = Rails.root.join('public/resources/FileNotFound.pdf')
9896
filename = 'FileNotFound.pdf'
9997
else

app/helpers/file_helper.rb

Lines changed: 58 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,7 @@ def sanitized_filename(filename)
125125
end
126126

127127
def task_file_dir_for_unit(unit, create = true)
128-
file_server = Doubtfire::Application.config.student_work_dir
129-
dst = "#{file_server}/" # trust the server config and passed in type for paths
130-
dst << sanitized_path("#{unit.code}-#{unit.id}", 'TaskFiles') << '/'
128+
dst = unit_work_root(unit) << 'TaskFiles/'
131129

132130
FileUtils.mkdir_p dst if create && (!Dir.exist? dst)
133131

@@ -176,6 +174,30 @@ def student_work_root
176174
Doubtfire::Application.config.student_work_dir
177175
end
178176

177+
def archive_root
178+
Doubtfire::Application.config.archive_dir
179+
end
180+
181+
# Get the path to the unit root - will take into consideration if archived
182+
#
183+
# @param [Unit] unit - the unit to get the root path for
184+
# @param [Boolean] archived - whether to use the archived property (true/false)
185+
# or force it to be the archived path (:force)
186+
def unit_work_root(unit, archived: true)
187+
dst = if (unit.archived && archived) || (archived == :force)
188+
"#{archive_root}/"
189+
else
190+
"#{student_work_root}/"
191+
end
192+
193+
dst << sanitized_path("#{unit.code}-#{unit.id}") << '/'
194+
end
195+
196+
def project_work_root(project, archived: true, username: nil)
197+
username = project.student.username.to_s if username.nil?
198+
unit_work_root(project.unit, archived: archived) << sanitized_path(username) << '/'
199+
end
200+
179201
#
180202
# Generates a path for storing student work
181203
# type = [:new, :in_process, :done, :pdf, :plagarism]
@@ -187,18 +209,17 @@ def student_work_dir(type = nil, task = nil, create = true)
187209
file_server = Doubtfire::Application.config.student_work_dir
188210
dst = "#{file_server}/" # trust the server config and passed in type for paths
189211

190-
if !(type.nil? || task.nil?)
212+
if !(type.nil? || task.nil?) # we have task and type
191213
if [:discussion, :pdf, :comment].include? type
192-
dst << sanitized_path("#{task.project.unit.code}-#{task.project.unit.id}", task.project.student.username.to_s, type.to_s) << '/'
214+
dst = project_work_root(task.project) << sanitized_path(type.to_s) << '/'
193215
elsif [:done, :plagarism].include? type
194-
dst << sanitized_path("#{task.project.unit.code}-#{task.project.unit.id}", task.project.student.username.to_s, type.to_s, task.id.to_s) << '/'
216+
dst = project_work_root(task.project) << sanitized_path(type.to_s, task.id.to_s) << '/'
195217
else # new and in_process -- just have task id
196218
# Add task id to dst if we want task
197219
dst << "#{type}/#{task.id}/"
198220
end
199-
elsif !type.nil?
221+
elsif !type.nil? # have type but not task
200222
if [:in_process, :new].include? type
201-
# Add task id to dst if we want task
202223
dst << "#{type}/"
203224
else
204225
raise 'Error in request to student work directory'
@@ -211,27 +232,40 @@ def student_work_dir(type = nil, task = nil, create = true)
211232
dst
212233
end
213234

214-
def dir_for_unit_code_and_id(unit_code, unit_id, create = true)
215-
file_server = Doubtfire::Application.config.student_work_dir
216-
dst = "#{file_server}/" # trust the server config and passed in type for paths
235+
def dir_for_unit_code_and_id(unit_code, unit_id, create: true, archived: false)
236+
dst = if archived
237+
"#{archive_root}/"
238+
else
239+
"#{student_work_root}/"
240+
end
241+
217242
dst << sanitized_path("#{unit_code}-#{unit_id}")
218243

219244
FileUtils.mkdir_p dst if create && !Dir.exist?(dst)
220245

221246
dst
222247
end
223248

224-
def unit_dir(unit, create = true)
225-
dir_for_unit_code_and_id(unit.code, unit.id, create)
249+
def unit_dir(unit, create: true, archived: true)
250+
dir_for_unit_code_and_id(unit.code, unit.id, create: create, archived: archived == :force || (archived && unit.archived))
226251
end
227252

228-
def root_portfolio_dir
229-
file_server = Doubtfire::Application.config.student_work_dir
253+
def root_portfolio_dir(archived: false)
254+
file_server = if archived
255+
archive_root
256+
else
257+
student_work_root
258+
end
259+
230260
"#{file_server}/portfolio/" # trust the server config and passed in type for paths
231261
end
232262

233-
def unit_portfolio_dir(unit, create = true)
234-
dst = root_portfolio_dir
263+
def unit_portfolio_dir(unit, create: true, archived: true)
264+
dst = if (unit.archived && archived) || (archived == :force)
265+
"#{archive_root}/portfolio/"
266+
else
267+
"#{student_work_root}/portfolio/"
268+
end
235269

236270
dst << sanitized_path("#{unit.code}-#{unit.id}") << '/'
237271

@@ -243,8 +277,8 @@ def unit_portfolio_dir(unit, create = true)
243277
#
244278
# Generates a path for storing student portfolios
245279
#
246-
def student_portfolio_dir(unit, username, create = true)
247-
dst = unit_portfolio_dir(unit, create)
280+
def student_portfolio_dir(unit, username, create: true, archived: true)
281+
dst = unit_portfolio_dir(unit, create: create, archived: archived)
248282

249283
dst << sanitized_path(username.to_s)
250284

@@ -253,8 +287,8 @@ def student_portfolio_dir(unit, username, create = true)
253287
dst
254288
end
255289

256-
def student_portfolio_path(unit, username, create = true)
257-
File.join(student_portfolio_dir(unit, username, create), FileHelper.sanitized_filename("#{username}-portfolio.pdf"))
290+
def student_portfolio_path(unit, username, create: true, archived: true)
291+
File.join(student_portfolio_dir(unit, username, create: create, archived: archived), FileHelper.sanitized_filename("#{username}-portfolio.pdf"))
258292
end
259293

260294
def comment_attachment_path(task_comment, attachment_extension)
@@ -679,10 +713,13 @@ def line_wrap(path, width: 160)
679713
module_function :student_group_work_dir
680714
module_function :student_work_dir
681715
module_function :student_work_root
716+
module_function :archive_root
682717
module_function :dir_for_unit_code_and_id
683718
module_function :unit_dir
684719
module_function :root_portfolio_dir
685720
module_function :unit_portfolio_dir
721+
module_function :unit_work_root
722+
module_function :project_work_root
686723
module_function :student_portfolio_dir
687724
module_function :student_portfolio_path
688725
module_function :comment_attachment_path

app/mailers/d2l_result_mailer.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
class D2lResultMailer < ApplicationMailer
2-
def result_message(unit, user, result_message = 'completed', success = true)
2+
def result_message(unit, user, result_message: 'completed', success: true)
33
email = user.email
44
return nil if email.blank?
55

app/models/group_submission.rb

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,9 @@ class GroupSubmission < ApplicationRecord
1818
FileHelper.delete_group_submission(group_submission)
1919

2020
# also remove evidence from group members
21-
tasks.each do |t|
22-
t.portfolio_evidence_path = nil
23-
t.save
24-
end
21+
# rubocop:disable Rails/SkipsModelValidations
22+
tasks.where('portfolio_evidence IS NOT NULL').update_all(portfolio_evidence: nil)
23+
# rubocop:enable Rails/SkipsModelValidations
2524
rescue => e
2625
logger.error "Failed to delete group submission #{group_submission.id}. Error: #{e.message}"
2726
end

app/models/pdf_generation/project_compile_portfolio_module.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,8 @@ def learning_summary_report_exists?
177177
# Portfolio production code
178178
#
179179
def portfolio_temp_path
180-
portfolio_dir = FileHelper.student_portfolio_dir(self.unit, self.student.username, false)
181-
portfolio_tmp_dir = File.join(portfolio_dir, 'tmp')
180+
portfolio_dir = FileHelper.student_portfolio_dir(self.unit, self.student.username, create: false)
181+
File.join(portfolio_dir, 'tmp')
182182
end
183183

184184
def portfolio_tmp_file_name(dict)
@@ -270,7 +270,7 @@ def remove_portfolio_file(idx, kind, name)
270270
end
271271

272272
def portfolio_path
273-
FileHelper.student_portfolio_path(self.unit, self.student.username, true)
273+
FileHelper.student_portfolio_path(self.unit, self.student.username, create: true)
274274
end
275275

276276
def portfolio_exists?

app/models/task.rb

Lines changed: 66 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,7 @@ def status
431431
end
432432

433433
def has_pdf
434-
!portfolio_evidence_path.nil? && File.exist?(portfolio_evidence_path) && !processing_pdf?
434+
!final_pdf_path.nil? && File.exist?(final_pdf_path) && !processing_pdf?
435435
end
436436

437437
def log_details
@@ -1003,6 +1003,17 @@ def move_files_to_in_process(source_folder = FileHelper.student_work_dir(:new))
10031003
end
10041004
end
10051005

1006+
def move_files_on_abbreviation_change(old_abbreviation)
1007+
# Move files from old abbreviation to new abbreviation
1008+
old_path = final_pdf_path(abbr: old_abbreviation)
1009+
new_path = final_pdf_path(ignore_portfolio_evidence: true)
1010+
1011+
return if old_path == new_path || !File.exist?(old_path)
1012+
1013+
FileUtils.mv(old_path, new_path)
1014+
update(portfolio_evidence: nil) unless portfolio_evidence.nil?
1015+
end
1016+
10061017
def __output_filename__(in_dir, idx, type)
10071018
pwd = FileUtils.pwd
10081019
Dir.chdir(in_dir)
@@ -1117,28 +1128,56 @@ def self.pygments_lang(extn)
11171128
end
11181129
end
11191130

1120-
def portfolio_evidence_path
1121-
# Add the student work dir to the start of the portfolio evidence
1122-
File.join(FileHelper.student_work_dir, self.portfolio_evidence) if self.portfolio_evidence.present?
1131+
def move_to_final_pdf_path
1132+
if portfolio_evidence.present?
1133+
# Move the portfolio evidence to the final pdf path
1134+
if File.exist?(portfolio_evidence_path)
1135+
new_path = final_pdf_path(ignore_portfolio_evidence: true)
1136+
FileUtils.mv(portfolio_evidence_path, new_path)
1137+
end
1138+
update(portfolio_evidence: nil)
1139+
end
11231140
end
11241141

1125-
def portfolio_evidence_path=(value)
1126-
# Strip the student work directory to store in database as relative path
1127-
self.portfolio_evidence = value.present? ? value.sub(FileHelper.student_work_dir, '') : nil
1142+
def portfolio_evidence_path
1143+
# Add the student work dir to the start of the portfolio evidence
1144+
if unit.archived
1145+
base = FileHelper.archive_root
1146+
else
1147+
base = FileHelper.student_work_dir
1148+
end
1149+
File.join(base, self.portfolio_evidence) if self.portfolio_evidence.present?
11281150
end
11291151

11301152
# The path to the PDF for this task's submission
1131-
def final_pdf_path
1132-
if group_task?
1133-
return nil if group_submission.nil? || group_submission.task_definition.nil?
1134-
1135-
File.join(
1136-
FileHelper.student_group_work_dir(:pdf, group_submission, task = nil, create = true),
1137-
FileHelper.sanitized_filename(FileHelper.sanitized_path("#{group_submission.task_definition.abbreviation}-#{group_submission.id}") + '.pdf')
1138-
)
1139-
else
1140-
File.join(student_work_dir(:pdf), FileHelper.sanitized_filename(FileHelper.sanitized_path("#{task_definition.abbreviation}-#{id}") + '.pdf'))
1153+
def final_pdf_path(abbr: nil, ignore_portfolio_evidence: false)
1154+
result = if group_task?
1155+
return nil if group_submission.nil? || group_submission.task_definition.nil?
1156+
1157+
abbr = group_submission.task_definition.abbreviation if abbr.nil?
1158+
1159+
File.join(
1160+
FileHelper.student_group_work_dir(:pdf, group_submission, task = nil, create = true),
1161+
FileHelper.sanitized_filename(FileHelper.sanitized_path("#{abbr}-#{group_submission.id}") + '.pdf')
1162+
)
1163+
else
1164+
abbr = task_definition.abbreviation if abbr.nil?
1165+
File.join(student_work_dir(:pdf), FileHelper.sanitized_filename(FileHelper.sanitized_path("#{abbr}-#{id}") + '.pdf'))
1166+
end
1167+
1168+
# see if we need to use the portfolio evidence
1169+
if portfolio_evidence.present? && !ignore_portfolio_evidence
1170+
evidence_loc = portfolio_evidence_path
1171+
1172+
# Remove portfolio evidence if possible
1173+
if evidence_loc == result || !File.exist?(evidence_loc)
1174+
update(portfolio_evidence: nil)
1175+
else
1176+
result = evidence_loc
1177+
end
11411178
end
1179+
1180+
result
11421181
end
11431182

11441183
# A custom error to capture the log message from the latex error
@@ -1202,31 +1241,20 @@ def convert_submission_to_pdf(source_folder: FileHelper.student_work_dir(:new),
12021241
end
12031242
end
12041243

1205-
# save the final pdf path to portfolio evidence - relative to student work folder
1206-
if group_task?
1207-
group_submission.tasks.each do |t|
1208-
t.portfolio_evidence_path = final_pdf_path
1209-
t.save
1210-
end
1211-
reload
1212-
else
1213-
self.portfolio_evidence_path = final_pdf_path
1214-
end
1215-
12161244
# Save the file... now using the full path!
1217-
File.open(portfolio_evidence_path, 'w') do |fout|
1245+
File.open(final_pdf_path, 'w') do |fout|
12181246
fout.puts pdf_text
12191247
end
12201248

1221-
FileHelper.compress_pdf(portfolio_evidence_path)
1249+
FileHelper.compress_pdf(final_pdf_path)
12221250

12231251
logger.info("PDF created for task #{self.id}")
12241252

12251253
# if the task is the draft learning summary task
12261254
if task_definition_id == unit.draft_task_definition_id
12271255
# if there is a learning summary, execute, if there isn't and a learning summary exists, don't execute
12281256
if project.uses_draft_learning_summary || !project.learning_summary_report_exists?
1229-
project.save_as_learning_summary_report portfolio_evidence_path
1257+
project.save_as_learning_summary_report final_pdf_path
12301258
end
12311259
end
12321260

@@ -1371,7 +1399,10 @@ def accept_submission(current_user, files, ui, contributions, trigger, alignment
13711399
#
13721400
# Set portfolio_evidence_path to nil while it gets processed
13731401
#
1374-
self.portfolio_evidence_path = nil
1402+
if portfolio_evidence.present?
1403+
FileUtils.rm_f(portfolio_evidence_path)
1404+
update(portfolio_evidence: nil)
1405+
end
13751406

13761407
files.each_with_index.map do |file, idx|
13771408
output_filename = File.join(tmp_dir, "#{idx.to_s.rjust(3, '0')}-#{file[:type]}#{File.extname(file[:filename]).downcase}")
@@ -1465,7 +1496,7 @@ def read_file_from_done(idx)
14651496
end
14661497

14671498
def archive_submission
1468-
FileUtils.rm_f(portfolio_evidence_path) if has_pdf
1499+
FileUtils.rm_f(final_pdf_path) if has_pdf
14691500
end
14701501

14711502
def overseer_enabled?
@@ -1484,8 +1515,8 @@ def delete_associated_files
14841515
zip_file = zip_file_path_for_done_task
14851516

14861517
FileUtils.rm(zip_file) if zip_file && File.exist?(zip_file)
1487-
1488-
FileUtils.rm(portfolio_evidence_path) if portfolio_evidence_path.present? && File.exist?(portfolio_evidence_path)
1518+
path = final_pdf_path
1519+
FileUtils.rm(path) if path.present? && File.exist?(path)
14891520

14901521
new_path = FileHelper.student_work_dir(:new, self, false)
14911522
FileUtils.rm_rf(new_path) if new_path.present? && File.directory?(new_path)

0 commit comments

Comments
 (0)