[open-ils-commits] r768 - in servres/branches/eg-schema-experiment: . conifer conifer/evergreen conifer/evergreen/backends conifer/evergreen/backends/postgresql_with_schemas conifer/evergreen/management conifer/evergreen/management/commands conifer/robin (gfawcett)

svn at svn.open-ils.org svn at svn.open-ils.org
Sun Feb 7 17:46:22 EST 2010


Author: gfawcett
Date: 2010-02-07 17:46:16 -0500 (Sun, 07 Feb 2010)
New Revision: 768

Added:
   servres/branches/eg-schema-experiment/conifer/evergreen/
   servres/branches/eg-schema-experiment/conifer/evergreen/__init__.py
   servres/branches/eg-schema-experiment/conifer/evergreen/backends/
   servres/branches/eg-schema-experiment/conifer/evergreen/backends/__init__.py
   servres/branches/eg-schema-experiment/conifer/evergreen/backends/postgresql_with_schemas/
   servres/branches/eg-schema-experiment/conifer/evergreen/backends/postgresql_with_schemas/__init__.py
   servres/branches/eg-schema-experiment/conifer/evergreen/backends/postgresql_with_schemas/base.py
   servres/branches/eg-schema-experiment/conifer/evergreen/backends/postgresql_with_schemas/introspection.py
   servres/branches/eg-schema-experiment/conifer/evergreen/backends/postgresql_with_schemas/operations.py
   servres/branches/eg-schema-experiment/conifer/evergreen/management/
   servres/branches/eg-schema-experiment/conifer/evergreen/management/__init__.py
   servres/branches/eg-schema-experiment/conifer/evergreen/management/commands/
   servres/branches/eg-schema-experiment/conifer/evergreen/management/commands/__init__.py
   servres/branches/eg-schema-experiment/conifer/evergreen/management/commands/inspectdb_evergreen.py
   servres/branches/eg-schema-experiment/conifer/robin/
   servres/branches/eg-schema-experiment/conifer/robin/__init__.py
   servres/branches/eg-schema-experiment/conifer/robin/models.py
   servres/branches/eg-schema-experiment/conifer/robin/tests.py
   servres/branches/eg-schema-experiment/conifer/robin/views.py
Modified:
   servres/branches/eg-schema-experiment/.gitignore
   servres/branches/eg-schema-experiment/conifer/settings.py
Log:
Added a custom Django backend that's Postgres-schema aware.

I've written a Django 'backend' for Postgres that can both:

* allow a Django model to be designed, whose tables exist in multiple
  schemas; and

* inspect an existing Postgres database with multiple schemas, and
  generate a decent Django model from it.

The backend is in 'evergreen/backends', and there is a supporting
Django 'management command' in 'evergreen/management'. The management
command is only needed if you want to inspect a database and generate
a Django model.

You can use the backend without the inpsector; but the inspector
depends upon the backend.

---------------
The Backend
---------------

The backend is an extension of the Postgres 'psycopg2' backend. You
must have psycopg2 installed if you want to use this backend.

To activate the backend, change your settings.py, and change
DATABASE_ENGINE to:

DATABASE_ENGINE = 'conifer.evergreen.backends.postgresql_with_schemas'

In your Django models file, you can specify the schema for each
model's table like this:

  class OrgUnit(models.Model):
      class Meta:
          db_table = u'actor.ou'

      id = models.IntegerField(primary_key=True)
      ...

If you're trying to remain backend-agnostic, and allow your model to
work with RDBMS backends other than Postgres, then you can do
something like this:

  POSTGRES_SCHEMAS = True  # set to false if not using postgres...

  class OrgUnit(models.Model):
      class Meta:
          db_table = u'actor.ou' if POSTGRES_SCHEMAS else u'actor_ou'

      id = models.IntegerField(primary_key=True)
      ...

So, if you ask to use Postgres schemas, you'll get a 'dot' in the
table name, otherwise you won't. Note that you could dynamically set
POSTGRES_SCHEMAS based on the value of DATABASE_ENGINE in settings.py.

---------------
The Inpsector
---------------

The inspector is the bit that can generate a Django model from an
existing database. Django comes with a builtin inspector: a management
command called 'inspectdb'. Unfortunately, 'inspectdb' is not
schema-aware, so I've added a custom management command called
'inspectdb_evergreen'. (That's a misnomer, it's not particularly
evergreen specific.)

