<commit_before>import functools
import os
import re

from flask import Blueprint, render_template, abort, request, url_for, redirect, flash
from flaskext.utils import get_next, PaginatedQuery, slugify
from wtfpeewee.orm import model_form

current_dir = os.path.dirname(__file__)

class ModelAdmin(object):
    """
    ModelAdmin provides create/edit/delete functionality for a peewee Model.
    """
    paginate_by = 20
    columns = None

    def __init__(self, admin, model):
        self.admin = admin
        self.model = model
        self.pk_name = self.model._meta.pk_name

    def get_url_name(self, name):
        return '%s.%s_%s' % (
            self.admin.blueprint.name,
            self.get_admin_name(),
            name,
        )

    def get_form(self):
        return model_form(self.model)

    def get_add_form(self):
        return self.get_form()

    def get_edit_form(self):
        return self.get_form()

    def get_query(self):
        return self.model.select()

    def get_object(self, pk):
        return self.get_query().get(**{self.pk_name: pk})

    def get_urls(self):
        return (
            ('/', self.index),
            ('/add/', self.add),
            ('/delete/', self.delete),
            ('/<pk>/', self.edit),
        )

    def get_columns(self):
        return self.model._meta.get_field_names()

    def column_is_sortable(self, col):
        return col in self.model._meta.fields

    def get_display_name(self):
        return self.model.__name__

    def get_admin_name(self):
        return slugify(self.model.__name__)

    def save_model(self, instance, form, adding=False):
        form.populate_obj(instance)
        instance.save()
        return instance

    def index(self):
        query = self.get_query()
        form = self.get_form()(request.args)

        ordering = request.args.get('ordering') or ''
        if ordering:
            desc, column = ordering.startswith('-'), ordering.lstrip('-')
            if self.column_is_sortable(column):
                query = query.order_by((column, desc and 'desc' or 'asc'))

        filters = []
        raw_filters = []
        for key in request.args:
            if key in ('ordering', 'page'):
                continue

            values = request.args.getlist(key)
            raw_filters.append((key, values))

            if len(values) == 1:
                filters.append((key, values[0]))
            else:
                filters.append(('%s__in' % key, values))

        if filters:
            query = query.filter(**dict(filters))

        pq = PaginatedQuery(query, self.paginate_by)

        if request.method == 'POST':
            id_list = request.form.getlist('id')
            return redirect(url_for(self.get_url_name('delete'), id=id_list))

        return render_template('admin/models/index.html',
            model_admin=self,
            query=pq,
            ordering=ordering,
            form=form,
            filters=filters,
            raw_filters=raw_filters,
        )

    def add(self):
        Form = self.get_add_form()

        if request.method == 'POST':
            form = Form(request.form)
            if form.validate():
                instance = self.save_model(self.model(), form, True)
                flash('New %s saved successfully' % self.get_display_name())
                return redirect(
                    url_for(self.get_url_name('edit'), pk=instance.get_pk())
                )
        else:
            form = Form()

        return render_template('admin/models/add.html', model_admin=self, form=form)

    def edit(self, pk):
        try:
            instance = self.get_object(pk)
        except self.model.DoesNotExist:
            abort(404)

        Form = self.get_edit_form()

        if request.method == 'POST':
            form = Form(request.form, obj=instance)
            if form.validate():
                self.save_model(instance, form, False)
                flash('Changes to %s saved successfully' % self.get_display_name())
                return redirect(
                    url_for(self.get_url_name('edit'), pk=instance.get_pk())
                )
        else:
            form = Form(obj=instance)

        return render_template('admin/models/edit.html', model_admin=self, instance=instance, form=form)

    def delete(self):
        if request.method == 'GET':
            id_list = request.args.getlist('id')
            query = self.model.select().where(**{
                '%s__in' % self.model._meta.pk_name: id_list
            })
        elif request.method == 'POST':
            id_list = request.form.getlist('id')
            query = self.model.delete().where(**{
                '%s__in' % self.model._meta.pk_name: id_list
            })
            results = query.execute()
            flash('Successfully deleted %s %ss' % (results, self.get_display_name()))
            return redirect(url_for(self.get_url_name('index')))

        return render_template('admin/models/delete.html', model_admin=self, query=query)

class AdminPanel(object):
    template_name = 'admin/panels/default.html'

    def __init__(self, admin, title):
        self.admin = admin
        self.title = title
        self.slug = slugify(self.title)

    def dashboard_url(self):
        return url_for('%s.index' % (self.admin.blueprint.name))

    def get_urls(self):
        return ()

    def get_url_name(self, name):
        return '%s.panel_%s_%s' % (
            self.admin.blueprint.name,
            self.slug,
            name,
        )

    def get_template_name(self):
        return self.template_name

    def get_context(self):
        return {}

    def render(self):
        return render_template(self.get_template_name(), panel=self, **self.get_context())

class BluePrintFactory(object):
    def __init__(self, name='admin', **kwargs):
        self.name = name
        self.import_name = __name__
        self.extra = kwargs

    def get_blueprint(self):
        return Blueprint(
            self.name,
            self.import_name,
            static_folder=os.path.join(current_dir, 'static'),
            template_folder=os.path.join(current_dir, 'templates'),
            **self.extra
        )

blueprint_factory = BluePrintFactory()

class AdminTemplateHelper(object):
    def __init__(self, admin):
        self.admin = admin
        self.app = self.admin.app

    def get_model_field(self, model, field):
        attr = getattr(model, field)
        if callable(attr):
            return attr()
        return attr

    def get_form_field(self, form, field_name):
        return getattr(form, field_name)

    def fix_underscores(self, s):
        return s.replace('_', ' ').title()

    def update_querystring(self, querystring, key, val):
        if not querystring:
            return '%s=%s' % (key, val)
        else:
            querystring = re.sub('%s(?:[^&]+)?&?' % key, '', querystring).rstrip('&')
            return ('%s&%s=%s' % (querystring, key, val)).lstrip('&')

    def get_verbose_name(self, model, column_name):
        try:
            field = model._meta.fields[column_name]
        except KeyError:
            return self.fix_underscores(column_name)
        else:
            return field.verbose_name

    def get_model_admins(self):
        return {'model_admins': self.admin.get_model_admins()}

    def prepare_environment(self):
        self.app.template_context_processors[None].append(self.get_model_admins)

        self.app.jinja_env.globals['get_model_field'] = self.get_model_field
        self.app.jinja_env.globals['get_form_field'] = self.get_form_field
        self.app.jinja_env.globals['get_verbose_name'] = self.get_verbose_name
        self.app.jinja_env.filters['fix_underscores'] = self.fix_underscores
        self.app.jinja_env.globals['update_querystring'] = self.update_querystring

class Admin(object):
    def __init__(self, app, auth, blueprint_factory=blueprint_factory,
                 template_helper=AdminTemplateHelper, prefix='/admin'):
        self.app = app
        self.auth = auth

        self._registry = {}
        self._panels = {}

        self.blueprint = blueprint_factory.get_blueprint()
        self.url_prefix = prefix

        self.template_helper = template_helper(self)
        self.template_helper.prepare_environment()

    def auth_required(self, func):
        @functools.wraps(func)
        def inner(*args, **kwargs):
            user = self.auth.get_logged_in_user()

            if not user:
                login_url = url_for('%s.login' % self.auth.blueprint.name, next=get_next())
                return redirect(login_url)

            if not self.check_user_permission(user):
                abort(403)

            return func(*args, **kwargs)
        return inner

    def check_user_permission(self, user):
        return user.admin

    def get_urls(self):
        return (
            ('/', self.auth_required(self.index)),
        )

    def register(self, model, admin_class=ModelAdmin, skip_urls=False):
        model_admin = admin_class(self, model)
        admin_name = model_admin.get_admin_name()

        self._registry[admin_name] = model_admin

    def unregister(self, model):
        del(self._registry[model])

    def register_panel(self, title, panel):
        panel_instance = panel(self, title)
        self._panels[title] = panel_instance

    def unregister_panel(self, title):
        del(self._panels[title])

    def get_model_admins(self):
        return sorted(self._registry.values(), key=lambda o: o.get_admin_name())

    def get_panels(self):
        return sorted(self._panels.values(), key=lambda o: o.slug)

    def index(self):
        return render_template('admin/index.html',
            model_admins=self.get_model_admins(),
            panels=self.get_panels(),
        )

    def register_blueprint(self, **kwargs):
        self.app.register_blueprint(
            self.blueprint,
            url_prefix=self.url_prefix,
            **kwargs
        )

    def configure_routes(self):
        for url, callback in self.get_urls():
            self.blueprint.route(url, methods=['GET', 'POST'])(callback)

        for model_admin in self._registry.values():
            admin_name = model_admin.get_admin_name()
            for url, callback in model_admin.get_urls():
                full_url = '/%s%s' % (admin_name, url)
                self.blueprint.add_url_rule(
                    full_url,
                    '%s_%s' % (admin_name, callback.__name__),
                    self.auth_required(callback),
                    methods=['GET', 'POST'],
                )

        for panel in self._panels.values():
            for url, callback in panel.get_urls():
                full_url = '/%s%s' % (panel.slug, url)
                self.blueprint.add_url_rule(
                    full_url,
                    'panel_%s_%s' % (panel.slug, callback.__name__),
                    self.auth_required(callback),
                    methods=['GET', 'POST'],
                )

    def setup(self):
        self.configure_routes()
        self.register_blueprint()
<commit_msg>Removing unused keyword arg<commit_after>import functools
import os
import re

from flask import Blueprint, render_template, abort, request, url_for, redirect, flash
from flaskext.utils import get_next, PaginatedQuery, slugify
from wtfpeewee.orm import model_form

