Skip to content

Commit 41c9f12

Browse files
committed
enforce term/course/section start and end dates
before we were recording these values but not doing anything about it. This commit starts scheduling delayed_jobs to activate/conclude enrollments in the background when dates pass. it does not conclude/publish courses/sections automatically, though. if we want that to be automated based on dates then I think we should do that in a separate commit. fixes #3356 Change-Id: Id94356fbc5b82196dd041fdb250607a7633cee9f Reviewed-on: https://gerrit.instructure.com/2431 Tested-by: Hudson <hudson@instructure.com> Reviewed-by: Bracken Mosbacker <bracken@instructure.com>
1 parent 98c6d03 commit 41c9f12

16 files changed

Lines changed: 772 additions & 17 deletions

app/controllers/courses_controller.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,16 @@ def check_enrollment
324324
params[:invitation] = pending_enrollment.uuid
325325
enrollment = pending_enrollment
326326
end
327+
if enrollment && enrollment.inactive?
328+
start_at, end_at = @context.enrollment_dates_for(enrollment)
329+
if start_at && start_at > Time.now
330+
flash[:notice] = "You do not have permission to access the course, #{@context.name}, until #{start_at.to_date.to_s}"
331+
else
332+
flash[:notice] = "Your membership in the course, #{@context.name}, is not yet activated"
333+
end
334+
redirect_to dashboard_url
335+
return true
336+
end
327337
if params[:invitation] && enrollment
328338
if enrollment.rejected?
329339
enrollment.workflow_state = 'active'

app/models/account.rb

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -429,8 +429,6 @@ def self_and_all_sub_accounts
429429
@self_and_all_sub_accounts ||= ActiveRecord::Base.connection.send(:select, "SELECT id FROM accounts WHERE accounts.root_account_id = #{self.id} OR accounts.parent_account_id = #{self.id}").map{|ref| ref['id'].to_i}.uniq + [self.id] #(self.all_accounts + [self]).map &:id
430430
end
431431

432-
# validates_uniqueness_of :name
433-
434432
def abstract_courses
435433
if self.is_a?(Department)
436434
self.department_abstract_courses

app/models/course.rb

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ class Course < ActiveRecord::Base
2222

2323
include Context
2424
include Workflow
25+
include EnrollmentDateRestrictions
2526

26-
attr_accessible :name, :section, :account, :group_weighting_scheme, :start_at, :conclude_at, :grading_standard_id, :is_public, :publish_grades_immediately, :allow_student_wiki_edits, :allow_student_assignment_edits, :hashtag, :show_public_context_messages, :syllabus_body, :hidden_tabs, :allow_student_forum_attachments, :default_wiki_editing_roles, :allow_student_organized_groups, :course_code, :default_view, :show_all_discussion_entries, :open_enrollment, :allow_wiki_comments, :turnitin_comments, :self_enrollment, :license, :indexed, :enrollment_term, :abstract_course, :root_account, :storage_quota
27+
attr_accessible :name, :section, :account, :group_weighting_scheme, :start_at, :conclude_at, :grading_standard_id, :is_public, :publish_grades_immediately, :allow_student_wiki_edits, :allow_student_assignment_edits, :hashtag, :show_public_context_messages, :syllabus_body, :hidden_tabs, :allow_student_forum_attachments, :default_wiki_editing_roles, :allow_student_organized_groups, :course_code, :default_view, :show_all_discussion_entries, :open_enrollment, :allow_wiki_comments, :turnitin_comments, :self_enrollment, :license, :indexed, :enrollment_term, :abstract_course, :root_account, :storage_quota, :restrict_enrollments_to_course_dates
2728

