<commit_before>import os
import re
import sys
import json
import yaml
import shutil
import logging
import subprocess

from yaml import Dumper
from watchmaker.managers.base import LinuxManager
from watchmaker.exceptions import SystemFatal as exceptionhandler

class Yum(LinuxManager):
    """
    Yum worker class.  This class handles linux distro validation and repo installation.
    """

    def __init__(self):
        """
        Instatiates the class.
        """
        super(Yum, self).__init__()
        self.dist = None
        self.version = None
        self.epel_version = None

    def _validate_distro(self):
        """
        Private method for validating the linux distrbution uses yum and is configurable.
        """

        self.dist = None
        self.version = None
        self.epel_version = None

        supported_dists = ('amazon', 'centos', 'red hat')

        match_supported_dist = re.compile(r"^({0})"
                                          "(?:[^0-9]+)"
                                          "([\d]+[.][\d]+)"
                                          "(?:.*)"
                                          .format('|'.join(supported_dists)))
        amazon_epel_versions = {
            '2014.03': '6',
            '2014.09': '6',
            '2015.03': '6',
            '2015.09': '6',
        }

        # Read first line from /etc/system-release
        try:
            with open(name='/etc/system-release', mode='rb') as f:
                release = f.readline().strip()
        except Exception as exc:
            raise SystemError('Could not read /etc/system-release. '
                              'Error: {0}'.format(exc))

        # Search the release file for a match against _supported_dists
        matched = match_supported_dist.search(release.lower())
        if matched is None:
            # Release not supported, exit with error
            raise SystemError('Unsupported OS distribution. OS must be one of: '
                              '{0}.'.format(', '.join(supported_dists)))

        # Assign dist,version from the match groups tuple, removing any spaces
        self.dist, self.version = (x.translate(None, ' ') for x in matched.groups())

        # Determine epel_version
        if 'amazon' == self.dist:
            self.epel_version = amazon_epel_versions.get(self.version, None)
        else:
            self.epel_version = self.version.split('.')[0]

        if self.epel_version is None:
            raise SystemError('Unsupported OS version! dist = {0}, version = {1}.'
                              .format(self.dist, self.version))

        logging.debug('Dist\t\t{0}'.format(self.dist))
        logging.debug('Version\t\t{0}'.format(self.version))
        logging.debug('EPEL Version\t{0}'.format(self.epel_version))

    def _repo(self, config):
        """
        Private method that validates that the config is properly formed.
        """
        if not isinstance(config['yumrepomap'], list):
            raise SystemError('`yumrepomap` must be a list!')

    def repo_install(self, configuration):
        """
        Checks the distribution version and installs yum repo definition files
        that are specific to that distribution.

        :param configuration: The configuration data required to install the yum repos.
        :type configuration: JSON
        """

        try:
            config = json.loads(configuration)
        except ValueError:
            logging.fatal('The configuration passed was not properly formed JSON.  Execution Halted.')
            sys.exit(1)

        if 'yumrepomap' in config and config['yumrepomap']:
            self._repo(config)
        else:
            logging.info('yumrepomap did not exist or was empty.')

        self._validate_distro()

        # TODO This block is weird.  Correct and done.
        for repo in config['yumrepomap']:

            if repo['dist'] in [self.dist, 'all']:
                logging.debug('{0} in {1} or all'.format(repo['dist'], self.dist))
                if 'epel_version' in repo and str(repo['epel_version']) != str(self.epel_version):
                    logging.error('epel_version is not valid for this repo. {0}'.format(self.epel_version))
                else:
                    logging.debug('All requirements have been validated for this repo.')
                    # Download the yum repo definition to /etc/yum.repos.d/
                    url = repo['url']
                    repofile = '/etc/yum.repos.d/{0}'.format(url.split('/')[-1])
                    self.download_file(url, repofile)
            else:
                logging.debug('{0} NOT in {1} or all'.format(repo['dist'], self.dist))

class Salt(LinuxManager):
    def __init__(self):
        super(Salt, self).__init__()
        self.salt_conf = None
        self.config = None
        self.workingdir = None
        self.formulastoinclude = list()
        self.formulaterminationstrings = list()
        self.sourceiss3bucket = None
        self.entenv = None
        self.saltbootstrapfilename = None
        self.yum_pkgs = [
            'policycoreutils-python',
            'selinux-policy-targeted',
            'salt-minion',
        ]

        self.salt_confpath = '/etc/salt'
        self.minionconf = '/etc/salt/minion'
        self.saltcall = '/usr/bin/salt-call'
        self.saltsrv = '/srv/salt'
        self.saltfileroot = os.sep.join((self.saltsrv, 'states'))
        self.saltformularoot = os.sep.join((self.saltsrv, 'formulas'))
        self.saltpillarroot = os.sep.join((self.saltsrv, 'pillar'))
        self.saltbaseenv = os.sep.join((self.saltfileroot, 'base'))

    def _configuration_validation(self):
        if 'git' == self.config['saltinstallmethod'].lower():
            if not self.config['saltbootstrapsource']:
                logging.error('Detected `git` as the install method, but the required parameter `saltbootstrapsource`',
                              'was not provided.')
            else:
                self.saltbootstrapfilename = self.config['saltbootstrapsource'].split('/')[-1]
            if not self.config['saltgitrepo']:
                logging.error('Detected `git` as the install method, but the required parameter `saltgitrepo` was not ',
                              'provided.')

    def _install_package(self):
        if 'yum' == self.config['saltinstallmethod'].lower():
            self._install_from_yum(self.yum_pkgs)
        elif 'git' == self.config['saltinstallmethod'].lower():
            self.download_file(self.config['saltbootstrapsource'], self.saltbootstrapfilename)
            bootstrapcmd = ['sh', self.saltbootstrapfilename, '-g', self.config['saltgitrepo']]
            if self.config['saltversion']:
                bootstrapcmd.append('git')
                bootstrapcmd.append(self.config['saltversion'])
            else:
                logging.debug('No salt version defined in config.')
            subprocess.call([bootstrapcmd])

    def _prepare_for_install(self):

        if self.config['formulastoinclude']:
            self.formulastoinclude = self.config['formulastoinclude']

        if self.config['formulaterminationstrings']:
            self.formulaterminationstrings = self.config['formulaterminationstrings']

        self.sourceiss3bucket = self.config['sourceiss3bucket']
        self.entenv = self.config['entenv']
        self.create_working_dir('/usr/tmp/', 'saltinstall')

        self.salt_results_logfile = self.config['salt_results_log'] or os.sep.join((self.workingdir,
                                                                                'saltcall.results.log'))

        self.salt_debug_logfile = self.config['salt_debug_log'] or os.sep.join((self.workingdir,
                                                                                'saltcall.debug.log'))

        self.saltcall_arguments = ['--out', 'yaml', '--out-file', self.salt_results_logfile, '--return', 'local',
                                   '--log-file', self.salt_debug_logfile, '--log-file-level', 'debug']

        for saltdir in [self.saltfileroot, self.saltbaseenv, self.saltformularoot]:
            try:
                os.makedirs(saltdir)
            except OSError:
                if not os.path.isdir(saltdir):
                    raise

    def _build_salt_formula(self):
        if self.config['saltcontentsource']:
            self.saltcontentfilename = self.config['saltcontentsource'].split('/')[-1]
            self.saltcontentfile = os.sep.join((self.workingdir, self.saltcontentfilename))
            self.download_file(self.config['saltcontentsource'], self.saltcontentfile, self.sourceiss3bucket)
            self.extract_contents(filepath=self.saltcontentfile, to_directory=self.saltsrv)

        # Download and extract any salt formulas specified in formulastoinclude
        formulas_conf = []
        for source_loc in self.formulastoinclude:
            filename = source_loc.split('/')[-1]
            file_loc = os.sep.join((self.workingdir, filename))
            self.download_file(source_loc, file_loc)
            self.extract_contents(filepath=file_loc, to_directory=self.saltformularoot)
            filebase = '.'.join(filename.split('.')[:-1])
            formulas_loc = os.sep.join((self.saltformularoot, filebase))

            for string in self.formulaterminationstrings:
                if filebase.endswith(string):
                    newformuladir = formulas_loc[:-len(string)]
                    if os.path.exists(newformuladir):
                        shutil.rmtree(newformuladir)
                    shutil.move(formulas_loc, newformuladir)
                    formulas_loc = newformuladir
            formulas_conf.append(formulas_loc)

        file_roots = [str(self.saltbaseenv)]
        file_roots += [str(x) for x in formulas_conf]

        self.salt_conf = {'file_roots':
                              {'base': file_roots},
                          'pillar_roots':
                              {'base': [str(self.saltpillarroot)]}
                          }

        with open(os.path.join(self.salt_confpath, 'minion.d', 'watchmaker.conf'), 'w') as f:
            yaml.dump(self.salt_conf, f, default_flow_style=False)

    def install(self, configuration):
        """

        :param configuration:
        :return:
        """

        try:
            self.config = json.loads(configuration)
        except ValueError:
            exceptionhandler('The configuration passed was not properly formed JSON.  Execution Halted.')

        self._configuration_validation()
        self._prepare_for_install()
        self._install_package()
        self._build_salt_formula()

        logging.info('Setting grain `systemprep`...')
        ent_env = {'enterprise_environment': str(self.entenv)}
        cmd = [self.saltcall, '--local', '--retcode-passthrough', 'grains.setval', 'systemprep',
               str(json.dumps(ent_env))]
        self.call_process(cmd)

        if self.config['oupath']:
            print('Setting grain `join-domain`...')
            oupath = {'oupath': self.config['oupath']}
            cmd = [self.saltcall, '--local', '--retcode-passthrough', 'grains.setval', '"join-domain"',
                   json.dumps(oupath)]
            self.call_process(cmd)

        print('Syncing custom salt modules...')
        cmd = [self.saltcall, '--local', '--retcode-passthrough', 'saltutil.sync_all']
        self.call_process(cmd)

        if 'none' == self.config['saltstates'].lower():
            print('No States were specified. Will not apply any salt states.')
        else:
            if 'highstate' == self.config['saltstates'].lower():
                logging.info('Detected the States parameter is set to `highstate`. Applying the salt `"highstate`" '
                             'to the system.')
                cmd = [self.saltcall, '--local', '--retcode-passthrough', 'state.highstate']
                cmd.extend(self.saltcall_arguments)
                self.call_process(cmd)

            else:
                logging.info('Detected the States parameter is set to: {0}. Applying the user-defined list of states '
                             'to the system.'.format(self.config['saltstates']))
                cmd = [self.saltcall, '--local', '--retcode-passthrough', 'state.sls', self.config['saltstates']]
                cmd.extend(self.saltcall_arguments)
                self.call_process(cmd)

        logging.info(
            'Salt states all applied successfully! Details are in the log {0}'.format(self.salt_results_logfile))

        if self.workingdir:
            self.cleanup()
