<commit_before># Copyright 2019 Canonical, Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import enum
import logging
import os
import time

import requests.exceptions

from subiquitycore.controller import BaseController
from subiquitycore.core import Skip

log = logging.getLogger('subiquity.controllers.refresh')

class CheckState(enum.IntEnum):
    NOT_STARTED = enum.auto()
    CHECKING = enum.auto()
    FAILED = enum.auto()

    AVAILABLE = enum.auto()
    UNAVAILABLE = enum.auto()

    def is_definite(self):
        return self in [self.AVAILABLE, self.UNAVAILABLE]

class SwitchState(enum.IntEnum):
    NOT_STARTED = enum.auto()
    SWITCHING = enum.auto()
    SWITCHED = enum.auto()

class RefreshController(BaseController):

    signals = [
        ('snapd-network-change', 'snapd_network_changed'),
    ]

    def __init__(self, common):
        super().__init__(common)
        self.snap_name = os.environ.get("SNAP_NAME", "subiquity")
        self.check_state = CheckState.NOT_STARTED
        self.switch_state = SwitchState.NOT_STARTED
        self.network_state = "down"
        self.view = None
        self.offered_first_time = False
        self.answers = self.all_answers.get("Refresh", {})

    def start(self):
        self.switch_state = SwitchState.SWITCHING
        channel = self.get_refresh_channel()
        self.run_in_bg(
            lambda: self._bg_switch_snap(channel),
            self._snap_switched)

    def get_refresh_channel(self):
        """Return the channel we should refresh subiquity to."""

        with open('/proc/cmdline') as fp:
            cmdline = fp.read()
        prefix="subquity-channel="
        for arg in cmdline.split():
            if arg.startswith(prefix):
                log.debug("found cmdline arg %s", arg)
                return arg[len(prefix):]

        info_file = '/cdrom/.disk/info'
        try:
            fp = open(info_file)
        except FileNotFoundError:
            if self.opts.dry_run:
                info = (
                    'Ubuntu-Server 18.04.2 LTS "Bionic Beaver" - '
                    'Release amd64 (20190214.3)')
            else:
                log.debug("failed to find .disk/info file")
                return
        else:
            with fp:
                info = fp.read()
        release = info.split()[1]
        return 'stable/ubuntu-' + release

    def _bg_switch_snap(self, channel):
        log.debug("switching %s to %s", self.snap_name, channel)
        try:
            response = self.snapd_connection.post(
                'v2/snaps/{}'.format(self.snap_name),
                {'action': 'switch', 'channel': channel})
            response.raise_for_status()
        except requests.exceptions.RequestException as e:
            log.exception("switching")
            return
        change = response.json()["change"]
        while True:
            try:
                response = self.snapd_connection.get(
                    'v2/changes/{}'.format(change))
                response.raise_for_status()
            except requests.exceptions.RequestException as e:
                log.exception("checking switch")
                return
            if response.json()["result"]["status"] == "Done":
                return
            time.sleep(0.1)

    def _snap_switched(self, fut):
        log.debug("snap switching completed")
        try:
            fut.result()
        except:
            log.exception("_snap_switched")
        self.switch_state = SwitchState.SWITCHED
        self._maybe_check_for_update()

    def snapd_network_changed(self):
        self.network_state = "up"
        self._maybe_check_for_update()

    def _maybe_check_for_update(self):
        # If we have not yet switched to the right channel, wait.
        if self.switch_state != SwitchState.SWITCHED:
            return
        # If the network is not yet up, wait.
        if self.network_state == "down":
            return
        # If we restarted into this version, don't check for a new version.
        if self.updated:
            return
        # If we got an answer, don't check again.
        if self.check_state.is_definite():
            return
        self.check_state = CheckState.CHECKING
        self.run_in_bg(self._bg_check_for_update, self._check_result)

    def _bg_check_for_update(self):
        return self.snapd_connection.get('v2/find', select='refresh')

    def _check_result(self, fut):
        # If we managed to send concurrent requests and one has
        # already provided an answer, just forget all about the other
        # one!
        if self.check_state.is_definite():
            return
        try:
            response = fut.result()
            response.raise_for_status()
        except requests.exceptions.RequestException as e:
            log.exception("checking for update")
            self.check_error = e
            self.check_state = CheckState.FAILED
        else:
            result = response.json()
            log.debug("_check_result %s", result)
            for snap in result["result"]:
                if snap["name"] == self.snap_name:
                    self.check_state = CheckState.AVAILABLE
                    break
            else:
                self.check_state = CheckState.UNAVAILABLE
        if self.view:
            self.view.update_check_state()

    def start_update(self, callback):
        update_marker = os.path.join(self.application.state_dir, 'updating')
        open(update_marker, 'w').close()
        self.run_in_bg(
            self._bg_start_update,
            lambda fut: self.update_started(fut, callback))

    def _bg_start_update(self):
        return self.snapd_connection.post(
            'v2/snaps/{}'.format(self.snap_name), {'action': 'refresh'})

    def update_started(self, fut, callback):
        try:
            response = fut.result()
            response.raise_for_status()
        except requests.exceptions.RequestException as e:
            log.exception("requesting update")
            self.update_state = CheckState.FAILED
            self.update_failure = e
            return
        result = response.json()
        log.debug("%s", result)
        callback(result['change'])

    def get_progress(self, change, callback):
        self.run_in_bg(
            lambda: self._bg_get_progress(change),
            lambda fut: self.got_progress(fut, callback))

    def _bg_get_progress(self, change):
        return self.snapd_connection.get('v2/changes/{}'.format(change))

    def got_progress(self, fut, callback):
        try:
            response = fut.result()
            response.raise_for_status()
        except requests.exceptions.RequestException as e:
            log.exception("checking for progress")
            self.update_state = CheckState.FAILED
            self.update_failure = e
            return
        result = response.json()
        callback(result['result'])

    def default(self, index=1):
        from subiquity.ui.views.refresh import RefreshView
        if self.updated:
            raise Skip()
        show = False
        if index == 1:
            if self.check_state == CheckState.AVAILABLE:
                show = True
                self.offered_first_time = True
        elif index == 2:
            if not self.offered_first_time:
                if self.check_state in [CheckState.AVAILABLE,
                                        CheckState.CHECKING]:
                    show = True
        else:
            raise AssertionError("unexpected index {}".format(index))
        if show:
            self.view = RefreshView(self)
            self.ui.set_body(self.view)
            if 'update' in self.answers:
                if self.answers['update']:
                    self.view.update()
                else:
                    self.done()
        else:
            raise Skip()

    def done(self, sender=None):
        self.signal.emit_signal('next-screen')

    def cancel(self, sender=None):
        self.signal.emit_signal('prev-screen')
<commit_msg>do not refresh if snap switch fails<commit_after># Copyright 2019 Canonical, Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import enum
import logging
import os
import time

import requests.exceptions

from subiquitycore.controller import BaseController
from subiquitycore.core import Skip

log = logging.getLogger('subiquity.controllers.refresh')

class CheckState(enum.IntEnum):
    NOT_STARTED = enum.auto()
    CHECKING = enum.auto()
    FAILED = enum.auto()

    AVAILABLE = enum.auto()
    UNAVAILABLE = enum.auto()

    def is_definite(self):
        return self in [self.AVAILABLE, self.UNAVAILABLE]

class SwitchState(enum.IntEnum):
    NOT_STARTED = enum.auto()
    SWITCHING = enum.auto()
    SWITCHED = enum.auto()

class RefreshController(BaseController):

    signals = [
        ('snapd-network-change', 'snapd_network_changed'),
    ]

    def __init__(self, common):
        super().__init__(common)
        self.snap_name = os.environ.get("SNAP_NAME", "subiquity")
        self.check_state = CheckState.NOT_STARTED
        self.switch_state = SwitchState.NOT_STARTED
        self.network_state = "down"
        self.view = None
        self.offered_first_time = False
        self.answers = self.all_answers.get("Refresh", {})

    def start(self):
        self.switch_state = SwitchState.SWITCHING
        channel = self.get_refresh_channel()
        self.run_in_bg(
            lambda: self._bg_switch_snap(channel),
            self._snap_switched)

    def get_refresh_channel(self):
        """Return the channel we should refresh subiquity to."""

        with open('/proc/cmdline') as fp:
            cmdline = fp.read()
        prefix="subquity-channel="
        for arg in cmdline.split():
            if arg.startswith(prefix):
                log.debug("found cmdline arg %s", arg)
                return arg[len(prefix):]

        info_file = '/cdrom/.disk/info'
        try:
            fp = open(info_file)
        except FileNotFoundError:
            if self.opts.dry_run:
                info = (
                    'Ubuntu-Server 18.04.2 LTS "Bionic Beaver" - '
                    'Release amd64 (20190214.3)')
            else:
                log.debug("failed to find .disk/info file")
                return
        else:
            with fp:
                info = fp.read()
        release = info.split()[1]
        return 'stable/ubuntu-' + release

    def _bg_switch_snap(self, channel):
        log.debug("switching %s to %s", self.snap_name, channel)
        try:
            response = self.snapd_connection.post(
                'v2/snaps/{}'.format(self.snap_name),
                {'action': 'switch', 'channel': channel})
            response.raise_for_status()
        except requests.exceptions.RequestException as e:
            log.exception("switching")
            return
        change = response.json()["change"]
        while True:
            try:
                response = self.snapd_connection.get(
                    'v2/changes/{}'.format(change))
                response.raise_for_status()
            except requests.exceptions.RequestException as e:
                log.exception("checking switch")
                return
            if response.json()["result"]["status"] == "Done":
                return True
            time.sleep(0.1)

    def _snap_switched(self, fut):
        log.debug("snap switching completed")
        try:
            success = fut.result()
        except:
            log.exception("_snap_switched")
            return
        if not success:
            return
        self.switch_state = SwitchState.SWITCHED
        self._maybe_check_for_update()

    def snapd_network_changed(self):
        self.network_state = "up"
        self._maybe_check_for_update()

    def _maybe_check_for_update(self):
        # If we have not yet switched to the right channel, wait.
        if self.switch_state != SwitchState.SWITCHED:
            return
        # If the network is not yet up, wait.
        if self.network_state == "down":
            return
        # If we restarted into this version, don't check for a new version.
        if self.updated:
            return
        # If we got an answer, don't check again.
        if self.check_state.is_definite():
            return
        self.check_state = CheckState.CHECKING
        self.run_in_bg(self._bg_check_for_update, self._check_result)

    def _bg_check_for_update(self):
        return self.snapd_connection.get('v2/find', select='refresh')

    def _check_result(self, fut):
        # If we managed to send concurrent requests and one has
        # already provided an answer, just forget all about the other
        # one!
        if self.check_state.is_definite():
            return
        try:
            response = fut.result()
            response.raise_for_status()
        except requests.exceptions.RequestException as e:
            log.exception("checking for update")
            self.check_error = e
            self.check_state = CheckState.FAILED
        else:
            result = response.json()
            log.debug("_check_result %s", result)
            for snap in result["result"]:
                if snap["name"] == self.snap_name:
                    self.check_state = CheckState.AVAILABLE
                    break
            else:
                self.check_state = CheckState.UNAVAILABLE
        if self.view:
            self.view.update_check_state()

    def start_update(self, callback):
        update_marker = os.path.join(self.application.state_dir, 'updating')
        open(update_marker, 'w').close()
        self.run_in_bg(
            self._bg_start_update,
            lambda fut: self.update_started(fut, callback))

    def _bg_start_update(self):
        return self.snapd_connection.post(
            'v2/snaps/{}'.format(self.snap_name), {'action': 'refresh'})

    def update_started(self, fut, callback):
        try:
            response = fut.result()
            response.raise_for_status()
        except requests.exceptions.RequestException as e:
            log.exception("requesting update")
            self.update_state = CheckState.FAILED
            self.update_failure = e
            return
        result = response.json()
        log.debug("%s", result)
        callback(result['change'])

    def get_progress(self, change, callback):
        self.run_in_bg(
            lambda: self._bg_get_progress(change),
            lambda fut: self.got_progress(fut, callback))

    def _bg_get_progress(self, change):
        return self.snapd_connection.get('v2/changes/{}'.format(change))

    def got_progress(self, fut, callback):
        try:
            response = fut.result()
            response.raise_for_status()
        except requests.exceptions.RequestException as e:
            log.exception("checking for progress")
            self.update_state = CheckState.FAILED
            self.update_failure = e
            return
        result = response.json()
        callback(result['result'])

    def default(self, index=1):
        from subiquity.ui.views.refresh import RefreshView
        if self.updated:
            raise Skip()
        show = False
        if index == 1:
            if self.check_state == CheckState.AVAILABLE:
                show = True
                self.offered_first_time = True
        elif index == 2:
            if not self.offered_first_time:
                if self.check_state in [CheckState.AVAILABLE,
                                        CheckState.CHECKING]:
                    show = True
        else:
            raise AssertionError("unexpected index {}".format(index))
        if show:
            self.view = RefreshView(self)
            self.ui.set_body(self.view)
            if 'update' in self.answers:
                if self.answers['update']:
                    self.view.update()
                else:
                    self.done()
        else:
            raise Skip()

    def done(self, sender=None):
        self.signal.emit_signal('next-screen')

    def cancel(self, sender=None):
        self.signal.emit_signal('prev-screen')