2829
serialize :tab_configuration
2930
belongs_to :root_account, :class_name => 'Account'
@@ -33,12 +34,12 @@ class Course < ActiveRecord::Base
3334
has_many :course_sections
3435
has_many :active_course_sections, :class_name => 'CourseSection', :conditions => {:workflow_state => 'active'}
3536
has_many :enrollments, :include => [:user, :course], :conditions => ['enrollments.workflow_state != ?', 'deleted'], :dependent => :destroy
36-
has_many :current_enrollments, :class_name => 'Enrollment', :conditions => ['enrollments.workflow_state != ? AND enrollments.workflow_state != ? AND enrollments.workflow_state != ?', 'rejected', 'completed', 'deleted'], :include => :user
37+
has_many :current_enrollments, :class_name => 'Enrollment', :conditions => ['enrollments.workflow_state != ? AND enrollments.workflow_state != ? AND enrollments.workflow_state != ? AND enrollments.workflow_state != ?', 'rejected', 'completed', 'deleted', 'inactive'], :include => :user
3738
has_many :prior_enrollments, :class_name => 'Enrollment', :include => [:user, :course], :conditions => "enrollments.workflow_state = 'completed'"
3839
has_many :students, :through => :student_enrollments, :source => :user, :order => :sortable_name
3940
has_many :all_students, :through => :all_student_enrollments, :source => :user, :order => :sortable_name
4041
has_many :participating_students, :through => :enrollments, :source => :user, :conditions => "enrollments.type = 'StudentEnrollment' and enrollments.workflow_state = 'active'"
41-
has_many :student_enrollments, :class_name => 'StudentEnrollment', :conditions => ['enrollments.workflow_state != ? AND enrollments.workflow_state != ? AND enrollments.workflow_state != ?', 'deleted', 'completed', 'rejected'], :include => :user #, :conditions => "type = 'StudentEnrollment'"
42+
has_many :student_enrollments, :class_name => 'StudentEnrollment', :conditions => ['enrollments.workflow_state != ? AND enrollments.workflow_state != ? AND enrollments.workflow_state != ? AND enrollments.workflow_state != ?', 'deleted', 'completed', 'rejected', 'inactive'], :include => :user #, :conditions => "type = 'StudentEnrollment'"
4243
has_many :all_student_enrollments, :class_name => 'StudentEnrollment', :conditions => ['enrollments.workflow_state != ?', 'deleted'], :include => :user
4344
has_many :detailed_enrollments, :class_name => 'Enrollment', :conditions => ['enrollments.workflow_state != ?', 'deleted'], :include => {:user => {:pseudonym => :communication_channel}}
4445
has_many :teachers, :through => :teacher_enrollments, :source => :user
@@ -726,6 +727,40 @@ def self.find_all_by_context_code(codes)
726727
Course.find(:all, :conditions => {:id => ids}, :include => :current_enrollments)
727728
end
728729

730+
def enrollment_dates_for(enrollment)
731+
if enrollment.start_at && enrollment.end_at
732+
[enrollment.start_at, enrollment.end_at]
733+
elsif (section = enrollment.course_section_id && course_sections.find_by_id(enrollment.course_section_id)) &&
734+
section && section.restrict_enrollments_to_section_dates && !enrollment.admin?
735+
[section.start_at, section.end_at]
736+
elsif self.restrict_enrollments_to_course_dates && !enrollment.admin?
737+
[start_at, end_at]
738+
elsif enrollment_term
739+
enrollment_term.enrollment_dates_for(enrollment)
740+
else
741+
[nil, nil]
742+
end
743+
end
744+
745+
def enrollment_state_based_on_date(enrollment)
746+
start_at, end_at = enrollment_dates_for(enrollment)
747+
if start_at && start_at >= Time.now
748+
'inactive'
749+
elsif end_at && end_at <= Time.now
750+
'completed'
751+
else
752+
'active'
753+
end
754+
end
755+
756+
def end_at
757+
conclude_at
758+
end
759+
760+
def end_at_changed?
761+
conclude_at_changed?
762+
end
763+
729764
def state_sortable
730765
case state
731766
when :invited

app/models/course_section.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
class CourseSection < ActiveRecord::Base
2020
include Workflow
21+
include EnrollmentDateRestrictions
22+
2123
attr_protected :sis_source_id, :sis_batch_id, :course_id, :abstract_course_id,
2224
:root_account_id, :enrollment_term_id, :sis_cross_listed_section_id, :sis_cross_listed_section_sis_batch_id
2325
belongs_to :course

app/models/enrollment.rb

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
class Enrollment < ActiveRecord::Base
2020