<commit_msg>Cleaning up import that is no longer used.<commit_after>import os
import re
import sys
import json
import yaml
import shutil
import logging
import subprocess

from watchmaker.managers.base import LinuxManager
from watchmaker.exceptions import SystemFatal as exceptionhandler

class Yum(LinuxManager):
    """
    Yum worker class.  This class handles linux distro validation and repo installation.
    """

    def __init__(self):
        """
        Instatiates the class.
        """
        super(Yum, self).__init__()
        self.dist = None
        self.version = None
        self.epel_version = None

    def _validate_distro(self):
        """
        Private method for validating the linux distrbution uses yum and is configurable.
        """

        self.dist = None
        self.version = None
        self.epel_version = None

        supported_dists = ('amazon', 'centos', 'red hat')

        match_supported_dist = re.compile(r"^({0})"
                                          "(?:[^0-9]+)"
                                          "([\d]+[.][\d]+)"
                                          "(?:.*)"
                                          .format('|'.join(supported_dists)))
        amazon_epel_versions = {
            '2014.03': '6',
            '2014.09': '6',
            '2015.03': '6',
            '2015.09': '6',
        }

        # Read first line from /etc/system-release
        try:
            with open(name='/etc/system-release', mode='rb') as f:
                release = f.readline().strip()
        except Exception as exc:
            raise SystemError('Could not read /etc/system-release. '
                              'Error: {0}'.format(exc))

        # Search the release file for a match against _supported_dists
        matched = match_supported_dist.search(release.lower())
        if matched is None:
            # Release not supported, exit with error
            raise SystemError('Unsupported OS distribution. OS must be one of: '
                              '{0}.'.format(', '.join(supported_dists)))

        # Assign dist,version from the match groups tuple, removing any spaces
        self.dist, self.version = (x.translate(None, ' ') for x in matched.groups())

        # Determine epel_version
        if 'amazon' == self.dist:
            self.epel_version = amazon_epel_versions.get(self.version, None)
        else:
            self.epel_version = self.version.split('.')[0]

        if self.epel_version is None:
            raise SystemError('Unsupported OS version! dist = {0}, version = {1}.'
                              .format(self.dist, self.version))

        logging.debug('Dist\t\t{0}'.format(self.dist))
        logging.debug('Version\t\t{0}'.format(self.version))
        logging.debug('EPEL Version\t{0}'.format(self.epel_version))

    def _repo(self, config):
        """
        Private method that validates that the config is properly formed.
        """
        if not isinstance(config['yumrepomap'], list):
            raise SystemError('`yumrepomap` must be a list!')

    def repo_install(self, configuration):
        """
        Checks the distribution version and installs yum repo definition files
        that are specific to that distribution.

        :param configuration: The configuration data required to install the yum repos.
        :type configuration: JSON
        """

        try:
            config = json.loads(configuration)
        except ValueError:
            logging.fatal('The configuration passed was not properly formed JSON.  Execution Halted.')
            sys.exit(1)

        if 'yumrepomap' in config and config['yumrepomap']:
            self._repo(config)
        else:
            logging.info('yumrepomap did not exist or was empty.')

        self._validate_distro()

        # TODO This block is weird.  Correct and done.
        for repo in config['yumrepomap']:

            if repo['dist'] in [self.dist, 'all']:
                logging.debug('{0} in {1} or all'.format(repo['dist'], self.dist))
                if 'epel_version' in repo and str(repo['epel_version']) != str(self.epel_version):
                    logging.error('epel_version is not valid for this repo. {0}'.format(self.epel_version))
                else:
                    logging.debug('All requirements have been validated for this repo.')
                    # Download the yum repo definition to /etc/yum.repos.d/
                    url = repo['url']
                    repofile = '/etc/yum.repos.d/{0}'.format(url.split('/')[-1])
                    self.download_file(url, repofile)
            else:
                logging.debug('{0} NOT in {1} or all'.format(repo['dist'], self.dist))

class Salt(LinuxManager):
    def __init__(self):
        super(Salt, self).__init__()
        self.salt_conf = None
        self.config = None
        self.workingdir = None
        self.formulastoinclude = list()
        self.formulaterminationstrings = list()
        self.sourceiss3bucket = None
        self.entenv = None
        self.saltbootstrapfilename = None
        self.yum_pkgs = [
            'policycoreutils-python',
            'selinux-policy-targeted',
            'salt-minion',
        ]

        self.salt_confpath = '/etc/salt'
        self.minionconf = '/etc/salt/minion'
        self.saltcall = '/usr/bin/salt-call'
        self.saltsrv = '/srv/salt'
        self.saltfileroot = os.sep.join((self.saltsrv, 'states'))
        self.saltformularoot = os.sep.join((self.saltsrv, 'formulas'))
        self.saltpillarroot = os.sep.join((self.saltsrv, 'pillar'))
        self.saltbaseenv = os.sep.join((self.saltfileroot, 'base'))

    def _configuration_validation(self):
        if 'git' == self.config['saltinstallmethod'].lower():
            if not self.config['saltbootstrapsource']:
                logging.error('Detected `git` as the install method, but the required parameter `saltbootstrapsource`',
                              'was not provided.')
            else:
                self.saltbootstrapfilename = self.config['saltbootstrapsource'].split('/')[-1]
            if not self.config['saltgitrepo']:
                logging.error('Detected `git` as the install method, but the required parameter `saltgitrepo` was not ',
                              'provided.')

    def _install_package(self):
        if 'yum' == self.config['saltinstallmethod'].lower():
            self._install_from_yum(self.yum_pkgs)
        elif 'git' == self.config['saltinstallmethod'].lower():
            self.download_file(self.config['saltbootstrapsource'], self.saltbootstrapfilename)
            bootstrapcmd = ['sh', self.saltbootstrapfilename, '-g', self.config['saltgitrepo']]
            if self.config['saltversion']:
                bootstrapcmd.append('git')
                bootstrapcmd.append(self.config['saltversion'])
            else:
                logging.debug('No salt version defined in config.')
            subprocess.call([bootstrapcmd])

    def _prepare_for_install(self):

        if self.config['formulastoinclude']:
            self.formulastoinclude = self.config['formulastoinclude']

        if self.config['formulaterminationstrings']:
            self.formulaterminationstrings = self.config['formulaterminationstrings']

        self.sourceiss3bucket = self.config['sourceiss3bucket']
        self.entenv = self.config['entenv']
        self.create_working_dir('/usr/tmp/', 'saltinstall')

        self.salt_results_logfile = self.config['salt_results_log'] or os.sep.join((self.workingdir,
                                                                                'saltcall.results.log'))

        self.salt_debug_logfile = self.config['salt_debug_log'] or os.sep.join((self.workingdir,
                                                                                'saltcall.debug.log'))

        self.saltcall_arguments = ['--out', 'yaml', '--out-file', self.salt_results_logfile, '--return', 'local',
                                   '--log-file', self.salt_debug_logfile, '--log-file-level', 'debug']

        for saltdir in [self.saltfileroot, self.saltbaseenv, self.saltformularoot]:
            try:
                os.makedirs(saltdir)
            except OSError:
                if not os.path.isdir(saltdir):
                    raise

    def _build_salt_formula(self):
        if self.config['saltcontentsource']:
            self.saltcontentfilename = self.config['saltcontentsource'].split('/')[-1]
            self.saltcontentfile = os.sep.join((self.workingdir, self.saltcontentfilename))
            self.download_file(self.config['saltcontentsource'], self.saltcontentfile, self.sourceiss3bucket)
            self.extract_contents(filepath=self.saltcontentfile, to_directory=self.saltsrv)

        # Download and extract any salt formulas specified in formulastoinclude
        formulas_conf = []
        for source_loc in self.formulastoinclude:
            filename = source_loc.split('/')[-1]
            file_loc = os.sep.join((self.workingdir, filename))
            self.download_file(source_loc, file_loc)
            self.extract_contents(filepath=file_loc, to_directory=self.saltformularoot)
            filebase = '.'.join(filename.split('.')[:-1])
            formulas_loc = os.sep.join((self.saltformularoot, filebase))

            for string in self.formulaterminationstrings:
                if filebase.endswith(string):
                    newformuladir = formulas_loc[:-len(string)]
                    if os.path.exists(newformuladir):
                        shutil.rmtree(newformuladir)
                    shutil.move(formulas_loc, newformuladir)
                    formulas_loc = newformuladir
            formulas_conf.append(formulas_loc)

        file_roots = [str(self.saltbaseenv)]
        file_roots += [str(x) for x in formulas_conf]

        self.salt_conf = {'file_roots':
                              {'base': file_roots},
                          'pillar_roots':
                              {'base': [str(self.saltpillarroot)]}
                          }

        with open(os.path.join(self.salt_confpath, 'minion.d', 'watchmaker.conf'), 'w') as f:
            yaml.dump(self.salt_conf, f, default_flow_style=False)

    def install(self, configuration):
        """

        :param configuration:
        :return:
        """

        try:
            self.config = json.loads(configuration)
        except ValueError:
            exceptionhandler('The configuration passed was not properly formed JSON.  Execution Halted.')

        self._configuration_validation()
        self._prepare_for_install()
        self._install_package()
        self._build_salt_formula()

        logging.info('Setting grain `systemprep`...')
        ent_env = {'enterprise_environment': str(self.entenv)}
        cmd = [self.saltcall, '--local', '--retcode-passthrough', 'grains.setval', 'systemprep',
               str(json.dumps(ent_env))]
        self.call_process(cmd)

        if self.config['oupath']:
            print('Setting grain `join-domain`...')
            oupath = {'oupath': self.config['oupath']}
            cmd = [self.saltcall, '--local', '--retcode-passthrough', 'grains.setval', '"join-domain"',
                   json.dumps(oupath)]
            self.call_process(cmd)

        print('Syncing custom salt modules...')
        cmd = [self.saltcall, '--local', '--retcode-passthrough', 'saltutil.sync_all']
        self.call_process(cmd)

        if 'none' == self.config['saltstates'].lower():
            print('No States were specified. Will not apply any salt states.')
        else:
            if 'highstate' == self.config['saltstates'].lower():
                logging.info('Detected the States parameter is set to `highstate`. Applying the salt `"highstate`" '
                             'to the system.')
                cmd = [self.saltcall, '--local', '--retcode-passthrough', 'state.highstate']
                cmd.extend(self.saltcall_arguments)
                self.call_process(cmd)

            else:
                logging.info('Detected the States parameter is set to: {0}. Applying the user-defined list of states '
                             'to the system.'.format(self.config['saltstates']))
                cmd = [self.saltcall, '--local', '--retcode-passthrough', 'state.sls', self.config['saltstates']]
                cmd.extend(self.saltcall_arguments)
                self.call_process(cmd)

        logging.info(
            'Salt states all applied successfully! Details are in the log {0}'.format(self.salt_results_logfile))

        if self.workingdir:
            self.cleanup()
