summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorMarc Garcia <garcia.marc@gmail.com>2009-07-01 23:33:02 +0000
committerMarc Garcia <garcia.marc@gmail.com>2009-07-01 23:33:02 +0000
commitf5095f8cb367e51e18e084ac118d6900ab9ba735 (patch)
tree58baa1119dead8fbb2a0d98af8f70cca0d55ef09 /django
parent87cd3dfa55770e5a987d43797c013d81530083db (diff)
[soc2009/i18n] merged up to trunk r11147
git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2009/i18n-improvements@11148 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'django')
-rw-r--r--django/contrib/admindocs/templates/admin_doc/model_index.html2
-rw-r--r--django/contrib/admindocs/views.py18
-rw-r--r--django/contrib/comments/views/comments.py3
-rw-r--r--django/contrib/contenttypes/generic.py2
-rw-r--r--django/contrib/gis/admin/options.py2
-rw-r--r--django/contrib/gis/db/models/sql/query.py2
-rw-r--r--django/contrib/gis/gdal/libgdal.py2
-rw-r--r--django/contrib/gis/maps/google/gmap.py4
-rw-r--r--django/contrib/gis/templates/gis/google/google-base.js7
-rw-r--r--django/contrib/gis/templates/gis/google/google-map.js12
-rw-r--r--django/contrib/gis/templates/gis/google/google-multi.js2
-rw-r--r--django/contrib/gis/templates/gis/google/google-single.js4
-rw-r--r--django/contrib/gis/tests/relatedapp/models.py2
-rw-r--r--django/contrib/gis/tests/relatedapp/tests.py7
-rw-r--r--django/contrib/gis/tests/test_geoip.py7
-rw-r--r--django/contrib/gis/utils/geoip.py85
-rw-r--r--django/core/mail.py87
-rw-r--r--django/core/management/commands/dumpdata.py2
-rw-r--r--django/core/urlresolvers.py1
-rw-r--r--django/db/backends/creation.py14
-rw-r--r--django/db/models/base.py33
-rw-r--r--django/db/models/fields/related.py31
-rw-r--r--django/templatetags/cache.py4
23 files changed, 228 insertions, 105 deletions
diff --git a/django/contrib/admindocs/templates/admin_doc/model_index.html b/django/contrib/admindocs/templates/admin_doc/model_index.html
index 4dd7caa2a6..47c94c0c70 100644
--- a/django/contrib/admindocs/templates/admin_doc/model_index.html
+++ b/django/contrib/admindocs/templates/admin_doc/model_index.html
@@ -36,7 +36,7 @@
<ul>
{% regroup models by app_label as grouped_models %}
{% for group in grouped_models %}
- <li><a href="#{{ group.grouper }}">{{ group.grouper|capfirst }}</a></li>
+ <li><a href="#app-{{ group.grouper }}">{{ group.grouper|capfirst }}</a></li>
{% endfor %}
</ul>
</div>
diff --git a/django/contrib/admindocs/views.py b/django/contrib/admindocs/views.py
index f66ea3e9b0..4f22fe0a0a 100644
--- a/django/contrib/admindocs/views.py
+++ b/django/contrib/admindocs/views.py
@@ -214,6 +214,22 @@ def model_detail(request, app_label, model_name):
'help_text': field.help_text,
})
+ # Gather many-to-many fields.
+ for field in opts.many_to_many:
+ data_type = related_object_name = field.rel.to.__name__
+ app_label = field.rel.to._meta.app_label
+ verbose = _("related `%(app_label)s.%(object_name)s` objects") % {'app_label': app_label, 'object_name': data_type}
+ fields.append({
+ 'name': "%s.all" % field.name,
+ "data_type": 'List',
+ 'verbose': utils.parse_rst(_("all %s") % verbose , 'model', _('model:') + opts.module_name),
+ })
+ fields.append({
+ 'name' : "%s.count" % field.name,
+ 'data_type' : 'Integer',
+ 'verbose' : utils.parse_rst(_("number of %s") % verbose , 'model', _('model:') + opts.module_name),
+ })
+
# Gather model methods.
for func_name, func in model.__dict__.items():
if (inspect.isfunction(func) and len(inspect.getargspec(func)[0]) == 1):
@@ -233,7 +249,7 @@ def model_detail(request, app_label, model_name):
})
# Gather related objects
- for rel in opts.get_all_related_objects():
+ for rel in opts.get_all_related_objects() + opts.get_all_related_many_to_many_objects():
verbose = _("related `%(app_label)s.%(object_name)s` objects") % {'app_label': rel.opts.app_label, 'object_name': rel.opts.object_name}
accessor = rel.get_accessor_name()
fields.append({
diff --git a/django/contrib/comments/views/comments.py b/django/contrib/comments/views/comments.py
index ae3a672a80..89a3dd9bba 100644
--- a/django/contrib/comments/views/comments.py
+++ b/django/contrib/comments/views/comments.py
@@ -37,6 +37,9 @@ def post_comment(request, next=None):
if not data.get('email', ''):
data["email"] = request.user.email
+ # Check to see if the POST data overrides the view's next argument.
+ next = data.get("next", next)
+
# Look up the object we're trying to comment about
ctype = data.get("content_type")
object_pk = data.get("object_pk")
diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py
index 5564548133..4df48ff9f5 100644
--- a/django/contrib/contenttypes/generic.py
+++ b/django/contrib/contenttypes/generic.py
@@ -317,7 +317,7 @@ class BaseGenericInlineFormSet(BaseModelFormSet):
def get_queryset(self):
# Avoid a circular import.
from django.contrib.contenttypes.models import ContentType
- if self.instance is None:
+ if self.instance is None or self.instance.pk is None:
return self.model._default_manager.none()
return self.model._default_manager.filter(**{
self.ct_field.name: ContentType.objects.get_for_model(self.instance),
diff --git a/django/contrib/gis/admin/options.py b/django/contrib/gis/admin/options.py
index 6a63a9fe78..fff1cb20a6 100644
--- a/django/contrib/gis/admin/options.py
+++ b/django/contrib/gis/admin/options.py
@@ -32,7 +32,7 @@ class GeoModelAdmin(ModelAdmin):
map_height = 400
map_srid = 4326
map_template = 'gis/admin/openlayers.html'
- openlayers_url = 'http://openlayers.org/api/2.7/OpenLayers.js'
+ openlayers_url = 'http://openlayers.org/api/2.8/OpenLayers.js'
point_zoom = num_zoom - 6
wms_url = 'http://labs.metacarta.com/wms/vmap0'
wms_layer = 'basic'
diff --git a/django/contrib/gis/db/models/sql/query.py b/django/contrib/gis/db/models/sql/query.py
index cf1ccf6483..5df15a88b1 100644
--- a/django/contrib/gis/db/models/sql/query.py
+++ b/django/contrib/gis/db/models/sql/query.py
@@ -225,7 +225,7 @@ class GeoQuery(sql.Query):
values.append(self.convert_values(value, field))
else:
values.extend(row[index_start:])
- return values
+ return tuple(values)
def convert_values(self, value, field):
"""
diff --git a/django/contrib/gis/gdal/libgdal.py b/django/contrib/gis/gdal/libgdal.py
index c1bb45c742..92e3165680 100644
--- a/django/contrib/gis/gdal/libgdal.py
+++ b/django/contrib/gis/gdal/libgdal.py
@@ -14,7 +14,7 @@ if lib_path:
lib_names = None
elif os.name == 'nt':
# Windows NT shared library
- lib_names = ['gdal15']
+ lib_names = ['gdal16', 'gdal15']
elif os.name == 'posix':
# *NIX library names.
lib_names = ['gdal', 'GDAL', 'gdal1.6.0', 'gdal1.5.0', 'gdal1.4.0']
diff --git a/django/contrib/gis/maps/google/gmap.py b/django/contrib/gis/maps/google/gmap.py
index de8f75c5a0..cca5dc941f 100644
--- a/django/contrib/gis/maps/google/gmap.py
+++ b/django/contrib/gis/maps/google/gmap.py
@@ -21,7 +21,7 @@ class GoogleMap(object):
def __init__(self, key=None, api_url=None, version=None,
center=None, zoom=None, dom_id='map',
kml_urls=[], polylines=None, polygons=None, markers=None,
- template='gis/google/google-single.js',
+ template='gis/google/google-map.js',
js_module='geodjango',
extra_context={}):
@@ -162,7 +162,7 @@ class GoogleMapSet(GoogleMap):
# This is the template used to generate the GMap load JavaScript for
# each map in the set.
- self.map_template = kwargs.pop('map_template', 'gis/google/google-map.js')
+ self.map_template = kwargs.pop('map_template', 'gis/google/google-single.js')
# Running GoogleMap.__init__(), and resetting the template
# value with default obtained above.
diff --git a/django/contrib/gis/templates/gis/google/google-base.js b/django/contrib/gis/templates/gis/google/google-base.js
deleted file mode 100644
index f3a91edbc4..0000000000
--- a/django/contrib/gis/templates/gis/google/google-base.js
+++ /dev/null
@@ -1,7 +0,0 @@
-{% block vars %}var geodjango = {};{% for icon in icons %}
-var {{ icon.varname }} = new GIcon(G_DEFAULT_ICON);
-{% if icon.image %}{{ icon.varname }}.image = "{{ icon.image }}";{% endif %}
-{% if icon.shadow %}{{ icon.varname }}.shadow = "{{ icon.shadow }}";{% endif %} {% if icon.shadowsize %}{{ icon.varname }}.shadowSize = new GSize({{ icon.shadowsize.0 }}, {{ icon.shadowsize.1 }});{% endif %}
-{% if icon.iconanchor %}{{ icon.varname }}.iconAnchor = new GPoint({{ icon.iconanchor.0 }}, {{ icon.iconanchor.1 }});{% endif %} {% if icon.iconsize %}{{ icon.varname }}.iconSize = new GSize({{ icon.iconsize.0 }}, {{ icon.iconsize.1 }});{% endif %}
-{% if icon.infowindowanchor %}{{ icon.varname }}.infoWindowAnchor = new GPoint({{ icon.infowindowanchor.0 }}, {{ icon.infowindowanchor.1 }});{% endif %}{% endfor %}{% endblock %}
-{% block functions %}{% endblock %} \ No newline at end of file
diff --git a/django/contrib/gis/templates/gis/google/google-map.js b/django/contrib/gis/templates/gis/google/google-map.js
index e5f3a0e0e3..06f11e35f3 100644
--- a/django/contrib/gis/templates/gis/google/google-map.js
+++ b/django/contrib/gis/templates/gis/google/google-map.js
@@ -1,10 +1,16 @@
{% autoescape off %}
+{% block vars %}var geodjango = {};{% for icon in icons %}
+var {{ icon.varname }} = new GIcon(G_DEFAULT_ICON);
+{% if icon.image %}{{ icon.varname }}.image = "{{ icon.image }}";{% endif %}
+{% if icon.shadow %}{{ icon.varname }}.shadow = "{{ icon.shadow }}";{% endif %} {% if icon.shadowsize %}{{ icon.varname }}.shadowSize = new GSize({{ icon.shadowsize.0 }}, {{ icon.shadowsize.1 }});{% endif %}
+{% if icon.iconanchor %}{{ icon.varname }}.iconAnchor = new GPoint({{ icon.iconanchor.0 }}, {{ icon.iconanchor.1 }});{% endif %} {% if icon.iconsize %}{{ icon.varname }}.iconSize = new GSize({{ icon.iconsize.0 }}, {{ icon.iconsize.1 }});{% endif %}
+{% if icon.infowindowanchor %}{{ icon.varname }}.infoWindowAnchor = new GPoint({{ icon.infowindowanchor.0 }}, {{ icon.infowindowanchor.1 }});{% endif %}{% endfor %}
+{% endblock vars %}{% block functions %}
{% block load %}{{ js_module }}.{{ dom_id }}_load = function(){
if (GBrowserIsCompatible()) {
{{ js_module }}.{{ dom_id }} = new GMap2(document.getElementById("{{ dom_id }}"));
{{ js_module }}.{{ dom_id }}.setCenter(new GLatLng({{ center.1 }}, {{ center.0 }}), {{ zoom }});
- {% block controls %}{{ js_module }}.{{ dom_id }}.addControl(new GSmallMapControl());
- {{ js_module }}.{{ dom_id }}.addControl(new GMapTypeControl());{% endblock %}
+ {% block controls %}{{ js_module }}.{{ dom_id }}.setUIToDefault();{% endblock %}
{% if calc_zoom %}var bounds = new GLatLngBounds(); var tmp_bounds = new GLatLngBounds();{% endif %}
{% for kml_url in kml_urls %}{{ js_module }}.{{ dom_id }}_kml{{ forloop.counter }} = new GGeoXml("{{ kml_url }}");
{{ js_module }}.{{ dom_id }}.addOverlay({{ js_module }}.{{ dom_id }}_kml{{ forloop.counter }});{% endfor %}
@@ -26,4 +32,4 @@
alert("Sorry, the Google Maps API is not compatible with this browser.");
}
}
-{% endblock %}{% endautoescape %}
+{% endblock load %}{% endblock functions %}{% endautoescape %}
diff --git a/django/contrib/gis/templates/gis/google/google-multi.js b/django/contrib/gis/templates/gis/google/google-multi.js
index 49ce584e36..e3c7e8f02b 100644
--- a/django/contrib/gis/templates/gis/google/google-multi.js
+++ b/django/contrib/gis/templates/gis/google/google-multi.js
@@ -1,4 +1,4 @@
-{% extends "gis/google/google-base.js" %}
+{% extends "gis/google/google-map.js" %}
{% block functions %}
{{ load_map_js }}
{{ js_module }}.load = function(){
diff --git a/django/contrib/gis/templates/gis/google/google-single.js b/django/contrib/gis/templates/gis/google/google-single.js
index ab7901e42e..b930e4594f 100644
--- a/django/contrib/gis/templates/gis/google/google-single.js
+++ b/django/contrib/gis/templates/gis/google/google-single.js
@@ -1,2 +1,2 @@
-{% extends "gis/google/google-base.js" %}
-{% block functions %}{% include "gis/google/google-map.js" %}{% endblock %} \ No newline at end of file
+{% extends "gis/google/google-map.js" %}
+{% block vars %}{# No vars here because used within GoogleMapSet #}{% endblock %} \ No newline at end of file
diff --git a/django/contrib/gis/tests/relatedapp/models.py b/django/contrib/gis/tests/relatedapp/models.py
index 1125d7fb85..726f9826c0 100644
--- a/django/contrib/gis/tests/relatedapp/models.py
+++ b/django/contrib/gis/tests/relatedapp/models.py
@@ -40,5 +40,5 @@ class Author(models.Model):
class Book(models.Model):
title = models.CharField(max_length=100)
- author = models.ForeignKey(Author, related_name='books')
+ author = models.ForeignKey(Author, related_name='books', null=True)
objects = models.GeoManager()
diff --git a/django/contrib/gis/tests/relatedapp/tests.py b/django/contrib/gis/tests/relatedapp/tests.py
index 8c4f83b15a..502a3c0be9 100644
--- a/django/contrib/gis/tests/relatedapp/tests.py
+++ b/django/contrib/gis/tests/relatedapp/tests.py
@@ -257,6 +257,13 @@ class RelatedGeoModelTest(unittest.TestCase):
self.assertEqual(1, len(qs))
self.assertEqual(3, qs[0].num_books)
+ def test13_select_related_null_fk(self):
+ "Testing `select_related` on a nullable ForeignKey via `GeoManager`. See #11381."
+ no_author = Book.objects.create(title='Without Author')
+ b = Book.objects.select_related('author').get(title='Without Author')
+ # Should be `None`, and not a 'dummy' model.
+ self.assertEqual(None, b.author)
+
# TODO: Related tests for KML, GML, and distance lookups.
def suite():
diff --git a/django/contrib/gis/tests/test_geoip.py b/django/contrib/gis/tests/test_geoip.py
index 44b080223c..430d61b6d5 100644
--- a/django/contrib/gis/tests/test_geoip.py
+++ b/django/contrib/gis/tests/test_geoip.py
@@ -84,16 +84,15 @@ class GeoIPTest(unittest.TestCase):
self.assertEqual('USA', d['country_code3'])
self.assertEqual('Houston', d['city'])
self.assertEqual('TX', d['region'])
- self.assertEqual('77002', d['postal_code'])
self.assertEqual(713, d['area_code'])
geom = g.geos(query)
self.failIf(not isinstance(geom, GEOSGeometry))
- lon, lat = (-95.366996765, 29.752300262)
+ lon, lat = (-95.4152, 29.7755)
lat_lon = g.lat_lon(query)
lat_lon = (lat_lon[1], lat_lon[0])
for tup in (geom.tuple, g.coords(query), g.lon_lat(query), lat_lon):
- self.assertAlmostEqual(lon, tup[0], 9)
- self.assertAlmostEqual(lat, tup[1], 9)
+ self.assertAlmostEqual(lon, tup[0], 4)
+ self.assertAlmostEqual(lat, tup[1], 4)
def suite():
s = unittest.TestSuite()
diff --git a/django/contrib/gis/utils/geoip.py b/django/contrib/gis/utils/geoip.py
index 8c21ab290a..eedaef95dd 100644
--- a/django/contrib/gis/utils/geoip.py
+++ b/django/contrib/gis/utils/geoip.py
@@ -6,7 +6,7 @@
GeoIP(R) is a registered trademark of MaxMind, LLC of Boston, Massachusetts.
For IP-based geolocation, this module requires the GeoLite Country and City
- datasets, in binary format (CSV will not work!). The datasets may be
+ datasets, in binary format (CSV will not work!). The datasets may be
downloaded from MaxMind at http://www.maxmind.com/download/geoip/database/.
Grab GeoIP.dat.gz and GeoLiteCity.dat.gz, and unzip them in the directory
corresponding to settings.GEOIP_PATH. See the GeoIP docstring and examples
@@ -34,7 +34,7 @@
>>> g.lat_lon('salon.com')
(37.789798736572266, -122.39420318603516)
>>> g.lon_lat('uh.edu')
- (-95.415199279785156, 29.77549934387207)
+ (-95.415199279785156, 29.77549934387207)
>>> g.geos('24.124.1.80').wkt
'POINT (-95.2087020874023438 39.0392990112304688)'
"""
@@ -45,7 +45,7 @@ from django.conf import settings
if not settings.configured: settings.configure()
# Creating the settings dictionary with any settings, if needed.
-GEOIP_SETTINGS = dict((key, getattr(settings, key))
+GEOIP_SETTINGS = dict((key, getattr(settings, key))
for key in ('GEOIP_PATH', 'GEOIP_LIBRARY_PATH', 'GEOIP_COUNTRY', 'GEOIP_CITY')
if hasattr(settings, key))
lib_path = GEOIP_SETTINGS.get('GEOIP_LIBRARY_PATH', None)
@@ -83,8 +83,17 @@ class GeoIPRecord(Structure):
('postal_code', c_char_p),
('latitude', c_float),
('longitude', c_float),
+ # TODO: In 1.4.6 this changed from `int dma_code;` to
+ # `union {int metro_code; int dma_code;};`. Change
+ # to a `ctypes.Union` in to accomodate in future when
+ # pre-1.4.6 versions are no longer distributed.
('dma_code', c_int),
('area_code', c_int),
+ # TODO: The following structure fields were added in 1.4.3 --
+ # uncomment these fields when sure previous versions are no
+ # longer distributed by package maintainers.
+ #('charset', c_int),
+ #('continent_code', c_char_p),
]
class GeoIPTag(Structure): pass
@@ -99,9 +108,12 @@ def record_output(func):
rec_by_addr = record_output(lgeoip.GeoIP_record_by_addr)
rec_by_name = record_output(lgeoip.GeoIP_record_by_name)
-# For opening up GeoIP databases.
+# For opening & closing GeoIP database files.
geoip_open = lgeoip.GeoIP_open
geoip_open.restype = DBTYPE
+geoip_close = lgeoip.GeoIP_delete
+geoip_close.argtypes = [DBTYPE]
+geoip_close.restype = None
# String output routines.
def string_output(func):
@@ -136,6 +148,12 @@ class GeoIP(object):
GEOIP_CHECK_CACHE = 2
GEOIP_INDEX_CACHE = 4
cache_options = dict((opt, None) for opt in (0, 1, 2, 4))
+ _city_file = ''
+ _country_file = ''
+
+ # Initially, pointers to GeoIP file references are NULL.
+ _city = None
+ _country = None
def __init__(self, path=None, cache=0, country=None, city=None):
"""
@@ -174,13 +192,19 @@ class GeoIP(object):
if not isinstance(path, basestring):
raise TypeError('Invalid path type: %s' % type(path).__name__)
- cntry_ptr, city_ptr = (None, None)
if os.path.isdir(path):
- # Getting the country and city files using the settings
- # dictionary. If no settings are provided, default names
- # are assigned.
- country = os.path.join(path, country or GEOIP_SETTINGS.get('GEOIP_COUNTRY', 'GeoIP.dat'))
- city = os.path.join(path, city or GEOIP_SETTINGS.get('GEOIP_CITY', 'GeoLiteCity.dat'))
+ # Constructing the GeoIP database filenames using the settings
+ # dictionary. If the database files for the GeoLite country
+ # and/or city datasets exist, then try and open them.
+ country_db = os.path.join(path, country or GEOIP_SETTINGS.get('GEOIP_COUNTRY', 'GeoIP.dat'))
+ if os.path.isfile(country_db):
+ self._country = geoip_open(country_db, cache)
+ self._country_file = country_db
+
+ city_db = os.path.join(path, city or GEOIP_SETTINGS.get('GEOIP_CITY', 'GeoLiteCity.dat'))
+ if os.path.isfile(city_db):
+ self._city = geoip_open(city_db, cache)
+ self._city_file = city_db
elif os.path.isfile(path):
# Otherwise, some detective work will be needed to figure
# out whether the given database path is for the GeoIP country
@@ -188,29 +212,22 @@ class GeoIP(object):
ptr = geoip_open(path, cache)
info = geoip_dbinfo(ptr)
if lite_regex.match(info):
- # GeoLite City database.
- city, city_ptr = path, ptr
+ # GeoLite City database detected.
+ self._city = ptr
+ self._city_file = path
elif free_regex.match(info):
- # GeoIP Country database.
- country, cntry_ptr = path, ptr
+ # GeoIP Country database detected.
+ self._country = ptr
+ self._country_file = path
else:
raise GeoIPException('Unable to recognize database edition: %s' % info)
else:
raise GeoIPException('GeoIP path must be a valid file or directory.')
-
- # `_init_db` does the dirty work.
- self._init_db(country, cache, '_country', cntry_ptr)
- self._init_db(city, cache, '_city', city_ptr)
- def _init_db(self, db_file, cache, attname, ptr=None):
- "Helper routine for setting GeoIP ctypes database properties."
- if ptr:
- # Pointer already retrieved.
- pass
- elif os.path.isfile(db_file or ''):
- ptr = geoip_open(db_file, cache)
- setattr(self, attname, ptr)
- setattr(self, '%s_file' % attname, db_file)
+ def __del__(self):
+ # Cleaning any GeoIP file handles lying around.
+ if self._country: geoip_close(self._country)
+ if self._city: geoip_close(self._city)
def _check_query(self, query, country=False, city=False, city_or_country=False):
"Helper routine for checking the query and database availability."
@@ -219,11 +236,11 @@ class GeoIP(object):
raise TypeError('GeoIP query must be a string, not type %s' % type(query).__name__)
# Extra checks for the existence of country and city databases.
- if city_or_country and self._country is None and self._city is None:
+ if city_or_country and not (self._country or self._city):
raise GeoIPException('Invalid GeoIP country and city data files.')
- elif country and self._country is None:
+ elif country and not self._country:
raise GeoIPException('Invalid GeoIP country data file: %s' % self._country_file)
- elif city and self._city is None:
+ elif city and not self._city:
raise GeoIPException('Invalid GeoIP city data file: %s' % self._city_file)
def city(self, query):
@@ -247,7 +264,7 @@ class GeoIP(object):
return dict((tup[0], getattr(record, tup[0])) for tup in record._fields_)
else:
return None
-
+
def country_code(self, query):
"Returns the country code for the given IP Address or FQDN."
self._check_query(query, city_or_country=True)
@@ -268,12 +285,12 @@ class GeoIP(object):
def country(self, query):
"""
- Returns a dictonary with with the country code and name when given an
+ Returns a dictonary with with the country code and name when given an
IP address or a Fully Qualified Domain Name (FQDN). For example, both
'24.124.1.80' and 'djangoproject.com' are valid parameters.
"""
# Returning the country code and name
- return {'country_code' : self.country_code(query),
+ return {'country_code' : self.country_code(query),
'country_name' : self.country_name(query),
}
@@ -318,7 +335,7 @@ class GeoIP(object):
ci = geoip_dbinfo(self._city)
return ci
city_info = property(city_info)
-
+
def info(self):
"Returns information about all GeoIP databases in use."
return 'Country:\n\t%s\nCity:\n\t%s' % (self.country_info, self.city_info)
diff --git a/django/core/mail.py b/django/core/mail.py
index 6a45b46587..c305699158 100644
--- a/django/core/mail.py
+++ b/django/core/mail.py
@@ -195,7 +195,7 @@ class EmailMessage(object):
A container for email information.
"""
content_subtype = 'plain'
- multipart_subtype = 'mixed'
+ mixed_subtype = 'mixed'
encoding = None # None => use settings default
def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
@@ -234,16 +234,7 @@ class EmailMessage(object):
encoding = self.encoding or settings.DEFAULT_CHARSET
msg = SafeMIMEText(smart_str(self.body, settings.DEFAULT_CHARSET),
self.content_subtype, encoding)
- if self.attachments:
- body_msg = msg
- msg = SafeMIMEMultipart(_subtype=self.multipart_subtype)
- if self.body:
- msg.attach(body_msg)
- for attachment in self.attachments:
- if isinstance(attachment, MIMEBase):
- msg.attach(attachment)
- else:
- msg.attach(self._create_attachment(*attachment))
+ msg = self._create_message(msg)
msg['Subject'] = self.subject
msg['From'] = self.extra_headers.pop('From', self.from_email)
msg['To'] = ', '.join(self.to)
@@ -277,8 +268,7 @@ class EmailMessage(object):
def attach(self, filename=None, content=None, mimetype=None):
"""
Attaches a file with the given filename and content. The filename can
- be omitted (useful for multipart/alternative messages) and the mimetype
- is guessed, if not provided.
+ be omitted and the mimetype is guessed, if not provided.
If the first parameter is a MIMEBase subclass it is inserted directly
into the resulting message attachments.
@@ -296,15 +286,26 @@ class EmailMessage(object):
content = open(path, 'rb').read()
self.attach(filename, content, mimetype)
- def _create_attachment(self, filename, content, mimetype=None):
+ def _create_message(self, msg):
+ return self._create_attachments(msg)
+
+ def _create_attachments(self, msg):
+ if self.attachments:
+ body_msg = msg
+ msg = SafeMIMEMultipart(_subtype=self.mixed_subtype)
+ if self.body:
+ msg.attach(body_msg)
+ for attachment in self.attachments:
+ if isinstance(attachment, MIMEBase):
+ msg.attach(attachment)
+ else:
+ msg.attach(self._create_attachment(*attachment))
+ return msg
+
+ def _create_mime_attachment(self, content, mimetype):
"""
- Converts the filename, content, mimetype triple into a MIME attachment
- object.
+ Converts the content, mimetype pair into a MIME attachment object.
"""
- if mimetype is None:
- mimetype, _ = mimetypes.guess_type(filename)
- if mimetype is None:
- mimetype = DEFAULT_ATTACHMENT_MIME_TYPE
basetype, subtype = mimetype.split('/', 1)
if basetype == 'text':
attachment = SafeMIMEText(smart_str(content,
@@ -314,6 +315,18 @@ class EmailMessage(object):
attachment = MIMEBase(basetype, subtype)
attachment.set_payload(content)
Encoders.encode_base64(attachment)
+ return attachment
+
+ def _create_attachment(self, filename, content, mimetype=None):
+ """
+ Converts the filename, content, mimetype triple into a MIME attachment
+ object.
+ """
+ if mimetype is None:
+ mimetype, _ = mimetypes.guess_type(filename)
+ if mimetype is None:
+ mimetype = DEFAULT_ATTACHMENT_MIME_TYPE
+ attachment = self._create_mime_attachment(content, mimetype)
if filename:
attachment.add_header('Content-Disposition', 'attachment',
filename=filename)
@@ -325,11 +338,39 @@ class EmailMultiAlternatives(EmailMessage):
messages. For example, including text and HTML versions of the text is
made easier.
"""
- multipart_subtype = 'alternative'
+ alternative_subtype = 'alternative'
- def attach_alternative(self, content, mimetype=None):
+ def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
+ connection=None, attachments=None, headers=None, alternatives=None):
+ """
+ Initialize a single email message (which can be sent to multiple
+ recipients).
+
+ All strings used to create the message can be unicode strings (or UTF-8
+ bytestrings). The SafeMIMEText class will handle any necessary encoding
+ conversions.
+ """
+ super(EmailMultiAlternatives, self).__init__(subject, body, from_email, to, bcc, connection, attachments, headers)
+ self.alternatives=alternatives or []
+
+ def attach_alternative(self, content, mimetype):
"""Attach an alternative content representation."""
- self.attach(content=content, mimetype=mimetype)
+ assert content is not None
+ assert mimetype is not None
+ self.alternatives.append((content, mimetype))
+
+ def _create_message(self, msg):
+ return self._create_attachments(self._create_alternatives(msg))
+
+ def _create_alternatives(self, msg):
+ if self.alternatives:
+ body_msg = msg
+ msg = SafeMIMEMultipart(_subtype=self.alternative_subtype)
+ if self.body:
+ msg.attach(body_msg)
+ for alternative in self.alternatives:
+ msg.attach(self._create_mime_attachment(*alternative))
+ return msg
def send_mail(subject, message, from_email, recipient_list,
fail_silently=False, auth_user=None, auth_password=None):
diff --git a/django/core/management/commands/dumpdata.py b/django/core/management/commands/dumpdata.py
index 1f2a2db981..9172d938d2 100644
--- a/django/core/management/commands/dumpdata.py
+++ b/django/core/management/commands/dumpdata.py
@@ -73,7 +73,7 @@ class Command(BaseCommand):
model_list = get_models(app)
for model in model_list:
- objects.extend(model.objects.all())
+ objects.extend(model._default_manager.all())
try:
return serializers.serialize(format, objects, indent=indent)
diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py
index ac83756f31..b3dcfdb8c1 100644
--- a/django/core/urlresolvers.py
+++ b/django/core/urlresolvers.py
@@ -195,6 +195,7 @@ class RegexURLResolver(object):
return sub_match[0], sub_match[1], sub_match_dict
tried.append(pattern.regex.pattern)
raise Resolver404, {'tried': tried, 'path': new_path}
+ raise Resolver404, {'tried': [], 'path' : path}
def _get_urlconf_module(self):
try:
diff --git a/django/db/backends/creation.py b/django/db/backends/creation.py
index f6041c73f3..1ac75426c1 100644
--- a/django/db/backends/creation.py
+++ b/django/db/backends/creation.py
@@ -25,6 +25,13 @@ class BaseDatabaseCreation(object):
def __init__(self, connection):
self.connection = connection
+ def _digest(self, *args):
+ """
+ Generates a 32-bit digest of a set of arguments that can be used to
+ shorten identifying names.
+ """
+ return '%x' % (abs(hash(args)) % 4294967296L) # 2**32
+
def sql_create_model(self, model, style, known_models=set()):
"""
Returns the SQL required to create a single model, as a tuple of:
@@ -128,7 +135,7 @@ class BaseDatabaseCreation(object):
col = opts.get_field(f.rel.field_name).column
# For MySQL, r_name must be unique in the first 64 characters.
# So we are careful with character usage here.
- r_name = '%s_refs_%s_%x' % (r_col, col, abs(hash((r_table, table))))
+ r_name = '%s_refs_%s_%s' % (r_col, col, self._digest(r_table, table))
final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % \
(qn(r_table), qn(truncate_name(r_name, self.connection.ops.max_name_length())),
qn(r_col), qn(table), qn(col),
@@ -187,8 +194,7 @@ class BaseDatabaseCreation(object):
output.append('\n'.join(table_output))
for r_table, r_col, table, col in deferred:
- r_name = '%s_refs_%s_%x' % (r_col, col,
- abs(hash((r_table, table))))
+ r_name = '%s_refs_%s_%s' % (r_col, col, self._digest(r_table, table))
output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' %
(qn(r_table),
qn(truncate_name(r_name, self.connection.ops.max_name_length())),
@@ -289,7 +295,7 @@ class BaseDatabaseCreation(object):
col = f.column
r_table = model._meta.db_table
r_col = model._meta.get_field(f.rel.field_name).column
- r_name = '%s_refs_%s_%x' % (col, r_col, abs(hash((table, r_table))))
+ r_name = '%s_refs_%s_%s' % (col, r_col, self._digest(table, r_table))
output.append('%s %s %s %s;' % \
(style.SQL_KEYWORD('ALTER TABLE'),
style.SQL_TABLE(qn(table)),
diff --git a/django/db/models/base.py b/django/db/models/base.py
index 13ff7e8f35..a5c99865a6 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -411,29 +411,37 @@ class Model(object):
save.alters_data = True
- def save_base(self, raw=False, cls=None, force_insert=False,
- force_update=False):
+ def save_base(self, raw=False, cls=None, origin=None,
+ force_insert=False, force_update=False):
"""
Does the heavy-lifting involved in saving. Subclasses shouldn't need to
override this method. It's separate from save() in order to hide the
need for overrides of save() to pass around internal-only parameters
- ('raw' and 'cls').
+ ('raw', 'cls', and 'origin').
"""
assert not (force_insert and force_update)
- if not cls:
+ if cls is None:
cls = self.__class__
- meta = self._meta
- signal = True
- signals.pre_save.send(sender=self.__class__, instance=self, raw=raw)
+ meta = cls._meta
+ if not meta.proxy:
+ origin = cls
else:
meta = cls._meta
- signal = False
+
+ if origin:
+ signals.pre_save.send(sender=origin, instance=self, raw=raw)
# If we are in a raw save, save the object exactly as presented.
# That means that we don't try to be smart about saving attributes
# that might have come from the parent class - we just save the
# attributes we have been given to the class we have been given.
- if not raw:
+ # We also go through this process to defer the save of proxy objects
+ # to their actual underlying model.
+ if not raw or meta.proxy:
+ if meta.proxy:
+ org = cls
+ else:
+ org = None
for parent, field in meta.parents.items():
# At this point, parent's primary key field may be unknown
# (for example, from administration form which doesn't fill
@@ -441,7 +449,8 @@ class Model(object):
if field and getattr(self, parent._meta.pk.attname) is None and getattr(self, field.attname) is not None:
setattr(self, parent._meta.pk.attname, getattr(self, field.attname))
- self.save_base(cls=parent)
+ self.save_base(cls=parent, origin=org)
+
if field:
setattr(self, field.attname, self._get_pk_val(parent._meta))
if meta.proxy:
@@ -492,8 +501,8 @@ class Model(object):
setattr(self, meta.pk.attname, result)
transaction.commit_unless_managed()
- if signal:
- signals.post_save.send(sender=self.__class__, instance=self,
+ if origin:
+ signals.post_save.send(sender=origin, instance=self,
created=(not record_exists), raw=raw)
save_base.alters_data = True
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index 419695b74b..529898ea27 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -112,9 +112,9 @@ class RelatedField(object):
def do_related_class(self, other, cls):
self.set_attributes_from_rel()
- related = RelatedObject(other, cls, self)
+ self.related = RelatedObject(other, cls, self)
if not cls._meta.abstract:
- self.contribute_to_related_class(other, related)
+ self.contribute_to_related_class(other, self.related)
def get_db_prep_lookup(self, lookup_type, value):
# If we are doing a lookup on a Related Field, we must be
@@ -132,6 +132,7 @@ class RelatedField(object):
v, field = getattr(v, v._meta.pk.name), v._meta.pk
except AttributeError:
pass
+
if field:
if lookup_type in ('range', 'in'):
v = [v]
@@ -183,7 +184,6 @@ class SingleRelatedObjectDescriptor(object):
def __get__(self, instance, instance_type=None):
if instance is None:
return self
-
try:
return getattr(instance, self.cache_name)
except AttributeError:
@@ -231,6 +231,7 @@ class ReverseSingleRelatedObjectDescriptor(object):
def __get__(self, instance, instance_type=None):
if instance is None:
return self
+
cache_name = self.field.get_cache_name()
try:
return getattr(instance, cache_name)
@@ -271,6 +272,29 @@ class ReverseSingleRelatedObjectDescriptor(object):
(value, instance._meta.object_name,
self.field.name, self.field.rel.to._meta.object_name))
+ # If we're setting the value of a OneToOneField to None, we need to clear
+ # out the cache on any old related object. Otherwise, deleting the
+ # previously-related object will also cause this object to be deleted,
+ # which is wrong.
+ if value is None:
+ # Look up the previously-related object, which may still be available
+ # since we've not yet cleared out the related field.
+ # Use the cache directly, instead of the accessor; if we haven't
+ # populated the cache, then we don't care - we're only accessing
+ # the object to invalidate the accessor cache, so there's no
+ # need to populate the cache just to expire it again.
+ related = getattr(instance, self.field.get_cache_name(), None)
+
+ # If we've got an old related object, we need to clear out its
+ # cache. This cache also might not exist if the related object
+ # hasn't been accessed yet.
+ if related:
+ cache_name = '_%s_cache' % self.field.related.get_accessor_name()
+ try:
+ delattr(related, cache_name)
+ except AttributeError:
+ pass
+
# Set the value of the related field
try:
val = getattr(value, self.field.rel.get_related_field().attname)
@@ -958,4 +982,3 @@ class ManyToManyField(RelatedField, Field):
# A ManyToManyField is not represented by a single column,
# so return None.
return None
-
diff --git a/django/templatetags/cache.py b/django/templatetags/cache.py
index 9c6ca76854..387dd8721c 100644
--- a/django/templatetags/cache.py
+++ b/django/templatetags/cache.py
@@ -3,6 +3,7 @@ from django.template import resolve_variable
from django.core.cache import cache
from django.utils.encoding import force_unicode
from django.utils.http import urlquote
+from django.utils.hashcompat import md5_constructor
register = Library()
@@ -23,7 +24,8 @@ class CacheNode(Node):
except (ValueError, TypeError):
raise TemplateSyntaxError('"cache" tag got a non-integer timeout value: %r' % expire_time)
# Build a unicode key for this fragment and all vary-on's.
- cache_key = u':'.join([self.fragment_name] + [urlquote(resolve_variable(var, context)) for var in self.vary_on])
+ args = md5_constructor(u':'.join([urlquote(resolve_variable(var, context)) for var in self.vary_on]))
+ cache_key = 'template.cache.%s.%s' % (self.fragment_name, args.hexdigest())
value = cache.get(cache_key)
if value is None:
value = self.nodelist.render(context)