current_dir = os.path.dirname(__file__)

class ModelAdmin(object):
    """
    ModelAdmin provides create/edit/delete functionality for a peewee Model.
    """
    paginate_by = 20
    columns = None

    def __init__(self, admin, model):
        self.admin = admin
        self.model = model
        self.pk_name = self.model._meta.pk_name

    def get_url_name(self, name):
        return '%s.%s_%s' % (
            self.admin.blueprint.name,
            self.get_admin_name(),
            name,
        )

    def get_form(self):
        return model_form(self.model)

    def get_add_form(self):
        return self.get_form()

    def get_edit_form(self):
        return self.get_form()

    def get_query(self):
        return self.model.select()

    def get_object(self, pk):
        return self.get_query().get(**{self.pk_name: pk})

    def get_urls(self):
        return (
            ('/', self.index),
            ('/add/', self.add),
            ('/delete/', self.delete),
            ('/<pk>/', self.edit),
        )

    def get_columns(self):
        return self.model._meta.get_field_names()

    def column_is_sortable(self, col):
        return col in self.model._meta.fields

    def get_display_name(self):
        return self.model.__name__

    def get_admin_name(self):
        return slugify(self.model.__name__)

    def save_model(self, instance, form, adding=False):
        form.populate_obj(instance)
        instance.save()
        return instance

    def index(self):
        query = self.get_query()
        form = self.get_form()(request.args)

        ordering = request.args.get('ordering') or ''
        if ordering:
            desc, column = ordering.startswith('-'), ordering.lstrip('-')
            if self.column_is_sortable(column):
                query = query.order_by((column, desc and 'desc' or 'asc'))

        filters = []
        raw_filters = []
        for key in request.args:
            if key in ('ordering', 'page'):
                continue

            values = request.args.getlist(key)
            raw_filters.append((key, values))

            if len(values) == 1:
                filters.append((key, values[0]))
            else:
                filters.append(('%s__in' % key, values))

        if filters:
            query = query.filter(**dict(filters))

        pq = PaginatedQuery(query, self.paginate_by)

        if request.method == 'POST':
            id_list = request.form.getlist('id')
            return redirect(url_for(self.get_url_name('delete'), id=id_list))

        return render_template('admin/models/index.html',
            model_admin=self,
            query=pq,
            ordering=ordering,
            form=form,
            filters=filters,
            raw_filters=raw_filters,
        )

    def add(self):
        Form = self.get_add_form()

        if request.method == 'POST':
            form = Form(request.form)
            if form.validate():
                instance = self.save_model(self.model(), form, True)
                flash('New %s saved successfully' % self.get_display_name())
                return redirect(
                    url_for(self.get_url_name('edit'), pk=instance.get_pk())
                )
        else:
            form = Form()

        return render_template('admin/models/add.html', model_admin=self, form=form)

    def edit(self, pk):
        try:
            instance = self.get_object(pk)
        except self.model.DoesNotExist:
            abort(404)

        Form = self.get_edit_form()

        if request.method == 'POST':
            form = Form(request.form, obj=instance)
            if form.validate():
                self.save_model(instance, form, False)
                flash('Changes to %s saved successfully' % self.get_display_name())
                return redirect(
                    url_for(self.get_url_name('edit'), pk=instance.get_pk())
                )
        else:
            form = Form(obj=instance)

        return render_template('admin/models/edit.html', model_admin=self, instance=instance, form=form)

    def delete(self):
        if request.method == 'GET':
            id_list = request.args.getlist('id')
            query = self.model.select().where(**{
                '%s__in' % self.model._meta.pk_name: id_list
            })
        elif request.method == 'POST':
            id_list = request.form.getlist('id')
            query = self.model.delete().where(**{
                '%s__in' % self.model._meta.pk_name: id_list
            })
            results = query.execute()
            flash('Successfully deleted %s %ss' % (results, self.get_display_name()))
            return redirect(url_for(self.get_url_name('index')))

        return render_template('admin/models/delete.html', model_admin=self, query=query)

class AdminPanel(object):
    template_name = 'admin/panels/default.html'

    def __init__(self, admin, title):
        self.admin = admin
        self.title = title
        self.slug = slugify(self.title)

    def dashboard_url(self):
        return url_for('%s.index' % (self.admin.blueprint.name))

    def get_urls(self):
        return ()

    def get_url_name(self, name):
        return '%s.panel_%s_%s' % (
            self.admin.blueprint.name,
            self.slug,
            name,
        )

    def get_template_name(self):
        return self.template_name

    def get_context(self):
        return {}

    def render(self):
        return render_template(self.get_template_name(), panel=self, **self.get_context())

class BluePrintFactory(object):
    def __init__(self, name='admin', **kwargs):
        self.name = name
        self.import_name = __name__
        self.extra = kwargs

    def get_blueprint(self):
        return Blueprint(
            self.name,
            self.import_name,
            static_folder=os.path.join(current_dir, 'static'),
            template_folder=os.path.join(current_dir, 'templates'),
            **self.extra
        )

blueprint_factory = BluePrintFactory()

class AdminTemplateHelper(object):
    def __init__(self, admin):
        self.admin = admin
        self.app = self.admin.app

    def get_model_field(self, model, field):
        attr = getattr(model, field)
        if callable(attr):
            return attr()
        return attr

    def get_form_field(self, form, field_name):
        return getattr(form, field_name)

    def fix_underscores(self, s):
        return s.replace('_', ' ').title()

    def update_querystring(self, querystring, key, val):
        if not querystring:
            return '%s=%s' % (key, val)
        else:
            querystring = re.sub('%s(?:[^&]+)?&?' % key, '', querystring).rstrip('&')
            return ('%s&%s=%s' % (querystring, key, val)).lstrip('&')

    def get_verbose_name(self, model, column_name):
        try:
            field = model._meta.fields[column_name]
        except KeyError:
            return self.fix_underscores(column_name)
        else:
            return field.verbose_name

    def get_model_admins(self):
        return {'model_admins': self.admin.get_model_admins()}

    def prepare_environment(self):
        self.app.template_context_processors[None].append(self.get_model_admins)

        self.app.jinja_env.globals['get_model_field'] = self.get_model_field
        self.app.jinja_env.globals['get_form_field'] = self.get_form_field
        self.app.jinja_env.globals['get_verbose_name'] = self.get_verbose_name
        self.app.jinja_env.filters['fix_underscores'] = self.fix_underscores
        self.app.jinja_env.globals['update_querystring'] = self.update_querystring

class Admin(object):
    def __init__(self, app, auth, blueprint_factory=blueprint_factory,
                 template_helper=AdminTemplateHelper, prefix='/admin'):
        self.app = app
        self.auth = auth

        self._registry = {}
        self._panels = {}

        self.blueprint = blueprint_factory.get_blueprint()
        self.url_prefix = prefix

        self.template_helper = template_helper(self)
        self.template_helper.prepare_environment()

    def auth_required(self, func):
        @functools.wraps(func)
        def inner(*args, **kwargs):
            user = self.auth.get_logged_in_user()

            if not user:
                login_url = url_for('%s.login' % self.auth.blueprint.name, next=get_next())
                return redirect(login_url)

            if not self.check_user_permission(user):
                abort(403)

            return func(*args, **kwargs)
        return inner

    def check_user_permission(self, user):
        return user.admin

    def get_urls(self):
        return (
            ('/', self.auth_required(self.index)),
        )

    def register(self, model, admin_class=ModelAdmin):
        model_admin = admin_class(self, model)
        admin_name = model_admin.get_admin_name()

        self._registry[admin_name] = model_admin

    def unregister(self, model):
        del(self._registry[model])

    def register_panel(self, title, panel):
        panel_instance = panel(self, title)
        self._panels[title] = panel_instance

    def unregister_panel(self, title):
        del(self._panels[title])

    def get_model_admins(self):
        return sorted(self._registry.values(), key=lambda o: o.get_admin_name())

    def get_panels(self):
        return sorted(self._panels.values(), key=lambda o: o.slug)

    def index(self):
        return render_template('admin/index.html',
            model_admins=self.get_model_admins(),
            panels=self.get_panels(),
        )

    def register_blueprint(self, **kwargs):
        self.app.register_blueprint(
            self.blueprint,
            url_prefix=self.url_prefix,
            **kwargs
        )

    def configure_routes(self):
        for url, callback in self.get_urls():
            self.blueprint.route(url, methods=['GET', 'POST'])(callback)

        for model_admin in self._registry.values():
            admin_name = model_admin.get_admin_name()
            for url, callback in model_admin.get_urls():
                full_url = '/%s%s' % (admin_name, url)
                self.blueprint.add_url_rule(
                    full_url,
                    '%s_%s' % (admin_name, callback.__name__),
                    self.auth_required(callback),
                    methods=['GET', 'POST'],
                )

        for panel in self._panels.values():
            for url, callback in panel.get_urls():
                full_url = '/%s%s' % (panel.slug, url)
                self.blueprint.add_url_rule(
                    full_url,
                    'panel_%s_%s' % (panel.slug, callback.__name__),
                    self.auth_required(callback),
                    methods=['GET', 'POST'],
                )

    def setup(self):
        self.configure_routes()
        self.register_blueprint()
<|endoftext|>


<commit_before>from .app import app, mail, celery, security
from .utils import load_modules, url_converters, fallbackRender
from .schemas.user import CurrentUser
from collections import namedtuple

from flask_security import current_user

app.url_map.converters['model'] = url_converters.ModelConverter
app.url_map.converters['user'] = url_converters.UserConverter

# LOAD all external modules
load_modules(app)

# then load our views:
from beavy import views

# allows them to register on blueprints before we do that setup