<|endoftext|>


<commit_before>"""
Module :mod:`migrate.changeset.ansisql`
---------------------------------------

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 sqlalchemy as sa
from sqlalchemy.engine.base import Connection, Dialect
from sqlalchemy.sql.compiler import SchemaGenerator
from migrate.changeset import constraint,exceptions

SchemaIterator = sa.engine.SchemaIterator

class RawAlterTableVisitor(object):
    """Common operations for 'alter table' statements"""
    def _to_table(self,param):
        if isinstance(param,(sa.Column,sa.Index,sa.schema.Constraint)):
            ret = param.table
        else:
            ret = param
        return ret
    def _to_table_name(self,param):
        ret = self._to_table(param)
        if isinstance(ret,sa.Table):
            ret = ret.fullname
        return ret

    def _do_quote_table_identifier(self, identifier):
        return '"%s"'%identifier

    def start_alter_table(self,param):
        table = self._to_table(param)
        table_name = self._to_table_name(table)
        self.append('\nALTER TABLE %s ' % self._do_quote_table_identifier(table_name))
        return table

    def _pk_constraint(self,table,column,status):
        """Create a primary key constraint from a table, column
        Status: true if the constraint is being added; false if being dropped
        """
        if isinstance(column,basestring):
            column = getattr(table.c,name)

        ret = constraint.PrimaryKeyConstraint(*table.primary_key)
        if status:
            # Created PK
            ret.c.append(column)
        else:
            # Dropped PK
            #cons.remove(col)
            names = [c.name for c in cons.c]
            index = names.index(col.name)
            del ret.c[index]

        # Allow explicit PK name assignment
        if isinstance(pk,basestring):
            ret.name = pk
        return ret

class AlterTableVisitor(SchemaIterator,RawAlterTableVisitor):
    """Common operations for 'alter table' statements"""

from sqlalchemy.schema import ForeignKeyConstraint

class ANSIColumnGenerator(AlterTableVisitor,SchemaGenerator):
    """Extends ansisql generator for column creation (alter table add col)"""
    #def __init__(self, *args, **kwargs):
    #    dialect = None
    #    if isinstance(args[0], Connection):
    #        dialect = args[0].engine.dialect
    #    elif isinstance(args[0], Dialect):
    #        dialect = args[0]
    #    else:
    #        raise exceptions.Error("Cannot infer dialect in __init__")
    #    super(ANSIColumnGenerator, self).__init__(dialect, *args,
    # **kwargs)

    def visit_column(self,column):
        """Create a column (table already exists); #32"""
        table = self.start_alter_table(column)
        self.append(" ADD ")
        pks = table.primary_key
        colspec = self.get_column_specification(column)
        self.append(colspec)

#        if column.foreign_keys:
#            self.append(" ")
#            for fk in column.foreign_keys:
#                self.add_foreignkey(fk.constraint)
#                continue
#                self.append(";\n\t ")
#                self.define_foreign_key(fk.constraint)
#        else:
        self.execute()

    def visit_table(self,table):
        pass

class ANSIColumnDropper(AlterTableVisitor):
    """Extends ansisql dropper for column dropping (alter table drop col)"""
    def visit_column(self,column):
        """Drop a column; #33"""
        table = self.start_alter_table(column)
        self.append(' DROP COLUMN %s'%self._do_quote_column_identifier(column.name))
        self.execute()
        #if column.primary_key:
        #    cons = self._pk_constraint(table,column,False)
        #    cons.create()

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 _do_quote_column_identifier(self, identifier):
        """override this function to define how identifiers (table and column names) should be written in the sql.
        for instance, in postgres, double quotes should surround the identifier
        """
        return identifier

    def visit_table(self,param):
        """Rename a table; #38. Other ops aren't supported."""
        table,newname = param
        self.start_alter_table(table)
        self.append("RENAME TO %s"%newname)
        self.execute()

    def visit_column(self,delta):
        """Rename/change a column; #34/#35"""
        # 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)
        if 'server_default' in keys:
            # Skip 'default': only handle server-side defaults, others are managed by the app, not the db.
            self._run_subvisit(delta,self._visit_column_default)
        #if 'primary_key' in keys:
        #    #self._run_subvisit(delta,self._visit_column_primary_key)
        #    self._visit_column_primary_key(delta)
        #if 'foreign_key' in keys:
        #    self._visit_column_foreign_key(delta)
        if 'name' in keys:
            self._run_subvisit(delta,self._visit_column_name)
    def _run_subvisit(self,delta,func,col_name=None,table_name=None):
        if table_name is None:
            table_name = self._to_table(delta.table)
        if col_name is None:
            col_name = delta.current_name
        ret = func(table_name,col_name,delta)
        self.execute()
        return ret

    def _visit_column_foreign_key(self,delta):
        table = delta.table
        column = getattr(table.c,delta.current_name)
        cons = constraint.ForeignKeyConstraint(column,autoload=True)
        fk = delta['foreign_key']
        if fk:
            # For now, cons.columns is limited to one column:
            # no multicolumn FKs
            column.foreign_key = ForeignKey(*cons.columns)
        else:
            column_foreign_key = None
        cons.drop()
        cons.create()
    def _visit_column_primary_key(self,delta):
        table = delta.table
        col = getattr(table.c,delta.current_name)
        pk = delta['primary_key']
        cons = self._pk_constraint(table,col,pk)
        cons.drop()
        cons.create()
    def _visit_column_nullable(self,table_name,col_name,delta):
        nullable = delta['nullable']
        table = self._to_table(delta)
        self.start_alter_table(table_name)
        self.append("ALTER COLUMN %s "%self._do_quote_column_identifier(col_name))
        if nullable:
            self.append("DROP NOT NULL")
        else:
            self.append("SET NOT NULL")
    def _visit_column_default(self,table_name,col_name,delta):
        server_default = delta['server_default']
        # Dummy column: get_col_default_string needs a column for some reason
        dummy = sa.Column(None,None,server_default=server_default)
        default_text = self.get_column_default_string(dummy)
        self.start_alter_table(table_name)
        self.append("ALTER COLUMN %s "%self._do_quote_column_identifier(col_name))
        if default_text is not None:
            self.append("SET DEFAULT %s"%default_text)
        else:
            self.append("DROP DEFAULT")
    def _visit_column_type(self,table_name,col_name,delta):
        type = delta['type']
        if not isinstance(type,sa.types.AbstractType):
            # It's the class itself, not an instance... make an instance
            type = type()
        #type_text = type.engine_impl(self.engine).get_col_spec()
        type_text = type.dialect_impl(self.dialect).get_col_spec()
        self.start_alter_table(table_name)
        self.append("ALTER COLUMN %s TYPE %s"%(self._do_quote_column_identifier(col_name),type_text))
    def _visit_column_name(self,table_name, col_name, delta):
        new_name = delta['name']
        self.start_alter_table(table_name)
        self.append('RENAME COLUMN %s TO %s'%(self._do_quote_column_identifier(col_name), self._do_quote_column_identifier(new_name)))

    def visit_index(self,param):
        """Rename an index; #36"""
        index,newname = param
        #self.start_alter_table(index)
        #self.append("RENAME INDEX %s TO %s"%(index.name,newname))
        self.append("ALTER INDEX %s RENAME TO %s"%(index.name,newname))
        self.execute()

class ANSIConstraintCommon(AlterTableVisitor):
    """
    Migrate's constraints require a separate creation function from SA's:
    Migrate's constraints are created independently of a table; SA's are
    created at the same time as the table.
    """
    def get_constraint_name(self,cons):
        if cons.name is not None:
            ret = cons.name
        else:
            ret = cons.name = cons.autoname()
        return ret

