Skip to content

Commit

Permalink
Implemented List Field and Dict Field
Browse files Browse the repository at this point in the history
  • Loading branch information
nesdis committed Aug 30, 2018
1 parent 0f093b9 commit e2edf09
Show file tree
Hide file tree
Showing 10 changed files with 105 additions and 39 deletions.
7 changes: 3 additions & 4 deletions djongo/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
from django.db.models import *
from django.db.models import __all__ as models_all

from .fields import (
ArrayModelField, ListField, DjongoManager,
EmbeddedModelField, ArrayReferenceField, ObjectIdField,
GenericObjectIdField
GenericObjectIdField, DictField
)
#from .json import JSONField
from django.db.models import __all__ as models_all

__all__ = models_all + [
'DjongoManager', 'ListField', 'ArrayModelField',
'EmbeddedModelField', 'ArrayReferenceField', 'ObjectIdField',
'GenericObjectIdField', 'DictField'
]
53 changes: 37 additions & 16 deletions djongo/models/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,34 +77,55 @@ def _client(self):
)


class ListField(Field):
class FormlessField(Field):
empty_strings_allowed = False

def formfield(self, **kwargs):
raise TypeError(
'A Formless Field cannot be modified from Django Admin.'
)


class ListField(FormlessField):
"""
MongoDB allows the saving of arbitrary data inside it's embedded array. The `ListField` is useful in such cases.
MongoDB allows the saving of python lists as BSON Array type data. The `ListField` is useful in such cases.
"""
empty_strings_allowed = False

def __init__(self, *args, **kwargs):
self._value = []
super().__init__(*args, **kwargs)
def get_db_prep_value(self, value, connection, prepared=False):
if not isinstance(value, list):
raise ValueError(
f'Value: {value} must be of type list'
)
return value

def __set__(self, instance, value):
def to_python(self, value):
if not isinstance(value, list):
raise ValueError('Value must be a list')
raise ValueError(
f'Value: {value} stored in DB must be of type list'
'Did you miss any Migrations?'
)

self._value = value

def __get__(self, instance, owner):
return self._value
class DictField(FormlessField):
"""
MongoDB allows the saving of python dicts as BSON object type data. The `DictField` is useful in such cases.
"""
empty_strings_allowed = False

def get_db_prep_value(self, value, connection, prepared=False):
if prepared:
return value

if not isinstance(value, list):
raise ValueError('Value must be a list')

if not isinstance(value, dict):
raise ValueError(
f'Value: {value} must be of type list'
)
return value

def to_python(self, value):
if not isinstance(value, dict):
raise ValueError(
f'Value: {value} stored in DB must be of type list'
'Did you miss any Migrations?'
)

class ArrayModelField(Field):
"""
Expand Down
9 changes: 5 additions & 4 deletions djongo/sql2mongo/operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ def _make_regex(self, token):
raise SQLDecodeError

to_match = to_match.replace('%', '.*')
self._regex = '^'+ to_match + '$'
self._regex = '^' + to_match + '$'

def to_mongo(self):
return {self._field: {'$regex': self._regex}}
Expand Down Expand Up @@ -250,6 +250,7 @@ def to_mongo(self):
}
}


class NotOp(_UnaryOp):
def __init__(self, *args, **kwargs):
super().__init__(name='NOT', *args, **kwargs)
Expand Down Expand Up @@ -450,9 +451,9 @@ def link_op():

elif isinstance(tok, Parenthesis):
if (tok[1].match(tokens.Name.Placeholder, '.*', regex=True)
or tok[1].match(tokens.Keyword, 'Null')
or isinstance(tok[1], IdentifierList)
or tok[1].ttype == tokens.DML
or tok[1].match(tokens.Keyword, 'Null')
or isinstance(tok[1], IdentifierList)
or tok[1].ttype == tokens.DML
):
pass
else:
Expand Down
19 changes: 19 additions & 0 deletions tests/djongo_tests/project/dummy/models/misc_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from djongo import models


class ListEntry(models.Model):
_id = models.ObjectIdField()
headline = models.CharField(max_length=255)
authors = models.ListField()

def __str__(self):
return self.headline

class DictEntry(models.Model):
_id = models.ObjectIdField()
headline = models.CharField(max_length=255)
blog = models.DictField()

def __str__(self):
return self.headline

8 changes: 5 additions & 3 deletions tests/djongo_tests/project/dummy/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from django.test import TestCase as DjangoTestCase
from logging import getLogger, StreamHandler

from django.test import TestCase as DjangoTestCase


class TestCase(DjangoTestCase):

@classmethod
def setUpClass(cls):
root_logger = getLogger()
root_logger.addHandler(StreamHandler())
super().setUpClass()
if not root_logger.hasHandlers():
root_logger.addHandler(StreamHandler())
38 changes: 32 additions & 6 deletions tests/djongo_tests/project/dummy/tests/test_models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from dummy.models.array_models import ArrayEntry, ArrayAuthor
from dummy.models.basic_models import Blog, Entry, Author
from dummy.models.embedded_models import EmbeddedBlog, EmbeddedEntry
from dummy.models.misc_models import ListEntry, DictEntry
from dummy.models.reference_models import ReferenceEntry, ReferenceAuthor
from . import TestCase

Expand Down Expand Up @@ -181,12 +182,6 @@ def test_join(self):
name='b2',
tagline='t2'
)
# a1 = Author.objects.create(
# name='a1'
# )
# a2 = Author.objects.create(
# name='a2'
# )
e1 = Entry.objects.create(
headline='h1',
blog=b1
Expand All @@ -199,3 +194,34 @@ def test_join(self):
bqs = Blog.objects.filter(id__in=eqs).values('name')
self.assertEquals(list(bqs), [{'name': 'b1'}, {'name': 'b2'}])


class TestMisc(TestCase):

def test_create(self):
e1 = ListEntry()
e1.authors = ['a1', 'a2']
e1.headline = 'h1'
e1.save()
g = ListEntry.objects.get(
headline='h1'
)
self.assertEqual(e1, g)

# g = ListEntry.objects.get(
# authors={'0.': 'a1'}
# )
self.assertEqual(e1, g)
e2 = DictEntry()
e2.headline = 'h2'
e2.blog = {
'name': 'b1'
}
e2.save()
g = DictEntry.objects.get(
headline='h2'
)
self.assertEqual(e2, g)
g = DictEntry.objects.get(
blog={'name': 'b1'}
)
self.assertEqual(e2, g)
3 changes: 2 additions & 1 deletion tests/djongo_tests/project/manage.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@

root_logger = getLogger()
root_logger.setLevel(DEBUG)
root_logger.addHandler(StreamHandler())
if not root_logger.hasHandlers():
root_logger.addHandler(StreamHandler())

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings")
try:
Expand Down
1 change: 0 additions & 1 deletion tests/djongo_tests/project/project/settings_precheckin.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django_extensions',
]


Expand Down
4 changes: 1 addition & 3 deletions tests/djongo_tests/project/project/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@
1. Import the include() function: from django.conf.urls import url, include
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
"""
from django.conf.urls import url
from django.contrib import admin

urlpatterns = [
url(r'^admin/', admin.site.urls),
# url(r'^admin/', admin.site.urls),
]
2 changes: 1 addition & 1 deletion tests/precheckin.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
cmds = [
'makemigrations dummy',
'migrate',
# 'inspectdb',
'inspectdb',
'test dummy.tests.test_models'
]
settings = '--settings=project.settings_precheckin'
Expand Down

0 comments on commit e2edf09

Please sign in to comment.