# register blueprints
from .blueprints import setup as register_blueprints
register_blueprints(app)

# inject current_user in the template
@app.context_processor
def inject_user():
    if current_user.is_anonymous():
        return dict(SERIALIZED_USER='false')
    return dict(SERIALIZED_USER=CurrentUser().dumps(current_user).data)

# defer emails send by security
# to do so via celery
@celery.task
def send_security_email(msg):
    mail.send(msg)

@security.send_mail_task
def delay_security_email(msg):
    send_security_email.delay(msg)

## ---- generate object capabilities
def generate_capability_maps(obj):
    capabilities_map = {
        # there are a few defaults we always have
        'listed': [],
        'searchable': [],
    }
    for typ, kls in obj.__mapper__.polymorphic_map.items():
        if hasattr(kls.class_, 'CAPABILITIES'):
            for cap in kls.class_.CAPABILITIES:
                capabilities_map.setdefault(cap, []).append(typ)

    caps = namedtuple('Cababilities', capabilities_map.keys())
    obj.TypesForCapability = caps(**capabilities_map)

def replaceHomeEndpoint(app):
    HOME_URL = app.config["HOME_URL"]
    original_endpoint = None
    for rule in app.url_map.iter_rules():
        if HOME_URL == rule.rule:
            original_endpoint = rule.endpoint
            rule.rule = "/"
            rule.compile()
            break

    if original_endpoint:
        app.url_map.add(
            app.url_rule_class(HOME_URL, alias=True,
                               endpoint=original_endpoint))

# default home, blank.

@app.route("/hello")
@fallbackRender('home.html')
def hello():
    return {"title": "home"}

<commit_msg>[Styles] PEP8-ing<commit_after>from .app import app, mail, celery, security
from .utils import load_modules, url_converters, fallbackRender
from .schemas.user import CurrentUser
from collections import namedtuple

from flask_security import current_user

app.url_map.converters['model'] = url_converters.ModelConverter
app.url_map.converters['user'] = url_converters.UserConverter

# LOAD all external modules
load_modules(app)

# then load our views:
from beavy import views

# allows them to register on blueprints before we do that setup

# register blueprints
from .blueprints import setup as register_blueprints
register_blueprints(app)

# inject current_user in the template
@app.context_processor
def inject_user():
    if current_user.is_anonymous():
        return dict(SERIALIZED_USER='false')
    return dict(SERIALIZED_USER=CurrentUser().dumps(current_user).data)

# defer emails send by security
# to do so via celery
@celery.task
def send_security_email(msg):
    mail.send(msg)

@security.send_mail_task
def delay_security_email(msg):
    send_security_email.delay(msg)

## ---- generate object capabilities
def generate_capability_maps(obj):
    capabilities_map = {
        # there are a few defaults we always have
        'listed': [],
        'searchable': [],
    }
    for typ, kls in obj.__mapper__.polymorphic_map.items():
        if hasattr(kls.class_, 'CAPABILITIES'):
            for cap in kls.class_.CAPABILITIES:
                capabilities_map.setdefault(cap, []).append(typ)

    caps = namedtuple('Cababilities', capabilities_map.keys())
    obj.TypesForCapability = caps(**capabilities_map)

def replaceHomeEndpoint(app):
    HOME_URL = app.config["HOME_URL"]
    original_endpoint = None
    for rule in app.url_map.iter_rules():
        if HOME_URL == rule.rule:
            original_endpoint = rule.endpoint
            rule.rule = "/"
            rule.compile()
            break

    if original_endpoint:
        app.url_map.add(
            app.url_rule_class(HOME_URL, alias=True,
                               endpoint=original_endpoint))

# default home, blank.

@app.route("/hello")
@fallbackRender('home.html')
def hello():
    return {"title": "home"}

<|endoftext|>


<commit_before># Copyright 2014 Camptocamp SA, 2017 ACSONE
# Copyright 2018 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

import logging

from odoo import api, fields, models, _
from odoo.addons.queue_job.job import job, Job

_logger = logging.getLogger(__name__)

BLOCK_SIZE = 1000

class AccountMove(models.Model):

    _inherit = 'account.move'

    to_post = fields.Boolean(
        string="Posting requested", readonly=True,
        help="Check this box to mark the move for batch posting")
    post_job_uuid = fields.Char(string="UUID of the Job to approve this move")

    @job(default_channel='root.account_move_batch_validate')
    def validate_one_move(self, move_id):
        move = self.browse(move_id)
        if move.exists():
            move.post()
        else:
            return _("Nothing to do because the record has been deleted")

    @api.model
    def _delay_post_marked(self, eta=None):
        """
        Create a job for every move marked for posting.
        If some moves already have a job, they are skipped.
        """
        AccountMoveObj = self.env[self._name]

        moves = self.search([
            ('to_post', '=', True),
            ('post_job_uuid', '=', False),
            ('state', '=', 'draft'),
        ])

        # maybe not creating too many dictionaries will make us a bit faster
        values = {'post_job_uuid': None}
        _logger.info(
            "%s jobs for posting moves have been created.", len(moves))

        for move in moves:
            new_job = AccountMoveObj.with_delay(eta=eta).validate_one_move(
                move.id)
            values['post_job_uuid'] = new_job.uuid
            move.write(values)
            self.env.cr.commit()  # pylint:disable=invalid-commit

    @api.model
    def _cancel_post_jobs(self):
        """
        Find moves where the mark has been removed and cancel the jobs.
        For the moves that are posted already it's too late: we skip them.
        """
        moves = self.search([
            ('to_post', '=', False),
            ('post_job_uuid', '!=', False),
            ('state', '=', 'draft'),
        ])

        for move in moves:
            job_rec = Job.load(self.env, move.post_job_uuid)
            if job_rec.state in ('pending', 'enqueued'):
                job_rec.set_done(
                    result=_("Task set to Done because the "
                             "user unmarked the move."))
                job_rec.store()

    @api.multi
    def mark_for_posting(self, eta=None):
        """
        Mark a list of moves for delayed posting, and enqueue the jobs.
        For massive amounts of moves, this becomes necessary to avoid
        MemoryError's
        """
        moves_count = len(self)
        _logger.info("%s moves marked for posting.", moves_count)

        values = {'to_post': True}

        for index in range(0, moves_count, BLOCK_SIZE):
            moves = self[index:index + BLOCK_SIZE]
            moves.write(values)
            # users like to see the flag sooner rather than later
            self.env.cr.commit()  # pylint:disable=invalid-commit
        self._delay_post_marked(eta=eta)

    @api.multi
    def unmark_for_posting(self):
        self.write({'to_post': False})
        self._cancel_post_jobs()
<commit_msg>[DOC] Reason usage of explicit `self.env.cr.commit()`-s<commit_after># Copyright 2014 Camptocamp SA, 2017 ACSONE
# Copyright 2018 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

import logging

from odoo import api, fields, models, _
from odoo.addons.queue_job.job import job, Job

_logger = logging.getLogger(__name__)

BLOCK_SIZE = 1000

class AccountMove(models.Model):

    _inherit = 'account.move'

    to_post = fields.Boolean(
        string="Posting requested", readonly=True,
        help="Check this box to mark the move for batch posting")
    post_job_uuid = fields.Char(string="UUID of the Job to approve this move")

    @job(default_channel='root.account_move_batch_validate')
    def validate_one_move(self, move_id):
        move = self.browse(move_id)
        if move.exists():
            move.post()
        else:
            return _("Nothing to do because the record has been deleted")

    @api.model
    def _delay_post_marked(self, eta=None):
        """
        Create a job for every move marked for posting.
        If some moves already have a job, they are skipped.
        """
        AccountMoveObj = self.env[self._name]

        moves = self.search([
            ('to_post', '=', True),
            ('post_job_uuid', '=', False),
            ('state', '=', 'draft'),
        ])

        # maybe not creating too many dictionaries will make us a bit faster
        values = {'post_job_uuid': None}
        _logger.info(
            "%s jobs for posting moves have been created.", len(moves))

        for move in moves:
            new_job = AccountMoveObj.with_delay(eta=eta).validate_one_move(
                move.id)
            values['post_job_uuid'] = new_job.uuid
            move.write(values)
            # Explicit committing is done for the capability of tracking
            # created jobs in live, during creation process
            self.env.cr.commit()  # pylint:disable=invalid-commit

    @api.model
    def _cancel_post_jobs(self):
        """
        Find moves where the mark has been removed and cancel the jobs.
        For the moves that are posted already it's too late: we skip them.
        """
        moves = self.search([
            ('to_post', '=', False),
            ('post_job_uuid', '!=', False),
            ('state', '=', 'draft'),
        ])

        for move in moves:
            job_rec = Job.load(self.env, move.post_job_uuid)
            if job_rec.state in ('pending', 'enqueued'):
                job_rec.set_done(
                    result=_("Task set to Done because the "
                             "user unmarked the move."))
                job_rec.store()

    @api.multi
    def mark_for_posting(self, eta=None):
        """
        Mark a list of moves for delayed posting, and enqueue the jobs.
        For massive amounts of moves, this becomes necessary to avoid
        MemoryError's
        """
        moves_count = len(self)
        _logger.info("%s moves marked for posting.", moves_count)

        values = {'to_post': True}

        for index in range(0, moves_count, BLOCK_SIZE):
            moves = self[index:index + BLOCK_SIZE]
            moves.write(values)
            # Explicit committing is done for the capability of tracking
            # created jobs in live, during creation process
            # users like to see the flag sooner rather than later
            self.env.cr.commit()  # pylint:disable=invalid-commit
        self._delay_post_marked(eta=eta)

    @api.multi
    def unmark_for_posting(self):
        self.write({'to_post': False})
        self._cancel_post_jobs()
