Commit a8084628 by chrisndodge

Merge pull request #1956 from edx/feature/cdodge/autoprovision-forums-master

Feature/cdodge/autoprovision forums master
parents 257dae63 e6a1a9fa
...@@ -34,6 +34,8 @@ from xmodule.course_module import CourseDescriptor ...@@ -34,6 +34,8 @@ from xmodule.course_module import CourseDescriptor
from xmodule.seq_module import SequenceDescriptor from xmodule.seq_module import SequenceDescriptor
from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.modulestore.exceptions import ItemNotFoundError
from django_comment_common.utils import are_permissions_roles_seeded
TEST_DATA_MODULESTORE = copy.deepcopy(settings.MODULESTORE) TEST_DATA_MODULESTORE = copy.deepcopy(settings.MODULESTORE)
TEST_DATA_MODULESTORE['default']['OPTIONS']['fs_root'] = path('common/test/data') TEST_DATA_MODULESTORE['default']['OPTIONS']['fs_root'] = path('common/test/data')
TEST_DATA_MODULESTORE['direct']['OPTIONS']['fs_root'] = path('common/test/data') TEST_DATA_MODULESTORE['direct']['OPTIONS']['fs_root'] = path('common/test/data')
...@@ -45,7 +47,7 @@ class MongoCollectionFindWrapper(object): ...@@ -45,7 +47,7 @@ class MongoCollectionFindWrapper(object):
self.counter = 0 self.counter = 0
def find(self, query, *args, **kwargs): def find(self, query, *args, **kwargs):
self.counter = self.counter+1 self.counter = self.counter + 1
return self.original(query, *args, **kwargs) return self.original(query, *args, **kwargs)
...@@ -352,7 +354,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): ...@@ -352,7 +354,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
clone_items = module_store.get_items(Location(['i4x', 'MITx', '999', 'vertical', None])) clone_items = module_store.get_items(Location(['i4x', 'MITx', '999', 'vertical', None]))
self.assertGreater(len(clone_items), 0) self.assertGreater(len(clone_items), 0)
for descriptor in items: for descriptor in items:
new_loc = descriptor.location._replace(org='MITx', course='999') new_loc = descriptor.location.replace(org='MITx', course='999')
print "Checking {0} should now also be at {1}".format(descriptor.location.url(), new_loc.url()) print "Checking {0} should now also be at {1}".format(descriptor.location.url(), new_loc.url())
resp = self.client.get(reverse('edit_unit', kwargs={'location': new_loc.url()})) resp = self.client.get(reverse('edit_unit', kwargs={'location': new_loc.url()}))
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
...@@ -375,15 +377,15 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): ...@@ -375,15 +377,15 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
self.assertEqual(len(items), 0) self.assertEqual(len(items), 0)
def verify_content_existence(self, modulestore, root_dir, location, dirname, category_name, filename_suffix=''): def verify_content_existence(self, modulestore, root_dir, location, dirname, category_name, filename_suffix=''):
fs = OSFS(root_dir / 'test_export') filesystem = OSFS(root_dir / 'test_export')
self.assertTrue(fs.exists(dirname)) self.assertTrue(filesystem.exists(dirname))
query_loc = Location('i4x', location.org, location.course, category_name, None) query_loc = Location('i4x', location.org, location.course, category_name, None)
items = modulestore.get_items(query_loc) items = modulestore.get_items(query_loc)
for item in items: for item in items:
fs = OSFS(root_dir / ('test_export/' + dirname)) filesystem = OSFS(root_dir / ('test_export/' + dirname))
self.assertTrue(fs.exists(item.location.name + filename_suffix)) self.assertTrue(filesystem.exists(item.location.name + filename_suffix))
def test_export_course(self): def test_export_course(self):
module_store = modulestore('direct') module_store = modulestore('direct')
...@@ -415,7 +417,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): ...@@ -415,7 +417,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
# add private to list of children # add private to list of children
sequential = module_store.get_item(Location(['i4x', 'edX', 'full', sequential = module_store.get_item(Location(['i4x', 'edX', 'full',
'sequential', 'Administrivia_and_Circuit_Elements', None])) 'sequential', 'Administrivia_and_Circuit_Elements', None]))
private_location_no_draft = private_vertical.location._replace(revision=None) private_location_no_draft = private_vertical.location.replace(revision=None)
module_store.update_children(sequential.location, sequential.children + module_store.update_children(sequential.location, sequential.children +
[private_location_no_draft.url()]) [private_location_no_draft.url()])
...@@ -440,20 +442,20 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): ...@@ -440,20 +442,20 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
self.verify_content_existence(module_store, root_dir, location, 'custom_tags', 'custom_tag_template') self.verify_content_existence(module_store, root_dir, location, 'custom_tags', 'custom_tag_template')
# check for graiding_policy.json # check for graiding_policy.json
fs = OSFS(root_dir / 'test_export/policies/6.002_Spring_2012') filesystem = OSFS(root_dir / 'test_export/policies/6.002_Spring_2012')
self.assertTrue(fs.exists('grading_policy.json')) self.assertTrue(filesystem.exists('grading_policy.json'))
course = module_store.get_item(location) course = module_store.get_item(location)
# compare what's on disk compared to what we have in our course # compare what's on disk compared to what we have in our course
with fs.open('grading_policy.json', 'r') as grading_policy: with filesystem.open('grading_policy.json', 'r') as grading_policy:
on_disk = loads(grading_policy.read()) on_disk = loads(grading_policy.read())
self.assertEqual(on_disk, course.grading_policy) self.assertEqual(on_disk, course.grading_policy)
#check for policy.json #check for policy.json
self.assertTrue(fs.exists('policy.json')) self.assertTrue(filesystem.exists('policy.json'))
# compare what's on disk to what we have in the course module # compare what's on disk to what we have in the course module
with fs.open('policy.json', 'r') as course_policy: with filesystem.open('policy.json', 'r') as course_policy:
on_disk = loads(course_policy.read()) on_disk = loads(course_policy.read())
self.assertIn('course/6.002_Spring_2012', on_disk) self.assertIn('course/6.002_Spring_2012', on_disk)
self.assertEqual(on_disk['course/6.002_Spring_2012'], own_metadata(course)) self.assertEqual(on_disk['course/6.002_Spring_2012'], own_metadata(course))
...@@ -608,6 +610,14 @@ class ContentStoreTest(ModuleStoreTestCase): ...@@ -608,6 +610,14 @@ class ContentStoreTest(ModuleStoreTestCase):
data = parse_json(resp) data = parse_json(resp)
self.assertEqual(data['id'], 'i4x://MITx/999/course/Robot_Super_Course') self.assertEqual(data['id'], 'i4x://MITx/999/course/Robot_Super_Course')
def test_create_course_check_forum_seeding(self):
"""Test new course creation and verify forum seeding """
resp = self.client.post(reverse('create_new_course'), self.course_data)
self.assertEqual(resp.status_code, 200)
data = parse_json(resp)
self.assertEqual(data['id'], 'i4x://MITx/999/course/Robot_Super_Course')
self.assertTrue(are_permissions_roles_seeded('MITx/999/Robot_Super_Course'))
def test_create_course_duplicate_course(self): def test_create_course_duplicate_course(self):
"""Test new course creation - error path""" """Test new course creation - error path"""
resp = self.client.post(reverse('create_new_course'), self.course_data) resp = self.client.post(reverse('create_new_course'), self.course_data)
...@@ -801,37 +811,37 @@ class ContentStoreTest(ModuleStoreTestCase): ...@@ -801,37 +811,37 @@ class ContentStoreTest(ModuleStoreTestCase):
self.assertEqual(200, resp.status_code) self.assertEqual(200, resp.status_code)
# go look at a subsection page # go look at a subsection page
subsection_location = loc._replace(category='sequential', name='test_sequence') subsection_location = loc.replace(category='sequential', name='test_sequence')
resp = self.client.get(reverse('edit_subsection', resp = self.client.get(reverse('edit_subsection',
kwargs={'location': subsection_location.url()})) kwargs={'location': subsection_location.url()}))
self.assertEqual(200, resp.status_code) self.assertEqual(200, resp.status_code)
# go look at the Edit page # go look at the Edit page
unit_location = loc._replace(category='vertical', name='test_vertical') unit_location = loc.replace(category='vertical', name='test_vertical')
resp = self.client.get(reverse('edit_unit', resp = self.client.get(reverse('edit_unit',
kwargs={'location': unit_location.url()})) kwargs={'location': unit_location.url()}))
self.assertEqual(200, resp.status_code) self.assertEqual(200, resp.status_code)
# delete a component # delete a component
del_loc = loc._replace(category='html', name='test_html') del_loc = loc.replace(category='html', name='test_html')
resp = self.client.post(reverse('delete_item'), resp = self.client.post(reverse('delete_item'),
json.dumps({'id': del_loc.url()}), "application/json") json.dumps({'id': del_loc.url()}), "application/json")
self.assertEqual(200, resp.status_code) self.assertEqual(200, resp.status_code)
# delete a unit # delete a unit
del_loc = loc._replace(category='vertical', name='test_vertical') del_loc = loc.replace(category='vertical', name='test_vertical')
resp = self.client.post(reverse('delete_item'), resp = self.client.post(reverse('delete_item'),
json.dumps({'id': del_loc.url()}), "application/json") json.dumps({'id': del_loc.url()}), "application/json")
self.assertEqual(200, resp.status_code) self.assertEqual(200, resp.status_code)
# delete a unit # delete a unit
del_loc = loc._replace(category='sequential', name='test_sequence') del_loc = loc.replace(category='sequential', name='test_sequence')
resp = self.client.post(reverse('delete_item'), resp = self.client.post(reverse('delete_item'),
json.dumps({'id': del_loc.url()}), "application/json") json.dumps({'id': del_loc.url()}), "application/json")
self.assertEqual(200, resp.status_code) self.assertEqual(200, resp.status_code)
# delete a chapter # delete a chapter
del_loc = loc._replace(category='chapter', name='chapter_2') del_loc = loc.replace(category='chapter', name='chapter_2')
resp = self.client.post(reverse('delete_item'), resp = self.client.post(reverse('delete_item'),
json.dumps({'id': del_loc.url()}), "application/json") json.dumps({'id': del_loc.url()}), "application/json")
self.assertEqual(200, resp.status_code) self.assertEqual(200, resp.status_code)
......
...@@ -13,17 +13,13 @@ from django.core.urlresolvers import reverse ...@@ -13,17 +13,13 @@ from django.core.urlresolvers import reverse
from mitxmako.shortcuts import render_to_response from mitxmako.shortcuts import render_to_response
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions \
import ItemNotFoundError, InvalidLocationError from xmodule.modulestore.exceptions import ItemNotFoundError, InvalidLocationError
from xmodule.modulestore import Location from xmodule.modulestore import Location
from contentstore.course_info_model \ from contentstore.course_info_model import get_course_updates, update_course_updates, delete_course_update
import get_course_updates, update_course_updates, delete_course_update from contentstore.utils import get_lms_link_for_item, add_extra_panel_tab, remove_extra_panel_tab
from contentstore.utils \ from models.settings.course_details import CourseDetails, CourseSettingsEncoder
import get_lms_link_for_item, add_extra_panel_tab, \
remove_extra_panel_tab
from models.settings.course_details \
import CourseDetails, CourseSettingsEncoder
from models.settings.course_grading import CourseGradingModel from models.settings.course_grading import CourseGradingModel
from models.settings.course_metadata import CourseMetadata from models.settings.course_metadata import CourseMetadata
from auth.authz import create_all_course_groups from auth.authz import create_all_course_groups
...@@ -35,6 +31,10 @@ from .tabs import initialize_course_tabs ...@@ -35,6 +31,10 @@ from .tabs import initialize_course_tabs
from .component import OPEN_ENDED_COMPONENT_TYPES, \ from .component import OPEN_ENDED_COMPONENT_TYPES, \
NOTE_COMPONENT_TYPES, ADVANCED_COMPONENT_POLICY_KEY NOTE_COMPONENT_TYPES, ADVANCED_COMPONENT_POLICY_KEY
from django_comment_common.utils import seed_permissions_roles
# TODO: should explicitly enumerate exports with __all__
__all__ = ['course_index', 'create_new_course', 'course_info', __all__ = ['course_index', 'create_new_course', 'course_info',
'course_info_updates', 'get_course_settings', 'course_info_updates', 'get_course_settings',
'course_config_graders_page', 'course_config_graders_page',
...@@ -136,6 +136,9 @@ def create_new_course(request): ...@@ -136,6 +136,9 @@ def create_new_course(request):
create_all_course_groups(request.user, new_course.location) create_all_course_groups(request.user, new_course.location)
# seed the forums
seed_permissions_roles(new_course.location.course_id)
return HttpResponse(json.dumps({'id': new_course.location.url()})) return HttpResponse(json.dumps({'id': new_course.location.url()}))
......
...@@ -322,6 +322,9 @@ INSTALLED_APPS = ( ...@@ -322,6 +322,9 @@ INSTALLED_APPS = (
'pipeline', 'pipeline',
'staticfiles', 'staticfiles',
'static_replace', 'static_replace',
# comment common
'django_comment_common',
) )
################# EDX MARKETING SITE ################################## ################# EDX MARKETING SITE ##################################
......
...@@ -127,8 +127,7 @@ CELERY_ALWAYS_EAGER = True ...@@ -127,8 +127,7 @@ CELERY_ALWAYS_EAGER = True
################################ DEBUG TOOLBAR ################################# ################################ DEBUG TOOLBAR #################################
INSTALLED_APPS += ('debug_toolbar', 'debug_toolbar_mongo') INSTALLED_APPS += ('debug_toolbar', 'debug_toolbar_mongo')
MIDDLEWARE_CLASSES += ('django_comment_client.utils.QueryCountDebugMiddleware', MIDDLEWARE_CLASSES += ('debug_toolbar.middleware.DebugToolbarMiddleware',)
'debug_toolbar.middleware.DebugToolbarMiddleware',)
INTERNAL_IPS = ('127.0.0.1',) INTERNAL_IPS = ('127.0.0.1',)
DEBUG_TOOLBAR_PANELS = ( DEBUG_TOOLBAR_PANELS = (
......
# -*- coding: utf-8 -*-
from south.v2 import SchemaMigration
class Migration(SchemaMigration):
#
# cdodge: This is basically an empty migration since everything has - up to now - managed in the django_comment_client app
# But going forward we should be using this migration
#
def forwards(self, orm):
pass
def backwards(self, orm):
pass
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}),
'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'blank': 'True'}),
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
'display_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
'email_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}),
'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}),
'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'django_comment_common.permission': {
'Meta': {'object_name': 'Permission'},
'name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'primary_key': 'True'}),
'roles': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'permissions'", 'symmetrical': 'False', 'to': "orm['django_comment_common.Role']"})
},
'django_comment_common.role': {
'Meta': {'object_name': 'Role'},
'course_id': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '30'}),
'users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'roles'", 'symmetrical': 'False', 'to': "orm['auth.User']"})
}
}
complete_apps = ['django_comment_common']
import logging
from django.db import models
from django.contrib.auth.models import User
from django.dispatch import receiver
from django.db.models.signals import post_save
from student.models import CourseEnrollment
from xmodule.modulestore.django import modulestore
from xmodule.course_module import CourseDescriptor
FORUM_ROLE_ADMINISTRATOR = 'Administrator'
FORUM_ROLE_MODERATOR = 'Moderator'
FORUM_ROLE_COMMUNITY_TA = 'Community TA'
FORUM_ROLE_STUDENT = 'Student'
@receiver(post_save, sender=CourseEnrollment)
def assign_default_role(sender, instance, **kwargs):
if instance.user.is_staff:
role = Role.objects.get_or_create(course_id=instance.course_id, name="Moderator")[0]
else:
role = Role.objects.get_or_create(course_id=instance.course_id, name="Student")[0]
logging.info("assign_default_role: adding %s as %s" % (instance.user, role))
instance.user.roles.add(role)
class Role(models.Model):
name = models.CharField(max_length=30, null=False, blank=False)
users = models.ManyToManyField(User, related_name="roles")
course_id = models.CharField(max_length=255, blank=True, db_index=True)
class Meta:
# use existing table that was originally created from django_comment_client app
db_table = 'django_comment_client_role'
def __unicode__(self):
return self.name + " for " + (self.course_id if self.course_id else "all courses")
def inherit_permissions(self, role): # TODO the name of this method is a little bit confusing,
# since it's one-off and doesn't handle inheritance later
if role.course_id and role.course_id != self.course_id:
logging.warning("%s cannot inherit permissions from %s due to course_id inconsistency", \
self, role)
for per in role.permissions.all():
self.add_permission(per)
def add_permission(self, permission):
self.permissions.add(Permission.objects.get_or_create(name=permission)[0])
def has_permission(self, permission):
course_loc = CourseDescriptor.id_to_location(self.course_id)
course = modulestore().get_instance(self.course_id, course_loc)
if self.name == FORUM_ROLE_STUDENT and \
(permission.startswith('edit') or permission.startswith('update') or permission.startswith('create')) and \
(not course.forum_posts_allowed):
return False
return self.permissions.filter(name=permission).exists()
class Permission(models.Model):
name = models.CharField(max_length=30, null=False, blank=False, primary_key=True)
roles = models.ManyToManyField(Role, related_name="permissions")
class Meta:
# use existing table that was originally created from django_comment_client app
db_table = 'django_comment_client_permission'
def __unicode__(self):
return self.name
from django_comment_common.models import Role
_STUDENT_ROLE_PERMISSIONS = ["vote", "update_thread", "follow_thread", "unfollow_thread",
"update_comment", "create_sub_comment", "unvote", "create_thread",
"follow_commentable", "unfollow_commentable", "create_comment", ]
_MODERATOR_ROLE_PERMISSIONS = ["edit_content", "delete_thread", "openclose_thread",
"endorse_comment", "delete_comment", "see_all_cohorts"]
_ADMINISTRATOR_ROLE_PERMISSIONS = ["manage_moderator"]
def seed_permissions_roles(course_id):
administrator_role = Role.objects.get_or_create(name="Administrator", course_id=course_id)[0]
moderator_role = Role.objects.get_or_create(name="Moderator", course_id=course_id)[0]
community_ta_role = Role.objects.get_or_create(name="Community TA", course_id=course_id)[0]
student_role = Role.objects.get_or_create(name="Student", course_id=course_id)[0]
for per in _STUDENT_ROLE_PERMISSIONS:
student_role.add_permission(per)
for per in _MODERATOR_ROLE_PERMISSIONS:
moderator_role.add_permission(per)
for per in _ADMINISTRATOR_ROLE_PERMISSIONS:
administrator_role.add_permission(per)
moderator_role.inherit_permissions(student_role)
# For now, Community TA == Moderator, except for the styling.
community_ta_role.inherit_permissions(moderator_role)
administrator_role.inherit_permissions(moderator_role)
def are_permissions_roles_seeded(course_id):
try:
administrator_role = Role.objects.get(name="Administrator", course_id=course_id)
moderator_role = Role.objects.get(name="Moderator", course_id=course_id)
student_role = Role.objects.get(name="Student", course_id=course_id)
except:
return False
for per in _STUDENT_ROLE_PERMISSIONS:
if not student_role.has_permission(per):
return False
for per in _MODERATOR_ROLE_PERMISSIONS + _STUDENT_ROLE_PERMISSIONS:
if not moderator_role.has_permission(per):
return False
for per in _ADMINISTRATOR_ROLE_PERMISSIONS + _MODERATOR_ROLE_PERMISSIONS + _STUDENT_ROLE_PERMISSIONS:
if not administrator_role.has_permission(per):
return False
return True
...@@ -9,7 +9,7 @@ import re ...@@ -9,7 +9,7 @@ import re
from collections import namedtuple from collections import namedtuple
from .exceptions import InvalidLocationError, InsufficientSpecificationError from .exceptions import InvalidLocationError, InsufficientSpecificationError
from xmodule.errortracker import ErrorLog, make_error_tracker from xmodule.errortracker import make_error_tracker
from bson.son import SON from bson.son import SON
log = logging.getLogger('mitx.' + 'modulestore') log = logging.getLogger('mitx.' + 'modulestore')
...@@ -64,7 +64,6 @@ class Location(_LocationBase): ...@@ -64,7 +64,6 @@ class Location(_LocationBase):
""" """
return re.sub('_+', '_', invalid.sub('_', value)) return re.sub('_+', '_', invalid.sub('_', value))
@staticmethod @staticmethod
def clean(value): def clean(value):
""" """
...@@ -72,7 +71,6 @@ class Location(_LocationBase): ...@@ -72,7 +71,6 @@ class Location(_LocationBase):
""" """
return Location._clean(value, INVALID_CHARS) return Location._clean(value, INVALID_CHARS)
@staticmethod @staticmethod
def clean_keeping_underscores(value): def clean_keeping_underscores(value):
""" """
...@@ -82,7 +80,6 @@ class Location(_LocationBase): ...@@ -82,7 +80,6 @@ class Location(_LocationBase):
""" """
return INVALID_CHARS.sub('_', value) return INVALID_CHARS.sub('_', value)
@staticmethod @staticmethod
def clean_for_url_name(value): def clean_for_url_name(value):
""" """
...@@ -154,9 +151,7 @@ class Location(_LocationBase): ...@@ -154,9 +151,7 @@ class Location(_LocationBase):
to mean wildcard selection. to mean wildcard selection.
""" """
if (org is None and course is None and category is None and name is None and revision is None):
if (org is None and course is None and category is None and
name is None and revision is None):
location = loc_or_tag location = loc_or_tag
else: else:
location = (loc_or_tag, org, course, category, name, revision) location = (loc_or_tag, org, course, category, name, revision)
...@@ -191,7 +186,7 @@ class Location(_LocationBase): ...@@ -191,7 +186,7 @@ class Location(_LocationBase):
match = MISSING_SLASH_URL_RE.match(location) match = MISSING_SLASH_URL_RE.match(location)
if match is None: if match is None:
log.debug('location is instance of %s but no URL match' % basestring) log.debug('location is instance of %s but no URL match' % basestring)
raise InvalidLocationError(location) raise InvalidLocationError(location)
groups = match.groupdict() groups = match.groupdict()
check_dict(groups) check_dict(groups)
return _LocationBase.__new__(_cls, **groups) return _LocationBase.__new__(_cls, **groups)
...@@ -233,7 +228,7 @@ class Location(_LocationBase): ...@@ -233,7 +228,7 @@ class Location(_LocationBase):
html id attributes html id attributes
""" """
s = "-".join(str(v) for v in self.list() s = "-".join(str(v) for v in self.list()
if v is not None) if v is not None)
return Location.clean_for_html(s) return Location.clean_for_html(s)
def dict(self): def dict(self):
...@@ -258,6 +253,12 @@ class Location(_LocationBase): ...@@ -258,6 +253,12 @@ class Location(_LocationBase):
at the location URL hierachy""" at the location URL hierachy"""
return "/".join([self.org, self.course, self.name]) return "/".join([self.org, self.course, self.name])
def replace(self, **kwargs):
'''
Expose a public method for replacing location elements
'''
return self._replace(**kwargs)
class ModuleStore(object): class ModuleStore(object):
""" """
...@@ -382,12 +383,6 @@ class ModuleStore(object): ...@@ -382,12 +383,6 @@ class ModuleStore(object):
''' '''
raise NotImplementedError raise NotImplementedError
def get_course(self, course_id):
'''
Look for a specific course id. Returns the course descriptor, or None if not found.
'''
raise NotImplementedError
def get_parent_locations(self, location, course_id): def get_parent_locations(self, location, course_id):
'''Find all locations that are the parents of this location in this '''Find all locations that are the parents of this location in this
course. Needed for path_to_location(). course. Needed for path_to_location().
...@@ -406,8 +401,7 @@ class ModuleStore(object): ...@@ -406,8 +401,7 @@ class ModuleStore(object):
courses = [ courses = [
course course
for course in self.get_courses() for course in self.get_courses()
if course.location.org == location.org if course.location.org == location.org and course.location.course == location.course
and course.location.course == location.course
] ]
return courses return courses
......
...@@ -13,11 +13,12 @@ def as_draft(location): ...@@ -13,11 +13,12 @@ def as_draft(location):
""" """
return Location(location)._replace(revision=DRAFT) return Location(location)._replace(revision=DRAFT)
def as_published(location): def as_published(location):
""" """
Returns the Location that is the published version for `location` Returns the Location that is the published version for `location`
""" """
return Location(location)._replace(revision=None) return Location(location)._replace(revision=None)
def wrap_draft(item): def wrap_draft(item):
......
...@@ -3,7 +3,6 @@ from time import gmtime ...@@ -3,7 +3,6 @@ from time import gmtime
from uuid import uuid4 from uuid import uuid4
from xmodule.modulestore import Location from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.timeparse import stringify_time
from xmodule.modulestore.inheritance import own_metadata from xmodule.modulestore.inheritance import own_metadata
......
...@@ -26,7 +26,7 @@ from course_groups.cohorts import get_cohort_id, is_commentable_cohorted ...@@ -26,7 +26,7 @@ from course_groups.cohorts import get_cohort_id, is_commentable_cohorted
from django_comment_client.utils import JsonResponse, JsonError, extract, get_courseware_context from django_comment_client.utils import JsonResponse, JsonError, extract, get_courseware_context
from django_comment_client.permissions import check_permissions_by_view, cached_has_permission from django_comment_client.permissions import check_permissions_by_view, cached_has_permission
from django_comment_client.models import Role from django_comment_common.models import Role
from courseware.access import has_access from courseware.access import has_access
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
......
from optparse import make_option from optparse import make_option
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from django_comment_client.models import Role from django_comment_common.models import Role
from django.contrib.auth.models import User from django.contrib.auth.models import User
......
...@@ -7,7 +7,7 @@ Enrollments. ...@@ -7,7 +7,7 @@ Enrollments.
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from student.models import CourseEnrollment from student.models import CourseEnrollment
from django_comment_client.models import assign_default_role from django_comment_common.models import assign_default_role
class Command(BaseCommand): class Command(BaseCommand):
......
...@@ -7,7 +7,7 @@ Enrollments. ...@@ -7,7 +7,7 @@ Enrollments.
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from student.models import CourseEnrollment from student.models import CourseEnrollment
from django_comment_client.models import assign_default_role from django_comment_common.models import assign_default_role
class Command(BaseCommand): class Command(BaseCommand):
......
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from django_comment_client.models import Role from django_comment_common.utils import seed_permissions_roles
class Command(BaseCommand): class Command(BaseCommand):
...@@ -13,26 +13,4 @@ class Command(BaseCommand): ...@@ -13,26 +13,4 @@ class Command(BaseCommand):
raise CommandError("Too many arguments") raise CommandError("Too many arguments")
course_id = args[0] course_id = args[0]
administrator_role = Role.objects.get_or_create(name="Administrator", course_id=course_id)[0] seed_permissions_roles(course_id)
moderator_role = Role.objects.get_or_create(name="Moderator", course_id=course_id)[0]
community_ta_role = Role.objects.get_or_create(name="Community TA", course_id=course_id)[0]
student_role = Role.objects.get_or_create(name="Student", course_id=course_id)[0]
for per in ["vote", "update_thread", "follow_thread", "unfollow_thread",
"update_comment", "create_sub_comment", "unvote", "create_thread",
"follow_commentable", "unfollow_commentable", "create_comment", ]:
student_role.add_permission(per)
for per in ["edit_content", "delete_thread", "openclose_thread",
"endorse_comment", "delete_comment", "see_all_cohorts"]:
moderator_role.add_permission(per)
for per in ["manage_moderator"]:
administrator_role.add_permission(per)
moderator_role.inherit_permissions(student_role)
# For now, Community TA == Moderator, except for the styling.
community_ta_role.inherit_permissions(moderator_role)
administrator_role.inherit_permissions(moderator_role)
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from django_comment_common.models import Permission, Role
from django.contrib.auth.models import User from django.contrib.auth.models import User
......
import logging # This file is intentionally blank. It has been moved to common/djangoapps/django_comment_common
from django.db import models
from django.contrib.auth.models import User
from django.dispatch import receiver
from django.db.models.signals import post_save
from student.models import CourseEnrollment
from courseware.courses import get_course_by_id
FORUM_ROLE_ADMINISTRATOR = 'Administrator'
FORUM_ROLE_MODERATOR = 'Moderator'
FORUM_ROLE_COMMUNITY_TA = 'Community TA'
FORUM_ROLE_STUDENT = 'Student'
@receiver(post_save, sender=CourseEnrollment)
def assign_default_role(sender, instance, **kwargs):
if instance.user.is_staff:
role = Role.objects.get_or_create(course_id=instance.course_id, name="Moderator")[0]
else:
role = Role.objects.get_or_create(course_id=instance.course_id, name="Student")[0]
logging.info("assign_default_role: adding %s as %s" % (instance.user, role))
instance.user.roles.add(role)
class Role(models.Model):
name = models.CharField(max_length=30, null=False, blank=False)
users = models.ManyToManyField(User, related_name="roles")
course_id = models.CharField(max_length=255, blank=True, db_index=True)
def __unicode__(self):
return self.name + " for " + (self.course_id if self.course_id else "all courses")
def inherit_permissions(self, role): # TODO the name of this method is a little bit confusing,
# since it's one-off and doesn't handle inheritance later
if role.course_id and role.course_id != self.course_id:
logging.warning("%s cannot inherit permissions from %s due to course_id inconsistency",
self, role)
for per in role.permissions.all():
self.add_permission(per)
def add_permission(self, permission):
self.permissions.add(Permission.objects.get_or_create(name=permission)[0])
def has_permission(self, permission):
course = get_course_by_id(self.course_id)
if self.name == FORUM_ROLE_STUDENT and \
(permission.startswith('edit') or permission.startswith('update') or permission.startswith('create')) and \
(not course.forum_posts_allowed):
return False
return self.permissions.filter(name=permission).exists()
class Permission(models.Model):
name = models.CharField(max_length=30, null=False, blank=False, primary_key=True)
roles = models.ManyToManyField(Role, related_name="permissions")
def __unicode__(self):
return self.name
from .models import Role, Permission from django_comment_common.models import Role, Permission
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.dispatch import receiver from django.dispatch import receiver
from student.models import CourseEnrollment from student.models import CourseEnrollment
......
...@@ -6,7 +6,7 @@ from django.test import TestCase ...@@ -6,7 +6,7 @@ from django.test import TestCase
from student.models import CourseEnrollment from student.models import CourseEnrollment
from django_comment_client.permissions import has_permission from django_comment_client.permissions import has_permission
from django_comment_client.models import Role from django_comment_common.models import Role
class PermissionsTestCase(TestCase): class PermissionsTestCase(TestCase):
......
from factory import DjangoModelFactory from factory import DjangoModelFactory
from django_comment_client.models import Role, Permission from django_comment_common.models import Role, Permission
class RoleFactory(DjangoModelFactory): class RoleFactory(DjangoModelFactory):
......
import django_comment_client.models as models import django_comment_common.models as models
import django_comment_client.permissions as permissions import django_comment_client.permissions as permissions
from django.test import TestCase from django.test import TestCase
......
from django.test import TestCase from django.test import TestCase
from student.tests.factories import UserFactory, CourseEnrollmentFactory from student.tests.factories import UserFactory, CourseEnrollmentFactory
from django_comment_common.models import Role, Permission
from factories import RoleFactory from factories import RoleFactory
import django_comment_client.utils as utils import django_comment_client.utils as utils
......
...@@ -14,7 +14,7 @@ from django.core.urlresolvers import reverse ...@@ -14,7 +14,7 @@ from django.core.urlresolvers import reverse
from django.db import connection from django.db import connection
from django.http import HttpResponse from django.http import HttpResponse
from django.utils import simplejson from django.utils import simplejson
from django_comment_client.models import Role from django_comment_common.models import Role
from django_comment_client.permissions import check_permissions_by_view from django_comment_client.permissions import check_permissions_by_view
from xmodule.modulestore.exceptions import NoPathToItem from xmodule.modulestore.exceptions import NoPathToItem
......
...@@ -9,7 +9,7 @@ from django.test.utils import override_settings ...@@ -9,7 +9,7 @@ from django.test.utils import override_settings
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django_comment_client.models import Role, FORUM_ROLE_ADMINISTRATOR, \ from django_comment_common.models import Role, FORUM_ROLE_ADMINISTRATOR, \
FORUM_ROLE_MODERATOR, FORUM_ROLE_COMMUNITY_TA, FORUM_ROLE_STUDENT FORUM_ROLE_MODERATOR, FORUM_ROLE_COMMUNITY_TA, FORUM_ROLE_STUDENT
from django_comment_client.utils import has_forum_access from django_comment_client.utils import has_forum_access
......
...@@ -27,7 +27,7 @@ from courseware.access import (has_access, get_access_group_name, ...@@ -27,7 +27,7 @@ from courseware.access import (has_access, get_access_group_name,
course_beta_test_group_name) course_beta_test_group_name)
from courseware.courses import get_course_with_access from courseware.courses import get_course_with_access
from courseware.models import StudentModule from courseware.models import StudentModule
from django_comment_client.models import (Role, from django_comment_common.models import (Role,
FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_ADMINISTRATOR,
FORUM_ROLE_MODERATOR, FORUM_ROLE_MODERATOR,
FORUM_ROLE_COMMUNITY_TA) FORUM_ROLE_COMMUNITY_TA)
......
...@@ -700,8 +700,7 @@ INSTALLED_APPS = ( ...@@ -700,8 +700,7 @@ INSTALLED_APPS = (
# Discussion forums # Discussion forums
'django_comment_client', 'django_comment_client',
'django_comment_common',
# Student notes
'notes', 'notes',
) )
......
...@@ -26,6 +26,16 @@ def run_tests(system, report_dir, test_id=nil, stop_on_failure=true) ...@@ -26,6 +26,16 @@ def run_tests(system, report_dir, test_id=nil, stop_on_failure=true)
end end
def run_acceptance_tests(system, report_dir, harvest_args) def run_acceptance_tests(system, report_dir, harvest_args)
# HACK: Since now the CMS depends on the existence of some database tables
# that used to be in LMS (Role/Permissions for Forums) we need to make
# sure the acceptance tests create/migrate the database tables
# that are represented in the LMS. We might be able to address this by moving
# out the migrations from lms/django_comment_client, but then we'd have to
# repair all the existing migrations from the upgrade tables in the DB.
if system == :cms
sh(django_admin('lms', 'acceptance', 'syncdb', '--noinput'))
sh(django_admin('lms', 'acceptance', 'migrate', '--noinput'))
end
sh(django_admin(system, 'acceptance', 'syncdb', '--noinput')) sh(django_admin(system, 'acceptance', 'syncdb', '--noinput'))
sh(django_admin(system, 'acceptance', 'migrate', '--noinput')) sh(django_admin(system, 'acceptance', 'migrate', '--noinput'))
sh(django_admin(system, 'acceptance', 'harvest', '--debug-mode', '--tag -skip', harvest_args)) sh(django_admin(system, 'acceptance', 'harvest', '--debug-mode', '--tag -skip', harvest_args))
......
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