Commit 9f6cfaea by Waheed Ahmed Committed by GitHub

Merge pull request #294 from edx/waheed/ecom-4978-top-level-object-access

Added Top level Object Access.
parents 9983c7a9 ea337ad7
from django.http import HttpResponseForbidden
from course_discovery.apps.publisher.models import Course, Seat
class ViewPermissionMixin(object):
def get_course(self):
publisher_object = self.get_object()
if isinstance(publisher_object, Course):
return publisher_object
if isinstance(publisher_object, Seat):
return publisher_object.course_run.course
if hasattr(publisher_object, 'course'):
return publisher_object.course
return None
def check_user(self, user):
course = self.get_course()
return check_view_permission(user, course)
def permission_failed(self):
return HttpResponseForbidden()
def dispatch(self, request, *args, **kwargs):
if not self.check_user(request.user):
return self.permission_failed()
return super(ViewPermissionMixin, self).dispatch(request, *args, **kwargs)
def check_view_permission(user, course):
return user.is_staff or user.has_perm(Course.VIEW_PERMISSION, course)
......@@ -70,6 +70,7 @@ class SeatFactory(factory.DjangoModelFactory):
class GroupFactory(factory.DjangoModelFactory):
name = FuzzyText(prefix="Test Group ")
class Meta:
model = Group
import ddt
from mock import patch
from django.conf import settings
from django.contrib.sites.models import Site
from django.core.urlresolvers import reverse
from django.forms import model_to_dict
from django.test import TestCase
from guardian.shortcuts import assign_perm
from course_discovery.apps.core.tests.factories import UserFactory, USER_PASSWORD
from course_discovery.apps.publisher.models import Course, CourseRun, Seat, State
from course_discovery.apps.publisher.tests import factories
from course_discovery.apps.publisher.tests.utils import create_non_staff_user_and_login
from course_discovery.apps.publisher.views import CourseRunDetailView
from course_discovery.apps.publisher.wrappers import CourseRunWrapper
from course_discovery.apps.publisher_comments.tests.factories import CommentFactory
......@@ -46,8 +50,8 @@ class CreateUpdateCourseViewTests(TestCase):
self.assertNotContains(response, 'Add new comment')
self.assertNotContains(response, 'Total Comments')
def test_update_course(self):
""" Verify that we can update an existing course. """
def test_update_course_with_staff(self):
""" Verify that staff user can update an existing course. """
course_dict = model_to_dict(self.course)
updated_course_title = 'Updated {}'.format(self.course.title)
course_dict['title'] = updated_course_title
......@@ -75,6 +79,73 @@ class CreateUpdateCourseViewTests(TestCase):
self.assertContains(response, 'Add new comment')
self.assertContains(response, comment.comment)
def test_course_edit_page_with_non_staff(self):
""" Verify that non staff user can't access course edit page without permission. """
non_staff_user, group = create_non_staff_user_and_login(self)
course_dict = model_to_dict(self.course)
updated_course_title = 'Updated {}'.format(self.course.title)
course_dict['title'] = updated_course_title
self.assertNotEqual(self.course.title, updated_course_title)
response = self.client.get(
reverse('publisher:publisher_courses_edit', kwargs={'pk': self.course.id})
)
self.assertEqual(response.status_code, 403)
# assign user a group and assign view permission on that group
non_staff_user.groups.add(group)
assign_perm(Course.VIEW_PERMISSION, group, self.course)
response = self.client.get(
reverse('publisher:publisher_courses_edit', kwargs={'pk': self.course.id})
)
self.assertEqual(response.status_code, 200)
def test_update_course_with_non_staff(self):
""" Tests for update course with non staff user. """
non_staff_user, group = create_non_staff_user_and_login(self)
course_dict = model_to_dict(self.course)
updated_course_title = 'Updated {}'.format(self.course.title)
course_dict['title'] = updated_course_title
self.assertNotEqual(self.course.title, updated_course_title)
response = self.client.post(
reverse('publisher:publisher_courses_edit', kwargs={'pk': self.course.id}),
course_dict
)
# verify that non staff user can't update course without permission
self.assertEqual(response.status_code, 403)
# assign user a group and assign view permission on that group
non_staff_user.groups.add(group)
assign_perm(Course.VIEW_PERMISSION, group, self.course)
response = self.client.post(
reverse('publisher:publisher_courses_edit', kwargs={'pk': self.course.id}),
course_dict
)
self.assertRedirects(
response,
expected_url=reverse('publisher:publisher_courses_edit', kwargs={'pk': self.course.id}),
status_code=302,
target_status_code=200
)
course = Course.objects.get(id=self.course.id)
# Assert that course is updated.
self.assertEqual(course.title, updated_course_title)
# add new and check the comment on edit page.
comment = CommentFactory(content_object=self.course, user=self.user, site=self.site)
response = self.client.get(reverse('publisher:publisher_courses_edit', kwargs={'pk': self.course.id}))
self.assertContains(response, 'Total Comments 1')
self.assertContains(response, 'Add new comment')
self.assertContains(response, comment.comment)
class CreateUpdateCourseRunViewTests(TestCase):
""" Tests for the publisher `CreateCourseRunView` and `UpdateCourseRunView`. """
......@@ -115,11 +186,78 @@ class CreateUpdateCourseRunViewTests(TestCase):
self.assertNotContains(response, 'Add new comment')
self.assertNotContains(response, 'Total Comments')
def test_update_course_run(self):
""" Verify that we can update an existing course run. """
def test_update_course_run_with_staff(self):
""" Verify that staff user can update an existing course run. """
updated_lms_course_id = 'course-v1:testX+AS121+2018_q1'
self.course_run_dict['lms_course_id'] = updated_lms_course_id
self.assertNotEqual(self.course_run.lms_course_id, updated_lms_course_id)
response = self.client.post(
reverse('publisher:publisher_course_runs_edit', kwargs={'pk': self.course_run.id}),
self.course_run_dict
)
self.assertRedirects(
response,
expected_url=reverse('publisher:publisher_course_runs_edit', kwargs={'pk': self.course_run.id}),
status_code=302,
target_status_code=200
)
course_run = CourseRun.objects.get(id=self.course_run.id)
# Assert that course run is updated.
self.assertEqual(course_run.lms_course_id, updated_lms_course_id)
# add new and check the comment on edit page.
comment = CommentFactory(content_object=self.course_run, user=self.user, site=self.site)
response = self.client.get(reverse('publisher:publisher_course_runs_edit', kwargs={'pk': self.course_run.id}))
self.assertContains(response, 'Total Comments 1')
self.assertContains(response, 'Add new comment')
self.assertContains(response, comment.comment)
def test_edit_course_run_page_with_non_staff(self):
""" Verify that non staff user can't access course run edit page without permission. """
non_staff_user, group = create_non_staff_user_and_login(self)
updated_lms_course_id = 'course-v1:testX+AS121+2018_q1'
self.course_run_dict['lms_course_id'] = updated_lms_course_id
self.assertNotEqual(self.course_run.lms_course_id, updated_lms_course_id)
response = self.client.get(
reverse('publisher:publisher_course_runs_edit', kwargs={'pk': self.course_run.id})
)
self.assertEqual(response.status_code, 403)
# assign user a group and assign view permission on that group
non_staff_user.groups.add(group)
assign_perm(Course.VIEW_PERMISSION, group, self.course_run.course)
response = self.client.get(
reverse('publisher:publisher_course_runs_edit', kwargs={'pk': self.course_run.id})
)
self.assertEqual(response.status_code, 200)
def test_update_course_run_with_non_staff(self):
""" Test for course run with non staff user. """
non_staff_user, group = create_non_staff_user_and_login(self)
updated_lms_course_id = 'course-v1:testX+AS121+2018_q1'
self.course_run_dict['lms_course_id'] = updated_lms_course_id
self.assertNotEqual(self.course_run.lms_course_id, updated_lms_course_id)
response = self.client.post(
reverse('publisher:publisher_course_runs_edit', kwargs={'pk': self.course_run.id}),
self.course_run_dict
)
# verify that non staff user can't update course run without permission
self.assertEqual(response.status_code, 403)
# assign user a group and assign view permission on that group
non_staff_user.groups.add(group)
assign_perm(Course.VIEW_PERMISSION, group, self.course_run.course)
response = self.client.post(
reverse('publisher:publisher_course_runs_edit', kwargs={'pk': self.course_run.id}),
self.course_run_dict
......@@ -155,6 +293,7 @@ class SeatsCreateUpdateViewTests(TestCase):
self.user = UserFactory(is_staff=True, is_superuser=True)
self.site = Site.objects.get(pk=settings.SITE_ID)
self.client.login(username=self.user.username, password=USER_PASSWORD)
self.seat_edit_url = reverse('publisher:publisher_seats_edit', kwargs={'pk': self.seat.id})
def test_seat_view_page(self):
""" Verify that we can open new seat page. """
......@@ -177,8 +316,8 @@ class SeatsCreateUpdateViewTests(TestCase):
self.assertEqual(seat.price, seat_price)
def test_update_seat(self):
""" Verify that we can update an existing seat. """
def test_update_seat_with_staff(self):
""" Verify that staff user can update an existing seat. """
self.assertEqual(self.seat.type, Seat.PROFESSIONAL)
updated_seat_price = 470.00
self.seat_dict['price'] = updated_seat_price
......@@ -191,7 +330,7 @@ class SeatsCreateUpdateViewTests(TestCase):
self.assertRedirects(
response,
expected_url=reverse('publisher:publisher_seats_edit', kwargs={'pk': self.seat.id}),
expected_url=self.seat_edit_url,
status_code=302,
target_status_code=200
)
......@@ -202,28 +341,94 @@ class SeatsCreateUpdateViewTests(TestCase):
self.assertEqual(seat.type, Seat.VERIFIED)
self.seat_dict['type'] = Seat.HONOR
response = self.client.post(
reverse('publisher:publisher_seats_edit', kwargs={'pk': self.seat.id}),
self.seat_dict
)
response = self.client.post(self.seat_edit_url, self.seat_dict)
seat = Seat.objects.get(id=self.seat.id)
# Assert that we can change seat type.
self.assertEqual(seat.type, Seat.HONOR)
self.assertRedirects(
response,
expected_url=reverse('publisher:publisher_seats_edit', kwargs={'pk': self.seat.id}),
expected_url=self.seat_edit_url,
status_code=302,
target_status_code=200
)
# add new and check the comment on edit page.
comment = CommentFactory(content_object=self.seat, user=self.user, site=self.site)
response = self.client.get(reverse('publisher:publisher_seats_edit', kwargs={'pk': self.seat.id}))
response = self.client.get(self.seat_edit_url)
self.assertContains(response, 'Total Comments 1')
self.assertContains(response, 'Add new comment')
self.assertContains(response, comment.comment)
def test_edit_seat_page_with_non_staff(self):
""" Verify that non staff user can't access seat edit page without permission. """
non_staff_user, group = create_non_staff_user_and_login(self)
response = self.client.get(reverse('publisher:publisher_seats_edit', kwargs={'pk': self.seat.id}))
self.assertEqual(response.status_code, 403)
# assign user a group and assign view permission on that group
non_staff_user.groups.add(group)
assign_perm(Course.VIEW_PERMISSION, group, self.seat.course_run.course)
response = self.client.get(reverse('publisher:publisher_seats_edit', kwargs={'pk': self.seat.id}))
self.assertEqual(response.status_code, 200)
def test_update_seat_with_non_staff(self):
""" Tests update seat for non staff user. """
non_staff_user, group = create_non_staff_user_and_login(self)
self.assertEqual(self.seat.type, Seat.PROFESSIONAL)
updated_seat_price = 470.00
self.seat_dict['price'] = updated_seat_price
self.seat_dict['type'] = Seat.VERIFIED
self.assertNotEqual(self.seat.price, updated_seat_price)
response = self.client.post(
reverse('publisher:publisher_seats_edit', kwargs={'pk': self.seat.id}),
self.seat_dict
)
# verify that non staff user can't update course seat without permission
self.assertEqual(response.status_code, 403)
# assign user a group and assign view permission on that group
non_staff_user.groups.add(group)
assign_perm(Course.VIEW_PERMISSION, group, self.seat.course_run.course)
response = self.client.post(
reverse('publisher:publisher_seats_edit', kwargs={'pk': self.seat.id}),
self.seat_dict
)
self.assertRedirects(
response,
expected_url=reverse('publisher:publisher_seats_edit', kwargs={'pk': self.seat.id}),
status_code=302,
target_status_code=200
)
seat = Seat.objects.get(id=self.seat.id)
# Assert that seat is updated.
self.assertEqual(seat.price, updated_seat_price)
self.assertEqual(seat.type, Seat.VERIFIED)
self.seat_dict['type'] = Seat.HONOR
response = self.client.post(
reverse('publisher:publisher_seats_edit', kwargs={'pk': self.seat.id}),
self.seat_dict
)
seat = Seat.objects.get(id=self.seat.id)
# Assert that we can change seat type.
self.assertEqual(seat.type, Seat.HONOR)
self.assertRedirects(
response,
expected_url=reverse('publisher:publisher_seats_edit', kwargs={'pk': self.seat.id}),
status_code=302,
target_status_code=200
)
@ddt.ddt
class CourseRunDetailTests(TestCase):
......@@ -232,6 +437,8 @@ class CourseRunDetailTests(TestCase):
def setUp(self):
super(CourseRunDetailTests, self).setUp()
self.course = factories.CourseFactory()
self.user = UserFactory(is_staff=True)
self.client.login(username=self.user.username, password=USER_PASSWORD)
self.course_run = factories.CourseRunFactory(course=self.course)
self._generate_seats([Seat.AUDIT, Seat.HONOR, Seat.VERIFIED, Seat.PROFESSIONAL])
self._generate_credit_seat()
......@@ -239,13 +446,35 @@ class CourseRunDetailTests(TestCase):
self.wrapped_course_run = CourseRunWrapper(self.course_run)
self.date_format = '%b %d, %Y, %H:%M:%S %p'
def test_page_without_data(self):
""" Verify that detail page without any data available for that course-run. """
def test_page_without_data_staff(self):
""" Verify that staff user can access detail page without any data
available for that course-run.
"""
course_run = factories.CourseRunFactory(course=self.course)
page_url = reverse('publisher:publisher_course_run_detail', args=[course_run.id])
response = self.client.get(page_url)
self.assertEqual(response.status_code, 200)
def test_page_with_invalid_id(self):
""" Verify that invalid course run id return 404. """
page_url = reverse('publisher:publisher_course_run_detail', args=[3434])
response = self.client.get(page_url)
self.assertEqual(response.status_code, 404)
def test_details_page_non_staff(self):
""" Verify that non staff user can't access detail page. """
non_staff_user, group = create_non_staff_user_and_login(self)
response = self.client.get(self.page_url)
self.assertEqual(response.status_code, 403)
non_staff_user.groups.add(group)
assign_perm(Course.VIEW_PERMISSION, group, self.course)
response = self.client.get(self.page_url)
self.assertEqual(response.status_code, 200)
def _generate_seats(self, modes):
""" Helper method to add seats for a course-run. """
for mode in modes:
......@@ -255,9 +484,9 @@ class CourseRunDetailTests(TestCase):
""" Helper method to add credit seat for a course-run. """
factories.SeatFactory(type='credit', course_run=self.course_run, credit_provider='ASU', credit_hours=9)
def test_course_run_detail_page(self):
def test_course_run_detail_page_staff(self):
""" Verify that detail page contains all the data for drupal, studio and
cat.
cat with staff user.
"""
response = self.client.get(self.page_url)
self.assertEqual(response.status_code, 200)
......@@ -382,6 +611,16 @@ class CourseRunDetailTests(TestCase):
self.assertContains(response, 'Total Comments 1')
self.assertContains(response, comment.comment)
def test_get_course_return_none(self):
""" Verify that `ViewPermissionMixin.get_course` return none
if `publisher_object` doesn't have `course` attr.
"""
non_staff_user, group = create_non_staff_user_and_login(self)
page_url = reverse('publisher:publisher_course_run_detail', args=[self.course_run.id])
with patch.object(CourseRunDetailView, 'get_object', return_value=non_staff_user):
response = self.client.get(page_url)
self.assertEqual(response.status_code, 403)
class ChangeStateViewTests(TestCase):
""" Tests for the `ChangeStateView`. """
......@@ -389,12 +628,14 @@ class ChangeStateViewTests(TestCase):
def setUp(self):
super(ChangeStateViewTests, self).setUp()
self.course = factories.CourseFactory()
self.user = UserFactory(is_staff=True)
self.client.login(username=self.user.username, password=USER_PASSWORD)
self.course_run = factories.CourseRunFactory(course=self.course)
self.page_url = reverse('publisher:publisher_course_run_detail', args=[self.course_run.id])
self.change_state_url = reverse('publisher:publisher_change_state', args=[self.course_run.id])
def test_detail_page_change_state(self):
""" Verify that we can change workflow state from detail page. """
def test_change_state_with_staff(self):
""" Verify that staff user can change workflow state from detail page. """
response = self.client.get(self.page_url)
self.assertContains(response, 'Status:')
self.assertContains(response, State.DRAFT.title())
......@@ -404,14 +645,34 @@ class ChangeStateViewTests(TestCase):
# assert that state is changed to `NEEDS_REVIEW`
self.assertContains(response, State.NEEDS_REVIEW.title().replace('_', ' '))
def test_detail_page_change_state_not_allowed(self):
""" Verify that we can't change workflow state from `DRAFT` to `PUBLISHED`. """
def test_change_state_not_allowed_with_staff(self):
""" Verify that staff user can't change workflow state from `DRAFT` to `PUBLISHED`. """
response = self.client.get(self.page_url)
self.assertContains(response, 'Status:')
self.assertContains(response, State.DRAFT.title())
# change workflow state from `DRAFT` to `PUBLISHED`
response = self.client.post(self.change_state_url, data={'state': State.PUBLISHED}, follow=True)
# assert that state is not changed to `PUBLISHED`
self.assertNotContains(response, State.PUBLISHED.title())
self.assertContains(response, 'There was an error in changing state.')
def test_change_state_with_no_staff(self):
""" Tests change state for non staff user. """
non_staff_user, group = create_non_staff_user_and_login(self)
response = self.client.post(self.change_state_url, data={'state': State.NEEDS_REVIEW}, follow=True)
# verify that non staff user can't change workflow state without permission
self.assertEqual(response.status_code, 403)
# assign user a group and assign view permission on that group
non_staff_user.groups.add(group)
assign_perm(Course.VIEW_PERMISSION, group, self.course)
response = self.client.get(self.page_url)
self.assertContains(response, 'Status:')
self.assertContains(response, State.DRAFT.title())
# change workflow state from `DRAFT` to `NEEDS_REVIEW`
response = self.client.post(self.change_state_url, data={'state': State.NEEDS_REVIEW}, follow=True)
# assert that state is changed to `NEEDS_REVIEW`
self.assertContains(response, State.NEEDS_REVIEW.title().replace('_', ' '))
from course_discovery.apps.core.tests.factories import UserFactory, USER_PASSWORD
from course_discovery.apps.publisher.tests import factories
def create_non_staff_user_and_login(test_class):
""" Create non staff user and login and return user and group. """
non_staff_user = UserFactory()
group = factories.GroupFactory()
test_class.client.logout()
test_class.client.login(username=non_staff_user.username, password=USER_PASSWORD)
return non_staff_user, group
......@@ -3,14 +3,17 @@ Course publisher views.
"""
from django.contrib import messages
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
from django.http import HttpResponseRedirect, HttpResponseForbidden
from django.utils.translation import ugettext_lazy as _
from django.views.generic import View
from django.views.generic.detail import DetailView
from django.views.generic.edit import CreateView, UpdateView
from django.views.generic.list import ListView
from django_fsm import TransitionNotAllowed
from guardian.shortcuts import get_objects_for_user
from course_discovery.apps.publisher.forms import CourseForm, CourseRunForm, SeatForm
from course_discovery.apps.publisher.mixins import ViewPermissionMixin, check_view_permission
from course_discovery.apps.publisher.models import Course, CourseRun, Seat
from course_discovery.apps.publisher.wrappers import CourseRunWrapper
......@@ -23,12 +26,16 @@ class CourseRunListView(ListView):
template_name = 'publisher/course_runs_list.html'
def get_queryset(self):
return [
CourseRunWrapper(course_run) for course_run in CourseRun.objects.select_related('course').all()
]
if self.request.user.is_staff:
course_runs = CourseRun.objects.select_related('course').all()
else:
courses = get_objects_for_user(self.request.user, Course.VIEW_PERMISSION, Course)
course_runs = CourseRun.objects.filter(course__in=courses).select_related('course').all()
return [CourseRunWrapper(course_run) for course_run in course_runs]
class CourseRunDetailView(DetailView):
class CourseRunDetailView(ViewPermissionMixin, DetailView):
""" Course Run Detail View."""
model = CourseRun
template_name = 'publisher/course_run_detail.html'
......@@ -57,10 +64,11 @@ class CreateCourseView(CreateView):
return reverse(self.success_url, kwargs={'pk': self.object.id})
class UpdateCourseView(UpdateView):
class UpdateCourseView(ViewPermissionMixin, UpdateView):
""" Update Course View."""
model = Course
form_class = CourseForm
permission_required = Course.VIEW_PERMISSION
template_name = 'publisher/course_form.html'
success_url = 'publisher:publisher_courses_edit'
......@@ -92,10 +100,11 @@ class CreateCourseRunView(CreateView):
return reverse(self.success_url, kwargs={'pk': self.object.id})
class UpdateCourseRunView(UpdateView):
class UpdateCourseRunView(ViewPermissionMixin, UpdateView):
""" Update Course Run View."""
model = CourseRun
form_class = CourseRunForm
permission_required = Course.VIEW_PERMISSION
template_name = 'publisher/course_run_form.html'
success_url = 'publisher:publisher_course_runs_edit'
......@@ -136,10 +145,11 @@ class CreateSeatView(CreateView):
return reverse(self.success_url, kwargs={'pk': self.object.id})
class UpdateSeatView(UpdateView):
class UpdateSeatView(ViewPermissionMixin, UpdateView):
""" Update Seat View."""
model = Seat
form_class = SeatForm
permission_required = Course.VIEW_PERMISSION
template_name = 'publisher/seat_form.html'
success_url = 'publisher:publisher_seats_edit'
......@@ -164,11 +174,16 @@ class ChangeStateView(View):
state = request.POST.get('state')
try:
course_run = CourseRun.objects.get(id=course_run_id)
if not check_view_permission(request.user, course_run.course):
return HttpResponseForbidden()
course_run.change_state(target=state)
# pylint: disable=no-member
messages.success(
request, 'Content moved to `{state}` successfully.'.format(state=course_run.current_state),
request, _('Content moved to `{state}` successfully.').format(state=course_run.current_state)
)
return HttpResponseRedirect(reverse('publisher:publisher_course_run_detail', kwargs={'pk': course_run_id}))
except (CourseRun.DoesNotExist, TransitionNotAllowed):
messages.error(request, 'There was an error in changing state.')
messages.error(request, _('There was an error in changing state.'))
return HttpResponseRedirect(reverse('publisher:publisher_course_run_detail', kwargs={'pk': course_run_id}))
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment