<commit_before>#!/usr/bin/python
# -*- coding: utf-8 -*-
##############################################################################
#
#   Copyright (C) 2010-2012 Associazione OpenERP Italia
#   (<http://www.openerp-italia.org>).
#   Copyright(c)2008-2010 SIA "KN dati".(http://kndati.lv) All Rights Reserved.
#                   General contacts <info@kndati.lv>
#
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU Affero General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU Affero General Public License
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################

import os
import sys
import logging
import openerp
import xlsxwriter # XLSX export
import openerp.netsvc as netsvc
import openerp.addons.decimal_precision as dp
from openerp.report import report_sxw
from openerp.report.report_sxw import rml_parse
from openerp.osv import fields, osv, expression, orm
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
from openerp import SUPERUSER_ID, api
from openerp import tools
from openerp.tools.translate import _
from openerp.tools.float_utils import float_round as round
from openerp.tools import (DEFAULT_SERVER_DATE_FORMAT,
    DEFAULT_SERVER_DATETIME_FORMAT,
    DATETIME_FORMATS_MAP,
    float_compare)

_logger = logging.getLogger(__name__)

class CampaignCampaign(orm.Model):
    """ Model name: CampaignCampaign
    """
    _inherit = 'campaign.campaign'

    # -------------------------------------------------------------------------
    # Button event:
    # -------------------------------------------------------------------------
    def export_report_as_xlsx(self, cr, uid, ids, context=None):
        ''' Export report in XLSX file
        '''
        def get_image(path, code, extension):
            ''' Generate image name
            '''
            name = '%s.%s' % (
                os.path.join(path, line[0]),
                extension,
                )
            # Test if file exists:
            if os.path.isfile(name):
                return name
            else:
                return False

        # ---------------------------------------------------------------------
        # Parameters:
        # ---------------------------------------------------------------------
        path = '/home/administrator/photo/xls/campaign'
        objects = self.browse(cr, uid, ids, context=context)
        filename = 'export.xlsx' #'%s.xlsx' % campaign_proxy.code
        fullname = os.path.join(path, filename)
        data = {} # Not from wizard

        # ---------------------------------------------------------------------
        # Create and open Workbook:
        # ---------------------------------------------------------------------
        WB = xlsxwriter.Workbook(fullname)
        WS = WB.add_worksheet()#campaign_proxy.code)

        # ---------------------------------------------------------------------
        # Format elements:
        # ---------------------------------------------------------------------
        format_header = WB.add_format({
            'bold': True,
            'font_name': 'Arial',
            'font_size': 11,
            })

        format_title = WB.add_format({
            'bold': True,
            'font_color': 'black',
            'font_name': 'Arial',
            'font_size': 10,
            'align': 'center',
            'valign': 'center',
            'bg_color': 'gray',
            'border': 1,
            })

        format_hidden = WB.add_format({
            'font_color': 'white',
            'font_name': 'Arial',
            'font_size': 8,
            })

        format_data = WB.add_format({
            'font_name': 'Arial',
            'font_size': 10,
            })

        # ---------------------------------------------------------------------
        # Export
        # ---------------------------------------------------------------------
        # Init setup:
        self.get_total_pack_block(cr, uid, objects, context=context) # no data

        # Column dimension:
        WS.set_column ('A:A', 0, None, {'hidden': 1}) # ID column
        WS.set_column ('B:B', 30) # Image colums

        # set language:

        row = 1
        context_lang = context.get('lang', 'it_IT')
        for o in objects: # NOTE: only one from button
            context['lang'] = o.partner_id.lang or 'it_IT'

            path = os.path.expanduser(o.thumb_album_id.path)
            extension = o.thumb_album_id.extension_image

            # -----------------------------------------------------------------
            # Header:
            # -----------------------------------------------------------------
            WS.write(row, 1, _('CAMPAGNA'), format_header)
            WS.write(row, 2, o.name, format_header)
            WS.write(row, 4, _('Cliente:'), format_header)
            WS.write(row, 5, o.partner_id.name, format_header)
            row += 1

            # -----------------------------------------------------------------
            # Body:
            # -----------------------------------------------------------------
            for mode, line in self.get_product_pack(
                    cr, uid, o.product_ids, data):
                row += 1
                # -------------------------------------------------------------
                # Body mode:
                # -------------------------------------------------------------
                # Title:
                WS.set_row(row, 30)
                if mode == 'HEADER':
                    WS.write(row, 1, _('Immagine'), format_title)
                    col = 1
                    for field in line:
                        col += 1
                        WS.set_column (col, col, 20) # Col large
                        WS.write(row, col, field, format_title)

                    # Add 2 extra col:
                    WS.write(row, col + 1, _('Campagna'), format_title)
                    WS.write(row, col + 2, _('Ordine'), format_title)

                # Hidden:
                elif mode == 'HIDDEN':
                    WS.set_row(row, 0)
                    WS.write(row, 0, 'id', format_hidden)
                    col = 2 + len(line)
                    WS.write(row, col, 'qty', format_hidden)
                    WS.write(row, col + 1, 'qty_ordered', format_hidden)

                # Body:
                else: # Product line
                    WS.set_row(row, 50)
                    WS.write(row, 0, mode, format_hidden)
                    image = get_image(path, line[0], extension)
                    if image:
                        WS.insert_image(row, 1, image, {
                            'x_scale': 1, 'y_scale': 1,
                            'x_offset': 2, 'y_offset': 2,
                            })
                    else:
                        WS.write(row, 1, 'Nessuna immagine', format_data)
                    col = 1
                    for field in line:
                        col += 1
                        WS.write(row, col, field, format_data)
        WB.close()
        context['lang'] = context_lang # previous lang
        return True

    # -------------------------------------------------------------------------
    # Report functions:
    # -------------------------------------------------------------------------
    def _get_active_objects(self, cr, uid, data=None, context=None):
        ''' Return active campaign
        '''
        if data is None:
            data = {} # TODO manage filter confirmed draft

        campaign_pool = self.pool.get('campaign.campaign')

        if data.get('mode', False) == 'draft':
            domain = [('state', 'in', ('draft', 'confirmed'))]
        else: # only confirmed
            domain = [('state', '=', 'confirmed')]

        campaign_ids = campaign_pool.search(cr, uid, domain, context=context)
        if not campaign_ids:
            raise osv.except_osv(
                _('Report error!'),
                _('No data, change filter'),
                )

        return campaign_pool.browse(cr, uid, campaign_ids, context=context)

    def get_total_pack_block(self, cr, uid, objects, data=None, context=None):
        ''' Read all package objects for decide how much colums
        '''
        if data is not None:
            objects = self._get_active_objects(
                cr, uid, data=data, context=context)

        self.pack_max = 1
        for campaign in objects:
            for line in campaign.product_ids:
                product = line.product_id
                if product.has_multipackage:
                    tot = sum([item.number for item in product.multi_pack_ids])
                else:
                    tot = 1
                if tot > self.pack_max:
                    self.pack_max = tot
        return ''

    def get_product_pack(self, cr, uid, relations, data=None, context=None):
        ''' Create a list for all package in product
            [(l, h, p, w)]
            fill extra element till pack_max
        '''
        if data is None:
            data = {}
            from_wizard = False
        else:
            from_wizard = True

        res = []
        empty = ['', '', '', ''] # XXX loop block if empty

        # ---------------------------------------------------------------------
        #                               Header block:
        # ---------------------------------------------------------------------
        header_data = [
            _('Codice prodotto'),
            _('Nome prodotto'),
            _('Quantità\nriservata'),
            _('Prezzo listino\n(IVA inclusa)'),
            _('Prezzo cessione\n(IVA esclusa)'),
            _('Altezza seduta'),
            _('Commenti\nAggiuntivi'),
            _('Altezza'),
            _('Larghezza'),
            _('Lunghezza'),
            _('Peso Prodotto'),
            _('Arriva Montato'),
            _('Unità Imballo'),
            _('Ean'),
            _('Materiali\n(struttura, rivestimenti, imbottiture, piani ecc.)'),
            _('Colore/i\n(indicare tutti i colori presenti)'),
            _('Informazioni\nlavaggio'),
            _('Rivestimento\nsfoderabile'),
            ]
        if from_wizard:
            header_data.append(_('Disponibilità'))

        for i in range(0, self.pack_max):
            header_data.extend([
                _('Lunghezza # %s') % (i + 1),
                _('Altezza # %s') % (i + 1),
                _('Profondità # %s') % (i + 1),
                _('Peso # %s') % (i + 1),
                ])
        res.append(('HEADER', header_data))

        # ---------------------------------------------------------------------
        #                               Hidden block:
        # ---------------------------------------------------------------------
        # Static data list:
        hidden_data = [
            'id', 'default_code', '', '', '', '', '', '', '', '', ''] #, '']
        if from_wizard:
            hidden_data.append('')

        # Dynamic part:
        for i in range(0, self.pack_max):
            hidden_data.extend(empty)

        res.append(('HIDDEN', hidden_data))

        # ---------------------------------------------------------------------
        #                            Data block:
        # ---------------------------------------------------------------------
        for relation in relations:
            product = relation.product_id # readability

            # Ean part:
            ean = product.ean13 or '' # normal
            if relation.packaging_id:
                if relation.packaging_id.ean:
                    ean = relation.packaging_id.ean # normal different pack
                else:
                    if ean: # warning:
                        ean = '%s (prod.)' % ean

            # ------------
            # Common part:
            # ------------
            data = [
                # Product:
                product.default_code,

                # Relation:
                '%s %s' % (
                    relation.description or product.name or '?',
                    product.colour or ''),
                int(relation.qty),
                #relation.price,
                relation.price * 1.22, # TODO parametrize
                relation.campaign_price,
                product.seat_height,
                product.campaign_comment,
                product.height,
                product.width,
                product.length,
                product.weight,
                _('Sì') if product.campaign_mounted else _('No'),
                int(relation.q_x_pack),
                # Product:
                ean,
                product.campaign_material or '',
                product.campaign_color or '',
                product.campaign_wash or '',
                _('Sì') if product.campaign_cover else _('No'),

                #0.0, #TODO what data?!?!? int(product.qty),
                ]

            if from_wizard:
                data.append(int(product.mx_lord_qty or 0.0))

            if product.has_multipackage:
                # -------------------------------------------------------------
                #                   Multipackage product:
                # -------------------------------------------------------------
                i = 0

                for pack in product.multi_pack_ids: # Loop on all elements
                    i += pack.number or 1
                    for item in range(0, pack.number or 1):
                        data.extend([ # pack
                            pack.height,
                            pack.width,
                            pack.length,
                            pack.weight,
                            ])

            elif relation.packaging_id: # extra pack setted
                # -------------------------------------------------------------
                #                  Extra package selected:
                # -------------------------------------------------------------
                i = 1 # only one

                data.extend([ # extra pack selected
                    relation.packaging_id.pack_h,
                    relation.packaging_id.pack_l,
                    relation.packaging_id.pack_p,
                    relation.packaging_id.weight,
                    ])
            else:
                i = 1 # only one
                # -------------------------------------------------------------
                #                    Default package:
                # -------------------------------------------------------------
                data.extend([ # extra pack selected
                    product.pack_h,
                    product.pack_l,
                    product.pack_p,
                    product.weight,
                    ])

            # -----------------------
            # Add empty extra fields:
            # -----------------------
            for item in range(0, self.pack_max - i):
                data.extend(empty)

            res.append((relation.id, data))
        return res