2121
include Workflow
22+
include EnrollmentDateRestrictions
2223

2324
belongs_to :course, :touch => true
2425
belongs_to :course_section
@@ -229,13 +230,23 @@ def state_sortable
229230
end
230231
end
231232

233+
def accept!
234+
res = accept
235+
raise "can't accept" unless res
236+
res
237+
end
238+
239+
def accept
240+
return false unless invited?
241+
ids = nil
242+
ids = self.user.dashboard_messages.find_all_by_context_id_and_context_type(self.id, 'Enrollment', :select => "id").map(&:id) if self.user
243+
Message.delete_all({:id => ids}) if ids && !ids.empty?
244+
update_attribute(:workflow_state, course.enrollment_state_based_on_date(self))
245+
true
246+
end
247+
232248
workflow do
233249
state :invited do
234-
event :accept, :transitions_to => :active do
235-
ids = nil
236-
ids = self.user.dashboard_messages.find_all_by_context_id_and_context_type(self.id, 'Enrollment', :select => "id").map(&:id) if self.user
237-
Message.delete_all({:id => ids}) if ids && !ids.empty?
238-
end
239250
event :reject, :transitions_to => :rejected
240251
event :complete, :transitions_to => :completed
241252
event :pend, :transitions_to => :pending
@@ -251,6 +262,10 @@ def state_sortable
251262
event :pend, :transitions_to => :pending
252263
end
253264

265+
state :inactive do
266+
event :activate, :transitions_to => :active
267+
end
268+
254269
state :deleted
255270
state :rejected do
256271
event :unreject, :transitions_to => :invited
@@ -290,7 +305,7 @@ def pending?
290305
end
291306

292307
def active_or_pending?
293-
self.active? || self.pending?
308+
self.active? || self.inactive? || self.pending?
294309
end
295310

296311
def email

app/models/enrollment_dates_override.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
#
1818

1919
class EnrollmentDatesOverride < ActiveRecord::Base
20+
include EnrollmentDateRestrictions
21+
2022
belongs_to :context, :polymorphic => true
2123
belongs_to :enrollment_term
2224
end

app/models/enrollment_term.rb

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
#
1818

1919
class EnrollmentTerm < ActiveRecord::Base
20-
attr_accessible :name, :start_at, :end_at
20+
include EnrollmentDateRestrictions
2121
include Workflow
22+
23+
attr_accessible :name, :start_at, :end_at, :ignore_term_date_restrictions
2224
belongs_to :root_account, :class_name => 'Account'
2325
has_many :enrollment_dates_overrides
2426
has_many :courses
@@ -43,6 +45,16 @@ def set_overrides(context, params)
4345
state :deleted
4446
end
4547

48+
def enrollment_dates_for(enrollment)
49+
return [nil, nil] if ignore_term_date_restrictions
50+
override = EnrollmentDatesOverride.find_by_enrollment_term_id_and_enrollment_type(self.id, enrollment.type.to_s)
51+
if override
52+
[override.start_at, override.end_at]
53+
else
54+
[start_at, end_at]
55+
end
56+
end
57+
4658
alias_method :destroy!, :destroy
4759
def destroy
4860
self.workflow_state = 'deleted'

app/views/courses/course_details.html.erb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,20 @@
186186
<span class="course_form"><%= f.text_field :conclude_at, :class => "date_entry" %></span>
187187
<span class="course_info conclude_at"><%= datetime_string(@context.conclude_at) || "No Date Set" %></span>
188188
</td>
189+
</tr><tr>
190+
<td></td>
191+
<td>
192+
<div class="course_form">
193+
<%= f.check_box :restrict_enrollments_to_course_dates %>
194+
<%= f.label :restrict_enrollments_to_course_dates, "Users can only access the course between these dates" %>
195+
<div style="font-size: 0.8em; padding-left: 25px;">
196+
This will override any term availability settings.
197+
</div>
198+
</div>
199+
<div class="course_info restrict_dates" style="font-size: 0.8em;">
200+
<%= @context.restrict_enrollments_to_course_dates ? "Users can only access the course between these dates" : "These dates will not affect course availability" %>
201+
</div>
202+
</td>
189203
</tr><tr>
190204
<td style="width: 10%;"><%= f.label :storage_quota, "File Storage:" %></td>
191205
<td>

