From a2a6579c108dfaa9f16da96a7313b942eb1b83f5 Mon Sep 17 00:00:00 2001 From: Cameron Matheson Date: Wed, 18 Apr 2012 17:47:03 -0600 Subject: [PATCH] multiple contexts per appointment group closes #7561 Test plan: - create some appointment groups: - make one with multiple courses - make one with a course, and then add more courses after saving - make one restricted to some (not all) sections in a course [1] - make one that has students sign up in groups [1] - make sure that only students that match the above criteria are able to see/reserve those appointment groups [1] you can only choose the group-signup option or restrict appointment groups to certain sections on creation (these options won't be availabe when editing later) Change-Id: I1cff5fb4ed233882c2061f8fd8a7ba6f2d4959b0 Reviewed-on: https://gerrit.instructure.com/9407 Tested-by: Jenkins Reviewed-by: Jon Jensen --- .../calendar/ContextSelector.coffee | 162 ++++++++++++++++++ .../EditAppointmentGroupDetails.coffee | 114 +++++++----- .../EditAppointmentGroupDialog.coffee | 10 +- app/coffeescripts/calendar/Scheduler.coffee | 18 +- app/coffeescripts/handlebars_helpers.coffee | 8 +- .../appointment_groups_controller.rb | 22 ++- app/controllers/calendars_controller.rb | 2 +- .../appointment_canceled_by_user.email.erb | 9 +- .../appointment_canceled_by_user.facebook.erb | 8 +- .../appointment_canceled_by_user.sms.erb | 2 +- .../appointment_deleted_for_user.email.erb | 11 +- .../appointment_deleted_for_user.facebook.erb | 8 +- .../appointment_deleted_for_user.sms.erb | 2 +- .../appointment_group_deleted.email.erb | 11 +- .../appointment_group_deleted.facebook.erb | 7 +- .../appointment_group_deleted.sms.erb | 2 +- .../appointment_group_published.email.erb | 10 +- .../appointment_group_published.facebook.erb | 7 +- .../appointment_group_published.sms.erb | 2 +- .../appointment_group_published.summary.erb | 6 +- .../appointment_group_updated.email.erb | 9 +- .../appointment_group_updated.facebook.erb | 7 +- .../appointment_group_updated.sms.erb | 2 +- .../appointment_group_updated.summary.erb | 6 +- .../appointment_reserved_by_user.email.erb | 8 +- .../appointment_reserved_by_user.facebook.erb | 8 +- .../appointment_reserved_by_user.sms.erb | 2 +- .../appointment_reserved_for_user.email.erb | 8 +- ...appointment_reserved_for_user.facebook.erb | 8 +- .../appointment_reserved_for_user.sms.erb | 2 +- .../appointment_reserved_for_user.summary.erb | 10 +- app/models/appointment_group.rb | 89 +++++++--- app/models/appointment_group_context.rb | 30 ++++ app/models/appointment_group_sub_context.rb | 4 +- app/models/assignment.rb | 1 - app/models/calendar_event.rb | 20 ++- app/models/course.rb | 3 +- app/models/user.rb | 4 +- app/stylesheets/calendar/scheduler.sass | 45 ++++- app/views/courses/_recent_event.html.erb | 4 +- .../calendar/appointmentGroupList.handlebars | 6 +- .../jst/calendar/contextSelector.handlebars | 4 + .../calendar/contextSelectorItem.handlebars | 13 ++ .../calendar/editAppointmentGroup.handlebars | 30 +--- ...22744_create_appointment_group_contexts.rb | 17 ++ ...4933_migrate_appointment_group_contexts.rb | 21 +++ lib/api/v1/calendar_event.rb | 23 ++- spec/apis/v1/appointment_groups_api_spec.rb | 68 ++++---- spec/apis/v1/calendar_events_api_spec.rb | 28 +-- spec/factories/calendar_event_factory.rb | 5 +- ...ppointment_group_deleted.email.erb_spec.rb | 6 +- ...intment_group_deleted.facebook.erb_spec.rb | 6 +- .../appointment_group_deleted.sms.erb_spec.rb | 2 +- ...ointment_group_deleted.twitter.erb_spec.rb | 2 +- ...ointment_group_published.email.erb_spec.rb | 8 +- ...tment_group_published.facebook.erb_spec.rb | 8 +- ...ppointment_group_published.sms.erb_spec.rb | 2 +- ...ntment_group_published.summary.erb_spec.rb | 2 +- ...ntment_group_published.twitter.erb_spec.rb | 2 +- ...ppointment_group_updated.email.erb_spec.rb | 8 +- ...intment_group_updated.facebook.erb_spec.rb | 8 +- .../appointment_group_updated.sms.erb_spec.rb | 2 +- ...ointment_group_updated.summary.erb_spec.rb | 2 +- ...ointment_group_updated.twitter.erb_spec.rb | 2 +- spec/models/appointment_group_spec.rb | 104 +++++++++-- spec/models/calendar_event_spec.rb | 33 ++-- spec/models/course_spec.rb | 4 +- spec/models/user_spec.rb | 6 +- spec/selenium/calendar2_common.rb | 4 +- spec/selenium/dashboard_spec.rb | 10 ++ spec/selenium/scheduler_spec.rb | 52 +++++- 71 files changed, 875 insertions(+), 304 deletions(-) create mode 100644 app/coffeescripts/calendar/ContextSelector.coffee create mode 100644 app/models/appointment_group_context.rb create mode 100644 app/views/jst/calendar/contextSelector.handlebars create mode 100644 app/views/jst/calendar/contextSelectorItem.handlebars create mode 100644 db/migrate/20120307222744_create_appointment_group_contexts.rb create mode 100644 db/migrate/20120430164933_migrate_appointment_group_contexts.rb diff --git a/app/coffeescripts/calendar/ContextSelector.coffee b/app/coffeescripts/calendar/ContextSelector.coffee new file mode 100644 index 00000000000..2c9de8ec01a --- /dev/null +++ b/app/coffeescripts/calendar/ContextSelector.coffee @@ -0,0 +1,162 @@ +define [ + 'jquery' + 'underscore' + 'jst/calendar/contextSelector' + 'jst/calendar/contextSelectorItem' + 'compiled/fn/preventDefault' +], ($, _, contextSelectorTemplate, contextSelectorItemTemplate, preventDefault) -> + + class ContextSelectorItem + constructor: (@context) -> + @state = 'off' + @locked = false + @sectionsLocked = false + + pubsubHelper = (fn) => + (sender) => + return if sender is this + fn.apply(this) + + $.subscribe '/contextSelector/disable', pubsubHelper(@disable) + $.subscribe '/contextSelector/enable', pubsubHelper(@enable) + $.subscribe '/contextSelector/uncheck', pubsubHelper(=> @setState('off')) + + $.subscribe '/contextSelector/lockSections', @lockSections + + render: ($list) -> + @$listItem = $(contextSelectorItemTemplate(@context)) + @$listItem.appendTo($list) + @$sectionsList = @$listItem.find('.ag_sections') + + @$listItem.find('.ag_sections_toggle').click preventDefault @toggleSections + @$contentCheckbox = @$listItem.find('[name="context_codes[]"]') + @$contentCheckbox.change preventDefault @change + @$sectionCheckboxes = @$listItem.find('[name="sections[]"]') + @$sectionCheckboxes.change @sectionChange + + toggleSections: (jsEvent) => + $(jsEvent.target).toggleClass('ag-sections-expanded') + @$sectionsList.toggleClass('hidden') + + change: => + newState = switch @state + when 'off' then 'on' + when 'on' then 'off' + when 'partial' then 'on' + @setState(newState) + + setState: (state) => + return if @locked + + @state = state + switch @state + when 'on', 'off' + checked = @state == 'on' + @$contentCheckbox.prop('checked', checked) + @$contentCheckbox.prop('indeterminate', false) + @$sectionCheckboxes.prop('checked', checked) + $.publish("/contextSelector/enable", [this]) + when 'partial' + @$contentCheckbox.prop('checked', true) + @$contentCheckbox.prop('indeterminate', true) + $.publish('/contextSelector/disable', [this]) + $.publish('/contextSelector/uncheck', [this]) + + $.publish('/contextSelector/changed') + + sectionChange: => + switch @$sectionCheckboxes.filter(':checked').length + when 0 + @setState('off') + when @$sectionCheckboxes.length + @setState('on') + else + @setState('partial') + + disable: -> + @$contentCheckbox.prop('disabled', true) + @disableSections() + + disableSections: -> + @$sectionCheckboxes.prop('disabled', true) + + enable: -> + unless @locked + @$contentCheckbox.prop('disabled', false) + @enableSections() + + enableSections: -> + unless @lockedSections + @$sectionCheckboxes.prop('disabled', false) + + lock: -> + @locked = true + @disable() + $.publish('/contextSelector/lockSections') + + lockSections: => + @lockedSections = true + @disableSections() + + isChecked: -> @state != 'off' + + sections: -> + checked = @$sectionCheckboxes.filter(':checked') + if checked.length == @$sectionCheckboxes.length + [] + else + _.map(checked, (cb) -> cb.value) + + class ContextSelector + constructor: (selector, @apptGroup, @contexts, contextsChangedCB, closeCB) -> + @$menu = $(selector).html contextSelectorTemplate() + $contextsList = @$menu.find('.ag-contexts') + + $.subscribe('/contextSelector/changed', => contextsChangedCB @selectedContexts(), @selectedSections()) + + @contextSelectorItems = {} + for c in @contexts + item = new ContextSelectorItem(c) + item.render($contextsList) + @contextSelectorItems[item.context.asset_string] = item + + if @apptGroup.sub_context_codes.length > 0 + # if you choose sub_contexts when creating an appointment + # group, the appointment group is locked down + # TODO: be smarter about this + for subContextCode in @apptGroup.sub_context_codes + $("[value='#{subContextCode}']").prop('checked', true) + for c, item of @contextSelectorItems + item.sectionChange() + item.lock() + else + for contextCode in @apptGroup.context_codes + @contextSelectorItems[contextCode].setState('on') + @contextSelectorItems[contextCode].lock() + + $('.ag_contexts_done').click preventDefault closeCB + + contextsChangedCB(@selectedContexts(), @selectedSections()) + + selectedContexts: -> + contexts = _.chain(@contextSelectorItems) + .values() + .filter( (c) -> c.state != 'off') + .map( (c) -> c.context.asset_string) + .value() + + numPartials = _.filter(contexts, (c) -> c.state == 'partial') + if numPartials > 1 or numPartials == 1 and contexts.length > 1 + throw "invalid state" + + contexts + + selectedSections: -> + sections = _.chain(@contextSelectorItems) + .values() + .map( (c) -> c.sections()) + .reject((ss) -> ss.length == 0) + .value() + + throw "invalid state" if sections.length > 1 + sections[0] diff --git a/app/coffeescripts/calendar/EditAppointmentGroupDetails.coffee b/app/coffeescripts/calendar/EditAppointmentGroupDetails.coffee index 01d61cb32d8..874bbdae10a 100644 --- a/app/coffeescripts/calendar/EditAppointmentGroupDetails.coffee +++ b/app/coffeescripts/calendar/EditAppointmentGroupDetails.coffee @@ -1,26 +1,34 @@ define [ 'jquery' + 'underscore' 'i18n!EditAppointmentGroupDetails' 'compiled/calendar/TimeBlockList' 'jst/calendar/editAppointmentGroup' 'jst/calendar/genericSelect' 'jst/calendar/sectionCheckboxes' + 'compiled/calendar/ContextSelector' + 'compiled/fn/preventDefault' 'jquery.ajaxJSON' 'jquery.disableWhileLoading' 'jquery.instructure_forms' -], ($, I18n, TimeBlockList, editAppointmentGroupTemplate, genericSelectTemplate, sectionCheckboxesTemplate) -> +], ($, _, I18n, TimeBlockList, editAppointmentGroupTemplate, genericSelectTemplate, sectionCheckboxesTemplate, ContextSelector, preventDefault) -> class EditAppointmentGroupDetails - constructor: (selector, @apptGroup, @contextChangeCB, @closeCB) -> + constructor: (selector, @apptGroup, @contexts, @closeCB) -> @currentContextInfo = null + $(selector).html editAppointmentGroupTemplate({ title: @apptGroup.title - contexts: @apptGroup.contexts + contexts: @contexts appointment_group: @apptGroup }) + + @contextsHash = {} + @contextsHash[c.asset_string] = c for c in @contexts + @form = $(selector).find("form") - @form.find("select.context_id").change(@contextChange).change() + @contextSelector = new ContextSelector('.ag-menu-container', @apptGroup, @contexts, @contextsChanged, @toggleContextsMenu) if @apptGroup.id @form.attr('action', @apptGroup.url) @@ -29,16 +37,17 @@ define [ @form.find(".context_id").val(@apptGroup.context_code).attr('disabled', true) @form.find("select.context_id").change() - @form.find(".group_category").attr('disabled', true) - @form.find('input[name="section_ids[]"]').attr('disabled', true) - @form.find(".group-signup-checkbox").attr('disabled', true) + @disableGroups() if @apptGroup.participant_type == 'Group' @form.find(".group-signup-checkbox").prop('checked', true) @form.find(".group_category").val(@apptGroup.sub_context_codes[0]) else @form.find(".group-signup-checkbox").prop('checked', false) else - @form.attr('action', @currentContextInfo.create_appointment_group_url) + # FIXME: put this url in ENV json or something + @form.attr('action', '/api/v1/appointment_groups') + + @form.find('.ag_contexts_selector').click preventDefault @toggleContextsMenu timeBlocks = ([appt.start, appt.end, true] for appt in @apptGroup.appointmentEvents || [] ) @timeBlockList = new TimeBlockList(@form.find(".time-block-list-body"), @form.find(".splitter"), timeBlocks) @@ -54,7 +63,6 @@ define [ checked = !!jsEvent.target.checked @form.find('.per_appointment_groups_label').toggle(checked) @form.find('.per_appointment_users_label').toggle(!checked) - @form.find(".section-signup").toggle(!checked) @form.find(".group-signup").toggle(checked) @form.find(".group-signup-checkbox").change() @@ -86,12 +94,6 @@ define [ if @apptGroup.workflow_state == 'active' @form.find("#appointment-blocks-active-button").attr('disabled', true).prop('checked', true) - contextInfoForCode: (code) -> - for context in @apptGroup.contexts - if context.asset_string == code - return context - return null - saveWithoutPublishingClick: (jsEvent) => jsEvent.preventDefault() @save(false) @@ -136,13 +138,23 @@ define [ params['appointment_group[participant_visibility]'] = if data.participant_visibility == '1' then 'protected' else 'private' - if create - params['appointment_group[context_code]'] = data.context_code + # get the context/section info from @contextSelector instead + delete data['context_codes[]'] + delete data['sections[]'] + contextCodes = @contextSelector.selectedContexts() + if contextCodes.length == 0 + $('.ag_contexts_selector').errorBox(I18n.t 'context_required', 'You need to select a calendar') + return + else + params['appointment_group[context_codes]'] = contextCodes + + if create if data.use_group_signup == '1' && data.group_category_id params['appointment_group[sub_context_codes]'] = [data.group_category_id] - else if data['section_ids[]']?.length > 0 - params['appointment_group[sub_context_codes]'] = data['section_ids[]'] + else + sections = @contextSelector.selectedSections() + params['appointment_group[sub_context_codes]'] = sections if sections # TODO: Provide UI for specifying this params['appointment_group[min_appointments_per_participant]'] = 1 @@ -155,31 +167,47 @@ define [ deferred = $.ajaxJSON @form.attr('action'), method, params, onSuccess, onError @form.disableWhileLoading(deferred) - contextChange: (jsEvent) => - context = $(jsEvent.target).val() - @currentContextInfo = @contextInfoForCode(context) - @apptGroup.contextInfo = @currentContextInfo - if @currentContextInfo == null then return + contextsChanged: (contextCodes, sectionCodes) => + # dropdown text + if sectionCodes + sectionCode = sectionCodes[0] + section = _.chain(@contexts) + .pluck('course_sections') + .flatten() + .find((s) -> s.asset_string == sectionCode) + .value() + text = section.name + if sectionCodes.length > 1 + text += I18n.t('and_n_sectionCodes', ' and %{n} others', n: sectionCodes.length - 1) + @form.find('.ag_contexts_selector').text(text) + else if contextCodes.length > 0 + contextCode = contextCodes[0] + text = @contextsHash[contextCode].name + if contextCodes.length > 1 + text += I18n.t('and_n_contexts', ' and %{n} others', n: contextCodes.length - 1) + @form.find('.ag_contexts_selector').text(text) + else + @form.find('.ag_contexts_selector').text(I18n.t 'select_calendars', 'Select Calendars') - # Update the sections and groups lists in the scheduler - if @currentContextInfo.course_sections - for courseSection in @currentContextInfo.course_sections - courseSection.selected = courseSection.asset_string in this.apptGroup.sub_context_codes - sectionsInfo = { sections: @currentContextInfo.course_sections } - @form.find('.section_select').html(sectionCheckboxesTemplate(sectionsInfo)) + # group selector + context = @contextsHash[contextCodes[0]] + if contextCodes.length == 1 and not sectionCodes and context.group_categories?.length > 0 + @enableGroups(context) + else + @disableGroups() - if !@currentContextInfo.group_categories || @currentContextInfo.group_categories.length == 0 - @form.find(".group-signup-checkbox").attr('disabled', true).prop('checked', false).change() - else if @currentContextInfo.group_categories - @form.find(".group-signup-checkbox").attr('disabled', false) - groupsInfo = - cssClass: 'group_category' - name: 'group_category_id' - collection: @currentContextInfo.group_categories - @form.find(".group_select").html(genericSelectTemplate(groupsInfo)) + disableGroups: -> + @form.find(".group-signup-checkbox").attr('disabled', true) + @form.find("group-signup").hide() - @contextChangeCB(context) + enableGroups: (contextInfo) -> + @form.find(".group-signup-checkbox").attr('disabled', false) + groupsInfo = + cssClass: 'group_category' + name: 'group_category_id' + collection: contextInfo.group_categories + @form.find(".group_select").html(genericSelectTemplate(groupsInfo)) + @form.find("group-signup").show() - # Update the edit and more options links with the new context, if this is a new group - if !@apptGroup.id - @form.attr('action', @currentContextInfo.create_appointment_group_url) + toggleContextsMenu: (jsEvent) => + $('.ag_contexts_menu').toggleClass('hidden') diff --git a/app/coffeescripts/calendar/EditAppointmentGroupDialog.coffee b/app/coffeescripts/calendar/EditAppointmentGroupDialog.coffee index 6cf3f50fe09..506e66cd568 100644 --- a/app/coffeescripts/calendar/EditAppointmentGroupDialog.coffee +++ b/app/coffeescripts/calendar/EditAppointmentGroupDialog.coffee @@ -11,20 +11,20 @@ define [ width: 'auto' resizable: false title: I18n.t('titles.edit_appointment_group', "Edit Appointment Group") + # this is dumb, but it prevents the columns from wrapping when + # the context selector drop down gets too long + dialog.dialog('widget').find('#edit_event').css('overflow', 'visible') class EditAppointmentGroupDialog - constructor: (@apptGroup, @parentCloseCB) -> + constructor: (@apptGroup, @contexts, @parentCloseCB) -> @currentContextInfo = null - contextChange: (newContext) => - # TODO: update the color? - closeCB: (saved) => dialog.dialog('close') @parentCloseCB(saved) show: => - @appointmentGroupsForm = new EditAppointmentGroupDetails(dialog.find(".wrapper"), @apptGroup, @contextChange, @closeCB) + @appointmentGroupsForm = new EditAppointmentGroupDetails(dialog.find(".wrapper"), @apptGroup, @contexts, @closeCB) buttons = if @apptGroup.workflow_state == 'active' [ diff --git a/app/coffeescripts/calendar/Scheduler.coffee b/app/coffeescripts/calendar/Scheduler.coffee index b8f0dd9d712..da8c884a38b 100644 --- a/app/coffeescripts/calendar/Scheduler.coffee +++ b/app/coffeescripts/calendar/Scheduler.coffee @@ -1,5 +1,6 @@ define [ 'jquery', + 'underscore' 'i18n!calendar' 'jst/calendar/appointmentGroupList' 'jst/calendar/schedulerRightSideAdminSection' @@ -11,7 +12,7 @@ define [ 'jquery.instructure_misc_plugins' 'vendor/jquery.ba-tinypubsub' 'vendor/jquery.spin' -], ($, I18n, appointmentGroupListTemplate, schedulerRightSideAdminSectionTemplate, EditAppointmentGroupDialog, MessageParticipantsDialog, deleteItemTemplate) -> +], ($, _, I18n, appointmentGroupListTemplate, schedulerRightSideAdminSectionTemplate, EditAppointmentGroupDialog, MessageParticipantsDialog, deleteItemTemplate) -> class Scheduler constructor: (selector, @calendar) -> @@ -33,6 +34,9 @@ define [ @rightSideAdminSection = $(schedulerRightSideAdminSectionTemplate()) @rightSideAdminSection.find(".create_link").click @createClick + @appointmentGroupContexts = _.filter @contexts, (c) -> + c.can_create_appointment_groups + $.subscribe "CommonEvent/eventSaved", @eventSaved $.subscribe "CommonEvent/eventDeleted", @eventDeleted @@ -40,11 +44,11 @@ define [ jsEvent.preventDefault() group = { - contexts: @calendar.contexts + context_codes: [] sub_context_codes: [] } - @createDialog = new EditAppointmentGroupDialog(group, @dialogCloseCB) + @createDialog = new EditAppointmentGroupDialog(group, @appointmentGroupContexts, @dialogCloseCB) @createDialog.show() dialogCloseCB: (saved) => @@ -138,9 +142,8 @@ define [ for appointmentEvent in group.appointmentEvents group.signed_up += appointmentEvent.childEvents.length if appointmentEvent.childEvents - # look up the context name for the group - for contextInfo in @contexts when contextInfo.asset_string == group.context_code - group.context = contextInfo + # look up the context names for the group + group.contexts = _.filter(@contexts, (c) -> c.asset_string in group.context_codes) group.published = group.workflow_state == "active" @@ -224,8 +227,7 @@ define [ group = @groups?[$(jsEvent.target).closest(".appointment-group-item").data('appointment-group-id')] return unless group - group.contexts = @calendar.contexts - @createDialog = new EditAppointmentGroupDialog(group, @dialogCloseCB) + @createDialog = new EditAppointmentGroupDialog(group, @appointmentGroupContexts, @dialogCloseCB) @createDialog.show() deleteLinkClick: (jsEvent) => diff --git a/app/coffeescripts/handlebars_helpers.coffee b/app/coffeescripts/handlebars_helpers.coffee index 2c841f27143..f2df69516ac 100644 --- a/app/coffeescripts/handlebars_helpers.coffee +++ b/app/coffeescripts/handlebars_helpers.coffee @@ -2,12 +2,13 @@ define [ 'vendor/handlebars.vm' 'i18nObj' 'jquery' + 'underscore' 'str/htmlEscape' 'compiled/util/semanticDateRange' 'jquery.instructure_date_and_time' 'jquery.instructure_misc_helpers' 'jquery.instructure_misc_plugins' -], (Handlebars, I18n, $, htmlEscape, semanticDateRange) -> +], (Handlebars, I18n, $, _, htmlEscape, semanticDateRange) -> Handlebars.registerHelper name, fn for name, fn of { t : (key, defaultValue, options) -> @@ -129,5 +130,10 @@ define [ # eachProp: (context, options) -> (options.fn(property: prop, value: context[prop]) for prop of context).join '' + + # evaluates the block for each item in context and passes the result to $.toSentence + toSentence: (context, options) -> + results = _.map(context, (c) -> options.fn(c)) + $.toSentence(results) } return Handlebars diff --git a/app/controllers/appointment_groups_controller.rb b/app/controllers/appointment_groups_controller.rb index bb30271784b..4617c9bc04c 100644 --- a/app/controllers/appointment_groups_controller.rb +++ b/app/controllers/appointment_groups_controller.rb @@ -20,7 +20,6 @@ class AppointmentGroupsController < ApplicationController include Api::V1::CalendarEvent before_filter :require_user - before_filter :get_context, :only => :create before_filter :get_appointment_group, :only => [:show, :update, :destroy, :users, :groups] def calendar_fragment(opts) @@ -51,8 +50,12 @@ class AppointmentGroupsController < ApplicationController end def create + contexts = get_contexts + raise ActiveRecord::RecordNotFound unless contexts.present? + publish = params[:appointment_group].delete(:publish) == '1' - @group = @context.appointment_groups.build(params[:appointment_group]) + params[:appointment_group][:contexts] = contexts + @group = AppointmentGroup.new(params[:appointment_group]) if authorized_action(@group, @current_user, :manage) if @group.save @group.publish! if publish @@ -75,6 +78,8 @@ class AppointmentGroupsController < ApplicationController end def update + contexts = get_contexts + @group.contexts = contexts if contexts if authorized_action(@group, @current_user, :update) publish = params[:appointment_group].delete(:publish) == "1" if @group.update_attributes(params[:appointment_group]) @@ -119,13 +124,18 @@ class AppointmentGroupsController < ApplicationController end end - def get_context - @context = Context.find_by_asset_string(params[:appointment_group].delete(:context_code)) if params[:appointment_group] && params[:appointment_group][:context_code] - raise ActiveRecord::RecordNotFound unless @context + def get_contexts + if params[:appointment_group] && params[:appointment_group][:context_codes] + context_codes = params[:appointment_group].delete(:context_codes) + contexts = context_codes.map do |code| + Context.find_by_asset_string(code) + end + end + contexts end def get_appointment_group @group = AppointmentGroup.find(params[:id].to_i) - @context = @group.context + @context = @group.contexts_for_user(@current_user).first # FIXME? end end diff --git a/app/controllers/calendars_controller.rb b/app/controllers/calendars_controller.rb index 1cba7d0dd0f..83a17427924 100644 --- a/app/controllers/calendars_controller.rb +++ b/app/controllers/calendars_controller.rb @@ -82,7 +82,7 @@ class CalendarsController < ApplicationController :can_create_calendar_events => context.respond_to?("calendar_events") && context.calendar_events.new.grants_right?(@current_user, session, :create), :can_create_assignments => context.respond_to?("assignments") && context.assignments.new.grants_right?(@current_user, session, :create), :assignment_groups => context.respond_to?("assignments") ? context.assignment_groups.active.scoped(:select => "id, name").map {|g| { :id => g.id, :name => g.name } } : [], - :can_create_appointment_groups => context.respond_to?("appointment_groups") && context.appointment_groups.new.grants_right?(@current_user, session, :create), + :can_create_appointment_groups => context.respond_to?("appointment_groups") && AppointmentGroup.new(:contexts => [context]).grants_right?(@current_user, session, :create), } if context.respond_to?("course_sections") info[:course_sections] = context.course_sections.active.scoped(:select => "id, name").map {|cs| { :id => cs.id, :asset_string => cs.asset_string, :name => cs.name } } diff --git a/app/messages/appointment_canceled_by_user.email.erb b/app/messages/appointment_canceled_by_user.email.erb index e2efc3dd9bc..224de89b28d 100644 --- a/app/messages/appointment_canceled_by_user.email.erb +++ b/app/messages/appointment_canceled_by_user.email.erb @@ -1,9 +1,12 @@ <% define_content :link do %> - http://<%= HostUrl.context_host(asset.appointment_group.context) %>/appointment_groups/<%= asset.appointment_group.id %> + http://<%= HostUrl.context_host(asset.appointment_group.contexts.first) %>/appointment_groups/<%= asset.appointment_group.id %> <% end %> +<% courses = asset.appointment_group.participant_type == 'Group' ? + asset.appointment_group.contexts.first.name : + asset.appointment_group.contexts_for_user(user).map(&:name).join(", ") %> <% define_content :subject do %> - <%= t('subject', 'Reservation canceled for "%{appointment_name}" (%{course})', :appointment_name => asset.title, :course => asset.appointment_group.context.name) %> + <%= t('subject', 'Reservation canceled for "%{appointment_name}" (%{course})', :appointment_name => asset.title, :course => courses) %> <% end %> <%= t :message, '%{user} canceled his/her reservation for "%{appointment_name}".', :user => asset.updating_user.name, :appointment_name => asset.title %> @@ -16,7 +19,7 @@ <% else -%> <%= before_label :attendee, "Attendee" %> <%= asset.context.name %> <% end -%> -<%= before_label :course, "Course" %> <%= asset.appointment_group.context.name %> +<%= before_label :course, "Course" %> <%= courses %> <% if asset.appointment_group.available_slots -%> <%= before_label :slots_remaining, "Available time slots" %> <%= asset.appointment_group.available_slots %> <% end -%> diff --git a/app/messages/appointment_canceled_by_user.facebook.erb b/app/messages/appointment_canceled_by_user.facebook.erb index ce4d0f2e9bd..80a495f48db 100644 --- a/app/messages/appointment_canceled_by_user.facebook.erb +++ b/app/messages/appointment_canceled_by_user.facebook.erb @@ -1,5 +1,5 @@ <% define_content :link do %> - http://<%= HostUrl.context_host(asset.appointment_group.context) %>/appointment_groups/<%= asset.appointment_group.id %> + http://<%= HostUrl.context_host(asset.appointment_group.contexts.first) %>/appointment_groups/<%= asset.appointment_group.id %> <% end %> <%= t :message, '%{user} canceled his/her reservation for "%{appointment_name}".', :user => asset.updating_user.name, :appointment_name => asset.title %> @@ -12,7 +12,9 @@ <% else -%> <%= before_label :attendee, "Attendee" %> <%= asset.context.name %>
<% end -%> -<%= before_label :course, "Course" %> <%= asset.appointment_group.context.name %>
+<%= before_label :course, "Course" %> <%= asset.appointment_group.participant_type == 'Group' ? + asset.appointment_group.contexts.first.name : + asset.appointment_group.contexts_for_user(user).map(&:name).join(", ") %>
<% if asset.appointment_group.available_slots -%> <%= before_label :slots_remaining, "Available time slots" %> <%= asset.appointment_group.available_slots %>
<% end -%> @@ -20,4 +22,4 @@ <%= before_label :cancel_reason, "Reason for canceling" %>
<%= asset.cancel_reason || t(:no_reason_given, "none given") %>