class ANSIConstraintGenerator(ANSIConstraintCommon):
    def get_constraint_specification(self,cons,**kwargs):
        if isinstance(cons,constraint.PrimaryKeyConstraint):
            col_names = ','.join([i.name for i in cons.columns])
            ret = "PRIMARY KEY (%s)"%col_names
            if cons.name:
                # Named constraint
                ret = ("CONSTRAINT %s "%cons.name)+ret
        elif isinstance(cons,constraint.ForeignKeyConstraint):
            params = dict(
                columns=','.join([c.name for c in cons.columns]),
                reftable=cons.reftable,
                referenced=','.join([c.name for c in cons.referenced]),
                name=self.get_constraint_name(cons),
            )
            ret = "CONSTRAINT %(name)s FOREIGN KEY (%(columns)s) "\
                "REFERENCES %(reftable)s (%(referenced)s)"%params
        elif isinstance(cons,constraint.CheckConstraint):
            ret = "CHECK (%s)"%cons.sqltext
        else:
            raise exceptions.InvalidConstraintError(cons)
        return ret
    def _visit_constraint(self,constraint):
        table = self.start_alter_table(constraint)
        self.append("ADD ")
        spec = self.get_constraint_specification(constraint)
        self.append(spec)
        self.execute()

    def visit_migrate_primary_key_constraint(self,*p,**k):
        return self._visit_constraint(*p,**k)

    def visit_migrate_foreign_key_constraint(self,*p,**k):
        return self._visit_constraint(*p,**k)

    def visit_migrate_check_constraint(self,*p,**k):
        return self._visit_constraint(*p,**k)

class ANSIConstraintDropper(ANSIConstraintCommon):
    def _visit_constraint(self,constraint):
        self.start_alter_table(constraint)
        self.append("DROP CONSTRAINT ")
        self.append(self.get_constraint_name(constraint))
        self.execute()

    def visit_migrate_primary_key_constraint(self,*p,**k):
        return self._visit_constraint(*p,**k)

    def visit_migrate_foreign_key_constraint(self,*p,**k):
        return self._visit_constraint(*p,**k)

    def visit_migrate_check_constraint(self,*p,**k):
        return self._visit_constraint(*p,**k)

class ANSIFKGenerator(AlterTableVisitor,SchemaGenerator):
    """Extends ansisql generator for column creation (alter table add col)"""
    def __init__(self, *args, **kwargs):
        self.fk = kwargs.get('fk', None)
        if self.fk:
            del kwargs['fk']
        super(ANSIFKGenerator, self).__init__(*args, **kwargs)

    def visit_column(self,column):
        """Create foreign keys for a column (table already exists); #32"""

        if self.fk:
            self.add_foreignkey(self.fk.constraint)

        if self.buffer.getvalue() !='':
            self.execute()

    def visit_table(self,table):
        pass

class ANSIDialect(object):
    columngenerator = ANSIColumnGenerator
    columndropper = ANSIColumnDropper
    schemachanger = ANSISchemaChanger
    columnfkgenerator = ANSIFKGenerator

    @classmethod
    def visitor(self,name):
        return getattr(self,name)
    def reflectconstraints(self,connection,table_name):
        raise NotImplementedError()
<commit_msg>make migrate.schema.ansisql PEP8 clean and add some sphinx docstrings<commit_after>"""
   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 sqlalchemy as sa
from sqlalchemy.engine.base import Connection, Dialect
from sqlalchemy.sql.compiler import SchemaGenerator
from sqlalchemy.schema import ForeignKeyConstraint
from migrate.changeset import constraint, exceptions

SchemaIterator = sa.engine.SchemaIterator

class RawAlterTableVisitor(object):
    """Common operations for ``ALTER TABLE`` statements."""

    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 _to_table_name(self, param):
        """Returns the table name for the given param object."""
        ret = self._to_table(param)
        if isinstance(ret, sa.Table):
            ret = ret.fullname
        return ret

    def _do_quote_table_identifier(self, identifier):
        """Returns a quoted version of the given table identifier."""
        return '"%s"'%identifier

    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. This may be
          a :class:`sqlalchemy.Column`, an :class:`sqlalchemy.Index`,
          a :class:`sqlachemy.schema.Constraint`, a
          :class:`sqlalchemy.Table` or a table name string
        """
        table = self._to_table(param)
        table_name = self._to_table_name(table)
        self.append('\nALTER TABLE %s ' % \
                        self._do_quote_table_identifier(table_name))
        return table

    def _pk_constraint(self, table, column, status):
        """Create a primary key constraint from a table, column.

        Status: true if the constraint is being added; false if being dropped
        """
        if isinstance(column, basestring):
            column = getattr(table.c, name)

        ret = constraint.PrimaryKeyConstraint(*table.primary_key)
        if status:
            # Created PK
            ret.c.append(column)
        else:
            # Dropped PK
            #cons.remove(col)
            names = [c.name for c in cons.c]
            index = names.index(col.name)
            del ret.c[index]

        # Allow explicit PK name assignment
        if isinstance(pk, basestring):
            ret.name = pk
        return ret

class AlterTableVisitor(SchemaIterator, RawAlterTableVisitor):
    """Common operations for 'alter table' statements"""

class ANSIColumnGenerator(AlterTableVisitor, SchemaGenerator):
    """Extends ansisql generator for column creation (alter table add col)"""
    #def __init__(self, *args, **kwargs):
    #    dialect = None
    #    if isinstance(args[0], Connection):
    #        dialect = args[0].engine.dialect
    #    elif isinstance(args[0], Dialect):
    #        dialect = args[0]
    #    else:
    #        raise exceptions.Error("Cannot infer dialect in __init__")
    #    super(ANSIColumnGenerator, self).__init__(dialect, *args,
    # **kwargs)

    def visit_column(self, column):
        """Create a column (table already exists); #32"""
        table = self.start_alter_table(column)
        self.append(" ADD ")
        pks = table.primary_key
        colspec = self.get_column_specification(column)
        self.append(colspec)

#        if column.foreign_keys:
#            self.append(" ")
#            for fk in column.foreign_keys:
#                self.add_foreignkey(fk.constraint)
#                continue
#                self.append(";\n\t ")
#                self.define_foreign_key(fk.constraint)
#        else:
        self.execute()

    def visit_table(self, table):
        pass

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

    def visit_column(self, column):
        """Drop a column; #33"""
        table = self.start_alter_table(column)
        self.append(' DROP COLUMN %s' % \
                        self._do_quote_column_identifier(column.name))
        self.execute()
        #if column.primary_key:
        #    cons = self._pk_constraint(table,column,False)
        #    cons.create()

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 _do_quote_column_identifier(self, identifier):
        """override this function to define how identifiers (table and
        column names) should be written in the SQL.  For instance, in
        PostgreSQL, double quotes should surround the identifier
        """
        return identifier

    def visit_table(self, param):
        """Rename a table; #38. Other ops aren't supported."""
        table, newname = param
        self.start_alter_table(table)
        self.append("RENAME TO %s"%newname)
        self.execute()

    def visit_column(self, delta):
        """Rename/change a column; #34/#35"""
        # 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)
        if 'server_default' in keys:
            # Skip 'default': only handle server-side defaults, others
            # are managed by the app, not the db.
            self._run_subvisit(delta, self._visit_column_default)
        #if 'primary_key' in keys:
        #    #self._run_subvisit(delta,self._visit_column_primary_key)
        #    self._visit_column_primary_key(delta)
        #if 'foreign_key' in keys:
        #    self._visit_column_foreign_key(delta)
        if 'name' in keys:
            self._run_subvisit(delta, self._visit_column_name)

    def _run_subvisit(self, delta, func, col_name=None, table_name=None):
        if table_name is None:
            table_name = self._to_table(delta.table)
        if col_name is None:
            col_name = delta.current_name
        ret = func(table_name, col_name, delta)
        self.execute()
        return ret

    def _visit_column_foreign_key(self, delta):
        table = delta.table
        column = getattr(table.c, delta.current_name)
        cons = constraint.ForeignKeyConstraint(column, autoload=True)
        fk = delta['foreign_key']
        if fk:
            # For now, cons.columns is limited to one column:
            # no multicolumn FKs
            column.foreign_key = ForeignKey(*cons.columns)
        else:
            column_foreign_key = None
        cons.drop()
        cons.create()

    def _visit_column_primary_key(self, delta):
        table = delta.table
        col = getattr(table.c, delta.current_name)
        pk = delta['primary_key']
        cons = self._pk_constraint(table, col, pk)
        cons.drop()
        cons.create()

    def _visit_column_nullable(self, table_name, col_name, delta):
        nullable = delta['nullable']
        table = self._to_table(delta)
        self.start_alter_table(table_name)
        self.append("ALTER COLUMN %s " % \
                        self._do_quote_column_identifier(col_name))
        if nullable:
            self.append("DROP NOT NULL")
        else:
            self.append("SET NOT NULL")

    def _visit_column_default(self, table_name, col_name, delta):
        server_default = delta['server_default']
        # Dummy column: get_col_default_string needs a column for some reason
        dummy = sa.Column(None, None, server_default=server_default)
        default_text = self.get_column_default_string(dummy)
        self.start_alter_table(table_name)
        self.append("ALTER COLUMN %s " % \
                        self._do_quote_column_identifier(col_name))
        if default_text is not None:
            self.append("SET DEFAULT %s"%default_text)
        else:
            self.append("DROP DEFAULT")

    def _visit_column_type(self, table_name, col_name, delta):
        type = delta['type']
        if not isinstance(type, sa.types.AbstractType):
            # It's the class itself, not an instance... make an instance
            type = type()
        #type_text = type.engine_impl(self.engine).get_col_spec()
        type_text = type.dialect_impl(self.dialect).get_col_spec()
        self.start_alter_table(table_name)
        self.append("ALTER COLUMN %s TYPE %s" % \
                        (self._do_quote_column_identifier(col_name),
                         type_text))

    def _visit_column_name(self, table_name, col_name, delta):
        new_name = delta['name']
        self.start_alter_table(table_name)
        self.append('RENAME COLUMN %s TO %s' % \
                        (self._do_quote_column_identifier(col_name),
                         self._do_quote_column_identifier(new_name)))

    def visit_index(self, param):
        """Rename an index; #36"""
        index, newname = param
        #self.start_alter_table(index)
        #self.append("RENAME INDEX %s TO %s"%(index.name,newname))
        self.append("ALTER INDEX %s RENAME TO %s" % (index.name, newname))
        self.execute()

