├── .gitignore ├── README.rst ├── djangojsonschema ├── __init__.py ├── forms.py ├── jsonschema.py └── tests │ ├── __init__.py │ ├── test_form_to_jsonschema.py │ └── test_jsonschemafield.py ├── requirements.txt ├── setup.py ├── tests ├── __init__.py ├── noseplugins.py ├── runtests.py ├── test_settings.py └── urls.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cdo] 2 | *~ 3 | 4 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | ============ 4 | Introduction 5 | ============ 6 | 7 | django-jsonschema converts Django Forms into JSON Schema compatibile representations 8 | 9 | ------------ 10 | Requirements 11 | ------------ 12 | 13 | * Python 2.6 or later 14 | * Django 1.4 or later 15 | 16 | 17 | ===== 18 | Usage 19 | ===== 20 | 21 | To convert a form to a JSON Schema:: 22 | 23 | from djangojsonschema.jsonschema import DjangoFormToJSONSchema 24 | 25 | schema_repr = DjangoFormToJSONSchema().convert_form(MyForm) 26 | 27 | 28 | To embed a JSON Schema as a form field:: 29 | 30 | from djangojsonschema.forms import JSONSchemaField 31 | 32 | #where schema is a python dictionay like schema_repr in the first exmaple 33 | 34 | class MyForm(forms.Form): 35 | subfield = JSONSchemaField(schema=schema) 36 | 37 | form = MyForm(data={'subfield':''}) 38 | form.validate() #will validate the subfield entry against schema 39 | form['subfield'].as_widget() #will render a textarea widget with a data-schemajson attribute 40 | -------------------------------------------------------------------------------- /djangojsonschema/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zbyte64/django-jsonschema/a323a35bd3462ddcad5128928930dafc2a52141e/djangojsonschema/__init__.py -------------------------------------------------------------------------------- /djangojsonschema/forms.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | import json 4 | 5 | from jsonschema import validate, ValidationError as JSONSchemaValidationError 6 | 7 | from django import forms 8 | from django.core.exceptions import ValidationError 9 | 10 | 11 | class SchemaValidator(object): 12 | def __init__(self, schema): 13 | self.schema = schema 14 | 15 | def __call__(self, value): 16 | try: 17 | decoded_value = json.loads(value) 18 | except ValueError as error: 19 | raise ValidationError(str(error)) 20 | try: 21 | validate(decoded_value, self.schema) 22 | except JSONSchemaValidationError as error: 23 | raise ValidationError('%s: %s' %('.'.join(error.path), error.message)) 24 | return value 25 | 26 | class JSONSchemaField(forms.CharField): 27 | ''' 28 | A django form field that takes in a schema definition and serializes the result in JSON. 29 | Renders to a textarea with a data-schemajson attribute containing the initial json . 30 | Javascript is loaded to convert the field into an Alpacajs powered form: http://www.alpacajs.org/ 31 | 32 | Upon return validate the submission using python-jsonschema 33 | ''' 34 | widget = forms.Textarea 35 | 36 | def __init__(self, schema, **kwargs): 37 | self.schema = schema 38 | super(JSONSchemaField, self).__init__(**kwargs) 39 | self.validators.append(SchemaValidator(schema=schema)) 40 | 41 | def widget_attrs(self, widget): 42 | attrs = super(JSONSchemaField, self).widget_attrs(widget) 43 | attrs.update(self.get_data_attributes()) 44 | return attrs 45 | 46 | def get_data_attributes(self): 47 | return { 48 | 'data-schemajson': json.dumps(self.schema) 49 | } 50 | 51 | #TODO make the js handling pluggable 52 | class Media: 53 | js = ('http://www.alpacajs.org/js/alpaca.min.js',) 54 | css = { 55 | 'all': ('http://www.alpacajs.org/css/alpaca.min.css',) 56 | } 57 | 58 | #CONSIDER: a JSONSchemaForm that has a schema attribute and constructs the appropriate base_fields with JSONSchemaFields for the complex subfields. 59 | #class MyForm(JSONSchemaForm): schema=schema; afield=forms.CharField() 60 | #model forms infer that we should save these values and should be custom, likely utilizing a single JSONSchemaField 61 | -------------------------------------------------------------------------------- /djangojsonschema/jsonschema.py: -------------------------------------------------------------------------------- 1 | #TODO find a better submodule name 2 | from django.forms import widgets, fields 3 | import inspect 4 | 5 | def pretty_name(name): 6 | """Converts 'first_name' to 'First name'""" 7 | if not name: 8 | return u'' 9 | return name.replace('_', ' ').capitalize() 10 | 11 | class DjangoFormToJSONSchema(object): 12 | def convert_form(self, form, json_schema=None): 13 | if json_schema is None: 14 | json_schema = { 15 | #'title':dockit_schema._meta 16 | #'description' 17 | 'type':'object', 18 | 'properties':{}, #TODO SortedDict 19 | 'required':[], #required fields should be in here 20 | } 21 | fields = form.base_fields 22 | 23 | # If a Form instance is given, use the 'fields' attribute if it exists 24 | # since instances are allowed to modify them. 25 | if not inspect.isclass(form) and hasattr(form, 'fields'): 26 | fields = form.fields 27 | 28 | for name, field in fields.iteritems(): 29 | json_schema['properties'][name] = self.convert_formfield(name, field, json_schema) 30 | if field.required: 31 | json_schema['required'].append(name) 32 | return json_schema 33 | 34 | input_type_map = { 35 | 'text': 'string', 36 | } 37 | 38 | def convert_formfield(self, name, field, json_schema): 39 | #TODO detect bound field 40 | widget = field.widget 41 | target_def = { 42 | 'title': field.label or pretty_name(name), 43 | 'description': field.help_text, 44 | } 45 | # if field.required: #removed since it is not correct 46 | # target_def['required'] = [name] #TODO this likely is not correct 47 | #TODO JSONSchemaField; include subschema and ref the type 48 | if isinstance(field, fields.URLField): 49 | target_def['type'] = 'string' 50 | target_def['format'] = 'url' 51 | elif isinstance(field, fields.FileField): 52 | target_def['type'] = 'string' 53 | target_def['format'] = 'uri' 54 | elif isinstance(field, fields.DateField): 55 | target_def['type'] = 'string' 56 | target_def['format'] = 'date' 57 | elif isinstance(field, fields.DateTimeField): 58 | target_def['type'] = 'string' 59 | target_def['format'] = 'datetime' 60 | elif isinstance(field, (fields.DecimalField, fields.FloatField)): 61 | target_def['type'] = 'number' 62 | elif isinstance(field, fields.IntegerField): 63 | target_def['type'] = 'integer' 64 | elif isinstance(field, fields.EmailField): 65 | target_def['type'] = 'string' 66 | target_def['format'] = 'email' 67 | elif isinstance(field, fields.NullBooleanField): 68 | target_def['type'] = 'boolean' 69 | elif isinstance(widget, widgets.CheckboxInput): 70 | target_def['type'] = 'boolean' 71 | elif isinstance(widget, widgets.Select): 72 | if widget.allow_multiple_selected: 73 | target_def['type'] = 'array' 74 | else: 75 | target_def['type'] = 'string' 76 | target_def['enum'] = [choice[0] for choice in field.choices] 77 | elif isinstance(widget, widgets.Input): 78 | translated_type = self.input_type_map.get(widget.input_type, 'string') 79 | target_def['type'] = translated_type 80 | else: 81 | target_def['type'] = 'string' 82 | return target_def 83 | 84 | class DjangoModelToJSONSchema(DjangoFormToJSONSchema): 85 | def convert_model(self, model, json_schema=None): 86 | model_form = None #TODO convert to model form 87 | #TODO handle many2many and inlines 88 | return self.convert_form(model_form, json_schema) 89 | 90 | #TODO move to django-dockit 91 | class DocKitSchemaToJSONSchema(DjangoFormToJSONSchema): 92 | def convert_dockitschema(self, dockit_schema, json_schema=None): 93 | if json_schema is None: 94 | json_schema = { 95 | #'title':dockit_schema._meta 96 | #'description' 97 | 'type':'object', 98 | 'properties':{}, #TODO SortedDict 99 | } 100 | for key, field in dockit_schema._meta.fields.iteritems(): 101 | json_schema['properties'][key] = self.convert_dockitfield(key, field, json_schema) 102 | return json_schema 103 | 104 | def convert_dockitfield(self, name, field, json_schema): 105 | #if simple field, get the form field 106 | if True: #TODO is simple 107 | formfield = field.formfield() 108 | return self.convert_formfield(formfield, json_schema) 109 | #else: #complex stuff 110 | target_def = {} 111 | 112 | -------------------------------------------------------------------------------- /djangojsonschema/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zbyte64/django-jsonschema/a323a35bd3462ddcad5128928930dafc2a52141e/djangojsonschema/tests/__init__.py -------------------------------------------------------------------------------- /djangojsonschema/tests/test_form_to_jsonschema.py: -------------------------------------------------------------------------------- 1 | from jsonschema import Draft4Validator 2 | 3 | from django.utils import unittest 4 | from django import forms 5 | 6 | from djangojsonschema.jsonschema import DjangoFormToJSONSchema 7 | 8 | 9 | check_schema = Draft4Validator.check_schema 10 | 11 | CHOICES = [('first', 'first'), ('second', 'second')] 12 | 13 | class TestForm(forms.Form): 14 | a_charfield = forms.CharField(help_text='Any string') 15 | a_textarea = forms.CharField(widget=forms.Textarea, help_text='Any paragraph') 16 | url = forms.URLField() 17 | a_boolean = forms.BooleanField() 18 | select_option = forms.ChoiceField(choices=CHOICES) 19 | a_date = forms.DateField() 20 | a_datetime = forms.DateTimeField() 21 | a_decimal = forms.DecimalField() 22 | an_email = forms.EmailField() 23 | a_file = forms.FileField() 24 | #a_filepath = forms.FilePathField() 25 | a_float = forms.FloatField() 26 | an_image = forms.ImageField() 27 | an_integer = forms.IntegerField() 28 | an_ipaddress = forms.IPAddressField() 29 | #a_generic_ipaddress = forms.GenericIPAddressField() 30 | a_multiple_choice = forms.MultipleChoiceField(choices=CHOICES) 31 | a_typed_multiple_choice = forms.TypedMultipleChoiceField(choices=CHOICES) 32 | a_null_boolean = forms.NullBooleanField() #not sure what this should be 33 | a_regex = forms.RegexField(regex=r'<([A-Z][A-Z0-9]*)\b[^>]*>(.*?)') #matches tags 34 | a_slug = forms.SlugField() 35 | a_time = forms.TimeField() 36 | 37 | class FormToJsonSchemaTestCase(unittest.TestCase): 38 | def setUp(self): 39 | self.encoder = DjangoFormToJSONSchema() 40 | 41 | def test_convert_form(self): 42 | form_repr = self.encoder.convert_form(TestForm) 43 | print form_repr 44 | try: 45 | check_schema(form_repr) 46 | except Exception as error: 47 | print error.message 48 | print error.path 49 | print error.validator 50 | print error.cause 51 | raise 52 | instance_form_repr = self.encoder.convert_form(TestForm()) 53 | check_schema(instance_form_repr) 54 | 55 | def test_convert_charfield(self): 56 | name = 'a_charfield' 57 | ideal_repr = { 58 | 'required': [name], 59 | 'type': 'string', 60 | 'description': u'Any string', 61 | 'title': 'A charfield', 62 | } 63 | field = TestForm.base_fields[name] 64 | json_schema = {'properties':{}} 65 | schema_repr = self.encoder.convert_formfield(name, field, json_schema) 66 | self.assertEqual(schema_repr, ideal_repr) 67 | 68 | def test_convert_charfield(self): 69 | #CONSIDER: the json spec doesn't define a textarea, this is an option of alpacajs 70 | name = 'a_textarea' 71 | ideal_repr = { 72 | 'required': [name], 73 | 'type': 'string', 74 | 'description': u'Any paragraph', 75 | 'title': 'A textarea', 76 | } 77 | field = TestForm.base_fields[name] 78 | json_schema = {'properties':{}} 79 | schema_repr = self.encoder.convert_formfield(name, field, json_schema) 80 | self.assertEqual(schema_repr, ideal_repr) 81 | 82 | def test_convert_urlfield(self): 83 | name = 'url' 84 | ideal_repr = { 85 | 'required': [name], 86 | 'type': 'string', 87 | 'format': 'url', 88 | 'description': u'', 89 | 'title': 'Url', 90 | } 91 | field = TestForm.base_fields[name] 92 | json_schema = {'properties':{}} 93 | schema_repr = self.encoder.convert_formfield(name, field, json_schema) 94 | self.assertEqual(schema_repr, ideal_repr) 95 | 96 | def test_convert_booleanfield(self): 97 | name = 'a_boolean' 98 | ideal_repr = { 99 | 'required': [name], 100 | 'type': 'boolean', 101 | 'description': u'', 102 | 'title': 'A boolean', 103 | } 104 | field = TestForm.base_fields[name] 105 | json_schema = {'properties':{}} 106 | schema_repr = self.encoder.convert_formfield(name, field, json_schema) 107 | self.assertEqual(schema_repr, ideal_repr) 108 | 109 | def test_convert_select_option(self): 110 | name = 'select_option' 111 | ideal_repr = { 112 | 'required': [name], 113 | 'type': 'string', 114 | 'description': u'', 115 | 'title': 'Select option', 116 | 'enum': ['first', 'second'], 117 | } 118 | field = TestForm.base_fields[name] 119 | json_schema = {'properties':{}} 120 | schema_repr = self.encoder.convert_formfield(name, field, json_schema) 121 | self.assertEqual(schema_repr, ideal_repr) 122 | 123 | def test_convert_date(self): 124 | name = 'a_date' 125 | ideal_repr = { 126 | 'required': [name], 127 | 'type': 'string', 128 | 'format': 'date', 129 | 'description': u'', 130 | 'title': 'A date', 131 | } 132 | field = TestForm.base_fields[name] 133 | json_schema = {'properties':{}} 134 | schema_repr = self.encoder.convert_formfield(name, field, json_schema) 135 | self.assertEqual(schema_repr, ideal_repr) 136 | 137 | def test_convert_datetime(self): 138 | name = 'a_datetime' 139 | ideal_repr = { 140 | 'required': [name], 141 | 'type': 'string', 142 | 'format': 'datetime', 143 | 'description': u'', 144 | 'title': 'A datetime', 145 | } 146 | field = TestForm.base_fields[name] 147 | json_schema = {'properties':{}} 148 | schema_repr = self.encoder.convert_formfield(name, field, json_schema) 149 | self.assertEqual(schema_repr, ideal_repr) 150 | 151 | def test_convert_decimal(self): 152 | name = 'a_decimal' 153 | ideal_repr = { 154 | 'required': [name], 155 | 'type': 'number', 156 | 'description': u'', 157 | 'title': 'A decimal', 158 | } 159 | field = TestForm.base_fields[name] 160 | json_schema = {'properties':{}} 161 | schema_repr = self.encoder.convert_formfield(name, field, json_schema) 162 | self.assertEqual(schema_repr, ideal_repr) 163 | 164 | def test_convert_email(self): 165 | name = 'an_email' 166 | ideal_repr = { 167 | 'required': [name], 168 | 'type': 'string', 169 | 'format': 'email', 170 | 'description': u'', 171 | 'title': 'An email', 172 | } 173 | field = TestForm.base_fields[name] 174 | json_schema = {'properties':{}} 175 | schema_repr = self.encoder.convert_formfield(name, field, json_schema) 176 | self.assertEqual(schema_repr, ideal_repr) 177 | 178 | def test_convert_file(self): 179 | name = 'a_file' 180 | ideal_repr = { 181 | 'required': [name], 182 | 'type': 'string', 183 | 'format': 'uri', 184 | 'description': u'', 185 | 'title': 'A file', 186 | } 187 | field = TestForm.base_fields[name] 188 | json_schema = {'properties':{}} 189 | schema_repr = self.encoder.convert_formfield(name, field, json_schema) 190 | self.assertEqual(schema_repr, ideal_repr) 191 | 192 | def test_convert_float(self): 193 | name = 'a_float' 194 | ideal_repr = { 195 | 'required': [name], 196 | 'type': 'number', 197 | 'description': u'', 198 | 'title': 'A float', 199 | } 200 | field = TestForm.base_fields[name] 201 | json_schema = {'properties':{}} 202 | schema_repr = self.encoder.convert_formfield(name, field, json_schema) 203 | self.assertEqual(schema_repr, ideal_repr) 204 | 205 | def test_convert_integer(self): 206 | name = 'an_integer' 207 | ideal_repr = { 208 | 'required': [name], 209 | 'type': 'integer', 210 | 'description': u'', 211 | 'title': 'An integer', 212 | } 213 | field = TestForm.base_fields[name] 214 | json_schema = {'properties':{}} 215 | schema_repr = self.encoder.convert_formfield(name, field, json_schema) 216 | self.assertEqual(schema_repr, ideal_repr) 217 | 218 | -------------------------------------------------------------------------------- /djangojsonschema/tests/test_jsonschemafield.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from django.utils import unittest 4 | from django import forms 5 | 6 | from djangojsonschema.jsonschema import DjangoFormToJSONSchema 7 | from djangojsonschema.forms import JSONSchemaField 8 | 9 | 10 | class SurveyForm(forms.Form): 11 | name = forms.CharField() 12 | ranking = forms.ChoiceField(choices=[('1', 'excellent'), ('2', 'average'), ('3', 'poor')]) 13 | 14 | class JSONSchemaFieldTestCase(unittest.TestCase): 15 | def setUp(self): 16 | self.encoder = DjangoFormToJSONSchema() 17 | self.schema = self.encoder.convert_form(SurveyForm) 18 | 19 | class TargetForm(forms.Form): 20 | ticket = forms.CharField() 21 | 22 | #presumable a very configurable survey can be plugged in here 23 | survey_entry = JSONSchemaField(schema=self.schema) 24 | 25 | self.targetform = TargetForm 26 | 27 | def test_field_presentation(self): 28 | field = self.targetform()['survey_entry'] 29 | html = field.as_widget() 30 | self.assertTrue('data-schemajson' in html) 31 | 32 | def test_field_validation(self): 33 | survey_response = { 34 | 'name': 'John Smith', 35 | 'ranking': '1', 36 | } 37 | post = { 38 | 'survey_entry': json.dumps(survey_response), 39 | 'ticket':'text' 40 | } 41 | form = self.targetform(data=post) 42 | self.assertTrue(form.is_valid(), str(form.errors)) 43 | 44 | survey_response = { 45 | 'name': 'John Smith', 46 | 'ranking': '5', 47 | } 48 | post = { 49 | 'survey_entry': json.dumps(survey_response), 50 | 'ticket':'text' 51 | } 52 | form = self.targetform(data=post) 53 | self.assertFalse(form.is_valid()) 54 | print form.errors 55 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | jsonschema 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | try: 4 | from setuptools import setup, find_packages 5 | except ImportError: 6 | from distutils.core import setup, find_packages 7 | 8 | VERSION = '0.1.0' 9 | PATH = os.path.dirname(os.path.abspath(__file__)) 10 | try: 11 | LONG_DESC = '\n===='+open(os.path.join(PATH, 'README.rst'), 'r').read().split('====', 1)[-1] 12 | except IOError: #happens when using tox 13 | LONG_DESC = '' 14 | 15 | setup(name='django-jsonschema', 16 | version=VERSION, 17 | description="django-jsonschema converts Django Forms into JSON Schema compatibile representations", 18 | long_description=LONG_DESC, 19 | classifiers=[ 20 | 'Programming Language :: Python', 21 | 'Environment :: Web Environment', 22 | 'Framework :: Django', 23 | 'Operating System :: OS Independent', 24 | 'Natural Language :: English', 25 | 'Development Status :: 4 - Beta', 26 | 'Intended Audience :: Developers', 27 | 'License :: OSI Approved :: BSD License', 28 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 29 | ], 30 | keywords='django json schema', 31 | author = 'Jason Kraus', 32 | author_email = 'zbyte64@gmail.com', 33 | maintainer = 'Jason Kraus', 34 | maintainer_email = 'zbyte64@gmail.com', 35 | url='http://github.com/zbyte64/django-jsonschema', 36 | license='New BSD License', 37 | packages=find_packages(exclude=['tests']), 38 | test_suite='tests.runtests.runtests', 39 | tests_require=( 40 | 'pep8', 41 | 'coverage', 42 | 'django', 43 | 'Mock', 44 | 'nose', 45 | 'django-nose', 46 | ), 47 | include_package_data = True, 48 | zip_safe = False, 49 | ) 50 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zbyte64/django-jsonschema/a323a35bd3462ddcad5128928930dafc2a52141e/tests/__init__.py -------------------------------------------------------------------------------- /tests/noseplugins.py: -------------------------------------------------------------------------------- 1 | from nose.selector import Selector 2 | from nose.plugins import Plugin 3 | 4 | import os 5 | import logging 6 | import sys 7 | import unittest 8 | 9 | import django 10 | import django.test 11 | from django.conf import settings 12 | 13 | log = logging.getLogger(__name__) 14 | log.setLevel(logging.INFO) 15 | log.addHandler(logging.StreamHandler(sys.stdout)) 16 | 17 | class TestDiscoverySelector(Selector): 18 | 19 | def wantDirectory(self, dirname): 20 | log.debug('Do we want dir: %s' % dirname) 21 | if 'wizard' in dirname and django.VERSION[0] <= 1 and django.VERSION[1] < 4: 22 | return False 23 | 24 | return super(TestDiscoverySelector, self).wantDirectory(dirname) 25 | 26 | def wantClass(self, cls): 27 | log.debug('Do we want class: %s (%s)' % (cls, issubclass(cls, django.test.TestCase))) 28 | return issubclass(cls, unittest.TestCase) 29 | 30 | def wantFile(self, filename): 31 | log.debug('Do we want file: %s' % filename) 32 | if 'wizard' in filename and django.VERSION[0] <= 1 and django.VERSION[1] < 4: 33 | return False 34 | return filename.endswith('.py') 35 | 36 | def wantModule(self, module): 37 | log.debug('Do we want module: %s' % module) 38 | parts = module.__name__.split('.') 39 | if 'wizard' in parts and django.VERSION[0] <= 1 and django.VERSION[1] < 4: 40 | return False 41 | 42 | return super(TestDiscoverySelector, self).wantModule(module) 43 | 44 | def wantFunction(self, function): 45 | log.debug('Do we want function: %s' % function) 46 | return False 47 | 48 | class TestDiscoveryPlugin(Plugin): 49 | enabled = True 50 | 51 | def configure(self, options, conf): 52 | pass 53 | 54 | def prepareTestLoader(self, loader): 55 | loader.selector = TestDiscoverySelector(loader.config) 56 | 57 | -------------------------------------------------------------------------------- /tests/runtests.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test support harness for doing setup.py test. 3 | See http://ericholscher.com/blog/2009/jun/29/enable-setuppy-test-your-django-apps/. 4 | """ 5 | import sys 6 | 7 | import os 8 | 9 | os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.test_settings' 10 | 11 | # Bootstrap Django's settings. 12 | from django.conf import settings 13 | settings.DATABASES = { 14 | 'default': {'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:'} 15 | } 16 | settings.TEST_RUNNER = "django_nose.NoseTestSuiteRunner" 17 | settings.NOSE_PLUGINS = ['tests.noseplugins.TestDiscoveryPlugin'] 18 | 19 | def runtests(): 20 | """Test runner for setup.py test.""" 21 | # Run you some tests. 22 | import django.test.utils 23 | runner_class = django.test.utils.get_runner(settings) 24 | test_runner = runner_class(verbosity=1, interactive=True) 25 | failures = test_runner.run_tests(['djangojsonschema']) 26 | 27 | # Okay, so this is a nasty hack. If this isn't here, `setup.py test` craps out 28 | # when generating a coverage report via Nose. I have no idea why, or what's 29 | # supposed to be going on here, but this seems to fix the problem, and I 30 | # *really* want coverage, so, unless someone can tell me *why* I shouldn't 31 | # do this, I'm going to just whistle innocently and keep on doing this. 32 | sys.exitfunc = lambda: 0 33 | 34 | sys.exit(failures) 35 | -------------------------------------------------------------------------------- /tests/test_settings.py: -------------------------------------------------------------------------------- 1 | # Django settings for {{ project_name }} project. 2 | import os 3 | 4 | PROJECT_DIR = os.path.dirname(os.path.abspath(__file__)) 5 | DEBUG = True 6 | TEMPLATE_DEBUG = DEBUG 7 | 8 | ADMINS = ( 9 | # ('Your Name', 'your_email@example.com'), 10 | ) 11 | 12 | MANAGERS = ADMINS 13 | 14 | DATABASES = { 15 | 'default': { 16 | 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. 17 | 'NAME': ':memory:', # Or path to database file if using sqlite3. 18 | 'USER': '', # Not used with sqlite3. 19 | 'PASSWORD': '', # Not used with sqlite3. 20 | 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. 21 | 'PORT': '', # Set to empty string for default. Not used with sqlite3. 22 | } 23 | } 24 | 25 | # Local time zone for this installation. Choices can be found here: 26 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name 27 | # although not all choices may be available on all operating systems. 28 | # On Unix systems, a value of None will cause Django to use the same 29 | # timezone as the operating system. 30 | # If running in a Windows environment this must be set to the same as your 31 | # system time zone. 32 | TIME_ZONE = 'America/Chicago' 33 | 34 | # Language code for this installation. All choices can be found here: 35 | # http://www.i18nguy.com/unicode/language-identifiers.html 36 | LANGUAGE_CODE = 'en-us' 37 | 38 | SITE_ID = 1 39 | 40 | # If you set this to False, Django will make some optimizations so as not 41 | # to load the internationalization machinery. 42 | USE_I18N = True 43 | 44 | # If you set this to False, Django will not format dates, numbers and 45 | # calendars according to the current locale 46 | USE_L10N = True 47 | 48 | # Absolute filesystem path to the directory that will hold user-uploaded files. 49 | # Example: "/home/media/media.lawrence.com/media/" 50 | MEDIA_ROOT = os.path.join(PROJECT_DIR, 'media') 51 | 52 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a 53 | # trailing slash. 54 | # Examples: "http://media.lawrence.com/media/", "http://example.com/media/" 55 | MEDIA_URL = '/media/' 56 | 57 | # Absolute path to the directory static files should be collected to. 58 | # Don't put anything in this directory yourself; store your static files 59 | # in apps' "static/" subdirectories and in STATICFILES_DIRS. 60 | # Example: "/home/media/media.lawrence.com/static/" 61 | STATIC_ROOT = os.path.join(PROJECT_DIR, 'static') 62 | 63 | # URL prefix for static files. 64 | # Example: "http://media.lawrence.com/static/" 65 | STATIC_URL = '/static/' 66 | 67 | # URL prefix for admin static files -- CSS, JavaScript and images. 68 | # Make sure to use a trailing slash. 69 | # Examples: "http://foo.com/static/admin/", "/static/admin/". 70 | ADMIN_MEDIA_PREFIX = '/static/admin/' 71 | 72 | # Additional locations of static files 73 | STATICFILES_DIRS = ( 74 | # Put strings here, like "/home/html/static" or "C:/www/django/static". 75 | # Always use forward slashes, even on Windows. 76 | # Don't forget to use absolute paths, not relative paths. 77 | ) 78 | 79 | # List of finder classes that know how to find static files in 80 | # various locations. 81 | STATICFILES_FINDERS = ( 82 | 'django.contrib.staticfiles.finders.FileSystemFinder', 83 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 84 | # 'django.contrib.staticfiles.finders.DefaultStorageFinder', 85 | ) 86 | 87 | # Make this unique, and don't share it with anybody. 88 | SECRET_KEY = 'NOTASECRET' 89 | 90 | # List of callables that know how to import templates from various sources. 91 | TEMPLATE_LOADERS = ( 92 | 'django.template.loaders.filesystem.Loader', 93 | 'django.template.loaders.app_directories.Loader', 94 | # 'django.template.loaders.eggs.Loader', 95 | ) 96 | 97 | MIDDLEWARE_CLASSES = ( 98 | 'django.middleware.common.CommonMiddleware', 99 | 'django.contrib.sessions.middleware.SessionMiddleware', 100 | 'django.middleware.csrf.CsrfViewMiddleware', 101 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 102 | 'django.contrib.messages.middleware.MessageMiddleware', 103 | ) 104 | 105 | ROOT_URLCONF = 'tests.urls' 106 | 107 | TEMPLATE_DIRS = ( 108 | # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". 109 | # Always use forward slashes, even on Windows. 110 | # Don't forget to use absolute paths, not relative paths. 111 | ) 112 | 113 | INSTALLED_APPS = [ 114 | 'django.contrib.auth', 115 | 'django.contrib.contenttypes', 116 | 'django.contrib.sessions', 117 | 'django.contrib.sites', 118 | 'django.contrib.messages', 119 | 'django.contrib.staticfiles', 120 | 121 | 'djangojsonschema', 122 | # Uncomment the next line to enable the admin: 123 | 'django.contrib.admin', 124 | # Uncomment the next line to enable admin documentation: 125 | # 'django.contrib.admindocs', 126 | ] 127 | 128 | # A sample logging configuration. The only tangible logging 129 | # performed by this configuration is to send an email to 130 | # the site admins on every HTTP 500 error. 131 | # See http://docs.djangoproject.com/en/dev/topics/logging for 132 | # more details on how to customize your logging configuration. 133 | LOGGING = { 134 | 'version': 1, 135 | 'disable_existing_loggers': False, 136 | 'handlers': { 137 | 'mail_admins': { 138 | 'level': 'ERROR', 139 | 'class': 'django.utils.log.AdminEmailHandler' 140 | } 141 | }, 142 | 'loggers': { 143 | 'django.request': { 144 | 'handlers': ['mail_admins'], 145 | 'level': 'ERROR', 146 | 'propagate': True, 147 | }, 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /tests/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import patterns, include, url 2 | from django.conf import settings 3 | 4 | # Uncomment the next two lines to enable the admin: 5 | from django.contrib import admin 6 | admin.autodiscover() 7 | 8 | urlpatterns = patterns('', 9 | # Examples: 10 | # url(r'^$', '{{ project_name }}.views.home', name='home'), 11 | # url(r'^{{ project_name }}/', include('{{ project_name }}.foo.urls')), 12 | 13 | # Uncomment the admin/doc line below to enable admin documentation: 14 | # url(r'^admin/doc/', include('django.contrib.admindocs.urls')), 15 | 16 | # Uncomment the next line to enable the admin: 17 | url(r'^admin/', include(admin.site.urls)), 18 | ) 19 | 20 | 21 | 22 | if settings.DEBUG: 23 | urlpatterns += patterns('django.views', 24 | (r'^media/(?P.*)$', 'static.serve', {'document_root': settings.MEDIA_ROOT}), 25 | ) 26 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # Tox (http://codespeak.net/~hpk/tox/) is a tool for running tests 2 | # in multiple virtualenvs. This configuration file will run the 3 | # test suite on all supported python versions. To use it, "pip install tox" 4 | # and then run "tox" from this directory. 5 | 6 | [tox] 7 | envlist = 8 | py26_django14, 9 | py27_django14, 10 | py26_django15, 11 | py27_django15, 12 | 13 | [testenv] 14 | commands = python setup.py test 15 | 16 | 17 | [testenv:py26_django15] 18 | basepython = python2.6 19 | deps = 20 | Django>=1.5, <1.6 21 | -r{toxinidir}/requirements.txt 22 | 23 | [testenv:py26_django14] 24 | basepython = python2.6 25 | deps = 26 | Django>=1.4, <1.5 27 | -r{toxinidir}/requirements.txt 28 | 29 | [testenv:py27_django15] 30 | basepython = python2.7 31 | deps = 32 | Django>=1.5, <1.6 33 | -r{toxinidir}/requirements.txt 34 | 35 | [testenv:py27_django14] 36 | basepython = python2.7 37 | deps = 38 | Django>=1.4, <1.5 39 | -r{toxinidir}/requirements.txt 40 | 41 | --------------------------------------------------------------------------------