diff --git a/.gitignore b/.gitignore index 2d028c7287..0ad4e34e23 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,4 @@ MANIFEST dist/ docs/_build/ tests/coverage_html/ -tests/.coverage \ No newline at end of file +tests/.coverage diff --git a/AUTHORS b/AUTHORS index 8cd86d38dc..973e32d05a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -36,6 +36,7 @@ The PRIMARY AUTHORS are (and/or have been): * Preston Holmes * Simon Charette * Donald Stufft + * Marc Tamlyn More information on the main contributors to Django can be found in docs/internals/committers.txt. @@ -121,6 +122,7 @@ answer newbie questions, and generally made Django that much better: Chris Cahoon Juan Manuel Caicedo Trevor Caira + Aaron Cannon Brett Cannon Ricardo Javier Cárdenes Medina Jeremy Carbaugh @@ -539,7 +541,6 @@ answer newbie questions, and generally made Django that much better: Aaron Swartz Ville Säävuori Mart Sõmermaa - Marc Tamlyn Christian Tanzer Tyler Tarabula Tyson Tate diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index a9af4baa59..53aef351c0 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -576,7 +576,7 @@ DEFAULT_EXCEPTION_REPORTER_FILTER = 'django.views.debug.SafeExceptionReporterFil ########### # The name of the class to use to run the test suite -TEST_RUNNER = 'django.test.simple.DjangoTestSuiteRunner' +TEST_RUNNER = 'django.test.runner.DiscoverRunner' ############ # FIXTURES # diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 9bc3d55454..7373837bb0 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -37,7 +37,7 @@ from django.utils.encoding import force_text HORIZONTAL, VERTICAL = 1, 2 # returns the
    class for a given radio_admin field -get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '') +get_ul_class = lambda x: 'radiolist%s' % (' inline' if x == HORIZONTAL else '') class IncorrectLookupParameters(Exception): @@ -189,7 +189,7 @@ class BaseModelAdmin(six.with_metaclass(RenameBaseModelAdminMethods)): kwargs['widget'] = widgets.AdminRadioSelect(attrs={ 'class': get_ul_class(self.radio_fields[db_field.name]), }) - kwargs['empty_label'] = db_field.blank and _('None') or None + kwargs['empty_label'] = _('None') if db_field.blank else None queryset = self.get_field_queryset(db, db_field, request) if queryset is not None: diff --git a/django/contrib/admindocs/tests/__init__.py b/django/contrib/admindocs/tests/__init__.py index 2a67f83bcb..e69de29bb2 100644 --- a/django/contrib/admindocs/tests/__init__.py +++ b/django/contrib/admindocs/tests/__init__.py @@ -1 +0,0 @@ -from .test_fields import TestFieldType diff --git a/django/contrib/admindocs/views.py b/django/contrib/admindocs/views.py index 6b1bea15f3..348727eb21 100644 --- a/django/contrib/admindocs/views.py +++ b/django/contrib/admindocs/views.py @@ -267,7 +267,7 @@ def model_detail(request, app_label, model_name): return render_to_response('admin_doc/model_detail.html', { 'root_path': urlresolvers.reverse('admin:index'), 'name': '%s.%s' % (opts.app_label, opts.object_name), - 'summary': _("Fields on %s objects") % opts.object_name, + 'summary': _("Attributes on %s objects") % opts.object_name, 'description': model.__doc__, 'fields': fields, }, context_instance=RequestContext(request)) diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py index e44e7a703e..0e08d8ef31 100644 --- a/django/contrib/auth/forms.py +++ b/django/contrib/auth/forms.py @@ -171,7 +171,7 @@ class AuthenticationForm(forms.Form): # Set the label for the "username" field. UserModel = get_user_model() self.username_field = UserModel._meta.get_field(UserModel.USERNAME_FIELD) - if not self.fields['username'].label: + if self.fields['username'].label is None: self.fields['username'].label = capfirst(self.username_field.verbose_name) def clean(self): diff --git a/django/contrib/auth/hashers.py b/django/contrib/auth/hashers.py index 092cccedde..6abdb5f476 100644 --- a/django/contrib/auth/hashers.py +++ b/django/contrib/auth/hashers.py @@ -9,7 +9,7 @@ from django.conf import settings from django.test.signals import setting_changed from django.utils import importlib from django.utils.datastructures import SortedDict -from django.utils.encoding import force_bytes, force_str +from django.utils.encoding import force_bytes, force_str, force_text from django.core.exceptions import ImproperlyConfigured from django.utils.crypto import ( pbkdf2, constant_time_compare, get_random_string) @@ -263,13 +263,13 @@ class BCryptSHA256PasswordHasher(BasePasswordHasher): Secure password hashing using the bcrypt algorithm (recommended) This is considered by many to be the most secure algorithm but you - must first install the py-bcrypt library. Please be warned that + must first install the bcrypt library. Please be warned that this library depends on native C code and might cause portability issues. """ algorithm = "bcrypt_sha256" digest = hashlib.sha256 - library = ("py-bcrypt", "bcrypt") + library = ("bcrypt", "bcrypt") rounds = 12 def salt(self): @@ -291,7 +291,7 @@ class BCryptSHA256PasswordHasher(BasePasswordHasher): password = force_bytes(password) data = bcrypt.hashpw(password, salt) - return "%s$%s" % (self.algorithm, data) + return "%s$%s" % (self.algorithm, force_text(data)) def verify(self, password, encoded): algorithm, data = encoded.split('$', 1) @@ -307,6 +307,9 @@ class BCryptSHA256PasswordHasher(BasePasswordHasher): else: password = force_bytes(password) + # Ensure that our data is a bytestring + data = force_bytes(data) + return constant_time_compare(data, bcrypt.hashpw(password, data)) def safe_summary(self, encoded): @@ -326,7 +329,7 @@ class BCryptPasswordHasher(BCryptSHA256PasswordHasher): Secure password hashing using the bcrypt algorithm This is considered by many to be the most secure algorithm but you - must first install the py-bcrypt library. Please be warned that + must first install the bcrypt library. Please be warned that this library depends on native C code and might cause portability issues. diff --git a/django/contrib/auth/tests/__init__.py b/django/contrib/auth/tests/__init__.py index 2e35e33d7f..2c308642e5 100644 --- a/django/contrib/auth/tests/__init__.py +++ b/django/contrib/auth/tests/__init__.py @@ -1,16 +1 @@ -from django.contrib.auth.tests.test_custom_user import * -from django.contrib.auth.tests.test_auth_backends import * -from django.contrib.auth.tests.test_basic import * -from django.contrib.auth.tests.test_context_processors import * -from django.contrib.auth.tests.test_decorators import * -from django.contrib.auth.tests.test_forms import * -from django.contrib.auth.tests.test_remote_user import * -from django.contrib.auth.tests.test_management import * -from django.contrib.auth.tests.test_models import * -from django.contrib.auth.tests.test_handlers import * -from django.contrib.auth.tests.test_hashers import * -from django.contrib.auth.tests.test_signals import * -from django.contrib.auth.tests.test_tokens import * -from django.contrib.auth.tests.test_views import * - # The password for the fixture data users is 'password' diff --git a/django/contrib/auth/tests/test_forms.py b/django/contrib/auth/tests/test_forms.py index 2c8f6e4faf..0b998105af 100644 --- a/django/contrib/auth/tests/test_forms.py +++ b/django/contrib/auth/tests/test_forms.py @@ -1,6 +1,8 @@ from __future__ import unicode_literals import os + +from django.contrib.auth import get_user_model from django.contrib.auth.models import User from django.contrib.auth.forms import (UserCreationForm, AuthenticationForm, PasswordChangeForm, SetPasswordForm, UserChangeForm, PasswordResetForm, @@ -13,6 +15,7 @@ from django.test.utils import override_settings from django.utils.encoding import force_text from django.utils._os import upath from django.utils import translation +from django.utils.text import capfirst from django.utils.translation import ugettext as _ @@ -146,6 +149,24 @@ class AuthenticationFormTest(TestCase): form = CustomAuthenticationForm() self.assertEqual(form['username'].label, "Name") + def test_username_field_label_not_set(self): + + class CustomAuthenticationForm(AuthenticationForm): + username = CharField() + + form = CustomAuthenticationForm() + UserModel = get_user_model() + username_field = UserModel._meta.get_field(UserModel.USERNAME_FIELD) + self.assertEqual(form.fields['username'].label, capfirst(username_field.verbose_name)) + + def test_username_field_label_empty_string(self): + + class CustomAuthenticationForm(AuthenticationForm): + username = CharField(label='') + + form = CustomAuthenticationForm() + self.assertEqual(form.fields['username'].label, "") + @skipIfCustomUser @override_settings(USE_TZ=False, PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) diff --git a/django/contrib/auth/tests/test_handlers.py b/django/contrib/auth/tests/test_handlers.py index 41063aaf4a..e0d2fa2200 100644 --- a/django/contrib/auth/tests/test_handlers.py +++ b/django/contrib/auth/tests/test_handlers.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from django.contrib.auth.handlers.modwsgi import check_password, groups_for_user from django.contrib.auth.models import User, Group -from django.contrib.auth.tests import CustomUser +from django.contrib.auth.tests.test_custom_user import CustomUser from django.contrib.auth.tests.utils import skipIfCustomUser from django.test import TransactionTestCase from django.test.utils import override_settings diff --git a/django/contrib/auth/tests/test_hashers.py b/django/contrib/auth/tests/test_hashers.py index 9253fcbc43..d49fdc412e 100644 --- a/django/contrib/auth/tests/test_hashers.py +++ b/django/contrib/auth/tests/test_hashers.py @@ -92,7 +92,7 @@ class TestUtilsHashPass(unittest.TestCase): self.assertFalse(check_password('lètmeiz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "crypt") - @skipUnless(bcrypt, "py-bcrypt not installed") + @skipUnless(bcrypt, "bcrypt not installed") def test_bcrypt_sha256(self): encoded = make_password('lètmein', hasher='bcrypt_sha256') self.assertTrue(is_password_usable(encoded)) @@ -108,7 +108,7 @@ class TestUtilsHashPass(unittest.TestCase): self.assertTrue(check_password(password, encoded)) self.assertFalse(check_password(password[:72], encoded)) - @skipUnless(bcrypt, "py-bcrypt not installed") + @skipUnless(bcrypt, "bcrypt not installed") def test_bcrypt(self): encoded = make_password('lètmein', hasher='bcrypt') self.assertTrue(is_password_usable(encoded)) diff --git a/django/contrib/auth/tests/test_management.py b/django/contrib/auth/tests/test_management.py index 687a5c31cb..04fd4941ab 100644 --- a/django/contrib/auth/tests/test_management.py +++ b/django/contrib/auth/tests/test_management.py @@ -5,7 +5,7 @@ from django.contrib.auth import models, management from django.contrib.auth.management import create_permissions from django.contrib.auth.management.commands import changepassword from django.contrib.auth.models import User -from django.contrib.auth.tests import CustomUser +from django.contrib.auth.tests.test_custom_user import CustomUser from django.contrib.auth.tests.utils import skipIfCustomUser from django.core.management import call_command from django.core.management.base import CommandError diff --git a/django/contrib/comments/views/utils.py b/django/contrib/comments/views/utils.py index 79f6376232..da70272282 100644 --- a/django/contrib/comments/views/utils.py +++ b/django/contrib/comments/views/utils.py @@ -37,7 +37,7 @@ def next_redirect(request, fallback, **get_kwargs): else: anchor = '' - joiner = ('?' in next) and '&' or '?' + joiner = '&' if '?' in next else '?' next += joiner + urlencode(get_kwargs) + anchor return HttpResponseRedirect(next) diff --git a/django/contrib/contenttypes/views.py b/django/contrib/contenttypes/views.py index 5c22cb6672..6d4a719289 100644 --- a/django/contrib/contenttypes/views.py +++ b/django/contrib/contenttypes/views.py @@ -75,7 +75,7 @@ def shortcut(request, content_type_id, object_id): # If all that malarkey found an object domain, use it. Otherwise, fall back # to whatever get_absolute_url() returned. if object_domain is not None: - protocol = request.is_secure() and 'https' or 'http' + protocol = 'https' if request.is_secure() else 'http' return http.HttpResponseRedirect('%s://%s%s' % (protocol, object_domain, absurl)) else: diff --git a/django/contrib/flatpages/tests/__init__.py b/django/contrib/flatpages/tests/__init__.py index 3703e23cbf..e69de29bb2 100644 --- a/django/contrib/flatpages/tests/__init__.py +++ b/django/contrib/flatpages/tests/__init__.py @@ -1,6 +0,0 @@ -from django.contrib.flatpages.tests.test_csrf import * -from django.contrib.flatpages.tests.test_forms import * -from django.contrib.flatpages.tests.test_models import * -from django.contrib.flatpages.tests.test_middleware import * -from django.contrib.flatpages.tests.test_templatetags import * -from django.contrib.flatpages.tests.test_views import * diff --git a/django/contrib/formtools/tests/__init__.py b/django/contrib/formtools/tests/__init__.py index 2bbeea8c5a..e69de29bb2 100644 --- a/django/contrib/formtools/tests/__init__.py +++ b/django/contrib/formtools/tests/__init__.py @@ -1,2 +0,0 @@ -from django.contrib.formtools.tests.tests import * -from django.contrib.formtools.tests.wizard import * diff --git a/django/contrib/formtools/tests/urls.py b/django/contrib/formtools/tests/urls.py index 739095d69f..f96f89ecdf 100644 --- a/django/contrib/formtools/tests/urls.py +++ b/django/contrib/formtools/tests/urls.py @@ -5,7 +5,7 @@ This is a URLconf to be loaded by tests.py. Add any URLs needed for tests only. from __future__ import absolute_import from django.conf.urls import patterns, url -from django.contrib.formtools.tests import TestFormPreview +from django.contrib.formtools.tests.tests import TestFormPreview from django.contrib.formtools.tests.forms import TestForm diff --git a/django/contrib/formtools/tests/wizard/test_forms.py b/django/contrib/formtools/tests/wizard/test_forms.py index 7425755cf5..21917822a7 100644 --- a/django/contrib/formtools/tests/wizard/test_forms.py +++ b/django/contrib/formtools/tests/wizard/test_forms.py @@ -17,7 +17,7 @@ from django.contrib.formtools.wizard.views import (WizardView, class DummyRequest(http.HttpRequest): def __init__(self, POST=None): super(DummyRequest, self).__init__() - self.method = POST and "POST" or "GET" + self.method = "POST" if POST else "GET" if POST is not None: self.POST.update(POST) self.session = {} diff --git a/django/contrib/gis/db/models/fields.py b/django/contrib/gis/db/models/fields.py index 249617f771..2e221b7477 100644 --- a/django/contrib/gis/db/models/fields.py +++ b/django/contrib/gis/db/models/fields.py @@ -44,6 +44,7 @@ class GeometryField(Field): # The OpenGIS Geometry name. geom_type = 'GEOMETRY' + form_class = forms.GeometryField # Geodetic units. geodetic_units = ('Decimal Degree', 'degree') @@ -201,11 +202,14 @@ class GeometryField(Field): return connection.ops.geo_db_type(self) def formfield(self, **kwargs): - defaults = {'form_class' : forms.GeometryField, + defaults = {'form_class' : self.form_class, 'geom_type' : self.geom_type, 'srid' : self.srid, } defaults.update(kwargs) + if (self.dim > 2 and not 'widget' in kwargs and + not getattr(defaults['form_class'].widget, 'supports_3d', False)): + defaults['widget'] = forms.Textarea return super(GeometryField, self).formfield(**defaults) def get_db_prep_lookup(self, lookup_type, value, connection, prepared=False): @@ -267,28 +271,35 @@ class GeometryField(Field): # The OpenGIS Geometry Type Fields class PointField(GeometryField): geom_type = 'POINT' + form_class = forms.PointField description = _("Point") class LineStringField(GeometryField): geom_type = 'LINESTRING' + form_class = forms.LineStringField description = _("Line string") class PolygonField(GeometryField): geom_type = 'POLYGON' + form_class = forms.PolygonField description = _("Polygon") class MultiPointField(GeometryField): geom_type = 'MULTIPOINT' + form_class = forms.MultiPointField description = _("Multi-point") class MultiLineStringField(GeometryField): geom_type = 'MULTILINESTRING' + form_class = forms.MultiLineStringField description = _("Multi-line string") class MultiPolygonField(GeometryField): geom_type = 'MULTIPOLYGON' + form_class = forms.MultiPolygonField description = _("Multi polygon") class GeometryCollectionField(GeometryField): geom_type = 'GEOMETRYCOLLECTION' + form_class = forms.GeometryCollectionField description = _("Geometry collection") diff --git a/django/contrib/gis/db/models/sql/compiler.py b/django/contrib/gis/db/models/sql/compiler.py index b488f59362..b2befd44b2 100644 --- a/django/contrib/gis/db/models/sql/compiler.py +++ b/django/contrib/gis/db/models/sql/compiler.py @@ -121,7 +121,7 @@ class GeoSQLCompiler(compiler.SQLCompiler): """ result = [] if opts is None: - opts = self.query.model._meta + opts = self.query.get_meta() aliases = set() only_load = self.deferred_to_columns() seen = self.query.included_inherited_models.copy() @@ -247,7 +247,7 @@ class GeoSQLCompiler(compiler.SQLCompiler): used. If `column` is specified, it will be used instead of the value in `field.column`. """ - if table_alias is None: table_alias = self.query.model._meta.db_table + if table_alias is None: table_alias = self.query.get_meta().db_table return "%s.%s" % (self.quote_name_unless_alias(table_alias), self.connection.ops.quote_name(column or field.column)) diff --git a/django/contrib/gis/forms/__init__.py b/django/contrib/gis/forms/__init__.py index 82971da6be..93a2d3847b 100644 --- a/django/contrib/gis/forms/__init__.py +++ b/django/contrib/gis/forms/__init__.py @@ -1,2 +1,5 @@ from django.forms import * -from django.contrib.gis.forms.fields import GeometryField +from .fields import (GeometryField, GeometryCollectionField, PointField, + MultiPointField, LineStringField, MultiLineStringField, PolygonField, + MultiPolygonField) +from .widgets import BaseGeometryWidget, OpenLayersWidget, OSMWidget diff --git a/django/contrib/gis/forms/fields.py b/django/contrib/gis/forms/fields.py index d3feac83e7..6e2cbd59f5 100644 --- a/django/contrib/gis/forms/fields.py +++ b/django/contrib/gis/forms/fields.py @@ -9,6 +9,7 @@ from django.utils.translation import ugettext_lazy as _ # While this couples the geographic forms to the GEOS library, # it decouples from database (by not importing SpatialBackend). from django.contrib.gis.geos import GEOSException, GEOSGeometry, fromstr +from .widgets import OpenLayersWidget class GeometryField(forms.Field): @@ -17,7 +18,8 @@ class GeometryField(forms.Field): accepted by GEOSGeometry is accepted by this form. By default, this includes WKT, HEXEWKB, WKB (in a buffer), and GeoJSON. """ - widget = forms.Textarea + widget = OpenLayersWidget + geom_type = 'GEOMETRY' default_error_messages = { 'required' : _('No geometry value provided.'), @@ -31,12 +33,13 @@ class GeometryField(forms.Field): # Pop out attributes from the database field, or use sensible # defaults (e.g., allow None). self.srid = kwargs.pop('srid', None) - self.geom_type = kwargs.pop('geom_type', 'GEOMETRY') + self.geom_type = kwargs.pop('geom_type', self.geom_type) if 'null' in kwargs: kwargs.pop('null', True) warnings.warn("Passing 'null' keyword argument to GeometryField is deprecated.", DeprecationWarning, stacklevel=2) super(GeometryField, self).__init__(**kwargs) + self.widget.attrs['geom_type'] = self.geom_type def to_python(self, value): """ @@ -98,3 +101,31 @@ class GeometryField(forms.Field): else: # Check for change of state of existence return bool(initial) != bool(data) + + +class GeometryCollectionField(GeometryField): + geom_type = 'GEOMETRYCOLLECTION' + + +class PointField(GeometryField): + geom_type = 'POINT' + + +class MultiPointField(GeometryField): + geom_type = 'MULTIPOINT' + + +class LineStringField(GeometryField): + geom_type = 'LINESTRING' + + +class MultiLineStringField(GeometryField): + geom_type = 'MULTILINESTRING' + + +class PolygonField(GeometryField): + geom_type = 'POLYGON' + + +class MultiPolygonField(GeometryField): + geom_type = 'MULTIPOLYGON' diff --git a/django/contrib/gis/forms/widgets.py b/django/contrib/gis/forms/widgets.py new file mode 100644 index 0000000000..d50c7c005a --- /dev/null +++ b/django/contrib/gis/forms/widgets.py @@ -0,0 +1,112 @@ +from __future__ import unicode_literals + +import logging + +from django.conf import settings +from django.contrib.gis import gdal +from django.contrib.gis.geos import GEOSGeometry, GEOSException +from django.forms.widgets import Widget +from django.template import loader +from django.utils import six +from django.utils import translation + +logger = logging.getLogger('django.contrib.gis') + + +class BaseGeometryWidget(Widget): + """ + The base class for rich geometry widgets. + Renders a map using the WKT of the geometry. + """ + geom_type = 'GEOMETRY' + map_srid = 4326 + map_width = 600 + map_height = 400 + display_wkt = False + + supports_3d = False + template_name = '' # set on subclasses + + def __init__(self, attrs=None): + self.attrs = {} + for key in ('geom_type', 'map_srid', 'map_width', 'map_height', 'display_wkt'): + self.attrs[key] = getattr(self, key) + if attrs: + self.attrs.update(attrs) + + def render(self, name, value, attrs=None): + # If a string reaches here (via a validation error on another + # field) then just reconstruct the Geometry. + if isinstance(value, six.string_types): + try: + value = GEOSGeometry(value) + except (GEOSException, ValueError) as err: + logger.error( + "Error creating geometry from value '%s' (%s)" % ( + value, err) + ) + value = None + + wkt = '' + if value: + # Check that srid of value and map match + if value.srid != self.map_srid: + try: + ogr = value.ogr + ogr.transform(self.map_srid) + wkt = ogr.wkt + except gdal.OGRException as err: + logger.error( + "Error transforming geometry from srid '%s' to srid '%s' (%s)" % ( + value.srid, self.map_srid, err) + ) + else: + wkt = value.wkt + + context = self.build_attrs(attrs, + name=name, + module='geodjango_%s' % name.replace('-','_'), # JS-safe + wkt=wkt, + geom_type=gdal.OGRGeomType(self.attrs['geom_type']), + STATIC_URL=settings.STATIC_URL, + LANGUAGE_BIDI=translation.get_language_bidi(), + ) + return loader.render_to_string(self.template_name, context) + + +class OpenLayersWidget(BaseGeometryWidget): + template_name = 'gis/openlayers.html' + class Media: + js = ( + 'http://openlayers.org/api/2.11/OpenLayers.js', + 'gis/js/OLMapWidget.js', + ) + + +class OSMWidget(BaseGeometryWidget): + """ + An OpenLayers/OpenStreetMap-based widget. + """ + template_name = 'gis/openlayers-osm.html' + default_lon = 5 + default_lat = 47 + + class Media: + js = ( + 'http://openlayers.org/api/2.11/OpenLayers.js', + 'http://www.openstreetmap.org/openlayers/OpenStreetMap.js', + 'gis/js/OLMapWidget.js', + ) + + @property + def map_srid(self): + # Use the official spherical mercator projection SRID on versions + # of GDAL that support it; otherwise, fallback to 900913. + if gdal.HAS_GDAL and gdal.GDAL_VERSION >= (1, 7): + return 3857 + else: + return 900913 + + def render(self, name, value, attrs=None): + return super(self, OSMWidget).render(name, value, + {'default_lon': self.default_lon, 'default_lat': self.default_lat}) diff --git a/django/contrib/gis/gdal/__init__.py b/django/contrib/gis/gdal/__init__.py index c33fbcb97a..2aa867bb69 100644 --- a/django/contrib/gis/gdal/__init__.py +++ b/django/contrib/gis/gdal/__init__.py @@ -31,6 +31,9 @@ to a non-existant file location (e.g., `GDAL_LIBRARY_PATH='/null/path'`; setting to None/False/'' will not work as a string must be given). """ +from django.contrib.gis.gdal.error import check_err, OGRException, OGRIndexError, SRSException +from django.contrib.gis.gdal.geomtype import OGRGeomType + # Attempting to import objects that depend on the GDAL library. The # HAS_GDAL flag will be set to True if the library is present on # the system. @@ -41,7 +44,7 @@ try: from django.contrib.gis.gdal.srs import SpatialReference, CoordTransform from django.contrib.gis.gdal.geometries import OGRGeometry HAS_GDAL = True -except Exception: +except OGRException: HAS_GDAL = False try: @@ -50,5 +53,3 @@ except ImportError: # No ctypes, but don't raise an exception. pass -from django.contrib.gis.gdal.error import check_err, OGRException, OGRIndexError, SRSException -from django.contrib.gis.gdal.geomtype import OGRGeomType diff --git a/django/contrib/gis/gdal/tests/__init__.py b/django/contrib/gis/gdal/tests/__init__.py index 262d294a43..e69de29bb2 100644 --- a/django/contrib/gis/gdal/tests/__init__.py +++ b/django/contrib/gis/gdal/tests/__init__.py @@ -1,28 +0,0 @@ -""" -Module for executing all of the GDAL tests. None -of these tests require the use of the database. -""" -from __future__ import absolute_import - -from django.utils.unittest import TestSuite, TextTestRunner - -# Importing the GDAL test modules. -from . import test_driver, test_ds, test_envelope, test_geom, test_srs - -test_suites = [test_driver.suite(), - test_ds.suite(), - test_envelope.suite(), - test_geom.suite(), - test_srs.suite(), - ] - -def suite(): - "Builds a test suite for the GDAL tests." - s = TestSuite() - for test_suite in test_suites: - s.addTest(test_suite) - return s - -def run(verbosity=1): - "Runs the GDAL tests." - TextTestRunner(verbosity=verbosity).run(suite()) diff --git a/django/contrib/gis/gdal/tests/test_driver.py b/django/contrib/gis/gdal/tests/test_driver.py index 06ec93f936..c27302da72 100644 --- a/django/contrib/gis/gdal/tests/test_driver.py +++ b/django/contrib/gis/gdal/tests/test_driver.py @@ -1,5 +1,10 @@ -import unittest -from django.contrib.gis.gdal import Driver, OGRException +from django.contrib.gis.gdal import HAS_GDAL +from django.utils import unittest +from django.utils.unittest import skipUnless + +if HAS_GDAL: + from django.contrib.gis.gdal import Driver, OGRException + valid_drivers = ('ESRI Shapefile', 'MapInfo File', 'TIGER', 'S57', 'DGN', 'Memory', 'CSV', 'GML', 'KML') @@ -12,6 +17,8 @@ aliases = {'eSrI' : 'ESRI Shapefile', 'sHp' : 'ESRI Shapefile', } + +@skipUnless(HAS_GDAL, "GDAL is required") class DriverTest(unittest.TestCase): def test01_valid_driver(self): @@ -30,11 +37,3 @@ class DriverTest(unittest.TestCase): for alias, full_name in aliases.items(): dr = Driver(alias) self.assertEqual(full_name, str(dr)) - -def suite(): - s = unittest.TestSuite() - s.addTest(unittest.makeSuite(DriverTest)) - return s - -def run(verbosity=2): - unittest.TextTestRunner(verbosity=verbosity).run(suite()) diff --git a/django/contrib/gis/gdal/tests/test_ds.py b/django/contrib/gis/gdal/tests/test_ds.py index a87a1c6c35..3664f055f2 100644 --- a/django/contrib/gis/gdal/tests/test_ds.py +++ b/django/contrib/gis/gdal/tests/test_ds.py @@ -1,32 +1,38 @@ import os -import unittest -from django.contrib.gis.gdal import DataSource, Envelope, OGRGeometry, OGRException, OGRIndexError, GDAL_VERSION -from django.contrib.gis.gdal.field import OFTReal, OFTInteger, OFTString + +from django.contrib.gis.gdal import HAS_GDAL from django.contrib.gis.geometry.test_data import get_ds_file, TestDS, TEST_DATA +from django.utils import unittest +from django.utils.unittest import skipUnless + +if HAS_GDAL: + from django.contrib.gis.gdal import DataSource, Envelope, OGRGeometry, OGRException, OGRIndexError, GDAL_VERSION + from django.contrib.gis.gdal.field import OFTReal, OFTInteger, OFTString + + # List of acceptable data sources. + ds_list = ( + TestDS('test_point', nfeat=5, nfld=3, geom='POINT', gtype=1, driver='ESRI Shapefile', + fields={'dbl' : OFTReal, 'int' : OFTInteger, 'str' : OFTString,}, + extent=(-1.35011,0.166623,-0.524093,0.824508), # Got extent from QGIS + srs_wkt='GEOGCS["GCS_WGS_1984",DATUM["WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]', + field_values={'dbl' : [float(i) for i in range(1, 6)], 'int' : list(range(1, 6)), 'str' : [str(i) for i in range(1, 6)]}, + fids=range(5)), + TestDS('test_vrt', ext='vrt', nfeat=3, nfld=3, geom='POINT', gtype='Point25D', driver='VRT', + fields={'POINT_X' : OFTString, 'POINT_Y' : OFTString, 'NUM' : OFTString}, # VRT uses CSV, which all types are OFTString. + extent=(1.0, 2.0, 100.0, 523.5), # Min/Max from CSV + field_values={'POINT_X' : ['1.0', '5.0', '100.0'], 'POINT_Y' : ['2.0', '23.0', '523.5'], 'NUM' : ['5', '17', '23']}, + fids=range(1,4)), + TestDS('test_poly', nfeat=3, nfld=3, geom='POLYGON', gtype=3, + driver='ESRI Shapefile', + fields={'float' : OFTReal, 'int' : OFTInteger, 'str' : OFTString,}, + extent=(-1.01513,-0.558245,0.161876,0.839637), # Got extent from QGIS + srs_wkt='GEOGCS["GCS_WGS_1984",DATUM["WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]'), + ) + +bad_ds = (TestDS('foo'),) -# List of acceptable data sources. -ds_list = (TestDS('test_point', nfeat=5, nfld=3, geom='POINT', gtype=1, driver='ESRI Shapefile', - fields={'dbl' : OFTReal, 'int' : OFTInteger, 'str' : OFTString,}, - extent=(-1.35011,0.166623,-0.524093,0.824508), # Got extent from QGIS - srs_wkt='GEOGCS["GCS_WGS_1984",DATUM["WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]', - field_values={'dbl' : [float(i) for i in range(1, 6)], 'int' : list(range(1, 6)), 'str' : [str(i) for i in range(1, 6)]}, - fids=range(5)), - TestDS('test_vrt', ext='vrt', nfeat=3, nfld=3, geom='POINT', gtype='Point25D', driver='VRT', - fields={'POINT_X' : OFTString, 'POINT_Y' : OFTString, 'NUM' : OFTString}, # VRT uses CSV, which all types are OFTString. - extent=(1.0, 2.0, 100.0, 523.5), # Min/Max from CSV - field_values={'POINT_X' : ['1.0', '5.0', '100.0'], 'POINT_Y' : ['2.0', '23.0', '523.5'], 'NUM' : ['5', '17', '23']}, - fids=range(1,4)), - TestDS('test_poly', nfeat=3, nfld=3, geom='POLYGON', gtype=3, - driver='ESRI Shapefile', - fields={'float' : OFTReal, 'int' : OFTInteger, 'str' : OFTString,}, - extent=(-1.01513,-0.558245,0.161876,0.839637), # Got extent from QGIS - srs_wkt='GEOGCS["GCS_WGS_1984",DATUM["WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]'), - ) - -bad_ds = (TestDS('foo'), - ) - +@skipUnless(HAS_GDAL, "GDAL is required") class DataSourceTest(unittest.TestCase): def test01_valid_shp(self): @@ -236,11 +242,3 @@ class DataSourceTest(unittest.TestCase): feat = ds[0][0] # Reference value obtained using `ogrinfo`. self.assertEqual(676586997978, feat.get('ALAND10')) - -def suite(): - s = unittest.TestSuite() - s.addTest(unittest.makeSuite(DataSourceTest)) - return s - -def run(verbosity=2): - unittest.TextTestRunner(verbosity=verbosity).run(suite()) diff --git a/django/contrib/gis/gdal/tests/test_envelope.py b/django/contrib/gis/gdal/tests/test_envelope.py index 07e12c0ca7..7518dc69aa 100644 --- a/django/contrib/gis/gdal/tests/test_envelope.py +++ b/django/contrib/gis/gdal/tests/test_envelope.py @@ -1,5 +1,9 @@ -from django.contrib.gis.gdal import Envelope, OGRException +from django.contrib.gis.gdal import HAS_GDAL from django.utils import unittest +from django.utils.unittest import skipUnless + +if HAS_GDAL: + from django.contrib.gis.gdal import Envelope, OGRException class TestPoint(object): @@ -7,11 +11,13 @@ class TestPoint(object): self.x = x self.y = y + +@skipUnless(HAS_GDAL, "GDAL is required") class EnvelopeTest(unittest.TestCase): def setUp(self): self.e = Envelope(0, 0, 5, 5) - + def test01_init(self): "Testing Envelope initilization." e1 = Envelope((0, 0, 5, 5)) @@ -85,11 +91,3 @@ class EnvelopeTest(unittest.TestCase): self.assertEqual((-1, 0, 5, 5), self.e) self.e.expand_to_include(TestPoint(10, 10)) self.assertEqual((-1, 0, 10, 10), self.e) - -def suite(): - s = unittest.TestSuite() - s.addTest(unittest.makeSuite(EnvelopeTest)) - return s - -def run(verbosity=2): - unittest.TextTestRunner(verbosity=verbosity).run(suite()) diff --git a/django/contrib/gis/gdal/tests/test_geom.py b/django/contrib/gis/gdal/tests/test_geom.py index 9b8ae6a26b..c048d2bb82 100644 --- a/django/contrib/gis/gdal/tests/test_geom.py +++ b/django/contrib/gis/gdal/tests/test_geom.py @@ -5,12 +5,19 @@ try: except ImportError: import pickle -from django.contrib.gis.gdal import (OGRGeometry, OGRGeomType, OGRException, - OGRIndexError, SpatialReference, CoordTransform, GDAL_VERSION) +from django.contrib.gis.gdal import HAS_GDAL from django.contrib.gis.geometry.test_data import TestDataMixin from django.utils.six.moves import xrange from django.utils import unittest +from django.utils.unittest import skipUnless +if HAS_GDAL: + from django.contrib.gis.gdal import (OGRGeometry, OGRGeomType, + OGRException, OGRIndexError, SpatialReference, CoordTransform, + GDAL_VERSION) + + +@skipUnless(HAS_GDAL, "GDAL is required") class OGRGeomTest(unittest.TestCase, TestDataMixin): "This tests the OGR Geometry." @@ -476,11 +483,3 @@ class OGRGeomTest(unittest.TestCase, TestDataMixin): "Testing equivalence methods with non-OGRGeometry instances." self.assertNotEqual(None, OGRGeometry('POINT(0 0)')) self.assertEqual(False, OGRGeometry('LINESTRING(0 0, 1 1)') == 3) - -def suite(): - s = unittest.TestSuite() - s.addTest(unittest.makeSuite(OGRGeomTest)) - return s - -def run(verbosity=2): - unittest.TextTestRunner(verbosity=verbosity).run(suite()) diff --git a/django/contrib/gis/gdal/tests/test_srs.py b/django/contrib/gis/gdal/tests/test_srs.py index 3d3bba7939..363b597dae 100644 --- a/django/contrib/gis/gdal/tests/test_srs.py +++ b/django/contrib/gis/gdal/tests/test_srs.py @@ -1,5 +1,9 @@ -from django.contrib.gis.gdal import SpatialReference, CoordTransform, OGRException, SRSException +from django.contrib.gis.gdal import HAS_GDAL from django.utils import unittest +from django.utils.unittest import skipUnless + +if HAS_GDAL: + from django.contrib.gis.gdal import SpatialReference, CoordTransform, OGRException, SRSException class TestSRS: @@ -46,6 +50,8 @@ well_known = (TestSRS('GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",637813 bad_srlist = ('Foobar', 'OOJCS["NAD83 / Texas South Central",GEOGCS["NAD83",DATUM["North_American_Datum_1983",SPHEROID["GRS 1980",6378137,298.257222101,AUTHORITY["EPSG","7019"]],AUTHORITY["EPSG","6269"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4269"]],PROJECTION["Lambert_Conformal_Conic_2SP"],PARAMETER["standard_parallel_1",30.28333333333333],PARAMETER["standard_parallel_2",28.38333333333333],PARAMETER["latitude_of_origin",27.83333333333333],PARAMETER["central_meridian",-99],PARAMETER["false_easting",600000],PARAMETER["false_northing",4000000],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AUTHORITY["EPSG","32140"]]',) + +@skipUnless(HAS_GDAL, "GDAL is required") class SpatialRefTest(unittest.TestCase): def test01_wkt(self): @@ -155,11 +161,3 @@ class SpatialRefTest(unittest.TestCase): self.assertEqual('EPSG', s1['AUTHORITY']) self.assertEqual(4326, int(s1['AUTHORITY', 1])) self.assertEqual(None, s1['FOOBAR']) - -def suite(): - s = unittest.TestSuite() - s.addTest(unittest.makeSuite(SpatialRefTest)) - return s - -def run(verbosity=2): - unittest.TextTestRunner(verbosity=verbosity).run(suite()) diff --git a/django/contrib/gis/geoip/tests.py b/django/contrib/gis/geoip/tests.py index bb4a3e7e23..3fa64bf6be 100644 --- a/django/contrib/gis/geoip/tests.py +++ b/django/contrib/gis/geoip/tests.py @@ -3,16 +3,28 @@ from __future__ import unicode_literals import os from django.conf import settings -from django.contrib.gis.geos import GEOSGeometry -from django.contrib.gis.geoip import GeoIP, GeoIPException +from django.contrib.gis.geos import HAS_GEOS +from django.contrib.gis.geoip import HAS_GEOIP from django.utils import unittest +from django.utils.unittest import skipUnless from django.utils import six +if HAS_GEOIP: + from . import GeoIP, GeoIPException + +if HAS_GEOS: + from ..geos import GEOSGeometry + + # Note: Requires use of both the GeoIP country and city datasets. # The GEOIP_DATA path should be the only setting set (the directory # should contain links or the actual database files 'GeoIP.dat' and # 'GeoLiteCity.dat'. + + +@skipUnless(HAS_GEOIP and getattr(settings, "GEOIP_PATH", None), + "GeoIP is required along with the GEOIP_DATA setting.") class GeoIPTest(unittest.TestCase): def test01_init(self): @@ -70,6 +82,7 @@ class GeoIPTest(unittest.TestCase): self.assertEqual({'country_code' : 'US', 'country_name' : 'United States'}, g.country(query)) + @skipUnless(HAS_GEOS, "Geos is required") def test04_city(self): "Testing GeoIP city querying methods." g = GeoIP(country='') @@ -105,12 +118,3 @@ class GeoIPTest(unittest.TestCase): g = GeoIP() d = g.city("www.osnabrueck.de") self.assertEqual('Osnabrück', d['city']) - - -def suite(): - s = unittest.TestSuite() - s.addTest(unittest.makeSuite(GeoIPTest)) - return s - -def run(verbosity=1): - unittest.TextTestRunner(verbosity=verbosity).run(suite()) diff --git a/django/contrib/gis/geos/__init__.py b/django/contrib/gis/geos/__init__.py index 5885a30bef..945f561fb7 100644 --- a/django/contrib/gis/geos/__init__.py +++ b/django/contrib/gis/geos/__init__.py @@ -3,12 +3,18 @@ The GeoDjango GEOS module. Please consult the GeoDjango documentation for more details: http://geodjango.org/docs/geos.html """ -from django.contrib.gis.geos.geometry import GEOSGeometry, wkt_regex, hex_regex -from django.contrib.gis.geos.point import Point -from django.contrib.gis.geos.linestring import LineString, LinearRing -from django.contrib.gis.geos.polygon import Polygon -from django.contrib.gis.geos.collections import GeometryCollection, MultiPoint, MultiLineString, MultiPolygon -from django.contrib.gis.geos.error import GEOSException, GEOSIndexError -from django.contrib.gis.geos.io import WKTReader, WKTWriter, WKBReader, WKBWriter -from django.contrib.gis.geos.factory import fromfile, fromstr -from django.contrib.gis.geos.libgeos import geos_version, geos_version_info, GEOS_PREPARE +try: + from .libgeos import geos_version, geos_version_info, GEOS_PREPARE + HAS_GEOS = True +except ImportError: + HAS_GEOS = False + +if HAS_GEOS: + from .geometry import GEOSGeometry, wkt_regex, hex_regex + from .point import Point + from .linestring import LineString, LinearRing + from .polygon import Polygon + from .collections import GeometryCollection, MultiPoint, MultiLineString, MultiPolygon + from .error import GEOSException, GEOSIndexError + from .io import WKTReader, WKTWriter, WKBReader, WKBWriter + from .factory import fromfile, fromstr diff --git a/django/contrib/gis/geos/tests/__init__.py b/django/contrib/gis/geos/tests/__init__.py index 6b715d8c59..e69de29bb2 100644 --- a/django/contrib/gis/geos/tests/__init__.py +++ b/django/contrib/gis/geos/tests/__init__.py @@ -1,28 +0,0 @@ -""" -GEOS Testing module. -""" -from __future__ import absolute_import - -from django.utils.unittest import TestSuite, TextTestRunner -from . import test_geos, test_io, test_geos_mutation, test_mutable_list - -test_suites = [ - test_geos.suite(), - test_io.suite(), - test_geos_mutation.suite(), - test_mutable_list.suite(), - ] - -def suite(): - "Builds a test suite for the GEOS tests." - s = TestSuite() - for suite in test_suites: - s.addTest(suite) - return s - -def run(verbosity=1): - "Runs the GEOS tests." - TextTestRunner(verbosity=verbosity).run(suite()) - -if __name__ == '__main__': - run(2) diff --git a/django/contrib/gis/geos/tests/test_geos.py b/django/contrib/gis/geos/tests/test_geos.py index 66d890d8cb..c7fe5b2321 100644 --- a/django/contrib/gis/geos/tests/test_geos.py +++ b/django/contrib/gis/geos/tests/test_geos.py @@ -6,20 +6,28 @@ import random from binascii import a2b_hex, b2a_hex from io import BytesIO +from django.contrib.gis.gdal import HAS_GDAL + from django.contrib.gis import memoryview -from django.contrib.gis.geos import (GEOSException, GEOSIndexError, GEOSGeometry, - GeometryCollection, Point, MultiPoint, Polygon, MultiPolygon, LinearRing, - LineString, MultiLineString, fromfile, fromstr, geos_version_info) -from django.contrib.gis.geos.base import gdal, numpy, GEOSBase -from django.contrib.gis.geos.libgeos import GEOS_PREPARE from django.contrib.gis.geometry.test_data import TestDataMixin from django.utils.encoding import force_bytes from django.utils import six from django.utils.six.moves import xrange from django.utils import unittest +from django.utils.unittest import skipUnless + +from .. import HAS_GEOS + +if HAS_GEOS: + from .. import (GEOSException, GEOSIndexError, GEOSGeometry, + GeometryCollection, Point, MultiPoint, Polygon, MultiPolygon, LinearRing, + LineString, MultiLineString, fromfile, fromstr, geos_version_info, + GEOS_PREPARE) + from ..base import gdal, numpy, GEOSBase +@skipUnless(HAS_GEOS, "Geos is required.") class GEOSTest(unittest.TestCase, TestDataMixin): @property @@ -198,7 +206,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): self.assertEqual(srid, poly.shell.srid) self.assertEqual(srid, fromstr(poly.ewkt).srid) # Checking export - @unittest.skipUnless(gdal.HAS_GDAL, "gdal is required") + @skipUnless(HAS_GDAL, "GDAL is required.") def test_json(self): "Testing GeoJSON input/output (via GDAL)." for g in self.geometries.json_geoms: @@ -662,6 +670,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): p3 = fromstr(p1.hex, srid=-1) # -1 is intended. self.assertEqual(-1, p3.srid) + @skipUnless(HAS_GDAL, "GDAL is required.") def test_custom_srid(self): """ Test with a srid unknown from GDAL """ pnt = Point(111200, 220900, srid=999999) @@ -851,7 +860,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): # And, they should be equal. self.assertEqual(gc1, gc2) - @unittest.skipUnless(gdal.HAS_GDAL, "gdal is required") + @skipUnless(HAS_GDAL, "GDAL is required.") def test_gdal(self): "Testing `ogr` and `srs` properties." g1 = fromstr('POINT(5 23)') @@ -878,7 +887,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): self.assertNotEqual(poly._ptr, cpy1._ptr) self.assertNotEqual(poly._ptr, cpy2._ptr) - @unittest.skipUnless(gdal.HAS_GDAL, "gdal is required to transform geometries") + @skipUnless(HAS_GDAL, "GDAL is required to transform geometries") def test_transform(self): "Testing `transform` method." orig = GEOSGeometry('POINT (-104.609 38.255)', 4326) @@ -903,7 +912,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): self.assertAlmostEqual(trans.x, p.x, prec) self.assertAlmostEqual(trans.y, p.y, prec) - @unittest.skipUnless(gdal.HAS_GDAL, "gdal is required to transform geometries") + @skipUnless(HAS_GDAL, "GDAL is required to transform geometries") def test_transform_3d(self): p3d = GEOSGeometry('POINT (5 23 100)', 4326) p3d.transform(2774) @@ -912,6 +921,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): else: self.assertIsNone(p3d.z) + @skipUnless(HAS_GDAL, "GDAL is required.") def test_transform_noop(self): """ Testing `transform` method (SRID match) """ # transform() should no-op if source & dest SRIDs match, @@ -962,6 +972,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): g = GEOSGeometry('POINT (-104.609 38.255)', srid=-1) self.assertRaises(GEOSException, g.transform, 2774, clone=True) + @skipUnless(HAS_GDAL, "GDAL is required.") def test_transform_nogdal(self): """ Testing `transform` method (GDAL not available) """ old_has_gdal = gdal.HAS_GDAL @@ -1016,7 +1027,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): self.assertEqual(geom, tmpg) if not no_srid: self.assertEqual(geom.srid, tmpg.srid) - @unittest.skipUnless(GEOS_PREPARE, "geos >= 3.1.0 is required") + @skipUnless(HAS_GEOS and GEOS_PREPARE, "geos >= 3.1.0 is required") def test_prepared(self): "Testing PreparedGeometry support." # Creating a simple multipolygon and getting a prepared version. @@ -1043,7 +1054,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): for geom, merged in zip(ref_geoms, ref_merged): self.assertEqual(merged, geom.merged) - @unittest.skipUnless(GEOS_PREPARE, "geos >= 3.1.0 is required") + @skipUnless(HAS_GEOS and GEOS_PREPARE, "geos >= 3.1.0 is required") def test_valid_reason(self): "Testing IsValidReason support" @@ -1058,7 +1069,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): self.assertIsInstance(g.valid_reason, six.string_types) self.assertTrue(g.valid_reason.startswith("Too few points in geometry component")) - @unittest.skipUnless(geos_version_info()['version'] >= '3.2.0', "geos >= 3.2.0 is required") + @skipUnless(HAS_GEOS and geos_version_info()['version'] >= '3.2.0', "geos >= 3.2.0 is required") def test_linearref(self): "Testing linear referencing" @@ -1091,12 +1102,3 @@ class GEOSTest(unittest.TestCase, TestDataMixin): self.assertTrue(m, msg="Unable to parse the version string '%s'" % v_init) self.assertEqual(m.group('version'), v_geos) self.assertEqual(m.group('capi_version'), v_capi) - - -def suite(): - s = unittest.TestSuite() - s.addTest(unittest.makeSuite(GEOSTest)) - return s - -def run(verbosity=2): - unittest.TextTestRunner(verbosity=verbosity).run(suite()) diff --git a/django/contrib/gis/geos/tests/test_geos_mutation.py b/django/contrib/gis/geos/tests/test_geos_mutation.py index 0c69b2bf4f..40b708a0ef 100644 --- a/django/contrib/gis/geos/tests/test_geos_mutation.py +++ b/django/contrib/gis/geos/tests/test_geos_mutation.py @@ -2,15 +2,23 @@ # Modified from original contribution by Aryeh Leib Taurog, which was # released under the New BSD license. -from django.contrib.gis.geos import * -from django.contrib.gis.geos.error import GEOSIndexError from django.utils import unittest +from django.utils.unittest import skipUnless + +from .. import HAS_GEOS + +if HAS_GEOS: + from .. import * + from ..error import GEOSIndexError + def getItem(o,i): return o[i] def delItem(o,i): del o[i] def setItem(o,i,v): o[i] = v -def api_get_distance(x): return x.distance(Point(-200,-200)) +if HAS_GEOS: + def api_get_distance(x): return x.distance(Point(-200,-200)) + def api_get_buffer(x): return x.buffer(10) def api_get_geom_typeid(x): return x.geom_typeid def api_get_num_coords(x): return x.num_coords @@ -29,6 +37,8 @@ geos_function_tests = [ val for name, val in vars().items() if hasattr(val, '__call__') and name.startswith('api_get_') ] + +@skipUnless(HAS_GEOS, "Geos is required.") class GEOSMutationTest(unittest.TestCase): """ Tests Pythonic Mutability of Python GEOS geometry wrappers @@ -122,14 +132,3 @@ class GEOSMutationTest(unittest.TestCase): lsa = MultiPoint(*map(Point,((5,5),(3,-2),(8,1)))) for f in geos_function_tests: self.assertEqual(f(lsa), f(mp), 'MultiPoint ' + f.__name__) - -def suite(): - s = unittest.TestSuite() - s.addTest(unittest.makeSuite(GEOSMutationTest)) - return s - -def run(verbosity=2): - unittest.TextTestRunner(verbosity=verbosity).run(suite()) - -if __name__ == '__main__': - run() diff --git a/django/contrib/gis/geos/tests/test_io.py b/django/contrib/gis/geos/tests/test_io.py index 45a9a220b1..38ca2e0923 100644 --- a/django/contrib/gis/geos/tests/test_io.py +++ b/django/contrib/gis/geos/tests/test_io.py @@ -4,10 +4,16 @@ import binascii import unittest from django.contrib.gis import memoryview -from django.contrib.gis.geos import GEOSGeometry, WKTReader, WKTWriter, WKBReader, WKBWriter, geos_version_info from django.utils import six +from django.utils.unittest import skipUnless + +from ..import HAS_GEOS + +if HAS_GEOS: + from .. import GEOSGeometry, WKTReader, WKTWriter, WKBReader, WKBWriter, geos_version_info +@skipUnless(HAS_GEOS, "Geos is required.") class GEOSIOTest(unittest.TestCase): def test01_wktreader(self): @@ -109,11 +115,3 @@ class GEOSIOTest(unittest.TestCase): wkb_w.srid = True self.assertEqual(hex3d_srid, wkb_w.write_hex(g)) self.assertEqual(wkb3d_srid, wkb_w.write(g)) - -def suite(): - s = unittest.TestSuite() - s.addTest(unittest.makeSuite(GEOSIOTest)) - return s - -def run(verbosity=2): - unittest.TextTestRunner(verbosity=verbosity).run(suite()) diff --git a/django/contrib/gis/geos/tests/test_mutable_list.py b/django/contrib/gis/geos/tests/test_mutable_list.py index 988d8417a2..a4a56f2e5f 100644 --- a/django/contrib/gis/geos/tests/test_mutable_list.py +++ b/django/contrib/gis/geos/tests/test_mutable_list.py @@ -395,15 +395,3 @@ class ListMixinTest(unittest.TestCase): class ListMixinTestSingle(ListMixinTest): listType = UserListB - -def suite(): - s = unittest.TestSuite() - s.addTest(unittest.makeSuite(ListMixinTest)) - s.addTest(unittest.makeSuite(ListMixinTestSingle)) - return s - -def run(verbosity=2): - unittest.TextTestRunner(verbosity=verbosity).run(suite()) - -if __name__ == '__main__': - run() diff --git a/django/contrib/gis/sitemaps/views.py b/django/contrib/gis/sitemaps/views.py index 36e48f7d20..9682c125d2 100644 --- a/django/contrib/gis/sitemaps/views.py +++ b/django/contrib/gis/sitemaps/views.py @@ -20,7 +20,7 @@ def index(request, sitemaps): """ current_site = get_current_site(request) sites = [] - protocol = request.is_secure() and 'https' or 'http' + protocol = 'https' if request.is_secure() else 'http' for section, site in sitemaps.items(): if callable(site): pages = site().paginator.num_pages diff --git a/django/contrib/gis/static/gis/js/OLMapWidget.js b/django/contrib/gis/static/gis/js/OLMapWidget.js new file mode 100644 index 0000000000..252196b369 --- /dev/null +++ b/django/contrib/gis/static/gis/js/OLMapWidget.js @@ -0,0 +1,371 @@ +(function() { +/** + * Transforms an array of features to a single feature with the merged + * geometry of geom_type + */ +OpenLayers.Util.properFeatures = function(features, geom_type) { + if (features.constructor == Array) { + var geoms = []; + for (var i=0; i + */ + +OpenLayers.Format.DjangoWKT = OpenLayers.Class(OpenLayers.Format.WKT, { + initialize: function(options) { + OpenLayers.Format.WKT.prototype.initialize.apply(this, [options]); + this.regExes.justComma = /\s*,\s*/; + }, + + parse: { + 'point': function(str) { + var coords = OpenLayers.String.trim(str).split(this.regExes.spaces); + return new OpenLayers.Feature.Vector( + new OpenLayers.Geometry.Point(coords[0], coords[1]) + ); + }, + + 'multipoint': function(str) { + var point; + var points = OpenLayers.String.trim(str).split(this.regExes.justComma); + var components = []; + for(var i=0, len=points.length; i0) { + pieces.push(','); + } + pieces.push(this.extractGeometry(collection[i])); + } + pieces.push(')'); + } else { + pieces.push(this.extractGeometry(features.geometry)); + } + return pieces.join(''); + }, + + CLASS_NAME: "OpenLayers.Format.DjangoWKT" +}); + +function MapWidget(options) { + this.map = null; + this.controls = null; + this.panel = null; + this.layers = {}; + this.wkt_f = new OpenLayers.Format.DjangoWKT(); + + // Mapping from OGRGeomType name to OpenLayers.Geometry name + if (options['geom_name'] == 'Unknown') options['geom_type'] = OpenLayers.Geometry; + else if (options['geom_name'] == 'GeometryCollection') options['geom_type'] = OpenLayers.Geometry.Collection; + else options['geom_type'] = eval('OpenLayers.Geometry' + options['geom_name']); + + // Default options + this.options = { + color: 'ee9900', + default_lat: 0, + default_lon: 0, + default_zoom: 4, + is_collection: options['geom_type'] instanceof OpenLayers.Geometry.Collection, + layerswitcher: false, + map_options: {}, + map_srid: 4326, + modifiable: true, + mouse_position: false, + opacity: 0.4, + point_zoom: 12, + scale_text: false, + scrollable: true + }; + + // Altering using user-provied options + for (var property in options) { + if (options.hasOwnProperty(property)) { + this.options[property] = options[property]; + } + } + + this.map = new OpenLayers.Map(this.options.map_id, this.options.map_options); + if (this.options.base_layer) this.layers.base = this.options.base_layer; + else this.layers.base = new OpenLayers.Layer.WMS('OpenLayers WMS', 'http://vmap0.tiles.osgeo.org/wms/vmap0', {layers: 'basic'}); + this.map.addLayer(this.layers.base); + + var defaults_style = { + 'fillColor': '#' + this.options.color, + 'fillOpacity': this.options.opacity, + 'strokeColor': '#' + this.options.color, + }; + if (this.options.geom_name == 'LineString') { + defaults_style['strokeWidth'] = 3; + } + var styleMap = new OpenLayers.StyleMap({'default': OpenLayers.Util.applyDefaults(defaults_style, OpenLayers.Feature.Vector.style['default'])}); + this.layers.vector = new OpenLayers.Layer.Vector(" " + this.options.name, {styleMap: styleMap}); + this.map.addLayer(this.layers.vector); + wkt = document.getElementById(this.options.id).value; + if (wkt) { + var feat = OpenLayers.Util.properFeatures(this.read_wkt(wkt), this.options.geom_type); + this.write_wkt(feat); + if (this.options.is_collection) { + for (var i=0; i 1) { + old_feats = [this.layers.vector.features[0]]; + this.layers.vector.removeFeatures(old_feats); + this.layers.vector.destroyFeatures(old_feats); + } + this.write_wkt(event.feature); + } +}; + +MapWidget.prototype.modify_wkt = function(event) { + if (this.options.is_collection) { + if (this.options.geom_name == 'MultiPoint') { + this.add_wkt(event); + return; + } else { + var feat = new OpenLayers.Feature.Vector(new this.options.geom_type()); + for (var i=0; i{% block map_css %} + #{{ id }}_map { width: {{ map_width }}px; height: {{ map_height }}px; } + #{{ id }}_map .aligned label { float: inherit; } + #{{ id }}_div_map { position: relative; vertical-align: top; float: {{ LANGUAGE_BIDI|yesno:"right,left" }}; } + {% if not display_wkt %}#{{ id }} { display: none; }{% endif %} + .olControlEditingToolbar .olControlModifyFeatureItemActive { + background-image: url("{{ STATIC_URL }}admin/img/gis/move_vertex_on.png"); + background-repeat: no-repeat; + } + .olControlEditingToolbar .olControlModifyFeatureItemInactive { + background-image: url("{{ STATIC_URL }}admin/img/gis/move_vertex_off.png"); + background-repeat: no-repeat; + }{% endblock %} + + +
    +
    + Delete all Features + {% if display_wkt %}

    WKT debugging window:

    {% endif %} + + +
    diff --git a/django/contrib/gis/tests/__init__.py b/django/contrib/gis/tests/__init__.py index 765c03018b..1703f3f1b3 100644 --- a/django/contrib/gis/tests/__init__.py +++ b/django/contrib/gis/tests/__init__.py @@ -1,13 +1,4 @@ -from django.conf import settings -from django.test.simple import build_suite, DjangoTestSuiteRunner -from django.utils import unittest - -from .test_geoforms import GeometryFieldTest -from .test_measure import DistanceTest, AreaTest -from .test_spatialrefsys import SpatialRefSysTest - - -def geo_apps(namespace=True, runtests=False): +def geo_apps(): """ Returns a list of GeoDjango test applications that reside in `django.contrib.gis.tests` that can be used with the current @@ -36,88 +27,4 @@ def geo_apps(namespace=True, runtests=False): # 3D apps use LayerMapping, which uses GDAL and require GEOS 3.1+. if connection.ops.postgis and GEOS_PREPARE: apps.append('geo3d') - if runtests: - return [('django.contrib.gis.tests', app) for app in apps] - elif namespace: - return ['django.contrib.gis.tests.%s' % app - for app in apps] - else: - return apps - - -def geodjango_suite(apps=True): - """ - Returns a TestSuite consisting only of GeoDjango tests that can be run. - """ - import sys - from django.db.models import get_app - - suite = unittest.TestSuite() - - # Adding the GEOS tests. - from django.contrib.gis.geos import tests as geos_tests - suite.addTest(geos_tests.suite()) - - # Adding GDAL tests, and any test suite that depends on GDAL, to the - # suite if GDAL is available. - from django.contrib.gis.gdal import HAS_GDAL - if HAS_GDAL: - from django.contrib.gis.gdal import tests as gdal_tests - suite.addTest(gdal_tests.suite()) - else: - sys.stderr.write('GDAL not available - no tests requiring GDAL will be run.\n') - - # Add GeoIP tests to the suite, if the library and data is available. - from django.contrib.gis.geoip import HAS_GEOIP - if HAS_GEOIP and hasattr(settings, 'GEOIP_PATH'): - from django.contrib.gis.geoip import tests as geoip_tests - suite.addTest(geoip_tests.suite()) - - # Finally, adding the suites for each of the GeoDjango test apps. - if apps: - for app_name in geo_apps(namespace=False): - suite.addTest(build_suite(get_app(app_name))) - - return suite - - -class GeoDjangoTestSuiteRunner(DjangoTestSuiteRunner): - - def setup_test_environment(self, **kwargs): - super(GeoDjangoTestSuiteRunner, self).setup_test_environment(**kwargs) - - # Saving original values of INSTALLED_APPS, ROOT_URLCONF, and SITE_ID. - self.old_installed = getattr(settings, 'INSTALLED_APPS', None) - self.old_root_urlconf = getattr(settings, 'ROOT_URLCONF', '') - self.old_site_id = getattr(settings, 'SITE_ID', None) - - # Constructing the new INSTALLED_APPS, and including applications - # within the GeoDjango test namespace. - new_installed = [ - 'django.contrib.sites', - 'django.contrib.sitemaps', - 'django.contrib.gis', - ] - - # Calling out to `geo_apps` to get GeoDjango applications supported - # for testing. - new_installed.extend(geo_apps()) - settings.INSTALLED_APPS = list(self.old_installed) + new_installed - - # SITE_ID needs to be set - settings.SITE_ID = 1 - - # ROOT_URLCONF needs to be set, else `AttributeErrors` are raised - # when TestCases are torn down that have `urls` defined. - settings.ROOT_URLCONF = '' - - - def teardown_test_environment(self, **kwargs): - super(GeoDjangoTestSuiteRunner, self).teardown_test_environment(**kwargs) - settings.INSTALLED_APPS = self.old_installed - settings.ROOT_URLCONF = self.old_root_urlconf - settings.SITE_ID = self.old_site_id - - - def build_suite(self, test_labels, extra_tests=None, **kwargs): - return geodjango_suite() + return [('django.contrib.gis.tests', app) for app in apps] diff --git a/django/contrib/gis/tests/distapp/tests.py b/django/contrib/gis/tests/distapp/tests.py index 5574b42738..2ed17a03bd 100644 --- a/django/contrib/gis/tests/distapp/tests.py +++ b/django/contrib/gis/tests/distapp/tests.py @@ -2,24 +2,33 @@ from __future__ import absolute_import from django.db import connection from django.db.models import Q -from django.contrib.gis.geos import GEOSGeometry, LineString +from django.contrib.gis.geos import HAS_GEOS from django.contrib.gis.measure import D # alias for Distance -from django.contrib.gis.tests.utils import oracle, postgis, spatialite, no_oracle, no_spatialite +from django.contrib.gis.tests.utils import ( + HAS_SPATIAL_DB, mysql, oracle, postgis, spatialite, no_oracle, no_spatialite +) from django.test import TestCase +from django.utils.unittest import skipUnless -from .models import (AustraliaCity, Interstate, SouthTexasInterstate, - SouthTexasCity, SouthTexasCityFt, CensusZipcode, SouthTexasZipcode) +if HAS_GEOS and HAS_SPATIAL_DB: + from django.contrib.gis.geos import GEOSGeometry, LineString + + from .models import (AustraliaCity, Interstate, SouthTexasInterstate, + SouthTexasCity, SouthTexasCityFt, CensusZipcode, SouthTexasZipcode) +@skipUnless(HAS_GEOS and HAS_SPATIAL_DB and not mysql, + "Geos and spatial db (not mysql) are required.") class DistanceTest(TestCase): - # A point we are testing distances with -- using a WGS84 - # coordinate that'll be implicitly transormed to that to - # the coordinate system of the field, EPSG:32140 (Texas South Central - # w/units in meters) - stx_pnt = GEOSGeometry('POINT (-95.370401017314293 29.704867409475465)', 4326) - # Another one for Australia - au_pnt = GEOSGeometry('POINT (150.791 -34.4919)', 4326) + if HAS_GEOS and HAS_SPATIAL_DB: + # A point we are testing distances with -- using a WGS84 + # coordinate that'll be implicitly transormed to that to + # the coordinate system of the field, EPSG:32140 (Texas South Central + # w/units in meters) + stx_pnt = GEOSGeometry('POINT (-95.370401017314293 29.704867409475465)', 4326) + # Another one for Australia + au_pnt = GEOSGeometry('POINT (150.791 -34.4919)', 4326) def get_names(self, qs): cities = [c.name for c in qs] diff --git a/django/contrib/gis/tests/geo3d/tests.py b/django/contrib/gis/tests/geo3d/tests.py index 6b40164422..df9f35690b 100644 --- a/django/contrib/gis/tests/geo3d/tests.py +++ b/django/contrib/gis/tests/geo3d/tests.py @@ -3,14 +3,22 @@ from __future__ import absolute_import, unicode_literals import os import re -from django.contrib.gis.db.models import Union, Extent3D -from django.contrib.gis.geos import GEOSGeometry, LineString, Point, Polygon -from django.contrib.gis.utils import LayerMapping, LayerMapError +from django.contrib.gis.gdal import HAS_GDAL +from django.contrib.gis.geos import HAS_GEOS +from django.contrib.gis.tests.utils import postgis from django.test import TestCase from django.utils._os import upath +from django.utils.unittest import skipUnless -from .models import (City3D, Interstate2D, Interstate3D, InterstateProj2D, - InterstateProj3D, Point2D, Point3D, MultiPoint3D, Polygon2D, Polygon3D) +if HAS_GEOS: + from django.contrib.gis.db.models import Union, Extent3D + from django.contrib.gis.geos import GEOSGeometry, LineString, Point, Polygon + + from .models import (City3D, Interstate2D, Interstate3D, InterstateProj2D, + InterstateProj3D, Point2D, Point3D, MultiPoint3D, Polygon2D, Polygon3D) + +if HAS_GDAL: + from django.contrib.gis.utils import LayerMapping, LayerMapError data_path = os.path.realpath(os.path.join(os.path.dirname(upath(__file__)), '..', 'data')) @@ -54,6 +62,7 @@ bbox_data = ( ) +@skipUnless(HAS_GEOS and HAS_GDAL and postgis, "Geos, GDAL and postgis are required.") class Geo3DTest(TestCase): """ Only a subset of the PostGIS routines are 3D-enabled, and this TestCase diff --git a/django/contrib/gis/tests/geoadmin/tests.py b/django/contrib/gis/tests/geoadmin/tests.py index 669914bdea..15874758be 100644 --- a/django/contrib/gis/tests/geoadmin/tests.py +++ b/django/contrib/gis/tests/geoadmin/tests.py @@ -1,12 +1,18 @@ from __future__ import absolute_import from django.test import TestCase -from django.contrib.gis import admin -from django.contrib.gis.geos import GEOSGeometry, Point +from django.contrib.gis.geos import HAS_GEOS +from django.contrib.gis.tests.utils import HAS_SPATIAL_DB +from django.utils.unittest import skipUnless -from .models import City +if HAS_GEOS and HAS_SPATIAL_DB: + from django.contrib.gis import admin + from django.contrib.gis.geos import Point + + from .models import City +@skipUnless(HAS_GEOS and HAS_SPATIAL_DB, "Geos and spatial db are required.") class GeoAdminTest(TestCase): urls = 'django.contrib.gis.tests.geoadmin.urls' @@ -24,10 +30,7 @@ class GeoAdminTest(TestCase): result) def test_olmap_WMS_rendering(self): - admin.site.unregister(City) - admin.site.register(City, admin.GeoModelAdmin) - - geoadmin = admin.site._registry[City] + geoadmin = admin.GeoModelAdmin(City, admin.site) result = geoadmin.get_map_widget(City._meta.get_field('point'))( ).render('point', Point(-79.460734, 40.18476)) self.assertIn( diff --git a/django/contrib/gis/tests/geoapp/test_feeds.py b/django/contrib/gis/tests/geoapp/test_feeds.py index c9cf6362c1..778cadc9d4 100644 --- a/django/contrib/gis/tests/geoapp/test_feeds.py +++ b/django/contrib/gis/tests/geoapp/test_feeds.py @@ -4,11 +4,16 @@ from xml.dom import minidom from django.conf import settings from django.contrib.sites.models import Site +from django.contrib.gis.geos import HAS_GEOS +from django.contrib.gis.tests.utils import HAS_SPATIAL_DB from django.test import TestCase +from django.utils.unittest import skipUnless -from .models import City +if HAS_GEOS: + from .models import City +@skipUnless(HAS_GEOS and HAS_SPATIAL_DB, "Geos and spatial db are required.") class GeoFeedTest(TestCase): urls = 'django.contrib.gis.tests.geoapp.urls' diff --git a/django/contrib/gis/tests/geoapp/test_regress.py b/django/contrib/gis/tests/geoapp/test_regress.py index a27b2d40f6..43dbcfd852 100644 --- a/django/contrib/gis/tests/geoapp/test_regress.py +++ b/django/contrib/gis/tests/geoapp/test_regress.py @@ -3,14 +3,19 @@ from __future__ import absolute_import, unicode_literals from datetime import datetime +from django.contrib.gis.geos import HAS_GEOS from django.contrib.gis.tests.utils import no_mysql, no_spatialite from django.contrib.gis.shortcuts import render_to_kmz +from django.contrib.gis.tests.utils import HAS_SPATIAL_DB from django.db.models import Count, Min from django.test import TestCase +from django.utils.unittest import skipUnless -from .models import City, PennsylvaniaCity, State, Truth +if HAS_GEOS: + from .models import City, PennsylvaniaCity, State, Truth +@skipUnless(HAS_GEOS and HAS_SPATIAL_DB, "Geos and spatial db are required.") class GeoRegressionTests(TestCase): def test_update(self): @@ -72,8 +77,8 @@ class GeoRegressionTests(TestCase): t1 = Truth.objects.create(val=True) t2 = Truth.objects.create(val=False) - val1 = Truth.objects.get(pk=1).val - val2 = Truth.objects.get(pk=2).val + val1 = Truth.objects.get(pk=t1.pk).val + val2 = Truth.objects.get(pk=t2.pk).val # verify types -- should't be 0/1 self.assertIsInstance(val1, bool) self.assertIsInstance(val2, bool) diff --git a/django/contrib/gis/tests/geoapp/test_sitemaps.py b/django/contrib/gis/tests/geoapp/test_sitemaps.py index aa2d97032c..337b4b75c6 100644 --- a/django/contrib/gis/tests/geoapp/test_sitemaps.py +++ b/django/contrib/gis/tests/geoapp/test_sitemaps.py @@ -5,12 +5,17 @@ from xml.dom import minidom import zipfile from django.conf import settings +from django.contrib.gis.geos import HAS_GEOS +from django.contrib.gis.tests.utils import HAS_SPATIAL_DB from django.contrib.sites.models import Site from django.test import TestCase +from django.utils.unittest import skipUnless -from .models import City, Country +if HAS_GEOS: + from .models import City, Country +@skipUnless(HAS_GEOS and HAS_SPATIAL_DB, "Geos and spatial db are required.") class GeoSitemapTest(TestCase): urls = 'django.contrib.gis.tests.geoapp.urls' diff --git a/django/contrib/gis/tests/geoapp/tests.py b/django/contrib/gis/tests/geoapp/tests.py index 672d78c1dd..cf6e316919 100644 --- a/django/contrib/gis/tests/geoapp/tests.py +++ b/django/contrib/gis/tests/geoapp/tests.py @@ -3,26 +3,31 @@ from __future__ import absolute_import import re from django.db import connection -from django.db.utils import DatabaseError from django.contrib.gis import gdal -from django.contrib.gis.geos import (fromstr, GEOSGeometry, - Point, LineString, LinearRing, Polygon, GeometryCollection) +from django.contrib.gis.geos import HAS_GEOS from django.contrib.gis.tests.utils import ( no_mysql, no_oracle, no_spatialite, mysql, oracle, postgis, spatialite) from django.test import TestCase from django.utils import six, unittest +from django.utils.unittest import skipUnless -from .models import Country, City, PennsylvaniaCity, State, Track +if HAS_GEOS: + from django.contrib.gis.geos import (fromstr, GEOSGeometry, + Point, LineString, LinearRing, Polygon, GeometryCollection) -from .test_feeds import GeoFeedTest -from .test_regress import GeoRegressionTests -from .test_sitemaps import GeoSitemapTest + from .models import Country, City, PennsylvaniaCity, State, Track - -if not spatialite: +if HAS_GEOS and not spatialite: from .models import Feature, MinusOneSRID + +def postgis_bug_version(): + spatial_version = getattr(connection.ops, "spatial_version", (0,0,0)) + return spatial_version and (2, 0, 0) <= spatial_version <= (2, 0, 1) + + +@skipUnless(HAS_GEOS and postgis, "Geos and postgis are required.") class GeoModelTest(TestCase): def test_fixtures(self): @@ -197,6 +202,7 @@ class GeoModelTest(TestCase): self.assertTrue(isinstance(cities2[0].point, Point)) +@skipUnless(HAS_GEOS and postgis, "Geos and postgis are required.") class GeoLookupTest(TestCase): @no_mysql @@ -297,7 +303,7 @@ class GeoLookupTest(TestCase): # The left/right lookup tests are known failures on PostGIS 2.0/2.0.1 # http://trac.osgeo.org/postgis/ticket/2035 - if connection.ops.postgis and (2, 0, 0) <= connection.ops.spatial_version <= (2, 0, 1): + if postgis_bug_version(): test_left_right_lookups = unittest.expectedFailure(test_left_right_lookups) def test_equals_lookups(self): @@ -382,6 +388,7 @@ class GeoLookupTest(TestCase): self.assertEqual('Lawrence', City.objects.get(point__relate=(ks.poly, intersects_mask)).name) +@skipUnless(HAS_GEOS and postgis, "Geos and postgis are required.") class GeoQuerySetTest(TestCase): # Please keep the tests in GeoQuerySet method's alphabetic order diff --git a/django/contrib/gis/tests/geogapp/tests.py b/django/contrib/gis/tests/geogapp/tests.py index a8c607c502..ed54999f90 100644 --- a/django/contrib/gis/tests/geogapp/tests.py +++ b/django/contrib/gis/tests/geogapp/tests.py @@ -5,14 +5,19 @@ from __future__ import absolute_import import os -from django.contrib.gis import gdal +from django.contrib.gis.gdal import HAS_GDAL +from django.contrib.gis.geos import HAS_GEOS from django.contrib.gis.measure import D +from django.contrib.gis.tests.utils import postgis from django.test import TestCase from django.utils._os import upath +from django.utils.unittest import skipUnless -from .models import City, County, Zipcode +if HAS_GEOS: + from .models import City, County, Zipcode +@skipUnless(HAS_GEOS and postgis, "Geos and postgis are required.") class GeographyTest(TestCase): def test01_fixture_load(self): @@ -54,11 +59,11 @@ class GeographyTest(TestCase): htown = City.objects.get(name='Houston') self.assertRaises(ValueError, City.objects.get, point__exact=htown.point) + @skipUnless(HAS_GDAL, "GDAL is required.") def test05_geography_layermapping(self): "Testing LayerMapping support on models with geography fields." # There is a similar test in `layermap` that uses the same data set, # but the County model here is a bit different. - if not gdal.HAS_GDAL: return from django.contrib.gis.utils import LayerMapping # Getting the shapefile and mapping dictionary. diff --git a/django/contrib/gis/tests/inspectapp/tests.py b/django/contrib/gis/tests/inspectapp/tests.py index d28bdf0290..668b87ba86 100644 --- a/django/contrib/gis/tests/inspectapp/tests.py +++ b/django/contrib/gis/tests/inspectapp/tests.py @@ -4,13 +4,19 @@ import os from django.db import connections from django.test import TestCase -from django.contrib.gis.gdal import Driver +from django.contrib.gis.gdal import HAS_GDAL from django.contrib.gis.geometry.test_data import TEST_DATA -from django.contrib.gis.utils.ogrinspect import ogrinspect +from django.contrib.gis.tests.utils import HAS_SPATIAL_DB +from django.utils.unittest import skipUnless -from .models import AllOGRFields +if HAS_GDAL: + from django.contrib.gis.gdal import Driver + from django.contrib.gis.utils.ogrinspect import ogrinspect + + from .models import AllOGRFields +@skipUnless(HAS_GDAL and HAS_SPATIAL_DB, "GDAL and spatial db are required.") class OGRInspectTest(TestCase): maxDiff = 1024 diff --git a/django/contrib/gis/tests/layermap/tests.py b/django/contrib/gis/tests/layermap/tests.py index 470e5be216..8379311a2b 100644 --- a/django/contrib/gis/tests/layermap/tests.py +++ b/django/contrib/gis/tests/layermap/tests.py @@ -5,19 +5,23 @@ import os from copy import copy from decimal import Decimal -from django.contrib.gis.gdal import DataSource -from django.contrib.gis.tests.utils import mysql -from django.contrib.gis.utils.layermapping import (LayerMapping, LayerMapError, - InvalidDecimal, MissingForeignKey) +from django.contrib.gis.gdal import HAS_GDAL +from django.contrib.gis.tests.utils import HAS_SPATIAL_DB, mysql from django.db import router from django.conf import settings from django.test import TestCase from django.utils import unittest +from django.utils.unittest import skipUnless from django.utils._os import upath -from .models import ( - City, County, CountyFeat, Interstate, ICity1, ICity2, Invalid, State, - city_mapping, co_mapping, cofeat_mapping, inter_mapping) +if HAS_GDAL: + from django.contrib.gis.utils.layermapping import (LayerMapping, + LayerMapError, InvalidDecimal, MissingForeignKey) + from django.contrib.gis.gdal import DataSource + + from .models import ( + City, County, CountyFeat, Interstate, ICity1, ICity2, Invalid, State, + city_mapping, co_mapping, cofeat_mapping, inter_mapping) shp_path = os.path.realpath(os.path.join(os.path.dirname(upath(__file__)), os.pardir, 'data')) @@ -32,6 +36,7 @@ NUMS = [1, 2, 1, 19, 1] # Number of polygons for each. STATES = ['Texas', 'Texas', 'Texas', 'Hawaii', 'Colorado'] +@skipUnless(HAS_GDAL and HAS_SPATIAL_DB, "GDAL and spatial db are required.") class LayerMapTest(TestCase): def test_init(self): @@ -310,6 +315,7 @@ class OtherRouter(object): return True +@skipUnless(HAS_GDAL and HAS_SPATIAL_DB, "GDAL and spatial db are required.") class LayerMapRouterTest(TestCase): def setUp(self): diff --git a/django/contrib/gis/tests/relatedapp/tests.py b/django/contrib/gis/tests/relatedapp/tests.py index 93a8ff4efd..6320edcff5 100644 --- a/django/contrib/gis/tests/relatedapp/tests.py +++ b/django/contrib/gis/tests/relatedapp/tests.py @@ -2,15 +2,20 @@ from __future__ import absolute_import from datetime import date -from django.contrib.gis.geos import GEOSGeometry, Point, MultiPoint -from django.contrib.gis.db.models import Collect, Count, Extent, F, Union -from django.contrib.gis.geometry.backend import Geometry -from django.contrib.gis.tests.utils import mysql, oracle, no_mysql, no_oracle, no_spatialite +from django.contrib.gis.geos import HAS_GEOS +from django.contrib.gis.tests.utils import HAS_SPATIAL_DB, mysql, oracle, no_mysql, no_oracle, no_spatialite from django.test import TestCase +from django.utils.unittest import skipUnless -from .models import City, Location, DirectoryEntry, Parcel, Book, Author, Article +if HAS_GEOS: + from django.contrib.gis.db.models import Collect, Count, Extent, F, Union + from django.contrib.gis.geometry.backend import Geometry + from django.contrib.gis.geos import GEOSGeometry, Point, MultiPoint + + from .models import City, Location, DirectoryEntry, Parcel, Book, Author, Article +@skipUnless(HAS_GEOS and HAS_SPATIAL_DB, "Geos and spatial db are required.") class RelatedGeoModelTest(TestCase): def test02_select_related(self): diff --git a/django/contrib/gis/tests/test_geoforms.py b/django/contrib/gis/tests/test_geoforms.py index 24bb50c6bc..402d9b944b 100644 --- a/django/contrib/gis/tests/test_geoforms.py +++ b/django/contrib/gis/tests/test_geoforms.py @@ -1,24 +1,25 @@ from django.forms import ValidationError from django.contrib.gis.gdal import HAS_GDAL from django.contrib.gis.tests.utils import HAS_SPATIALREFSYS +from django.test import SimpleTestCase from django.utils import six -from django.utils import unittest +from django.utils.unittest import skipUnless if HAS_SPATIALREFSYS: from django.contrib.gis import forms from django.contrib.gis.geos import GEOSGeometry -@unittest.skipUnless(HAS_GDAL and HAS_SPATIALREFSYS, "GeometryFieldTest needs gdal support and a spatial database") -class GeometryFieldTest(unittest.TestCase): +@skipUnless(HAS_GDAL and HAS_SPATIALREFSYS, "GeometryFieldTest needs gdal support and a spatial database") +class GeometryFieldTest(SimpleTestCase): - def test00_init(self): + def test_init(self): "Testing GeometryField initialization with defaults." fld = forms.GeometryField() for bad_default in ('blah', 3, 'FoO', None, 0): self.assertRaises(ValidationError, fld.clean, bad_default) - def test01_srid(self): + def test_srid(self): "Testing GeometryField with a SRID set." # Input that doesn't specify the SRID is assumed to be in the SRID # of the input field. @@ -34,7 +35,7 @@ class GeometryFieldTest(unittest.TestCase): cleaned_geom = fld.clean('SRID=4326;POINT (-95.363151 29.763374)') self.assertTrue(xform_geom.equals_exact(cleaned_geom, tol)) - def test02_null(self): + def test_null(self): "Testing GeometryField's handling of null (None) geometries." # Form fields, by default, are required (`required=True`) fld = forms.GeometryField() @@ -46,7 +47,7 @@ class GeometryFieldTest(unittest.TestCase): fld = forms.GeometryField(required=False) self.assertIsNone(fld.clean(None)) - def test03_geom_type(self): + def test_geom_type(self): "Testing GeometryField's handling of different geometry types." # By default, all geometry types are allowed. fld = forms.GeometryField() @@ -60,7 +61,7 @@ class GeometryFieldTest(unittest.TestCase): # but rejected by `clean` self.assertRaises(forms.ValidationError, pnt_fld.clean, 'LINESTRING(0 0, 1 1)') - def test04_to_python(self): + def test_to_python(self): """ Testing to_python returns a correct GEOSGeometry object or a ValidationError @@ -74,13 +75,169 @@ class GeometryFieldTest(unittest.TestCase): self.assertRaises(forms.ValidationError, fld.to_python, wkt) -def suite(): - s = unittest.TestSuite() - s.addTest(unittest.makeSuite(GeometryFieldTest)) - return s +@skipUnless(HAS_GDAL and HAS_SPATIALREFSYS, + "SpecializedFieldTest needs gdal support and a spatial database") +class SpecializedFieldTest(SimpleTestCase): + def setUp(self): + self.geometries = { + 'point': GEOSGeometry("SRID=4326;POINT(9.052734375 42.451171875)"), + 'multipoint': GEOSGeometry("SRID=4326;MULTIPOINT(" + "(13.18634033203125 14.504356384277344)," + "(13.207969665527 14.490966796875)," + "(13.177070617675 14.454917907714))"), + 'linestring': GEOSGeometry("SRID=4326;LINESTRING(" + "-8.26171875 -0.52734375," + "-7.734375 4.21875," + "6.85546875 3.779296875," + "5.44921875 -3.515625)"), + 'multilinestring': GEOSGeometry("SRID=4326;MULTILINESTRING(" + "(-16.435546875 -2.98828125," + "-17.2265625 2.98828125," + "-0.703125 3.515625," + "-1.494140625 -3.33984375)," + "(-8.0859375 -5.9765625," + "8.525390625 -8.7890625," + "12.392578125 -0.87890625," + "10.01953125 7.646484375))"), + 'polygon': GEOSGeometry("SRID=4326;POLYGON(" + "(-1.669921875 6.240234375," + "-3.8671875 -0.615234375," + "5.9765625 -3.955078125," + "18.193359375 3.955078125," + "9.84375 9.4921875," + "-1.669921875 6.240234375))"), + 'multipolygon': GEOSGeometry("SRID=4326;MULTIPOLYGON(" + "((-17.578125 13.095703125," + "-17.2265625 10.8984375," + "-13.974609375 10.1953125," + "-13.359375 12.744140625," + "-15.732421875 13.7109375," + "-17.578125 13.095703125))," + "((-8.525390625 5.537109375," + "-8.876953125 2.548828125," + "-5.888671875 1.93359375," + "-5.09765625 4.21875," + "-6.064453125 6.240234375," + "-8.525390625 5.537109375)))"), + 'geometrycollection': GEOSGeometry("SRID=4326;GEOMETRYCOLLECTION(" + "POINT(5.625 -0.263671875)," + "POINT(6.767578125 -3.603515625)," + "POINT(8.525390625 0.087890625)," + "POINT(8.0859375 -2.13134765625)," + "LINESTRING(" + "6.273193359375 -1.175537109375," + "5.77880859375 -1.812744140625," + "7.27294921875 -2.230224609375," + "7.657470703125 -1.25244140625))"), + } -def run(verbosity=2): - unittest.TextTestRunner(verbosity=verbosity).run(suite()) + def assertMapWidget(self, form_instance): + """ + Make sure the MapWidget js is passed in the form media and a MapWidget + is actually created + """ + self.assertTrue(form_instance.is_valid()) + rendered = form_instance.as_p() + self.assertIn('new MapWidget(options);', rendered) + self.assertIn('gis/js/OLMapWidget.js', str(form_instance.media)) -if __name__=="__main__": - run() + def assertTextarea(self, geom, rendered): + """Makes sure the wkt and a textarea are in the content""" + + self.assertIn('