From f6bf41e59ac032c253e3b4e1b267010c6d456a26 Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Mon, 14 Nov 2005 01:44:35 +0000 Subject: [PATCH 01/10] Fixed #121 -- Django now quotes all names in SQL queries. Also added unit tests to confirm. Thanks, Robin Munn and Sune. git-svn-id: http://code.djangoproject.com/svn/django/trunk@1224 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/bin/daily_cleanup.py | 6 +- django/core/db/backends/ado_mssql.py | 4 +- django/core/db/backends/postgresql.py | 4 +- django/core/management.py | 126 +++++++++++++------ django/core/meta/__init__.py | 168 +++++++++++++++++-------- django/models/auth.py | 25 +++- docs/model-api.txt | 29 +++-- tests/testapp/models/__init__.py | 2 +- tests/testapp/models/reserved_names.py | 47 +++++++ 9 files changed, 295 insertions(+), 116 deletions(-) create mode 100644 tests/testapp/models/reserved_names.py diff --git a/django/bin/daily_cleanup.py b/django/bin/daily_cleanup.py index 6f2e8da4c0..52e2ef43fd 100644 --- a/django/bin/daily_cleanup.py +++ b/django/bin/daily_cleanup.py @@ -7,8 +7,10 @@ DOCUMENTATION_DIRECTORY = '/home/html/documentation/' def clean_up(): # Clean up old database records cursor = db.cursor() - cursor.execute("DELETE FROM core_sessions WHERE expire_date < NOW()") - cursor.execute("DELETE FROM registration_challenges WHERE request_date < NOW() - INTERVAL '1 week'") + cursor.execute("DELETE FROM %s WHERE %s < NOW()" % \ + (db.quote_name('core_sessions'), db.quote_name('expire_date'))) + cursor.execute("DELETE FROM %s WHERE %s < NOW() - INTERVAL '1 week'" % \ + (db.quote_name('registration_challenges'), db.quote_name('request_date'))) db.commit() if __name__ == "__main__": diff --git a/django/core/db/backends/ado_mssql.py b/django/core/db/backends/ado_mssql.py index d4f2a359c5..cc2f861ea7 100644 --- a/django/core/db/backends/ado_mssql.py +++ b/django/core/db/backends/ado_mssql.py @@ -149,8 +149,8 @@ DATA_TYPES = { 'NullBooleanField': 'bit', 'OneToOneField': 'int', 'PhoneNumberField': 'varchar(20)', - 'PositiveIntegerField': 'int CONSTRAINT [CK_int_pos_%(name)s] CHECK ([%(name)s] > 0)', - 'PositiveSmallIntegerField': 'smallint CONSTRAINT [CK_smallint_pos_%(name)s] CHECK ([%(name)s] > 0)', + 'PositiveIntegerField': 'int CONSTRAINT [CK_int_pos_%(column)s] CHECK ([%(column)s] > 0)', + 'PositiveSmallIntegerField': 'smallint CONSTRAINT [CK_smallint_pos_%(column)s] CHECK ([%(column)s] > 0)', 'SlugField': 'varchar(50)', 'SmallIntegerField': 'smallint', 'TextField': 'text', diff --git a/django/core/db/backends/postgresql.py b/django/core/db/backends/postgresql.py index b6d34fc814..1db6fe40c3 100644 --- a/django/core/db/backends/postgresql.py +++ b/django/core/db/backends/postgresql.py @@ -170,8 +170,8 @@ DATA_TYPES = { 'NullBooleanField': 'boolean', 'OneToOneField': 'integer', 'PhoneNumberField': 'varchar(20)', - 'PositiveIntegerField': 'integer CHECK (%(name)s >= 0)', - 'PositiveSmallIntegerField': 'smallint CHECK (%(name)s >= 0)', + 'PositiveIntegerField': 'integer CHECK (%(column)s >= 0)', + 'PositiveSmallIntegerField': 'smallint CHECK (%(column)s >= 0)', 'SlugField': 'varchar(50)', 'SmallIntegerField': 'smallint', 'TextField': 'text', diff --git a/django/core/management.py b/django/core/management.py index 8a9fd4020d..405371ebce 100644 --- a/django/core/management.py +++ b/django/core/management.py @@ -19,7 +19,10 @@ APP_ARGS = '[modelmodule ...]' PROJECT_TEMPLATE_DIR = os.path.join(django.__path__[0], 'conf', '%s_template') def _get_packages_insert(app_label): - return "INSERT INTO packages (label, name) VALUES ('%s', '%s');" % (app_label, app_label) + from django.core.db import db + return "INSERT INTO %s (%s, %s) VALUES ('%s', '%s');" % \ + (db.quote_name('packages'), db.quote_name('label'), db.quote_name('name'), + app_label, app_label) def _get_permission_codename(action, opts): return '%s_%s' % (action, opts.object_name.lower()) @@ -33,12 +36,16 @@ def _get_all_permissions(opts): return perms + list(opts.permissions) def _get_permission_insert(name, codename, opts): - return "INSERT INTO auth_permissions (name, package, codename) VALUES ('%s', '%s', '%s');" % \ - (name.replace("'", "''"), opts.app_label, codename) + from django.core.db import db + return "INSERT INTO %s (%s, %s, %s) VALUES ('%s', '%s', '%s');" % \ + (db.quote_name('auth_permissions'), db.quote_name('name'), db.quote_name('package'), + db.quote_name('codename'), name.replace("'", "''"), opts.app_label, codename) def _get_contenttype_insert(opts): - return "INSERT INTO content_types (name, package, python_module_name) VALUES ('%s', '%s', '%s');" % \ - (opts.verbose_name, opts.app_label, opts.module_name) + from django.core.db import db + return "INSERT INTO %s (%s, %s, %s) VALUES ('%s', '%s', '%s');" % \ + (db.quote_name('content_types'), db.quote_name('name'), db.quote_name('package'), + db.quote_name('python_module_name'), opts.verbose_name, opts.app_label, opts.module_name) def _is_valid_dir_name(s): return bool(re.search(r'^\w+$', s)) @@ -64,7 +71,7 @@ def get_sql_create(mod): data_type = f.get_internal_type() col_type = db.DATA_TYPES[data_type] if col_type is not None: - field_output = [f.column, col_type % rel_field.__dict__] + field_output = [db.db.quote_name(f.column), col_type % rel_field.__dict__] field_output.append('%sNULL' % (not f.null and 'NOT ' or '')) if f.unique: field_output.append('UNIQUE') @@ -72,14 +79,16 @@ def get_sql_create(mod): field_output.append('PRIMARY KEY') if f.rel: field_output.append('REFERENCES %s (%s)' % \ - (f.rel.to.db_table, f.rel.to.get_field(f.rel.field_name).column)) + (db.db.quote_name(f.rel.to.db_table), + db.db.quote_name(f.rel.to.get_field(f.rel.field_name).column))) table_output.append(' '.join(field_output)) if opts.order_with_respect_to: - table_output.append('_order %s NULL' % db.DATA_TYPES['IntegerField']) + table_output.append('%s %s NULL' % (db.db.quote_name('_order'), db.DATA_TYPES['IntegerField'])) for field_constraints in opts.unique_together: - table_output.append('UNIQUE (%s)' % ", ".join([opts.get_field(f).column for f in field_constraints])) + table_output.append('UNIQUE (%s)' % \ + ", ".join([db.db.quote_name(opts.get_field(f).column) for f in field_constraints])) - full_statement = ['CREATE TABLE %s (' % opts.db_table] + full_statement = ['CREATE TABLE %s (' % db.db.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(');') @@ -88,13 +97,21 @@ def get_sql_create(mod): for klass in mod._MODELS: opts = klass._meta for f in opts.many_to_many: - table_output = ['CREATE TABLE %s (' % f.get_m2m_db_table(opts)] - table_output.append(' id %s NOT NULL PRIMARY KEY,' % db.DATA_TYPES['AutoField']) - table_output.append(' %s_id %s NOT NULL REFERENCES %s (%s),' % \ - (opts.object_name.lower(), db.DATA_TYPES[get_rel_data_type(opts.pk)] % opts.pk.__dict__, opts.db_table, opts.pk.column)) - table_output.append(' %s_id %s NOT NULL REFERENCES %s (%s),' % \ - (f.rel.to.object_name.lower(), db.DATA_TYPES[get_rel_data_type(f.rel.to.pk)] % f.rel.to.pk.__dict__, f.rel.to.db_table, f.rel.to.pk.column)) - table_output.append(' UNIQUE (%s_id, %s_id)' % (opts.object_name.lower(), f.rel.to.object_name.lower())) + table_output = ['CREATE TABLE %s (' % db.db.quote_name(f.get_m2m_db_table(opts))] + table_output.append(' %s %s NOT NULL PRIMARY KEY,' % (db.db.quote_name('id'), db.DATA_TYPES['AutoField'])) + table_output.append(' %s %s NOT NULL REFERENCES %s (%s),' % \ + (db.db.quote_name(opts.object_name.lower() + '_id'), + db.DATA_TYPES[get_rel_data_type(opts.pk)] % opts.pk.__dict__, + db.db.quote_name(opts.db_table), + db.db.quote_name(opts.pk.column))) + table_output.append(' %s %s NOT NULL REFERENCES %s (%s),' % \ + (db.db.quote_name(f.rel.to.object_name.lower() + '_id'), + db.DATA_TYPES[get_rel_data_type(f.rel.to.pk)] % f.rel.to.pk.__dict__, + db.db.quote_name(f.rel.to.db_table), + db.db.quote_name(f.rel.to.pk.column))) + table_output.append(' UNIQUE (%s, %s)' % \ + (db.db.quote_name(opts.object_name.lower() + '_id'), + db.db.quote_name(f.rel.to.object_name.lower() + '_id'))) table_output.append(');') final_output.append('\n'.join(table_output)) return final_output @@ -114,7 +131,7 @@ def get_sql_delete(mod): try: if cursor is not None: # Check whether the table exists. - cursor.execute("SELECT 1 FROM django_admin_log LIMIT 1") + cursor.execute("SELECT 1 FROM %s LIMIT 1" % db.db.quote_name('django_admin_log')) except: # The table doesn't exist, so it doesn't need to be dropped. db.db.rollback() @@ -129,12 +146,12 @@ def get_sql_delete(mod): try: if cursor is not None: # Check whether the table exists. - cursor.execute("SELECT 1 FROM %s LIMIT 1" % klass._meta.db_table) + cursor.execute("SELECT 1 FROM %s LIMIT 1" % db.db.quote_name(klass._meta.db_table)) except: # The table doesn't exist, so it doesn't need to be dropped. db.db.rollback() else: - output.append("DROP TABLE %s;" % klass._meta.db_table) + output.append("DROP TABLE %s;" % db.db.quote_name(klass._meta.db_table)) # Output DROP TABLE statements for many-to-many tables. for klass in mod._MODELS: @@ -142,25 +159,31 @@ def get_sql_delete(mod): for f in opts.many_to_many: try: if cursor is not None: - cursor.execute("SELECT 1 FROM %s LIMIT 1" % f.get_m2m_db_table(opts)) + cursor.execute("SELECT 1 FROM %s LIMIT 1" % db.db.quote_name(f.get_m2m_db_table(opts))) except: db.db.rollback() else: - output.append("DROP TABLE %s;" % f.get_m2m_db_table(opts)) + output.append("DROP TABLE %s;" % db.db.quote_name(f.get_m2m_db_table(opts))) app_label = mod._MODELS[0]._meta.app_label # Delete from packages, auth_permissions, content_types. - output.append("DELETE FROM packages WHERE label = '%s';" % app_label) - output.append("DELETE FROM auth_permissions WHERE package = '%s';" % app_label) - output.append("DELETE FROM content_types WHERE package = '%s';" % app_label) + output.append("DELETE FROM %s WHERE %s = '%s';" % \ + (db.db.quote_name('packages'), db.db.quote_name('label'), app_label)) + output.append("DELETE FROM %s WHERE %s = '%s';" % \ + (db.db.quote_name('auth_permissions'), db.db.quote_name('package'), app_label)) + output.append("DELETE FROM %s WHERE %s = '%s';" % \ + (db.db.quote_name('content_types'), db.db.quote_name('package'), app_label)) # Delete from the admin log. if cursor is not None: - cursor.execute("SELECT id FROM content_types WHERE package = %s", [app_label]) + cursor.execute("SELECT %s FROM %s WHERE %s = %%s" % \ + (db.db.quote_name('id'), db.db.quote_name('content_types'), + db.db.quote_name('package')), [app_label]) if admin_log_exists: for row in cursor.fetchall(): - output.append("DELETE FROM django_admin_log WHERE content_type_id = %s;" % row[0]) + output.append("DELETE FROM %s WHERE %s = %s;" % \ + (db.db.quote_name('django_admin_log'), db.db.quote_name('content_type_id'), row[0])) # Close database connection explicitly, in case this output is being piped # directly into a database client, to avoid locking issues. @@ -206,25 +229,29 @@ get_sql_initial_data.args = APP_ARGS def get_sql_sequence_reset(mod): "Returns a list of the SQL statements to reset PostgreSQL sequences for the given module." - from django.core import meta + from django.core import db, meta output = [] for klass in mod._MODELS: for f in klass._meta.fields: if isinstance(f, meta.AutoField): - output.append("SELECT setval('%s_%s_seq', (SELECT max(%s) FROM %s));" % (klass._meta.db_table, f.column, f.column, klass._meta.db_table)) + output.append("SELECT setval('%s_%s_seq', (SELECT max(%s) FROM %s));" % \ + (klass._meta.db_table, f.column, db.db.quote_name(f.column), + db.db.quote_name(klass._meta.db_table))) return output get_sql_sequence_reset.help_doc = "Prints the SQL statements for resetting PostgreSQL sequences for the given model module name(s)." get_sql_sequence_reset.args = APP_ARGS def get_sql_indexes(mod): "Returns a list of the CREATE INDEX SQL statements for the given module." + from django.core.db import db output = [] for klass in mod._MODELS: for f in klass._meta.fields: if f.db_index: unique = f.unique and "UNIQUE " or "" output.append("CREATE %sINDEX %s_%s ON %s (%s);" % \ - (unique, klass._meta.db_table, f.column, klass._meta.db_table, f.column)) + (unique, klass._meta.db_table, f.column, + db.quote_name(klass._meta.db_table), db.quote_name(f.column))) return output get_sql_indexes.help_doc = "Prints the CREATE INDEX SQL statements for the given model module name(s)." get_sql_indexes.args = APP_ARGS @@ -242,7 +269,8 @@ def database_check(mod): app_label = mod._MODELS[0]._meta.app_label # Check that the package exists in the database. - cursor.execute("SELECT 1 FROM packages WHERE label = %s", [app_label]) + cursor.execute("SELECT 1 FROM %s WHERE %s = %%s" % \ + (db.db.quote_name('packages'), db.db.quote_name('label')), [app_label]) if cursor.rowcount < 1: # sys.stderr.write("The '%s' package isn't installed.\n" % app_label) print _get_packages_insert(app_label) @@ -256,34 +284,46 @@ def database_check(mod): perms_seen.update(dict(perms)) contenttypes_seen[opts.module_name] = 1 for codename, name in perms: - cursor.execute("SELECT 1 FROM auth_permissions WHERE package = %s AND codename = %s", (app_label, codename)) + cursor.execute("SELECT 1 FROM %s WHERE %s = %%s AND %s = %%s" % \ + (db.db.quote_name('auth_permissions'), db.db.quote_name('package'), + db.db.quote_name('codename')), (app_label, codename)) if cursor.rowcount < 1: # sys.stderr.write("The '%s.%s' permission doesn't exist.\n" % (app_label, codename)) print _get_permission_insert(name, codename, opts) - cursor.execute("SELECT 1 FROM content_types WHERE package = %s AND python_module_name = %s", (app_label, opts.module_name)) + cursor.execute("SELECT 1 FROM %s WHERE %s = %%s AND %s = %%s" % \ + (db.db.quote_name('content_types'), db.db.quote_name('package'), + db.db.quote_name('python_module_name')), (app_label, opts.module_name)) if cursor.rowcount < 1: # sys.stderr.write("The '%s.%s' content type doesn't exist.\n" % (app_label, opts.module_name)) print _get_contenttype_insert(opts) # Check that there aren't any *extra* permissions in the DB that the model # doesn't know about. - cursor.execute("SELECT codename FROM auth_permissions WHERE package = %s", (app_label,)) + cursor.execute("SELECT %s FROM %s WHERE %s = %%s" % \ + (db.db.quote_name('codename'), db.db.quote_name('auth_permissions'), + db.db.quote_name('package')), (app_label,)) for row in cursor.fetchall(): try: perms_seen[row[0]] except KeyError: # sys.stderr.write("A permission called '%s.%s' was found in the database but not in the model.\n" % (app_label, row[0])) - print "DELETE FROM auth_permissions WHERE package='%s' AND codename = '%s';" % (app_label, row[0]) + print "DELETE FROM %s WHERE %s='%s' AND %s = '%s';" % \ + (db.db.quote_name('auth_permissions'), db.db.quote_name('package'), + app_label, db.db.quote_name('codename'), row[0]) # Check that there aren't any *extra* content types in the DB that the # model doesn't know about. - cursor.execute("SELECT python_module_name FROM content_types WHERE package = %s", (app_label,)) + cursor.execute("SELECT %s FROM %s WHERE %s = %%s" % \ + (db.db.quote_name('python_module_name'), db.db.quote_name('content_types'), + db.db.quote_name('package')), (app_label,)) for row in cursor.fetchall(): try: contenttypes_seen[row[0]] except KeyError: # sys.stderr.write("A content type called '%s.%s' was found in the database but not in the model.\n" % (app_label, row[0])) - print "DELETE FROM content_types WHERE package='%s' AND python_module_name = '%s';" % (app_label, row[0]) + print "DELETE FROM %s WHERE %s='%s' AND %s = '%s';" % \ + (db.db.quote_name('content_types'), db.db.quote_name('package'), + app_label, db.db.quote_name('python_module_name'), row[0]) database_check.help_doc = "Checks that everything is installed in the database for the given model module name(s) and prints SQL statements if needed." database_check.args = APP_ARGS @@ -318,7 +358,9 @@ def init(): cursor = db.db.cursor() for sql in get_sql_create(core) + get_sql_create(auth) + get_sql_initial_data(core) + get_sql_initial_data(auth): cursor.execute(sql) - cursor.execute("INSERT INTO %s (domain, name) VALUES ('example.com', 'Example site')" % core.Site._meta.db_table) + cursor.execute("INSERT INTO %s (%s, %s) VALUES ('example.com', 'Example site')" % \ + (db.db.quote_name(core.Site._meta.db_table), db.db.quote_name('domain'), + db.db.quote_name('name'))) except Exception, e: sys.stderr.write("Error: The database couldn't be initialized.\n%s\n" % e) try: @@ -687,7 +729,7 @@ def createcachetable(tablename): table_output = [] index_output = [] for f in fields: - field_output = [f.column, db.DATA_TYPES[f.get_internal_type()] % f.__dict__] + field_output = [db.db.quote_name(f.column), db.DATA_TYPES[f.get_internal_type()] % f.__dict__] field_output.append("%sNULL" % (not f.null and "NOT " or "")) if f.unique: field_output.append("UNIQUE") @@ -695,9 +737,11 @@ def createcachetable(tablename): field_output.append("PRIMARY KEY") if f.db_index: unique = f.unique and "UNIQUE " or "" - index_output.append("CREATE %sINDEX %s_%s ON %s (%s);" % (unique, tablename, f.column, tablename, f.column)) + index_output.append("CREATE %sINDEX %s_%s ON %s (%s);" % \ + (unique, tablename, f.column, db.db.quote_name(tablename), + db.db.quote_name(f.column))) table_output.append(" ".join(field_output)) - full_statement = ["CREATE TABLE %s (" % tablename] + full_statement = ["CREATE TABLE %s (" % db.db.quote_name(tablename)] for i, line in enumerate(table_output): full_statement.append(' %s%s' % (line, i < len(table_output)-1 and ',' or '')) full_statement.append(');') diff --git a/django/core/meta/__init__.py b/django/core/meta/__init__.py index 22ce51d0a5..328a2f449c 100644 --- a/django/core/meta/__init__.py +++ b/django/core/meta/__init__.py @@ -57,14 +57,16 @@ def orderfield2column(f, opts): return f def orderlist2sql(order_list, opts, prefix=''): + if prefix.endswith('.'): + prefix = db.db.quote_name(prefix[:-1]) + '.' output = [] for f in handle_legacy_orderlist(order_list): if f.startswith('-'): - output.append('%s%s DESC' % (prefix, orderfield2column(f[1:], opts))) + output.append('%s%s DESC' % (prefix, db.db.quote_name(orderfield2column(f[1:], opts)))) elif f == '?': output.append(db.get_random_function_sql()) else: - output.append('%s%s ASC' % (prefix, orderfield2column(f, opts))) + output.append('%s%s ASC' % (prefix, db.db.quote_name(orderfield2column(f, opts)))) return ', '.join(output) def get_module(app_label, module_name): @@ -785,27 +787,31 @@ def method_save(opts, self): record_exists = True if pk_set: # Determine whether a record with the primary key already exists. - cursor.execute("SELECT 1 FROM %s WHERE %s=%%s LIMIT 1" % (opts.db_table, opts.pk.column), [pk_val]) + cursor.execute("SELECT 1 FROM %s WHERE %s=%%s LIMIT 1" % \ + (db.db.quote_name(opts.db_table), db.db.quote_name(opts.pk.column)), [pk_val]) # If it does already exist, do an UPDATE. if cursor.fetchone(): db_values = [f.get_db_prep_save(f.pre_save(getattr(self, f.attname), False)) for f in non_pks] - cursor.execute("UPDATE %s SET %s WHERE %s=%%s" % (opts.db_table, - ','.join(['%s=%%s' % f.column for f in non_pks]), opts.pk.attname), + cursor.execute("UPDATE %s SET %s WHERE %s=%%s" % \ + (db.db.quote_name(opts.db_table), + ','.join(['%s=%%s' % db.db.quote_name(f.column) for f in non_pks]), + db.db.quote_name(opts.pk.attname)), db_values + [pk_val]) else: record_exists = False if not pk_set or not record_exists: - field_names = [f.column for f in opts.fields if not isinstance(f, AutoField)] + field_names = [db.db.quote_name(f.column) for f in opts.fields if not isinstance(f, AutoField)] placeholders = ['%s'] * len(field_names) db_values = [f.get_db_prep_save(f.pre_save(getattr(self, f.attname), True)) for f in opts.fields if not isinstance(f, AutoField)] if opts.order_with_respect_to: - field_names.append('_order') + field_names.append(db.db.quote_name('_order')) # TODO: This assumes the database supports subqueries. placeholders.append('(SELECT COUNT(*) FROM %s WHERE %s = %%s)' % \ - (opts.db_table, opts.order_with_respect_to.column)) + (db.db.quote_name(opts.db_table), db.db.quote_name(opts.order_with_respect_to.column))) db_values.append(getattr(self, opts.order_with_respect_to.attname)) - cursor.execute("INSERT INTO %s (%s) VALUES (%s)" % (opts.db_table, - ','.join(field_names), ','.join(placeholders)), db_values) + cursor.execute("INSERT INTO %s (%s) VALUES (%s)" % \ + (db.db.quote_name(opts.db_table), ','.join(field_names), + ','.join(placeholders)), db_values) if opts.has_auto_field: setattr(self, opts.pk.attname, db.get_last_insert_id(cursor, opts.db_table, opts.pk.column)) db.db.commit() @@ -832,12 +838,17 @@ def method_delete(opts, self): for sub_obj in getattr(self, 'get_%s_list' % rel_opts_name)(): sub_obj.delete() for rel_opts, rel_field in opts.get_all_related_many_to_many_objects(): - cursor.execute("DELETE FROM %s WHERE %s_id=%%s" % (rel_field.get_m2m_db_table(rel_opts), - self._meta.object_name.lower()), [getattr(self, opts.pk.attname)]) + cursor.execute("DELETE FROM %s WHERE %s=%%s" % \ + (db.db.quote_name(rel_field.get_m2m_db_table(rel_opts)), + db.db.quote_name(self._meta.object_name.lower() + '_id')), [getattr(self, opts.pk.attname)]) for f in opts.many_to_many: - cursor.execute("DELETE FROM %s WHERE %s_id=%%s" % (f.get_m2m_db_table(opts), self._meta.object_name.lower()), + cursor.execute("DELETE FROM %s WHERE %s=%%s" % \ + (db.db.quote_name(f.get_m2m_db_table(opts)), + db.db.quote_name(self._meta.object_name.lower() + '_id')), [getattr(self, opts.pk.attname)]) - cursor.execute("DELETE FROM %s WHERE %s=%%s" % (opts.db_table, opts.pk.column), [getattr(self, opts.pk.attname)]) + cursor.execute("DELETE FROM %s WHERE %s=%%s" % \ + (db.db.quote_name(opts.db_table), db.db.quote_name(opts.pk.column)), + [getattr(self, opts.pk.attname)]) db.db.commit() setattr(self, opts.pk.attname, None) for f in opts.fields: @@ -854,16 +865,20 @@ def method_delete(opts, self): def method_get_next_in_order(opts, order_field, self): if not hasattr(self, '_next_in_order_cache'): self._next_in_order_cache = opts.get_model_module().get_object(order_by=('_order',), - where=['_order > (SELECT _order FROM %s WHERE %s=%%s)' % (opts.db_table, opts.pk.column), - '%s=%%s' % order_field.column], limit=1, + where=['%s > (SELECT %s FROM %s WHERE %s=%%s)' % \ + (db.db.quote_name('_order'), db.db.quote_name('_order'), + db.db.quote_name(opts.db_table), db.db.quote_name(opts.pk.column)), + '%s=%%s' % db.db.quote_name(order_field.column)], limit=1, params=[getattr(self, opts.pk.attname), getattr(self, order_field.attname)]) return self._next_in_order_cache def method_get_previous_in_order(opts, order_field, self): if not hasattr(self, '_previous_in_order_cache'): self._previous_in_order_cache = opts.get_model_module().get_object(order_by=('-_order',), - where=['_order < (SELECT _order FROM %s WHERE %s=%%s)' % (opts.db_table, opts.pk.column), - '%s=%%s' % order_field.column], limit=1, + where=['%s < (SELECT %s FROM %s WHERE %s=%%s)' % \ + (db.db.quote_name('_order'), db.db.quote_name('_order'), + db.db.quote_name(opts.db_table), db.db.quote_name(opts.pk.column)), + '%s=%%s' % db.db.quote_name(order_field.column)], limit=1, params=[getattr(self, opts.pk.attname), getattr(self, order_field.attname)]) return self._previous_in_order_cache @@ -888,10 +903,13 @@ def method_get_many_to_many(field_with_rel, self): cache_var = '_%s_cache' % field_with_rel.name if not hasattr(self, cache_var): mod = rel.get_model_module() - sql = "SELECT %s FROM %s a, %s b WHERE a.%s = b.%s_id AND b.%s_id = %%s %s" % \ - (','.join(['a.%s' % f.column for f in rel.fields]), rel.db_table, - field_with_rel.get_m2m_db_table(self._meta), rel.pk.column, - rel.object_name.lower(), self._meta.object_name.lower(), rel.get_order_sql('a')) + sql = "SELECT %s FROM %s a, %s b WHERE a.%s = b.%s AND b.%s = %%s %s" % \ + (','.join(['a.%s' % db.db.quote_name(f.column) for f in rel.fields]), + db.db.quote_name(rel.db_table), + db.db.quote_name(field_with_rel.get_m2m_db_table(self._meta)), + db.db.quote_name(rel.pk.column), + db.db.quote_name(rel.object_name.lower() + '_id'), + db.db.quote_name(self._meta.object_name.lower() + '_id'), rel.get_order_sql('a')) cursor = db.db.cursor() cursor.execute(sql, [getattr(self, self._meta.pk.attname)]) setattr(self, cache_var, [getattr(mod, rel.object_name)(*row) for row in cursor.fetchall()]) @@ -916,10 +934,16 @@ def method_set_many_to_many(rel_field, self, id_list): cursor = db.db.cursor() this_id = getattr(self, self._meta.pk.attname) if ids_to_delete: - sql = "DELETE FROM %s WHERE %s_id = %%s AND %s_id IN (%s)" % (m2m_table, self._meta.object_name.lower(), rel.object_name.lower(), ','.join(map(str, ids_to_delete))) + sql = "DELETE FROM %s WHERE %s = %%s AND %s IN (%s)" % \ + (db.db.quote_name(m2m_table), + db.db.quote_name(self._meta.object_name.lower() + '_id'), + db.db.quote_name(rel.object_name.lower() + '_id'), ','.join(map(str, ids_to_delete))) cursor.execute(sql, [this_id]) if ids_to_add: - sql = "INSERT INTO %s (%s_id, %s_id) VALUES (%%s, %%s)" % (m2m_table, self._meta.object_name.lower(), rel.object_name.lower()) + sql = "INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \ + (db.db.quote_name(m2m_table), + db.db.quote_name(self._meta.object_name.lower() + '_id'), + db.db.quote_name(rel.object_name.lower() + '_id')) cursor.executemany(sql, [(this_id, i) for i in ids_to_add]) db.db.commit() try: @@ -965,8 +989,13 @@ def method_set_related_many_to_many(rel_opts, rel_field, self, id_list): m2m_table = rel_field.get_m2m_db_table(rel_opts) this_id = getattr(self, self._meta.pk.attname) cursor = db.db.cursor() - cursor.execute("DELETE FROM %s WHERE %s_id = %%s" % (m2m_table, rel.object_name.lower()), [this_id]) - sql = "INSERT INTO %s (%s_id, %s_id) VALUES (%%s, %%s)" % (m2m_table, rel.object_name.lower(), rel_opts.object_name.lower()) + cursor.execute("DELETE FROM %s WHERE %s = %%s" % \ + (db.db.quote_name(m2m_table), + db.db.quote_name(rel.object_name.lower() + '_id')), [this_id]) + sql = "INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \ + (db.db.quote_name(m2m_table), + db.db.quote_name(rel.object_name.lower() + '_id'), + db.db.quote_name(rel_opts.object_name.lower() + '_id')) cursor.executemany(sql, [(this_id, i) for i in id_list]) db.db.commit() @@ -975,7 +1004,10 @@ def method_set_related_many_to_many(rel_opts, rel_field, self, id_list): def method_set_order(ordered_obj, self, id_list): cursor = db.db.cursor() # Example: "UPDATE poll_choices SET _order = %s WHERE poll_id = %s AND id = %s" - sql = "UPDATE %s SET _order = %%s WHERE %s = %%s AND %s = %%s" % (ordered_obj.db_table, ordered_obj.order_with_respect_to.column, ordered_obj.pk.column) + sql = "UPDATE %s SET %s = %%s WHERE %s = %%s AND %s = %%s" % \ + (db.db.quote_name(ordered_obj.db_table), db.db.quote_name('_order'), + db.db.quote_name(ordered_obj.order_with_respect_to.column), + db.db.quote_name(ordered_obj.pk.column)) rel_val = getattr(self, ordered_obj.order_with_respect_to.rel.field_name) cursor.executemany(sql, [(i, rel_val, j) for i, j in enumerate(id_list)]) db.db.commit() @@ -983,7 +1015,11 @@ def method_set_order(ordered_obj, self, id_list): def method_get_order(ordered_obj, self): cursor = db.db.cursor() # Example: "SELECT id FROM poll_choices WHERE poll_id = %s ORDER BY _order" - sql = "SELECT %s FROM %s WHERE %s = %%s ORDER BY _order" % (ordered_obj.pk.column, ordered_obj.db_table, ordered_obj.order_with_respect_to.column) + sql = "SELECT %s FROM %s WHERE %s = %%s ORDER BY %s" % \ + (db.db.quote_name(ordered_obj.pk.column), + db.db.quote_name(ordered_obj.db_table), + db.db.quote_name(ordered_obj.order_with_respect_to.column), + db.db.quote_name('_order')) rel_val = getattr(self, ordered_obj.order_with_respect_to.rel.field_name) cursor.execute(sql, [rel_val]) return [r[0] for r in cursor.fetchall()] @@ -993,7 +1029,8 @@ def method_get_order(ordered_obj, self): def method_get_next_or_previous(get_object_func, opts, field, is_next, self, **kwargs): op = is_next and '>' or '<' kwargs.setdefault('where', []).append('(%s %s %%s OR (%s = %%s AND %s %s %%s))' % \ - (field.column, op, field.column, opts.pk.column, op)) + (db.db.quote_name(field.column), op, db.db.quote_name(field.column), + db.db.quote_name(opts.pk.column), op)) param = str(getattr(self, field.attname)) kwargs.setdefault('params', []).extend([param, param, getattr(self, opts.pk.attname)]) kwargs['order_by'] = [(not is_next and '-' or '') + field.name, (not is_next and '-' or '') + opts.pk.name] @@ -1080,6 +1117,9 @@ def get_absolute_url(opts, func, self): return settings.ABSOLUTE_URL_OVERRIDES.get('%s.%s' % (opts.app_label, opts.module_name), func)(self) def _get_where_clause(lookup_type, table_prefix, field_name, value): + if table_prefix.endswith('.'): + table_prefix = db.db.quote_name(table_prefix[:-1])+'.' + field_name = db.db.quote_name(field_name) try: return '%s%s %s %%s' % (table_prefix, field_name, db.OPERATOR_MAPPING[lookup_type]) except KeyError: @@ -1160,7 +1200,7 @@ def function_get_values_iterator(opts, klass, **kwargs): cursor = db.db.cursor() _, sql, params = function_get_sql_clause(opts, **kwargs) - select = ['%s.%s' % (opts.db_table, f) for f in fields] + select = ['%s.%s' % (db.db.quote_name(opts.db_table), db.db.quote_name(f)) for f in fields] cursor.execute("SELECT " + (kwargs.get('distinct') and "DISTINCT " or "") + ",".join(select) + sql, params) while 1: rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE) @@ -1181,14 +1221,16 @@ def _fill_table_cache(opts, select, tables, where, old_prefix, cache_tables_seen if f.rel and not f.null: db_table = f.rel.to.db_table if db_table not in cache_tables_seen: - tables.append(db_table) + tables.append(db.db.quote_name(db_table)) else: # The table was already seen, so give it a table alias. new_prefix = '%s%s' % (db_table, len(cache_tables_seen)) - tables.append('%s %s' % (db_table, new_prefix)) + tables.append('%s %s' % (db.db.quote_name(db_table), db.db.quote_name(new_prefix))) db_table = new_prefix cache_tables_seen.append(db_table) - where.append('%s.%s = %s.%s' % (old_prefix, f.column, db_table, f.rel.get_related_field().column)) - select.extend(['%s.%s' % (db_table, f2.column) for f2 in f.rel.to.fields]) + where.append('%s.%s = %s.%s' % \ + (db.db.quote_name(old_prefix), db.db.quote_name(f.column), + db.db.quote_name(db_table), db.db.quote_name(f.rel.get_related_field().column))) + select.extend(['%s.%s' % (db.db.quote_name(db_table), db.db.quote_name(f2.column)) for f2 in f.rel.to.fields]) _fill_table_cache(f.rel.to, select, tables, where, db_table, cache_tables_seen) def _throw_bad_kwarg_error(kwarg): @@ -1247,11 +1289,15 @@ def _parse_lookup(kwarg_items, opts, table_count=0): # Try many-to-many relationships first... for f in current_opts.many_to_many: if f.name == current: - rel_table_alias = 't%s' % table_count + rel_table_alias = db.db.quote_name('t%s' % table_count) table_count += 1 - tables.append('%s %s' % (f.get_m2m_db_table(current_opts), rel_table_alias)) - join_where.append('%s.%s = %s.%s_id' % (current_table_alias, current_opts.pk.column, - rel_table_alias, current_opts.object_name.lower())) + tables.append('%s %s' % \ + (db.db.quote_name(f.get_m2m_db_table(current_opts)), rel_table_alias)) + join_where.append('%s.%s = %s.%s' % \ + (db.db.quote_name(current_table_alias), + db.db.quote_name(current_opts.pk.column), + rel_table_alias, + db.db.quote_name(current_opts.object_name.lower() + '_id'))) # Optimization: In the case of primary-key lookups, we # don't have to do an extra join. if lookup_list and lookup_list[0] == f.rel.to.pk.name and lookup_type == 'exact': @@ -1262,9 +1308,13 @@ def _parse_lookup(kwarg_items, opts, table_count=0): param_required = False else: new_table_alias = 't%s' % table_count - tables.append('%s %s' % (f.rel.to.db_table, new_table_alias)) - join_where.append('%s.%s_id = %s.%s' % (rel_table_alias, f.rel.to.object_name.lower(), - new_table_alias, f.rel.to.pk.column)) + tables.append('%s %s' % (db.db.quote_name(f.rel.to.db_table), + db.db.quote_name(new_table_alias))) + join_where.append('%s.%s = %s.%s' % \ + (db.db.quote_name(rel_table_alias), + db.db.quote_name(f.rel.to.object_name.lower() + '_id'), + db.db.quote_name(new_table_alias), + db.db.quote_name(f.rel.to.pk.column))) current_table_alias = new_table_alias param_required = True current_opts = f.rel.to @@ -1287,9 +1337,11 @@ def _parse_lookup(kwarg_items, opts, table_count=0): params.extend(f.get_db_prep_lookup(lookup_type, kwarg_value)) else: new_table_alias = 't%s' % table_count - tables.append('%s %s' % (f.rel.to.db_table, new_table_alias)) - join_where.append('%s.%s = %s.%s' % (current_table_alias, f.column, \ - new_table_alias, f.rel.to.pk.column)) + tables.append('%s %s' % \ + (db.db.quote_name(f.rel.to.db_table), db.db.quote_name(new_table_alias))) + join_where.append('%s.%s = %s.%s' % \ + (db.db.quote_name(current_table_alias), db.db.quote_name(f.column), + db.db.quote_name(new_table_alias), db.db.quote_name(f.rel.to.pk.column))) current_table_alias = new_table_alias param_required = True current_opts = f.rel.to @@ -1308,8 +1360,9 @@ def _parse_lookup(kwarg_items, opts, table_count=0): return tables, join_where, where, params, table_count def function_get_sql_clause(opts, **kwargs): - select = ["%s.%s" % (opts.db_table, f.column) for f in opts.fields] + select = ["%s.%s" % (db.db.quote_name(opts.db_table), db.db.quote_name(f.column)) for f in opts.fields] tables = [opts.db_table] + (kwargs.get('tables') and kwargs['tables'][:] or []) + tables = [db.db.quote_name(t) for t in tables] where = kwargs.get('where') and kwargs['where'][:] or [] params = kwargs.get('params') and kwargs['params'][:] or [] @@ -1328,7 +1381,7 @@ def function_get_sql_clause(opts, **kwargs): # Add any additional SELECTs passed in via kwargs. if kwargs.get('select'): - select.extend(['(%s) AS %s' % (s[1], s[0]) for s in kwargs['select']]) + select.extend(['(%s) AS %s' % (db.db.quote_name(s[1]), db.db.quote_name(s[0])) for s in kwargs['select']]) # ORDER BY clause order_by = [] @@ -1342,13 +1395,17 @@ def function_get_sql_clause(opts, **kwargs): else: col_name = f order = "ASC" - # Use the database table as a column prefix if it wasn't given, - # and if the requested column isn't a custom SELECT. - if "." not in col_name and col_name not in [k[0] for k in kwargs.get('select', [])]: - table_prefix = opts.db_table + '.' + if "." in col_name: + table_prefix, col_name = col_name.split('.', 1) + table_prefix = db.db.quote_name(table_prefix) + '.' else: - table_prefix = '' - order_by.append('%s%s %s' % (table_prefix, orderfield2column(col_name, opts), order)) + # Use the database table as a column prefix if it wasn't given, + # and if the requested column isn't a custom SELECT. + if "." not in col_name and col_name not in [k[0] for k in kwargs.get('select', [])]: + table_prefix = db.db.quote_name(opts.db_table) + '.' + else: + table_prefix = '' + order_by.append('%s%s %s' % (table_prefix, db.db.quote_name(orderfield2column(col_name, opts)), order)) order_by = ", ".join(order_by) # LIMIT and OFFSET clauses @@ -1363,7 +1420,7 @@ def function_get_sql_clause(opts, **kwargs): def function_get_in_bulk(opts, klass, *args, **kwargs): id_list = args and args[0] or kwargs['id_list'] assert id_list != [], "get_in_bulk() cannot be passed an empty list." - kwargs['where'] = ["%s.%s IN (%s)" % (opts.db_table, opts.pk.column, ",".join(['%s'] * len(id_list)))] + kwargs['where'] = ["%s.%s IN (%s)" % (db.db.quote_name(opts.db_table), db.db.quote_name(opts.pk.column), ",".join(['%s'] * len(id_list)))] kwargs['params'] = id_list obj_list = function_get_list(opts, klass, **kwargs) return dict([(getattr(o, opts.pk.attname), o) for o in obj_list]) @@ -1384,9 +1441,10 @@ def function_get_date_list(opts, field, *args, **kwargs): assert order in ('ASC', 'DESC'), "'order' must be either 'ASC' or 'DESC'" kwargs['order_by'] = [] # Clear this because it'll mess things up otherwise. if field.null: - kwargs.setdefault('where', []).append('%s.%s IS NOT NULL' % (opts.db_table, field.column)) + kwargs.setdefault('where', []).append('%s.%s IS NOT NULL' % \ + (db.db.quote_name(opts.db_table), db.db.quote_name(field.column))) select, sql, params = function_get_sql_clause(opts, **kwargs) - sql = 'SELECT %s %s GROUP BY 1 ORDER BY 1' % (db.get_date_trunc_sql(kind, '%s.%s' % (opts.db_table, field.column)), sql) + sql = 'SELECT %s %s GROUP BY 1 ORDER BY 1' % (db.get_date_trunc_sql(kind, '%s.%s' % (db.db.quote_name(opts.db_table), db.db.quote_name(field.column))), sql) cursor = db.db.cursor() cursor.execute(sql, params) # We have to manually run typecast_timestamp(str()) on the results, because diff --git a/django/models/auth.py b/django/models/auth.py index ef13fbea69..d0c13f66ce 100644 --- a/django/models/auth.py +++ b/django/models/auth.py @@ -91,12 +91,25 @@ class User(meta.Model): if not hasattr(self, '_group_perm_cache'): import sets cursor = db.cursor() - cursor.execute(""" - SELECT p.package, p.codename - FROM auth_permissions p, auth_groups_permissions gp, auth_users_groups ug - WHERE p.id = gp.permission_id - AND gp.group_id = ug.group_id - AND ug.user_id = %s""", [self.id]) + # The SQL below works out to the following, after DB quoting: + # cursor.execute(""" + # SELECT p.package, p.codename + # FROM auth_permissions p, auth_groups_permissions gp, auth_users_groups ug + # WHERE p.id = gp.permission_id + # AND gp.group_id = ug.group_id + # AND ug.user_id = %s""", [self.id]) + sql = """ + SELECT p.%s, p.%s + FROM %s p, %s gp, %s ug + WHERE p.%s = gp.%s + AND gp.%s = ug.%s + AND ug.%s = %%s""" % ( + db.quote_name('package'), db.quote_name('codename'), + db.quote_name('auth_permissions'), db.quote_name('auth_groups_permissions'), + db.quote_name('auth_users_groups'), db.quote_name('id'), + db.quote_name('permission_id'), db.quote_name('group_id'), + db.quote_name('group_id'), db.quote_name('user_id')) + cursor.execute(sql, [self.id]) self._group_perm_cache = sets.Set(["%s.%s" % (row[0], row[1]) for row in cursor.fetchall()]) return self._group_perm_cache diff --git a/docs/model-api.txt b/docs/model-api.txt index 51086e426e..1864de7e6a 100644 --- a/docs/model-api.txt +++ b/docs/model-api.txt @@ -34,15 +34,21 @@ Django will use ``first_name`` and ``last_name`` as the database column names. Each field type, except for ``ForeignKey``, ``ManyToManyField`` and ``OneToOneField``, takes an optional first positional argument -- a -human-readable name. If the human-readable name isn't given, Django will use -the machine-readable name, converting underscores to spaces. +human-readable name. If the human-readable name isn't given, Django will +automatically create the human-readable name by using the machine-readable +name, converting underscores to spaces. -Example:: +In this example, the human-readable name is ``"Person's first name"``:: first_name = meta.CharField("Person's first name", maxlength=30) -For ``ForeignKey``, ``ManyToManyField`` and ``OneToOneField``, use the -``verbose_name`` keyword argument:: +In this example, the human-readable name is ``"first name"``:: + + first_name = meta.CharField(maxlength=30) + +``ForeignKey``, ``ManyToManyField`` and ``OneToOneField`` require the first +argument to be a model class, so use the ``verbose_name`` keyword argument to +specify the human-readable name:: poll = meta.ForeignKey(Poll, verbose_name="the related poll") sites = meta.ManyToManyField(Site, verbose_name="list of sites") @@ -111,6 +117,11 @@ The following arguments are available to all field types. All are optional. The name of the database column to use for this field. If this isn't given, Django will use the field's name. + If your database column name is an SQL reserved word, or contains + characters that aren't allowed in Python variable names -- notably, the + hyphen -- that's OK. Django quotes column and table names behind the + scenes. + ``db_index`` If ``True``, ``django-admin.py sqlindexes`` will output a ``CREATE INDEX`` statement for this field. @@ -700,6 +711,10 @@ Here's a list of all possible ``META`` options. No options are required. Adding If this isn't given, Django will use ``app_label + '_' + module_name``. + If your database table name is an SQL reserved word, or contains characters + that aren't allowed in Python variable names -- notably, the hyphen -- + that's OK. Django quotes column and table names behind the scenes. + ``exceptions`` Names of extra exception subclasses to include in the generated module. These exceptions are available from instance methods and from module-level @@ -732,8 +747,8 @@ Here's a list of all possible ``META`` options. No options are required. Adding module_name = "pizza_orders" If this isn't given, Django will use a lowercased version of the class - name, plus "s". This "poor man's pluralization" is intentional: Any other - level of magic pluralization would get confusing. + name, plus ``"s"``. This "poor man's pluralization" is intentional: Any + other level of magic pluralization would get confusing. ``order_with_respect_to`` Marks this object as "orderable" with respect to the given field. This is diff --git a/tests/testapp/models/__init__.py b/tests/testapp/models/__init__.py index baa048d123..c0e39fae7d 100644 --- a/tests/testapp/models/__init__.py +++ b/tests/testapp/models/__init__.py @@ -1,4 +1,4 @@ __all__ = ['basic', 'repr', 'custom_methods', 'many_to_one', 'many_to_many', 'ordering', 'lookup', 'get_latest', 'm2m_intermediary', 'one_to_one', 'm2o_recursive', 'm2o_recursive2', 'save_delete_hooks', 'custom_pk', - 'subclassing', 'many_to_one_null', 'custom_columns'] + 'subclassing', 'many_to_one_null', 'custom_columns', 'reserved_names'] diff --git a/tests/testapp/models/reserved_names.py b/tests/testapp/models/reserved_names.py new file mode 100644 index 0000000000..eabe41e5bd --- /dev/null +++ b/tests/testapp/models/reserved_names.py @@ -0,0 +1,47 @@ +""" +18. Using SQL reserved names + +Need to use a reserved SQL name as a column name or table name? Need to include +a hyphen in a column or table name? No problem. Django quotes names +appropriately behind the scenes, so your database won't complain about +reserved-name usage. +""" + +from django.core import meta + +class Thing(meta.Model): + when = meta.CharField(maxlength=1, primary_key=True) + join = meta.CharField(maxlength=1) + like = meta.CharField(maxlength=1) + drop = meta.CharField(maxlength=1) + alter = meta.CharField(maxlength=1) + having = meta.CharField(maxlength=1) + where = meta.CharField(maxlength=1) + has_hyphen = meta.CharField(maxlength=1, db_column='has-hyphen') + class META: + db_table = 'select' + + def __repr__(self): + return self.when + +API_TESTS = """ +>>> t = things.Thing(when='a', join='b', like='c', drop='d', alter='e', having='f', where='g', has_hyphen='h') +>>> t.save() +>>> print t.when +a + +>>> u = things.Thing(when='h', join='i', like='j', drop='k', alter='l', having='m', where='n') +>>> u.save() +>>> print u.when +h + +>>> things.get_list(order_by=['when']) +[a, h] +>>> v = things.get_object(pk='a') +>>> print v.join +b +>>> print v.where +g +>>> things.get_list(order_by=['select.when']) +[a, h] +""" From 33b7ef290ee0b4a5dbbb4f58b352f63e1b9ed2e8 Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Mon, 14 Nov 2005 04:16:09 +0000 Subject: [PATCH 02/10] Fixed #786 -- Atom feeds now put links in a href element. Thanks, mattycakes git-svn-id: http://code.djangoproject.com/svn/django/trunk@1225 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/utils/feedgenerator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/utils/feedgenerator.py b/django/utils/feedgenerator.py index 696b55d493..fe0f9ce263 100644 --- a/django/utils/feedgenerator.py +++ b/django/utils/feedgenerator.py @@ -209,7 +209,7 @@ class Atom1Feed(SyndicationFeed): for item in self.items: handler.startElement(u"entry", {}) handler.addQuickElement(u"title", item['title']) - handler.addQuickElement(u"link", item['link']) + handler.addQuickElement(u"link", u"", {u"href": item['link']}) if item['pubdate'] is not None: handler.addQuickElement(u"updated", rfc2822_date(item['pubdate']).decode('ascii')) From 7b315b47aa09611ff293fd6cfaf21b86f7045a2b Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Mon, 14 Nov 2005 04:28:31 +0000 Subject: [PATCH 03/10] Fixed #784 -- Atom feeds now use RFC3339 datetime format git-svn-id: http://code.djangoproject.com/svn/django/trunk@1226 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/utils/feedgenerator.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/django/utils/feedgenerator.py b/django/utils/feedgenerator.py index fe0f9ce263..86c1a046ec 100644 --- a/django/utils/feedgenerator.py +++ b/django/utils/feedgenerator.py @@ -27,6 +27,9 @@ from xml.parsers.expat import ExpatError def rfc2822_date(date): return email.Utils.formatdate(time.mktime(date.timetuple())) +def rfc3339_date(date): + return date.strftime('%Y-%m-%dT%H:%M:%SZ') + def get_tag_uri(url, date): "Creates a TagURI. See http://diveintomark.org/archives/2004/05/28/howto-atom-id" tag = re.sub('^http://', '', url) @@ -189,7 +192,7 @@ class Atom1Feed(SyndicationFeed): handler.addQuickElement(u"title", self.feed['title']) handler.addQuickElement(u"link", "", {u"href": self.feed['link']}) handler.addQuickElement(u"id", self.feed['link']) - handler.addQuickElement(u"updated", rfc2822_date(self.latest_post_date()).decode('ascii')) + handler.addQuickElement(u"updated", rfc3339_date(self.latest_post_date()).decode('ascii')) if self.feed['author_name'] is not None: handler.startElement(u"author", {}) handler.addQuickElement(u"name", self.feed['author_name']) @@ -211,7 +214,7 @@ class Atom1Feed(SyndicationFeed): handler.addQuickElement(u"title", item['title']) handler.addQuickElement(u"link", u"", {u"href": item['link']}) if item['pubdate'] is not None: - handler.addQuickElement(u"updated", rfc2822_date(item['pubdate']).decode('ascii')) + handler.addQuickElement(u"updated", rfc3339_date(item['pubdate']).decode('ascii')) # Author information. if item['author_name'] is not None: From 7626cb058107ac1ecb0795162e4c9fc4f92356dd Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Mon, 14 Nov 2005 04:59:20 +0000 Subject: [PATCH 04/10] Improved Atom feed-generating framework to output . Added a feed_url hook to feedgenerator for this purpose, and changed the syndication Feed and views to use it. Also updated docs. git-svn-id: http://code.djangoproject.com/svn/django/trunk@1227 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/syndication/feeds.py | 6 ++++-- django/contrib/syndication/views.py | 2 +- django/utils/feedgenerator.py | 8 ++++++-- docs/syndication_feeds.txt | 18 +++++++++++++++++- 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/django/contrib/syndication/feeds.py b/django/contrib/syndication/feeds.py index c495990c51..8b49334fec 100644 --- a/django/contrib/syndication/feeds.py +++ b/django/contrib/syndication/feeds.py @@ -17,8 +17,9 @@ class Feed: item_enclosure_url = None feed_type = feedgenerator.DefaultFeed - def __init__(self, slug): + def __init__(self, slug, feed_url): self.slug = slug + self.feed_url = feed_url def item_link(self, item): try: @@ -56,7 +57,8 @@ class Feed: title = self.__get_dynamic_attr('title', obj), link = link, description = self.__get_dynamic_attr('description', obj), - language = LANGUAGE_CODE.decode() + language = LANGUAGE_CODE.decode(), + feed_url = add_domain(current_site, self.feed_url), ) try: diff --git a/django/contrib/syndication/views.py b/django/contrib/syndication/views.py index 3bab3bcac9..5117746f19 100644 --- a/django/contrib/syndication/views.py +++ b/django/contrib/syndication/views.py @@ -17,7 +17,7 @@ def feed(request, url, feed_dict=None): raise Http404, "Slug %r isn't registered." % slug try: - feedgen = f(slug).get_feed(param) + feedgen = f(slug, request.path).get_feed(param) except feeds.FeedDoesNotExist: raise Http404, "Invalid feed parameters. Slug %r is valid, but other parameters, or lack thereof, are not." % slug diff --git a/django/utils/feedgenerator.py b/django/utils/feedgenerator.py index 86c1a046ec..a8dd5c39df 100644 --- a/django/utils/feedgenerator.py +++ b/django/utils/feedgenerator.py @@ -41,7 +41,8 @@ def get_tag_uri(url, date): class SyndicationFeed: "Base class for all syndication feeds. Subclasses should provide write()" def __init__(self, title, link, description, language=None, author_email=None, - author_name=None, author_link=None, subtitle=None, categories=None): + author_name=None, author_link=None, subtitle=None, categories=None, + feed_url=None): self.feed = { 'title': title, 'link': link, @@ -52,6 +53,7 @@ class SyndicationFeed: 'author_link': author_link, 'subtitle': subtitle, 'categories': categories or (), + 'feed_url': feed_url, } self.items = [] @@ -190,7 +192,9 @@ class Atom1Feed(SyndicationFeed): else: handler.startElement(u"feed", {u"xmlns": self.ns}) handler.addQuickElement(u"title", self.feed['title']) - handler.addQuickElement(u"link", "", {u"href": self.feed['link']}) + handler.addQuickElement(u"link", "", {u"rel": u"alternate", u"href": self.feed['link']}) + if self.feed['feed_url'] is not None: + handler.addQuickElement(u"link", "", {u"rel": u"self", u"href": self.feed['feed_url']}) handler.addQuickElement(u"id", self.feed['link']) handler.addQuickElement(u"updated", rfc3339_date(self.latest_post_date()).decode('ascii')) if self.feed['author_name'] is not None: diff --git a/docs/syndication_feeds.txt b/docs/syndication_feeds.txt index 4298bc81b6..453fe71e9d 100644 --- a/docs/syndication_feeds.txt +++ b/docs/syndication_feeds.txt @@ -266,6 +266,21 @@ comes directly from your `LANGUAGE_CODE setting`_. .. _LANGUAGE_CODE setting: http://www.djangoproject.com/documentation/settings/#language-code +URLs +---- + +The ``link`` method/attribute can return either an absolute URL (e.g. +``"/blog/"``) or a URL with the fully-qualified domain and protocol (e.g. +``"http://www.example.com/blog/"``). If ``link`` doesn't return the domain, +the syndication framework will insert the domain of the current site, according +to your `SITE_ID setting`_. + +Atom feeds require a ```` that defines the feed's current +location. The syndication framework populates this automatically, using the +domain of the current site according to the SITE_ID setting. + +.. _SITE_ID setting: http://www.djangoproject.com/documentation/settings/#site-id + Publishing Atom and RSS feeds in tandem --------------------------------------- @@ -501,7 +516,8 @@ Each of these three classes knows how to render a certain type of feed as XML. They share this interface: ``__init__(title, link, description, language=None, author_email=None,`` -``author_name=None, author_link=None, subtitle=None, categories=None)`` +``author_name=None, author_link=None, subtitle=None, categories=None,`` +``feed_url=None)`` Initializes the feed with the given metadata, which applies to the entire feed (i.e., not just to a specific item in the feed). From 74865663bbaddf4d2d8cd5472e2f137ebe0ec347 Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Mon, 14 Nov 2005 05:15:40 +0000 Subject: [PATCH 05/10] Fixed #787 -- High-level syndication framework now picks up author details. Also updated documentation. Thanks, mattycakes git-svn-id: http://code.djangoproject.com/svn/django/trunk@1228 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/syndication/feeds.py | 19 +++++- django/utils/feedgenerator.py | 5 +- docs/syndication_feeds.txt | 102 ++++++++++++++++++++++++++++ 3 files changed, 123 insertions(+), 3 deletions(-) diff --git a/django/contrib/syndication/feeds.py b/django/contrib/syndication/feeds.py index 8b49334fec..49b34858dc 100644 --- a/django/contrib/syndication/feeds.py +++ b/django/contrib/syndication/feeds.py @@ -27,8 +27,11 @@ class Feed: except AttributeError: raise ImproperlyConfigured, "Give your %s class a get_absolute_url() method, or define an item_link() method in your Feed class." % item.__class__.__name__ - def __get_dynamic_attr(self, attname, obj): - attr = getattr(self, attname) + def __get_dynamic_attr(self, attname, obj, default=None): + try: + attr = getattr(self, attname) + except AttributeError: + return default if callable(attr): try: return attr(obj) @@ -59,6 +62,9 @@ class Feed: description = self.__get_dynamic_attr('description', obj), language = LANGUAGE_CODE.decode(), feed_url = add_domain(current_site, self.feed_url), + author_name = self.__get_dynamic_attr('author_name', obj), + author_link = self.__get_dynamic_attr('author_link', obj), + author_email = self.__get_dynamic_attr('author_email', obj), ) try: @@ -80,6 +86,12 @@ class Feed: length = str(self.__get_dynamic_attr('item_enclosure_length', item)).decode('utf-8'), mime_type = self.__get_dynamic_attr('item_enclosure_mime_type', item).decode('utf-8'), ) + author_name = self.__get_dynamic_attr('item_author_name', item) + if author_name is not None: + author_email = self.__get_dynamic_attr('item_author_email', item) + author_link = self.__get_dynamic_attr('item_author_link', item) + else: + author_email = author_link = None feed.add_item( title = title_template.render(Context({'obj': item, 'site': current_site})).decode('utf-8'), link = link, @@ -87,5 +99,8 @@ class Feed: unique_id = link, enclosure = enc, pubdate = self.__get_dynamic_attr('item_pubdate', item), + author_name = author_name, + author_email = author_email, + author_link = author_link, ) return feed diff --git a/django/utils/feedgenerator.py b/django/utils/feedgenerator.py index a8dd5c39df..22db5abe7f 100644 --- a/django/utils/feedgenerator.py +++ b/django/utils/feedgenerator.py @@ -58,7 +58,7 @@ class SyndicationFeed: self.items = [] def add_item(self, title, link, description, author_email=None, - author_name=None, pubdate=None, comments=None, + author_name=None, author_link=None, pubdate=None, comments=None, unique_id=None, enclosure=None, categories=()): """ Adds an item to the feed. All args are expected to be Python Unicode @@ -71,6 +71,7 @@ class SyndicationFeed: 'description': description, 'author_email': author_email, 'author_name': author_name, + 'author_link': author_link, 'pubdate': pubdate, 'comments': comments, 'unique_id': unique_id, @@ -226,6 +227,8 @@ class Atom1Feed(SyndicationFeed): handler.addQuickElement(u"name", item['author_name']) if item['author_email'] is not None: handler.addQuickElement(u"email", item['author_email']) + if item['author_link'] is not None: + handler.addQuickElement(u"uri", item['author_link']) handler.endElement(u"author") # Unique ID. diff --git a/docs/syndication_feeds.txt b/docs/syndication_feeds.txt index 453fe71e9d..94c10180ad 100644 --- a/docs/syndication_feeds.txt +++ b/docs/syndication_feeds.txt @@ -386,6 +386,55 @@ This example illustrates all possible attributes and methods for a ``Feed`` clas description = 'Foo bar baz.' # Hard-coded description. + # AUTHOR NAME --One of the following three is optional. The framework + # looks for them in this order. + + def author_name(self, obj): + """ + Takes the object returned by get_object() and returns the feed's + author's name as a normal Python string. + """ + + def author_name(self): + """ + Returns the feed's author's name as a normal Python string. + """ + + author_name = 'Sally Smith' # Hard-coded author name. + + # AUTHOR E-MAIL --One of the following three is optional. The framework + # looks for them in this order. + + def author_email(self, obj): + """ + Takes the object returned by get_object() and returns the feed's + author's e-mail as a normal Python string. + """ + + def author_name(self): + """ + Returns the feed's author's e-mail as a normal Python string. + """ + + author_email = 'test@example.com' # Hard-coded author e-mail. + + # AUTHOR LINK --One of the following three is optional. The framework + # looks for them in this order. In each case, the URL should include + # the "http://" and domain name. + + def author_link(self, obj): + """ + Takes the object returned by get_object() and returns the feed's + author's URL as a normal Python string. + """ + + def author_link(self): + """ + Returns the feed's author's URL as a normal Python string. + """ + + author_link = 'http://www.example.com/' # Hard-coded author URL. + # ITEMS -- One of the following three is required. The framework looks # for them in this order. @@ -428,6 +477,59 @@ This example illustrates all possible attributes and methods for a ``Feed`` clas Returns the URL for every item in the feed. """ + # ITEM AUTHOR NAME --One of the following three is optional. The + # framework looks for them in this order. + + def item_author_name(self, item): + """ + Takes an item, as returned by items(), and returns the item's + author's name as a normal Python string. + """ + + def item_author_name(self): + """ + Returns the author name for every item in the feed. + """ + + item_author_name = 'Sally Smith' # Hard-coded author name. + + # ITEM AUTHOR E-MAIL --One of the following three is optional. The + # framework looks for them in this order. + # + # If you specify this, you must specify item_author_name. + + def item_author_email(self, obj): + """ + Takes an item, as returned by items(), and returns the item's + author's e-mail as a normal Python string. + """ + + def item_author_email(self): + """ + Returns the author e-mail for every item in the feed. + """ + + item_author_email = 'test@example.com' # Hard-coded author e-mail. + + # ITEM AUTHOR LINK --One of the following three is optional. The + # framework looks for them in this order. In each case, the URL should + # include the "http://" and domain name. + # + # If you specify this, you must specify item_author_name. + + def item_author_link(self, obj): + """ + Takes an item, as returned by items(), and returns the item's + author's URL as a normal Python string. + """ + + def item_author_link(self): + """ + Returns the author URL for every item in the feed. + """ + + item_author_link = 'http://www.example.com/' # Hard-coded author URL. + # ITEM ENCLOSURE URL -- One of these three is required if you're # publishing enclosures. The framework looks for them in this order. From 4decd32b0ebd1275abda421874ada9daa166a0c1 Mon Sep 17 00:00:00 2001 From: Georg Bauer Date: Mon, 14 Nov 2005 13:28:42 +0000 Subject: [PATCH 06/10] added the missing icenlandic message file (didn't svn add last time and didn't notice) git-svn-id: http://code.djangoproject.com/svn/django/trunk@1229 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/conf/locale/is/LC_MESSAGES/django.mo | Bin 0 -> 17844 bytes django/conf/locale/is/LC_MESSAGES/django.po | 1013 +++++++++++++++++++ 2 files changed, 1013 insertions(+) create mode 100644 django/conf/locale/is/LC_MESSAGES/django.mo create mode 100644 django/conf/locale/is/LC_MESSAGES/django.po diff --git a/django/conf/locale/is/LC_MESSAGES/django.mo b/django/conf/locale/is/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..184eea60005eb52aed970962d69c693f71bd267c GIT binary patch literal 17844 zcmc(leUxNnediwnh>oZzs8JCwqhohNzs!Irwllym)65J^!!SJqgC@YOs#{fe>eju* zed(rfb(Lhn#ApzsMu{U6%?KI{GRY*4!n&4;ZlV%5*<^DPj3+tS&3ZODIY~}7XXDx3 z&-Z!mt*(BVfw+I{J^j@8zC5qL_uupM_g;3^M-10@DHl>c^E_i-b%rsMFVb6M9(buS zbKr--*Moly9svIZyb0X*GGl7s!{CpDXP$2iU6>2OGr`w`XMhDhQVlV|Y&M$x)!Owu2*Ehg7f=`3v;6H(vgRgsqF|P#|K+)$e zkWc0#pz41f+yee5@CNX#SGsxa12wOM;FaL*;3D`jP;_}Q#1y_9RJ{w!=S`s2J6S$o z242JSTS3k99#HGP7u*Hj4{AM6gYN?WynO!2SGoFKQ1y%Q`JVFm-V*NzHIJVI&jmjW zYMh6`G4K)a&EW5YSAgfd+RbkpDE{vNReu+#er^IcfVY4WF(14XOu!d|4}s#}=RnQt z*FmlSOXc&|K=t9szFyp8-AaEhxu*;34o{@Xx_FfHjzZ8F)Xad7K3A2md>$aqqgw$=7>8t>ZyZ z^M9y({xm3l{wk<>JyCxD3dqpr>!9fVeNcMvHzk_aIeMNC)_MPGQ2oBUe19K!70(|4 zMTc*JcYyy5L=?>~&&lKMp!oJVP<;44C_8X2${=~30JZLU@F4hypxR%8a%kP#LGeEa zH-VoA4}sqWZv`)*lNs<{kf!DdQ1kjja2I$!L=;`#0c!pY@JuiQMgJZ+2L3KM4*orO z7Wf(l6TLi8^t%MqIGe#1cqjN4@S9*0lrpM*3)FabfoFsFfa3dmK&}6S;12L4$e;P& z{D^*=n1sf^29&&nB^KcGcz!P^e!mY?{~rU-0Urj%zehlg^BbVr56bsXm(PC&s{LPs z(%1i4zCVXSC8y_un*T-R^EjyaT@Gp;Z!h29394QP)Hp@?{9aIWdw==-K>7U>;5ocM z20kDBLW#c#O5VO&KK}uz`TrTHet!UJoPPpk@63ej=M|vVdl9I5mx9j+-vVmQz5oT&Qkf@-%2 zs{eO_>i?%o{8>iAI{QWYhc{~HE-@h*L??Ki3A-EHKDaN${-U6!sFM~f1{ss6!FvZA?foELl_&NbH zb#oW^CGZ$1xw!MK&aS@))Oeo+HU4Ko@%IT(>wK#G{`C^S1IqsW7f|*70o1zw4=6so zbk_AV0R}w32^3vEQNI5)$W+Z2K&|_)z}vuc-sal-;BKD#pzQV6z;W<@fvW%dt&YEQ z;DbE3!FPcF0=^o&b(@QSQc(RI2N5mvLoftqP>wy|$3e;GUxJr{o33{4ZU%4Q`9VOThEME#M4zEhxUHAg03nF?b<(HqJ?O+6cY`d^@Oihe7r8PEh>07ZiVg z0o1%cRpKMy3wZt_sP z0$;&%0&1N5K-GHy)Ht64&jKFQ0z4^nhJO?i^?Q*BIS4No%{JVAokiD%g3>ia*xHh_!5^pS`Z3DGlnzDLBNR-9 z`5I-6@^MOa&2#f!N?yJ=4o*=%OF>Po-`E1ao+8>kMG(IsB$ z+Defg>v|1k(ca+?z&j}~qFhSR^#G+$DJWm2=z2dz{QZg^xbCJ%{;KO@Zr)ryl=x2Y zBb0YgP%9g!{wnww5#@FEj##b4PlK2bdr8K0ZK6C%IYHS+xrkC-k8^V$Wr6Y? z%Jq~lP;|YUa+$qDN5MBxGRjX-zD4;t%73KXLecf}lpT~C^e}S$`|{o!d3QVa()kAE z!;~v1Z>Q*b(1F#JnENLvm`byk@{5$WQpPE|W*nH=5)YO5yI`%n7bKMLQhtqcKjl-D zFHts9zCo$3^SQZ)BK!55l--mMQCgH^6kR7NpRsqw{0evhrCB};K1caw$}9E2brURCOvC&In&-dp2`lOd9UKT__J@>jv8hClzkNFx*8*d@#`Kg}=Ubl$y zuong1o}HPu6y|NOu70v?shvCQ_vELOXA&+|jeXjPlN0FB_ZN+91Mahy@kOmQ)YItEDbVXF^)Pl0l z#;n<1w3tiLDze;M>o=ml*G$qb^$*SOo%J?l=Gq``!6$QV>faMap&y$a2tq3;!Rz+D zo}Xn)N!qAhEk7r5Gfca~3bSTMQXqhFk~1&Z9xMikLO*Y7rp+|zGUBw^aZgZho1NPa z%-wP8o&&q@IIw4a?vCpY?%iv4vXq%T>;`6MP@mRM(5(e2H;_nutVg7=Wg?AvOLhWj zkRiE0U_?7$uBn}Hk#SQ;C8DIooahgNpd*bg6U<;7D?JR&6Zb02a+FWPQ@&Ld z_sQ%mdQn)HY9e+842^JHc?pg#@T-nZzhSBW#7(pR6=;4X@T8nl-mJM}U=sL+kZh z-0gR0~F)B zpk{y4!cY4=1rbUU*w%HclumFe{nNx-iB*eIqb|=r(QkbbCpf%PLE#GqVR* z*=U4PMb)+yhh`!3SXF`A^*ziZYOhi4G_1N?L+nRc;YM9}I52yyZ7IR?TYebZ2SgHF z3evit1s-M;#q<9Zcm&Ay=+j@)Lt&ytv#hH*17``V&` zGk{1d5r85Cl;O~!LsMlrR2g1E4MI69de-_)n|)$+;MJpqU&($E+xrGliVf0iaAlqP zity^$W2Um_V1*yqv#;`5!=YDJXax1J%i7$6Tr`S6R+3eH?nQnbU0!#Ls?Kxi0(1PM z^pPj1sdF*^&mBCsDXbc%mJp>k!ysx9o21fLOfBr|diG18a9~xF3?o{GXtWqAxunN- zw5qzb4azerU73^+;T`Km8s_~m93_6wITHqsJ;z7eOg^J(=H*La9sihkONbfa(IT4N z>|4K}*h(?2#I>HJDKT-xwD%JbVxBAWu-`hV(Fk>af>oB9?=LXQ;DVnQsjLb)4XUnR zr}+y(TC){`lySYeG-VB2gYH%1S8U!qv2}(!YTIvtzO8uhY$dZ0;`&2q)bMr?;xZev z5G9L#$1FfnFAwg{UA7}1ydMuT&Z2^130uNwlI+p2d7)(Fz zD)mSRED_^eVlooA&nP)w?uMc%t>Y zSY$aZa2Z2_UnfZSs?d%QGi%#)2(#Y|@1`ZssT9c@5f-bLJz{&JH@gm|ByFToK*q0( zfcez2BqE1Lc`+~JVsi~2Iw2F|dw8+PlGo<)5_T(QW9>Ze&CU=Bvp9MkuDE30ir1BY zYK4oeKWxHwTYtjFb(_YmEKOviJ)ORZbnDd3U*Q3Jftmf3fjA0M73oz}? zEoT_y({Y~7WPwlYG^DE~jd(^B1lQQ2VZ$L4yUBo<9x1BJc}jx2`W5wjucz=(wUnIO znU2)3=?rsm0ksPsCx=mm@)*nyaR*jfDa^mB`Q&I@n_di4p#$wA&8%4?%N3%%=AaEo zhZ(BVJP*Zze?+KAvNw!b;YlUG*2*GNQdB3faw!F#02kx5_h65N|2ag+2Z@DX-u%uh z$mg=QH#KEViFlU!%dTzOP%*VX;^C~l%gik{vEF@Pe&t?~x6mWDX%f|1okzFQV0Oc% z%-m{))GBC~IKF+cU8}ZY6oI2rqyEF?@~3W+sm-eGf-+dXH5emD%^*tO?Tgs;upJg; zqLH#Aq^jE4ku_0_J8fu3aN+C?^?aKWF1vSUrYdvfHY6;QvZB@vNkkgc-fh|u8(Jy+ zd=M_mfZ+NoW5qBHe5-`u)*wQNpJ2(M(9Gr7oQPZlc zB`=R>HMX2;EvWk>adxWO&~sEkPpX*S1?u#Xu3T}Lk*SRndh-yFsh1`X2)Got6dH9Ka+1eujAG<2>6T3f*~M<%$t$G)jenhpDbYENV877Q^%&wAy~;g;#m8}{33i0@Z1d(TrnYRE+H$$K#bL1o-N|# z)oEFyf#0kRwPzqv7{5*Gm9lxYt;m~GS1#An%$h+uH5a2Fn3!4b%37FjI5f2{k#U%+ zW-;sSsk5g}09{}H$i6lr*XP@gxxL$O+__<2RHS}1bsa$+N+0*^W47(Gw|VY%TkKuD z&D*kpt*$69yf_>@o`$1`GQr?+f8O{iK$S&!SQ@>tdr9#3f2g{ zaaF;&wSy*VN0X7mnF+cZIh;9de>!tHU0(zV)L7faB70(fY8UBQ>ez*c$b7-l`f(JX z_|I*wyF#VEi$SxAXxYVfQoo6=n(c$fTR}Q_+*~_2mJ|5H7vuNR!DEq`OJNj(o_R&r zQgrat;3O}i!y&Wki(l1ALzSnjJf9hSG>Qr@Xy#$sWOqt5CAEZAYM$q_W^j}^pP#`= zX0j)W;H{DZn78sYChPb*kI6zf;_hj$R{}5U!{&s3Lm1i>Ti1Ru3zCs0o@=E3ESs{h z8)ft2!BIvXoJ?KJH+Zzu!NoG{jo#r&@3u*AA7?Qwv?xYXX7a&f-Ow}?-|^$}=Q#)> zM-3lWQ)R`04AMkJZ}6Dc9Gu{!4bFPm;890WIw;fwDO{@o)!XD40HSVbPg~Kh(+RzF z@Mz8n5G1YS-Em|EwO#XBQuP)rQ$_W;NY0~3Neqc&?Z?t-)=4c1*tx~;GTu??T`c*} z^23AUXL3k!q)Yf9imjxt;U*sgg5IHUb7!{!YuZi(*4z~ zFuEl(ExlJA)*wiyZDIw$7mY?5E}y!rZBAOfa=B}|XR`yLvVE@Zh#aG^JZT5)-rAm4 zOfO)(7{}|r5JWh`2O*qvS-aR>J)-AGKRAKRmMvhG700T<&cU&`Soa`j9}$D)0Y3!( zP1w*G9OaxY?C6B1i@hW&@tZk5aN0C=jKfblIO(TmaKuV`O2xzYLaM404%F7_vUFqc z)FQf~DzYJ^TrxUJ^B7Wq4kJ-rf)uABD~th3gEhF?TNsjXC~w#~8sI-{*Rkv?IbF*d zK=y{&5k>2eVWb2PvfS&i8Fe#iFr%k5xp?p-^_;t6P@VqZUy)a|Jj)B~Y+c zQXh8@j`xbx0+cz;;m@fTsq-H#T?=J*uSKUp*fn!aUb1ZbWNHsEML-0$CL*HXrM^Qp zEP-9AB}4{GO3KDQCmA88b#ZX4g)A+r3{%}+bcQC#$4WqpZfR3EiRm(8ieYJr$}(=4 z-45;w`#w09A}}&ACkTU}T`}w@w4JmqNo0bkmdW`R=>Fitn325lVD|iH@Bwy~MMRE@ zr}bl{>+s+tG&VpCp!TeX&fJ}>226Jr4(WvSuEyXbhe9|a=1IW5+F0?%S)g5hr-Lra zO<-YKtcRtF`$ezUj<`XCEMc{1`+*bfSU#u2c~7W>vxb`u9jS>na9Yzy#lNOkl!3fP zZp?3;s>dz?pTUoc06OqVaO&N`(YO@D?WNCWU2>PO^F}MZqf*X)N(>&yvXoY3gfPy4 z4{5?-Ijcsrd<7&rSQ1a-!Euh2^y|E1(!mb!GdQk;rCoj(L$-3bxyp*w2FE+69&SNe zj~pRFSPprVb)3Bo3`%FbJPP(vJv-mDO0S16<=> zLWFYD4RmF;?7t6Aaz^JuAK$W`D9Bo}H3v`f9PZ@F#-oz?X>$u)fMBQ(D~^&5a#nar z52YJ9c%r3MpISdXjP7X#G6~B8?&%^>qS+OLAj2zsEN4;jUwECnH@mIaK6XH6DSAQX*`Sx~nM+^U0n$XNSc$ z@K+wwkm-W0Ho%i8cd^ea*fG)%iW}Q5)*7KzjnIQKx@fIYO4&*rq;+a1(te8=W7@-D zma>s8CZ*e3Ru*hwPRbzj?|fmEy0vj4Y+;pVu|>p(2CT88F3Pnwn4>cUT^@p88|{+m z4W4vfB=x%;+)`ID4&QAC6Q)BYUhT29xrVBuhSnu>mt`)zk9?TIc^E0U;on)%HvKN2 zh&gwCLy(dzk@;Y?Rvn3rd@>{V<$)MR4gU}CSi=qIBZ`l8^f(p6GP?NS0B2jJ=d1F} zRho+_7>S&T)sYex6=4=QPi%$Hex$U}BbJk4&WgBl0|)RsM9C?pTr^fjfLz%i^eM>? z!ziM+VHT3eBXm--y0#hh)FSD0{PbcoayC}xr()z9i{!GID(iM>!n0VZ6`Nu>WkpX6 zY;JLxt-_7!bWA$KvQSZ*)}u)z0{9h#xXo^+f|Pa6#YQ{Q(Gw|BNqy#TLy+Y|v2H(Z zWSxLP(W#LMtk(|pSM}8uVJ+ukC_2$j1d|o&ENE{;D`7;t^}$SK4a5Lq%9iyAl0-*8 zf^}8K?M!>SiF(S{NSa=wgq?oOu^t?9*_sV)ZJ0ljl7tF@qQm?q5di0g3-sxyhS89hFugXMED z<~`DZT{vSb_j2k!tWG|cj5g=!N`;Obwg!- zYYx~dKHH>z4Y@E#awyia+ubUXer2}A4jI4Af;9A5D9#=q+Xc929los?jR=ioNy+B9K`y~!BOQ0fs}`hw6;|YLON9!bCpLSy>5TTGFZ)>hO}5+BroEu z$ZjRur%hF=O;L9;hsEY(&i2`6HgIUvW!oK8h*qMnJrn(Rnac~!E@fW!kPgim*?i<* zG8W3*j@ipQ7O6d^8}1HhQ?8c!Eo;v%_HNQ19cv&kq$fKYB@_IIG2QS_HPxoVdd*_j zYQ-i}*=}$|I|Q(9o@?msk?HDi;_E9p(+Kw1RnIl)Zjv+kE9lNsO;e3lxOI}&8MqC*k#%4T`3G$0N37yo(W|ilt!8_H{=5W4?AahXeygws!*W3!N?k$ zw%g=S`yA7z7N*)&^f`)ba@vMvWXu&imUc)YO+Rld%L%jxWeBI#i47^Sgu2%epY5a> z4NfQoRsw5}0MQy}plv%At`JNcm_gXFQ-M6#cet`5?lEMOY^*Y8+ZBdjxS?amvl)UN zu_JrrYtJP^H_hlHDY_PhJ+-GJcBdIKHi@juc~`G`#CsuUgo^FV&}`V|UU^_hFLu$U z=I$(#q+@EzTYOaYkzSUGJz`FXyzRLlX%Y<4abeCs^Ag2H_7)aPE%XbWS+uewAZ=#N i!SSfWDWQ9pP^%=Qn`EYd3g^MeEGLEPul#H&@P7f-)NsH6 literal 0 HcmV?d00001 diff --git a/django/conf/locale/is/LC_MESSAGES/django.po b/django/conf/locale/is/LC_MESSAGES/django.po new file mode 100644 index 0000000000..d1dba14849 --- /dev/null +++ b/django/conf/locale/is/LC_MESSAGES/django.po @@ -0,0 +1,1013 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: Django 1.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2005-11-13 13:41-0600\n" +"PO-Revision-Date: 2005-11-13 18:08-0000\n" +"Last-Translator: Dagur Páll Ammendrup \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Poedit-Language: Icelandic\n" +"X-Poedit-Country: ICELAND\n" +"Plural-Forms: nplurals=2; plural=n != 1\n" + +#: contrib/admin/models/admin.py:6 +msgid "action time" +msgstr "tími aðgerðar" + +#: contrib/admin/models/admin.py:9 +msgid "object id" +msgstr "einkenni hlutar" + +#: contrib/admin/models/admin.py:10 +msgid "object repr" +msgstr "framsetning hlutar" + +#: contrib/admin/models/admin.py:11 +msgid "action flag" +msgstr "aðgerðarveifa" + +#: contrib/admin/models/admin.py:12 +msgid "change message" +msgstr "breyta skilaboði" + +#: contrib/admin/models/admin.py:15 +msgid "log entry" +msgstr "kladdafærsla" + +#: contrib/admin/models/admin.py:16 +msgid "log entries" +msgstr "kladdafærslur" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/500.html:4 +#: contrib/admin/templates/admin/base.html:29 +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/logged_out.html:4 +#: contrib/admin/templates/registration/password_reset_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +msgid "Home" +msgstr "Heim" + +#: contrib/admin/templates/admin/object_history.html:5 +msgid "History" +msgstr "Saga" + +#: contrib/admin/templates/admin/object_history.html:18 +msgid "Date/time" +msgstr "Dagsetning/tími" + +#: contrib/admin/templates/admin/object_history.html:19 models/auth.py:47 +msgid "User" +msgstr "Notandi" + +#: contrib/admin/templates/admin/object_history.html:20 +msgid "Action" +msgstr "Aðgerð" + +#: contrib/admin/templates/admin/object_history.html:26 +msgid "DATE_WITH_TIME_FULL" +msgstr "N j, Y, P" + +#: contrib/admin/templates/admin/object_history.html:36 +msgid "" +"This object doesn't have a change history. It probably wasn't added via this " +"admin site." +msgstr "" +"Þessi hlutur hefur enga breytingasögu. Hann var líklega ekki búinn til á " +"þessari stjórnunarsíðu." + +#: contrib/admin/templates/admin/base_site.html:4 +msgid "Django site admin" +msgstr "Django vefstjóri" + +#: contrib/admin/templates/admin/base_site.html:7 +msgid "Django administration" +msgstr "Django vefstjórn" + +#: contrib/admin/templates/admin/500.html:4 +msgid "Server error" +msgstr "Kerfisvilla" + +#: contrib/admin/templates/admin/500.html:6 +msgid "Server error (500)" +msgstr "Kerfisvilla (500)" + +#: contrib/admin/templates/admin/500.html:9 +msgid "Server Error (500)" +msgstr "Kerfisvilla (500)" + +#: contrib/admin/templates/admin/500.html:10 +msgid "" +"There's been an error. It's been reported to the site administrators via e-" +"mail and should be fixed shortly. Thanks for your patience." +msgstr "" +"Villa hefur komið upp. Hún hefur verið tilkynnt vefstjórunum með tölvupósti " +"og ætti að verða lagað fljótlega. Þökkum þolinmæðina." + +#: contrib/admin/templates/admin/404.html:4 +#: contrib/admin/templates/admin/404.html:8 +msgid "Page not found" +msgstr "Síða fannst ekki" + +#: contrib/admin/templates/admin/404.html:10 +msgid "We're sorry, but the requested page could not be found." +msgstr "Því miður fannst umbeðin síða ekki." + +#: contrib/admin/templates/admin/index.html:27 +msgid "Add" +msgstr "Bæta við" + +#: contrib/admin/templates/admin/index.html:33 +msgid "Change" +msgstr "Breyta" + +#: contrib/admin/templates/admin/index.html:43 +msgid "You don't have permission to edit anything." +msgstr "Þú hefur ekki réttindi til að breyta nokkru" + +#: contrib/admin/templates/admin/index.html:51 +msgid "Recent Actions" +msgstr "Nýlega framkvæmdar aðgerðir" + +#: contrib/admin/templates/admin/index.html:52 +msgid "My Actions" +msgstr "Aðgerðir mínar" + +#: contrib/admin/templates/admin/index.html:56 +msgid "None available" +msgstr "Engin fáanleg" + +#: contrib/admin/templates/admin/login.html:15 +msgid "Username:" +msgstr "Notandanafn:" + +#: contrib/admin/templates/admin/login.html:18 +msgid "Password:" +msgstr "Lykilorð:" + +#: contrib/admin/templates/admin/login.html:20 +msgid "Have you forgotten your password?" +msgstr "Gleymdir þú lykilorðinu þínu?" + +#: contrib/admin/templates/admin/login.html:24 +msgid "Log in" +msgstr "Skrá inn" + +#: contrib/admin/templates/admin/base.html:23 +msgid "Welcome," +msgstr "Velkomin(n)," + +#: contrib/admin/templates/admin/base.html:23 +msgid "Change password" +msgstr "Breyta lykilorði" + +#: contrib/admin/templates/admin/base.html:23 +msgid "Log out" +msgstr "Skrá út" + +#: contrib/admin/templates/admin/delete_confirmation.html:7 +#, python-format +msgid "" +"Deleting the %(object_name)s '%(object)s' would result in deleting related " +"objects, but your account doesn't have permission to delete the following " +"types of objects:" +msgstr "" +"Eyðing á %(object_name)s '%(object)s' hefði í för með sér eyðingu á tengdum " +"hlutum en þú hefur ekki réttindi til að eyða eftirfarandi hlutum:" + +#: contrib/admin/templates/admin/delete_confirmation.html:14 +#, python-format +msgid "" +"Are you sure you want to delete the %(object_name)s \"%(object)s\"? All of " +"the following related items will be deleted:" +msgstr "" +"Ertu viss um að þú viljir eyða %(object_name)s \"%(object)s\"? Öllu " +"eftirfarandi verður eytt:" + +#: contrib/admin/templates/admin/delete_confirmation.html:18 +msgid "Yes, I'm sure" +msgstr "Já ég er viss." + +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/registration/password_change_form.html:6 +#: contrib/admin/templates/registration/password_change_form.html:10 +msgid "Password change" +msgstr "Breyta lykilorði" + +#: contrib/admin/templates/registration/password_change_done.html:6 +#: contrib/admin/templates/registration/password_change_done.html:10 +msgid "Password change successful" +msgstr "Breyting á lykilorði tókst" + +#: contrib/admin/templates/registration/password_change_done.html:12 +msgid "Your password was changed." +msgstr "Lykilorði þínu var breytt" + +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:6 +#: contrib/admin/templates/registration/password_reset_done.html:4 +msgid "Password reset" +msgstr "Endursetja lykilorð" + +#: contrib/admin/templates/registration/password_reset_form.html:12 +msgid "" +"Forgotten your password? Enter your e-mail address below, and we'll reset " +"your password and e-mail the new one to you." +msgstr "" +"Gleymdir þú lykilorðinu þínu? Settu inn póstfangið þitt að neðan og við " +"munum endursetja lykilorðið þitt og senda það nýja til þín." + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "E-mail address:" +msgstr "Tölvupóstfang:" + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "Reset my password" +msgstr "Endursetja lykilorðið mitt" + +#: contrib/admin/templates/registration/logged_out.html:8 +msgid "Thanks for spending some quality time with the Web site today." +msgstr "Takk fyrir að nota tíma þinn í vefsíðuna í dag." + +#: contrib/admin/templates/registration/logged_out.html:10 +msgid "Log in again" +msgstr "Skráðu þig inn aftur" + +#: contrib/admin/templates/registration/password_reset_done.html:6 +#: contrib/admin/templates/registration/password_reset_done.html:10 +msgid "Password reset successful" +msgstr "Endursetning á lykilorði tókst" + +#: contrib/admin/templates/registration/password_reset_done.html:12 +msgid "" +"We've e-mailed a new password to the e-mail address you submitted. You " +"should be receiving it shortly." +msgstr "" +"Við sendum nýtt lykilorð á tölvupóstfangið sem þú gafst upp. Það ætti að " +"berast þér fljótlega." + +#: contrib/admin/templates/registration/password_change_form.html:12 +msgid "" +"Please enter your old password, for security's sake, and then enter your new " +"password twice so we can verify you typed it in correctly." +msgstr "" +"Vinsamlegast settu inn gamla lykilorðið þitt til öryggis. Skrifaðu svo nýja " +"lykilorðið tvisvar til þess að hægt sé að ganga úr skugga um að þú hafir " +"ritað það rétt." + +#: contrib/admin/templates/registration/password_change_form.html:17 +msgid "Old password:" +msgstr "Gamla lykilorðið:" + +#: contrib/admin/templates/registration/password_change_form.html:19 +msgid "New password:" +msgstr "Nýja lykilorðið:" + +#: contrib/admin/templates/registration/password_change_form.html:21 +msgid "Confirm password:" +msgstr "Staðfesta lykilorð:" + +#: contrib/admin/templates/registration/password_change_form.html:23 +msgid "Change my password" +msgstr "Breyta lykilorðinu mínu" + +#: contrib/admin/templates/registration/password_reset_email.html:2 +msgid "You're receiving this e-mail because you requested a password reset" +msgstr "" +"Þú fékkst þennan tölvupóst vegna þess að þú baðst um endursetningu á " +"lykilorðinu þínu" + +#: contrib/admin/templates/registration/password_reset_email.html:3 +#, python-format +msgid "for your user account at %(site_name)s" +msgstr "fyrir notandareikning þinn á %(site_name)s" + +#: contrib/admin/templates/registration/password_reset_email.html:5 +#, python-format +msgid "Your new password is: %(new_password)s" +msgstr "Nýja lykilorðið þitt er: %(new_password)s" + +#: contrib/admin/templates/registration/password_reset_email.html:7 +msgid "Feel free to change this password by going to this page:" +msgstr "Þér er frjálst að breyta lykilorðinu með því að fara á þessa síðu:" + +#: contrib/admin/templates/registration/password_reset_email.html:11 +msgid "Your username, in case you've forgotten:" +msgstr "Notandanafnið þitt ef þú skyldir hafa gleymt því:" + +#: contrib/admin/templates/registration/password_reset_email.html:13 +msgid "Thanks for using our site!" +msgstr "Takk fyrir að nota vefinn okkar!" + +#: contrib/admin/templates/registration/password_reset_email.html:15 +#, python-format +msgid "The %(site_name)s team" +msgstr "%(site_name)s hópurinn" + +#: contrib/redirects/models/redirects.py:7 +msgid "redirect from" +msgstr "beina frá" + +#: contrib/redirects/models/redirects.py:8 +msgid "" +"This should be an absolute path, excluding the domain name. Example: '/" +"events/search/'." +msgstr "Þetta þarf að vera full slóð án lénsins. Dæmi: '/events/search/'." + +#: contrib/redirects/models/redirects.py:9 +msgid "redirect to" +msgstr "beina til" + +#: contrib/redirects/models/redirects.py:10 +msgid "" +"This can be either an absolute path (as above) or a full URL starting with " +"'http://'." +msgstr "" +"Þetta getur verið full slóð (eins og hér að ofan) eða veffang með 'http://' " +"fremst." + +#: contrib/redirects/models/redirects.py:12 +msgid "redirect" +msgstr "beining" + +#: contrib/redirects/models/redirects.py:13 +msgid "redirects" +msgstr "beiningar" + +#: contrib/flatpages/models/flatpages.py:6 +msgid "URL" +msgstr "veffang" + +#: contrib/flatpages/models/flatpages.py:7 +msgid "" +"Example: '/about/contact/'. Make sure to have leading and trailing slashes." +msgstr "Dænu: '/about/contact/'. Passaðu að hafa skástrik fremst og aftast." + +#: contrib/flatpages/models/flatpages.py:8 +msgid "title" +msgstr "titill" + +#: contrib/flatpages/models/flatpages.py:9 +msgid "content" +msgstr "innihald" + +#: contrib/flatpages/models/flatpages.py:10 +msgid "enable comments" +msgstr "virkja athugasemdir" + +#: contrib/flatpages/models/flatpages.py:11 +msgid "template name" +msgstr "nafn sniðmáts" + +#: contrib/flatpages/models/flatpages.py:12 +#, fuzzy +msgid "" +"Example: 'flatpages/contact_page'. If this isn't provided, the system will " +"use 'flatpages/default'." +msgstr "" +"Dæmi: 'flatfiles/contact_page'. Ef ekkert er gefið upp mun kerfið nota " +"'flatfiles/default'." + +#: contrib/flatpages/models/flatpages.py:13 +msgid "registration required" +msgstr "skráning nauðsynleg" + +#: contrib/flatpages/models/flatpages.py:13 +msgid "If this is checked, only logged-in users will be able to view the page." +msgstr "Ef þetta er valið fá bara innskráðir notendur að sjá síðuna." + +#: contrib/flatpages/models/flatpages.py:17 +msgid "flat page" +msgstr "flöt síða" + +#: contrib/flatpages/models/flatpages.py:18 +msgid "flat pages" +msgstr "flatar síður" + +#: utils/translation.py:335 +msgid "DATE_FORMAT" +msgstr "" + +#: utils/translation.py:336 +msgid "DATETIME_FORMAT" +msgstr "" + +#: utils/translation.py:337 +msgid "TIME_FORMAT" +msgstr "" + +#: utils/dates.py:6 +msgid "Monday" +msgstr "mánudagur" + +#: utils/dates.py:6 +msgid "Tuesday" +msgstr "þriðjudagur" + +#: utils/dates.py:6 +msgid "Wednesday" +msgstr "miðvikudagur" + +#: utils/dates.py:6 +msgid "Thursday" +msgstr "fimmtudagur" + +#: utils/dates.py:6 +msgid "Friday" +msgstr "föstudagur" + +#: utils/dates.py:7 +msgid "Saturday" +msgstr "laugardagur" + +#: utils/dates.py:7 +msgid "Sunday" +msgstr "sunnudagur" + +#: utils/dates.py:14 +msgid "January" +msgstr "janúar" + +#: utils/dates.py:14 +msgid "February" +msgstr "febrúar" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "March" +msgstr "mars" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "April" +msgstr "apríl" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "May" +msgstr "maí" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "June" +msgstr "júní" + +#: utils/dates.py:15 utils/dates.py:27 +msgid "July" +msgstr "júlí" + +#: utils/dates.py:15 +msgid "August" +msgstr "ágúst" + +#: utils/dates.py:15 +msgid "September" +msgstr "september" + +#: utils/dates.py:15 +msgid "October" +msgstr "október" + +#: utils/dates.py:15 +msgid "November" +msgstr "nóvember" + +#: utils/dates.py:16 +msgid "December" +msgstr "desember" + +#: utils/dates.py:27 +msgid "Jan." +msgstr "jan." + +#: utils/dates.py:27 +msgid "Feb." +msgstr "feb." + +#: utils/dates.py:28 +msgid "Aug." +msgstr "ág." + +#: utils/dates.py:28 +msgid "Sept." +msgstr "sept." + +#: utils/dates.py:28 +msgid "Oct." +msgstr "okt." + +#: utils/dates.py:28 +msgid "Nov." +msgstr "nóv." + +#: utils/dates.py:28 +msgid "Dec." +msgstr "des." + +#: models/core.py:7 +msgid "domain name" +msgstr "lén" + +#: models/core.py:8 +msgid "display name" +msgstr "birtingarnafn" + +#: models/core.py:10 +msgid "site" +msgstr "vefur" + +#: models/core.py:11 +msgid "sites" +msgstr "vefir" + +#: models/core.py:28 +msgid "label" +msgstr "merking" + +#: models/core.py:29 models/core.py:40 models/auth.py:6 models/auth.py:19 +msgid "name" +msgstr "nafn" + +#: models/core.py:31 +msgid "package" +msgstr "pakki" + +#: models/core.py:32 +msgid "packages" +msgstr "pakkar" + +#: models/core.py:42 +msgid "python module name" +msgstr "nafn python einingar" + +#: models/core.py:44 +msgid "content type" +msgstr "efnistag" + +#: models/core.py:45 +msgid "content types" +msgstr "efnistög" + +#: models/core.py:67 +msgid "session key" +msgstr "setulykill" + +#: models/core.py:68 +msgid "session data" +msgstr "setugögn" + +#: models/core.py:69 +msgid "expire date" +msgstr "fyrningardagsetning" + +#: models/core.py:71 +msgid "session" +msgstr "seta" + +#: models/core.py:72 +msgid "sessions" +msgstr "setur" + +#: models/auth.py:8 +msgid "codename" +msgstr "vinnuheiti" + +#: models/auth.py:10 +msgid "Permission" +msgstr "Réttindi" + +#: models/auth.py:11 models/auth.py:58 +msgid "Permissions" +msgstr "Réttindi" + +#: models/auth.py:22 +msgid "Group" +msgstr "Hópur" + +#: models/auth.py:23 models/auth.py:60 +msgid "Groups" +msgstr "Hópar" + +#: models/auth.py:33 +msgid "username" +msgstr "notandanafn" + +#: models/auth.py:34 +msgid "first name" +msgstr "fornafn" + +#: models/auth.py:35 +msgid "last name" +msgstr "eftirnafn" + +#: models/auth.py:36 +msgid "e-mail address" +msgstr "tölvupóstfang" + +#: models/auth.py:37 +msgid "password" +msgstr "lykilorð" + +#: models/auth.py:37 +msgid "Use an MD5 hash -- not the raw password." +msgstr "Notaðu MD5 hakk -- ekki hrátt lykilorðið" + +#: models/auth.py:38 +msgid "staff status" +msgstr "staða starfsfólks" + +#: models/auth.py:38 +msgid "Designates whether the user can log into this admin site." +msgstr "" +"Segir til um hvort notandinn getur skráð sig inn á þennan stjórnunarvef." + +#: models/auth.py:39 +msgid "active" +msgstr "virkur" + +#: models/auth.py:40 +msgid "superuser status" +msgstr "staða ofurnotanda" + +#: models/auth.py:41 +msgid "last login" +msgstr "síðasta innskráning" + +#: models/auth.py:42 +msgid "date joined" +msgstr "skráning dags." + +#: models/auth.py:44 +msgid "" +"In addition to the permissions manually assigned, this user will also get " +"all permissions granted to each group he/she is in." +msgstr "" +"Auk réttindanna sem notandanum var gefið sérstaklega fær hann öll þau " +"réttindi sem hópurinn hans hefur." + +#: models/auth.py:48 +msgid "Users" +msgstr "Notendur" + +#: models/auth.py:57 +msgid "Personal info" +msgstr "Persónuupplýsingar" + +#: models/auth.py:59 +msgid "Important dates" +msgstr "Mikilvægar dagsetningar" + +#: models/auth.py:182 +msgid "Message" +msgstr "Skilaboð" + +#: conf/global_settings.py:36 +msgid "Bengali" +msgstr "Bengalska" + +#: conf/global_settings.py:37 +msgid "Czech" +msgstr "Tékkneska" + +#: conf/global_settings.py:38 +msgid "Welsh" +msgstr "Velska" + +#: conf/global_settings.py:39 +msgid "German" +msgstr "Þýska" + +#: conf/global_settings.py:40 +msgid "English" +msgstr "Enska" + +#: conf/global_settings.py:41 +msgid "Spanish" +msgstr "Spænska" + +#: conf/global_settings.py:42 +msgid "French" +msgstr "Franska" + +#: conf/global_settings.py:43 +msgid "Galician" +msgstr "Galíska" + +#: conf/global_settings.py:44 +msgid "Icelandic" +msgstr "" + +#: conf/global_settings.py:45 +msgid "Italian" +msgstr "Ítalska" + +#: conf/global_settings.py:46 +msgid "Norwegian" +msgstr "Norska" + +#: conf/global_settings.py:47 +msgid "Brazilian" +msgstr "Brasilíska" + +#: conf/global_settings.py:48 +msgid "Romanian" +msgstr "Rúmenska" + +#: conf/global_settings.py:49 +msgid "Russian" +msgstr "Rússneska" + +#: conf/global_settings.py:50 +msgid "Slovak" +msgstr "Slóvanska" + +#: conf/global_settings.py:51 +msgid "Serbian" +msgstr "Serbíska" + +#: conf/global_settings.py:52 +msgid "Swedish" +msgstr "" + +#: conf/global_settings.py:53 +msgid "Simplified Chinese" +msgstr "Einfölduð Kínverska " + +#: core/validators.py:59 +msgid "This value must contain only letters, numbers and underscores." +msgstr "Þetta gildi má einungis innihalda stafi, tölur og undirstrik." + +#: core/validators.py:63 +msgid "This value must contain only letters, numbers, underscores and slashes." +msgstr "" +"Þetta gildi má einungis innihalda stafi, tölur, undirstrik og skástrik." + +#: core/validators.py:71 +msgid "Uppercase letters are not allowed here." +msgstr "Hástafir eru ekki leyfðir hér." + +#: core/validators.py:75 +msgid "Lowercase letters are not allowed here." +msgstr "Lágstafir eru ekki leyfðir hér." + +#: core/validators.py:82 +msgid "Enter only digits separated by commas." +msgstr "Skrifaðu einungis tölur aðskildar með kommum." + +#: core/validators.py:94 +msgid "Enter valid e-mail addresses separated by commas." +msgstr "Skrifaðu gild póstföng aðskilin með kommum." + +#: core/validators.py:98 +msgid "Please enter a valid IP address." +msgstr "Vinsamlegast skrifaðu gilda IP tölu." + +#: core/validators.py:102 +msgid "Empty values are not allowed here." +msgstr "Tóm gildi eru ekki leyfð hér." + +#: core/validators.py:106 +msgid "Non-numeric characters aren't allowed here." +msgstr "Aðeins tölustafir eru leyfðir hér." + +#: core/validators.py:110 +msgid "This value can't be comprised solely of digits." +msgstr "Þetta gildi verður að vera samsett úr fleiru en tölustöfum." + +#: core/validators.py:115 +msgid "Enter a whole number." +msgstr "Settu inn heila tölu." + +#: core/validators.py:119 +msgid "Only alphabetical characters are allowed here." +msgstr "Einungis bókstafir eru leyfðir hér." + +#: core/validators.py:123 +msgid "Enter a valid date in YYYY-MM-DD format." +msgstr "Skrifaðu gilda dagsetningu á YYYY-MM-DD forminu." + +#: core/validators.py:127 +msgid "Enter a valid time in HH:MM format." +msgstr "Skrifaðu gildan tíma á HH:MM forminu." + +#: core/validators.py:131 +msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." +msgstr "Skrifaðu gilda dagsetningu/tíma á YYYY-MM-DD HH:MM forminu." + +#: core/validators.py:135 +msgid "Enter a valid e-mail address." +msgstr "Skrifaðu gilt tölvupóstfang." + +#: core/validators.py:147 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "" +"Hladdu upp gilda myndskrá. Skráin sem þú hlóðst upp var annað hvort ekki " +"mynd eða gölluð mynd." + +#: core/validators.py:154 +#, python-format +msgid "The URL %s does not point to a valid image." +msgstr "Veffangið %s bendir ekki á gilda mynd." + +#: core/validators.py:158 +#, python-format +msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid." +msgstr "Símanúmer verða að vera á XXX-XXX-XXXX forminu. \"%s\" er ógilt." + +#: core/validators.py:166 +#, python-format +msgid "The URL %s does not point to a valid QuickTime video." +msgstr "Veffangið %s bendir ekki á gilt QuickTime myndskeið." + +#: core/validators.py:170 +msgid "A valid URL is required." +msgstr "Gilds veffangs er krafist" + +#: core/validators.py:184 +#, python-format +msgid "" +"Valid HTML is required. Specific errors are:\n" +"%s" +msgstr "" +"Gilt HTML er nauðsynlegt. Sérstakar villur:\n" +"%s" + +#: core/validators.py:191 +#, python-format +msgid "Badly formed XML: %s" +msgstr "Illa formað XML: %s" + +#: core/validators.py:201 +#, python-format +msgid "Invalid URL: %s" +msgstr "Ógilt veffang: %s" + +#: core/validators.py:205 core/validators.py:207 +#, python-format +msgid "The URL %s is a broken link." +msgstr "Veffangið %s er brotinn hlekkur." + +#: core/validators.py:213 +msgid "Enter a valid U.S. state abbreviation." +msgstr "Skrifaðu gilda styttingu á bandarísku fylkisnafni." + +#: core/validators.py:228 +#, python-format +msgid "Watch your mouth! The word %s is not allowed here." +msgid_plural "Watch your mouth! The words %s are not allowed here." +msgstr[0] "Passaðu orðbragðið! Orðið %s er ekki leyft hér." +msgstr[1] "Passaðu orðbragðið! Orðin %s eru ekki leyfð hér." + +#: core/validators.py:235 +#, python-format +msgid "This field must match the '%s' field." +msgstr "Þessi reitur verður að passa við '%s' reitinn." + +#: core/validators.py:254 +msgid "Please enter something for at least one field." +msgstr "Vinsamlegast fylltu út einn reit að minnsta kosti." + +#: core/validators.py:263 core/validators.py:274 +msgid "Please enter both fields or leave them both empty." +msgstr "Vinsamlegast fylltu út í báða reitina eða skildu þá eftir tóma." + +#: core/validators.py:281 +#, python-format +msgid "This field must be given if %(field)s is %(value)s" +msgstr "Þessi reitur á að vera óútfylltur ef %(field)s er %(value)s" + +#: core/validators.py:293 +#, python-format +msgid "This field must be given if %(field)s is not %(value)s" +msgstr "Þessi reitur verður að vera útfylltur ef %(field)s er ekki %(value)s" + +#: core/validators.py:312 +msgid "Duplicate values are not allowed." +msgstr "Endurtekin gildi eru ekki leyfð." + +#: core/validators.py:335 +#, python-format +msgid "This value must be a power of %s." +msgstr "Þessi reitur verður að vera veldi af %s." + +#: core/validators.py:346 +msgid "Please enter a valid decimal number." +msgstr "Vinsamlegast settu inn gilda tugatölu." + +#: core/validators.py:348 +#, python-format +msgid "Please enter a valid decimal number with at most %s total digit." +msgid_plural "" +"Please enter a valid decimal number with at most %s total digits." +msgstr[0] "Vinsamlegast skrifaðu gilda tugatölu með %s tugastaf í mesta lagi." +msgstr[1] "Vinsamlegast skrifaðu gilda tugatölu með %s tugastafi í mesta lagi." + +#: core/validators.py:351 +#, python-format +msgid "Please enter a valid decimal number with at most %s decimal place." +msgid_plural "" +"Please enter a valid decimal number with at most %s decimal places." +msgstr[0] "Vinsamlegast skrifaðu gilda tugatölu með %s tugasæti í mesta lagi." +msgstr[1] "Vinsamlegast skrifaðu gilda tugatölu með %s tugasæti í mesta lagi." + +#: core/validators.py:361 +#, python-format +msgid "Make sure your uploaded file is at least %s bytes big." +msgstr "" +"Gakktu úr skugga um að upphlaðin skrá sé að minnsta kosti %s bæti að stærð." + +#: core/validators.py:362 +#, python-format +msgid "Make sure your uploaded file is at most %s bytes big." +msgstr "" +"Gakktu úr skugga um að upphlaðin skrá sé í mesta lagi %s bæti að stærð." + +#: core/validators.py:375 +msgid "The format for this field is wrong." +msgstr "Sniðið á þessum reit í rangt." + +#: core/validators.py:390 +msgid "This field is invalid." +msgstr "Þessi reitur er ótækur." + +#: core/validators.py:425 +#, python-format +msgid "Could not retrieve anything from %s." +msgstr "Gat engu náð úr %s." + +#: core/validators.py:428 +#, python-format +msgid "" +"The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'." +msgstr "Veffangið %(url)s skilaði ótæka efnistagishausnum '%(contenttype)s'." + +#: core/validators.py:461 +#, python-format +msgid "" +"Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with " +"\"%(start)s\".)" +msgstr "" +"Vinsamlegast lokaðu opna %(tag)s taginu sem byrjar á línu %(line)s (línan " +"hefst á \"%(start)s\")." + +#: core/validators.py:465 +#, python-format +msgid "" +"Some text starting on line %(line)s is not allowed in that context. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"Texti sem hefst á línu %(line)s er ekki leyfður í því samhengi. (Line starts " +"with \"%(start)s\".)" + +#: core/validators.py:470 +#, python-format +msgid "" +"\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%" +"(start)s\".)" +msgstr "" +"\"%(attr)s\" á línu %(line)s er ótækt eigindi (línan hefst á \"%(start)s\")." + +#: core/validators.py:475 +#, python-format +msgid "" +"\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%" +"(start)s\".)" +msgstr "" +"\"<%(tag)s>\" á línu %(line)s er ótækt tag (línan hefst á \"%(start)s\"." + +#: core/validators.py:479 +#, python-format +msgid "" +"A tag on line %(line)s is missing one or more required attributes. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"Tag á línu %(line)s vantar eitt eða fleiri nauðsynleg eigindi (línan hefst á " +"\"%(start)s\")." + +#: core/validators.py:484 +#, python-format +msgid "" +"The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"\"%(attr)s\" eigindið á línu %(line)s hefur ótækt gildi (línan hefst á \"%" +"(start)s\")." + +#: core/meta/fields.py:111 +msgid " Separate multiple IDs with commas." +msgstr "Notaðu kommur til að aðskilja kenni." + +#: core/meta/fields.py:114 +msgid "" +" Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgstr "" +"Haltu niðri \"Control\", eða \"Command\" á Mac til þess að velja fleira en " +"eitt." From 5319f285a669c0012f07a37352dd6addd4da5509 Mon Sep 17 00:00:00 2001 From: Georg Bauer Date: Mon, 14 Nov 2005 13:29:38 +0000 Subject: [PATCH 07/10] fixes bug with blocktrans and parameters not actually doing the translation (thx. nesh for the report) git-svn-id: http://code.djangoproject.com/svn/django/trunk@1230 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/templatetags/i18n.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/django/templatetags/i18n.py b/django/templatetags/i18n.py index e9702f2dc3..8873586c24 100644 --- a/django/templatetags/i18n.py +++ b/django/templatetags/i18n.py @@ -62,11 +62,11 @@ class BlockTranslateNode(Node): context.push() for var,val in self.extra_context.items(): context[var] = resolve_variable_with_filters(val, context) - singular = self.render_token_list(self.singular) % context + singular = self.render_token_list(self.singular) if self.plural and self.countervar and self.counter: count = resolve_variable_with_filters(self.counter, context) context[self.countervar] = count - plural = self.render_token_list(self.plural) % context + plural = self.render_token_list(self.plural) result = translation.ngettext(singular, plural, count) % context else: result = translation.gettext(singular) % context From 1775ee7d089c8e3131ca426af194453d7fd1829d Mon Sep 17 00:00:00 2001 From: Georg Bauer Date: Mon, 14 Nov 2005 15:34:47 +0000 Subject: [PATCH 08/10] fixes #790 and #791 - updated cs and sk translations git-svn-id: http://code.djangoproject.com/svn/django/trunk@1232 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/conf/locale/cs/LC_MESSAGES/django.mo | Bin 17639 -> 17872 bytes django/conf/locale/cs/LC_MESSAGES/django.po | 235 +++++++------------- django/conf/locale/sk/LC_MESSAGES/django.mo | Bin 17545 -> 17767 bytes django/conf/locale/sk/LC_MESSAGES/django.po | 136 ++--------- 4 files changed, 97 insertions(+), 274 deletions(-) diff --git a/django/conf/locale/cs/LC_MESSAGES/django.mo b/django/conf/locale/cs/LC_MESSAGES/django.mo index 36953fd16dcc04ee1d71d312ad065da98a787e23..2c513f29281943627837968ae1d5dde4b4880f10 100644 GIT binary patch delta 4763 zcmZ|S2~btn0mkuzAfRlDAgBqVVHFYtA&8)Xh$12ykwkDQWiwJ&WQl0-DT%m66C2U2 zE>%OeYLka1)@T&932GZ9cG^x`Q3=bU@) z1#fKfxU|K?aW&Yh&2U^H_mB^~jTz=)%)9`#8uRf$V6sW-LbG zRMZ8Q+0Pr$kNSF~E3+Nd-$B%c52G)3UJ!GS&<`6i7qtS%t(~az zx@^50HS<5%`e&F;{W@x(vD^>MI0>}^shEcoP!nrKM>B<;wjpGQ`-@SiFDBZ0vaM%W zC!hv45&dy0>VmT|7R&HK+<<9#5;f2}s6Fu?)cM~JVg1#S_fTW{V*qO3g<}H7U?3Ku zW>AhA*do+QEVuP0REKL&-*3eSa0lx9XHYZlM*f-4ZTojaS$_s&{D-+O5Q7@XXw)Xk zM0J>l6Yx>35-?jsw>BP%C;KEAcX_-?ZWG_VK8CE^2~~X%r?k ziJt#gD9qN0n1qG&#&=9JYG8XX53izDDu(sd43f|j(^0o16JxOrDry2Z zZT&0M0KZ2~B%0CbyaaUU#0M$pLfNSLH1xv9Y<;f%yc&IIuSH*MvbLZ$;TBupjha9^ zs^251eotd4UO@G8EspiqjBnZ#zd>K>ed65%@I#Gw01m*xsE%W8dm8HeO#H%wU5&-m z;}VP+h0UnF^*XZIc%vD!0!mSv_hKUJubJMYK_mJSb-{0NEq;soy!n22dkgCE+lX4i z1K1A_qrQI(wGyW>0I#5K(dWpcZ*HUZzyMwf{JJq=4hsGh5^X~Us^jUXfs~?VScZBU z7GfZ_qkg*`Mcv!;s0-i1SoC6x>wAgFgJaT=YnxIWhZ~Rw&NwCJ+A?yjbRl{ z6zZv%j(VP3Q7iE$)WELcSiFI&Fp^(wy4NqGX4sCJ$RX4~j-ghl(|R7W^!#_*6M|CQ z9fzY>ipfdy&cv6Aq>#- zf0Tmm%~>3Tw^1E?r@1%HAPl0OjFFgwx(C}h;znj-5pp}tHr$0DqLyxUhP&ehsF|!p&1@ZNARDaPP%~?@^;c1k*#Qi| z)2Io4f?@cD{{GUE-ld@q`?58)iJDLytw(*n71hBm%*TTmhyTPf3}^JgxDuz}7Nkpa z1*0*Lou+}Lq9!~OTd_Wi^)IJzmxgehKi(MrnMQs*gdG@@e&!OwNN4-DVQ5Qam>hF@Re}=mEw`_Y~mW#*QVL~YA-bGu-BJUzofZANE zQMaZAT^B;_`gU9IK%I9AHSmvY`(@kyXVeP)6*cgGpjP-c2I%?so#cMJ2BTgyG1hd{ zOs1eZn1i}-1!`u?Z2KDYq~3~NxEVFmXHk#se$@A!7=p*q122&{u5aEW_mUTgcI{fC zd)0Fsq>xP5qpsswtJqII?p6O4RQ)wVrn)zYUCUIk{OZF?OLlR0D=(M~^37L%Q%=UA!xA4yFcu#)7GUz4Fk z_iP@~vDA&3k6TEct-pi1*QdxmWF0v`w2#&h9Tj8*d4OmG?;$@W6Uj`?{~T!|?~)Rt zO|q3V5*=emDY=jABW>jW9aH(>MKYdrkkv%TA~KvzC+XxDqz}>cUH>zAS_O_+5NXZ5vKtiY=eD3caA7Cy$Xg$!Ri~{F3Y@Wu%zs7)xFvQN(*8KXmjbqe%c! zTPR5-UF2<|qn*^aO78nMka8^T@wr|j zCNEpDpY&3==Wb-Nb}qP delta 4534 zcmYk;3v|w90LSrXw!NFn*oNF~bKQ)YWoa5#Xx56fDYuwaLl*dFVVN0^J~kC!nJ|HL_X z6$3E8weLE|6jErz2?v94H8#by?geHGa*x@8t*{n(xA_cv;J2t31o04xCIoeV6zWB> z7>x0lgh|*I^D)dg#*|Qq;zR}N1^clZ9!E9k686F?7>yC!eIt&-E;tW0GHZ~3riKq) z|0NE768&t%&3k0Y0w1*qqjp&GKOBjewk!VXU8fqQW%9>NLe>BI`hEG)uBxEO=k zDm`#1CSWzHAq`lJ;VcYwXbozyZbfzEeN+ePZ2g!+L38>Qs%59_1?P}=nV(QY-?Xc{ z3vRH+poTOVAHdW|xzQ|1(^VOKGg_nTziqM>;f z)q`c|kEN(7D97G-8KbcidVjF>e^EUMWEgZ`Yt)M(kRLe{gF!e9)#Fsub(t89S*Qlg zL^XI;G~*viVKyi9;OFcYFJUnCa%}KppJFEUqCUp(&z#~zi^~)1F19K71ob`04m5rG zx-UvaHRw*%3o~&cPPE^j>dW}+i?f{2cKZ%B)J^)iJqtlSpdD%?A}|CKP*aqG>`-$z zdTW7TBNkQE>1K9v354qHo z;$S?0>?Csyb$t|DK-)77V=xP~3znd^??#1q zdR~ia&>_?f$L;yAQ7<@+A$SfoC6}=^Mh$cun21^%w_z*H!br?TJ%1(o_J5fQoY;&S znw^-Ab@&j5^82iw7N9@7p#p2Dzsm2f=6(tjd&H;rsUFqSOQ;@R!*&>+c%%?sOwRT=dJ!coD;XaH;4=Z*m z_Q!BsKA7xD=;hI4iC_&c-3Q42R)S)T(dJNsU}0 zj>ZaPcFYCTNOolpj=@~vP|r_i#p?bHhk{;wKWdJ0FcJ$f4a>0?Hee@gcALA(d!gob zI_g0MsOK!S=Syt83^msqQQNo*)zF=&-wCINf?oU;>VeaL1gnU!1pJ{DvB$e^4#=XQDL3O;L+5 z0=1p`p&B&AIvUlHsi^z&P%oZ~>Ohe_UyAbQqIsGqn=G^E#^F}7$<~iq6S2Q7i*@7>d675^`It(kkqFXw zY@;xTiW6lI~l@9n2Sx=^uP@-eL=0Dag|Nr?BrD(Ft zp7|8B$iw6jqGN;a)BRWr38WjzCS8e+cgYhZfD{uQc_fGE_h1;Q(fl8z(3iYPbQF+% zWIP!}bR2Lo@8celVe4X*Eq{RNWESZ}Mv=zj4GOKva5C4P9E1D42g7zX@s_sP;O`yQ pJ}}5TK4NyWp!moENrMxTy!A070=!9a=ls2~3E9Ek&4Xi`{|COf%5?w$ diff --git a/django/conf/locale/cs/LC_MESSAGES/django.po b/django/conf/locale/cs/LC_MESSAGES/django.po index a49032f22e..510589c4e7 100644 --- a/django/conf/locale/cs/LC_MESSAGES/django.po +++ b/django/conf/locale/cs/LC_MESSAGES/django.po @@ -8,14 +8,13 @@ msgstr "" "Project-Id-Version: Django Czech translation\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2005-11-13 13:40-0600\n" -"PO-Revision-Date: 2005-11-10 19:29+0100\n" +"PO-Revision-Date: 2005-11-14 15:34+0100\n" "Last-Translator: Radek Svarz \n" "Language-Team: Czech\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%" -"10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" "X-Poedit-Language: Czech\n" "X-Poedit-Country: CZECH REPUBLIC\n" @@ -66,7 +65,8 @@ msgstr "Historie" msgid "Date/time" msgstr "Datum/čas" -#: contrib/admin/templates/admin/object_history.html:19 models/auth.py:47 +#: contrib/admin/templates/admin/object_history.html:19 +#: models/auth.py:47 msgid "User" msgstr "Uživatel" @@ -79,12 +79,8 @@ msgid "DATE_WITH_TIME_FULL" msgstr "Plné datum s časem" #: contrib/admin/templates/admin/object_history.html:36 -msgid "" -"This object doesn't have a change history. It probably wasn't added via this " -"admin site." -msgstr "" -"Tento objekt nemá historii změn. Pravděpodobně nebyl přidán přes " -"administrátorské rozhraní." +msgid "This object doesn't have a change history. It probably wasn't added via this admin site." +msgstr "Tento objekt nemá historii změn. Pravděpodobně nebyl přidán přes administrátorské rozhraní." #: contrib/admin/templates/admin/base_site.html:4 msgid "Django site admin" @@ -107,12 +103,8 @@ msgid "Server Error (500)" msgstr "Chyba serveru (500)" #: contrib/admin/templates/admin/500.html:10 -msgid "" -"There's been an error. It's been reported to the site administrators via e-" -"mail and should be fixed shortly. Thanks for your patience." -msgstr "" -"Nastala chyba. Ta byla oznámena administrátorovi serveru pomocí e-mailu a " -"měla by být brzy odstraněna. Děkujeme za trpělivost." +msgid "There's been an error. It's been reported to the site administrators via e-mail and should be fixed shortly. Thanks for your patience." +msgstr "Nastala chyba. Ta byla oznámena administrátorovi serveru pomocí e-mailu a měla by být brzy odstraněna. Děkujeme za trpělivost." #: contrib/admin/templates/admin/404.html:4 #: contrib/admin/templates/admin/404.html:8 @@ -177,22 +169,13 @@ msgstr "Odhlásit se" #: contrib/admin/templates/admin/delete_confirmation.html:7 #, fuzzy, python-format -msgid "" -"Deleting the %(object_name)s '%(object)s' would result in deleting related " -"objects, but your account doesn't have permission to delete the following " -"types of objects:" -msgstr "" -"Mazání %(object_name)s '%(object)s' by vyústilo ve vymazání souvisejících " -"objektů, ale Váš účet nemá oprávnění pro mazání následujících typů objektů:" +msgid "Deleting the %(object_name)s '%(object)s' would result in deleting related objects, but your account doesn't have permission to delete the following types of objects:" +msgstr "Mazání %(object_name)s '%(object)s' by vyústilo ve vymazání souvisejících objektů, ale Váš účet nemá oprávnění pro mazání následujících typů objektů:" #: contrib/admin/templates/admin/delete_confirmation.html:14 #, python-format -msgid "" -"Are you sure you want to delete the %(object_name)s \"%(object)s\"? All of " -"the following related items will be deleted:" -msgstr "" -"Jste si jist(á), že chcete smazat %(object_name)s \"%(object)s\"? Všechny " -"následující související položky budou smazány:" +msgid "Are you sure you want to delete the %(object_name)s \"%(object)s\"? All of the following related items will be deleted:" +msgstr "Jste si jist(á), že chcete smazat %(object_name)s \"%(object)s\"? Všechny následující související položky budou smazány:" #: contrib/admin/templates/admin/delete_confirmation.html:18 msgid "Yes, I'm sure" @@ -221,12 +204,8 @@ msgid "Password reset" msgstr "Obnovení hesla" #: contrib/admin/templates/registration/password_reset_form.html:12 -msgid "" -"Forgotten your password? Enter your e-mail address below, and we'll reset " -"your password and e-mail the new one to you." -msgstr "" -"Zapomněl(a) jste heslo? Vložte níže Vaši e-mailovou adresu a my Vaše heslo " -"obnovíme a zašleme Vám e-mailem nové." +msgid "Forgotten your password? Enter your e-mail address below, and we'll reset your password and e-mail the new one to you." +msgstr "Zapomněl(a) jste heslo? Vložte níže Vaši e-mailovou adresu a my Vaše heslo obnovíme a zašleme Vám e-mailem nové." #: contrib/admin/templates/registration/password_reset_form.html:16 msgid "E-mail address:" @@ -250,20 +229,12 @@ msgid "Password reset successful" msgstr "Obnovení hesla bylo úspěšné" #: contrib/admin/templates/registration/password_reset_done.html:12 -msgid "" -"We've e-mailed a new password to the e-mail address you submitted. You " -"should be receiving it shortly." -msgstr "" -"Poslali jsme Vám e-mailem nové heslo na adresu, kterou jste zadal(a). Měl(a) " -"byste ji dostat během okamžiku." +msgid "We've e-mailed a new password to the e-mail address you submitted. You should be receiving it shortly." +msgstr "Poslali jsme Vám e-mailem nové heslo na adresu, kterou jste zadal(a). Měl(a) byste ji dostat během okamžiku." #: contrib/admin/templates/registration/password_change_form.html:12 -msgid "" -"Please enter your old password, for security's sake, and then enter your new " -"password twice so we can verify you typed it in correctly." -msgstr "" -"Prosíme, pro zabezpečení vložte svoje staré heslo a poté vložte dvakrát nové " -"heslo, takže můžeme ověřit, že jste ho napsal(a) správně." +msgid "Please enter your old password, for security's sake, and then enter your new password twice so we can verify you typed it in correctly." +msgstr "Prosíme, pro zabezpečení vložte svoje staré heslo a poté vložte dvakrát nové heslo, takže můžeme ověřit, že jste ho napsal(a) správně." #: contrib/admin/templates/registration/password_change_form.html:17 msgid "Old password:" @@ -317,23 +288,16 @@ msgid "redirect from" msgstr "přesměrovat z" #: contrib/redirects/models/redirects.py:8 -msgid "" -"This should be an absolute path, excluding the domain name. Example: '/" -"events/search/'." -msgstr "" -"Toto by měla být absolutní cesta, bez domény. Např. '/udalosti/hledat/'." +msgid "This should be an absolute path, excluding the domain name. Example: '/events/search/'." +msgstr "Toto by měla být absolutní cesta, bez domény. Např. '/udalosti/hledat/'." #: contrib/redirects/models/redirects.py:9 msgid "redirect to" msgstr "přesměrovat na" #: contrib/redirects/models/redirects.py:10 -msgid "" -"This can be either an absolute path (as above) or a full URL starting with " -"'http://'." -msgstr "" -"Toto může být buď absolutní cesta (jako nahoře) nebo plné URL začínající na " -"'http://'." +msgid "This can be either an absolute path (as above) or a full URL starting with 'http://'." +msgstr "Toto může být buď absolutní cesta (jako nahoře) nebo plné URL začínající na 'http://'." #: contrib/redirects/models/redirects.py:12 msgid "redirect" @@ -348,10 +312,8 @@ msgid "URL" msgstr "URL" #: contrib/flatpages/models/flatpages.py:7 -msgid "" -"Example: '/about/contact/'. Make sure to have leading and trailing slashes." -msgstr "" -"Příklad: '/o/kontakt/'. Ujistěte se, že máte počáteční a konečná lomítka." +msgid "Example: '/about/contact/'. Make sure to have leading and trailing slashes." +msgstr "Příklad: '/o/kontakt/'. Ujistěte se, že máte počáteční a konečná lomítka." #: contrib/flatpages/models/flatpages.py:8 msgid "title" @@ -371,12 +333,8 @@ msgstr "jméno šablony" #: contrib/flatpages/models/flatpages.py:12 #, fuzzy -msgid "" -"Example: 'flatpages/contact_page'. If this isn't provided, the system will " -"use 'flatpages/default'." -msgstr "" -"Například: 'flatfiles/kontaktni_stranka'. Pokud toto není zadáno, systém " -"použije 'flatfiles/default'." +msgid "Example: 'flatpages/contact_page'. If this isn't provided, the system will use 'flatpages/default'." +msgstr "Například: 'flatfiles/kontaktni_stranka'. Pokud toto není zadáno, systém použije 'flatfiles/default'." #: contrib/flatpages/models/flatpages.py:13 msgid "registration required" @@ -384,9 +342,7 @@ msgstr "nutná registrace" #: contrib/flatpages/models/flatpages.py:13 msgid "If this is checked, only logged-in users will be able to view the page." -msgstr "" -"Pokud je zaškrtnuto, pouze přihlášení uživatelé budou moci prohlížet tuto " -"stránku." +msgstr "Pokud je zaškrtnuto, pouze přihlášení uživatelé budou moci prohlížet tuto stránku." #: contrib/flatpages/models/flatpages.py:17 msgid "flat page" @@ -398,15 +354,15 @@ msgstr "statické stránky" #: utils/translation.py:335 msgid "DATE_FORMAT" -msgstr "" +msgstr "Formát data" #: utils/translation.py:336 msgid "DATETIME_FORMAT" -msgstr "" +msgstr "Formát data a času" #: utils/translation.py:337 msgid "TIME_FORMAT" -msgstr "" +msgstr "Formát času" #: utils/dates.py:6 msgid "Monday" @@ -444,23 +400,28 @@ msgstr "Leden" msgid "February" msgstr "Únor" -#: utils/dates.py:14 utils/dates.py:27 +#: utils/dates.py:14 +#: utils/dates.py:27 msgid "March" msgstr "Březen" -#: utils/dates.py:14 utils/dates.py:27 +#: utils/dates.py:14 +#: utils/dates.py:27 msgid "April" msgstr "Duben" -#: utils/dates.py:14 utils/dates.py:27 +#: utils/dates.py:14 +#: utils/dates.py:27 msgid "May" msgstr "Květen" -#: utils/dates.py:14 utils/dates.py:27 +#: utils/dates.py:14 +#: utils/dates.py:27 msgid "June" msgstr "Červen" -#: utils/dates.py:15 utils/dates.py:27 +#: utils/dates.py:15 +#: utils/dates.py:27 msgid "July" msgstr "Červenec" @@ -532,7 +493,10 @@ msgstr "weby" msgid "label" msgstr "nadpis" -#: models/core.py:29 models/core.py:40 models/auth.py:6 models/auth.py:19 +#: models/core.py:29 +#: models/core.py:40 +#: models/auth.py:6 +#: models/auth.py:19 msgid "name" msgstr "jméno" @@ -584,7 +548,8 @@ msgstr "codename" msgid "Permission" msgstr "Oprávnění" -#: models/auth.py:11 models/auth.py:58 +#: models/auth.py:11 +#: models/auth.py:58 msgid "Permissions" msgstr "Oprávnění" @@ -592,7 +557,8 @@ msgstr "Oprávnění" msgid "Group" msgstr "Skupina" -#: models/auth.py:23 models/auth.py:60 +#: models/auth.py:23 +#: models/auth.py:60 msgid "Groups" msgstr "Skupiny" @@ -646,12 +612,8 @@ msgid "date joined" msgstr "datum zaregistrování" #: models/auth.py:44 -msgid "" -"In addition to the permissions manually assigned, this user will also get " -"all permissions granted to each group he/she is in." -msgstr "" -"Kromě manuálně přidělených oprávnění uživatel dostane všechna oprávnění pro " -"každou skupinu, ve které je." +msgid "In addition to the permissions manually assigned, this user will also get all permissions granted to each group he/she is in." +msgstr "Kromě manuálně přidělených oprávnění uživatel dostane všechna oprávnění pro každou skupinu, ve které je." #: models/auth.py:48 msgid "Users" @@ -703,7 +665,7 @@ msgstr "Galicijsky" #: conf/global_settings.py:44 msgid "Icelandic" -msgstr "" +msgstr "Islandština" #: conf/global_settings.py:45 msgid "Italian" @@ -735,7 +697,7 @@ msgstr "Srbsky" #: conf/global_settings.py:52 msgid "Swedish" -msgstr "" +msgstr "Švédsky" #: conf/global_settings.py:53 msgid "Simplified Chinese" @@ -747,8 +709,7 @@ msgstr "Tato hodnota musí obsahovat pouze znaky, čísla nebo podtržítka." #: core/validators.py:63 msgid "This value must contain only letters, numbers, underscores and slashes." -msgstr "" -"Tato hodnota musí obsahovat pouze znaky, čísla, podtržítka nebo lomítka." +msgstr "Tato hodnota musí obsahovat pouze znaky, čísla, podtržítka nebo lomítka." #: core/validators.py:71 msgid "Uppercase letters are not allowed here." @@ -807,12 +768,8 @@ msgid "Enter a valid e-mail address." msgstr "Vložte platnou e-mailovou adresu." #: core/validators.py:147 -msgid "" -"Upload a valid image. The file you uploaded was either not an image or a " -"corrupted image." -msgstr "" -"Nahrajte na server platný obrázek. Soubor, který jste nahrál(a) nebyl " -"obrázek, nebo byl porušen." +msgid "Upload a valid image. The file you uploaded was either not an image or a corrupted image." +msgstr "Nahrajte na server platný obrázek. Soubor, který jste nahrál(a) nebyl obrázek, nebo byl porušen." #: core/validators.py:154 #, python-format @@ -852,7 +809,8 @@ msgstr "Špatně formované XML: %s" msgid "Invalid URL: %s" msgstr "Neplatné URL: %s" -#: core/validators.py:205 core/validators.py:207 +#: core/validators.py:205 +#: core/validators.py:207 #, python-format msgid "The URL %s is a broken link." msgstr "Odkaz na URL %s je rozbitý." @@ -878,7 +836,8 @@ msgstr "Toto pole se musí shodovat s polem '%s'." msgid "Please enter something for at least one field." msgstr "Prosíme, vložte něco alespoň pro jedno pole." -#: core/validators.py:263 core/validators.py:274 +#: core/validators.py:263 +#: core/validators.py:274 msgid "Please enter both fields or leave them both empty." msgstr "Prosíme, vložte obě pole, nebo je nechte obě prázdná." @@ -908,8 +867,7 @@ msgstr "Prosíme, vložte platné číslo." #: core/validators.py:348 #, python-format msgid "Please enter a valid decimal number with at most %s total digit." -msgid_plural "" -"Please enter a valid decimal number with at most %s total digits." +msgid_plural "Please enter a valid decimal number with at most %s total digits." msgstr[0] "Prosíme, vložte platné číslo s nejvíce %s cifrou celkem." msgstr[1] "Prosíme, vložte platné číslo s nejvíce %s ciframi celkem." msgstr[2] "Prosíme, vložte platné číslo s nejvíce %s ciframi celkem." @@ -917,16 +875,10 @@ msgstr[2] "Prosíme, vložte platné číslo s nejvíce %s ciframi celkem." #: core/validators.py:351 #, python-format msgid "Please enter a valid decimal number with at most %s decimal place." -msgid_plural "" -"Please enter a valid decimal number with at most %s decimal places." -msgstr[0] "" -"Prosíme, vložte platné číslo s nejvíce %s cifrou za desetinnou čárkou celkem." -msgstr[1] "" -"Prosíme, vložte platné číslo s nejvíce %s ciframi za desetinnou čárkou " -"celkem." -msgstr[2] "" -"Prosíme, vložte platné číslo s nejvíce %s ciframi za desetinnou čárkou " -"celkem." +msgid_plural "Please enter a valid decimal number with at most %s decimal places." +msgstr[0] "Prosíme, vložte platné číslo s nejvíce %s cifrou za desetinnou čárkou celkem." +msgstr[1] "Prosíme, vložte platné číslo s nejvíce %s ciframi za desetinnou čárkou celkem." +msgstr[2] "Prosíme, vložte platné číslo s nejvíce %s ciframi za desetinnou čárkou celkem." #: core/validators.py:361 #, python-format @@ -953,71 +905,44 @@ msgstr "Nemohl jsem získat nic z %s." #: core/validators.py:428 #, python-format -msgid "" -"The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'." +msgid "The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'." msgstr "URL %(url)s vrátilo neplatnou hlavičku Content-Type '%(contenttype)s'." #: core/validators.py:461 #, python-format -msgid "" -"Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with " -"\"%(start)s\".)" -msgstr "" -"Prosíme, zavřete nezavřenou značku %(tag)s z řádky %(line)s. (Řádka začíná s " -"\"%(start)s\".)" +msgid "Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with \"%(start)s\".)" +msgstr "Prosíme, zavřete nezavřenou značku %(tag)s z řádky %(line)s. (Řádka začíná s \"%(start)s\".)" #: core/validators.py:465 #, python-format -msgid "" -"Some text starting on line %(line)s is not allowed in that context. (Line " -"starts with \"%(start)s\".)" -msgstr "" -"Nějaký text začínající na řádce %(line)s není povolen v tomto kontextu. " -"(Řádka začíná s \"%(start)s\".)" +msgid "Some text starting on line %(line)s is not allowed in that context. (Line starts with \"%(start)s\".)" +msgstr "Nějaký text začínající na řádce %(line)s není povolen v tomto kontextu. (Řádka začíná s \"%(start)s\".)" #: core/validators.py:470 #, python-format -msgid "" -"\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%" -"(start)s\".)" -msgstr "" -"\"%(attr)s\" na řádce %(line)s je neplatný atribut. (Řádka začíná s \"%" -"(start)s\".)" +msgid "\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%(start)s\".)" +msgstr "\"%(attr)s\" na řádce %(line)s je neplatný atribut. (Řádka začíná s \"%(start)s\".)" #: core/validators.py:475 #, python-format -msgid "" -"\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%" -"(start)s\".)" -msgstr "" -"\"<%(tag)s>\" na řádce %(line)s je neplatná značka. (Řádka začíná s \"%" -"(start)s\".)" +msgid "\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%(start)s\".)" +msgstr "\"<%(tag)s>\" na řádce %(line)s je neplatná značka. (Řádka začíná s \"%(start)s\".)" #: core/validators.py:479 #, python-format -msgid "" -"A tag on line %(line)s is missing one or more required attributes. (Line " -"starts with \"%(start)s\".)" -msgstr "" -"Značce na řádce %(line)s schází jeden nebo více požadovaných atributů. " -"(Řádka začíná s \"%(start)s\".)" +msgid "A tag on line %(line)s is missing one or more required attributes. (Line starts with \"%(start)s\".)" +msgstr "Značce na řádce %(line)s schází jeden nebo více požadovaných atributů. (Řádka začíná s \"%(start)s\".)" #: core/validators.py:484 #, python-format -msgid "" -"The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line " -"starts with \"%(start)s\".)" -msgstr "" -"Atribut \"%(attr)s\" na řádce %(line)s má neplatnou hodnotu. (Řádka začína s " -"\"%(start)s\".)" +msgid "The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line starts with \"%(start)s\".)" +msgstr "Atribut \"%(attr)s\" na řádce %(line)s má neplatnou hodnotu. (Řádka začína s \"%(start)s\".)" #: core/meta/fields.py:111 msgid " Separate multiple IDs with commas." msgstr "Oddělte více identifikátorů čárkami." #: core/meta/fields.py:114 -msgid "" -" Hold down \"Control\", or \"Command\" on a Mac, to select more than one." -msgstr "" -"Podržte \"Control\", nebo \"Command\" na Macu pro vybrání více jak jedné " -"položky." +msgid " Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgstr "Podržte \"Control\", nebo \"Command\" na Macu pro vybrání více jak jedné položky." + diff --git a/django/conf/locale/sk/LC_MESSAGES/django.mo b/django/conf/locale/sk/LC_MESSAGES/django.mo index 0c5a1dd7305c5fe851cd1b7c27bc36cb16805dda..b0e31e6d18dd70c213747907f0e9daa92cd9aea6 100644 GIT binary patch delta 4608 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 HF+Jq}KsMoS delta 4390 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 H=7j$bWoo&K diff --git a/django/conf/locale/sk/LC_MESSAGES/django.po b/django/conf/locale/sk/LC_MESSAGES/django.po index 993e415e16..4ddcbd8c81 100644 --- a/django/conf/locale/sk/LC_MESSAGES/django.po +++ b/django/conf/locale/sk/LC_MESSAGES/django.po @@ -8,10 +8,10 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2005-11-13 13:41-0600\n" +"POT-Creation-Date: 2005-11-14 07:26-0500\n" "PO-Revision-Date: 2005-11-10 23:22-0500\n" "Last-Translator: Vladimir Labath \n" -"Language-Team: Slovak \n" +"Language-Team: Slovak \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -393,18 +393,6 @@ msgstr "plochá stránka" msgid "flat pages" msgstr "ploché stránky" -#: utils/translation.py:335 -msgid "DATE_FORMAT" -msgstr "" - -#: utils/translation.py:336 -msgid "DATETIME_FORMAT" -msgstr "" - -#: utils/translation.py:337 -msgid "TIME_FORMAT" -msgstr "" - #: utils/dates.py:6 msgid "Monday" msgstr "Pondelok" @@ -509,6 +497,18 @@ msgstr "" msgid "Dec." msgstr "" +#: utils/translation.py:335 +msgid "DATE_FORMAT" +msgstr "DATUM_FORMAT" + +#: utils/translation.py:336 +msgid "DATETIME_FORMAT" +msgstr "DATUMCAS_FORMAT" + +#: utils/translation.py:337 +msgid "TIME_FORMAT" +msgstr "CAS_FORMAT" + #: models/core.py:7 msgid "domain name" msgstr "meno domény" @@ -662,7 +662,7 @@ msgstr "Osobné údaje" msgid "Important dates" msgstr "Dôležité údaje" -#: models/auth.py:182 +#: models/auth.py:195 msgid "Message" msgstr "Zpráva" @@ -724,7 +724,7 @@ msgstr "Ruský" #: conf/global_settings.py:50 msgid "Slovak" -msgstr "" +msgstr "Slovenský" #: conf/global_settings.py:51 msgid "Serbian" @@ -732,7 +732,7 @@ msgstr "Srbský" #: conf/global_settings.py:52 msgid "Swedish" -msgstr "" +msgstr "Švédsky" #: conf/global_settings.py:53 msgid "Simplified Chinese" @@ -1014,105 +1014,3 @@ msgstr "" " Podržte \"Control\", alebo \"Command\" na Mac_u, na výber viac ako jednej " "položky." -#~ msgid "Comment posted" -#~ msgstr "Komentár bol poslaný" - -#~ msgid "Comment posted successfully" -#~ msgstr "Komentár bol úspešne odoslaný" - -#~ msgid "Thanks for contributing." -#~ msgstr "Ďakujeme za príspevok." - -#~ msgid "View your comment" -#~ msgstr "Pozri svôj príspevok" - -# templates/blog/entries_detail.html:24 -#~ msgid "Post a comment" -#~ msgstr "Pridaj komentár" - -# templates/blog/entries_detail.html:15 -#~ msgid "Comments" -#~ msgstr "Komentáre" - -#~ msgid "Comment" -#~ msgstr "Komentár" - -#~ msgid "Your name" -#~ msgstr "Vaše meno" - -#~ msgid "Preview revised comment" -#~ msgstr "Prezretie upraveného komentára" - -#~ msgid "Post public comment" -#~ msgstr "Zveréjniť komentár" - -# templates/blog/entries_detail.html:24 -#~ msgid "Preview comment" -#~ msgstr "Prezrieť komentár" - -# templates/blog/entries_detail.html:15 -#~ msgid "Preview your comment" -#~ msgstr "Prezrite si svoj komentár" - -#~ msgid "Posted by" -#~ msgstr "Poslané" - -#~ msgid "Or edit it again" -#~ msgstr "Alebo ho vytvorte znova" - -#~ msgid "Please correct the following errors." -#~ msgstr "Odstráňte nasledujúce chyby, prosím." - -#~ msgid "" -#~ "Looks like you followed a bad link. If you think it is our fault, please " -#~ "let us know" -#~ msgstr "" -#~ "Pozrite si linku , kde vzikla chyba. Ak si myslíte, že je to naša chyba, " -#~ "dajte nám to vedieť, " -#~ "prosím" - -#~ msgid "" -#~ "Here is a link to the homepage. You know, just in case" -#~ msgstr "" -#~ "Tu je adresa úvodnej stránky. Ak by ste ju potrebovali" - -#~ msgid "Recent comments" -#~ msgstr "Posledný komentár" - -#~ msgid "Previous" -#~ msgstr "Predchádzajúci/a" - -#~ msgid "Page" -#~ msgstr "Stránka" - -#~ msgid "Next" -#~ msgstr "Ďalší/ia" - -#~ msgid "Latest entries" -#~ msgstr "Posledné záznamy" - -#~ msgid "archive" -#~ msgstr "archív" - -#~ msgid "Page unavailable" -#~ msgstr "Stránka je neprístupná" - -#~ msgid "We're sorry, but the requested page is currently unavailable" -#~ msgstr "Ľutujeme ale požadovaná stránka je neprístupná" - -#~ msgid "" -#~ "We're messing around with things internally, and the server had a bit of " -#~ "a hiccup" -#~ msgstr "Máme problém na servri, ktorý sa snažíme odstrániť" - -#~ msgid "Please try again later" -#~ msgstr "Skúste znovu neskôr, prosím" - -#~ msgid "Latest miesta" -#~ msgstr "Posledné miesta" - -#~ msgid "on" -#~ msgstr " " - -#~ msgid "at" -#~ msgstr "o" From 0710243ea12f9f88189197703d4c2e9e2e9fe741 Mon Sep 17 00:00:00 2001 From: Jacob Kaplan-Moss Date: Mon, 14 Nov 2005 17:44:50 +0000 Subject: [PATCH 09/10] Added "pretty" error pages to be used when DEBUG is True. git-svn-id: http://code.djangoproject.com/svn/django/trunk@1233 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/handlers/base.py | 34 +-- django/views/debug.py | 438 +++++++++++++++++++++++++++++++++++ 2 files changed, 446 insertions(+), 26 deletions(-) create mode 100644 django/views/debug.py diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py index 190a8b02c2..ba2e286721 100644 --- a/django/core/handlers/base.py +++ b/django/core/handlers/base.py @@ -86,14 +86,14 @@ class BaseHandler: return response except exceptions.Http404, e: if DEBUG: - return self.get_technical_error_response(is404=True, exception=e) + return self.get_technical_error_response(request, is404=True, exception=e) else: callback, param_dict = resolver.resolve404() return callback(request, **param_dict) except db.DatabaseError: db.db.rollback() if DEBUG: - return self.get_technical_error_response() + return self.get_technical_error_response(request) else: subject = 'Database error (%s IP): %s' % ((request.META.get('REMOTE_ADDR') in INTERNAL_IPS and 'internal' or 'EXTERNAL'), getattr(request, 'path', '')) message = "%s\n\n%s" % (self._get_traceback(), request) @@ -103,7 +103,7 @@ class BaseHandler: return httpwrappers.HttpResponseForbidden('