class Parser(report_sxw.rml_parse):
    counters = {}
    context = False

    def __init__(self, cr, uid, name, context):
        self.context = context
        super(Parser, self).__init__(cr, uid, name, context)
        self.localcontext.update({
            'get_objects': self.get_objects,
            'load_context_image': self.load_context_image,
            'get_total_pack_block': self.get_total_pack_block,
            'get_product_pack': self.get_product_pack,
        })

    def get_objects(self, objects, data=None):
        ''' Return active campaign
        '''
        # Readability:
        cr = self.cr
        uid = self.uid
        context = {}

        return self.pool.get('campaign.campaign')._get_active_objects(
            cr, uid, data=data, context=context)

    def load_context_image(self, album_id, product_id):
        ''' Load image from album (used in ODT document, not in ODS)
        '''
        # Readability:
        cr = self.cr
        uid = self.uid
        context = {'album_id': album_id}

        product_pool = self.pool.get('product.product')
        product_proxy = product_pool.browse(cr, uid, product_id,
            context={'album_id': album_id})
        return product_proxy.product_image_context

    def get_total_pack_block(self, objects, data=None):
        ''' Read all package objects for decide how much colums
        '''
        # Readability:
        cr = self.cr
        uid = self.uid
        context = {}

        return self.pool.get('campaign.campaign').get_total_pack_block(
            cr, uid, objects, data=data, context=context)

    def get_product_pack(self, relations, data=None):
        ''' Create a list for all package in product
            [(l, h, p, w)]
            fill extra element till pack_max
        '''
        # Readability:
        cr = self.cr
        uid = self.uid
        context = {}

        return self.pool.get('campaign.campaign').get_product_pack(
            cr, uid, relations, data=data, context=context)

# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
<commit_msg>translate problem<commit_after>#!/usr/bin/python
# -*- coding: utf-8 -*-
##############################################################################
#
#   Copyright (C) 2010-2012 Associazione OpenERP Italia
#   (<http://www.openerp-italia.org>).
#   Copyright(c)2008-2010 SIA "KN dati".(http://kndati.lv) All Rights Reserved.
#                   General contacts <info@kndati.lv>
#
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU Affero General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU Affero General Public License
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################

import os
import sys
import logging
import openerp
import xlsxwriter # XLSX export
import openerp.netsvc as netsvc
import openerp.addons.decimal_precision as dp
from openerp.report import report_sxw
from openerp.report.report_sxw import rml_parse
from openerp.osv import fields, osv, expression, orm
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
from openerp import SUPERUSER_ID, api
from openerp import tools
from openerp.tools.translate import _
from openerp.tools.float_utils import float_round as round
from openerp.tools import (DEFAULT_SERVER_DATE_FORMAT,
    DEFAULT_SERVER_DATETIME_FORMAT,
    DATETIME_FORMATS_MAP,
    float_compare)

_logger = logging.getLogger(__name__)