-<%= t :instructions, "View the appointment" %> \ No newline at end of file +<%= t :instructions, "View the appointment" %> diff --git a/app/messages/appointment_canceled_by_user.sms.erb b/app/messages/appointment_canceled_by_user.sms.erb index 2d3e84a95d7..9d41a51c80a 100644 --- a/app/messages/appointment_canceled_by_user.sms.erb +++ b/app/messages/appointment_canceled_by_user.sms.erb @@ -1,3 +1,3 @@ <%= t :message, '%{user} canceled his/her reservation for "%{appointment_name}".', :user => asset.updating_user.name, :appointment_name => asset.title %> -<%= t :more_info, "More info at %{url}", :url => HostUrl.context_host(asset.appointment_group.context) %> \ No newline at end of file +<%= t :more_info, "More info at %{url}", :url => HostUrl.context_host(asset.appointment_group.contexts.first) %> diff --git a/app/messages/appointment_deleted_for_user.email.erb b/app/messages/appointment_deleted_for_user.email.erb index d73cab53e36..df0f4fc4c25 100644 --- a/app/messages/appointment_deleted_for_user.email.erb +++ b/app/messages/appointment_deleted_for_user.email.erb @@ -1,9 +1,12 @@ <% define_content :link do %> - http://<%= HostUrl.context_host(asset.appointment_group.context) %>/appointment_groups/<%= asset.appointment_group.id %> + http://<%= HostUrl.context_host(asset.appointment_group.contexts.first) %>/appointment_groups/<%= asset.appointment_group.id %> <% end %> +<% courses = asset.appointment_group.participant_type == 'Group' ? + asset.appointment_group.contexts.first.name : + asset.appointment_group.contexts_for_user(user).map(&:name).join(", ") %> <% define_content :subject do %> - <%= t('subject', 'Your time slot for "%{appointment_name}" has been canceled (%{course})', :appointment_name => asset.title, :course => asset.appointment_group.context.name) %> + <%= t('subject', 'Your time slot for "%{appointment_name}" has been canceled (%{course})', :appointment_name => asset.title, :course => courses) %> <% end %> <%= t :message, '%{user} canceled your time slot for "%{appointment_name}".', :user => asset.updating_user.name, :appointment_name => asset.title %> @@ -16,9 +19,9 @@ <% else -%> <%= before_label :attendee, "Attendee" %> <%= asset.context.name %> <% end -%> -<%= before_label :course, "Course" %> <%= asset.appointment_group.context.name %> +<%= before_label :course, "Course" %> <%= courses %> <%= before_label :cancel_reason, "Reason for canceling" %> <%= asset.cancel_reason || t(:no_reason_given, "none given") %> -<%= t :instructions, 'Sign up for a different time slot at the following link: %{link}', :link => content(:link) %> \ No newline at end of file +<%= t :instructions, 'Sign up for a different time slot at the following link: %{link}', :link => content(:link) %> diff --git a/app/messages/appointment_deleted_for_user.facebook.erb b/app/messages/appointment_deleted_for_user.facebook.erb index ea09bb4773a..6be3c710c22 100644 --- a/app/messages/appointment_deleted_for_user.facebook.erb +++ b/app/messages/appointment_deleted_for_user.facebook.erb @@ -1,5 +1,5 @@ <% define_content :link do %> - http://<%= HostUrl.context_host(asset.appointment_group.context) %>/appointment_groups/<%= asset.appointment_group.id %> + http://<%= HostUrl.context_host(asset.appointment_group.contexts.first) %>/appointment_groups/<%= asset.appointment_group.id %> <% end %> <%= t :message, '%{user} canceled your time slot for "%{appointment_name}".', :user => asset.updating_user.name, :appointment_name => asset.title %>
@@ -12,9 +12,11 @@ <% else -%> <%= before_label :attendee, "Attendee" %> <%= asset.context.name %>
<% end -%> -<%= before_label :course, "Course" %> <%= asset.appointment_group.context.name %>
+<%= before_label :course, "Course" %> <%= asset.appointment_group.participant_type == 'Group' ? + asset.appointment_group.contexts.first.name : + asset.appointment_group.contexts_for_user(user).map(&:name).join(", ") %>

<%= before_label :cancel_reason, "Reason for canceling" %>
<%= asset.cancel_reason || t(:no_reason_given, "none given") %>

