diff --git a/django_cookbook/model/__init__.py b/django_cookbook/model/__init__.py new file mode 100644 index 0000000..369eb28 --- /dev/null +++ b/django_cookbook/model/__init__.py @@ -0,0 +1 @@ +__author__ = 'dbate' diff --git a/django_cookbook/model/fields.py b/django_cookbook/model/fields.py new file mode 100644 index 0000000..bf3e1c5 --- /dev/null +++ b/django_cookbook/model/fields.py @@ -0,0 +1,42 @@ +import json +from django.core.exceptions import ValidationError +from django.db.models import TextField +import re + + +def is_iterable(obj): + try: + iter(obj) + return True + except TypeError: + return False + + +class IterField(TextField): + """ + Stores the an iterable object in the the database. The data is stored as JSON and so all the values given to the + field must be serializable by the "json" module. This includes dict, list, tuple, str, int, float, True, False and + None + """ + + LIST_RE = re.compile(r"\[(.*)\]") + + def to_python(self, value): + if isinstance(value, list): + return value + + if not isinstance(value, str): + raise ValidationError("Invalid input for a IterField instance") + + if not value: + return [] + + # We could store the data as a string representation of the iterable which we then "eval" but this would allow + # for malicious data to be stores in the field so we need to do some sanity checking on the string. We let the + # json library handle this. + return json.loads(value) + + def get_prep_value(self, value): + return json.dumps(value, separators=(',', ':')) + + diff --git a/django_cookbook/test/__init__.py b/django_cookbook/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/django_cookbook/test/settings.py b/django_cookbook/test/settings.py new file mode 100644 index 0000000..2ae39bd --- /dev/null +++ b/django_cookbook/test/settings.py @@ -0,0 +1,80 @@ +""" +Django settings for quokka project. + +For more information on this file, see +https://docs.djangoproject.com/en/dev/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/dev/ref/settings/ +""" + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +import os +from django.conf.global_settings import MEDIA_ROOT +import sys + +import socket + +try: + HOSTNAME = socket.gethostname() +except: + HOSTNAME = 'localhost' + +BASE_DIR = os.path.dirname(os.path.dirname(__file__)) + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/dev/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = '!j!rcmgm!@*zhjkq)3tl*r&&zug3&4hklo*s)#b*5_-=u0s1iw' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +TEMPLATE_DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = ( + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', +) + +MIDDLEWARE_CLASSES = ( + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +) + + + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': ':memory:', + } +} + +# Internationalization +# https://docs.djangoproject.com/en/dev/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True \ No newline at end of file diff --git a/django_cookbook/test/unit/__init__.py b/django_cookbook/test/unit/__init__.py new file mode 100644 index 0000000..369eb28 --- /dev/null +++ b/django_cookbook/test/unit/__init__.py @@ -0,0 +1 @@ +__author__ = 'dbate' diff --git a/django_cookbook/test/unit/test_IterField_GetPrepValue.py b/django_cookbook/test/unit/test_IterField_GetPrepValue.py new file mode 100644 index 0000000..fc84074 --- /dev/null +++ b/django_cookbook/test/unit/test_IterField_GetPrepValue.py @@ -0,0 +1,37 @@ +import os +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_cookbook.test.settings") + +from django_cookbook.model.fields import IterField +from django.test import TestCase + + +class IterField_GetPrepValue(TestCase): + def test_ValueIsAnEmptyList_StringIsEmptyList(self): + field = IterField() + + self.assertEqual("[]", field.get_prep_value([])) + + def test_ValueIsAnEmptyDictionary_StringIsEmptyDict(self): + field = IterField() + + self.assertEqual("{}", field.get_prep_value({})) + + def test_ValueIsListOfElements_StringIsJsonRepresentationOfThatListWithNoSpaces(self): + field = IterField() + + self.assertEqual("[1,2,3,4,5]", field.get_prep_value([1, 2, 3, 4, 5])) + + def test_ValueIsListOfListsAndDictionaries_StringIsJsonRepresentationOfThatListWithNoSpaces(self): + field = IterField() + + self.assertEqual("[[1,2,3],{\"4\":5}]", field.get_prep_value([[1, 2, 3], {4: 5}])) + + def test_ValueIsDictionaryOfElements_StringIsJsonRepresentationOfThatListWithNoSpaces(self): + field = IterField() + + self.assertEqual("{\"1\":2,\"3\":4,\"5\":6}", field.get_prep_value({1: 2, 3: 4, 5: 6})) + + def test_ValueIsDictionaryOfListsAndDictionaries_StringIsJsonRepresentationOfThatListWithNoSpaces(self): + field = IterField() + + self.assertEqual("{\"1\":{\"2\":3},\"4\":[5,6]}", field.get_prep_value({1: {2: 3}, 4: [5, 6]})) diff --git a/django_cookbook/test/unit/test_IterField_PrepToPython.py b/django_cookbook/test/unit/test_IterField_PrepToPython.py new file mode 100644 index 0000000..d8e16db --- /dev/null +++ b/django_cookbook/test/unit/test_IterField_PrepToPython.py @@ -0,0 +1,40 @@ +from django.test import TestCase +from django_cookbook.model.fields import IterField + + +class IterField_PrepToPython(TestCase): + def test_ValueIsEmptyList_ResultMatchesInput(self): + value = [] + field = IterField() + + prepped = field.get_prep_value(value) + py = field.to_python(prepped) + + self.assertEqual(value, py) + + def test_ValueIsListWithElementsListAndDicts_ResultMatchesInput(self): + value = [1, 2, 3, [4, 5, 6], {"foo": 8}] + field = IterField() + + prepped = field.get_prep_value(value) + py = field.to_python(prepped) + + self.assertEqual(value, py) + + def test_ValueIsEmptyDict_ResultMatchesInput(self): + value = {} + field = IterField() + + prepped = field.get_prep_value(value) + py = field.to_python(prepped) + + self.assertEqual(value, py) + + def test_ValueIsDictWithElementsListAndDicts_ResultMatchesInput(self): + value = {"foo": 1, "bar": [2, 3, 4], "boo": {"moo": 5}} + field = IterField() + + prepped = field.get_prep_value(value) + py = field.to_python(prepped) + + self.assertEqual(value, py) \ No newline at end of file diff --git a/django_cookbook/test/unit/test_IterField_ToPython.py b/django_cookbook/test/unit/test_IterField_ToPython.py new file mode 100644 index 0000000..f0c21f6 --- /dev/null +++ b/django_cookbook/test/unit/test_IterField_ToPython.py @@ -0,0 +1,37 @@ +import os +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_cookbook.test.settings") + +from django.test import TestCase +from django_cookbook.model.fields import IterField + + +class IterField_ToPython(TestCase): + def test_StringIsEmpty_ReturnedValueIsEmptyList(self): + field = IterField() + + self.assertEqual([], field.to_python("")) + + def test_StringIsEmptyList_ReturnedValueIsEmptyList(self): + field = IterField() + + self.assertEqual([], field.to_python("[]")) + + def test_StringIsListWithOneField_ReturnedValueIsListWithThatField(self): + field = IterField() + + self.assertEqual(["foo"], field.to_python('["foo"]')) + + def test_StringIsListWithMultipleField_ReturnedValueIsListWithThoseFields(self): + field = IterField() + + self.assertEqual(["foo", "bar", "boo"], field.to_python('["foo","bar","boo"]')) + + def test_StringIsListWithMultipleFieldOneOfWhichIsAList_ReturnedValueIsListWithThoseFields(self): + field = IterField() + + self.assertEqual(["foo", ["bar", "boo"], "moo"], field.to_python('["foo",["bar","boo"],"moo"]')) + + def test_StringIsDictionary_ReturnedValueIsADictionaryWithTheCorrectFields(self): + field = IterField() + + self.assertEqual({"foo": "bar", "boo": ["moo", "maa"]}, field.to_python('{"foo":"bar", "boo":["moo","maa"]}')) \ No newline at end of file diff --git a/django_cookbook/users.py b/django_cookbook/users.py index 8b1ad8f..32a63aa 100644 --- a/django_cookbook/users.py +++ b/django_cookbook/users.py @@ -5,11 +5,15 @@ def get_authenticated_users(include=[], exclude=[]): """ - Gets a lit of all the authenticated user who are in the include list and not in the exclude list + Gets a lit of all the authenticated user who are in the include list and not in the exclude list. + For example, assuming your user profile has a "friends" property to get all authenticated friends you would use: - :param include: - :param exclude: - :return: + >>> get_authenticated_users(include=user.friends, exclude=[user]) + + :param include: A list of users to include, if False no include filter is applied + :param exclude: A list of users to exclude + + :return: A query set containing all authenticate uses in """ # get the ids of the authenticated sessions sessions = Session.objects.filter(expire_date__gte=datetime.now()) diff --git a/setup.py b/setup.py index 57422b1..4b165bd 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ setup( name='django_cookbook', version='0.0.1', - packages=['django_cookbook', 'django_cookbook.templatetags'], + packages=['django_cookbook', 'django_cookbook.templatetags', 'django_cookbook.model'], url='', license='', author='',