class CampaignCampaign(orm.Model):
    """ Model name: CampaignCampaign
    """
    _inherit = 'campaign.campaign'

    # -------------------------------------------------------------------------
    # Button event:
    # -------------------------------------------------------------------------
    def export_report_as_xlsx(self, cr, uid, ids, context=None):
        ''' Export report in XLSX file
        '''
        def get_image(path, code, extension):
            ''' Generate image name
            '''
            name = '%s.%s' % (
                os.path.join(path, line[0]),
                extension,
                )
            # Test if file exists:
            if os.path.isfile(name):
                return name
            else:
                return False

        # ---------------------------------------------------------------------
        # Parameters:
        # ---------------------------------------------------------------------
        path = '/home/administrator/photo/xls/campaign'
        objects = self.browse(cr, uid, ids, context=context)
        filename = 'export.xlsx' #'%s.xlsx' % campaign_proxy.code
        fullname = os.path.join(path, filename)
        data = {} # Not from wizard

        # ---------------------------------------------------------------------
        # Create and open Workbook:
        # ---------------------------------------------------------------------
        WB = xlsxwriter.Workbook(fullname)
        WS = WB.add_worksheet()#campaign_proxy.code)

        # ---------------------------------------------------------------------
        # Format elements:
        # ---------------------------------------------------------------------
        format_header = WB.add_format({
            'bold': True,
            'font_name': 'Arial',
            'font_size': 11,
            })

        format_title = WB.add_format({
            'bold': True,
            'font_color': 'black',
            'font_name': 'Arial',
            'font_size': 10,
            'align': 'center',
            'valign': 'center',
            'bg_color': 'gray',
            'border': 1,
            })

        format_hidden = WB.add_format({
            'font_color': 'white',
            'font_name': 'Arial',
            'font_size': 8,
            })

        format_data = WB.add_format({
            'font_name': 'Arial',
            'font_size': 10,
            })

        # ---------------------------------------------------------------------
        # Export
        # ---------------------------------------------------------------------
        # Init setup:
        self.get_total_pack_block(cr, uid, objects, context=context) # no data

        # Column dimension:
        WS.set_column ('A:A', 0, None, {'hidden': 1}) # ID column
        WS.set_column ('B:B', 30) # Image colums

        # set language:

        row = 1
        context_lang = context.get('lang', 'it_IT')
        for o in objects: # NOTE: only one from button
            context['lang'] = o.partner_id.lang or 'it_IT'

            path = os.path.expanduser(o.thumb_album_id.path)
            extension = o.thumb_album_id.extension_image

            # -----------------------------------------------------------------
            # Header:
            # -----------------------------------------------------------------
            WS.write(row, 1, _('CAMPAIGN'), format_header)
            WS.write(row, 2, o.name, format_header)
            WS.write(row, 4, _('Customer:'), format_header)
            WS.write(row, 5, o.partner_id.name, format_header)
            row += 1

            # -----------------------------------------------------------------
            # Body:
            # -----------------------------------------------------------------
            for mode, line in self.get_product_pack(
                    cr, uid, o.product_ids, data, context=context):
                row += 1
                # -------------------------------------------------------------
                # Body mode:
                # -------------------------------------------------------------
                # Title:
                WS.set_row(row, 30)
                if mode == 'HEADER':
                    WS.write(row, 1, _('Imagine'), format_title)
                    col = 1
                    for field in line:
                        col += 1
                        WS.set_column (col, col, 20) # Col large
                        WS.write(row, col, field, format_title)

                    # Add 2 extra col:
                    WS.write(row, col + 1, _('Campaign'), format_title)
                    WS.write(row, col + 2, _('Order'), format_title)

                # Hidden:
                elif mode == 'HIDDEN':
                    WS.set_row(row, 0)
                    WS.write(row, 0, 'id', format_hidden)
                    col = 2 + len(line)
                    WS.write(row, col, 'qty', format_hidden)
                    WS.write(row, col + 1, 'qty_ordered', format_hidden)

                # Body:
                else: # Product line
                    WS.set_row(row, 50)
                    WS.write(row, 0, mode, format_hidden)
                    image = get_image(path, line[0], extension)
                    if image:
                        WS.insert_image(row, 1, image, {
                            'x_scale': 1, 'y_scale': 1,
                            'x_offset': 2, 'y_offset': 2,
                            })
                    else:
                        WS.write(row, 1, 'No image', format_data)
                    col = 1
                    for field in line:
                        col += 1
                        WS.write(row, col, field, format_data)
        WB.close()
        context['lang'] = context_lang # previous lang
        return True

    # -------------------------------------------------------------------------
    # Report functions:
    # -------------------------------------------------------------------------
    def _get_active_objects(self, cr, uid, data=None, context=None):
        ''' Return active campaign
        '''
        if data is None:
            data = {} # TODO manage filter confirmed draft

        campaign_pool = self.pool.get('campaign.campaign')

        if data.get('mode', False) == 'draft':
            domain = [('state', 'in', ('draft', 'confirmed'))]
        else: # only confirmed
            domain = [('state', '=', 'confirmed')]

        campaign_ids = campaign_pool.search(cr, uid, domain, context=context)
        if not campaign_ids:
            raise osv.except_osv(
                _('Report error!'),
                _('No data, change filter'),
                )

        return campaign_pool.browse(cr, uid, campaign_ids, context=context)

    def get_total_pack_block(self, cr, uid, objects, data=None, context=None):
        ''' Read all package objects for decide how much colums
        '''
        if data is not None:
            objects = self._get_active_objects(
                cr, uid, data=data, context=context)

        self.pack_max = 1
        for campaign in objects:
            for line in campaign.product_ids:
                product = line.product_id
                if product.has_multipackage:
                    tot = sum([item.number for item in product.multi_pack_ids])
                else:
                    tot = 1
                if tot > self.pack_max:
                    self.pack_max = tot
        return ''

    def get_product_pack(self, cr, uid, relations, data=None, context=None):
        ''' Create a list for all package in product
            [(l, h, p, w)]
            fill extra element till pack_max
        '''
        if data is None:
            data = {}
            from_wizard = False
        else:
            from_wizard = True

        res = []
        empty = ['', '', '', ''] # XXX loop block if empty

        # ---------------------------------------------------------------------
        #                               Header block:
        # ---------------------------------------------------------------------
        header_data = [
            _('Product code'),
            _('Product name'),
            _('Reserved quantity'),
            _('Pricelist\n(VAT included)'),
            _('Transfer price\n(VAT excluded)'),
            _('Seat height'),
            _('Extra comment'),
            _('Height'),
            _('Width'),
            _('Length'),
            _('Product weight'),
            _('Mounted'),
            _('Package unit'),
            _('EAN'),
            _('Material\n(structure, coatings, padding, plans etc.)'),
            _('Color/s\n(write all color present)'),
            _('Wash informations'),
            _('Removable cover'),
            ]
        if from_wizard:
            header_data.append(_('Availability'))

        for i in range(0, self.pack_max):
            header_data.extend([
                _('Length # %s') % (i + 1),
                _('Height # %s') % (i + 1),
                _('Depth # %s') % (i + 1),
                _('Weight # %s') % (i + 1),
                ])
        res.append(('HEADER', header_data))

        # ---------------------------------------------------------------------
        #                               Hidden block:
        # ---------------------------------------------------------------------
        # Static data list:
        hidden_data = [
            'id', 'default_code', '', '', '', '', '', '', '', '', ''] #, '']
        if from_wizard:
            hidden_data.append('')

        # Dynamic part:
        for i in range(0, self.pack_max):
            hidden_data.extend(empty)

        res.append(('HIDDEN', hidden_data))

        # ---------------------------------------------------------------------
        #                            Data block:
        # ---------------------------------------------------------------------
        for relation in relations:
            product = relation.product_id # readability

            # Ean part:
            ean = product.ean13 or '' # normal
            if relation.packaging_id:
                if relation.packaging_id.ean:
                    ean = relation.packaging_id.ean # normal different pack
                else:
                    if ean: # warning:
                        ean = '%s (prod.)' % ean

            # ------------
            # Common part:
            # ------------
            data = [
                # Product:
                product.default_code,

                # Relation:
                '%s %s' % (
                    relation.description or product.name or '?',
                    product.colour or ''),
                int(relation.qty),
                #relation.price,
                relation.price * 1.22, # TODO parametrize
                relation.campaign_price,
                product.seat_height,
                product.campaign_comment,
                product.height,
                product.width,
                product.length,
                product.weight,
                _('Yes') if product.campaign_mounted else _('No'),
                int(relation.q_x_pack),
                # Product:
                ean,
                product.campaign_material or '',
                product.campaign_color or '',
                product.campaign_wash or '',
                _('Yes') if product.campaign_cover else _('No'),

                #0.0, #TODO what data?!?!? int(product.qty),
                ]

            if from_wizard:
                data.append(int(product.mx_lord_qty or 0.0))

            if product.has_multipackage:
                # -------------------------------------------------------------
                #                   Multipackage product:
                # -------------------------------------------------------------
                i = 0

                for pack in product.multi_pack_ids: # Loop on all elements
                    i += pack.number or 1
                    for item in range(0, pack.number or 1):
                        data.extend([ # pack
                            pack.height,
                            pack.width,
                            pack.length,
                            pack.weight,
                            ])

            elif relation.packaging_id: # extra pack setted
                # -------------------------------------------------------------
                #                  Extra package selected:
                # -------------------------------------------------------------
                i = 1 # only one

                data.extend([ # extra pack selected
                    relation.packaging_id.pack_h,
                    relation.packaging_id.pack_l,
                    relation.packaging_id.pack_p,
                    relation.packaging_id.weight,
                    ])
            else:
                i = 1 # only one
                # -------------------------------------------------------------
                #                    Default package:
                # -------------------------------------------------------------
                data.extend([ # extra pack selected
                    product.pack_h,
                    product.pack_l,
                    product.pack_p,
                    product.weight,
                    ])

            # -----------------------
            # Add empty extra fields:
            # -----------------------
            for item in range(0, self.pack_max - i):
                data.extend(empty)

            res.append((relation.id, data))
        return res

class Parser(report_sxw.rml_parse):
    counters = {}
    context = False

    def __init__(self, cr, uid, name, context):
        self.context = context
        super(Parser, self).__init__(cr, uid, name, context)
        self.localcontext.update({
            'get_objects': self.get_objects,
            'load_context_image': self.load_context_image,
            'get_total_pack_block': self.get_total_pack_block,
            'get_product_pack': self.get_product_pack,
        })

    def get_objects(self, objects, data=None):
        ''' Return active campaign
        '''
        # Readability:
        cr = self.cr
        uid = self.uid
        context = {}

        return self.pool.get('campaign.campaign')._get_active_objects(
            cr, uid, data=data, context=context)

    def load_context_image(self, album_id, product_id):
        ''' Load image from album (used in ODT document, not in ODS)
        '''
        # Readability:
        cr = self.cr
        uid = self.uid
        context = {'album_id': album_id}

        product_pool = self.pool.get('product.product')
        product_proxy = product_pool.browse(cr, uid, product_id,
            context={'album_id': album_id})
        return product_proxy.product_image_context

    def get_total_pack_block(self, objects, data=None):
        ''' Read all package objects for decide how much colums
        '''
        # Readability:
        cr = self.cr
        uid = self.uid
        context = {}

        return self.pool.get('campaign.campaign').get_total_pack_block(
            cr, uid, objects, data=data, context=context)

    def get_product_pack(self, relations, data=None):
        ''' Create a list for all package in product
            [(l, h, p, w)]
            fill extra element till pack_max
        '''
        # Readability:
        cr = self.cr
        uid = self.uid
        context = {}

        return self.pool.get('campaign.campaign').get_product_pack(
            cr, uid, relations, data=data, context=context)

# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
<|endoftext|>


<commit_before>#!/usr/bin/env python
# Initial basic test app for turning on and off various GPIO ports
#
# The following script can be used to test a specific GPIO pin.  Enter the pin address as defined
# in the gpio list to test the pin.  Not entering a pin number will go through each pin and activate it.  See
# arguments secion for specific argument details.
#
# Additional Modifications By: Chris Usey (chrisausey@gmail.com)

import time
import wiringpi2 as wiringpi
import argparse
import ConfigParser
import ast
import os
import hardware_controller as hc

# Configurations
home_directory = os.getenv("SYNCHRONIZED_LIGHTS_HOME")
config = ConfigParser.RawConfigParser()
config.read(home_directory + '/py/synchronized_lights.cfg')

# Arguments
parser = argparse.ArgumentParser()
parser.add_argument('--led', type=int, default=-1, help='led to flash (0-24)')
parser.add_argument('--sleep', type=float, default=.5, help='time to sleep between flash')
args = parser.parse_args()

pin = args.led
sleep = args.sleep

# Cleanup Pins
hc.TurnOffLights()
hc.SetPinsAsInputs()

# Initialize all pins
hc.SetPinsAsOutputs()
hc.TurnOffLights()

# Blink the LED's
count = 0
if (pin == -1):
	print "Blink All pins " + str(count) + " of 5"
	for item in hc.gpio:
		print "Activating Pin: " + str(item)
		#wiringpi.digitalWrite(item,GPIOACTIVE)
		hc.TurnOnLight(item)
		time.sleep(sleep)
		print "Deactivating Pin: " + str(item)
		#wiringpi.digitalWrite(item,GPIOINACTIVE)
		hc.TurnOffLight(item)
		time.sleep(sleep)
else:
	while count < 5:
		print "Blink pin " + str(pin) + ": " + str(count) + " of 5"
		print "Activating Pin: " + str(pin)
		#wiringpi.digitalWrite(gpioList[pin],GPIOACTIVE)
		hc.TurnOnLight(hc.gpio[pin])
		time.sleep(sleep)
		print "Deactivating Pin: " + str(pin)
		#wiringpi.digitalWrite(gpioList[pin],GPIOINACTIVE)
		hc.TurnOffLight(hc.gpio[pin])
		time.sleep(sleep)
		count+=1

# Cleanup Pins
hc.TurnOffLights()
hc.SetPinsAsInputs()

<commit_msg>comment cleanup<commit_after>#!/usr/bin/env python
# Initial basic test app for turning on and off various GPIO ports
#
# The following script can be used to test a specific GPIO pin.  Enter the pin address as defined
# in the gpio list to test the pin.  Not entering a pin number will go through each pin and activate it.  See
# arguments secion for specific argument details.
#
# Additional Modifications By: Chris Usey (chrisausey@gmail.com)

import time
import wiringpi2 as wiringpi
import argparse
import ConfigParser
import ast
import os
import hardware_controller as hc

# Configurations
home_directory = os.getenv("SYNCHRONIZED_LIGHTS_HOME")
config = ConfigParser.RawConfigParser()
config.read(home_directory + '/py/synchronized_lights.cfg')

# Arguments
parser = argparse.ArgumentParser()
parser.add_argument('--led', type=int, default=-1, help='led to flash (0-24)')
parser.add_argument('--sleep', type=float, default=.5, help='time to sleep between flash')
args = parser.parse_args()

pin = args.led
sleep = args.sleep

# Cleanup Pins
hc.TurnOffLights()
hc.SetPinsAsInputs()

# Initialize all pins
hc.SetPinsAsOutputs()
hc.TurnOffLights()

# Blink the LED's
count = 0
if (pin == -1):
	print "Blink All pins " + str(count) + " of 5"
	for item in hc.gpio:
		print "Activating Pin: " + str(item)
		hc.TurnOnLight(item)
		time.sleep(sleep)
		print "Deactivating Pin: " + str(item)
		hc.TurnOffLight(item)
		time.sleep(sleep)
else:
	while count < 5:
		print "Blink pin " + str(pin) + ": " + str(count) + " of 5"
		print "Activating Pin: " + str(pin)
		hc.TurnOnLight(hc.gpio[pin])
		time.sleep(sleep)
		print "Deactivating Pin: " + str(pin)
		hc.TurnOffLight(hc.gpio[pin])
		time.sleep(sleep)
		count+=1

# Cleanup Pins
hc.TurnOffLights()
hc.SetPinsAsInputs()

<|endoftext|>


<commit_before># -*- coding: utf8 -*-
# This file is part of PyBossa.
#
# Copyright (C) 2013 SF Isle of Man Limited
#
# PyBossa is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# PyBossa is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with PyBossa.  If not, see <http://www.gnu.org/licenses/>.

import os
import logging
from itsdangerous import URLSafeTimedSerializer
from flask import Flask, url_for, session, request
from flask.ext.login import LoginManager, current_user
from flaskext.gravatar import Gravatar
from flask.ext.mail import Mail
from flask.ext.sqlalchemy import SQLAlchemy
#from flask.ext.debugtoolbar import DebugToolbarExtension
from flask.ext.heroku import Heroku
from flask.ext.babel import Babel
from redis.sentinel import Sentinel

from pybossa import default_settings as settings

from raven.contrib.flask import Sentry

def create_app():
    app = Flask(__name__)
    if 'DATABASE_URL' in os.environ:
        heroku = Heroku(app)
    configure_app(app)
    setup_error_email(app)
    setup_logging(app)
    login_manager.setup_app(app)
    # Set up Gravatar for users
    gravatar = Gravatar(app, size = 100, rating = 'g', default = 'mm', force_default = False, force_lower = False)
    return app

def configure_app(app):
    app.config.from_object(settings)
    app.config.from_envvar('PYBOSSA_SETTINGS', silent=True)
    # parent directory
    here = os.path.dirname(os.path.abspath( __file__ ))
    config_path = os.path.join(os.path.dirname(here), 'settings_local.py')
    if os.path.exists(config_path):
        app.config.from_pyfile(config_path)

from logging.handlers import SMTPHandler
def setup_error_email(app):
    ADMINS = app.config.get('ADMINS', '')
    if not app.debug and ADMINS:
        mail_handler = SMTPHandler('127.0.0.1',
                                   'server-error@no-reply.com',
                                   ADMINS, 'error')
        mail_handler.setLevel(logging.ERROR)
        app.logger.addHandler(mail_handler)

from logging.handlers import RotatingFileHandler
from logging import Formatter
def setup_logging(app):
    log_file_path = app.config.get('LOG_FILE')
    log_level = app.config.get('LOG_LEVEL', logging.WARN)
    if log_file_path:
        file_handler = RotatingFileHandler(log_file_path)
        file_handler.setFormatter(Formatter(
            '%(name)s:%(levelname)s:[%(asctime)s] %(message)s '
            '[in %(pathname)s:%(lineno)d]'
            ))
        file_handler.setLevel(log_level)
        app.logger.addHandler(file_handler)
        logger = logging.getLogger('pybossa')
        logger.setLevel(log_level)
        logger.addHandler(file_handler)

login_manager = LoginManager()
login_manager.login_view = 'account.signin'
login_manager.login_message = u"Please sign in to access this page."
app = create_app()

sentinel = Sentinel(app.config['REDIS_SENTINEL'], socket_timeout=0.1)
redis_master = sentinel.master_for('mymaster')
redis_slave = sentinel.slave_for('mymaster')

#toolbar = DebugToolbarExtension(app)
db = SQLAlchemy(app)
mail = Mail(app)
signer = URLSafeTimedSerializer(app.config['ITSDANGEORUSKEY'])
if app.config.get('SENTRY_DSN'):
    sentr = Sentry(app)

babel = Babel(app)

@babel.localeselector
def get_locale():
    if current_user.is_authenticated():
        lang = current_user.locale
    else:
        lang = session.get('lang',
                           request.accept_languages.best_match(app.config['LOCALES']))
    if lang is None:
        lang = 'en'
    return lang