<|endoftext|>


<commit_before>from django.db import models
from cyder.cydns.soa.models import Soa
import ipaddr
import pdb

class Reverse_Domain( models.Model ):
    """A reverse DNS domain is used build reverse bind files."""
    IP_TYPE_CHOICES = ( ('4','IPv4'),('6','IPv6') )
    id                      = models.AutoField(primary_key=True)
    name                    = models.CharField(max_length=100)
    master_reverse_domain   = models.ForeignKey("self", null=True)
    soa                     = models.ForeignKey(Soa, null=True)
    ip_type                 = models.CharField(max_length=1, choices=IP_TYPE_CHOICES, default='4')

    def __str__(self):
        return "<Reverse_Domain '%s'>" % (self.name)
    def __repr__(self):
        return  self.__str__()

    class Meta:
        db_table = 'reverse_domain'

class ReverseDomainNotFoundError(Exception):
    """This exception is thrown when you are trying to add an Ip to the database and it cannot be paired with a reverse domain. The solution is to create a reverse domain for the Ip to live in.
    """
    def __init__(self, msg ):
        self.msg = msg
    def __str__(self):
        return self.msg
    def __repr__(self):
        return  self.__str__()
class ReverseDomainExistsError(Exception):
    """This exception is thrown when you try to create a reverse domain that already exists."""
    def __init__(self, msg ):
        self.msg = msg
    def __str__(self):
        return self.msg
    def __repr__(self):
        return  self.__str__()
class ReverseChildDomainExistsError(Exception):
    """This exception is thrown when you try to delete a reverse domain that has child reverese domains. A reverse domain should only be deleted when it has no child reverse domains."""
    def __init__(self, msg ):
        self.msg = msg
    def __str__(self):
        return self.msg
    def __repr__(self):
        return  self.__str__()

class MasterReverseDomainNotFoundError(Exception):
    """All reverse domains should have a logical master (or parent) reverse domain. If you try to create a reverse domain that should have a master reverse domain and *doesn't* this exception is thrown."""
    def __init__(self, msg ):
        self.msg = msg
    def __str__(self):
        return self.msg
    def __repr__(self):
        return  self.__str__()

def ip_to_reverse_domain( ip, ip_type ):
    """Given an ip return the most specific reverse domain that the ip can belong to.

    :param ip: The ip to which we are using to search for a reverse domain.
    :type ip: str
    :param ip_type: The type of Ip address ip is. It should be either an IPv4 or IPv6 address.
    :type ip_type: str -- '4' or '6'
    :returns: reverse_domain -- Reverse_Domain object
    :raises: ReverseDomainNotFoundError
    """
    if ip_type == '6':
        ip = nibblize(ip)
    tokens = ip.split('.')
    reverse_domain = None
    for i in reversed(range(1,len(tokens))):
        search_reverse_domain = '.'.join(tokens[:-i])
        tmp_reverse_domain = Reverse_Domain.objects.filter( name = search_reverse_domain, ip_type=ip_type )
        if tmp_reverse_domain:
            reverse_domain = tmp_reverse_domain[0]
        else:
            break # Since we have a full tree we must have reached a leaf node.
    if reverse_domain:
        return reverse_domain
    else:
        raise ReverseDomainNotFoundError("Error: Could not find reverse domain for ip '%s'" % (ip))

def _dname_to_master_reverse_domain( dname, ip_type="4" ):
    """Given an name return the most specific reverse_domain that the ip can belong to.

    note::
        A name x.y.z can be split up into x y and z. The reverse_domains, 'y.z' and 'z' should exist.

    :param dname: The domain which we are using to search for a master reverse domain.
    :type dname: str
    :param ip_type: The type of reverse domain. It should be either an IPv4 or IPv6 address.
    :type ip_type: str -- '4' or '6'
    :returns: Reverse_Domain or None -- None if the reverse domain is a TLD
    :raises: MasterReverseDomainNotFoundError
    """
    dname = dname.rstrip('.')
    tokens = dname.split('.')
    master_reverse_domain = None
    for i in reversed(range(1,len(tokens))):
        parent_dname = '.'.join(tokens[:-i])
        possible_master_reverse_domain = Reverse_Domain.objects.filter( name = parent_dname, ip_type=ip_type )
        if not possible_master_reverse_domain:
            raise MasterReverseDomainNotFoundError("Error: Coud not find master domain for %s.\
                                                                Consider creating it." % (dname))
        else:
            master_reverse_domain = possible_master_reverse_domain[0]
    return master_reverse_domain

def _add_generic_reverse_domain( dname, ip_type ):
    if Reverse_Domain.objects.filter( name = dname ):
        raise ReverseDomainExistsError( "Error: The reverse domain %s already exists." % (dname) )
    #For now just add it. MUST ADD LOGIC HERE TODO
    if ip_type == '6':
        dname = dname.lower()

    master_reverse_domain = _dname_to_master_reverse_domain( dname, ip_type=ip_type )
    if not master_reverse_domain:
        soa = None
    else:
        soa = master_reverse_domain.soa

    reverse_domain = Reverse_Domain( name=dname, master_reverse_domain=master_reverse_domain, soa=soa, ip_type=ip_type )
    reverse_domain.save()
    _reassign_reverse_ips( reverse_domain, master_reverse_domain, ip_type )
    return reverse_domain

def _remove_generic_reverse_domain( dname, ip_type ):
    if not Reverse_Domain.objects.filter( name = dname, ip_type = ip_type ):
        raise ReverseDomainNotFoundError( "Error: %s was not found." % (dname))
    reverse_domain = Reverse_Domain.objects.filter( name = dname, ip_type = ip_type )[0] # It's cached

    # Check if it has children.
    children = Reverse_Domain.objects.filter( master_reverse_domain = reverse_domain )
    if children:
        error = ""
        for child in children:
            error += child.__str__()+", "
        raise ReverseChildDomainExistsError("Error: Domain %s has children: %s" % (dname, error))

    ips = reverse_domain.ip_set.iterator()
    for ip in ips:
        ip.reverse_domain = reverse_domain.master_reverse_domain
        ip.save()
    reverse_domain.delete()

def _reassign_reverse_ips( reverse_domain_1, reverse_domain_2, ip_type ):
    """There are some formalities that need to happen when a reverse domain is added and deleted. For example, when adding say we had the ip address 128.193.4.0 and it had the reverse_domain 128.193. If we add the reverse_domain 128.193.4, our 128.193.4.0 no longer belongs to the 128.193 domain. We need to re-asign the ip to it's correct reverse domain.

    :param reverse_domain_1: The domain which could possible have addresses added to it.
    :type reverse_domain_1: str
    :param reverse_domain_2: The domain that has ip's which might not belong to it anymore.
    :type reverse_domain_2: str
    """

    if reverse_domain_2 is None:
        return
    ips = reverse_domain_2.ip_set.iterator()
    for ip in ips:
        correct_reverse_domain = ip_to_reverse_domain( ip.__str__(), ip_type=ip_type )
        if correct_reverse_domain != ip.reverse_domain:
            ip.reverse_domain = correct_reverse_domain
            ip.save()

def boot_strap_add_ipv6_reverse_domain( ip ):
    """This function is here to help create IPv6 reverse domains.

    .. note::
        Every nibble in the reverse domain should not exists for this function to exit successfully.

    :param ip: The ip addres to be converted to nibble format.
    :type ip: str
    :raises: ReverseDomainNotFoundError
    """
    for i in range(1,len(ip)+1,2):
        cur_reverse_domain = ip[:i]
        reverse_domain = add_reverse_ipv6_domain( cur_reverse_domain )
    return reverse_domain

"""
@param: addr - A valid IPv6 string
@return: correct reverse zone representation
>>> nibblize('2620:0105:F000::1')
'2.6.2.0.0.1.0.5.F.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1'
>>> nibblize('2620:0105:F000:9::1')
'2.6.2.0.0.1.0.5.f.0.0.0.0.0.0.9.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1'
>>> nibblize('2620:0105:F000:9:0:1::1')
'2.6.2.0.0.1.0.5.f.0.0.0.0.0.0.9.0.0.0.0.0.0.0.1.0.0.0.0.0.0.0.1'
"""
def nibblize( addr ):
    """Given an IPv6 address is 'colon' format, return the address in 'nibble' form::

        nibblize('2620:0105:F000::1')
        '2.6.2.0.0.1.0.5.F.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1'

    :param addr: The ip address to convert
    :type addr: str
    """
    ip_str = ipaddr.IPv6Address(addr).exploded
    return '.'.join(list(ip_str.replace(':','')))

def add_reverse_ipv4_domain( dname ):
    """This function adds a an IPv4 reverse domain.

    :param dname: The reverse domain to be added.
    :type dname: str
    :param ip_type: The type of reverse domain. It should be either an IPv4 or IPv6 address.
    :type ip_type: str -- '4' or '6'
    :raises: ReverseDomainExistsError
    """
    return _add_generic_reverse_domain( str(dname), '4' )
def remove_reverse_ipv4_domain( dname ):
    """This function removes an IPv4 reverse domain.

    :param dname: The reverse domain to be removed.
    :type dname: str
    :param ip_type: The type of reverse domain. It should be either an IPv4 or IPv6 address.
    :type ip_type: str -- '4' or '6'
    :raises: ReverseDomainExistsError

    .. note::
        Think about reverse_domains (and domains for that matter) as a tree. This function is only
        defined to work on *leaf nodes*. If you attempt to remove a none leaf reverse_domain a
        ReverseChildDomainExistsError will be thrown.
    """
    return _remove_generic_reverse_domain( str(dname), '4' )
def add_reverse_ipv6_domain( dname ):
    """This function adds a an IPv6 reverse domain.

    :param dname: The reverse domain to be added.
    :type dname: str
    :param ip_type: The type of reverse domain. It should be either an IPv4 or IPv6 address.
    :type ip_type: str -- '4' or '6'
    :raises: ReverseDomainExistsError
    """
    return _add_generic_reverse_domain( str(dname), '6' )