<|endoftext|>


<commit_before>#!/usr/bin/env python

import argparse
import boto3
import logging
import os
import sys
import time

from ecsUpdate import ApplyECS

logging.basicConfig(format="%(asctime)s %(levelname)s [%(threadName)s] - %(message)s", stream=sys.stdout,
                    level=logging.INFO)

class ApplyCF:
    def __init__(self):
        self.success_status = ['CREATE_COMPLETE','UPDATE_COMPLETE']
        self.failed_status = ['CREATE_FAILED', 'ROLLBACK_IN_PROGRESS','ROLLBACK_FAILED','ROLLBACK_COMPLETE','UPDATE_ROLLBACK_IN_PROGRESS','UPDATE_ROLLBACK_FAILED','UPDATE_ROLLBACK_COMPLETE']

    def catfile(self, fn):
      with open(fn) as f:
        print f.read()

    def main(self, dir_, cf_params):

        file_dir = os.path.dirname(os.path.realpath(__file__))
        job_path = os.path.join(file_dir, dir_)

        self.load(job_path, cf_params)

    def load(self, job_path, cf_params):
        name = cf_params['name']
        env = cf_params['env']
        cluster = cf_params['cluster']
        region = cf_params['region']
        elb_name = 'ecs-elb-' + cluster
        cf_client = boto3.client('cloudformation', region_name=region)

        self.extract_common_ecs_params(cf_params, cluster, elb_name, env, region)

        for subdir, dirs, files in os.walk(job_path):
            for fn in files:
                filename = os.path.join(subdir, fn)

                # Skip non-cf files
                ext = filename.split('.')[-1]
                if ext != 'template':
                    continue
                name = filename.split('/')[-1].split('.')[0]
                logging.info("Processing CloudFormation Template: " + filename)
                parameters = [{'ParameterKey': 'name', 'ParameterValue': name}]

                if name is None or name in filename:
                    with open(filename, 'r') as f_h:
                        try:
                            cf_template = f_h.read()
                        except:
                            logging.exception("Error reading file %s " % filename)
                            self.catfile(filename)
                            raise
                        validate_response = {}
                        try:
                            validate_response = cf_client.validate_template(TemplateBody=cf_template)
                            logging.info("CloudFormation template validated")
                        except Exception as e:
                            logging.error("Error validating file: %s" % filename)
                            logging.error(validate_response)
                            logging.exception(e)
                            sys.exit(1)

                        try:
                            for cf_parameter in validate_response['Parameters']:
                                if cf_parameter['ParameterKey'] not in cf_params:
                                    logging.warning("Parameter: %s is specified by template in %s but not specified after --cfparams" % (filename, cf_parameter['ParameterKey']))
                                parameters.append({'ParameterKey': cf_parameter['ParameterKey'],
                                                   'ParameterValue': cf_params[cf_parameter['ParameterKey']]})
                            service_name = "%s-%s-%s" % (env, name, cluster)
                            existing_stacks = cf_client.list_stacks()
                            cf_command = cf_client.create_stack
                            for stack in existing_stacks['StackSummaries']:
                                if stack['StackName'] == service_name and stack['StackStatus'] != 'DELETE_COMPLETE' :
                                    cf_command = cf_client.update_stack
                                    break
                            logging.info("Updating CloudFormation Stack: " + service_name)
                            cf_response = cf_command(StackName=service_name, TemplateBody=cf_template, Parameters=parameters, Capabilities=["CAPABILITY_IAM"])
                        except Exception as e:
                            logging.error("Error executing CloudFormation: %s" % filename)
                            logging.exception(e)
                            sys.exit(1)
                        logging.info(cf_response)

                        while True:
                            time.sleep(5)

                            try:
                                describe_stacks_response = cf_client.describe_stacks(StackName=service_name)
                                stack_status = describe_stacks_response['Stacks'][0]['StackStatus']
                                if stack_status in self.success_status:
                                    logging.info("Stack create/update complete, status: %s" % stack_status)
                                    sys.exit(0)
                                elif stack_status in self.failed_status:
                                    logging.error("Stack create/update failed, status: %s" % stack_status)
                                    sys.exit(1)
                            except Exception as e:
                                logging.error("CloudFormation executed OK but stack was not created/updated: %s" % service_name)
                                logging.exception(e)
                                sys.exit(1)

    def extract_common_ecs_params(self, cf_params, cluster, elb_name, env, region):
        elb_client = boto3.client('elbv2', region_name=region)
        balancer_arn, vpc_id = ApplyECS.get_load_balancer(elb_client, elb_name, cluster, env)
        listener_arn = ApplyECS.get_elb_listener(elb_client, balancer_arn)
        cf_params['vpcid'] = vpc_id
        cf_params['listenerarn'] = listener_arn
        response = elb_client.describe_rules(ListenerArn=listener_arn)
        rules = response['Rules']
        top_priority = max([r['Priority'] if r['Priority'] != 'default' else "0" for r in rules])
        cf_params['priority'] = str(int(top_priority) + 1)

def argv_to_dict(args):
    argsdict = {}
    for farg in args:
        if farg.startswith('--'):
            key = farg[2:]
        else:
            value = farg
            argsdict[key] = value
    return argsdict

if __name__ == '__main__':
    p = ApplyCF()
    print(sys.argv)

    parser = argparse.ArgumentParser(description='Uploads / Updates ECS Task & Service Definitions to an ECS Cluster')
    parser.add_argument('--dir', help='relative directory name of service and task definitions', default='ecs')
    parser.add_argument('--cfparams', nargs=argparse.REMAINDER)
    args = parser.parse_args()
    cf_params = argv_to_dict(args.cfparams)

    p.main(args.dir, cf_params)
<commit_msg>Updated program help description.<commit_after>#!/usr/bin/env python

import argparse
import boto3
import logging
import os
import sys
import time

from ecsUpdate import ApplyECS

logging.basicConfig(format="%(asctime)s %(levelname)s [%(threadName)s] - %(message)s", stream=sys.stdout,
                    level=logging.INFO)

class ApplyCF:
    def __init__(self):
        self.success_status = ['CREATE_COMPLETE','UPDATE_COMPLETE']
        self.failed_status = ['CREATE_FAILED', 'ROLLBACK_IN_PROGRESS','ROLLBACK_FAILED','ROLLBACK_COMPLETE','UPDATE_ROLLBACK_IN_PROGRESS','UPDATE_ROLLBACK_FAILED','UPDATE_ROLLBACK_COMPLETE']

    def catfile(self, fn):
      with open(fn) as f:
        print f.read()

    def main(self, dir_, cf_params):

        file_dir = os.path.dirname(os.path.realpath(__file__))
        job_path = os.path.join(file_dir, dir_)

        self.load(job_path, cf_params)

    def load(self, job_path, cf_params):
        name = cf_params['name']
        env = cf_params['env']
        cluster = cf_params['cluster']
        region = cf_params['region']
        elb_name = 'ecs-elb-' + cluster
        cf_client = boto3.client('cloudformation', region_name=region)

        self.extract_common_ecs_params(cf_params, cluster, elb_name, env, region)

        for subdir, dirs, files in os.walk(job_path):
            for fn in files:
                filename = os.path.join(subdir, fn)

                # Skip non-cf files
                ext = filename.split('.')[-1]
                if ext != 'template':
                    continue
                name = filename.split('/')[-1].split('.')[0]
                logging.info("Processing CloudFormation Template: " + filename)
                parameters = [{'ParameterKey': 'name', 'ParameterValue': name}]

                if name is None or name in filename:
                    with open(filename, 'r') as f_h:
                        try:
                            cf_template = f_h.read()
                        except:
                            logging.exception("Error reading file %s " % filename)
                            self.catfile(filename)
                            raise
                        validate_response = {}
                        try:
                            validate_response = cf_client.validate_template(TemplateBody=cf_template)
                            logging.info("CloudFormation template validated")
                        except Exception as e:
                            logging.error("Error validating file: %s" % filename)
                            logging.error(validate_response)
                            logging.exception(e)
                            sys.exit(1)

                        try:
                            for cf_parameter in validate_response['Parameters']:
                                if cf_parameter['ParameterKey'] not in cf_params:
                                    logging.warning("Parameter: %s is specified by template in %s but not specified after --cfparams" % (filename, cf_parameter['ParameterKey']))
                                parameters.append({'ParameterKey': cf_parameter['ParameterKey'],
                                                   'ParameterValue': cf_params[cf_parameter['ParameterKey']]})
                            service_name = "%s-%s-%s" % (env, name, cluster)
                            existing_stacks = cf_client.list_stacks()
                            cf_command = cf_client.create_stack
                            for stack in existing_stacks['StackSummaries']:
                                if stack['StackName'] == service_name and stack['StackStatus'] != 'DELETE_COMPLETE' :
                                    cf_command = cf_client.update_stack
                                    break
                            logging.info("Updating CloudFormation Stack: " + service_name)
                            cf_response = cf_command(StackName=service_name, TemplateBody=cf_template, Parameters=parameters, Capabilities=["CAPABILITY_IAM"])
                        except Exception as e:
                            logging.error("Error executing CloudFormation: %s" % filename)
                            logging.exception(e)
                            sys.exit(1)
                        logging.info(cf_response)

                        while True:
                            time.sleep(5)

                            try:
                                describe_stacks_response = cf_client.describe_stacks(StackName=service_name)
                                stack_status = describe_stacks_response['Stacks'][0]['StackStatus']
                                if stack_status in self.success_status:
                                    logging.info("Stack create/update complete, status: %s" % stack_status)
                                    sys.exit(0)
                                elif stack_status in self.failed_status:
                                    logging.error("Stack create/update failed, status: %s" % stack_status)
                                    sys.exit(1)
                            except Exception as e:
                                logging.error("CloudFormation executed OK but stack was not created/updated: %s" % service_name)
                                logging.exception(e)
                                sys.exit(1)

    def extract_common_ecs_params(self, cf_params, cluster, elb_name, env, region):
        elb_client = boto3.client('elbv2', region_name=region)
        balancer_arn, vpc_id = ApplyECS.get_load_balancer(elb_client, elb_name, cluster, env)
        listener_arn = ApplyECS.get_elb_listener(elb_client, balancer_arn)
        cf_params['vpcid'] = vpc_id
        cf_params['listenerarn'] = listener_arn
        response = elb_client.describe_rules(ListenerArn=listener_arn)
        rules = response['Rules']
        top_priority = max([r['Priority'] if r['Priority'] != 'default' else "0" for r in rules])
        cf_params['priority'] = str(int(top_priority) + 1)