class ANSIConstraintCommon(AlterTableVisitor):
    """
    Migrate's constraints require a separate creation function from
    SA's: Migrate's constraints are created independently of a table;
    SA's are created at the same time as the table.
    """

    def get_constraint_name(self, cons):
        if cons.name is not None:
            ret = cons.name
        else:
            ret = cons.name = cons.autoname()
        return ret

class ANSIConstraintGenerator(ANSIConstraintCommon):

    def get_constraint_specification(self, cons, **kwargs):
        if isinstance(cons, constraint.PrimaryKeyConstraint):
            col_names = ','.join([i.name for i in cons.columns])
            ret = "PRIMARY KEY (%s)" % col_names
            if cons.name:
                # Named constraint
                ret = ("CONSTRAINT %s " % cons.name)+ret
        elif isinstance(cons, constraint.ForeignKeyConstraint):
            params = dict(
                columns=','.join([c.name for c in cons.columns]),
                reftable=cons.reftable,
                referenced=','.join([c.name for c in cons.referenced]),
                name=self.get_constraint_name(cons),
            )
            ret = "CONSTRAINT %(name)s FOREIGN KEY (%(columns)s) "\
                "REFERENCES %(reftable)s (%(referenced)s)" % params
        elif isinstance(cons, constraint.CheckConstraint):
            ret = "CHECK (%s)" % cons.sqltext
        else:
            raise exceptions.InvalidConstraintError(cons)
        return ret

    def _visit_constraint(self, constraint):
        table = self.start_alter_table(constraint)
        self.append("ADD ")
        spec = self.get_constraint_specification(constraint)
        self.append(spec)
        self.execute()

    def visit_migrate_primary_key_constraint(self, *p, **k):
        return self._visit_constraint(*p, **k)

    def visit_migrate_foreign_key_constraint(self, *p, **k):
        return self._visit_constraint(*p, **k)

    def visit_migrate_check_constraint(self, *p, **k):
        return self._visit_constraint(*p, **k)

class ANSIConstraintDropper(ANSIConstraintCommon):

    def _visit_constraint(self, constraint):
        self.start_alter_table(constraint)
        self.append("DROP CONSTRAINT ")
        self.append(self.get_constraint_name(constraint))
        self.execute()

    def visit_migrate_primary_key_constraint(self, *p, **k):
        return self._visit_constraint(*p, **k)

    def visit_migrate_foreign_key_constraint(self, *p, **k):
        return self._visit_constraint(*p, **k)

    def visit_migrate_check_constraint(self, *p, **k):
        return self._visit_constraint(*p, **k)

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

    def __init__(self, *args, **kwargs):
        self.fk = kwargs.get('fk', None)
        if self.fk:
            del kwargs['fk']
        super(ANSIFKGenerator, self).__init__(*args, **kwargs)

    def visit_column(self, column):
        """Create foreign keys for a column (table already exists); #32"""

        if self.fk:
            self.add_foreignkey(self.fk.constraint)

        if self.buffer.getvalue() !='':
            self.execute()

    def visit_table(self, table):
        pass

class ANSIDialect(object):
    columngenerator = ANSIColumnGenerator
    columndropper = ANSIColumnDropper
    schemachanger = ANSISchemaChanger
    columnfkgenerator = ANSIFKGenerator

    @classmethod
    def visitor(self, name):
        return getattr(self, name)

    def reflectconstraints(self, connection, table_name):
        raise NotImplementedError()
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db.models import Max
from django.forms.fields import CharField
from django.template.loader import select_template
from django.utils.html import format_html, strip_tags, strip_entities
from django.utils.safestring import mark_safe
from django.utils.text import Truncator
from django.utils.translation import ugettext_lazy as _
try:
    from html.parser import HTMLParser  # py3
except ImportError:
    from HTMLParser import HTMLParser  # py2
from cms.plugin_pool import plugin_pool
from djangocms_text_ckeditor.widgets import TextEditorWidget
from djangocms_text_ckeditor.utils import plugin_tags_to_user_html
from cmsplugin_cascade.fields import PartialFormField
from cmsplugin_cascade.link.cms_plugins import TextLinkPlugin
from cmsplugin_cascade.link.forms import LinkForm, TextLinkFormMixin
from cmsplugin_cascade.link.plugin_base import LinkElementMixin
from cmsplugin_cascade.bootstrap3.buttons import BootstrapButtonMixin
from shop import settings as shop_settings
from shop.models.cart import CartModel
from shop.modifiers.pool import cart_modifiers_pool
from .plugin_base import ShopButtonPluginBase, DialogFormPluginBase

class ProceedButtonForm(TextLinkFormMixin, LinkForm):
    link_content = CharField(label=_("Putton Content"))
    LINK_TYPE_CHOICES = (('cmspage', _("CMS Page")), ('RELOAD_PAGE', _("Reload Page")), ('PURCHASE_NOW', _("Purchase Now")),)

class ShopProceedButton(BootstrapButtonMixin, ShopButtonPluginBase):
    """
    This button is used to proceed from one checkout step to the next one.
    """
    name = _("Proceed Button")
    parent_classes = ('BootstrapColumnPlugin',)
    model_mixins = (LinkElementMixin,)

    def get_form(self, request, obj=None, **kwargs):
        kwargs.update(form=ProceedButtonForm)
        return super(ShopProceedButton, self).get_form(request, obj, **kwargs)

    def get_render_template(self, context, instance, placeholder):
        template_names = [
            '{}/checkout/proceed-button.html'.format(shop_settings.APP_LABEL),
            'shop/checkout/proceed-button.html',
        ]
        return select_template(template_names)

plugin_pool.register_plugin(ShopProceedButton)

class CustomerFormPlugin(DialogFormPluginBase):
    """
    provides the form to edit customer specific data stored in model `Customer`.
    """
    name = _("Customer Form")
    form_class = 'shop.forms.checkout.CustomerForm'
    template_leaf_name = 'customer.html'

    def get_form_data(self, request):
        return {'instance': request.user}

DialogFormPluginBase.register_plugin(CustomerFormPlugin)

class GuestFormPlugin(CustomerFormPlugin):
    name = _("Guest Form")
    form_class = 'shop.forms.checkout.GuestForm'

DialogFormPluginBase.register_plugin(GuestFormPlugin)

class CheckoutAddressPluginBase(DialogFormPluginBase):
    def get_form_data(self, request):
        filter_args = {'user': request.user, '{}__isnull'.format(self.FormClass.priority_field): False}
        AddressModel = self.FormClass.get_model()
        address = AddressModel.objects.filter(**filter_args).order_by(self.FormClass.priority_field).first()
        if address:
            return {'instance': address}
        else:
            aggr = AddressModel.objects.filter(user=request.user).aggregate(Max(self.FormClass.priority_field))
            initial = {'priority': aggr['{}__max'.format(self.FormClass.priority_field)] or 0}
            return {'initial': initial}

class ShippingAddressFormPlugin(CheckoutAddressPluginBase):
    name = _("Shipping Address Form")
    form_class = 'shop.forms.checkout.ShippingAddressForm'
    template_leaf_name = 'shipping-address.html'

DialogFormPluginBase.register_plugin(ShippingAddressFormPlugin)

class BillingAddressFormPlugin(CheckoutAddressPluginBase):
    name = _("Billing Address Form")
    form_class = 'shop.forms.checkout.BillingAddressForm'
    template_leaf_name = 'billing-address.html'

DialogFormPluginBase.register_plugin(BillingAddressFormPlugin)

class PaymentMethodFormPlugin(DialogFormPluginBase):
    name = _("Payment Method Form")
    form_class = 'shop.forms.checkout.PaymentMethodForm'
    template_leaf_name = 'payment-method.html'

    def get_form_data(self, request):
        # if there is only one payment method available, always set it as default
        payment_modifier_choices = self.FormClass.base_fields['payment_modifier'].choices
        if len(payment_modifier_choices) == 1:
            default_payment_modifier = payment_modifier_choices[0][0]
        else:
            default_payment_modifier = None
        cart = CartModel.objects.get_from_request(request)
        initial = {'payment_modifier': cart.extra.get('payment_modifier', default_payment_modifier)}
        return {'initial': initial}

    def render(self, context, instance, placeholder):
        super(PaymentMethodFormPlugin, self).render(context, instance, placeholder)
        for payment_modifier in cart_modifiers_pool.get_payment_modifiers():
            payment_modifier.update_render_context(context)
        return context

if cart_modifiers_pool.get_payment_modifiers():
    # Plugin is registered only if at least one payment modifier exists
    DialogFormPluginBase.register_plugin(PaymentMethodFormPlugin)

class ShippingMethodFormPlugin(DialogFormPluginBase):
    name = _("Shipping Method Form")
    form_class = 'shop.forms.checkout.ShippingMethodForm'
    template_leaf_name = 'shipping-method.html'

    def get_form_data(self, request):
        # if there is only one shipping method available, always set it as default
        shipping_modifier_choices = self.FormClass.base_fields['shipping_modifier'].choices
        if len(shipping_modifier_choices) == 1:
            default_shipping_modifier = shipping_modifier_choices[0][0]
        else:
            default_shipping_modifier = None
        cart = CartModel.objects.get_from_request(request)
        initial = {'shipping_modifier': cart.extra.get('shipping_modifier', default_shipping_modifier)}
        return {'initial': initial}

    def render(self, context, instance, placeholder):
        super(ShippingMethodFormPlugin, self).render(context, instance, placeholder)
        for shipping_modifier in cart_modifiers_pool.get_shipping_modifiers():
            shipping_modifier.update_render_context(context)
        return context

if cart_modifiers_pool.get_shipping_modifiers():
    # Plugin is registered only if at least one shipping modifier exists
    DialogFormPluginBase.register_plugin(ShippingMethodFormPlugin)

class ExtraAnnotationFormPlugin(DialogFormPluginBase):
    name = _("Extra Annotation Form")
    form_class = 'shop.forms.checkout.ExtraAnnotationForm'
    template_leaf_name = 'extra-annotation.html'

    def get_form_data(self, request):
        cart = CartModel.objects.get_from_request(request)
        initial = {'annotation': cart.extra.get('annotation', '')}
        return {'initial': initial}

DialogFormPluginBase.register_plugin(ExtraAnnotationFormPlugin)

