Changeset - 186b1cf7f759
[Not reviewed]
beta
0 6 0
Marcin Kuzminski - 13 years ago 2012-09-01 19:31:31
marcin@python-works.com
Step6a for migrations from 1.3.6
to 1.4.0
6 files changed with 226 insertions and 31 deletions:
0 comments (0 inline, 0 general)
rhodecode/lib/db_manage.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
"""
 
    rhodecode.lib.db_manage
 
    ~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
    Database creation, and setup module for RhodeCode. Used for creation
 
    of database as well as for migration operations
 

	
 
    :created_on: Apr 10, 2010
 
    :author: marcink
 
    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
 
    :license: GPLv3, see COPYING for more details.
 
"""
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 

	
 
import os
 
import sys
 
import uuid
 
import logging
 
from os.path import dirname as dn, join as jn
 

	
 
from rhodecode import __dbversion__
 
from rhodecode.model import meta
 

	
 
from rhodecode.model.user import UserModel
 
from rhodecode.lib.utils import ask_ok
 
from rhodecode.model import init_model
 
from rhodecode.model.db import User, Permission, RhodeCodeUi, \
 
    RhodeCodeSetting, UserToPerm, DbMigrateVersion, RepoGroup,\
 
    UserRepoGroupToPerm
 

	
 
from sqlalchemy.engine import create_engine
 
from sqlalchemy.schema import MetaData
 
from rhodecode.model.repos_group import ReposGroupModel
 
#from rhodecode.model import meta
 