def argv_to_dict(args):
    argsdict = {}
    for farg in args:
        if farg.startswith('--'):
            key = farg[2:]
        else:
            value = farg
            argsdict[key] = value
    return argsdict

if __name__ == '__main__':
    p = ApplyCF()
    print(sys.argv)

    parser = argparse.ArgumentParser(description='Executes CloudFormation templates to create / update ECS related resources')
    parser.add_argument('--dir', help='relative directory name of service and task definitions', default='ecs')
    parser.add_argument('--cfparams', nargs=argparse.REMAINDER)
    args = parser.parse_args()
    cf_params = argv_to_dict(args.cfparams)

    p.main(args.dir, cf_params)
<|endoftext|>


<commit_before>#!/usr/bin/python
# -*- coding: utf-8 -*-

# Hive Netius System
# Copyright (c) 2008-2016 Hive Solutions Lda.
#
# This file is part of Hive Netius System.
#
# Hive Netius System is free software: you can redistribute it and/or modify
# it under the terms of the Apache License as published by the Apache
# Foundation, either version 2.0 of the License, or (at your option) any
# later version.
#
# Hive Netius System is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# Apache License for more details.
#
# You should have received a copy of the Apache License along with
# Hive Netius System. If not, see <http://www.apache.org/licenses/>.

__author__ = "João Magalhães <joamag@hive.pt>"
""" The author(s) of the module """

__version__ = "1.0.0"
""" The version of the module """

__revision__ = "$LastChangedRevision$"
""" The revision number of the module """

__date__ = "$LastChangedDate$"
""" The last change date of the module """

__copyright__ = "Copyright (c) 2008-2016 Hive Solutions Lda."
""" The copyright for the module """

__license__ = "Apache License, Version 2.0"
""" The license for the module """

import struct

import netius.common

from . import http

class HTTP2Connection(http.HTTPConnection):

    def __init__(self, legacy = True, *args, **kwargs):
        http.HTTPConnection.__init__(self, *args, **kwargs)
        self.legacy = legacy
        self.preface = False
        self.preface_b = b""

    def open(self, *args, **kwargs):
        http.HTTPConnection.open(self, *args, **kwargs)
        if not self.legacy: self.set_h2()

    def set_h2(self):
        self.legacy = False
        if self.parser: self.parser.destroy()
        self.parser = netius.common.HTTP2Parser(self, store = True)
        self.parser.bind("on_frame", self.on_frame)
        self.parser.bind("on_headers", self.on_headers)
        self.parser.bind("on_settings", self.on_settings)
        self.parser.bind("on_ping", self.on_ping)
        self.parser.bind("on_goaway", self.on_goaway)
        self.parser.bind("on_window_update", self.on_window_update)

    def parse(self, data):
        if not self.legacy and not self.preface:
            data = self.parse_preface(data)
            if not data: return
        return self.parser.parse(data)

    def parse_preface(self, data):
        """
        Tries to run the parsing on the preface part of the
        connection establishment using the provided data
        note that the data is buffered in case the proper size
        has not been reached for proper validation.

        This should be the first step when trying to establish
        a proper HTTP 2 connection.

        :type data: String
        :param data: The data buffer that is going to be used to
        try to parse the connection preface.
        :rtype: String
        :return: The resulting data after the preface has been
        parsed, this should be empty or invalid in case no data
        is pending to be parsed.
        """

        # adds the current data to the buffer of bytes pending
        # in the preface parsing and then verified that the proper
        # preface size has been reached, in case it has not returned
        # an invalid value immediately (no further parsing)
        self.preface_b += data
        preface_l = len(netius.common.HTTP2_PREFACE)
        is_size = len(self.preface_b) >= preface_l
        if not is_size: return None

        # retrieves the preface string from the buffer (according to size)
        # and runs the string based verification, raising an exception in
        # case there's a mismatch in the string validation
        preface = self.preface_b[:preface_l]
        if not preface == netius.common.HTTP2_PREFACE:
            raise netius.ParserError("Invalid preface")

        # sets the preface (parsed) flag indicating that the preface has
        # been parsed for the current connection
        self.preface = True

        # retrieves the extra data added to the preface buffer and then
        # unsets the same buffer (no more preface parsing)
        data = self.preface_b[preface_l:]
        self.preface_b = b""

        # calls the proper callback for the preface sending both the current
        # instance and the associated parser for handling
        self.owner.on_preface_http2(self, self.parser)

        # returns the remaining data pending to be parsed so that it may be
        # parsed by any extra operation
        return data

    def send_response(
        self,
        data = None,
        headers = None,
        version = None,
        code = 200,
        code_s = None,
        apply = False,
        stream = None,
        final = True,
        flush = True,
        delay = False,
        callback = None
    ):
        # in case the legacy mode is enabled the send response call is
        # forwarded to the upper layers so that it's handled properly
        if self.legacy: return http.HTTPConnection.send_response(
            self,
            data = data,
            headers = headers,
            version = version,
            code = code,
            code_s = code_s,
            apply = apply,
            stream = stream,
            final = final,
            flush = flush,
            delay = delay,
            callback = callback
        )

        # retrieves the various parts that define the response
        # and runs a series of normalization processes to retrieve
        # the relevant information of the data to be sent to client
        data = data or ""
        data = netius.legacy.bytes(data)
        headers = headers or dict()
        data_l = len(data) if data else 0
        is_empty = code in (204, 304) and data_l == 0

        # verifies if the content length header is currently present
        # in the provided headers and in case it's not inserts it
        if not "content-length" in headers and not is_empty:
            headers["content-length"] = str(data_l)

        # in case the apply flag is set the apply all operation is performed
        # so that a series of headers are applied to the current context
        # (things like the name of the server connection, etc)
        if apply: self.owner._apply_all(self.parser, self, headers)

        # sends the initial headers data (including status line), this should
        # trigger the initial data sent to the peer/client
        count = self.send_header(
            headers = headers,
            version = version,
            code = code,
            code_s = code_s,
            stream = stream
        )

        # sends the part/payload information (data) to the client and optionally
        # flushes the current internal buffers to enforce sending of the value
        count += self.send_part(
            data,
            stream = stream,
            final = final,
            flush = flush,
            delay = delay,
            callback = callback
        )
        return count

    def send_header(
        self,
        headers = None,
        version = None,
        code = 200,
        code_s = None,
        stream = None,
        delay = False,
        callback = None
    ):
        # in case the legacy mode is enabled the send header call is
        # forwarded to the upper layers so that it's handled properly
        if self.legacy: return http.HTTPConnection.send_header(
            self,
            headers = headers,
            version = version,
            code = code,
            code_s = code_s,
            stream = stream,
            delay = delay,
            callback = callback
        )

        # verifies if the headers value has been provided and in case it
        # has not creates a new empty dictionary (runtime compatibility)
        headers = headers or dict()

        # defines the proper default base http version in case it has not
        # been provided as part the default values
        version = version or "HTTP/2.0"

        # creates the headers base list that is going to store the various
        # header tuples representing the headers in canonical http2 form
        headers_b = []
        headers_b.append((":status", str(code)))

        # iterates over the complete set of raw header values to normalize
        # them and add them to the currently defined base list
        for key, value in headers.items():
            key = netius.common.header_down(key)
            if key in ("connection",): continue
            if not type(value) == list: value = (value,)
            for _value in value: headers_b.append((key, _value))

        return self.send_headers(
            headers_b,
            stream = stream,
            delay = delay,
            callback = callback
        )

    def send_part(
        self,
        data,
        stream = None,
        final = True,
        flush = False,
        delay = False,
        callback = None
    ):
        if self.legacy: return http.HTTPConnection.send_part(
            self,
            data,
            stream = stream,
            final = final,
            flush = flush,
            delay = delay,
            callback = callback
        )
        if flush:
            count = self.send_data(
                data,
                stream = stream,
                end_stream = final
            )
            self.flush(callback = callback)
        else:
            count = self.send_data(
                data,
                stream = stream,
                end_stream = final,
                delay = delay,
                callback = callback
            )
        return count

    def send_frame(
        self,
        type = 0x01,
        flags = 0x00,
        payload = b"",
        stream = 0x00,
        delay = False,
        callback = None
    ):
        size = len(payload)
        header = struct.pack("!BHBBI", 0x00, size, type, flags, stream)
        message = header + payload
        return self.send(message, delay = delay, callback = callback)

    def send_data(
        self,
        data = b"",
        end_stream = True,
        stream = None,
        delay = False,
        callback = None
    ):
        flags = 0x00
        if end_stream: flags |= 0x01
        return self.send_frame(
            type = netius.common.DATA,
            flags = flags,
            payload = data,
            stream = stream,
            delay = delay,
            callback = callback
        )

    def send_headers(
        self,
        headers = [],
        end_stream = False,
        end_headers = True,
        stream = None,
        delay = False,
        callback = None
    ):
        flags = 0x00
        if end_stream: flags |= 0x01
        if end_headers: flags |= 0x04
        payload = self.parser.encoder.encode(headers)
        return self.send_frame(
            type = netius.common.HEADERS,
            flags = flags,
            payload = payload,
            stream = stream,
            delay = delay,
            callback = callback
        )

    def send_rst_stream(
        self,
        error_code = 0x00,
        stream = None,
        delay = False,
        callback = None
    ):
        payload = struct.pack("!I", error_code)
        return self.send_frame(
            type = netius.common.RST_STREAM,
            payload = payload,
            stream = stream,
            delay = delay,
            callback = callback
        )

    def send_settings(
        self,
        settings = (),
        ack = False,
        delay = False,
        callback = None
    ):
        flags = 0x00
        if ack: flags |= 0x01
        buffer = []
        for ident, value in settings:
            setting_s = struct.pack("!HI", ident, value)
            buffer.append(setting_s)
        payload = b"".join(buffer)
        return self.send_frame(
            type = netius.common.SETTINGS,
            flags = flags,
            payload = payload,
            delay = delay,
            callback = callback
        )

    def send_ping(
        self,
        ack = False,
        delay = False,
        callback = None
    ):
        flags = 0x00
        if ack: flags |= 0x01
        payload = b"\0\0\0\0\0\0\0\0"
        return self.send_frame(
            type = netius.common.PING,
            flags = flags,
            payload = payload,
            delay = delay,
            callback = callback
        )

    def send_window_update(self, increment = 0, delay = False, callback = None):
        payload = struct.pack("!I", increment)
        return self.send_frame(
            type = netius.common.WINDOW_UPDATE,
            payload = payload,
            delay = delay,
            callback = callback
        )

    def on_frame(self):
        self.owner.on_frame_http2(self, self.parser)

    def on_headers(self, stream):
        self.owner.on_headers_http2(self, self.parser, stream)

    def on_settings(self, settings, ack):
        self.owner.on_settings_http2(self, self.parser, settings, ack)

    def on_ping(self, ack):
        self.owner.on_ping_http2(self, self.parser, ack)

    def on_goaway(self, last_stream, error_code, extra):
        self.owner.on_goaway_http2(self, self.parser, last_stream, error_code, extra)

    def on_window_update(self, increment):
        self.owner.on_window_update_http2(self, self.parser, increment)