To use the schema-aware inspector, you must do the following:

* Edit your settings.py to point to your database:

  DATABASE_NAME   = 'mydatabase'
  DATABASE_HOST   = 'dbserver'

* Specify the new custom backend:

  DATABASE_ENGINE = 'conifer.evergreen.backends.postgresql_with_schemas'

* Specify which schemas you want to inspect. If you want to inspect
  the 'public' schema, you must add it explicitly. There is no default:
  you must be explicit.

  DATABASE_PG_SCHEMAS = ['actor','asset','config','permission','reserves']

* Finally, add 'evergreen' to the installed apps, so that the custom
  management command will be available.

  INSTALLED_APPS = (
      'eg_schema_experiment.evergreen',
      'django.contrib.auth',
      ...)

* Save your settings.py changes, and at the command line, change to
the project directory (where settings.py is located) and run:

  ./manage.py inspectdb_evergreen

This will dump a sample model to standard output. Most likely you want
to first create a directory for your application, and then dump this
model into that directory, e.g:

  ./manage.py startapp myapp
  ./manage inspectdb_evergreen > myapp/models.py

And then add your new app to the installed apps:

  INSTALLED_APPS = (
      'conifer.evergreen',
      'django.contrib.auth',
      ...,
      'conifer.myapp')

...and you're ready to start writing your Django application.

Note that the model generated by 'inspectdb_evergreen' uses the
'POSTGRES_SCHEMAS' technique described above to keep the model
database-agnostic: that is, it's possible to configure the model to
work with databases that do not use schemas, such as MySQL or SQLite.

Modified: servres/branches/eg-schema-experiment/.gitignore
===================================================================
--- servres/branches/eg-schema-experiment/.gitignore	2010-02-04 19:35:55 UTC (rev 767)
+++ servres/branches/eg-schema-experiment/.gitignore	2010-02-07 22:46:16 UTC (rev 768)
@@ -8,3 +8,4 @@
 xsip
 TAGS
 private_local_settings.py
+*~
\ No newline at end of file

Added: servres/branches/eg-schema-experiment/conifer/evergreen/__init__.py
===================================================================

Added: servres/branches/eg-schema-experiment/conifer/evergreen/backends/__init__.py
===================================================================

Added: servres/branches/eg-schema-experiment/conifer/evergreen/backends/postgresql_with_schemas/__init__.py
===================================================================


Property changes on: servres/branches/eg-schema-experiment/conifer/evergreen/backends/postgresql_with_schemas/__init__.py
___________________________________________________________________
Name: svn:executable
   + *