class AcceptConditionFormPlugin(DialogFormPluginBase):
    """
    Provides the form to accept any condition.
    """
    name = _("Accept Condition")
    form_class = 'shop.forms.checkout.AcceptConditionForm'
    template_leaf_name = 'accept-condition.html'
    html_parser = HTMLParser()
    change_form_template = 'cascade/admin/text_plugin_change_form.html'

    @classmethod
    def get_identifier(cls, instance):
        identifier = super(AcceptConditionFormPlugin, cls).get_identifier(instance)
        html_content = cls.html_parser.unescape(instance.glossary.get('html_content', ''))
        html_content = strip_entities(strip_tags(html_content))
        html_content = Truncator(html_content).words(3, truncate=' ...')
        return format_html('{}{}', identifier, html_content)

    def get_form(self, request, obj=None, **kwargs):
        if obj:
            html_content = self.html_parser.unescape(obj.glossary.get('html_content', ''))
            obj.glossary.update(html_content=html_content)
            text_editor_widget = TextEditorWidget(installed_plugins=[TextLinkPlugin], pk=obj.pk,
                                           placeholder=obj.placeholder, plugin_language=obj.language)
            kwargs['glossary_fields'] = (
                PartialFormField('html_content', text_editor_widget, label=_("HTML content")),
            )
        return super(AcceptConditionFormPlugin, self).get_form(request, obj, **kwargs)

    def render(self, context, instance, placeholder):
        super(AcceptConditionFormPlugin, self).render(context, instance, placeholder)
        accept_condition_form = context['accept_condition_form.plugin_{}'.format(instance.id)]
        html_content = self.html_parser.unescape(instance.glossary.get('html_content', ''))
        html_content = plugin_tags_to_user_html(html_content, context, placeholder)
        # transfer the stored HTML content into the widget's label
        accept_condition_form['accept'].field.widget.choice_label = mark_safe(html_content)
        context['accept_condition_form'] = accept_condition_form
        return context

DialogFormPluginBase.register_plugin(AcceptConditionFormPlugin)
<commit_msg>fixed typo<commit_after># -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db.models import Max
from django.forms.fields import CharField
from django.template.loader import select_template
from django.utils.html import format_html, strip_tags, strip_entities
from django.utils.safestring import mark_safe
from django.utils.text import Truncator
from django.utils.translation import ugettext_lazy as _
try:
    from html.parser import HTMLParser  # py3
except ImportError:
    from HTMLParser import HTMLParser  # py2
from cms.plugin_pool import plugin_pool
from djangocms_text_ckeditor.widgets import TextEditorWidget
from djangocms_text_ckeditor.utils import plugin_tags_to_user_html
from cmsplugin_cascade.fields import PartialFormField
from cmsplugin_cascade.link.cms_plugins import TextLinkPlugin
from cmsplugin_cascade.link.forms import LinkForm, TextLinkFormMixin
from cmsplugin_cascade.link.plugin_base import LinkElementMixin
from cmsplugin_cascade.bootstrap3.buttons import BootstrapButtonMixin
from shop import settings as shop_settings
from shop.models.cart import CartModel
from shop.modifiers.pool import cart_modifiers_pool
from .plugin_base import ShopButtonPluginBase, DialogFormPluginBase

class ProceedButtonForm(TextLinkFormMixin, LinkForm):
    link_content = CharField(label=_("Button Content"))
    LINK_TYPE_CHOICES = (('cmspage', _("CMS Page")), ('RELOAD_PAGE', _("Reload Page")), ('PURCHASE_NOW', _("Purchase Now")),)

class ShopProceedButton(BootstrapButtonMixin, ShopButtonPluginBase):
    """
    This button is used to proceed from one checkout step to the next one.
    """
    name = _("Proceed Button")
    parent_classes = ('BootstrapColumnPlugin',)
    model_mixins = (LinkElementMixin,)

    def get_form(self, request, obj=None, **kwargs):
        kwargs.update(form=ProceedButtonForm)
        return super(ShopProceedButton, self).get_form(request, obj, **kwargs)

    def get_render_template(self, context, instance, placeholder):
        template_names = [
            '{}/checkout/proceed-button.html'.format(shop_settings.APP_LABEL),
            'shop/checkout/proceed-button.html',
        ]
        return select_template(template_names)

plugin_pool.register_plugin(ShopProceedButton)

class CustomerFormPlugin(DialogFormPluginBase):
    """
    provides the form to edit customer specific data stored in model `Customer`.
    """
    name = _("Customer Form")
    form_class = 'shop.forms.checkout.CustomerForm'
    template_leaf_name = 'customer.html'

    def get_form_data(self, request):
        return {'instance': request.user}

DialogFormPluginBase.register_plugin(CustomerFormPlugin)

class GuestFormPlugin(CustomerFormPlugin):
    name = _("Guest Form")
    form_class = 'shop.forms.checkout.GuestForm'

DialogFormPluginBase.register_plugin(GuestFormPlugin)

class CheckoutAddressPluginBase(DialogFormPluginBase):
    def get_form_data(self, request):
        filter_args = {'user': request.user, '{}__isnull'.format(self.FormClass.priority_field): False}
        AddressModel = self.FormClass.get_model()
        address = AddressModel.objects.filter(**filter_args).order_by(self.FormClass.priority_field).first()
        if address:
            return {'instance': address}
        else:
            aggr = AddressModel.objects.filter(user=request.user).aggregate(Max(self.FormClass.priority_field))
            initial = {'priority': aggr['{}__max'.format(self.FormClass.priority_field)] or 0}
            return {'initial': initial}

class ShippingAddressFormPlugin(CheckoutAddressPluginBase):
    name = _("Shipping Address Form")
    form_class = 'shop.forms.checkout.ShippingAddressForm'
    template_leaf_name = 'shipping-address.html'

DialogFormPluginBase.register_plugin(ShippingAddressFormPlugin)

class BillingAddressFormPlugin(CheckoutAddressPluginBase):
    name = _("Billing Address Form")
    form_class = 'shop.forms.checkout.BillingAddressForm'
    template_leaf_name = 'billing-address.html'

DialogFormPluginBase.register_plugin(BillingAddressFormPlugin)

class PaymentMethodFormPlugin(DialogFormPluginBase):
    name = _("Payment Method Form")
    form_class = 'shop.forms.checkout.PaymentMethodForm'
    template_leaf_name = 'payment-method.html'

    def get_form_data(self, request):
        # if there is only one payment method available, always set it as default
        payment_modifier_choices = self.FormClass.base_fields['payment_modifier'].choices
        if len(payment_modifier_choices) == 1:
            default_payment_modifier = payment_modifier_choices[0][0]
        else:
            default_payment_modifier = None
        cart = CartModel.objects.get_from_request(request)
        initial = {'payment_modifier': cart.extra.get('payment_modifier', default_payment_modifier)}
        return {'initial': initial}

    def render(self, context, instance, placeholder):
        super(PaymentMethodFormPlugin, self).render(context, instance, placeholder)
        for payment_modifier in cart_modifiers_pool.get_payment_modifiers():
            payment_modifier.update_render_context(context)
        return context

if cart_modifiers_pool.get_payment_modifiers():
    # Plugin is registered only if at least one payment modifier exists
    DialogFormPluginBase.register_plugin(PaymentMethodFormPlugin)

class ShippingMethodFormPlugin(DialogFormPluginBase):
    name = _("Shipping Method Form")
    form_class = 'shop.forms.checkout.ShippingMethodForm'
    template_leaf_name = 'shipping-method.html'

    def get_form_data(self, request):
        # if there is only one shipping method available, always set it as default
        shipping_modifier_choices = self.FormClass.base_fields['shipping_modifier'].choices
        if len(shipping_modifier_choices) == 1:
            default_shipping_modifier = shipping_modifier_choices[0][0]
        else:
            default_shipping_modifier = None
        cart = CartModel.objects.get_from_request(request)
        initial = {'shipping_modifier': cart.extra.get('shipping_modifier', default_shipping_modifier)}
        return {'initial': initial}

    def render(self, context, instance, placeholder):
        super(ShippingMethodFormPlugin, self).render(context, instance, placeholder)
        for shipping_modifier in cart_modifiers_pool.get_shipping_modifiers():
            shipping_modifier.update_render_context(context)
        return context

if cart_modifiers_pool.get_shipping_modifiers():
    # Plugin is registered only if at least one shipping modifier exists
    DialogFormPluginBase.register_plugin(ShippingMethodFormPlugin)

class ExtraAnnotationFormPlugin(DialogFormPluginBase):
    name = _("Extra Annotation Form")
    form_class = 'shop.forms.checkout.ExtraAnnotationForm'
    template_leaf_name = 'extra-annotation.html'

    def get_form_data(self, request):
        cart = CartModel.objects.get_from_request(request)
        initial = {'annotation': cart.extra.get('annotation', '')}
        return {'initial': initial}

DialogFormPluginBase.register_plugin(ExtraAnnotationFormPlugin)

class AcceptConditionFormPlugin(DialogFormPluginBase):
    """
    Provides the form to accept any condition.
    """
    name = _("Accept Condition")
    form_class = 'shop.forms.checkout.AcceptConditionForm'
    template_leaf_name = 'accept-condition.html'
    html_parser = HTMLParser()
    change_form_template = 'cascade/admin/text_plugin_change_form.html'

    @classmethod
    def get_identifier(cls, instance):
        identifier = super(AcceptConditionFormPlugin, cls).get_identifier(instance)
        html_content = cls.html_parser.unescape(instance.glossary.get('html_content', ''))
        html_content = strip_entities(strip_tags(html_content))
        html_content = Truncator(html_content).words(3, truncate=' ...')
        return format_html('{}{}', identifier, html_content)

    def get_form(self, request, obj=None, **kwargs):
        if obj:
            html_content = self.html_parser.unescape(obj.glossary.get('html_content', ''))
            obj.glossary.update(html_content=html_content)
            text_editor_widget = TextEditorWidget(installed_plugins=[TextLinkPlugin], pk=obj.pk,
                                           placeholder=obj.placeholder, plugin_language=obj.language)
            kwargs['glossary_fields'] = (
                PartialFormField('html_content', text_editor_widget, label=_("HTML content")),
            )
        return super(AcceptConditionFormPlugin, self).get_form(request, obj, **kwargs)

    def render(self, context, instance, placeholder):
        super(AcceptConditionFormPlugin, self).render(context, instance, placeholder)
        accept_condition_form = context['accept_condition_form.plugin_{}'.format(instance.id)]
        html_content = self.html_parser.unescape(instance.glossary.get('html_content', ''))
        html_content = plugin_tags_to_user_html(html_content, context, placeholder)
        # transfer the stored HTML content into the widget's label
        accept_condition_form['accept'].field.widget.choice_label = mark_safe(html_content)
        context['accept_condition_form'] = accept_condition_form
        return context