<commit_msg>Exclude from coverage external modules like Sentry & Mail<commit_after># -*- coding: utf8 -*-
# This file is part of PyBossa.
#
# Copyright (C) 2013 SF Isle of Man Limited
#
# PyBossa is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# PyBossa is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with PyBossa.  If not, see <http://www.gnu.org/licenses/>.

import os
import logging
from itsdangerous import URLSafeTimedSerializer
from flask import Flask, url_for, session, request
from flask.ext.login import LoginManager, current_user
from flaskext.gravatar import Gravatar
from flask.ext.mail import Mail
from flask.ext.sqlalchemy import SQLAlchemy
#from flask.ext.debugtoolbar import DebugToolbarExtension
from flask.ext.heroku import Heroku
from flask.ext.babel import Babel
from redis.sentinel import Sentinel

from pybossa import default_settings as settings

from raven.contrib.flask import Sentry

def create_app():
    app = Flask(__name__)
    if 'DATABASE_URL' in os.environ:
        heroku = Heroku(app)
    configure_app(app)
    setup_error_email(app)
    setup_logging(app)
    login_manager.setup_app(app)
    # Set up Gravatar for users
    gravatar = Gravatar(app, size = 100, rating = 'g', default = 'mm', force_default = False, force_lower = False)
    return app

def configure_app(app):
    app.config.from_object(settings)
    app.config.from_envvar('PYBOSSA_SETTINGS', silent=True)
    # parent directory
    here = os.path.dirname(os.path.abspath( __file__ ))
    config_path = os.path.join(os.path.dirname(here), 'settings_local.py')
    if os.path.exists(config_path):
        app.config.from_pyfile(config_path)

from logging.handlers import SMTPHandler
def setup_error_email(app):
    ADMINS = app.config.get('ADMINS', '')
    if not app.debug and ADMINS: # pragma: no cover
        mail_handler = SMTPHandler('127.0.0.1',
                                   'server-error@no-reply.com',
                                   ADMINS, 'error')
        mail_handler.setLevel(logging.ERROR)
        app.logger.addHandler(mail_handler)

from logging.handlers import RotatingFileHandler
from logging import Formatter
def setup_logging(app):
    log_file_path = app.config.get('LOG_FILE')
    log_level = app.config.get('LOG_LEVEL', logging.WARN)
    if log_file_path:
        file_handler = RotatingFileHandler(log_file_path)
        file_handler.setFormatter(Formatter(
            '%(name)s:%(levelname)s:[%(asctime)s] %(message)s '
            '[in %(pathname)s:%(lineno)d]'
            ))
        file_handler.setLevel(log_level)
        app.logger.addHandler(file_handler)
        logger = logging.getLogger('pybossa')
        logger.setLevel(log_level)
        logger.addHandler(file_handler)

login_manager = LoginManager()
login_manager.login_view = 'account.signin'
login_manager.login_message = u"Please sign in to access this page."
app = create_app()

sentinel = Sentinel(app.config['REDIS_SENTINEL'], socket_timeout=0.1)
redis_master = sentinel.master_for('mymaster')
redis_slave = sentinel.slave_for('mymaster')

#toolbar = DebugToolbarExtension(app)
db = SQLAlchemy(app)
mail = Mail(app)
signer = URLSafeTimedSerializer(app.config['ITSDANGEORUSKEY'])
if app.config.get('SENTRY_DSN'): # pragma: no cover
    sentr = Sentry(app)

babel = Babel(app)

@babel.localeselector
def get_locale():
    if current_user.is_authenticated():
        lang = current_user.locale
    else:
        lang = session.get('lang',
                           request.accept_languages.best_match(app.config['LOCALES']))
    if lang is None:
        lang = 'en'
    return lang
<|endoftext|>


<commit_before># -*- coding: utf8 -*-
# This file is part of PyBossa.
#
# Copyright (C) 2014 SF Isle of Man Limited
#
# PyBossa is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# PyBossa is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with PyBossa.  If not, see <http://www.gnu.org/licenses/>.
"""Jobs module for running background tasks in PyBossa server."""
from flask.ext.mail import Message
from pybossa.core import mail
from pybossa.util import with_cache_disabled
from rq_scheduler import Scheduler

MINUTE = 60
HOUR = 60 * 60

def schedule_priority_jobs(queue_name, interval):
    """Schedule all PyBossa high priority jobs."""
    jobs = get_scheduled_jobs()
    from pybossa.core import sentinel
    from rq import Queue
    redis_conn = sentinel.master

    n_jobs = 0
    queue = Queue(queue_name, connection=redis_conn)
    for job in jobs:
        if (job['interval'] <= interval):
                n_jobs += 1
                queue.enqueue_call(func=job['name'],
                                   args=job['args'],
                                   kwargs=job['kwargs'],
                                   timeout=job['timeout'])
    msg = "%s jobs in %s have been enqueued" % (n_jobs, queue_name)
    return msg

def get_scheduled_jobs(): # pragma: no cover
    """Return a list of scheduled jobs."""
    # Default ones
    # A job is a dict with the following format: dict(name, args, kwargs,
    # interval)
    jobs = [
            dict(name=warm_up_stats, args=[], kwargs={},
                 interval=HOUR, timeout=(10 * MINUTE)),
            dict(name=warn_old_project_owners, args=[], kwargs={},
                 interval=(24 * HOUR), timeout=(10 * MINUTE)),
            dict(name=warm_cache, args=[], kwargs={},
                 interval=(10 * MINUTE), timeout=(10 * MINUTE))]
    # Create ZIPs for all projects
    zip_jobs = get_export_task_jobs()
    # Based on type of user
    project_jobs = get_project_jobs()
    return zip_jobs + jobs + project_jobs

def get_export_task_jobs():
    """Export tasks to zip"""
    from pybossa.core import db, user_repo
    from pybossa.model.app import App
    apps = db.slave_session.query(App).all()
    # Append all ZIP generation for each app as a task
    jobs = []
    for app_x in apps:
        checkuser = user_repo.get(app_x.owner_id)
        # Check if Pro User, if yes use a shorter schedule
        schedule_hours = 24
        if checkuser.pro:
            schedule_hours = 4
        jobs.append(dict(name = project_export,
                         args = [app_x.id], kwargs={},
                         interval=(schedule_hours * HOUR),
                         timeout = (10 * MINUTE)))
    return jobs

def project_export(id):
    from pybossa.core import project_repo, json_exporter, csv_exporter
    app = project_repo.get(id)
    if app is not None:
        print "Export project id %d" % id
        json_exporter.pregenerate_zip_files(app)
        csv_exporter.pregenerate_zip_files(app)

def get_project_jobs():
    """Return a list of jobs based on user type."""
    from pybossa.cache import apps as cached_apps
    return create_dict_jobs(cached_apps.get_from_pro_user(),
                            get_app_stats,
                            interval=(10 * MINUTE),
                            timeout=(10 * MINUTE))

def create_dict_jobs(data, function,
                     interval=(24 * HOUR), timeout=(10 * MINUTE)):
    jobs = []
    for d in data:
        jobs.append(dict(name=function,
                         args=[d['id'], d['short_name']], kwargs={},
                         interval=interval,
                         timeout=timeout))
    return jobs

@with_cache_disabled
def get_app_stats(id, short_name): # pragma: no cover
    """Get stats for app."""
    import pybossa.cache.apps as cached_apps
    import pybossa.cache.project_stats as stats
    from flask import current_app

    cached_apps.get_app(short_name)
    cached_apps.n_tasks(id)
    cached_apps.n_task_runs(id)
    cached_apps.overall_progress(id)
    cached_apps.last_activity(id)
    cached_apps.n_completed_tasks(id)
    cached_apps.n_volunteers(id)
    stats.get_stats(id, current_app.config.get('GEO'))

@with_cache_disabled
def warm_up_stats(): # pragma: no cover
    """Background job for warming stats."""
    print "Running on the background warm_up_stats"
    from pybossa.cache.site_stats import (n_auth_users, n_anon_users,
                                          n_tasks_site, n_total_tasks_site,
                                          n_task_runs_site,
                                          get_top5_apps_24_hours,
                                          get_top5_users_24_hours, get_locs)
    n_auth_users()
    n_anon_users()
    n_tasks_site()
    n_total_tasks_site()
    n_task_runs_site()
    get_top5_apps_24_hours()
    get_top5_users_24_hours()
    get_locs()

    return True