Added: servres/branches/eg-schema-experiment/conifer/evergreen/backends/postgresql_with_schemas/base.py
===================================================================
--- servres/branches/eg-schema-experiment/conifer/evergreen/backends/postgresql_with_schemas/base.py	                        (rev 0)
+++ servres/branches/eg-schema-experiment/conifer/evergreen/backends/postgresql_with_schemas/base.py	2010-02-07 22:46:16 UTC (rev 768)
@@ -0,0 +1,54 @@
+"""
+Adapted psycopg2 backend for Django.
+"""
+
+from django.db.backends.postgresql_psycopg2.base import (DatabaseFeatures, 
+                                                         DatabaseError, IntegrityError, 
+                                                         DatabaseWrapper)
+from django.conf import settings
+from .operations import DatabaseOperations
+from .introspection import DatabaseIntrospection
+
+class DatabaseWrapper(DatabaseWrapper):
+    def __init__(self, *args, **kwargs):
+        super(DatabaseWrapper, self).__init__(*args, **kwargs)
+        self.ops = DatabaseOperations()
+        self.introspection = DatabaseIntrospection(self)
+
+    def _cursor(self):
+        cursor = super(DatabaseWrapper, self)._cursor()
+        #schemas = self.settings_dict.get('DATABASE_PG_SCHEMAS')
+        schemas = getattr(settings, 'DATABASE_PG_SCHEMAS', ['public'])
+        cursor.execute('set search_path to %s;' % ','.join(schemas))
+        return cursor
+
+        set_tz = False
+        settings_dict = self.settings_dict
+        if self.connection is None:
+            set_tz = True
+            if settings_dict['DATABASE_NAME'] == '':
+                from django.core.exceptions import ImproperlyConfigured
+                raise ImproperlyConfigured("You need to specify DATABASE_NAME in your Django settings file.")
+            conn_string = "dbname=%s" % settings_dict['DATABASE_NAME']
+            if settings_dict['DATABASE_USER']:
+                conn_string = "user=%s %s" % (settings_dict['DATABASE_USER'], conn_string)
+            if settings_dict['DATABASE_PASSWORD']:
+                conn_string += " password='%s'" % settings_dict['DATABASE_PASSWORD']
+            if settings_dict['DATABASE_HOST']:
+                conn_string += " host=%s" % settings_dict['DATABASE_HOST']
+            if settings_dict['DATABASE_PORT']:
+                conn_string += " port=%s" % settings_dict['DATABASE_PORT']
+            self.connection = Database.connect(conn_string, **settings_dict['DATABASE_OPTIONS'])
+            self.connection.set_isolation_level(1) # make transactions transparent to all cursors
+            connection_created.send(sender=self.__class__)
+        cursor = self.connection.cursor()
+        if set_tz:
+            cursor.execute("SET TIME ZONE %s", [settings_dict['TIME_ZONE']])
+            if not hasattr(self, '_version'):
+                self.__class__._version = get_version(cursor)
+            if self._version[0:2] < (8, 0):
+                # No savepoint support for earlier version of PostgreSQL.
+                self.features.uses_savepoints = False
+        cursor.execute("SET client_encoding to 'UNICODE'")
+        cursor = UnicodeCursorWrapper(cursor, 'utf-8')
+        return cursor


Property changes on: servres/branches/eg-schema-experiment/conifer/evergreen/backends/postgresql_with_schemas/base.py
___________________________________________________________________
Name: svn:executable
   + *