class HTTP2Server(http.HTTPServer):

    def __init__(self, legacy = True, safe = False, *args, **kwargs):
        self._protocols = []
        self.legacy, self.safe = legacy, safe
        http.HTTPServer.__init__(self, *args, **kwargs)

    def get_protocols(self):
        if self._protocols: return self._protocols
        if not self.safe: self._protocols.extend(["h2"])
        if self.legacy: self._protocols.extend(["http/1.1", "http/1.0"])
        return self._protocols

    def new_connection(self, socket, address, ssl = False):
        return HTTP2Connection(
            owner = self,
            socket = socket,
            address = address,
            ssl = ssl,
            encoding = self.encoding,
            legacy = self.legacy
        )

    def on_ssl(self, connection):
        http.HTTPServer.on_ssl(self, connection)
        if self.safe: return
        protocol = connection.ssl_protocol()
        if not protocol == "h2": return
        connection.set_h2()

    def on_preface_http2(self, connection, parser):
        settings = [
            (1, 4096),
            (2, 0),
            (3, 100),
            (4, 65535),
            (5, 16384),
            (6, 16384)
        ]
        connection.send_settings(settings = settings)

    def on_frame_http2(self, connection, parser):
        is_debug = self.is_debug()
        is_debug and self._log_frame(connection, parser)

    def on_headers_http2(self, connection, parser, stream):
        if not stream.is_ready: return
        self.on_data_http(stream, stream)

    def on_settings_http2(self, connection, parser, settings, ack):
        if ack: return
        self.debug("Received settings %s for connection" % str(settings))
        connection.send_settings(ack = True)

    def on_ping_http2(self, connection, parser, ack):
        if ack: return
        connection.send_ping(ack = True)

    def on_goaway_http2(self, connection, parser, last_stream, error_code, extra):
        self._log_error(error_code, extra)

    def on_window_update_http2(self, connection, parser, increment):
        self.debug("Window updated with increment %d bytes" % increment)

    def _log_frame(self, connection, parser):
        self.debug(
            "Received frame 0x%02x (%s) for stream %d with length %d bytes" %\
            (parser.type, parser.type_s, parser.stream, parser.length)
        )

    def _log_error(self, error_code, extra):
        self.warning(
            "Received error 0x%02x with message '%s'" %\
            (error_code, extra)
        )
<commit_msg>small fix<commit_after>#!/usr/bin/python
# -*- coding: utf-8 -*-

# Hive Netius System
# Copyright (c) 2008-2016 Hive Solutions Lda.
#
# This file is part of Hive Netius System.
#
# Hive Netius System is free software: you can redistribute it and/or modify
# it under the terms of the Apache License as published by the Apache
# Foundation, either version 2.0 of the License, or (at your option) any
# later version.
#
# Hive Netius System is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# Apache License for more details.
#
# You should have received a copy of the Apache License along with
# Hive Netius System. If not, see <http://www.apache.org/licenses/>.

__author__ = "João Magalhães <joamag@hive.pt>"
""" The author(s) of the module """

__version__ = "1.0.0"
""" The version of the module """

__revision__ = "$LastChangedRevision$"
""" The revision number of the module """

__date__ = "$LastChangedDate$"
""" The last change date of the module """

__copyright__ = "Copyright (c) 2008-2016 Hive Solutions Lda."
""" The copyright for the module """

__license__ = "Apache License, Version 2.0"
""" The license for the module """

import struct

import netius.common

from . import http