Permission denied

') except: # Handle everything else, including SuspiciousOperation, etc. if DEBUG: - return self.get_technical_error_response() + return self.get_technical_error_response(request) else: subject = 'Coding error (%s IP): %s' % ((request.META.get('REMOTE_ADDR') in INTERNAL_IPS and 'internal' or 'EXTERNAL'), getattr(request, 'path', '')) try: @@ -123,35 +123,17 @@ class BaseHandler: callback, param_dict = resolver.resolve500() return callback(request, **param_dict) - def get_technical_error_response(self, is404=False, exception=None): + def get_technical_error_response(self, request, is404=False, exception=None): """ Returns an HttpResponse that displays a TECHNICAL error message for a fundamental database or coding error. """ + import sys + from django.views import debug if is404: - from django.conf.settings import ROOT_URLCONF - from django.utils.html import escape - html = [''] - html.append('Error 404') - # Explicitly tell robots not to archive this, in case this slips - # onto a production site. - html.append('') - html.append('

Error 404

') - try: - tried = exception.args[0]['tried'] - except (IndexError, TypeError): - if exception.args: - html.append('

%s

' % escape(exception.args[0])) - else: - html.append('

Using the URLconf defined in %s, Django tried these URL patterns, in this order:

' % ROOT_URLCONF) - html.append('
    %s
