Index: django/contrib/gis/geos/geometry.py
===================================================================
--- django/contrib/gis/geos/geometry.py	(revision 13863)
+++ django/contrib/gis/geos/geometry.py	(working copy)
@@ -275,6 +275,15 @@
         "This property tests the validity of this Geometry."
         return capi.geos_isvalid(self.ptr)
 
+    @property
+    def valid_reason(self):
+        """
+        Returns a string containing the reason for any invalidity.
+        """
+        if not GEOS_PREPARE:
+            raise GEOSException('Upgrade GEOS to 3.1 to get validity reason.')               
+        return capi.geos_isvalidreason(self.ptr)
+
     #### Binary predicates. ####
     def contains(self, other):
         "Returns true if other.within(this) returns true."
Index: django/contrib/gis/geos/prototypes/misc.py
===================================================================
--- django/contrib/gis/geos/prototypes/misc.py	(revision 13863)
+++ django/contrib/gis/geos/prototypes/misc.py	(working copy)
@@ -4,7 +4,8 @@
 """
 from ctypes import c_int, c_double, POINTER
 from django.contrib.gis.geos.libgeos import GEOM_PTR
-from django.contrib.gis.geos.prototypes.errcheck import check_dbl
+from django.contrib.gis.geos.prototypes.errcheck import check_dbl, check_string
+from django.contrib.gis.geos.prototypes.geom import geos_char_p
 from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc
 
 ### ctypes generator function ###
@@ -26,3 +27,9 @@
 geos_area = dbl_from_geom(GEOSFunc('GEOSArea'))
 geos_distance = dbl_from_geom(GEOSFunc('GEOSDistance'), num_geom=2)
 geos_length = dbl_from_geom(GEOSFunc('GEOSLength'))
+
+# Validity reason
+geos_isvalidreason = GEOSFunc('GEOSisValidReason')
+geos_isvalidreason.argtypes = [GEOM_PTR]
+geos_isvalidreason.restype = geos_char_p
+geos_isvalidreason.errcheck = check_string
Index: django/contrib/gis/geos/prototypes/__init__.py
===================================================================
--- django/contrib/gis/geos/prototypes/__init__.py	(revision 13863)
+++ django/contrib/gis/geos/prototypes/__init__.py	(working copy)
@@ -18,7 +18,7 @@
     to_hex, to_wkb, to_wkt
 
 # Miscellaneous routines.
-from django.contrib.gis.geos.prototypes.misc import geos_area, geos_distance, geos_length
+from django.contrib.gis.geos.prototypes.misc import geos_area, geos_distance, geos_length, geos_isvalidreason
 
 # Predicates
 from django.contrib.gis.geos.prototypes.predicates import geos_hasz, geos_isempty, \
Index: django/contrib/gis/geos/tests/test_geos.py
===================================================================
--- django/contrib/gis/geos/tests/test_geos.py	(revision 13863)
+++ django/contrib/gis/geos/tests/test_geos.py	(working copy)
@@ -2,6 +2,7 @@
 from django.contrib.gis.geos import *
 from django.contrib.gis.geos.base import gdal, numpy, GEOSBase
 from django.contrib.gis.tests.geometries import *
+from django.contrib.gis.geos.libgeos import GEOS_PREPARE
 
 class GEOSTest(unittest.TestCase):
 
@@ -919,6 +920,22 @@
         for geom, merged in zip(ref_geoms, ref_merged):
             self.assertEqual(merged, geom.merged)
 
+    def test27_valid_reason(self):
+        "Testing IsValidReason support"
+        if not GEOS_PREPARE:
+            print >>sys.stderr, "Skipping tests (GEOS < v3.1)"
+            return
+            
+        g = GEOSGeometry("POINT(0 0)")
+        self.assert_(g.valid)
+        self.assert_(isinstance(g.valid_reason, basestring))
+        self.assertEqual(g.valid_reason, "Valid Geometry")
+        
+        g = GEOSGeometry("LINESTRING(0 0, 0 0)")
+        self.assert_(not g.valid)
+        self.assert_(isinstance(g.valid_reason, basestring))
+        self.assertEqual(g.valid_reason, "Too few points in geometry component[0 0]")
+
 def suite():
     s = unittest.TestSuite()
     s.addTest(unittest.makeSuite(GEOSTest))
Index: docs/ref/contrib/gis/geos.txt
===================================================================
--- docs/ref/contrib/gis/geos.txt	(revision 13863)
+++ docs/ref/contrib/gis/geos.txt	(working copy)
@@ -219,6 +219,12 @@
 
 Returns a boolean indicating whether the geometry is valid.
 
+.. attribute:: GEOSGeometry.valid_reason
+
+.. versionadded:: 1.3
+
+Returns a string describing the reason why a geometry is invalid.
+
 .. attribute:: GEOSGeometry.srid
 
 Property that may be used to retrieve or set the SRID associated with the