class HTTP2Connection(http.HTTPConnection):

    def __init__(self, legacy = True, *args, **kwargs):
        http.HTTPConnection.__init__(self, *args, **kwargs)
        self.legacy = legacy
        self.preface = False
        self.preface_b = b""

    def open(self, *args, **kwargs):
        http.HTTPConnection.open(self, *args, **kwargs)
        if not self.legacy: self.set_h2()

    def set_h2(self):
        self.legacy = False
        if self.parser: self.parser.destroy()
        self.parser = netius.common.HTTP2Parser(self, store = True)
        self.parser.bind("on_frame", self.on_frame)
        self.parser.bind("on_headers", self.on_headers)
        self.parser.bind("on_settings", self.on_settings)
        self.parser.bind("on_ping", self.on_ping)
        self.parser.bind("on_goaway", self.on_goaway)
        self.parser.bind("on_window_update", self.on_window_update)

    def parse(self, data):
        if not self.legacy and not self.preface:
            data = self.parse_preface(data)
            if not data: return
        return self.parser.parse(data)

    def parse_preface(self, data):
        """
        Tries to run the parsing on the preface part of the
        connection establishment using the provided data
        note that the data is buffered in case the proper size
        has not been reached for proper validation.

        This should be the first step when trying to establish
        a proper HTTP 2 connection.

        :type data: String
        :param data: The data buffer that is going to be used to
        try to parse the connection preface.
        :rtype: String
        :return: The resulting data after the preface has been
        parsed, this should be empty or invalid in case no data
        is pending to be parsed.
        """

        # adds the current data to the buffer of bytes pending
        # in the preface parsing and then verified that the proper
        # preface size has been reached, in case it has not returned
        # an invalid value immediately (no further parsing)
        self.preface_b += data
        preface_l = len(netius.common.HTTP2_PREFACE)
        is_size = len(self.preface_b) >= preface_l
        if not is_size: return None

        # retrieves the preface string from the buffer (according to size)
        # and runs the string based verification, raising an exception in
        # case there's a mismatch in the string validation
        preface = self.preface_b[:preface_l]
        if not preface == netius.common.HTTP2_PREFACE:
            raise netius.ParserError("Invalid preface")

        # sets the preface (parsed) flag indicating that the preface has
        # been parsed for the current connection
        self.preface = True

        # retrieves the extra data added to the preface buffer and then
        # unsets the same buffer (no more preface parsing)
        data = self.preface_b[preface_l:]
        self.preface_b = b""

        # calls the proper callback for the preface sending both the current
        # instance and the associated parser for handling
        self.owner.on_preface_http2(self, self.parser)

        # returns the remaining data pending to be parsed so that it may be
        # parsed by any extra operation
        return data

    def send_response(
        self,
        data = None,
        headers = None,
        version = None,
        code = 200,
        code_s = None,
        apply = False,
        stream = None,
        final = True,
        flush = True,
        delay = False,
        callback = None
    ):
        # in case the legacy mode is enabled the send response call is
        # forwarded to the upper layers so that it's handled properly
        if self.legacy: return http.HTTPConnection.send_response(
            self,
            data = data,
            headers = headers,
            version = version,
            code = code,
            code_s = code_s,
            apply = apply,
            stream = stream,
            final = final,
            flush = flush,
            delay = delay,
            callback = callback
        )

        # retrieves the various parts that define the response
        # and runs a series of normalization processes to retrieve
        # the relevant information of the data to be sent to client
        data = data or ""
        data = netius.legacy.bytes(data)
        headers = headers or dict()
        data_l = len(data) if data else 0
        is_empty = code in (204, 304) and data_l == 0

        # verifies if the content length header is currently present
        # in the provided headers and in case it's not inserts it
        if not "content-length" in headers and not is_empty:
            headers["content-length"] = str(data_l)

        # in case the apply flag is set the apply all operation is performed
        # so that a series of headers are applied to the current context
        # (things like the name of the server connection, etc)
        if apply: self.owner._apply_all(self.parser, self, headers)

        # sends the initial headers data (including status line), this should
        # trigger the initial data sent to the peer/client
        count = self.send_header(
            headers = headers,
            version = version,
            code = code,
            code_s = code_s,
            stream = stream
        )

        # sends the part/payload information (data) to the client and optionally
        # flushes the current internal buffers to enforce sending of the value
        count += self.send_part(
            data,
            stream = stream,
            final = final,
            flush = flush,
            delay = delay,
            callback = callback
        )
        return count

    def send_header(
        self,
        headers = None,
        version = None,
        code = 200,
        code_s = None,
        stream = None,
        delay = False,
        callback = None
    ):
        # in case the legacy mode is enabled the send header call is
        # forwarded to the upper layers so that it's handled properly
        if self.legacy: return http.HTTPConnection.send_header(
            self,
            headers = headers,
            version = version,
            code = code,
            code_s = code_s,
            stream = stream,
            delay = delay,
            callback = callback
        )

        # verifies if the headers value has been provided and in case it
        # has not creates a new empty dictionary (runtime compatibility)
        headers = headers or dict()

        # defines the proper default base http version in case it has not
        # been provided as part the default values
        version = version or "HTTP/2.0"

        # creates the headers base list that is going to store the various
        # header tuples representing the headers in canonical http2 form
        headers_b = []
        headers_b.append((":status", str(code)))

        # iterates over the complete set of raw header values to normalize
        # them and add them to the currently defined base list
        for key, value in headers.items():
            key = netius.common.header_down(key)
            if key in ("connection",): continue
            if not type(value) == list: value = (value,)
            for _value in value: headers_b.append((key, _value))

        return self.send_headers(
            headers_b,
            stream = stream,
            delay = delay,
            callback = callback
        )

    def send_part(
        self,
        data,
        stream = None,
        final = True,
        flush = False,
        delay = False,
        callback = None
    ):
        if self.legacy: return http.HTTPConnection.send_part(
            self,
            data,
            stream = stream,
            final = final,
            flush = flush,
            delay = delay,
            callback = callback
        )
        if flush:
            count = self.send_data(
                data,
                stream = stream,
                end_stream = final
            )
            self.flush(callback = callback)
        else:
            count = self.send_data(
                data,
                stream = stream,
                end_stream = final,
                delay = delay,
                callback = callback
            )
        return count

    def send_frame(
        self,
        type = 0x01,
        flags = 0x00,
        payload = b"",
        stream = 0x00,
        delay = False,
        callback = None
    ):
        size = len(payload)
        header = struct.pack("!BHBBI", 0x00, size, type, flags, stream)
        message = header + payload
        return self.send(message, delay = delay, callback = callback)

    def send_data(
        self,
        data = b"",
        end_stream = True,
        stream = None,
        delay = False,
        callback = None
    ):
        flags = 0x00
        if end_stream: flags |= 0x01
        return self.send_frame(
            type = netius.common.DATA,
            flags = flags,
            payload = data,
            stream = stream,
            delay = delay,
            callback = callback
        )

    def send_headers(
        self,
        headers = [],
        end_stream = False,
        end_headers = True,
        stream = None,
        delay = False,
        callback = None
    ):
        flags = 0x00
        if end_stream: flags |= 0x01
        if end_headers: flags |= 0x04
        payload = self.parser.encoder.encode(headers)
        return self.send_frame(
            type = netius.common.HEADERS,
            flags = flags,
            payload = payload,
            stream = stream,
            delay = delay,
            callback = callback
        )

    def send_rst_stream(
        self,
        error_code = 0x00,
        stream = None,
        delay = False,
        callback = None
    ):
        payload = struct.pack("!I", error_code)
        return self.send_frame(
            type = netius.common.RST_STREAM,
            payload = payload,
            stream = stream,
            delay = delay,
            callback = callback
        )

    def send_settings(
        self,
        settings = (),
        ack = False,
        delay = False,
        callback = None
    ):
        flags = 0x00
        if ack: flags |= 0x01
        buffer = []
        for ident, value in settings:
            setting_s = struct.pack("!HI", ident, value)
            buffer.append(setting_s)
        payload = b"".join(buffer)
        return self.send_frame(
            type = netius.common.SETTINGS,
            flags = flags,
            payload = payload,
            delay = delay,
            callback = callback
        )

    def send_ping(
        self,
        ack = False,
        delay = False,
        callback = None
    ):
        flags = 0x00
        if ack: flags |= 0x01
        payload = b"\0\0\0\0\0\0\0\0"
        return self.send_frame(
            type = netius.common.PING,
            flags = flags,
            payload = payload,
            delay = delay,
            callback = callback
        )

    def send_window_update(self, increment = 0, delay = False, callback = None):
        payload = struct.pack("!I", increment)
        return self.send_frame(
            type = netius.common.WINDOW_UPDATE,
            payload = payload,
            delay = delay,
            callback = callback
        )

    def on_frame(self):
        self.owner.on_frame_http2(self, self.parser)

    def on_headers(self, stream):
        self.owner.on_headers_http2(self, self.parser, stream)

    def on_settings(self, settings, ack):
        self.owner.on_settings_http2(self, self.parser, settings, ack)

    def on_ping(self, ack):
        self.owner.on_ping_http2(self, self.parser, ack)

    def on_goaway(self, last_stream, error_code, extra):
        self.owner.on_goaway_http2(self, self.parser, last_stream, error_code, extra)

    def on_window_update(self, increment):
        self.owner.on_window_update_http2(self, self.parser, increment)

class HTTP2Server(http.HTTPServer):

    def __init__(self, legacy = True, safe = False, *args, **kwargs):
        self._protocols = []
        self.legacy, self.safe = legacy, safe
        http.HTTPServer.__init__(self, *args, **kwargs)

    def get_protocols(self):
        if self._protocols: return self._protocols
        if not self.safe: self._protocols.extend(["h2"])
        if self.legacy: self._protocols.extend(["http/1.1", "http/1.0"])
        return self._protocols

    def new_connection(self, socket, address, ssl = False):
        return HTTP2Connection(
            owner = self,
            socket = socket,
            address = address,
            ssl = ssl,
            encoding = self.encoding,
            legacy = self.legacy
        )

    def on_ssl(self, connection):
        http.HTTPServer.on_ssl(self, connection)
        if self.safe: return
        protocol = connection.ssl_protocol()
        if not protocol == "h2": return
        connection.set_h2()

    def on_preface_http2(self, connection, parser):
        settings = [
            (1, 4096),
            (2, 0),
            (3, 100),
            (4, 65535),
            (5, 16384),
            (6, 16384)
        ]
        connection.send_settings(settings = settings)

    def on_frame_http2(self, connection, parser):
        is_debug = self.is_debug()
        is_debug and self._log_frame(connection, parser)

    def on_headers_http2(self, connection, parser, stream):
        if not stream.is_ready: return
        self.on_data_http(stream, stream)

    def on_settings_http2(self, connection, parser, settings, ack):
        if ack: return
        self.debug("Received settings %s for connection" % str(settings))
        connection.send_settings(ack = True)

    def on_ping_http2(self, connection, parser, ack):
        if ack: return
        connection.send_ping(ack = True)

    def on_goaway_http2(self, connection, parser, last_stream, error_code, extra):
        if error_code == 0x00: return
        self._log_error(error_code, extra)

    def on_window_update_http2(self, connection, parser, increment):
        self.debug("Window updated with increment %d bytes" % increment)

    def _log_frame(self, connection, parser):
        self.debug(
            "Received frame 0x%02x (%s) for stream %d with length %d bytes" %\
            (parser.type, parser.type_s, parser.stream, parser.length)
        )

    def _log_error(self, error_code, extra):
        self.warning(
            "Received error 0x%02x with message '%s'" %\
            (error_code, extra)
        )
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-

from flask import flash, redirect, g, render_template, request
from werkzeug.datastructures import MultiDict
from coaster.views import render_with, requires_permission, route, UrlForView, ModelView
from baseframe import _
from baseframe.forms import render_delete_sqla, render_form

from .. import app, funnelapp, lastuser
from ..models import Label, db, Project, Profile
from ..forms import LabelForm, SublabelForm
from .mixins import ProjectViewMixin
from .decorators import legacy_redirect

@route('/<profile>/<project>/labels')
class ProjectLabelView(ProjectViewMixin, UrlForView, ModelView):
    __decorators__ = [legacy_redirect]

    @route('')
    @render_with('labels.html.jinja2')
    @lastuser.requires_login
    @requires_permission('edit_project')
    def labels(self):
        return dict(project=self.obj, labels=self.obj.labels)

    @route('new', methods=['GET', 'POST'])
    @lastuser.requires_login
    @render_with('labels_form.html.jinja2')
    @requires_permission('admin')
    def new_label(self):
        form = LabelForm(model=Label, parent=self.obj.parent)
        emptysubform = SublabelForm(MultiDict({}))
        if form.validate_on_submit():
            # This form can send one or multiple values for title and icon_emoji.
            # If the label doesn't have any sublabel, one value is sent for each list,
            # and those values are also available at `form.data`.
            # But in case there are sublabels, the sublabel values are in the list
            # in the order they appeared on the create form.
            titlelist = request.values.getlist('title')
            emojilist = request.values.getlist('icon_emoji')
            # first values of both lists belong to the parent label
            titlelist.pop(0)
            emojilist.pop(0)

            label = Label(title=form.data.get('title'), icon_emoji=form.data.get('icon_emoji'), project=self.obj)
            label.restricted = form.data.get('restricted')
            label.make_name()
            self.obj.labels.append(label)
            db.session.add(label)

            for idx, title in enumerate(titlelist):
                subform = SublabelForm(MultiDict({
                    'csrf_token': form.csrf_token.data, 'title': titlelist[idx],
                    'icon_emoji': emojilist[idx]
                    }))

                if not subform.validate():
                    flash(_("Error with a sublabel: {}").format(subform.errors.pop()), category='error')
                    return dict(title="Add label", form=form, project=self.obj)
                else:
                    subl = Label(project=self.obj)
                    subform.populate_obj(subl)
                    subl.make_name()
                    db.session.add(subl)
                    label.options.append(subl)

            db.session.commit()
            return redirect(self.obj.url_for('labels'), code=303)
        return dict(title="Add label", form=form, emptysubform=emptysubform, project=self.obj)