DialogFormPluginBase.register_plugin(AcceptConditionFormPlugin)
<|endoftext|>


<commit_before>from tastypie.resources import ModelResource, ALL, ALL_WITH_RELATIONS
from tastypie import fields
from datea_api.apps.api.authorization import DateaBaseAuthorization
from datea_api.apps.api.authentication import ApiKeyPlusWebAuthentication
from datea_api.apps.api.base_resources import JSONDefaultMixin
from tastypie.authentication import ApiKeyAuthentication
from tastypie.cache import SimpleCache
from tastypie.throttle import CacheThrottle
from datea_api.apps.account.utils import get_domain_from_url
from django.contrib.contenttypes.models import ContentType
from tastypie.exceptions import ImmediateHttpResponse
from datea_api.apps.api.status_codes import *

from .models import Vote

class VoteResource(JSONDefaultMixin, ModelResource):

    user = fields.ToOneField('datea_api.apps.account.resources.UserResource',
            attribute='user', full=False, readonly=True)

    def hydrate(self, bundle):
        if bundle.request.method == 'POST':
            bundle.obj.user = bundle.data['user'] = bundle.request.user
            bundle.obj.client_domain = bundle.data['client_domain'] = get_domain_from_url(bundle.request.META.get("HTTP_ORIGIN", ""))

            # cannot vote one's own object
            if 'vote_key' in  bundle.data:
                model, pk = bundle.data['vote_key']
            elif 'content_type' in bundle.data and 'object_id' in bundle.data:
                model = bundle.data['content_type']
                pk = bundle.data['object_id']

            ctype = ContentType.objects.get(model=model)
            target_obj = ctype.get_object_for_this_type(pk=int(pk))

            if request.user.id == target_obj.user.id:
                response = self.create_response(bundle.request,{'status': BAD_REQUEST,
                        'error': 'not on own objects'}, status=BAD_REQUEST)
                raise ImmediateHttpResponse(response=response)

            bundle.obj.content_type = ctype

        return bundle

    def dehydrate(self, bundle):
        bundle.data['content_type'] = bundle.obj.content_type.model
        return bundle

    def get_object_list(self, request):
        if 'content_type' in request.GET:
            return super(VoteResource, self).get_object_list(request).filter(content_type__model=request.GET.get('content_type'))
        else:
            return super(VoteResource, self).get_object_list(request)

    class Meta:
        queryset = Vote.objects.all()
        resource_name = 'vote'
        allowed_methods = ['get','post','delete']
        excludes = ['client_domain']
        filtering = {
            'user': ALL_WITH_RELATIONS,
            'vote_key': ALL,
            'object_id': ALL,
            'content_type': ALL_WITH_RELATIONS
        }
        authentication = ApiKeyPlusWebAuthentication()
        authorization = DateaBaseAuthorization()
        always_return_data = True
        cache = SimpleCache(timeout=10)
        throttle = CacheThrottle()<commit_msg>debug vote rsrc<commit_after>from tastypie.resources import ModelResource, ALL, ALL_WITH_RELATIONS
from tastypie import fields
from datea_api.apps.api.authorization import DateaBaseAuthorization
from datea_api.apps.api.authentication import ApiKeyPlusWebAuthentication
from datea_api.apps.api.base_resources import JSONDefaultMixin
from tastypie.authentication import ApiKeyAuthentication
from tastypie.cache import SimpleCache
from tastypie.throttle import CacheThrottle
from datea_api.apps.account.utils import get_domain_from_url
from django.contrib.contenttypes.models import ContentType
from tastypie.exceptions import ImmediateHttpResponse
from datea_api.apps.api.status_codes import *

from .models import Vote

class VoteResource(JSONDefaultMixin, ModelResource):

    user = fields.ToOneField('datea_api.apps.account.resources.UserResource',
            attribute='user', full=False, readonly=True)

    def hydrate(self, bundle):
        if bundle.request.method == 'POST':
            bundle.obj.user = bundle.data['user'] = bundle.request.user
            bundle.obj.client_domain = bundle.data['client_domain'] = get_domain_from_url(bundle.request.META.get("HTTP_ORIGIN", ""))

            # cannot vote one's own object
            if 'vote_key' in  bundle.data:
                model, pk = bundle.data['vote_key'].split('.')
            elif 'content_type' in bundle.data and 'object_id' in bundle.data:
                model = bundle.data['content_type']
                pk = bundle.data['object_id']

            ctype = ContentType.objects.get(model=model)
            target_obj = ctype.get_object_for_this_type(pk=int(pk))

            if request.user.id == target_obj.user.id:
                response = self.create_response(bundle.request,{'status': BAD_REQUEST,
                        'error': 'not on own objects'}, status=BAD_REQUEST)
                raise ImmediateHttpResponse(response=response)

            bundle.obj.content_type = ctype

        return bundle

    def dehydrate(self, bundle):
        bundle.data['content_type'] = bundle.obj.content_type.model
        return bundle

    def get_object_list(self, request):
        if 'content_type' in request.GET:
            return super(VoteResource, self).get_object_list(request).filter(content_type__model=request.GET.get('content_type'))
        else:
            return super(VoteResource, self).get_object_list(request)

    class Meta:
        queryset = Vote.objects.all()
        resource_name = 'vote'
        allowed_methods = ['get','post','delete']
        excludes = ['client_domain']
        filtering = {
            'user': ALL_WITH_RELATIONS,
            'vote_key': ALL,
            'object_id': ALL,
            'content_type': ALL_WITH_RELATIONS
        }
        authentication = ApiKeyPlusWebAuthentication()
        authorization = DateaBaseAuthorization()
        always_return_data = True
        cache = SimpleCache(timeout=10)
        throttle = CacheThrottle()<|endoftext|>


<commit_before>'''
Created on 26.06.2011

@author: michi
'''

from sqlalchemy.orm import joinedload, joinedload_all, object_mapper, \
    RelationshipProperty, ColumnProperty, aliased, contains_eager

from ems.util import GenClause   #@UnresolvedImport
from ems.thirdparty.odict import OrderedDict

from sqlalchemy.util import symbol
from sqlalchemy.sql.expression import and_, or_
from lib.ems.model.sa.orm.base_object import OrmBaseObject

class PathClauseList(object):
    def __init__(self, conjunction, initialClauses=[]):
        self.conjunction = conjunction
        self.clauses = []

        #self.clauses = initialClauses

    def append(self, clause):
        self.clauses.append(clause)

#    def __str__(self):
#        conjString = ""
#        conjStrings = []
#        #return str(id(self))
#        for clause in self.clauses:
#            conjStrings.append(conjString)
#            conjStrings.append(str(clause))
#            conjString = " " + self.conjunction + " "
#
#
#        return "".join(conjStrings)
#
#    def __repr__(self):
#        return self.__str__()

class AndList(PathClauseList):
    def __init__(self, *args):
        super(AndList, self).__init__('AND',*args)

class OrList(PathClauseList):
    def __init__(self, *args):
        super(OrList, self).__init__('OR',*args)

class PathClause(GenClause):
    def like(self, other):
        self.operator = 'like'
        self.right = other
        return self

    def notLike(self, other):
        self.operator = 'notLike'
        self.right = other
        return self

class SAQueryBuilder(object):

    def __init__(self, ormObj, filterJoins=()):
        self._ormObj = ormObj
        self._mapper = object_mapper(self._ormObj)
        self._propertyNames = []
        self._propertyNamesDecorated = []
        self._properties = {}
        self._joinNames = []
        self._joinNameClasses = {}
        self._propertyNameClasses = {}
        self._filterJoins = filterJoins

    @property
    def ormObj(self):
        return self._ormObj

    def getPropertyNames(self):
        if not len(self._propertyNames):
            self._extractPropertiesAndJoins(self._ormObj)
        return self._propertyNames

    propertyNames = property(getPropertyNames)

    def getPropertyNamesDecorated(self):
        if not len(self._propertyNamesDecorated):
            self._loadPropertyNamesDecorated()
        return self._propertyNamesDecorated

    propertyNamesDecorated = property(getPropertyNamesDecorated)

    def _loadPropertyNamesDecorated(self, ormClass=None, pathStack=[]):

        if ormClass is None:
            ormClass = self._ormObj.__class__

        dec = ormClass.__ormDecorator__()

        for propertyName in dec.getShownProperties():

            pathStack.append(propertyName)
            propertyPath = ".".join(pathStack)

            if self.properties.has_key(propertyPath):
                if isinstance(self.properties[propertyPath],
                              RelationshipProperty):
                    if propertyPath in self.joinNames:
                        joinClass = self.joinNameClasses[propertyPath]
                        self._propertyNamesDecorated.append(propertyPath)
                        self._loadPropertyNamesDecorated(joinClass, pathStack)

                if isinstance(self.properties[propertyPath],
                              ColumnProperty):
                    self._propertyNamesDecorated.append(propertyPath)

            pathStack.pop()
        return self._propertyNamesDecorated

    def _addJoinPath(self, joins, joinPath):
        pathName = []
        for pathPart in joinPath.split('.'):
            pathName.append(pathPart)
            path = ".".join(pathName)
            if path not in joins:
                joins.append(path)

    def _calculateJoinNames(self, propertyNames, filter):
        joins = []
        filterPropertyNames = []
        if not len(self._properties):
            self._extractPropertiesAndJoins(self._ormObj)

        if isinstance(filter, PathClauseList):
            #raise NotImplementedError("Or und And kommt noch")
            filterPropertyNames = self._extractPropertyNamesFromClauseList(filter, [])
        if isinstance(filter, PathClause):
            filterPropertyNames = [filter.left]

        for propertyName in filterPropertyNames:
            property = self.properties[propertyName]
            if isinstance(property, RelationshipProperty):
#                print "%s is RelationShip" % propertyName
                self._addJoinPath(joins, propertyName)
            else:
#                print "%s is Column" % propertyName
                split = propertyName.split('.')
                if len(split):
                    parentName = ".".join(split[:-1])
                    if parentName in self.joinNames:
                        self._addJoinPath(joins, parentName)
                        #if parentName not in joins:
                        #    joins.append(parentName)
                else:
                    parentName = None

                #print self.properties[propertyName]
                #print "prop: %s parent: %s" % (propertyName, parentName)

        for propertyName in propertyNames:
            property = self.properties[propertyName]
            if isinstance(property, RelationshipProperty):
                self._addJoinPath(joins, propertyName)
            else:
                split = propertyName.split('.')
                if len(split) > 1:
                    parentName = ".".join(split[:-1])
                    self._addJoinPath(joins, parentName)