-<%= t :instructions, 'Sign up for a different time slot' %> \ No newline at end of file +<%= t :instructions, 'Sign up for a different time slot' %> diff --git a/app/messages/appointment_deleted_for_user.sms.erb b/app/messages/appointment_deleted_for_user.sms.erb index 1c05b6e1776..6d84452e554 100644 --- a/app/messages/appointment_deleted_for_user.sms.erb +++ b/app/messages/appointment_deleted_for_user.sms.erb @@ -1,3 +1,3 @@ <%= t :message, 'Your time slot for "%{appointment_name} was canceled".', :appointment_name => asset.title %> -<%= t :more_info, "More info at %{url}", :url => HostUrl.context_host(asset.appointment_group.context) %> \ No newline at end of file +<%= t :more_info, "More info at %{url}", :url => HostUrl.context_host(asset.appointment_group.contexts.first) %> diff --git a/app/messages/appointment_group_deleted.email.erb b/app/messages/appointment_group_deleted.email.erb index ec063d2f839..a9539f4bc24 100644 --- a/app/messages/appointment_group_deleted.email.erb +++ b/app/messages/appointment_group_deleted.email.erb @@ -1,16 +1,19 @@ <% define_content :link do %> - http://<%= HostUrl.context_host(asset.context) %>/appointment_groups + http://<%= HostUrl.context_host(asset.contexts.first) %>/appointment_groups <% end %> <% define_content :subject do %> - <%= t('subject', 'Appointments for "%{appointment_name}" have been canceled (%{course})', :appointment_name => asset.title, :course => asset.context.name) %> + <%= t('subject', 'Appointments for "%{appointment_name}" have been canceled (%{course})', :appointment_name => asset.title, :course => asset.contexts_for_user(user).map(&:name).join(", ")) %> <% end %> <%= t :message, 'All appointments for "%{appointment_name}" have been canceled.', :appointment_name => asset.title %> <%= before_label :details, "Appointment details" %> <%= before_label :dates, "Date(s)" %> <%= date_string(asset.start_at, asset.end_at, :no_words) %> -<%= before_label :course, "Course" %> <%= asset.context.name %> +<%= before_label :course, "Course" %> <%= + asset.participant_type == 'Group' ? + asset.contexts.first.name : + asset.contexts_for_user(user).map(&:name).join(", ") %>
<%= before_label :cancel_reason, "Reason for canceling" %> -<%= asset.cancel_reason || t(:no_reason_given, "none given") %> \ No newline at end of file +<%= asset.cancel_reason || t(:no_reason_given, "none given") %> diff --git a/app/messages/appointment_group_deleted.facebook.erb b/app/messages/appointment_group_deleted.facebook.erb index 8b85d0e3a86..75526190b79 100644 --- a/app/messages/appointment_group_deleted.facebook.erb +++ b/app/messages/appointment_group_deleted.facebook.erb @@ -2,7 +2,10 @@
<%= before_label :details, "Appointment details" %>
<%= before_label :dates, "Date(s)" %> <%= date_string(asset.start_at, asset.end_at, :no_words) %>
-<%= before_label :course, "Course" %> <%= asset.context.name %>
+<%= before_label :course, "Course" %> <%= + asset.participant_type == 'Group' ? + asset.contexts.first.name : + asset.contexts_for_user(user).map(&:name).join(", ") %>

