diff options
| author | Adrian Holovaty <adrian@holovaty.com> | 2007-07-01 01:17:51 +0000 |
|---|---|---|
| committer | Adrian Holovaty <adrian@holovaty.com> | 2007-07-01 01:17:51 +0000 |
| commit | 6f4e933fcc78da11ec43214efd3472192030eced (patch) | |
| tree | d92140118d3afa00bcd909f475dd1a694c15c08f /django/core | |
| parent | 7a981380def046f753ff70a7c25efcd781ff6ac6 (diff) | |
newforms-admin: Merged to [5571]
git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@5572 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'django/core')
| -rw-r--r-- | django/core/mail.py | 100 | ||||
| -rw-r--r-- | django/core/management.py | 119 | ||||
| -rw-r--r-- | django/core/servers/basehttp.py | 5 | ||||
| -rw-r--r-- | django/core/urlresolvers.py | 92 |
4 files changed, 252 insertions, 64 deletions
diff --git a/django/core/mail.py b/django/core/mail.py index 8661d84287..e9d8a150a1 100644 --- a/django/core/mail.py +++ b/django/core/mail.py @@ -3,10 +3,13 @@ Tools for sending email. """ from django.conf import settings +from email import Charset, Encoders from email.MIMEText import MIMEText +from email.MIMEMultipart import MIMEMultipart +from email.MIMEBase import MIMEBase from email.Header import Header from email.Utils import formatdate -from email import Charset +import mimetypes import os import smtplib import socket @@ -17,6 +20,10 @@ import random # some spam filters. Charset.add_charset('utf-8', Charset.SHORTEST, Charset.QP, 'utf-8') +# Default MIME type to use on attachments (if it is not explicitly given +# and cannot be guessed). +DEFAULT_ATTACHMENT_MIME_TYPE = 'application/octet-stream' + # Cache the hostname, but do it lazily: socket.getfqdn() can take a couple of # seconds, which slows down the restart of the server. class CachedDnsName(object): @@ -55,14 +62,22 @@ def make_msgid(idstring=None): class BadHeaderError(ValueError): pass -class SafeMIMEText(MIMEText): +class SafeHeaderMixin(object): def __setitem__(self, name, val): "Forbids multi-line headers, to prevent header injection." if '\n' in val or '\r' in val: raise BadHeaderError, "Header values can't contain newlines (got %r for header %r)" % (val, name) if name == "Subject": val = Header(val, settings.DEFAULT_CHARSET) - MIMEText.__setitem__(self, name, val) + # Note: using super() here is safe; any __setitem__ overrides must use + # the same argument signature. + super(SafeHeaderMixin, self).__setitem__(name, val) + +class SafeMIMEText(MIMEText, SafeHeaderMixin): + pass + +class SafeMIMEMultipart(MIMEMultipart, SafeHeaderMixin): + pass class SMTPConnection(object): """ @@ -154,12 +169,19 @@ class EmailMessage(object): """ A container for email information. """ - def __init__(self, subject='', body='', from_email=None, to=None, bcc=None, connection=None): + content_subtype = 'plain' + multipart_subtype = 'mixed' + encoding = None # None => use settings default + + def __init__(self, subject='', body='', from_email=None, to=None, bcc=None, + connection=None, attachments=None, headers=None): self.to = to or [] self.bcc = bcc or [] self.from_email = from_email or settings.DEFAULT_FROM_EMAIL self.subject = subject self.body = body + self.attachments = attachments or [] + self.extra_headers = headers or {} self.connection = connection def get_connection(self, fail_silently=False): @@ -168,7 +190,18 @@ class EmailMessage(object): return self.connection def message(self): - msg = SafeMIMEText(self.body, 'plain', settings.DEFAULT_CHARSET) + encoding = self.encoding or settings.DEFAULT_CHARSET + msg = SafeMIMEText(self.body, self.content_subtype, encoding) + if self.attachments: + body_msg = msg + msg = SafeMIMEMultipart(_subtype=self.multipart_subtype) + if self.body: + msg.attach(body_msg) + for attachment in self.attachments: + if isinstance(attachment, MIMEBase): + msg.attach(attachment) + else: + msg.attach(self._create_attachment(*attachment)) msg['Subject'] = self.subject msg['From'] = self.from_email msg['To'] = ', '.join(self.to) @@ -176,6 +209,8 @@ class EmailMessage(object): msg['Message-ID'] = make_msgid() if self.bcc: msg['Bcc'] = ', '.join(self.bcc) + for name, value in self.extra_headers.items(): + msg[name] = value return msg def recipients(self): @@ -189,6 +224,61 @@ class EmailMessage(object): """Send the email message.""" return self.get_connection(fail_silently).send_messages([self]) + def attach(self, filename=None, content=None, mimetype=None): + """ + Attaches a file with the given filename and content. The filename can + be omitted (useful for multipart/alternative messages) and the mimetype + is guessed, if not provided. + + If the first parameter is a MIMEBase subclass it is inserted directly + into the resulting message attachments. + """ + if isinstance(filename, MIMEBase): + assert content == mimetype == None + self.attachements.append(filename) + else: + assert content is not None + self.attachments.append((filename, content, mimetype)) + + def attach_file(self, path, mimetype=None): + """Attaches a file from the filesystem.""" + filename = os.path.basename(path) + content = open(path, 'rb').read() + self.attach(filename, content, mimetype) + + def _create_attachment(self, filename, content, mimetype=None): + """ + Convert the filename, content, mimetype triple into a MIME attachment + object. + """ + if mimetype is None: + mimetype, _ = mimetypes.guess_type(filename) + if mimetype is None: + mimetype = DEFAULT_ATTACHMENT_MIME_TYPE + basetype, subtype = mimetype.split('/', 1) + if basetype == 'text': + attachment = SafeMIMEText(content, subtype, settings.DEFAULT_CHARSET) + else: + # Encode non-text attachments with base64. + attachment = MIMEBase(basetype, subtype) + attachment.set_payload(content) + Encoders.encode_base64(attachment) + if filename: + attachment.add_header('Content-Disposition', 'attachment', filename=filename) + return attachment + +class EmailMultiAlternatives(EmailMessage): + """ + A version of EmailMessage that makes it easy to send multipart/alternative + messages. For example, including text and HTML versions of the text is + made easier. + """ + multipart_subtype = 'alternative' + + def attach_alternative(self, content, mimetype=None): + """Attach an alternative content representation.""" + self.attach(content=content, mimetype=mimetype) + def send_mail(subject, message, from_email, recipient_list, fail_silently=False, auth_user=None, auth_password=None): """ Easy wrapper for sending a single message to a recipient list. All members diff --git a/django/core/management.py b/django/core/management.py index 29ee9c2f90..1c049a790b 100644 --- a/django/core/management.py +++ b/django/core/management.py @@ -7,9 +7,10 @@ from optparse import OptionParser from django.utils import termcolors import os, re, shutil, sys, textwrap -# For Python 2.3 -if not hasattr(__builtins__, 'set'): - from sets import Set as set +try: + set +except NameError: + from sets import Set as set # Python 2.3 fallback # For backwards compatibility: get_version() used to be in this module. get_version = django.get_version @@ -58,12 +59,16 @@ def _is_valid_dir_name(s): def _get_installed_models(table_list): "Gets a set of all models that are installed, given a list of existing tables" - from django.db import models + from django.db import backend, models all_models = [] for app in models.get_apps(): for model in models.get_models(app): all_models.append(model) - return set([m for m in all_models if m._meta.db_table in table_list]) + if backend.uses_case_insensitive_names: + converter = str.upper + else: + converter = lambda x: x + return set([m for m in all_models if converter(m._meta.db_table) in map(converter, table_list)]) def _get_table_list(): "Gets a list of all db tables that are physically installed." @@ -99,6 +104,7 @@ get_rel_data_type = lambda f: (f.get_internal_type() in ('AutoField', 'PositiveI def get_sql_create(app): "Returns a list of the CREATE TABLE SQL statements for the given app." from django.db import get_creation_module, models + data_types = get_creation_module().DATA_TYPES if not data_types: @@ -170,15 +176,20 @@ def _get_sql_model_create(model, known_models=set()): rel_field = f data_type = f.get_internal_type() col_type = data_types[data_type] + tablespace = f.db_tablespace or opts.db_tablespace if col_type is not None: # Make the definition (e.g. 'foo VARCHAR(30)') for this field. field_output = [style.SQL_FIELD(backend.quote_name(f.column)), style.SQL_COLTYPE(col_type % rel_field.__dict__)] field_output.append(style.SQL_KEYWORD('%sNULL' % (not f.null and 'NOT ' or ''))) - if f.unique: + if f.unique and (not f.primary_key or backend.allows_unique_and_pk): field_output.append(style.SQL_KEYWORD('UNIQUE')) if f.primary_key: field_output.append(style.SQL_KEYWORD('PRIMARY KEY')) + if tablespace and backend.supports_tablespaces and (f.unique or f.primary_key) and backend.autoindexes_primary_keys: + # We must specify the index tablespace inline, because we + # won't be generating a CREATE INDEX statement for this field. + field_output.append(backend.get_tablespace_sql(tablespace, inline=True)) if f.rel: if f.rel.to in known_models: field_output.append(style.SQL_KEYWORD('REFERENCES') + ' ' + \ @@ -202,9 +213,19 @@ def _get_sql_model_create(model, known_models=set()): full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE(backend.quote_name(opts.db_table)) + ' ('] for i, line in enumerate(table_output): # Combine and add commas. full_statement.append(' %s%s' % (line, i < len(table_output)-1 and ',' or '')) - full_statement.append(');') + full_statement.append(')') + if opts.db_tablespace and backend.supports_tablespaces: + full_statement.append(backend.get_tablespace_sql(opts.db_tablespace)) + full_statement.append(';') final_output.append('\n'.join(full_statement)) + if opts.has_auto_field and hasattr(backend, 'get_autoinc_sql'): + # Add any extra SQL needed to support auto-incrementing primary keys + autoinc_sql = backend.get_autoinc_sql(opts.db_table) + if autoinc_sql: + for stmt in autoinc_sql: + final_output.append(stmt) + return final_output, pending_references def _get_sql_for_pending_references(model, pending_references): @@ -212,6 +233,7 @@ def _get_sql_for_pending_references(model, pending_references): Get any ALTER TABLE statements to add constraints after the fact. """ from django.db import backend, get_creation_module + from django.db.backends.util import truncate_name data_types = get_creation_module().DATA_TYPES final_output = [] @@ -228,7 +250,7 @@ def _get_sql_for_pending_references(model, pending_references): # So we are careful with character usage here. r_name = '%s_refs_%s_%x' % (r_col, col, abs(hash((r_table, table)))) final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % \ - (backend.quote_name(r_table), r_name, + (backend.quote_name(r_table), truncate_name(r_name, backend.get_max_name_length()), backend.quote_name(r_col), backend.quote_name(table), backend.quote_name(col), backend.get_deferrable_sql())) del pending_references[model] @@ -244,12 +266,18 @@ def _get_many_to_many_sql_for_model(model): final_output = [] for f in opts.many_to_many: if not isinstance(f.rel, generic.GenericRel): + tablespace = f.db_tablespace or opts.db_tablespace + if tablespace and backend.supports_tablespaces and backend.autoindexes_primary_keys: + tablespace_sql = ' ' + backend.get_tablespace_sql(tablespace, inline=True) + else: + tablespace_sql = '' table_output = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + \ style.SQL_TABLE(backend.quote_name(f.m2m_db_table())) + ' ('] - table_output.append(' %s %s %s,' % \ + table_output.append(' %s %s %s%s,' % \ (style.SQL_FIELD(backend.quote_name('id')), style.SQL_COLTYPE(data_types['AutoField']), - style.SQL_KEYWORD('NOT NULL PRIMARY KEY'))) + style.SQL_KEYWORD('NOT NULL PRIMARY KEY'), + tablespace_sql)) table_output.append(' %s %s %s %s (%s)%s,' % \ (style.SQL_FIELD(backend.quote_name(f.m2m_column_name())), style.SQL_COLTYPE(data_types[get_rel_data_type(opts.pk)] % opts.pk.__dict__), @@ -264,17 +292,30 @@ def _get_many_to_many_sql_for_model(model): style.SQL_TABLE(backend.quote_name(f.rel.to._meta.db_table)), style.SQL_FIELD(backend.quote_name(f.rel.to._meta.pk.column)), backend.get_deferrable_sql())) - table_output.append(' %s (%s, %s)' % \ + table_output.append(' %s (%s, %s)%s' % \ (style.SQL_KEYWORD('UNIQUE'), style.SQL_FIELD(backend.quote_name(f.m2m_column_name())), - style.SQL_FIELD(backend.quote_name(f.m2m_reverse_name())))) - table_output.append(');') + style.SQL_FIELD(backend.quote_name(f.m2m_reverse_name())), + tablespace_sql)) + table_output.append(')') + if opts.db_tablespace and backend.supports_tablespaces: + # f.db_tablespace is only for indices, so ignore its value here. + table_output.append(backend.get_tablespace_sql(opts.db_tablespace)) + table_output.append(';') final_output.append('\n'.join(table_output)) + + # Add any extra SQL needed to support auto-incrementing PKs + autoinc_sql = backend.get_autoinc_sql(f.m2m_db_table()) + if autoinc_sql: + for stmt in autoinc_sql: + final_output.append(stmt) + return final_output def get_sql_delete(app): "Returns a list of the DROP TABLE SQL statements for the given app." from django.db import backend, connection, models, get_introspection_module + from django.db.backends.util import truncate_name introspection = get_introspection_module() # This should work even if a connection isn't available @@ -288,6 +329,10 @@ def get_sql_delete(app): table_names = introspection.get_table_list(cursor) else: table_names = [] + if backend.uses_case_insensitive_names: + table_name_converter = str.upper + else: + table_name_converter = lambda x: x output = [] @@ -297,7 +342,7 @@ def get_sql_delete(app): references_to_delete = {} app_models = models.get_models(app) for model in app_models: - if cursor and model._meta.db_table in table_names: + if cursor and table_name_converter(model._meta.db_table) in table_names: # The table exists, so it needs to be dropped opts = model._meta for f in opts.fields: @@ -307,7 +352,7 @@ def get_sql_delete(app): to_delete.add(model) for model in app_models: - if cursor and model._meta.db_table in table_names: + if cursor and table_name_converter(model._meta.db_table) in table_names: # Drop the table now output.append('%s %s;' % (style.SQL_KEYWORD('DROP TABLE'), style.SQL_TABLE(backend.quote_name(model._meta.db_table)))) @@ -317,20 +362,26 @@ def get_sql_delete(app): col = f.column r_table = model._meta.db_table r_col = model._meta.get_field(f.rel.field_name).column + r_name = '%s_refs_%s_%x' % (col, r_col, abs(hash((table, r_table)))) output.append('%s %s %s %s;' % \ (style.SQL_KEYWORD('ALTER TABLE'), style.SQL_TABLE(backend.quote_name(table)), style.SQL_KEYWORD(backend.get_drop_foreignkey_sql()), - style.SQL_FIELD(backend.quote_name('%s_refs_%s_%x' % (col, r_col, abs(hash((table, r_table)))))))) + style.SQL_FIELD(truncate_name(r_name, backend.get_max_name_length())))) del references_to_delete[model] + if model._meta.has_auto_field and hasattr(backend, 'get_drop_sequence'): + output.append(backend.get_drop_sequence(model._meta.db_table)) # Output DROP TABLE statements for many-to-many tables. for model in app_models: opts = model._meta for f in opts.many_to_many: - if cursor and f.m2m_db_table() in table_names: + if cursor and table_name_converter(f.m2m_db_table()) in table_names: output.append("%s %s;" % (style.SQL_KEYWORD('DROP TABLE'), style.SQL_TABLE(backend.quote_name(f.m2m_db_table())))) + if hasattr(backend, 'get_drop_sequence'): + output.append(backend.get_drop_sequence("%s_%s" % (model._meta.db_table, f.column))) + app_label = app_models[0]._meta.app_label @@ -429,14 +480,20 @@ def get_sql_indexes_for_model(model): output = [] for f in model._meta.fields: - if f.db_index: + if f.db_index and not ((f.primary_key or f.unique) and backend.autoindexes_primary_keys): unique = f.unique and 'UNIQUE ' or '' + tablespace = f.db_tablespace or model._meta.db_tablespace + if tablespace and backend.supports_tablespaces: + tablespace_sql = ' ' + backend.get_tablespace_sql(tablespace) + else: + tablespace_sql = '' output.append( style.SQL_KEYWORD('CREATE %sINDEX' % unique) + ' ' + \ style.SQL_TABLE(backend.quote_name('%s_%s' % (model._meta.db_table, f.column))) + ' ' + \ style.SQL_KEYWORD('ON') + ' ' + \ style.SQL_TABLE(backend.quote_name(model._meta.db_table)) + ' ' + \ - "(%s);" % style.SQL_FIELD(backend.quote_name(f.column)) + "(%s)" % style.SQL_FIELD(backend.quote_name(f.column)) + \ + "%s;" % tablespace_sql ) return output @@ -460,7 +517,7 @@ def _emit_post_sync_signal(created_models, verbosity, interactive): def syncdb(verbosity=1, interactive=True): "Creates the database tables for all apps in INSTALLED_APPS whose tables haven't already been created." - from django.db import connection, transaction, models, get_creation_module + from django.db import backend, connection, transaction, models, get_creation_module from django.conf import settings disable_termcolors() @@ -483,6 +540,10 @@ def syncdb(verbosity=1, interactive=True): # Get a list of all existing database tables, # so we know what needs to be added. table_list = _get_table_list() + if backend.uses_case_insensitive_names: + table_name_converter = str.upper + else: + table_name_converter = lambda x: x # Get a list of already installed *models* so that references work right. seen_models = _get_installed_models(table_list) @@ -497,7 +558,7 @@ def syncdb(verbosity=1, interactive=True): # Create the model's database table, if it doesn't already exist. if verbosity >= 2: print "Processing %s.%s model" % (app_name, model._meta.object_name) - if model._meta.db_table in table_list: + if table_name_converter(model._meta.db_table) in table_list: continue sql, references = _get_sql_model_create(model, seen_models) seen_models.add(model) @@ -509,7 +570,7 @@ def syncdb(verbosity=1, interactive=True): print "Creating table %s" % model._meta.db_table for statement in sql: cursor.execute(statement) - table_list.append(model._meta.db_table) + table_list.append(table_name_converter(model._meta.db_table)) # Create the m2m tables. This must be done after all tables have been created # to ensure that all referred tables will exist. @@ -828,7 +889,7 @@ def inspectdb(): except NotImplementedError: indexes = {} for i, row in enumerate(introspection_module.get_table_description(cursor, table_name)): - att_name = row[0] + att_name = row[0].lower() comment_notes = [] # Holds Field notes, to be displayed in a Python comment. extra_params = {} # Holds Field parameters such as 'db_column'. @@ -1324,7 +1385,7 @@ def load_data(fixture_labels, verbosity=1): # Keep a count of the installed objects and fixtures count = [0,0] models = set() - + humanize = lambda dirname: dirname and "'%s'" % dirname or 'absolute path' # Get a cursor (even though we don't need one yet). This has @@ -1402,7 +1463,7 @@ def load_data(fixture_labels, verbosity=1): if verbosity > 1: print "No %s fixture '%s' in %s." % \ (format, fixture_name, humanize(fixture_dir)) - + if count[0] > 0: sequence_sql = backend.get_sql_sequence_reset(style, models) if sequence_sql: @@ -1410,10 +1471,10 @@ def load_data(fixture_labels, verbosity=1): print "Resetting sequences" for line in sequence_sql: cursor.execute(line) - + transaction.commit() transaction.leave_transaction_management() - + if count[0] == 0: if verbosity > 0: print "No fixtures found." @@ -1628,7 +1689,9 @@ def execute_from_command_line(action_mapping=DEFAULT_ACTION_MAPPING, argv=None): if not mod_list: parser.print_usage_and_exit() if action not in NO_SQL_TRANSACTION: - print style.SQL_KEYWORD("BEGIN;") + from django.db import backend + if backend.get_start_transaction_sql(): + print style.SQL_KEYWORD(backend.get_start_transaction_sql()) for mod in mod_list: if action == 'reset': output = action_mapping[action](mod, options.interactive) diff --git a/django/core/servers/basehttp.py b/django/core/servers/basehttp.py index 80a0bf6a91..64ed739473 100644 --- a/django/core/servers/basehttp.py +++ b/django/core/servers/basehttp.py @@ -9,7 +9,7 @@ been reviewed for security issues. Don't use it for production use. from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer from types import ListType, StringType -import os, re, sys, time, urllib +import os, re, sys, time, urllib, mimetypes __version__ = "0.1" __all__ = ['WSGIServer','WSGIRequestHandler','demo_app'] @@ -630,6 +630,9 @@ class AdminMediaHandler(object): else: status = '200 OK' headers = {} + mime_type = mimetypes.guess_type(file_path)[0] + if mime_type: + headers['Content-Type'] = mime_type output = [fp.read()] fp.close() start_response(status, headers.items()) diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py index 38b3263da1..f4c2dc4677 100644 --- a/django/core/urlresolvers.py +++ b/django/core/urlresolvers.py @@ -9,8 +9,17 @@ a string) and returns a tuple in this format: from django.http import Http404 from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist +from django.utils.functional import memoize import re +try: + reversed +except NameError: + from django.utils.itercompat import reversed # Python 2.3 fallback + +_resolver_cache = {} # Maps urlconf modules to RegexURLResolver instances. +_callable_cache = {} # Maps view and url pattern names to their view functions. + class Resolver404(Http404): pass @@ -18,6 +27,34 @@ class NoReverseMatch(Exception): # Don't make this raise an error when used in a template. silent_variable_failure = True +def get_callable(lookup_view, can_fail=False): + """ + Convert a string version of a function name to the callable object. + + If the lookup_view is not an import path, it is assumed to be a URL pattern + label and the original string is returned. + + If can_fail is True, lookup_view might be a URL pattern label, so errors + during the import fail and the string is returned. + """ + if not callable(lookup_view): + mod_name, func_name = get_mod_func(lookup_view) + try: + if func_name != '': + lookup_view = getattr(__import__(mod_name, {}, {}, ['']), func_name) + except (ImportError, AttributeError): + if not can_fail: + raise + return lookup_view +get_callable = memoize(get_callable, _callable_cache) + +def get_resolver(urlconf): + if urlconf is None: + from django.conf import settings + urlconf = settings.ROOT_URLCONF + return RegexURLResolver(r'^/', urlconf) +get_resolver = memoize(get_resolver, _resolver_cache) + def get_mod_func(callback): # Converts 'django.views.news.stories.story_detail' to # ['django.views.news.stories', 'story_detail'] @@ -129,12 +166,13 @@ class RegexURLPattern(object): def _get_callback(self): if self._callback is not None: return self._callback - mod_name, func_name = get_mod_func(self._callback_str) try: - self._callback = getattr(__import__(mod_name, {}, {}, ['']), func_name) + self._callback = get_callable(self._callback_str) except ImportError, e: + mod_name, _ = get_mod_func(self._callback_str) raise ViewDoesNotExist, "Could not import %s. Error was: %s" % (mod_name, str(e)) except AttributeError, e: + mod_name, func_name = get_mod_func(self._callback_str) raise ViewDoesNotExist, "Tried %s in module %s. Error was: %s" % (func_name, mod_name, str(e)) return self._callback callback = property(_get_callback) @@ -160,6 +198,19 @@ class RegexURLResolver(object): self.urlconf_name = urlconf_name self.callback = None self.default_kwargs = default_kwargs or {} + self._reverse_dict = {} + + def _get_reverse_dict(self): + if not self._reverse_dict and hasattr(self.urlconf_module, 'urlpatterns'): + for pattern in reversed(self.urlconf_module.urlpatterns): + if isinstance(pattern, RegexURLResolver): + for key, value in pattern.reverse_dict.iteritems(): + self._reverse_dict[key] = (pattern,) + value + else: + self._reverse_dict[pattern.callback] = (pattern,) + self._reverse_dict[pattern.name] = (pattern,) + return self._reverse_dict + reverse_dict = property(_get_reverse_dict) def resolve(self, path): tried = [] @@ -209,24 +260,12 @@ class RegexURLResolver(object): return self._resolve_special('500') def reverse(self, lookup_view, *args, **kwargs): - if not callable(lookup_view): - mod_name, func_name = get_mod_func(lookup_view) - try: - lookup_view = getattr(__import__(mod_name, {}, {}, ['']), func_name) - except (ImportError, AttributeError): - if func_name != '': - raise NoReverseMatch - for pattern in self.urlconf_module.urlpatterns: - if isinstance(pattern, RegexURLResolver): - try: - return pattern.reverse_helper(lookup_view, *args, **kwargs) - except NoReverseMatch: - continue - elif pattern.callback == lookup_view or pattern.name == lookup_view: - try: - return pattern.reverse_helper(*args, **kwargs) - except NoReverseMatch: - continue + try: + lookup_view = get_callable(lookup_view, True) + except (ImportError, AttributeError): + raise NoReverseMatch + if lookup_view in self.reverse_dict: + return ''.join([reverse_helper(part.regex, *args, **kwargs) for part in self.reverse_dict[lookup_view]]) raise NoReverseMatch def reverse_helper(self, lookup_view, *args, **kwargs): @@ -235,17 +274,10 @@ class RegexURLResolver(object): return result + sub_match def resolve(path, urlconf=None): - if urlconf is None: - from django.conf import settings - urlconf = settings.ROOT_URLCONF - resolver = RegexURLResolver(r'^/', urlconf) - return resolver.resolve(path) + return get_resolver(urlconf).resolve(path) def reverse(viewname, urlconf=None, args=None, kwargs=None): args = args or [] kwargs = kwargs or {} - if urlconf is None: - from django.conf import settings - urlconf = settings.ROOT_URLCONF - resolver = RegexURLResolver(r'^/', urlconf) - return '/' + resolver.reverse(viewname, *args, **kwargs) + return '/' + get_resolver(urlconf).reverse(viewname, *args, **kwargs) + |
