瀏覽代碼

HUE-3847 [core] Enable multi-group selection for saved group configurations

Jenny Kim 9 年之前
父節點
當前提交
6efc737

+ 81 - 53
desktop/core/src/desktop/configuration/README.md

@@ -23,7 +23,7 @@
 
 Returns a JSON response with `status` and `configuration` where configuration contains a dictionary of all configurable apps and their defined configuration, as well as any default and group saved configurations.
 
-Each record in `configuration` will map to a dictionary that contains a **required** `properties` record which maps to a list of defined properties for the app. Optionally, the app may also contain a `default` list of properties, and/or `group` properties where each configured group ID is returned with corresponding properties.
+Each record in `configuration` will map to a dictionary that contains a **required** `properties` record which maps to a list of defined properties for the app. Optionally, the app may also contain a `default` list of properties, and/or `group` properties where each object includes a list of group ID with corresponding properties.
 
 #### Example Request
 GET /desktop/api/configurations/
@@ -126,56 +126,84 @@ None
                     ]
                 }
             ],
-            "groups": {
-                "1": [
-                    {
-                        "multiple": true,
-                        "value": [
-                            {
-                                "path": "/user/test/myudfs.jar",
-                                "type": "jar"
-                            }
-                        ],
-                        "nice_name": "Files",
-                        "key": "files",
-                        "help_text": "Add one or more files, jars, or archives to the list of resources.",
-                        "type": "hdfs-files"
-                    },
-                    {
-                        "multiple": true,
-                        "value": [
-                            {
-                                "class_name": "org.hue.udf.MyUpper",
-                                "name": "myUpper"
-                            }
-                        ],
-                        "nice_name": "Functions",
-                        "key": "functions",
-                        "help_text": "Add one or more registered UDFs (requires function name and fully-qualified class name).",
-                        "type": "functions"
-                    },
-                    {
-                        "multiple": true,
-                        "value": [
-                            {
-                                "key": "mapreduce.job.queuename",
-                                "value": "mr"
-                            }
-                        ],
-                        "nice_name": "Settings",
-                        "key": "settings",
-                        "help_text": "Hive and Hadoop configuration properties.",
-                        "type": "settings",
-                        "options": [
-                            "hive.map.aggr",
-                            "hive.exec.compress.output",
-                            "hive.exec.parallel",
-                            "hive.execution.engine",
-                            "mapreduce.job.queuename"
-                        ]
-                    }
-                ]
-            }
+            "groups": [
+                {
+                    "group_ids": [1, 2],
+                    "properties": [
+                        {
+                            "multiple": true,
+                            "value": [
+                                {
+                                    "path": "/user/test/myudfs.jar",
+                                    "type": "jar"
+                                }
+                            ],
+                            "nice_name": "Files",
+                            "key": "files",
+                            "help_text": "Add one or more files, jars, or archives to the list of resources.",
+                            "type": "hdfs-files"
+                        },
+                        {
+                            "multiple": true,
+                            "value": [
+                                {
+                                    "class_name": "org.hue.udf.MyUpper",
+                                    "name": "myUpper"
+                                }
+                            ],
+                            "nice_name": "Functions",
+                            "key": "functions",
+                            "help_text": "Add one or more registered UDFs (requires function name and fully-qualified class name).",
+                            "type": "functions"
+                        },
+                        {
+                            "multiple": true,
+                            "value": [
+                                {
+                                    "key": "mapreduce.job.queuename",
+                                    "value": "mr"
+                                }
+                            ],
+                            "nice_name": "Settings",
+                            "key": "settings",
+                            "help_text": "Hive and Hadoop configuration properties.",
+                            "type": "settings",
+                            "options": [
+                                "hive.map.aggr",
+                                "hive.exec.compress.output",
+                                "hive.exec.parallel",
+                                "hive.execution.engine",
+                                "mapreduce.job.queuename"
+                            ]
+                        }
+                    ]
+                },
+                {
+                    "group_ids": [3, 4, 5],
+                    "properties": [
+                        {
+                            "multiple": true,
+                            "value": [
+                                {
+                                    "key": "mapreduce.job.queuename",
+                                    "value": "mr"
+                                }
+                            ],
+                            "nice_name": "Settings",
+                            "key": "settings",
+                            "help_text": "Hive and Hadoop configuration properties.",
+                            "type": "settings",
+                            "options": [
+                                "hive.map.aggr",
+                                "hive.exec.compress.output",
+                                "hive.exec.parallel",
+                                "hive.execution.engine",
+                                "mapreduce.job.queuename"
+                            ]
+                        }
+                    ]
+                }
+            ]
         }
     }
 }