<%= before_label :cancel_reason, "Reason for canceling" %>
-<%= asset.cancel_reason || t(:no_reason_given, "none given") %> \ No newline at end of file +<%= asset.cancel_reason || t(:no_reason_given, "none given") %> diff --git a/app/messages/appointment_group_deleted.sms.erb b/app/messages/appointment_group_deleted.sms.erb index a12e4da6c0d..e43ed23e1c3 100644 --- a/app/messages/appointment_group_deleted.sms.erb +++ b/app/messages/appointment_group_deleted.sms.erb @@ -1,3 +1,3 @@ <%= t :message, 'All appointments for "%{appointment_name} have been canceled".', :appointment_name => asset.title %> -<%= t :more_info, "More info at %{url}", :url => HostUrl.context_host(asset.context) %> \ No newline at end of file +<%= t :more_info, "More info at %{url}", :url => HostUrl.context_host(asset.contexts.first) %> diff --git a/app/messages/appointment_group_published.email.erb b/app/messages/appointment_group_published.email.erb index f558766198b..f798307ab71 100644 --- a/app/messages/appointment_group_published.email.erb +++ b/app/messages/appointment_group_published.email.erb @@ -1,9 +1,10 @@ <% define_content :link do %> - http://<%= HostUrl.context_host(asset.context) %>/appointment_groups/<%= asset.id %> + http://<%= HostUrl.context_host(asset.contexts.first) %>/appointment_groups/<%= asset.id %> <% end %> <% define_content :subject do %> - <%= t('subject', 'Appointment "%{appointment_name}" is available for signup (%{course})', :appointment_name => asset.title, :course => asset.context.name) %> + <%= t('subject', 'Appointment "%{appointment_name}" is available for signup (%{course})', :appointment_name => asset.title, + :course => asset.contexts_for_user(user).map(&:name).join(", ")) %> <% end %> <%= t :message, 'Time slots for "%{appointment_name}" are now available for signup.', :appointment_name => asset.title %> @@ -14,7 +15,10 @@ asset.participant_type == 'Group' ? t(:group_signup, "Group (%{group_category})", :group_category => asset.sub_contexts.first.name) : t(:individual_signup, "Individual") %> -<%= before_label :course, "Course" %> <%= asset.context.name %> +<%= before_label :course, "Course" %> <%= + asset.participant_type == 'Group' ? + asset.contexts.first.name : + asset.contexts_for_user(user).map(&:name).join(", ") %> <% if asset.available_slots -%> <%= before_label :slots_remaining, "Available time slots" %> <%= asset.available_slots %> <% end -%> diff --git a/app/messages/appointment_group_published.facebook.erb b/app/messages/appointment_group_published.facebook.erb index 64334373030..b04044485bf 100644 --- a/app/messages/appointment_group_published.facebook.erb +++ b/app/messages/appointment_group_published.facebook.erb @@ -1,5 +1,5 @@ <% define_content :link do %> - http://<%= HostUrl.context_host(asset.context) %>/appointment_groups/<%= asset.id %> + http://<%= HostUrl.context_host(asset.contexts.first) %>/appointment_groups/<%= asset.id %> <% end %> <%= t :message, 'Time slots for "%{appointment_name}" are now available for signup.', :appointment_name => asset.title %>
@@ -10,7 +10,10 @@ asset.participant_type == 'Group' ? t(:group_signup, "Group (%{group_category})", :group_category => asset.sub_contexts.first.name) : t(:individual_signup, "Individual") %>
-<%= before_label :course, "Course" %> <%= asset.context.name %>
+<%= before_label :course, "Course" %> <%= + asset.participant_type == 'Group' ? + asset.contexts.first.name : + asset.contexts_for_user(user).map(&:name).join(", ") %>
<% if asset.available_slots -%> <%= before_label :slots_remaining, "Available time slots" %> <%= asset.available_slots %>
<% end -%> diff --git a/app/messages/appointment_group_published.sms.erb b/app/messages/appointment_group_published.sms.erb index 3280a15ca4e..2b10344a461 100644 --- a/app/messages/appointment_group_published.sms.erb +++ b/app/messages/appointment_group_published.sms.erb @@ -1,3 +1,3 @@ <%= t :message, 'Signups are open for "%{appointment_name}".', :appointment_name => asset.title %> -<%= t :more_info, "More info at %{url}", :url => HostUrl.context_host(asset.context) %> \ No newline at end of file +<%= t :more_info, "More info at %{url}", :url => HostUrl.context_host(asset.contexts.first) %> diff --git a/app/messages/appointment_group_published.summary.erb b/app/messages/appointment_group_published.summary.erb index 24e3c0a9a58..bc3fc2d9e21 100644 --- a/app/messages/appointment_group_published.summary.erb +++ b/app/messages/appointment_group_published.summary.erb @@ -1,9 +1,9 @@ <% define_content :link do %> - http://<%= HostUrl.context_host(asset.context) %>/appointment_groups/<%= asset.id %> + http://<%= HostUrl.context_host(asset.contexts.first) %>/appointment_groups/<%= asset.id %> <% end %> <% define_content :subject do %> - <%= t('subject', 'Appointment "%{appointment_name}" is available for signup (%{course})', :appointment_name => asset.title, :course => asset.context.name) %> + <%= t('subject', 'Appointment "%{appointment_name}" is available for signup (%{course})', :appointment_name => asset.title, :course => asset.contexts_for_user(user).map(&:name).join(", ")) %> <% end %> -<%= t :message, 'Time slots for "%{appointment_name}" are now available for signup (%{dates}).', :appointment_name => asset.title, :dates => date_string(asset.start_at, asset.end_at, :no_words) %> \ No newline at end of file +<%= t :message, 'Time slots for "%{appointment_name}" are now available for signup (%{dates}).', :appointment_name => asset.title, :dates => date_string(asset.start_at, asset.end_at, :no_words) %> diff --git a/app/messages/appointment_group_updated.email.erb b/app/messages/appointment_group_updated.email.erb index dbf660dab72..b3e20697b37 100644 --- a/app/messages/appointment_group_updated.email.erb +++ b/app/messages/appointment_group_updated.email.erb @@ -1,9 +1,9 @@ <% define_content :link do %> - http://<%= HostUrl.context_host(asset.context) %>/appointment_groups/<%= asset.id %> + http://<%= HostUrl.context_host(asset.contexts.first) %>/appointment_groups/<%= asset.id %> <% end %> <% define_content :subject do %> - <%= t('subject', 'Appointment "%{appointment_name}" has been updated (%{course})', :appointment_name => asset.title, :course => asset.context.name) %> + <%= t('subject', 'Appointment "%{appointment_name}" has been updated (%{course})', :appointment_name => asset.title, :course => asset.contexts_for_user(user).map(&:name).join(", ")) %> <% end %> <%= t :message, 'Time slots have been added to "%{appointment_name}" and are available for signup.', :appointment_name => asset.title %> @@ -14,7 +14,10 @@ asset.participant_type == 'Group' ? t(:group_signup, "Group (%{group_category})", :group_category => asset.sub_contexts.first.name) : t(:individual_signup, "Individual") %> -<%= before_label :course, "Course" %> <%= asset.context.name %> +<%= before_label :course, "Course" %> <%= + asset.participant_type == 'Group' ? + asset.contexts.first.name : + asset.contexts_for_user(user).map(&:name).join(", ") %> <% if asset.available_slots -%> <%= before_label :slots_remaining, "Available time slots" %> <%= asset.available_slots %> <% end -%> diff --git a/app/messages/appointment_group_updated.facebook.erb b/app/messages/appointment_group_updated.facebook.erb index e478367ed58..09315f5da98 100644 --- a/app/messages/appointment_group_updated.facebook.erb +++ b/app/messages/appointment_group_updated.facebook.erb @@ -1,5 +1,5 @@ <% define_content :link do %> - http://<%= HostUrl.context_host(asset.context) %>/appointment_groups/<%= asset.id %> + http://<%= HostUrl.context_host(asset.contexts.first) %>/appointment_groups/<%= asset.id %> <% end %> <%= t :message, 'Time slots have been added to "%{appointment_name}" and are available for signup.', :appointment_name => asset.title %>
@@ -10,7 +10,10 @@ asset.participant_type == 'Group' ? t(:group_signup, "Group (%{group_category})", :group_category => asset.sub_contexts.first.name) : t(:individual_signup, "Individual") %>
-<%= before_label :course, "Course" %> <%= asset.context.name %>
+<%= before_label :course, "Course" %> <%= + asset.participant_type == 'Group' ? + asset.contexts.first.name : + asset.contexts_for_user(user).map(&:name).join(", ") %>
<% if asset.available_slots -%> <%= before_label :slots_remaining, "Available time slots" %> <%= asset.available_slots %>
<% end -%> diff --git a/app/messages/appointment_group_updated.sms.erb b/app/messages/appointment_group_updated.sms.erb index 874b8e69ade..a3acce029ac 100644 --- a/app/messages/appointment_group_updated.sms.erb +++ b/app/messages/appointment_group_updated.sms.erb @@ -1,3 +1,3 @@ <%= t :message, 'New time slots are available for "%{appointment_name}".', :appointment_name => asset.title %> -<%= t :more_info, "More info at %{url}", :url => HostUrl.context_host(asset.context) %> \ No newline at end of file +<%= t :more_info, "More info at %{url}", :url => HostUrl.context_host(asset.contexts.first) %> diff --git a/app/messages/appointment_group_updated.summary.erb b/app/messages/appointment_group_updated.summary.erb index 0ea1be42139..a00dbba8dcb 100644 --- a/app/messages/appointment_group_updated.summary.erb +++ b/app/messages/appointment_group_updated.summary.erb @@ -1,9 +1,9 @@ <% define_content :link do %> - http://<%= HostUrl.context_host(asset.context) %>/appointment_groups/<%= asset.id %> + http://<%= HostUrl.context_host(asset.contexts.first) %>/appointment_groups/<%= asset.id %> <% end %> <% define_content :subject do %> - <%= t('subject', 'Appointment "%{appointment_name}" has been updated (%{course})', :appointment_name => asset.title, :course => asset.context.name) %> + <%= t('subject', 'Appointment "%{appointment_name}" has been updated (%{course})', :appointment_name => asset.title, :course => asset.contexts_for_user(user).map(&:name).join(", ")) %> <% end %> -<%= t :message, 'Time slots have been added to "%{appointment_name}" and are available for signup (%{dates}).', :appointment_name => asset.title, :dates => date_string(asset.start_at, asset.end_at, :no_words) %> \ No newline at end of file +<%= t :message, 'Time slots have been added to "%{appointment_name}" and are available for signup (%{dates}).', :appointment_name => asset.title, :dates => date_string(asset.start_at, asset.end_at, :no_words) %> diff --git a/app/messages/appointment_reserved_by_user.email.erb b/app/messages/appointment_reserved_by_user.email.erb index ed9c9c23217..6b479275c0e 100644 --- a/app/messages/appointment_reserved_by_user.email.erb +++ b/app/messages/appointment_reserved_by_user.email.erb @@ -1,9 +1,9 @@ <% define_content :link do %> - http://<%= HostUrl.context_host(asset.appointment_group.context) %>/appointment_groups/<%= asset.appointment_group.id %> + http://<%= HostUrl.context_host(asset.appointment_group.contexts.first) %>/appointment_groups/<%= asset.appointment_group.id %> <% end %> <% define_content :subject do %> - <%= t('subject', 'User signed up for "%{appointment_name}" (%{course})', :appointment_name => asset.title, :course => asset.appointment_group.context.name) %> + <%= t('subject', 'User signed up for "%{appointment_name}" (%{course})', :appointment_name => asset.title, :course => asset.appointment_group.contexts_for_user(user).map(&:name).join(", ")) %> <% end %> <%= t :message, '%{user} has signed up for "%{appointment_name}".', :user => asset.updating_user.name, :appointment_name => asset.title %> @@ -16,7 +16,9 @@ <% else -%> <%= before_label :attendee, "Attendee" %> <%= asset.context.name %> <% end -%> -<%= before_label :course, "Course" %> <%= asset.appointment_group.context.name %> +<%= before_label :course, "Course" %> <%= courses = asset.appointment_group.participant_type == 'Group' ? + asset.appointment_group.contexts.first.name : + asset.appointment_group.contexts_for_user(user).map(&:name).join(", ") %> <% if asset.appointment_group.available_slots -%> <%= before_label :slots_remaining, "Available time slots" %> <%= asset.appointment_group.available_slots %> <% end -%> diff --git a/app/messages/appointment_reserved_by_user.facebook.erb b/app/messages/appointment_reserved_by_user.facebook.erb index e9d92ddd8bd..1bfe4b1e4cd 100644 --- a/app/messages/appointment_reserved_by_user.facebook.erb +++ b/app/messages/appointment_reserved_by_user.facebook.erb @@ -1,5 +1,5 @@ <% define_content :link do %> - http://<%= HostUrl.context_host(asset.appointment_group.context) %>/appointment_groups/<%= asset.appointment_group.id %> + http://<%= HostUrl.context_host(asset.appointment_group.contexts.first) %>/appointment_groups/<%= asset.appointment_group.id %> <% end %> <%= t :message, '%{user} has signed up for "%{appointment_name}".', :user => asset.updating_user.name, :appointment_name => asset.title %>
@@ -12,9 +12,11 @@ <% else -%> <%= before_label :attendee, "Attendee" %> <%= asset.context.name %>
<% end -%> -<%= before_label :course, "Course" %> <%= asset.appointment_group.context.name %>
+<%= before_label :course, "Course" %> <%= asset.appointment_group.participant_type == 'Group' ? + asset.appointment_group.contexts.first.name : + asset.appointment_group.contexts_for_user(user).map(&:name).join(", ") %>
<% if asset.appointment_group.available_slots -%> <%= before_label :slots_remaining, "Available time slots" %> <%= asset.appointment_group.available_slots %>
<% end -%>
-<%= t :instructions, "View the appointment" %> \ No newline at end of file +<%= t :instructions, "View the appointment" %> diff --git a/app/messages/appointment_reserved_by_user.sms.erb b/app/messages/appointment_reserved_by_user.sms.erb index c56c0f51664..7113dd2767b 100644 --- a/app/messages/appointment_reserved_by_user.sms.erb +++ b/app/messages/appointment_reserved_by_user.sms.erb @@ -1,3 +1,3 @@ <%= t :message, '%{user} has signed up for "%{appointment_name}".', :user => asset.updating_user.name, :appointment_name => asset.title %> -<%= t :more_info, "More info at %{url}", :url => HostUrl.context_host(asset.appointment_group.context) %> \ No newline at end of file +<%= t :more_info, "More info at %{url}", :url => HostUrl.context_host(asset.appointment_group.contexts.first) %> diff --git a/app/messages/appointment_reserved_for_user.email.erb b/app/messages/appointment_reserved_for_user.email.erb index 1f67780303e..1f4e91c0204 100644 --- a/app/messages/appointment_reserved_for_user.email.erb +++ b/app/messages/appointment_reserved_for_user.email.erb @@ -1,5 +1,5 @@ <% define_content :link do %> - http://<%= HostUrl.context_host(asset.appointment_group.context) %>/appointment_groups/<%= asset.appointment_group.id %> + http://<%= HostUrl.context_host(asset.appointment_group.contexts.first) %>/appointment_groups/<%= asset.appointment_group.id %> <% end %> <% define_content :subject do %> @@ -16,9 +16,11 @@ <% else -%> <%= before_label :attendee, "Attendee" %> <%= asset.context.name %> <% end -%> -<%= before_label :course, "Course" %> <%= asset.appointment_group.context.name %> +<%= before_label :course, "Course" %> <%= asset.appointment_group.participant_type == 'Group' ? + asset.appointment_group.contexts.first.name : + asset.appointment_group.contexts_for_user(user).map(&:name).join(", ") %> <% if asset.appointment_group.available_slots -%> <%= before_label :slots_remaining, "Available time slots" %> <%= asset.appointment_group.available_slots %> <% end -%> -<%= t :instructions, 'Sign up for a different time slot at the following link: %{link}', :link => content(:link) %> \ No newline at end of file +<%= t :instructions, 'Sign up for a different time slot at the following link: %{link}', :link => content(:link) %> diff --git a/app/messages/appointment_reserved_for_user.facebook.erb b/app/messages/appointment_reserved_for_user.facebook.erb index 5cbc1cf8185..fb6f217d610 100644 --- a/app/messages/appointment_reserved_for_user.facebook.erb +++ b/app/messages/appointment_reserved_for_user.facebook.erb @@ -1,5 +1,5 @@ <% define_content :link do %> - http://<%= HostUrl.context_host(asset.appointment_group.context) %>/appointment_groups/<%= asset.appointment_group.id %> + http://<%= HostUrl.context_host(asset.appointment_group.contexts.first) %>/appointment_groups/<%= asset.appointment_group.id %> <% end %> <%= t :message, '%{user} has signed you up for "%{appointment_name}".', :user => asset.updating_user.name, :appointment_name => asset.title %>
@@ -12,9 +12,11 @@ <% else -%> <%= before_label :attendee, "Attendee" %> <%= asset.context.name %>
<% end -%> -<%= before_label :course, "Course" %> <%= asset.appointment_group.context.name %>
+<%= before_label :course, "Course" %> <%= asset.appointment_group.participant_type == 'Group' ? + asset.appointment_group.contexts.first.name : + asset.appointment_group.contexts_for_user(user).map(&:name).join(", ") %>
<% if asset.appointment_group.available_slots -%> <%= before_label :slots_remaining, "Available time slots" %> <%= asset.appointment_group.available_slots %>
<% end -%>
-<%= t :instructions, 'Sign up for a different time slot' %> \ No newline at end of file +<%= t :instructions, 'Sign up for a different time slot' %> diff --git a/app/messages/appointment_reserved_for_user.sms.erb b/app/messages/appointment_reserved_for_user.sms.erb index 2a17889e3ef..214294d0a1f 100644 --- a/app/messages/appointment_reserved_for_user.sms.erb +++ b/app/messages/appointment_reserved_for_user.sms.erb @@ -1,3 +1,3 @@ <%= t :message, 'You have been signed up for "%{appointment_name}".', :appointment_name => asset.title %> -<%= t :more_info, "More info at %{url}", :url => HostUrl.context_host(asset.appointment_group.context) %> \ No newline at end of file +<%= t :more_info, "More info at %{url}", :url => HostUrl.context_host(asset.appointment_group.contexts.first) %> diff --git a/app/messages/appointment_reserved_for_user.summary.erb b/app/messages/appointment_reserved_for_user.summary.erb index 6b8f4082a4e..80e17da1a1c 100644 --- a/app/messages/appointment_reserved_for_user.summary.erb +++ b/app/messages/appointment_reserved_for_user.summary.erb @@ -1,9 +1,13 @@ <% define_content :link do %> - http://<%= HostUrl.context_host(asset.appointment_group.context) %>/appointment_groups/<%= asset.appointment_group.id %> + http://<%= HostUrl.context_host(asset.appointment_group.contexts.first) %>/appointment_groups/<%= asset.appointment_group.id %> <% end %> <% define_content :subject do %> - <%= t('subject', 'You have been signed up for "%{appointment_name}" (%{course})', :appointment_name => asset.title, :course => asset.appointment_group.context.name) %> + <%= t('subject', 'You have been signed up for "%{appointment_name}" (%{course})', + :appointment_name => asset.title, + :course => asset.appointment_group.participant_type == 'Group' ? + asset.appointment_group.contexts.first.name : + asset.appointment_group.contexts_for_user(user).map(&:name).join(", ")) %> <% end %> -<%= t :message, '%{user} has signed you up for "%{appointment_name}" (%{date_and_time}).', :user => asset.updating_user.name, :appointment_name => asset.title, :date_and_time => datetime_string(asset.start_at, asset.end_at) %> \ No newline at end of file +<%= t :message, '%{user} has signed you up for "%{appointment_name}" (%{date_and_time}).', :user => asset.updating_user.name, :appointment_name => asset.title, :date_and_time => datetime_string(asset.start_at, asset.end_at) %> diff --git a/app/models/appointment_group.rb b/app/models/appointment_group.rb index b5777199ce7..bf3d09c69f3 100644 --- a/app/models/appointment_group.rb +++ b/app/models/appointment_group.rb @@ -26,10 +26,13 @@ class AppointmentGroup < ActiveRecord::Base # appointments_participants conditions have the correct table alias has_many :_appointments, opts.merge(:conditions => opts[:conditions].gsub(/calendar_events\./, 'calendar_events_join.')) has_many :appointments_participants, :through => :_appointments, :source => :child_events, :conditions => "calendar_events.workflow_state <> 'deleted'", :order => :start_at - belongs_to :context, :polymorphic => true - alias_method :effective_context, :context + has_many :appointment_group_contexts has_many :appointment_group_sub_contexts, :include => :sub_context + def contexts + appointment_group_contexts.map &:context + end + def sub_contexts appointment_group_sub_contexts.map &:sub_context end @@ -40,7 +43,6 @@ class AppointmentGroup < ActiveRecord::Base validates_length_of :title, :maximum => maximum_string_length validates_length_of :description, :maximum => maximum_long_text_length, :allow_nil => true, :allow_blank => true - validates_presence_of :context validates_inclusion_of :participant_visibility, :in => ['private', 'protected'] # presumably we might add public if we decide to show appointments on the public calendar feed validates_each :appointments do |record, attr, value| next unless record.new_appointments.present? || record.validation_event_override @@ -55,8 +57,14 @@ class AppointmentGroup < ActiveRecord::Base end end - attr_accessible :title, :description, :location_name, :location_address, :context, :sub_context_codes, :participants_per_appointment, :min_appointments_per_participant, :max_appointments_per_participant, :new_appointments, :participant_visibility, :cancel_reason - attr_readonly :context_id, :context_type, :context_code + def validate + if appointment_group_contexts.empty? + errors.add(:appointment_group_contexts, + t('errors.needs_contexts', 'Must have at least one context')) + end + end + + attr_accessible :title, :description, :location_name, :location_address, :contexts, :sub_context_codes, :participants_per_appointment, :min_appointments_per_participant, :max_appointments_per_participant, :new_appointments, :participant_visibility, :cancel_reason # when creating/updating an appointment, you can give it a list of (new) # appointment times. these will be added to the existing appointment times @@ -73,12 +81,22 @@ class AppointmentGroup < ActiveRecord::Base end attr_accessor :validation_event_override attr_accessor :cancel_reason + attr_accessor :context def reload remove_instance_variable :@new_appointments if @new_appointments super end + def contexts=(new_contexts) + new_contexts -= self.contexts + self.appointment_group_contexts += new_contexts.map do |context| + AppointmentGroupContext.create! :appointment_group => self, + :context => context + end + appointments.update_all :effective_context_code => contexts.map(&:asset_string).join(",") + end + def sub_context_codes=(codes) if new_record? sub_contexts = codes.map do |code| @@ -104,11 +122,13 @@ class AppointmentGroup < ActiveRecord::Base codes = user.appointment_context_codes { :select => "DISTINCT appointment_groups.*", - :joins => "LEFT JOIN appointment_group_sub_contexts sc " \ + :joins => "JOIN appointment_group_contexts agc " \ + "ON appointment_groups.id = agc.appointment_group_id " \ + "LEFT JOIN appointment_group_sub_contexts sc " \ "ON appointment_groups.id = sc.appointment_group_id", :conditions => [<<-COND, codes[:primary], codes[:secondary]] workflow_state = 'active' - AND context_code IN (?) + AND agc.context_code IN (?) AND ( sc.sub_context_code IS NULL OR sc.sub_context_code IN (?) @@ -128,13 +148,15 @@ class AppointmentGroup < ActiveRecord::Base end { :select => "DISTINCT appointment_groups.*", - :joins => "LEFT JOIN appointment_group_sub_contexts sc " \ + :joins => "JOIN appointment_group_contexts agc " \ + "ON appointment_groups.id = agc.appointment_group_id " \ + "LEFT JOIN appointment_group_sub_contexts sc " \ "ON appointment_groups.id = sc.appointment_group_id", :conditions => [<<-COND, codes[:full] + codes[:limited], codes[:full], codes[:secondary]] workflow_state <> 'deleted' - AND context_code IN (?) + AND agc.context_code IN (?) AND ( - context_code IN (?) + agc.context_code IN (?) OR sc.sub_context_code IN (?) ) COND @@ -153,9 +175,13 @@ class AppointmentGroup < ActiveRecord::Base set_policy do given { |user, session| next false if deleted? - next false unless cached_context_grants_right?(user, nil, :manage_calendar) - next true if appointment_group_sub_contexts.any? { |sc| sc.sub_context_type == 'CourseSection' && context.section_visibilities_for(user).any?{ |v| sc.sub_context_id == v[:course_section_id] } } - !context.visibility_limited_to_course_sections?(user) + next false unless contexts.all? { |c| c.grants_right? user, nil, :manage_calendar } + if appointment_group_sub_contexts.present? + raise "you can't have multiple contexts and sub_contexts" if appointment_group_contexts.size > 1 + context = contexts.first + next true if appointment_group_sub_contexts.any? { |sc| sc.sub_context_type == 'CourseSection' && context.section_visibilities_for(user).any?{ |v| sc.sub_context_id == v[:course_section_id] } } + end + !contexts.all? { |c| c.visibility_limited_to_course_sections?(user) } } can :manage and can :manage_calendar and can :read and can :read_appointment_participants and can :create and can :update and can :delete @@ -177,15 +203,15 @@ class AppointmentGroup < ActiveRecord::Base set_broadcast_policy do dispatch :appointment_group_published to { possible_users } - whenever { context.available? && active? && workflow_state_changed? } + whenever { contexts.any?(&:available?) && active? && workflow_state_changed? } dispatch :appointment_group_updated to { possible_users } - whenever { context.available? && active? && new_appointments && !workflow_state_changed? } + whenever { contexts.any?(&:available?) && active? && new_appointments && !workflow_state_changed? } dispatch :appointment_group_deleted to { possible_users } - whenever { context.available? && deleted? && workflow_state_changed? } + whenever { contexts.any?(&:available?) && deleted? && workflow_state_changed? } end def possible_users @@ -195,17 +221,20 @@ class AppointmentGroup < ActiveRecord::Base end def instructors - sub_context_type == 'CourseSection' ? - context.participating_instructors.restrict_to_sections(sub_context_id).uniq : - context.participating_instructors.uniq + if sub_context_type == "CourseSection" + contexts.map { |c| c.participating_instructors.restrict_to_sections(sub_context_id) }.flatten.uniq + else + contexts.map(&:participating_instructors).flatten.uniq + end end def possible_participants(registration_status=nil) participants = if participant_type == 'User' sub_contexts.empty? ? - context.participating_students : + contexts.map(&:participating_students).flatten : sub_contexts.map(&:participating_students).flatten else + # FIXME? sub_contexts.map(&:groups).flatten end participant_ids = self.participant_ids @@ -242,7 +271,7 @@ class AppointmentGroup < ActiveRecord::Base def eligible_participant?(participant) return false unless participant && participant.class.base_ar_class.name == participant_type codes = participant.appointment_context_codes - return false unless codes[:primary].include?(context_code) + return false unless (codes[:primary]. & appointment_group_contexts.map(&:context_code)).present? return false unless sub_context_codes.empty? || (codes[:secondary] & sub_context_codes).present? true end @@ -337,7 +366,6 @@ class AppointmentGroup < ActiveRecord::Base end def default_values - self.context_code ||= context_string self.participant_visibility ||= 'private' end @@ -357,4 +385,21 @@ class AppointmentGroup < ActiveRecord::Base self.appointments.map{ |a| a.destroy(false) } end end + + def contexts_for_user(user) + context_codes = context_codes_for_user(user) + course_ids = appointment_group_contexts.all(:conditions => {:context_code => context_codes}).map(&:context_id) + Course.all(:conditions => {:id => course_ids}) + end + + def context_codes_for_user(user) + manageable_codes = user.manageable_appointment_context_codes + user_codes = user.appointment_context_codes[:primary] | + manageable_codes[:full] | manageable_codes[:limited] + context_codes & user_codes + end + + def context_codes + appointment_group_contexts.map(&:context_code) + end end diff --git a/app/models/appointment_group_context.rb b/app/models/appointment_group_context.rb new file mode 100644 index 00000000000..03efdffb4de --- /dev/null +++ b/app/models/appointment_group_context.rb @@ -0,0 +1,30 @@ +# +# Copyright (C) 2012 Instructure, Inc. +# +# This file is part of Canvas. +# +# Canvas is free software: you can redistribute it and/or modify it under +# the terms of the GNU Affero General Public License as published by the Free +# Software Foundation, version 3 of the License. +# +# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License along +# with this program. If not, see . +# + +class AppointmentGroupContext < ActiveRecord::Base + belongs_to :appointment_group + belongs_to :context, :polymorphic => true + + attr_accessible :appointment_group, :context + + before_validation :default_values + + def default_values + self.context_code ||= context_string + end +end diff --git a/app/models/appointment_group_sub_context.rb b/app/models/appointment_group_sub_context.rb index ebf35ca1bb3..b01b5e2e33e 100644 --- a/app/models/appointment_group_sub_context.rb +++ b/app/models/appointment_group_sub_context.rb @@ -24,10 +24,10 @@ class AppointmentGroupSubContext < ActiveRecord::Base validates_each :sub_context do |record, attr, value| if record.participant_type == 'User' - record.errors.add(attr, t('errors.invalid_course_section', 'Invalid course section')) unless value.blank? || value.is_a?(CourseSection) && value.course == record.appointment_group.context + record.errors.add(attr, t('errors.invalid_course_section', 'Invalid course section')) unless value.blank? || value.is_a?(CourseSection) && record.appointment_group.contexts.any? { |c| c == value.course } else record.errors.add(attr, t('errors.missing_group_category', 'Group appointments must have a group category')) unless value.present? && value.is_a?(GroupCategory) - record.errors.add(attr, t('errors.invalid_group_category', 'Invalid group category')) unless value && value.context == record.appointment_group.context + record.errors.add(attr, t('errors.invalid_group_category', 'Invalid group category')) unless value && record.appointment_group.contexts.any? { |c| c == value.context } end end diff --git a/app/models/assignment.rb b/app/models/assignment.rb index dd2bbc9c282..0bd2ab545e6 100644 --- a/app/models/assignment.rb +++ b/app/models/assignment.rb @@ -46,7 +46,6 @@ class Assignment < ActiveRecord::Base has_one :rubric, :through => :rubric_association has_one :teacher_enrollment, :class_name => 'TeacherEnrollment', :foreign_key => 'course_id', :primary_key => 'context_id', :include => :user, :conditions => ['enrollments.workflow_state = ?', 'active'] belongs_to :context, :polymorphic => true - alias_method :effective_context, :context belongs_to :cloned_item belongs_to :grading_standard belongs_to :group_category diff --git a/app/models/calendar_event.rb b/app/models/calendar_event.rb index 23e801d9afd..d99234849b3 100644 --- a/app/models/calendar_event.rb +++ b/app/models/calendar_event.rb @@ -125,7 +125,13 @@ class CalendarEvent < ActiveRecord::Base all_codes = codes | effectively_courses_codes group_codes = codes.grep(/\Aappointment_group_\d+\z/) codes -= group_codes - {:conditions => [<<-SQL, all_codes, codes, group_codes, effectively_courses_codes, codes]} + + codes_conditions = codes.map { |code| + wildcard(quoted_table_name + '.effective_context_code', code, :delimiter => ',') + }.join(" OR ") + codes_conditions = self.connection.quote(false) if codes_conditions.blank? + + {:conditions => [<<-SQL, all_codes, codes, group_codes, effectively_courses_codes]} calendar_events.context_code IN (?) AND ( ( -- explicit contexts (e.g. course_123) @@ -137,7 +143,7 @@ class CalendarEvent < ActiveRecord::Base ) OR ( -- own appointment_participants, or section events in the course calendar_events.context_code IN (?) - AND calendar_events.effective_context_code IN (?) + AND (#{codes_conditions}) ) ) SQL @@ -186,13 +192,13 @@ class CalendarEvent < ActiveRecord::Base if parent_event self.effective_context_code = if appointment_group # appointment participant - appointment_group.context_code if appointment_group.participant_type == 'User' - else # e.g. section-level event - parent_event.context_code - end + appointment_group.appointment_group_contexts.map(&:context_code).join(',') if appointment_group.participant_type == 'User' + else # e.g. section-level event + parent_event.context_code + end (locked? ? LOCKED_ATTRIBUTES : CASCADED_ATTRIBUTES).each{ |attr| send("#{attr}=", parent_event.send(attr)) } elsif context.is_a?(AppointmentGroup) - self.effective_context_code = context.context_code + self.effective_context_code = context.appointment_group_contexts.map(&:context_code).join(",") if new_record? AppointmentGroup::EVENT_ATTRIBUTES.each { |attr| send("#{attr}=", attr == :description ? context.description_html : context.send(attr)) } if locked? diff --git a/app/models/course.rb b/app/models/course.rb index 12993b5cabd..caba42dcc8b 100644 --- a/app/models/course.rb +++ b/app/models/course.rb @@ -162,7 +162,8 @@ class Course < ActiveRecord::Base has_many :content_exports has_many :course_imports has_many :alerts, :as => :context, :include => :criteria - has_many :appointment_groups, :as => :context + has_many :appointment_group_contexts, :as => :context + has_many :appointment_groups, :through => :appointment_group_contexts has_many :appointment_participants, :class_name => 'CalendarEvent', :foreign_key => :effective_context_code, :primary_key => :asset_string, :conditions => "workflow_state = 'locked' AND parent_calendar_event_id IS NOT NULL" attr_accessor :import_source has_many :zip_file_imports, :as => :context diff --git a/app/models/user.rb b/app/models/user.rb index e3618b15d30..43297abe350 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1680,7 +1680,9 @@ class User < ActiveRecord::Base events = CalendarEvent.active.for_user_and_context_codes(self, context_codes).between(Time.now.utc, opts[:end_at]).scoped(:limit => opts[:limit]).reject(&:hidden?) events += Assignment.active.for_context_codes(context_codes).due_between(Time.now.utc, opts[:end_at]).scoped(:limit => opts[:limit]).include_submitted_count - events += AppointmentGroup.manageable_by(self, context_codes).intersecting(Time.now.utc, opts[:end_at]).scoped(:limit => opts[:limit]) + appointment_groups = AppointmentGroup.manageable_by(self, context_codes).intersecting(Time.now.utc, opts[:end_at]).scoped(:limit => opts[:limit]) + appointment_groups.each { |ag| ag.context = ag.contexts_for_user(self).first } + events += appointment_groups events.sort_by{|e| [e.start_at, e.title] }.uniq.first(opts[:limit]) end diff --git a/app/stylesheets/calendar/scheduler.sass b/app/stylesheets/calendar/scheduler.sass index 4127a7ac733..855aaf0e852 100644 --- a/app/stylesheets/calendar/scheduler.sass +++ b/app/stylesheets/calendar/scheduler.sass @@ -41,10 +41,11 @@ button.single_item_done_button margin: 10px .unpublished-ag background-color: #f2f2f2 +.ag-context + margin-bottom: 10px + color: #666 .ag-context a color: #666 - margin-bottom: 10px - display: block .ag-x-of-x-signed-up font-size: 11px @@ -120,6 +121,40 @@ button.single_item_done_button margin: 0 padding: 0 +.ag_contexts_selector + width: 225px +.ag-menu-container + position: relative +.ag_contexts_menu + position: absolute + width: 223px + background: white + border: 1px solid #b6b6b6 + margin-top: -3px + padding-bottom: 5px +.ag-contexts + +reset_list + max-height: 150px + overflow-y: auto +.ag_sections + +reset_list + padding-left: 35px +.ag_sections_toggle + display: block + width: 16px + height: 16px + float: left + background-image: url(/images/jqueryui/icon_sprite.png) + background-position: -16px -80px +.ag-sections-expanded + background-position: 0px -64px + +.ag_contexts_done + float: right + +.hidden + display: none + .delete-block-link +accessible_text_replacement display: block @@ -154,6 +189,12 @@ button.single_item_done_button .context_id max-width: 200px +.select-calendar-container + margin: 0 0 1.5em + +.group-signup + margin: 0 0 1.5em + #message_participants_form padding: 10px textarea diff --git a/app/views/courses/_recent_event.html.erb b/app/views/courses/_recent_event.html.erb index 79582459210..c77bfa74b38 100644 --- a/app/views/courses/_recent_event.html.erb +++ b/app/views/courses/_recent_event.html.erb @@ -35,7 +35,9 @@ <%= datetime_string(recent_event.start_at, :event, recent_event.end_at) %> <% end %> <% if show_context %> - <%= recent_event.effective_context.short_name %> + + <%= recent_event.is_a?(CalendarEvent) ? recent_event.effective_context.short_name : recent_event.context.short_name %> + <% end %> diff --git a/app/views/jst/calendar/appointmentGroupList.handlebars b/app/views/jst/calendar/appointmentGroupList.handlebars index cc40a98d29b..cdf61c6f7da 100644 --- a/app/views/jst/calendar/appointmentGroupList.handlebars +++ b/app/views/jst/calendar/appointmentGroupList.handlebars @@ -32,9 +32,9 @@ {{/if}}