@with_cache_disabled
def warm_cache(): # pragma: no cover
    """Background job to warm cache."""
    from pybossa.core import create_app
    app = create_app(run_as_server=False)
    # Cache 3 pages
    apps_cached = []
    pages = range(1, 4)
    import pybossa.cache.apps as cached_apps
    import pybossa.cache.categories as cached_cat
    import pybossa.cache.users as cached_users
    import pybossa.cache.project_stats as stats

    def warm_app(id, short_name, featured=False):
        if id not in apps_cached:
            cached_apps.get_app(short_name)
            cached_apps.n_tasks(id)
            n_task_runs = cached_apps.n_task_runs(id)
            cached_apps.overall_progress(id)
            cached_apps.last_activity(id)
            cached_apps.n_completed_tasks(id)
            cached_apps.n_volunteers(id)
            if n_task_runs >= 1000 or featured:
                print ("Getting stats for %s as it has %s task runs" %
                       (short_name, n_task_runs))
                stats.get_stats(id, app.config.get('GEO'))
            apps_cached.append(id)

    # Cache top projects
    apps = cached_apps.get_top()
    for a in apps:
        warm_app(a['id'], a['short_name'])
    for page in pages:
        apps = cached_apps.get_featured('featured', page,
                                        app.config['APPS_PER_PAGE'])
        for a in apps:
            warm_app(a['id'], a['short_name'], featured=True)

    # Categories
    categories = cached_cat.get_used()
    for c in categories:
        for page in pages:
            apps = cached_apps.get(c['short_name'],
                                   page,
                                   app.config['APPS_PER_PAGE'])
            for a in apps:
                warm_app(a['id'], a['short_name'])
    # Users
    cached_users.get_leaderboard(app.config['LEADERBOARD'], 'anonymous')
    cached_users.get_top()

    return True

def get_non_updated_apps():
    """Return a list of non updated apps."""
    from sqlalchemy.sql import text
    from pybossa.model.app import App
    from pybossa.core import db
    sql = text('''SELECT id FROM app WHERE TO_DATE(updated,
                'YYYY-MM-DD\THH24:MI:SS.US') <= NOW() - '3 month':: INTERVAL
               AND contacted != True LIMIT 25''')
    results = db.slave_session.execute(sql)
    apps = []
    for row in results:
        a = App.query.get(row.id)
        apps.append(a)
    return apps

def warn_old_project_owners():
    """E-mail the project owners not updated in the last 3 months."""
    from pybossa.core import mail, project_repo
    from flask import current_app
    from flask.ext.mail import Message

    apps = get_non_updated_apps()

    with mail.connect() as conn:
        for a in apps:
            message = ("Dear %s,\
                       \
                       Your project %s has been inactive for the last 3 months.\
                       And we would like to inform you that if you need help \
                       with it, just contact us answering to this email.\
                       \
                       Otherwise, we will archive the project, removing it \
                       from the server. You have one month to upload any new \
                       tasks, add a new blog post, or engage new volunteers.\
                       \
                       If at the end the project is deleted, we will send you \
                       a ZIP file where you can download your project.\
                       \
                       All the best,\
                       \
                       The team.") % (a.owner.fullname, a.name)
            subject = ('Your %s project: %s has been inactive'
                       % (current_app.config.get('BRAND'), a.name))
            msg = Message(recipients=[a.owner.email_addr],
                          body=message,
                          subject=subject)
            conn.send(msg)
            a.contacted = True
            project_repo.update(a)
    return True

def send_mail(message_dict):
    message = Message(**message_dict)
    mail.send(message)

def import_tasks(tasks_info, app_id):
    from pybossa.core import task_repo, project_repo
    from flask import current_app
    import pybossa.importers as importers

    app = project_repo.get(app_id)
    msg = importers.create_tasks(task_repo, tasks_info, app_id)
    msg = msg + ' to your project %s!' % app.name
    subject = 'Tasks Import to your project %s' % app.name
    body = 'Hello,\n\n' + msg + '\n\nAll the best,\nThe %s team.' % current_app.config.get('BRAND')
    mail_dict = dict(recipients=[app.owner.email_addr],
                     subject=subject, body=body)
    send_mail(mail_dict)
    return msg
<commit_msg>Get user stats.<commit_after># -*- coding: utf8 -*-
# This file is part of PyBossa.
#
# Copyright (C) 2014 SF Isle of Man Limited
#
# PyBossa is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# PyBossa is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with PyBossa.  If not, see <http://www.gnu.org/licenses/>.
"""Jobs module for running background tasks in PyBossa server."""
from flask.ext.mail import Message
from pybossa.core import mail
from pybossa.util import with_cache_disabled
from rq_scheduler import Scheduler

MINUTE = 60
HOUR = 60 * 60

def schedule_priority_jobs(queue_name, interval):
    """Schedule all PyBossa high priority jobs."""
    jobs = get_scheduled_jobs()
    from pybossa.core import sentinel
    from rq import Queue
    redis_conn = sentinel.master

    n_jobs = 0
    queue = Queue(queue_name, connection=redis_conn)
    for job in jobs:
        if (job['interval'] <= interval):
                n_jobs += 1
                queue.enqueue_call(func=job['name'],
                                   args=job['args'],
                                   kwargs=job['kwargs'],
                                   timeout=job['timeout'])
    msg = "%s jobs in %s have been enqueued" % (n_jobs, queue_name)
    return msg

def get_scheduled_jobs(): # pragma: no cover
    """Return a list of scheduled jobs."""
    # Default ones
    # A job is a dict with the following format: dict(name, args, kwargs,
    # interval)
    jobs = [
            dict(name=warm_up_stats, args=[], kwargs={},
                 interval=HOUR, timeout=(10 * MINUTE)),
            dict(name=warn_old_project_owners, args=[], kwargs={},
                 interval=(24 * HOUR), timeout=(10 * MINUTE)),
            dict(name=warm_cache, args=[], kwargs={},
                 interval=(10 * MINUTE), timeout=(10 * MINUTE))]
    # Create ZIPs for all projects
    zip_jobs = get_export_task_jobs()
    # Based on type of user
    project_jobs = get_project_jobs()
    return zip_jobs + jobs + project_jobs

def get_export_task_jobs():
    """Export tasks to zip"""
    from pybossa.core import db, user_repo
    from pybossa.model.app import App
    apps = db.slave_session.query(App).all()
    # Append all ZIP generation for each app as a task
    jobs = []
    for app_x in apps:
        checkuser = user_repo.get(app_x.owner_id)
        # Check if Pro User, if yes use a shorter schedule
        schedule_hours = 24
        if checkuser.pro:
            schedule_hours = 4
        jobs.append(dict(name = project_export,
                         args = [app_x.id], kwargs={},
                         interval=(schedule_hours * HOUR),
                         timeout = (10 * MINUTE)))
    return jobs

def project_export(id):
    from pybossa.core import project_repo, json_exporter, csv_exporter
    app = project_repo.get(id)
    if app is not None:
        print "Export project id %d" % id
        json_exporter.pregenerate_zip_files(app)
        csv_exporter.pregenerate_zip_files(app)

def get_project_jobs():
    """Return a list of jobs based on user type."""
    from pybossa.cache import apps as cached_apps
    return create_dict_jobs(cached_apps.get_from_pro_user(),
                            get_app_stats,
                            interval=(10 * MINUTE),
                            timeout=(10 * MINUTE))

def create_dict_jobs(data, function,
                     interval=(24 * HOUR), timeout=(10 * MINUTE)):
    jobs = []
    for d in data:
        jobs.append(dict(name=function,
                         args=[d['id'], d['short_name']], kwargs={},
                         interval=interval,
                         timeout=timeout))
    return jobs

@with_cache_disabled
def get_app_stats(id, short_name): # pragma: no cover
    """Get stats for app."""
    import pybossa.cache.apps as cached_apps
    import pybossa.cache.project_stats as stats
    from flask import current_app

    cached_apps.get_app(short_name)
    cached_apps.n_tasks(id)
    cached_apps.n_task_runs(id)
    cached_apps.overall_progress(id)
    cached_apps.last_activity(id)
    cached_apps.n_completed_tasks(id)
    cached_apps.n_volunteers(id)
    stats.get_stats(id, current_app.config.get('GEO'))

@with_cache_disabled
def warm_up_stats(): # pragma: no cover
    """Background job for warming stats."""
    print "Running on the background warm_up_stats"
    from pybossa.cache.site_stats import (n_auth_users, n_anon_users,
                                          n_tasks_site, n_total_tasks_site,
                                          n_task_runs_site,
                                          get_top5_apps_24_hours,
                                          get_top5_users_24_hours, get_locs)
    n_auth_users()
    n_anon_users()
    n_tasks_site()
    n_total_tasks_site()
    n_task_runs_site()
    get_top5_apps_24_hours()
    get_top5_users_24_hours()
    get_locs()

    return True