def remove_reverse_ipv6_domain( dname ):
    """This function removes an IPv6 reverse domain.

    :param dname: The reverse domain to be removed.
    :type dname: str
    :param ip_type: The type of reverse domain. It should be either an IPv4 or IPv6 address.
    :type ip_type: str -- '4' or '6'
    :raises: ReverseDomainExistsError

    .. note::
        Think about reverse_domains (and domains for that matter) as a tree. This function is only
        defined to work on *leaf nodes*. If you attempt to remove a none leaf reverse_domain a
        ReverseChildDomainExistsError will be thrown.
    """
    return _remove_generic_reverse_domain( str(dname), '6' )
<commit_msg>Left some comments.<commit_after>from django.db import models
from cyder.cydns.soa.models import Soa
import ipaddr
import pdb

class Reverse_Domain( models.Model ):
    """A reverse DNS domain is used build reverse bind files."""
    IP_TYPE_CHOICES = ( ('4','IPv4'),('6','IPv6') )
    id                      = models.AutoField(primary_key=True)
    name                    = models.CharField(max_length=100)
    master_reverse_domain   = models.ForeignKey("self", null=True)
    soa                     = models.ForeignKey(Soa, null=True)
    ip_type                 = models.CharField(max_length=1, choices=IP_TYPE_CHOICES, default='4')

    def __str__(self):
        return "<Reverse_Domain '%s'>" % (self.name)
    def __repr__(self):
        return  self.__str__()

    class Meta:
        db_table = 'reverse_domain'

#TODO. Subclass These exceptions.
class ReverseDomainNotFoundError(Exception):
    """This exception is thrown when you are trying to add an Ip to the database and it cannot be paired with a reverse domain. The solution is to create a reverse domain for the Ip to live in.
    """
    def __init__(self, msg ):
        self.msg = msg
    def __str__(self):
        return self.msg
    def __repr__(self):
        return  self.__str__()
class ReverseDomainExistsError(Exception):
    """This exception is thrown when you try to create a reverse domain that already exists."""
    def __init__(self, msg ):
        self.msg = msg
    def __str__(self):
        return self.msg
    def __repr__(self):
        return  self.__str__()
class ReverseChildDomainExistsError(Exception):
    """This exception is thrown when you try to delete a reverse domain that has child reverese domains. A reverse domain should only be deleted when it has no child reverse domains."""
    def __init__(self, msg ):
        self.msg = msg
    def __str__(self):
        return self.msg
    def __repr__(self):
        return  self.__str__()

class MasterReverseDomainNotFoundError(Exception):
    """All reverse domains should have a logical master (or parent) reverse domain. If you try to create a reverse domain that should have a master reverse domain and *doesn't* this exception is thrown."""
    def __init__(self, msg ):
        self.msg = msg
    def __str__(self):
        return self.msg
    def __repr__(self):
        return  self.__str__()

def ip_to_reverse_domain( ip, ip_type ):
    """Given an ip return the most specific reverse domain that the ip can belong to.

    :param ip: The ip to which we are using to search for a reverse domain.
    :type ip: str
    :param ip_type: The type of Ip address ip is. It should be either an IPv4 or IPv6 address.
    :type ip_type: str -- '4' or '6'
    :returns: reverse_domain -- Reverse_Domain object
    :raises: ReverseDomainNotFoundError
    """
    if ip_type == '6':
        ip = nibblize(ip)
    tokens = ip.split('.')
    reverse_domain = None
    for i in reversed(range(1,len(tokens))):
        search_reverse_domain = '.'.join(tokens[:-i])
        tmp_reverse_domain = Reverse_Domain.objects.filter( name = search_reverse_domain, ip_type=ip_type )
        if tmp_reverse_domain:
            reverse_domain = tmp_reverse_domain[0]
        else:
            break # Since we have a full tree we must have reached a leaf node.
    if reverse_domain:
        return reverse_domain
    else:
        raise ReverseDomainNotFoundError("Error: Could not find reverse domain for ip '%s'" % (ip))

def _dname_to_master_reverse_domain( dname, ip_type="4" ):
    """Given an name return the most specific reverse_domain that the ip can belong to.

    note::
        A name x.y.z can be split up into x y and z. The reverse_domains, 'y.z' and 'z' should exist.

    :param dname: The domain which we are using to search for a master reverse domain.
    :type dname: str
    :param ip_type: The type of reverse domain. It should be either an IPv4 or IPv6 address.
    :type ip_type: str -- '4' or '6'
    :returns: Reverse_Domain or None -- None if the reverse domain is a TLD
    :raises: MasterReverseDomainNotFoundError
    """
    dname = dname.rstrip('.') #TODO, do we need this?
    tokens = dname.split('.')
    master_reverse_domain = None
    for i in reversed(range(1,len(tokens))):
        parent_dname = '.'.join(tokens[:-i])
        possible_master_reverse_domain = Reverse_Domain.objects.filter( name = parent_dname, ip_type=ip_type )
        if not possible_master_reverse_domain:
            raise MasterReverseDomainNotFoundError("Error: Coud not find master domain for %s.\
                                                                Consider creating it." % (dname))
        else:
            master_reverse_domain = possible_master_reverse_domain[0]
    return master_reverse_domain

def _add_generic_reverse_domain( dname, ip_type ):
    if Reverse_Domain.objects.filter( name = dname ):
        raise ReverseDomainExistsError( "Error: The reverse domain %s already exists." % (dname) )

    #TODO, validate dname
    if ip_type == '6':
        dname = dname.lower()

    master_reverse_domain = _dname_to_master_reverse_domain( dname, ip_type=ip_type )
    if not master_reverse_domain:
        soa = None
    else:
        soa = master_reverse_domain.soa

    reverse_domain = Reverse_Domain( name=dname, master_reverse_domain=master_reverse_domain, soa=soa, ip_type=ip_type )
    reverse_domain.save()
    _reassign_reverse_ips( reverse_domain, master_reverse_domain, ip_type )
    return reverse_domain

def _remove_generic_reverse_domain( dname, ip_type ):
    #TODO, validate dname
    if not Reverse_Domain.objects.filter( name = dname, ip_type = ip_type ):
        raise ReverseDomainNotFoundError( "Error: %s was not found." % (dname))
    reverse_domain = Reverse_Domain.objects.filter( name = dname, ip_type = ip_type )[0] # It's cached

    # Check if it has children.
    children = Reverse_Domain.objects.filter( master_reverse_domain = reverse_domain )
    if children:
        error = ""
        for child in children:
            error += child.__str__()+", "
        raise ReverseChildDomainExistsError("Error: Domain %s has children: %s" % (dname, error)) #TODO, error[:-2]?

    ips = reverse_domain.ip_set.iterator()
    for ip in ips:
        ip.reverse_domain = reverse_domain.master_reverse_domain
        ip.save()
    reverse_domain.delete()

def _reassign_reverse_ips( reverse_domain_1, reverse_domain_2, ip_type ):
    """There are some formalities that need to happen when a reverse domain is added and deleted. For example, when adding say we had the ip address 128.193.4.0 and it had the reverse_domain 128.193. If we add the reverse_domain 128.193.4, our 128.193.4.0 no longer belongs to the 128.193 domain. We need to re-asign the ip to it's correct reverse domain.

    :param reverse_domain_1: The domain which could possible have addresses added to it.
    :type reverse_domain_1: str
    :param reverse_domain_2: The domain that has ip's which might not belong to it anymore.
    :type reverse_domain_2: str
    """

    if reverse_domain_2 is None:
        return
    ips = reverse_domain_2.ip_set.iterator()
    for ip in ips:
        correct_reverse_domain = ip_to_reverse_domain( ip.__str__(), ip_type=ip_type )
        if correct_reverse_domain != ip.reverse_domain:
            ip.reverse_domain = correct_reverse_domain
            ip.save()

def boot_strap_add_ipv6_reverse_domain( ip ):
    """This function is here to help create IPv6 reverse domains.

    .. note::
        Every nibble in the reverse domain should not exists for this function to exit successfully.

    :param ip: The ip addres to be converted to nibble format.
    :type ip: str
    :raises: ReverseDomainNotFoundError
    """
    #TODO, validate ip
    for i in range(1,len(ip)+1,2):
        cur_reverse_domain = ip[:i]
        reverse_domain = add_reverse_ipv6_domain( cur_reverse_domain )
    return reverse_domain

"""
@param: addr - A valid IPv6 string
@return: correct reverse zone representation
>>> nibblize('2620:0105:F000::1')
'2.6.2.0.0.1.0.5.F.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1'
>>> nibblize('2620:0105:F000:9::1')
'2.6.2.0.0.1.0.5.f.0.0.0.0.0.0.9.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1'
>>> nibblize('2620:0105:F000:9:0:1::1')
'2.6.2.0.0.1.0.5.f.0.0.0.0.0.0.9.0.0.0.0.0.0.0.1.0.0.0.0.0.0.0.1'
"""
def nibblize( addr ):
    """Given an IPv6 address is 'colon' format, return the address in 'nibble' form::

        nibblize('2620:0105:F000::1')
        '2.6.2.0.0.1.0.5.F.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1'

    :param addr: The ip address to convert
    :type addr: str
    """
    ip_str = ipaddr.IPv6Address(addr).exploded #TODO, surround this in try except, catch CyAddressValueError
    return '.'.join(list(ip_str.replace(':','')))

def add_reverse_ipv4_domain( dname ):
    """This function adds a an IPv4 reverse domain.

    :param dname: The reverse domain to be added.
    :type dname: str
    :param ip_type: The type of reverse domain. It should be either an IPv4 or IPv6 address.
    :type ip_type: str -- '4' or '6'
    :raises: ReverseDomainExistsError
    """
    return _add_generic_reverse_domain( str(dname), '4' )
def remove_reverse_ipv4_domain( dname ):
    """This function removes an IPv4 reverse domain.

    :param dname: The reverse domain to be removed.
    :type dname: str
    :param ip_type: The type of reverse domain. It should be either an IPv4 or IPv6 address.
    :type ip_type: str -- '4' or '6'
    :raises: ReverseDomainExistsError

    .. note::
        Think about reverse_domains (and domains for that matter) as a tree. This function is only
        defined to work on *leaf nodes*. If you attempt to remove a none leaf reverse_domain a
        ReverseChildDomainExistsError will be thrown.
    """
    return _remove_generic_reverse_domain( str(dname), '4' )
def add_reverse_ipv6_domain( dname ):
    """This function adds a an IPv6 reverse domain.

    :param dname: The reverse domain to be added.
    :type dname: str
    :param ip_type: The type of reverse domain. It should be either an IPv4 or IPv6 address.
    :type ip_type: str -- '4' or '6'
    :raises: ReverseDomainExistsError
    """
    return _add_generic_reverse_domain( str(dname), '6' )
