From 18ffdb1772ba60e085cff8fd9a1d4a7b129b4032 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 16 Sep 2013 12:52:05 -0400 Subject: Fixed #17627 -- Renamed util.py files to utils.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thanks PaulM for the suggestion and Luke Granger-Brown and Wiktor Kołodziej for the initial patch. --- AUTHORS | 2 + django/contrib/admin/actions.py | 2 +- django/contrib/admin/filters.py | 2 +- django/contrib/admin/helpers.py | 6 +- django/contrib/admin/models.py | 2 +- django/contrib/admin/options.py | 2 +- django/contrib/admin/templatetags/admin_list.py | 2 +- django/contrib/admin/templatetags/admin_urls.py | 2 +- django/contrib/admin/util.py | 473 --------------------- django/contrib/admin/utils.py | 473 +++++++++++++++++++++ django/contrib/admin/validation.py | 2 +- django/contrib/admin/views/main.py | 2 +- django/contrib/admin/widgets.py | 2 +- django/contrib/auth/forms.py | 2 +- django/contrib/comments/forms.py | 2 +- django/contrib/gis/db/backends/oracle/creation.py | 2 +- .../contrib/gis/db/backends/oracle/operations.py | 2 +- .../contrib/gis/db/backends/postgis/operations.py | 2 +- .../gis/db/backends/spatialite/operations.py | 2 +- django/contrib/gis/db/backends/util.py | 44 -- django/contrib/gis/db/backends/utils.py | 44 ++ django/contrib/gis/db/models/sql/compiler.py | 2 +- django/db/backends/__init__.py | 8 +- django/db/backends/creation.py | 2 +- django/db/backends/mysql/base.py | 10 +- django/db/backends/mysql/creation.py | 2 +- django/db/backends/oracle/base.py | 11 +- django/db/backends/postgresql_psycopg2/creation.py | 2 +- django/db/backends/schema.py | 2 +- django/db/backends/sqlite3/base.py | 18 +- django/db/backends/util.py | 167 -------- django/db/backends/utils.py | 167 ++++++++ django/db/models/fields/__init__.py | 6 +- django/db/models/fields/related.py | 4 +- django/db/models/options.py | 2 +- django/db/models/query_utils.py | 4 +- django/db/models/sql/compiler.py | 6 +- django/forms/fields.py | 2 +- django/forms/forms.py | 2 +- django/forms/formsets.py | 2 +- django/forms/models.py | 2 +- django/forms/util.py | 117 ----- django/forms/utils.py | 117 +++++ django/forms/widgets.py | 2 +- docs/internals/deprecation.txt | 9 + docs/ref/forms/api.txt | 8 +- docs/ref/forms/validation.txt | 4 +- docs/releases/1.7.txt | 11 + docs/topics/forms/formsets.txt | 2 +- tests/admin_custom_urls/tests.py | 2 +- tests/admin_util/tests.py | 2 +- tests/admin_views/admin.py | 2 +- tests/admin_views/tests.py | 4 +- tests/backends/tests.py | 2 +- tests/db_typecasts/tests.py | 2 +- tests/forms_tests/tests/test_error_messages.py | 2 +- tests/forms_tests/tests/test_extra.py | 2 +- tests/forms_tests/tests/test_fields.py | 6 +- tests/forms_tests/tests/test_formsets.py | 2 +- tests/forms_tests/tests/test_util.py | 4 +- tests/model_formsets_regress/tests.py | 2 +- tests/proxy_models/tests.py | 2 +- 62 files changed, 912 insertions(+), 885 deletions(-) delete mode 100644 django/contrib/admin/util.py create mode 100644 django/contrib/admin/utils.py delete mode 100644 django/contrib/gis/db/backends/util.py create mode 100644 django/contrib/gis/db/backends/utils.py delete mode 100644 django/db/backends/util.py create mode 100644 django/db/backends/utils.py delete mode 100644 django/forms/util.py create mode 100644 django/forms/utils.py diff --git a/AUTHORS b/AUTHORS index d43715f119..a2a0eb979a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -264,6 +264,7 @@ answer newbie questions, and generally made Django that much better: David Gouldin pradeep.gowda@gmail.com Collin Grady + Luke Granger-Brown Gabriel Grant Martin Green Daniel Greenfeld @@ -348,6 +349,7 @@ answer newbie questions, and generally made Django that much better: Cameron Knight (ckknight) Nena Kojadin Igor Kolar + Wiktor Kołodziej Tomáš Kopeček Gasper Koren Mikhail Korobov diff --git a/django/contrib/admin/actions.py b/django/contrib/admin/actions.py index a4c5828f37..f67599a432 100644 --- a/django/contrib/admin/actions.py +++ b/django/contrib/admin/actions.py @@ -5,7 +5,7 @@ Built-in, globally-available admin actions. from django.core.exceptions import PermissionDenied from django.contrib import messages from django.contrib.admin import helpers -from django.contrib.admin.util import get_deleted_objects, model_ngettext +from django.contrib.admin.utils import get_deleted_objects, model_ngettext from django.db import router from django.template.response import TemplateResponse from django.utils.encoding import force_text diff --git a/django/contrib/admin/filters.py b/django/contrib/admin/filters.py index 7bba3a797f..4f04a474dd 100644 --- a/django/contrib/admin/filters.py +++ b/django/contrib/admin/filters.py @@ -12,7 +12,7 @@ from django.core.exceptions import ImproperlyConfigured, ValidationError from django.utils.encoding import smart_text, force_text from django.utils.translation import ugettext_lazy as _ from django.utils import timezone -from django.contrib.admin.util import (get_model_from_relation, +from django.contrib.admin.utils import (get_model_from_relation, reverse_field_path, get_limit_choices_to_from_path, prepare_lookup_value) from django.contrib.admin.options import IncorrectLookupParameters diff --git a/django/contrib/admin/helpers.py b/django/contrib/admin/helpers.py index a91988bc47..cd784f5cbd 100644 --- a/django/contrib/admin/helpers.py +++ b/django/contrib/admin/helpers.py @@ -1,13 +1,13 @@ from __future__ import unicode_literals from django import forms -from django.contrib.admin.util import (flatten_fieldsets, lookup_field, +from django.contrib.admin.utils import (flatten_fieldsets, lookup_field, display_for_field, label_for_field, help_text_for_field) from django.contrib.admin.templatetags.admin_static import static from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ObjectDoesNotExist from django.db.models.fields.related import ManyToManyRel -from django.forms.util import flatatt +from django.forms.utils import flatatt from django.template.defaultfilters import capfirst from django.utils.encoding import force_text, smart_text from django.utils.html import conditional_escape, format_html @@ -308,7 +308,7 @@ class InlineFieldset(Fieldset): yield Fieldline(self.form, field, self.readonly_fields, model_admin=self.model_admin) -class AdminErrorList(forms.util.ErrorList): +class AdminErrorList(forms.utils.ErrorList): """ Stores all errors for the form/formsets in an add/change stage view. """ diff --git a/django/contrib/admin/models.py b/django/contrib/admin/models.py index dc282b7e57..870c2b27a1 100644 --- a/django/contrib/admin/models.py +++ b/django/contrib/admin/models.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals from django.db import models from django.conf import settings from django.contrib.contenttypes.models import ContentType -from django.contrib.admin.util import quote +from django.contrib.admin.utils import quote from django.core.urlresolvers import reverse, NoReverseMatch from django.utils.translation import ugettext, ugettext_lazy as _ from django.utils.encoding import smart_text diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 1b215879d1..937bbd1ebb 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -8,7 +8,7 @@ from django import forms from django.conf import settings from django.contrib import messages from django.contrib.admin import widgets, helpers -from django.contrib.admin.util import (unquote, flatten_fieldsets, get_deleted_objects, +from django.contrib.admin.utils import (unquote, flatten_fieldsets, get_deleted_objects, model_format_dict, NestedObjects, lookup_needs_distinct) from django.contrib.admin import validation from django.contrib.admin.templatetags.admin_static import static diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py index ba109d1454..2b1fd8ad34 100644 --- a/django/contrib/admin/templatetags/admin_list.py +++ b/django/contrib/admin/templatetags/admin_list.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import datetime from django.contrib.admin.templatetags.admin_urls import add_preserved_filters -from django.contrib.admin.util import (lookup_field, display_for_field, +from django.contrib.admin.utils import (lookup_field, display_for_field, display_for_value, label_for_field) from django.contrib.admin.views.main import (ALL_VAR, EMPTY_CHANGELIST_VALUE, ORDER_VAR, PAGE_VAR, SEARCH_VAR) diff --git a/django/contrib/admin/templatetags/admin_urls.py b/django/contrib/admin/templatetags/admin_urls.py index fd45bf306a..68c7083ecf 100644 --- a/django/contrib/admin/templatetags/admin_urls.py +++ b/django/contrib/admin/templatetags/admin_urls.py @@ -4,7 +4,7 @@ except ImportError: from urlparse import parse_qsl, urlparse, urlunparse from django import template -from django.contrib.admin.util import quote +from django.contrib.admin.utils import quote from django.core.urlresolvers import resolve, Resolver404 from django.utils.http import urlencode diff --git a/django/contrib/admin/util.py b/django/contrib/admin/util.py deleted file mode 100644 index f9b17ad186..0000000000 --- a/django/contrib/admin/util.py +++ /dev/null @@ -1,473 +0,0 @@ -from __future__ import unicode_literals - -import datetime -import decimal - -from django.contrib.auth import get_permission_codename -from django.db import models -from django.db.models.constants import LOOKUP_SEP -from django.db.models.deletion import Collector -from django.db.models.related import RelatedObject -from django.forms.forms import pretty_name -from django.utils import formats -from django.utils.html import format_html -from django.utils.text import capfirst -from django.utils import timezone -from django.utils.encoding import force_str, force_text, smart_text -from django.utils import six -from django.utils.translation import ungettext -from django.core.urlresolvers import reverse, NoReverseMatch - -def lookup_needs_distinct(opts, lookup_path): - """ - Returns True if 'distinct()' should be used to query the given lookup path. - """ - field_name = lookup_path.split('__', 1)[0] - field = opts.get_field_by_name(field_name)[0] - if ((hasattr(field, 'rel') and - isinstance(field.rel, models.ManyToManyRel)) or - (isinstance(field, models.related.RelatedObject) and - not field.field.unique)): - return True - return False - -def prepare_lookup_value(key, value): - """ - Returns a lookup value prepared to be used in queryset filtering. - """ - # if key ends with __in, split parameter into separate values - if key.endswith('__in'): - value = value.split(',') - # if key ends with __isnull, special case '' and the string literals 'false' and '0' - if key.endswith('__isnull'): - if value.lower() in ('', 'false', '0'): - value = False - else: - value = True - return value - -def quote(s): - """ - Ensure that primary key values do not confuse the admin URLs by escaping - any '/', '_' and ':' and similarly problematic characters. - Similar to urllib.quote, except that the quoting is slightly different so - that it doesn't get automatically unquoted by the Web browser. - """ - if not isinstance(s, six.string_types): - return s - res = list(s) - for i in range(len(res)): - c = res[i] - if c in """:/_#?;@&=+$,"<>%\\""": - res[i] = '_%02X' % ord(c) - return ''.join(res) - - -def unquote(s): - """ - Undo the effects of quote(). Based heavily on urllib.unquote(). - """ - mychr = chr - myatoi = int - list = s.split('_') - res = [list[0]] - myappend = res.append - del list[0] - for item in list: - if item[1:2]: - try: - myappend(mychr(myatoi(item[:2], 16)) + item[2:]) - except ValueError: - myappend('_' + item) - else: - myappend('_' + item) - return "".join(res) - - -def flatten_fieldsets(fieldsets): - """Returns a list of field names from an admin fieldsets structure.""" - field_names = [] - for name, opts in fieldsets: - for field in opts['fields']: - if isinstance(field, (list, tuple)): - field_names.extend(field) - else: - field_names.append(field) - return field_names - - -def get_deleted_objects(objs, opts, user, admin_site, using): - """ - Find all objects related to ``objs`` that should also be deleted. ``objs`` - must be a homogenous iterable of objects (e.g. a QuerySet). - - Returns a nested list of strings suitable for display in the - template with the ``unordered_list`` filter. - - """ - collector = NestedObjects(using=using) - collector.collect(objs) - perms_needed = set() - - def format_callback(obj): - has_admin = obj.__class__ in admin_site._registry - opts = obj._meta - - no_edit_link = '%s: %s' % (capfirst(opts.verbose_name), - force_text(obj)) - - if has_admin: - try: - admin_url = reverse('%s:%s_%s_change' - % (admin_site.name, - opts.app_label, - opts.model_name), - None, (quote(obj._get_pk_val()),)) - except NoReverseMatch: - # Change url doesn't exist -- don't display link to edit - return no_edit_link - - p = '%s.%s' % (opts.app_label, - get_permission_codename('delete', opts)) - if not user.has_perm(p): - perms_needed.add(opts.verbose_name) - # Display a link to the admin page. - return format_html('{0}: {2}', - capfirst(opts.verbose_name), - admin_url, - obj) - else: - # Don't display link to edit, because it either has no - # admin or is edited inline. - return no_edit_link - - to_delete = collector.nested(format_callback) - - protected = [format_callback(obj) for obj in collector.protected] - - return to_delete, perms_needed, protected - - -class NestedObjects(Collector): - def __init__(self, *args, **kwargs): - super(NestedObjects, self).__init__(*args, **kwargs) - self.edges = {} # {from_instance: [to_instances]} - self.protected = set() - - def add_edge(self, source, target): - self.edges.setdefault(source, []).append(target) - - def collect(self, objs, source_attr=None, **kwargs): - for obj in objs: - if source_attr: - self.add_edge(getattr(obj, source_attr), obj) - else: - self.add_edge(None, obj) - try: - return super(NestedObjects, self).collect(objs, source_attr=source_attr, **kwargs) - except models.ProtectedError as e: - self.protected.update(e.protected_objects) - - def related_objects(self, related, objs): - qs = super(NestedObjects, self).related_objects(related, objs) - return qs.select_related(related.field.name) - - def _nested(self, obj, seen, format_callback): - if obj in seen: - return [] - seen.add(obj) - children = [] - for child in self.edges.get(obj, ()): - children.extend(self._nested(child, seen, format_callback)) - if format_callback: - ret = [format_callback(obj)] - else: - ret = [obj] - if children: - ret.append(children) - return ret - - def nested(self, format_callback=None): - """ - Return the graph as a nested list. - - """ - seen = set() - roots = [] - for root in self.edges.get(None, ()): - roots.extend(self._nested(root, seen, format_callback)) - return roots - - def can_fast_delete(self, *args, **kwargs): - """ - We always want to load the objects into memory so that we can display - them to the user in confirm page. - """ - return False - - -def model_format_dict(obj): - """ - Return a `dict` with keys 'verbose_name' and 'verbose_name_plural', - typically for use with string formatting. - - `obj` may be a `Model` instance, `Model` subclass, or `QuerySet` instance. - - """ - if isinstance(obj, (models.Model, models.base.ModelBase)): - opts = obj._meta - elif isinstance(obj, models.query.QuerySet): - opts = obj.model._meta - else: - opts = obj - return { - 'verbose_name': force_text(opts.verbose_name), - 'verbose_name_plural': force_text(opts.verbose_name_plural) - } - - -def model_ngettext(obj, n=None): - """ - Return the appropriate `verbose_name` or `verbose_name_plural` value for - `obj` depending on the count `n`. - - `obj` may be a `Model` instance, `Model` subclass, or `QuerySet` instance. - If `obj` is a `QuerySet` instance, `n` is optional and the length of the - `QuerySet` is used. - - """ - if isinstance(obj, models.query.QuerySet): - if n is None: - n = obj.count() - obj = obj.model - d = model_format_dict(obj) - singular, plural = d["verbose_name"], d["verbose_name_plural"] - return ungettext(singular, plural, n or 0) - - -def lookup_field(name, obj, model_admin=None): - opts = obj._meta - try: - f = opts.get_field(name) - except models.FieldDoesNotExist: - # For non-field values, the value is either a method, property or - # returned via a callable. - if callable(name): - attr = name - value = attr(obj) - elif (model_admin is not None and hasattr(model_admin, name) and - not name == '__str__' and not name == '__unicode__'): - attr = getattr(model_admin, name) - value = attr(obj) - else: - attr = getattr(obj, name) - if callable(attr): - value = attr() - else: - value = attr - f = None - else: - attr = None - value = getattr(obj, name) - return f, attr, value - - -def label_for_field(name, model, model_admin=None, return_attr=False): - """ - Returns a sensible label for a field name. The name can be a callable, - property (but not created with @property decorator) or the name of an - object's attribute, as well as a genuine fields. If return_attr is - True, the resolved attribute (which could be a callable) is also returned. - This will be None if (and only if) the name refers to a field. - """ - attr = None - try: - field = model._meta.get_field_by_name(name)[0] - if isinstance(field, RelatedObject): - label = field.opts.verbose_name - else: - label = field.verbose_name - except models.FieldDoesNotExist: - if name == "__unicode__": - label = force_text(model._meta.verbose_name) - attr = six.text_type - elif name == "__str__": - label = force_str(model._meta.verbose_name) - attr = bytes - else: - if callable(name): - attr = name - elif model_admin is not None and hasattr(model_admin, name): - attr = getattr(model_admin, name) - elif hasattr(model, name): - attr = getattr(model, name) - else: - message = "Unable to lookup '%s' on %s" % (name, model._meta.object_name) - if model_admin: - message += " or %s" % (model_admin.__class__.__name__,) - raise AttributeError(message) - - if hasattr(attr, "short_description"): - label = attr.short_description - elif (isinstance(attr, property) and - hasattr(attr, "fget") and - hasattr(attr.fget, "short_description")): - label = attr.fget.short_description - elif callable(attr): - if attr.__name__ == "": - label = "--" - else: - label = pretty_name(attr.__name__) - else: - label = pretty_name(name) - if return_attr: - return (label, attr) - else: - return label - - -def help_text_for_field(name, model): - help_text = "" - try: - field_data = model._meta.get_field_by_name(name) - except models.FieldDoesNotExist: - pass - else: - field = field_data[0] - if not isinstance(field, RelatedObject): - help_text = field.help_text - return smart_text(help_text) - - -def display_for_field(value, field): - from django.contrib.admin.templatetags.admin_list import _boolean_icon - from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE - - if field.flatchoices: - return dict(field.flatchoices).get(value, EMPTY_CHANGELIST_VALUE) - # NullBooleanField needs special-case null-handling, so it comes - # before the general null test. - elif isinstance(field, models.BooleanField) or isinstance(field, models.NullBooleanField): - return _boolean_icon(value) - elif value is None: - return EMPTY_CHANGELIST_VALUE - elif isinstance(field, models.DateTimeField): - return formats.localize(timezone.template_localtime(value)) - elif isinstance(field, (models.DateField, models.TimeField)): - return formats.localize(value) - elif isinstance(field, models.DecimalField): - return formats.number_format(value, field.decimal_places) - elif isinstance(field, models.FloatField): - return formats.number_format(value) - else: - return smart_text(value) - - -def display_for_value(value, boolean=False): - from django.contrib.admin.templatetags.admin_list import _boolean_icon - from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE - - if boolean: - return _boolean_icon(value) - elif value is None: - return EMPTY_CHANGELIST_VALUE - elif isinstance(value, datetime.datetime): - return formats.localize(timezone.template_localtime(value)) - elif isinstance(value, (datetime.date, datetime.time)): - return formats.localize(value) - elif isinstance(value, six.integer_types + (decimal.Decimal, float)): - return formats.number_format(value) - else: - return smart_text(value) - - -class NotRelationField(Exception): - pass - - -def get_model_from_relation(field): - if isinstance(field, models.related.RelatedObject): - return field.model - elif getattr(field, 'rel'): # or isinstance? - return field.rel.to - else: - raise NotRelationField - - -def reverse_field_path(model, path): - """ Create a reversed field path. - - E.g. Given (Order, "user__groups"), - return (Group, "user__order"). - - Final field must be a related model, not a data field. - - """ - reversed_path = [] - parent = model - pieces = path.split(LOOKUP_SEP) - for piece in pieces: - field, model, direct, m2m = parent._meta.get_field_by_name(piece) - # skip trailing data field if extant: - if len(reversed_path) == len(pieces)-1: # final iteration - try: - get_model_from_relation(field) - except NotRelationField: - break - if direct: - related_name = field.related_query_name() - parent = field.rel.to - else: - related_name = field.field.name - parent = field.model - reversed_path.insert(0, related_name) - return (parent, LOOKUP_SEP.join(reversed_path)) - - -def get_fields_from_path(model, path): - """ Return list of Fields given path relative to model. - - e.g. (ModelX, "user__groups__name") -> [ - , - , - , - ] - """ - pieces = path.split(LOOKUP_SEP) - fields = [] - for piece in pieces: - if fields: - parent = get_model_from_relation(fields[-1]) - else: - parent = model - fields.append(parent._meta.get_field_by_name(piece)[0]) - return fields - - -def remove_trailing_data_field(fields): - """ Discard trailing non-relation field if extant. """ - try: - get_model_from_relation(fields[-1]) - except NotRelationField: - fields = fields[:-1] - return fields - - -def get_limit_choices_to_from_path(model, path): - """ Return Q object for limiting choices if applicable. - - If final model in path is linked via a ForeignKey or ManyToManyField which - has a `limit_choices_to` attribute, return it as a Q object. - """ - - fields = get_fields_from_path(model, path) - fields = remove_trailing_data_field(fields) - limit_choices_to = ( - fields and hasattr(fields[-1], 'rel') and - getattr(fields[-1].rel, 'limit_choices_to', None)) - if not limit_choices_to: - return models.Q() # empty Q - elif isinstance(limit_choices_to, models.Q): - return limit_choices_to # already a Q - else: - return models.Q(**limit_choices_to) # convert dict to Q diff --git a/django/contrib/admin/utils.py b/django/contrib/admin/utils.py new file mode 100644 index 0000000000..f9b17ad186 --- /dev/null +++ b/django/contrib/admin/utils.py @@ -0,0 +1,473 @@ +from __future__ import unicode_literals + +import datetime +import decimal + +from django.contrib.auth import get_permission_codename +from django.db import models +from django.db.models.constants import LOOKUP_SEP +from django.db.models.deletion import Collector +from django.db.models.related import RelatedObject +from django.forms.forms import pretty_name +from django.utils import formats +from django.utils.html import format_html +from django.utils.text import capfirst +from django.utils import timezone +from django.utils.encoding import force_str, force_text, smart_text +from django.utils import six +from django.utils.translation import ungettext +from django.core.urlresolvers import reverse, NoReverseMatch + +def lookup_needs_distinct(opts, lookup_path): + """ + Returns True if 'distinct()' should be used to query the given lookup path. + """ + field_name = lookup_path.split('__', 1)[0] + field = opts.get_field_by_name(field_name)[0] + if ((hasattr(field, 'rel') and + isinstance(field.rel, models.ManyToManyRel)) or + (isinstance(field, models.related.RelatedObject) and + not field.field.unique)): + return True + return False + +def prepare_lookup_value(key, value): + """ + Returns a lookup value prepared to be used in queryset filtering. + """ + # if key ends with __in, split parameter into separate values + if key.endswith('__in'): + value = value.split(',') + # if key ends with __isnull, special case '' and the string literals 'false' and '0' + if key.endswith('__isnull'): + if value.lower() in ('', 'false', '0'): + value = False + else: + value = True + return value + +def quote(s): + """ + Ensure that primary key values do not confuse the admin URLs by escaping + any '/', '_' and ':' and similarly problematic characters. + Similar to urllib.quote, except that the quoting is slightly different so + that it doesn't get automatically unquoted by the Web browser. + """ + if not isinstance(s, six.string_types): + return s + res = list(s) + for i in range(len(res)): + c = res[i] + if c in """:/_#?;@&=+$,"<>%\\""": + res[i] = '_%02X' % ord(c) + return ''.join(res) + + +def unquote(s): + """ + Undo the effects of quote(). Based heavily on urllib.unquote(). + """ + mychr = chr + myatoi = int + list = s.split('_') + res = [list[0]] + myappend = res.append + del list[0] + for item in list: + if item[1:2]: + try: + myappend(mychr(myatoi(item[:2], 16)) + item[2:]) + except ValueError: + myappend('_' + item) + else: + myappend('_' + item) + return "".join(res) + + +def flatten_fieldsets(fieldsets): + """Returns a list of field names from an admin fieldsets structure.""" + field_names = [] + for name, opts in fieldsets: + for field in opts['fields']: + if isinstance(field, (list, tuple)): + field_names.extend(field) + else: + field_names.append(field) + return field_names + + +def get_deleted_objects(objs, opts, user, admin_site, using): + """ + Find all objects related to ``objs`` that should also be deleted. ``objs`` + must be a homogenous iterable of objects (e.g. a QuerySet). + + Returns a nested list of strings suitable for display in the + template with the ``unordered_list`` filter. + + """ + collector = NestedObjects(using=using) + collector.collect(objs) + perms_needed = set() + + def format_callback(obj): + has_admin = obj.__class__ in admin_site._registry + opts = obj._meta + + no_edit_link = '%s: %s' % (capfirst(opts.verbose_name), + force_text(obj)) + + if has_admin: + try: + admin_url = reverse('%s:%s_%s_change' + % (admin_site.name, + opts.app_label, + opts.model_name), + None, (quote(obj._get_pk_val()),)) + except NoReverseMatch: + # Change url doesn't exist -- don't display link to edit + return no_edit_link + + p = '%s.%s' % (opts.app_label, + get_permission_codename('delete', opts)) + if not user.has_perm(p): + perms_needed.add(opts.verbose_name) + # Display a link to the admin page. + return format_html('{0}: {2}', + capfirst(opts.verbose_name), + admin_url, + obj) + else: + # Don't display link to edit, because it either has no + # admin or is edited inline. + return no_edit_link + + to_delete = collector.nested(format_callback) + + protected = [format_callback(obj) for obj in collector.protected] + + return to_delete, perms_needed, protected + + +class NestedObjects(Collector): + def __init__(self, *args, **kwargs): + super(NestedObjects, self).__init__(*args, **kwargs) + self.edges = {} # {from_instance: [to_instances]} + self.protected = set() + + def add_edge(self, source, target): + self.edges.setdefault(source, []).append(target) + + def collect(self, objs, source_attr=None, **kwargs): + for obj in objs: + if source_attr: + self.add_edge(getattr(obj, source_attr), obj) + else: + self.add_edge(None, obj) + try: + return super(NestedObjects, self).collect(objs, source_attr=source_attr, **kwargs) + except models.ProtectedError as e: + self.protected.update(e.protected_objects) + + def related_objects(self, related, objs): + qs = super(NestedObjects, self).related_objects(related, objs) + return qs.select_related(related.field.name) + + def _nested(self, obj, seen, format_callback): + if obj in seen: + return [] + seen.add(obj) + children = [] + for child in self.edges.get(obj, ()): + children.extend(self._nested(child, seen, format_callback)) + if format_callback: + ret = [format_callback(obj)] + else: + ret = [obj] + if children: + ret.append(children) + return ret + + def nested(self, format_callback=None): + """ + Return the graph as a nested list. + + """ + seen = set() + roots = [] + for root in self.edges.get(None, ()): + roots.extend(self._nested(root, seen, format_callback)) + return roots + + def can_fast_delete(self, *args, **kwargs): + """ + We always want to load the objects into memory so that we can display + them to the user in confirm page. + """ + return False + + +def model_format_dict(obj): + """ + Return a `dict` with keys 'verbose_name' and 'verbose_name_plural', + typically for use with string formatting. + + `obj` may be a `Model` instance, `Model` subclass, or `QuerySet` instance. + + """ + if isinstance(obj, (models.Model, models.base.ModelBase)): + opts = obj._meta + elif isinstance(obj, models.query.QuerySet): + opts = obj.model._meta + else: + opts = obj + return { + 'verbose_name': force_text(opts.verbose_name), + 'verbose_name_plural': force_text(opts.verbose_name_plural) + } + + +def model_ngettext(obj, n=None): + """ + Return the appropriate `verbose_name` or `verbose_name_plural` value for + `obj` depending on the count `n`. + + `obj` may be a `Model` instance, `Model` subclass, or `QuerySet` instance. + If `obj` is a `QuerySet` instance, `n` is optional and the length of the + `QuerySet` is used. + + """ + if isinstance(obj, models.query.QuerySet): + if n is None: + n = obj.count() + obj = obj.model + d = model_format_dict(obj) + singular, plural = d["verbose_name"], d["verbose_name_plural"] + return ungettext(singular, plural, n or 0) + + +def lookup_field(name, obj, model_admin=None): + opts = obj._meta + try: + f = opts.get_field(name) + except models.FieldDoesNotExist: + # For non-field values, the value is either a method, property or + # returned via a callable. + if callable(name): + attr = name + value = attr(obj) + elif (model_admin is not None and hasattr(model_admin, name) and + not name == '__str__' and not name == '__unicode__'): + attr = getattr(model_admin, name) + value = attr(obj) + else: + attr = getattr(obj, name) + if callable(attr): + value = attr() + else: + value = attr + f = None + else: + attr = None + value = getattr(obj, name) + return f, attr, value + + +def label_for_field(name, model, model_admin=None, return_attr=False): + """ + Returns a sensible label for a field name. The name can be a callable, + property (but not created with @property decorator) or the name of an + object's attribute, as well as a genuine fields. If return_attr is + True, the resolved attribute (which could be a callable) is also returned. + This will be None if (and only if) the name refers to a field. + """ + attr = None + try: + field = model._meta.get_field_by_name(name)[0] + if isinstance(field, RelatedObject): + label = field.opts.verbose_name + else: + label = field.verbose_name + except models.FieldDoesNotExist: + if name == "__unicode__": + label = force_text(model._meta.verbose_name) + attr = six.text_type + elif name == "__str__": + label = force_str(model._meta.verbose_name) + attr = bytes + else: + if callable(name): + attr = name + elif model_admin is not None and hasattr(model_admin, name): + attr = getattr(model_admin, name) + elif hasattr(model, name): + attr = getattr(model, name) + else: + message = "Unable to lookup '%s' on %s" % (name, model._meta.object_name) + if model_admin: + message += " or %s" % (model_admin.__class__.__name__,) + raise AttributeError(message) + + if hasattr(attr, "short_description"): + label = attr.short_description + elif (isinstance(attr, property) and + hasattr(attr, "fget") and + hasattr(attr.fget, "short_description")): + label = attr.fget.short_description + elif callable(attr): + if attr.__name__ == "": + label = "--" + else: + label = pretty_name(attr.__name__) + else: + label = pretty_name(name) + if return_attr: + return (label, attr) + else: + return label + + +def help_text_for_field(name, model): + help_text = "" + try: + field_data = model._meta.get_field_by_name(name) + except models.FieldDoesNotExist: + pass + else: + field = field_data[0] + if not isinstance(field, RelatedObject): + help_text = field.help_text + return smart_text(help_text) + + +def display_for_field(value, field): + from django.contrib.admin.templatetags.admin_list import _boolean_icon + from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE + + if field.flatchoices: + return dict(field.flatchoices).get(value, EMPTY_CHANGELIST_VALUE) + # NullBooleanField needs special-case null-handling, so it comes + # before the general null test. + elif isinstance(field, models.BooleanField) or isinstance(field, models.NullBooleanField): + return _boolean_icon(value) + elif value is None: + return EMPTY_CHANGELIST_VALUE + elif isinstance(field, models.DateTimeField): + return formats.localize(timezone.template_localtime(value)) + elif isinstance(field, (models.DateField, models.TimeField)): + return formats.localize(value) + elif isinstance(field, models.DecimalField): + return formats.number_format(value, field.decimal_places) + elif isinstance(field, models.FloatField): + return formats.number_format(value) + else: + return smart_text(value) + + +def display_for_value(value, boolean=False): + from django.contrib.admin.templatetags.admin_list import _boolean_icon + from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE + + if boolean: + return _boolean_icon(value) + elif value is None: + return EMPTY_CHANGELIST_VALUE + elif isinstance(value, datetime.datetime): + return formats.localize(timezone.template_localtime(value)) + elif isinstance(value, (datetime.date, datetime.time)): + return formats.localize(value) + elif isinstance(value, six.integer_types + (decimal.Decimal, float)): + return formats.number_format(value) + else: + return smart_text(value) + + +class NotRelationField(Exception): + pass + + +def get_model_from_relation(field): + if isinstance(field, models.related.RelatedObject): + return field.model + elif getattr(field, 'rel'): # or isinstance? + return field.rel.to + else: + raise NotRelationField + + +def reverse_field_path(model, path): + """ Create a reversed field path. + + E.g. Given (Order, "user__groups"), + return (Group, "user__order"). + + Final field must be a related model, not a data field. + + """ + reversed_path = [] + parent = model + pieces = path.split(LOOKUP_SEP) + for piece in pieces: + field, model, direct, m2m = parent._meta.get_field_by_name(piece) + # skip trailing data field if extant: + if len(reversed_path) == len(pieces)-1: # final iteration + try: + get_model_from_relation(field) + except NotRelationField: + break + if direct: + related_name = field.related_query_name() + parent = field.rel.to + else: + related_name = field.field.name + parent = field.model + reversed_path.insert(0, related_name) + return (parent, LOOKUP_SEP.join(reversed_path)) + + +def get_fields_from_path(model, path): + """ Return list of Fields given path relative to model. + + e.g. (ModelX, "user__groups__name") -> [ + , + , + , + ] + """ + pieces = path.split(LOOKUP_SEP) + fields = [] + for piece in pieces: + if fields: + parent = get_model_from_relation(fields[-1]) + else: + parent = model + fields.append(parent._meta.get_field_by_name(piece)[0]) + return fields + + +def remove_trailing_data_field(fields): + """ Discard trailing non-relation field if extant. """ + try: + get_model_from_relation(fields[-1]) + except NotRelationField: + fields = fields[:-1] + return fields + + +def get_limit_choices_to_from_path(model, path): + """ Return Q object for limiting choices if applicable. + + If final model in path is linked via a ForeignKey or ManyToManyField which + has a `limit_choices_to` attribute, return it as a Q object. + """ + + fields = get_fields_from_path(model, path) + fields = remove_trailing_data_field(fields) + limit_choices_to = ( + fields and hasattr(fields[-1], 'rel') and + getattr(fields[-1].rel, 'limit_choices_to', None)) + if not limit_choices_to: + return models.Q() # empty Q + elif isinstance(limit_choices_to, models.Q): + return limit_choices_to # already a Q + else: + return models.Q(**limit_choices_to) # convert dict to Q diff --git a/django/contrib/admin/validation.py b/django/contrib/admin/validation.py index fb9634d114..495f72f727 100644 --- a/django/contrib/admin/validation.py +++ b/django/contrib/admin/validation.py @@ -2,7 +2,7 @@ from django.core.exceptions import ImproperlyConfigured from django.db import models from django.db.models.fields import FieldDoesNotExist from django.forms.models import BaseModelForm, BaseModelFormSet, _get_foreign_key -from django.contrib.admin.util import get_fields_from_path, NotRelationField +from django.contrib.admin.utils import get_fields_from_path, NotRelationField """ Does basic ModelAdmin option validation. Calls custom validation diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py index 14f7655251..cea0cba2b8 100644 --- a/django/contrib/admin/views/main.py +++ b/django/contrib/admin/views/main.py @@ -16,7 +16,7 @@ from django.utils.http import urlencode from django.contrib.admin import FieldListFilter from django.contrib.admin.exceptions import DisallowedModelAdminLookup from django.contrib.admin.options import IncorrectLookupParameters, IS_POPUP_VAR, TO_FIELD_VAR -from django.contrib.admin.util import (quote, get_fields_from_path, +from django.contrib.admin.utils import (quote, get_fields_from_path, lookup_needs_distinct, prepare_lookup_value) # Changelist settings diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py index fe6c9e13a9..2d50f47396 100644 --- a/django/contrib/admin/widgets.py +++ b/django/contrib/admin/widgets.py @@ -9,7 +9,7 @@ from django import forms from django.contrib.admin.templatetags.admin_static import static from django.core.urlresolvers import reverse from django.forms.widgets import RadioFieldRenderer -from django.forms.util import flatatt +from django.forms.utils import flatatt from django.utils.html import escape, format_html, format_html_join, smart_urlquote from django.utils.text import Truncator from django.utils.translation import ugettext as _ diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py index 98956771b3..5019e9ab9e 100644 --- a/django/contrib/auth/forms.py +++ b/django/contrib/auth/forms.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals from collections import OrderedDict from django import forms -from django.forms.util import flatatt +from django.forms.utils import flatatt from django.template import loader from django.utils.encoding import force_bytes from django.utils.html import format_html, format_html_join diff --git a/django/contrib/comments/forms.py b/django/contrib/comments/forms.py index bd254d2733..17d2ef8f09 100644 --- a/django/contrib/comments/forms.py +++ b/django/contrib/comments/forms.py @@ -1,6 +1,6 @@ import time from django import forms -from django.forms.util import ErrorDict +from django.forms.utils import ErrorDict from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.contrib.comments.models import Comment diff --git a/django/contrib/gis/db/backends/oracle/creation.py b/django/contrib/gis/db/backends/oracle/creation.py index 043da916f5..14f8d4d150 100644 --- a/django/contrib/gis/db/backends/oracle/creation.py +++ b/django/contrib/gis/db/backends/oracle/creation.py @@ -1,5 +1,5 @@ from django.db.backends.oracle.creation import DatabaseCreation -from django.db.backends.util import truncate_name +from django.db.backends.utils import truncate_name class OracleCreation(DatabaseCreation): diff --git a/django/contrib/gis/db/backends/oracle/operations.py b/django/contrib/gis/db/backends/oracle/operations.py index 628018320b..a8f798d300 100644 --- a/django/contrib/gis/db/backends/oracle/operations.py +++ b/django/contrib/gis/db/backends/oracle/operations.py @@ -13,7 +13,7 @@ from decimal import Decimal from django.db.backends.oracle.base import DatabaseOperations from django.contrib.gis.db.backends.base import BaseSpatialOperations from django.contrib.gis.db.backends.oracle.adapter import OracleSpatialAdapter -from django.contrib.gis.db.backends.util import SpatialFunction +from django.contrib.gis.db.backends.utils import SpatialFunction from django.contrib.gis.geometry.backend import Geometry from django.contrib.gis.measure import Distance from django.utils import six diff --git a/django/contrib/gis/db/backends/postgis/operations.py b/django/contrib/gis/db/backends/postgis/operations.py index 3863bea992..0bff02cd11 100644 --- a/django/contrib/gis/db/backends/postgis/operations.py +++ b/django/contrib/gis/db/backends/postgis/operations.py @@ -3,7 +3,7 @@ from decimal import Decimal from django.conf import settings from django.contrib.gis.db.backends.base import BaseSpatialOperations -from django.contrib.gis.db.backends.util import SpatialOperation, SpatialFunction +from django.contrib.gis.db.backends.utils import SpatialOperation, SpatialFunction from django.contrib.gis.db.backends.postgis.adapter import PostGISAdapter from django.contrib.gis.geometry.backend import Geometry from django.contrib.gis.measure import Distance diff --git a/django/contrib/gis/db/backends/spatialite/operations.py b/django/contrib/gis/db/backends/spatialite/operations.py index 3cb2004dfd..b20b1b9157 100644 --- a/django/contrib/gis/db/backends/spatialite/operations.py +++ b/django/contrib/gis/db/backends/spatialite/operations.py @@ -3,7 +3,7 @@ import sys from decimal import Decimal from django.contrib.gis.db.backends.base import BaseSpatialOperations -from django.contrib.gis.db.backends.util import SpatialOperation, SpatialFunction +from django.contrib.gis.db.backends.utils import SpatialOperation, SpatialFunction from django.contrib.gis.db.backends.spatialite.adapter import SpatiaLiteAdapter from django.contrib.gis.geometry.backend import Geometry from django.contrib.gis.measure import Distance diff --git a/django/contrib/gis/db/backends/util.py b/django/contrib/gis/db/backends/util.py deleted file mode 100644 index 2612810659..0000000000 --- a/django/contrib/gis/db/backends/util.py +++ /dev/null @@ -1,44 +0,0 @@ -""" -A collection of utility routines and classes used by the spatial -backends. -""" - -class SpatialOperation(object): - """ - Base class for generating spatial SQL. - """ - sql_template = '%(geo_col)s %(operator)s %(geometry)s' - - def __init__(self, function='', operator='', result='', **kwargs): - self.function = function - self.operator = operator - self.result = result - self.extra = kwargs - - def as_sql(self, geo_col, geometry='%s'): - return self.sql_template % self.params(geo_col, geometry), [] - - def params(self, geo_col, geometry): - params = {'function' : self.function, - 'geo_col' : geo_col, - 'geometry' : geometry, - 'operator' : self.operator, - 'result' : self.result, - } - params.update(self.extra) - return params - -class SpatialFunction(SpatialOperation): - """ - Base class for generating spatial SQL related to a function. - """ - sql_template = '%(function)s(%(geo_col)s, %(geometry)s)' - - def __init__(self, func, result='', operator='', **kwargs): - # Getting the function prefix. - default = {'function' : func, - 'operator' : operator, - 'result' : result - } - kwargs.update(default) - super(SpatialFunction, self).__init__(**kwargs) diff --git a/django/contrib/gis/db/backends/utils.py b/django/contrib/gis/db/backends/utils.py new file mode 100644 index 0000000000..2612810659 --- /dev/null +++ b/django/contrib/gis/db/backends/utils.py @@ -0,0 +1,44 @@ +""" +A collection of utility routines and classes used by the spatial +backends. +""" + +class SpatialOperation(object): + """ + Base class for generating spatial SQL. + """ + sql_template = '%(geo_col)s %(operator)s %(geometry)s' + + def __init__(self, function='', operator='', result='', **kwargs): + self.function = function + self.operator = operator + self.result = result + self.extra = kwargs + + def as_sql(self, geo_col, geometry='%s'): + return self.sql_template % self.params(geo_col, geometry), [] + + def params(self, geo_col, geometry): + params = {'function' : self.function, + 'geo_col' : geo_col, + 'geometry' : geometry, + 'operator' : self.operator, + 'result' : self.result, + } + params.update(self.extra) + return params + +class SpatialFunction(SpatialOperation): + """ + Base class for generating spatial SQL related to a function. + """ + sql_template = '%(function)s(%(geo_col)s, %(geometry)s)' + + def __init__(self, func, result='', operator='', **kwargs): + # Getting the function prefix. + default = {'function' : func, + 'operator' : operator, + 'result' : result + } + kwargs.update(default) + super(SpatialFunction, self).__init__(**kwargs) diff --git a/django/contrib/gis/db/models/sql/compiler.py b/django/contrib/gis/db/models/sql/compiler.py index b89df0ae95..94ce30ba5d 100644 --- a/django/contrib/gis/db/models/sql/compiler.py +++ b/django/contrib/gis/db/models/sql/compiler.py @@ -1,7 +1,7 @@ import datetime from django.conf import settings -from django.db.backends.util import truncate_name, typecast_date, typecast_timestamp +from django.db.backends.utils import truncate_name, typecast_date, typecast_timestamp from django.db.models.sql import compiler from django.db.models.sql.constants import MULTI from django.utils import six diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index 44024c5349..49d6b80ae4 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -14,7 +14,7 @@ from importlib import import_module from django.conf import settings from django.db import DEFAULT_DB_ALIAS from django.db.backends.signals import connection_created -from django.db.backends import util +from django.db.backends import utils from django.db.transaction import TransactionManagementError from django.db.utils import DatabaseErrorWrapper from django.utils.functional import cached_property @@ -158,7 +158,7 @@ class BaseDatabaseWrapper(object): (self.use_debug_cursor is None and settings.DEBUG)): cursor = self.make_debug_cursor(self._cursor()) else: - cursor = util.CursorWrapper(self._cursor(), self) + cursor = utils.CursorWrapper(self._cursor(), self) return cursor def commit(self): @@ -495,7 +495,7 @@ class BaseDatabaseWrapper(object): """ Creates a cursor that logs all queries in self.queries. """ - return util.CursorDebugWrapper(cursor, self) + return utils.CursorDebugWrapper(cursor, self) @contextmanager def temporary_connection(self): @@ -1131,7 +1131,7 @@ class BaseDatabaseOperations(object): """ if value is None: return None - return util.format_number(value, max_digits, decimal_places) + return utils.format_number(value, max_digits, decimal_places) def year_lookup_bounds_for_date_field(self, value): """ diff --git a/django/db/backends/creation.py b/django/db/backends/creation.py index eba3952d0f..9c700c96b2 100644 --- a/django/db/backends/creation.py +++ b/django/db/backends/creation.py @@ -8,7 +8,7 @@ from django.db.utils import load_backend from django.utils.encoding import force_bytes from django.utils.six.moves import input -from .util import truncate_name +from .utils import truncate_name # The prefix to put on the default database name when creating # the test database. diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index a5fcead7fb..2d33f45705 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -37,7 +37,7 @@ except ImportError: from django.conf import settings from django.db import utils -from django.db.backends import (util, BaseDatabaseFeatures, +from django.db.backends import (utils as backend_utils, BaseDatabaseFeatures, BaseDatabaseOperations, BaseDatabaseWrapper) from django.db.backends.mysql.client import DatabaseClient from django.db.backends.mysql.creation import DatabaseCreation @@ -91,9 +91,9 @@ def adapt_datetime_with_timezone_support(value, conv): # timezone support is active, Django expects timezone-aware datetime objects. django_conversions = conversions.copy() django_conversions.update({ - FIELD_TYPE.TIME: util.typecast_time, - FIELD_TYPE.DECIMAL: util.typecast_decimal, - FIELD_TYPE.NEWDECIMAL: util.typecast_decimal, + FIELD_TYPE.TIME: backend_utils.typecast_time, + FIELD_TYPE.DECIMAL: backend_utils.typecast_decimal, + FIELD_TYPE.NEWDECIMAL: backend_utils.typecast_decimal, FIELD_TYPE.DATETIME: parse_datetime_with_timezone_support, datetime.datetime: adapt_datetime_with_timezone_support, }) @@ -109,7 +109,7 @@ server_version_re = re.compile(r'(\d{1,2})\.(\d{1,2})\.(\d{1,2})') # MySQL-4.1 and newer, so the MysqlDebugWrapper is unnecessary. Since the # point is to raise Warnings as exceptions, this can be done with the Python # warning module, and this is setup when the connection is created, and the -# standard util.CursorDebugWrapper can be used. Also, using sql_mode +# standard backend_utils.CursorDebugWrapper can be used. Also, using sql_mode # TRADITIONAL will automatically cause most warnings to be treated as errors. class CursorWrapper(object): diff --git a/django/db/backends/mysql/creation.py b/django/db/backends/mysql/creation.py index b59b225f4f..b15c3371fb 100644 --- a/django/db/backends/mysql/creation.py +++ b/django/db/backends/mysql/creation.py @@ -61,7 +61,7 @@ class DatabaseCreation(BaseDatabaseCreation): index_name = "%s_%s" % (model._meta.db_table, self._digest([f.name for f in fields])) - from ..util import truncate_name + from ..utils import truncate_name return [ style.SQL_KEYWORD("DROP INDEX") + " " + diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index c9b3fbfd27..eead105b24 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -53,7 +53,8 @@ except ImportError: from django.conf import settings from django.db import utils -from django.db.backends import BaseDatabaseFeatures, BaseDatabaseOperations, BaseDatabaseWrapper, BaseDatabaseValidation, util +from django.db.backends import (BaseDatabaseFeatures, BaseDatabaseOperations, + BaseDatabaseWrapper, BaseDatabaseValidation, utils as backend_utils) from django.db.backends.oracle.client import DatabaseClient from django.db.backends.oracle.creation import DatabaseCreation from django.db.backends.oracle.introspection import DatabaseIntrospection @@ -239,7 +240,7 @@ WHEN (new.%(col_name)s IS NULL) value = float(value) # Convert floats to decimals elif value is not None and field and field.get_internal_type() == 'DecimalField': - value = util.typecast_decimal(field.format_number(value)) + value = backend_utils.typecast_decimal(field.format_number(value)) # cx_Oracle always returns datetime.datetime objects for # DATE and TIMESTAMP columns, but Django wants to see a # python datetime.date, .time, or .datetime. We use the type @@ -316,7 +317,7 @@ WHEN (new.%(col_name)s IS NULL) # always defaults to uppercase. # We simplify things by making Oracle identifiers always uppercase. if not name.startswith('"') and not name.endswith('"'): - name = '"%s"' % util.truncate_name(name.upper(), + name = '"%s"' % backend_utils.truncate_name(name.upper(), self.max_name_length()) # Oracle puts the query text into a (query % args) construct, so % signs # in names need to be escaped. The '%%' will be collapsed back to '%' at @@ -482,11 +483,11 @@ WHEN (new.%(col_name)s IS NULL) def _get_sequence_name(self, table): name_length = self.max_name_length() - 3 - return '%s_SQ' % util.truncate_name(table, name_length).upper() + return '%s_SQ' % backend_utils.truncate_name(table, name_length).upper() def _get_trigger_name(self, table): name_length = self.max_name_length() - 3 - return '%s_TR' % util.truncate_name(table, name_length).upper() + return '%s_TR' % backend_utils.truncate_name(table, name_length).upper() def bulk_insert_sql(self, fields, num_values): items_sql = "SELECT %s FROM DUAL" % ", ".join(["%s"] * len(fields)) diff --git a/django/db/backends/postgresql_psycopg2/creation.py b/django/db/backends/postgresql_psycopg2/creation.py index 954e198764..c66b6c6f55 100644 --- a/django/db/backends/postgresql_psycopg2/creation.py +++ b/django/db/backends/postgresql_psycopg2/creation.py @@ -1,5 +1,5 @@ from django.db.backends.creation import BaseDatabaseCreation -from django.db.backends.util import truncate_name +from django.db.backends.utils import truncate_name class DatabaseCreation(BaseDatabaseCreation): diff --git a/django/db/backends/schema.py b/django/db/backends/schema.py index bc529f47e0..9568400556 100644 --- a/django/db/backends/schema.py +++ b/django/db/backends/schema.py @@ -2,7 +2,7 @@ import hashlib import operator from django.db.backends.creation import BaseDatabaseCreation -from django.db.backends.util import truncate_name +from django.db.backends.utils import truncate_name from django.db.models.fields.related import ManyToManyField from django.db.transaction import atomic from django.utils.log import getLogger diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py index 8641207aec..80d88c5cab 100644 --- a/django/db/backends/sqlite3/base.py +++ b/django/db/backends/sqlite3/base.py @@ -13,7 +13,7 @@ import re from django.conf import settings from django.db import utils -from django.db.backends import (util, BaseDatabaseFeatures, +from django.db.backends import (utils as backend_utils, BaseDatabaseFeatures, BaseDatabaseOperations, BaseDatabaseWrapper, BaseDatabaseValidation) from django.db.backends.sqlite3.client import DatabaseClient from django.db.backends.sqlite3.creation import DatabaseCreation @@ -80,10 +80,10 @@ Database.register_converter(str("date"), decoder(parse_date)) Database.register_converter(str("datetime"), decoder(parse_datetime_with_timezone_support)) Database.register_converter(str("timestamp"), decoder(parse_datetime_with_timezone_support)) Database.register_converter(str("TIMESTAMP"), decoder(parse_datetime_with_timezone_support)) -Database.register_converter(str("decimal"), decoder(util.typecast_decimal)) +Database.register_converter(str("decimal"), decoder(backend_utils.typecast_decimal)) Database.register_adapter(datetime.datetime, adapt_datetime_with_timezone_support) -Database.register_adapter(decimal.Decimal, util.rev_typecast_decimal) +Database.register_adapter(decimal.Decimal, backend_utils.rev_typecast_decimal) if six.PY2: Database.register_adapter(str, lambda s: s.decode('utf-8')) Database.register_adapter(SafeBytes, lambda s: s.decode('utf-8')) @@ -282,7 +282,7 @@ class DatabaseOperations(BaseDatabaseOperations): internal_type = field.get_internal_type() if internal_type == 'DecimalField': - return util.typecast_decimal(field.format_number(value)) + return backend_utils.typecast_decimal(field.format_number(value)) elif internal_type and internal_type.endswith('IntegerField') or internal_type == 'AutoField': return int(value) elif internal_type == 'DateField': @@ -487,7 +487,7 @@ def _sqlite_date_extract(lookup_type, dt): if dt is None: return None try: - dt = util.typecast_timestamp(dt) + dt = backend_utils.typecast_timestamp(dt) except (ValueError, TypeError): return None if lookup_type == 'week_day': @@ -498,7 +498,7 @@ def _sqlite_date_extract(lookup_type, dt): def _sqlite_date_trunc(lookup_type, dt): try: - dt = util.typecast_timestamp(dt) + dt = backend_utils.typecast_timestamp(dt) except (ValueError, TypeError): return None if lookup_type == 'year': @@ -513,7 +513,7 @@ def _sqlite_datetime_extract(lookup_type, dt, tzname): if dt is None: return None try: - dt = util.typecast_timestamp(dt) + dt = backend_utils.typecast_timestamp(dt) except (ValueError, TypeError): return None if tzname is not None: @@ -526,7 +526,7 @@ def _sqlite_datetime_extract(lookup_type, dt, tzname): def _sqlite_datetime_trunc(lookup_type, dt, tzname): try: - dt = util.typecast_timestamp(dt) + dt = backend_utils.typecast_timestamp(dt) except (ValueError, TypeError): return None if tzname is not None: @@ -547,7 +547,7 @@ def _sqlite_datetime_trunc(lookup_type, dt, tzname): def _sqlite_format_dtdelta(dt, conn, days, secs, usecs): try: - dt = util.typecast_timestamp(dt) + dt = backend_utils.typecast_timestamp(dt) delta = datetime.timedelta(int(days), int(secs), int(usecs)) if conn.strip() == '+': dt = dt + delta diff --git a/django/db/backends/util.py b/django/db/backends/util.py deleted file mode 100644 index f8157c50e7..0000000000 --- a/django/db/backends/util.py +++ /dev/null @@ -1,167 +0,0 @@ -from __future__ import unicode_literals - -import datetime -import decimal -import hashlib -import logging -from time import time - -from django.conf import settings -from django.utils.encoding import force_bytes -from django.utils.timezone import utc - - -logger = logging.getLogger('django.db.backends') - - -class CursorWrapper(object): - def __init__(self, cursor, db): - self.cursor = cursor - self.db = db - - def __getattr__(self, attr): - if attr in ('execute', 'executemany', 'callproc'): - self.db.set_dirty() - cursor_attr = getattr(self.cursor, attr) - if attr in ('callproc', 'close', 'execute', 'executemany', - 'fetchone', 'fetchmany', 'fetchall', 'nextset'): - return self.db.wrap_database_errors()(cursor_attr) - else: - return cursor_attr - - def __iter__(self): - return iter(self.cursor) - - -class CursorDebugWrapper(CursorWrapper): - - def execute(self, sql, params=None): - self.db.set_dirty() - start = time() - try: - with self.db.wrap_database_errors(): - if params is None: - # params default might be backend specific - return self.cursor.execute(sql) - return self.cursor.execute(sql, params) - finally: - stop = time() - duration = stop - start - sql = self.db.ops.last_executed_query(self.cursor, sql, params) - self.db.queries.append({ - 'sql': sql, - 'time': "%.3f" % duration, - }) - logger.debug('(%.3f) %s; args=%s' % (duration, sql, params), - extra={'duration': duration, 'sql': sql, 'params': params} - ) - - def executemany(self, sql, param_list): - self.db.set_dirty() - start = time() - try: - with self.db.wrap_database_errors(): - return self.cursor.executemany(sql, param_list) - finally: - stop = time() - duration = stop - start - try: - times = len(param_list) - except TypeError: # param_list could be an iterator - times = '?' - self.db.queries.append({ - 'sql': '%s times: %s' % (times, sql), - 'time': "%.3f" % duration, - }) - logger.debug('(%.3f) %s; args=%s' % (duration, sql, param_list), - extra={'duration': duration, 'sql': sql, 'params': param_list} - ) - - -############################################### -# Converters from database (string) to Python # -############################################### - -def typecast_date(s): - return datetime.date(*map(int, s.split('-'))) if s else None # returns None if s is null - - -def typecast_time(s): # does NOT store time zone information - if not s: - return None - hour, minutes, seconds = s.split(':') - if '.' in seconds: # check whether seconds have a fractional part - seconds, microseconds = seconds.split('.') - else: - microseconds = '0' - return datetime.time(int(hour), int(minutes), int(seconds), int(float('.' + microseconds) * 1000000)) - - -def typecast_timestamp(s): # does NOT store time zone information - # "2005-07-29 15:48:00.590358-05" - # "2005-07-29 09:56:00-05" - if not s: - return None - if not ' ' in s: - return typecast_date(s) - d, t = s.split() - # Extract timezone information, if it exists. Currently we just throw - # it away, but in the future we may make use of it. - if '-' in t: - t, tz = t.split('-', 1) - tz = '-' + tz - elif '+' in t: - t, tz = t.split('+', 1) - tz = '+' + tz - else: - tz = '' - dates = d.split('-') - times = t.split(':') - seconds = times[2] - if '.' in seconds: # check whether seconds have a fractional part - seconds, microseconds = seconds.split('.') - else: - microseconds = '0' - tzinfo = utc if settings.USE_TZ else None - return datetime.datetime(int(dates[0]), int(dates[1]), int(dates[2]), - int(times[0]), int(times[1]), int(seconds), - int((microseconds + '000000')[:6]), tzinfo) - - -def typecast_decimal(s): - if s is None or s == '': - return None - return decimal.Decimal(s) - - -############################################### -# Converters from Python to database (string) # -############################################### - -def rev_typecast_decimal(d): - if d is None: - return None - return str(d) - - -def truncate_name(name, length=None, hash_len=4): - """Shortens a string to a repeatable mangled version with the given length. - """ - if length is None or len(name) <= length: - return name - - hsh = hashlib.md5(force_bytes(name)).hexdigest()[:hash_len] - return '%s%s' % (name[:length - hash_len], hsh) - - -def format_number(value, max_digits, decimal_places): - """ - Formats a number into a string with the requisite number of digits and - decimal places. - """ - if isinstance(value, decimal.Decimal): - context = decimal.getcontext().copy() - context.prec = max_digits - return "{0:f}".format(value.quantize(decimal.Decimal(".1") ** decimal_places, context=context)) - else: - return "%.*f" % (decimal_places, value) diff --git a/django/db/backends/utils.py b/django/db/backends/utils.py new file mode 100644 index 0000000000..f8157c50e7 --- /dev/null +++ b/django/db/backends/utils.py @@ -0,0 +1,167 @@ +from __future__ import unicode_literals + +import datetime +import decimal +import hashlib +import logging +from time import time + +from django.conf import settings +from django.utils.encoding import force_bytes +from django.utils.timezone import utc + + +logger = logging.getLogger('django.db.backends') + + +class CursorWrapper(object): + def __init__(self, cursor, db): + self.cursor = cursor + self.db = db + + def __getattr__(self, attr): + if attr in ('execute', 'executemany', 'callproc'): + self.db.set_dirty() + cursor_attr = getattr(self.cursor, attr) + if attr in ('callproc', 'close', 'execute', 'executemany', + 'fetchone', 'fetchmany', 'fetchall', 'nextset'): + return self.db.wrap_database_errors()(cursor_attr) + else: + return cursor_attr + + def __iter__(self): + return iter(self.cursor) + + +class CursorDebugWrapper(CursorWrapper): + + def execute(self, sql, params=None): + self.db.set_dirty() + start = time() + try: + with self.db.wrap_database_errors(): + if params is None: + # params default might be backend specific + return self.cursor.execute(sql) + return self.cursor.execute(sql, params) + finally: + stop = time() + duration = stop - start + sql = self.db.ops.last_executed_query(self.cursor, sql, params) + self.db.queries.append({ + 'sql': sql, + 'time': "%.3f" % duration, + }) + logger.debug('(%.3f) %s; args=%s' % (duration, sql, params), + extra={'duration': duration, 'sql': sql, 'params': params} + ) + + def executemany(self, sql, param_list): + self.db.set_dirty() + start = time() + try: + with self.db.wrap_database_errors(): + return self.cursor.executemany(sql, param_list) + finally: + stop = time() + duration = stop - start + try: + times = len(param_list) + except TypeError: # param_list could be an iterator + times = '?' + self.db.queries.append({ + 'sql': '%s times: %s' % (times, sql), + 'time': "%.3f" % duration, + }) + logger.debug('(%.3f) %s; args=%s' % (duration, sql, param_list), + extra={'duration': duration, 'sql': sql, 'params': param_list} + ) + + +############################################### +# Converters from database (string) to Python # +############################################### + +def typecast_date(s): + return datetime.date(*map(int, s.split('-'))) if s else None # returns None if s is null + + +def typecast_time(s): # does NOT store time zone information + if not s: + return None + hour, minutes, seconds = s.split(':') + if '.' in seconds: # check whether seconds have a fractional part + seconds, microseconds = seconds.split('.') + else: + microseconds = '0' + return datetime.time(int(hour), int(minutes), int(seconds), int(float('.' + microseconds) * 1000000)) + + +def typecast_timestamp(s): # does NOT store time zone information + # "2005-07-29 15:48:00.590358-05" + # "2005-07-29 09:56:00-05" + if not s: + return None + if not ' ' in s: + return typecast_date(s) + d, t = s.split() + # Extract timezone information, if it exists. Currently we just throw + # it away, but in the future we may make use of it. + if '-' in t: + t, tz = t.split('-', 1) + tz = '-' + tz + elif '+' in t: + t, tz = t.split('+', 1) + tz = '+' + tz + else: + tz = '' + dates = d.split('-') + times = t.split(':') + seconds = times[2] + if '.' in seconds: # check whether seconds have a fractional part + seconds, microseconds = seconds.split('.') + else: + microseconds = '0' + tzinfo = utc if settings.USE_TZ else None + return datetime.datetime(int(dates[0]), int(dates[1]), int(dates[2]), + int(times[0]), int(times[1]), int(seconds), + int((microseconds + '000000')[:6]), tzinfo) + + +def typecast_decimal(s): + if s is None or s == '': + return None + return decimal.Decimal(s) + + +############################################### +# Converters from Python to database (string) # +############################################### + +def rev_typecast_decimal(d): + if d is None: + return None + return str(d) + + +def truncate_name(name, length=None, hash_len=4): + """Shortens a string to a repeatable mangled version with the given length. + """ + if length is None or len(name) <= length: + return name + + hsh = hashlib.md5(force_bytes(name)).hexdigest()[:hash_len] + return '%s%s' % (name[:length - hash_len], hsh) + + +def format_number(value, max_digits, decimal_places): + """ + Formats a number into a string with the requisite number of digits and + decimal places. + """ + if isinstance(value, decimal.Decimal): + context = decimal.getcontext().copy() + context.prec = max_digits + return "{0:f}".format(value.quantize(decimal.Decimal(".1") ** decimal_places, context=context)) + else: + return "%.*f" % (decimal_places, value) diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 5d74e2002d..e019855826 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -1122,14 +1122,14 @@ class DecimalField(Field): Formats a number into a string with the requisite number of digits and decimal places. """ - # Method moved to django.db.backends.util. + # Method moved to django.db.backends.utils. # # It is preserved because it is used by the oracle backend # (django.db.backends.oracle.query), and also for # backwards-compatibility with any external code which may have used # this method. - from django.db.backends import util - return util.format_number(value, self.max_digits, self.decimal_places) + from django.db.backends import utils + return utils.format_number(value, self.max_digits, self.decimal_places) def get_db_prep_save(self, value, connection): return connection.ops.value_to_db_decimal(self.to_python(value), diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 4caed4f559..fd9e8fa4d8 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -1,7 +1,7 @@ from operator import attrgetter from django.db import connection, connections, router -from django.db.backends import util +from django.db.backends import utils from django.db.models import signals from django.db.models.fields import (AutoField, Field, IntegerField, PositiveIntegerField, PositiveSmallIntegerField, FieldDoesNotExist) @@ -1440,7 +1440,7 @@ class ManyToManyField(RelatedField): elif self.db_table: return self.db_table else: - return util.truncate_name('%s_%s' % (opts.db_table, self.name), + return utils.truncate_name('%s_%s' % (opts.db_table, self.name), connection.ops.max_name_length()) def _get_m2m_attr(self, related, attr): diff --git a/django/db/models/options.py b/django/db/models/options.py index c89270eabb..8e0e1a2210 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -77,7 +77,7 @@ class Options(object): def contribute_to_class(self, cls, name): from django.db import connection - from django.db.backends.util import truncate_name + from django.db.backends.utils import truncate_name cls._meta = self self.model = cls diff --git a/django/db/models/query_utils.py b/django/db/models/query_utils.py index 2a92978beb..a7d98f60fe 100644 --- a/django/db/models/query_utils.py +++ b/django/db/models/query_utils.py @@ -7,7 +7,7 @@ circular import difficulties. """ from __future__ import unicode_literals -from django.db.backends import util +from django.db.backends import utils from django.utils import six from django.utils import tree @@ -194,7 +194,7 @@ def deferred_class_factory(model, attrs): # name using the passed in attrs. It's OK to reuse an existing class # object if the attrs are identical. name = "%s_Deferred_%s" % (model.__name__, '_'.join(sorted(list(attrs)))) - name = util.truncate_name(name, 80, 32) + name = utils.truncate_name(name, 80, 32) overrides = dict((attr, DeferredAttribute(attr, model)) for attr in attrs) overrides["Meta"] = Meta diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index c42149e45a..ba2c6c91b3 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -2,7 +2,7 @@ import datetime from django.conf import settings from django.core.exceptions import FieldError -from django.db.backends.util import truncate_name +from django.db.backends.utils import truncate_name from django.db.models.constants import LOOKUP_SEP from django.db.models.query_utils import select_related_descend, QueryWrapper from django.db.models.sql.constants import (SINGLE, MULTI, ORDER_DIR, @@ -1049,7 +1049,7 @@ class SQLDateCompiler(SQLCompiler): from django.db.models.fields import DateField fields = [DateField()] else: - from django.db.backends.util import typecast_date + from django.db.backends.utils import typecast_date needs_string_cast = self.connection.features.needs_datetime_string_cast offset = len(self.query.extra_select) @@ -1075,7 +1075,7 @@ class SQLDateTimeCompiler(SQLCompiler): from django.db.models.fields import DateTimeField fields = [DateTimeField()] else: - from django.db.backends.util import typecast_timestamp + from django.db.backends.utils import typecast_timestamp needs_string_cast = self.connection.features.needs_datetime_string_cast offset = len(self.query.extra_select) diff --git a/django/forms/fields.py b/django/forms/fields.py index 80550c232b..d90b8dbb51 100644 --- a/django/forms/fields.py +++ b/django/forms/fields.py @@ -14,7 +14,7 @@ from io import BytesIO from django.core import validators from django.core.exceptions import ValidationError -from django.forms.util import ErrorList, from_current_timezone, to_current_timezone +from django.forms.utils import ErrorList, from_current_timezone, to_current_timezone from django.forms.widgets import ( TextInput, NumberInput, EmailInput, URLInput, HiddenInput, MultipleHiddenInput, ClearableFileInput, CheckboxInput, Select, diff --git a/django/forms/forms.py b/django/forms/forms.py index e04d6a0781..64d1550066 100644 --- a/django/forms/forms.py +++ b/django/forms/forms.py @@ -10,7 +10,7 @@ import warnings from django.core.exceptions import ValidationError from django.forms.fields import Field, FileField -from django.forms.util import flatatt, ErrorDict, ErrorList +from django.forms.utils import flatatt, ErrorDict, ErrorList from django.forms.widgets import Media, media_property, TextInput, Textarea from django.utils.html import conditional_escape, format_html from django.utils.encoding import smart_text, force_text, python_2_unicode_compatible diff --git a/django/forms/formsets.py b/django/forms/formsets.py index 02b07a8604..59bfe33a63 100644 --- a/django/forms/formsets.py +++ b/django/forms/formsets.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals from django.core.exceptions import ValidationError from django.forms import Form from django.forms.fields import IntegerField, BooleanField -from django.forms.util import ErrorList +from django.forms.utils import ErrorList from django.forms.widgets import HiddenInput from django.utils.encoding import python_2_unicode_compatible from django.utils.functional import cached_property diff --git a/django/forms/models.py b/django/forms/models.py index f292606862..c05c452383 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -12,7 +12,7 @@ from django.core.exceptions import ValidationError, NON_FIELD_ERRORS, FieldError from django.forms.fields import Field, ChoiceField from django.forms.forms import BaseForm, get_declared_fields from django.forms.formsets import BaseFormSet, formset_factory -from django.forms.util import ErrorList +from django.forms.utils import ErrorList from django.forms.widgets import (SelectMultiple, HiddenInput, MultipleHiddenInput, media_property, CheckboxSelectMultiple) from django.utils.encoding import smart_text, force_text diff --git a/django/forms/util.py b/django/forms/util.py deleted file mode 100644 index bb5bb8c2e4..0000000000 --- a/django/forms/util.py +++ /dev/null @@ -1,117 +0,0 @@ -from __future__ import unicode_literals - -import warnings - -from django.conf import settings -from django.utils.html import format_html, format_html_join -from django.utils.encoding import force_text, python_2_unicode_compatible -from django.utils import timezone -from django.utils.translation import ugettext_lazy as _ -from django.utils import six -import sys - -# Import ValidationError so that it can be imported from this -# module to maintain backwards compatibility. -from django.core.exceptions import ValidationError - -def flatatt(attrs): - """ - Convert a dictionary of attributes to a single string. - The returned string will contain a leading space followed by key="value", - XML-style pairs. It is assumed that the keys do not need to be XML-escaped. - If the passed dictionary is empty, then return an empty string. - - The result is passed through 'mark_safe'. - """ - for attr_name, value in attrs.items(): - if type(value) is bool: - warnings.warn( - "In Django 1.8, widget attribute %(attr_name)s=%(bool_value)s " - "will %(action)s. To preserve current behavior, use the " - "string '%(bool_value)s' instead of the boolean value." % { - 'attr_name': attr_name, - 'action': "be rendered as '%s'" % attr_name if value else "not be rendered", - 'bool_value': value, - }, - DeprecationWarning - ) - return format_html_join('', ' {0}="{1}"', sorted(attrs.items())) - -@python_2_unicode_compatible -class ErrorDict(dict): - """ - A collection of errors that knows how to display itself in various formats. - - The dictionary keys are the field names, and the values are the errors. - """ - def __str__(self): - return self.as_ul() - - def as_ul(self): - if not self: return '' - return format_html('
    {0}