' % ''.join(['
  • %s
  • ' % escape(t).replace(' ', ' ') for t in tried])) - html.append("

    The current URL, %r, didn't match any of these.

    " % exception.args[0]['path']) - html.append("

    You're seeing this error because you have DEBUG = True in your Django settings file. Change that to False, and Django will display a standard 404 page.

    ") - html.append('') - return httpwrappers.HttpResponseNotFound('\n'.join(html)) + return debug.technical_404_response(request, exception) else: - output = "There's been an error:\n\n%s" % self._get_traceback() - return httpwrappers.HttpResponseServerError(output, mimetype='text/plain') + return debug.technical_500_response(request, *sys.exc_info()) def _get_traceback(self): "Helper function to return the traceback as a string" diff --git a/django/views/debug.py b/django/views/debug.py new file mode 100644 index 0000000000..2d3b7e9150 --- /dev/null +++ b/django/views/debug.py @@ -0,0 +1,438 @@ +import os +import sys +import inspect +from django.conf import settings +from os.path import dirname, join as pathjoin +from django.core.template import Template, Context +from django.utils.httpwrappers import HttpResponseServerError, HttpResponseNotFound + +def technical_500_response(request, exc_type, exc_value, tb): + """ + Create a technical server error response. The last three arguments are + the values returned from sys.exc_info() and friends. + """ + frames = [] + while tb is not None: + filename = tb.tb_frame.f_code.co_filename + function = tb.tb_frame.f_code.co_name + lineno = tb.tb_lineno - 1 + pre_context_lineno, pre_context, context_line, post_context = _get_lines_from_file(filename, lineno, 7) + frames.append({ + 'tb' : tb, + 'filename' : filename, + 'function' : function, + 'lineno' : lineno, + 'vars' : tb.tb_frame.f_locals, + 'id' : id(tb), + 'pre_context' : pre_context, + 'context_line' : context_line, + 'post_context' : post_context, + 'pre_context_lineno' : pre_context_lineno, + }) + tb = tb.tb_next + + t = Template(TECHNICAL_500_TEMPLATE) + c = Context({ + 'exception_type' : exc_type.__name__, + 'exception_value' : exc_value, + 'frames' : frames, + 'lastframe' : frames[-1], + 'request' : request, + 'request_protocol' : os.environ.get("HTTPS") == "on" and "https" or "http", + 'settings' : dict([(k, getattr(settings, k)) for k in dir(settings) if k.isupper()]), + + }) + return HttpResponseServerError(t.render(c)) + +def technical_404_response(request, exception): + """ + Create a technical 404 error response. The exception should be the Http404 + exception. + """ + try: + tried = exception.args[0]['tried'] + except (IndexError, TypeError): + tried = [] + + t = Template(TECHNICAL_404_TEMPLATE) + c = Context({ + 'root_urlconf' : settings.ROOT_URLCONF, + 'urlpatterns' : tried, + 'reason' : str(exception), + 'request' : request, + 'request_protocol' : os.environ.get("HTTPS") == "on" and "https" or "http", + 'settings' : dict([(k, getattr(settings, k)) for k in dir(settings) if k.isupper()]), + }) + return HttpResponseNotFound(t.render(c)) + +def _get_lines_from_file(filename, lineno, context_lines): + """ + Returns context_lines before and after lineno from file. + Returns (pre_context_lineno, pre_context, context_line, post_context). + """ + try: + source = open(filename).readlines() + lower_bound = max(0, lineno - context_lines) + upper_bound = lineno + context_lines + + pre_context = [line.strip('\n') for line in source[lower_bound:lineno]] + context_line = source[lineno].strip('\n') + post_context = [line.strip('\n') for line in source[lineno+1:upper_bound]] + + return lower_bound, pre_context, context_line, post_context + except (OSError, IOError): + return None, [], None, [] + +# +# Templates are embedded in the file so that we know the error handler will +# always work even if the template loader is broken. +# + +TECHNICAL_500_TEMPLATE = """ + + + + + {{ exception_type }} at {{ request.path }} + + + + + +
    +

    {{ exception_type }} at {{ request.path }}

    +

    {{ exception_value }}

    + + + + + + + + + + + + + + + + + + + + + +
    Request Method:{{ request.META.REQUEST_METHOD }}
    Request URL:{{ request_protocol }}://{{ request.META.HTTP_HOST }}{{ request.path }}
    Exception Type:{{ exception_type }}
    Exception Value:{{ exception_value }}
    Exception Location:{{ lastframe.filename }} in {{ lastframe.function }}, line {{ lastframe.lineno }}
    +
    + +
    +

    Traceback (innermost last)

    +
      + {% for frame in frames %} +
    • + {{ frame.filename }} in {{ frame.function }} + + {% if frame.context_line %} +
      + {% if frame.pre_context %} +
        {% for line in frame.pre_context %}
      1. {{ line|escape }}
      2. {% endfor %}
      + {% endif %} +
      1. {{ frame.context_line|escape }}
      + {% if frame.post_context %} +
        {% for line in frame.post_context %}
      1. {{ line|escape }}
      2. {% endfor %}
      + {% endif %} +
      + {% endif %} + + {% if frame.vars %} + + + + + + + + + + {% for var in frame.vars.items|dictsort:"0" %} + + + + + {% endfor %} + +
      VariableValue
      {{ var.0 }}
      {{ var.1|pprint|escape }}
      + {% endif %} +
    • + {% endfor %} +
    +
    + +
    +

    Request information

    + +

    GET

    + {% if request.GET %} + + + + + + + + + {% for var in request.GET.items %} + + + + + {% endfor %} + +
    VariableValue
    {{ var.0 }}
    {{ var.1|pprint|escape }}
    + {% else %} +

    No GET data

    + {% endif %} + +

    POST

    + {% if request.POST %} + + + + + + + + + {% for var in request.POST.items %} + + + + + {% endfor %} + +
    VariableValue
    {{ var.0 }}
    {{ var.1|pprint|escape }}
    + {% else %} +

    No POST data

    + {% endif %} + +

    + {% if request.COOKIES %} + + + + + + + + + {% for var in request.COOKIES.items %} + + + + + {% endfor %} + +
    VariableValue
    {{ var.0 }}
    {{ var.1|pprint|escape }}
    + {% else %} +

    No cookie data

    + {% endif %} + +

    META

    + + + + + + + + + {% for var in request.META.items|dictsort:"0" %} + + + + + {% endfor %} + +
    VariableValue
    {{ var.0 }}
    {{ var.1|pprint|escape }}
    + +

    Settings

    +

    Using settings module {{ settings.SETTINGS_MODULE }}

    + + + + + + + + + {% for var in settings.items|dictsort:"0" %} + + + + + {% endfor %} + +
    SettingValue
    {{ var.0 }}
    {{ var.1|pprint|escape }}
    + +
    + +
    +

    + You're seeing this error because you have DEBUG = True in your + Django settings file. Change that to False, and Django will + display a standard 500 page. +

    +
    + + + +""" + +TECHNICAL_404_TEMPLATE = """ + + + + + Page not found at {{ request.path }} + + + + +
    +

    Page not found (404)

    + + + + + + + + + +
    Request Method:{{ request.META.REQUEST_METHOD }}
    Request URL:{{ request_protocol }}://{{ request.META.HTTP_HOST }}{{ request.path }}
    +
    +
    + {% if urlpatterns %} +

    + Using the URLconf defined in {{ settings.ROOT_URLCONF }}, + Django tried these URL patterns, in this order: +

    +
      + {% for pattern in urlpatterns %} +
    1. {{ pattern|escape }}
    2. + {% endfor %} +
    +

    The current URL, {{ request.path }}, didn't match any of these.

    + {% else %} +

    {{ reason|escape }}

    + {% endif %} +
    + +
    +

    + You're seeing this error because you have DEBUG = True in + your Django settings file. Change that to False, and Django + will display a standard 404 page. +

    +
    + + +""" \ No newline at end of file From 065dd334de34940a7703b09bbbd4567b55d10598 Mon Sep 17 00:00:00 2001 From: Jacob Kaplan-Moss Date: Mon, 14 Nov 2005 18:50:13 +0000 Subject: [PATCH 10/10] Added NOINDEX tag to debug 500 page (for robots) git-svn-id: http://code.djangoproject.com/svn/django/trunk@1234 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/views/debug.py | 1 + 1 file changed, 1 insertion(+) diff --git a/django/views/debug.py b/django/views/debug.py index 2d3b7e9150..d5323c0b59 100644 --- a/django/views/debug.py +++ b/django/views/debug.py @@ -93,6 +93,7 @@ TECHNICAL_500_TEMPLATE = """ + {{ exception_type }} at {{ request.path }}