class CreateUserView(View): @method_decorator(login_required) @method_decorator(role_required('user manager')) def get(self, request): form = UserCreationForm() return render_to_response('users/create.html', context=RequestContext(request, locals())) @method_decorator(login_required) @method_decorator(role_required('user manager')) def post(self, request): # Create and validate the form form = UserCreationForm(request.POST, request.FILES) if form.is_valid(): # Retrieve the user, set the password, and create him/her user, password = form.instance, form.cleaned_data['password'] user.save() user.refresh_from_db() grades = form.cleaned_data['grades'] user.grades = grades user.set_password(password) user.save() # Redirect to user list return redirect(reverse_lazy('users:list')) return render_to_response('users/create.html', context=RequestContext(request, locals()), status=401)
class CreateMaterialView(View): @method_decorator(login_required) @method_decorator(role_required('content manager')) def get(self, request): form = MaterialForm(initial={'user': request.user}) fields = { 'suggested_ages': SchoolGrade, 'types': Type, 'themes': Theme, 'languages': Language } for field, queryset in fields.iteritems(): form.fields[field].queryset = queryset.objects.active() return render_to_response('materials/create.html', context=RequestContext(request, locals())) @method_decorator(login_required) @method_decorator(role_required('content manager')) @method_decorator(csrf_protect) def post(self, request): form = MaterialForm(request.POST, request.FILES, initial={'user': request.user}) if form.is_valid(): material = form.instance #TODO:set upload_to attribute to destination path form.save() ActionLog.objects.log_content( 'Registered new material entry (id: %s)' % material.id, user=request.user, status=201) return redirect( reverse_lazy('content:view', kwargs={'content_id': material.id})) ActionLog.objects.log_content('Failed to register new material entry', user=request.user, status=401) return render_to_response('materials/create.html', context=RequestContext(request, locals()), status=401)
class SearchView(View): @method_decorator(login_required) @method_decorator(role_required('parent')) def get(self, request): users = User.objects.active() return render_to_response('users/list.html', context=RequestContext(request, locals()))
class MaterialDownloadView(View): """ This view handles the logic for downloading material """ @method_decorator(login_required) @method_decorator(role_required('parent')) def get(self, request, content_id=0): """ This method is called when the user requests to download a material It validates the material exists and is active, registers the download and serves the file Returns forbidden response if unsuccessful and HttpResponse with the material if successful """ # Attempt to load the material try: material = Material.objects.active().get(id=content_id) except Material.DoesNotExist: # If not exists, write in Action Log and return forbidden ActionLog.objects.log_content( 'Attempted to load nonexistent material (id: %s)' % content_id, user=request.user, status=403) return HttpResponseForbidden() else: # Check if the material has a content attached if bool(material.content.name) is False: # If no content, write in Action Log and return forbidden ActionLog.objects.log_content( 'Attempted to download material without attached content (id: %s)' % content_id, user=request.user, status=403) return HttpResponseForbidden() # Register the download Download.objects.create( user=request.user, material=material, ) # Get the material content type we'll use it later content_type, _ = guess_type(material.content.name, strict=True) # Write in Action Log ActionLog.objects.log_content('Downloaded material (id: %s)' % content_id, user=request.user, status=200) # Read the file into the output stream and send it to the user return HttpResponse(material.content.read(), content_type=content_type)
class ReportView(View): """ Represents a generic report view which compiles a report within a transaction to speed up report generation. The report is compiled once and stored in the server for history purposes. The report is compiled as an HTML which presents the collected data in a simple way. This reports may only be created by an administrator. """ report_name = '' report_class = None template = '' @method_decorator(login_required) @method_decorator(role_required('administrator')) # @method_decorator(cache_page(1200)) def get(self, request): # Record starting time start = now() # Perform basic filters based on time range (if set) query = self.report_class.objects.active() # Extract information within a transaction with atomic(): data = self.generate_report(query) # Render the report name = self.report_name report_doc = render(template_name=self.template, context=RequestContext(request, locals()), request=request).encode('utf-8') # Count the elapsed time time = (now() - start).total_seconds() # Create the report output (save it if required to) filename = 'report_%s.html' % calendar.timegm(start.timetuple()) with open('reports/%s' % filename, 'w') as f: f.write(report_doc) # Send file as attachment ActionLog.objects.log_reports( 'Generated report (name: %s) in %s seconds' % (self.report_name, time), user=request.user) return HttpResponse(report_doc) def generate_report(self, query): raise NotImplementedError()
class ViewUserView(View): """ Class responsible to handle user requests inquiring about user profiles: Receives the logged user request containing the target user profile id to display. It evaluates if the user according to its role should be allowed or denied access to the target user profile Returns the target user profile view on success, 40X otherwise. """ @method_decorator(login_required) @method_decorator(role_required('teacher')) def get(self, request, user_id=0): try: u = User.objects.get(id=user_id) except User.DoesNotExist: ActionLog.objects.log_account( 'Invalid user profile information request : (user_id: %s)' % user_id, user=request.user, status=401) return HttpResponseForbidden() else: if request.user.belongs_to('user manager'): form = UserViewForm(instance=u) else: if request.user.id == int(user_id): form = UserViewForm(instance=u) else: ActionLog.objects.log_account( 'Attempted to view user profile information without enough privileges : (user_id: %s)' % user_id, user=request.user, status=401) return HttpResponseForbidden() ActionLog.objects.log_account( 'Displayed user profile information (email address: %s)' % u.email_address, user=request.user) return render_to_response('users/view.html', context=RequestContext(request, locals()))
class DownloadsView(View): """ """ @method_decorator(login_required) @method_decorator(role_required('teacher')) def get(self, request, content_id = 0): # Serve file # Save download try: material = Material.objects.get(id = content_id) except Material.DoesNotExist: ActionLog.objects.log_content('Failed to locate material with ID \'%s\'' % content_id, status = 403, user = request.user) return HttpResponseForbidden() else: Download.objects.create( user = request.user, material = material, ) ActionLog.objects.log_tags('Successfully downloaded material (id: %s)' % content_id, user = request.user)
class SearchView(View): """ This view handles the material search function. The filters are received and then look in the database for materials that match with the filters. The average of the material rating is calculated and added to the properties of the object. The number of the materials to be displayed is handle with the paginator method. At the end, the objects are returned with a json format. """ @method_decorator(login_required) @method_decorator(role_required('parent')) def get(self, request): form = SearchForm() return render_to_response('home.html', context=RequestContext(request, locals())) @method_decorator(login_required) @method_decorator(ajax_required) @method_decorator(role_required('parent')) @method_decorator(cache_page(300)) def post(self, request): # Filter material based on query query = Material.objects.active() form = SearchForm(request.POST) form.is_valid() data = form.cleaned_data params = (Q(title__icontains=data['search']) | Q(description__icontains=data['search'])) query = query.filter(params) params_list = [] if (len(data['grades']) > 0): params_list.append(('suggested_ages__in', data['grades'])) if (len(data['type']) > 0): params_list.append(('types__in', data['type'])) if (len(data['language']) > 0): params_list.append(('languages__in', data['language'])) if (len(data['theme']) > 0): params_list.append(('themes__in', data['theme'])) if len(params_list) > 0: params2 = [Q(x) for x in params_list] query = query.filter(reduce(operator.or_, params2)) # Annotate the results with rating average query = query.annotate(rating=Avg('comments__rating_value')) # Paginate the results and serialize the response paginator = Paginator(query, request.GET.get('page_size', 10)) page = request.GET.get('page', 1) try: materials = paginator.page(page) except EmptyPage: materials = paginator.page(paginator.num_pages) except PageNotAnInteger: materials = paginator.page(1) ActionLog.objects.log_content('Queried for materials (filters: %s)' % data, user=request.user, status=302) return JsonResponse({ 'version': '1.0.0', 'status': 302, 'pages': { 'prev': materials.previous_page_number() if materials.has_previous() else None, 'next': materials.next_page_number() if materials.has_next() else None, 'current': page, 'total': paginator.num_pages, 'count': query.count(), 'page_size': paginator.per_page }, 'results': [{ 'id': m.id, 'title': m.title, 'description': m.description, 'rating': int(round(m.rating or 0)), 'tags': { 'types': [t.name for t in m.types.filter(active=True)], 'themes': [t.name for t in m.themes.filter(active=True)], 'languages': [t.name for t in m.languages.filter(active=True)], 'ages': [t.name for t in m.suggested_ages.filter(active=True)] } } for m in materials] })
class TagsView(View): """ Inputs: request.GET['query'] (optional): The filter criteria to use in order to select tags. Defaults to None. Outputs: A JSON document complying with the following schema: { "$schema": "http://json-schema.org/draft-04/schema#", "id": "http://jsonschema.net/tag-list", "type": "object", "properties": { "type": { "id": "http://jsonschema.net/tag-list/type", "type": "string" }, "data": { "id": "http://jsonschema.net/tag-list/data", "type": "array", "items": [ { "id": "http://jsonschema.net/tag-list/data/tag", "type": "object", "properties": { "id": { "id": "http://jsonschema.net/tag-list/data/tag/id", "type": "integer" }, "name": { "id": "http://jsonschema.net/tag-list/data/tag/name", "type": "string" } } } ] } }, "required": [ "type", "data" ] } Possible values include: { "type": "theme", "data": [] } { "type": "language", "data": [ { "id": 1, "name": "inglés" } ] } """ @method_decorator(login_required) @method_decorator(ajax_required) @method_decorator(role_required('content manager')) def get(self, request, tag_type=''): # Retrieve parameters filters = request.GET.get('filter', '') # Query all tags from the specified tag, if valid if tag_type == 'theme': data = Theme.objects.active().filter(name__icontains=filters) elif tag_type == 'type': data = Type.objects.active().filter(name__icontains=filters) elif tag_type == 'language': data = Language.objects.active().filter(name__icontains=filters) else: # If type is invalid, return error ActionLog.objects.log_tags('Failed to display %s tags' % tag_type, user=request.user, status=401) return HttpResponseForbidden() # Return tags list JSON ActionLog.objects.log_tags('Located tag cluster', user=request.user) return JsonResponse({ 'version': '1.0.0', 'status': 200, 'type': tag_type, 'data': [{ 'id': tag.id, 'name': tag.name } for tag in data] }) @method_decorator(login_required) @method_decorator(ajax_required) @method_decorator(csrf_protect) @method_decorator(role_required('content manager')) def post(self, request, tag_id=0, tag_type='', action=''): # Tag creation request if action == 'create': # Retrieve parameters name = request.POST['name'] # Determine tag type, if valid if tag_type == 'type': tag_cls = Type elif tag_type == 'theme': tag_cls = Theme elif tag_type == 'language': tag_cls = Language else: # If not valid, return error ActionLog.objects.log_tags( 'Failed to create tag entry (id: %s)' % tag_id, user=request.user, status=401) return HttpResponseForbidden() # Ensure that a tag with the same name does not exist if not tag_cls.objects.active().filter(name__iexact=name).exists(): # Create tag tag = tag_cls.objects.create(name=name) # Return response JSON ActionLog.objects.log_tags('Created tag entry (id: %s)' % tag_id, user=request.user, status=201) return JsonResponse( { 'version': '1.0.0', 'status': 201, 'data': { 'type': tag_type, 'id': tag.id, 'name': tag.name } }, status=201) elif bool(tag_cls.objects.inactive().filter( name__iexact=name)) is True: tag = tag_cls.objects.inactive().get(name__iexact=name) tag.active = True tag.save() return JsonResponse( { 'version': '1.0.0', 'status': 201, 'data': { 'type': tag_type, 'id': tag.id, 'name': tag.name } }, status=201) # Return duplicate tag response JSON ActionLog.objects.log_tags('Failed to create tag entry (id: %s)' % tag_id, user=request.user, status=302) return JsonResponse({ 'version': '1.0.0', 'status': 302 }, status=302) # Tag edition request elif action == 'edit': # Retrieve tag name name = request.POST['name'] if tag_type == 'theme': tag = Theme.objects.get(id=tag_id) tag.name = name tag.save() elif tag_type == 'type': tag = Type.objects.get(id=tag_id) tag.name = name tag.save() elif tag_type == 'language': tag = Language.objects.get(id=tag_id) tag.name = name tag.save() ActionLog.objects.log_tags('Edited tag (category: %s, id: %s)' % (tag_type, tag_id), user=request.user, status=201) # Return response JSON return JsonResponse({ 'version': '1.0.0', 'status': 200, 'data': { 'type': tag_type, 'id': tag_id, 'name': name } }) #Tag deletion request elif action == 'delete': if tag_type == 'theme': Theme.objects.get(id=tag_id).delete() elif tag_type == 'type': Type.objects.get(id=tag_id).delete() elif tag_type == 'language': Language.objects.get(id=tag_id).delete() else: # If not valid, return an error ActionLog.objects.log_tags( 'Failed to delete tag entry (id: %s)' % tag_id, user=request.user, status=401) return HttpResponseForbidden() # Return response JSON ActionLog.objects.log_tags('Deleted tag (id: %s)' % tag_id, user=request.user) return JsonResponse({'version': '1.0.0', 'status': 200})
class EditMaterialView(View): @method_decorator(login_required) @method_decorator(role_required('content manager')) def get(self, request, content_id=0): material = Material.objects.active().get(id=content_id) form = MaterialForm(instance=material, initial={'user': request.user}) fields = { 'suggested_ages': SchoolGrade, 'types': Type, 'themes': Theme, 'languages': Language } for field, queryset in fields.iteritems(): form.fields[field].queryset = queryset.objects.active() return render_to_response('materials/edit.html', context=RequestContext(request, locals())) @method_decorator(login_required) @method_decorator(role_required('content manager')) @method_decorator(csrf_protect) def post(self, request, content_id=0): material = Material.objects.active().get(id=content_id) form = MaterialForm(request.POST, request.FILES, instance=material, initial={'user': request.user}) #TODO: prepopulate form (checkboxes) if form.is_valid(): material = form.instance form.save() ActionLog.objects.log_content('Edited material entry (id: %s)' % content_id, user=request.user, status=200) return redirect( reverse_lazy('content:view', kwargs={'content_id': material.id})) ActionLog.objects.log_content( 'Attempted to edit material entry (id: %s)' % content_id, user=request.user, status=401) return render_to_response('materials/edit.html', context=RequestContext(request, locals())) @method_decorator(login_required) @method_decorator(role_required('content manager')) @method_decorator(csrf_protect) def delete(self, request, content_id=0): material = Material.objects.active().get(id=content_id) ActionLog.objects.log_content('Deleted material (id: %s)' % material.id, status=200, user=request.user) material.delete() return JsonResponse(data={ 'version': '1.0.0', 'status': 200, 'material': { 'id': material.id, 'status': 'delete' } }, content_type='application/json')
class MaterialDetailView(View): @method_decorator(login_required) @method_decorator(role_required('parent')) def get(self, request, content_id=0): try: material = Material.objects.active().get(id=content_id) except Material.DoesNotExist: ActionLog.objects.log_content( 'Attempted to load nonexistent material (id: %s)' % content_id, user=request.user, status=403) return HttpResponseForbidden() else: in_portfolio = Portfolio.objects.user(request.user).items.filter( material=material, active=True).exists() has_commented = Comment.objects.active().filter( user=request.user, material=material).exists() comments = Comment.objects.active().filter(material=material) types = material.types.active() languages = material.themes.active() themes = material.languages.active() ages = material.suggested_ages.active() ActionLog.objects.log_content('Viewed material (id: %s)' % content_id, user=request.user) return render_to_response('materials/detail.html', context=RequestContext( request, locals())) @method_decorator(login_required) @method_decorator(ajax_required) @method_decorator(role_required('teacher')) def post(self, request, content_id=0): """ This method is called when the user posts a comment on a material detail view. It validates that the user has already rated such material and that the comment's length is lower than 500 chars """ try: material = Material.objects.active().get(id=content_id) except Material.DoesNotExist: ActionLog.objects.log_content( 'Attempted to recover nonexistent material (id: %s)' % content_id, user=request.user, status=403) return HttpResponseForbidden() else: if Comment.objects.active().filter(user=request.user, material=material).exists(): ActionLog.objects.log_content( 'User has already issued comment for this material (id: %s)' % content_id, user=request.user, status=403) return HttpResponseForbidden() content, rating = request.POST.get('content', None), request.POST.get( 'rating_value', None) if content is None or rating is None: ActionLog.objects.log_content( 'Comment data was missing or is invalid', user=request.user, status=403) return HttpResponseForbidden() comment = Comment.objects.create(user=request.user, material=material, content=content, rating_value=rating) ActionLog.objects.log_content( 'Added comment for material (id: %s)' % content_id, user=request.user, status=201) return JsonResponse( { 'version': '1.0.0', 'status': 201, 'data': { 'content': comment.content, 'user': request.user.id, 'material': material.id, 'rating': int(comment.rating_value) } }, status=201)
class ReportsView(View): """ Inputs: request.GET['query'] (optional): The filter criteria to use in order to select tags. Defaults to None. Outputs: A JSON document complying with the following schema: { "$schema": "http://json-schema.org/draft-04/schema#", "id": "http://jsonschema.net/tag-list", "type": "object", "properties": { "data": { "id": "http://jsonschema.net/tag-list/data", "type": "array", "items": [ { "description": "string" "material": "object" "user": "******" } ] } }, "required": [ "type", "data" ] } Possible values include: { "data": [ { "description": "test", "material": "test_material", "user": "******" } ] } { "data": [] } """ @method_decorator(login_required) @method_decorator(role_required('content manager')) def get(self, request): """ Receives a get request and returns a JSON with all active reports currently in progress """ # Query reports in progress reports = Report.objects.active().filter(status=1) # Write action log ActionLog.objects.log_reports('Listed reports', user=request.user) # Send response return render_to_response('reports/view.html', context=RequestContext(request, locals())) @method_decorator(login_required) @method_decorator(ajax_required) @method_decorator(csrf_protect) @method_decorator(role_required('teacher')) def post(self, request, content_id=0): """ Receives a post request and material id, creates new report for that material and returns a JSON response """ # Attempt to load the material try: material = Material.objects.active().get(id=content_id) except Material.DoesNotExist: # If material doesn't exist, write in action log and returned forbidden response ActionLog.objects.log_reports( 'Failed to locate material with ID \'%s\'' % content_id, status=403, user=request.user) return HttpResponseForbidden() else: # Get description parameter self.description = request.POST['description'] # Create new report for specified material with the received description. Defaults to "in progress" status new_report = Report.objects.create(user=request.user, material=material, description=self.description, status=1) # Write action log ActionLog.objects.log_reports('Created report (id: %s)' % new_report.id, user=request.user, status=201) # Return response JSON return JsonResponse( { 'version': '1.0.0', 'status': 201, 'data': { 'description': new_report.description, 'material': new_report.material.id, 'user': new_report.user.id } }, status=201) @method_decorator(login_required) @method_decorator(ajax_required) @method_decorator(csrf_protect) @method_decorator(role_required('content manager')) def patch(self, request, report_id=0): """ Receives a patch request and report id. Modifies specified report to "resolved" status and returns JSON """ # Query database for specified report report = Report.objects.get(id=report_id) # Change status report.status = 2 report.save() # Write action log ActionLog.objects.log_reports('Updated report ( id: %s) to resolved' % report_id, user=request.user, status=201) # Return response JSON return JsonResponse({ 'version': '1.0.0', 'status': 200, 'data': { 'description': report.description, 'material': report.material.id, 'user': report.user.id } }) @method_decorator(login_required) @method_decorator(ajax_required) @method_decorator(csrf_protect) @method_decorator(role_required('content manager')) def delete(self, request, report_id=0): """ Receives a delete request and report id. Modifies specified report to "rejected" status and returns JSON """ # Query database for specified report report = Report.objects.get(id=report_id) # Change status report.status = 4 report.save() # Write action log ActionLog.objects.log_reports('Rejected report ( id: %s)' % report_id, user=request.user, status=201) # Return response JSON return JsonResponse({ 'version': '1.0.0', 'status': 200, 'data': { 'description': report.description, 'material': report.material.id, 'user': report.user.id } })
class EditUserView(View): """ Class responsible to handle user requests to edit user profiles: Receives the logged user request containing the target user profile id to edit. It evaluates if the user according to its role should be allowed or denied the edit to the target user profile Returns the target user profile edit form on success, 40X otherwise. """ @method_decorator(login_required) @method_decorator(role_required('teacher')) def get(self, request, user_id=0): try: u = User.objects.active().get(id=user_id) except User.DoesNotExist: ActionLog.objects.log_account( 'Invalid user profile information request : (user_id: %s)' % user_id, user=request.user, status=401) return HttpResponseForbidden() else: if request.user.belongs_to('user manager'): form = AdminEditForm(instance=u) else: if request.user.id == int(user_id): form = BasicEditForm(instance=u) else: ActionLog.objects.log_account( 'Attempted to view user profile information without enough privileges : (user_id: %s)' % user_id, user=request.user, status=401) return HttpResponseForbidden() return render_to_response('users/edit.html', context=RequestContext(request, locals())) @method_decorator(csrf_protect) @method_decorator(login_required) @method_decorator(role_required('teacher')) def post(self, request, user_id=0): try: u = User.objects.active().get(id=user_id) except User.DoesNotExist: ActionLog.objects.log_account( 'User requested to edit does not exist: (user_id: %s)' % user_id, user=request.user, status=401) return HttpResponseForbidden() else: if request.user.belongs_to('user manager'): form = AdminEditForm(request.POST, request.FILES, instance=u) else: if request.user.id == int(user_id): form = BasicEditForm(request.POST, request.FILES, instance=u) else: ActionLog.objects.log_account( 'Attempted to edit user profile information without enough privileges : (user_id: %s)' % user_id, user=request.user, status=401) return HttpResponseForbidden() # Validate the form and, if valid, save the user if form.is_valid(): user = form.instance ActionLog.objects.log_account( 'Edited user profile information (email address: %s)' % user.email_address, user=request.user) form.save(commit=True) password = form.cleaned_data['password'] if password is not None: if password is not '' or is_password_usable( password) and not user.check_password(password): user.set_password(password) user.save() return redirect( reverse_lazy('users:view', kwargs={'user_id': user.id})) # Log the error and resend the form ActionLog.objects.log_account( 'Attempted to edit user profile information with invalid detail(s) (email address: %s)' % u.email_address, user=request.user, status=401) return render_to_response('users/edit.html', context=RequestContext(request, locals())) @method_decorator(ajax_required) @method_decorator(login_required) @method_decorator(csrf_protect) @method_decorator(role_required('user manager')) def delete(self, request, user_id=0): #Fetch the user to delete try: u = User.objects.active().get(id=user_id) #It either does not exist or it is inactive except User.DoesNotExist: ActionLog.objects.log_account('Attempted to delete user account', user=request.user, status=401) return HttpResponseForbidden() else: u.delete() ActionLog.objects.log_account( 'Deleted user account (email address: %s)' % u.email_address, user=request.user) return JsonResponse({'version': '1.0.0', 'status': 200})
class SelectReportView(View): @method_decorator(login_required) @method_decorator(role_required('administrator')) def get(self, request): return render_to_response('reporting/select-report.html', context=RequestContext(request, locals()))
class PortfolioView(View): """ This view handles the portfolio (favorite items). The provided views, except for the GET route which displays the portfolio for the current user, are all AJAX-based and called directly through the interface. The interface provided by the GET route is updated by the execution of the other methods. This view allows the user to: add an existing material to his/her portfolio (if it has not already been added) and remove a material from his/her portfolio (if it exists in the portfolio currently). """ @method_decorator(login_required) @method_decorator(role_required('teacher')) def get(self, request): portfolio = Portfolio.objects.user(request.user) paginator = Paginator(portfolio.items.filter(active=True), request.GET.get('page_size', 15)) page = request.GET.get('page', 1) try: items = paginator.page(page) except EmptyPage: items = paginator.page(paginator.num_pages) except PageNotAnInteger: items = paginator.page(1) return render_to_response('materials/portfolio.html', context=RequestContext(request, locals())) @method_decorator(ajax_required) @method_decorator(login_required) @method_decorator(csrf_protect) @method_decorator(role_required('teacher')) def put(self, request, content_id=0): # Attempt to load the material try: material = Material.objects.active().get(id=content_id) except Material.DoesNotExist: ActionLog.objects.log_content( 'Failed to locate material with ID \'%s\'' % content_id, status=403, user=request.user) return HttpResponseForbidden() else: # Check the material is not already added portfolio = Portfolio.objects.user(request.user) if portfolio.items.filter(material=material, active=True).exists(): ActionLog.objects.log_content( 'Cannot add already added material', status=403, user=request.user) return HttpResponseForbidden() # Add the item to the portfolio portfolio.items.create(material=material, portfolio=portfolio) ActionLog.objects.log_content( 'Added material ID \'%s\' to user portfolio' % content_id, status=201, user=request.user) # Serialize the material return JsonResponse( { 'version': '1.0.0', 'status': 201, 'material': { 'id': material.id, 'title': material.title, 'description': material.description, 'content': material.content.url if bool(material.content) is True else None, 'link': material.link } }, status=201) @method_decorator(ajax_required) @method_decorator(login_required) @method_decorator(csrf_protect) @method_decorator(role_required('teacher')) def delete(self, request, content_id=0): # Attempt to load the material try: material = Material.objects.active().get(id=content_id) except Material.DoesNotExist: ActionLog.objects.log_content( 'Failed to locate material with ID \'%s\'' % content_id, status=403, user=request.user) return HttpResponseForbidden() else: # Check the material is not already added portfolio = Portfolio.objects.user(request.user) if not portfolio.items.filter(active=True).filter( material=material).exists(): ActionLog.objects.log_content( 'Cannot remove non-included material', status=403, user=request.user) return HttpResponseForbidden() # Add the item to the portfolio portfolio.items.filter(active=True).get(material=material).delete() ActionLog.objects.log_content( 'Removed material ID \'%s\' to user portfolio' % content_id, user=request.user) # Serialize the material return JsonResponse({ 'version': '1.0.0', 'status': 200, 'material': { 'id': material.id, 'title': material.title } })