diff --git a/common/test/db_cache/bok_choy_schema.sql b/common/test/db_cache/bok_choy_schema.sql index 9856d86aa3..0281c3ed6a 100644 --- a/common/test/db_cache/bok_choy_schema.sql +++ b/common/test/db_cache/bok_choy_schema.sql @@ -1875,33 +1875,6 @@ CREATE TABLE `instructor_task_instructortask` ( CONSTRAINT `instructor_task_in_requester_id_3383acfe2fe42391_fk_auth_user_id` FOREIGN KEY (`requester_id`) REFERENCES `auth_user` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -DROP TABLE IF EXISTS `licenses_coursesoftware`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `licenses_coursesoftware` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `name` varchar(255) NOT NULL, - `full_name` varchar(255) NOT NULL, - `url` varchar(255) NOT NULL, - `course_id` varchar(255) NOT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; -/*!40101 SET character_set_client = @saved_cs_client */; -DROP TABLE IF EXISTS `licenses_userlicense`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `licenses_userlicense` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `serial` varchar(255) NOT NULL, - `software_id` int(11) NOT NULL, - `user_id` int(11) DEFAULT NULL, - PRIMARY KEY (`id`), - KEY `licen_software_id_209fd400b7c7a3ca_fk_licenses_coursesoftware_id` (`software_id`), - KEY `licenses_userlicense_user_id_7d98c37aa4438a34_fk_auth_user_id` (`user_id`), - CONSTRAINT `licen_software_id_209fd400b7c7a3ca_fk_licenses_coursesoftware_id` FOREIGN KEY (`software_id`) REFERENCES `licenses_coursesoftware` (`id`), - CONSTRAINT `licenses_userlicense_user_id_7d98c37aa4438a34_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; -/*!40101 SET character_set_client = @saved_cs_client */; DROP TABLE IF EXISTS `lms_xblock_xblockasidesconfig`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; diff --git a/lms/djangoapps/licenses/__init__.py b/lms/djangoapps/licenses/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/lms/djangoapps/licenses/management/__init__.py b/lms/djangoapps/licenses/management/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/lms/djangoapps/licenses/management/commands/__init__.py b/lms/djangoapps/licenses/management/commands/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/lms/djangoapps/licenses/management/commands/generate_serial_numbers.py b/lms/djangoapps/licenses/management/commands/generate_serial_numbers.py deleted file mode 100644 index 7e935a831c..0000000000 --- a/lms/djangoapps/licenses/management/commands/generate_serial_numbers.py +++ /dev/null @@ -1,60 +0,0 @@ -from uuid import uuid4 - -from django.utils.html import escape -from django.core.management.base import BaseCommand, CommandError - -from xmodule.modulestore.django import modulestore - -from licenses.models import CourseSoftware, UserLicense -from opaque_keys.edx.locations import SlashSeparatedCourseKey - - -class Command(BaseCommand): - help = """Generate random serial numbers for software used in a course. - - Usage: generate_serial_numbers - - is the number of numbers to generate. - - Example: - - import_serial_numbers MITx/6.002x/2012_Fall matlab 100 - - """ - args = "course_id software_id count" - - def handle(self, *args, **options): - course_id, software_name, count = self._parse_arguments(args) - - software, _ = CourseSoftware.objects.get_or_create(course_id=course_id, - name=software_name) - self._generate_serials(software, count) - - def _parse_arguments(self, args): - if len(args) != 3: - raise CommandError("Incorrect number of arguments") - - course_id = args[0] - course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) - if not modulestore().has_course(course_key): - raise CommandError("Unknown course_id") - - software_name = escape(args[1].lower()) - - try: - count = int(args[2]) - except ValueError: - raise CommandError("Invalid argument.") - - return course_key, software_name, count - - def _generate_serials(self, software, count): - print "Generating {0} serials".format(count) - - # add serial numbers them to the database - for _ in xrange(count): - serial = str(uuid4()) - license = UserLicense(software=software, serial=serial) - license.save() - - print "{0} new serial numbers generated.".format(count) diff --git a/lms/djangoapps/licenses/management/commands/import_serial_numbers.py b/lms/djangoapps/licenses/management/commands/import_serial_numbers.py deleted file mode 100644 index fbb93d3a28..0000000000 --- a/lms/djangoapps/licenses/management/commands/import_serial_numbers.py +++ /dev/null @@ -1,66 +0,0 @@ -import os.path - -from django.utils.html import escape -from django.core.management.base import BaseCommand, CommandError - -from xmodule.modulestore.django import modulestore - -from licenses.models import CourseSoftware, UserLicense -from opaque_keys.edx.locations import SlashSeparatedCourseKey - - -class Command(BaseCommand): - help = """Imports serial numbers for software used in a course. - - Usage: import_serial_numbers - - is a text file that list one available serial number per line. - - Example: - - import_serial_numbers MITx/6.002x/2012_Fall matlab serials.txt - - """ - args = "course_id software_id serial_file" - - def handle(self, *args, **options): - course_id, software_name, filename = self._parse_arguments(args) - - software, _ = CourseSoftware.objects.get_or_create(course_id=course_id, - name=software_name) - self._import_serials(software, filename) - - def _parse_arguments(self, args): - if len(args) != 3: - raise CommandError("Incorrect number of arguments") - - course_id = args[0] - course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) - if not modulestore().has_course(course_key): - raise CommandError("Unknown course_id") - - software_name = escape(args[1].lower()) - - filename = os.path.abspath(args[2]) - if not os.path.exists(filename): - raise CommandError("Cannot find filename {0}".format(filename)) - - return course_key, software_name, filename - - def _import_serials(self, software, filename): - print "Importing serial numbers for {0}.".format(software) - - serials = set(unicode(l.strip()) for l in open(filename)) - - # remove serial numbers we already have - licenses = UserLicense.objects.filter(software=software) - known_serials = set(l.serial for l in licenses) - if known_serials: - serials = serials.difference(known_serials) - - # add serial numbers them to the database - for serial in serials: - license = UserLicense(software=software, serial=serial) - license.save() - - print "{0} new serial numbers imported.".format(len(serials)) diff --git a/lms/djangoapps/licenses/migrations/0001_initial.py b/lms/djangoapps/licenses/migrations/0001_initial.py deleted file mode 100644 index a7a26b6926..0000000000 --- a/lms/djangoapps/licenses/migrations/0001_initial.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models -from django.conf import settings -import xmodule_django.models - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name='CourseSoftware', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('name', models.CharField(max_length=255)), - ('full_name', models.CharField(max_length=255)), - ('url', models.CharField(max_length=255)), - ('course_id', xmodule_django.models.CourseKeyField(max_length=255)), - ], - ), - migrations.CreateModel( - name='UserLicense', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('serial', models.CharField(max_length=255)), - ('software', models.ForeignKey(to='licenses.CourseSoftware')), - ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, null=True)), - ], - ), - ] diff --git a/lms/djangoapps/licenses/migrations/__init__.py b/lms/djangoapps/licenses/migrations/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/lms/djangoapps/licenses/models.py b/lms/djangoapps/licenses/models.py deleted file mode 100644 index 675db4d143..0000000000 --- a/lms/djangoapps/licenses/models.py +++ /dev/null @@ -1,83 +0,0 @@ -import logging - -from django.db import models, transaction - -from student.models import User - -from xmodule_django.models import CourseKeyField - -log = logging.getLogger("edx.licenses") - - -class CourseSoftware(models.Model): - name = models.CharField(max_length=255) - full_name = models.CharField(max_length=255) - url = models.CharField(max_length=255) - course_id = CourseKeyField(max_length=255) - - def __unicode__(self): - return u'{0} for {1}'.format(self.name, self.course_id) - - -class UserLicense(models.Model): - software = models.ForeignKey(CourseSoftware, db_index=True) - user = models.ForeignKey(User, null=True) - serial = models.CharField(max_length=255) - - -def get_courses_licenses(user, courses): - course_ids = set(course.id for course in courses) - all_software = CourseSoftware.objects.filter(course_id__in=course_ids) - - assigned_licenses = UserLicense.objects.filter(software__in=all_software, - user=user) - - licenses = dict.fromkeys(all_software, None) - for license in assigned_licenses: - licenses[license.software] = license - - log.info(assigned_licenses) - log.info(licenses) - - return licenses - - -def get_license(user, software): - try: - # TODO: temporary fix for when somehow a user got more that one license. - # The proper fix should use Meta.unique_together in the UserLicense model. - licenses = UserLicense.objects.filter(user=user, software=software) - license = licenses[0] if licenses else None - except UserLicense.DoesNotExist: - license = None - - return license - - -def get_or_create_license(user, software): - license = get_license(user, software) - if license is None: - license = _create_license(user, software) - - return license - - -def _create_license(user, software): - license = None - - try: - # find one license that has not been assigned, locking the - # table/rows with select_for_update to prevent race conditions - with transaction.atomic(): - selected = UserLicense.objects.select_for_update() - license = selected.filter(user__isnull=True, software=software)[0] - license.user = user - license.save() - except IndexError: - # there are no free licenses - log.error('No serial numbers available for %s', software) - license = None - # TODO [rocha]look if someone has unenrolled from the class - # and already has a serial number - - return license diff --git a/lms/djangoapps/licenses/tests.py b/lms/djangoapps/licenses/tests.py deleted file mode 100644 index 32d047dbec..0000000000 --- a/lms/djangoapps/licenses/tests.py +++ /dev/null @@ -1,227 +0,0 @@ -"""Tests for License package""" -import logging -import json - -from uuid import uuid4 -from random import shuffle -from tempfile import NamedTemporaryFile -import factory -from factory.django import DjangoModelFactory - -from django.test import TestCase -from django.test.client import Client -from django.core.management import call_command -from django.core.urlresolvers import reverse -from nose.tools import assert_true - -from licenses.models import CourseSoftware, UserLicense - -from student.tests.factories import UserFactory -from xmodule.modulestore.tests.factories import CourseFactory -from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase - -COURSE_1 = 'edX/toy/2012_Fall' - -SOFTWARE_1 = 'matlab' -SOFTWARE_2 = 'stata' - -SERIAL_1 = '123456abcde' - -log = logging.getLogger(__name__) - - -class CourseSoftwareFactory(DjangoModelFactory): - '''Factory for generating CourseSoftware objects in database''' - class Meta(object): - model = CourseSoftware - - name = SOFTWARE_1 - full_name = SOFTWARE_1 - url = SOFTWARE_1 - course_id = COURSE_1 - - -class UserLicenseFactory(DjangoModelFactory): - ''' - Factory for generating UserLicense objects in database - - By default, the user assigned is null, indicating that the - serial number has not yet been assigned. - ''' - class Meta(object): - model = UserLicense - - user = None - software = factory.SubFactory(CourseSoftwareFactory) - serial = SERIAL_1 - - -class LicenseTestCase(TestCase): - '''Tests for licenses.views''' - def setUp(self): - '''creates a user and logs in''' - - super(LicenseTestCase, self).setUp() - # self.setup_viewtest_user() - self.user = UserFactory(username='test', - email='test@edx.org', password='test_password') - self.client = Client() - assert_true(self.client.login(username='test', password='test_password')) - self.software = CourseSoftwareFactory() - - def test_get_license(self): - UserLicenseFactory(user=self.user, software=self.software) - response = self.client.post(reverse('user_software_license'), - {'software': SOFTWARE_1, 'generate': 'false'}, - HTTP_X_REQUESTED_WITH='XMLHttpRequest', - HTTP_REFERER='/courses/{0}/some_page'.format(COURSE_1)) - self.assertEqual(200, response.status_code) - json_returned = json.loads(response.content) - self.assertFalse('error' in json_returned) - self.assertTrue('serial' in json_returned) - self.assertEquals(json_returned['serial'], SERIAL_1) - - def test_get_nonexistent_license(self): - response = self.client.post(reverse('user_software_license'), - {'software': SOFTWARE_1, 'generate': 'false'}, - HTTP_X_REQUESTED_WITH='XMLHttpRequest', - HTTP_REFERER='/courses/{0}/some_page'.format(COURSE_1)) - self.assertEqual(200, response.status_code) - json_returned = json.loads(response.content) - self.assertFalse('serial' in json_returned) - self.assertTrue('error' in json_returned) - - def test_create_nonexistent_license(self): - '''Should not assign a license to an unlicensed user when none are available''' - response = self.client.post(reverse('user_software_license'), - {'software': SOFTWARE_1, 'generate': 'true'}, - HTTP_X_REQUESTED_WITH='XMLHttpRequest', - HTTP_REFERER='/courses/{0}/some_page'.format(COURSE_1)) - self.assertEqual(200, response.status_code) - json_returned = json.loads(response.content) - self.assertFalse('serial' in json_returned) - self.assertTrue('error' in json_returned) - - def test_create_license(self): - '''Should assign a license to an unlicensed user if one is unassigned''' - # create an unassigned license - UserLicenseFactory(software=self.software) - response = self.client.post(reverse('user_software_license'), - {'software': SOFTWARE_1, 'generate': 'true'}, - HTTP_X_REQUESTED_WITH='XMLHttpRequest', - HTTP_REFERER='/courses/{0}/some_page'.format(COURSE_1)) - self.assertEqual(200, response.status_code) - json_returned = json.loads(response.content) - self.assertFalse('error' in json_returned) - self.assertTrue('serial' in json_returned) - self.assertEquals(json_returned['serial'], SERIAL_1) - - def test_get_license_from_wrong_course(self): - response = self.client.post(reverse('user_software_license'), - {'software': SOFTWARE_1, 'generate': 'false'}, - HTTP_X_REQUESTED_WITH='XMLHttpRequest', - HTTP_REFERER='/courses/{0}/some_page'.format('some/other/course')) - self.assertEqual(404, response.status_code) - - def test_get_license_from_non_ajax(self): - response = self.client.post(reverse('user_software_license'), - {'software': SOFTWARE_1, 'generate': 'false'}, - HTTP_REFERER='/courses/{0}/some_page'.format(COURSE_1)) - self.assertEqual(404, response.status_code) - - def test_get_license_without_software(self): - response = self.client.post(reverse('user_software_license'), - {'generate': 'false'}, - HTTP_X_REQUESTED_WITH='XMLHttpRequest', - HTTP_REFERER='/courses/{0}/some_page'.format(COURSE_1)) - self.assertEqual(404, response.status_code) - - def test_get_license_without_login(self): - self.client.logout() - response = self.client.post(reverse('user_software_license'), - {'software': SOFTWARE_1, 'generate': 'false'}, - HTTP_X_REQUESTED_WITH='XMLHttpRequest', - HTTP_REFERER='/courses/{0}/some_page'.format(COURSE_1)) - # if we're not logged in, we should be referred to the login page - self.assertEqual(302, response.status_code) - - -class CommandTest(ModuleStoreTestCase): - '''Test management command for importing serial numbers''' - def setUp(self): - super(CommandTest, self).setUp() - - course = CourseFactory.create() - self.course_id = course.id - - def test_import_serial_numbers(self): - size = 20 - - log.debug('Adding one set of serials for {0}'.format(SOFTWARE_1)) - with generate_serials_file(size) as temp_file: - args = [self.course_id.to_deprecated_string(), SOFTWARE_1, temp_file.name] - call_command('import_serial_numbers', *args) - - log.debug('Adding one set of serials for {0}'.format(SOFTWARE_2)) - with generate_serials_file(size) as temp_file: - args = [self.course_id.to_deprecated_string(), SOFTWARE_2, temp_file.name] - call_command('import_serial_numbers', *args) - - log.debug('There should be only 2 course-software entries') - software_count = CourseSoftware.objects.all().count() - self.assertEqual(2, software_count) - - log.debug('We added two sets of {0} serials'.format(size)) - licenses_count = UserLicense.objects.all().count() - self.assertEqual(2 * size, licenses_count) - - log.debug('Adding more serial numbers to {0}'.format(SOFTWARE_1)) - with generate_serials_file(size) as temp_file: - args = [self.course_id.to_deprecated_string(), SOFTWARE_1, temp_file.name] - call_command('import_serial_numbers', *args) - - log.debug('There should be still only 2 course-software entries') - software_count = CourseSoftware.objects.all().count() - self.assertEqual(2, software_count) - - log.debug( - "Now we should have 3 sets of %s serials", - size, - ) - licenses_count = UserLicense.objects.all().count() - self.assertEqual(3 * size, licenses_count) - - software = CourseSoftware.objects.get(pk=1) - - lics = UserLicense.objects.filter(software=software)[:size] - known_serials = list(l.serial for l in lics) - known_serials.extend(generate_serials(10)) - - shuffle(known_serials) - - log.debug('Adding some new and old serials to {0}'.format(SOFTWARE_1)) - with NamedTemporaryFile() as tmpfile: - tmpfile.write('\n'.join(known_serials)) - tmpfile.flush() - args = [self.course_id.to_deprecated_string(), SOFTWARE_1, tmpfile.name] - call_command('import_serial_numbers', *args) - - log.debug('Check if we added only the new ones') - licenses_count = UserLicense.objects.filter(software=software).count() - self.assertEqual((2 * size) + 10, licenses_count) - - -def generate_serials(size=20): - '''generate a list of serial numbers''' - return [str(uuid4()) for _ in range(size)] - - -def generate_serials_file(size=20): - '''output list of generated serial numbers to a temp file''' - serials = generate_serials(size) - - temp_file = NamedTemporaryFile() - temp_file.write('\n'.join(serials)) - temp_file.flush() - - return temp_file diff --git a/lms/djangoapps/licenses/views.py b/lms/djangoapps/licenses/views.py deleted file mode 100644 index c30cebd915..0000000000 --- a/lms/djangoapps/licenses/views.py +++ /dev/null @@ -1,90 +0,0 @@ -import logging -import json -import re -from urlparse import urlparse -from collections import namedtuple, defaultdict - - -from edxmako.shortcuts import render_to_string -from opaque_keys.edx.locations import SlashSeparatedCourseKey - -from django.contrib.auth.decorators import login_required -from django.contrib.auth.models import User -from django.http import HttpResponse, Http404 -from django.views.decorators.csrf import requires_csrf_token - -from licenses.models import CourseSoftware -from licenses.models import get_courses_licenses, get_or_create_license, get_license - - -log = logging.getLogger("edx.licenses") - - -License = namedtuple('License', 'software serial') - - -def get_licenses_by_course(user, courses): - licenses = get_courses_licenses(user, courses) - licenses_by_course = defaultdict(list) - - # create missing licenses and group by course_id - for software, license in licenses.iteritems(): - if license is None: - licenses[software] = get_or_create_license(user, software) - - course_id = software.course_id - serial = license.serial if license else None - licenses_by_course[course_id].append(License(software, serial)) - - # render elements - data_by_course = {} - for course_id, licenses in licenses_by_course.iteritems(): - context = {'licenses': licenses} - template = 'licenses/serial_numbers.html' - data_by_course[course_id] = render_to_string(template, context) - - return data_by_course - - -@login_required -@requires_csrf_token -def user_software_license(request): - if request.method != 'POST' or not request.is_ajax(): - raise Http404 - - # get the course id from the referer - url_path = urlparse(request.META.get('HTTP_REFERER', '')).path - pattern = re.compile('^/courses/(?P[^/]+/[^/]+/[^/]+)/.*/?$') - match = re.match(pattern, url_path) - - if not match: - raise Http404 - course_id = match.groupdict().get('id', '') - course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) - - user_id = request.session.get('_auth_user_id') - software_name = request.POST.get('software') - generate = request.POST.get('generate', False) == 'true' - - try: - software = CourseSoftware.objects.get(name=software_name, - course_id=course_key) - except CourseSoftware.DoesNotExist: - raise Http404 - - try: - user = User.objects.get(id=user_id) - except User.DoesNotExist: - raise Http404 - - if generate: - software_license = get_or_create_license(user, software) - else: - software_license = get_license(user, software) - - if software_license: - response = {'serial': software_license.serial} - else: - response = {'error': 'No serial number found'} - - return HttpResponse(json.dumps(response), content_type='application/json') diff --git a/lms/envs/common.py b/lms/envs/common.py index 66f47211f7..593ac0bf06 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -1831,7 +1831,6 @@ INSTALLED_APPS = ( 'instructor', 'instructor_task', 'open_ended_grading', - 'licenses', 'openedx.core.djangoapps.course_groups', 'bulk_email', 'branding', diff --git a/lms/templates/licenses/serial_numbers.html b/lms/templates/licenses/serial_numbers.html deleted file mode 100644 index a9f048f18f..0000000000 --- a/lms/templates/licenses/serial_numbers.html +++ /dev/null @@ -1,11 +0,0 @@ -<%! from django.utils.translation import ugettext as _ %> -
-% for license in licenses: -
${license.software.name}:
- % if license.serial: -
${license.serial}
- % else: -
${_("None Available")}
- % endif -% endfor -
diff --git a/lms/urls.py b/lms/urls.py index 0f32ac6de8..0c7fe11750 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -310,14 +310,6 @@ if settings.COURSEWARE_ENABLED: name='xblock_resource_url', ), - # Software Licenses - - # TODO: for now, this is the endpoint of an ajax replay - # service that retrieve and assigns license numbers for - # software assigned to a course. The numbers have to be loaded - # into the database. - url(r'^software-licenses$', 'licenses.views.user_software_license', name="user_software_license"), - url( r'^courses/{}/xqueue/(?P[^/]*)/(?P.*?)/(?P[^/]*)$'.format( settings.COURSE_ID_PATTERN