Skip to content

Commit 6a86eb1

Browse files
committed
initial commit of MongoDB backend
1 parent ff8e48e commit 6a86eb1

20 files changed

+1123
-85
lines changed

.github/workflows/mongodb_settings.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
DATABASES = {
2+
"default": {
3+
"ENGINE": "django_mongodb",
4+
"NAME": "djangotests",
5+
},
6+
"other": {
7+
"ENGINE": "django_mongodb",
8+
"NAME": "djangotests-other",
9+
},
10+
}
11+
DEFAULT_AUTO_FIELD = "django_mongodb.fields.MongoAutoField"
12+
PASSWORD_HASHERS = ("django.contrib.auth.hashers.MD5PasswordHasher",)
13+
SECRET_KEY = "django_tests_secret_key"
14+
USE_TZ = False

.github/workflows/test-python.yml

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ name: Python Tests
22

33
on:
44
push:
5+
branches:
6+
- main
57
pull_request:
68
workflow_dispatch:
79

@@ -21,7 +23,7 @@ jobs:
2123
- uses: actions/checkout@v4
2224
- uses: actions/setup-python@v5
2325
with:
24-
python-version: 3.8
26+
python-version: '3.10'
2527
cache: 'pip'
2628
cache-dependency-path: 'pyproject.toml'
2729
- name: Install Python dependencies
@@ -32,33 +34,47 @@ jobs:
3234
pre-commit run --hook-stage=manual --all-files
3335
3436
build:
35-
# supercharge/mongodb-github-action requires containers so we don't test other platforms
36-
runs-on: ${{ matrix.os }}
37-
strategy:
38-
matrix:
39-
os: [ubuntu-20.04]
40-
python-version: ["3.8", "3.11", "pypy-3.9"]
41-
fail-fast: false
42-
name: CPython ${{ matrix.python-version }}-${{ matrix.os }}
37+
name: Django Test Suite
38+
runs-on: ubuntu-latest
4339
steps:
44-
- uses: actions/checkout@v4
45-
- name: Setup Python
46-
uses: actions/setup-python@v5
40+
- name: Checkout django-mongodb
41+
uses: actions/checkout@v4
42+
- name: install the django-mongodb backend
43+
run: |
44+
pip3 install --upgrade pip
45+
pip3 install -e .
46+
- name: Checkout Django
47+
uses: actions/checkout@v4
4748
with:
48-
python-version: ${{ matrix.python-version }}
49-
cache: 'pip'
50-
cache-dependency-path: 'pyproject.toml'
51-
- name: Install dependencies
49+
repository: 'mongodb-forks/django'
50+
ref: 'mongodb-5.0.x'
51+
path: 'django_repo'
52+
- name: Install system packages for Django's Python test dependencies
5253
run: |
53-
pip install -U pip
54-
pip install -e ".[test]"
54+
sudo apt-get update
55+
sudo apt-get install libmemcached-dev
56+
- name: Install Django and its Python test dependencies
57+
run: |
58+
cd django_repo/tests/
59+
pip3 install -e ..
60+
pip3 install -r requirements/py3.txt
61+
- name: Copy the test settings file
62+
run: cp .github/workflows/mongodb_settings.py django_repo/tests/
5563
- name: Start MongoDB
5664
uses: supercharge/[email protected]
5765
with:
5866
mongodb-version: 4.4
5967
- name: Run tests
60-
run: |
61-
pytest
68+
run: >
69+
python3 django_repo/tests/runtests.py --settings mongodb_settings -v 2
70+
basic
71+
empty
72+
from_db_value
73+
model_fields.test_datetimefield
74+
model_fields.test_decimalfield
75+
model_fields.test_charfield
76+
model_fields.test_textfield
77+
or_lookups
6278
6379
docs:
6480
name: Docs Checks
@@ -69,8 +85,7 @@ jobs:
6985
with:
7086
cache: 'pip'
7187
cache-dependency-path: 'pyproject.toml'
72-
# Build docs on lowest supported Python for furo
73-
python-version: '3.8'
88+
python-version: '3.10'
7489
- name: Install dependencies
7590
run: |
7691
pip install -U pip

.pre-commit-config.yaml

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -72,17 +72,8 @@ repos:
7272
args: ["--schemafile", "https://json.schemastore.org/github-workflow"]
7373
stages: [manual]
7474