def remove_reverse_ipv6_domain( dname ):
    """This function removes an IPv6 reverse domain.

    :param dname: The reverse domain to be removed.
    :type dname: str
    :param ip_type: The type of reverse domain. It should be either an IPv4 or IPv6 address.
    :type ip_type: str -- '4' or '6'
    :raises: ReverseDomainExistsError

    .. note::
        Think about reverse_domains (and domains for that matter) as a tree. This function is only
        defined to work on *leaf nodes*. If you attempt to remove a none leaf reverse_domain a
        ReverseChildDomainExistsError will be thrown.
    """
    return _remove_generic_reverse_domain( str(dname), '6' )
<|endoftext|>


<commit_before>#
# Copyright (C) 2015 Satoru SATOH <ssato @ redhat.com>
# License: MIT
#
"""A module to consolidate access to template engine backends.
"""
from __future__ import absolute_import

import operator

from anytemplate.globals import LOGGER

import anytemplate.engines.base
import anytemplate.engines.strtemplate

ENGINES = [anytemplate.engines.strtemplate.Engine, ]

try:
    import anytemplate.engines.jinja2
    ENGINES.append(anytemplate.engines.jinja2.Engine)
except ImportError:
    LOGGER.info("jinja2 support was disable as needed module looks missing")

try:
    import anytemplate.engines.mako
    ENGINES.append(anytemplate.engines.mako.Engine)
except ImportError:
    LOGGER.info("mako support was disable as needed module looks missing")

try:
    import anytemplate.engines.tenjin
    ENGINES.append(anytemplate.engines.tenjin.Engine)
except ImportError:
    LOGGER.info("tenjin support was disable as needed module looks missing")

import anytemplate.engines.cheetah
if anytemplate.engines.cheetah.Template is None:
    LOGGER.info("Cheetah support was disable as needed module looks missing")
else:
    ENGINES.append(anytemplate.engines.cheetah.Engine)

try:
    import anytemplate.engines.pystache
    ENGINES.append(anytemplate.engines.pystache.Engine)
except ImportError:
    LOGGER.info("Pystache (mustache) support was disable as needed module "
                "looks missing")

def list_engines_by_priority(engines=None):
    """
    Return a list of engines supported sorted by each priority.
    """
    if engines is None:
        engines = ENGINES

    return sorted(engines, key=operator.methodcaller("priority"))

def find_by_filename(filename, engines=None):
    """
    Find a list of template engine classes to render template `filename`.

    :param filename: Template file name (may be a absolute/relative path)
    :param engines: Template engines

    :return: A list of engines support given template file
    """
    if engines is None:
        engines = ENGINES

    if filename is None:
        return list_engines_by_priority(engines)

    return sorted((e for e in engines if e.supports(filename)),
                  key=operator.methodcaller("priority"))

def find_by_name(name, engines=None):
    """
    Find a template engine class specified by its name `name`.

    :param name: Template name
    :param engines: Template engines

    :return: A template engine or None if no any template engine of given name
        were found.
    """
    if engines is None:
        engines = ENGINES

    for egn in engines:
        if egn.name() == name:
            return egn

    return None

# vim:sw=4:ts=4:et:
<commit_msg>change: allow passing no args to .engine.find_by_filename to use it as list_engine() (no args)<commit_after>#
# Copyright (C) 2015 Satoru SATOH <ssato @ redhat.com>
# License: MIT
#
"""A module to consolidate access to template engine backends.
"""
from __future__ import absolute_import

import operator

from anytemplate.globals import LOGGER

import anytemplate.engines.base
import anytemplate.engines.strtemplate

ENGINES = [anytemplate.engines.strtemplate.Engine, ]

try:
    import anytemplate.engines.jinja2
    ENGINES.append(anytemplate.engines.jinja2.Engine)
except ImportError:
    LOGGER.info("jinja2 support was disable as needed module looks missing")

try:
    import anytemplate.engines.mako
    ENGINES.append(anytemplate.engines.mako.Engine)
except ImportError:
    LOGGER.info("mako support was disable as needed module looks missing")

try:
    import anytemplate.engines.tenjin
    ENGINES.append(anytemplate.engines.tenjin.Engine)
except ImportError:
    LOGGER.info("tenjin support was disable as needed module looks missing")

import anytemplate.engines.cheetah
if anytemplate.engines.cheetah.Template is None:
    LOGGER.info("Cheetah support was disable as needed module looks missing")
else:
    ENGINES.append(anytemplate.engines.cheetah.Engine)

try:
    import anytemplate.engines.pystache
    ENGINES.append(anytemplate.engines.pystache.Engine)
except ImportError:
    LOGGER.info("Pystache (mustache) support was disable as needed module "
                "looks missing")

def list_engines_by_priority(engines=None):
    """
    Return a list of engines supported sorted by each priority.
    """
    if engines is None:
        engines = ENGINES

    return sorted(engines, key=operator.methodcaller("priority"))

def find_by_filename(filename=None, engines=None):
    """
    Find a list of template engine classes to render template `filename`.

    :param filename: Template file name (may be a absolute/relative path)
    :param engines: Template engines

    :return: A list of engines support given template file
    """
    if engines is None:
        engines = ENGINES

    if filename is None:
        return list_engines_by_priority(engines)

    return sorted((e for e in engines if e.supports(filename)),
                  key=operator.methodcaller("priority"))

def find_by_name(name, engines=None):
    """
    Find a template engine class specified by its name `name`.

    :param name: Template name
    :param engines: Template engines

    :return: A template engine or None if no any template engine of given name
        were found.
    """
    if engines is None:
        engines = ENGINES

    for egn in engines:
        if egn.name() == name:
            return egn

    return None

# vim:sw=4:ts=4:et:
<|endoftext|>


<commit_before>from binder.views import ModelView
from django.db import transaction
from django.db.models import Q
from binder.exceptions import BinderForbidden, BinderNotFound
from django.conf import settings

import logging

from enum import Enum

logger = logging.getLogger(__name__)

class PermissionNotCheckedError(Exception):
	pass

class ScopingError(BinderForbidden):
	pass

class UnexpectedScopeException(Exception):
	pass

class Scope(Enum):
	VIEW = 'view'
	ADD = 'add'
	CHANGE = 'change'
	DELETE = 'delete'

class PermissionView(ModelView):
	@property
	def _permission_definition(self):
		return settings.BINDER_PERMISSION

	def get_queryset(self, request):
		queryset = self.model.objects
		return self.scope_view(request, queryset)

	def _require_model_perm(self, perm_type, request, pk=None):
		'''
		Check if you have a model permission, and return the scopes
		'''
		if hasattr(self, 'perms_via'):
			model = self.perms_via
		else:
			model = self.model

		setattr(request, '_has_permission_check', True)

		# Allow the superuser to do everything
		if request.user.is_superuser:
			return ['all']

		perm = '{}.{}_{}'.format(model._meta.app_label, perm_type, model.__name__.lower())

		if getattr(request, '_permission', None) is None:
			self._parse_permissions(request)

		if perm not in request._permission:
			raise BinderForbidden(perm, request.user)

		scopes = request._permission[perm]
		if perm_type not in ['view', 'add', 'change', 'delete'] and len(scopes) > 0:
			raise Exception('Scoping for permission {} can not be done. Scoping is only possible for view, add, '
							'change and delete'.format(perm_type))
		return scopes

	def _parse_permissions(self, request):
		'''
		Translate high level permissions to low level permissions on the request
		'''
		permissions = request.user.get_all_permissions()
		permissions.add('default')

		# Mapping from permission_name => list of scopes
		_permission_class = {}
		for p in permissions:
			if p in self._permission_definition:
				for permission, scope in self._permission_definition[p]:
					if permission not in _permission_class:
						_permission_class[permission] = []  # Permission, without any scopes
					if scope is not None:
						_permission_class[permission].append(scope)
		request._permission = _permission_class

	def _has_one_of_permissions(self, request, permissions):
		'''
		Check if we have one of a set of permissions
		'''
		for p in permissions:
			if request.user.has_perm(p):
				logger.debug('passed permission check: {}'.format(p))
				return True
		return False

	def dispatch(self, request, *args, **kwargs):
		'''
		make sure that permissions are checked, and scoping is done
		'''
		setattr(request, '_has_permission_check', False)
		with transaction.atomic():
			result = super().dispatch(request, *args, **kwargs)

			# If an error occured, we can return the result directly
			if result.status_code >= 300:
				return result

			permission_checked = getattr(request, '_has_permission_check', True)
			if not permission_checked:
				raise PermissionError('No permission check done. Shame on you!')

			scopes = getattr(request, '_scopes', [])

			if request.method.lower() == 'get':
				if Scope.VIEW not in scopes:
					raise PermissionError('No view scoping done!')
			elif request.method.lower() == 'delete':
				if Scope.DELETE not in scopes:
					raise PermissionError('No delete scoping done!')
			else:
				# NOTE: The DELETE here is actually for undeletes.  We
				# could use CHANGE scope in delete() if undelete=True,
				# but it makes more sense that delete and undelete are
				# both allowed iff you have "delete" permission.
				if Scope.ADD not in scopes and Scope.CHANGE not in scopes and Scope.DELETE not in scopes:
					raise PermissionError('No change or add scoping done!')
		return result

	def dispatch_file_field(self, request, pk=None, file_field=None):
		'''
		GET requests are not permission checked in binder
		'''
		if pk is not None:
			# Check if we have permission for the object to get
			objs = self._get_objs(self.get_queryset(request).filter(pk=pk), request)
			if len(objs) != 1:
				raise BinderNotFound()

		if request.method == 'POST':
			self.scope_add(request, None, [])
		elif request.method == 'PUT' or request.method == 'add':
			self.scope_change(request, objs[0], [])

		return super().dispatch_file_field(request, pk, file_field)

	def _scope_view_all(self, request):
		return self.model.objects

	def _scope_add_all(self, request, object, values):
		return True

	def _scope_change_all(self, request, object, values):
		return True

	def _scope_delete_all(self, request, object, values):
		return True

	@staticmethod
	def _save_scope(request, scope):
		'''
		Save that we did a scoping on the request object. This allows us to check that scoping is done
		'''
		if getattr(request, '_scopes', None) is None:
			request._scopes = []
		request._scopes.append(scope)

	'''
	Scope the creation/changing of an object
	'''
	def _store(self, obj, values, request, ignore_unknown_fields=False):

		if obj.pk is None:
			self.scope_add(request, obj, values)
		else:
			self.scope_change(request, obj, values)

		return super()._store(obj, values, request, ignore_unknown_fields)

	def scope_add(self, request, object, values):
		'''
		Scope adding of an object. Raises binderforbidden error if the user does not have the scope to add a model
		'''
		scopes = self._require_model_perm('add', request)
		can_add = False

		for s in scopes:
			scope_name = '_scope_add_{}'.format(s)
			if getattr(self, scope_name, None) is None:
				raise UnexpectedScopeException(
					'Scope {} is not implemented for model {}'.format(scope_name, self.model))
			can_add |= getattr(self, scope_name)(request, object, values)

		if not can_add:
			raise ScopingError(user=request.user, perm='You do not have a scope that allows you to add model={}'.format(self.model))

		self._save_scope(request, Scope.ADD)

	def scope_change(self, request, object, values):
		scopes = self._require_model_perm('change', request)
		can_change = False

		for s in scopes:
			scope_name = '_scope_change_{}'.format(s)
			if getattr(self, scope_name, None) is None:
				raise UnexpectedScopeException(
					'Scope {} is not implemented for model {}'.format(scope_name, self.model))
			can_change |= getattr(self, scope_name)(request, object, values)

		if not can_change:
			raise ScopingError(user=request.user, perm='You do not have a scope that allows you to change model={}'.format(self.model))

		self._save_scope(request, Scope.CHANGE)

	'''Do a scoping on a possibly empty list'''
	def scope_change_list(self, request, objects, values):
		for o in objects:
			self.scope_change(request, o, values)
		else:
			setattr(request, '_has_permission_check', True)
		self._save_scope(request, Scope.CHANGE)

	def scope_view(self, request, queryset):
		'''
		Performs the scopes for a get request
		'''
		scopes = self._require_model_perm('view', request)
		scope_queries = []
		for s in scopes:
			scope_name = '_scope_view_{}'.format(s)
			if getattr(self, scope_name, None) is None:
				raise UnexpectedScopeException(
					'Scope {} is not implemented for model {}'.format(scope_name, self.model))
			scope_queries.append(getattr(self, scope_name)(request))
		subfilter = Q(id__in=[-1])
		for scope_query in scope_queries:
			subfilter |= Q(id__in=scope_query.values('id'))

		self._save_scope(request, Scope.VIEW)

		return queryset.filter(subfilter)

	def delete(self, request, pk=None, undelete=False):
		query = self.get_queryset(request)
		object = query.filter(pk=pk)

		if len(object) == 0:
			raise BinderNotFound()

		self.scope_delete(request, object, {})

		return super().delete(request, pk, undelete)

	def scope_delete(self, request, object, values):
		'''
		Performs the scopes for deletion of an obbject
		'''
		scopes = self._require_model_perm('delete', request)

		can_change = False

		for s in scopes:
			scope_name = '_scope_delete_{}'.format(s)
			if getattr(self, scope_name, None) is None:
				raise UnexpectedScopeException(
					'Scope {} is not implemented for model {}'.format(scope_name, self.model))
			can_change |= getattr(self, scope_name)(request, object, values)

		if not can_change:
			raise ScopingError(user=request.user, perm='You do not have a scope that allows you to delete model={}'.format(self.model))

		self._save_scope(request, Scope.DELETE)

	def view_history(self, request, pk=None, **kwargs):
		if not pk:
			raise BinderNotFound()

		# We must have permission to view the object. If not we can not view the history
		data = self._get_objs(self.get_queryset(request), request=request)
		if not data:
			raise BinderNotFound()

		return super().view_history(request, pk, **kwargs)