@route('/<project>/labels', subdomain='<profile>')
class FunnelProjectLabelView(ProjectLabelView):
    pass

ProjectLabelView.init_app(app)
FunnelProjectLabelView.init_app(funnelapp)

@route('/<profile>/<project>/labels/<label>')
class LabelView(UrlForView, ModelView):
    __decorators__ = [lastuser.requires_login, legacy_redirect]
    model = Label
    route_model_map = {
        'profile': 'project.profile.name', 'project': 'project.name',
        'label': 'name'}

    def loader(self, profile, project, label):
        proj = Project.query.join(Profile).filter(
                Profile.name == profile, Project.name == project
            ).first_or_404()
        label = self.model.query.filter_by(project=proj, name=label).first_or_404()
        g.profile = label.project.profile
        return label

    @route('edit', methods=['GET', 'POST'])
    @lastuser.requires_login
    @render_with('labels_form.html.jinja2')
    @requires_permission('edit_project')
    def edit(self):
        emptysubform = SublabelForm(MultiDict({}))
        subforms = []
        if self.obj.main_label:
            # It's a sublabel
            form = SublabelForm(obj=self.obj, model=Label, parent=self.obj.project)
        else:
            form = LabelForm(obj=self.obj, model=Label, parent=self.obj.project)
            if self.obj.is_parent:
                for subl in self.obj.children:
                    subforms.append(SublabelForm(obj=subl, parent=self.obj.project))

        if form.validate_on_submit():
            form.populate_obj(self.obj)

            idlist = request.values.getlist('id')
            titlelist = request.values.getlist('title')
            emojilist = request.values.getlist('icon_emoji')

            idlist.pop(0)
            titlelist.pop(0)
            emojilist.pop(0)

            for idx, title in enumerate(titlelist):
                if idlist[idx]:
                    # existing sublabel
                    subl = Label.query.filter_by(project=self.obj.project, id=idlist[idx]).first()
                    subl.title = titlelist[idx]
                    subl.icon_emoji = emojilist[idx]
                else:
                    subform = SublabelForm(MultiDict({
                        'csrf_token': form.csrf_token.data, 'title': titlelist[idx],
                        'icon_emoji': emojilist[idx]
                        }))

                    if not subform.validate():
                        flash(_("Error with a sublabel: {}").format(subform.errors.pop()), category='error')
                        return dict(title="Edit label", form=form, project=self.obj.project)
                    else:
                        subl = Label(project=self.obj.project)
                        subform.populate_obj(subl)
                        subl.make_name()
                        self.obj.project.labels.append(subl)
                        self.obj.children.append(subl)
                        db.session.add(subl)

            db.session.commit()
            return redirect(self.obj.project.url_for('labels'), code=303)
        return dict(title="Edit label", form=form, subforms=subforms, emptysubform=emptysubform, project=self.obj.project)

    @route('archive', methods=['POST'])
    @lastuser.requires_login
    @requires_permission('admin')
    def archive(self):
        return redirect(self.obj.project.url_for('labels'), code=303)

@route('/<project>/labels/<label>', subdomain='<profile>')
class FunnelLabelView(LabelView):
    pass

LabelView.init_app(app)
FunnelLabelView.init_app(funnelapp)
<commit_msg>Uncommitted file in label main/options commit<commit_after># -*- coding: utf-8 -*-

from flask import flash, redirect, g, render_template, request
from werkzeug.datastructures import MultiDict
from coaster.views import render_with, requires_permission, route, UrlForView, ModelView
from baseframe import _
from baseframe.forms import render_delete_sqla, render_form

from .. import app, funnelapp, lastuser
from ..models import Label, db, Project, Profile
from ..forms import LabelForm, SublabelForm
from .mixins import ProjectViewMixin
from .decorators import legacy_redirect

@route('/<profile>/<project>/labels')
class ProjectLabelView(ProjectViewMixin, UrlForView, ModelView):
    __decorators__ = [legacy_redirect]

    @route('')
    @render_with('labels.html.jinja2')
    @lastuser.requires_login
    @requires_permission('edit_project')
    def labels(self):
        return dict(project=self.obj, labels=self.obj.labels)

    @route('new', methods=['GET', 'POST'])
    @lastuser.requires_login
    @render_with('labels_form.html.jinja2')
    @requires_permission('admin')
    def new_label(self):
        form = LabelForm(model=Label, parent=self.obj.parent)
        emptysubform = SublabelForm(MultiDict({}))
        if form.validate_on_submit():
            # This form can send one or multiple values for title and icon_emoji.
            # If the label doesn't have any sublabel, one value is sent for each list,
            # and those values are also available at `form.data`.
            # But in case there are sublabels, the sublabel values are in the list
            # in the order they appeared on the create form.
            titlelist = request.values.getlist('title')
            emojilist = request.values.getlist('icon_emoji')
            # first values of both lists belong to the parent label
            titlelist.pop(0)
            emojilist.pop(0)

            label = Label(title=form.data.get('title'), icon_emoji=form.data.get('icon_emoji'), project=self.obj)
            label.restricted = form.data.get('restricted')
            label.make_name()
            self.obj.labels.append(label)
            db.session.add(label)

            for idx, title in enumerate(titlelist):
                subform = SublabelForm(MultiDict({
                    'csrf_token': form.csrf_token.data, 'title': titlelist[idx],
                    'icon_emoji': emojilist[idx]
                    }))

                if not subform.validate():
                    flash(_("Error with a sublabel: {}").format(subform.errors.pop()), category='error')
                    return dict(title="Add label", form=form, project=self.obj)
                else:
                    subl = Label(project=self.obj)
                    subform.populate_obj(subl)
                    subl.make_name()
                    db.session.add(subl)
                    label.options.append(subl)

            db.session.commit()
            return redirect(self.obj.url_for('labels'), code=303)
        return dict(title="Add label", form=form, emptysubform=emptysubform, project=self.obj)

@route('/<project>/labels', subdomain='<profile>')
class FunnelProjectLabelView(ProjectLabelView):
    pass

ProjectLabelView.init_app(app)
FunnelProjectLabelView.init_app(funnelapp)

@route('/<profile>/<project>/labels/<label>')
class LabelView(UrlForView, ModelView):
    __decorators__ = [lastuser.requires_login, legacy_redirect]
    model = Label
    route_model_map = {
        'profile': 'project.profile.name', 'project': 'project.name',
        'label': 'name'}

    def loader(self, profile, project, label):
        proj = Project.query.join(Profile).filter(
                Profile.name == profile, Project.name == project
            ).first_or_404()
        label = self.model.query.filter_by(project=proj, name=label).first_or_404()
        g.profile = label.project.profile
        return label

    @route('edit', methods=['GET', 'POST'])
    @lastuser.requires_login
    @render_with('labels_form.html.jinja2')
    @requires_permission('edit_project')
    def edit(self):
        emptysubform = SublabelForm(MultiDict({}))
        subforms = []
        if self.obj.main_label:
            # It's a sublabel
            form = SublabelForm(obj=self.obj, model=Label, parent=self.obj.project)
        else:
            form = LabelForm(obj=self.obj, model=Label, parent=self.obj.project)
            if self.obj.is_parent:
                for subl in self.obj.options:
                    subforms.append(SublabelForm(obj=subl, parent=self.obj.project))

        if form.validate_on_submit():
            form.populate_obj(self.obj)

            idlist = request.values.getlist('id')
            titlelist = request.values.getlist('title')
            emojilist = request.values.getlist('icon_emoji')

            idlist.pop(0)
            titlelist.pop(0)
            emojilist.pop(0)

            for idx, title in enumerate(titlelist):
                if idlist[idx]:
                    # existing sublabel
                    subl = Label.query.filter_by(project=self.obj.project, id=idlist[idx]).first()
                    subl.title = titlelist[idx]
                    subl.icon_emoji = emojilist[idx]
                else:
                    subform = SublabelForm(MultiDict({
                        'csrf_token': form.csrf_token.data, 'title': titlelist[idx],
                        'icon_emoji': emojilist[idx]
                        }))

                    if not subform.validate():
                        flash(_("Error with a sublabel: {}").format(subform.errors.pop()), category='error')
                        return dict(title="Edit label", form=form, project=self.obj.project)
                    else:
                        subl = Label(project=self.obj.project)
                        subform.populate_obj(subl)
                        subl.make_name()
                        self.obj.project.labels.append(subl)
                        self.obj.options.append(subl)
                        db.session.add(subl)

            db.session.commit()
            return redirect(self.obj.project.url_for('labels'), code=303)
        return dict(title="Edit label", form=form, subforms=subforms, emptysubform=emptysubform, project=self.obj.project)

    @route('archive', methods=['POST'])
    @lastuser.requires_login
    @requires_permission('admin')
    def archive(self):
        return redirect(self.obj.project.url_for('labels'), code=303)

@route('/<project>/labels/<label>', subdomain='<profile>')
class FunnelLabelView(LabelView):
    pass

LabelView.init_app(app)
FunnelLabelView.init_app(funnelapp)
<|endoftext|>