Added: servres/branches/eg-schema-experiment/conifer/evergreen/backends/postgresql_with_schemas/introspection.py
===================================================================
--- servres/branches/eg-schema-experiment/conifer/evergreen/backends/postgresql_with_schemas/introspection.py	                        (rev 0)
+++ servres/branches/eg-schema-experiment/conifer/evergreen/backends/postgresql_with_schemas/introspection.py	2010-02-07 22:46:16 UTC (rev 768)
@@ -0,0 +1,98 @@
+from django.db.backends.postgresql.introspection import DatabaseIntrospection as PostgresDatabaseIntrospection
+from django.conf import settings
+
+SCHEMAS = getattr(settings, 'DATABASE_PG_SCHEMAS', ['public'])
+
+class DatabaseIntrospection(PostgresDatabaseIntrospection):
+
+    def get_relations(self, cursor, table_name):
+        """
+        Returns a dictionary of {field_index: (field_index_other_table, other_table)}
+        representing all relationships to the given table. Indexes are 0-based.
+        """
+        cursor.execute("""
+            SELECT con.conkey, con.confkey, c2.relname
+            FROM pg_constraint con, pg_class c1, pg_class c2
+            WHERE c1.oid = con.conrelid
+                AND c2.oid = con.confrelid
+                AND c1.relname = %s
+                AND con.contype = 'f'""", [table_name])
+        relations = {}
+        for row in cursor.fetchall():
+            # row[0] and row[1] are single-item lists, so grab the single item.
+            relations[row[0][0] - 1] = (row[1][0] - 1, row[2])
+        return relations
+
+
+    def get_table_list(self, cursor):
+        "Returns a list of table names in the current database."
+        schemas = ','.join("'%s'" % s for s in SCHEMAS)
+        cursor.execute("""
+            SELECT nspname || '.' || c.relname
+            FROM pg_catalog.pg_class c
+            LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
+            WHERE c.relkind IN ('r', 'v', '')
+                AND n.nspname IN (%s)
+                AND pg_catalog.pg_table_is_visible(c.oid)"""  % schemas)
+        return [row[0] for row in cursor.fetchall()]
+
+    def get_table_description(self, cursor, table_name):
+        "Returns a description of the table, with the DB-API cursor.description interface."
+        cursor.execute("SELECT * FROM %s LIMIT 1" % self.connection.ops.quote_name(table_name))
+        return cursor.description
+
+    def get_indexes(self, cursor, table_name):
+        """
+        Returns a dictionary of fieldname -> infodict for the given table,
+        where each infodict is in the format:
+            {'primary_key': boolean representing whether it's the primary key,
+             'unique': boolean representing whether it's a unique index}
+        """
+        # This query retrieves each index on the given table, including the
+        # first associated field name
+        qualified_table_name = table_name if '.' in table_name else 'public.' + table_name
+        cursor.execute("""
+            SELECT attr.attname, idx.indkey, idx.indisunique, idx.indisprimary
+            FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace A ON A.oid = c.relnamespace, 
+            pg_catalog.pg_class c2,
+            pg_catalog.pg_index idx, pg_catalog.pg_attribute attr
+            WHERE c.oid = idx.indrelid
+            AND idx.indexrelid = c2.oid
+            AND attr.attrelid = c.oid
+            AND attr.attnum = idx.indkey[0]
+            and A.nspname || '.' || c.relname = %s""", [qualified_table_name])
+        indexes = {}
+        for row in cursor.fetchall():
+            # row[1] (idx.indkey) is stored in the DB as an array. It comes out as
+            # a string of space-separated integers. This designates the field
+            # indexes (1-based) of the fields that have indexes on the table.
+            # Here, we skip any indexes across multiple fields.
+            if ' ' in row[1]:
+                continue
+            indexes[row[0]] = {'primary_key': row[3], 'unique': row[2]}
+        return indexes
+
+
+    def get_relations(self, cursor, table_name):
+        """
+        Returns a dictionary of {field_index: (field_index_other_table, other_table)}
+        representing all relationships to the given table. Indexes are 0-based.
+        """
+        qualified_table_name = table_name if '.' in table_name else 'public.' + table_name
+        cursor.execute("""
+           SELECT con.conkey, con.confkey, B.nspname || '.' || c2.relname
+           FROM pg_constraint con, 
+           pg_class c1 LEFT JOIN pg_catalog.pg_namespace A ON A.oid = c1.relnamespace, 
+           pg_class c2 LEFT JOIN pg_catalog.pg_namespace B ON B.oid = c2.relnamespace
+           WHERE c1.oid = con.conrelid
+           AND c2.oid = con.confrelid
+           and A.nspname || '.' || c1.relname = %s
+           AND con.contype = 'f';
+           """, [qualified_table_name])
+        relations = {}
+        for row in cursor.fetchall():
+            try:
+                relations[row[0][0] - 1] = (row[1][0] - 1, row[2])
+            except ValueError:
+                continue
+        return relations


Property changes on: servres/branches/eg-schema-experiment/conifer/evergreen/backends/postgresql_with_schemas/introspection.py
___________________________________________________________________
Name: svn:executable
   + *

Added: servres/branches/eg-schema-experiment/conifer/evergreen/backends/postgresql_with_schemas/operations.py
===================================================================
--- servres/branches/eg-schema-experiment/conifer/evergreen/backends/postgresql_with_schemas/operations.py	                        (rev 0)
+++ servres/branches/eg-schema-experiment/conifer/evergreen/backends/postgresql_with_schemas/operations.py	2010-02-07 22:46:16 UTC (rev 768)
@@ -0,0 +1,19 @@
+from django.db.backends.postgresql.operations import DatabaseOperations as PostgresDatabaseOperations
+
+class DatabaseOperations(PostgresDatabaseOperations):
+
+    def last_insert_id(self, cursor, table_name, pk_name):
+        if '"' in table_name:
+            table_name = table_name.replace('"', '')
+            cursor.execute("SELECT CURRVAL('%s_%s_seq')" % (table_name, pk_name))
+            return cursor.fetchone()[0]
+        else:
+            return super(DatabaseOperations, self).last_insert_id(cursor, table_name, pk_name)
+
+    def quote_name(self, name):
+        if '.' in name:
+            return name
+        if name.startswith('"') and name.endswith('"'):
+            return name # Quoting once is enough.
+        return '"%s"' % name
+

Added: servres/branches/eg-schema-experiment/conifer/evergreen/management/__init__.py
===================================================================

Added: servres/branches/eg-schema-experiment/conifer/evergreen/management/commands/__init__.py
===================================================================