def no_scoping_required(*args, **kwargs):
	def decorator(func):
		def wrapper(self, request, *args, **kwargs):
			if request.method == 'GET':
				scope = Scope.VIEW
			elif request.method == 'POST':
				scope = Scope.ADD
			elif request.method == 'PUT':
				scope = Scope.CHANGE
			else:
				scope = Scope.DELETE
			PermissionView._save_scope(request=request, scope=scope)
			return func(self, request, *args, **kwargs)
		return wrapper
	return decorator
<commit_msg>Code formatting.<commit_after>import logging
rom enum import Enum

from django.db import transaction
from django.db.models import Q
from django.conf import settings

from binder.exceptions import BinderForbidden, BinderNotFound
from binder.views import ModelView

logger = logging.getLogger(__name__)

class PermissionNotCheckedError(Exception):
	pass

class ScopingError(BinderForbidden):
	pass

class UnexpectedScopeException(Exception):
	pass

class Scope(Enum):
	VIEW = 'view'
	ADD = 'add'
	CHANGE = 'change'
	DELETE = 'delete'

class PermissionView(ModelView):
	@property
	def _permission_definition(self):
		return settings.BINDER_PERMISSION

	def get_queryset(self, request):
		queryset = self.model.objects
		return self.scope_view(request, queryset)

	def _require_model_perm(self, perm_type, request, pk=None):
		"""
		Check if you have a model permission, and return the scopes
		"""
		if hasattr(self, 'perms_via'):
			model = self.perms_via
		else:
			model = self.model

		setattr(request, '_has_permission_check', True)

		# Allow the superuser to do everything
		if request.user.is_superuser:
			return ['all']

		perm = '{}.{}_{}'.format(model._meta.app_label, perm_type, model.__name__.lower())

		if getattr(request, '_permission', None) is None:
			self._parse_permissions(request)

		if perm not in request._permission:
			raise BinderForbidden(perm, request.user)

		scopes = request._permission[perm]
		if perm_type not in ['view', 'add', 'change', 'delete'] and len(scopes) > 0:
			raise Exception('Scoping for permission {} can not be done. Scoping is only possible for view, add, '
							'change and delete'.format(perm_type))
		return scopes

	def _parse_permissions(self, request):
		"""
		Translate high level permissions to low level permissions on the request
		"""
		permissions = request.user.get_all_permissions()
		permissions.add('default')

		# Mapping from permission_name => list of scopes
		_permission_class = {}
		for p in permissions:
			if p in self._permission_definition:
				for permission, scope in self._permission_definition[p]:
					if permission not in _permission_class:
						_permission_class[permission] = []  # Permission, without any scopes
					if scope is not None:
						_permission_class[permission].append(scope)
		request._permission = _permission_class

	def _has_one_of_permissions(self, request, permissions):
		"""
		Check if we have one of a set of permissions
		"""
		for p in permissions:
			if request.user.has_perm(p):
				logger.debug('passed permission check: {}'.format(p))
				return True
		return False

	def dispatch(self, request, *args, **kwargs):
		"""
		Make sure that permissions are checked, and scoping is done
		"""
		setattr(request, '_has_permission_check', False)
		with transaction.atomic():
			result = super().dispatch(request, *args, **kwargs)

			# If an error occured, we can return the result directly
			if result.status_code >= 300:
				return result

			permission_checked = getattr(request, '_has_permission_check', True)
			if not permission_checked:
				raise PermissionError('No permission check done. Shame on you!')

			scopes = getattr(request, '_scopes', [])

			if request.method.lower() == 'get':
				if Scope.VIEW not in scopes:
					raise PermissionError('No view scoping done!')
			elif request.method.lower() == 'delete':
				if Scope.DELETE not in scopes:
					raise PermissionError('No delete scoping done!')
			else:
				# NOTE: The DELETE here is actually for undeletes.  We
				# could use CHANGE scope in delete() if undelete=True,
				# but it makes more sense that delete and undelete are
				# both allowed iff you have "delete" permission.
				if Scope.ADD not in scopes and Scope.CHANGE not in scopes and Scope.DELETE not in scopes:
					raise PermissionError('No change or add scoping done!')
		return result

	def dispatch_file_field(self, request, pk=None, file_field=None):
		"""
		GET requests are not permission checked in binder
		"""
		if pk is not None:
			# Check if we have permission for the object to get
			objs = self._get_objs(self.get_queryset(request).filter(pk=pk), request)
			if len(objs) != 1:
				raise BinderNotFound()

		if request.method == 'POST':
			self.scope_add(request, None, [])
		elif request.method == 'PUT' or request.method == 'add':
			self.scope_change(request, objs[0], [])

		return super().dispatch_file_field(request, pk, file_field)

	def _scope_view_all(self, request):
		return self.model.objects

	def _scope_add_all(self, request, object, values):
		return True

	def _scope_change_all(self, request, object, values):
		return True

	def _scope_delete_all(self, request, object, values):
		return True

	@staticmethod
	def _save_scope(request, scope):
		"""
		Save that we did a scoping on the request object. This allows us to check that scoping is done
		"""
		if getattr(request, '_scopes', None) is None:
			request._scopes = []
		request._scopes.append(scope)

	def _store(self, obj, values, request, ignore_unknown_fields=False):
		"""
		Scope the creation/changing of an object
		"""
		if obj.pk is None:
			self.scope_add(request, obj, values)
		else:
			self.scope_change(request, obj, values)

		return super()._store(obj, values, request, ignore_unknown_fields)

	def scope_add(self, request, object, values):
		"""
		Scope adding of an object. Raises binderforbidden error if the user does not have the scope to add a model
		"""
		scopes = self._require_model_perm('add', request)
		can_add = False

		for s in scopes:
			scope_name = '_scope_add_{}'.format(s)
			if getattr(self, scope_name, None) is None:
				raise UnexpectedScopeException(
					'Scope {} is not implemented for model {}'.format(scope_name, self.model))
			can_add |= getattr(self, scope_name)(request, object, values)

		if not can_add:
			raise ScopingError(user=request.user, perm='You do not have a scope that allows you to add model={}'.format(self.model))

		self._save_scope(request, Scope.ADD)

	def scope_change(self, request, object, values):
		scopes = self._require_model_perm('change', request)
		can_change = False

		for s in scopes:
			scope_name = '_scope_change_{}'.format(s)
			if getattr(self, scope_name, None) is None:
				raise UnexpectedScopeException(
					'Scope {} is not implemented for model {}'.format(scope_name, self.model))
			can_change |= getattr(self, scope_name)(request, object, values)

		if not can_change:
			raise ScopingError(user=request.user, perm='You do not have a scope that allows you to change model={}'.format(self.model))

		self._save_scope(request, Scope.CHANGE)

	def scope_change_list(self, request, objects, values):
		"""
		Do a scoping on a possibly empty list
		"""
		for o in objects:
			self.scope_change(request, o, values)
		else:
			setattr(request, '_has_permission_check', True)
		self._save_scope(request, Scope.CHANGE)

	def scope_view(self, request, queryset):
		"""
		Performs the scopes for a get request
		"""
		scopes = self._require_model_perm('view', request)
		scope_queries = []
		for s in scopes:
			scope_name = '_scope_view_{}'.format(s)
			if getattr(self, scope_name, None) is None:
				raise UnexpectedScopeException(
					'Scope {} is not implemented for model {}'.format(scope_name, self.model))
			scope_queries.append(getattr(self, scope_name)(request))
		subfilter = Q(id__in=[-1])
		for scope_query in scope_queries:
			subfilter |= Q(id__in=scope_query.values('id'))

		self._save_scope(request, Scope.VIEW)

		return queryset.filter(subfilter)

	def delete(self, request, pk=None, undelete=False):
		query = self.get_queryset(request)
		object = query.filter(pk=pk)

		if len(object) == 0:
			raise BinderNotFound()

		self.scope_delete(request, object, {})

		return super().delete(request, pk, undelete)

	def scope_delete(self, request, object, values):
		"""
		Performs the scopes for deletion of an obbject
		"""
		scopes = self._require_model_perm('delete', request)

		can_change = False

		for s in scopes:
			scope_name = '_scope_delete_{}'.format(s)
			if getattr(self, scope_name, None) is None:
				raise UnexpectedScopeException(
					'Scope {} is not implemented for model {}'.format(scope_name, self.model))
			can_change |= getattr(self, scope_name)(request, object, values)

		if not can_change:
			raise ScopingError(user=request.user, perm='You do not have a scope that allows you to delete model={}'.format(self.model))

		self._save_scope(request, Scope.DELETE)

	def view_history(self, request, pk=None, **kwargs):
		if not pk:
			raise BinderNotFound()

		# We must have permission to view the object. If not we can not view the history
		data = self._get_objs(self.get_queryset(request), request=request)
		if not data:
			raise BinderNotFound()

		return super().view_history(request, pk, **kwargs)

