Repository: joke2k/django-environ
Pull request #50 (2015-10-20 16:52:06)

Added logic for parsing Oracle database URLs
Added support for Oracle-style URLs either in the form:

* Explicit: `oracle://username:password@host:port/sid`
* TNS: `oracle://username:password@sid`

I don't know if these are canonical or exhaustive, but they seem to work. Also, there's some variation in the normal behavior of setting `config['PORT']` to `None` because `django.db.backends.oracle.base` tries to strip the `PORT` value before converting to int and so this fails when `PORT` is `None`. Strangely, the stripping logic isn't in the mysql or postgresql code, which is why a port value of `None` is valid for those backends.

`django/db/backends/oracle/base.py`
```python
193     def _connect_string(self):
194         settings_dict = self.settings_dict
195         if not settings_dict['HOST'].strip():
196             settings_dict['HOST'] = 'localhost'
197         if settings_dict['PORT'].strip():
198             dsn = Database.makedsn(settings_dict['HOST'],
199                                    int(settings_dict['PORT']),
200                                    settings_dict['NAME'])
```


Patch:
diff --git a/README.rst b/README.rst
index 151f8d7..947b271 100644
--- a/README.rst
+++ b/README.rst
@@ -177,6 +177,7 @@ Supported Types
     -  MySQL for GeoDjango: mysqlgis://
     -  SQLITE: sqlite://
     -  SQLITE with SPATIALITE for GeoDjango: spatialite://
+    -  Oracle: oracle://
     -  LDAP: ldap://
 - cache_url
     -  Database: dbcache://
diff --git a/environ/environ.py b/environ/environ.py
index ca07f93..edbeccb 100644
--- a/environ/environ.py
+++ b/environ/environ.py
@@ -63,6 +63,7 @@ class Env(object):
         'mysql': 'django.db.backends.mysql',
         'mysql2': 'django.db.backends.mysql',
         'mysqlgis': 'django.contrib.gis.db.backends.mysql',
+        'oracle': 'django.db.backends.oracle',
         'spatialite': 'django.contrib.gis.db.backends.spatialite',
         'sqlite': 'django.db.backends.sqlite3',
         'ldap': 'ldapdb.backends.ldap',
@@ -322,7 +323,7 @@ class Env(object):
     @classmethod
     def db_url_config(cls, url, engine=None):
         """Pulled from DJ-Database-URL, parse an arbitrary Database URL.
-        Support currently exists for PostgreSQL, PostGIS, MySQL and SQLite.
+        Support currently exists for PostgreSQL, PostGIS, MySQL, Oracle and SQLite.
 
         SQLite connects to file based databases. The same URL format is used, omitting the hostname,
         and using the "file" portion as the filename of the database.
@@ -371,6 +372,13 @@ class Env(object):
             'PORT': _cast_int(url.port),
         })
 
+        if url.scheme == 'oracle' and path == '':
+            config['NAME'] = config['HOST']
+            config['HOST'] = ''
+
+        if url.scheme == 'oracle' and config['PORT'] is None:
+            del(config['PORT']) # Django oracle/base.py strips port and fails on None
+
         if url.query:
             config_options = {}
             for k, v in urlparse.parse_qs(url.query).items():


Test patch:
diff --git a/environ/test.py b/environ/test.py
index 7f66c34..ddf5ec4 100644
--- a/environ/test.py
+++ b/environ/test.py
@@ -16,6 +16,8 @@ class BaseTests(unittest.TestCase):
     MYSQL = 'mysql://bea6eb0:69772142@us-cdbr-east.cleardb.com/heroku_97681?reconnect=true'
     MYSQLGIS = 'mysqlgis://user:password@127.0.0.1/some_database'
     SQLITE = 'sqlite:////full/path/to/your/database/file.sqlite'
+    ORACLE_TNS = 'oracle://user:password@sid/'
+    ORACLE = 'oracle://user:password@host:1521/sid'
     MEMCACHE = 'memcache://127.0.0.1:11211'
     REDIS = 'rediscache://127.0.0.1:6379:1?client_class=django_redis.client.DefaultClient&password=secret'
     EMAIL = 'smtps://user@domain.com:password@smtp.example.com:587'
@@ -45,6 +47,8 @@ class BaseTests(unittest.TestCase):
                     DATABASE_MYSQL_URL=cls.MYSQL,
                     DATABASE_MYSQL_GIS_URL=cls.MYSQLGIS,
                     DATABASE_SQLITE_URL=cls.SQLITE,
+                    DATABASE_ORACLE_URL=cls.ORACLE,
+                    DATABASE_ORACLE_TNS_URL=cls.ORACLE_TNS,
                     CACHE_URL=cls.MEMCACHE,
                     CACHE_REDIS=cls.REDIS,
                     EMAIL_URL=cls.EMAIL,
@@ -167,6 +171,22 @@ class EnvTests(BaseTests):
         self.assertEqual(mysql_gis_config['PASSWORD'], 'password')
         self.assertEqual(mysql_gis_config['PORT'], None)
 
+        oracle_config = self.env.db('DATABASE_ORACLE_TNS_URL')
+        self.assertEqual(oracle_config['ENGINE'], 'django.db.backends.oracle')
+        self.assertEqual(oracle_config['NAME'], 'sid')
+        self.assertEqual(oracle_config['HOST'], '')
+        self.assertEqual(oracle_config['USER'], 'user')
+        self.assertEqual(oracle_config['PASSWORD'], 'password')
+        self.assertFalse('PORT' in oracle_config)
+
+        oracle_config = self.env.db('DATABASE_ORACLE_URL')
+        self.assertEqual(oracle_config['ENGINE'], 'django.db.backends.oracle')
+        self.assertEqual(oracle_config['NAME'], 'sid')
+        self.assertEqual(oracle_config['HOST'], 'host')
+        self.assertEqual(oracle_config['USER'], 'user')
+        self.assertEqual(oracle_config['PASSWORD'], 'password')
+        self.assertEqual(oracle_config['PORT'], 1521)
+
         sqlite_config = self.env.db('DATABASE_SQLITE_URL')
         self.assertEqual(sqlite_config['ENGINE'], 'django.db.backends.sqlite3')
         self.assertEqual(sqlite_config['NAME'], '/full/path/to/your/database/file.sqlite')
diff --git a/environ/test_env.txt b/environ/test_env.txt
index 112ca3e..f4c39ca 100644
--- a/environ/test_env.txt
+++ b/environ/test_env.txt
@@ -25,3 +25,5 @@ STR_VAR=bar
 INT_LIST=42,33
 CYRILLIC_VAR=фуубар
 INT_TUPLE=(42,33)
+DATABASE_ORACLE_TNS_URL=oracle://user:password@sid
+DATABASE_ORACLE_URL=oracle://user:password@host:1521/sid