From 693408eb4ea0f0fef8b1e93fd6af14b8fd4f4d6d Mon Sep 17 00:00:00 2001 From: Dmitry Viskov Date: Fri, 3 Jun 2016 15:44:39 +0300 Subject: [PATCH] Ability to create two or more LTI consumers through the Django admin with an empty instance_guid field. --- .../migrations/0003_auto_20161118_1040.py | 20 +++++++++++++ lms/djangoapps/lti_provider/models.py | 3 +- .../lti_provider/tests/test_outcomes.py | 24 ++++++++++++++++ openedx/core/djangolib/fields.py | 28 +++++++++++++++++++ 4 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 lms/djangoapps/lti_provider/migrations/0003_auto_20161118_1040.py create mode 100644 openedx/core/djangolib/fields.py diff --git a/lms/djangoapps/lti_provider/migrations/0003_auto_20161118_1040.py b/lms/djangoapps/lti_provider/migrations/0003_auto_20161118_1040.py new file mode 100644 index 0000000000..394439e891 --- /dev/null +++ b/lms/djangoapps/lti_provider/migrations/0003_auto_20161118_1040.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +import openedx.core.djangolib.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('lti_provider', '0002_auto_20160325_0407'), + ] + + operations = [ + migrations.AlterField( + model_name='lticonsumer', + name='instance_guid', + field=openedx.core.djangolib.fields.CharNullField(max_length=255, unique=True, null=True, blank=True), + ), + ] diff --git a/lms/djangoapps/lti_provider/models.py b/lms/djangoapps/lti_provider/models.py index 34cf9d3492..2dcc0cb61d 100644 --- a/lms/djangoapps/lti_provider/models.py +++ b/lms/djangoapps/lti_provider/models.py @@ -13,6 +13,7 @@ from django.db import models import logging from openedx.core.djangoapps.xmodule_django.models import CourseKeyField, UsageKeyField +from openedx.core.djangolib.fields import CharNullField from provider.utils import short_token @@ -28,7 +29,7 @@ class LtiConsumer(models.Model): consumer_name = models.CharField(max_length=255, unique=True) consumer_key = models.CharField(max_length=32, unique=True, db_index=True, default=short_token) consumer_secret = models.CharField(max_length=32, unique=True, default=short_token) - instance_guid = models.CharField(max_length=255, blank=True, null=True, unique=True) + instance_guid = CharNullField(max_length=255, blank=True, null=True, unique=True) @staticmethod def get_or_supplement(instance_guid, consumer_key): diff --git a/lms/djangoapps/lti_provider/tests/test_outcomes.py b/lms/djangoapps/lti_provider/tests/test_outcomes.py index 85394a6336..e7a195554d 100644 --- a/lms/djangoapps/lti_provider/tests/test_outcomes.py +++ b/lms/djangoapps/lti_provider/tests/test_outcomes.py @@ -346,6 +346,30 @@ class TestAssignmentsForProblem(ModuleStoreTestCase): assignment.save() return assignment + def test_create_two_lti_consumers_with_empty_instance_guid(self): + """ + Test ability to create two or more LTI consumers through the Django admin + with empty instance_guid field. + A blank guid field is required when a customer enables a new secret/key combination for + LTI integration with their LMS. + """ + lti_consumer_first = LtiConsumer( + consumer_name='lti_consumer_name_second', + consumer_key='lti_consumer_key_second', + consumer_secret='lti_consumer_secret_second', + instance_guid='' + ) + lti_consumer_first.save() + lti_consumer_second = LtiConsumer( + consumer_name='lti_consumer_name_third', + consumer_key='lti_consumer_key_third', + consumer_secret='lti_consumer_secret_third', + instance_guid='' + ) + lti_consumer_second.save() + count = LtiConsumer.objects.count() + self.assertEqual(count, 3) + def test_with_no_graded_assignments(self): with check_mongo_calls(3): assignments = outcomes.get_assignments_for_problem( diff --git a/openedx/core/djangolib/fields.py b/openedx/core/djangolib/fields.py new file mode 100644 index 0000000000..f1087c55d5 --- /dev/null +++ b/openedx/core/djangolib/fields.py @@ -0,0 +1,28 @@ +""" +Custom Django fields. +""" +from django.db import models + + +class CharNullField(models.CharField): + """CharField that stores NULL but returns ''""" + + description = "CharField that stores NULL but returns ''" + + def to_python(self, value): + """Converts the value into the correct Python object.""" + if isinstance(value, models.CharField): + return value + if value is None: + return "" + else: + return value + + def get_db_prep_value(self, value, connection, prepared=False): + """Converts value to a backend-specific value.""" + if not prepared: + value = self.get_prep_value(value) + if value == "": + return None + else: + return value