#                    if parentName not in joins:
#                        joins.append(parentName)
            #print split

        #print joins
        return joins

    def _buildJoinAliases(self, joinNames):
        sortedAliases = OrderedDict()
        aliases = {}
        for joinName in joinNames:
            #print self.joinNameClasses[joinName]
            aliases[joinName] = aliased(self.joinNameClasses[joinName])
        return aliases

    def _extractPropertyNamesFromClauseList(self, clauseList, propertyNames=[]):

        for clause in clauseList.clauses:
            if isinstance(clause, PathClauseList):
                self._extractPropertyNamesFromClauseList(clause, propertyNames)
            elif isinstance(clause, PathClause):
                if clause.left not in propertyNames:
                    propertyNames.append(clause.left)

        return propertyNames

    def getQuery(self, session, propertySelection=[], appendOptions=None, filter=None):

        if not len(propertySelection):
            propertySelection = self.propertyNamesDecorated

        uniqueJoinNames = self._calculateJoinNames(propertySelection, filter)
#        print uniqueJoinNames
        aliases = self._buildJoinAliases(uniqueJoinNames)
        joinNames = aliases.keys()
        joinNames.sort()

        query = session.query(self._ormObj.__class__)

        for joinName in joinNames:
            parentClass = None
            split = joinName.split('.')
            #print split
            if len(split) > 1:
                propertyName = split[-1:][0]
                parentName = ".".join(split[:-1])
                parentClass = aliases[parentName]
                query = query.outerjoin((aliases[joinName],parentClass.__getattr__(propertyName)))
            else:
                parentClass = self._ormObj.__class__
                propertyName = joinName
                query = query.outerjoin((aliases[joinName],parentClass.__dict__[propertyName]))

        query = self._addFilter(query, filter, aliases)
        containsEagers = self._getContainsEagers(propertySelection, aliases,
                                                joinNames)

        if len(containsEagers):
            if appendOptions is not None:
                for option in appendOptions:
                    containsEagers.append(option)
            query = query.options(*containsEagers)
#        print str(query).replace('LEFT OUTER JOIN', '\nLEFT OUTER JOIN')
        return query

    def _getContainsEagers(self, propertyNames, aliases, joinNames):
        neededJoins = []
#        print "PropertyNames:"
        for propertyName in propertyNames:
#            print propertyName
            property = self.properties[propertyName]
            if isinstance(property, RelationshipProperty):
                #print "Relationship: %s" % propertyName
                split = propertyName.split('.')
                stack = []
                for part in split:
                    stack.append(part)
                    stackedProperty = u".".join(stack)
                    #print "Einzeln: %s" % stackedProperty
                    if stackedProperty not in neededJoins:
                        neededJoins.append(stackedProperty)
                        #print "I add %s" % stackedProperty

            elif isinstance(property, ColumnProperty):
#                print "ColumnProperty: %s" % propertyName
                split = propertyName.split('.')
                if len(split) > 1:
                    joinSplit = split[:-1]
                    #print "joinName of Property: %s" % joinName
                    stack = []
                    for part in joinSplit:
                        stack.append(part)
                        stackedProperty = u".".join(stack)
                        if stackedProperty not in neededJoins:
                            neededJoins.append(stackedProperty)
#                    if joinName not in neededJoins:
#                        neededJoins.append(joinName)

        containsEagers = []
#        print "neededJoins:"
        for neededJoin in neededJoins:
#            print neededJoin
            containsEagers.append(contains_eager(neededJoin, alias=aliases[neededJoin]))

        return containsEagers

    def _addFilter(self, query, filter, aliases):
        #print filter
        if isinstance(filter, PathClauseList):
            saClauseList = self._convertPathClauseList(filter, aliases)
            query = query.filter(saClauseList)

        if isinstance(filter, PathClause):
            try:
                clause = self._convertPathClause(filter, aliases)
                query = query.filter(clause)
            except:
                print "Cannot convert PathClause %s" % clause

        return query

    def _convertPathClauseList(self, clauseList, aliases, parentClause=None):
        if isinstance(clauseList, PathClauseList):
            if clauseList.conjunction == "AND":
                saClause = and_()
            elif clauseList.conjunction == 'OR':
                saClause = or_()
            for clause in clauseList.clauses:
                if isinstance(clause, PathClause):
                    try:
                        saClause.append(self._convertPathClause(clause, aliases))
                    except:
                         print "Cannot convert PathClause %s" % clause

                elif isinstance(clause, PathClauseList):
                    saClause.append(self._convertPathClauseList(clause, aliases, saClause))
            return saClause

    def _convertPathClause(self, clause, aliases):

        property = self.properties[clause.left]
        if isinstance(property, ColumnProperty):
            split = clause.left.split('.')
            if len(split) < 2:
                return self._ormObj.__class__.__dict__[clause.left].__getattribute__(self._translateOperator(clause.operator))(clause.right)
            else:
                parentName = ".".join(split[:-1])
                propName = split[-1:][0]
                method = self._translateOperator(clause.operator)
                return aliases[parentName].__getattr__(propName).__getattr__(method)(clause.right)

        if isinstance(property, RelationshipProperty):
            split = clause.left.split('.')
#            print clause
#            if not isinstance(clause.right, OrmBaseObject):
#                return
#            print type(clause.right)
            if len(split) < 2:
                return self._ormObj.__class__.__dict__[clause.left].__eq__(clause.right)
            else:
                parentName = ".".join(split[:-1])
                propName = split[-1:][0]

                return aliases[parentName].__getattr__(propName).__eq__(clause.right)

    def _translateOperator(self, operator):
        table = {'==':'__eq__',
         '!=':'__ne__',
         '<':'__lt__',
         '<=':'__le__',
         '>':'__gt__',
         '>=':'__ge__',
         'like':'like',
         'BEFORE':'<',
         'AFTER':'>'}
        return table[operator]
    @property
    def propertyName2Class(self):
        return self._propertyNameClasses

    @property
    def properties(self):
        if not len(self._properties):
            self._extractPropertiesAndJoins(self._ormObj)
        return self._properties

    def getJoinNames(self):
        if not len(self._joinNames):
            self._extractPropertiesAndJoins(self._ormObj)

        return self._joinNames

    joinNames = property(getJoinNames)

    def getJoinNameClasses(self):
        if not len(self._joinNameClasses):
            self._extractPropertiesAndJoins(self._ormObj)
        return self._joinNameClasses

    joinNameClasses = property(getJoinNameClasses)

    def _extractPropertiesAndJoins(self, obj, pathStack=[],
                                        alreadyAddedClasses=[],
                                        recursionCounter = -1):
        recursionCounter += 1
        if recursionCounter == 0:
            pathStack = []
            alreadyAddedClasses = []

        if recursionCounter > 100:
            raise StopIteration()

        mapper = object_mapper(obj)
        alreadyAddedClasses.append(obj.__class__)

        for prop in mapper.iterate_properties:
            if isinstance(prop, ColumnProperty):
                if len(pathStack):
                    propertyName = "%s.%s" % (".".join(pathStack), prop.key)
                else:
                    propertyName = prop.key
                self._propertyNames.append(propertyName)
                self._properties[propertyName] = prop
                self._propertyNameClasses[propertyName] = obj.__class__

            elif isinstance(prop, RelationshipProperty):
                if not prop.uselist:
                    classType = prop.mapper.class_
                    if classType is not self._ormObj.__class__:
                        if len(pathStack):
                            joinName = "%s.%s" % (".".join(pathStack), prop.key)
                        else:
                            joinName = prop.key

                        if joinName not in self._joinNames:
                            self._joinNames.append(joinName)

                        self._propertyNames.append(joinName)
                        self._propertyNameClasses[joinName] = obj.__class__
                        self._properties[joinName] = prop

                        pathStack.append(prop.key)
                        self._joinNameClasses[joinName] = classType
                        if classType not in alreadyAddedClasses:
                            self._extractPropertiesAndJoins(classType(), pathStack,
                                                           alreadyAddedClasses,
                                                           recursionCounter)

                        pathStack.pop()<commit_msg>Kleinen Einrückfehler behoben<commit_after>'''
Created on 26.06.2011

@author: michi
'''

from sqlalchemy.orm import joinedload, joinedload_all, object_mapper, \
    RelationshipProperty, ColumnProperty, aliased, contains_eager

from ems.util import GenClause   #@UnresolvedImport
from ems.thirdparty.odict import OrderedDict

from sqlalchemy.util import symbol
from sqlalchemy.sql.expression import and_, or_
from lib.ems.model.sa.orm.base_object import OrmBaseObject

class PathClauseList(object):
    def __init__(self, conjunction, initialClauses=[]):
        self.conjunction = conjunction
        self.clauses = []

        #self.clauses = initialClauses

    def append(self, clause):
        self.clauses.append(clause)

#    def __str__(self):
#        conjString = ""
#        conjStrings = []
#        #return str(id(self))
#        for clause in self.clauses:
#            conjStrings.append(conjString)
#            conjStrings.append(str(clause))
#            conjString = " " + self.conjunction + " "
#
#
#        return "".join(conjStrings)
#
#    def __repr__(self):
#        return self.__str__()

class AndList(PathClauseList):
    def __init__(self, *args):
        super(AndList, self).__init__('AND',*args)

class OrList(PathClauseList):
    def __init__(self, *args):
        super(OrList, self).__init__('OR',*args)

class PathClause(GenClause):
    def like(self, other):
        self.operator = 'like'
        self.right = other
        return self

    def notLike(self, other):
        self.operator = 'notLike'
        self.right = other
        return self

