Commit eefb4871 authored by Pierre-Elliott Bécue's avatar Pierre-Elliott Bécue

New upstream version 1.2.0

parent 8d25e2ab
[flake8]
max-line-length = 120
# W503: 'and' on start of line
ignore = W503
; vim:set ft=dosini:
ChangeLog
=========
1.2.0 (2018-07-28)
------------------
*Bugfix:*
* *[#153]* Don't send empty updates to the LDAP Server.
* *[#159]* Fix saving composite PK objects.
* *[#140]* Fix ordering by distinguished name.
* *[#57]* Deduplicate values in ``ListField``.
*New:*
* Normalize value of nullable empty attributes: return ``0`` / ``0.0`` if the field is not nullable,
``None`` otherwise.
*Packaging:*
* Include required test files in the 'sdist' tarball; these can be required by projects.
1.1.0 (2018-07-14)
------------------
*New:*
* Switch back to ``python-ldap`` instead of ``pyldap``. Users updating a virtualenv must
uninstall ``pyldap`` **before** installing ``python-ldap``:
.. code-block:: sh
pip uninstall pyldap
pip install python-ldap
* Add ``DateTimeField`` and ``TimestampField``, with proper lookups.
1.0.0 (2018-03-06)
------------------
......
......@@ -2,9 +2,9 @@ include AUTHORS ChangeLog LICENSE README.rst
include requirements*.txt
graft ldapdb
prune examples
graft tests
graft examples
global-exclude *.py[cod] __pycache__ .*swp
exclude Makefile manage_dev.py tox.ini .flake8
include Makefile manage_dev.py tox.ini .flake8
PACKAGE := ldapdb
TESTS_DIR := examples
# Error on all warnings, except in python's site.py module and distutils' imp.py module.
PYWARNINGS = -Wdefault -Werror \
-Wignore::DeprecationWarning:site:165 \
-Wignore::PendingDeprecationWarning:imp \
-Wignore::DeprecationWarning:distutils
default:
install:
python setup.py install
clean:
find . -type f -name '*.pyc' -delete
find . -type f -path '*/__pycache__/*' -delete
find . -type d -empty -delete
update:
pip install --upgrade pip setuptools
pip install -r requirements_dev.txt
pip freeze
release:
fullrelease
.PHONY: default install clean update release
testall:
tox
test:
python $(PYWARNINGS) manage_dev.py test
.PHONY: test testall
lint: flake8 isort check-manifest
flake8:
flake8 --config .flake8 $(PACKAGE) $(TESTS_DIR)
isort:
isort $(PACKAGE) $(TESTS_DIR) --recursive --check-only --diff --project $(PACKAGE) --project $(TESTS_DIR)
check-manifest:
check-manifest
.PHONY: isort lint flake8 check-manifest
Metadata-Version: 1.1
Metadata-Version: 1.2
Name: django-ldapdb
Version: 1.0.0
Version: 1.2.0
Summary: An LDAP database backend for Django
Home-page: https://github.com/django-ldapdb/django-ldapdb
Author: Raphaël Barrois
Author-email: raphael.barrois+django-ldapdb@polytechnique.org
Author: Jeremy Laine
Author-email: jeremy.laine@m4x.org
Maintainer: Raphaël Barrois
Maintainer-email: raphael.barrois+django-ldapdb@polytechnique.org
License: BSD
Description-Content-Type: UNKNOWN
Description: django-ldapdb
=============
......@@ -57,10 +58,10 @@ Description: django-ldapdb
Windows
~~~~~~~
``django-ldapdb`` depends on the `pyldap <https://pypi.python.org/pypi/pyldap>` project.
``django-ldapdb`` depends on the `python-ldap <https://pypi.python.org/pypi/python-ldap>` project.
Either follow `its Windows installation guide <https://www.python-ldap.org/en/latest/installing.html>`_,
or install a pre-built version from https://www.lfd.uci.edu/~gohlke/pythonlibs/#pyldap
(choose the ``.whl`` file matching your Python/Windows combination, and install it with ``pip install pyldap-2.4...whl``).
or install a pre-built version from https://www.lfd.uci.edu/~gohlke/pythonlibs/#python-ldap
(choose the ``.whl`` file matching your Python/Windows combination, and install it with ``pip install python-ldap-3...whl``).
and then you can also install ``django-ldapdb`` with
......@@ -141,6 +142,27 @@ Description: django-ldapdb
will have the DN ``cn=foo,ou=groups,dc=nodomain,dc=org``.
Supported fields
----------------
djanglo-ldapdb provides the following fields, all imported from ``ldapdb.models.fields``:
Similar to Django:
* ``IntegerField``
* ``FloatField``
* ``CharField``
* ``ImageField``
* ``DateTimeField``
Specific to a LDAP server:
* ``ListField`` (holds a list of text values)
* ``TimestampField`` (Stores a datetime as a posix timestamp, typically for posixAccount)
Legacy:
* ``DateField`` (Stores a date in an arbitrary format. A LDAP server has no notion of ``Date``).
Tuning django-ldapdb
--------------------
......
......@@ -48,10 +48,10 @@ You might also need the usual ``LDAP`` packages from your distribution, usually
Windows
~~~~~~~
``django-ldapdb`` depends on the `pyldap <https://pypi.python.org/pypi/pyldap>` project.
``django-ldapdb`` depends on the `python-ldap <https://pypi.python.org/pypi/python-ldap>` project.
Either follow `its Windows installation guide <https://www.python-ldap.org/en/latest/installing.html>`_,
or install a pre-built version from https://www.lfd.uci.edu/~gohlke/pythonlibs/#pyldap
(choose the ``.whl`` file matching your Python/Windows combination, and install it with ``pip install pyldap-2.4...whl``).
or install a pre-built version from https://www.lfd.uci.edu/~gohlke/pythonlibs/#python-ldap
(choose the ``.whl`` file matching your Python/Windows combination, and install it with ``pip install python-ldap-3...whl``).
and then you can also install ``django-ldapdb`` with
......@@ -132,6 +132,27 @@ and add this to your ``admin.py``:
will have the DN ``cn=foo,ou=groups,dc=nodomain,dc=org``.
Supported fields
----------------
djanglo-ldapdb provides the following fields, all imported from ``ldapdb.models.fields``:
Similar to Django:
* ``IntegerField``
* ``FloatField``
* ``CharField``
* ``ImageField``
* ``DateTimeField``
Specific to a LDAP server:
* ``ListField`` (holds a list of text values)
* ``TimestampField`` (Stores a datetime as a posix timestamp, typically for posixAccount)
Legacy:
* ``DateField`` (Stores a date in an arbitrary format. A LDAP server has no notion of ``Date``).
Tuning django-ldapdb
--------------------
......
Metadata-Version: 1.1
Metadata-Version: 1.2
Name: django-ldapdb
Version: 1.0.0
Version: 1.2.0
Summary: An LDAP database backend for Django
Home-page: https://github.com/django-ldapdb/django-ldapdb
Author: Raphaël Barrois
Author-email: raphael.barrois+django-ldapdb@polytechnique.org
Author: Jeremy Laine
Author-email: jeremy.laine@m4x.org
Maintainer: Raphaël Barrois
Maintainer-email: raphael.barrois+django-ldapdb@polytechnique.org
License: BSD
Description-Content-Type: UNKNOWN
Description: django-ldapdb
=============
......@@ -57,10 +58,10 @@ Description: django-ldapdb
Windows
~~~~~~~
``django-ldapdb`` depends on the `pyldap <https://pypi.python.org/pypi/pyldap>` project.
``django-ldapdb`` depends on the `python-ldap <https://pypi.python.org/pypi/python-ldap>` project.
Either follow `its Windows installation guide <https://www.python-ldap.org/en/latest/installing.html>`_,
or install a pre-built version from https://www.lfd.uci.edu/~gohlke/pythonlibs/#pyldap
(choose the ``.whl`` file matching your Python/Windows combination, and install it with ``pip install pyldap-2.4...whl``).
or install a pre-built version from https://www.lfd.uci.edu/~gohlke/pythonlibs/#python-ldap
(choose the ``.whl`` file matching your Python/Windows combination, and install it with ``pip install python-ldap-3...whl``).
and then you can also install ``django-ldapdb`` with
......@@ -141,6 +142,27 @@ Description: django-ldapdb
will have the DN ``cn=foo,ou=groups,dc=nodomain,dc=org``.
Supported fields
----------------
djanglo-ldapdb provides the following fields, all imported from ``ldapdb.models.fields``:
Similar to Django:
* ``IntegerField``
* ``FloatField``
* ``CharField``
* ``ImageField``
* ``DateTimeField``
Specific to a LDAP server:
* ``ListField`` (holds a list of text values)
* ``TimestampField`` (Stores a datetime as a posix timestamp, typically for posixAccount)
Legacy:
* ``DateField`` (Stores a date in an arbitrary format. A LDAP server has no notion of ``Date``).
Tuning django-ldapdb
--------------------
......
.flake8
AUTHORS
ChangeLog
LICENSE
MANIFEST.in
Makefile
README.rst
manage_dev.py
requirements_dev.txt
requirements_test.txt
setup.cfg
setup.py
tox.ini
django_ldapdb.egg-info/PKG-INFO
django_ldapdb.egg-info/SOURCES.txt
django_ldapdb.egg-info/dependency_links.txt
django_ldapdb.egg-info/requires.txt
django_ldapdb.egg-info/top_level.txt
django_ldapdb.egg-info/zip-safe
examples/__init__.py
examples/admin.py
examples/models.py
examples/settings.py
examples/tests.py
examples/urls.py
examples/fixtures/test_users.json
ldapdb/__init__.py
ldapdb/router.py
ldapdb/tests.py
......
Django>=1.8
pyldap>=2.4.25
Django>=1.11
python-ldap>=3.0
......@@ -5,7 +5,7 @@
from __future__ import unicode_literals
import ldapdb.models
from ldapdb.models.fields import CharField, ImageField, IntegerField, ListField
from ldapdb.models import fields
class LdapUser(ldapdb.models.Model):
......@@ -15,24 +15,28 @@ class LdapUser(ldapdb.models.Model):
# LDAP meta-data
base_dn = "ou=people,dc=example,dc=org"
object_classes = ['posixAccount', 'shadowAccount', 'inetOrgPerson']
last_modified = fields.DateTimeField(db_column='modifyTimestamp')
# inetOrgPerson
first_name = CharField(db_column='givenName', verbose_name="Prime name")
last_name = CharField("Final name", db_column='sn')
full_name = CharField(db_column='cn')
email = CharField(db_column='mail')
phone = CharField(db_column='telephoneNumber', blank=True)
mobile_phone = CharField(db_column='mobile', blank=True)
photo = ImageField(db_column='jpegPhoto')
first_name = fields.CharField(db_column='givenName', verbose_name="Prime name")
last_name = fields.CharField("Final name", db_column='sn')
full_name = fields.CharField(db_column='cn')
email = fields.CharField(db_column='mail')
phone = fields.CharField(db_column='telephoneNumber', blank=True)
mobile_phone = fields.CharField(db_column='mobile', blank=True)
photo = fields.ImageField(db_column='jpegPhoto')
# posixAccount
uid = IntegerField(db_column='uidNumber', unique=True)
group = IntegerField(db_column='gidNumber')
gecos = CharField(db_column='gecos')
home_directory = CharField(db_column='homeDirectory')
login_shell = CharField(db_column='loginShell', default='/bin/bash')
username = CharField(db_column='uid', primary_key=True)
password = CharField(db_column='userPassword')
uid = fields.IntegerField(db_column='uidNumber', unique=True)
group = fields.IntegerField(db_column='gidNumber')
gecos = fields.CharField(db_column='gecos')
home_directory = fields.CharField(db_column='homeDirectory')
login_shell = fields.CharField(db_column='loginShell', default='/bin/bash')
username = fields.CharField(db_column='uid', primary_key=True)
password = fields.CharField(db_column='userPassword')
# shadowAccount
last_password_change = fields.TimestampField(db_column='shadowLastChange')
def __str__(self):
return self.username
......@@ -50,12 +54,29 @@ class LdapGroup(ldapdb.models.Model):
object_classes = ['posixGroup']
# posixGroup attributes
gid = IntegerField(db_column='gidNumber', unique=True)
name = CharField(db_column='cn', max_length=200, primary_key=True)
usernames = ListField(db_column='memberUid')
gid = fields.IntegerField(db_column='gidNumber', unique=True)
name = fields.CharField(db_column='cn', max_length=200, primary_key=True)
usernames = fields.ListField(db_column='memberUid')
def __str__(self):
return self.name
def __unicode__(self):
return self.name
class LdapMultiPKRoom(ldapdb.models.Model):
"""
Class for representing a room, using a composite primary key.
"""
# LDAP meta-data
base_dn = "ou=rooms,dc=example,dc=org"
object_classes = ['room']
# room attributes
name = fields.CharField(db_column='cn', max_length=200, primary_key=True)
number = fields.CharField(db_column='roomNumber', max_length=10, primary_key=True)
phone = fields.CharField(db_column='telephoneNumber', max_length=20, blank=True, null=True)
def __str__(self):
return "%s (%s)" % (self.name, self.number)
......@@ -51,6 +51,8 @@ USE_I18N = True
# calendars according to the current locale
USE_L10N = True
USE_TZ = True
# Absolute filesystem path to the directory that will hold user-uploaded files.
# Example: "/home/media/media.lawrence.com/"
MEDIA_ROOT = ''
......
This diff is collapsed.
......@@ -128,7 +128,7 @@ class LdapDatabase(object):
ldap.USER_CANCELLED,
Error,
):
"""Exceptions related to the pyldap interface."""
"""Exceptions related to the python-ldap interface."""
class InternalError(
DatabaseError,
......
......@@ -209,15 +209,18 @@ class SQLCompiler(compiler.SQLCompiler):
sort_field = self.query.model._meta.pk.name
field = self.query.model._meta.get_field(sort_field)
def get_key(obj):
attr = field.from_ldap(
obj[1].get(field.db_column, []),
connection=self.connection,
)
if hasattr(attr, 'lower'):
attr = attr.lower()
return attr
vals = sorted(vals, key=get_key, reverse=reverse)
if sort_field == 'dn':
vals = sorted(vals, key=lambda pair: pair[0], reverse=reverse)
else:
def get_key(obj):
attr = field.from_ldap(
obj[1].get(field.db_column, []),
connection=self.connection,
)
if hasattr(attr, 'lower'):
attr = attr.lower()
return attr
vals = sorted(vals, key=get_key, reverse=reverse)
# process results
pos = 0
......
......@@ -11,7 +11,7 @@ import ldap
from django.db import connections, router
from django.db.models import signals
import ldapdb # noqa
from . import fields as ldapdb_fields
logger = logging.getLogger('ldapdb')
......@@ -20,7 +20,7 @@ class Model(django.db.models.base.Model):
"""
Base class for all LDAP models.
"""
dn = django.db.models.fields.CharField(max_length=200, primary_key=True)
dn = ldapdb_fields.CharField(max_length=200, primary_key=True)
# meta-data
base_dn = None
......@@ -29,7 +29,7 @@ class Model(django.db.models.base.Model):
def __init__(self, *args, **kwargs):
super(Model, self).__init__(*args, **kwargs)
self.saved_pk = self.pk
self._saved_dn = self.dn
def build_rdn(self):
"""
......@@ -49,7 +49,6 @@ class Model(django.db.models.base.Model):
Build the Distinguished Name for this entry.
"""
return "%s,%s" % (self.build_rdn(), self.base_dn)
raise Exception("Could not build Distinguished Name")
def delete(self, using=None):
"""
......@@ -90,7 +89,7 @@ class Model(django.db.models.base.Model):
if create:
old = None
else:
old = cls.objects.using(using).get(pk=self.saved_pk)
old = cls.objects.using(using).get(dn=self._saved_dn)
changes = {
field.db_column: (
None if old is None else get_field_value(field, old),
......@@ -114,7 +113,7 @@ class Model(django.db.models.base.Model):
new_values = hidden_values + [
(colname, change[1])
for colname, change in sorted(changes.items())
if change[1] is not None
if change[1] != []
]
new_dn = self.build_dn()
logger.debug("Creating new LDAP entry %s", new_dn)
......@@ -128,7 +127,7 @@ class Model(django.db.models.base.Model):
if old_value == new_value:
continue
modlist.append((
ldap.MOD_DELETE if new_value is None else ldap.MOD_REPLACE,
ldap.MOD_DELETE if new_value == [] else ldap.MOD_REPLACE,
colname,
new_value,
))
......@@ -137,14 +136,15 @@ class Model(django.db.models.base.Model):
logger.debug("renaming ldap entry %s to %s", old_dn, new_dn)
connection.rename_s(old_dn, self.build_rdn())
logger.debug("Modifying existing LDAP entry %s", new_dn)
connection.modify_s(new_dn, modlist)
if modlist:
logger.debug("Modifying existing LDAP entry %s", new_dn)
connection.modify_s(new_dn, modlist)
updated = True
self.dn = new_dn
# Finishing
self.saved_pk = self.pk
self._saved_dn = self.dn
return updated
@classmethod
......
This diff is collapsed.
......@@ -5,16 +5,22 @@
from __future__ import unicode_literals
import datetime
from django.db import connections
from django.db.models import expressions
from django.db.models.sql import query as django_query
from django.db.models.sql.where import AND, OR, WhereNode
from django.test import TestCase
from django.utils import timezone
from ldapdb import escape_ldap_filter, models
from ldapdb.backends.ldap import compiler as ldapdb_compiler
from ldapdb.models.fields import (CharField, DateField, FloatField,
IntegerField, ListField)
from ldapdb.models import fields
UTC = timezone.utc
UTC_PLUS_ONE = timezone.get_fixed_timezone(60)
UTC_MINUS_2_HALF = timezone.get_fixed_timezone(-150)
class FakeModel(models.Model):
......@@ -23,11 +29,59 @@ class FakeModel(models.Model):
base_dn = 'ou=test,dc=example,dc=org'
object_classes = ['inetOrgPerson']
name = CharField(db_column='cn')
name = fields.CharField(db_column='cn')
class DateTimeTests(TestCase):
CONVERSIONS = {
'': None,
'20180102030405.067874Z': datetime.datetime(2018, 1, 2, 3, 4, 5, 67874, tzinfo=UTC),
# Sub-microsecond is ignored by Python
'20180102030405.067874846Z': datetime.datetime(2018, 1, 2, 3, 4, 5, 67874, tzinfo=UTC),
# Sub-hour precision is optional
'2018010203Z': datetime.datetime(2018, 1, 2, 3, tzinfo=UTC),
# Support UTC offsets
'201801020304+0100': datetime.datetime(2018, 1, 2, 3, 4, tzinfo=UTC_PLUS_ONE),
# Minutes are optional for UTC offsets
'201801020304+01': datetime.datetime(2018, 1, 2, 3, 4, tzinfo=UTC_PLUS_ONE),
# Check negative offsets
'201801020304-0230': datetime.datetime(2018, 1, 2, 3, 4, tzinfo=UTC_MINUS_2_HALF),
}
def test_conversions(self):
for raw, expected in sorted(self.CONVERSIONS.items()):
converted = fields.datetime_from_ldap(raw)
self.assertEqual(
expected,
converted,
"Mismatch for %r: expected=%r, got=%r" % (raw, expected, converted),
)
class TimestampTests(TestCase):
CONVERSIONS = {
0: datetime.datetime(1970, 1, 1, tzinfo=UTC),
1530139989: datetime.datetime(2018, 6, 27, 22, 53, 9, tzinfo=UTC),
}
def test_conversions(self):
for raw, expected in sorted(self.CONVERSIONS.items()):
converted = fields.datetime_from_timestamp(raw)
self.assertEqual(
expected,
converted,
"Mismatch for %r: expected=%r, got=%r" % (raw, expected, converted),
)
retro_converted = fields.timestamp_from_datetime(converted)
self.assertEqual(
raw,
retro_converted,
"Mismatch for %r: expected=%r, got=%r" % (raw, raw, retro_converted),
)
class WhereTestCase(TestCase):
def _build_lookup(self, field_name, lookup, value, field=CharField):
def _build_lookup(self, field_name, lookup, value, field=fields.CharField):
fake_field = field()
fake_field.set_attributes_from_name(field_name)
lhs = expressions.Col('faketable', fake_field, fake_field)
......@@ -53,7 +107,7 @@ class WhereTestCase(TestCase):
self.assertEqual(escape_ldap_filter('foo\\bar*wiz'), 'foo\\5cbar\\2awiz')
def test_char_field_max_length(self):
self.assertEqual(CharField(max_length=42).max_length, 42)
self.assertEqual(fields.CharField(max_length=42).max_length, 42)
def test_char_field_exact(self):
where = WhereNode()
......@@ -102,56 +156,77 @@ class WhereTestCase(TestCase):
def test_integer_field(self):
where = WhereNode()
where.add(self._build_lookup("uid", 'exact', 1, field=IntegerField), AND)
where.add(self._build_lookup("uid", 'exact', 1, field=fields.IntegerField), AND)
self.assertEqual(self._where_as_ldap(where), "(uid=1)")
where = WhereNode()
where.add(self._build_lookup("uid", 'gte', 1, field=IntegerField), AND)
where.add(self._build_lookup("uid", 'gte', 1, field=fields.IntegerField), AND)
self.assertEqual(self._where_as_ldap(where), "(uid>=1)")
where = WhereNode()
where.add(self._build_lookup("uid", 'lte', 1, field=IntegerField), AND)
where.add(self._build_lookup("uid", 'lte', 1, field=fields.IntegerField), AND)
self.assertEqual(self._where_as_ldap(where), "(uid<=1)")
where = WhereNode()
where.add(self._build_lookup("uid", 'in', [1, 2], field=IntegerField), AND)
where.add(self._build_lookup("uid", 'in', [1, 2], field=fields.IntegerField), AND)
self.assertEqual(self._where_as_ldap(where), "(|(uid=1)(uid=2))")
def test_float_field(self):
where = WhereNode()
where.add(self._build_lookup("uid", 'exact', 1.2, field=FloatField), AND)
where.add(self._build_lookup("uid", 'exact', 1.2, field=fields.FloatField), AND)
self.assertEqual(self._where_as_ldap(where), "(uid=1.2)")
where = WhereNode()
where.add(self._build_lookup("uid", 'gte', 1.2, field=FloatField), AND)
where.add(self._build_lookup("uid", 'gte', 1.2, field=fields.FloatField), AND)
self.assertEqual(self._where_as_ldap(where), "(uid>=1.2)")
where = WhereNode()
where.add(self._build_lookup("uid", 'lte', 1.2, field=FloatField), AND)
where.add(self._build_lookup("uid", 'lte', 1.2, field=fields.FloatField), AND)
self.assertEqual(self._where_as_ldap(where), "(uid<=1.2)")
def test_list_field_contains(self):
where = WhereNode()
where.add(self._build_lookup("memberUid", 'contains', 'foouser', field=ListField), AND)
where.add(self._build_lookup("memberUid", 'contains', 'foouser', field=fields.ListField), AND)
self.assertEqual(self._where_as_ldap(where), "(memberUid=foouser)")
where = WhereNode()
where.add(self._build_lookup("memberUid", 'contains', '(foouser)', field=ListField), AND)
where.add(self._build_lookup("memberUid", 'contains', '(foouser)', field=fields.ListField), AND)
self.assertEqual(self._where_as_ldap(where), "(memberUid=\\28foouser\\29)")
def test_date_field(self):
where = WhereNode()
where.add(self._build_lookup("birthday", 'exact', '2013-09-03', field=DateField), AND)
where.add(self._build_lookup("birthday", 'exact', '2013-09-03', field=fields.DateField), AND)
self.assertEqual(self._where_as_ldap(where), "(birthday=2013-09-03)")
def test_datetime_field(self):
dt = datetime.datetime(2018, 6, 25, 20, 21, 22, tzinfo=UTC)
where = WhereNode()
where.add(self._build_lookup("modifyTimestamp", 'exact', dt, field=fields.DateTimeField,), AND)
self.assertEqual(self._where_as_ldap(where), "(modifyTimestamp=20180625202122.000000Z)")
where = WhereNode()
where.add(self._build_lookup("modifyTimestamp", 'lte', dt, field=fields.DateTimeField,), AND)
self.assertEqual(self._where_as_ldap(where), "(modifyTimestamp<=20180625202122.000000Z)")
where = WhereNode()
where.add(self._build_lookup("modifyTimestamp", 'gte', dt, field=fields.DateTimeField,), AND)
self.assertEqual(self._where_as_ldap(where), "(modifyTimestamp>=20180625202122.000000Z)")
def test_timestamp_field(self):
dt = datetime.datetime(2018, 6, 25, 20, 21, 22, tzinfo=UTC)
where = WhereNode()
where.add(self._build_lookup("shadowLastChange", 'exact', dt, field=fields.TimestampField), AND)
self.assertEqual(self._where_as_ldap(where), "(shadowLastChange=1529958082)")
def test_and(self):
where = WhereNode()
where.add(self._build_lookup("cn", 'exact', "foo", field=CharField), AND)
where.add(self._build_lookup("givenName", 'exact', "bar", field=CharField), AND)
where.add(self._build_lookup("cn", 'exact', "foo", field=fields.CharField), AND)
where.add(self._build_lookup("givenName", 'exact', "bar", field=fields.CharField), AND)
self.assertEqual(self._where_as_ldap(where), "(&(cn=foo)(givenName=bar))")
def test_or(self):
where = WhereNode()
where.add(self._build_lookup("cn", 'exact', "foo", field=CharField), AND)
where.add(self._build_lookup("givenName", 'exact', "bar", field=CharField), OR)
where.add(self._build_lookup("cn", 'exact', "foo", field=fields.CharField), AND)
where.add(self._build_lookup("givenName", 'exact', "bar", field=fields.CharField), OR)
self.assertEqual(self._where_as_ldap(where), "(|(cn=foo)(givenName=bar))")
......@@ -4,6 +4,6 @@
from __future__ import unicode_literals
__version__ = '1.0.0'
__version__ = '1.2.0'
VERSION = __version__
......@@ -45,8 +45,8 @@ setup(
url="https://github.com/{pn}/{pn}".format(pn=PACKAGE),
packages=find_packages(exclude=['tests*', 'examples*']),
install_requires=[
'Django>=1.8',
'pyldap>=2.4.25',
'Django>=1.11',
'python-ldap>=3.0',
],
setup_requires=[
'setuptools>=0.8',
......