From 4d8e6f651a8762fa8524837c437c2f8e25ad121f Mon Sep 17 00:00:00 2001 From: =?utf8?q?Marek=20St=C4=99pniowski?= Date: Fri, 9 Jan 2009 10:34:42 +0100 Subject: [PATCH 01/16] =?utf8?q?Small=20changes=20to=20Dariusz=20Ga=C5=82e?= =?utf8?q?cki=20contribution=20description=20in=20about=5Fus.html.?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- wolnelektury/templates/info/about_us.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wolnelektury/templates/info/about_us.html b/wolnelektury/templates/info/about_us.html index 7005583ef..f529bf700 100644 --- a/wolnelektury/templates/info/about_us.html +++ b/wolnelektury/templates/info/about_us.html @@ -34,7 +34,8 @@ i prof. Piotr Śliwiński.

Digitalizacją i korektą tekstów zajmuje się Biblioteka Narodowa. Serwis internetowy został zaprojektowany - przez 2ia. Autorem jezyka XML jest Dariusz Gałecki. Obsługę prawną Wolnych Lektur zapewnia Kancelaria Grynhoff, + przez 2ia. Autorem języka składu tekstów Wolnych Lektur opartego na języku XML jest Dariusz Gałecki. Obsługę + prawną Wolnych Lektur zapewnia Kancelaria Grynhoff, Woźny, Maliński. Hosting serwisu zapewnia firma EO Networks. W opracowaniu technicznym tekstów pomaga wydawnictwo Korporacja Ha!Art. Logo Wolne Lektury jest dziełem agencji PZL. Projekt objęli patronatem medialnym: Dziennik, Elle, Tok.fm, Biblioteka Analiz, -- 2.20.1 From d9d5b7bc7f432058fd3bccfb096c88438e5364a9 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Marek=20St=C4=99pniowski?= Date: Sat, 10 Jan 2009 19:31:06 +0100 Subject: [PATCH 02/16] Added MP3 and OGG to book short_html view. --- apps/catalogue/models.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/catalogue/models.py b/apps/catalogue/models.py index 6ab090df7..0cd19cc0c 100644 --- a/apps/catalogue/models.py +++ b/apps/catalogue/models.py @@ -120,11 +120,15 @@ class Book(models.Model): if self.html_file: formats.append(u'Czytaj online' % reverse('book_text', kwargs={'slug': self.slug})) if self.pdf_file: - formats.append(u'Plik PDF' % self.pdf_file.url) + formats.append(u'PDF' % self.pdf_file.url) if self.odt_file: - formats.append(u'Plik ODT' % self.odt_file.url) + formats.append(u'ODT' % self.odt_file.url) if self.txt_file: - formats.append(u'Plik TXT' % self.txt_file.url) + formats.append(u'TXT' % self.txt_file.url) + if self.mp3_file: + formats.append(u'MP3' % self.mp3_file.url) + if self.ogg_file: + formats.append(u'OGG' % self.ogg_file.url) self._short_html = unicode(render_to_string('catalogue/book_short.html', {'book': self, 'tags': tags, 'formats': formats})) -- 2.20.1 From f05daac38bd815128ba11b77b1b40dc03d2b5fcf Mon Sep 17 00:00:00 2001 From: =?utf8?q?Marek=20St=C4=99pniowski?= Date: Sat, 21 Feb 2009 19:46:29 +0100 Subject: [PATCH 03/16] =?utf8?q?Uaktualnienie=20django-south.=20Wy=C5=9Bwi?= =?utf8?q?etlanie=20link=C3=B3w=20do=20Lektury.Gazeta.pl=20na=20stronach?= =?utf8?q?=20lektur=20i=20w=20katalogu.?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- .../migrations/0004_add_gazeta_links.py | 13 + apps/catalogue/models.py | 2 + apps/south/__init__.py | 4 +- apps/south/db/generic.py | 295 +++++++++++++----- apps/south/db/mysql.py | 41 ++- apps/south/db/postgresql_psycopg2.py | 24 +- apps/south/db/sql_server/__init__.py | 0 apps/south/db/sql_server/pyodbc.py | 25 ++ apps/south/db/sqlite3.py | 34 +- apps/south/install/README | 1 - apps/south/install/setup.py | 13 - apps/south/management/commands/migrate.py | 17 +- .../management/commands/startmigration.py | 194 ++++++++++-- apps/south/management/commands/syncdb.py | 31 +- apps/south/management/commands/test.py | 12 + apps/south/migration.py | 182 ++++++++--- apps/south/setup.py | 26 ++ apps/south/tests/db.py | 106 ++++++- .../fakeapp/migrations/0003_alter_spam.py | 12 + apps/south/tests/logic.py | 45 ++- .../templates/catalogue/book_detail.html | 7 +- .../catalogue/tagged_object_list.html | 3 + 22 files changed, 883 insertions(+), 204 deletions(-) create mode 100644 apps/catalogue/migrations/0004_add_gazeta_links.py create mode 100644 apps/south/db/sql_server/__init__.py create mode 100644 apps/south/db/sql_server/pyodbc.py delete mode 100644 apps/south/install/README delete mode 100755 apps/south/install/setup.py create mode 100644 apps/south/management/commands/test.py create mode 100755 apps/south/setup.py create mode 100644 apps/south/tests/fakeapp/migrations/0003_alter_spam.py diff --git a/apps/catalogue/migrations/0004_add_gazeta_links.py b/apps/catalogue/migrations/0004_add_gazeta_links.py new file mode 100644 index 000000000..d750a673f --- /dev/null +++ b/apps/catalogue/migrations/0004_add_gazeta_links.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +from south.db import db +from django.db import models + + +class Migration: + def forwards(self): + db.add_column('catalogue_tag', 'gazeta_link', models.CharField(blank=True, max_length=240)) + db.add_column('catalogue_book', 'gazeta_link', models.CharField(blank=True, max_length=240)) + + def backwards(self): + db.delete_column('catalogue_tag', 'gazeta_link') + db.delete_column('catalogue_book', 'gazeta_link') diff --git a/apps/catalogue/models.py b/apps/catalogue/models.py index 0cd19cc0c..5bfe6cbb8 100644 --- a/apps/catalogue/models.py +++ b/apps/catalogue/models.py @@ -47,6 +47,7 @@ class Tag(TagBase): user = models.ForeignKey(User, blank=True, null=True) book_count = models.IntegerField(_('book count'), default=0, blank=False, null=False) + gazeta_link = models.CharField(blank=True, max_length=240) def has_description(self): return len(self.description) > 0 @@ -88,6 +89,7 @@ class Book(models.Model): _short_html = models.TextField(_('short HTML'), editable=False) parent_number = models.IntegerField(_('parent number'), default=0) extra_info = JSONField(_('extra information')) + gazeta_link = models.CharField(blank=True, max_length=240) # Formats xml_file = models.FileField(_('XML file'), upload_to=book_upload_path('xml'), blank=True) diff --git a/apps/south/__init__.py b/apps/south/__init__.py index e797d8585..3e5972eeb 100644 --- a/apps/south/__init__.py +++ b/apps/south/__init__.py @@ -2,5 +2,5 @@ South - Useable migrations for Django apps """ -__version__ = "0.3" -__authors__ = ["Andrew Godwin ", "Andy McCurdy "] \ No newline at end of file +__version__ = "0.4" +__authors__ = ["Andrew Godwin ", "Andy McCurdy "] diff --git a/apps/south/db/generic.py b/apps/south/db/generic.py index 09dde0362..4a5b512d7 100644 --- a/apps/south/db/generic.py +++ b/apps/south/db/generic.py @@ -1,10 +1,23 @@ +import datetime from django.core.management.color import no_style from django.db import connection, transaction, models from django.db.backends.util import truncate_name +from django.db.models.fields import NOT_PROVIDED from django.dispatch import dispatcher from django.conf import settings + +def alias(attrname): + """ + Returns a function which calls 'attrname' - for function aliasing. + We can't just use foo = bar, as this breaks subclassing. + """ + def func(self, *args, **kwds): + return getattr(self, attrname)(*args, **kwds) + return func + + class DatabaseOperations(object): """ @@ -12,10 +25,14 @@ class DatabaseOperations(object): Some of this code comes from Django Evolution. """ + # We assume the generic DB can handle DDL transactions. MySQL wil change this. + has_ddl_transactions = True + def __init__(self): self.debug = False self.deferred_sql = [] - + self.dry_run = False + self.pending_create_signals = [] def execute(self, sql, params=[]): """ @@ -25,31 +42,50 @@ class DatabaseOperations(object): cursor = connection.cursor() if self.debug: print " = %s" % sql, params + + if self.dry_run: + return [] + cursor.execute(sql, params) try: return cursor.fetchall() except: return [] - - + + def add_deferred_sql(self, sql): """ Add a SQL statement to the deferred list, that won't be executed until this instance's execute_deferred_sql method is run. """ self.deferred_sql.append(sql) - - + + def execute_deferred_sql(self): """ Executes all deferred SQL, resetting the deferred_sql list """ for sql in self.deferred_sql: self.execute(sql) - + self.deferred_sql = [] + def clear_deferred_sql(self): + """ + Resets the deferred_sql list to empty. + """ + self.deferred_sql = [] + + + def clear_run_data(self): + """ + Resets variables to how they should be before a run. Used for dry runs. + """ + self.clear_deferred_sql() + self.pending_create_signals = [] + + def create_table(self, table_name, fields): """ Creates the table 'table_name'. 'fields' is a tuple of fields, @@ -57,14 +93,22 @@ class DatabaseOperations(object): django.db.models.fields.Field object """ qn = connection.ops.quote_name + + # allow fields to be a dictionary + # removed for now - philosophical reasons (this is almost certainly not what you want) + #try: + # fields = fields.items() + #except AttributeError: + # pass + columns = [ self.column_sql(table_name, field_name, field) for field_name, field in fields ] - + self.execute('CREATE TABLE %s (%s);' % (qn(table_name), ', '.join([col for col in columns if col]))) - - add_table = create_table # Alias for consistency's sake + + add_table = alias('create_table') # Alias for consistency's sake def rename_table(self, old_table_name, table_name): @@ -86,16 +130,26 @@ class DatabaseOperations(object): qn = connection.ops.quote_name params = (qn(table_name), ) self.execute('DROP TABLE %s;' % params) - - drop_table = delete_table + + drop_table = alias('delete_table') - def add_column(self, table_name, name, field): + def clear_table(self, table_name): + """ + Deletes all rows from 'table_name'. + """ + qn = connection.ops.quote_name + params = (qn(table_name), ) + self.execute('DELETE FROM %s;' % params) + + add_column_string = 'ALTER TABLE %s ADD COLUMN %s;' + + def add_column(self, table_name, name, field, keep_default=True): """ Adds the column 'name' to the table 'table_name'. Uses the 'field' paramater, a django.db.models.fields.Field instance, to generate the necessary sql - + @param table_name: The name of the table to add the column to @param name: The name of the column to add @param field: The field to use @@ -107,68 +161,82 @@ class DatabaseOperations(object): qn(table_name), sql, ) - sql = 'ALTER TABLE %s ADD COLUMN %s;' % params + sql = self.add_column_string % params self.execute(sql) - - + + # Now, drop the default if we need to + if not keep_default and field.default: + field.default = NOT_PROVIDED + self.alter_column(table_name, name, field, explicit_name=False) + alter_string_set_type = 'ALTER COLUMN %(column)s TYPE %(type)s' - alter_string_set_null = 'ALTER COLUMN %(column)s SET NOT NULL' - alter_string_drop_null = 'ALTER COLUMN %(column)s DROP NOT NULL' - - def alter_column(self, table_name, name, field): + alter_string_set_null = 'ALTER COLUMN %(column)s DROP NOT NULL' + alter_string_drop_null = 'ALTER COLUMN %(column)s SET NOT NULL' + allows_combined_alters = True + + def alter_column(self, table_name, name, field, explicit_name=True): """ Alters the given column name so it will match the given field. Note that conversion between the two by the database must be possible. - + Will not automatically add _id by default; to have this behavour, pass + explicit_name=False. + @param table_name: The name of the table to add the column to @param name: The name of the column to alter @param field: The new field definition to use """ - + # hook for the field to do any resolution prior to it's attributes being queried if hasattr(field, 'south_init'): field.south_init() - + qn = connection.ops.quote_name + # Add _id or whatever if we need to + if not explicit_name: + field.set_attributes_from_name(name) + name = field.column + # First, change the type params = { "column": qn(name), "type": field.db_type(), } - sqls = [self.alter_string_set_type % params] - - + + # SQLs is a list of (SQL, values) pairs. + sqls = [(self.alter_string_set_type % params, [])] + # Next, set any default - params = ( - qn(name), - ) - if not field.null and field.has_default(): default = field.get_default() - if isinstance(default, basestring): - default = "'%s'" % default - params += ("SET DEFAULT %s",) + sqls.append(('ALTER COLUMN %s SET DEFAULT %%s ' % (qn(name),), [default])) else: - params += ("DROP DEFAULT",) - - sqls.append('ALTER COLUMN %s %s ' % params) - - + sqls.append(('ALTER COLUMN %s DROP DEFAULT' % (qn(name),), [])) + + # Next, nullity params = { "column": qn(name), "type": field.db_type(), } if field.null: - sqls.append(self.alter_string_drop_null % params) + sqls.append((self.alter_string_set_null % params, [])) else: - sqls.append(self.alter_string_set_null % params) - - + sqls.append((self.alter_string_drop_null % params, [])) + + # TODO: Unique - - self.execute("ALTER TABLE %s %s;" % (qn(table_name), ", ".join(sqls))) + + if self.allows_combined_alters: + sqls, values = zip(*sqls) + self.execute( + "ALTER TABLE %s %s;" % (qn(table_name), ", ".join(sqls)), + flatten(values), + ) + else: + # Databases like e.g. MySQL don't like more than one alter at once. + for sql, values in sqls: + self.execute("ALTER TABLE %s %s;" % (qn(table_name), sql), values) def column_sql(self, table_name, field_name, field, tablespace=''): @@ -176,13 +244,13 @@ class DatabaseOperations(object): Creates the SQL snippet for a column. Used by add_column and add_table. """ qn = connection.ops.quote_name - + field.set_attributes_from_name(field_name) - + # hook for the field to do any resolution prior to it's attributes being queried if hasattr(field, 'south_init'): field.south_init() - + sql = field.db_type() if sql: field_output = [qn(field.column), sql] @@ -190,26 +258,40 @@ class DatabaseOperations(object): if field.primary_key: field_output.append('PRIMARY KEY') elif field.unique: - field_output.append('UNIQUE') - + # Instead of using UNIQUE, add a unique index with a predictable name + self.add_deferred_sql( + self.create_index_sql( + table_name, + [field.column], + unique = True, + db_tablespace = tablespace, + ) + ) + tablespace = field.db_tablespace or tablespace if tablespace and connection.features.supports_tablespaces and field.unique: # We must specify the index tablespace inline, because we # won't be generating a CREATE INDEX statement for this field. field_output.append(connection.ops.tablespace_sql(tablespace, inline=True)) - + sql = ' '.join(field_output) sqlparams = () # if the field is "NOT NULL" and a default value is provided, create the column with it # this allows the addition of a NOT NULL field to a table with existing rows if not field.null and field.has_default(): default = field.get_default() + # If the default is a callable, then call it! + if callable(default): + default = default() + # Now do some very cheap quoting. TODO: Redesign return values to avoid this. if isinstance(default, basestring): default = "'%s'" % default.replace("'", "''") + elif isinstance(default, datetime.date): + default = "'%s'" % default sql += " DEFAULT %s" sqlparams = (default) - - if field.rel: + + if field.rel and self.supports_foreign_keys: self.add_deferred_sql( self.foreign_key_sql( table_name, @@ -218,10 +300,10 @@ class DatabaseOperations(object): field.rel.to._meta.get_field(field.rel.field_name).column ) ) - + if field.db_index and not field.unique: self.add_deferred_sql(self.create_index_sql(table_name, [field.column])) - + if hasattr(field, 'post_create_sql'): style = no_style() for stmt in field.post_create_sql(style, table_name): @@ -231,21 +313,26 @@ class DatabaseOperations(object): return sql % sqlparams else: return None - + + + supports_foreign_keys = True + def foreign_key_sql(self, from_table_name, from_column_name, to_table_name, to_column_name): """ Generates a full SQL statement to add a foreign key constraint """ + qn = connection.ops.quote_name constraint_name = '%s_refs_%s_%x' % (from_column_name, to_column_name, abs(hash((from_table_name, to_table_name)))) return 'ALTER TABLE %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % ( - from_table_name, - truncate_name(constraint_name, connection.ops.max_name_length()), - from_column_name, - to_table_name, - to_column_name, + qn(from_table_name), + qn(truncate_name(constraint_name, connection.ops.max_name_length())), + qn(from_column_name), + qn(to_table_name), + qn(to_column_name), connection.ops.deferrable_sql() # Django knows this ) - + + def create_index_name(self, table_name, column_names): """ Generate a unique name for the index @@ -256,45 +343,55 @@ class DatabaseOperations(object): return '%s_%s%s' % (table_name, column_names[0], index_unique_name) + def create_index_sql(self, table_name, column_names, unique=False, db_tablespace=''): """ Generates a create index statement on 'table_name' for a list of 'column_names' """ + qn = connection.ops.quote_name if not column_names: print "No column names supplied on which to create an index" return '' - + if db_tablespace and connection.features.supports_tablespaces: tablespace_sql = ' ' + connection.ops.tablespace_sql(db_tablespace) else: tablespace_sql = '' - + index_name = self.create_index_name(table_name, column_names) qn = connection.ops.quote_name return 'CREATE %sINDEX %s ON %s (%s)%s;' % ( unique and 'UNIQUE ' or '', - index_name, - table_name, + qn(index_name), + qn(table_name), ','.join([qn(field) for field in column_names]), tablespace_sql - ) - + ) + def create_index(self, table_name, column_names, unique=False, db_tablespace=''): """ Executes a create index statement """ sql = self.create_index_sql(table_name, column_names, unique, db_tablespace) self.execute(sql) + drop_index_string = 'DROP INDEX %(index_name)s' + def delete_index(self, table_name, column_names, db_tablespace=''): """ Deletes an index created with create_index. This is possible using only columns due to the deterministic index naming function which relies on column names. """ + if isinstance(column_names, (str, unicode)): + column_names = [column_names] name = self.create_index_name(table_name, column_names) - sql = "DROP INDEX %s" % name + qn = connection.ops.quote_name + sql = self.drop_index_string % {"index_name": qn(name), "table_name": qn(table_name)} self.execute(sql) + drop_index = alias('delete_index') + + delete_column_string = 'ALTER TABLE %s DROP COLUMN %s CASCADE;' def delete_column(self, table_name, name): """ @@ -302,7 +399,9 @@ class DatabaseOperations(object): """ qn = connection.ops.quote_name params = (qn(table_name), qn(name)) - self.execute('ALTER TABLE %s DROP COLUMN %s CASCADE;' % params, []) + self.execute(self.delete_column_string % params, []) + + drop_column = alias('delete_column') def rename_column(self, table_name, old, new): @@ -317,6 +416,8 @@ class DatabaseOperations(object): Makes sure the following commands are inside a transaction. Must be followed by a (commit|rollback)_transaction call. """ + if self.dry_run: + return transaction.commit_unless_managed() transaction.enter_transaction_management() transaction.managed(True) @@ -327,6 +428,8 @@ class DatabaseOperations(object): Commits the current transaction. Must be preceded by a start_transaction call. """ + if self.dry_run: + return transaction.commit() transaction.leave_transaction_management() @@ -336,53 +439,67 @@ class DatabaseOperations(object): Rolls back the current transaction. Must be preceded by a start_transaction call. """ + if self.dry_run: + return transaction.rollback() transaction.leave_transaction_management() - - + + def send_create_signal(self, app_label, model_names): + self.pending_create_signals.append((app_label, model_names)) + + + def send_pending_create_signals(self): + for (app_label, model_names) in self.pending_create_signals: + self.really_send_create_signal(app_label, model_names) + self.pending_create_signals = [] + + + def really_send_create_signal(self, app_label, model_names): """ Sends a post_syncdb signal for the model specified. - + If the model is not found (perhaps it's been deleted?), no signal is sent. - + TODO: The behavior of django.contrib.* apps seems flawed in that they don't respect created_models. Rather, they blindly execute over all models within the app sending the signal. This is a patch we should push Django to make For now, this should work. """ + if self.debug: + print " - Sending post_syncdb signal for %s: %s" % (app_label, model_names) app = models.get_app(app_label) if not app: return - + created_models = [] for model_name in model_names: model = models.get_model(app_label, model_name) if model: created_models.append(model) - + if created_models: # syncdb defaults -- perhaps take these as options? verbosity = 1 interactive = True - + if hasattr(dispatcher, "send"): dispatcher.send(signal=models.signals.post_syncdb, sender=app, - app=app, created_models=created_models, - verbosity=verbosity, interactive=interactive) + app=app, created_models=created_models, + verbosity=verbosity, interactive=interactive) else: models.signals.post_syncdb.send(sender=app, - app=app, created_models=created_models, - verbosity=verbosity, interactive=interactive) - + app=app, created_models=created_models, + verbosity=verbosity, interactive=interactive) + def mock_model(self, model_name, db_table, db_tablespace='', - pk_field_name='id', pk_field_type=models.AutoField, - pk_field_kwargs={}): + pk_field_name='id', pk_field_type=models.AutoField, + pk_field_args=[], pk_field_kwargs={}): """ Generates a MockModel class that provides enough information to be used by a foreign key/many-to-many relationship. - + Migrations should prefer to use these rather than actual models as models could get deleted over time, but these can remain in migration files forever. @@ -397,7 +514,7 @@ class DatabaseOperations(object): if pk_field_type == models.AutoField: pk_field_kwargs['primary_key'] = True - self.pk = pk_field_type(**pk_field_kwargs) + self.pk = pk_field_type(*pk_field_args, **pk_field_kwargs) self.pk.set_attributes_from_name(pk_field_name) self.abstract = False @@ -416,3 +533,11 @@ class DatabaseOperations(object): MockModel._meta = MockOptions() MockModel._meta.model = MockModel return MockModel + +# Single-level flattening of lists +def flatten(ls): + nl = [] + for l in ls: + nl += l + return nl + diff --git a/apps/south/db/mysql.py b/apps/south/db/mysql.py index c3659fc48..a05c0714d 100644 --- a/apps/south/db/mysql.py +++ b/apps/south/db/mysql.py @@ -1,5 +1,6 @@ from django.db import connection +from django.conf import settings from south.db import generic class DatabaseOperations(generic.DatabaseOperations): @@ -11,9 +12,20 @@ class DatabaseOperations(generic.DatabaseOperations): alter_string_set_type = '' alter_string_set_null = 'MODIFY %(column)s %(type)s NULL;' alter_string_drop_null = 'MODIFY %(column)s %(type)s NOT NULL;' + drop_index_string = 'DROP INDEX %(index_name)s ON %(table_name)s' + allows_combined_alters = False + has_ddl_transactions = False + + def execute(self, sql, params=[]): + if hasattr(settings, "DATABASE_STORAGE_ENGINE") and \ + settings.DATABASE_STORAGE_ENGINE: + generic.DatabaseOperations.execute(self, "SET storage_engine=%s;" % + settings.DATABASE_STORAGE_ENGINE) + return generic.DatabaseOperations.execute(self, sql, params) + execute.__doc__ = generic.DatabaseOperations.execute.__doc__ def rename_column(self, table_name, old, new): - if old == new: + if old == new or self.dry_run: return [] qn = connection.ops.quote_name @@ -27,17 +39,22 @@ class DatabaseOperations(generic.DatabaseOperations): qn(table_name), qn(old), qn(new), - "%s %s %s %s %s" % ( - rows[0][1], - rows[0][2] == "YES" and "NULL" or "NOT NULL", - rows[0][3] == "PRI" and "PRIMARY KEY" or "", - rows[0][4] and "DEFAULT %s" % rows[0][4] or "", - rows[0][5] or "", - ), + rows[0][1], + rows[0][2] == "YES" and "NULL" or "NOT NULL", + rows[0][3] == "PRI" and "PRIMARY KEY" or "", + rows[0][4] and "DEFAULT " or "", + rows[0][4] and "%s" or "", + rows[0][5] or "", ) - self.execute('ALTER TABLE %s CHANGE COLUMN %s %s %s;' % params) - - + + sql = 'ALTER TABLE %s CHANGE COLUMN %s %s %s %s %s %s %s %s;' % params + + if rows[0][4]: + self.execute(sql, (rows[0][4],)) + else: + self.execute(sql) + + def rename_table(self, old_table_name, table_name): """ Renames the table 'old_table_name' to 'table_name'. @@ -47,4 +64,4 @@ class DatabaseOperations(generic.DatabaseOperations): return qn = connection.ops.quote_name params = (qn(old_table_name), qn(table_name)) - self.execute('RENAME TABLE %s TO %s;' % params) \ No newline at end of file + self.execute('RENAME TABLE %s TO %s;' % params) diff --git a/apps/south/db/postgresql_psycopg2.py b/apps/south/db/postgresql_psycopg2.py index 278eb3e9f..839b4b16f 100644 --- a/apps/south/db/postgresql_psycopg2.py +++ b/apps/south/db/postgresql_psycopg2.py @@ -16,6 +16,7 @@ class DatabaseOperations(generic.DatabaseOperations): self.execute('ALTER TABLE %s RENAME COLUMN %s TO %s;' % params) def rename_table(self, old_table_name, table_name): + "will rename the table and an associated ID sequence and primary key index" # First, rename the table generic.DatabaseOperations.rename_table(self, old_table_name, table_name) # Then, try renaming the ID sequence @@ -25,8 +26,27 @@ class DatabaseOperations(generic.DatabaseOperations): try: generic.DatabaseOperations.rename_table(self, old_table_name+"_id_seq", table_name+"_id_seq") except: - print " ~ No such sequence (ignoring error)" + if self.debug: + print " ~ No such sequence (ignoring error)" self.rollback_transaction() else: self.commit_transaction() - self.start_transaction() \ No newline at end of file + self.start_transaction() + + # Rename primary key index, will not rename other indices on + # the table that are used by django (e.g. foreign keys). Until + # figure out how, you need to do this yourself. + try: + generic.DatabaseOperations.rename_table(self, old_table_name+"_pkey", table_name+ "_pkey") + except: + if self.debug: + print " ~ No such primary key (ignoring error)" + self.rollback_transaction() + else: + self.commit_transaction() + self.start_transaction() + + + def rename_index(self, old_index_name, index_name): + "Rename an index individually" + generic.DatabaseOperations.rename_table(self, old_index_name, index_name) diff --git a/apps/south/db/sql_server/__init__.py b/apps/south/db/sql_server/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/south/db/sql_server/pyodbc.py b/apps/south/db/sql_server/pyodbc.py new file mode 100644 index 000000000..58c51669c --- /dev/null +++ b/apps/south/db/sql_server/pyodbc.py @@ -0,0 +1,25 @@ +from django.db import connection +from django.db.models.fields import * +from south.db import generic + +class DatabaseOperations(generic.DatabaseOperations): + """ + django-pyodbc (sql_server.pyodbc) implementation of database operations. + """ + + add_column_string = 'ALTER TABLE %s ADD %s;' + alter_string_set_type = 'ALTER COLUMN %(column)s %(type)s' + allows_combined_alters = False + delete_column_string = 'ALTER TABLE %s DROP COLUMN %s;' + + def create_table(self, table_name, fields): + # Tweak stuff as needed + for name,f in fields: + if isinstance(f, BooleanField): + if f.default == True: + f.default = 1 + if f.default == False: + f.default = 0 + + # Run + generic.DatabaseOperations.create_table(self, table_name, fields) diff --git a/apps/south/db/sqlite3.py b/apps/south/db/sqlite3.py index 6073b4d4c..1fac1b83d 100644 --- a/apps/south/db/sqlite3.py +++ b/apps/south/db/sqlite3.py @@ -8,5 +8,35 @@ class DatabaseOperations(generic.DatabaseOperations): SQLite3 implementation of database operations. """ - def __init__(self): - raise NotImplementedError("Support for SQLite3 is not yet complete.") \ No newline at end of file + # SQLite ignores foreign key constraints. I wish I could. + supports_foreign_keys = False + + # You can't add UNIQUE columns with an ALTER TABLE. + def add_column(self, table_name, name, field, *args, **kwds): + # Run ALTER TABLE with no unique column + unique, field._unique, field.db_index = field.unique, False, False + generic.DatabaseOperations.add_column(self, table_name, name, field, *args, **kwds) + # If it _was_ unique, make an index on it. + if unique: + self.create_index(table_name, [name], unique=True) + + # SQLite doesn't have ALTER COLUMN + def alter_column(self, table_name, name, field, explicit_name=True): + """ + Not supported under SQLite. + """ + raise NotImplementedError("SQLite does not support altering columns.") + + # Nor DROP COLUMN + def delete_column(self, table_name, name, field): + """ + Not supported under SQLite. + """ + raise NotImplementedError("SQLite does not support deleting columns.") + + # Nor RENAME COLUMN + def rename_column(self, table_name, old, new): + """ + Not supported under SQLite. + """ + raise NotImplementedError("SQLite does not support renaming columns.") \ No newline at end of file diff --git a/apps/south/install/README b/apps/south/install/README deleted file mode 100644 index 897b51d34..000000000 --- a/apps/south/install/README +++ /dev/null @@ -1 +0,0 @@ -To use this setup.py, make sure you checked out this trunk or branch into a directory called 'south', copy the setup.py into the directory above it, and off you go. diff --git a/apps/south/install/setup.py b/apps/south/install/setup.py deleted file mode 100755 index 6da3d4afc..000000000 --- a/apps/south/install/setup.py +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/python - -from setuptools import setup, find_packages - -setup( - name='South', - version='0.3', - description='South: Migrations for Django', - author='Andrew Godwin & Andy McCurdy', - author_email='south@aeracode.org', - url='http://south.aeracode.org/', - packages=["south", "south.db", "south.management", "south.management.commands", "south.tests"], -) diff --git a/apps/south/management/commands/migrate.py b/apps/south/management/commands/migrate.py index d2d69982f..1cc2a2908 100644 --- a/apps/south/management/commands/migrate.py +++ b/apps/south/management/commands/migrate.py @@ -16,11 +16,20 @@ class Command(BaseCommand): help='Only runs or rolls back the migration specified, and none around it.'), make_option('--fake', action='store_true', dest='fake', default=False, help="Pretends to do the migrations, but doesn't actually execute them."), + + make_option('--db-dry-run', action='store_true', dest='db_dry_run', default=False, + help="Doesn't execute the SQL generated by the db methods, and doesn't store a record that the migration(s) occurred. Useful to test migrations before applying them."), ) + if '--verbosity' not in [opt.get_opt_string() for opt in BaseCommand.option_list]: + option_list += ( + make_option('--verbosity', action='store', dest='verbosity', default='1', + type='choice', choices=['0', '1', '2'], + help='Verbosity level; 0=minimal output, 1=normal output, 2=all output'), + ) help = "Runs migrations for all apps." - def handle(self, app=None, target=None, skip=False, merge=False, only=False, backwards=False, fake=False, **options): - + def handle(self, app=None, target=None, skip=False, merge=False, only=False, backwards=False, fake=False, db_dry_run=False, **options): + # Work out what the resolve mode is resolve_mode = merge and "merge" or (skip and "skip" or None) # Turn on db debugging @@ -46,10 +55,14 @@ class Command(BaseCommand): apps = [migration.get_app(app)] else: apps = migration.get_migrated_apps() + silent = options.get('verbosity', 0) == 0 for app in apps: migration.migrate_app( app, resolve_mode = resolve_mode, target_name = target, fake = fake, + db_dry_run = db_dry_run, + silent = silent, + load_inital_data = True, ) diff --git a/apps/south/management/commands/startmigration.py b/apps/south/management/commands/startmigration.py index f52efe7aa..1a8da9977 100644 --- a/apps/south/management/commands/startmigration.py +++ b/apps/south/management/commands/startmigration.py @@ -3,6 +3,7 @@ from django.core.management.color import no_style from django.db import models from django.db.models.fields.related import RECURSIVE_RELATIONSHIP_CONSTANT from django.contrib.contenttypes.generic import GenericRelation +from django.db.models.fields import FieldDoesNotExist from optparse import make_option from south import migration import sys @@ -17,18 +18,23 @@ class Command(BaseCommand): option_list = BaseCommand.option_list + ( make_option('--model', action='append', dest='model_list', type='string', help='Generate a Create Table migration for the specified model. Add multiple models to this migration with subsequent --model parameters.'), + make_option('--add-field', action='append', dest='field_list', type='string', + help='Generate an Add Column migration for the specified modelname.fieldname - you can use this multiple times to add more than one column.'), make_option('--initial', action='store_true', dest='initial', default=False, help='Generate the initial schema for the app.'), ) help = "Creates a new template migration for the given app" - def handle(self, app=None, name="", model_list=None, initial=False, **options): + def handle(self, app=None, name="", model_list=None, field_list=None, initial=False, **options): # If model_list is None, then it's an empty list model_list = model_list or [] + # If field_list is None, then it's an empty list + field_list = field_list or [] + # make sure --model and --all aren't both specified - if initial and model_list: + if initial and (model_list or field_list): print "You cannot use --initial and other options together" return @@ -67,7 +73,22 @@ class Command(BaseCommand): return models_to_migrate.append(model) - + + # See what fields need to be included + fields_to_add = [] + for field_spec in field_list: + model_name, field_name = field_spec.split(".", 1) + model = models.get_model(app, model_name) + if not model: + print "Couldn't find model '%s' in app '%s'" % (model_name, app) + return + try: + field = model._meta.get_field(field_name) + except FieldDoesNotExist: + print "Model '%s' doesn't have a field '%s'" % (model_name, field_name) + return + fields_to_add.append((model, field_name, field)) + # Make the migrations directory if it's not there app_module_path = app_models_module.__name__.split('.')[0:-1] try: @@ -80,11 +101,15 @@ class Command(BaseCommand): os.path.dirname(app_module.__file__), "migrations", ) + # Make sure there's a migrations directory and __init__.py if not os.path.isdir(migrations_dir): print "Creating migrations directory at '%s'..." % migrations_dir os.mkdir(migrations_dir) + init_path = os.path.join(migrations_dir, "__init__.py") + if not os.path.isfile(init_path): # Touch the init py file - open(os.path.join(migrations_dir, "__init__.py"), "w").close() + print "Creating __init__.py in '%s'..." % migrations_dir + open(init_path, "w").close() # See what filename is next in line. We assume they use numbers. migrations = migration.get_migration_names(migration.get_app(app)) highest_number = 0 @@ -102,23 +127,110 @@ class Command(BaseCommand): ) # If there's a model, make the migration skeleton, else leave it bare forwards, backwards = '', '' + if fields_to_add: + # First, do the added fields + for model, field_name, field in fields_to_add: + field_definition = generate_field_definition(model, field) + + if isinstance(field, models.ManyToManyField): + # Make a mock model for each side + mock_model = "\n".join([ + create_mock_model(model, " "), + create_mock_model(field.rel.to, " ") + ]) + # And a field defn, that's actually a table creation + forwards += ''' + # Mock Model +%s + # Adding ManyToManyField '%s.%s' + db.create_table('%s', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('%s', models.ForeignKey(%s, null=False)), + ('%s', models.ForeignKey(%s, null=False)) + )) ''' % ( + mock_model, + model._meta.object_name, + field.name, + field.m2m_db_table(), + field.m2m_column_name()[:-3], # strip off the '_id' at the end + model._meta.object_name, + field.m2m_reverse_name()[:-3], # strip off the '_id' at the ned + field.rel.to._meta.object_name + ) + backwards += ''' + # Dropping ManyToManyField '%s.%s' + db.drop_table('%s')''' % ( + model._meta.object_name, + field.name, + field.m2m_db_table() + ) + continue + elif field.rel: # ForeignKey, etc. + mock_model = create_mock_model(field.rel.to, " ") + field_definition = related_field_definition(field, field_definition) + else: + mock_model = None + + # If we can't get it (inspect madness?) then insert placeholder + if not field_definition: + print "Warning: Could not generate field definition for %s.%s, manual editing of migration required." % \ + (model._meta.object_name, field.name) + field_definition = '<<< REPLACE THIS WITH FIELD DEFINITION FOR %s.%s >>>' % (model._meta.object_name, f.name) + + if mock_model: + forwards += ''' + # Mock model +%s + ''' % (mock_model) + + forwards += ''' + # Adding field '%s.%s' + db.add_column(%r, %r, %s) + ''' % ( + model._meta.object_name, + field.name, + model._meta.db_table, + field.name, + field_definition, + ) + backwards += ''' + # Deleting field '%s.%s' + db.delete_column(%r, %r) + ''' % ( + model._meta.object_name, + field.name, + model._meta.db_table, + field.column, + ) + if models_to_migrate: + # Now, do the added models for model in models_to_migrate: table_name = model._meta.db_table mock_models = [] fields = [] for f in model._meta.local_fields: - # look up the field definition to see how this was created + + # Look up the field definition to see how this was created field_definition = generate_field_definition(model, f) - if field_definition: + + # If it's a OneToOneField, and ends in _ptr, just use it + if isinstance(f, models.OneToOneField) and f.name.endswith("_ptr"): + mock_models.append(create_mock_model(f.rel.to, " ")) + field_definition = "models.OneToOneField(%s)" % f.rel.to.__name__ + + # It's probably normal then + elif field_definition: if isinstance(f, models.ForeignKey): - mock_models.append(create_mock_model(f.rel.to)) + mock_models.append(create_mock_model(f.rel.to, " ")) field_definition = related_field_definition(f, field_definition) - + + # Oh noes, no defn found else: print "Warning: Could not generate field definition for %s.%s, manual editing of migration required." % \ (model._meta.object_name, f.name) + print f, type(f) field_definition = '<<< REPLACE THIS WITH FIELD DEFINITION FOR %s.%s >>>' % (model._meta.object_name, f.name) @@ -128,12 +240,12 @@ class Command(BaseCommand): forwards += ''' # Mock Models - %s - ''' % "\n ".join(mock_models) +%s + ''' % "\n".join(mock_models) forwards += ''' # Model '%s' - db.create_table('%s', ( + db.create_table(%r, ( %s ))''' % ( model._meta.object_name, @@ -155,11 +267,11 @@ class Command(BaseCommand): if m.rel.through: continue - mock_models = [create_mock_model(model), create_mock_model(m.rel.to)] + mock_models = [create_mock_model(model, " "), create_mock_model(m.rel.to, " ")] forwards += ''' # Mock Models - %s +%s # M2M field '%s.%s' db.create_table('%s', ( @@ -167,7 +279,7 @@ class Command(BaseCommand): ('%s', models.ForeignKey(%s, null=False)), ('%s', models.ForeignKey(%s, null=False)) )) ''' % ( - "\n ".join(mock_models), + "\n".join(mock_models), model._meta.object_name, m.name, m.m2m_db_table(), @@ -204,12 +316,20 @@ class Command(BaseCommand): "','".join(model._meta.object_name for model in models_to_migrate) ) - else: + # Try sniffing the encoding using PEP 0263's method + encoding = None + first_two_lines = inspect.getsourcelines(app_models_module)[0][:2] + for line in first_two_lines: + if re.search("coding[:=]\s*([-\w.]+)", line): + encoding = line + + if (not forwards) and (not backwards): forwards = '"Write your forwards migration here"' backwards = '"Write your backwards migration here"' fp = open(os.path.join(migrations_dir, new_filename), "w") - fp.write(""" + fp.write("""%s from south.db import db +from django.db import models from %s.models import * class Migration: @@ -219,7 +339,7 @@ class Migration: def backwards(self): %s -""" % ('.'.join(app_module_path), forwards, backwards)) +""" % (encoding or "", '.'.join(app_module_path), forwards, backwards)) fp.close() print "Created %s." % new_filename @@ -248,8 +368,11 @@ def generate_field_definition(model, field): # the correct comment. if test_field(stripped_definition): return stripped_definition - - index = field_definition.index('#', index+1) + + try: + index = field_definition.index('#', index+1) + except ValueError: + break return field_definition @@ -263,7 +386,7 @@ def generate_field_definition(model, field): source = inspect.getsourcelines(model) if not source: raise Exception("Could not find source to model: '%s'" % (model.__name__)) - + # look for a line starting with the field name start_field_re = re.compile(r'\s*%s\s*=\s*(.*)' % field.name) for line in source[0]: @@ -329,21 +452,40 @@ def related_field_definition(field, field_definition): return field_definition -def create_mock_model(model): +def create_mock_model(model, indent=" "): # produce a string representing the python syntax necessary for creating # a mock model using the supplied real model - if model._meta.pk.__class__.__module__ != 'django.db.models.fields': + if not model._meta.pk.__class__.__module__.startswith('django.db.models.fields'): # we can fix this with some clever imports, but it doesn't seem necessary to # spend time on just yet - print "Can't generate a mock model for %s because it's primary key isn't a default django field" % model + print "Can't generate a mock model for %s because it's primary key isn't a default django field; it's type %s." % (model, model._meta.pk.__class__) sys.exit() - return "%s = db.mock_model(model_name='%s', db_table='%s', db_tablespace='%s', pk_field_name='%s', pk_field_type=models.%s)" % \ + pk_field_args = [] + pk_field_kwargs = {} + other_mocks = [] + # If it's a OneToOneField or ForeignKey, take it's first arg + if model._meta.pk.__class__.__name__ in ["OneToOneField", "ForeignKey"]: + if model._meta.pk.rel.to == model: + pk_field_args += ["'self'"] + else: + pk_field_args += [model._meta.pk.rel.to._meta.object_name] + other_mocks += [model._meta.pk.rel.to] + + # Perhaps it has a max_length set? + if model._meta.pk.max_length: + pk_field_kwargs["max_length"] = model._meta.pk.max_length + + return "%s%s%s = db.mock_model(model_name='%s', db_table='%s', db_tablespace='%s', pk_field_name='%s', pk_field_type=models.%s, pk_field_args=[%s], pk_field_kwargs=%r)" % \ ( + "\n".join([create_mock_model(m, indent) for m in other_mocks]+[""]), + indent, model._meta.object_name, model._meta.object_name, model._meta.db_table, model._meta.db_tablespace, model._meta.pk.name, - model._meta.pk.__class__.__name__ - ) \ No newline at end of file + model._meta.pk.__class__.__name__, + ", ".join(pk_field_args), + pk_field_kwargs, + ) diff --git a/apps/south/management/commands/syncdb.py b/apps/south/management/commands/syncdb.py index 6ffc12015..7b160c27a 100644 --- a/apps/south/management/commands/syncdb.py +++ b/apps/south/management/commands/syncdb.py @@ -1,4 +1,4 @@ -from django.core.management.base import NoArgsCommand +from django.core.management.base import NoArgsCommand, BaseCommand from django.core.management.color import no_style from django.utils.datastructures import SortedDict from optparse import make_option @@ -15,14 +15,17 @@ def get_app_name(app): class Command(NoArgsCommand): option_list = NoArgsCommand.option_list + ( - make_option('--verbosity', action='store', dest='verbosity', default='1', - type='choice', choices=['0', '1', '2'], - help='Verbosity level; 0=minimal output, 1=normal output, 2=all output'), make_option('--noinput', action='store_false', dest='interactive', default=True, help='Tells Django to NOT prompt the user for input of any kind.'), make_option('--migrate', action='store_true', dest='migrate', default=False, help='Tells South to also perform migrations after the sync. Default for during testing, and other internal calls.'), ) + if '--verbosity' not in [opt.get_opt_string() for opt in BaseCommand.option_list]: + option_list += ( + make_option('--verbosity', action='store', dest='verbosity', default='1', + type='choice', choices=['0', '1', '2'], + help='Verbosity level; 0=minimal output, 1=normal output, 2=all output'), + ) help = "Create the database tables for all apps in INSTALLED_APPS whose tables haven't already been created, except those which use migrations." def handle_noargs(self, **options): @@ -37,8 +40,10 @@ class Command(NoArgsCommand): else: # This is a migrated app, leave it apps_migrated.append(app_name) + verbosity = int(options.get('verbosity', 0)) # Run syncdb on only the ones needed - print "Syncing..." + if verbosity > 0: + print "Syncing..." old_installed, settings.INSTALLED_APPS = settings.INSTALLED_APPS, apps_needing_sync old_app_store, cache.app_store = cache.app_store, SortedDict([ (k, v) for (k, v) in cache.app_store.items() @@ -49,13 +54,17 @@ class Command(NoArgsCommand): cache.app_store = old_app_store # Migrate if needed if options.get('migrate', True): - print "Migrating..." - management.call_command('migrate') + if verbosity > 0: + print "Migrating..." + management.call_command('migrate', **options) # Be obvious about what we did - print "\nSynced:\n > %s" % "\n > ".join(apps_needing_sync) + if verbosity > 0: + print "\nSynced:\n > %s" % "\n > ".join(apps_needing_sync) if options.get('migrate', True): - print "\nMigrated:\n - %s" % "\n - ".join(apps_migrated) + if verbosity > 0: + print "\nMigrated:\n - %s" % "\n - ".join(apps_migrated) else: - print "\nNot synced (use migrations):\n - %s" % "\n - ".join(apps_migrated) - print "(use ./manage.py migrate to migrate these)" + if verbosity > 0: + print "\nNot synced (use migrations):\n - %s" % "\n - ".join(apps_migrated) + print "(use ./manage.py migrate to migrate these)" diff --git a/apps/south/management/commands/test.py b/apps/south/management/commands/test.py new file mode 100644 index 000000000..eef8f3157 --- /dev/null +++ b/apps/south/management/commands/test.py @@ -0,0 +1,12 @@ +from django.core import management +from django.core.management.commands import test +from django.core.management.commands import syncdb + +class Command(test.Command): + + def handle(self, *args, **kwargs): + # point at the core syncdb command when creating tests + # tests should always be up to date with the most recent model structure + management.get_commands() + management._commands['syncdb'] = 'django.core' + super(Command, self).handle(*args, **kwargs) \ No newline at end of file diff --git a/apps/south/migration.py b/apps/south/migration.py index c650c5475..6452442d3 100644 --- a/apps/south/migration.py +++ b/apps/south/migration.py @@ -2,8 +2,11 @@ import datetime import os import sys +import traceback from django.conf import settings from django.db import models +from django.core.exceptions import ImproperlyConfigured +from django.core.management import call_command from models import MigrationHistory from south.db import db @@ -57,7 +60,7 @@ def get_migration_names(app): return sorted([ filename[:-3] for filename in os.listdir(os.path.dirname(app.__file__)) - if filename.endswith(".py") and filename != "__init__.py" + if filename.endswith(".py") and filename != "__init__.py" and not filename.startswith(".") ]) @@ -77,7 +80,9 @@ def get_migration(app, name): module = __import__(app.__name__ + "." + name, '', '', ['Migration']) return module.Migration except ImportError: - raise ValueError("Migration %s:%s does not exist." % (get_app_name(app), name)) + print " ! Migration %s:%s probably doesn't exist." % (get_app_name(app), name) + print " - Traceback:" + raise def all_migrations(): @@ -201,61 +206,116 @@ def needed_before_backwards(tree, app, name, sameapp=True): return remove_duplicates(needed) -def run_forwards(app, migrations, fake=False, silent=False): +def run_migrations(toprint, torun, recorder, app, migrations, fake=False, db_dry_run=False, silent=False): """ Runs the specified migrations forwards, in order. """ for migration in migrations: app_name = get_app_name(app) if not silent: - print " > %s: %s" % (app_name, migration) + print toprint % (app_name, migration) klass = get_migration(app, migration) + if fake: if not silent: print " (faked)" else: - db.start_transaction() + + # If the database doesn't support running DDL inside a transaction + # *cough*MySQL*cough* then do a dry run first. + if not db.has_ddl_transactions: + db.dry_run = True + db.debug, old_debug = False, db.debug + try: + getattr(klass(), torun)() + except: + traceback.print_exc() + print " ! Error found during dry run of migration! Aborting." + return False + db.debug = old_debug + db.clear_run_data() + + db.dry_run = bool(db_dry_run) + + if db.has_ddl_transactions: + db.start_transaction() try: - klass().forwards() + getattr(klass(), torun)() db.execute_deferred_sql() except: - db.rollback_transaction() - raise + if db.has_ddl_transactions: + db.rollback_transaction() + raise + else: + traceback.print_exc() + print " ! Error found during real run of migration! Aborting." + print + print " ! Since you have a database that does not support running" + print " ! schema-altering statements in transactions, we have had to" + print " ! leave it in an interim state between migrations." + if torun == "forwards": + print + print " ! You *might* be able to recover with:" + db.debug = db.dry_run = True + klass().backwards() + print + print " ! The South developers regret this has happened, and would" + print " ! like to gently persuade you to consider a slightly" + print " ! easier-to-deal-with DBMS." + return False else: - db.commit_transaction() + if db.has_ddl_transactions: + db.commit_transaction() + + if not db_dry_run: + # Record us as having done this + recorder(app_name, migration) + + +def run_forwards(app, migrations, fake=False, db_dry_run=False, silent=False): + """ + Runs the specified migrations forwards, in order. + """ + + def record(app_name, migration): # Record us as having done this record = MigrationHistory.for_migration(app_name, migration) record.applied = datetime.datetime.utcnow() record.save() - - -def run_backwards(app, migrations, ignore=[], fake=False, silent=False): + + return run_migrations( + toprint = " > %s: %s", + torun = "forwards", + recorder = record, + app = app, + migrations = migrations, + fake = fake, + db_dry_run = db_dry_run, + silent = silent, + ) + + +def run_backwards(app, migrations, ignore=[], fake=False, db_dry_run=False, silent=False): """ Runs the specified migrations backwards, in order, skipping those migrations in 'ignore'. """ - for migration in migrations: - if migration not in ignore: - app_name = get_app_name(app) - if not silent: - print " < %s: %s" % (app_name, migration) - klass = get_migration(app, migration) - if fake: - if not silent: - print " (faked)" - else: - db.start_transaction() - try: - klass().backwards() - db.execute_deferred_sql() - except: - db.rollback_transaction() - raise - else: - db.commit_transaction() - # Record us as having not done this - record = MigrationHistory.for_migration(app_name, migration) - record.delete() + + def record(app_name, migration): + # Record us as having not done this + record = MigrationHistory.for_migration(app_name, migration) + record.delete() + + return run_migrations( + toprint = " < %s: %s", + torun = "backwards", + recorder = record, + app = app, + migrations = [x for x in migrations if x not in ignore], + fake = fake, + db_dry_run = db_dry_run, + silent = silent, + ) def right_side_of(x, y): @@ -291,7 +351,7 @@ def backwards_problems(tree, backwards, done, silent=False): return problems -def migrate_app(app, target_name=None, resolve_mode=None, fake=False, yes=False, silent=False): +def migrate_app(app, target_name=None, resolve_mode=None, fake=False, db_dry_run=False, yes=False, silent=False, load_inital_data=False): app_name = get_app_name(app) @@ -310,6 +370,12 @@ def migrate_app(app, target_name=None, resolve_mode=None, fake=False, yes=False, tree = dependency_tree() migrations = get_migration_names(app) + # If there aren't any, quit quizically + if not migrations: + if not silent: + print "? You have no migrations for the '%s' app. You might want some." % app_name + return + if target_name not in migrations and target_name not in ["zero", None]: matches = [x for x in migrations if x.startswith(target_name)] if len(matches) == 1: @@ -331,7 +397,15 @@ def migrate_app(app, target_name=None, resolve_mode=None, fake=False, yes=False, return # Check there's no strange ones in the database - ghost_migrations = [m for m in MigrationHistory.objects.filter(applied__isnull = False) if get_app(m.app_name) not in tree or m.migration not in tree[get_app(m.app_name)]] + ghost_migrations = [] + for m in MigrationHistory.objects.filter(applied__isnull = False): + try: + if get_app(m.app_name) not in tree or m.migration not in tree[get_app(m.app_name)]: + ghost_migrations.append(m) + except ImproperlyConfigured: + pass + + if ghost_migrations: if not silent: print " ! These migrations are in the database but not on disk:" @@ -361,7 +435,12 @@ def migrate_app(app, target_name=None, resolve_mode=None, fake=False, yes=False, backwards = [] # Get the list of currently applied migrations from the db - current_migrations = [(get_app(m.app_name), m.migration) for m in MigrationHistory.objects.filter(applied__isnull = False)] + current_migrations = [] + for m in MigrationHistory.objects.filter(applied__isnull = False): + try: + current_migrations.append((get_app(m.app_name), m.migration)) + except ImproperlyConfigured: + pass direction = None bad = False @@ -416,15 +495,34 @@ def migrate_app(app, target_name=None, resolve_mode=None, fake=False, yes=False, if direction == 1: if not silent: print " - Migrating forwards to %s." % target_name - for mapp, mname in forwards: - if (mapp, mname) not in current_migrations: - run_forwards(mapp, [mname], fake=fake, silent=silent) + try: + for mapp, mname in forwards: + if (mapp, mname) not in current_migrations: + result = run_forwards(mapp, [mname], fake=fake, db_dry_run=db_dry_run, silent=silent) + if result is False: # The migrations errored, but nicely. + return + finally: + # Call any pending post_syncdb signals + db.send_pending_create_signals() + # Now load initial data, only if we're really doing things and ended up at current + if not fake and not db_dry_run and load_inital_data and target_name == migrations[-1]: + print " - Loading initial data for %s." % app_name + # Override Django's get_apps call temporarily to only load from the + # current app + old_get_apps, models.get_apps = ( + models.get_apps, + lambda: [models.get_app(get_app_name(app))], + ) + # Load the initial fixture + call_command('loaddata', 'initial_data', verbosity=1) + # Un-override + models.get_apps = old_get_apps elif direction == -1: if not silent: print " - Migrating backwards to just after %s." % target_name for mapp, mname in backwards: if (mapp, mname) in current_migrations: - run_backwards(mapp, [mname], fake=fake, silent=silent) + run_backwards(mapp, [mname], fake=fake, db_dry_run=db_dry_run, silent=silent) else: if not silent: - print "- Nothing to migrate." \ No newline at end of file + print "- Nothing to migrate." diff --git a/apps/south/setup.py b/apps/south/setup.py new file mode 100755 index 000000000..9e0958364 --- /dev/null +++ b/apps/south/setup.py @@ -0,0 +1,26 @@ +#!/usr/bin/python + +from setuptools import setup, find_packages + +setup( + name='South', + version='0.4', + description='South: Migrations for Django', + long_description='South is an intelligent database migrations library for the Django web framework. It is database-independent and DVCS-friendly, as well as a whole host of other features.', + author='Andrew Godwin & Andy McCurdy', + author_email='south@aeracode.org', + url='http://south.aeracode.org/', + download_url='http://south.aeracode.org/wiki/Download', + classifiers=[ + "Development Status :: 5 - Production/Stable", + "Framework :: Django", + "Intended Audience :: Developers", + "Intended Audience :: System Administrators", + "Intended Audience :: System Administrators", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + "Topic :: Software Development" + ], + packages=["south", "south.db", "south.management", "south.management.commands", "south.tests", "south.tests.fakeapp", "south.tests.fakeapp.migrations"], + package_dir = {"south" : ""}, +) diff --git a/apps/south/tests/db.py b/apps/south/tests/db.py index c47f02198..b7bb14541 100644 --- a/apps/south/tests/db.py +++ b/apps/south/tests/db.py @@ -21,6 +21,7 @@ class TestOperations(unittest.TestCase): def setUp(self): db.debug = False + db.clear_deferred_sql() def test_create(self): """ @@ -45,7 +46,7 @@ class TestOperations(unittest.TestCase): db.rollback_transaction() db.start_transaction() # Remove the table - db.delete_table("test1") + db.drop_table("test1") # Make sure it went try: cursor.execute("SELECT * FROM test1") @@ -63,6 +64,23 @@ class TestOperations(unittest.TestCase): pass db.rollback_transaction() + def test_foreign_keys(self): + """ + Tests foreign key creation, especially uppercase (see #61) + """ + Test = db.mock_model(model_name='Test', db_table='test5a', + db_tablespace='', pk_field_name='ID', + pk_field_type=models.AutoField, pk_field_args=[]) + cursor = connection.cursor() + db.start_transaction() + db.create_table("test5a", [('ID', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True))]) + db.create_table("test5b", [ + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('UNIQUE', models.ForeignKey(Test)), + ]) + db.execute_deferred_sql() + db.rollback_transaction() + def test_rename(self): """ Test column renaming @@ -80,4 +98,88 @@ class TestOperations(unittest.TestCase): self.fail("Just-renamed column could be selected!") except: pass - db.rollback_transaction() \ No newline at end of file + db.rollback_transaction() + db.delete_table("test2") + + def test_dry_rename(self): + """ + Test column renaming while --dry-run is turned on (should do nothing) + See ticket #65 + """ + cursor = connection.cursor() + db.create_table("test2", [('spam', models.BooleanField(default=False))]) + db.start_transaction() + # Make sure we can select the column + cursor.execute("SELECT spam FROM test2") + # Rename it + db.dry_run = True + db.rename_column("test2", "spam", "eggs") + db.dry_run = False + cursor.execute("SELECT spam FROM test2") + try: + cursor.execute("SELECT eggs FROM test2") + self.fail("Dry-renamed new column could be selected!") + except: + pass + db.rollback_transaction() + db.delete_table("test2") + + def test_table_rename(self): + """ + Test column renaming + """ + cursor = connection.cursor() + db.create_table("testtr", [('spam', models.BooleanField(default=False))]) + db.start_transaction() + # Make sure we can select the column + cursor.execute("SELECT spam FROM testtr") + # Rename it + db.rename_table("testtr", "testtr2") + cursor.execute("SELECT spam FROM testtr2") + try: + cursor.execute("SELECT spam FROM testtr") + self.fail("Just-renamed column could be selected!") + except: + pass + db.rollback_transaction() + db.delete_table("testtr2") + + def test_index(self): + """ + Test the index operations + """ + db.create_table("test3", [ + ('SELECT', models.BooleanField(default=False)), + ('eggs', models.IntegerField(unique=True)), + ]) + db.execute_deferred_sql() + db.start_transaction() + # Add an index on that column + db.create_index("test3", ["SELECT"]) + # Add another index on two columns + db.create_index("test3", ["SELECT", "eggs"]) + # Delete them both + db.delete_index("test3", ["SELECT"]) + db.delete_index("test3", ["SELECT", "eggs"]) + # Delete the unique index + db.delete_index("test3", ["eggs"]) + db.rollback_transaction() + db.delete_table("test3") + + def test_alter(self): + """ + Test altering columns/tables + """ + db.create_table("test4", [ + ('spam', models.BooleanField(default=False)), + ('eggs', models.IntegerField()), + ]) + db.start_transaction() + # Add a column + db.add_column("test4", "add1", models.IntegerField(default=3), keep_default=False) + # Add a FK with keep_default=False (#69) + User = db.mock_model(model_name='User', db_table='auth_user', db_tablespace='', pk_field_name='id', pk_field_type=models.AutoField, pk_field_args=[], pk_field_kwargs={}) + db.add_column("test4", "user", models.ForeignKey(User), keep_default=False) + + db.rollback_transaction() + db.delete_table("test4") \ No newline at end of file diff --git a/apps/south/tests/fakeapp/migrations/0003_alter_spam.py b/apps/south/tests/fakeapp/migrations/0003_alter_spam.py new file mode 100644 index 000000000..3a9aea49e --- /dev/null +++ b/apps/south/tests/fakeapp/migrations/0003_alter_spam.py @@ -0,0 +1,12 @@ +from south.db import db +from django.db import models + +class Migration: + + def forwards(self): + + db.alter_column("southtest_spam", 'name', models.CharField(max_length=255, null=True)) + + def backwards(self): + + db.alter_column("southtest_spam", 'name', models.CharField(max_length=255)) diff --git a/apps/south/tests/logic.py b/apps/south/tests/logic.py index dfb441eed..862c52dce 100644 --- a/apps/south/tests/logic.py +++ b/apps/south/tests/logic.py @@ -117,7 +117,7 @@ class TestMigrationLogic(unittest.TestCase): app = self.create_test_app() self.assertEqual( - ["0001_spam", "0002_eggs"], + ["0001_spam", "0002_eggs", "0003_alter_spam"], migration.get_migration_names(app), ) @@ -129,9 +129,10 @@ class TestMigrationLogic(unittest.TestCase): # Can't use vanilla import, modules beginning with numbers aren't in grammar M1 = __import__("fakeapp.migrations.0001_spam", {}, {}, ['Migration']).Migration M2 = __import__("fakeapp.migrations.0002_eggs", {}, {}, ['Migration']).Migration + M3 = __import__("fakeapp.migrations.0003_alter_spam", {}, {}, ['Migration']).Migration self.assertEqual( - [M1, M2], + [M1, M2, M3], list(migration.get_migration_classes(app)), ) @@ -147,7 +148,7 @@ class TestMigrationLogic(unittest.TestCase): self.assertEqual(M1, migration.get_migration(app, "0001_spam")) self.assertEqual(M2, migration.get_migration(app, "0002_eggs")) - self.assertRaises(ValueError, migration.get_migration, app, "0001_jam") + self.assertRaises((ImportError, ValueError), migration.get_migration, app, "0001_jam") def test_all_migrations(self): @@ -158,6 +159,7 @@ class TestMigrationLogic(unittest.TestCase): {app: { "0001_spam": migration.get_migration(app, "0001_spam"), "0002_eggs": migration.get_migration(app, "0002_eggs"), + "0003_alter_spam": migration.get_migration(app, "0003_alter_spam"), }}, migration.all_migrations(), ) @@ -186,6 +188,7 @@ class TestMigrationLogic(unittest.TestCase): ( (u"fakeapp", u"0001_spam"), (u"fakeapp", u"0002_eggs"), + (u"fakeapp", u"0003_alter_spam"), ), migration.MigrationHistory.objects.values_list("app_name", "migration"), ) @@ -241,13 +244,49 @@ class TestMigrationLogic(unittest.TestCase): ( (u"fakeapp", u"0001_spam"), (u"fakeapp", u"0002_eggs"), + (u"fakeapp", u"0003_alter_spam"), ), migration.MigrationHistory.objects.values_list("app_name", "migration"), ) # Now roll them backwards + migration.migrate_app(app, target_name="0002", resolve_mode=None, fake=False, silent=True) migration.migrate_app(app, target_name="0001", resolve_mode=None, fake=True, silent=True) migration.migrate_app(app, target_name="zero", resolve_mode=None, fake=False, silent=True) # Finish with none + self.assertEqual(list(migration.MigrationHistory.objects.all()), []) + + def test_alter_column_null(self): + def null_ok(): + from django.db import connection, transaction + # the DBAPI introspection module fails on postgres NULLs. + cursor = connection.cursor() + try: + cursor.execute("INSERT INTO southtest_spam (id, weight, expires, name) VALUES (100, 10.1, now(), NULL);") + except: + transaction.rollback() + return False + else: + cursor.execute("DELETE FROM southtest_spam") + transaction.commit() + return True + + app = migration.get_app("fakeapp") + self.assertEqual(list(migration.MigrationHistory.objects.all()), []) + + # by default name is NOT NULL + migration.migrate_app(app, target_name="0002", resolve_mode=None, fake=False, silent=True) + self.failIf(null_ok()) + + # after 0003, it should be NULL + migration.migrate_app(app, target_name="0003", resolve_mode=None, fake=False, silent=True) + self.assert_(null_ok()) + + # make sure it is NOT NULL again + migration.migrate_app(app, target_name="0002", resolve_mode=None, fake=False, silent=True) + self.failIf(null_ok(), 'name not null after migration') + + # finish with no migrations, otherwise other tests fail... + migration.migrate_app(app, target_name="zero", resolve_mode=None, fake=False, silent=True) self.assertEqual(list(migration.MigrationHistory.objects.all()), []) \ No newline at end of file diff --git a/wolnelektury/templates/catalogue/book_detail.html b/wolnelektury/templates/catalogue/book_detail.html index f4805b9a6..d94ebe9be 100644 --- a/wolnelektury/templates/catalogue/book_detail.html +++ b/wolnelektury/templates/catalogue/book_detail.html @@ -94,9 +94,14 @@ {{ tag }} {% endfor %} + +