from rhodecode.model.meta import Session, Base
 

	
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class DbManage(object):
 
    def __init__(self, log_sql, dbconf, root, tests=False):
 
        self.dbname = dbconf.split('/')[-1]
 
        self.tests = tests
 
        self.root = root
 
        self.dburi = dbconf
 
        self.log_sql = log_sql
 
        self.db_exists = False
 
        self.init_db()
 

	
 
    def init_db(self):
 
        engine = create_engine(self.dburi, echo=self.log_sql)
 
        init_model(engine)
 
        self.sa = meta.Session()
 
        self.sa = Session()
 

	
 
    def create_tables(self, override=False, defaults={}):
 
        """
 
        Create a auth database
 
        """
 
        quiet = defaults.get('quiet')
 
        log.info("Any existing database is going to be destroyed")
 
        if self.tests or quiet:
 
            destroy = True
 
        else:
 
            destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
 
        if not destroy:
 
            sys.exit()
 
        if destroy:
 
            meta.Base.metadata.drop_all()
 
            Base.metadata.drop_all()
 

	
 
        checkfirst = not override
 
        meta.Base.metadata.create_all(checkfirst=checkfirst)
 
        Base.metadata.create_all(checkfirst=checkfirst)
 
        log.info('Created tables for %s' % self.dbname)
 

	
 
    def set_db_version(self):
 
        ver = DbMigrateVersion()
 
        ver.version = __dbversion__
 
        ver.repository_id = 'rhodecode_db_migrations'
 
        ver.repository_path = 'versions'
 
        self.sa.add(ver)
 
        log.info('db version set to: %s' % __dbversion__)
 

	
 
    def upgrade(self):
 
        """
 
        Upgrades given database schema to given revision following
 
        all needed steps, to perform the upgrade
 

	
 
        """
 

	
 
        from rhodecode.lib.dbmigrate.migrate.versioning import api
 
        from rhodecode.lib.dbmigrate.migrate.exceptions import \
 
            DatabaseNotControlledError
 

	
 
        if 'sqlite' in self.dburi:
 
            print (
 
               '********************** WARNING **********************\n'
 
               'Make sure your version of sqlite is at least 3.7.X.  \n'
 
               'Earlier versions are known to fail on some migrations\n'
 
               '*****************************************************\n'
 
            )
 
        upgrade = ask_ok('You are about to perform database upgrade, make '
 
                         'sure You backed up your database before. '
 
                         'Continue ? [y/n]')
 
        if not upgrade:
 
            sys.exit('Nothing done')
 

	
 
        repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
 
                             'rhodecode/lib/dbmigrate')
 
        db_uri = self.dburi
 

	
 
        try:
 
            curr_version = api.db_version(db_uri, repository_path)
 
            msg = ('Found current database under version'
 
                 ' control with version %s' % curr_version)
 

	
 
        except (RuntimeError, DatabaseNotControlledError):
 
            curr_version = 1
 
            msg = ('Current database is not under version control. Setting'
 
                   ' as version %s' % curr_version)
 
            api.version_control(db_uri, repository_path, curr_version)
 

	
 
        print (msg)
 

	
 
        if curr_version == __dbversion__:
 
            sys.exit('This database is already at the newest version')
 

	
 
        #======================================================================
 
        # UPGRADE STEPS
 
        #======================================================================
 
        class UpgradeSteps(object):
 
            """
 
            Those steps follow schema versions so for example schema
 
            for example schema with seq 002 == step_2 and so on.
 
            """
 

	
 
            def __init__(self, klass):
 
                self.klass = klass
 

	
 
            def step_0(self):
 
                # step 0 is the schema upgrade, and than follow proper upgrades
 
                print ('attempting to do database upgrade to version %s' \
 
                                % __dbversion__)
 
                api.upgrade(db_uri, repository_path, __dbversion__)
 
                print ('Schema upgrade completed')
 

	
 
            def step_1(self):
 
                pass
 

	
 
            def step_2(self):
 
                print ('Patching repo paths for newer version of RhodeCode')
 
                self.klass.fix_repo_paths()
 

	
 
                print ('Patching default user of RhodeCode')
 
                self.klass.fix_default_user()
 

	
 
                log.info('Changing ui settings')
 
                self.klass.create_ui_settings()
 

	
 
            def step_3(self):
 
                print ('Adding additional settings into RhodeCode db')
 
                self.klass.fix_settings()
 
                print ('Adding ldap defaults')
 
                self.klass.create_ldap_options(skip_existing=True)
 

	
 
            def step_4(self):
 
                print ('create permissions and fix groups')
 
                self.klass.create_permissions()
 
                self.klass.fixup_groups()
 

	
 
            def step_5(self):
 
                pass
 

	
 
            def step_6(self):
 
                pass
 
                print ('re-checking permissions')
 
                self.klass.create_permissions()
 

	
 
                print ('installing new hooks')
 
                hooks4 = RhodeCodeUi()
 
                hooks4.ui_key = RhodeCodeUi.HOOK_PRE_PUSH
 
                hooks4.ui_value = 'python:rhodecode.lib.hooks.pre_push'
 
                Session().add(hooks4)
 

	
 
                hooks6 = RhodeCodeUi()
 
                hooks6.ui_section = 'hooks'
 
                hooks6.ui_key = RhodeCodeUi.HOOK_PRE_PULL
 
                hooks6.ui_value = 'python:rhodecode.lib.hooks.pre_pull'
 
                Session().add(hooks6)
 

	
 
                print ('installing hgsubversion option')
 
                # enable hgsubversion disabled by default
 
                hgsubversion = RhodeCodeUi()
 
                hgsubversion.ui_section = 'extensions'
 
                hgsubversion.ui_key = 'hgsubversion'
 
                hgsubversion.ui_value = ''
 
                hgsubversion.ui_active = False
 
                Session().add(hgsubversion)
 

	
 
                print ('installing hg git option')
 
                # enable hggit disabled by default
 
                hggit = RhodeCodeUi()
 
                hggit.ui_section = 'extensions'
 
                hggit.ui_key = 'hggit'
 
                hggit.ui_value = ''
 
                hggit.ui_active = False
 
                Session().add(hggit)
 

	
 
                print ('re-check default permissions')
 
                self.klass.populate_default_permissions()
 

	
 
        upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
 

	
 
        # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
 
        for step in upgrade_steps:
 
            print ('performing upgrade step %s' % step)
 
            getattr(UpgradeSteps(self), 'step_%s' % step)()
 
            self.sa.commit()
 

	
 
    def fix_repo_paths(self):
 
        """
 
        Fixes a old rhodecode version path into new one without a '*'
 
        """
 

	
 
        paths = self.sa.query(RhodeCodeUi)\
 
                .filter(RhodeCodeUi.ui_key == '/')\
 
                .scalar()
 

	
 
        paths.ui_value = paths.ui_value.replace('*', '')
 

	
 
        try:
 
            self.sa.add(paths)
 
            self.sa.commit()
 
        except:
 
            self.sa.rollback()
 
            raise
 

	
 
    def fix_default_user(self):
 
        """
 
        Fixes a old default user with some 'nicer' default values,
 
        used mostly for anonymous access
 
        """
 
        def_user = self.sa.query(User)\
 
                .filter(User.username == 'default')\
 
                .one()
 

	
 
        def_user.name = 'Anonymous'
 
        def_user.lastname = 'User'
 
        def_user.email = 'anonymous@rhodecode.org'
 

	
 
        try:
 
            self.sa.add(def_user)
 
            self.sa.commit()
 
        except:
 
            self.sa.rollback()
 
            raise
 

	
 
    def fix_settings(self):
 
        """
 
        Fixes rhodecode settings adds ga_code key for google analytics
 
        """
 

	
 
        hgsettings3 = RhodeCodeSetting('ga_code', '')
 

	
 
        try:
 
            self.sa.add(hgsettings3)
 
            self.sa.commit()
 
        except:
 
            self.sa.rollback()
 
            raise
 

	
 
    def admin_prompt(self, second=False, defaults={}):
 
        if not self.tests:
 
            import getpass
 

	
 
            # defaults
 
            username = defaults.get('username')
 
            password = defaults.get('password')
 
            email = defaults.get('email')
 

	
 
            def get_password():
 
                password = getpass.getpass('Specify admin password '
 
                                           '(min 6 chars):')
 
                confirm = getpass.getpass('Confirm password:')
 

	
 
                if password != confirm:
 
                    log.error('passwords mismatch')
 
                    return False
 
                if len(password) < 6:
 
                    log.error('password is to short use at least 6 characters')
 
                    return False
 

	
 
                return password
 
            if username is None:
 
                username = raw_input('Specify admin username:')
 
            if password is None:
 
                password = get_password()
 
                if not password:
 
                    #second try
 
                    password = get_password()
 
                    if not password:
 
                        sys.exit()
 
            if email is None:
 
                email = raw_input('Specify admin email:')
 
            self.create_user(username, password, email, True)
 
        else:
 
            log.info('creating admin and regular test users')
 
