From bccb982d735bb0a2d6d83fa545389ca9ac831ab8 Mon Sep 17 00:00:00 2001 From: jlaine Date: Mon, 31 May 2010 17:30:22 +0000 Subject: [PATCH 01/16] introduce Compiler for django 1.2 compatibility git-svn-id: https://svn.bolloretelecom.eu/opensource/django-ldapdb/trunk@898 e071eeec-0327-468d-9b6a-08194a12b294 --- ldapdb/models/query.py | 118 +++++++++++++++++++++++------------------ 1 file changed, 67 insertions(+), 51 deletions(-) diff --git a/ldapdb/models/query.py b/ldapdb/models/query.py index d49b44a..fc211b4 100644 --- a/ldapdb/models/query.py +++ b/ldapdb/models/query.py @@ -63,6 +63,67 @@ class Constraint(BaseConstraint): return (self.alias, self.col, db_type), params +class Compiler(object): + def __init__(self, query, connection, using): + self.query = query + self.connection = connection + self.using = using + + def results_iter(self): + query = self.query + + filterstr = ''.join(['(objectClass=%s)' % cls for cls in query.model.object_classes]) + sql, params = query.where.as_sql() + filterstr += sql + filterstr = '(&%s)' % filterstr + attrlist = [ x.db_column for x in query.model._meta.local_fields if x.db_column ] + + try: + vals = self.connection.search_s( + query.model.base_dn, + ldap.SCOPE_SUBTREE, + filterstr=filterstr, + attrlist=attrlist, + ) + except: + raise query.model.DoesNotExist + + # perform sorting + if query.extra_order_by: + ordering = query.extra_order_by + elif not query.default_ordering: + ordering = query.order_by + else: + ordering = query.order_by or query.model._meta.ordering + def cmpvals(x, y): + for field in ordering: + if field.startswith('-'): + field = field[1:] + negate = True + else: + negate = False + attr = query.model._meta.get_field(field).db_column + attr_x = x[1].get(attr, '').lower() + attr_y = y[1].get(attr, '').lower() + val = negate and cmp(attr_y, attr_x) or cmp(attr_x, attr_y) + if val: + return val + return 0 + vals = sorted(vals, cmp=cmpvals) + + # process results + for dn, attrs in vals: + row = [] + for field in iter(query.model._meta.fields): + if field.attname == 'dn': + row.append(dn) + elif hasattr(field, 'from_ldap'): + row.append(field.from_ldap(attrs.get(field.db_column, []), connection=self.connection)) + else: + row.append(None) + yield row + + class WhereNode(BaseWhereNode): def add(self, data, connector): if not isinstance(data, (list, tuple)): @@ -128,7 +189,7 @@ class Query(BaseQuery): filterstr = '(&%s)' % filterstr try: - vals = ldapdb.connection.search_s( + vals = self.connection.search_s( self.model.base_dn, ldap.SCOPE_SUBTREE, filterstr=filterstr, @@ -139,57 +200,12 @@ class Query(BaseQuery): return len(vals) - def results_iter(self): - filterstr = ''.join(['(objectClass=%s)' % cls for cls in self.model.object_classes]) - sql, params = self.where.as_sql() - filterstr += sql - filterstr = '(&%s)' % filterstr - attrlist = [ x.db_column for x in self.model._meta.local_fields if x.db_column ] + def get_compiler(self, using=None, connection=None): + return Compiler(self, ldapdb.connection, using) - try: - vals = ldapdb.connection.search_s( - self.model.base_dn, - ldap.SCOPE_SUBTREE, - filterstr=filterstr, - attrlist=attrlist, - ) - except: - raise self.model.DoesNotExist - - # perform sorting - if self.extra_order_by: - ordering = self.extra_order_by - elif not self.default_ordering: - ordering = self.order_by - else: - ordering = self.order_by or self.model._meta.ordering - def cmpvals(x, y): - for field in ordering: - if field.startswith('-'): - field = field[1:] - negate = True - else: - negate = False - attr = self.model._meta.get_field(field).db_column - attr_x = x[1].get(attr, '').lower() - attr_y = y[1].get(attr, '').lower() - val = negate and cmp(attr_y, attr_x) or cmp(attr_x, attr_y) - if val: - return val - return 0 - vals = sorted(vals, cmp=cmpvals) - - # process results - for dn, attrs in vals: - row = [] - for field in iter(self.model._meta.fields): - if field.attname == 'dn': - row.append(dn) - elif hasattr(field, 'from_ldap'): - row.append(field.from_ldap(attrs.get(field.db_column, []), connection=ldapdb.connection)) - else: - row.append(None) - yield row + def results_iter(self): + "For django 1.1 compatibility" + return self.get_compiler().results_iter() class QuerySet(BaseQuerySet): def __init__(self, model=None, query=None, using=None): -- 2.30.2 From 8a9319586dc6fb44b83ba161bcc5a651da536cf1 Mon Sep 17 00:00:00 2001 From: jlaine Date: Mon, 31 May 2010 17:33:34 +0000 Subject: [PATCH 02/16] add settings for testing purposes git-svn-id: https://svn.bolloretelecom.eu/opensource/django-ldapdb/trunk@899 e071eeec-0327-468d-9b6a-08194a12b294 --- settings.py | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 settings.py diff --git a/settings.py b/settings.py new file mode 100644 index 0000000..d8f7bb4 --- /dev/null +++ b/settings.py @@ -0,0 +1,86 @@ +# Django settings for django-ldapdb project. + +DEBUG = True +TEMPLATE_DEBUG = DEBUG + +ADMINS = ( + # ('Your Name', 'your_email@domain.com'), +) + +MANAGERS = ADMINS + +DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. +DATABASE_NAME = 'ldapdb.db' # Or path to database file if using sqlite3. +DATABASE_USER = '' # Not used with sqlite3. +DATABASE_PASSWORD = '' # Not used with sqlite3. +DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3. +DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3. + +# Local time zone for this installation. Choices can be found here: +# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name +# although not all choices may be available on all operating systems. +# If running in a Windows environment this must be set to the same as your +# system time zone. +TIME_ZONE = 'America/Chicago' + +# Language code for this installation. All choices can be found here: +# http://www.i18nguy.com/unicode/language-identifiers.html +LANGUAGE_CODE = 'en-us' + +SITE_ID = 1 + +# If you set this to False, Django will make some optimizations so as not +# to load the internationalization machinery. +USE_I18N = True + +# Absolute path to the directory that holds media. +# Example: "/home/media/media.lawrence.com/" +MEDIA_ROOT = '' + +# URL that handles the media served from MEDIA_ROOT. Make sure to use a +# trailing slash if there is a path component (optional in other cases). +# Examples: "http://media.lawrence.com", "http://example.com/media/" +MEDIA_URL = '' + +# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a +# trailing slash. +# Examples: "http://foo.com/media/", "/media/". +ADMIN_MEDIA_PREFIX = '/media/' + +# Make this unique, and don't share it with anybody. +SECRET_KEY = 'some_random_secret_key' + +# List of callables that know how to import templates from various sources. +TEMPLATE_LOADERS = ( + 'django.template.loaders.filesystem.load_template_source', + 'django.template.loaders.app_directories.load_template_source', +# 'django.template.loaders.eggs.load_template_source', +) + +MIDDLEWARE_CLASSES = ( + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', +) + +ROOT_URLCONF = 'urls' + +TEMPLATE_DIRS = ( + # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". + # Always use forward slashes, even on Windows. + # Don't forget to use absolute paths, not relative paths. +) + +INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + 'ldapdb', + 'examples', + 'django.contrib.admin', +) + +LDAPDB_BIND_DN="cn=admin,dc=nodomain" +LDAPDB_BIND_PASSWORD="test" +LDAPDB_SERVER_URI="ldap://" -- 2.30.2 From 59f6aaa70d7a7fca741ba4849b2d0604db32bbdc Mon Sep 17 00:00:00 2001 From: jlaine Date: Mon, 31 May 2010 18:00:08 +0000 Subject: [PATCH 03/16] test admin interface search git-svn-id: https://svn.bolloretelecom.eu/opensource/django-ldapdb/trunk@900 e071eeec-0327-468d-9b6a-08194a12b294 --- examples/admin.py | 1 + examples/tests.py | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/examples/admin.py b/examples/admin.py index 8948bbc..8b33ecb 100644 --- a/examples/admin.py +++ b/examples/admin.py @@ -23,6 +23,7 @@ from examples.models import LdapGroup, LdapUser class LdapGroupAdmin(admin.ModelAdmin): exclude = ['dn', 'usernames'] + search_fields = ['name'] class LdapUserAdmin(admin.ModelAdmin): exclude = ['dn', 'password', 'photo'] diff --git a/examples/tests.py b/examples/tests.py index f98fbdf..08d85b7 100644 --- a/examples/tests.py +++ b/examples/tests.py @@ -162,6 +162,12 @@ class AdminTestCase(BaseTestCase): g.usernames = ['foouser', 'baruser'] g.save() + g = LdapGroup() + g.name = "bargroup" + g.gid = 1001 + g.usernames = ['zoouser', 'baruser'] + g.save() + u = LdapUser() u.first_name = "Foo" u.last_name = "User" @@ -189,6 +195,10 @@ class AdminTestCase(BaseTestCase): self.assertContains(response, "foogroup") self.assertContains(response, "1000") + def test_group_search(self): + response = self.client.get('/admin/examples/ldapgroup/?q=foo') + self.assertContains(response, "foogroup") + def test_user_list(self): response = self.client.get('/admin/examples/ldapuser/') self.assertContains(response, "Ldap users") -- 2.30.2 From a6255cb185ff6ce55d8760ef9b3aefbb9ddc4194 Mon Sep 17 00:00:00 2001 From: jlaine Date: Mon, 31 May 2010 18:07:06 +0000 Subject: [PATCH 04/16] fix ordering git-svn-id: https://svn.bolloretelecom.eu/opensource/django-ldapdb/trunk@901 e071eeec-0327-468d-9b6a-08194a12b294 --- ldapdb/models/query.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ldapdb/models/query.py b/ldapdb/models/query.py index fc211b4..d0958b1 100644 --- a/ldapdb/models/query.py +++ b/ldapdb/models/query.py @@ -96,15 +96,15 @@ class Compiler(object): else: ordering = query.order_by or query.model._meta.ordering def cmpvals(x, y): - for field in ordering: - if field.startswith('-'): - field = field[1:] + for fieldname in ordering: + if fieldname.startswith('-'): + fieldname = fieldname[1:] negate = True else: negate = False - attr = query.model._meta.get_field(field).db_column - attr_x = x[1].get(attr, '').lower() - attr_y = y[1].get(attr, '').lower() + field = query.model._meta.get_field(fieldname) + attr_x = field.from_ldap(x[1].get(field.db_column, []), connection=self.connection).lower() + attr_y = field.from_ldap(y[1].get(field.db_column, []), connection=self.connection).lower() val = negate and cmp(attr_y, attr_x) or cmp(attr_x, attr_y) if val: return val -- 2.30.2 From 82901736cde9aae9fbe8b6acc0aad2141ecd9583 Mon Sep 17 00:00:00 2001 From: jlaine Date: Mon, 31 May 2010 18:28:59 +0000 Subject: [PATCH 05/16] fix ordering on int fields git-svn-id: https://svn.bolloretelecom.eu/opensource/django-ldapdb/trunk@902 e071eeec-0327-468d-9b6a-08194a12b294 --- ldapdb/models/query.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ldapdb/models/query.py b/ldapdb/models/query.py index d0958b1..bbb46e2 100644 --- a/ldapdb/models/query.py +++ b/ldapdb/models/query.py @@ -103,8 +103,13 @@ class Compiler(object): else: negate = False field = query.model._meta.get_field(fieldname) - attr_x = field.from_ldap(x[1].get(field.db_column, []), connection=self.connection).lower() - attr_y = field.from_ldap(y[1].get(field.db_column, []), connection=self.connection).lower() + attr_x = field.from_ldap(x[1].get(field.db_column, []), connection=self.connection) + attr_y = field.from_ldap(y[1].get(field.db_column, []), connection=self.connection) + # perform case insensitive comparison + if hasattr(attr_x, 'lower'): + attr_x = attr_x.lower() + if hasattr(attr_y, 'lower'): + attr_y = attr_y.lower() val = negate and cmp(attr_y, attr_x) or cmp(attr_x, attr_y) if val: return val -- 2.30.2 From 9c88ec7a692ff145e42b06d332cdf614efa33c99 Mon Sep 17 00:00:00 2001 From: jlaine Date: Mon, 31 May 2010 18:31:20 +0000 Subject: [PATCH 06/16] improve unit tests git-svn-id: https://svn.bolloretelecom.eu/opensource/django-ldapdb/trunk@903 e071eeec-0327-468d-9b6a-08194a12b294 --- examples/admin.py | 2 ++ examples/tests.py | 55 +++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 48 insertions(+), 9 deletions(-) diff --git a/examples/admin.py b/examples/admin.py index 8b33ecb..67282a6 100644 --- a/examples/admin.py +++ b/examples/admin.py @@ -23,10 +23,12 @@ from examples.models import LdapGroup, LdapUser class LdapGroupAdmin(admin.ModelAdmin): exclude = ['dn', 'usernames'] + list_display = ['name', 'gid'] search_fields = ['name'] class LdapUserAdmin(admin.ModelAdmin): exclude = ['dn', 'password', 'photo'] + list_display = ['username', 'uid'] search_fields = ['first_name', 'last_name', 'full_name', 'username'] admin.site.register(LdapGroup, LdapGroupAdmin) diff --git a/examples/tests.py b/examples/tests.py index 08d85b7..70d53e3 100644 --- a/examples/tests.py +++ b/examples/tests.py @@ -18,16 +18,15 @@ # along with this program. If not, see . # -import ldap - from django.test import TestCase -from ldapdb import connection +import ldap +import ldapdb from examples.models import LdapUser, LdapGroup class BaseTestCase(TestCase): def setUp(self): - cursor = connection._cursor() + cursor = ldapdb.connection._cursor() for base in [LdapGroup.base_dn, LdapUser.base_dn]: rdn = base.split(',')[0] key, val = rdn.split('=') @@ -38,7 +37,7 @@ class BaseTestCase(TestCase): pass def tearDown(self): - cursor = connection._cursor() + cursor = ldapdb.connection._cursor() for base in [LdapGroup.base_dn, LdapUser.base_dn]: try: results = cursor.connection.search_s(base, ldap.SCOPE_SUBTREE) @@ -121,7 +120,7 @@ class UserTestCase(BaseTestCase): u.group = 1000 u.home_directory = "/home/foouser" - u.uid = 1000 + u.uid = 2000 u.username = "foouser" u.photo = '\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x01\x00H\x00H\x00\x00\xff\xfe\x00\x1cCreated with GIMP on a Mac\xff\xdb\x00C\x00\x05\x03\x04\x04\x04\x03\x05\x04\x04\x04\x05\x05\x05\x06\x07\x0c\x08\x07\x07\x07\x07\x0f\x0b\x0b\t\x0c\x11\x0f\x12\x12\x11\x0f\x11\x11\x13\x16\x1c\x17\x13\x14\x1a\x15\x11\x11\x18!\x18\x1a\x1d\x1d\x1f\x1f\x1f\x13\x17"$"\x1e$\x1c\x1e\x1f\x1e\xff\xdb\x00C\x01\x05\x05\x05\x07\x06\x07\x0e\x08\x08\x0e\x1e\x14\x11\x14\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\xff\xc0\x00\x11\x08\x00\x08\x00\x08\x03\x01"\x00\x02\x11\x01\x03\x11\x01\xff\xc4\x00\x15\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\xff\xc4\x00\x19\x10\x00\x03\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x06\x11A\xff\xc4\x00\x14\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xc4\x00\x14\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00?\x00\x9d\xf29wU5Q\xd6\xfd\x00\x01\xff\xd9' u.save() @@ -134,7 +133,7 @@ class UserTestCase(BaseTestCase): self.assertEquals(u.group, 1000) self.assertEquals(u.home_directory, '/home/foouser') - self.assertEquals(u.uid, 1000) + self.assertEquals(u.uid, 2000) self.assertEquals(u.username, 'foouser') self.assertEquals(u.photo, '\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x01\x00H\x00H\x00\x00\xff\xfe\x00\x1cCreated with GIMP on a Mac\xff\xdb\x00C\x00\x05\x03\x04\x04\x04\x03\x05\x04\x04\x04\x05\x05\x05\x06\x07\x0c\x08\x07\x07\x07\x07\x0f\x0b\x0b\t\x0c\x11\x0f\x12\x12\x11\x0f\x11\x11\x13\x16\x1c\x17\x13\x14\x1a\x15\x11\x11\x18!\x18\x1a\x1d\x1d\x1f\x1f\x1f\x13\x17"$"\x1e$\x1c\x1e\x1f\x1e\xff\xdb\x00C\x01\x05\x05\x05\x07\x06\x07\x0e\x08\x08\x0e\x1e\x14\x11\x14\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\xff\xc0\x00\x11\x08\x00\x08\x00\x08\x03\x01"\x00\x02\x11\x01\x03\x11\x01\xff\xc4\x00\x15\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\xff\xc4\x00\x19\x10\x00\x03\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x06\x11A\xff\xc4\x00\x14\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xc4\x00\x14\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00?\x00\x9d\xf29wU5Q\xd6\xfd\x00\x01\xff\xd9') @@ -174,10 +173,20 @@ class AdminTestCase(BaseTestCase): u.full_name = "Foo User" u.group = 1000 u.home_directory = "/home/foouser" - u.uid = 1000 + u.uid = 2000 u.username = "foouser" u.save() + u = LdapUser() + u.first_name = "Bar" + u.last_name = "User" + u.full_name = "Bar User" + u.group = 1001 + u.home_directory = "/home/baruser" + u.uid = 2001 + u.username = "baruser" + u.save() + self.client.login(username="test_user", password="password") def test_index(self): @@ -189,6 +198,19 @@ class AdminTestCase(BaseTestCase): response = self.client.get('/admin/examples/ldapgroup/') self.assertContains(response, "Ldap groups") self.assertContains(response, "foogroup") + self.assertContains(response, "1000") + + # order by name + response = self.client.get('/admin/examples/ldapgroup/?o=1') + self.assertContains(response, "Ldap groups") + self.assertContains(response, "foogroup") + self.assertContains(response, "1000") + + # order by gid + response = self.client.get('/admin/examples/ldapgroup/?o=2') + self.assertContains(response, "Ldap groups") + self.assertContains(response, "foogroup") + self.assertContains(response, "1000") def test_group_detail(self): response = self.client.get('/admin/examples/ldapgroup/foogroup/') @@ -197,14 +219,29 @@ class AdminTestCase(BaseTestCase): def test_group_search(self): response = self.client.get('/admin/examples/ldapgroup/?q=foo') + self.assertContains(response, "Ldap groups") self.assertContains(response, "foogroup") + self.assertContains(response, "1000") def test_user_list(self): response = self.client.get('/admin/examples/ldapuser/') self.assertContains(response, "Ldap users") self.assertContains(response, "foouser") + self.assertContains(response, "2000") + + # order by username + response = self.client.get('/admin/examples/ldapuser/?o=1') + self.assertContains(response, "Ldap users") + self.assertContains(response, "foouser") + self.assertContains(response, "2000") + + # order by uid + response = self.client.get('/admin/examples/ldapuser/?o=2') + self.assertContains(response, "Ldap users") + self.assertContains(response, "foouser") + self.assertContains(response, "2000") def test_user_detail(self): response = self.client.get('/admin/examples/ldapuser/foouser/') self.assertContains(response, "foouser") - self.assertContains(response, "1000") + self.assertContains(response, "2000") -- 2.30.2 From 8ce4d769e2f75d829afbcc761b01c06a6219e3e0 Mon Sep 17 00:00:00 2001 From: jlaine Date: Mon, 31 May 2010 18:37:26 +0000 Subject: [PATCH 07/16] test ordering git-svn-id: https://svn.bolloretelecom.eu/opensource/django-ldapdb/trunk@904 e071eeec-0327-468d-9b6a-08194a12b294 --- examples/tests.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/examples/tests.py b/examples/tests.py index 70d53e3..533df5c 100644 --- a/examples/tests.py +++ b/examples/tests.py @@ -78,6 +78,7 @@ class GroupTestCase(BaseTestCase): self.assertEquals(g.gid, 1000) self.assertEquals(g.usernames, ['foouser', 'baruser']) + # try to get non-existent entry qs = LdapGroup.objects.filter(name='does_not_exist') self.assertEquals(len(qs), 0) @@ -90,6 +91,31 @@ class GroupTestCase(BaseTestCase): self.assertRaises(LdapGroup.DoesNotExist, LdapGroup.objects.get, name='does_not_exist') + def test_order_by(self): + # ascending name + qs = LdapGroup.objects.order_by('name') + self.assertEquals(len(qs), 2) + self.assertEquals(qs[0].name, 'bargroup') + self.assertEquals(qs[1].name, 'foogroup') + + # descending name + qs = LdapGroup.objects.order_by('-name') + self.assertEquals(len(qs), 2) + self.assertEquals(qs[0].name, 'foogroup') + self.assertEquals(qs[1].name, 'bargroup') + + # ascending gid + qs = LdapGroup.objects.order_by('gid') + self.assertEquals(len(qs), 2) + self.assertEquals(qs[0].gid, 1000) + self.assertEquals(qs[1].gid, 1001) + + # descending gid + qs = LdapGroup.objects.order_by('-gid') + self.assertEquals(len(qs), 2) + self.assertEquals(qs[0].gid, 1001) + self.assertEquals(qs[1].gid, 1000) + def test_update(self): g = LdapGroup.objects.get(name='foogroup') -- 2.30.2 From 6cba2f11be4c464583e2e303d33a013918358e5a Mon Sep 17 00:00:00 2001 From: jlaine Date: Mon, 31 May 2010 18:45:48 +0000 Subject: [PATCH 08/16] accept "using" keyword for get_count git-svn-id: https://svn.bolloretelecom.eu/opensource/django-ldapdb/trunk@905 e071eeec-0327-468d-9b6a-08194a12b294 --- ldapdb/models/query.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ldapdb/models/query.py b/ldapdb/models/query.py index bbb46e2..f433956 100644 --- a/ldapdb/models/query.py +++ b/ldapdb/models/query.py @@ -187,7 +187,7 @@ class Query(BaseQuery): super(Query, self).__init__(*args, **kwargs) self.connection = ldapdb.connection - def get_count(self): + def get_count(self, using=None): filterstr = ''.join(['(objectClass=%s)' % cls for cls in self.model.object_classes]) sql, params = self.where.as_sql() filterstr += sql -- 2.30.2 From a215a2ba69e247e614fa4cffda46ced4e51706bb Mon Sep 17 00:00:00 2001 From: jlaine Date: Mon, 31 May 2010 18:54:36 +0000 Subject: [PATCH 09/16] fix get_count() git-svn-id: https://svn.bolloretelecom.eu/opensource/django-ldapdb/trunk@906 e071eeec-0327-468d-9b6a-08194a12b294 --- ldapdb/models/query.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ldapdb/models/query.py b/ldapdb/models/query.py index f433956..5b7e6af 100644 --- a/ldapdb/models/query.py +++ b/ldapdb/models/query.py @@ -194,7 +194,7 @@ class Query(BaseQuery): filterstr = '(&%s)' % filterstr try: - vals = self.connection.search_s( + vals = ldapdb.connection.search_s( self.model.base_dn, ldap.SCOPE_SUBTREE, filterstr=filterstr, -- 2.30.2 From c15d0f62f8b92e9a2c69d9b93ded4cf964ef710c Mon Sep 17 00:00:00 2001 From: jlaine Date: Mon, 31 May 2010 19:06:42 +0000 Subject: [PATCH 10/16] fix and test deletion from admin interface git-svn-id: https://svn.bolloretelecom.eu/opensource/django-ldapdb/trunk@907 e071eeec-0327-468d-9b6a-08194a12b294 --- examples/tests.py | 9 +++++++++ ldapdb/models/base.py | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/examples/tests.py b/examples/tests.py index 533df5c..465c857 100644 --- a/examples/tests.py +++ b/examples/tests.py @@ -243,6 +243,10 @@ class AdminTestCase(BaseTestCase): self.assertContains(response, "foogroup") self.assertContains(response, "1000") + def test_group_delete(self): + response = self.client.post('/admin/examples/ldapgroup/foogroup/delete/', {'yes': 'post'}) + self.assertRedirects(response, '/admin/examples/ldapgroup/') + def test_group_search(self): response = self.client.get('/admin/examples/ldapgroup/?q=foo') self.assertContains(response, "Ldap groups") @@ -271,3 +275,8 @@ class AdminTestCase(BaseTestCase): response = self.client.get('/admin/examples/ldapuser/foouser/') self.assertContains(response, "foouser") self.assertContains(response, "2000") + + def test_user_delete(self): + response = self.client.post('/admin/examples/ldapuser/foouser/delete/', {'yes': 'post'}) + self.assertRedirects(response, '/admin/examples/ldapuser/') + diff --git a/ldapdb/models/base.py b/ldapdb/models/base.py index 9291c6b..41d00e8 100644 --- a/ldapdb/models/base.py +++ b/ldapdb/models/base.py @@ -60,6 +60,12 @@ class Model(django.db.models.base.Model): super(Model, self).__init__(*args, **kwargs) self.saved_pk = self.pk + def _collect_sub_objects(self, collector): + """ + This private API seems to be called by the admin interface in django 1.2 + """ + pass + def build_rdn(self): """ Build the Relative Distinguished Name for this entry. -- 2.30.2 From 26178aed79f88bec01122855fa0661d166355744 Mon Sep 17 00:00:00 2001 From: jlaine Date: Mon, 31 May 2010 19:38:39 +0000 Subject: [PATCH 11/16] rework ldap filtering git-svn-id: https://svn.bolloretelecom.eu/opensource/django-ldapdb/trunk@908 e071eeec-0327-468d-9b6a-08194a12b294 --- ldapdb/models/query.py | 41 +++++++++++++++-------------------------- 1 file changed, 15 insertions(+), 26 deletions(-) diff --git a/ldapdb/models/query.py b/ldapdb/models/query.py index 5b7e6af..0d4b0e1 100644 --- a/ldapdb/models/query.py +++ b/ldapdb/models/query.py @@ -71,22 +71,14 @@ class Compiler(object): def results_iter(self): query = self.query - - filterstr = ''.join(['(objectClass=%s)' % cls for cls in query.model.object_classes]) - sql, params = query.where.as_sql() - filterstr += sql - filterstr = '(&%s)' % filterstr attrlist = [ x.db_column for x in query.model._meta.local_fields if x.db_column ] - try: - vals = self.connection.search_s( - query.model.base_dn, - ldap.SCOPE_SUBTREE, - filterstr=filterstr, - attrlist=attrlist, - ) - except: - raise query.model.DoesNotExist + vals = self.connection.search_s( + query.model.base_dn, + ldap.SCOPE_SUBTREE, + filterstr=query._ldap_filter(), + attrlist=attrlist, + ) # perform sorting if query.extra_order_by: @@ -187,22 +179,19 @@ class Query(BaseQuery): super(Query, self).__init__(*args, **kwargs) self.connection = ldapdb.connection - def get_count(self, using=None): + def _ldap_filter(self): filterstr = ''.join(['(objectClass=%s)' % cls for cls in self.model.object_classes]) sql, params = self.where.as_sql() filterstr += sql - filterstr = '(&%s)' % filterstr - - try: - vals = ldapdb.connection.search_s( - self.model.base_dn, - ldap.SCOPE_SUBTREE, - filterstr=filterstr, - attrlist=[], - ) - except: - raise self.model.DoesNotExist + return '(&%s)' % filterstr + def get_count(self, using=None): + vals = ldapdb.connection.search_s( + self.model.base_dn, + ldap.SCOPE_SUBTREE, + filterstr=self._ldap_filter(), + attrlist=[], + ) return len(vals) def get_compiler(self, using=None, connection=None): -- 2.30.2 From 4abdee082b2cb5697cf0e6436df871a36cccdbe6 Mon Sep 17 00:00:00 2001 From: jlaine Date: Mon, 31 May 2010 19:43:24 +0000 Subject: [PATCH 12/16] implement and test bulk deletion git-svn-id: https://svn.bolloretelecom.eu/opensource/django-ldapdb/trunk@909 e071eeec-0327-468d-9b6a-08194a12b294 --- examples/tests.py | 6 ++++++ ldapdb/models/query.py | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/examples/tests.py b/examples/tests.py index 465c857..e108852 100644 --- a/examples/tests.py +++ b/examples/tests.py @@ -116,6 +116,12 @@ class GroupTestCase(BaseTestCase): self.assertEquals(qs[0].gid, 1001) self.assertEquals(qs[1].gid, 1000) + def test_bulk_delete(self): + LdapGroup.objects.all().delete() + + qs = LdapGroup.objects.all() + self.assertEquals(len(qs), 0) + def test_update(self): g = LdapGroup.objects.get(name='foogroup') diff --git a/ldapdb/models/query.py b/ldapdb/models/query.py index 0d4b0e1..4f0273c 100644 --- a/ldapdb/models/query.py +++ b/ldapdb/models/query.py @@ -214,3 +214,15 @@ class QuerySet(BaseQuerySet): query = Query(model, None, WhereNode) super(QuerySet, self).__init__(model=model, query=query) + def delete(self): + "Bulk deletion." + vals = ldapdb.connection.search_s( + self.model.base_dn, + ldap.SCOPE_SUBTREE, + filterstr=self.query._ldap_filter(), + attrlist=[], + ) + # FIXME : there is probably a more efficient way to do this + for dn, attrs in vals: + ldapdb.connection.delete_s(dn) + -- 2.30.2 From 326c4e332a9447a6972a0f53f9769a5a3b965fba Mon Sep 17 00:00:00 2001 From: jlaine Date: Mon, 31 May 2010 19:45:42 +0000 Subject: [PATCH 13/16] touchup documentation git-svn-id: https://svn.bolloretelecom.eu/opensource/django-ldapdb/trunk@910 e071eeec-0327-468d-9b6a-08194a12b294 --- examples/tests.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/tests.py b/examples/tests.py index e108852..26d07d3 100644 --- a/examples/tests.py +++ b/examples/tests.py @@ -78,7 +78,7 @@ class GroupTestCase(BaseTestCase): self.assertEquals(g.gid, 1000) self.assertEquals(g.usernames, ['foouser', 'baruser']) - # try to get non-existent entry + # try to filter non-existent entries qs = LdapGroup.objects.filter(name='does_not_exist') self.assertEquals(len(qs), 0) @@ -89,6 +89,7 @@ class GroupTestCase(BaseTestCase): self.assertEquals(g.gid, 1000) self.assertEquals(g.usernames, ['foouser', 'baruser']) + # try to get a non-existent entry self.assertRaises(LdapGroup.DoesNotExist, LdapGroup.objects.get, name='does_not_exist') def test_order_by(self): -- 2.30.2 From 1586dbefbbebc802312bccfe946fae20febeaf5a Mon Sep 17 00:00:00 2001 From: jlaine Date: Mon, 31 May 2010 19:52:13 +0000 Subject: [PATCH 14/16] catch ldap.NO_SUCH_OBJECT for calls to ldap.search_s git-svn-id: https://svn.bolloretelecom.eu/opensource/django-ldapdb/trunk@911 e071eeec-0327-468d-9b6a-08194a12b294 --- ldapdb/models/query.py | 47 ++++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/ldapdb/models/query.py b/ldapdb/models/query.py index 4f0273c..8999c17 100644 --- a/ldapdb/models/query.py +++ b/ldapdb/models/query.py @@ -73,12 +73,16 @@ class Compiler(object): query = self.query attrlist = [ x.db_column for x in query.model._meta.local_fields if x.db_column ] - vals = self.connection.search_s( - query.model.base_dn, - ldap.SCOPE_SUBTREE, - filterstr=query._ldap_filter(), - attrlist=attrlist, - ) + try: + vals = self.connection.search_s( + query.model.base_dn, + ldap.SCOPE_SUBTREE, + filterstr=query._ldap_filter(), + attrlist=attrlist, + ) + except ldap.NO_SUCH_OBJECT: + return + raise query.model.DoesNotExist # perform sorting if query.extra_order_by: @@ -186,12 +190,15 @@ class Query(BaseQuery): return '(&%s)' % filterstr def get_count(self, using=None): - vals = ldapdb.connection.search_s( - self.model.base_dn, - ldap.SCOPE_SUBTREE, - filterstr=self._ldap_filter(), - attrlist=[], - ) + try: + vals = ldapdb.connection.search_s( + self.model.base_dn, + ldap.SCOPE_SUBTREE, + filterstr=self._ldap_filter(), + attrlist=[], + ) + except ldap.NO_SUCH_OBJECT: + return 0 return len(vals) def get_compiler(self, using=None, connection=None): @@ -216,12 +223,16 @@ class QuerySet(BaseQuerySet): def delete(self): "Bulk deletion." - vals = ldapdb.connection.search_s( - self.model.base_dn, - ldap.SCOPE_SUBTREE, - filterstr=self.query._ldap_filter(), - attrlist=[], - ) + try: + vals = ldapdb.connection.search_s( + self.model.base_dn, + ldap.SCOPE_SUBTREE, + filterstr=self.query._ldap_filter(), + attrlist=[], + ) + except ldap.NO_SUCH_OBJECT: + return + # FIXME : there is probably a more efficient way to do this for dn, attrs in vals: ldapdb.connection.delete_s(dn) -- 2.30.2 From 5581ef499d94127504e076f540e71ef412fd88df Mon Sep 17 00:00:00 2001 From: jlaine Date: Mon, 31 May 2010 19:56:59 +0000 Subject: [PATCH 15/16] add more groups for tests git-svn-id: https://svn.bolloretelecom.eu/opensource/django-ldapdb/trunk@912 e071eeec-0327-468d-9b6a-08194a12b294 --- examples/tests.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/examples/tests.py b/examples/tests.py index 26d07d3..e96ad90 100644 --- a/examples/tests.py +++ b/examples/tests.py @@ -62,12 +62,18 @@ class GroupTestCase(BaseTestCase): g.usernames = ['zoouser', 'baruser'] g.save() + g = LdapGroup() + g.name = "wizgroup" + g.gid = 1002 + g.usernames = ['wizuser', 'baruser'] + g.save() + def test_filter(self): qs = LdapGroup.objects.none() self.assertEquals(len(qs), 0) qs = LdapGroup.objects.all() - self.assertEquals(len(qs), 2) + self.assertEquals(len(qs), 3) qs = LdapGroup.objects.filter(name='foogroup') self.assertEquals(len(qs), 1) @@ -95,27 +101,31 @@ class GroupTestCase(BaseTestCase): def test_order_by(self): # ascending name qs = LdapGroup.objects.order_by('name') - self.assertEquals(len(qs), 2) + self.assertEquals(len(qs), 3) self.assertEquals(qs[0].name, 'bargroup') self.assertEquals(qs[1].name, 'foogroup') + self.assertEquals(qs[2].name, 'wizgroup') # descending name qs = LdapGroup.objects.order_by('-name') - self.assertEquals(len(qs), 2) - self.assertEquals(qs[0].name, 'foogroup') - self.assertEquals(qs[1].name, 'bargroup') + self.assertEquals(len(qs), 3) + self.assertEquals(qs[0].name, 'wizgroup') + self.assertEquals(qs[1].name, 'foogroup') + self.assertEquals(qs[2].name, 'bargroup') # ascending gid qs = LdapGroup.objects.order_by('gid') - self.assertEquals(len(qs), 2) + self.assertEquals(len(qs), 3) self.assertEquals(qs[0].gid, 1000) self.assertEquals(qs[1].gid, 1001) + self.assertEquals(qs[2].gid, 1002) # descending gid qs = LdapGroup.objects.order_by('-gid') - self.assertEquals(len(qs), 2) - self.assertEquals(qs[0].gid, 1001) - self.assertEquals(qs[1].gid, 1000) + self.assertEquals(len(qs), 3) + self.assertEquals(qs[0].gid, 1002) + self.assertEquals(qs[1].gid, 1001) + self.assertEquals(qs[2].gid, 1000) def test_bulk_delete(self): LdapGroup.objects.all().delete() @@ -140,7 +150,7 @@ class GroupTestCase(BaseTestCase): g.delete() qs = LdapGroup.objects.all() - self.assertEquals(len(qs), 1) + self.assertEquals(len(qs), 2) class UserTestCase(BaseTestCase): def setUp(self): -- 2.30.2 From 86f79b1135e0983d0a48e211685c57ce76ea09e0 Mon Sep 17 00:00:00 2001 From: jlaine Date: Mon, 31 May 2010 20:12:06 +0000 Subject: [PATCH 16/16] fix slice handling git-svn-id: https://svn.bolloretelecom.eu/opensource/django-ldapdb/trunk@913 e071eeec-0327-468d-9b6a-08194a12b294 --- examples/tests.py | 24 ++++++++++++++++++++++++ ldapdb/models/query.py | 9 +++++++++ 2 files changed, 33 insertions(+) diff --git a/examples/tests.py b/examples/tests.py index e96ad90..f99ea2e 100644 --- a/examples/tests.py +++ b/examples/tests.py @@ -133,6 +133,30 @@ class GroupTestCase(BaseTestCase): qs = LdapGroup.objects.all() self.assertEquals(len(qs), 0) + def test_slice(self): + qs = LdapGroup.objects.all() + objs = list(qs) + self.assertEquals(len(objs), 3) + self.assertEquals(objs[0].gid, 1000) + self.assertEquals(objs[1].gid, 1001) + self.assertEquals(objs[2].gid, 1002) + + qs = LdapGroup.objects.all() + objs = qs[:2] + for o in objs: + return + print objs + self.assertEquals(len(objs), 2) + self.assertEquals(objs[0].gid, 1000) + self.assertEquals(objs[1].gid, 1001) + return + + qs = LdapGroup.objects.all() + objs = qs[1:] + self.assertEquals(len(objs), 2) + self.assertEquals(objs[0].gid, 1001) + self.assertEquals(objs[1].gid, 1002) + def test_update(self): g = LdapGroup.objects.get(name='foogroup') diff --git a/ldapdb/models/query.py b/ldapdb/models/query.py index 8999c17..e422630 100644 --- a/ldapdb/models/query.py +++ b/ldapdb/models/query.py @@ -113,7 +113,15 @@ class Compiler(object): vals = sorted(vals, cmp=cmpvals) # process results + pos = 0 for dn, attrs in vals: + # FIXME : This is not optimal, we retrieve more results than we need + # but there is probably no other options as we can't perform ordering + # server side. + if (self.query.low_mark and pos < self.query.low_mark) or \ + (self.query.high_mark is not None and pos >= self.query.high_mark): + pos += 1 + continue row = [] for field in iter(query.model._meta.fields): if field.attname == 'dn': @@ -123,6 +131,7 @@ class Compiler(object): else: row.append(None) yield row + pos += 1 class WhereNode(BaseWhereNode): -- 2.30.2