@@ -365,7 +393,7 @@ GET /desktop/api/configurations/user/?app=hive&user_id=1
     "configuration": {
         "is_default": false,
         "app": "hive",
-        "group": "default",
+        "groups": [1],
         "properties": [
             {
                 "multiple": true,
@@ -497,7 +525,7 @@ POST /desktop/api/configurations/user
     "configuration": {
         "is_default": false,
         "app": "hive",
-        "group": null,
+        "groups": [],
         "properties": [
             {
                 "multiple": true,

+ 31 - 42
desktop/core/src/desktop/configuration/api.py

@@ -130,37 +130,17 @@ def app_configuration_for_user(request):
 @api_error_handler
 @require_POST
 def delete_default_configuration(request):
-  app = request.POST.get('app')
-  is_default = request.POST.get('is_default', 'false')
-  group_id = request.POST.get('group_id')
-  user_id = request.POST.get('user_id')
+  config_id = request.POST.get('id')
 
-  if not app or not (is_default or group_id or user_id):
-    raise PopupException(_('save_default_configuration requires app and is_default, group_id or user_id'))
-
-  if is_default and is_default.lower() == 'true':
-    kwargs = {'app': app, 'is_default': True}
-  elif group_id:
-    try:
-      group = Group.objects.get(id=int(group_id))
-      kwargs = {'app': app, 'is_default': False, 'group': group}
-    except Group.DoesNotExist, e:
-      raise PopupException(_('Could not find group with ID: %s') % group_id)
-  elif user_id:
-    try:
-      user = User.objects.get(id=int(user_id))
-      kwargs = {'app': app, 'is_default': False, 'user': user}
-    except User.DoesNotExist, e:
-      raise PopupException(_('Could not find user with ID: %s') % user_id)
-  else:
-    raise PopupException(_('Cannot find configuration for %(app)s with: is_default=%(is_default)s, group_id=%(group_id)s, user_id=%(user_id)s') %
-                         {'app': app, 'is_default': is_default, 'group_id': group_id, 'user_id': user_id})
+  if not config_id:
+    raise PopupException(_('delete_default_configuration requires id of configuration.'))
 
   try:
-    DefaultConfiguration.objects.get(**kwargs).delete()
+    config_id = int(config_id)
+    config = DefaultConfiguration.objects.get(id=config_id)
+    config.delete()
   except DefaultConfiguration.DoesNotExist, e:
-    raise PopupException(_('Cannot find configuration for %(app)s with: is_default=%(is_default)s, group_id=%(group_id)s, user_id=%(user_id)s') %
-                         {'app': app, 'is_default': is_default, 'group_id': group_id, 'user_id': user_id})
+    raise PopupException(_('Could not find configuration with ID: %d') % config_id)
 
   return JsonResponse({
     'status': 0,
@@ -191,10 +171,13 @@ def _get_default_configurations():
       app_configs[app_name].update({'default': default_config.properties_list})
 
     # Get group configs
-    if DefaultConfiguration.objects.filter(app=app_name, group__isnull=False).exists():
-      app_configs[app_name].update({'groups': {}})
-      for grp_config in DefaultConfiguration.objects.filter(app=app_name, group__isnull=False).all():
-        app_configs[app_name]['groups'].update({grp_config.group.id: grp_config.properties_list})
+    if DefaultConfiguration.objects.filter(app=app_name, groups__isnull=False).exists():
+      app_configs[app_name].update({'groups': []})
+      for grp_config in DefaultConfiguration.objects.filter(app=app_name, groups__isnull=False).all():
+        app_configs[app_name]['groups'].append({
+          'group_ids': [group.id for group in grp_config.groups.all()],
+          'properties': grp_config.properties_list
+        })
 
   return app_configs
 
@@ -218,7 +201,7 @@ def _update_default_and_group_configurations(configurations):
   """
   with transaction.atomic():
     # delete all previous default and group configurations
-    DefaultConfiguration.objects.filter(Q(is_default=True) | Q(group__isnull=False)).delete()
+    DefaultConfiguration.objects.filter(Q(is_default=True) | Q(groups__isnull=False)).delete()
 
     for app, configs in configurations.items():
       if 'default' in configs:
@@ -227,23 +210,29 @@ def _update_default_and_group_configurations(configurations):
         LOG.info('Saved default configuration for app: %s' % app)
 
       if 'groups' in configs:
-        for group_id, properties in configs['groups'].items():
+        for group_config in configs['groups']:
+          group_ids = group_config.get('group_ids')
+          properties = group_config.get('properties')
           try:
-            group = Group.objects.get(id=int(group_id))
+            groups = [Group.objects.get(id=group_id) for group_id in group_ids]
+            _save_configuration(app, properties, is_default=False, groups=groups)
           except Group.DoesNotExist, e:
-            raise PopupException(_('Could not find group with ID: %s') % group_id)
-          _save_configuration(app, properties, is_default=False, group=group)
-          LOG.info('Saved group configuration for app: %s and group_id: %s' % (app, group_id))
+            raise PopupException(_('Could not find one or more groups with IDs: %s') % ', '.join(group_ids))
 
   return _get_default_configurations()
 
 
-def _save_configuration(app, properties, is_default=False, group=None, user=None):
-  if not (is_default or group or user):
-    raise PopupException(_('_save_configuration requires app, properties, and is_default, group_id or user_id'))
+def _save_configuration(app, properties, is_default=False, groups=None, user=None):
+  if not (is_default or groups or user):
+    raise PopupException(_('_save_configuration requires app, properties, and is_default, groups or user_id'))
+
+  if is_default or user is not None:
+    kwargs = {'app': app, 'is_default': is_default, 'user': user}
+    config, created = DefaultConfiguration.objects.get_or_create(**kwargs)
+  else:  # group config
+    config = DefaultConfiguration.objects.create(app=app, is_default=False, user=None)
+    config.groups.add(*groups)
 
-  kwargs = {'app': app, 'is_default': is_default, 'group': group, 'user': user}
-  config, created = DefaultConfiguration.objects.get_or_create(**kwargs)
   config.properties = json.dumps(properties)
   config.save()
   return config

+ 17 - 13
desktop/core/src/desktop/configuration/tests.py

@@ -91,9 +91,10 @@ class TestDefaultConfiguration(object):
             'options': []
           }
         ],
-        'groups': {
-          str(self.group.id): [
-            {
+        'groups': [
+          {
+            'group_ids': [self.group.id],
+            'properties': {
               'multiple': True,
               'value': [{'key': 'hive.execution.engine', 'value': 'spark'}],
               'nice_name': 'Settings',
@@ -102,8 +103,8 @@ class TestDefaultConfiguration(object):
               'type': 'settings',
               'options': []
             }
-          ]
-        }
+          }
+        ]
       }
     }
 
@@ -115,8 +116,8 @@ class TestDefaultConfiguration(object):
     config = DefaultConfiguration.objects.get(app='hive', is_default=True)
     assert_equal(config.properties_list, configuration['hive']['default'], config.properties_list)
 
-    config = DefaultConfiguration.objects.get(app='hive', group=self.group)
-    assert_equal(config.properties_list, configuration['hive']['groups'][str(self.group.id)], config.properties_list)
+    config = DefaultConfiguration.objects.get(app='hive', groups__in=[self.group])
+    assert_equal(config.properties_list, configuration['hive']['groups'][0]['properties'], config.properties_list)
 
 
   def test_get_default_configurations(self):
@@ -188,7 +189,7 @@ class TestDefaultConfiguration(object):
     assert_equal(content['configuration']['app'], 'hive', content)
     assert_equal(content['configuration']['is_default'], True, content)
     assert_equal(content['configuration']['user'], None, content)
-    assert_equal(content['configuration']['group'], None, content)
+    assert_equal(content['configuration']['group_ids'], [], content)
     assert_equal(content['configuration']['properties'], properties, content)
 
     # Creating a group configuration returns group config
@@ -204,9 +205,12 @@ class TestDefaultConfiguration(object):
     configuration = {
       app: {
         'default': properties,
-        'groups': {
-          str(self.group.id): group_properties
-        }
+        'groups': [
+          {
+            'group_ids': [self.group.id],
+            'properties': group_properties
+          }
+        ]
       }
     }
 
@@ -220,7 +224,7 @@ class TestDefaultConfiguration(object):
     assert_equal(content['configuration']['app'], 'hive', content)
     assert_equal(content['configuration']['is_default'], False, content)
     assert_equal(content['configuration']['user'], None, content)
-    assert_equal(content['configuration']['group'], self.group.name, content)
+    assert_equal(content['configuration']['group_ids'], [self.group.id], content)
     assert_equal(content['configuration']['properties'], group_properties, content)
 
     # Creating a user configuration returns user config
@@ -242,5 +246,5 @@ class TestDefaultConfiguration(object):
     assert_equal(content['configuration']['app'], 'hive', content)
     assert_equal(content['configuration']['is_default'], False, content)
     assert_equal(content['configuration']['user'], self.user.username, content)
-    assert_equal(content['configuration']['group'], None, content)
+    assert_equal(content['configuration']['group_ids'], [], content)
     assert_equal(content['configuration']['properties'], user_properties, content)

+ 158 - 0
desktop/core/src/desktop/migrations/0022_auto__del_field_defaultconfiguration_group__del_unique_defaultconfigur.py

@@ -0,0 +1,158 @@
+# -*- coding: utf-8 -*-
+from south.utils import datetime_utils as datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        # Removing unique constraint on 'DefaultConfiguration', fields ['app', 'is_default', 'group', 'user']
+        db.delete_unique(u'desktop_defaultconfiguration', ['app', 'is_default', 'group_id', 'user_id'])
+
+        # Deleting field 'DefaultConfiguration.group'
+        db.delete_column(u'desktop_defaultconfiguration', 'group_id')
+
+        # Adding M2M table for field groups on 'DefaultConfiguration'
+        m2m_table_name = 'defaultconfiguration_groups'
+        db.create_table(m2m_table_name, (
+            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+            ('defaultconfiguration', models.ForeignKey(orm[u'desktop.defaultconfiguration'], null=False)),
+            ('group', models.ForeignKey(orm[u'auth.group'], null=False))
+        ))
+        db.create_unique(m2m_table_name, ['defaultconfiguration_id', 'group_id'])
+
+        # Adding unique constraint on 'DefaultConfiguration', fields ['app', 'is_default', 'user']
+        db.create_unique(u'desktop_defaultconfiguration', ['app', 'is_default', 'user_id'])
+
+
+    def backwards(self, orm):
+        # Removing unique constraint on 'DefaultConfiguration', fields ['app', 'is_default', 'user']
+        db.delete_unique(u'desktop_defaultconfiguration', ['app', 'is_default', 'user_id'])
+
+        # Adding field 'DefaultConfiguration.group'
+        db.add_column(u'desktop_defaultconfiguration', 'group',
+                      self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.Group'], null=True, blank=True),
+                      keep_default=False)
+
+        # Removing M2M table for field groups on 'DefaultConfiguration'
+        db.delete_table('defaultconfiguration_groups')
+
+        # Adding unique constraint on 'DefaultConfiguration', fields ['app', 'is_default', 'group', 'user']
+        db.create_unique(u'desktop_defaultconfiguration', ['app', 'is_default', 'group_id', 'user_id'])
+
+
+    models = {
+        u'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        u'auth.permission': {
+            'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        u'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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
+            u'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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        u'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'}),
+            u'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'})
+        },
+        u'desktop.defaultconfiguration': {
+            'Meta': {'ordering': "['app', '-is_default', 'user']", 'unique_together': "(('app', 'is_default', 'user'),)", 'object_name': 'DefaultConfiguration'},
+            'app': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'db_index': 'True', 'to': u"orm['auth.Group']", 'db_table': "'defaultconfiguration_groups'", 'symmetrical': 'False'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_default': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
+            'properties': ('django.db.models.fields.TextField', [], {'default': "'[]'"}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
+        },
+        u'desktop.document': {
+            'Meta': {'unique_together': "(('content_type', 'object_id'),)", 'object_name': 'Document'},
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
+            'description': ('django.db.models.fields.TextField', [], {'default': "''"}),
+            'extra': ('django.db.models.fields.TextField', [], {'default': "''"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'doc_owner'", 'to': u"orm['auth.User']"}),
+            'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['desktop.DocumentTag']", 'db_index': 'True', 'symmetrical': 'False'}),
+            'version': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'})
+        },
+        u'desktop.document2': {
+            'Meta': {'ordering': "['-last_modified']", 'unique_together': "(('uuid', 'version', 'is_history'),)", 'object_name': 'Document2'},
+            'data': ('django.db.models.fields.TextField', [], {'default': "'{}'"}),
+            'dependencies': ('django.db.models.fields.related.ManyToManyField', [], {'db_index': 'True', 'related_name': "'dependents'", 'symmetrical': 'False', 'to': u"orm['desktop.Document2']"}),
+            'description': ('django.db.models.fields.TextField', [], {'default': "''"}),
+            'extra': ('django.db.models.fields.TextField', [], {'default': "''"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_history': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
+            'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
+            'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'doc2_owner'", 'to': u"orm['auth.User']"}),
+            'parent_directory': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': u"orm['desktop.Document2']"}),
+            'type': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'db_index': 'True'}),
+            'uuid': ('django.db.models.fields.CharField', [], {'default': "'15b84c63-8dab-44c1-900b-659ec11ad5bc'", 'max_length': '36', 'db_index': 'True'}),
+            'version': ('django.db.models.fields.SmallIntegerField', [], {'default': '1', 'db_index': 'True'})
+        },
+        u'desktop.document2permission': {
+            'Meta': {'unique_together': "(('doc', 'perms'),)", 'object_name': 'Document2Permission'},
+            'doc': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['desktop.Document2']"}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'db_index': 'True', 'to': u"orm['auth.Group']", 'db_table': "'documentpermission2_groups'", 'symmetrical': 'False'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'perms': ('django.db.models.fields.CharField', [], {'default': "'read'", 'max_length': '10', 'db_index': 'True'}),
+            'users': ('django.db.models.fields.related.ManyToManyField', [], {'db_index': 'True', 'to': u"orm['auth.User']", 'db_table': "'documentpermission2_users'", 'symmetrical': 'False'})
+        },
+        u'desktop.documentpermission': {
+            'Meta': {'unique_together': "(('doc', 'perms'),)", 'object_name': 'DocumentPermission'},
+            'doc': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['desktop.Document']"}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'db_index': 'True', 'to': u"orm['auth.Group']", 'db_table': "'documentpermission_groups'", 'symmetrical': 'False'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'perms': ('django.db.models.fields.CharField', [], {'default': "'read'", 'max_length': '10'}),
+            'users': ('django.db.models.fields.related.ManyToManyField', [], {'db_index': 'True', 'to': u"orm['auth.User']", 'db_table': "'documentpermission_users'", 'symmetrical': 'False'})
+        },
+        u'desktop.documenttag': {
+            'Meta': {'unique_together': "(('owner', 'tag'),)", 'object_name': 'DocumentTag'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}),
+            'tag': ('django.db.models.fields.SlugField', [], {'max_length': '50'})
+        },
+        u'desktop.settings': {
+            'Meta': {'object_name': 'Settings'},
+            'collect_usage': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'tours_and_tutorials': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'})
+        },
+        u'desktop.userpreferences': {
+            'Meta': {'object_name': 'UserPreferences'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'key': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}),
+            'value': ('django.db.models.fields.TextField', [], {'max_length': '4096'})
+        }
+    }
+
+    complete_apps = ['desktop']

+ 5 - 5
desktop/core/src/desktop/models.py

@@ -87,7 +87,7 @@ class DefaultConfigurationManager(models.Manager):
     except DefaultConfiguration.DoesNotExist:
       pass
 
-    configs = super(DefaultConfigurationManager, self).get_queryset().filter(app=app, group__in=user.groups.all())
+    configs = super(DefaultConfigurationManager, self).get_queryset().filter(app=app, groups__in=user.groups.all())
     if configs.count() > 0:
       return configs[0]
 
@@ -108,14 +108,13 @@ class DefaultConfiguration(models.Model):
   properties = models.TextField(default='[]', help_text=_t('JSON-formatted default properties values.'))
 
   is_default = models.BooleanField(default=False, db_index=True)
-  group = models.ForeignKey(auth_models.Group, blank=True, null=True, db_index=True)
+  groups = models.ManyToManyField(auth_models.Group, db_index=True, db_table='defaultconfiguration_groups')
   user = models.ForeignKey(auth_models.User, blank=True, null=True, db_index=True)
 
   objects = DefaultConfigurationManager()
 
   class Meta:
-    unique_together = ('app', 'is_default', 'group', 'user')
-    ordering = ["app", "-is_default", "group", "user"]
+    ordering = ["app", "-is_default", "user"]
 
 
   @property
@@ -138,10 +137,11 @@ class DefaultConfiguration(models.Model):
 
   def to_dict(self):
     return {
+      'id': self.id,
       'app': self.app,
       'properties': self.properties_list,
       'is_default': self.is_default,
-      'group': self.group.name if self.group else None,
+      'group_ids': [group.id for group in self.groups.all()],
       'user': self.user.username if self.user else None
     }