75-
- repo: https://github.com/ariebovenberg/slotscheck
76-
rev: v0.17.0
77-
hooks:
78-
- id: slotscheck
79-
files: \.py$
80-
exclude: "^(test|docs)/"
81-
stages: [manual]
82-
args: ["--no-strict-imports"]
83-
8475
- repo: https://github.com/codespell-project/codespell
8576
rev: "v2.2.6"
8677
hooks:
8778
- id: codespell
88-
args: ["-L", ""]
79+
args: ["-L", "nin"]

README.md

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,65 @@
11
# MongoDB backend for Django
22

3-
This library is in the early stages of development, and so it's possible the API may change in the future - we definitely want to continue expanding it. We welcome your feedback as we continue to explore and build this tool.
3+
This backend is in the pre-alpha stage of development. Backwards incompatible
4+
changes may be made without notice. We welcome your feedback as we continue to
5+
explore and build.
46

57
## Install and usage
68

79
Use the version of `django-mongodb` that corresponds to your version of
810
Django. For example, to get the latest compatible release for Django 5.0.x:
911

10-
`pip install django-mongodb==0.1.*`
12+
`pip install django-mongodb==5.0.*`
1113

1214
The minor release number of Django doesn't correspond to the minor release
1315
number of django-mongodb. Use the latest minor release of each.
1416

17+
While django-mongodb only has pre-releases (alphas or betas), you'll see an
18+
error with a list of the available versions. In that case, include `--pre` to
19+
allow `pip` to install the latest pre-release.
20+
21+
For example, if django-mongodb 5.0 alpha 1 is the latest available version
22+
of the 5.0 release series:
23+
24+
```
25+
$ pip install django-mongodb==5.0.*
26+
ERROR: Could not find a version that satisfies the requirement
27+
django-mongodb==5.0.* (from versions: ..., 5.0a1)
28+
29+
$ pip install --pre django-mongodb==5.0.*
30+
...
31+
Successfully installed ... django-mongodb-5.0a1 ...
32+
```
33+
1534
Configure the Django `DATABASES` setting similar to this:
1635

1736
```python
1837
DATABASES = {
1938
"default": {
2039
"ENGINE": "django_mongodb",
21-
"NAME": "MY_DATABASE",
22-
"SCHEMA": "MY_SCHEMA",
23-
"WAREHOUSE": "MY_WAREHOUSE",
40+
"NAME": "my_database",
2441
"USER": "my_user",
2542
"PASSWORD": "my_password",
26-
"ACCOUNT": "my_account",
2743
},
2844
}
2945
```
3046

3147
## Known issues and limitations
3248

33-
TODO
49+
- The following `QuerySet` methods aren't supported:
50+
- `aggregate()`
51+
- `distinct()`
52+
- `extra()`
53+
- `select_related()`
3454

35-
## Troubleshooting
55+
- Queries with joins aren't supported.
3656

37-
### Debug logging
57+
## Troubleshooting
3858