class SAQueryBuilder(object):

    def __init__(self, ormObj, filterJoins=()):
        self._ormObj = ormObj
        self._mapper = object_mapper(self._ormObj)
        self._propertyNames = []
        self._propertyNamesDecorated = []
        self._properties = {}
        self._joinNames = []
        self._joinNameClasses = {}
        self._propertyNameClasses = {}
        self._filterJoins = filterJoins

    @property
    def ormObj(self):
        return self._ormObj

    def getPropertyNames(self):
        if not len(self._propertyNames):
            self._extractPropertiesAndJoins(self._ormObj)
        return self._propertyNames

    propertyNames = property(getPropertyNames)

    def getPropertyNamesDecorated(self):
        if not len(self._propertyNamesDecorated):
            self._loadPropertyNamesDecorated()
        return self._propertyNamesDecorated

    propertyNamesDecorated = property(getPropertyNamesDecorated)

    def _loadPropertyNamesDecorated(self, ormClass=None, pathStack=[]):

        if ormClass is None:
            ormClass = self._ormObj.__class__

        dec = ormClass.__ormDecorator__()

        for propertyName in dec.getShownProperties():

            pathStack.append(propertyName)
            propertyPath = ".".join(pathStack)

            if self.properties.has_key(propertyPath):
                if isinstance(self.properties[propertyPath],
                              RelationshipProperty):
                    if propertyPath in self.joinNames:
                        joinClass = self.joinNameClasses[propertyPath]
                        self._propertyNamesDecorated.append(propertyPath)
                        self._loadPropertyNamesDecorated(joinClass, pathStack)

                if isinstance(self.properties[propertyPath],
                              ColumnProperty):
                    self._propertyNamesDecorated.append(propertyPath)

            pathStack.pop()
        return self._propertyNamesDecorated

    def _addJoinPath(self, joins, joinPath):
        pathName = []
        for pathPart in joinPath.split('.'):
            pathName.append(pathPart)
            path = ".".join(pathName)
            if path not in joins:
                joins.append(path)

    def _calculateJoinNames(self, propertyNames, filter):
        joins = []
        filterPropertyNames = []
        if not len(self._properties):
            self._extractPropertiesAndJoins(self._ormObj)

        if isinstance(filter, PathClauseList):
            #raise NotImplementedError("Or und And kommt noch")
            filterPropertyNames = self._extractPropertyNamesFromClauseList(filter, [])
        if isinstance(filter, PathClause):
            filterPropertyNames = [filter.left]

        for propertyName in filterPropertyNames:
            property = self.properties[propertyName]
            if isinstance(property, RelationshipProperty):
#                print "%s is RelationShip" % propertyName
                self._addJoinPath(joins, propertyName)
            else:
#                print "%s is Column" % propertyName
                split = propertyName.split('.')
                if len(split):
                    parentName = ".".join(split[:-1])
                    if parentName in self.joinNames:
                        self._addJoinPath(joins, parentName)
                        #if parentName not in joins:
                        #    joins.append(parentName)
                else:
                    parentName = None

                #print self.properties[propertyName]
                #print "prop: %s parent: %s" % (propertyName, parentName)

        for propertyName in propertyNames:
            property = self.properties[propertyName]
            if isinstance(property, RelationshipProperty):
                self._addJoinPath(joins, propertyName)
            else:
                split = propertyName.split('.')
                if len(split) > 1:
                    parentName = ".".join(split[:-1])
                    self._addJoinPath(joins, parentName)
#                    if parentName not in joins:
#                        joins.append(parentName)
            #print split

        #print joins
        return joins

    def _buildJoinAliases(self, joinNames):
        sortedAliases = OrderedDict()
        aliases = {}
        for joinName in joinNames:
            #print self.joinNameClasses[joinName]
            aliases[joinName] = aliased(self.joinNameClasses[joinName])
        return aliases

    def _extractPropertyNamesFromClauseList(self, clauseList, propertyNames=[]):

        for clause in clauseList.clauses:
            if isinstance(clause, PathClauseList):
                self._extractPropertyNamesFromClauseList(clause, propertyNames)
            elif isinstance(clause, PathClause):
                if clause.left not in propertyNames:
                    propertyNames.append(clause.left)

        return propertyNames

    def getQuery(self, session, propertySelection=[], appendOptions=None, filter=None):

        if not len(propertySelection):
            propertySelection = self.propertyNamesDecorated

        uniqueJoinNames = self._calculateJoinNames(propertySelection, filter)
#        print uniqueJoinNames
        aliases = self._buildJoinAliases(uniqueJoinNames)
        joinNames = aliases.keys()
        joinNames.sort()

        query = session.query(self._ormObj.__class__)

        for joinName in joinNames:
            parentClass = None
            split = joinName.split('.')
            #print split
            if len(split) > 1:
                propertyName = split[-1:][0]
                parentName = ".".join(split[:-1])
                parentClass = aliases[parentName]
                query = query.outerjoin((aliases[joinName],parentClass.__getattr__(propertyName)))
            else:
                parentClass = self._ormObj.__class__
                propertyName = joinName
                query = query.outerjoin((aliases[joinName],parentClass.__dict__[propertyName]))

        query = self._addFilter(query, filter, aliases)
        containsEagers = self._getContainsEagers(propertySelection, aliases,
                                                joinNames)

        if len(containsEagers):
            if appendOptions is not None:
                for option in appendOptions:
                    containsEagers.append(option)
            query = query.options(*containsEagers)
#        print str(query).replace('LEFT OUTER JOIN', '\nLEFT OUTER JOIN')
        return query

    def _getContainsEagers(self, propertyNames, aliases, joinNames):
        neededJoins = []
#        print "PropertyNames:"
        for propertyName in propertyNames:
#            print propertyName
            property = self.properties[propertyName]
            if isinstance(property, RelationshipProperty):
                #print "Relationship: %s" % propertyName
                split = propertyName.split('.')
                stack = []
                for part in split:
                    stack.append(part)
                    stackedProperty = u".".join(stack)
                    #print "Einzeln: %s" % stackedProperty
                    if stackedProperty not in neededJoins:
                        neededJoins.append(stackedProperty)
                        #print "I add %s" % stackedProperty

            elif isinstance(property, ColumnProperty):
#                print "ColumnProperty: %s" % propertyName
                split = propertyName.split('.')
                if len(split) > 1:
                    joinSplit = split[:-1]
                    #print "joinName of Property: %s" % joinName
                    stack = []
                    for part in joinSplit:
                        stack.append(part)
                        stackedProperty = u".".join(stack)
                        if stackedProperty not in neededJoins:
                            neededJoins.append(stackedProperty)
#                    if joinName not in neededJoins:
#                        neededJoins.append(joinName)

        containsEagers = []
#        print "neededJoins:"
        for neededJoin in neededJoins:
#            print neededJoin
            containsEagers.append(contains_eager(neededJoin, alias=aliases[neededJoin]))

        return containsEagers

    def _addFilter(self, query, filter, aliases):
        #print filter
        if isinstance(filter, PathClauseList):
            saClauseList = self._convertPathClauseList(filter, aliases)
            query = query.filter(saClauseList)

        if isinstance(filter, PathClause):
            try:
                clause = self._convertPathClause(filter, aliases)
                query = query.filter(clause)
            except:
                print "Cannot convert PathClause %s" % clause

        return query

    def _convertPathClauseList(self, clauseList, aliases, parentClause=None):
        if isinstance(clauseList, PathClauseList):
            if clauseList.conjunction == "AND":
                saClause = and_()
            elif clauseList.conjunction == 'OR':
                saClause = or_()
            for clause in clauseList.clauses:
                if isinstance(clause, PathClause):
                    try:
                        saClause.append(self._convertPathClause(clause, aliases))
                    except:
                        print "Cannot convert PathClause %s" % clause

                elif isinstance(clause, PathClauseList):
                    saClause.append(self._convertPathClauseList(clause, aliases, saClause))
            return saClause

    def _convertPathClause(self, clause, aliases):

        property = self.properties[clause.left]
        if isinstance(property, ColumnProperty):
            split = clause.left.split('.')
            if len(split) < 2:
                return self._ormObj.__class__.__dict__[clause.left].__getattribute__(self._translateOperator(clause.operator))(clause.right)
            else:
                parentName = ".".join(split[:-1])
                propName = split[-1:][0]
                method = self._translateOperator(clause.operator)
                return aliases[parentName].__getattr__(propName).__getattr__(method)(clause.right)

        if isinstance(property, RelationshipProperty):
            split = clause.left.split('.')
#            print clause
#            if not isinstance(clause.right, OrmBaseObject):
#                return
#            print type(clause.right)
            if len(split) < 2:
                return self._ormObj.__class__.__dict__[clause.left].__eq__(clause.right)
            else:
                parentName = ".".join(split[:-1])
                propName = split[-1:][0]

                return aliases[parentName].__getattr__(propName).__eq__(clause.right)

    def _translateOperator(self, operator):
        table = {'==':'__eq__',
         '!=':'__ne__',
         '<':'__lt__',
         '<=':'__le__',
         '>':'__gt__',
         '>=':'__ge__',
         'like':'like',
         'BEFORE':'<',
         'AFTER':'>'}
        return table[operator]
    @property
    def propertyName2Class(self):
        return self._propertyNameClasses

    @property
    def properties(self):
        if not len(self._properties):
            self._extractPropertiesAndJoins(self._ormObj)
        return self._properties

    def getJoinNames(self):
        if not len(self._joinNames):
            self._extractPropertiesAndJoins(self._ormObj)

        return self._joinNames

    joinNames = property(getJoinNames)

    def getJoinNameClasses(self):
        if not len(self._joinNameClasses):
            self._extractPropertiesAndJoins(self._ormObj)
        return self._joinNameClasses

    joinNameClasses = property(getJoinNameClasses)

    def _extractPropertiesAndJoins(self, obj, pathStack=[],
                                        alreadyAddedClasses=[],
                                        recursionCounter = -1):
        recursionCounter += 1
        if recursionCounter == 0:
            pathStack = []
            alreadyAddedClasses = []

        if recursionCounter > 100:
            raise StopIteration()

        mapper = object_mapper(obj)
        alreadyAddedClasses.append(obj.__class__)

        for prop in mapper.iterate_properties:
            if isinstance(prop, ColumnProperty):
                if len(pathStack):
                    propertyName = "%s.%s" % (".".join(pathStack), prop.key)
                else:
                    propertyName = prop.key
                self._propertyNames.append(propertyName)
                self._properties[propertyName] = prop
                self._propertyNameClasses[propertyName] = obj.__class__

            elif isinstance(prop, RelationshipProperty):
                if not prop.uselist:
                    classType = prop.mapper.class_
                    if classType is not self._ormObj.__class__:
                        if len(pathStack):
                            joinName = "%s.%s" % (".".join(pathStack), prop.key)
                        else:
                            joinName = prop.key

                        if joinName not in self._joinNames:
                            self._joinNames.append(joinName)

                        self._propertyNames.append(joinName)
                        self._propertyNameClasses[joinName] = obj.__class__
                        self._properties[joinName] = prop

                        pathStack.append(prop.key)
                        self._joinNameClasses[joinName] = classType
                        if classType not in alreadyAddedClasses:
                            self._extractPropertiesAndJoins(classType(), pathStack,
                                                           alreadyAddedClasses,
                                                           recursionCounter)

                        pathStack.pop()<|endoftext|>