Added: servres/branches/eg-schema-experiment/conifer/evergreen/management/commands/inspectdb_evergreen.py
===================================================================
--- servres/branches/eg-schema-experiment/conifer/evergreen/management/commands/inspectdb_evergreen.py	                        (rev 0)
+++ servres/branches/eg-schema-experiment/conifer/evergreen/management/commands/inspectdb_evergreen.py	2010-02-07 22:46:16 UTC (rev 768)
@@ -0,0 +1,135 @@
+from django.core.management.base import NoArgsCommand, CommandError
+
+class Command(NoArgsCommand):
+    help = "Like inspectdb, but aware of Postgres schema names. Uses DATABASE_PG_SCHEMAS setting."
+
+    requires_model_validation = False
+
+    def handle_noargs(self, **options):
+        try:
+            for line in self.handle_inspection():
+                print line
+        except NotImplementedError:
+            raise CommandError("Database inspection isn't supported for the currently selected database backend.")
+
+    def handle_inspection(self):
+        from django.db import connection
+        import keyword
+
+        table2model = lambda table_name: table_name.title().replace('_', '').replace(' ', '').replace('-', '').replace('.', '')
+
+        cursor = connection.cursor()
+        yield "# This is an auto-generated Django model module."
+        yield "# You'll have to do the following manually to clean this up:"
+        yield "#     * Rearrange models' order"
+        yield "#     * Make sure each model has one field with primary_key=True"
+        yield "# Feel free to rename the models, but don't rename db_table values or field names."
+        yield "#"
+        yield "# Also note: You'll have to insert the output of 'django-admin.py sqlcustom [appname]'"
+        yield "# into your database."
+        yield ''
+        yield 'from django.db import models'
+        yield ''
+
+        yield 'POSTGRES_SCHEMAS = True\n'
+
+        for table_name in connection.introspection.get_table_list(cursor):
+            encountered = set()
+            yield 'class %s(models.Model):' % table2model(table_name)
+            try:
+                relations = connection.introspection.get_relations(cursor, table_name)
+            except NotImplementedError:
+                relations = {}
+            try:
+                indexes = connection.introspection.get_indexes(cursor, table_name)
+            except NotImplementedError:
+                indexes = {}
+            for i, row in enumerate(connection.introspection.get_table_description(cursor, table_name)):
+                column_name = row[0]
+                att_name = column_name.lower()
+                comment_notes = [] # Holds Field notes, to be displayed in a Python comment.
+                extra_params = {}  # Holds Field parameters such as 'db_column'.
+
+                # If the column name can't be used verbatim as a Python
+                # attribute, set the "db_column" for this Field.
+                if ' ' in att_name or '-' in att_name or keyword.iskeyword(att_name) or column_name != att_name:
+                    extra_params['db_column'] = column_name
+
+                # Modify the field name to make it Python-compatible.
+                if ' ' in att_name:
+                    att_name = att_name.replace(' ', '_')
+                    comment_notes.append('Field renamed to remove spaces.')
+                if '-' in att_name:
+                    att_name = att_name.replace('-', '_')
+                    comment_notes.append('Field renamed to remove dashes.')
+                if keyword.iskeyword(att_name):
+                    att_name += '_field'
+                    comment_notes.append('Field renamed because it was a Python reserved word.')
+                if column_name != att_name:
+                    comment_notes.append('Field name made lowercase.')
+
+                if i in relations:
+                    rel_to = relations[i][1] == table_name and "'self'" or "'%s'" % table2model(relations[i][1])
+                    field_type = 'ForeignKey(%s' % rel_to
+                    if att_name.endswith('_id'):
+                        att_name = att_name[:-3]
+                    else:
+                        extra_params['db_column'] = column_name
+                        if rel_to in encountered:
+                            extra_params['related_name'] = column_name
+                        encountered.add(rel_to)
+                            
+                else:
+                    try:
+                        field_type = connection.introspection.get_field_type(row[1], row)
+                    except KeyError:
+                        field_type = 'TextField'
+                        comment_notes.append('This field type is a guess.')
+
+                    # This is a hook for DATA_TYPES_REVERSE to return a tuple of
+                    # (field_type, extra_params_dict).
+                    if type(field_type) is tuple:
+                        field_type, new_params = field_type
+                        extra_params.update(new_params)
+
+                    # Add max_length for all CharFields.
+                    if field_type == 'CharField' and row[3]:
+                        extra_params['max_length'] = row[3]
+
+                    if field_type == 'DecimalField':
+                        extra_params['max_digits'] = row[4]
+                        extra_params['decimal_places'] = row[5]
+
+                    # Add primary_key and unique, if necessary.
+                    if column_name in indexes:
+                        if indexes[column_name]['primary_key']:
+                            extra_params['primary_key'] = True
+                        elif indexes[column_name]['unique']:
+                            extra_params['unique'] = True
+
+                    field_type += '('
+
+                # Don't output 'id = meta.AutoField(primary_key=True)', because
+                # that's assumed if it doesn't exist.
+                if att_name == 'id' and field_type == 'AutoField(' and extra_params == {'primary_key': True}:
+                    continue
+
+                # Add 'null' and 'blank', if the 'null_ok' flag was present in the
+                # table description.
+                if row[6]: # If it's NULL...
+                    extra_params['blank'] = True
+                    if not field_type in ('TextField(', 'CharField('):
+                        extra_params['null'] = True
+
+                field_desc = '%s = models.%s' % (att_name, field_type)
+                if extra_params:
+                    if not field_desc.endswith('('):
+                        field_desc += ', '
+                    field_desc += ', '.join(['%s=%r' % (k, v) for k, v in extra_params.items()])
+                field_desc += ')'
+                if comment_notes:
+                    field_desc += ' # ' + ' '.join(comment_notes)
+                yield '    %s' % field_desc
+            yield '    class Meta:'
+            yield '        db_table = %r if POSTGRES_SCHEMAS else %r' % (table_name, table_name.replace('.','_'))
+            yield ''