@@ -431,123 +469,114 @@ class DbManage(object):
 
            sys.exit('max retries reached')
 
        if path_ok is False:
 
            retries -= 1
 
            return self.config_prompt(test_repo_path, retries)
 

	
 
        return path
 

	
 
    def create_settings(self, path):
 

	
 
        self.create_ui_settings()
 

	
 
        #HG UI OPTIONS
 
        web1 = RhodeCodeUi()
 
        web1.ui_section = 'web'
 
        web1.ui_key = 'push_ssl'
 
        web1.ui_value = 'false'
 

	
 
        web2 = RhodeCodeUi()
 
        web2.ui_section = 'web'
 
        web2.ui_key = 'allow_archive'
 
        web2.ui_value = 'gz zip bz2'
 

	
 
        web3 = RhodeCodeUi()
 
        web3.ui_section = 'web'
 
        web3.ui_key = 'allow_push'
 
        web3.ui_value = '*'
 

	
 
        web4 = RhodeCodeUi()
 
        web4.ui_section = 'web'
 
        web4.ui_key = 'baseurl'
 
        web4.ui_value = '/'
 

	
 
        paths = RhodeCodeUi()
 
        paths.ui_section = 'paths'
 
        paths.ui_key = '/'
 
        paths.ui_value = path
 

	
 
        phases = RhodeCodeUi()
 
        phases.ui_section = 'phases'
 
        phases.ui_key = 'publish'
 
        phases.ui_value = False
 

	
 
        sett1 = RhodeCodeSetting('realm', 'RhodeCode authentication')
 
        sett2 = RhodeCodeSetting('title', 'RhodeCode')
 
        sett3 = RhodeCodeSetting('ga_code', '')
 

	
 
        sett4 = RhodeCodeSetting('show_public_icon', True)
 
        sett5 = RhodeCodeSetting('show_private_icon', True)
 
        sett6 = RhodeCodeSetting('stylify_metatags', False)
 

	
 
        self.sa.add(web1)
 
        self.sa.add(web2)
 
        self.sa.add(web3)
 
        self.sa.add(web4)
 
        self.sa.add(paths)
 
        self.sa.add(sett1)
 
        self.sa.add(sett2)
 
        self.sa.add(sett3)
 
        self.sa.add(sett4)
 
        self.sa.add(sett5)
 
        self.sa.add(sett6)
 

	
 
        self.create_ldap_options()
 

	
 
        log.info('created ui config')
 

	
 
    def create_user(self, username, password, email='', admin=False):
 
        log.info('creating user %s' % username)
 
        UserModel().create_or_update(username, password, email,
 
                                     firstname='RhodeCode', lastname='Admin',
 
                                     active=True, admin=admin)
 

	
 
    def create_default_user(self):
 
        log.info('creating default user')
 
        # create default user for handling default permissions.
 
        UserModel().create_or_update(username='default',
 
                              password=str(uuid.uuid1())[:8],
 
                              email='anonymous@rhodecode.org',
 
                              firstname='Anonymous', lastname='User')
 

	
 
    def create_permissions(self):
 
        # module.(access|create|change|delete)_[name]
 
        # module.(none|read|write|admin)
 

	
 
        for p in Permission.PERMS:
 
            if not Permission.get_by_key(p[0]):
 
                new_perm = Permission()
 
                new_perm.permission_name = p[0]
 
                new_perm.permission_longname = p[0]
 
                self.sa.add(new_perm)
 

	
 
    def populate_default_permissions(self):
 
        log.info('creating default user permissions')
 

	
 
        default_user = User.get_by_username('default')
 

	
 
        for def_perm in ['hg.register.manual_activate', 'hg.create.repository',
 
                         'hg.fork.repository', 'repository.read']:
 

	
 
            perm = self.sa.query(Permission)\
 
             .filter(Permission.permission_name == def_perm)\
 
             .scalar()
 
            if not perm:
 
                raise Exception(
 
                  'CRITICAL: permission %s not found inside database !!'
 
                  % def_perm
 
                )
 
            if not UserToPerm.query()\
 
                .filter(UserToPerm.permission == perm)\
 
                .filter(UserToPerm.user == default_user).scalar():
 
        reg_perm = UserToPerm()
 
        reg_perm.user = default_user
 
        reg_perm.permission = self.sa.query(Permission)\
 
         .filter(Permission.permission_name == 'hg.register.manual_activate')\
 
         .scalar()
 
                reg_perm.permission = perm
 
        self.sa.add(reg_perm)
 

	
 
        create_repo_perm = UserToPerm()
 
        create_repo_perm.user = default_user
 
        create_repo_perm.permission = self.sa.query(Permission)\
 
         .filter(Permission.permission_name == 'hg.create.repository')\
 
         .scalar()
 
        self.sa.add(create_repo_perm)
 

	
 
        default_fork_perm = UserToPerm()
 
        default_fork_perm.user = default_user
 
        default_fork_perm.permission = self.sa.query(Permission)\
 
         .filter(Permission.permission_name == 'hg.fork.repository')\
 
         .scalar()
 
        self.sa.add(default_fork_perm)
 

	
 
        default_repo_perm = UserToPerm()
 
        default_repo_perm.user = default_user
 
        default_repo_perm.permission = self.sa.query(Permission)\
 
         .filter(Permission.permission_name == 'repository.read')\
 
         .scalar()
 
        self.sa.add(default_repo_perm)
rhodecode/lib/dbmigrate/migrate/changeset/ansisql.py
Show inline comments
 
"""
 
   Extensions to SQLAlchemy for altering existing tables.
 

	
 
   At the moment, this isn't so much based off of ANSI as much as
 
   things that just happen to work with multiple databases.
 
"""
 
import StringIO
 

	
 
import sqlalchemy as sa
 
from sqlalchemy.schema import SchemaVisitor
 
from sqlalchemy.engine.default import DefaultDialect
 
from sqlalchemy.sql import ClauseElement
 
from sqlalchemy.schema import (ForeignKeyConstraint,
 
                               PrimaryKeyConstraint,
 
                               CheckConstraint,
 
                               UniqueConstraint,
 
                               Index)
 

	
 
from rhodecode.lib.dbmigrate.migrate import exceptions
 
from rhodecode.lib.dbmigrate.migrate.changeset import constraint
 

	
 
from sqlalchemy.schema import AddConstraint, DropConstraint
 
from sqlalchemy.sql.compiler import DDLCompiler
 
SchemaGenerator = SchemaDropper = DDLCompiler
 

	
 

	
 