{{title}}

- {{#if context}} - - {{/if}} +
+ {{#toSentence contexts}}{{name}}{{/toSentence}} +
{{#if location_name}}
{{#t "location"}}Location:{{/t}} diff --git a/app/views/jst/calendar/contextSelector.handlebars b/app/views/jst/calendar/contextSelector.handlebars new file mode 100644 index 00000000000..3b9b20523f7 --- /dev/null +++ b/app/views/jst/calendar/contextSelector.handlebars @@ -0,0 +1,4 @@ + diff --git a/app/views/jst/calendar/contextSelectorItem.handlebars b/app/views/jst/calendar/contextSelectorItem.handlebars new file mode 100644 index 00000000000..5e1af0076b5 --- /dev/null +++ b/app/views/jst/calendar/contextSelectorItem.handlebars @@ -0,0 +1,13 @@ +
  • + + + + +
  • diff --git a/app/views/jst/calendar/editAppointmentGroup.handlebars b/app/views/jst/calendar/editAppointmentGroup.handlebars index 6e78a8fea45..26c314e7fa1 100644 --- a/app/views/jst/calendar/editAppointmentGroup.handlebars +++ b/app/views/jst/calendar/editAppointmentGroup.handlebars @@ -8,33 +8,21 @@ {{#t "location"}}Location{{/t}}

    -

    +

    {{#t "calendar"}}Calendar{{/t}}
    - -

    + +
    +

    -

    -

    diff --git a/db/migrate/20120307222744_create_appointment_group_contexts.rb b/db/migrate/20120307222744_create_appointment_group_contexts.rb new file mode 100644 index 00000000000..a38d0b043cb --- /dev/null +++ b/db/migrate/20120307222744_create_appointment_group_contexts.rb @@ -0,0 +1,17 @@ +class CreateAppointmentGroupContexts < ActiveRecord::Migration + tag :predeploy + + def self.up + create_table :appointment_group_contexts do |t| + t.references :appointment_group, :limit => 8 + t.string :context_code + t.integer :context_id, :limit => 8 + t.string :context_type + t.timestamps + end + end + + def self.down + drop_table :appointment_group_contexts + end +end diff --git a/db/migrate/20120430164933_migrate_appointment_group_contexts.rb b/db/migrate/20120430164933_migrate_appointment_group_contexts.rb new file mode 100644 index 00000000000..13eca507707 --- /dev/null +++ b/db/migrate/20120430164933_migrate_appointment_group_contexts.rb @@ -0,0 +1,21 @@ +class MigrateAppointmentGroupContexts < ActiveRecord::Migration + tag :postdeploy + + def self.up + records = AppointmentGroup.all.map { |ag| + { + :appointment_group_id => ag.id, + :context_code => ag.context_code, + :context_type => ag.context_type, + :context_id => ag.context_id, + :updated_at => ag.updated_at, + :created_at => ag.created_at + } + } + + bulk_insert :appointment_group_contexts, records + end + + def self.down + end +end diff --git a/lib/api/v1/calendar_event.rb b/lib/api/v1/calendar_event.rb index d2a36f2feda..dbe06d5b5ff 100644 --- a/lib/api/v1/calendar_event.rb +++ b/lib/api/v1/calendar_event.rb @@ -37,8 +37,20 @@ module Api::V1::CalendarEvent participant = nil hash = api_json(event, user, session, :only => %w(id created_at updated_at start_at end_at all_day all_day_date title description location_address location_name workflow_state)) - hash['context_code'] = event.context_code - hash['effective_context_code'] = event.effective_context_code if event.effective_context_code + + appointment_group_id = (options[:appointment_group_id] || event.appointment_group.try(:id)) + + if event.effective_context_code + if appointment_group_id + codes_for_user = AppointmentGroup.find(appointment_group_id).contexts_for_user(user).map(&:asset_string) + hash['context_code'] = (event.effective_context_code.split(',') & codes_for_user).first + hash['effective_context_code'] = hash['context_code'] + else + hash['effective_context_code'] = event.effective_context_code + end + end + hash['context_code'] ||= event.context_code + hash["child_events_count"] = event.child_events.size hash['parent_event_id'] = event.parent_calendar_event_id hash['hidden'] = event.hidden? @@ -50,7 +62,7 @@ module Api::V1::CalendarEvent hash['group'] = group_json(event.context, user, session, :include => ['users']) end end - if appointment_group_id = (options[:appointment_group_id] || event.appointment_group.try(:id)) + if appointment_group_id hash['appointment_group_id'] = appointment_group_id hash['appointment_group_url'] = api_v1_appointment_group_url(appointment_group_id) if options[:current_participant] && event.has_asset?(options[:current_participant]) @@ -99,11 +111,12 @@ module Api::V1::CalendarEvent def appointment_group_json(group, user, session, options={}) orig_context = @context - @context = group.context + @context = group.contexts_for_user(user).first @user_json_is_admin = nil # when returning multiple groups, @current_user may be admin over some contexts but not others. so we need to recheck include = options[:include] || [] - hash = api_json(group, user, session, :only => %w{id context_code created_at description end_at location_address location_name max_appointments_per_participant min_appointments_per_participant participants_per_appointment start_at title updated_at workflow_state participant_visibility}, :methods => :sub_context_codes) + hash = api_json(group, user, session, :only => %w{id created_at description end_at location_address location_name max_appointments_per_participant min_appointments_per_participant participants_per_appointment start_at title updated_at workflow_state participant_visibility}, :methods => :sub_context_codes) + hash['context_codes'] = group.context_codes_for_user(user) hash['requiring_action'] = group.requiring_action?(user) if group.new_appointments.present? hash['new_appointments'] = group.new_appointments.map{ |event| calendar_event_json(event, user, session, :skip_details => true, :appointment_group_id => group.id) } diff --git a/spec/apis/v1/appointment_groups_api_spec.rb b/spec/apis/v1/appointment_groups_api_spec.rb index 11debcce630..aa0e16244cd 100644 --- a/spec/apis/v1/appointment_groups_api_spec.rb +++ b/spec/apis/v1/appointment_groups_api_spec.rb @@ -25,7 +25,7 @@ describe AppointmentGroupsController, :type => :integration do end expected_fields = [ - 'appointments_count', 'context_code', 'created_at', 'description', + 'appointments_count', 'context_codes', 'created_at', 'description', 'end_at', 'id', 'location_address', 'location_name', 'max_appointments_per_participant', 'min_appointments_per_participant', 'participant_type', 'participant_visibility', @@ -34,14 +34,14 @@ describe AppointmentGroupsController, :type => :integration do ] it 'should return manageable appointment groups' do - ag1 = @course.appointment_groups.create(:title => "something") + ag1 = AppointmentGroup.create!(:title => "something", :contexts => [@course]) cat = @course.group_categories.create - ag2 = @course.appointment_groups.create(:title => "another", :sub_context_codes => [cat.asset_string]) - ag3 = Course.create.appointment_groups.create(:title => "inaccessible") - ag4 = @course.appointment_groups.create(:title => "past", :new_appointments => [["#{Time.now.year - 1}-01-01 12:00:00", "#{Time.now.year - 1}-01-01 13:00:00"]]) + ag2 = AppointmentGroup.create!(:title => "another", :contexts => [@course], :sub_context_codes => [cat.asset_string]) + ag3 = AppointmentGroup.create!(:title => "inaccessible", :contexts => [Course.create!]) + ag4 = AppointmentGroup.create!(:title => "past", :contexts => [@course], :new_appointments => [["#{Time.now.year - 1}-01-01 12:00:00", "#{Time.now.year - 1}-01-01 13:00:00"]]) json = api_call(:get, "/api/v1/appointment_groups?scope=manageable", { - :controller => 'appointment_groups', :action => 'index', :format => 'json', :scope => 'manageable'}) + :controller => 'appointment_groups', :action => 'index', :format => 'json', :scope => 'manageable'}) json.size.should eql 2 json.first.keys.sort.should eql expected_fields json.first.slice('id', 'title', 'participant_type').should eql({'id' => ag1.id, 'title' => 'something', 'participant_type' => 'User'}) @@ -49,36 +49,36 @@ describe AppointmentGroupsController, :type => :integration do end it "should return past manageable appointment groups, if requested" do - ag = @course.appointment_groups.create(:title => "past", :new_appointments => [["#{Time.now.year - 1}-01-01 12:00:00", "#{Time.now.year - 1}-01-01 13:00:00"]]) + ag = AppointmentGroup.create!(:title => "past", :new_appointments => [["#{Time.now.year - 1}-01-01 12:00:00", "#{Time.now.year - 1}-01-01 13:00:00"]], :contexts => [@course]) json = api_call(:get, "/api/v1/appointment_groups?scope=manageable&include_past_appointments=1", { :controller => 'appointment_groups', :action => 'index', :format => 'json', :scope => 'manageable', :include_past_appointments => '1'}) json.size.should eql 1 end it 'should return reservable appointment groups' do - ag1 = @course.appointment_groups.create(:title => "can't reserve") + ag1 = AppointmentGroup.create!(:title => "can't reserve", :contexts => [@course]) ag1.publish! - ag2 = Course.create.appointment_groups.create(:title => "me neither") + ag2 = AppointmentGroup.create!(:title => "me neither", :contexts => [Course.create!]) ag2.publish! student_in_course :course => course(:active_all => true), :user => @me - ag3 = @course.appointment_groups.create(:title => "enrollment not active") + ag3 = AppointmentGroup.create!(:title => "enrollment not active", :contexts => [@course]) ag3.publish! student_in_course :course => course(:active_all => true), :user => @me, :active_all => true - ag4 = @course.appointment_groups.create(:title => "unpublished") - ag5 = @course.appointment_groups.create(:title => "no times") + ag4 = AppointmentGroup.create!(:title => "unpublished", :contexts => [@course]) + ag5 = AppointmentGroup.create!(:title => "no times", :contexts => [@course]) ag5.publish! - ag6 = @course.appointment_groups.create(:title => "yay", :new_appointments => [["#{Time.now.year + 1}-01-01 12:00:00", "#{Time.now.year + 1}-01-01 13:00:00"]]) + ag6 = AppointmentGroup.create!(:title => "yay", :new_appointments => [["#{Time.now.year + 1}-01-01 12:00:00", "#{Time.now.year + 1}-01-01 13:00:00"]], :contexts => [@course]) ag6.publish! cat = @course.group_categories.create mygroup = cat.groups.create(:context => @course) mygroup.users << @me @me.reload - ag7 = @course.appointment_groups.create(:title => "double yay", :sub_context_codes => [cat.asset_string], :new_appointments => [["#{Time.now.year + 1}-01-01 13:00:00", "#{Time.now.year + 1}-01-01 14:00:00"]]) + ag7 = AppointmentGroup.create!(:title => "double yay", :sub_context_codes => [cat.asset_string], :new_appointments => [["#{Time.now.year + 1}-01-01 13:00:00", "#{Time.now.year + 1}-01-01 14:00:00"]], :contexts => [@course]) ag7.publish! - ag8 = @course.appointment_groups.create(:title => "past", :new_appointments => [["#{Time.now.year - 1}-01-01 12:00:00", "#{Time.now.year - 1}-01-01 13:00:00"]]) + ag8 = AppointmentGroup.create!(:title => "past", :new_appointments => [["#{Time.now.year - 1}-01-01 12:00:00", "#{Time.now.year - 1}-01-01 13:00:00"]], :contexts => [@course]) ag8.publish! json = api_call(:get, "/api/v1/appointment_groups?scope=reservable", { @@ -91,7 +91,7 @@ describe AppointmentGroupsController, :type => :integration do it "should return past reservable appointment groups, if requested" do student_in_course :course => course(:active_all => true), :user => @me, :active_all => true - ag = @course.appointment_groups.create(:title => "past", :new_appointments => [["#{Time.now.year - 1}-01-01 12:00:00", "#{Time.now.year - 1}-01-01 13:00:00"]]) + ag = AppointmentGroup.create!(:title => "past", :new_appointments => [["#{Time.now.year - 1}-01-01 12:00:00", "#{Time.now.year - 1}-01-01 13:00:00"]], :contexts => [@course]) ag.publish! json = api_call(:get, "/api/v1/appointment_groups?scope=reservable&include_past_appointments=1", { :controller => 'appointment_groups', :action => 'index', :format => 'json', :scope => 'reservable', :include_past_appointments => '1'}) @@ -99,7 +99,7 @@ describe AppointmentGroupsController, :type => :integration do end it 'should paginate appointment groups' do - ids = 25.times.map { |i| @course.appointment_groups.create(:title => "#{i}").id } + ids = 25.times.map { |i| AppointmentGroup.create!(:title => "#{i}".object_id, :contexts => [@course]) } json = api_call(:get, "/api/v1/appointment_groups?scope=manageable&per_page=10", { :controller => 'appointment_groups', :action => 'index', :format => 'json', :scope => 'manageable', :per_page => '10'}) @@ -114,7 +114,7 @@ describe AppointmentGroupsController, :type => :integration do end it 'should include appointments and child_events, if requested' do - ag = @course.appointment_groups.create(:title => "something", :new_appointments => [["#{Time.now.year + 1}-01-01 12:00:00", "#{Time.now.year + 1}-01-01 13:00:00"]]) + ag = AppointmentGroup.create!(:title => "something", :new_appointments => [["#{Time.now.year + 1}-01-01 12:00:00", "#{Time.now.year + 1}-01-01 13:00:00"]], :contexts => [@course]) student = student_in_course(:course => @course, :active_all => true).user ag.appointments.first.reserve_for student, @me @user = @me @@ -136,7 +136,7 @@ describe AppointmentGroupsController, :type => :integration do end it 'should get a manageable appointment group' do - ag = @course.appointment_groups.create(:title => "something", :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]]) + ag = AppointmentGroup.create!(:title => "something", :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]], :contexts => [@course]) json = api_call(:get, "/api/v1/appointment_groups/#{ag.id}", { :controller => 'appointment_groups', :action => 'show', :format => 'json', :id => ag.id.to_s}) @@ -149,7 +149,7 @@ describe AppointmentGroupsController, :type => :integration do end it 'should include child_events, if requested' do - ag = @course.appointment_groups.create(:title => "something", :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]]) + ag = AppointmentGroup.create!(:title => "something", :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]], :contexts => [@course]) student = student_in_course(:course => @course, :active_all => true).user ag.appointments.first.reserve_for student, @me @user = @me @@ -169,7 +169,7 @@ describe AppointmentGroupsController, :type => :integration do it 'should get a reservable appointment group' do student_in_course :course => course(:active_all => true), :user => @me, :active_all => true @user = @me - ag = @course.appointment_groups.create(:title => "yay", :new_appointments => [["#{Time.now.year + 1}-01-01 12:00:00", "#{Time.now.year + 1}-01-01 13:00:00"]]) + ag = AppointmentGroup.create!(:title => "yay", :new_appointments => [["#{Time.now.year + 1}-01-01 12:00:00", "#{Time.now.year + 1}-01-01 13:00:00"]], :contexts => [@course]) ag.publish! json = api_call(:get, "/api/v1/appointment_groups/#{ag.id}", { @@ -182,7 +182,7 @@ describe AppointmentGroupsController, :type => :integration do it 'should require action until the min has been met' do student_in_course :course => course(:active_all => true), :user => @me, :active_all => true @user = @me - ag = @course.appointment_groups.create(:title => "yay", :new_appointments => [["#{Time.now.year + 1}-01-01 12:00:00", "#{Time.now.year + 1}-01-01 13:00:00"]], :min_appointments_per_participant => 1) + ag = AppointmentGroup.create!(:title => "yay", :new_appointments => [["#{Time.now.year + 1}-01-01 12:00:00", "#{Time.now.year + 1}-01-01 13:00:00"]], :min_appointments_per_participant => 1, :contexts => [@course]) ag.publish! appt = ag.appointments.first @@ -206,14 +206,14 @@ describe AppointmentGroupsController, :type => :integration do @user = @me raw_api_call(:post, "/api/v1/appointment_groups", {:controller => 'appointment_groups', :action => 'create', :format => 'json'}, - {:appointment_group => {:context_code => @course.asset_string, :title => "ohai"} }) + {:appointment_group => {:context_codes => [@course.asset_string], :title => "ohai"} }) JSON.parse(response.body)['status'].should == 'unauthorized' end it 'should create a new appointment group' do json = api_call(:post, "/api/v1/appointment_groups", {:controller => 'appointment_groups', :action => 'create', :format => 'json'}, - {:appointment_group => {:context_code => @course.asset_string, :title => "ohai", :new_appointments => {'0' => ["2012-01-01 12:00:00", "2012-01-01 13:00:00"]}} }) + {:appointment_group => {:context_codes => [@course.asset_string], :title => "ohai", :new_appointments => {'0' => ["2012-01-01 12:00:00", "2012-01-01 13:00:00"]}} }) json.keys.sort.should eql((expected_fields + ['new_appointments']).sort) json['start_at'].should eql "2012-01-01T12:00:00Z" json['end_at'].should eql "2012-01-01T13:00:00Z" @@ -224,7 +224,7 @@ describe AppointmentGroupsController, :type => :integration do it 'should create a new appointment group with a sub_context' do json = api_call(:post, "/api/v1/appointment_groups", {:controller => 'appointment_groups', :action => 'create', :format => 'json'}, - {:appointment_group => {:context_code => @course.asset_string, :sub_context_codes => [@course.default_section.asset_string], :title => "ohai"} }) + {:appointment_group => {:context_codes => [@course.asset_string], :sub_context_codes => [@course.default_section.asset_string], :title => "ohai"} }) json.keys.sort.should eql expected_fields json['workflow_state'].should eql 'pending' json['sub_context_codes'].should eql [@course.default_section.asset_string] @@ -233,7 +233,7 @@ describe AppointmentGroupsController, :type => :integration do it 'should enforce update permissions' do student_in_course :course => course(:active_all => true), :user => @me, :active_all => true @user = @me - ag = @course.appointment_groups.create(:title => "something", :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]]) + ag = AppointmentGroup.create!(:title => "something", :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]], :contexts => [@course]) raw_api_call(:put, "/api/v1/appointment_groups/#{ag.id}", {:controller => 'appointment_groups', :action => 'update', :format => 'json', :id => ag.id.to_s}, {:appointment_group => {:title => "lol"} }) @@ -241,7 +241,7 @@ describe AppointmentGroupsController, :type => :integration do end it 'should update an appointment group' do - ag = @course.appointment_groups.create(:title => "something", :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]]) + ag = AppointmentGroup.create!(:title => "something", :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]], :contexts => [@course]) json = api_call(:put, "/api/v1/appointment_groups/#{ag.id}", {:controller => 'appointment_groups', :action => 'update', :format => 'json', :id => ag.id.to_s}, {:appointment_group => {:title => "lol"} }) @@ -250,7 +250,7 @@ describe AppointmentGroupsController, :type => :integration do end it 'should ignore updates to readonly fields' do - ag = @course.appointment_groups.create(:title => "something", :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]]) + ag = AppointmentGroup.create!(:title => "something", :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]], :contexts => [@course]) json = api_call(:put, "/api/v1/appointment_groups/#{ag.id}", {:controller => 'appointment_groups', :action => 'update', :format => 'json', :id => ag.id.to_s}, {:appointment_group => {:title => "lol", :sub_context_codes => [@course.default_section.asset_string]} }) @@ -260,7 +260,7 @@ describe AppointmentGroupsController, :type => :integration do end it 'should publish an appointment group in an update through the api' do - ag = @course.appointment_groups.create(:title => "something", :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]]) + ag = AppointmentGroup.create!(:title => "something", :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]], :contexts => [@course]) ag.workflow_state.should == 'pending' json = api_call(:put, "/api/v1/appointment_groups/#{ag.id}", {:controller => 'appointment_groups', :action => 'update', :format => 'json', :id => ag.id.to_s}, @@ -272,7 +272,7 @@ describe AppointmentGroupsController, :type => :integration do it 'should publish an appointment group when creating through the api when requested' do json = api_call(:post, "/api/v1/appointment_groups", {:controller => 'appointment_groups', :action => 'create', :format => 'json'}, - {:appointment_group => {:context_code => @course.asset_string, :title => "ohai", :new_appointments => {'0' => ["2012-01-01 12:00:00", "2012-01-01 13:00:00"]}, :publish => '1'} }) + {:appointment_group => {:context_codes => [@course.asset_string], :title => "ohai", :new_appointments => {'0' => ["2012-01-01 12:00:00", "2012-01-01 13:00:00"]}, :publish => '1'} }) json['workflow_state'].should eql 'active' AppointmentGroup.find(json['id']).workflow_state.should eql 'active' end @@ -280,14 +280,14 @@ describe AppointmentGroupsController, :type => :integration do it 'should enforce delete permissions' do student_in_course :course => course(:active_all => true), :user => @me, :active_all => true @user = @me - ag = @course.appointment_groups.create(:title => "something", :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]]) + ag = AppointmentGroup.create!(:title => "something", :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]], :contexts => [@course]) raw_api_call(:delete, "/api/v1/appointment_groups/#{ag.id}", {:controller => 'appointment_groups', :action => 'destroy', :format => 'json', :id => ag.id.to_s}) JSON.parse(response.body)['status'].should == 'unauthorized' end it 'should delete an appointment group' do - ag = @course.appointment_groups.create(:title => "something", :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]]) + ag = AppointmentGroup.create!(:title => "something", :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]], :contexts => [@course]) json = api_call(:delete, "/api/v1/appointment_groups/#{ag.id}", {:controller => 'appointment_groups', :action => 'destroy', :format => 'json', :id => ag.id.to_s}) json.keys.sort.should eql expected_fields @@ -297,7 +297,7 @@ describe AppointmentGroupsController, :type => :integration do types = { 'users' => proc { - @ag = @course.appointment_groups.create(:title => "yay", :new_appointments => [["#{Time.now.year + 1}-01-01 12:00:00", "#{Time.now.year + 1}-01-01 13:00:00"], ["#{Time.now.year + 1}-01-01 13:00:00", "#{Time.now.year + 1}-01-01 14:00:00"]]) + @ag = AppointmentGroup.create!(:title => "yay", :new_appointments => [["#{Time.now.year + 1}-01-01 12:00:00", "#{Time.now.year + 1}-01-01 13:00:00"], ["#{Time.now.year + 1}-01-01 13:00:00", "#{Time.now.year + 1}-01-01 14:00:00"]], :contexts => [@course]) @ag.publish! student1 = student_in_course(:course => @course, :active_all => true).user @ag.appointments.first.reserve_for student1, @me @@ -305,7 +305,7 @@ describe AppointmentGroupsController, :type => :integration do }, 'groups' => proc { cat = @course.group_categories.create - @ag = @course.appointment_groups.create(:title => "yay", :sub_context_codes => [cat.asset_string], :new_appointments => [["#{Time.now.year + 1}-01-01 12:00:00", "#{Time.now.year + 1}-01-01 13:00:00"], ["#{Time.now.year + 1}-01-01 13:00:00", "#{Time.now.year + 1}-01-01 14:00:00"]]) + @ag = AppointmentGroup.create!(:title => "yay", :sub_context_codes => [cat.asset_string], :new_appointments => [["#{Time.now.year + 1}-01-01 12:00:00", "#{Time.now.year + 1}-01-01 13:00:00"], ["#{Time.now.year + 1}-01-01 13:00:00", "#{Time.now.year + 1}-01-01 14:00:00"]], :contexts => [@course]) @ag.publish! group1 = cat.groups.create(:context => @course) group1.users << student_in_course(:course => @course, :active_all => true).user diff --git a/spec/apis/v1/calendar_events_api_spec.rb b/spec/apis/v1/calendar_events_api_spec.rb index c01ea3500a4..49bb16e86ea 100644 --- a/spec/apis/v1/calendar_events_api_spec.rb +++ b/spec/apis/v1/calendar_events_api_spec.rb @@ -120,7 +120,7 @@ describe CalendarEventsApiController, :type => :integration do context 'appointments' do it 'should include appointments for teachers (with participant info)' do - ag1 = @course.appointment_groups.create(:title => "something", :participants_per_appointment => 4, :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]]) + ag1 = AppointmentGroup.create!(:title => "something", :participants_per_appointment => 4, :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]], :contexts => [@course]) event1 = ag1.appointments.first student_ids = [] 3.times { @@ -129,7 +129,7 @@ describe CalendarEventsApiController, :type => :integration do } cat = @course.group_categories.create - ag2 = @course.appointment_groups.create(:title => "something", :participants_per_appointment => 4, :sub_context_codes => [cat.asset_string], :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]]) + ag2 = AppointmentGroup.create!(:title => "something", :participants_per_appointment => 4, :sub_context_codes => [cat.asset_string], :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]], :contexts => [@course]) event2 = ag2.appointments.first group_ids = [] group_student_ids = [] @@ -172,13 +172,13 @@ describe CalendarEventsApiController, :type => :integration do @teacher = @course.admins.first student_in_course :course => @course, :user => @me, :active_all => true - group1 = @course.appointment_groups.create(:title => "something", :participants_per_appointment => 4, :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]]) + group1 = AppointmentGroup.create!(:title => "something", :participants_per_appointment => 4, :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]], :contexts => [@course]) group1.publish! event1 = group1.appointments.first 3.times { event1.reserve_for(student_in_course(:course => @course, :active_all => true).user, @teacher) } cat = @course.group_categories.create - group2 = @course.appointment_groups.create(:title => "something", :participants_per_appointment => 4, :sub_context_codes => [cat.asset_string], :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]]) + group2 = AppointmentGroup.create!(:title => "something", :participants_per_appointment => 4, :sub_context_codes => [cat.asset_string], :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]], :contexts => [@course]) group2.publish! event2 = group2.appointments.first g = cat.groups.create(:context => @course) @@ -208,7 +208,7 @@ describe CalendarEventsApiController, :type => :integration do @teacher = @course.admins.first student_in_course :course => @course, :user => @me, :active_all => true - group = @course.appointment_groups.create(:title => "something", :participants_per_appointment => 4, :participant_visibility => 'private', :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]]) + group = AppointmentGroup.create!(:title => "something", :participants_per_appointment => 4, :participant_visibility => 'private', :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]], :contexts => [@course]) group.publish! event = group.appointments.first event.reserve_for(@me, @teacher) @@ -231,7 +231,7 @@ describe CalendarEventsApiController, :type => :integration do @teacher = @course.admins.first student_in_course :course => @course, :user => @me, :active_all => true - group = @course.appointment_groups.create(:title => "something", :participants_per_appointment => 4, :participant_visibility => 'protected', :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]]) + group = AppointmentGroup.create!(:title => "something", :participants_per_appointment => 4, :participant_visibility => 'protected', :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]], :contexts => [@course]) group.publish! event = group.appointments.first event.reserve_for(@me, @teacher) @@ -257,8 +257,15 @@ describe CalendarEventsApiController, :type => :integration do @teacher = @course.admins.first student_in_course :course => @course, :user => @me, :active_all => true otherguy = student_in_course(:course => @course, :active_all => true).user - ag1 = @course.appointment_groups.create(:title => "something", :participants_per_appointment => 4, :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]]) + + course1 = @course + course_with_teacher(:user => @teacher, :active_all => true) + course2, @course = @course, course1 + + ag1 = AppointmentGroup.create!(:title => "something", :participants_per_appointment => 4, :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]], :contexts => [course2]) ag1.publish! + ag1.contexts = [course1, course2] + ag1.save! event1 = ag1.appointments.first my_personal_appointment = event1.reserve_for(@me, @me) event1.reserve_for(otherguy, otherguy) @@ -269,7 +276,8 @@ describe CalendarEventsApiController, :type => :integration do othergroup = cat.groups.create(:context => @course) othergroup.users << otherguy @me.reload - ag2 = @course.appointment_groups.create(:title => "something", :participants_per_appointment => 4, :sub_context_codes => [cat.asset_string], :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]]) + + ag2 = AppointmentGroup.create!(:title => "something", :participants_per_appointment => 4, :sub_context_codes => [cat.asset_string], :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]], :contexts => [course1, course2]) ag2.publish! event2 = ag2.appointments.first my_group_appointment = event2.reserve_for(mygroup, @me) @@ -322,7 +330,7 @@ describe CalendarEventsApiController, :type => :integration do student_in_course(:course => @course, :user => (@other_guy = user), :active_all => true) - @ag1 = @course.appointment_groups.create(:title => "something", :participants_per_appointment => 4, :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00", "2012-01-01 13:00:00", "2012-01-01 14:00:00"]]) + @ag1 = AppointmentGroup.create!(:title => "something", :participants_per_appointment => 4, :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00", "2012-01-01 13:00:00", "2012-01-01 14:00:00"]], :contexts => [@course]) @ag1.publish! @event1 = @ag1.appointments.first @event2 = @ag1.appointments.last @@ -333,7 +341,7 @@ describe CalendarEventsApiController, :type => :integration do @group.users << @other_guy @other_group = cat.groups.create(:context => @course) @me.reload - @ag2 = @course.appointment_groups.create(:title => "something", :participants_per_appointment => 4, :sub_context_codes => [cat.asset_string], :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]]) + @ag2 = AppointmentGroup.create!(:title => "something", :participants_per_appointment => 4, :sub_context_codes => [cat.asset_string], :new_appointments => [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]], :contexts => [@course]) @ag2.publish! @event3 = @ag2.appointments.first diff --git a/spec/factories/calendar_event_factory.rb b/spec/factories/calendar_event_factory.rb index 0ca451f6b91..d4abfe85838 100644 --- a/spec/factories/calendar_event_factory.rb +++ b/spec/factories/calendar_event_factory.rb @@ -49,7 +49,7 @@ def appointment_group_model(opts={}) if sub_context = opts.delete(:sub_context) opts[:sub_context_codes] = [sub_context.asset_string] end - @appointment_group = @course.appointment_groups.create!(valid_appointment_group_attributes.merge(opts)) + @appointment_group = AppointmentGroup.create!(valid_appointment_group_attributes.merge(opts)) @appointment_group end @@ -61,6 +61,7 @@ end def valid_appointment_group_attributes { - :title => "some title" + :title => "some title", + :contexts => [@course] } end diff --git a/spec/messages/appointment_group_deleted.email.erb_spec.rb b/spec/messages/appointment_group_deleted.email.erb_spec.rb index 4d1bf850ad3..65d75f0386d 100644 --- a/spec/messages/appointment_group_deleted.email.erb_spec.rb +++ b/spec/messages/appointment_group_deleted.email.erb_spec.rb @@ -21,11 +21,11 @@ require File.expand_path(File.dirname(__FILE__) + '/messages_helper') describe 'appointment_group_deleted.email' do it "should render" do - user = user_model - appointment_group_model(:context => course_model) + course_with_student(:active_all => true) + appointment_group_model(:contexts => [@course]) @appointment_group.cancel_reason = 'just because' - generate_message(:appointment_group_deleted, :email, @appointment_group) + generate_message(:appointment_group_deleted, :email, @appointment_group, :user => @user) @message.subject.should include('some title') @message.body.should include('some title') diff --git a/spec/messages/appointment_group_deleted.facebook.erb_spec.rb b/spec/messages/appointment_group_deleted.facebook.erb_spec.rb index d766d9173f0..32923aaf036 100644 --- a/spec/messages/appointment_group_deleted.facebook.erb_spec.rb +++ b/spec/messages/appointment_group_deleted.facebook.erb_spec.rb @@ -21,11 +21,11 @@ require File.expand_path(File.dirname(__FILE__) + '/messages_helper') describe 'appointment_group_deleted.facebook' do it "should render" do - user = user_model - appointment_group_model(:context => course_model) + student_in_course(:active_all => true) + appointment_group_model(:contexts => [@course]) @appointment_group.cancel_reason = 'just because' - generate_message(:appointment_group_deleted, :facebook, @appointment_group) + generate_message(:appointment_group_deleted, :facebook, @appointment_group, :user => @user) @message.body.should include('some title') @message.body.should include('just because') diff --git a/spec/messages/appointment_group_deleted.sms.erb_spec.rb b/spec/messages/appointment_group_deleted.sms.erb_spec.rb index b9daac10845..1a3a7ea215b 100644 --- a/spec/messages/appointment_group_deleted.sms.erb_spec.rb +++ b/spec/messages/appointment_group_deleted.sms.erb_spec.rb @@ -22,7 +22,7 @@ require File.expand_path(File.dirname(__FILE__) + '/messages_helper') describe 'appointment_group_deleted.sms' do it "should render" do user = user_model - appointment_group_model(:context => course_model) + appointment_group_model(:contexts => [course_model]) @appointment_group.cancel_reason = 'just because' generate_message(:appointment_group_deleted, :sms, @appointment_group) diff --git a/spec/messages/appointment_group_deleted.twitter.erb_spec.rb b/spec/messages/appointment_group_deleted.twitter.erb_spec.rb index fd215659e55..82c2369a538 100644 --- a/spec/messages/appointment_group_deleted.twitter.erb_spec.rb +++ b/spec/messages/appointment_group_deleted.twitter.erb_spec.rb @@ -22,7 +22,7 @@ require File.expand_path(File.dirname(__FILE__) + '/messages_helper') describe 'appointment_group_deleted.twitter' do it "should render" do user = user_model - appointment_group_model(:context => course_model) + appointment_group_model(:contexts => [course_model]) @appointment_group.cancel_reason = 'just because' generate_message(:appointment_group_deleted, :twitter, @appointment_group) diff --git a/spec/messages/appointment_group_published.email.erb_spec.rb b/spec/messages/appointment_group_published.email.erb_spec.rb index 5c74714ffa9..713696ce193 100644 --- a/spec/messages/appointment_group_published.email.erb_spec.rb +++ b/spec/messages/appointment_group_published.email.erb_spec.rb @@ -21,10 +21,10 @@ require File.expand_path(File.dirname(__FILE__) + '/messages_helper') describe 'appointment_group_published.email' do it "should render" do - user = user_model - appointment_group_model(:context => course_model) + course_with_student(:active_all => true) + appointment_group_model(:contexts => [@course]) - generate_message(:appointment_group_published, :email, @appointment_group) + generate_message(:appointment_group_published, :email, @appointment_group, :user => @user) @message.subject.should include('some title') @message.body.should include('some title') @@ -36,7 +36,7 @@ describe 'appointment_group_published.email' do user = user_model @course = course_model cat = @course.group_categories.create(:name => 'teh category') - appointment_group_model(:context => @course, :sub_context => cat) + appointment_group_model(:contexts => [@course], :sub_context => cat) generate_message(:appointment_group_published, :email, @appointment_group) diff --git a/spec/messages/appointment_group_published.facebook.erb_spec.rb b/spec/messages/appointment_group_published.facebook.erb_spec.rb index 8c0f4d7b5ef..205334c3fda 100644 --- a/spec/messages/appointment_group_published.facebook.erb_spec.rb +++ b/spec/messages/appointment_group_published.facebook.erb_spec.rb @@ -21,10 +21,10 @@ require File.expand_path(File.dirname(__FILE__) + '/messages_helper') describe 'appointment_group_published.facebook' do it "should render" do - user = user_model - appointment_group_model(:context => course_model) + course_with_student(:active_all => true) + appointment_group_model(:contexts => [@course]) - generate_message(:appointment_group_published, :facebook, @appointment_group) + generate_message(:appointment_group_published, :facebook, @appointment_group, :user => @user) @message.body.should include('some title') @message.body.should include(@course.name) @@ -35,7 +35,7 @@ describe 'appointment_group_published.facebook' do user = user_model @course = course_model cat = @course.group_categories.create(:name => 'teh category') - appointment_group_model(:context => @course, :sub_context => cat) + appointment_group_model(:contexts => [@course], :sub_context => cat) generate_message(:appointment_group_published, :facebook, @appointment_group) diff --git a/spec/messages/appointment_group_published.sms.erb_spec.rb b/spec/messages/appointment_group_published.sms.erb_spec.rb index f3b8d64ecc5..4bb0fb97374 100644 --- a/spec/messages/appointment_group_published.sms.erb_spec.rb +++ b/spec/messages/appointment_group_published.sms.erb_spec.rb @@ -22,7 +22,7 @@ require File.expand_path(File.dirname(__FILE__) + '/messages_helper') describe 'appointment_group_published.sms' do it "should render" do user = user_model - appointment_group_model(:context => course_model) + appointment_group_model(:contexts => [course_model]) generate_message(:appointment_group_published, :sms, @appointment_group) diff --git a/spec/messages/appointment_group_published.summary.erb_spec.rb b/spec/messages/appointment_group_published.summary.erb_spec.rb index 999c6d90d92..63c3335ce59 100644 --- a/spec/messages/appointment_group_published.summary.erb_spec.rb +++ b/spec/messages/appointment_group_published.summary.erb_spec.rb @@ -22,7 +22,7 @@ require File.expand_path(File.dirname(__FILE__) + '/messages_helper') describe 'appointment_group_published.summary' do it "should render" do user = user_model - appointment_group_model(:context => course_model) + appointment_group_model(:contexts => [course_model]) generate_message(:appointment_group_published, :summary, @appointment_group) diff --git a/spec/messages/appointment_group_published.twitter.erb_spec.rb b/spec/messages/appointment_group_published.twitter.erb_spec.rb index 226653a9184..f6be63d7f71 100644 --- a/spec/messages/appointment_group_published.twitter.erb_spec.rb +++ b/spec/messages/appointment_group_published.twitter.erb_spec.rb @@ -22,7 +22,7 @@ require File.expand_path(File.dirname(__FILE__) + '/messages_helper') describe 'appointment_group_published.twitter' do it "should render" do user = user_model - appointment_group_model(:context => course_model) + appointment_group_model(:contexts => [course_model]) generate_message(:appointment_group_published, :twitter, @appointment_group) diff --git a/spec/messages/appointment_group_updated.email.erb_spec.rb b/spec/messages/appointment_group_updated.email.erb_spec.rb index 3180d930b6e..6c364d14b37 100644 --- a/spec/messages/appointment_group_updated.email.erb_spec.rb +++ b/spec/messages/appointment_group_updated.email.erb_spec.rb @@ -21,10 +21,10 @@ require File.expand_path(File.dirname(__FILE__) + '/messages_helper') describe 'appointment_group_updated.email' do it "should render" do - user = user_model - appointment_group_model(:context => course_model) + user = course_with_student(:active_all => true) + appointment_group_model(:contexts => [@course]) - generate_message(:appointment_group_updated, :email, @appointment_group) + generate_message(:appointment_group_updated, :email, @appointment_group, :user => @user) @message.subject.should include('some title') @message.body.should include('some title') @@ -36,7 +36,7 @@ describe 'appointment_group_updated.email' do user = user_model @course = course_model cat = @course.group_categories.create(:name => 'teh category') - appointment_group_model(:context => @course, :sub_context => cat) + appointment_group_model(:contexts => [@course], :sub_context => cat) generate_message(:appointment_group_updated, :email, @appointment_group) diff --git a/spec/messages/appointment_group_updated.facebook.erb_spec.rb b/spec/messages/appointment_group_updated.facebook.erb_spec.rb index 84728ab6fca..2e255ee058e 100644 --- a/spec/messages/appointment_group_updated.facebook.erb_spec.rb +++ b/spec/messages/appointment_group_updated.facebook.erb_spec.rb @@ -21,10 +21,10 @@ require File.expand_path(File.dirname(__FILE__) + '/messages_helper') describe 'appointment_group_updated.facebook' do it "should render" do - user = user_model - appointment_group_model(:context => course_model) + course_with_student(:active_all => true) + appointment_group_model(:contexts => [@course]) - generate_message(:appointment_group_updated, :facebook, @appointment_group) + generate_message(:appointment_group_updated, :facebook, @appointment_group, :user => @user) @message.body.should include('some title') @message.body.should include(@course.name) @@ -35,7 +35,7 @@ describe 'appointment_group_updated.facebook' do user = user_model @course = course_model cat = @course.group_categories.create(:name => 'teh category') - appointment_group_model(:context => @course, :sub_context => cat) + appointment_group_model(:contexts => [@course], :sub_context => cat) generate_message(:appointment_group_updated, :facebook, @appointment_group) diff --git a/spec/messages/appointment_group_updated.sms.erb_spec.rb b/spec/messages/appointment_group_updated.sms.erb_spec.rb index 243b58dc1e6..b8735cfaa4e 100644 --- a/spec/messages/appointment_group_updated.sms.erb_spec.rb +++ b/spec/messages/appointment_group_updated.sms.erb_spec.rb @@ -22,7 +22,7 @@ require File.expand_path(File.dirname(__FILE__) + '/messages_helper') describe 'appointment_group_updated.sms' do it "should render" do user = user_model - appointment_group_model(:context => course_model) + appointment_group_model(:contexts => [course_model]) generate_message(:appointment_group_updated, :sms, @appointment_group) diff --git a/spec/messages/appointment_group_updated.summary.erb_spec.rb b/spec/messages/appointment_group_updated.summary.erb_spec.rb index aa1005ff4a6..e04a292188a 100644 --- a/spec/messages/appointment_group_updated.summary.erb_spec.rb +++ b/spec/messages/appointment_group_updated.summary.erb_spec.rb @@ -22,7 +22,7 @@ require File.expand_path(File.dirname(__FILE__) + '/messages_helper') describe 'appointment_group_updated.email' do it "should render" do user = user_model - appointment_group_model(:context => course_model) + appointment_group_model(:contexts => [course_model]) generate_message(:appointment_group_updated, :summary, @appointment_group) diff --git a/spec/messages/appointment_group_updated.twitter.erb_spec.rb b/spec/messages/appointment_group_updated.twitter.erb_spec.rb index 57f3595c342..aa0995a840e 100644 --- a/spec/messages/appointment_group_updated.twitter.erb_spec.rb +++ b/spec/messages/appointment_group_updated.twitter.erb_spec.rb @@ -22,7 +22,7 @@ require File.expand_path(File.dirname(__FILE__) + '/messages_helper') describe 'appointment_group_updated.twitter' do it "should render" do user = user_model - appointment_group_model(:context => course_model) + appointment_group_model(:contexts => [course_model]) generate_message(:appointment_group_updated, :twitter, @appointment_group) diff --git a/spec/models/appointment_group_spec.rb b/spec/models/appointment_group_spec.rb index 80e7a22a77a..4f2f66934fe 100644 --- a/spec/models/appointment_group_spec.rb +++ b/spec/models/appointment_group_spec.rb @@ -27,7 +27,7 @@ describe AppointmentGroup do it "should ensure the course section matches the course" do AppointmentGroup.new( :title => "test", - :context => @course, + :contexts => [@course], :sub_context_codes => [CourseSection.create.asset_string] ).should_not be_valid end @@ -35,7 +35,7 @@ describe AppointmentGroup do it "should ensure the group category matches the course" do AppointmentGroup.new( :title => "test", - :context => @course, + :contexts => [@course], :sub_context_codes => [GroupCategory.create.asset_string] ).should_not be_valid end @@ -43,18 +43,62 @@ describe AppointmentGroup do it "should ignore invalid sub context types" do group = AppointmentGroup.new( :title => "test", - :context => @course, + :contexts => [@course], :sub_context_codes => [Account.create.asset_string] ) group.should be_valid group.sub_context_codes.should be_empty end + + it "should only add contexts" do + course1 = @course + course_with_student(:active_all => true) + course2 = @course + + group = AppointmentGroup.new( + :title => "test", + :contexts => [course1], + :sub_context_codes => [Account.create.asset_string] + ) + + group.contexts = [course2] + group.save! + + group.contexts.should eql [course1, course2] + + # also make sure you can't get duplicates + group.contexts = [course1] + group.save! + group.contexts.should eql [course1, course2] + end + end + + context "add context" do + it "should update appointments effective_context_code" do + course(:active_all => true) + course1 = @course + course(:active_all => true) + course2 = @course + + group = AppointmentGroup.create!( + :title => "test", + :contexts => [course1], + :new_appointments => [['2012-01-01 12:00:00', '2012-01-01 13:00:00']] + ) + + group.appointments.map(&:effective_context_code).should eql [course1.asset_string] + + group.contexts = [course1, course2] + group.save! + group.reload + group.appointments.map(&:effective_context_code).should eql ["#{course1.asset_string},#{course2.asset_string}"] + end end context "add_appointment" do before do course_with_student(:active_all => true) - @ag = AppointmentGroup.create!(:title => "test", :context => @course, :new_appointments => [['2012-01-01 12:00:00', '2012-01-01 13:00:00']]) + @ag = AppointmentGroup.create!(:title => "test", :contexts => [@course], :new_appointments => [['2012-01-01 12:00:00', '2012-01-01 13:00:00']]) @appointment = @ag.appointments.first @appointment.should_not be_nil end @@ -117,22 +161,32 @@ describe AppointmentGroup do @course.enroll_user(@user, 'TaEnrollment', :section => section2, :limit_privileges_to_course_section => true).accept! @ta = @user - @g1 = AppointmentGroup.create(:title => "test", :context => @course) + @g1 = AppointmentGroup.create(:title => "test", :contexts => [@course]) @g1.publish! - @g2 = AppointmentGroup.create(:title => "test", :context => @course) - @g3 = AppointmentGroup.create(:title => "test", :context => @course, :sub_context_codes => [@course.default_section.asset_string]) + @g2 = AppointmentGroup.create(:title => "test", :contexts => [@course]) + @g3 = AppointmentGroup.create(:title => "test", :contexts => [@course], :sub_context_codes => [@course.default_section.asset_string]) @g3.publish! - @g4 = AppointmentGroup.create(:title => "test", :context => @course, :sub_context_codes => [gc.asset_string]) + @g4 = AppointmentGroup.create(:title => "test", :contexts => [@course], :sub_context_codes => [gc.asset_string]) @g4.publish! - @g5 = AppointmentGroup.create(:title => "test", :context => @course, :sub_context_codes => [section2.asset_string]) + @g5 = AppointmentGroup.create(:title => "test", :contexts => [@course], :sub_context_codes => [section2.asset_string]) @g5.publish! - @g6 = AppointmentGroup.create(:title => "test", :context => other_course) + @g6 = AppointmentGroup.create(:title => "test", :contexts => [other_course]) @g6.publish! # multiple sub_contexts - @g7 = AppointmentGroup.create(:title => "test", :context => @course, :sub_context_codes => [@course.default_section.asset_string, section2.asset_string]) + @g7 = AppointmentGroup.create(:title => "test", :contexts => [@course], :sub_context_codes => [@course.default_section.asset_string, section2.asset_string]) @g7.publish! + # multiple contexts + course_bak, teacher_bak = @course, @teacher + course_with_teacher(:active_all => true) + @course2, @teacher2 = @course, @teacher + course_with_teacher(:user => @teacher2, :active_all => true) + teacher_in_course(:course => @course) + @course3, @teacher3, @course, @teacher = @course, @teacher, course_bak, teacher_bak + @g8 = AppointmentGroup.create(:title => "test", :contexts => [@course2, @course3]) + @g8.publish! + @groups = [@g1, @g2, @g3, @g4, @g5, @g7] end @@ -169,6 +223,16 @@ describe AppointmentGroup do @g7.grants_right?(@student, nil, :reserve).should be_true @g7.grants_right?(@student_in_section2, nil, :reserve).should be_true @g7.grants_right?(@student_in_section3, nil, :reserve).should be_false + + # multiple contexts + @student_in_course1 = @student + student_in_course(:course => @course2, :active_all => true) + @student_in_course2 = @user + student_in_course(:course => @course3, :active_all => true) + @student_in_course3 = @user + @g8.grants_right?(@student_in_course1, nil, :reserve).should be_false + @g8.grants_right?(@student_in_course2, nil, :reserve).should be_true + @g8.grants_right?(@student_in_course3, nil, :reserve).should be_true end @@ -199,6 +263,11 @@ describe AppointmentGroup do visible_groups = AppointmentGroup.manageable_by(@student).sort_by(&:id) visible_groups.should eql [] @groups.each{ |g| g.grants_right?(@student, nil, :manage).should be_false } + + # multiple contexts + @g8.grants_right?(@teacher, nil, :manage).should be_false # not in any courses + @g8.grants_right?(@teacher2, nil, :manage).should be_true + @g8.grants_right?(@teacher3, nil, :manage).should be_false # not in all courses end end @@ -216,7 +285,7 @@ describe AppointmentGroup do channel.confirm end - @ag = @course.appointment_groups.create(:title => "test", :new_appointments => [['2012-01-01 13:00:00', '2012-01-01 14:00:00']]) + @ag = AppointmentGroup.create!(:title => "test", :contexts => [@course], :new_appointments => [['2012-01-01 13:00:00', '2012-01-01 14:00:00']]) end it "should notify all participants when publishing" do @@ -244,8 +313,9 @@ describe AppointmentGroup do @unpublished_course.enroll_user(@student, 'StudentEnrollment') @unpublished_course.enroll_user(@teacher, 'TeacherEnrollment') - @ag = @unpublished_course.appointment_groups.create(:title => "test", - :new_appointments => [['2012-01-01 13:00:00', '2012-01-01 14:00:00']]) + @ag = AppointmentGroup.create!(:title => "test", + :contexts => [@unpublished_course], + :new_appointments => [['2012-01-01 13:00:00', '2012-01-01 14:00:00']]) @ag.publish! @ag.messages_sent.should be_empty @@ -258,7 +328,7 @@ describe AppointmentGroup do course_with_teacher(:active_all => true) @teacher = @user - ag = AppointmentGroup.create(:title => "test", :context => @course, :new_appointments => [['2012-01-01 17:00:00', '2012-01-01 18:00:00']]) + ag = AppointmentGroup.create(:title => "test", :contexts => [@course], :new_appointments => [['2012-01-01 17:00:00', '2012-01-01 18:00:00']]) appt = ag.appointments.first participants = 3.times.map { student_in_course(:course => @course, :active_all => true) @@ -279,7 +349,7 @@ describe AppointmentGroup do before do course_with_teacher(:active_all => true) @teacher = @user - @ag = @course.appointment_groups.create(:title => "test", :participants_per_appointment => 2, :new_appointments => [["#{Time.now.year + 1}-01-01 12:00:00", "#{Time.now.year + 1}-01-01 13:00:00"], ["#{Time.now.year + 1}-01-01 13:00:00", "#{Time.now.year + 1}-01-01 14:00:00"]]) + @ag = AppointmentGroup.create(:title => "test", :contexts => [@course], :participants_per_appointment => 2, :new_appointments => [["#{Time.now.year + 1}-01-01 12:00:00", "#{Time.now.year + 1}-01-01 13:00:00"], ["#{Time.now.year + 1}-01-01 13:00:00", "#{Time.now.year + 1}-01-01 14:00:00"]]) @appointment = @ag.appointments.first @ag.reload.available_slots.should eql 4 end @@ -354,7 +424,7 @@ describe AppointmentGroup do @gc = @group1.group_category @group2 = @gc.groups.create!(:name => "group2") - @ag = @course.appointment_groups.create(:title => "test", :participants_per_appointment => 2, :new_appointments => [["#{Time.now.year + 1}-01-01 12:00:00", "#{Time.now.year + 1}-01-01 13:00:00"], ["#{Time.now.year + 1}-01-01 13:00:00", "#{Time.now.year + 1}-01-01 14:00:00"]]) + @ag = AppointmentGroup.create!(:title => "test", :contexts => [@course], :participants_per_appointment => 2, :new_appointments => [["#{Time.now.year + 1}-01-01 12:00:00", "#{Time.now.year + 1}-01-01 13:00:00"], ["#{Time.now.year + 1}-01-01 13:00:00", "#{Time.now.year + 1}-01-01 14:00:00"]]) end it "should return possible participants" do diff --git a/spec/models/calendar_event_spec.rb b/spec/models/calendar_event_spec.rb index 4c252a48304..885179dd768 100644 --- a/spec/models/calendar_event_spec.rb +++ b/spec/models/calendar_event_spec.rb @@ -160,10 +160,13 @@ describe CalendarEvent do it "should return events implicitly tied to the contexts (via effective_context_string)" do @teacher = user @course.enroll_teacher(@teacher).accept! - g1 = @course.appointment_groups.create(:title => "foo") + course1 = @course + course_with_teacher(@teacher) + course2, @course = @course, course1 + g1 = AppointmentGroup.create(:title => "foo", :contexts => [course1, course2]) g1.publish! a1 = g1.appointments.create.reserve_for(@student, @student) - g2 = @course.appointment_groups.create(:title => "foo", :sub_context_codes => [@course.default_section.asset_string]) + g2 = AppointmentGroup.create(:title => "foo", :contexts => [@course], :sub_context_codes => [@course.default_section.asset_string]) g2.publish! a2 = g2.appointments.create.reserve_for(@student, @student) pe = @course.calendar_events.create! @@ -258,7 +261,7 @@ describe CalendarEvent do @student1 = @user @other_section = @course.course_sections.create! @other_course = Course.create! - @ag = AppointmentGroup.create(:title => "test", :context => @course) + @ag = AppointmentGroup.create(:title => "test", :contexts => [@course]) @ag.publish! @appointment = @ag.appointments.create(:start_at => '2012-01-01 12:00:00', :end_at => '2012-01-01 13:00:00') end @@ -280,7 +283,7 @@ describe CalendarEvent do @group = c1.groups.create(:context => @course) @group.users << @student1 << @student2 - @ag2 = AppointmentGroup.create!(:title => "test", :context => @course, :sub_context_codes => [c1.asset_string]) + @ag2 = AppointmentGroup.create!(:title => "test", :contexts => [@course], :sub_context_codes => [c1.asset_string]) @ag2.publish! @appointment2 = @ag2.appointments.create(:start_at => '2012-01-01 12:00:00', :end_at => '2012-01-01 13:00:00') @@ -337,7 +340,7 @@ describe CalendarEvent do end it "should allow multiple participants in an appointment, up to the limit" do - ag = AppointmentGroup.create(:title => "test", :context => @course, :participants_per_appointment => 2, + ag = AppointmentGroup.create(:title => "test", :contexts => [@course], :participants_per_appointment => 2, :new_appointments => [['2012-01-01 13:00:00', '2012-01-01 14:00:00']] ) ag.publish! @@ -356,7 +359,7 @@ describe CalendarEvent do it "should give preference to the calendar's appointment limit" do ag = AppointmentGroup.create!( :title => "testing...", - :context => @course, + :contexts => [@course], :participants_per_appointment => 2, :new_appointments => [['2012-01-01 13:00:00', '2012-01-01 14:00:00']] ) @@ -384,7 +387,7 @@ describe CalendarEvent do it "should revert to the appointment group's participant_limit when appropriate" do ag = AppointmentGroup.create!( :title => "testing...", - :context => @course, + :contexts => [@course], :participants_per_appointment => 2, :new_appointments => [['2012-01-01 13:00:00', '2012-01-01 14:00:00']] ) @@ -403,7 +406,7 @@ describe CalendarEvent do end it "should not let participants exceed max_appointments_per_participant" do - ag = AppointmentGroup.create(:title => "test", :context => @course, :max_appointments_per_participant => 1, + ag = AppointmentGroup.create(:title => "test", :contexts => [@course], :max_appointments_per_participant => 1, :new_appointments => [['2012-01-01 12:00:00', '2012-01-01 13:00:00'], ['2012-01-01 13:00:00', '2012-01-01 14:00:00']] ) ag.publish! @@ -415,7 +418,7 @@ describe CalendarEvent do end it "should cancel existing reservations if cancel_existing = true" do - ag = AppointmentGroup.create(:title => "test", :context => @course, :max_appointments_per_participant => 1, + ag = AppointmentGroup.create(:title => "test", :contexts => [@course], :max_appointments_per_participant => 1, :new_appointments => [['2012-01-01 12:00:00', '2012-01-01 13:00:00'], ['2012-01-01 13:00:00', '2012-01-01 14:00:00']] ) ag.publish! @@ -428,7 +431,7 @@ describe CalendarEvent do end it "should enforce the section" do - ag = AppointmentGroup.create(:title => "test", :context => @course.course_sections.create, + ag = AppointmentGroup.create(:title => "test", :contexts => [@course.course_sections.create], :new_appointments => [['2012-01-01 12:00:00', '2012-01-01 13:00:00']] ) ag.publish! @@ -445,7 +448,7 @@ describe CalendarEvent do c2 = @course.group_categories.create g2 = c2.groups.create(:context => @course) - ag = AppointmentGroup.create(:title => "test", :context => @course, :sub_context_codes => [c1.asset_string], + ag = AppointmentGroup.create(:title => "test", :contexts => [@course], :sub_context_codes => [c1.asset_string], :new_appointments => [['2012-01-01 12:00:00', '2012-01-01 13:00:00']] ) appointment = ag.appointments.first @@ -479,7 +482,7 @@ describe CalendarEvent do end it "should unlock the appointment when the last reservation is canceled" do - ag = AppointmentGroup.create(:title => "test", :context => @course, :participants_per_appointment => 2, + ag = AppointmentGroup.create(:title => "test", :contexts => [@course], :participants_per_appointment => 2, :new_appointments => [['2012-01-01 13:00:00', '2012-01-01 14:00:00']] ) appointment = ag.appointments.first @@ -497,7 +500,7 @@ describe CalendarEvent do end it "should copy the group attributes to the initial appointments" do - ag = AppointmentGroup.create(:title => "test", :context => @course, :description => "hello\nworld", + ag = AppointmentGroup.create(:title => "test", :contexts => [@course], :description => "hello\nworld", :new_appointments => [['2012-01-01 12:00:00', '2012-01-01 13:00:00']] ) e = ag.appointments.first @@ -524,7 +527,7 @@ describe CalendarEvent do end it "should copy the group attributes to subsequent appointments" do - ag = AppointmentGroup.create(:title => "test", :context => @course) + ag = AppointmentGroup.create(:title => "test", :contexts => [@course]) ag.update_attributes( :title => 'haha', :new_appointments => [['2012-01-01 12:00:00', '2012-01-01 13:00:00']] @@ -540,7 +543,7 @@ describe CalendarEvent do end it "should allow a user to re-reserve a slot after canceling" do - ag = AppointmentGroup.create(:title => "test", :context => @course, :participants_per_appointment => 1, + ag = AppointmentGroup.create(:title => "test", :contexts => [@course], :participants_per_appointment => 1, :new_appointments => [['2012-01-01 13:00:00', '2012-01-01 14:00:00']] ) appointment = ag.appointments.first diff --git a/spec/models/course_spec.rb b/spec/models/course_spec.rb index 71b36f8f1e9..8a0bfe0b18a 100644 --- a/spec/models/course_spec.rb +++ b/spec/models/course_spec.rb @@ -319,7 +319,7 @@ describe Course do event2.updating_user = @teacher event2.save! event3 = event2.child_events.first - appointment_group = @course.appointment_groups.create + appointment_group = AppointmentGroup.create! :title => "ag", :contexts => [@course] appointment_group.publish! assignment = @course.assignments.create! @@ -2425,7 +2425,7 @@ describe Course, "conclusions" do context "appointment cancelation" do before do course_with_student(:active_all => true) - @ag = @course.appointment_groups.create(:title => "test", :new_appointments => [['2010-01-01 13:00:00', '2010-01-01 14:00:00'], ["#{Time.now.year + 1}-01-01 13:00:00", "#{Time.now.year + 1}-01-01 14:00:00"]]) + @ag = AppointmentGroup.create!(:title => "test", :contexts => [@course], :new_appointments => [['2010-01-01 13:00:00', '2010-01-01 14:00:00'], ["#{Time.now.year + 1}-01-01 13:00:00", "#{Time.now.year + 1}-01-01 14:00:00"]]) @ag.appointments.each do |a| a.reserve_for(@user, @user) end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index b5e6a77af86..9919b83f13b 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -1460,7 +1460,7 @@ describe User do describe "calendar_events_for_calendar" do it "should include own scheduled appointments" do course_with_student(:active_all => true) - ag = @course.appointment_groups.create(:title => 'test appointment', :new_appointments => [[Time.now, Time.now + 1.hour], [Time.now + 1.hour, Time.now + 2.hour]]) + ag = AppointmentGroup.create!(:title => 'test appointment', :contexts => [@course], :new_appointments => [[Time.now, Time.now + 1.hour], [Time.now + 1.hour, Time.now + 2.hour]]) ag.appointments.first.reserve_for(@user, @user) events = @user.calendar_events_for_calendar events.size.should eql 1 @@ -1470,7 +1470,7 @@ describe User do it "should include manageable appointments" do course(:active_all => true) @user = @course.instructors.first - ag = @course.appointment_groups.create(:title => 'test appointment', :new_appointments => [[Time.now, Time.now + 1.hour]]) + ag = AppointmentGroup.create!(:title => 'test appointment', :contexts => [@course], :new_appointments => [[Time.now, Time.now + 1.hour]]) events = @user.calendar_events_for_calendar events.size.should eql 1 events.first.title.should eql 'test appointment' @@ -1481,7 +1481,7 @@ describe User do it "should include manageable appointment groups" do course(:active_all => true) @user = @course.instructors.first - ag = @course.appointment_groups.create(:title => 'test appointment', :new_appointments => [[Time.now, Time.now + 1.hour]]) + ag = AppointmentGroup.create!(:title => 'test appointment', :contexts => [@course], :new_appointments => [[Time.now, Time.now + 1.hour]]) events = @user.upcoming_events events.size.should eql 1 events.first.title.should eql 'test appointment' diff --git a/spec/selenium/calendar2_common.rb b/spec/selenium/calendar2_common.rb index 7ee5d1ea1ec..e6f87bb1939 100644 --- a/spec/selenium/calendar2_common.rb +++ b/spec/selenium/calendar2_common.rb @@ -9,12 +9,12 @@ shared_examples_for "calendar2 selenium tests" do tomorrow = Date.today.to_s default_params = { :title => "new appointment group", - :context => @course, + :contexts => [@course], :new_appointments => [ [tomorrow + ' 12:00:00', tomorrow + ' 13:00:00'], ] } - ag = @course.appointment_groups.create!(default_params.merge(params)) + ag = AppointmentGroup.create!(default_params.merge(params)) ag.publish! ag.title end diff --git a/spec/selenium/dashboard_spec.rb b/spec/selenium/dashboard_spec.rb index 2e40e4e2bd3..d62b06f0c03 100644 --- a/spec/selenium/dashboard_spec.rb +++ b/spec/selenium/dashboard_spec.rb @@ -331,6 +331,16 @@ describe "dashboard" do assignment_menu.should include_text(assignment.title) end + it "should display appointment groups in todo list" do + ag = AppointmentGroup.create! :title => "appointment group", + :contexts => [@course], + :new_appointments => [[Time.now.utc + 2.hour, Time.now.utc + 3.hour]] + student_in_course(:course => @course, :active_all => true) + ag.appointments.first.reserve_for(@student, @student) + get "/" + f('#right-side .events_list').text.should include 'appointment group' + end + it "should show submitted essay quizzes in the todo list" do quiz_title = 'new quiz' student_in_course diff --git a/spec/selenium/scheduler_spec.rb b/spec/selenium/scheduler_spec.rb index 66145c6d3c4..65c9be8d1fc 100644 --- a/spec/selenium/scheduler_spec.rb +++ b/spec/selenium/scheduler_spec.rb @@ -9,9 +9,11 @@ describe "scheduler" do def fill_out_appointment_group_form(new_appointment_text) driver.find_element(:css, '.create_link').click - edit_form = driver.find_element(:id, 'edit_appointment_form') + edit_form = f('#edit_appointment_form') keep_trying_until { edit_form.should be_displayed } replace_content(find_with_jquery('input[name="title"]'), new_appointment_text) + f('.ag_contexts_selector').click + f('[name="context_codes[]"]').click date_field = edit_form.find_element(:css, '.date_field') date_field.click wait_for_animations @@ -300,7 +302,7 @@ describe "scheduler" do end it "should allow me to override the participant limit on a slot-by-slot basis" do - create_appointment_group :participants_per_appointment => 2, :context => @course + create_appointment_group :participants_per_appointment => 2 get "/calendar2" wait_for_ajaximations click_scheduler_link @@ -325,6 +327,36 @@ describe "scheduler" do ag.appointments.first.participants_per_appointment.should be_nil end + it "should allow me to create a course with multiple contexts" do + course1 = @course + course_with_teacher(:user => @teacher, :active_all => true) + get "/calendar2" + click_scheduler_link + fill_out_appointment_group_form('multiple contexts') + course_box = f("[value=#{@course.asset_string}]") + course_box.click + ff('.ag_sections_toggle').last.click + + # sections should get checked by their parent + section_box = f("[value=#{@course.course_sections.first.asset_string}]") + section_box[:checked].should be_true + + # unchecking all sections should uncheck their parent + section_box.click + course_box[:checked].should be_false + + # checking all sections should check parent + section_box.click + course_box[:checked].should be_true + + driver.find_element(:css, '.ui-dialog-buttonset .ui-button-primary').click + wait_for_ajaximations + ag = AppointmentGroup.first + ag.contexts.should include course1 + ag.contexts.should include @course + ag.sub_contexts.should eql [] + end + end context "as a student" do @@ -339,6 +371,22 @@ describe "scheduler" do wait_for_ajax_requests end + it "should let me reserve appointment groups for contexts I am in" do + my_course = @course + course_with_student(:active_all => true) + other_course = @course + + create_appointment_group(:contexts => [other_course, my_course]) + + get "/calendar2" + click_scheduler_link + wait_for_ajaximations + click_appointment_link + + reserve_appointment_manual(0) + driver.find_element(:css, '.fc-event').should include_text "Reserved" + end + it "should allow me to cancel existing reservation and sign up for the appointment group from the calendar" do tomorrow = (Date.today + 1).to_s create_appointment_group(:max_appointments_per_participant => 1,