Added: servres/branches/eg-schema-experiment/conifer/robin/__init__.py
===================================================================


Property changes on: servres/branches/eg-schema-experiment/conifer/robin/__init__.py
___________________________________________________________________
Name: svn:executable
   + *

Added: servres/branches/eg-schema-experiment/conifer/robin/models.py
===================================================================
--- servres/branches/eg-schema-experiment/conifer/robin/models.py	                        (rev 0)
+++ servres/branches/eg-schema-experiment/conifer/robin/models.py	2010-02-07 22:46:16 UTC (rev 768)
@@ -0,0 +1,147 @@
+# This is an auto-generated Django model module.
+# You'll have to do the following manually to clean this up:
+#     * Rearrange models' order
+#     * Make sure each model has one field with primary_key=True
+# Feel free to rename the models, but don't rename db_table values or field names.
+#
+# Also note: You'll have to insert the output of 'django-admin.py sqlcustom [appname]'
+# into your database.
+
+from django.db import models
+
+POSTGRES_SCHEMAS = True
+
+class ConfigReservesTerms(models.Model):
+    id = models.IntegerField(primary_key=True)
+    ou = models.ForeignKey('ActorOu', db_column='ou')
+    academic_year = models.DateField()
+    term = models.TextField()
+    class Meta:
+        db_table = u'config.reserves_terms' if POSTGRES_SCHEMAS else u'config_reserves_terms'
+
+class ConfigReservesMediaTypes(models.Model):
+    id = models.IntegerField(primary_key=True)
+    type = models.TextField()
+    class Meta:
+        db_table = u'config.reserves_media_types' if POSTGRES_SCHEMAS else u'config_reserves_media_types'
+
+class ReservesItem(models.Model):
+    id = models.IntegerField(primary_key=True)
+    cat_id = models.IntegerField()
+    title = models.TextField()
+    author = models.TextField()
+    call_number = models.TextField()
+    url = models.TextField()
+    original_circ_modifier = models.ForeignKey('ConfigCircModifer', db_column='original_circ_modifier')
+    reserves_circ_modifier = models.ForeignKey('ConfigCircModifer', related_name='reserves_circ_modifier', db_column='reserves_circ_modifier')
+    original_location = models.ForeignKey('AssetCopyLocation', db_column='original_location')
+    reserves_location = models.ForeignKey('AssetCopyLocation', related_name='reserves_location', db_column='reserves_location')
+    copyright_notice = models.TextField()
+    copyright_fee = models.DecimalField(max_digits=6, decimal_places=2)
+    media_type = models.ForeignKey('ConfigReservesMediaTypes', db_column='media_type')
+    note = models.TextField()
+    archive = models.BooleanField()
+    class Meta:
+        db_table = u'reserves.item' if POSTGRES_SCHEMAS else u'reserves_item'
+
+class ReservesEventLog(models.Model):
+    id = models.IntegerField(primary_key=True)
+    entity = models.ForeignKey('ReservesItem')
+    entity_type = models.ForeignKey('ConfigReservesLogTypes', db_column='entity_type')
+    action = models.ForeignKey('ConfigReservesActions', db_column='action')
+    time_stamp = models.DateTimeField()
+    class Meta:
+        db_table = u'reserves.event_log' if POSTGRES_SCHEMAS else u'reserves_event_log'
+
+class ReservesCourseMembersMap(models.Model):
+    id = models.IntegerField(primary_key=True)
+    course = models.ForeignKey('ReservesCourse')
+    member = models.ForeignKey('ActorUsr')
+    member_access = models.ForeignKey('PermissionGrpTree', db_column='member_access')
+    receive_email = models.BooleanField()
+    class Meta:
+        db_table = u'reserves.course_members_map' if POSTGRES_SCHEMAS else u'reserves_course_members_map'
+
+class ReservesCourseContainer(models.Model):
+    id = models.IntegerField(primary_key=True)
+    name = models.TextField()
+    parent = models.ForeignKey('self', db_column='parent')
+    course = models.ForeignKey('ReservesCourse')
+    item = models.ForeignKey('ReservesItem')
+    note = models.TextField()
+    class Meta:
+        db_table = u'reserves.course_container' if POSTGRES_SCHEMAS else u'reserves_course_container'
+
+class ReservesCourse(models.Model):
+    id = models.IntegerField(primary_key=True)
+    name = models.TextField()
+    code = models.TextField()
+    term = models.ForeignKey('ConfigReservesTerms', db_column='term')
+    location = models.ForeignKey('AssetCopyLocation', db_column='location')
+    default_loan = models.ForeignKey('ConfigCircModifer', db_column='default_loan')
+    owner = models.ForeignKey('ActorUsr', db_column='owner')
+    note = models.TextField()
+    archive = models.BooleanField()
+    class Meta:
+        db_table = u'reserves.course' if POSTGRES_SCHEMAS else u'reserves_course'
+
+class PermissionGrpTree(models.Model):
+    id = models.IntegerField(primary_key=True)
+    description = models.TextField()
+    class Meta:
+        db_table = u'permission.grp_tree' if POSTGRES_SCHEMAS else u'permission_grp_tree'
+
+class ConfigReservesLogTypes(models.Model):
+    id = models.IntegerField(primary_key=True)
+    type = models.TextField()
+    class Meta:
+        db_table = u'config.reserves_log_types' if POSTGRES_SCHEMAS else u'config_reserves_log_types'
+
+class ConfigReservesActions(models.Model):
+    id = models.IntegerField(primary_key=True)
+    action = models.TextField()
+    class Meta:
+        db_table = u'config.reserves_actions' if POSTGRES_SCHEMAS else u'config_reserves_actions'
+
+class ActorUsr(models.Model):
+    id = models.IntegerField(primary_key=True)
+    description = models.TextField()
+    class Meta:
+        db_table = u'actor.usr' if POSTGRES_SCHEMAS else u'actor_usr'
+
+class ConfigReserves(models.Model):
+    id = models.IntegerField(primary_key=True)
+    ou = models.ForeignKey('ActorOu', db_column='ou')
+    default_transit_status = models.ForeignKey('ConfigCopyStatus', db_column='default_transit_status')
+    default_copyright = models.TextField()
+    default_copyright_fee = models.DecimalField(max_digits=6, decimal_places=2)
+    default_original_circ_modifier = models.ForeignKey('ConfigCircModifer', db_column='default_original_circ_modifier')
+    default_original_location = models.ForeignKey('AssetCopyLocation', db_column='default_original_location')
+    reserves_email = models.TextField()
+    class Meta:
+        db_table = u'config.reserves' if POSTGRES_SCHEMAS else u'config_reserves'
+
+class ConfigCopyStatus(models.Model):
+    id = models.IntegerField(primary_key=True)
+    description = models.TextField()
+    class Meta:
+        db_table = u'config.copy_status' if POSTGRES_SCHEMAS else u'config_copy_status'
+
+class ConfigCircModifer(models.Model):
+    id = models.IntegerField(primary_key=True)
+    description = models.TextField()
+    class Meta:
+        db_table = u'config.circ_modifer' if POSTGRES_SCHEMAS else u'config_circ_modifer'
+
+class AssetCopyLocation(models.Model):
+    id = models.IntegerField(primary_key=True)
+    description = models.TextField()
+    class Meta:
+        db_table = u'asset.copy_location' if POSTGRES_SCHEMAS else u'asset_copy_location'
+
+class ActorOu(models.Model):
+    id = models.IntegerField(primary_key=True)
+    description = models.TextField()
+    class Meta:
+        db_table = u'actor.ou' if POSTGRES_SCHEMAS else u'actor_ou'
+