W innych miejscach

+
diff --git a/wolnelektury/templates/catalogue/tagged_object_list.html b/wolnelektury/templates/catalogue/tagged_object_list.html index 5bcab78a8..f2e3c2430 100644 --- a/wolnelektury/templates/catalogue/tagged_object_list.html +++ b/wolnelektury/templates/catalogue/tagged_object_list.html @@ -30,6 +30,9 @@ Pobierz wszystkie książki z tej półki {% endif %} + {% if last_tag.gazeta_link %} +

Przeczytaj omówienia lektur w Lektury.Gazeta.pl

+ {% endif %}
    {% for book in object_list %}
  1. -- 2.20.1 From d0c863efe1e0e2b0b2d41c6482c42733a1b57df4 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Marek=20St=C4=99pniowski?= Date: Tue, 21 Apr 2009 00:20:24 +0200 Subject: [PATCH 04/16] =?utf8?q?Zmiana=20adresu=20URL=20z=20aktualno=C5=9B?= =?utf8?q?ciami=20i=20konwertowanie=20adresu=20z=20unicode=20do=20str=20w?= =?utf8?q?=20tagu=20latest=5Fblog=5Fposts.?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- apps/catalogue/templatetags/catalogue_tags.py | 10 +++++----- wolnelektury/templates/catalogue/main_page.html | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/catalogue/templatetags/catalogue_tags.py b/apps/catalogue/templatetags/catalogue_tags.py index 3c34e8cdd..d3f608ac9 100644 --- a/apps/catalogue/templatetags/catalogue_tags.py +++ b/apps/catalogue/templatetags/catalogue_tags.py @@ -1,4 +1,7 @@ # -*- coding: utf-8 -*- +import feedparser +import datetime + from django import template from django.template import Node, Variable from django.utils.encoding import smart_str @@ -190,12 +193,9 @@ class CatalogueURLNode(Node): @register.inclusion_tag('catalogue/latest_blog_posts.html') -def latest_blog_posts(feed_url, posts_to_show=5): - import feedparser - import datetime - +def latest_blog_posts(feed_url, posts_to_show=5): try: - feed = feedparser.parse(feed_url) + feed = feedparser.parse(str(feed_url)) posts = [] for i in range(posts_to_show): pub_date = feed['entries'][i].updated_parsed diff --git a/wolnelektury/templates/catalogue/main_page.html b/wolnelektury/templates/catalogue/main_page.html index 43a3636f7..af48f8402 100644 --- a/wolnelektury/templates/catalogue/main_page.html +++ b/wolnelektury/templates/catalogue/main_page.html @@ -253,7 +253,7 @@

    Aktualności

    {% cache 1800 latest-blog-posts %} - {% latest_blog_posts "http://www.nowoczesnapolska.org.pl/feed/?s=Wolne%20Lektury" %} + {% latest_blog_posts "http://www.nowoczesnapolska.org.pl/tematy/wolne-lektury/feed/" %} {% endcache %}

    Zobacz nasz blog ⇒

    -- 2.20.1 From b3a2a942879cf81025be9cbfcb924b3ffca038c0 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Marek=20St=C4=99pniowski?= Date: Thu, 23 Apr 2009 21:54:36 +0200 Subject: [PATCH 05/16] =?utf8?q?Zmiana=20tytu=C5=82=C3=B3w=20link=C3=B3w?= =?utf8?q?=20do=20Lektury.Gazeta.pl.?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- wolnelektury/templates/catalogue/book_detail.html | 2 +- wolnelektury/templates/catalogue/tagged_object_list.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/wolnelektury/templates/catalogue/book_detail.html b/wolnelektury/templates/catalogue/book_detail.html index d94ebe9be..a31d8c43f 100644 --- a/wolnelektury/templates/catalogue/book_detail.html +++ b/wolnelektury/templates/catalogue/book_detail.html @@ -100,7 +100,7 @@
  2. Lektura na wiki projektu
  3. Lektura w CBN Polona
  4. {% if book.gazeta_link %} -
  5. Omówienie lektury w Lektury.Gazeta.pl
  6. +
  7. Lektura w Lektury.Gazeta.pl
  8. {% endif %}