class AlterTableVisitor(SchemaVisitor):
 
    """Common operations for ``ALTER TABLE`` statements."""
 

	
 
    # engine.Compiler looks for .statement
 
    # when it spawns off a new compiler
 
    statement = ClauseElement()
 

	
 
    def append(self, s):
 
        """Append content to the SchemaIterator's query buffer."""
 

	
 
        self.buffer.write(s)
 

	
 
    def execute(self):
 
        """Execute the contents of the SchemaIterator's buffer."""
 
        try:
 
            return self.connection.execute(self.buffer.getvalue())
 
        finally:
 
            self.buffer.truncate(0)
 

	
 
    def __init__(self, dialect, connection, **kw):
 
        self.connection = connection
 
        self.buffer = StringIO.StringIO()
 
        self.preparer = dialect.identifier_preparer
 
        self.dialect = dialect
 

	
 
    def traverse_single(self, elem):
 
        ret = super(AlterTableVisitor, self).traverse_single(elem)
 
        if ret:
 
            # adapt to 0.6 which uses a string-returning
 
            # object
 
            self.append(" %s" % ret)
 

	
 
    def _to_table(self, param):
 
        """Returns the table object for the given param object."""
 
        if isinstance(param, (sa.Column, sa.Index, sa.schema.Constraint)):
 
            ret = param.table
 
        else:
 
            ret = param
 
        return ret
 

	
 
    def start_alter_table(self, param):
 
        """Returns the start of an ``ALTER TABLE`` SQL-Statement.
 

	
 
        Use the param object to determine the table name and use it
 
        for building the SQL statement.
 

	
 
        :param param: object to determine the table from
 
        :type param: :class:`sqlalchemy.Column`, :class:`sqlalchemy.Index`,
 
          :class:`sqlalchemy.schema.Constraint`, :class:`sqlalchemy.Table`,
 
          or string (table name)
 
        """
 
        table = self._to_table(param)
 
        self.append('\nALTER TABLE %s ' % self.preparer.format_table(table))
 
        return table
 

	
 

	
 
class ANSIColumnGenerator(AlterTableVisitor, SchemaGenerator):
 
    """Extends ansisql generator for column creation (alter table add col)"""
 

	
 
    def visit_column(self, column):
 
        """Create a column (table already exists).
 

	
 
        :param column: column object
 
        :type column: :class:`sqlalchemy.Column` instance
 
        """
 
        if column.default is not None:
 
            self.traverse_single(column.default)
 

	
 
        table = self.start_alter_table(column)
 
        self.append("ADD ")
 

	
 
        self.append(self.get_column_specification(column))
 

	
 
        for cons in column.constraints:
 
            self.traverse_single(cons)
 
        self.execute()
 

	
 
        # ALTER TABLE STATEMENTS
 

	
 
        # add indexes and unique constraints
 
        if column.index_name:
 
            Index(column.index_name,column).create()
 
        elif column.unique_name:
 
            constraint.UniqueConstraint(column,
 
                                        name=column.unique_name).create()
 

	
 
        # SA bounds FK constraints to table, add manually
 
        for fk in column.foreign_keys:
 
            self.add_foreignkey(fk.constraint)
 

	
 
        # add primary key constraint if needed
 
        if column.primary_key_name:
 
            cons = constraint.PrimaryKeyConstraint(column,
 
                                                   name=column.primary_key_name)
 
            cons.create()
 

	
 
    def add_foreignkey(self, fk):
 
        self.connection.execute(AddConstraint(fk))
 

	
 
class ANSIColumnDropper(AlterTableVisitor, SchemaDropper):
 
    """Extends ANSI SQL dropper for column dropping (``ALTER TABLE
 
    DROP COLUMN``).
 
    """
 

	
 
    def visit_column(self, column):
 
        """Drop a column from its table.
 

	
 
        :param column: the column object
 
        :type column: :class:`sqlalchemy.Column`
 
        """
 
        table = self.start_alter_table(column)
 
        self.append('DROP COLUMN %s' % self.preparer.format_column(column))
 
        self.execute()
 

	
 

	
 
class ANSISchemaChanger(AlterTableVisitor, SchemaGenerator):
 
    """Manages changes to existing schema elements.
 

	
 
    Note that columns are schema elements; ``ALTER TABLE ADD COLUMN``
 
    is in SchemaGenerator.
 

	
 
    All items may be renamed. Columns can also have many of their properties -
 
    type, for example - changed.
 

	
 
    Each function is passed a tuple, containing (object, name); where
 
    object is a type of object you'd expect for that function
 
    (ie. table for visit_table) and name is the object's new
 
    name. NONE means the name is unchanged.
 
    """
 

	
 
    def visit_table(self, table):
 
        """Rename a table. Other ops aren't supported."""
 
        self.start_alter_table(table)
 
        self.append("RENAME TO %s" % self.preparer.quote(table.new_name,
 
                                                         table.quote))
 
        self.execute()
 

	
 
    def visit_index(self, index):
 
        """Rename an index"""
 
        if hasattr(self, '_validate_identifier'):
 
            # SA <= 0.6.3
 
            self.append("ALTER INDEX %s RENAME TO %s" % (
 
                    self.preparer.quote(
 
                        self._validate_identifier(
 
                            index.name, True), index.quote),
 
                    self.preparer.quote(
 
                        self._validate_identifier(
 
                            index.new_name, True), index.quote)))
 
        else:
 
            # SA >= 0.6.5
 
            self.append("ALTER INDEX %s RENAME TO %s" % (
 
                    self.preparer.quote(
 
                        self._index_identifier(
 
                            index.name), index.quote),
 
                    self.preparer.quote(
 
                        self._index_identifier(
 
                            index.new_name), index.quote)))
 
        self.execute()
 

	
 
    def visit_column(self, delta):
 
        """Rename/change a column."""
 
        # ALTER COLUMN is implemented as several ALTER statements
 
        keys = delta.keys()
 
        if 'type' in keys:
 
            self._run_subvisit(delta, self._visit_column_type)
 
        if 'nullable' in keys:
 
            self._run_subvisit(delta, self._visit_column_nullable)
rhodecode/lib/dbmigrate/migrate/changeset/databases/sqlite.py
Show inline comments
 
"""
 
   `SQLite`_ database specific implementations of changeset classes.
 

	
 
   .. _`SQLite`: http://www.sqlite.org/
 
"""
 