def no_scoping_required(*args, **kwargs):
	def decorator(func):
		def wrapper(self, request, *args, **kwargs):
			if request.method == 'GET':
				scope = Scope.VIEW
			elif request.method == 'POST':
				scope = Scope.ADD
			elif request.method == 'PUT':
				scope = Scope.CHANGE
			else:
				scope = Scope.DELETE
			PermissionView._save_scope(request=request, scope=scope)
			return func(self, request, *args, **kwargs)
		return wrapper
	return decorator
<|endoftext|>


<commit_before>from flask_assets import Bundle
from flask_assets import Environment

assets = Environment()

js = Bundle('lib/vue/dist/vue.js',
            'lib/underscore/underscore.js',
            'js/lexers.js',
            'js/app.js',
            'js/pastes.js',
            filters='uglifyjs', output='js/compiled.js')
assets.register('js_all', js)

css = Bundle('css/app.scss',
             'css/pastes.scss',
             # 'css/embed.scss',
             filters='scss', output='css/compiled.css')

assets.register('css_all', css)
<commit_msg>生成语法列表<commit_after>from flask_assets import Bundle
from flask_assets import Environment

assets = Environment()

js = Bundle('lib/vue/dist/vue.js',
            'lib/underscore/underscore.js',
            'js/lexers.js',
            'js/app.js',
            'js/pastes.js',
            'js/tags.js',
            'js/users.js',
            filters='uglifyjs', output='js/compiled.js')
assets.register('js_all', js)

css = Bundle('css/app.scss',
             'css/pastes.scss',
             # 'css/embed.scss',
             filters='scss', output='css/compiled.css')

assets.register('css_all', css)
<|endoftext|>


<commit_before>import argparse
import concurrent.futures
import json
import os
import subprocess
import sys

updates = {}

def eprint(*args, **kwargs):
    print(*args, file=sys.stderr, **kwargs)