app/views/sections/show.html.erb

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
<% content_for :right_side do %>
44
<div class="rs-margin-all">
55
<a href="#" class="button button-sidebar-wide edit_section_link"><%= image_tag "edit.png" %> Edit Section</a>
6+
<a href="<%= context_url(@context, :context_details_url) %>" class="button button-sidebar-wide edit_section_link"><%= image_tag "back.png" %> Back to Course Settings</a>
67
</div>
78
<% end %>
89

@@ -85,9 +86,25 @@ h3 .tally {
8586
<% form_for @section, :url => context_url(@context, :context_section_url, @section), :html => {:id => "edit_section_form", :style => "display: none;"} do |f| %>
8687
<table class="formtable">
8788
<tr>
88-
<td><%= f.label :name, "Section Name" %></td>
89+
<td><%= f.label :name, "Section Name:" %></td>
8990
<td><%= f.text_field :name %></td>
9091
</tr>
92+
<tr>
93+
<td><%= f.label :start_at, "Starts:" %></td>
94+
<td><%= f.text_field :start_at, :class => "datetime_field", :value => datetime_string(@section.start_at), :style => 'width: 120px;' %></td>
95+
</tr>
96+
<tr>
97+
<td><%= f.label :end_at, "Ends:" %></td>
98+
<td><%= f.text_field :end_at, :class => "datetime_field", :value => datetime_string(@section.end_at), :style => 'width: 120px;' %></td>
99+
</tr>
100+
<tr>
101+
<td colspan="2">
102+
<%= f.check_box :restrict_enrollments_to_section_dates %>
103+
<%= f.label :restrict_enrollments_to_section_dates, "Users can only access the course between these dates" %>
104+
<div style="font-size: 0.8em; padding-left: 25px;">
105+
This will override any term or course date settings.
106+
</td>
107+
</tr>
91108
<tr>
92109
<td colspan="2">
93110
<div class="button-container">
@@ -98,8 +115,22 @@ h3 .tally {
98115
</tr>
99116
</table>
100117
<% end %>
118+
<h2><%= @section.name %></h2>
119+
<div style="margin-bottom: 20px;">
120+
<div>
121+
<%= pluralize((@current_enrollments + @completed_enrollments).length, 'Enrollment') %>
122+
</div>
123+
<% if @section.start_at || @section.end_at %>
124+
<div>
125+
Runs from <%= datetime_string(@section.start_at) || 'whenever' %> to <%= datetime_string(@section.end_at) || 'whenever' %>
126+
</div>
127+
<div style="margin-left: 10px; font-size: 0.8em;">
128+
<%= 'Students can only access the course between these dates' if @section.restrict_enrollments_to_section_dates %>
129+
</div>
130+
<% end %>
131+
</div>
101132
<% unless @current_enrollments.empty? %>
102-
<h2>Current Enrollments</h2>
133+
<h3>Current Enrollments</h3>
103134
<ul class="user_list">
104135
<% @current_enrollments.each do |enrollment| %>
105136
<%= render :partial => "shared/enrollment", :object => enrollment, :locals => {:include_type => true} %>
@@ -137,6 +168,7 @@ $(document).ready(function() {
137168
}
138169
});
139170
});
171+
$(".datetime_field").datetime_field();
140172
});
141173
</script>
142174
<% end %>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
class AddRestrictionOptionsToCoursesAndSections < ActiveRecord::Migration
2+
def self.up
3+
add_column :courses, :restrict_enrollments_to_course_dates, :boolean
4+
add_column :course_sections, :restrict_enrollments_to_section_dates, :boolean
5+
add_column :enrollment_terms, :ignore_term_date_restrictions, :boolean
6+
end
7+
8+
def self.down
9+
remove_column :courses, :restrict_enrollments_to_course_dates
10+
remove_column :course_sections, :restrict_enrollments_to_section_dates
11+
remove_column :enrollment_terms, :ignore_term_date_restrictions
12+
end
13+
end

0 commit comments

Comments
 (0)