from UserDict import DictMixin
 
from copy import copy
 

	
 
from sqlalchemy.databases import sqlite as sa_base
 

	
 
from rhodecode.lib.dbmigrate.migrate import exceptions
 
from rhodecode.lib.dbmigrate.migrate.changeset import ansisql, SQLA_06
 

	
 
SQLiteSchemaGenerator = sa_base.SQLiteDDLCompiler
 

	
 

	
 
class SQLiteCommon(object):
 

	
 
    def _not_supported(self, op):
 
        raise exceptions.NotSupportedError("SQLite does not support "
 
            "%s; see http://www.sqlite.org/lang_altertable.html" % op)
 

	
 

	
 
class SQLiteHelper(SQLiteCommon):
 

	
 
    def recreate_table(self,table,column=None,delta=None):
 
        table_name = self.preparer.format_table(table)
 

	
 
        # we remove all indexes so as not to have
 
        # problems during copy and re-create
 
        for index in table.indexes:
 
            index.drop()
 

	
 
        self.append('ALTER TABLE %s RENAME TO migration_tmp' % table_name)
 
        self.execute()
 

	
 
        insertion_string = self._modify_table(table, column, delta)
 

	
 
        table.create(bind=self.connection)
 
        self.append(insertion_string % {'table_name': table_name})
 
        self.execute()
 
        self.append('DROP TABLE migration_tmp')
 
        self.execute()
 

	
 
    def visit_column(self, delta):
 
        if isinstance(delta, DictMixin):
 
            column = delta.result_column
 
            table = self._to_table(delta.table)
 
        else:
 
            column = delta
 
            table = self._to_table(column.table)
 

	
 
        self.recreate_table(table,column,delta)
 

	
 
class SQLiteColumnGenerator(SQLiteSchemaGenerator,
 
                            ansisql.ANSIColumnGenerator,
 
                            # at the end so we get the normal
 
                            # visit_column by default
 
                            SQLiteHelper,
 
                            SQLiteCommon
 
                            ):
 
    """SQLite ColumnGenerator"""
 

	
 
    def _modify_table(self, table, column, delta):
 
        columns = ' ,'.join(map(
 
                self.preparer.format_column,
 
                [c for c in table.columns if c.name!=column.name]))
 
        return ('INSERT INTO %%(table_name)s (%(cols)s) '
 
                'SELECT %(cols)s from migration_tmp')%{'cols':columns}
 

	
 
    def visit_column(self,column):
 
        if column.foreign_keys:
 
            SQLiteHelper.visit_column(self,column)
 
        else:
 
            super(SQLiteColumnGenerator,self).visit_column(column)
 

	
 
class SQLiteColumnDropper(SQLiteHelper, ansisql.ANSIColumnDropper):
 
    """SQLite ColumnDropper"""
 

	
 
    def _modify_table(self, table, column, delta):
 

	
 
        columns = ' ,'.join(map(self.preparer.format_column, table.columns))
 
        return 'INSERT INTO %(table_name)s SELECT ' + columns + \
 
            ' from migration_tmp'
 

	
 
    def visit_column(self,column):
 
        # For SQLite, we *have* to remove the column here so the table
 
        # is re-created properly.
 
        column.remove_from_table(column.table,unset_table=False)
 
        super(SQLiteColumnDropper,self).visit_column(column)
 

	
 

	
 
class SQLiteSchemaChanger(SQLiteHelper, ansisql.ANSISchemaChanger):
 
    """SQLite SchemaChanger"""
 

	
 
    def _modify_table(self, table, column, delta):
 
        return 'INSERT INTO %(table_name)s SELECT * from migration_tmp'
 

	
 
    def visit_index(self, index):
 
        """Does not support ALTER INDEX"""
 
        self._not_supported('ALTER INDEX')
 

	
 

	
 
class SQLiteConstraintGenerator(ansisql.ANSIConstraintGenerator, SQLiteHelper, SQLiteCommon):
 

	
 
    def visit_migrate_primary_key_constraint(self, constraint):
 
        tmpl = "CREATE UNIQUE INDEX %s ON %s ( %s )"
 
        cols = ', '.join(map(self.preparer.format_column, constraint.columns))
 
        tname = self.preparer.format_table(constraint.table)
 
        name = self.get_constraint_name(constraint)
 
        msg = tmpl % (name, tname, cols)
 
        self.append(msg)
 
        self.execute()
 

	
 
    def _modify_table(self, table, column, delta):
 
        return 'INSERT INTO %(table_name)s SELECT * from migration_tmp'
 

	
 
    def visit_migrate_foreign_key_constraint(self, *p, **k):
 
        self.recreate_table(p[0].table)
 

	
 
    def visit_migrate_unique_constraint(self, *p, **k):
 
        self.recreate_table(p[0].table)
 

	
 

	
 
class SQLiteConstraintDropper(ansisql.ANSIColumnDropper,
 
                              SQLiteCommon,
 
                              ansisql.ANSIConstraintCommon):
 

	
 
    def visit_migrate_primary_key_constraint(self, constraint):
 
        tmpl = "DROP INDEX %s "
 
        name = self.get_constraint_name(constraint)
 
        msg = tmpl % (name)
 
        self.append(msg)
 
        self.execute()
 

	
 
    def visit_migrate_foreign_key_constraint(self, *p, **k):
 
        self._not_supported('ALTER TABLE DROP CONSTRAINT')
 

	
 
    def visit_migrate_check_constraint(self, *p, **k):
 
        self._not_supported('ALTER TABLE DROP CONSTRAINT')
 

	
 
    def visit_migrate_unique_constraint(self, *p, **k):
 
        self._not_supported('ALTER TABLE DROP CONSTRAINT')
 

	
 

	
 
# TODO: technically primary key is a NOT NULL + UNIQUE constraint, should add NOT NULL to index
 

	
 