def run_update_script(package):
    eprint(f" - {package['name']}: UPDATING ...")

    subprocess.run(package['updateScript'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=True)

def main(max_workers, keep_going, packages):
    with open(sys.argv[1]) as f:
        packages = json.load(f)

    eprint()
    eprint('Going to be running update for following packages:')
    for package in packages:
        eprint(f" - {package['name']}")
    eprint()

    confirm = input('Press Enter key to continue...')
    if confirm == '':
        eprint()
        eprint('Running update for:')

        with concurrent.futures.ProcessPoolExecutor(max_workers=max_workers) as executor:
            for package in packages:
                updates[executor.submit(run_update_script, package)] = package

            for future in concurrent.futures.as_completed(updates):
                package = updates[future]

                try:
                    future.result()
                    eprint(f" - {package['name']}: DONE.")
                except subprocess.CalledProcessError as e:
                    eprint(f" - {package['name']}: ERROR")
                    eprint()
                    eprint(f"--- SHOWING ERROR LOG FOR {package['name']} ----------------------")
                    eprint()
                    eprint(e.stdout.decode('utf-8'))
                    with open(f"{package['pname']}.log", 'wb') as f:
                        f.write(e.stdout)
                    eprint()
                    eprint(f"--- SHOWING ERROR LOG FOR {package['name']} ----------------------")

                    if not keep_going:
                        sys.exit(1)

        eprint()
        eprint('Packages updated!')
        sys.exit()
    else:
        eprint('Aborting!')
        sys.exit(130)

parser = argparse.ArgumentParser(description='Update packages')
parser.add_argument('--max-workers', '-j', dest='max_workers', type=int, help='Number of updates to run concurrently', nargs='?', default=4)
parser.add_argument('--keep-going', '-k', dest='keep_going', action='store_true', help='Do not stop after first failure')
parser.add_argument('packages', help='JSON file containing the list of package names and their update scripts')

if __name__ == '__main__':
    args = parser.parse_args()

    try:
        main(args.max_workers, args.keep_going, args.packages)
    except (KeyboardInterrupt, SystemExit) as e:
        for update in updates:
            update.cancel()

        sys.exit(e.code if isinstance(e, SystemExit) else 130)
<commit_msg>update.nix: use ThreadPoolExecutor<commit_after>import argparse
import concurrent.futures
import json
import os
import subprocess
import sys

updates = {}

def eprint(*args, **kwargs):
    print(*args, file=sys.stderr, **kwargs)

def run_update_script(package):
    eprint(f" - {package['name']}: UPDATING ...")

    subprocess.run(package['updateScript'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=True)

def main(max_workers, keep_going, packages):
    with open(sys.argv[1]) as f:
        packages = json.load(f)

    eprint()
    eprint('Going to be running update for following packages:')
    for package in packages:
        eprint(f" - {package['name']}")
    eprint()

    confirm = input('Press Enter key to continue...')
    if confirm == '':
        eprint()
        eprint('Running update for:')

        with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
            for package in packages:
                updates[executor.submit(run_update_script, package)] = package

            for future in concurrent.futures.as_completed(updates):
                package = updates[future]

                try:
                    future.result()
                    eprint(f" - {package['name']}: DONE.")
                except subprocess.CalledProcessError as e:
                    eprint(f" - {package['name']}: ERROR")
                    eprint()
                    eprint(f"--- SHOWING ERROR LOG FOR {package['name']} ----------------------")
                    eprint()
                    eprint(e.stdout.decode('utf-8'))
                    with open(f"{package['pname']}.log", 'wb') as f:
                        f.write(e.stdout)
                    eprint()
                    eprint(f"--- SHOWING ERROR LOG FOR {package['name']} ----------------------")

                    if not keep_going:
                        sys.exit(1)

        eprint()
        eprint('Packages updated!')
        sys.exit()
    else:
        eprint('Aborting!')
        sys.exit(130)

parser = argparse.ArgumentParser(description='Update packages')
parser.add_argument('--max-workers', '-j', dest='max_workers', type=int, help='Number of updates to run concurrently', nargs='?', default=4)
parser.add_argument('--keep-going', '-k', dest='keep_going', action='store_true', help='Do not stop after first failure')
parser.add_argument('packages', help='JSON file containing the list of package names and their update scripts')

if __name__ == '__main__':
    args = parser.parse_args()

    try:
        main(args.max_workers, args.keep_going, args.packages)
    except (KeyboardInterrupt, SystemExit) as e:
        for update in updates:
            update.cancel()

        sys.exit(e.code if isinstance(e, SystemExit) else 130)
<|endoftext|>


<commit_before>"""
    weasyprint.float
    ----------------

    Layout for floating boxes.

    :copyright: Copyright 2011-2014 Simon Sapin and contributors, see AUTHORS.
    :license: BSD, see LICENSE for details.

"""

from ..formatting_structure import boxes
from .markers import list_marker_layout
from .min_max import handle_min_max_width
from .percentages import resolve_percentages, resolve_position_percentages
from .preferred import shrink_to_fit
from .tables import table_wrapper_width

@handle_min_max_width
def float_width(box, context, containing_block):
    # Check that box.width is auto even if the caller does it too, because
    # the handle_min_max_width decorator can change the value
    if box.width == 'auto':
        box.width = shrink_to_fit(context, box, containing_block.width)

def float_layout(context, box, containing_block, device_size, absolute_boxes,
                 fixed_boxes):
    """Set the width and position of floating ``box``."""
    # avoid a circular imports
    from .blocks import block_container_layout
    from .flex import flex_layout
    from .inlines import inline_replaced_box_width_height

    cb_width, cb_height = (containing_block.width, containing_block.height)
    resolve_percentages(box, (cb_width, cb_height))

    # TODO: This is only handled later in blocks.block_container_layout
    # http://www.w3.org/TR/CSS21/visudet.html#normal-block
    if cb_height == 'auto':
        cb_height = (
            containing_block.position_y - containing_block.content_box_y())

    resolve_position_percentages(box, (cb_width, cb_height))

    if box.margin_left == 'auto':
        box.margin_left = 0
    if box.margin_right == 'auto':
        box.margin_right = 0
    if box.margin_top == 'auto':
        box.margin_top = 0
    if box.margin_bottom == 'auto':
        box.margin_bottom = 0

    clearance = get_clearance(context, box)
    if clearance is not None:
        box.position_y += clearance

    if isinstance(box, boxes.BlockReplacedBox):
        inline_replaced_box_width_height(box, device_size=None)
    elif box.width == 'auto':
        float_width(box, context, containing_block)

    if box.is_table_wrapper:
        table_wrapper_width(context, box, (cb_width, cb_height))

    if isinstance(box, boxes.BlockContainerBox):
        context.create_block_formatting_context()
        box, _, _, _, _ = block_container_layout(
            context, box, max_position_y=float('inf'),
            skip_stack=None, device_size=device_size, page_is_empty=False,
            absolute_boxes=absolute_boxes, fixed_boxes=fixed_boxes,
            adjoining_margins=None)
        list_marker_layout(context, box)
        context.finish_block_formatting_context(box)
    elif isinstance(box, boxes.FlexContainerBox):
        box, _, _, _, _ = flex_layout(
            context, box, max_position_y=float('inf'),
            skip_stack=None, containing_block=containing_block,
            device_size=device_size, page_is_empty=False,
            absolute_boxes=absolute_boxes, fixed_boxes=fixed_boxes)
    else:
        assert isinstance(box, boxes.BlockReplacedBox)

    box = find_float_position(context, box, containing_block)

    context.excluded_shapes.append(box)

    return box

def find_float_position(context, box, containing_block):
    """Get the right position of the float ``box``."""
    # See http://www.w3.org/TR/CSS2/visuren.html#float-position

    # Point 4 is already handled as box.position_y is set according to the
    # containing box top position, with collapsing margins handled

    # Points 5 and 6, box.position_y is set to the highest position_y possible
    if context.excluded_shapes:
        highest_y = context.excluded_shapes[-1].position_y
        if box.position_y < highest_y:
            box.translate(0, highest_y - box.position_y)

    # Points 1 and 2
    position_x, position_y, available_width = avoid_collisions(
        context, box, containing_block)

    # Point 9
    # position_y is set now, let's define position_x
    # for float: left elements, it's already done!
    if box.style['float'] == 'right':
        position_x += available_width - box.margin_width()

    box.translate(position_x - box.position_x, position_y - box.position_y)

    return box

def get_clearance(context, box, collapsed_margin=0):
    """Return None if there is no clearance, otherwise the clearance value."""
    clearance = None
    hypothetical_position = box.position_y + collapsed_margin
    # Hypothetical position is the position of the top border edge
    for excluded_shape in context.excluded_shapes:
        if box.style['clear'] in (excluded_shape.style['float'], 'both'):
            y, h = excluded_shape.position_y, excluded_shape.margin_height()
            if hypothetical_position < y + h:
                clearance = max(
                    (clearance or 0), y + h - hypothetical_position)
    return clearance

def avoid_collisions(context, box, containing_block, outer=True):
    excluded_shapes = context.excluded_shapes
    position_y = box.position_y if outer else box.border_box_y()

    box_width = box.margin_width() if outer else box.border_width()
    box_height = box.margin_height() if outer else box.border_height()

    if box.border_height() == 0 and box.is_floated():
        return 0, 0, containing_block.width

    while True:
        colliding_shapes = []
        for shape in excluded_shapes:
            # Assign locals to avoid slow attribute lookups.
            shape_position_y = shape.position_y
            shape_margin_height = shape.margin_height()
            if ((shape_position_y < position_y <
                 shape_position_y + shape_margin_height) or
                (shape_position_y < position_y + box_height <
                 shape_position_y + shape_margin_height) or
                (shape_position_y >= position_y and
                 shape_position_y + shape_margin_height <=
                 position_y + box_height)):
                colliding_shapes.append(shape)
        left_bounds = [
            shape.position_x + shape.margin_width()
            for shape in colliding_shapes
            if shape.style['float'] == 'left']
        right_bounds = [
            shape.position_x
            for shape in colliding_shapes
            if shape.style['float'] == 'right']

        # Set the default maximum bounds
        max_left_bound = containing_block.content_box_x()
        max_right_bound = \
            containing_block.content_box_x() + containing_block.width

        if not outer:
            max_left_bound += box.margin_left
            max_right_bound -= box.margin_right

        # Set the real maximum bounds according to sibling float elements
        if left_bounds or right_bounds:
            if left_bounds:
                max_left_bound = max(max(left_bounds), max_left_bound)
            if right_bounds:
                max_right_bound = min(min(right_bounds), max_right_bound)

            # Points 3, 7 and 8
            if box_width > max_right_bound - max_left_bound:
                # The box does not fit here
                new_positon_y = min(
                    shape.position_y + shape.margin_height()
                    for shape in colliding_shapes)
                if new_positon_y > position_y:
                    # We can find a solution with a higher position_y
                    position_y = new_positon_y
                    continue
                # No solution, we must put the box here
        break

    position_x = max_left_bound
    available_width = max_right_bound - max_left_bound

    if not outer:
        position_x -= box.margin_left
        position_y -= box.margin_top

    return position_x, position_y, available_width
<commit_msg>Fix comment<commit_after>"""
    weasyprint.float
    ----------------

    Layout for floating boxes.

    :copyright: Copyright 2011-2014 Simon Sapin and contributors, see AUTHORS.
    :license: BSD, see LICENSE for details.

"""

from ..formatting_structure import boxes
from .markers import list_marker_layout
from .min_max import handle_min_max_width
from .percentages import resolve_percentages, resolve_position_percentages
from .preferred import shrink_to_fit
from .tables import table_wrapper_width

@handle_min_max_width
def float_width(box, context, containing_block):
    # Check that box.width is auto even if the caller does it too, because
    # the handle_min_max_width decorator can change the value
    if box.width == 'auto':
        box.width = shrink_to_fit(context, box, containing_block.width)

def float_layout(context, box, containing_block, device_size, absolute_boxes,
                 fixed_boxes):
    """Set the width and position of floating ``box``."""
    # Avoid circular imports
    from .blocks import block_container_layout
    from .flex import flex_layout
    from .inlines import inline_replaced_box_width_height

    cb_width, cb_height = (containing_block.width, containing_block.height)
    resolve_percentages(box, (cb_width, cb_height))

    # TODO: This is only handled later in blocks.block_container_layout
    # http://www.w3.org/TR/CSS21/visudet.html#normal-block
    if cb_height == 'auto':
        cb_height = (
            containing_block.position_y - containing_block.content_box_y())

    resolve_position_percentages(box, (cb_width, cb_height))

    if box.margin_left == 'auto':
        box.margin_left = 0
    if box.margin_right == 'auto':
        box.margin_right = 0
    if box.margin_top == 'auto':
        box.margin_top = 0
    if box.margin_bottom == 'auto':
        box.margin_bottom = 0

    clearance = get_clearance(context, box)
    if clearance is not None:
        box.position_y += clearance

    if isinstance(box, boxes.BlockReplacedBox):
        inline_replaced_box_width_height(box, device_size=None)
    elif box.width == 'auto':
        float_width(box, context, containing_block)

    if box.is_table_wrapper:
        table_wrapper_width(context, box, (cb_width, cb_height))

    if isinstance(box, boxes.BlockContainerBox):
        context.create_block_formatting_context()
        box, _, _, _, _ = block_container_layout(
            context, box, max_position_y=float('inf'),
            skip_stack=None, device_size=device_size, page_is_empty=False,
            absolute_boxes=absolute_boxes, fixed_boxes=fixed_boxes,
            adjoining_margins=None)
        list_marker_layout(context, box)
        context.finish_block_formatting_context(box)
    elif isinstance(box, boxes.FlexContainerBox):
        box, _, _, _, _ = flex_layout(
            context, box, max_position_y=float('inf'),
            skip_stack=None, containing_block=containing_block,
            device_size=device_size, page_is_empty=False,
            absolute_boxes=absolute_boxes, fixed_boxes=fixed_boxes)
    else:
        assert isinstance(box, boxes.BlockReplacedBox)

    box = find_float_position(context, box, containing_block)

    context.excluded_shapes.append(box)

    return box

def find_float_position(context, box, containing_block):
    """Get the right position of the float ``box``."""
    # See http://www.w3.org/TR/CSS2/visuren.html#float-position

    # Point 4 is already handled as box.position_y is set according to the
    # containing box top position, with collapsing margins handled

    # Points 5 and 6, box.position_y is set to the highest position_y possible
    if context.excluded_shapes:
        highest_y = context.excluded_shapes[-1].position_y
        if box.position_y < highest_y:
            box.translate(0, highest_y - box.position_y)

    # Points 1 and 2
    position_x, position_y, available_width = avoid_collisions(
        context, box, containing_block)

    # Point 9
    # position_y is set now, let's define position_x
    # for float: left elements, it's already done!
    if box.style['float'] == 'right':
        position_x += available_width - box.margin_width()

    box.translate(position_x - box.position_x, position_y - box.position_y)

    return box

def get_clearance(context, box, collapsed_margin=0):
    """Return None if there is no clearance, otherwise the clearance value."""
    clearance = None
    hypothetical_position = box.position_y + collapsed_margin
    # Hypothetical position is the position of the top border edge
    for excluded_shape in context.excluded_shapes:
        if box.style['clear'] in (excluded_shape.style['float'], 'both'):
            y, h = excluded_shape.position_y, excluded_shape.margin_height()
            if hypothetical_position < y + h:
                clearance = max(
                    (clearance or 0), y + h - hypothetical_position)
    return clearance

def avoid_collisions(context, box, containing_block, outer=True):
    excluded_shapes = context.excluded_shapes
    position_y = box.position_y if outer else box.border_box_y()

    box_width = box.margin_width() if outer else box.border_width()
    box_height = box.margin_height() if outer else box.border_height()

    if box.border_height() == 0 and box.is_floated():
        return 0, 0, containing_block.width

    while True:
        colliding_shapes = []
        for shape in excluded_shapes:
            # Assign locals to avoid slow attribute lookups.
            shape_position_y = shape.position_y
            shape_margin_height = shape.margin_height()
            if ((shape_position_y < position_y <
                 shape_position_y + shape_margin_height) or
                (shape_position_y < position_y + box_height <
                 shape_position_y + shape_margin_height) or
                (shape_position_y >= position_y and
                 shape_position_y + shape_margin_height <=
                 position_y + box_height)):
                colliding_shapes.append(shape)
        left_bounds = [
            shape.position_x + shape.margin_width()
            for shape in colliding_shapes
            if shape.style['float'] == 'left']
        right_bounds = [
            shape.position_x
            for shape in colliding_shapes
            if shape.style['float'] == 'right']

        # Set the default maximum bounds
        max_left_bound = containing_block.content_box_x()
        max_right_bound = \
            containing_block.content_box_x() + containing_block.width

        if not outer:
            max_left_bound += box.margin_left
            max_right_bound -= box.margin_right

        # Set the real maximum bounds according to sibling float elements
        if left_bounds or right_bounds:
            if left_bounds:
                max_left_bound = max(max(left_bounds), max_left_bound)
            if right_bounds:
                max_right_bound = min(min(right_bounds), max_right_bound)

            # Points 3, 7 and 8
            if box_width > max_right_bound - max_left_bound:
                # The box does not fit here
                new_positon_y = min(
                    shape.position_y + shape.margin_height()
                    for shape in colliding_shapes)
                if new_positon_y > position_y:
                    # We can find a solution with a higher position_y
                    position_y = new_positon_y
                    continue
                # No solution, we must put the box here
        break

    position_x = max_left_bound
    available_width = max_right_bound - max_left_bound

    if not outer:
        position_x -= box.margin_left
        position_y -= box.margin_top

    return position_x, position_y, available_width
<|endoftext|>