From 19f737d544ed2c8f87ace5d1c4a88c10f053a81d Mon Sep 17 00:00:00 2001 From: stephensanchez Date: Tue, 25 Nov 2014 18:39:25 +0000 Subject: [PATCH] Creating a new Organization based preference model. --- ...e_userorgtag_user_org_key__chg_field_us.py | 110 ++++++++++++++++++ common/djangoapps/user_api/models.py | 17 +++ common/djangoapps/user_api/tests/factories.py | 12 +- .../djangoapps/user_api/tests/test_models.py | 48 +++++++- 4 files changed, 181 insertions(+), 6 deletions(-) create mode 100644 common/djangoapps/user_api/migrations/0004_auto__add_userorgtag__add_unique_userorgtag_user_org_key__chg_field_us.py diff --git a/common/djangoapps/user_api/migrations/0004_auto__add_userorgtag__add_unique_userorgtag_user_org_key__chg_field_us.py b/common/djangoapps/user_api/migrations/0004_auto__add_userorgtag__add_unique_userorgtag_user_org_key__chg_field_us.py new file mode 100644 index 0000000000..8ea25eb60f --- /dev/null +++ b/common/djangoapps/user_api/migrations/0004_auto__add_userorgtag__add_unique_userorgtag_user_org_key__chg_field_us.py @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding model 'UserOrgTag' + db.create_table('user_api_userorgtag', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('created', self.gf('model_utils.fields.AutoCreatedField')(default=datetime.datetime.now)), + ('modified', self.gf('model_utils.fields.AutoLastModifiedField')(default=datetime.datetime.now)), + ('user', self.gf('django.db.models.fields.related.ForeignKey')(related_name='+', to=orm['auth.User'])), + ('key', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)), + ('org', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)), + ('value', self.gf('django.db.models.fields.TextField')()), + )) + db.send_create_signal('user_api', ['UserOrgTag']) + + # Adding unique constraint on 'UserOrgTag', fields ['user', 'org', 'key'] + db.create_unique('user_api_userorgtag', ['user_id', 'org', 'key']) + + # Create a composite index of user_id, org, and key. + db.create_index('user_api_userorgtag', ['user_id', 'org', 'key']) + + # Changing field 'UserCourseTag.course_id' + db.alter_column('user_api_usercoursetag', 'course_id', self.gf('xmodule_django.models.CourseKeyField')(max_length=255)) + + def backwards(self, orm): + + # Delete the composite index of user_id, org, and key. + db.delete_index('user_api_userorgtag', ['user_id', 'org', 'key']) + + # Removing unique constraint on 'UserOrgTag', fields ['user', 'org', 'key'] + db.delete_unique('user_api_userorgtag', ['user_id', 'org', 'key']) + + # Deleting model 'UserOrgTag' + db.delete_table('user_api_userorgtag') + + # Changing field 'UserCourseTag.course_id' + db.alter_column('user_api_usercoursetag', 'course_id', self.gf('django.db.models.fields.CharField')(max_length=255)) + + 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'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': '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'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + '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'}) + }, + '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'}) + }, + 'user_api.usercoursetag': { + 'Meta': {'unique_together': "(('user', 'course_id', 'key'),)", 'object_name': 'UserCourseTag'}, + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['auth.User']"}), + 'value': ('django.db.models.fields.TextField', [], {}) + }, + 'user_api.userorgtag': { + 'Meta': {'unique_together': "(('user', 'org', 'key'),)", 'object_name': 'UserOrgTag'}, + 'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}), + 'org': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['auth.User']"}), + 'value': ('django.db.models.fields.TextField', [], {}) + }, + 'user_api.userpreference': { + 'Meta': {'unique_together': "(('user', 'key'),)", 'object_name': 'UserPreference'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'preferences'", 'to': "orm['auth.User']"}), + 'value': ('django.db.models.fields.TextField', [], {}) + } + } + + complete_apps = ['user_api'] diff --git a/common/djangoapps/user_api/models.py b/common/djangoapps/user_api/models.py index 3c49b43b9a..b9675c83c5 100644 --- a/common/djangoapps/user_api/models.py +++ b/common/djangoapps/user_api/models.py @@ -1,6 +1,7 @@ from django.contrib.auth.models import User from django.core.validators import RegexValidator from django.db import models +from model_utils.models import TimeStampedModel from xmodule_django.models import CourseKeyField @@ -59,3 +60,19 @@ class UserCourseTag(models.Model): class Meta: # pylint: disable=missing-docstring unique_together = ("user", "course_id", "key") + + +class UserOrgTag(TimeStampedModel): + """ Per-Organization user tags. + + Allows settings to be configured at an organization level. + + """ + user = models.ForeignKey(User, db_index=True, related_name="+") + key = models.CharField(max_length=255, db_index=True) + org = models.CharField(max_length=255, db_index=True) + value = models.TextField() + + class Meta: + """ Meta class for defining unique constraints. """ + unique_together = ("user", "org", "key") diff --git a/common/djangoapps/user_api/tests/factories.py b/common/djangoapps/user_api/tests/factories.py index 9aafc0d661..3308c2f2ab 100644 --- a/common/djangoapps/user_api/tests/factories.py +++ b/common/djangoapps/user_api/tests/factories.py @@ -2,7 +2,7 @@ from factory.django import DjangoModelFactory from factory import SubFactory from student.tests.factories import UserFactory -from user_api.models import UserPreference, UserCourseTag +from user_api.models import UserPreference, UserCourseTag, UserOrgTag from opaque_keys.edx.locations import SlashSeparatedCourseKey @@ -23,3 +23,13 @@ class UserCourseTagFactory(DjangoModelFactory): course_id = SlashSeparatedCourseKey('org', 'course', 'run') key = None value = None + + +class UserOrgTagFactory(DjangoModelFactory): + """ Simple factory class for generating UserOrgTags """ + FACTORY_FOR = UserOrgTag + + user = SubFactory(UserFactory) + org = 'org' + key = None + value = None diff --git a/common/djangoapps/user_api/tests/test_models.py b/common/djangoapps/user_api/tests/test_models.py index b187cd2360..fabcbec8d2 100644 --- a/common/djangoapps/user_api/tests/test_models.py +++ b/common/djangoapps/user_api/tests/test_models.py @@ -1,7 +1,8 @@ from django.db import IntegrityError from django.test import TestCase +from xmodule.modulestore.tests.factories import CourseFactory from student.tests.factories import UserFactory -from user_api.tests.factories import UserPreferenceFactory +from user_api.tests.factories import UserPreferenceFactory, UserCourseTagFactory, UserOrgTagFactory from user_api.models import UserPreference @@ -19,15 +20,52 @@ class UserPreferenceModelTest(TestCase): def test_arbitrary_values(self): user = UserFactory.create() - UserPreferenceFactory.create(user=user, key="testkey0", value="") - UserPreferenceFactory.create(user=user, key="testkey1", value="This is some English text!") - UserPreferenceFactory.create(user=user, key="testkey2", value="{'some': 'json'}") - UserPreferenceFactory.create( + self._create_and_assert(user=user, key="testkey0", value="") + self._create_and_assert(user=user, key="testkey1", value="This is some English text!") + self._create_and_assert(user=user, key="testkey2", value="{'some': 'json'}") + self._create_and_assert( user=user, key="testkey3", value="\xe8\xbf\x99\xe6\x98\xaf\xe4\xb8\xad\xe5\x9b\xbd\xe6\x96\x87\xe5\xad\x97'" ) + def _create_and_assert(self, user, key, value): + """Create a new preference and assert the values. """ + preference = UserPreferenceFactory.create(user=user, key=key, value=value) + self.assertEqual(preference.user, user) + self.assertEqual(preference.key, key) + self.assertEqual(preference.value, value) + return preference + + def test_create_user_course_tags(self): + """Create user preference tags and confirm properties are set accordingly. """ + user = UserFactory.create() + course = CourseFactory.create() + tag = UserCourseTagFactory.create(user=user, course_id=course.id, key="testkey", value="foobar") + self.assertEquals(tag.user, user) + self.assertEquals(tag.course_id, course.id) + self.assertEquals(tag.key, "testkey") + self.assertEquals(tag.value, "foobar") + + def test_create_user_org_tags(self): + """Create org specific user tags and confirm all properties are set """ + user = UserFactory.create() + course = CourseFactory.create() + tag = UserOrgTagFactory.create(user=user, org=course.id.org, key="testkey", value="foobar") + self.assertEquals(tag.user, user) + self.assertEquals(tag.org, course.id.org) + self.assertEquals(tag.key, "testkey") + self.assertEquals(tag.value, "foobar") + self.assertIsNotNone(tag.created) + self.assertIsNotNone(tag.modified) + + # Modify the tag and save it. Check if the modified timestamp is updated. + original_modified = tag.modified + tag.value = "barfoo" + tag.save() + self.assertEquals(tag.value, "barfoo") + self.assertNotEqual(original_modified, tag.modified) + def test_get_set_preference(self): # Checks that you can set a preference and get that preference later # Also, tests that no preference is returned for keys that are not set