class SQLiteDialect(ansisql.ANSIDialect):
rhodecode/lib/dbmigrate/schema/db_1_3_0.py
Show inline comments
 
@@ -1197,96 +1197,124 @@ class ChangesetComment(Base, BaseModel):
 
    def get_users(cls, revision):
 
        """
 
        Returns user associated with this changesetComment. ie those
 
        who actually commented
 

	
 
        :param cls:
 
        :param revision:
 
        """
 
        return Session.query(User)\
 
                .filter(cls.revision == revision)\
 
                .join(ChangesetComment.author).all()
 

	
 

	
 
class Notification(Base, BaseModel):
 
    __tablename__ = 'notifications'
 
    __table_args__ = (
 
        {'extend_existing': True, 'mysql_engine':'InnoDB',
 
         'mysql_charset': 'utf8'},
 
    )
 

	
 
    TYPE_CHANGESET_COMMENT = u'cs_comment'
 
    TYPE_MESSAGE = u'message'
 
    TYPE_MENTION = u'mention'
 
    TYPE_REGISTRATION = u'registration'
 

	
 
    notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
 
    subject = Column('subject', Unicode(512), nullable=True)
 
    body = Column('body', Unicode(50000), nullable=True)
 
    created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
 
    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
 
    type_ = Column('type', Unicode(256))
 

	
 
    created_by_user = relationship('User')
 
    notifications_to_users = relationship('UserNotification', lazy='joined',
 
                                          cascade="all, delete, delete-orphan")
 

	
 
    @property
 
    def recipients(self):
 
        return [x.user for x in UserNotification.query()\
 
                .filter(UserNotification.notification == self).all()]
 

	
 
    @classmethod
 
    def create(cls, created_by, subject, body, recipients, type_=None):
 
        if type_ is None:
 
            type_ = Notification.TYPE_MESSAGE
 

	
 
        notification = cls()
 
        notification.created_by_user = created_by
 
        notification.subject = subject
 
        notification.body = body
 
        notification.type_ = type_
 
        notification.created_on = datetime.datetime.now()
 

	
 
        for u in recipients:
 
            assoc = UserNotification()
 
            assoc.notification = notification
 
            u.notifications.append(assoc)
 
        Session.add(notification)
 
        return notification
 

	
 
    @property
 
    def description(self):
 
        from rhodecode.model.notification import NotificationModel
 
        return NotificationModel().make_description(self)
 

	
 

	
 
class UserNotification(Base, BaseModel):
 
    __tablename__ = 'user_to_notification'
 
    __table_args__ = (
 
        UniqueConstraint('user_id', 'notification_id'),
 
        {'extend_existing': True, 'mysql_engine':'InnoDB',
 
         'mysql_charset': 'utf8'}
 
    )
 
    user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
 
    notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
 
    read = Column('read', Boolean, default=False)
 
    sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
 

	
 
    user = relationship('User', lazy="joined")
 
    notification = relationship('Notification', lazy="joined",
 
                                order_by=lambda: Notification.created_on.desc(),)
 

	
 
    def mark_as_read(self):
 
        self.read = True
 
        Session.add(self)
 

	
 

	
 
class DbMigrateVersion(Base, BaseModel):
 
    __tablename__ = 'db_migrate_version'
 
    __table_args__ = (
 
        {'extend_existing': True, 'mysql_engine':'InnoDB',
 
         'mysql_charset': 'utf8'},
 
    )
 
    repository_id = Column('repository_id', String(250), primary_key=True)
 
    repository_path = Column('repository_path', Text)
 
    version = Column('version', Integer)
 

	
 
## this is migration from 1_4_0, but now it's here to overcome a problem of
 
## attaching a FK to this from 1_3_0 !
 

	
 

	
 
class PullRequest(Base, BaseModel):
 
    __tablename__ = 'pull_requests'
 
    __table_args__ = (
 
        {'extend_existing': True, 'mysql_engine': 'InnoDB',
 
         'mysql_charset': 'utf8'},
 
    )
 

	
 
    STATUS_NEW = u'new'
 
    STATUS_OPEN = u'open'
 
    STATUS_CLOSED = u'closed'
 

	
 
    pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
 
    title = Column('title', Unicode(256), nullable=True)
 
    description = Column('description', UnicodeText(10240), nullable=True)
 
    status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
 
    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
 
    updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
 
    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
 
    _revisions = Column('revisions', UnicodeText(20500))  # 500 revisions max
 
    org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
 
    org_ref = Column('org_ref', Unicode(256), nullable=False)
 
    other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
 
    other_ref = Column('other_ref', Unicode(256), nullable=False)
 
\ No newline at end of file
rhodecode/lib/dbmigrate/versions/006_version_1_4_0.py
Show inline comments
 
import logging
 
import datetime
 

	
 
from sqlalchemy import *
 
from sqlalchemy.exc import DatabaseError
 
from sqlalchemy.orm import relation, backref, class_mapper
 
from sqlalchemy.orm.session import Session
 
from sqlalchemy.ext.declarative import declarative_base
 

	
 
from rhodecode.lib.dbmigrate.migrate import *
 
from rhodecode.lib.dbmigrate.migrate.changeset import *
 

	
 
from rhodecode.model.meta import Base
 
