diff --git a/AUTHORS b/AUTHORS index 9c9e67c50a..fb00ca43fe 100644 --- a/AUTHORS +++ b/AUTHORS @@ -60,6 +60,7 @@ answer newbie questions, and generally made Django that much better: Amit Chakradeo ChaosKCW Ian Clelland + crankycoder@gmail.com Matt Croydon Jonathan Daugherty (cygnus) Jason Davies (Esaj) @@ -125,12 +126,14 @@ answer newbie questions, and generally made Django that much better: Ivan Sagalaev (Maniac) David Schein sopel + Thomas Steinacher Radek Švarz Swaroop C H Aaron Swartz Tom Tobin Tom Insam Joe Topjian + Karen Tracey Amit Upadhyay Geert Vanderkelen Milton Waddams diff --git a/django/bin/compile-messages.py b/django/bin/compile-messages.py index 5f653df95d..44a84de379 100755 --- a/django/bin/compile-messages.py +++ b/django/bin/compile-messages.py @@ -14,7 +14,7 @@ def compile_messages(): print "this script should be run from the django svn tree or your project or app tree" sys.exit(1) - for (dirpath, dirnames, filenames) in os.walk(basedir): + for dirpath, dirnames, filenames in os.walk(basedir): for f in filenames: if f.endswith('.po'): sys.stderr.write('processing file %s in %s\n' % (f, dirpath)) diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 3d203d97de..25bafbdce9 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -255,6 +255,7 @@ MIDDLEWARE_CLASSES = ( SESSION_COOKIE_NAME = 'sessionid' # Cookie name. This can be whatever you want. SESSION_COOKIE_AGE = 60 * 60 * 24 * 7 * 2 # Age of cookie, in seconds (default: 2 weeks). SESSION_COOKIE_DOMAIN = None # A string like ".lawrence.com", or None for standard domain cookie. +SESSION_COOKIE_SECURE = False # Whether the session cookie should be secure (https:// only). SESSION_SAVE_EVERY_REQUEST = False # Whether to save the session data on every request. SESSION_EXPIRE_AT_BROWSER_CLOSE = False # Whether sessions expire when a user closes his browser. diff --git a/django/conf/project_template/settings.py b/django/conf/project_template/settings.py index 14da715db5..63e07c061a 100644 --- a/django/conf/project_template/settings.py +++ b/django/conf/project_template/settings.py @@ -60,8 +60,9 @@ MIDDLEWARE_CLASSES = ( ROOT_URLCONF = '{{ project_name }}.urls' TEMPLATE_DIRS = ( - # Put strings here, like "/home/html/django_templates". + # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". # Always use forward slashes, even on Windows. + # Don't forget to use absolute paths, not relative paths. ) INSTALLED_APPS = ( diff --git a/django/contrib/admin/templates/admin/change_form.html b/django/contrib/admin/templates/admin/change_form.html index e61eb5513b..b1fdc5ebdb 100644 --- a/django/contrib/admin/templates/admin/change_form.html +++ b/django/contrib/admin/templates/admin/change_form.html @@ -21,7 +21,7 @@ {% if has_absolute_url %}
  • {% trans "View on site" %}
  • {% endif%} {% endif %}{% endif %} -
    {% block form_top %}{% endblock %} +{% block form_top %}{% endblock %}
    {% if is_popup %}{% endif %} {% if opts.admin.save_on_top %}{% submit_row %}{% endif %} diff --git a/django/contrib/admin/templates/admin/edit_inline_tabular.html b/django/contrib/admin/templates/admin/edit_inline_tabular.html index 13d528331b..3d059c8b3d 100644 --- a/django/contrib/admin/templates/admin/edit_inline_tabular.html +++ b/django/contrib/admin/templates/admin/edit_inline_tabular.html @@ -7,6 +7,7 @@ {{ fw.field.verbose_name|capfirst|escape }} {% endif %} {% endfor %} + {% for fcw in bound_related_object.form_field_collection_wrappers %} {% if change %}{% if original_row_needed %} {% if fcw.obj.original %} diff --git a/django/contrib/admin/urls.py b/django/contrib/admin/urls.py index 88cf8306a3..aaf9841e45 100644 --- a/django/contrib/admin/urls.py +++ b/django/contrib/admin/urls.py @@ -21,7 +21,6 @@ urlpatterns = patterns('', ('^doc/tags/$', 'django.contrib.admin.views.doc.template_tag_index'), ('^doc/filters/$', 'django.contrib.admin.views.doc.template_filter_index'), ('^doc/views/$', 'django.contrib.admin.views.doc.view_index'), - ('^doc/views/jump/$', 'django.contrib.admin.views.doc.jump_to_view'), ('^doc/views/(?P[^/]+)/$', 'django.contrib.admin.views.doc.view_detail'), ('^doc/models/$', 'django.contrib.admin.views.doc.model_index'), ('^doc/models/(?P[^\.]+)\.(?P[^/]+)/$', 'django.contrib.admin.views.doc.model_detail'), diff --git a/django/contrib/admin/views/doc.py b/django/contrib/admin/views/doc.py index 68799fcc17..d1541abee9 100644 --- a/django/contrib/admin/views/doc.py +++ b/django/contrib/admin/views/doc.py @@ -43,11 +43,11 @@ def template_tag_index(request): for tag_name, tag_func in library.tags.items(): title, body, metadata = utils.parse_docstring(tag_func.__doc__) if title: - title = utils.parse_rst(title, 'tag', 'tag:' + tag_name) + title = utils.parse_rst(title, 'tag', _('tag:') + tag_name) if body: - body = utils.parse_rst(body, 'tag', 'tag:' + tag_name) + body = utils.parse_rst(body, 'tag', _('tag:') + tag_name) for key in metadata: - metadata[key] = utils.parse_rst(metadata[key], 'tag', 'tag:' + tag_name) + metadata[key] = utils.parse_rst(metadata[key], 'tag', _('tag:') + tag_name) if library in template.builtins: tag_library = None else: @@ -74,11 +74,11 @@ def template_filter_index(request): for filter_name, filter_func in library.filters.items(): title, body, metadata = utils.parse_docstring(filter_func.__doc__) if title: - title = utils.parse_rst(title, 'filter', 'filter:' + filter_name) + title = utils.parse_rst(title, 'filter', _('filter:') + filter_name) if body: - body = utils.parse_rst(body, 'filter', 'filter:' + filter_name) + body = utils.parse_rst(body, 'filter', _('filter:') + filter_name) for key in metadata: - metadata[key] = utils.parse_rst(metadata[key], 'filter', 'filter:' + filter_name) + metadata[key] = utils.parse_rst(metadata[key], 'filter', _('filter:') + filter_name) if library in template.builtins: tag_library = None else: @@ -132,11 +132,11 @@ def view_detail(request, view): raise Http404 title, body, metadata = utils.parse_docstring(view_func.__doc__) if title: - title = utils.parse_rst(title, 'view', 'view:' + view) + title = utils.parse_rst(title, 'view', _('view:') + view) if body: - body = utils.parse_rst(body, 'view', 'view:' + view) + body = utils.parse_rst(body, 'view', _('view:') + view) for key in metadata: - metadata[key] = utils.parse_rst(metadata[key], 'model', 'view:' + view) + metadata[key] = utils.parse_rst(metadata[key], 'model', _('view:') + view) return render_to_response('admin_doc/view_detail.html', { 'name': view, 'summary': title, @@ -161,14 +161,14 @@ def model_detail(request, app_label, model_name): try: app_mod = models.get_app(app_label) except ImproperlyConfigured: - raise Http404, "App %r not found" % app_label + raise Http404, _("App %r not found") % app_label model = None for m in models.get_models(app_mod): if m._meta.object_name.lower() == model_name: model = m break if model is None: - raise Http404, "Model %r not found in app %r" % (model_name, app_label) + raise Http404, _("Model %r not found in app %r") % (model_name, app_label) opts = model._meta @@ -180,7 +180,7 @@ def model_detail(request, app_label, model_name): if isinstance(field, models.ForeignKey): data_type = related_object_name = field.rel.to.__name__ app_label = field.rel.to._meta.app_label - verbose = utils.parse_rst(("the related `%s.%s` object" % (app_label, data_type)), 'model', 'model:' + data_type) + verbose = utils.parse_rst((_("the related `%s.%s` object") % (app_label, data_type)), 'model', _('model:') + data_type) else: data_type = get_readable_field_data_type(field) verbose = field.verbose_name @@ -202,7 +202,7 @@ def model_detail(request, app_label, model_name): continue verbose = func.__doc__ if verbose: - verbose = utils.parse_rst(utils.trim_docstring(verbose), 'model', 'model:' + opts.module_name) + verbose = utils.parse_rst(utils.trim_docstring(verbose), 'model', _('model:') + opts.module_name) fields.append({ 'name': func_name, 'data_type': get_return_data_type(func_name), @@ -211,17 +211,17 @@ def model_detail(request, app_label, model_name): # Gather related objects for rel in opts.get_all_related_objects(): - verbose = "related `%s.%s` objects" % (rel.opts.app_label, rel.opts.object_name) + verbose = _("related `%s.%s` objects") % (rel.opts.app_label, rel.opts.object_name) accessor = rel.get_accessor_name() fields.append({ 'name' : "%s.all" % accessor, 'data_type' : 'List', - 'verbose' : utils.parse_rst("all " + verbose , 'model', 'model:' + opts.module_name), + 'verbose' : utils.parse_rst(_("all %s") % verbose , 'model', _('model:') + opts.module_name), }) fields.append({ 'name' : "%s.count" % accessor, 'data_type' : 'Integer', - 'verbose' : utils.parse_rst("number of " + verbose , 'model', 'model:' + opts.module_name), + 'verbose' : utils.parse_rst(_("number of %s") % verbose , 'model', _('model:') + opts.module_name), }) return render_to_response('admin_doc/model_detail.html', { @@ -336,7 +336,7 @@ def extract_views_from_urlpatterns(urlpatterns, base=''): elif hasattr(p, '_get_url_patterns'): views.extend(extract_views_from_urlpatterns(p.url_patterns, base + p.regex.pattern)) else: - raise TypeError, "%s does not appear to be a urlpattern object" % p + raise TypeError, _("%s does not appear to be a urlpattern object") % p return views named_group_matcher = re.compile(r'\(\?P(<\w+>).+?\)') diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py index 705dfad6c8..0f6167fc8f 100644 --- a/django/contrib/admin/views/main.py +++ b/django/contrib/admin/views/main.py @@ -263,7 +263,7 @@ def add_stage(request, app_label, model_name, show_delete=False, form_url='', po post_url_continue += "?_popup=1" return HttpResponseRedirect(post_url_continue % pk_value) if request.POST.has_key("_popup"): - return HttpResponse('' % \ + return HttpResponse('' % \ (pk_value, str(new_object).replace('"', '\\"'))) elif request.POST.has_key("_addanother"): request.user.message_set.create(message=msg + ' ' + (_("You may add another %s below.") % opts.verbose_name)) diff --git a/django/contrib/flatpages/README.TXT b/django/contrib/flatpages/README.TXT index e46aa1f9bf..d3071377f5 100644 --- a/django/contrib/flatpages/README.TXT +++ b/django/contrib/flatpages/README.TXT @@ -2,7 +2,7 @@ This is an optional add-on app, flatpages. For full documentation, see either of these: - * The file django/docs/flatpages.txt in the Django distribution + * The file docs/flatpages.txt in the Django distribution * http://www.djangoproject.com/documentation/flatpages/ on the Web -Both have identical content. \ No newline at end of file +Both have identical content. diff --git a/django/contrib/sessions/middleware.py b/django/contrib/sessions/middleware.py index dde4f1a6c0..2337ad8a61 100644 --- a/django/contrib/sessions/middleware.py +++ b/django/contrib/sessions/middleware.py @@ -88,5 +88,6 @@ class SessionMiddleware(object): new_session = Session.objects.save(session_key, request.session._session, datetime.datetime.now() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE)) response.set_cookie(settings.SESSION_COOKIE_NAME, session_key, - max_age=max_age, expires=expires, domain=settings.SESSION_COOKIE_DOMAIN) + max_age=max_age, expires=expires, domain=settings.SESSION_COOKIE_DOMAIN, + secure=settings.SESSION_COOKIE_SECURE or None) return response diff --git a/django/core/management.py b/django/core/management.py index c910c15589..e315b20674 100644 --- a/django/core/management.py +++ b/django/core/management.py @@ -110,9 +110,14 @@ def get_sql_create(app): sys.exit(1) # Get installed models, so we generate REFERENCES right + # We trim models from the current app so that the sqlreset command does + # not generate invalid SQL (leaving models out of known_models is + # harmless, so we can be conservative). manager = model._default_manager tables = manager.get_table_list() - installed_models = manager.get_installed_models(tables) + installed_models = [ model for model in + manager.get_installed_models(tables) + if model not in app_models ] models_output = set(installed_models) builder = creation.builder builder.models_already_seen.update(models_output) @@ -136,11 +141,14 @@ def get_sql_create(app): # but don't exist physically not_installed_models = set(pending_references.keys()) if not_installed_models: - final_output.append('-- The following references should be added but depend on non-existant tables:') + alter_sql = [] for model in not_installed_models: - final_output.extend(['-- ' + sql - for sql in pending_references.pop(model)]) - + alter_sql.extend(['-- ' + sql + for sql in pending_references.pop(model)]) + if alter_sql: + final_output.append('-- The following references should be added ' + 'but depend on non-existent tables:') + final_output.extend(alter_sql) # convert BoundStatements into strings final_output = map(str, final_output) return final_output @@ -563,9 +571,7 @@ def inspectdb(): introspection_module = get_introspection_module() - def table2model(table_name): - object_name = table_name.title().replace('_', '') - return object_name.endswith('s') and object_name[:-1] or object_name + table2model = lambda table_name: table_name.title().replace('_', '') cursor = connection.cursor() yield "# This is an auto-generated Django model module." @@ -594,6 +600,10 @@ def inspectdb(): comment_notes = [] # Holds Field notes, to be displayed in a Python comment. extra_params = {} # Holds Field parameters such as 'db_column'. + if ' ' in att_name: + extra_params['db_column'] = att_name + att_name = att_name.replace(' ', '') + comment_notes.append('Field renamed to remove spaces.') if keyword.iskeyword(att_name): extra_params['db_column'] = att_name att_name += '_field' @@ -1010,7 +1020,14 @@ def dbshell(): dbshell.args = "" def runfcgi(args): - """Run this project as a FastCGI application. requires flup.""" + "Runs this project as a FastCGI application. Requires flup." + from django.conf import settings + from django.utils import translation + # Activate the current language, because it won't get activated later. + try: + translation.activate(settings.LANGUAGE_CODE) + except AttributeError: + pass from django.core.servers.fastcgi import runfastcgi runfastcgi(args) runfcgi.args = '[various KEY=val options, use `runfcgi help` for help]' @@ -1167,7 +1184,11 @@ def execute_from_command_line(action_mapping=DEFAULT_ACTION_MAPPING, argv=None): if action not in NO_SQL_TRANSACTION: print style.SQL_KEYWORD("COMMIT;") -def execute_manager(settings_mod, argv=None): +def setup_environ(settings_mod): + """ + Configure the runtime environment. This can also be used by external + scripts wanting to set up a similar environment to manage.py. + """ # Add this project to sys.path so that it's importable in the conventional # way. For example, if this file (manage.py) lives in a directory # "myproject", this code would add "/path/to/myproject" to sys.path. @@ -1179,7 +1200,10 @@ def execute_manager(settings_mod, argv=None): # Set DJANGO_SETTINGS_MODULE appropriately. os.environ['DJANGO_SETTINGS_MODULE'] = '%s.settings' % project_name + return project_directory +def execute_manager(settings_mod, argv=None): + project_directory = setup_environ(settings_mod) action_mapping = DEFAULT_ACTION_MAPPING.copy() # Remove the "startproject" command from the action_mapping, because that's diff --git a/django/core/servers/basehttp.py b/django/core/servers/basehttp.py index 7ce5706c23..4bd0e50e53 100644 --- a/django/core/servers/basehttp.py +++ b/django/core/servers/basehttp.py @@ -547,10 +547,6 @@ class WSGIRequestHandler(BaseHTTPRequestHandler): env['PATH_INFO'] = urllib.unquote(path) env['QUERY_STRING'] = query - - host = self.address_string() - if host != self.client_address[0]: - env['REMOTE_HOST'] = host env['REMOTE_ADDR'] = self.client_address[0] if self.headers.typeheader is None: diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py index 1da25c9f83..2f557b90a6 100644 --- a/django/core/urlresolvers.py +++ b/django/core/urlresolvers.py @@ -86,10 +86,15 @@ class MatchChecker(object): class RegexURLPattern(object): def __init__(self, regex, callback, default_args=None): # regex is a string representing a regular expression. - # callback is something like 'foo.views.news.stories.story_detail', - # which represents the path to a module and a view function name. + # callback is either a string like 'foo.views.news.stories.story_detail' + # which represents the path to a module and a view function name, or a + # callable object (view). self.regex = re.compile(regex) - self.callback = callback + if callable(callback): + self._callback = callback + else: + self._callback = None + self._callback_str = callback self.default_args = default_args or {} def resolve(self, path): @@ -106,23 +111,28 @@ class RegexURLPattern(object): # In both cases, pass any extra_kwargs as **kwargs. kwargs.update(self.default_args) - try: # Lazily load self.func. - return self.func, args, kwargs - except AttributeError: - self.func = self.get_callback() - return self.func, args, kwargs + return self.callback, args, kwargs - def get_callback(self): - mod_name, func_name = get_mod_func(self.callback) + def _get_callback(self): + if self._callback is not None: + return self._callback + mod_name, func_name = get_mod_func(self._callback_str) try: - return getattr(__import__(mod_name, '', '', ['']), func_name) + self._callback = getattr(__import__(mod_name, '', '', ['']), func_name) except ImportError, e: raise ViewDoesNotExist, "Could not import %s. Error was: %s" % (mod_name, str(e)) except AttributeError, e: raise ViewDoesNotExist, "Tried %s in module %s. Error was: %s" % (func_name, mod_name, str(e)) + return self._callback + callback = property(_get_callback) def reverse(self, viewname, *args, **kwargs): - if viewname != self.callback: + mod_name, func_name = get_mod_func(viewname) + try: + lookup_view = getattr(__import__(mod_name, '', '', ['']), func_name) + except (ImportError, AttributeError): + raise NoReverseMatch + if lookup_view != self.callback: raise NoReverseMatch return self.reverse_helper(*args, **kwargs) @@ -185,22 +195,28 @@ class RegexURLResolver(object): def resolve500(self): return self._resolve_special('500') - def reverse(self, viewname, *args, **kwargs): + 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): + raise NoReverseMatch for pattern in self.urlconf_module.urlpatterns: if isinstance(pattern, RegexURLResolver): try: - return pattern.reverse_helper(viewname, *args, **kwargs) + return pattern.reverse_helper(lookup_view, *args, **kwargs) except NoReverseMatch: continue - elif pattern.callback == viewname: + elif pattern.callback == lookup_view: try: return pattern.reverse_helper(*args, **kwargs) except NoReverseMatch: continue raise NoReverseMatch - def reverse_helper(self, viewname, *args, **kwargs): - sub_match = self.reverse(viewname, *args, **kwargs) + def reverse_helper(self, lookup_view, *args, **kwargs): + sub_match = self.reverse(lookup_view, *args, **kwargs) result = reverse_helper(self.regex, *args, **kwargs) return result + sub_match diff --git a/django/db/backends/ansi/sql.py b/django/db/backends/ansi/sql.py index 70928d3560..6298749b1d 100644 --- a/django/db/backends/ansi/sql.py +++ b/django/db/backends/ansi/sql.py @@ -139,7 +139,7 @@ class SchemaBuilder(object): col = opts.get_field(f.rel.field_name).column # For MySQL, r_name must be unique in the first 64 # characters. So we are careful with character usage here. - r_name = '%s_refs_%s_%x' % (r_col, col, + r_name = '%s_refs_%s_%x' % (col, r_col, abs(hash((r_table, table)))) sql = style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s);' % \ (quote_name(table), quote_name(r_name), @@ -262,8 +262,10 @@ class SchemaBuilder(object): style.SQL_TABLE(qn(table)), style.SQL_KEYWORD( backend.get_drop_foreignkey_sql()), - style.SQL_FIELD(qn("%s_referencing_%s_%s" % - (col, r_table, r_col)))), + style.SQL_FIELD(qn("%s_refs_%s_%x" % + (col, r_col, + abs(hash((table, r_table))))) + )), db.connection)) del references_to_delete[model] # many to many: drop any many-many tables that are my diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py index adfc1d1762..308bc12aa8 100644 --- a/django/db/backends/postgresql_psycopg2/base.py +++ b/django/db/backends/postgresql_psycopg2/base.py @@ -11,6 +11,10 @@ except ImportError, e: from django.core.exceptions import ImproperlyConfigured raise ImproperlyConfigured, "Error loading psycopg2 module: %s" % e +# Register Unicode conversions +import psycopg2.extensions +psycopg2.extensions.register_type(psycopg2.extensions.UNICODE) + DatabaseError = Database.DatabaseError try: diff --git a/django/db/models/__init__.py b/django/db/models/__init__.py index 54df38cc7b..0308dd047a 100644 --- a/django/db/models/__init__.py +++ b/django/db/models/__init__.py @@ -25,7 +25,7 @@ def permalink(func): def inner(*args, **kwargs): bits = func(*args, **kwargs) viewname = bits[0] - return reverse(bits[0], None, *bits[1:2]) + return reverse(bits[0], None, *bits[1:3]) return inner class LazyDate(object): @@ -47,7 +47,7 @@ class LazyDate(object): return "" % self.delta def __get_value__(self): - return datetime.datetime.now() + self.delta + return (datetime.datetime.now() + self.delta).date() def __getattr__(self, attr): return getattr(self.__get_value__(), attr) diff --git a/django/db/models/base.py b/django/db/models/base.py index 7cde8f944c..da957efc2c 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -180,11 +180,12 @@ class Model(object): # If it does already exist, do an UPDATE. if cursor.fetchone(): db_values = [f.get_db_prep_save(f.pre_save(self, False)) for f in non_pks] - cursor.execute("UPDATE %s SET %s WHERE %s=%%s" % \ - (qn(self._meta.db_table), - ','.join(['%s=%%s' % qn(f.column) for f in non_pks]), - qn(self._meta.pk.column)), - db_values + [pk_val]) + if db_values: + cursor.execute("UPDATE %s SET %s WHERE %s=%%s" % \ + (qn(self._meta.db_table), + ','.join(['%s=%%s' % qn(f.column) for f in non_pks]), + qn(self._meta.pk.column)), + db_values + [pk_val]) else: record_exists = False if not pk_set or not record_exists: diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 35c0f375a1..1c85b5b68a 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -20,7 +20,7 @@ BLANK_CHOICE_DASH = [("", "---------")] BLANK_CHOICE_NONE = [("", "None")] # prepares a value for use in a LIKE query -prep_for_like_query = lambda x: str(x).replace("%", "\%").replace("_", "\_") +prep_for_like_query = lambda x: str(x).replace("\\", "\\\\").replace("%", "\%").replace("_", "\_") # returns the
      class for a given radio_admin value get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '') diff --git a/django/db/models/loading.py b/django/db/models/loading.py index 123436cf60..76952401d5 100644 --- a/django/db/models/loading.py +++ b/django/db/models/loading.py @@ -35,7 +35,7 @@ def get_apps(): _app_errors[app_name] = e return _app_list -def get_app(app_label, emptyOK = False): +def get_app(app_label, emptyOK=False): "Returns the module containing the models for the given app_label. If the app has no models in it and 'emptyOK' is True, returns None." get_apps() # Run get_apps() to populate the _app_list cache. Slightly hackish. for app_name in settings.INSTALLED_APPS: @@ -83,7 +83,7 @@ def get_models(app_mod=None, creation_order=False): model_list.extend(get_models(app_mod)) return model_list -def get_model(app_label, model_name, seed_cache = True): +def get_model(app_label, model_name, seed_cache=True): """ Returns the model matching the given app_label and case-insensitive model_name. diff --git a/django/db/models/manipulators.py b/django/db/models/manipulators.py index 69e13dd160..b0ce48dec5 100644 --- a/django/db/models/manipulators.py +++ b/django/db/models/manipulators.py @@ -138,7 +138,7 @@ class AutomaticManipulator(forms.Manipulator): child_follow = self.follow.get(related.name, None) if child_follow: - obj_list = expanded_data[related.var_name].items() + obj_list = expanded_data.get(related.var_name, {}).items() if not obj_list: continue diff --git a/django/http/__init__.py b/django/http/__init__.py index 06f6a106ee..c4ac302ec5 100644 --- a/django/http/__init__.py +++ b/django/http/__init__.py @@ -38,7 +38,7 @@ class HttpRequest(object): def get_full_path(self): return '' - + def is_secure(self): return os.environ.get("HTTPS") == "on" @@ -203,11 +203,14 @@ class HttpResponse(object): if val is not None: self.cookies[key][var.replace('_', '-')] = val - def delete_cookie(self, key): - try: - self.cookies[key]['max_age'] = 0 - except KeyError: - pass + def delete_cookie(self, key, path='/', domain=None): + self.cookies[key] = '' + if path is not None: + self.cookies[key]['path'] = path + if domain is not None: + self.cookies[key]['domain'] = path + self.cookies[key]['expires'] = 0 + self.cookies[key]['max-age'] = 0 def _get_content(self): content = ''.join(self._iterator) diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index c75f371ec8..a2e9d2f405 100644 --- a/django/template/defaultfilters.py +++ b/django/template/defaultfilters.py @@ -339,7 +339,7 @@ def date(value, arg=None): def time(value, arg=None): "Formats a time according to the given format" from django.utils.dateformat import time_format - if not value: + if value in (None, ''): return '' if arg is None: arg = settings.TIME_FORMAT @@ -437,7 +437,7 @@ def pluralize(value, arg='s'): is used instead. If the provided argument contains a comma, the text before the comma is used for the singular case. """ - if not ',' in arg: + if not ',' in arg: arg = ',' + arg bits = arg.split(',') if len(bits) > 2: diff --git a/django/template/loaders/filesystem.py b/django/template/loaders/filesystem.py index 0c94021fb8..d01f54c5fe 100644 --- a/django/template/loaders/filesystem.py +++ b/django/template/loaders/filesystem.py @@ -17,7 +17,7 @@ def load_template_source(template_name, template_dirs=None): return (open(filepath).read(), filepath) except IOError: tried.append(filepath) - if template_dirs: + if tried: error_msg = "Tried %s" % tried else: error_msg = "Your TEMPLATE_DIRS setting is empty. Change it to point to at least one template directory." diff --git a/django/views/generic/create_update.py b/django/views/generic/create_update.py index 27ba327ff5..034253a549 100644 --- a/django/views/generic/create_update.py +++ b/django/views/generic/create_update.py @@ -6,6 +6,7 @@ from django.contrib.auth.views import redirect_to_login from django.template import RequestContext from django.http import Http404, HttpResponse, HttpResponseRedirect from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured +from django.utils.translation import gettext def create_object(request, model, template_name=None, template_loader=loader, extra_context=None, post_save_redirect=None, @@ -39,7 +40,7 @@ def create_object(request, model, template_name=None, new_object = manipulator.save(new_data) if request.user.is_authenticated(): - request.user.message_set.create(message="The %s was created successfully." % model._meta.verbose_name) + request.user.message_set.create(message=gettext("The %(verbose_name)s was created successfully.") % {"verbose_name": model._meta.verbose_name}) # Redirect to the new object: first by trying post_save_redirect, # then by obj.get_absolute_url; fail if neither works. @@ -113,7 +114,7 @@ def update_object(request, model, object_id=None, slug=None, object = manipulator.save(new_data) if request.user.is_authenticated(): - request.user.message_set.create(message="The %s was updated successfully." % model._meta.verbose_name) + request.user.message_set.create(message=gettext("The %(verbose_name)s was updated successfully.") % {"verbose_name": model._meta.verbose_name}) # Do a post-after-redirect so that reload works, etc. if post_save_redirect: @@ -180,7 +181,7 @@ def delete_object(request, model, post_delete_redirect, if request.method == 'POST': object.delete() if request.user.is_authenticated(): - request.user.message_set.create(message="The %s was deleted." % model._meta.verbose_name) + request.user.message_set.create(message=gettext("The %(verbose_name)s was deleted.") % {"verbose_name": model._meta.verbose_name}) return HttpResponseRedirect(post_delete_redirect) else: if not template_name: diff --git a/docs/db-api.txt b/docs/db-api.txt index a7cf30813d..bd178dbd7d 100644 --- a/docs/db-api.txt +++ b/docs/db-api.txt @@ -718,12 +718,12 @@ The ``DoesNotExist`` exception inherits from A convenience method for creating an object and saving it all in one step. Thus:: p = Person.objects.create(first_name="Bruce", last_name="Springsteen") - + and:: p = Person(first_name="Bruce", last_name="Springsteen") p.save() - + are equivalent. ``get_or_create(**kwargs)`` @@ -1471,11 +1471,12 @@ the ``ForeignKey`` ``Manager`` has these additional methods: b.entry_set.remove(e) # Disassociates Entry e from Blog b. In order to prevent database inconsistency, this method only exists on - ``ForeignKey``s where ``null=True``. If the related field can't be set to - ``None`` (``NULL``), then an object can't be removed from a relation - without being added to another. In the above example, removing ``e`` from - ``b.entry_set()`` is equivalent to doing ``e.blog = None``, and because - the ``blog`` ``ForeignKey`` doesn't have ``null=True``, this is invalid. + ``ForeignKey`` objects where ``null=True``. If the related field can't be + set to ``None`` (``NULL``), then an object can't be removed from a + relation without being added to another. In the above example, removing + ``e`` from ``b.entry_set()`` is equivalent to doing ``e.blog = None``, + and because the ``blog`` ``ForeignKey`` doesn't have ``null=True``, this + is invalid. * ``clear()``: Removes all objects from the related object set. @@ -1559,13 +1560,13 @@ Queries over related objects ---------------------------- Queries involving related objects follow the same rules as queries involving -normal value fields. When specifying the the value for a query to match, you -may use either an object instance itself, or the primary key value for the +normal value fields. When specifying the the value for a query to match, you +may use either an object instance itself, or the primary key value for the object. For example, if you have a Blog object ``b`` with ``id=5``, the following three queries would be identical:: - + Entry.objects.filter(blog=b) # Query using object instance Entry.objects.filter(blog=b.id) # Query using id from instance Entry.objects.filter(blog=5) # Query using id directly diff --git a/docs/documentation.txt b/docs/documentation.txt new file mode 100644 index 0000000000..bacfb176b1 --- /dev/null +++ b/docs/documentation.txt @@ -0,0 +1,148 @@ +==================================== +How to read the Django documentation +==================================== + +We've put a lot of effort into making Django's documentation useful, easy to +read and as complete as possible. Here are a few tips on how to make the best +of it, along with some style guidelines. + +(Yes, this is documentation about documentation. Rest assured we have no plans +to write a document about how to read the document about documentation.) + +How documentation is updated +============================ + +Just as the Django code base is developed and improved on a daily basis, our +documentation is consistently improving. We improve documentation for several +reasons: + + * To make content fixes, such as grammar/typo corrections. + * To add information and/or examples to existing sections that need to be + expanded. + * To document Django features that aren't yet documented. (The list of + such features is shrinking but exists nonetheless.) + * To add documentation for new features as new features get added, or as + Django APIs or behaviors change. + +Django's documentation is kept in the same source control system as its code. +It lives in the `django/trunk/docs`_ directory of our Subversion repository. +Each document is a separate text file that covers a narrowly focused topic, +such as the "generic views" framework or how to construct a database model. + +.. _django/trunk/docs: http://code.djangoproject.com/browser/django/trunk/docs + +Where to get it +=============== + +You can read Django documentation in several ways. They are, in order of +preference: + +On the Web +---------- + +The most recent version of the Django documentation lives at +http://www.djangoproject.com/documentation/ . These HTML pages are generated +automatically from the text files in source control every 15 minutes. That +means they reflect the "latest and greatest" in Django -- they include the very +latest corrections and additions, and they discuss the latest Django features, +which may only be available to users of the Django development version. (See +"Differences between versions" below.) + +A key advantage of the Web-based documentation is the comment section at the +bottom of each document. This is an area for anybody to submit changes, +corrections and suggestions about the given document. The Django developers +frequently monitor the comments there and use them to improve the documentation +for everybody. + +We encourage you to help improve the docs: it's easy! Note, however, that +comments should explicitly relate to the documentation, rather than asking +broad tech-support questions. If you need help with your particular Django +setup, try the `django-users mailing list`_ instead of posting a comment to the +documentation. + +.. _django-users mailing list: http://groups.google.com/group/django-users + +In plain text +------------- + +For offline reading, or just for convenience, you can read the Django +documentation in plain text. + +If you're using an official release of Django, note that the zipped package +(tarball) of the code includes a ``docs/`` directory, which contains all the +documentation for that release. + +If you're using the development version of Django (aka the Subversion "trunk"), +note that the ``docs/`` directory contains all of the documentation. You can +``svn update`` it, just as you ``svn update`` the Python code, in order to get +the latest changes. + +You can check out the latest Django documentation from Subversion using this +shell command:: + + svn co http://code.djangoproject.com/svn/django/trunk/docs/ django_docs + +One low-tech way of taking advantage of the text documentation is by using the +Unix ``grep`` utility to search for a phrase in all of the documentation. For +example, this will show you each mention of the phrase "edit_inline" in any +Django document:: + + grep edit_inline /path/to/django/docs/*.txt + +Formatting +~~~~~~~~~~ + +The text documentation is written in ReST (ReStructured Text) format. That +means it's easy to read but is also formatted in a way that makes it easy to +convert into other formats, such as HTML. If you're interested, the script that +converts the ReST text docs into djangoproject.com's HTML lives at +`djangoproject.com/django_website/apps/docs/parts/build_documentation.py`_ in +the Django Subversion repository. + +.. _djangoproject.com/django_website/apps/docs/parts/build_documentation.py: http://code.djangoproject.com/browser/djangoproject.com/django_website/apps/docs/parts/build_documentation.py + +Differences between versions +============================ + +As previously mentioned, the text documentation in our Subversion repository +contains the "latest and greatest" changes and additions. These changes often +include documentation of new features added in the Django development version +-- the Subversion ("trunk") version of Django. For that reason, it's worth +pointing out our policy on keeping straight the documentation for various +versions of the framework. + +We follow this policy: + + * The primary documentation on djangoproject.com is an HTML version of the + latest docs in Subversion. These docs always correspond to the latest + official Django release, plus whatever features we've added/changed in + the framework *since* the latest release. + + * As we add features to Django's development version, we try to update the + documentation in the same Subversion commit transaction. + + * To distinguish feature changes/additions in the docs, we use the phrase + **New in Django development version**. In practice, this means that the + current documentation on djangoproject.com can be used by users of either + the latest release *or* the development version. + + * Documentation for a particular Django release is frozen once the version + has been released officially. It remains a snapshot of the docs as of the + moment of the release. We will make exceptions to this rule in + the case of retroactive security updates or other such retroactive + changes. Once documentation is frozen, we add a note to the top of each + frozen document that says "These docs are frozen for Django version XXX" + and links to the current version of that document. + + * Once a document is frozen for a Django release, we remove comments from + that page, in favor of having comments on the latest version of that + document. This is for the sake of maintainability and usability, so that + users have one, and only one, place to leave comments on a particular + document. We realize that some people may be stuck on a previous version + of Django, but we believe the usability problems with multiple versions + of a document the outweigh the benefits. + + * The `main documentation Web page`_ includes links to documentation for + all previous versions. + +.. _main documentation Web page: http://www.djangoproject.com/documentation/ diff --git a/docs/generic_views.txt b/docs/generic_views.txt index aab878467f..fdab97de27 100644 --- a/docs/generic_views.txt +++ b/docs/generic_views.txt @@ -127,7 +127,7 @@ If the given URL is ``None``, Django will return an ``HttpResponseGone`` (410). This example redirects from ``/foo//`` to ``/bar//``:: urlpatterns = patterns('django.views.generic.simple', - ('^foo/(?p\d+)/$', 'redirect_to', {'url': '/bar/%(id)s/'}), + ('^foo/(?P\d+)/$', 'redirect_to', {'url': '/bar/%(id)s/'}), ) This example returns a 410 HTTP error for requests to ``/bar/``:: diff --git a/docs/model-api.txt b/docs/model-api.txt index c369508c65..502ceaf7ff 100644 --- a/docs/model-api.txt +++ b/docs/model-api.txt @@ -223,6 +223,13 @@ steps: the absolute URL to your image in a template with ``{{ object.get_mug_shot_url }}``. +For example, say your ``MEDIA_ROOT`` is set to ``'/home/media'``, and +``upload_to`` is set to ``'photos/%Y/%m/%d'``. The ``'%Y/%m/%d'`` part of +``upload_to`` is strftime formatting; ``'%Y'`` is the four-digit year, +``'%m'`` is the two-digit month and ``'%d'`` is the two-digit day. If you +upload a file on Jan. 15, 2007, it will be saved in the directory +``/home/media/photos/2007/01/15``. + .. _`strftime formatting`: http://docs.python.org/lib/module-time.html#l2h-1941 ``FilePathField`` diff --git a/docs/request_response.txt b/docs/request_response.txt index 7480a6d3bb..1f3b9d5804 100644 --- a/docs/request_response.txt +++ b/docs/request_response.txt @@ -149,7 +149,7 @@ Methods Returns the ``path``, plus an appended query string, if applicable. Example: ``"/music/bands/the_beatles/?print=true"`` - + ``is_secure()`` Returns ``True`` if the request is secure; that is, if it was made with HTTPS. @@ -380,10 +380,14 @@ Methods .. _`cookie Morsel`: http://www.python.org/doc/current/lib/morsel-objects.html -``delete_cookie(key)`` +``delete_cookie(key, path='/', domain=None)`` Deletes the cookie with the given key. Fails silently if the key doesn't exist. + The ``path`` and ``domain`` arguments are new in the Django development version. + Due to the way cookies work, ``path`` and ``domain`` should be the same + values you used in ``set_cookie()`` -- otherwise the cookie may not be deleted. + ``content`` Returns the content as a Python string, encoding it from a Unicode object if necessary. Note this is a property, not a method, so use ``r.content`` diff --git a/docs/sessions.txt b/docs/sessions.txt index c473d0a3db..d39f42c3bf 100644 --- a/docs/sessions.txt +++ b/docs/sessions.txt @@ -245,6 +245,17 @@ Default: ``'sessionid'`` The name of the cookie to use for sessions. This can be whatever you want. +SESSION_COOKIE_SECURE +--------------------- + +**New in Django development version** + +Default: ``False`` + +Whether to use a secure cookie for the session cookie. If this is set to +``True``, the cookie will be marked as "secure," which means browsers may +ensure that the cookie is only sent under an HTTPS connection. + SESSION_EXPIRE_AT_BROWSER_CLOSE ------------------------------- diff --git a/docs/settings.txt b/docs/settings.txt index 099196e56e..67e0498e1a 100644 --- a/docs/settings.txt +++ b/docs/settings.txt @@ -647,6 +647,18 @@ Default: ``'sessionid'`` The name of the cookie to use for sessions. This can be whatever you want. See the `session docs`_. +SESSION_COOKIE_SECURE +--------------------- + +**New in Django development version** + +Default: ``False`` + +Whether to use a secure cookie for the session cookie. If this is set to +``True``, the cookie will be marked as "secure," which means browsers may +ensure that the cookie is only sent under an HTTPS connection. +See the `session docs`_. + SESSION_EXPIRE_AT_BROWSER_CLOSE ------------------------------- diff --git a/docs/url_dispatch.txt b/docs/url_dispatch.txt index 6158014fc8..00a7af027a 100644 --- a/docs/url_dispatch.txt +++ b/docs/url_dispatch.txt @@ -431,3 +431,48 @@ Note that extra options will *always* be passed to *every* line in the included URLconf, regardless of whether the line's view actually accepts those options as valid. For this reason, this technique is only useful if you're certain that every view in the the included URLconf accepts the extra options you're passing. + +Passing callable objects instead of strings +=========================================== + +**New in the Django development version.** + +Some developers find it more natural to pass the actual Python function object +rather than a string containing the path to its module. This alternative is +supported -- you can pass any callable object as the view. + +For example, given this URLconf in "string" notation:: + + urlpatterns = patterns('', + (r'^archive/$', 'mysite.views.archive'), + (r'^about/$', 'mysite.views.about'), + (r'^contact/$', 'mysite.views.contact'), + ) + +You can accomplish the same thing by passing objects rather than strings. Just +be sure to import the objects:: + + from mysite.views import archive, about, contact + + urlpatterns = patterns('', + (r'^archive/$', archive), + (r'^about/$', about), + (r'^contact/$', contact), + ) + +The following example is functionally identical. It's just a bit more compact +because it imports the module that contains the views, rather than importing +each view individually:: + + from mysite import views + + urlpatterns = patterns('', + (r'^archive/$', views.archive), + (r'^about/$', views.about), + (r'^contact/$', views.contact), + ) + +The style you use is up to you. + +Note that if you use this technique -- passing objects rather than strings -- +the view prefix (as explained in "The view prefix" above) will have no effect. diff --git a/setup.py b/setup.py index a9823e5c4c..1d5c5a0cda 100644 --- a/setup.py +++ b/setup.py @@ -16,6 +16,7 @@ setup( '': ['*.TXT'], 'django.conf': ['locale/*/LC_MESSAGES/*'], 'django.contrib.admin': ['templates/admin/*.html', + 'templates/admin/auth/user/*.html', 'templates/admin_doc/*.html', 'templates/registration/*.html', 'templates/widget/*.html', diff --git a/tests/modeltests/empty/models.py b/tests/modeltests/empty/models.py index c50878398d..0f5a0b870f 100644 --- a/tests/modeltests/empty/models.py +++ b/tests/modeltests/empty/models.py @@ -20,5 +20,7 @@ API_TESTS = """ 2 >>> m.id is not None True +>>> existing = Empty(m.id) +>>> existing.save() """ diff --git a/tests/modeltests/lookup/models.py b/tests/modeltests/lookup/models.py index a2c0a14158..55bb373a4b 100644 --- a/tests/modeltests/lookup/models.py +++ b/tests/modeltests/lookup/models.py @@ -15,7 +15,7 @@ class Article(models.Model): def __str__(self): return self.headline -API_TESTS = """ +API_TESTS = r""" # Create a couple of Articles. >>> from datetime import datetime >>> a1 = Article(headline='Article 1', pub_date=datetime(2005, 7, 26)) @@ -161,13 +161,14 @@ DoesNotExist: Article matching query does not exist. # Underscores and percent signs have special meaning in the underlying -# database library, but Django handles the quoting of them automatically. +# SQL code, but Django handles the quoting of them automatically. >>> a8 = Article(headline='Article_ with underscore', pub_date=datetime(2005, 11, 20)) >>> a8.save() >>> Article.objects.filter(headline__startswith='Article') [, , , , , , , ] >>> Article.objects.filter(headline__startswith='Article_') [] + >>> a9 = Article(headline='Article% with percent sign', pub_date=datetime(2005, 11, 21)) >>> a9.save() >>> Article.objects.filter(headline__startswith='Article') @@ -182,4 +183,12 @@ DoesNotExist: Article matching query does not exist. [, , , , , , , ] >>> Article.objects.exclude(headline="Article 7") [, , , , , , , ] + +# Backslashes also have special meaning in the underlying SQL code, but Django +# automatically quotes them appropriately. +>>> a10 = Article(headline='Article with \\ backslash', pub_date=datetime(2005, 11, 22)) +>>> a10.save() +>>> Article.objects.filter(headline__contains='\\') +[] + """ diff --git a/tests/othertests/defaultfilters.py b/tests/othertests/defaultfilters.py index 1636b948d0..9b1cfda833 100644 --- a/tests/othertests/defaultfilters.py +++ b/tests/othertests/defaultfilters.py @@ -231,6 +231,9 @@ False >>> time(datetime.time(13), "h") '01' +>>> time(datetime.time(0), "h") +'12' + # real testing is done in timesince.py, where we can provide our own 'now' >>> timesince(datetime.datetime.now() - datetime.timedelta(1)) '1 day'