@with_cache_disabled
def warm_cache(): # pragma: no cover
    """Background job to warm cache."""
    from pybossa.core import create_app
    app = create_app(run_as_server=False)
    # Cache 3 pages
    apps_cached = []
    pages = range(1, 4)
    import pybossa.cache.apps as cached_apps
    import pybossa.cache.categories as cached_cat
    import pybossa.cache.users as cached_users
    import pybossa.cache.project_stats as stats

    def warm_app(id, short_name, featured=False):
        if id not in apps_cached:
            cached_apps.get_app(short_name)
            cached_apps.n_tasks(id)
            n_task_runs = cached_apps.n_task_runs(id)
            cached_apps.overall_progress(id)
            cached_apps.last_activity(id)
            cached_apps.n_completed_tasks(id)
            cached_apps.n_volunteers(id)
            if n_task_runs >= 1000 or featured:
                print ("Getting stats for %s as it has %s task runs" %
                       (short_name, n_task_runs))
                stats.get_stats(id, app.config.get('GEO'))
            apps_cached.append(id)

    # Cache top projects
    apps = cached_apps.get_top()
    for a in apps:
        warm_app(a['id'], a['short_name'])
    for page in pages:
        apps = cached_apps.get_featured('featured', page,
                                        app.config['APPS_PER_PAGE'])
        for a in apps:
            warm_app(a['id'], a['short_name'], featured=True)

    # Categories
    categories = cached_cat.get_used()
    for c in categories:
        for page in pages:
            apps = cached_apps.get(c['short_name'],
                                   page,
                                   app.config['APPS_PER_PAGE'])
            for a in apps:
                warm_app(a['id'], a['short_name'])
    # Users
    users = cached_users.get_leaderboard(app.config['LEADERBOARD'], 'anonymous')
    for user in users:
        cached_users.get_user_summary(user['name'])
    cached_users.get_top()

    return True

def get_non_updated_apps():
    """Return a list of non updated apps."""
    from sqlalchemy.sql import text
    from pybossa.model.app import App
    from pybossa.core import db
    sql = text('''SELECT id FROM app WHERE TO_DATE(updated,
                'YYYY-MM-DD\THH24:MI:SS.US') <= NOW() - '3 month':: INTERVAL
               AND contacted != True LIMIT 25''')
    results = db.slave_session.execute(sql)
    apps = []
    for row in results:
        a = App.query.get(row.id)
        apps.append(a)
    return apps

def warn_old_project_owners():
    """E-mail the project owners not updated in the last 3 months."""
    from pybossa.core import mail, project_repo
    from flask import current_app
    from flask.ext.mail import Message

    apps = get_non_updated_apps()

    with mail.connect() as conn:
        for a in apps:
            message = ("Dear %s,\
                       \
                       Your project %s has been inactive for the last 3 months.\
                       And we would like to inform you that if you need help \
                       with it, just contact us answering to this email.\
                       \
                       Otherwise, we will archive the project, removing it \
                       from the server. You have one month to upload any new \
                       tasks, add a new blog post, or engage new volunteers.\
                       \
                       If at the end the project is deleted, we will send you \
                       a ZIP file where you can download your project.\
                       \
                       All the best,\
                       \
                       The team.") % (a.owner.fullname, a.name)
            subject = ('Your %s project: %s has been inactive'
                       % (current_app.config.get('BRAND'), a.name))
            msg = Message(recipients=[a.owner.email_addr],
                          body=message,
                          subject=subject)
            conn.send(msg)
            a.contacted = True
            project_repo.update(a)
    return True

def send_mail(message_dict):
    message = Message(**message_dict)
    mail.send(message)

def import_tasks(tasks_info, app_id):
    from pybossa.core import task_repo, project_repo
    from flask import current_app
    import pybossa.importers as importers

    app = project_repo.get(app_id)
    msg = importers.create_tasks(task_repo, tasks_info, app_id)
    msg = msg + ' to your project %s!' % app.name
    subject = 'Tasks Import to your project %s' % app.name
    body = 'Hello,\n\n' + msg + '\n\nAll the best,\nThe %s team.' % current_app.config.get('BRAND')
    mail_dict = dict(recipients=[app.owner.email_addr],
                     subject=subject, body=body)
    send_mail(mail_dict)
    return msg
<|endoftext|>


<commit_before>"""
A combination of utilities used internally by pycassa and utilities
available for use by others working with pycassa.

"""

import random
import numbers
import uuid
import time

__all__ = ['convert_time_to_uuid', 'convert_uuid_to_time']

def convert_time_to_uuid(time_arg, lowest_val=True, randomize=False):
    """
    Converts a datetime or timestamp to a type 1 UUID.

    This is to assist with getting a time slice of columns or creating
    columns when column names are TimeUUID. Note that this is done
    automatically in most cases if name packing and value packing are
    enabled.

    Also, be careful not to rely on this when specifying a discrete
    set of columns to fetch, as the non-timestamp portions of the
    UUID will be generated randomly. This problem does not matter
    with slice arguments, however, as the non-timestamp portions
    can be set to their lowest or highest possible values.

    :Parameters:
        `datetime`: datetime or timestamp
            - The time to use for the timestamp portion of the UUID.
              Expected inputs to this would either be a datetime object
              or a timestamp with the same precision produced by
              :meth:`time.time()`. That is, sub-second precision should
              be below the decimal place.
        `lowest_val`: boolean
            - Whether the UUID produced should be the lowest possible value
              UUID with the same timestamp as datetime or the highest possible
              value.
        `randomize`: boolean
            - Whether the clock and node bits of the UUID should be randomly
              generated.  The `lowest_val` argument will be ignored if this
              is true.

    """
    if isinstance(time_arg, uuid.UUID):
        return time_arg

    nanoseconds = 0
    if hasattr(time_arg, 'timetuple'):
        nanoseconds = int(time.mktime(time_arg.timetuple()) * 1e9)
    elif isinstance(time_arg, numbers.Number):
        nanoseconds = int(time_arg * 1e9)
    else:
        raise ValueError('Argument for a v1 UUID column name or value was ' +
                'neither a UUID, a datetime, or a number')

    # 0x01b21dd213814000 is the number of 100-ns intervals between the
    # UUID epoch 1582-10-15 00:00:00 and the Unix epoch 1970-01-01 00:00:00.
    timestamp = int(nanoseconds/100) + 0x01b21dd213814000L

    time_low = timestamp & 0xffffffffL
    time_mid = (timestamp >> 32L) & 0xffffL
    time_hi_version = (timestamp >> 48L) & 0x0fffL

    if randomize:
        rand_bits = random.getrandbits(8 + 8 + 48)
        clock_seq_low = rand_bits & 0xffL # 8 bits, no offset
        clock_seq_hi_variant = (rand_bits & 0xff00L) / 0x100 # 8 bits, 8 offset
        node = (rand_bits & 0xffffffffffff0000L) / 0x10000L # 48 bits, 16 offset
    else:
        if lowest_val:
            # Make the lowest value UUID with the same timestamp
            clock_seq_low = 0 & 0xffL
            clock_seq_hi_variant = 0 & 0x3fL
            node = 0 & 0xffffffffffffL # 48 bits
        else:
            # Make the highest value UUID with the same timestamp
            clock_seq_low = 0xffL
            clock_seq_hi_variant = 0x3fL
            node = 0xffffffffffffL # 48 bits
    return uuid.UUID(fields=(time_low, time_mid, time_hi_version,
                        clock_seq_hi_variant, clock_seq_low, node), version=1)

def convert_uuid_to_time(uuid_arg):
    """
    Converts a version 1 UUID to a timestamp with the same precision
    as :meth:`time.time()` returns.  This is useful for examining the
    results of queries returning v1 UUIDs.

    :param uuid_arg: a version 1 UUID

    """
    ts = uuid_arg.get_time()
    return (ts - 0x01b21dd213814000L)/1e7

# Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Michael Bayer mike_mp@zzzcomputing.com
#
# The 'as_interface' method is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php

import operator