Property changes on: servres/branches/eg-schema-experiment/conifer/robin/models.py
___________________________________________________________________
Name: svn:executable
   + *

Added: servres/branches/eg-schema-experiment/conifer/robin/tests.py
===================================================================
--- servres/branches/eg-schema-experiment/conifer/robin/tests.py	                        (rev 0)
+++ servres/branches/eg-schema-experiment/conifer/robin/tests.py	2010-02-07 22:46:16 UTC (rev 768)
@@ -0,0 +1,23 @@
+"""
+This file demonstrates two different styles of tests (one doctest and one
+unittest). These will both pass when you run "manage.py test".
+
+Replace these with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+
+class SimpleTest(TestCase):
+    def test_basic_addition(self):
+        """
+        Tests that 1 + 1 always equals 2.
+        """
+        self.failUnlessEqual(1 + 1, 2)
+
+__test__ = {"doctest": """
+Another way to test that 1 + 1 is equal to 2.
+
+>>> 1 + 1 == 2
+True
+"""}
+


Property changes on: servres/branches/eg-schema-experiment/conifer/robin/tests.py
___________________________________________________________________
Name: svn:executable
   + *

Added: servres/branches/eg-schema-experiment/conifer/robin/views.py
===================================================================
--- servres/branches/eg-schema-experiment/conifer/robin/views.py	                        (rev 0)
+++ servres/branches/eg-schema-experiment/conifer/robin/views.py	2010-02-07 22:46:16 UTC (rev 768)
@@ -0,0 +1 @@
+# Create your views here.


Property changes on: servres/branches/eg-schema-experiment/conifer/robin/views.py
___________________________________________________________________
Name: svn:executable
   + *

Modified: servres/branches/eg-schema-experiment/conifer/settings.py
===================================================================
--- servres/branches/eg-schema-experiment/conifer/settings.py	2010-02-04 19:35:55 UTC (rev 767)
+++ servres/branches/eg-schema-experiment/conifer/settings.py	2010-02-07 22:46:16 UTC (rev 768)
@@ -16,11 +16,12 @@
 
 MANAGERS = ADMINS
 
-DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
-DATABASE_NAME = HERE('syrup.sqlite') # Or path to database file if using sqlite3.
-DATABASE_USER = ''             # Not used with sqlite3.
-DATABASE_PASSWORD = ''         # Not used with sqlite3.
-DATABASE_HOST = ''             # Set to empty string for localhost. Not used with sqlite3.
+DATABASE_ENGINE = 'conifer.evergreen.backends.postgresql_with_schemas'
+DATABASE_PG_SCHEMAS = ['actor','asset','config','permission','reserves']
+DATABASE_NAME = 'robin'             # Or path to database file if using sqlite3.
+DATABASE_USER = 'postgres'             # Not used with sqlite3.
+DATABASE_PASSWORD = 'XXX'         # Not used with sqlite3.
+DATABASE_HOST = 'localhost'             # Set to empty string for localhost. Not used with sqlite3.
 DATABASE_PORT = ''             # Set to empty string for default. Not used with sqlite3.
 
 # Local time zone for this installation. Choices can be found here:
@@ -91,6 +92,8 @@
     'django.contrib.sessions',
     'django.contrib.sites',
     'django.contrib.admin',
+    'conifer.evergreen',
+    'conifer.robin',
     'conifer.syrup',
 )
 



More information about the open-ils-commits mailing list