diff --git a/wolnelektury/templates/catalogue/tagged_object_list.html b/wolnelektury/templates/catalogue/tagged_object_list.html index f2e3c2430..d1d0b56b6 100644 --- a/wolnelektury/templates/catalogue/tagged_object_list.html +++ b/wolnelektury/templates/catalogue/tagged_object_list.html @@ -31,7 +31,7 @@ {% endif %} {% if last_tag.gazeta_link %} -

Przeczytaj omówienia lektur w Lektury.Gazeta.pl

+

{{ last_tag }} w Lektury.Gazeta.pl

{% endif %}
    {% for book in object_list %} -- 2.20.1 From 1e5176481313c3129d2f353993dbb721b21ef232 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Marek=20St=C4=99pniowski?= Date: Thu, 23 Apr 2009 22:21:51 +0200 Subject: [PATCH 06/16] Added scripts and data for importing links from gazeta and wikipedia. --- scripts/gazeta-links | 60 ++++++++++++++++++++++++++++++++ scripts/import_links.py | 36 +++++++++++++++++++ scripts/wiki-links | 76 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 172 insertions(+) create mode 100644 scripts/gazeta-links create mode 100644 scripts/import_links.py create mode 100644 scripts/wiki-links diff --git a/scripts/gazeta-links b/scripts/gazeta-links new file mode 100644 index 000000000..922969bec --- /dev/null +++ b/scripts/gazeta-links @@ -0,0 +1,60 @@ +aleksander-fredro http://aleksander_fredro.lektury.gazeta.pl/lektury/1,90376,5202533,Aleksander_Fredro_-_biografia_autora.html +johann-wolfgang-von-goethe http://goethe_johann_wolfgang.lektury.gazeta.pl/lektury/1,89634,5132561,Johann_Wolfgang_Goethe_-_biografia.html +jan-kochanowski http://jan_kochanowski.lektury.gazeta.pl/lektury/1,90379,5202621,Jan_Kochanowski_-_biografia.html +maria-konopnicka http://konopnicka_maria.lektury.gazeta.pl/lektury/1,89639,5132605,Maria_Konopnicka_-_biografia.html +ignacy-krasicki http://krasicki_ignacy.lektury.gazeta.pl/lektury/1,89642,5132887,Ignacy_Krasicki_-_biografia.html +zygmunt-krasinski http://zygmunt_krasinski.lektury.gazeta.pl/lektury/1,90374,5202485,Zygmunt_Krasinski_-_biografia.html +boleslaw-lesmian http://lesmian_boleslaw.lektury.gazeta.pl/lektury/1,89643,5132898,Boleslaw_Lesmian_-_biografia_.html +adam-mickiewicz http://mickiewicz_adam.lektury.gazeta.pl/lektury/1,89644,5053153,Adam_Mickiewicz_-_biografia_.html +jan-poguelin-moliere http://molier.lektury.gazeta.pl/lektury/1,93571,5609143,Molier___biografia.html +jan-andrzej-morsztyn http://jan_andrzej_morsztyn.lektury.gazeta.pl/lektury/1,93983,5660227,Jan_Andrzej_Morsztyn___biografia.html +boleslaw-prus http://prus_boleslaw.lektury.gazeta.pl/lektury/1,89649,5133153,Boleslaw_Prus_-_biografia_.html +wladyslaw-stanislaw-reymont http://reymont_wladyslaw_stanislaw.lektury.gazeta.pl/lektury/1,89650,5133327,Wladyslaw_Reymont_-_biografia_.html +henryk-sienkiewicz http://sienkiewicz_henryk.lektury.gazeta.pl/lektury/1,89652,5133345,Henryk_Sienkiewicz_-_biografia_.html +juliusz-slowacki http://slowacki_juliusz.lektury.gazeta.pl/lektury/1,89653,5133353,Juliusz_Slowacki_-_biografia.html +sofokles http://sofokles.lektury.gazeta.pl/lektury/1,89654,5133364,Sofokles_-_biografia.html +stanislaw-wyspianski http://stanislaw_wyspianski.lektury.gazeta.pl/lektury/1,93611,5613067,Stanislaw_Wyspianski___biografia.html +gabriela-zapolska http://zapolska_gabriela.lektury.gazeta.pl/lektury/1,89656,5133395,Gabriela_Zapolska___biografia.html +stefan-zeromski http://zeromski_stefan.lektury.gazeta.pl/lektury/1,89657,5710923,Stefan_Zeromski___biografia.html +antygona http://antygona.lektury.gazeta.pl/lektury/1,89451,5099628,Antygona_.html +bajki-i-przypowiesci http://bajki.lektury.gazeta.pl/lektury/1,89472,5102409,Bajki.html +ballady-i-romanse http://ballady_i_romanse.lektury.gazeta.pl/lektury/1,89069,5041258,Ballady_i_romanse_.html +chlopi http://chlopi.lektury.gazeta.pl/lektury/1,89425,5095761,Chlopi_.html +cierpienia-mlodego-wertera http://cierpienia_mlodego_wertera.lektury.gazeta.pl/lektury/1,89169,5056058,Cierpienia_mlodego_Wertera_.html +cuda-milosci-przebog-jak-zyje-serca-juz-nie-majac http://cuda_milosci.lektury.gazeta.pl/lektury/1,93979,5660231,Cuda_milosci.html +do-trupa http://do_trupa.lektury.gazeta.pl/lektury/1,93982,5662283,Do_trupa.html +doktor-piotr http://doktor_piotr.lektury.gazeta.pl/lektury/1,88823,4998689,Doktor_Piotr.html +dusiolek http://dusiolek.lektury.gazeta.pl/lektury/1,88877,5010900,Dusiolek_.html +dziewczyna http://dziewczyna.lektury.gazeta.pl/lektury/1,88889,5011210,Dziewczyna_.html +lalka http://lalka.lektury.gazeta.pl/lektury/1,90217,5183302,Lalka.html +latarnik http://latarnik.lektury.gazeta.pl/lektury/1,90267,5187298,Latarnik.html +ludzie-bezdomni http://ludzie_bezdomni.lektury.gazeta.pl/lektury/1,88876,5010793,Ludzie_bezdomni_.html +moralnosc-pani-dulskiej http://moralnosc_pani_dulskiej.lektury.gazeta.pl/lektury/1,88833,5000418,Moralnosc_pani_Dulskiej.html +nasza-szkapa http://nasza_szkapa.lektury.gazeta.pl/lektury/1,88532,4965124,Nasza_szkapa.html +nie-boska-komedia http://nie_boska_komedia.lektury.gazeta.pl/lektury/1,90331,5192068,Nie-Boska_komedia.html +niestatek-oczy-sa-ogien-czolo-jest-zwierciadlem http://niestatek_i.lektury.gazeta.pl/lektury/1,93981,5662285,Niestatek_I.html +niestatek-predzej-kto-wiatr-w-wor-zamknie-predzej-i-promieni http://niestatek_ii.lektury.gazeta.pl/lektury/1,93980,5662286,Niestatek_II.html +oda-do-mlodosci http://oda_do_mlodosci.lektury.gazeta.pl/lektury/1,89064,5039102,Oda_do_mlodosci_.html +odprawa-poslow-greckich http://odprawa_poslow_greckich.lektury.gazeta.pl/lektury/1,93754,5632238,Odprawa_poslow_greckich.html +piesni-ksiegi-wtore-piesn-v-wieczna-sromota-i-nienagrodzona http://piesn_o_spustoszeniu_podola.lektury.gazeta.pl/lektury/1,90236,5184717,Piesn_o_spustoszeniu_Podola.html +piesn-swietojanska-o-sobotce http://piesn_swietojanska_o_sobotce.lektury.gazeta.pl/lektury/1,90235,5184500,Piesn_swietojanska_o_Sobotce.html +grob-agamemnona http://podroz_do_ziemi_swietej.lektury.gazeta.pl/lektury/1,89071,5041538,Podroz_do_Ziemi_Swietej_z_Neapolu.html +przedwiosnie http://przedwiosnie.lektury.gazeta.pl/lektury/1,89150,5054152,Przedwiosnie_.html +satyry http://satyry.lektury.gazeta.pl/lektury/1,89474,5102514,Satyry.html +sonety-krymskie http://sonety_krymskie.lektury.gazeta.pl/lektury/1,89091,5043442,Sonety_krymskie_.html +swietoszek http://swietoszek.lektury.gazeta.pl/lektury/1,93570,5609125,Swietoszek.html +testament-moj http://testament_moj.lektury.gazeta.pl/lektury/1,89107,5045974,Testament_moj_.html +treny http://treny.lektury.gazeta.pl/lektury/1,90265,5186977,Treny.html +urszula-kochanowska http://urszula_kochanowska.lektury.gazeta.pl/lektury/1,88921,5014764,Urszula_Kochanowska_.html +w-malinowym-chrusniaku http://w_malinowym_chrusniaku.lektury.gazeta.pl/lektury/1,88922,5014945,W_malinowym_chrusniaku_.html +wesele http://wesele.lektury.gazeta.pl/lektury/1,93757,5636695,Wesele.html +zemsta http://zemsta.lektury.gazeta.pl/lektury/1,90268,5187676,Zemsta.html +starozytnosc http://antyk.lektury.gazeta.pl/lektury/1,94412,5402224,Antyk.html +sredniowiecze http://sredniowiecze.lektury.gazeta.pl/lektury/1,94433,5402392,Sredniowiecze.html +renesans http://renesans.lektury.gazeta.pl/lektury/1,91344,5409175,Renesans.html +barok http://barok.lektury.gazeta.pl/lektury/1,91347,5707242,Barok.html +oswiecenie http://oswiecenie.lektury.gazeta.pl/lektury/1,91352,5409223,Oswiecenie.html +romantyzm http://romantyzm.lektury.gazeta.pl/lektury/1,91353,5410249,Romantyzm.html +pozytywizm http://pozytywizm.lektury.gazeta.pl/lektury/1,91355,5409315,Pozytywizm.html +1,91356,5409328,Mloda_Polska.html http://www.wolnelektury.pl/katalog/modernizm/ +dwudziestolecie-miedzywojenne http://dwudziestolecie_miedzywojenne.lektury.gazeta.pl/lektury/1,91357,5409357,Dwudziestolecie_miedzywojenne.html diff --git a/scripts/import_links.py b/scripts/import_links.py new file mode 100644 index 000000000..ea4380fb8 --- /dev/null +++ b/scripts/import_links.py @@ -0,0 +1,36 @@ +import sys +sys.path.insert(0, '../apps') +sys.path.insert(0, '../lib') +sys.path.insert(0, '../wolnelektury') +sys.path.insert(0, '..') + +from django.core.management import setup_environ +from wolnelektury import settings +import sys + +setup_environ(settings) + +from catalogue.models import Book, Tag + + +def import_tags(file_name, attribute): + for line in file(file_name): + slug, link = line.split() + link = link.strip('\n') + try: + book = Book.objects.get(slug=slug) + setattr(book, attribute, link) + book.save() + print 'Link %s for book %s added!' % (link, book) + except Book.DoesNotExist: + try: + tag = Tag.objects.get(slug=slug) + setattr(tag, attribute, link) + tag.save() + print 'Link %s for tag %s added!' % (link, tag) + except Tag.DoesNotExist: + print 'Invalid slug %s!' % slug + + +import_tags('gazeta-links', 'gazeta_link') + diff --git a/scripts/wiki-links b/scripts/wiki-links new file mode 100644 index 000000000..c9cd04582 --- /dev/null +++ b/scripts/wiki-links @@ -0,0 +1,76 @@ +hans-christian-andersen http://pl.wikipedia.org/wiki/Hans_Christian_Andersen +adam-asnyk http://pl.wikipedia.org/wiki/Adam_Asnyk +honore-de-balzac http://pl.wikipedia.org/wiki/Honor%C3%A9_de_Balzac +george-gordon-byron http://pl.wikipedia.org/wiki/George_Gordon_Byron +anton-czechow http://pl.wikipedia.org/wiki/Anton_Czechow +aleksander-fredro http://pl.wikipedia.org/wiki/Aleksander_Fredro +johann-wolfgang-von-goethe http://pl.wikipedia.org/wiki/Goethe +bruno-jasienski http://pl.wikipedia.org/wiki/Bruno_Jasie%C5%84ski +franciszek-karpinski http://pl.wikipedia.org/wiki/Franciszek_Karpi%C5%84ski +jan-kasprowicz http://pl.wikipedia.org/wiki/Jan_Kasprowicz +jan-kochanowski http://pl.wikipedia.org/wiki/Jan_Kochanowski +maria-konopnicka http://pl.wikipedia.org/wiki/Maria_Konopnicka +ignacy-krasicki http://pl.wikipedia.org/wiki/Ignacy_Krasicki +zygmunt-krasinski http://pl.wikipedia.org/wiki/Zygmunt_Krasi%C5%84ski +boleslaw-lesmian http://pl.wikipedia.org/wiki/Boles%C5%82aw_Le%C5%9Bmian +adam-mickiewicz http://pl.wikipedia.org/wiki/Adam_Mickiewicz +moliere http://pl.wikipedia.org/wiki/Moliere +jan-andrzej-morsztyn http://pl.wikipedia.org/wiki/Jan_Andrzej_Morsztyn +artur-oppman http://pl.wikipedia.org/wiki/Artur_Oppman +boleslaw-prus http://pl.wikipedia.org/wiki/Boles%C5%82aw_Prus +wladyslaw-stanislaw-reymont http://pl.wikipedia.org/wiki/Reymont +mikolaj-sep-szarzynski http://pl.wikipedia.org/wiki/Miko%C5%82aj_S%C4%99p_Szarzy%C5%84ski +william-shakespeare http://pl.wikipedia.org/wiki/Szekspir +henryk-sienkiewicz http://pl.wikipedia.org/wiki/Henryk_Sienkiewicz +juliusz-slowacki http://pl.wikipedia.org/wiki/Juliusz_S%C5%82owacki +sofokles http://pl.wikipedia.org/wiki/Sofokles +stanislaw-wyspianski http://pl.wikipedia.org/wiki/Stanis%C5%82aw_Wyspia%C5%84ski +gabriela-zapolska http://pl.wikipedia.org/wiki/Gabriela_Zapolska +stefan-zeromski http://pl.wikipedia.org/wiki/Stefan_%C5%BBeromski +brzydkie-kaczatko http://pl.wikipedia.org/wiki/Brzydkie_kacz%C4%85tko +krolowa-sniegu http://pl.wikipedia.org/wiki/Kr%C3%B3lowa_%C5%9Bniegu +bogurodzica http://pl.wikipedia.org/wiki/Bogurodzica_(pie%C5%9B%C5%84) +ojciec-goriot http://pl.wikipedia.org/wiki/Ojciec_Goriot +giaur http://pl.wikipedia.org/wiki/Giaur_(George_Byron) +zemsta http://pl.wikipedia.org/wiki/Zemsta_(komedia) +cierpienia-mlodego-wertera http://pl.wikipedia.org/wiki/Cierpienia_m%C5%82odego_Wertera +odprawa-poslow-greckich http://pl.wikipedia.org/wiki/Odprawa_pos%C5%82%C3%B3w_greckich +dym http://pl.wikipedia.org/wiki/Dym_(nowela) +nasza-szkapa http://pl.wikipedia.org/wiki/Nasza_szkapa +satyry-czesc-pierwsza-zona-modna http://pl.wikipedia.org/wiki/%C5%BBona_modna +nie-boska-komedia http://pl.wikipedia.org/wiki/Nie-Boska_komedia +dziewczyna http://pl.wikipedia.org/wiki/Dziewczyna_(Boles%C5%82aw_Le%C5%9Bmian) +dziady http://pl.wikipedia.org/wiki/Dziady_(dramat) +oda-do-mlodosci http://pl.wikipedia.org/wiki/Oda_do_m%C5%82odo%C5%9Bci +ballady-i-romanse-romantycznosc http://pl.wikipedia.org/wiki/Romantyczno%C5%9B%C4%87_(Mickiewicz) +sonety-krymskie http://pl.wikipedia.org/wiki/Sonety_krymskie +reduta-ordona http://pl.wikipedia.org/wiki/Reduta_Ordona +swietoszek http://pl.wikipedia.org/wiki/%C5%9Awi%C4%99toszek +lalka http://pl.wikipedia.org/wiki/Lalka_(powie%C5%9B%C4%87) +chlopi http://pl.wikipedia.org/wiki/Ch%C5%82opi_(powie%C5%9B%C4%87) +hamlet http://pl.wikipedia.org/wiki/Hamlet +romeo-i-julia http://pl.wikipedia.org/wiki/Romeo_i_Julia +janko-muzykant http://pl.wikipedia.org/wiki/Janko_Muzykant +latarnik http://pl.wikipedia.org/wiki/Latarnik_(nowela) +w-pustyni-i-w-puszczy http://pl.wikipedia.org/wiki/W_pustyni_i_w_puszczy +balladyna http://pl.wikipedia.org/wiki/Balladyna +grob-agamemnona http://pl.wikipedia.org/wiki/Gr%C3%B3b_Agamemnona +hymn-o-zachodzie-slonca-na-morzu http://pl.wikipedia.org/wiki/Hymn_o_zachodzie_s%C5%82o%C5%84ca_na_morzu +odpowiedz-na-psalmy-przyszlosci http://pl.wikipedia.org/wiki/Odpowied%C5%BA_na_Psalmy_przysz%C5%82o%C5%9Bci +testament-moj http://pl.wikipedia.org/wiki/Testament_m%C3%B3j +antygona http://pl.wikipedia.org/wiki/Antygona_(dramat) +krol-edyp http://pl.wikipedia.org/wiki/Kr%C3%B3l_Edyp +wesele http://pl.wikipedia.org/wiki/Wesele_(dramat) +moralnosc-pani-dulskiej http://pl.wikipedia.org/wiki/Moralno%C5%9B%C4%87_Pani_Dulskiej +ludzie-bezdomni http://pl.wikipedia.org/wiki/Ludzie_bezdomni +przedwiosnie http://pl.wikipedia.org/wiki/Przedwio%C5%9Bnie_(powie%C5%9B%C4%87) +silaczka http://pl.wikipedia.org/wiki/Si%C5%82aczka +treny http://pl.wikipedia.org/wiki/Treny +starozytnosc http://pl.wikipedia.org/wiki/Staro%C5%BCytno%C5%9B%C4%87 +sredniowiecze http://pl.wikipedia.org/wiki/%C5%9Aredniowiecze +renesans http://pl.wikipedia.org/wiki/Renesans +barok http://pl.wikipedia.org/wiki/Barok +oswiecenie http://pl.wikipedia.org/wiki/O%C5%9Bwiecenie_(okres) +romantyzm http://pl.wikipedia.org/wiki/Romantyzm +pozytywizm http://pl.wikipedia.org/wiki/Pozytywizm +dwudziestolecie-miedzywojenne http://pl.wikipedia.org/wiki/Dwudziestolecie_mi%C4%99dzywojenne_w_Polsce -- 2.20.1 From 8d9f0f1b6f7607545370131c60738e37c3fd4d6f Mon Sep 17 00:00:00 2001 From: =?utf8?q?Marek=20St=C4=99pniowski?= Date: Thu, 23 Apr 2009 22:23:35 +0200 Subject: [PATCH 07/16] Added wiki links to catalogue.Book and catalogue.Tag models. --- apps/catalogue/migrations/0005_add_wiki_links.py | 13 +++++++++++++ apps/catalogue/models.py | 3 +++ 2 files changed, 16 insertions(+) create mode 100644 apps/catalogue/migrations/0005_add_wiki_links.py diff --git a/apps/catalogue/migrations/0005_add_wiki_links.py b/apps/catalogue/migrations/0005_add_wiki_links.py new file mode 100644 index 000000000..3ac91df21 --- /dev/null +++ b/apps/catalogue/migrations/0005_add_wiki_links.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +from south.db import db +from django.db import models + + +class Migration: + def forwards(self): + db.add_column('catalogue_tag', 'wiki_link', models.CharField(blank=True, max_length=240)) + db.add_column('catalogue_book', 'wiki_link', models.CharField(blank=True, max_length=240)) + + def backwards(self): + db.delete_column('catalogue_tag', 'wiki_link') + db.delete_column('catalogue_book', 'wiki_link') diff --git a/apps/catalogue/models.py b/apps/catalogue/models.py index 5bfe6cbb8..4dde4e0d8 100644 --- a/apps/catalogue/models.py +++ b/apps/catalogue/models.py @@ -48,6 +48,7 @@ class Tag(TagBase): user = models.ForeignKey(User, blank=True, null=True) book_count = models.IntegerField(_('book count'), default=0, blank=False, null=False) gazeta_link = models.CharField(blank=True, max_length=240) + wiki_link = models.CharField(blank=True, max_length=240) def has_description(self): return len(self.description) > 0 @@ -90,6 +91,8 @@ class Book(models.Model): parent_number = models.IntegerField(_('parent number'), default=0) extra_info = JSONField(_('extra information')) gazeta_link = models.CharField(blank=True, max_length=240) + wiki_link = models.CharField(blank=True, max_length=240) + # Formats xml_file = models.FileField(_('XML file'), upload_to=book_upload_path('xml'), blank=True) -- 2.20.1 From d9e5d0484e0bae0268b4575e189700beda93f93d Mon Sep 17 00:00:00 2001 From: =?utf8?q?Marek=20St=C4=99pniowski?= Date: Thu, 23 Apr 2009 22:24:45 +0200 Subject: [PATCH 08/16] Added importing links from wikipedia to import_links.py script. --- scripts/import_links.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/import_links.py b/scripts/import_links.py index ea4380fb8..671212d83 100644 --- a/scripts/import_links.py +++ b/scripts/import_links.py @@ -13,7 +13,7 @@ setup_environ(settings) from catalogue.models import Book, Tag -def import_tags(file_name, attribute): +def import_links(file_name, attribute): for line in file(file_name): slug, link = line.split() link = link.strip('\n') @@ -32,5 +32,5 @@ def import_tags(file_name, attribute): print 'Invalid slug %s!' % slug -import_tags('gazeta-links', 'gazeta_link') - +import_links('gazeta-links', 'gazeta_link') +import_links('wiki-links', 'wiki_link') -- 2.20.1 From 6dc2540f663c92368314d610d161f3044fcc845c Mon Sep 17 00:00:00 2001 From: =?utf8?q?Marek=20St=C4=99pniowski?= Date: Thu, 23 Apr 2009 22:29:08 +0200 Subject: [PATCH 09/16] Displaying links to Wikipedia in templates. --- wolnelektury/templates/catalogue/book_detail.html | 5 ++++- wolnelektury/templates/catalogue/tagged_object_list.html | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/wolnelektury/templates/catalogue/book_detail.html b/wolnelektury/templates/catalogue/book_detail.html index a31d8c43f..b9e95d0f7 100644 --- a/wolnelektury/templates/catalogue/book_detail.html +++ b/wolnelektury/templates/catalogue/book_detail.html @@ -100,7 +100,10 @@
  1. Lektura na wiki projektu
  2. Lektura w CBN Polona
  3. {% if book.gazeta_link %} -
  4. Lektura w Lektury.Gazeta.pl
  5. +
  6. Opis lektury w Lektury.Gazeta.pl
  7. + {% endif %} + {% if book.wiki_link %} +
  8. Opis lektury w Wikipedii
  9. {% endif %} diff --git a/wolnelektury/templates/catalogue/tagged_object_list.html b/wolnelektury/templates/catalogue/tagged_object_list.html index d1d0b56b6..06e437d5c 100644 --- a/wolnelektury/templates/catalogue/tagged_object_list.html +++ b/wolnelektury/templates/catalogue/tagged_object_list.html @@ -33,6 +33,9 @@ {% if last_tag.gazeta_link %}

    {{ last_tag }} w Lektury.Gazeta.pl

    {% endif %} + {% if last_tag.wiki_link %} +

    {{ last_tag }} w Wikipedii

    + {% endif %}
      {% for book in object_list %}
    1. -- 2.20.1 From 831053edbeb1ae2dd5e2a5fa4d2dbdbe0640c96d Mon Sep 17 00:00:00 2001 From: =?utf8?q?Marek=20St=C4=99pniowski?= Date: Thu, 23 Apr 2009 23:17:44 +0200 Subject: [PATCH 10/16] Added new partners' logos to footer. --- wolnelektury/media/img/footer.png | Bin 46440 -> 68428 bytes wolnelektury/templates/base.html | 20 +------------------- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/wolnelektury/media/img/footer.png b/wolnelektury/media/img/footer.png index 884ce95852b0714ecfd540fe5a84d6bde83888d0..f360e0159f7846319f661bb0217197801eb413f3 100644 GIT binary patch literal 68428 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

      gE%`#M!f9H6 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 Tzx{)y3_#%N>gTe~DWM4fL@woa literal 46440 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

      Partnerzy serwisu - - Kurier Wileński - Ministerstwo Edukacji Narodowej - Biblioteka Analiz - Przekrój - TVP Kultura - Elle - Radio TOK.FM - Tygodnik Powszechny - Dziennik Polska-Europa-Swiat - Ministerstwo Kultury i Dziedzictwa Narodowego - Biblioteka Narodowa - PZL - EO Networks - Kancelaria Prawna Grynhoff Woźny Maliński Spółka komandytowa - Information is art - Fundacja Nowoczesna Polska - - +Gazeta EdukacjaKurier WileńskiRadio znad WiliiMinisterstwo Edukacji NarodowejBiblioteka AnalizPrzekrójTVP KulturaElleRadio TOK.FMTygodnik PowszechnyDziennik Polska-Europa-SwiatMinisterstwo Kultury i Dziedzictwa NarodowegoBiblioteka NarodowaPZLEO NetworksKancelaria Prawna Grynhoff Woźny Maliński Spółka komandytowaInformation is artFundacja Nowoczesna Polska

      -- 2.20.1 From 1cd04c089c97bd20d66453089ed61b547d088cbe Mon Sep 17 00:00:00 2001 From: =?utf8?q?Marek=20St=C4=99pniowski?= Date: Thu, 28 May 2009 23:53:25 +0200 Subject: [PATCH 11/16] Zmiana alt i title logo Edukacja.Gazeta.pl w stopce serwisu. --- wolnelektury/templates/base.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wolnelektury/templates/base.html b/wolnelektury/templates/base.html index 8a14bb9b6..6e8342653 100644 --- a/wolnelektury/templates/base.html +++ b/wolnelektury/templates/base.html @@ -57,7 +57,7 @@

      Partnerzy serwisu -Gazeta EdukacjaKurier WileńskiRadio znad WiliiMinisterstwo Edukacji NarodowejBiblioteka AnalizPrzekrójTVP KulturaElleRadio TOK.FMTygodnik PowszechnyDziennik Polska-Europa-SwiatMinisterstwo Kultury i Dziedzictwa NarodowegoBiblioteka NarodowaPZLEO NetworksKancelaria Prawna Grynhoff Woźny Maliński Spółka komandytowaInformation is artFundacja Nowoczesna Polska +GazetaEdukacja.pl - matura, testy, egzaminy, studiaKurier WileńskiRadio znad WiliiMinisterstwo Edukacji NarodowejBiblioteka AnalizPrzekrójTVP KulturaElleRadio TOK.FMTygodnik PowszechnyDziennik Polska-Europa-SwiatMinisterstwo Kultury i Dziedzictwa NarodowegoBiblioteka NarodowaPZLEO NetworksKancelaria Prawna Grynhoff Woźny Maliński Spółka komandytowaInformation is artFundacja Nowoczesna Polska

      -- 2.20.1 From 31badf91680a7ddb83f1d4fcc952bf52ac25b6b6 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Marek=20St=C4=99pniowski?= Date: Thu, 28 May 2009 23:53:53 +0200 Subject: [PATCH 12/16] =?utf8?q?Zmiana=20nazw=20link=C3=B3w=20do=20Lektury?= =?utf8?q?.Gazeta.pl=20i=20Wikipedii=20na=20te=20wyznaczone=20przez=20Mari?= =?utf8?q?=C4=99.?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- .../templates/catalogue/tagged_object_list.html | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/wolnelektury/templates/catalogue/tagged_object_list.html b/wolnelektury/templates/catalogue/tagged_object_list.html index 06e437d5c..b5b8ca74d 100644 --- a/wolnelektury/templates/catalogue/tagged_object_list.html +++ b/wolnelektury/templates/catalogue/tagged_object_list.html @@ -31,10 +31,16 @@
      {% endif %} {% if last_tag.gazeta_link %} -

      {{ last_tag }} w Lektury.Gazeta.pl

      +

      + {% ifequal last_tag.category "author" %}Przeczytaj omówienia utworów autora w serwisie Lektury.Gazeta.pl{% endifequal %} + {% ifequal last_tag.category "epoch" %}Przeczytaj omówienia z epoki {{ last_tag }} w serwisie Lektury.Gazeta.pl{% endifequal %} +

      {% endif %} {% if last_tag.wiki_link %} -

      {{ last_tag }} w Wikipedii

      +

      + {% ifequal last_tag.category "author" %}Przeczytaj artykuł o autorze w Wikipedii{% endifequal %} + {% ifequal last_tag.category "epoch" %}Przeczytaj artykuł o epoce {{ last_tag }} w Wikipedii{% endifequal %} +

      {% endif %}
        {% for book in object_list %} -- 2.20.1 From f43c8bff6740bd9da629558539b0354c61a82062 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Marek=20St=C4=99pniowski?= Date: Sun, 21 Jun 2009 22:33:25 +0200 Subject: [PATCH 13/16] Dodanie szkieletu dokumentacji. --- doc/Makefile | 88 +++++++++++++++++++ doc/conf.py | 204 +++++++++++++++++++++++++++++++++++++++++++ doc/index.rst | 25 ++++++ doc/installation.rst | 43 +++++++++ doc/make.bat | 112 ++++++++++++++++++++++++ 5 files changed, 472 insertions(+) create mode 100644 doc/Makefile create mode 100644 doc/conf.py create mode 100644 doc/index.rst create mode 100644 doc/installation.rst create mode 100644 doc/make.bat diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 000000000..dfe1c401c --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,88 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d _build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf _build/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) _build/html + @echo + @echo "Build finished. The HTML pages are in _build/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) _build/dirhtml + @echo + @echo "Build finished. The HTML pages are in _build/dirhtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) _build/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) _build/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) _build/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in _build/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) _build/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in _build/qthelp, like this:" + @echo "# qcollectiongenerator _build/qthelp/WolneLektury.pl.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile _build/qthelp/WolneLektury.pl.qhc" + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) _build/latex + @echo + @echo "Build finished; the LaTeX files are in _build/latex." + @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ + "run these through (pdf)latex." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) _build/changes + @echo + @echo "The overview file is in _build/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) _build/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in _build/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) _build/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in _build/doctest/output.txt." diff --git a/doc/conf.py b/doc/conf.py new file mode 100644 index 000000000..60d7d55a1 --- /dev/null +++ b/doc/conf.py @@ -0,0 +1,204 @@ +# -*- coding: utf-8 -*- +# +# WolneLektury.pl documentation build configuration file, created by +# sphinx-quickstart on Tue Jun 16 18:49:15 2009. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os +from os.path import abspath, join, dirname + +sys.path.insert(0, abspath(join(dirname(__file__), '../wolnelektury'))) +sys.path.insert(0, abspath(join(dirname(__file__), '../apps'))) +sys.path.insert(0, abspath(join(dirname(__file__), '../lib'))) + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.append(os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.coverage', 'sphinx.ext.intersphinx'] + +intersphinx_mapping = { + 'http://docs.python.org/dev': None, + 'http://docs.djangoproject.com/en/dev': 'http://docs.djangoproject.com/en/dev/_objects', +} + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +source_encoding = 'utf-8' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'WolneLektury.pl' +copyright = u'2007, Fundacja Nowoczesna Polska' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '1.0' +# The full version, including alpha/beta/rc tags. +release = '1.0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +language = 'pl' + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of documents that shouldn't be included in the build. +#unused_docs = [] + +# List of directories, relative to source directory, that shouldn't be searched +# for source files. +exclude_trees = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. Major themes that come with +# Sphinx are currently 'default' and 'sphinxdoc'. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +html_title = u'%s %s - dokumentacja' % (project, version) + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_use_modindex = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = '' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'WolneLekturypldoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'WolneLekturypl.tex', u'WolneLektury.pl Documentation', + u'Marek Stepniowski', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_use_modindex = True diff --git a/doc/index.rst b/doc/index.rst new file mode 100644 index 000000000..f24d758b9 --- /dev/null +++ b/doc/index.rst @@ -0,0 +1,25 @@ +.. WolneLektury.pl documentation master file, created by + sphinx-quickstart on Tue Jun 16 18:49:15 2009. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Dokumentacja serwisu WolneLektury.pl +==================================== + +Spis treści +----------- +.. toctree:: + :maxdepth: 2 + + installation + +.. autoclass:: catalogue.models.Tag + :members: + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/doc/installation.rst b/doc/installation.rst new file mode 100644 index 000000000..6c801c4c4 --- /dev/null +++ b/doc/installation.rst @@ -0,0 +1,43 @@ +========== +Instalacja +========== + +Wymagania +--------- +Do działania serwisu wymagane są: + +* `Python 2.5 `_ +* `Django 1.0 `_ +* `lxml 2.2 `_ + +Jeżeli używasz Pythona 2.4 lub chcesz użyć bazy danych innej niż SQLite, wymagana jest jeszcze: + +* biblioteka do obsługi wybranej bazy danych (`biblioteki wspierane przez Django `_) + +Do pracy nad dokumentacją, którą teraz czytasz, potrzebne są: + +* `Sphinx 0.6.2 `_ i zależności + +Wyższe wersje wymienionych powyżej bibliotek i aplikacji powinny działać równie dobrze, aczkolwiek nie było to testowane. + +Uruchomienie +------------ +Po instalacji wszystkich zależności należy ściągnąć kod serwisu poleceniem:: + + git clone http://jakies.repozytorium.pewnie.github + +Następnie należy zainstalować bazę danych:: + + cd wolnelektury/wolnelektury + ./manage.py syncdb + +Oraz zaimportować lektury z katalogu books:: + + ./manage.py importbooks ../books + +Teraz wystarczy uruchomić serwer deweloperski poleceniem:: + + ./manage.py runserver + +W wyniku powinniśmy otrzymać całkiem funkcjonalny serwer. + diff --git a/doc/make.bat b/doc/make.bat new file mode 100644 index 000000000..e3c48caff --- /dev/null +++ b/doc/make.bat @@ -0,0 +1,112 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +set SPHINXBUILD=sphinx-build +set ALLSPHINXOPTS=-d _build/doctrees %SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. changes to make an overview over all changed/added/deprecated items + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (_build\*) do rmdir /q /s %%i + del /q /s _build\* + goto end +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% _build/html + echo. + echo.Build finished. The HTML pages are in _build/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% _build/dirhtml + echo. + echo.Build finished. The HTML pages are in _build/dirhtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% _build/pickle + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% _build/json + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% _build/htmlhelp + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in _build/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% _build/qthelp + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in _build/qthelp, like this: + echo.^> qcollectiongenerator _build\qthelp\WolneLektury.pl.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile _build\qthelp\WolneLektury.pl.ghc + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% _build/latex + echo. + echo.Build finished; the LaTeX files are in _build/latex. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% _build/changes + echo. + echo.The overview file is in _build/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% _build/linkcheck + echo. + echo.Link check complete; look for any errors in the above output ^ +or in _build/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% _build/doctest + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in _build/doctest/output.txt. + goto end +) + +:end -- 2.20.1 From b7d5131321533374cdcb85542ef4dcb53269863d Mon Sep 17 00:00:00 2001 From: =?utf8?q?Marek=20St=C4=99pniowski?= Date: Mon, 22 Jun 2009 21:05:27 +0200 Subject: [PATCH 14/16] =?utf8?q?Oznaczenie=20link=C3=B3w=20przekazywanych?= =?utf8?q?=20do=20book=5Fshort.html=20i=20fragment=5Fshort.html=20jako=20s?= =?utf8?q?afe=5Fstring.?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- apps/catalogue/models.py | 6 ++++-- wolnelektury/templates/catalogue/book_short.html | 4 ++-- wolnelektury/templates/catalogue/fragment_short.html | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/catalogue/models.py b/apps/catalogue/models.py index 4dde4e0d8..58025fabd 100644 --- a/apps/catalogue/models.py +++ b/apps/catalogue/models.py @@ -119,7 +119,7 @@ class Book(models.Model): return mark_safe(self._short_html) else: tags = self.tags.filter(~Q(category__in=('set', 'theme', 'book'))) - tags = [u'%s' % (tag.get_absolute_url(), tag.name) for tag in tags] + tags = [mark_safe(u'%s' % (tag.get_absolute_url(), tag.name)) for tag in tags] formats = [] if self.html_file: @@ -135,6 +135,8 @@ class Book(models.Model): if self.ogg_file: formats.append(u'OGG' % self.ogg_file.url) + formats = [mark_safe(format) for format in formats] + self._short_html = unicode(render_to_string('catalogue/book_short.html', {'book': self, 'tags': tags, 'formats': formats})) self.save() @@ -312,7 +314,7 @@ class Fragment(models.Model): if len(self._short_html): return mark_safe(self._short_html) else: - book_authors = [u'%s' % (tag.get_absolute_url(), tag.name) + book_authors = [mark_safe(u'%s' % (tag.get_absolute_url(), tag.name)) for tag in self.book.tags if tag.category == 'author'] self._short_html = unicode(render_to_string('catalogue/fragment_short.html', diff --git a/wolnelektury/templates/catalogue/book_short.html b/wolnelektury/templates/catalogue/book_short.html index 41d3212f3..3012069f9 100644 --- a/wolnelektury/templates/catalogue/book_short.html +++ b/wolnelektury/templates/catalogue/book_short.html @@ -10,8 +10,8 @@

        {{ book.title }}

        {% if formats %} -

        Na skróty: {{ formats|join:", "|safe }}

        +

        Na skróty: {{ formats|join:", " }}

        {% endif %} -

        Utwór w kategoriach: {{ tags|join:", "|safe }}

        +

        Utwór w kategoriach: {{ tags|join:", " }}

        diff --git a/wolnelektury/templates/catalogue/fragment_short.html b/wolnelektury/templates/catalogue/fragment_short.html index 1afdebb53..aaab2bfd3 100644 --- a/wolnelektury/templates/catalogue/fragment_short.html +++ b/wolnelektury/templates/catalogue/fragment_short.html @@ -15,7 +15,7 @@ {% endif %}
        -- 2.20.1 From cc27a215809ae78a17bfca92228beeaf07939eee Mon Sep 17 00:00:00 2001 From: =?utf8?q?Marek=20St=C4=99pniowski?= Date: Wed, 24 Jun 2009 19:25:09 +0200 Subject: [PATCH 15/16] =?utf8?q?Dodanie=20test=C3=B3w=20regresji=20do=20mo?= =?utf8?q?du=C5=82u=20dcparser=20w=20bibliotece=20librarian.?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- lib/librarian/tests/__init__.py | 84 +++++++++++++++++++ .../tests/andersen_brzydkie_kaczatko.xml | 24 ++++++ lib/librarian/tests/kochanowski_piesn7.xml | 27 ++++++ lib/librarian/tests/mickiewicz_rybka.xml | 28 +++++++ lib/librarian/tests/sofokles_antygona.xml | 25 ++++++ 5 files changed, 188 insertions(+) create mode 100644 lib/librarian/tests/__init__.py create mode 100644 lib/librarian/tests/andersen_brzydkie_kaczatko.xml create mode 100644 lib/librarian/tests/kochanowski_piesn7.xml create mode 100644 lib/librarian/tests/mickiewicz_rybka.xml create mode 100644 lib/librarian/tests/sofokles_antygona.xml diff --git a/lib/librarian/tests/__init__.py b/lib/librarian/tests/__init__.py new file mode 100644 index 000000000..ca4d95f88 --- /dev/null +++ b/lib/librarian/tests/__init__.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- +import unittest +from os.path import dirname, join, realpath + +from librarian import dcparser + + +def test_file_path(file_name): + return realpath(join(dirname(__file__), file_name)) + + +class TestDCParser(unittest.TestCase): + KNOWN_RESULTS = ( + ('andersen_brzydkie_kaczatko.xml', { + 'publisher': u'Fundacja Nowoczesna Polska', + 'about': u'http://wiki.wolnepodreczniki.pl/Lektury:Andersen/Brzydkie_kaczątko', + 'source_name': u'Andersen, Hans Christian (1805-1875), Baśnie, Gebethner i Wolff, wyd. 7, Kraków, 1925', + 'author': u'Andersen, Hans Christian', + 'url': u'http://wolnelektury.pl/katalog/lektura/brzydkie-kaczatko', + 'created_at': u'2007-08-14', + 'title': u'Brzydkie kaczątko', + 'kind': u'Epika', + 'source_url': u'http://www.polona.pl/dlibra/doccontent2?id=3563&dirids=4', + 'translator': u'Niewiadomska, Cecylia', + 'released_to_public_domain_at': u'1925-01-01', + 'epoch': u'Romantyzm', + 'genre': u'Baśń', + 'technical_editor': u'Gałecki, Dariusz' + }), + ('kochanowski_piesn7.xml', { + 'publisher': u'Fundacja Nowoczesna Polska', + 'about': u'http://wiki.wolnepodreczniki.pl/Lektury:Kochanowski/Pieśni/Pieśń_VII_(1)', + 'source_name': u'Kochanowski, Jan (1530-1584), Dzieła polskie, tom 1, oprac. Julian Krzyżanowski, wyd. 8, Państwowy Instytut Wydawniczy, Warszawa, 1976', + 'author': u'Kochanowski, Jan', + 'url': u'http://wolnelektury.pl/katalog/lektura/piesni-ksiegi-pierwsze-piesn-vii-trudna-rada-w-tej-mierze-pr', + 'created_at': u'2007-08-31', + 'title': u'Pieśń VII (Trudna rada w tej mierze: przyjdzie się rozjechać...)', + 'kind': u'Liryka', + 'source_url': u'http://www.polona.pl/Content/1499', + 'released_to_public_domain_at': u'1584-01-01', + 'epoch': u'Renesans', + 'genre': u'Pieśń', + 'technical_editor': u'Gałecki, Dariusz' + }), + ('mickiewicz_rybka.xml', { + 'publisher': u'Fundacja Nowoczesna Polska', + 'about': 'http://wiki.wolnepodreczniki.pl/Lektury:Mickiewicz/Ballady/Rybka', + 'source_name': u'Mickiewicz, Adam (1798-1855), Poezje, tom 1 (Wiersze młodzieńcze - Ballady i romanse - Wiersze do r. 1824), Krakowska Spółdzielnia Wydawnicza, wyd. 2 zwiększone, Kraków, 1922', + 'author': u'Mickiewicz, Adam', + 'url': u'http://wolnelektury.pl/katalog/lektura/ballady-i-romanse-rybka', + 'created_at': u'2007-09-06', + 'title': u'Rybka', + 'kind': u'Liryka', + 'source_url': u'http://www.polona.pl/Content/2222', + 'released_to_public_domain_at': u'1855-01-01', + 'epoch': u'Romantyzm', + 'genre': u'Ballada', + 'technical_editor': u'Sutkowska, Olga' + }), + ('sofokles_antygona.xml', { + 'publisher': u'Fundacja Nowoczesna Polska', + 'about': 'http://wiki.wolnepodreczniki.pl/Lektury:Sofokles/Antygona', + 'source_name': u'Sofokles (496-406 a.C.), Antygona, Zakład Narodowy im. Ossolińskich, wyd. 7, Lwów, 1939', + 'author': u'Sofokles', + 'url': u'http://wolnelektury.pl/katalog/lektura/antygona', + 'created_at': u'2007-08-30', + 'title': u'Antygona', + 'kind': u'Dramat', + 'source_url': u'http://www.polona.pl/Content/3768', + 'translator': u'Morawski, Kazimierz', + 'released_to_public_domain_at': u'1925-01-01', + 'epoch': u'Starożytność', + 'genre': u'Tragedia', + 'technical_editor': u'Gałecki, Dariusz' + }), + ) + + def test_parse(self): + for file_name, result in self.KNOWN_RESULTS: + self.assertEqual(dcparser.parse(test_file_path(file_name)).to_dict(), result) + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/lib/librarian/tests/andersen_brzydkie_kaczatko.xml b/lib/librarian/tests/andersen_brzydkie_kaczatko.xml new file mode 100644 index 000000000..d653a9b5f --- /dev/null +++ b/lib/librarian/tests/andersen_brzydkie_kaczatko.xml @@ -0,0 +1,24 @@ + + + Andersen, Hans Christian + Brzydkie kaczątko + Niewiadomska, Cecylia + Gałecki, Dariusz + Fundacja Nowoczesna Polska + Romantyzm + Epika + Baśń + Publikacja zrealizowana w ramach projektu Wolne Lektury (http://wolnelektury.pl). Reprodukcja cyfrowa wykonana przez Bibliotekę Narodową z egzemplarza pochodzącego ze zbiorów BN. + http://wolnelektury.pl/katalog/lektura/brzydkie-kaczatko + http://www.polona.pl/dlibra/doccontent2?id=3563&dirids=4 + Andersen, Hans Christian (1805-1875), Baśnie, Gebethner i Wolff, wyd. 7, Kraków, 1925 + Domena publiczna - tłumacz Cecylia Niewiadomska zm. 1925 + 1925 + xml + text + text + 2007-08-14 + SP1 + pol + + \ No newline at end of file diff --git a/lib/librarian/tests/kochanowski_piesn7.xml b/lib/librarian/tests/kochanowski_piesn7.xml new file mode 100644 index 000000000..96be1ae0e --- /dev/null +++ b/lib/librarian/tests/kochanowski_piesn7.xml @@ -0,0 +1,27 @@ + + + Kochanowski, Jan + Pieśń VII (Trudna rada w tej mierze: przyjdzie się rozjechać...) + http://www.wolnelektury.pl/lektura/piesni-ksiegi-pierwsze + Sekuła, Aleksandra + Krzyżanowski, Julian + Otwinowska, Barbara + Gałecki, Dariusz + Fundacja Nowoczesna Polska + Renesans + Liryka + Pieśń + Publikacja zrealizowana w ramach projektu Wolne Lektury (http://wolnelektury.pl). Reprodukcja cyfrowa wykonana przez Bibliotekę Narodową z egzemplarza pochodzącego ze zbiorów BN. + http://wolnelektury.pl/katalog/lektura/piesni-ksiegi-pierwsze-piesn-vii-trudna-rada-w-tej-mierze-pr + http://www.polona.pl/Content/1499 + Kochanowski, Jan (1530-1584), Dzieła polskie, tom 1, oprac. Julian Krzyżanowski, wyd. 8, Państwowy Instytut Wydawniczy, Warszawa, 1976 + Domena publiczna - Jan Kochanowski zm. 1584 + 1584 + xml + text + text + 2007-08-31 + L + pol + + \ No newline at end of file diff --git a/lib/librarian/tests/mickiewicz_rybka.xml b/lib/librarian/tests/mickiewicz_rybka.xml new file mode 100644 index 000000000..0796a5b0f --- /dev/null +++ b/lib/librarian/tests/mickiewicz_rybka.xml @@ -0,0 +1,28 @@ + + + Mickiewicz, Adam + Rybka + http://www.wolnelektury.pl/lektura/ballady-i-romanse + Sekuła, Aleksandra + Kallenbach, Józef + Sutkowska, Olga + Fundacja Nowoczesna Polska + Romantyzm + Liryka + Ballada + Publikacja zrealizowana w ramach projektu Wolne Lektury (http://wolnelektury.pl). Reprodukcja cyfrowa wykonana przez Bibliotekę Narodową z egzemplarza pochodzącego ze zbiorów BN. + http://wolnelektury.pl/katalog/lektura/ballady-i-romanse-rybka + http://www.polona.pl/Content/2222 + Mickiewicz, Adam (1798-1855), Poezje, tom 1 (Wiersze młodzieńcze - Ballady i romanse - Wiersze do r. 1824), Krakowska Spółdzielnia Wydawnicza, wyd. 2 zwiększone, Kraków, 1922 + Domena publiczna - Adam Mickiewicz zm. 1855 + 1855 + xml + text + text + 2007-09-06 + SP2 + G + L + pol + + \ No newline at end of file diff --git a/lib/librarian/tests/sofokles_antygona.xml b/lib/librarian/tests/sofokles_antygona.xml new file mode 100644 index 000000000..4acb2d4fc --- /dev/null +++ b/lib/librarian/tests/sofokles_antygona.xml @@ -0,0 +1,25 @@ + + + Sofokles + Antygona + Sekuła, Aleksandra + Morawski, Kazimierz + Gałecki, Dariusz + Fundacja Nowoczesna Polska + Starożytność + Dramat + Tragedia + Publikacja zrealizowana w ramach projektu Wolne Lektury (http://wolnelektury.pl). Reprodukcja cyfrowa wykonana przez Bibliotekę Narodową z egzemplarza pochodzącego ze zbiorów BN. + http://wolnelektury.pl/katalog/lektura/antygona + http://www.polona.pl/Content/3768 + Sofokles (496-406 a.C.), Antygona, Zakład Narodowy im. Ossolińskich, wyd. 7, Lwów, 1939 + Domena publiczna - tłumacz Kazimierz Morawski zm. 1925 + 1925 + xml + text + text + 2007-08-30 + G + pol + + \ No newline at end of file -- 2.20.1 From e6e2a0c15e0c3910e1a89cf3f96d4f768c6888d9 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Marek=20St=C4=99pniowski?= Date: Wed, 24 Jun 2009 19:44:50 +0200 Subject: [PATCH 16/16] =?utf8?q?Dodanie=20odczytywania=20URLa=20i=20opisu?= =?utf8?q?=20licencji=20z=20plik=C3=B3w=20DublinCore=20do=20biblioteki=20l?= =?utf8?q?ibrarian.?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- lib/librarian/dcparser.py | 2 ++ lib/librarian/tests/__init__.py | 28 +++++++++++++++++++---- lib/librarian/tests/biedrzycki_akslop.xml | 25 ++++++++++++++++++++ 3 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 lib/librarian/tests/biedrzycki_akslop.xml diff --git a/lib/librarian/dcparser.py b/lib/librarian/dcparser.py index a4106863d..557509c95 100644 --- a/lib/librarian/dcparser.py +++ b/lib/librarian/dcparser.py @@ -117,6 +117,8 @@ class BookInfo(object): DC('source.URL') : ('source_url', str_to_unicode), DC('identifier.url') : ('url', str_to_unicode), DC('relation.hasPart') : ('parts', str_to_unicode_list), + DC('rights.license') : ('license', str_to_unicode), + DC('rights') : ('license_description', str_to_unicode), } @classmethod diff --git a/lib/librarian/tests/__init__.py b/lib/librarian/tests/__init__.py index ca4d95f88..c9b7f4c06 100644 --- a/lib/librarian/tests/__init__.py +++ b/lib/librarian/tests/__init__.py @@ -25,7 +25,8 @@ class TestDCParser(unittest.TestCase): 'released_to_public_domain_at': u'1925-01-01', 'epoch': u'Romantyzm', 'genre': u'Baśń', - 'technical_editor': u'Gałecki, Dariusz' + 'technical_editor': u'Gałecki, Dariusz', + 'license_description': u'Domena publiczna - tłumacz Cecylia Niewiadomska zm. 1925', }), ('kochanowski_piesn7.xml', { 'publisher': u'Fundacja Nowoczesna Polska', @@ -40,7 +41,8 @@ class TestDCParser(unittest.TestCase): 'released_to_public_domain_at': u'1584-01-01', 'epoch': u'Renesans', 'genre': u'Pieśń', - 'technical_editor': u'Gałecki, Dariusz' + 'technical_editor': u'Gałecki, Dariusz', + 'license_description': u'Domena publiczna - Jan Kochanowski zm. 1584 ', }), ('mickiewicz_rybka.xml', { 'publisher': u'Fundacja Nowoczesna Polska', @@ -55,7 +57,8 @@ class TestDCParser(unittest.TestCase): 'released_to_public_domain_at': u'1855-01-01', 'epoch': u'Romantyzm', 'genre': u'Ballada', - 'technical_editor': u'Sutkowska, Olga' + 'technical_editor': u'Sutkowska, Olga', + 'license_description': u'Domena publiczna - Adam Mickiewicz zm. 1855', }), ('sofokles_antygona.xml', { 'publisher': u'Fundacja Nowoczesna Polska', @@ -71,7 +74,24 @@ class TestDCParser(unittest.TestCase): 'released_to_public_domain_at': u'1925-01-01', 'epoch': u'Starożytność', 'genre': u'Tragedia', - 'technical_editor': u'Gałecki, Dariusz' + 'technical_editor': u'Gałecki, Dariusz', + 'license_description': u'Domena publiczna - tłumacz Kazimierz Morawski zm. 1925', + }), + ('biedrzycki_akslop.xml', { + 'publisher': u'Fundacja Nowoczesna Polska', + 'about': 'http://wiki.wolnepodreczniki.pl/Lektury:Biedrzycki/Akslop', + 'source_name': u'Miłosz Biedrzycki, * ("Gwiazdka"), Fundacja "brulion", Kraków-Warszawa, 1993', + 'author': u'Biedrzycki, Miłosz', + 'url': u'http://wolnelektury.pl/katalog/lektura/akslop', + 'created_at': u'2009-06-04', + 'title': u'Akslop', + 'kind': u'Liryka', + 'source_url': u'http://free.art.pl/mlb/gwiazdka.html#t1', + 'epoch': u'Współczesność', + 'genre': u'Wiersz', + 'technical_editor': u'Sutkowska, Olga', + 'license': u'http://creativecommons.org/licenses/by-sa/3.0/', + 'license_description': u'Creative Commons Uznanie Autorstwa - Na Tych Samych Warunkach 3.0.PL' }), ) diff --git a/lib/librarian/tests/biedrzycki_akslop.xml b/lib/librarian/tests/biedrzycki_akslop.xml new file mode 100644 index 000000000..da0cd9fa6 --- /dev/null +++ b/lib/librarian/tests/biedrzycki_akslop.xml @@ -0,0 +1,25 @@ + + + Biedrzycki, Miłosz + Akslop + Sekuła, Aleksandra + Sutkowska, Olga + Fundacja Nowoczesna Polska + Współczesność + Liryka + Wiersz + Publikacja zrealizowana w ramach projektu Wolne Lektury (http://wolnelektury.pl). + http://wolnelektury.pl/katalog/lektura/akslop + http://free.art.pl/mlb/gwiazdka.html#t1 + Miłosz Biedrzycki, * ("Gwiazdka"), Fundacja "brulion", Kraków-Warszawa, 1993 + Creative Commons Uznanie Autorstwa - Na Tych Samych Warunkach 3.0.PL + http://creativecommons.org/licenses/by-sa/3.0/ + xml + text + text + 2009-06-04 + L + pol + + \ No newline at end of file -- 2.20.1