def as_interface(obj, cls=None, methods=None, required=None):
    """Ensure basic interface compliance for an instance or dict of callables.

    Checks that ``obj`` implements public methods of ``cls`` or has members
    listed in ``methods``.  If ``required`` is not supplied, implementing at
    least one interface method is sufficient.  Methods present on ``obj`` that
    are not in the interface are ignored.

    If ``obj`` is a dict and ``dict`` does not meet the interface
    requirements, the keys of the dictionary are inspected. Keys present in
    ``obj`` that are not in the interface will raise TypeErrors.

    Raises TypeError if ``obj`` does not meet the interface criteria.

    In all passing cases, an object with callable members is returned.  In the
    simple case, ``obj`` is returned as-is; if dict processing kicks in then
    an anonymous class is returned.

    obj
      A type, instance, or dictionary of callables.
    cls
      Optional, a type.  All public methods of cls are considered the
      interface.  An ``obj`` instance of cls will always pass, ignoring
      ``required``..
    methods
      Optional, a sequence of method names to consider as the interface.
    required
      Optional, a sequence of mandatory implementations. If omitted, an
      ``obj`` that provides at least one interface method is considered
      sufficient.  As a convenience, required may be a type, in which case
      all public methods of the type are required.

    """
    if not cls and not methods:
        raise TypeError('a class or collection of method names are required')

    if isinstance(cls, type) and isinstance(obj, cls):
        return obj

    interface = set(methods or [m for m in dir(cls) if not m.startswith('_')])
    implemented = set(dir(obj))

    complies = operator.ge
    if isinstance(required, type):
        required = interface
    elif not required:
        required = set()
        complies = operator.gt
    else:
        required = set(required)

    if complies(implemented.intersection(interface), required):
        return obj

    # No dict duck typing here.
    if not type(obj) is dict:
        qualifier = complies is operator.gt and 'any of' or 'all of'
        raise TypeError("%r does not implement %s: %s" % (
            obj, qualifier, ', '.join(interface)))

    class AnonymousInterface(object):
        """A callable-holding shell."""

    if cls:
        AnonymousInterface.__name__ = 'Anonymous' + cls.__name__
    found = set()

    for method, impl in dictlike_iteritems(obj):
        if method not in interface:
            raise TypeError("%r: unknown in this interface" % method)
        if not callable(impl):
            raise TypeError("%r=%r is not callable" % (method, impl))
        setattr(AnonymousInterface, method, staticmethod(impl))
        found.add(method)

    if complies(found, required):
        return AnonymousInterface

    raise TypeError("dictionary does not contain required keys %s" %
                    ', '.join(required - found))
<commit_msg>Python 2.4-compatibile checking of number types.<commit_after>"""
A combination of utilities used internally by pycassa and utilities
available for use by others working with pycassa.

"""

import random
import uuid
import time

__all__ = ['convert_time_to_uuid', 'convert_uuid_to_time']

_number_types = frozenset((int, long, float))

def convert_time_to_uuid(time_arg, lowest_val=True, randomize=False):
    """
    Converts a datetime or timestamp to a type 1 UUID.

    This is to assist with getting a time slice of columns or creating
    columns when column names are TimeUUID. Note that this is done
    automatically in most cases if name packing and value packing are
    enabled.

    Also, be careful not to rely on this when specifying a discrete
    set of columns to fetch, as the non-timestamp portions of the
    UUID will be generated randomly. This problem does not matter
    with slice arguments, however, as the non-timestamp portions
    can be set to their lowest or highest possible values.

    :Parameters:
        `datetime`: datetime or timestamp
            - The time to use for the timestamp portion of the UUID.
              Expected inputs to this would either be a datetime object
              or a timestamp with the same precision produced by
              :meth:`time.time()`. That is, sub-second precision should
              be below the decimal place.
        `lowest_val`: boolean
            - Whether the UUID produced should be the lowest possible value
              UUID with the same timestamp as datetime or the highest possible
              value.
        `randomize`: boolean
            - Whether the clock and node bits of the UUID should be randomly
              generated.  The `lowest_val` argument will be ignored if this
              is true.

    """
    if isinstance(time_arg, uuid.UUID):
        return time_arg

    nanoseconds = 0
    if hasattr(time_arg, 'timetuple'):
        nanoseconds = int(time.mktime(time_arg.timetuple()) * 1e9)
    elif type(time_arg) in _number_types:
        nanoseconds = int(time_arg * 1e9)
    else:
        raise ValueError('Argument for a v1 UUID column name or value was ' +
                'neither a UUID, a datetime, or a number')

    # 0x01b21dd213814000 is the number of 100-ns intervals between the
    # UUID epoch 1582-10-15 00:00:00 and the Unix epoch 1970-01-01 00:00:00.
    timestamp = int(nanoseconds/100) + 0x01b21dd213814000L

    time_low = timestamp & 0xffffffffL
    time_mid = (timestamp >> 32L) & 0xffffL
    time_hi_version = (timestamp >> 48L) & 0x0fffL

    if randomize:
        rand_bits = random.getrandbits(8 + 8 + 48)
        clock_seq_low = rand_bits & 0xffL # 8 bits, no offset
        clock_seq_hi_variant = (rand_bits & 0xff00L) / 0x100 # 8 bits, 8 offset
        node = (rand_bits & 0xffffffffffff0000L) / 0x10000L # 48 bits, 16 offset
    else:
        if lowest_val:
            # Make the lowest value UUID with the same timestamp
            clock_seq_low = 0 & 0xffL
            clock_seq_hi_variant = 0 & 0x3fL
            node = 0 & 0xffffffffffffL # 48 bits
        else:
            # Make the highest value UUID with the same timestamp
            clock_seq_low = 0xffL
            clock_seq_hi_variant = 0x3fL
            node = 0xffffffffffffL # 48 bits
    return uuid.UUID(fields=(time_low, time_mid, time_hi_version,
                        clock_seq_hi_variant, clock_seq_low, node), version=1)

def convert_uuid_to_time(uuid_arg):
    """
    Converts a version 1 UUID to a timestamp with the same precision
    as :meth:`time.time()` returns.  This is useful for examining the
    results of queries returning v1 UUIDs.

    :param uuid_arg: a version 1 UUID

    """
    ts = uuid_arg.get_time()
    return (ts - 0x01b21dd213814000L)/1e7

# Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Michael Bayer mike_mp@zzzcomputing.com
#
# The 'as_interface' method is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php

import operator

def as_interface(obj, cls=None, methods=None, required=None):
    """Ensure basic interface compliance for an instance or dict of callables.

    Checks that ``obj`` implements public methods of ``cls`` or has members
    listed in ``methods``.  If ``required`` is not supplied, implementing at
    least one interface method is sufficient.  Methods present on ``obj`` that
    are not in the interface are ignored.

    If ``obj`` is a dict and ``dict`` does not meet the interface
    requirements, the keys of the dictionary are inspected. Keys present in
    ``obj`` that are not in the interface will raise TypeErrors.

    Raises TypeError if ``obj`` does not meet the interface criteria.

    In all passing cases, an object with callable members is returned.  In the
    simple case, ``obj`` is returned as-is; if dict processing kicks in then
    an anonymous class is returned.

    obj
      A type, instance, or dictionary of callables.
    cls
      Optional, a type.  All public methods of cls are considered the
      interface.  An ``obj`` instance of cls will always pass, ignoring
      ``required``..
    methods
      Optional, a sequence of method names to consider as the interface.
    required
      Optional, a sequence of mandatory implementations. If omitted, an
      ``obj`` that provides at least one interface method is considered
      sufficient.  As a convenience, required may be a type, in which case
      all public methods of the type are required.

    """
    if not cls and not methods:
        raise TypeError('a class or collection of method names are required')

    if isinstance(cls, type) and isinstance(obj, cls):
        return obj

    interface = set(methods or [m for m in dir(cls) if not m.startswith('_')])
    implemented = set(dir(obj))

    complies = operator.ge
    if isinstance(required, type):
        required = interface
    elif not required:
        required = set()
        complies = operator.gt
    else:
        required = set(required)

    if complies(implemented.intersection(interface), required):
        return obj

    # No dict duck typing here.
    if not type(obj) is dict:
        qualifier = complies is operator.gt and 'any of' or 'all of'
        raise TypeError("%r does not implement %s: %s" % (
            obj, qualifier, ', '.join(interface)))

    class AnonymousInterface(object):
        """A callable-holding shell."""

    if cls:
        AnonymousInterface.__name__ = 'Anonymous' + cls.__name__
    found = set()

    for method, impl in dictlike_iteritems(obj):
        if method not in interface:
            raise TypeError("%r: unknown in this interface" % method)
        if not callable(impl):
            raise TypeError("%r=%r is not callable" % (method, impl))
        setattr(AnonymousInterface, method, staticmethod(impl))
        found.add(method)

    if complies(found, required):
        return AnonymousInterface

    raise TypeError("dictionary does not contain required keys %s" %
                    ', '.join(required - found))
<|endoftext|>