', - format_html_join('', '
  • {0}{1}
  • ', - ((k, force_text(v)) - for k, v in self.items()) - )) - - def as_text(self): - return '\n'.join('* %s\n%s' % (k, '\n'.join(' * %s' % force_text(i) for i in v)) for k, v in self.items()) - -@python_2_unicode_compatible -class ErrorList(list): - """ - A collection of errors that knows how to display itself in various formats. - """ - def __str__(self): - return self.as_ul() - - def as_ul(self): - if not self: return '' - return format_html('
      {0}
    ', - format_html_join('', '
  • {0}
  • ', - ((force_text(e),) for e in self) - ) - ) - - def as_text(self): - if not self: return '' - return '\n'.join('* %s' % force_text(e) for e in self) - - def __repr__(self): - return repr([force_text(e) for e in self]) - -# Utilities for time zone support in DateTimeField et al. - -def from_current_timezone(value): - """ - When time zone support is enabled, convert naive datetimes - entered in the current time zone to aware datetimes. - """ - if settings.USE_TZ and value is not None and timezone.is_naive(value): - current_timezone = timezone.get_current_timezone() - try: - return timezone.make_aware(value, current_timezone) - except Exception: - message = _( - '%(datetime)s couldn\'t be interpreted ' - 'in time zone %(current_timezone)s; it ' - 'may be ambiguous or it may not exist.' - ) - params = {'datetime': value, 'current_timezone': current_timezone} - six.reraise(ValidationError, ValidationError( - message, - code='ambiguous_timezone', - params=params, - ), sys.exc_info()[2]) - return value - -def to_current_timezone(value): - """ - When time zone support is enabled, convert aware datetimes - to naive dateimes in the current time zone for display. - """ - if settings.USE_TZ and value is not None and timezone.is_aware(value): - current_timezone = timezone.get_current_timezone() - return timezone.make_naive(value, current_timezone) - return value diff --git a/django/forms/utils.py b/django/forms/utils.py new file mode 100644 index 0000000000..bb5bb8c2e4 --- /dev/null +++ b/django/forms/utils.py @@ -0,0 +1,117 @@ +from __future__ import unicode_literals + +import warnings + +from django.conf import settings +from django.utils.html import format_html, format_html_join +from django.utils.encoding import force_text, python_2_unicode_compatible +from django.utils import timezone +from django.utils.translation import ugettext_lazy as _ +from django.utils import six +import sys + +# Import ValidationError so that it can be imported from this +# module to maintain backwards compatibility. +from django.core.exceptions import ValidationError + +def flatatt(attrs): + """ + Convert a dictionary of attributes to a single string. + The returned string will contain a leading space followed by key="value", + XML-style pairs. It is assumed that the keys do not need to be XML-escaped. + If the passed dictionary is empty, then return an empty string. + + The result is passed through 'mark_safe'. + """ + for attr_name, value in attrs.items(): + if type(value) is bool: + warnings.warn( + "In Django 1.8, widget attribute %(attr_name)s=%(bool_value)s " + "will %(action)s. To preserve current behavior, use the " + "string '%(bool_value)s' instead of the boolean value." % { + 'attr_name': attr_name, + 'action': "be rendered as '%s'" % attr_name if value else "not be rendered", + 'bool_value': value, + }, + DeprecationWarning + ) + return format_html_join('', ' {0}="{1}"', sorted(attrs.items())) + +@python_2_unicode_compatible +class ErrorDict(dict): + """ + A collection of errors that knows how to display itself in various formats. + + The dictionary keys are the field names, and the values are the errors. + """ + def __str__(self): + return self.as_ul() + + def as_ul(self): + if not self: return '' + return format_html('
      {0}
    ', + format_html_join('', '
  • {0}{1}
  • ', + ((k, force_text(v)) + for k, v in self.items()) + )) + + def as_text(self): + return '\n'.join('* %s\n%s' % (k, '\n'.join(' * %s' % force_text(i) for i in v)) for k, v in self.items()) + +@python_2_unicode_compatible +class ErrorList(list): + """ + A collection of errors that knows how to display itself in various formats. + """ + def __str__(self): + return self.as_ul() + + def as_ul(self): + if not self: return '' + return format_html('
      {0}
    ', + format_html_join('', '
  • {0}
  • ', + ((force_text(e),) for e in self) + ) + ) + + def as_text(self): + if not self: return '' + return '\n'.join('* %s' % force_text(e) for e in self) + + def __repr__(self): + return repr([force_text(e) for e in self]) + +# Utilities for time zone support in DateTimeField et al. + +def from_current_timezone(value): + """ + When time zone support is enabled, convert naive datetimes + entered in the current time zone to aware datetimes. + """ + if settings.USE_TZ and value is not None and timezone.is_naive(value): + current_timezone = timezone.get_current_timezone() + try: + return timezone.make_aware(value, current_timezone) + except Exception: + message = _( + '%(datetime)s couldn\'t be interpreted ' + 'in time zone %(current_timezone)s; it ' + 'may be ambiguous or it may not exist.' + ) + params = {'datetime': value, 'current_timezone': current_timezone} + six.reraise(ValidationError, ValidationError( + message, + code='ambiguous_timezone', + params=params, + ), sys.exc_info()[2]) + return value + +def to_current_timezone(value): + """ + When time zone support is enabled, convert aware datetimes + to naive dateimes in the current time zone for display. + """ + if settings.USE_TZ and value is not None and timezone.is_aware(value): + current_timezone = timezone.get_current_timezone() + return timezone.make_naive(value, current_timezone) + return value diff --git a/django/forms/widgets.py b/django/forms/widgets.py index ea701bdcf9..372c0f440b 100644 --- a/django/forms/widgets.py +++ b/django/forms/widgets.py @@ -9,7 +9,7 @@ from itertools import chain import warnings from django.conf import settings -from django.forms.util import flatatt, to_current_timezone +from django.forms.utils import flatatt, to_current_timezone from django.utils.datastructures import MultiValueDict, MergeDict from django.utils.html import conditional_escape, format_html from django.utils.translation import ugettext_lazy diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 054e3b32fc..9e473dfa46 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -444,6 +444,15 @@ these changes. * ``ModelAdmin.declared_fieldsets`` will be removed. +* Instances of ``util.py`` in the Django codebase have been renamed to + ``utils.py`` in an effort to unify all util and utils references. + The modules that provided backwards compatability will be removed: + + * ``django.contrib.admin.util`` + * ``django.contrib.gis.db.backends.util`` + * ``django.db.backends.util`` + * ``django.forms.util`` + 2.0 --- diff --git a/docs/ref/forms/api.txt b/docs/ref/forms/api.txt index f084273cd9..977da4b7be 100644 --- a/docs/ref/forms/api.txt +++ b/docs/ref/forms/api.txt @@ -571,12 +571,12 @@ method you're using:: Customizing the error list format ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -By default, forms use ``django.forms.util.ErrorList`` to format validation +By default, forms use ``django.forms.utils.ErrorList`` to format validation errors. If you'd like to use an alternate class for displaying errors, you can pass that in at construction time (replace ``__unicode__`` by ``__str__`` on Python 3):: - >>> from django.forms.util import ErrorList + >>> from django.forms.utils import ErrorList >>> class DivErrorList(ErrorList): ... def __unicode__(self): ... return self.as_divs() @@ -592,6 +592,10 @@ Python 3)::

    Sender:

    Cc myself:

    +.. versionchanged:: 1.7 + + ``django.forms.util`` was renamed to ``django.forms.utils``. + More granular output ~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/ref/forms/validation.txt b/docs/ref/forms/validation.txt index b6113098bb..c1e619113c 100644 --- a/docs/ref/forms/validation.txt +++ b/docs/ref/forms/validation.txt @@ -219,10 +219,10 @@ through the ``Form.non_field_errors()`` method. When you really do need to attach the error to a particular field, you should store (or amend) a key in the ``Form._errors`` attribute. This attribute is an -instance of a ``django.forms.util.ErrorDict`` class. Essentially, though, it's +instance of a ``django.forms.utils.ErrorDict`` class. Essentially, though, it's just a dictionary. There is a key in the dictionary for each field in the form that has an error. Each value in the dictionary is a -``django.forms.util.ErrorList`` instance, which is a list that knows how to +``django.forms.utils.ErrorList`` instance, which is a list that knows how to display itself in different ways. So you can treat ``_errors`` as a dictionary mapping field names to lists. diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index 3e247fd211..685efe04b3 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -460,3 +460,14 @@ a bug and has been addressed. The ``syncdb`` command has been deprecated in favour of the new ``migrate`` command. ``migrate`` takes the same arguments as ``syncdb`` used to plus a few more, so it's safe to just change the name you're calling and nothing else. + +``util`` modules renamed to ``utils`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following instances of ``util.py`` in the Django codebase have been renamed +to ``utils.py`` in an effort to unify all util and utils references: + +* ``django.contrib.admin.util`` +* ``django.contrib.gis.db.backends.util`` +* ``django.db.backends.util`` +* ``django.forms.util`` diff --git a/docs/topics/forms/formsets.txt b/docs/topics/forms/formsets.txt index 8be6e10ff6..384187bdaf 100644 --- a/docs/topics/forms/formsets.txt +++ b/docs/topics/forms/formsets.txt @@ -215,7 +215,7 @@ this management data, an exception will be raised:: >>> formset = ArticleFormSet(data) Traceback (most recent call last): ... - django.forms.util.ValidationError: [u'ManagementForm data is missing or has been tampered with'] + django.forms.utils.ValidationError: [u'ManagementForm data is missing or has been tampered with'] It is used to keep track of how many form instances are being displayed. If you are adding new forms via JavaScript, you should increment the count fields diff --git a/tests/admin_custom_urls/tests.py b/tests/admin_custom_urls/tests.py index 257638afb1..1dc9da7efd 100644 --- a/tests/admin_custom_urls/tests.py +++ b/tests/admin_custom_urls/tests.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals import warnings -from django.contrib.admin.util import quote +from django.contrib.admin.utils import quote from django.core.urlresolvers import reverse from django.template.response import TemplateResponse from django.test import TestCase diff --git a/tests/admin_util/tests.py b/tests/admin_util/tests.py index 8c63e90ce1..31bb412af4 100644 --- a/tests/admin_util/tests.py +++ b/tests/admin_util/tests.py @@ -5,7 +5,7 @@ from datetime import datetime from django.conf import settings from django.contrib import admin from django.contrib.admin import helpers -from django.contrib.admin.util import (display_for_field, flatten_fieldsets, +from django.contrib.admin.utils import (display_for_field, flatten_fieldsets, label_for_field, lookup_field, NestedObjects) from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE from django.contrib.sites.models import Site diff --git a/tests/admin_views/admin.py b/tests/admin_views/admin.py index 5649a3c492..8d864378ce 100644 --- a/tests/admin_views/admin.py +++ b/tests/admin_views/admin.py @@ -768,7 +768,7 @@ site.register(UnchangeableObject, UnchangeableObjectAdmin) # related OneToOne object not registered in admin # when deleting Book so as exercise all four troublesome (w.r.t escaping # and calling force_text to avoid problems on Python 2.3) paths through -# contrib.admin.util's get_deleted_objects function. +# contrib.admin.utils's get_deleted_objects function. site.register(Book, inlines=[ChapterInline]) site.register(Promo) site.register(ChapterXtra1, ChapterXtra1Admin) diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py index 75e8a51acd..4793a27f23 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -15,14 +15,14 @@ from django.contrib.auth import get_permission_codename from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME from django.contrib.admin.models import LogEntry, DELETION from django.contrib.admin.sites import LOGIN_FORM_KEY -from django.contrib.admin.util import quote +from django.contrib.admin.utils import quote from django.contrib.admin.views.main import IS_POPUP_VAR from django.contrib.admin.tests import AdminSeleniumWebDriverTestCase from django.contrib.auth import REDIRECT_FIELD_NAME from django.contrib.auth.models import Group, User, Permission from django.contrib.contenttypes.models import ContentType from django.db import connection -from django.forms.util import ErrorList +from django.forms.utils import ErrorList from django.template.response import TemplateResponse from django.test import TestCase from django.test.utils import patch_logger diff --git a/tests/backends/tests.py b/tests/backends/tests.py index 903aedec20..a6badac9a9 100644 --- a/tests/backends/tests.py +++ b/tests/backends/tests.py @@ -15,7 +15,7 @@ from django.db import (connection, connections, DEFAULT_DB_ALIAS, from django.db.backends.signals import connection_created from django.db.backends.sqlite3.base import DatabaseOperations from django.db.backends.postgresql_psycopg2 import version as pg_version -from django.db.backends.util import format_number +from django.db.backends.utils import format_number from django.db.models import Sum, Avg, Variance, StdDev from django.db.models.fields import (AutoField, DateField, DateTimeField, DecimalField, IntegerField, TimeField) diff --git a/tests/db_typecasts/tests.py b/tests/db_typecasts/tests.py index fc5305dcdd..27b89c0c8c 100644 --- a/tests/db_typecasts/tests.py +++ b/tests/db_typecasts/tests.py @@ -3,7 +3,7 @@ import datetime import unittest -from django.db.backends import util as typecasts +from django.db.backends import utils as typecasts from django.utils import six diff --git a/tests/forms_tests/tests/test_error_messages.py b/tests/forms_tests/tests/test_error_messages.py index 2b1bec1647..a884bdc75b 100644 --- a/tests/forms_tests/tests/test_error_messages.py +++ b/tests/forms_tests/tests/test_error_messages.py @@ -215,7 +215,7 @@ class FormsErrorMessagesTestCase(TestCase, AssertFormErrorsMixin): raise ValidationError("I like to be awkward.") @python_2_unicode_compatible - class CustomErrorList(util.ErrorList): + class CustomErrorList(utils.ErrorList): def __str__(self): return self.as_divs() diff --git a/tests/forms_tests/tests/test_extra.py b/tests/forms_tests/tests/test_extra.py index 21186682d5..d0409155dd 100644 --- a/tests/forms_tests/tests/test_extra.py +++ b/tests/forms_tests/tests/test_extra.py @@ -5,7 +5,7 @@ import datetime from django.forms import * from django.forms.extras import SelectDateWidget -from django.forms.util import ErrorList +from django.forms.utils import ErrorList from django.test import TestCase from django.test.utils import override_settings from django.utils import six diff --git a/tests/forms_tests/tests/test_fields.py b/tests/forms_tests/tests/test_fields.py index 6f305d78c4..990aa9d1f9 100644 --- a/tests/forms_tests/tests/test_fields.py +++ b/tests/forms_tests/tests/test_fields.py @@ -1142,7 +1142,7 @@ class FieldsTests(SimpleTestCase): ('/django/forms/forms.py', 'forms.py'), ('/django/forms/formsets.py', 'formsets.py'), ('/django/forms/models.py', 'models.py'), - ('/django/forms/util.py', 'util.py'), + ('/django/forms/utils.py', 'utils.py'), ('/django/forms/widgets.py', 'widgets.py') ] for exp, got in zip(expected, fix_os_paths(f.choices)): @@ -1162,7 +1162,7 @@ class FieldsTests(SimpleTestCase): ('/django/forms/forms.py', 'forms.py'), ('/django/forms/formsets.py', 'formsets.py'), ('/django/forms/models.py', 'models.py'), - ('/django/forms/util.py', 'util.py'), + ('/django/forms/utils.py', 'utils.py'), ('/django/forms/widgets.py', 'widgets.py') ] for exp, got in zip(expected, fix_os_paths(f.choices)): @@ -1182,7 +1182,7 @@ class FieldsTests(SimpleTestCase): ('/django/forms/forms.py', 'forms.py'), ('/django/forms/formsets.py', 'formsets.py'), ('/django/forms/models.py', 'models.py'), - ('/django/forms/util.py', 'util.py'), + ('/django/forms/utils.py', 'utils.py'), ('/django/forms/widgets.py', 'widgets.py') ] for exp, got in zip(expected, fix_os_paths(f.choices)): diff --git a/tests/forms_tests/tests/test_formsets.py b/tests/forms_tests/tests/test_formsets.py index bd34babef6..12d68283cb 100644 --- a/tests/forms_tests/tests/test_formsets.py +++ b/tests/forms_tests/tests/test_formsets.py @@ -6,7 +6,7 @@ import datetime from django.forms import (CharField, DateField, FileField, Form, IntegerField, SplitDateTimeField, ValidationError, formsets) from django.forms.formsets import BaseFormSet, formset_factory -from django.forms.util import ErrorList +from django.forms.utils import ErrorList from django.test import TestCase diff --git a/tests/forms_tests/tests/test_util.py b/tests/forms_tests/tests/test_util.py index c2d213ae23..87f9f0d048 100644 --- a/tests/forms_tests/tests/test_util.py +++ b/tests/forms_tests/tests/test_util.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from django.core.exceptions import ValidationError -from django.forms.util import flatatt, ErrorDict, ErrorList +from django.forms.utils import flatatt, ErrorDict, ErrorList from django.test import TestCase from django.utils.safestring import mark_safe from django.utils import six @@ -11,7 +11,7 @@ from django.utils.encoding import python_2_unicode_compatible class FormsUtilTestCase(TestCase): - # Tests for forms/util.py module. + # Tests for forms/utils.py module. def test_flatatt(self): ########### diff --git a/tests/model_formsets_regress/tests.py b/tests/model_formsets_regress/tests.py index 782c7d6fbc..6715b5b10e 100644 --- a/tests/model_formsets_regress/tests.py +++ b/tests/model_formsets_regress/tests.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from django import forms from django.forms.formsets import BaseFormSet, DELETION_FIELD_NAME -from django.forms.util import ErrorDict, ErrorList +from django.forms.utils import ErrorDict, ErrorList from django.forms.models import modelform_factory, inlineformset_factory, modelformset_factory, BaseModelFormSet from django.test import TestCase from django.utils import six diff --git a/tests/proxy_models/tests.py b/tests/proxy_models/tests.py index f9cb132225..988996c9f1 100644 --- a/tests/proxy_models/tests.py +++ b/tests/proxy_models/tests.py @@ -382,7 +382,7 @@ class ProxyModelAdminTests(TestCase): base_user = BaseUser.objects.all()[0] issue = Issue.objects.all()[0] with self.assertNumQueries(7): - collector = admin.util.NestedObjects('default') + collector = admin.utils.NestedObjects('default') collector.collect(ProxyTrackerUser.objects.all()) self.assertTrue(tracker_user in collector.edges.get(None, ())) self.assertTrue(base_user in collector.edges.get(None, ())) -- cgit v1.3