3959
TODO
60+
61+
## Credits
62+
63+
This project began by borrowing code from Django non-rel's
64+
[MongoDB Engine](https://github.com/django-nonrel/mongodb-engine),
65+
abandoned since 2015 and Django 1.6 (2-clause BSD license).

django_mongodb/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
__version__ = "5.0a0"
2+
3+
# Check Django compatibility before other imports which may fail if the
4+
# wrong version of Django is installed.
5+
from .utils import check_django_compatability
6+
7+
check_django_compatability()

django_mongodb/_version.py

Lines changed: 0 additions & 3 deletions
This file was deleted.

django_mongodb/base.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
from django.core.exceptions import ImproperlyConfigured
2+
from django.db.backends.base.base import BaseDatabaseWrapper
3+
from django.db.backends.signals import connection_created
4+
from pymongo.collection import Collection
5+
from pymongo.mongo_client import MongoClient
6+
7+
from . import dbapi as Database
8+
from .client import DatabaseClient
9+
from .creation import DatabaseCreation
10+
from .features import DatabaseFeatures
11+
from .introspection import DatabaseIntrospection
12+
from .operations import DatabaseOperations
13+
from .schema import DatabaseSchemaEditor
14+
15+
16+
class Cursor:
17+
"""A "nodb" cursor that does nothing except work on a context manager."""
18+
19+
def __enter__(self):
20+
pass
21+
22+
def __exit__(self, exception_type, exception_value, exception_traceback):
23+
pass
24+
25+
26+
class DatabaseWrapper(BaseDatabaseWrapper):
27+
data_types = {
28+
"AutoField": "int",
29+
"BigAutoField": "long",
30+
"BinaryField": "binData",
31+
"BooleanField": "bool",
32+
"CharField": "string",
33+
"DateField": "date",
34+
"DateTimeField": "date",
35+
"DecimalField": "decimal",
36+
"DurationField": "long",
37+
"FileField": "string",
38+
"FilePathField": "string",
39+
"FloatField": "double",
40+
"IntegerField": "int",
41+
"BigIntegerField": "long",
42+
"GenericIPAddressField": "string",
43+
"NullBooleanField": "bool",
44+
"OneToOneField": "int",
45+
"PositiveIntegerField": "long",
46+
"PositiveSmallIntegerField": "int",
47+
"SlugField": "string",
48+
"SmallIntegerField": "int",
49+
"TextField": "string",
50+
"TimeField": "date",
51+
"UUIDField": "string",
52+
}
53+
54+
vendor = "mongodb"
55+
Database = Database
56+
SchemaEditorClass = DatabaseSchemaEditor
57+
client_class = DatabaseClient
58+
creation_class = DatabaseCreation
59+
features_class = DatabaseFeatures
60+
introspection_class = DatabaseIntrospection
61+
ops_class = DatabaseOperations
62+
63+
def __init__(self, *args, **kwargs):
64+
super().__init__(*args, **kwargs)
65+
self.connected = False
66+
del self.connection
67+
68+
def get_collection(self, name, **kwargs):
69+
return Collection(self.database, name, **kwargs)
70+
71+
def __getattr__(self, attr):
72+
"""
73+
Connect to the database the first time `connection` or `database` are
74+
accessed.
75+
"""
76+
if attr in ["connection", "database"]:
77+
assert not self.connected
78+
self._connect()
79+
return getattr(self, attr)
80+
raise AttributeError(attr)
81+
82+
def _connect(self):
83+
settings_dict = self.settings_dict
84+
85+
options = settings_dict["OPTIONS"]
86+
# TODO: review and document OPERATIONS: https://github.com/mongodb-labs/django-mongodb/issues/6
87+
self.operation_flags = options.pop("OPERATIONS", {})
88+
if not any(k in ["save", "delete", "update"] for k in self.operation_flags):
89+
# Flags apply to all operations.
90+
flags = self.operation_flags
91+
self.operation_flags = {"save": flags, "delete": flags, "update": flags}
92+
93+
self.connection = MongoClient(
94+
host=settings_dict["HOST"] or None, port=int(settings_dict["PORT"] or 27017), **options
95+
)
96+
db_name = settings_dict["NAME"]
97+
if db_name:
98+
self.database = self.connection[db_name]
99+
100+
user = settings_dict["USER"]
101+
password = settings_dict["PASSWORD"]
102+
if user and password and not self.database.authenticate(user, password):
103+
raise ImproperlyConfigured("Invalid username or password.")
104+
105+
self.connected = True
106+
connection_created.send(sender=self.__class__, connection=self)
107+
108+
def _commit(self):
109+
pass
110+
111+
def _rollback(self):
112+
pass
113+
114+
def close(self):
115+
if self.connected:
116+
del self.connection
117+
del self.database
118+
self.connected = False
119+
120+
def cursor(self):
121+
return Cursor()

django_mongodb/client.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import signal
2+
3+
from django.db.backends.base.client import BaseDatabaseClient
4+
5+
6+
class DatabaseClient(BaseDatabaseClient):
7+
executable_name = "mongo"
8+
9+
@classmethod
10+
def settings_to_cmd_args_env(cls, settings_dict, parameters):
11+
raise NotImplementedError
12+
13+
def runshell(self, parameters):
14+
sigint_handler = signal.getsignal(signal.SIGINT)
15+
try:
16+
# Allow SIGINT to pass to mongo to abort queries.
17+
signal.signal(signal.SIGINT, signal.SIG_IGN)
18+
super().runshell(parameters)
19+
finally:
20+
# Restore the original SIGINT handler.
21+
signal.signal(signal.SIGINT, sigint_handler)

0 commit comments

Comments
 (0)