def share_collection(request, collection_id=None): """ Share a collection with users by creating a temporary url for authorized users to use in order to add themselves into the collection's authorized users list """ role_bucket = services.get_or_update_role_bucket(request) course_collections = LTIService(request).getCourseCollections() collection_list = queries.getCollectionList( role_bucket, collection_ids=course_collections) collection = Collection.objects.get(id=collection_id) collection_share_form = CollectionShareForm() context = { 'share_form': collection_share_form, 'nav_collections': collection_list, 'collection': collection, } if request.POST: collection_share_form = CollectionShareForm(request.POST) if collection_share_form.is_valid(): expired_in = str(collection_share_form.cleaned_data['expired_in']) # This creates an array of the share data (collection id and expiration data # for the url). Random data is appended/prepended to "obfuscate" the url. # This isn't intended to be secure by any means, just make it harder to guess. # The assumption is that a share URL will only grant a user student-level # access, so they can't do any damage if they "guess" the URL. If that # assumption changes, then this implementation should be revisited to make it # more secure and tamper-proof. share_data = [collection_id, expired_in] share_data.insert(0, utils.generate_random_id(3)) share_data.append(utils.generate_random_id(3)) share_key = '!!'.join(share_data) # This pads the share key so that the base64 encoded string doesn't include # base64 padding, which is typically the equals sign ("="). The equals sign # must be percent encoded as %3D which looks bad. Since 3 ascii characters (8 # bits) are encoded with 4 base64 characters (6 bits each) this should ensure # that the share_key is always a multiple of 3. if (len(share_key) % 3) > 0: share_key = share_key + utils.generate_random_id( 3 - (len(share_key) % 3)) secret_share_key = base64.urlsafe_b64encode( share_key.encode("ascii", "ignore")) context['share_form'] = collection_share_form context['secret_share_key'] = secret_share_key log.info('URL generated to share collection %s.' % collection_id, extra={'user': request.user}) analytics.track(actor=request.user, verb=analytics.VERBS.shared, object=analytics.OBJECTS.collection, context={"collection_id": collection_id}) else: context['share_form'] = collection_share_form return render(request, 'collections/share.html', context)
def deck_view_helper(request, current_collection, deck_cards): role_bucket = services.get_or_update_role_bucket(request) canvas_course_collections = LTIService(request).getCourseCollections() collection_list = queries.getCollectionList(role_bucket, collection_ids=canvas_course_collections) is_quiz_mode = request.GET.get('mode') == 'quiz' is_deck_admin = current_collection.id in (set(role_bucket['ADMINISTRATOR']) | set(role_bucket['INSTRUCTOR'])) card_id = request.GET.get('card_id', '') cards = [] for dcard in deck_cards: card_fields = {'show':[],'reveal':[]} for cfield in dcard.card.cards_fields_set.all(): if cfield.field.display: bucket = 'show' else: bucket = 'reveal' card_fields[bucket].append({ 'type': cfield.field.field_type, 'label': cfield.field.label, 'show_label': cfield.field.show_label, 'value': cfield.value, }) cards.append({ 'card_id': dcard.card.id, 'color': dcard.card.color, 'fields': card_fields }) return [cards, collection_list, is_quiz_mode, is_deck_admin, card_id]
def deck_view_helper(request, current_collection, deck_cards): role_bucket = services.get_or_update_role_bucket(request) canvas_course_collections = LTIService(request).getCourseCollections() collection_list = queries.getCollectionList( role_bucket, collection_ids=canvas_course_collections) is_quiz_mode = request.GET.get('mode') == 'quiz' is_deck_admin = current_collection.id in (set(role_bucket['ADMINISTRATOR']) | set(role_bucket['INSTRUCTOR'])) card_id = request.GET.get('card_id', '') cards = [] for dcard in deck_cards: card_fields = {'show': [], 'reveal': []} for cfield in dcard.card.cards_fields_set.all(): if cfield.field.display: bucket = 'show' else: bucket = 'reveal' card_fields[bucket].append({ 'type': cfield.field.field_type, 'label': cfield.field.label, 'show_label': cfield.field.show_label, 'value': cfield.value, }) cards.append({ 'card_id': dcard.card.id, 'color': dcard.card.color, 'fields': card_fields }) return [cards, collection_list, is_quiz_mode, is_deck_admin, card_id]
def create(request): """Creates a collection.""" role_bucket = services.get_or_update_role_bucket(request) course_collections = LTIService(request).getCourseCollections() collection_filters = dict( collection_ids=course_collections, can_filter=not queries.is_superuser_or_staff(request.user)) collection_list = queries.getCollectionList(role_bucket, **collection_filters) if request.method == 'POST': lti_req = LTIService(request) published = not lti_req.isTeacher() request.POST = request.POST.copy() request.POST['published'] = published collection_form = CollectionForm(request.POST) card_template_id = collection_form.data['card_template'] if collection_form.is_valid(): collection = collection_form.save() lti_req.associateCourse(collection.id) services.add_user_to_collection( user=request.user, collection=collection, role=Users_Collections.ADMINISTRATOR) #update role_bucket to add admin permission to the user for this newly created collection services.get_or_update_role_bucket( request, collection.id, Users_Collections.role_map[Users_Collections.ADMINISTRATOR]) log.info('Collection %s created.' % collection.id, extra={'user': request.user}) analytics.track(actor=request.user, verb=analytics.VERBS.created, object=analytics.OBJECTS.collection, context={"custom": False}) return redirect(collection) else: rel_templates = CardTemplate.objects.filter( Q(owner__isnull=True) | Q(owner=request.user)) initial = {'card_template': '1'} card_template_id = initial['card_template'] collection_form = CollectionForm(query_set=rel_templates, initial=initial) # Pre-populate the "preview" of the card template # This view is also called via AJAX on the page. prev_request = HttpRequest() prev_request.method = 'GET' prev_request.GET['card_template_id'] = card_template_id prev_response = card_template.preview(prev_request) card_template_preview_html = prev_response.content context = { "nav_collections": collection_list, "active_collection": None, "collection_form": collection_form, "card_template_preview_html": card_template_preview_html } return render(request, 'collections/create.html', context)
def share_collection(request, collection_id=None): """ Share a collection with users by creating a temporary url for authorized users to use in order to add themselves into the collection's authorized users list """ role_bucket = services.get_or_update_role_bucket(request) course_collections = LTIService(request).getCourseCollections() collection_list = queries.getCollectionList(role_bucket, collection_ids=course_collections) collection = Collection.objects.get(id=collection_id) collection_share_form = CollectionShareForm() context = { 'share_form': collection_share_form, 'nav_collections': collection_list, 'collection': collection, } if request.POST: collection_share_form = CollectionShareForm(request.POST) if collection_share_form.is_valid(): expired_in = str(collection_share_form.cleaned_data['expired_in']) # This creates an array of the share data (collection id and expiration data # for the url). Random data is appended/prepended to "obfuscate" the url. # This isn't intended to be secure by any means, just make it harder to guess. # The assumption is that a share URL will only grant a user student-level # access, so they can't do any damage if they "guess" the URL. If that # assumption changes, then this implementation should be revisited to make it # more secure and tamper-proof. share_data = [collection_id, expired_in] share_data.insert(0, utils.generate_random_id(3)) share_data.append(utils.generate_random_id(3)) share_key = '!!'.join(share_data) # This pads the share key so that the base64 encoded string doesn't include # base64 padding, which is typically the equals sign ("="). The equals sign # must be percent encoded as %3D which looks bad. Since 3 ascii characters (8 # bits) are encoded with 4 base64 characters (6 bits each) this should ensure # that the share_key is always a multiple of 3. if (len(share_key) % 3) > 0: share_key = share_key + utils.generate_random_id(3 - (len(share_key) % 3)) secret_share_key = base64.urlsafe_b64encode(share_key.encode("ascii", "ignore")) context['share_form'] = collection_share_form context['secret_share_key'] = secret_share_key log.info('URL generated to share collection %s.' %collection_id, extra={'user': request.user}) analytics.track( actor=request.user, verb=analytics.VERBS.shared, object=analytics.OBJECTS.collection, context={"collection_id": collection_id} ) else: context['share_form'] = collection_share_form return render(request, 'collections/share.html', context)
def index(request, collection_id=None): """Displays a set of collections to the user depending on whether or not the collections are private or public and whether or not the user has permission.""" if collection_id is not None: collection_id = int(collection_id) role_bucket = services.get_or_update_role_bucket(request) lti_req = LTIService(request) course_collections = lti_req.getCourseCollections() is_teacher = lti_req.isTeacher() or queries.is_superuser_or_staff( request.user) collection_admin_perms = [] for r in (Users_Collections.ADMINISTRATOR, Users_Collections.INSTRUCTOR): role_name = Users_Collections.role_map[r] collection_admin_perms.extend(role_bucket[role_name]) copy_collections = queries.getCopyCollectionList(request.user) collection_filters = dict( collection_ids=course_collections, can_filter=not queries.is_superuser_or_staff(request.user)) collection_list = queries.getCollectionList(role_bucket, **collection_filters) display_collections = queries.groupCollectionsByList(collection_list) context = { "nav_collections": collection_list, "display_collections": display_collections, "copy_collections": copy_collections, "active_collection": None, "user_collection_role": role_bucket, "collection_admin_perms": collection_admin_perms, "is_teacher": is_teacher, } if collection_id: try: cur_collection = Collection.objects.get(id=collection_id) except Collection.DoesNotExist: raise Http404 filtered_collection_list = [ c for c in collection_list if c['id'] == cur_collection.id ] if len(filtered_collection_list) == 0: raise Http404 context['active_collection'] = filtered_collection_list[0] analytics.track(actor=request.user, verb=analytics.VERBS.viewed, object=analytics.OBJECTS.collection, context={"collection_id": collection_id}) return render(request, 'collections/index.html', context)
def create_edit_card(request, deck_id=None): """Create a new card or edit an existing one from the collection card template.""" deck = Deck.objects.get(id=deck_id) current_collection = deck.collection card_color_select = widgets.Select(attrs=None, choices=Card.COLOR_CHOICES) role_bucket = services.get_or_update_role_bucket(request) canvas_course_collections = LTIService(request).getCourseCollections() collection_list = queries.getCollectionList( role_bucket, collection_ids=canvas_course_collections) # Only has card_id if we are editing a card card_id = request.GET.get('card_id', '') if card_id: card = Card.objects.get(id=card_id) card_color = card.color else: card_color = Card.DEFAULT_COLOR if card_id: field_list = [{ "id": cfield.field.id, "type": cfield.field.field_type, "label": cfield.field.label, "bucket": "show" if cfield.field.display else "reveal", "show_label": cfield.field.show_label, "value": cfield.value } for cfield in card.cards_fields_set.all()] else: field_list = [{ "id": field.id, "type": field.field_type, "bucket": "show" if field.display else "reveal", "label": field.label, "show_label": field.show_label, "value": "" } for field in current_collection.card_template.fields.all()] card_fields = {'show': [], 'reveal': []} for field in field_list: card_fields[field['bucket']].append(field) is_all_cards = request.GET.get('is_all_cards', 0) context = { "is_all_cards": int(is_all_cards), "deck": deck, "card_id": card_id if card_id else '', "collection": current_collection, "nav_collections": collection_list, "card_fields": card_fields, "card_color_select": card_color_select.render("card_color", card_color) } return render(request, 'decks/edit_card.html', context)
def create_edit_card(request, deck_id=None): """Create a new card or edit an existing one from the collection card template.""" deck = Deck.objects.get(id=deck_id) current_collection = deck.collection card_color_select = widgets.Select(attrs=None, choices=Card.COLOR_CHOICES) role_bucket = services.get_or_update_role_bucket(request) canvas_course_collections = LTIService(request).getCourseCollections() collection_list = queries.getCollectionList(role_bucket, collection_ids=canvas_course_collections) # Only has card_id if we are editing a card card_id = request.GET.get('card_id', '') if card_id: card = Card.objects.get(id=card_id) card_color = card.color else: card_color = Card.DEFAULT_COLOR if card_id: field_list = [{ "id":cfield.field.id, "type": cfield.field.field_type, "label": cfield.field.label, "bucket": "show" if cfield.field.display else "reveal", "show_label": cfield.field.show_label, "value": cfield.value } for cfield in card.cards_fields_set.all()] else: field_list = [{ "id": field.id, "type": field.field_type, "bucket": "show" if field.display else "reveal", "label": field.label, "show_label": field.show_label, "value": "" } for field in current_collection.card_template.fields.all()] card_fields = {'show':[], 'reveal':[]} for field in field_list: card_fields[field['bucket']].append(field) is_all_cards = request.GET.get('is_all_cards', 0) context = { "is_all_cards": int(is_all_cards), "deck": deck, "card_id": card_id if card_id else '', "collection": current_collection, "nav_collections": collection_list, "card_fields": card_fields, "card_color_select": card_color_select.render("card_color", card_color) } return render(request, 'decks/edit_card.html', context)
def create(request): """Creates a collection.""" role_bucket = services.get_or_update_role_bucket(request) course_collections = LTIService(request).getCourseCollections() collection_filters = dict(collection_ids=course_collections, can_filter=not queries.is_superuser_or_staff(request.user)) collection_list = queries.getCollectionList(role_bucket, **collection_filters) if request.method == 'POST': lti_req = LTIService(request) published = not lti_req.isTeacher() request.POST = request.POST.copy() request.POST['published'] = published collection_form = CollectionForm(request.POST) card_template_id = collection_form.data['card_template'] if collection_form.is_valid(): collection = collection_form.save() lti_req.associateCourse(collection.id) services.add_user_to_collection(user=request.user, collection=collection, role=Users_Collections.ADMINISTRATOR) #update role_bucket to add admin permission to the user for this newly created collection services.get_or_update_role_bucket(request, collection.id, Users_Collections.role_map[Users_Collections.ADMINISTRATOR]) log.info('Collection %s created.' %collection.id, extra={'user': request.user}) analytics.track( actor=request.user, verb=analytics.VERBS.created, object=analytics.OBJECTS.collection, context={"custom": False} ) return redirect(collection) else: rel_templates = CardTemplate.objects.filter(Q(owner__isnull=True) | Q(owner=request.user)) initial = {'card_template': '1'} card_template_id = initial['card_template'] collection_form = CollectionForm(query_set=rel_templates,initial=initial) # Pre-populate the "preview" of the card template # This view is also called via AJAX on the page. prev_request = HttpRequest() prev_request.method = 'GET' prev_request.GET['card_template_id'] = card_template_id prev_response = card_template.preview(prev_request) card_template_preview_html = prev_response.content context = { "nav_collections": collection_list, "active_collection": None, "collection_form": collection_form, "card_template_preview_html": card_template_preview_html } return render(request, 'collections/create.html', context)
def index(request, collection_id=None): """Displays a set of collections to the user depending on whether or not the collections are private or public and whether or not the user has permission.""" if collection_id is not None: collection_id = int(collection_id) role_bucket = services.get_or_update_role_bucket(request) lti_req = LTIService(request) course_collections = lti_req.getCourseCollections() is_teacher = lti_req.isTeacher() or queries.is_superuser_or_staff(request.user) collection_admin_perms = [] for r in (Users_Collections.ADMINISTRATOR, Users_Collections.INSTRUCTOR): role_name = Users_Collections.role_map[r] collection_admin_perms.extend(role_bucket[role_name]) copy_collections = queries.getCopyCollectionList(request.user) collection_filters = dict(collection_ids=course_collections, can_filter=not queries.is_superuser_or_staff(request.user)) collection_list = queries.getCollectionList(role_bucket, **collection_filters) display_collections = queries.groupCollectionsByList(collection_list) context = { "nav_collections": collection_list, "display_collections": display_collections, "copy_collections": copy_collections, "active_collection": None, "user_collection_role": role_bucket, "collection_admin_perms": collection_admin_perms, "is_teacher": is_teacher, } if collection_id: try: cur_collection = Collection.objects.get(id=collection_id) except Collection.DoesNotExist: raise Http404 filtered_collection_list = [c for c in collection_list if c['id'] == cur_collection.id] if len(filtered_collection_list) == 0: raise Http404 context['active_collection'] = filtered_collection_list[0] analytics.track( actor=request.user, verb=analytics.VERBS.viewed, object=analytics.OBJECTS.collection, context={"collection_id": collection_id} ) return render(request, 'collections/index.html', context)
def custom_create(request): """ Creates a collection with custom template. """ upload_error = '' course_name = '' role_bucket = services.get_or_update_role_bucket(request) lti_req = LTIService(request) course_collections = lti_req.getCourseCollections() collection_filters = dict( collection_ids=course_collections, can_filter=not queries.is_superuser_or_staff(request.user)) collection_list = queries.getCollectionList(role_bucket, **collection_filters) if request.method == 'POST': d = {'user': request.user} log.info('The user is uploading a custom deck.', extra=d) course_name = request.POST.get('course', '') if course_name == '' or 'file' not in request.FILES: if course_name == '' and 'file' not in request.FILES: upload_error = "Course name needed. No file selected." elif course_name != '' and 'file' not in request.FILES: upload_error = 'No file selected' else: upload_error = 'Course name needed.' else: try: is_teacher = lti_req.isTeacher() deck = services.handle_custom_file(request.FILES['file'], course_name, request.user, is_teacher) lti_req.associateCourse(deck.collection.id) log.info( 'Custom deck %(d)s successfully added to the new collection %(c)s.' % { 'c': str(deck.collection.id), 'd': str(deck.id) }, extra=d) return redirect(deck) except Exception, e: upload_error = str(e) msg = 'The following error occurred when the user tried uploading a deck: ' log.error(msg + upload_error, extra=d)
def edit(request, collection_id=None): """Edits a collection.""" collection = Collection.objects.get(id=collection_id) role_bucket = services.get_or_update_role_bucket(request) course_collections = LTIService(request).getCourseCollections() collection_filters = dict( collection_ids=course_collections, can_filter=not queries.is_superuser_or_staff(request.user)) collection_list = queries.getCollectionList(role_bucket, **collection_filters) if request.method == 'POST': collection_form = CollectionForm(request.POST, instance=collection) if collection_form.is_valid(): collection = collection_form.save() response = redirect(collection) response['Location'] += '?instructor=edit' analytics.track(actor=request.user, verb=analytics.VERBS.modified, object=analytics.OBJECTS.collection, context={"collection_id": collection_id}) return response else: collection_form = CollectionForm(instance=collection) collection_decks = [] for c in collection_list: if c['id'] == collection.id: collection_decks = c['decks'] break print collection_decks context = { "collection_form": collection_form, "nav_collections": collection_list, "collection": collection, "collection_decks": collection_decks, } return render(request, 'collections/edit.html', context)
def edit(request, collection_id=None): """Edits a collection.""" collection = Collection.objects.get(id=collection_id) role_bucket = services.get_or_update_role_bucket(request) course_collections = LTIService(request).getCourseCollections() collection_filters = dict(collection_ids=course_collections, can_filter=not queries.is_superuser_or_staff(request.user)) collection_list = queries.getCollectionList(role_bucket, **collection_filters) if request.method == 'POST': collection_form = CollectionForm(request.POST, instance=collection) if collection_form.is_valid(): collection = collection_form.save() response = redirect(collection) response['Location'] += '?instructor=edit' analytics.track( actor=request.user, verb=analytics.VERBS.modified, object=analytics.OBJECTS.collection, context={"collection_id": collection_id} ) return response else: collection_form = CollectionForm(instance=collection) collection_decks = [] for c in collection_list: if c['id'] == collection.id: collection_decks = c['decks'] break print collection_decks context = { "collection_form": collection_form, "nav_collections": collection_list, "collection": collection, "collection_decks": collection_decks, } return render(request, 'collections/edit.html', context)
def upload_deck(request, deck_id=None): ''' Imports a deck of cards from an excel spreadsheet. ''' upload_error = '' deck = Deck.objects.get(id=deck_id) current_collection = deck.collection role_bucket = services.get_or_update_role_bucket(request) canvas_course_collections = LTIService(request).getCourseCollections() collection_list = queries.getCollectionList( role_bucket, collection_ids=canvas_course_collections) if request.method == 'POST': d = {'user': request.user} log.info('The user is uploading a new deck.', extra=d) deck_form = DeckImportForm(request.POST, request.FILES) if deck_form.is_valid(): if 'file' in request.FILES: try: services.handle_uploaded_deck_file(deck, request.FILES['file']) log.info( 'New deck successfully added to the collection %(c)s.' % {'c': str(deck.collection.id)}, extra=d) analytics.track( actor=request.user, verb=analytics.VERBS.uploaded, object=analytics.OBJECTS.deck, context={"deck_id": deck_id}, ) return redirect(deck) except Exception, e: upload_error = str(e) msg = 'The following error occurred when the user tried uploading a deck: ' log.error(msg + upload_error, extra=d) else: log.info('No file selected.', extra=d) else: log.error('Deck Form is not valid.', extra=d)
def upload_deck(request, deck_id=None): ''' Imports a deck of cards from an excel spreadsheet. ''' upload_error = '' deck = Deck.objects.get(id=deck_id) current_collection = deck.collection role_bucket = services.get_or_update_role_bucket(request) canvas_course_collections = LTIService(request).getCourseCollections() collection_list = queries.getCollectionList(role_bucket, collection_ids=canvas_course_collections) if request.method == 'POST': d = {'user': request.user} log.info('The user is uploading a new deck.', extra=d) deck_form = DeckImportForm(request.POST, request.FILES) if deck_form.is_valid(): if 'file' in request.FILES: try: services.handle_uploaded_deck_file(deck, request.FILES['file']) log.info('New deck successfully added to the collection %(c)s.' %{'c': str(deck.collection.id)}, extra=d) analytics.track( actor=request.user, verb=analytics.VERBS.uploaded, object=analytics.OBJECTS.deck, context={"deck_id": deck_id}, ) return redirect(deck) except Exception, e: upload_error = str(e) msg = 'The following error occurred when the user tried uploading a deck: ' log.error(msg + upload_error, extra=d) else: log.info('No file selected.', extra=d) else: log.error('Deck Form is not valid.', extra=d)
def custom_create(request): """ Creates a collection with custom template. """ upload_error = '' course_name = '' role_bucket = services.get_or_update_role_bucket(request) lti_req = LTIService(request) course_collections = lti_req.getCourseCollections() collection_filters = dict(collection_ids=course_collections, can_filter=not queries.is_superuser_or_staff(request.user)) collection_list = queries.getCollectionList(role_bucket, **collection_filters) if request.method == 'POST': d = {'user': request.user} log.info('The user is uploading a custom deck.', extra=d) course_name = request.POST.get('course', '') if course_name == '' or 'file' not in request.FILES: if course_name == '' and 'file' not in request.FILES: upload_error = "Course name needed. No file selected." elif course_name != '' and 'file' not in request.FILES: upload_error = 'No file selected' else: upload_error = 'Course name needed.' else: try: is_teacher = lti_req.isTeacher() deck = services.handle_custom_file(request.FILES['file'], course_name, request.user, is_teacher) lti_req.associateCourse(deck.collection.id) log.info('Custom deck %(d)s successfully added to the new collection %(c)s.' %{'c': str(deck.collection.id), 'd':str(deck.id)}, extra=d) return redirect(deck) except Exception, e: upload_error = str(e) msg = 'The following error occurred when the user tried uploading a deck: ' log.error(msg + upload_error , extra=d)