from rhodecode.model import meta
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
def upgrade(migrate_engine):
 
    """ Upgrade operations go here.
 
    """
 
    Upgrade operations go here.
 
    Don't create your own engine; bind migrate_engine to your metadata
 
    """
 

	
 
    #TODO when 1.4 is ready fill it !
 
    #==========================================================================
 
    # USEREMAILMAP
 
    #==========================================================================
 
    from rhodecode.lib.dbmigrate.schema.db_1_4_0 import UserEmailMap
 
    tbl = UserEmailMap.__table__
 
    tbl.create()
 
    #==========================================================================
 
    # PULL REQUEST
 
    #==========================================================================
 
    from rhodecode.lib.dbmigrate.schema.db_1_4_0 import PullRequest
 
    tbl = PullRequest.__table__
 
    tbl.create()
 

	
 
    #==========================================================================
 
    # PULL REQUEST REVIEWERS
 
    #==========================================================================
 
    from rhodecode.lib.dbmigrate.schema.db_1_4_0 import PullRequestReviewers
 
    tbl = PullRequestReviewers.__table__
 
    tbl.create()
 

	
 
    #==========================================================================
 
    # CHANGESET STATUS
 
    #==========================================================================
 
    from rhodecode.lib.dbmigrate.schema.db_1_4_0 import ChangesetStatus
 
    tbl = ChangesetStatus.__table__
 
    tbl.create()
 

	
 
    ## RESET COMPLETLY THE metadata for sqlalchemy to use the 1_3_0 Base 
 
    Base = declarative_base()
 
    Base.metadata.clear()
 
    Base.metadata = MetaData()
 
    Base.metadata.bind = migrate_engine
 
    meta.Base = Base
 

	
 
    #==========================================================================
 
    # USERS TABLE
 
    #==========================================================================
 
    from rhodecode.lib.dbmigrate.schema.db_1_3_0 import User
 
    tbl = User.__table__
 

	
 
    # change column name -> firstname
 
    col = User.__table__.columns.name
 
    col.alter(index=Index('u_username_idx', 'username'))
 
    col.alter(index=Index('u_email_idx', 'email'))
 
    col.alter(name="firstname", table=tbl)
 

	
 
    inherit_default_permissions = Column("inherit_default_permissions",
 
                                         Boolean(), nullable=True, unique=None,
 
                                         default=True)
 
    inherit_default_permissions.create(table=tbl)
 
    inherit_default_permissions.alter(nullable=False, default=True, table=tbl)
 

	
 
    #==========================================================================
 
    # REPOSITORIES
 
    #==========================================================================
 
    from rhodecode.lib.dbmigrate.schema.db_1_3_0 import Repository
 
    tbl = Repository.__table__
 

	
 
    enable_locking = Column("enable_locking", Boolean(), nullable=True,
 
                            unique=None, default=False)
 
    enable_locking.create(table=tbl)
 
    enable_locking.alter(nullable=False, default=False, table=tbl)
 

	
 
    _locked = Column("locked", String(255), nullable=True, unique=False,
 
                     default=None)
 
    _locked.create(table=tbl)
 

	
 
    landing_rev = Column("landing_revision", String(255), nullable=True,
 
                         unique=False, default='tip')
 
    landing_rev.create(table=tbl)
 
    landing_rev.alter(nullable=False, default='tip', table=tbl)
 

	
 
    #==========================================================================
 
    # GROUPS
 
    #==========================================================================
 
    from rhodecode.lib.dbmigrate.schema.db_1_3_0 import RepoGroup
 
    tbl = RepoGroup.__table__
 
    enable_locking = Column("enable_locking", Boolean(), nullable=True,
 
                            unique=None, default=False)
 
    enable_locking.create(table=tbl)
 
    enable_locking.alter(nullable=False, default=False)
 

	
 
    #==========================================================================
 
    # CACHE INVALIDATION
 
    #==========================================================================
 
    from rhodecode.lib.dbmigrate.schema.db_1_3_0 import CacheInvalidation
 
    tbl = CacheInvalidation.__table__
 

	
 
    # change column name -> firstname
 
    col = CacheInvalidation.__table__.columns.cache_key
 
    col.alter(index=Index('key_idx', 'cache_key'))
 

	
 
    #==========================================================================
 
    # NOTIFICATION
 
    #==========================================================================
 
    from rhodecode.lib.dbmigrate.schema.db_1_3_0 import Notification
 
    tbl = Notification.__table__
 

	
 
    # change column name -> firstname
 
    col = Notification.__table__.columns.type
 
    col.alter(index=Index('notification_type_idx', 'type'),)
 

	
 
    #==========================================================================
 
    # CHANGESET_COMMENTS
 
    #==========================================================================
 
    from rhodecode.lib.dbmigrate.schema.db_1_3_0 import ChangesetComment
 

	
 
    tbl = ChangesetComment.__table__
 

	
 
    col = ChangesetComment.__table__.columns.revision
 
    col.alter(index=Index('cc_revision_idx', 'revision'),)
 

	
 
    hl_lines = Column('hl_lines', Unicode(512), nullable=True)
 
    hl_lines.create(table=tbl)
 

	
 
    created_on = Column('created_on', DateTime(timezone=False), nullable=True,
 
                        default=datetime.datetime.now)
 
    created_on.create(table=tbl)
 
    created_on.alter(nullable=False, default=datetime.datetime.now)
 
    modified_at = Column('modified_at', DateTime(timezone=False), nullable=False,
 
                         default=datetime.datetime.now)
 
    modified_at.alter(type=DateTime(timezone=False), table=tbl)
 

	
 
    pull_request_id = Column("pull_request_id", Integer(),
 
                             ForeignKey('pull_requests.pull_request_id'),
 
                             nullable=True)
 
    pull_request_id.create(table=tbl)
 
    ## RESET COMPLETLY THE metadata for sqlalchemy back after using 1_3_0
 
    Base = declarative_base()
 
    Base.metadata.clear()
 
    Base.metadata = MetaData()
 
    Base.metadata.bind = migrate_engine
 
    meta.Base = Base
 

	
 

	
 
def downgrade(migrate_engine):
 
    meta = MetaData()
 
    meta.bind = migrate_engine
rhodecode/model/db.py
Show inline comments
 
@@ -377,192 +377,193 @@ class User(Base, BaseModel):
 

	
 
        if cache:
 
            q = q.options(FromCache(
 
                            "sql_cache_short",
 
                            "get_user_%s" % _hash_key(username)
 
                          )
 
            )
 
        return q.scalar()
 

	
 
    @classmethod
 
    def get_by_api_key(cls, api_key, cache=False):
 
        q = cls.query().filter(cls.api_key == api_key)
 

	
 
        if cache:
 
            q = q.options(FromCache("sql_cache_short",
 
                                    "get_api_key_%s" % api_key))
 
        return q.scalar()
 

	
 
    @classmethod
 
    def get_by_email(cls, email, case_insensitive=False, cache=False):
 
        if case_insensitive:
 
            q = cls.query().filter(cls.email.ilike(email))
 
        else:
 
            q = cls.query().filter(cls.email == email)
 

	
 
        if cache:
 
            q = q.options(FromCache("sql_cache_short",
 
                                    "get_email_key_%s" % email))
 

	
 
        ret = q.scalar()
 
        if ret is None:
 
            q = UserEmailMap.query()
 
            # try fetching in alternate email map
 
            if case_insensitive:
 
                q = q.filter(UserEmailMap.email.ilike(email))
 
            else:
 
                q = q.filter(UserEmailMap.email == email)
 
            q = q.options(joinedload(UserEmailMap.user))
 
            if cache:
 
                q = q.options(FromCache("sql_cache_short",
 
                                        "get_email_map_key_%s" % email))
 
            ret = getattr(q.scalar(), 'user', None)
 

	
 
        return ret
 

	
 
    def update_lastlogin(self):
 
        """Update user lastlogin"""
 
        self.last_login = datetime.datetime.now()
 
        Session().add(self)
 
        log.debug('updated user %s lastlogin' % self.username)
 

	
 
    def get_api_data(self):
 
        """
 
        Common function for generating user related data for API
 
        """
 
        user = self
 
        data = dict(
 
            user_id=user.user_id,
 
            username=user.username,
 
            firstname=user.name,
 
            lastname=user.lastname,
 
            email=user.email,
 
            emails=user.emails,
 
            api_key=user.api_key,
 
            active=user.active,
 
            admin=user.admin,
 
            ldap_dn=user.ldap_dn,
 
            last_login=user.last_login,
 
        )
 
        return data
 

	
 
    def __json__(self):
 
        data = dict(
 
            full_name=self.full_name,
 
            full_name_or_username=self.full_name_or_username,
 
            short_contact=self.short_contact,
 
            full_contact=self.full_contact
 
        )
 
        data.update(self.get_api_data())
 
        return data
 

	
 

	
 
class UserEmailMap(Base, BaseModel):
 
    __tablename__ = 'user_email_map'
 
    __table_args__ = (
 
        Index('uem_email_idx', 'email'),
 
        UniqueConstraint('email'),
 
        {'extend_existing': True, 'mysql_engine': 'InnoDB',
 
         'mysql_charset': 'utf8'}
 
    )
 
    __mapper_args__ = {}
 

	
 
    email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
 
    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
 
    _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
 

	
 
    test = Column("test", String(255),nullable=False,default=True)
 
    user = relationship('User', lazy='joined')
 

	
 
    @validates('_email')
 
    def validate_email(self, key, email):
 
        # check if this email is not main one
 
        main_email = Session().query(User).filter(User.email == email).scalar()
 
        if main_email is not None:
 
            raise AttributeError('email %s is present is user table' % email)
 
        return email
 

	
 
    @hybrid_property
 
    def email(self):
 
        return self._email
 

	
 
    @email.setter
 
    def email(self, val):
 
        self._email = val.lower() if val else None
 

	
 

	
 
class UserLog(Base, BaseModel):
 
    __tablename__ = 'user_logs'
 
    __table_args__ = (
 
        {'extend_existing': True, 'mysql_engine': 'InnoDB',
 
         'mysql_charset': 'utf8'},
 
    )
 
    user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
 
    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
 
    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
 
    repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 
    user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 
    action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 
    action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
 

	
 
    @property
 
    def action_as_day(self):
 
        return datetime.date(*self.action_date.timetuple()[:3])
 

	
 
    user = relationship('User')
 
    repository = relationship('Repository', cascade='')
 

	
 

	
 
class UsersGroup(Base, BaseModel):
 
    __tablename__ = 'users_groups'
 
    __table_args__ = (
 
        {'extend_existing': True, 'mysql_engine': 'InnoDB',
 
         'mysql_charset': 'utf8'},
 
    )
 

	
 
    users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
 
    users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
 
    users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
 
    inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
 

	
 
    members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
 
    users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
 
    users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
 

	
 
    def __unicode__(self):
 
        return u'<userGroup(%s)>' % (self.users_group_name)
 

	
 
    @classmethod
 
    def get_by_group_name(cls, group_name, cache=False,
 
                          case_insensitive=False):
 
        if case_insensitive:
 
            q = cls.query().filter(cls.users_group_name.ilike(group_name))
 
        else:
 
            q = cls.query().filter(cls.users_group_name == group_name)
 
        if cache:
 
            q = q.options(FromCache(
 
                            "sql_cache_short",
 
                            "get_user_%s" % _hash_key(group_name)
 
                          )
 
            )
 
        return q.scalar()
 

	
 
    @classmethod
 
    def get(cls, users_group_id, cache=False):
 
        users_group = cls.query()
 
        if cache:
 
            users_group = users_group.options(FromCache("sql_cache_short",
 
                                    "get_users_group_%s" % users_group_id))
 
        return users_group.get(users_group_id)
 

	
 
    def get_api_data(self):
 
        users_group = self
 

	
 
        data = dict(
 
            users_group_id=users_group.users_group_id,
 
            group_name=users_group.users_group_name,
 
            active=users_group.users_group_active,
 
        )
 

	
 
        return data
 

	
 

	
 
class UsersGroupMember(Base, BaseModel):
0 comments (0 inline, 0 general)