Commit 215fde0b by Matt Drayer Committed by Jonathan Piacenti

mattdrayer/update-delete-course-cmd: Added new course deletion logic

parent 46b0de01
......@@ -2,12 +2,12 @@
### Script for cloning a course
###
from django.core.management.base import BaseCommand, CommandError
from .prompt import query_yes_no
from contentstore.utils import delete_course_and_groups
from opaque_keys.edx.keys import CourseKey
from opaque_keys import InvalidKeyError
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from xmodule.modulestore import ModuleStoreEnum
from util.prompt import query_yes_no
class Command(BaseCommand):
......@@ -28,6 +28,7 @@ class Command(BaseCommand):
if commit:
print('Actually going to delete the course from DB....')
print('Note: There is a corresponding LMS cleanup command you should run afterwards')
if query_yes_no("Deleting course {0}. Confirm?".format(course_key), default="no"):
if query_yes_no("Are you sure. This action cannot be undone!", default="no"):
......
......@@ -22,6 +22,7 @@ from opaque_keys.edx.keys import UsageKey, CourseKey
from student.roles import CourseInstructorRole, CourseStaffRole
from student.models import CourseEnrollment
from student import auth
from util.signals import course_deleted
log = logging.getLogger(__name__)
......@@ -76,9 +77,17 @@ def delete_course_and_groups(course_key, user_id):
# in the django layer, we need to remove all the user permissions groups associated with this course
try:
remove_all_instructors(course_key)
print 'User permissions removed, continuing...'
except Exception as err:
log.error("Error in deleting course groups for {0}: {1}".format(course_key, err))
# Broadcast the deletion event to CMS listeners
print 'Notifying CMS system components...'
course_deleted.send(sender=None, course_key=course_key)
print 'CMS Course Cleanup Complete!'
print 'You must now execute this same command in LMS to clean up orphaned records'
print 'COMMAND: ./manage.py lms delete_course_references <course_id> commit'
def get_lms_link_for_item(location, preview=False):
"""
......
"""
Enables interactivity for CLI operations
"""
import sys
......@@ -11,13 +14,8 @@ def query_yes_no(question, default="yes"):
The "answer" return value is one of "yes" or "no".
"""
valid = {
"yes": True,
"y": True,
"ye": True,
"no": False,
"n": False,
}
valid = {"yes": True, "y": True, "ye": True,
"no": False, "n": False}
if default is None:
prompt = " [y/n] "
elif default == "yes":
......
"""
https://docs.djangoproject.com/en/dev/topics/signals/
"""
from django.dispatch import Signal
# Platform Event: Course Deleted
# * Broadcasts to listeners when a particular course has been removed from the system
# * Important because Courses are not Django ORM entities, so model events aren't available
course_deleted = Signal(providing_args=["course"]) # pylint: disable=C0103
"""
Initialization module for gradebook djangoapp
"""
import api_manager.receivers
......@@ -2122,7 +2122,6 @@ class CoursesApiTests(ModuleStoreTestCase):
start_date,
end_date,
org_id)
response = self.do_get(course_metrics_uri)
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data['users_not_started']), 5)
......
"""
Signal handlers supporting various gradebook use cases
"""
from django.dispatch import receiver
from util.signals import course_deleted
from .models import CourseGroupRelationship, CourseContentGroupRelationship
@receiver(course_deleted)
def on_course_deleted(sender, **kwargs): # pylint: disable=W0613
"""
Listens for a 'course_deleted' signal and when observed
removes model entries for the specified course
"""
course_key = kwargs['course_key']
CourseGroupRelationship.objects.filter(course_id=course_key).delete()
CourseContentGroupRelationship.objects.filter(course_id=course_key).delete()
# pylint: disable=E1101
"""
Run these tests @ Devstack:
paver test_system -s lms --test_id=lms/djangoapps/gradebook/tests.py
"""
from datetime import datetime
import uuid
from django.contrib.auth.models import Group, User
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from util.signals import course_deleted
from api_manager.models import GroupProfile, CourseGroupRelationship, CourseContentGroupRelationship
class ApiManagerReceiversTests(ModuleStoreTestCase):
""" Test suite for signal receivers """
def setUp(self):
# Create a course to work with
self.course = CourseFactory.create(
start=datetime(2014, 6, 16, 14, 30),
end=datetime(2015, 1, 16)
)
test_data = '<html>{}</html>'.format(str(uuid.uuid4()))
self.chapter = ItemFactory.create(
category="chapter",
parent_location=self.course.location,
data=test_data,
due=datetime(2014, 5, 16, 14, 30),
display_name="Overview"
)
self.user = User.objects.create(email='testuser@edx.org', username='testuser', password='testpassword', is_active=True)
def test_receiver_on_course_deleted(self):
"""
Test the workflow
"""
# Set up the data to be removed
group = Group.objects.create(name='TestGroup')
group_profile = GroupProfile.objects.create(group=group)
CourseGroupRelationship.objects.create(
course_id=unicode(self.course.id),
group=group
)
CourseContentGroupRelationship.objects.create(
course_id=unicode(self.course.id),
content_id=unicode(self.chapter.location),
group_profile=group_profile
)
self.assertEqual(CourseGroupRelationship.objects.filter(course_id=unicode(self.course.id)).count(), 1)
self.assertEqual(CourseContentGroupRelationship.objects.filter(course_id=self.course.id, content_id=unicode(self.chapter.location)).count(), 1)
# Emit the signal
course_deleted.send(sender=None, course_key=self.course.id)
# Validate that the course references were removed
self.assertEqual(CourseGroupRelationship.objects.filter(course_id=unicode(self.course.id)).count(), 0)
self.assertEqual(CourseContentGroupRelationship.objects.filter(course_id=self.course.id, content_id=unicode(self.chapter.location)).count(), 0)
"""
Script for deleting orphaned (or not-orphaned!) course references
Note: This script should be run AFTER the corresponding CMS script,
because the CMS script performs the actual modulestore removal.
"""
from django.conf import settings
from django.core.management.base import BaseCommand, CommandError
from opaque_keys.edx.keys import CourseKey
from opaque_keys import InvalidKeyError
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from util.prompt import query_yes_no
from util.signals import course_deleted
class Command(BaseCommand):
"""
Command class for course reference removal
"""
help = '''Deletes database records having reference to the specified course'''
def handle(self, *args, **options):
"""
This handler operation does the actual work!
"""
if len(args) != 1 and len(args) != 2:
raise CommandError("delete_course requires one or more arguments: <course_id> |commit|")
try:
course_key = CourseKey.from_string(args[0])
except InvalidKeyError:
course_key = SlashSeparatedCourseKey.from_deprecated_string(args[0])
commit = False
if len(args) == 2:
commit = args[1] == 'commit'
if commit:
print('Actually going to delete the course references from LMS database....')
print('Note: There is a corresponding CMS command you must run BEFORE this command.')
if hasattr(settings, 'TEST_ROOT'):
course_deleted.send(sender=None, course_key=course_key)
else:
if query_yes_no("Deleting ALL records with references to course {0}. Confirm?".format(course_key), default="no"):
if query_yes_no("Are you sure. This action cannot be undone!", default="no"):
# Broadcast the deletion event to CMS listeners
print 'Notifying LMS system components...'
course_deleted.send(sender=None, course_key=course_key)
print 'LMS Course Cleanup Complete!'
"""
Run these tests @ Devstack:
paver test_system -s lms --fast_test --test_id=lms/djangoapps/courseware/management/tests/test_delete_course_references.py
"""
from datetime import datetime
import uuid
from django.conf import settings
from django.contrib.auth.models import Group, User
from django.test import TestCase
from django.test.utils import override_settings
from courseware.management.commands import delete_course_references
from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
if settings.FEATURES.get('API', False):
from api_manager.models import GroupProfile, CourseGroupRelationship, CourseContentGroupRelationship
if settings.FEATURES.get('PROJECTS_APP', False):
from projects import models as project_models
if settings.FEATURES.get('STUDENT_GRADEBOOK', False):
from gradebook import models as gradebook_models
if settings.FEATURES.get('STUDENT_PROGRESS', False):
from progress import models as progress_models
@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
class DeleteCourseReferencesTests(TestCase):
"""
Test suite for course reference deletion script
"""
def setUp(self):
# Create a course to work with
self.course = CourseFactory.create(
start=datetime(2014, 6, 16, 14, 30),
end=datetime(2015, 1, 16)
)
test_data = '<html>{}</html>'.format(str(uuid.uuid4()))
self.chapter = ItemFactory.create(
category="chapter",
parent_location=self.course.location,
data=test_data,
due=datetime(2014, 5, 16, 14, 30),
display_name="Overview"
)
self.user = User.objects.create(email='testuser@edx.org', username='testuser', password='testpassword', is_active=True)
if settings.FEATURES.get('API', False):
def test_delete_course_references_api_manager(self):
"""
Test the workflow
"""
# Set up the data to be removed
group = Group.objects.create(name='TestGroup')
group_profile = GroupProfile.objects.create(group=group)
course_group_relationship = CourseGroupRelationship.objects.create(
course_id=unicode(self.course.id),
group=group
)
content_group_relationship = CourseContentGroupRelationship.objects.create(
course_id=unicode(self.course.id),
content_id=unicode(self.chapter.location),
group_profile=group_profile
)
self.assertEqual(CourseGroupRelationship.objects.filter(id=course_group_relationship.id).count(), 1)
self.assertEqual(CourseContentGroupRelationship.objects.filter(id=content_group_relationship.id).count(), 1)
# Run the data migration
delete_course_references.Command().handle(unicode(self.course.id), 'commit')
# Validate that the course references were removed
self.assertEqual(CourseGroupRelationship.objects.filter(id=course_group_relationship.id).count(), 0)
self.assertEqual(CourseContentGroupRelationship.objects.filter(id=content_group_relationship.id).count(), 0)
if settings.FEATURES.get('PROJECTS_APP', False):
def test_delete_course_references_projects(self):
project = project_models.Project.objects.create(
course_id=unicode(self.course.id),
content_id=unicode(self.chapter.location)
)
workgroup = project_models.Workgroup.objects.create(
project=project,
name='TEST WORKGROUP'
)
workgroup_user = project_models.WorkgroupUser.objects.create(
workgroup=workgroup,
user=self.user
)
workgroup_review = project_models.WorkgroupReview.objects.create(
workgroup=workgroup,
reviewer=self.user,
question='test',
answer='test',
content_id=unicode(self.chapter.location),
)
workgroup_peer_review = project_models.WorkgroupPeerReview.objects.create(
workgroup=workgroup,
user=self.user,
reviewer=self.user,
question='test',
answer='test',
content_id=unicode(self.chapter.location),
)
workgroup_submission = project_models.WorkgroupSubmission.objects.create(
workgroup=workgroup,
user=self.user,
document_id='test',
document_url='test',
document_mime_type='test',
)
workgroup_submission_review = project_models.WorkgroupSubmissionReview.objects.create(
submission=workgroup_submission,
reviewer=self.user,
question='test',
answer='test',
content_id=unicode(self.chapter.location),
)
self.assertEqual(project_models.Project.objects.filter(id=project.id).count(), 1)
self.assertEqual(project_models.Workgroup.objects.filter(id=workgroup.id).count(), 1)
self.assertEqual(project_models.WorkgroupUser.objects.filter(id=workgroup_user.id).count(), 1)
self.assertEqual(project_models.WorkgroupReview.objects.filter(id=workgroup_review.id).count(), 1)
self.assertEqual(project_models.WorkgroupSubmission.objects.filter(id=workgroup_submission.id).count(), 1)
self.assertEqual(project_models.WorkgroupSubmissionReview.objects.filter(id=workgroup_submission_review.id).count(), 1)
self.assertEqual(project_models.WorkgroupPeerReview.objects.filter(id=workgroup_peer_review.id).count(), 1)
# Run the course deletion command
delete_course_references.Command().handle(unicode(self.course.id), 'commit')
# Validate that the course references were removed
self.assertEqual(project_models.Project.objects.filter(id=project.id).count(), 0)
self.assertEqual(project_models.Workgroup.objects.filter(id=workgroup.id).count(), 0)
self.assertEqual(project_models.WorkgroupUser.objects.filter(id=workgroup_user.id).count(), 0)
self.assertEqual(project_models.WorkgroupReview.objects.filter(id=workgroup_review.id).count(), 0)
self.assertEqual(project_models.WorkgroupSubmission.objects.filter(id=workgroup_submission.id).count(), 0)
self.assertEqual(project_models.WorkgroupSubmissionReview.objects.filter(id=workgroup_submission_review.id).count(), 0)
self.assertEqual(project_models.WorkgroupPeerReview.objects.filter(id=workgroup_peer_review.id).count(), 0)
if settings.FEATURES.get('STUDENT_GRADEBOOK', False):
def test_delete_course_references_gradebook(self):
gradebook = gradebook_models.StudentGradebook.objects.create(
user=self.user,
course_id=unicode(self.course.id),
grade=0.65,
proforma_grade=0.75
)
self.assertEqual(gradebook_models.StudentGradebook.objects.filter(id=gradebook.id).count(), 1)
self.assertEqual(gradebook_models.StudentGradebookHistory.objects.filter(user=self.user, course_id=self.course.id).count(), 1)
# Run the course deletion command
delete_course_references.Command().handle(unicode(self.course.id), 'commit')
# Validate that the course references were removed
self.assertEqual(gradebook_models.StudentGradebook.objects.filter(id=gradebook.id).count(), 0)
self.assertEqual(gradebook_models.StudentGradebookHistory.objects.filter(user=self.user, course_id=self.course.id).count(), 0)
if settings.FEATURES.get('STUDENT_PROGRESS', False):
def test_delete_course_references_progress(self):
completion = progress_models.CourseModuleCompletion.objects.create(
user=self.user,
course_id=unicode(self.course.id),
content_id=unicode(self.chapter.location)
)
progress = progress_models.StudentProgress.objects.create(
user=self.user,
course_id=self.course.id,
completions=10
)
self.assertEqual(progress_models.CourseModuleCompletion.objects.filter(id=completion.id).count(), 1)
self.assertEqual(progress_models.StudentProgress.objects.filter(course_id=self.course.id).count(), 1)
self.assertEqual(progress_models.StudentProgressHistory.objects.filter(user=self.user, course_id=self.course.id).count(), 1)
# Run the course deletion command
delete_course_references.Command().handle(unicode(self.course.id), 'commit')
# Validate that the course references were removed
self.assertEqual(progress_models.CourseModuleCompletion.objects.filter(id=completion.id).count(), 0)
self.assertEqual(progress_models.StudentProgress.objects.filter(id=progress.id).count(), 0)
self.assertEqual(progress_models.StudentProgressHistory.objects.filter(user=self.user, course_id=self.course.id).count(), 0)
......@@ -6,8 +6,9 @@ from django.dispatch import receiver
from courseware import grades
from courseware.signals import score_changed
from util.request import RequestMockWithoutMiddleware
from util.signals import course_deleted
from gradebook.models import StudentGradebook
from gradebook.models import StudentGradebook, StudentGradebookHistory
@receiver(score_changed)
......@@ -33,3 +34,14 @@ def on_score_changed(sender, **kwargs):
gradebook_entry.save()
except StudentGradebook.DoesNotExist:
StudentGradebook.objects.create(user=user, course_id=course_key, grade=grade, proforma_grade=proforma_grade)
@receiver(course_deleted)
def on_course_deleted(sender, **kwargs): # pylint: disable=W0613
"""
Listens for a 'course_deleted' signal and when observed
removes model entries for the specified course
"""
course_key = kwargs['course_key']
StudentGradebook.objects.filter(course_id=course_key).delete()
StudentGradebookHistory.objects.filter(course_id=course_key).delete()
......@@ -21,7 +21,7 @@ from courseware.tests.factories import StaffFactory
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from gradebook.models import StudentGradebook, StudentGradebookHistory
from util.signals import course_deleted
@override_settings(STUDENT_GRADEBOOK=True)
class GradebookTests(ModuleStoreTestCase):
......@@ -247,3 +247,23 @@ class GradebookTests(ModuleStoreTestCase):
history = StudentGradebookHistory.objects.all()
self.assertEqual(len(history), 0)
def test_receiver_on_course_deleted(self):
self._create_course(start=datetime(2010, 1, 1, tzinfo=UTC()), end=datetime(2020, 1, 1, tzinfo=UTC()))
module = self.get_module_for_user(self.user, self.course, self.problem)
grade_dict = {'value': 0.75, 'max_value': 1, 'user_id': self.user.id}
module.system.publish(module, 'grade', grade_dict)
gradebook = StudentGradebook.objects.all()
self.assertEqual(len(gradebook), 1)
history = StudentGradebookHistory.objects.all()
self.assertEqual(len(history), 1)
course_deleted.send(sender=None, course_key=self.course.id)
gradebook = StudentGradebook.objects.all()
self.assertEqual(len(gradebook), 0)
history = StudentGradebookHistory.objects.all()
self.assertEqual(len(history), 0)
......@@ -30,7 +30,7 @@ class MoveOrganizationEntriesTests(TestCase):
workgroup.save()
for i in xrange(1, 9):
org = self.organization()
org = Organization()
org.name = 'test_and_company {}'.format(i)
org.display_name = 'test display name {}'.format(i)
org.contact_name = 'test contact name {}'.format(i)
......
"""
Initialization module for progress djangoapp
"""
import progress.receivers
# Need to move receivers from signals.py to receivers.py
import progress.signals
"""
Signal handlers supporting various gradebook use cases
"""
from django.dispatch import receiver
from util.signals import course_deleted
from progress.models import CourseModuleCompletion, StudentProgress, StudentProgressHistory
@receiver(course_deleted)
def on_course_deleted(sender, **kwargs): # pylint: disable=W0613
"""
Listens for a 'course_deleted' signal and when observed
removes model entries for the specified course
"""
course_key = kwargs['course_key']
CourseModuleCompletion.objects.filter(course_id=unicode(course_key)).delete()
StudentProgress.objects.filter(course_id=course_key).delete()
StudentProgressHistory.objects.filter(course_id=course_key).delete()
......@@ -22,9 +22,10 @@ from student.tests.factories import UserFactory, AdminFactory
from courseware.tests.factories import StaffFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from progress.models import CourseModuleCompletion
from progress.models import CourseModuleCompletion, StudentProgress, StudentProgressHistory
from courseware.model_data import FieldDataCache
from courseware import module_render
from util.signals import course_deleted
@override_settings(STUDENT_GRADEBOOK=True)
......@@ -270,3 +271,22 @@ class CourseModuleCompletionTests(ModuleStoreTestCase):
course_id=self.course.id,
content_id=self.problem4.location
)
def test_receiver_on_course_deleted(self):
self._create_course(start=datetime(2010, 1, 1, tzinfo=UTC()), end=datetime(2020, 1, 1, tzinfo=UTC()))
module = self.get_module_for_user(self.user, self.course, self.problem)
module.system.publish(module, 'progress', {})
progress = StudentProgress.objects.all()
self.assertEqual(len(progress), 1)
history = StudentProgressHistory.objects.all()
self.assertEqual(len(history), 1)
course_deleted.send(sender=None, course_key=self.course.id)
progress = StudentProgress.objects.all()
self.assertEqual(len(progress), 0)
history = StudentProgressHistory.objects.all()
self.assertEqual(len(history), 0)
"""
Initialization module for projects djangoapp
"""
import projects.receivers
"""
Signal handlers supporting various gradebook use cases
"""
from django.dispatch import receiver
from util.signals import course_deleted
from projects import models
@receiver(course_deleted)
def on_course_deleted(sender, **kwargs): # pylint: disable=W0613
"""
Listens for a 'course_deleted' signal and when observed
removes model entries for the specified course
"""
course_key = kwargs.get('course_key')
if course_key:
projects = models.Project.objects.filter(course_id=unicode(course_key))
for project in projects:
workgroups = models.Workgroup.objects.filter(project=project)
for workgroup in workgroups:
submissions = models.WorkgroupSubmission.objects.filter(workgroup=workgroup)
for submission in submissions:
submission_reviews = models.WorkgroupSubmissionReview.objects.filter(submission=submission)
for submission_review in submission_reviews:
models.WorkgroupSubmissionReview.objects.filter(id=submission_review.id).delete()
models.WorkgroupSubmission.objects.filter(id=submission.id).delete()
models.WorkgroupPeerReview.objects.filter(workgroup=workgroup).delete()
models.WorkgroupReview.objects.filter(workgroup=workgroup).delete()
models.WorkgroupUser.objects.filter(workgroup=workgroup).delete()
models.Workgroup.objects.filter(id=workgroup.id).delete()
models.Project.objects.filter(id=project.id).delete()
# pylint: disable=E1101
"""
Run these tests @ Devstack:
paver test_system -s lms --test_id=lms/djangoapps/gradebook/tests.py
"""
from datetime import datetime
import uuid
from django.contrib.auth.models import User
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from util.signals import course_deleted
from projects import models
class ProjectsReceiversTests(ModuleStoreTestCase):
""" Test suite for signal receivers """
def setUp(self):
# Create a course to work with
self.course = CourseFactory.create(
start=datetime(2014, 6, 16, 14, 30),
end=datetime(2015, 1, 16)
)
test_data = '<html>{}</html>'.format(str(uuid.uuid4()))
self.chapter = ItemFactory.create(
category="chapter",
parent_location=self.course.location,
data=test_data,
due=datetime(2014, 5, 16, 14, 30),
display_name="Overview"
)
self.user = User.objects.create(email='testuser@edx.org', username='testuser', password='testpassword', is_active=True)
def test_receiver_on_course_deleted(self):
project = models.Project.objects.create(
course_id=unicode(self.course.id),
content_id=unicode(self.chapter.location)
)
workgroup = models.Workgroup.objects.create(
project=project,
name='TEST WORKGROUP'
)
workgroup_user = models.WorkgroupUser.objects.create(
workgroup=workgroup,
user=self.user
)
workgroup_review = models.WorkgroupReview.objects.create(
workgroup=workgroup,
reviewer=self.user,
question='test',
answer='test',
content_id=unicode(self.chapter.location),
)
workgroup_peer_review = models.WorkgroupPeerReview.objects.create(
workgroup=workgroup,
user=self.user,
reviewer=self.user,
question='test',
answer='test',
content_id=unicode(self.chapter.location),
)
workgroup_submission = models.WorkgroupSubmission.objects.create(
workgroup=workgroup,
user=self.user,
document_id='test',
document_url='test',
document_mime_type='test',
)
workgroup_submission_review = models.WorkgroupSubmissionReview.objects.create(
submission=workgroup_submission,
reviewer=self.user,
question='test',
answer='test',
content_id=unicode(self.chapter.location),
)
self.assertEqual(models.Project.objects.filter(id=project.id).count(), 1)
self.assertEqual(models.Workgroup.objects.filter(id=workgroup.id).count(), 1)
self.assertEqual(models.WorkgroupUser.objects.filter(id=workgroup_user.id).count(), 1)
self.assertEqual(models.WorkgroupReview.objects.filter(id=workgroup_review.id).count(), 1)
self.assertEqual(models.WorkgroupSubmission.objects.filter(id=workgroup_submission.id).count(), 1)
self.assertEqual(models.WorkgroupSubmissionReview.objects.filter(id=workgroup_submission_review.id).count(), 1)
self.assertEqual(models.WorkgroupPeerReview.objects.filter(id=workgroup_peer_review.id).count(), 1)
# Run the data migration
course_deleted.send(sender=None, course_key=self.course.id)
# Validate that the course references were removed
self.assertEqual(models.Project.objects.filter(id=project.id).count(), 0)
self.assertEqual(models.Workgroup.objects.filter(id=workgroup.id).count(), 0)
self.assertEqual(models.WorkgroupUser.objects.filter(id=workgroup_user.id).count(), 0)
self.assertEqual(models.WorkgroupReview.objects.filter(id=workgroup_review.id).count(), 0)
self.assertEqual(models.WorkgroupSubmission.objects.filter(id=workgroup_submission.id).count(), 0)
self.assertEqual(models.WorkgroupSubmissionReview.objects.filter(id=workgroup_submission_review.id).count(), 0)
self.assertEqual(models.WorkgroupPeerReview.objects.filter(id=workgroup_peer_review.id).count(), 0)
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