<commit_before>#
#   Copyright (c) 2011-2013 Canonical Ltd.
#
#   This file is part of: SST (selenium-simple-test)
#   https://launchpad.net/selenium-simple-test
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.
#

import platform
import shutil
import subprocess

from selenium.common import exceptions as selenium_exceptions
from selenium.webdriver import firefox

class FirefoxBinary(firefox.firefox_binary.FirefoxBinary):
    """Workarounds selenium firefox issues.

    There is race condition in the way firefox is spawned. The exact cause
    hasn't been properly diagnosed yet but it's around:

    - getting a free port from the OS with selenium.webdriver.common.utils
      free_port(),

    - release the port immediately but record it in ff prefs so that ff can
      listen on that port for the internal http server.

    It has been observed that this leads to hanging processes for 'firefox
    -silent'.
    """

    def _start_from_profile_path(self, path):
        self._firefox_env["XRE_PROFILE_PATH"] = path

        if platform.system().lower() == 'linux':
            self._modify_link_library_path()
        command = [self._start_cmd, "-silent"]
        if self.command_line is not None:
            for cli in self.command_line:
                command.append(cli)

# The following exists upstream and is known to create hanging firefoxes,
# leading to zombies.
#        subprocess.Popen(command, stdout=self._log_file,
#              stderr=subprocess.STDOUT,
#              env=self._firefox_env).communicate()
        command[1] = '-foreground'
        self.process = subprocess.Popen(
            command, stdout=self._log_file, stderr=subprocess.STDOUT,
            env=self._firefox_env)

class WebDriver(firefox.webdriver.WebDriver):
    """Workarounds selenium firefox issues."""

    def __init__(self, firefox_profile=None, firefox_binary=None, timeout=30,
                 capabilities=None, proxy=None):
        try:
            super(WebDriver, self).__init__(
                firefox_profile, FirefoxBinary(), timeout, capabilities, proxy)
        except selenium_exceptions.WebDriverException:
            # If we can't start, cleanup profile
            shutil.rmtree(self.profile.path)
            if self.profile.tempfolder is not None:
                shutil.rmtree(self.profile.tempfolder)
            raise
<commit_msg>Remove selenium dependency when not using selenium tests<commit_after>#
#   Copyright (c) 2011-2013 Canonical Ltd.
#
#   This file is part of: SST (selenium-simple-test)
#   https://launchpad.net/selenium-simple-test
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.
#

import logging
import os
import platform
import shutil
import subprocess

LOG = logging.getLogger(__name__)

try:
    # NOTE: Several distribution can't ship selenium due to its
    # non-free license. So they have to patch it out of test-requirements.txt
    # Avoid import failure and force not running selenium tests.
    # The entire file is encapsulated in the try block because the classes
    # inherit from the firefox class contained in selenium.webdriver, and
    # python will throw a NameError if the import is skipped.
    from selenium.common import exceptions as selenium_exceptions
    from selenium.webdriver import firefox

    class FirefoxBinary(firefox.firefox_binary.FirefoxBinary):
        """Workarounds selenium firefox issues.

        There is race condition in the way firefox is spawned. The exact cause
        hasn't been properly diagnosed yet but it's around:

        - getting a free port from the OS with selenium.webdriver.common.utils
        free_port(),

        - release the port immediately but record it in ff prefs so that ff can
        listen on that port for the internal http server.

        It has been observed that this leads to hanging processes for 'firefox
        -silent'.
        """

        def _start_from_profile_path(self, path):
            self._firefox_env["XRE_PROFILE_PATH"] = path

            if platform.system().lower() == 'linux':
                self._modify_link_library_path()
            command = [self._start_cmd, "-silent"]
            if self.command_line is not None:
                for cli in self.command_line:
                    command.append(cli)

    # The following exists upstream and is known to create hanging firefoxes,
    # leading to zombies.
    #        subprocess.Popen(command, stdout=self._log_file,
    #              stderr=subprocess.STDOUT,
    #              env=self._firefox_env).communicate()
            command[1] = '-foreground'
            self.process = subprocess.Popen(
                command, stdout=self._log_file, stderr=subprocess.STDOUT,
                env=self._firefox_env)

    class WebDriver(firefox.webdriver.WebDriver):
        """Workarounds selenium firefox issues."""

        def __init__(self, firefox_profile=None, firefox_binary=None,
                     timeout=30, capabilities=None, proxy=None):
            try:
                super(WebDriver, self).__init__(
                    firefox_profile, FirefoxBinary(), timeout, capabilities,
                    proxy)
            except selenium_exceptions.WebDriverException:
                # If we can't start, cleanup profile
                shutil.rmtree(self.profile.path)
                if self.profile.tempfolder is not None:
                    shutil.rmtree(self.profile.tempfolder)
                raise

except ImportError as e:
    # NOTE(saschpe): Several distribution can't ship selenium due to its
    # non-free license. So they have to patch it out of test-requirements.txt
    # Avoid import failure and force not running selenium tests.
    LOG.warning("{0}, force WITH_SELENIUM=False".format(str(e)))
    os.environ['WITH_SELENIUM'] = ''
<|endoftext|>


<commit_before>#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (C) 2014 Mick Krippendorf <m.krippendorf@freenet.de>

__version__ = '0.2.3a'
__date__ = '2014-09-27'
__author__ = 'Mick Krippendorf <m.krippendorf@freenet.de>'
__license__ = 'MIT'

from hornet import *

from hornet.symbols import (
    queens, solution, noattack, Rest, Ns, S, X, Y, X1, Y1, Xs, Ys, Y0s, Qs,
)

def main():

    db = Database()

    db.tell(

        queens(S) <<
            let(Ns, promote(list(range(1, 6)))) &
            solution(Ns, Ns, [], S),

        solution([X|Xs], Y0s, Qs, [X/Y|S]) <<
            select(Y, Y0s, Ys) &
            noattack(X/Y, Qs) &
            solution(Xs, Ys, [X/Y|Qs], S),
        solution([], _, _, []),

        noattack(X/Y, [X1/Y1|Rest]) <<
            arithmetic_not_equal(Y, Y1) &
            arithmetic_not_equal(Y1 - Y, X1 - X) &
            arithmetic_not_equal(Y1 - Y, X - X1) &
            noattack(X/Y, Rest),
        noattack(_, []),

    )

    for subst in db.ask(queens(S)):
        print(subst[S])

if __name__ == '__main__':
    main()
<commit_msg>removed unnecessary promotion in queens.py<commit_after>#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (C) 2014 Mick Krippendorf <m.krippendorf@freenet.de>

__version__ = '0.2.3a'
__date__ = '2014-09-27'
__author__ = 'Mick Krippendorf <m.krippendorf@freenet.de>'
__license__ = 'MIT'

from hornet import *

from hornet.symbols import (
    queens, solution, noattack, Rest, Ns, S, X, Y, X1, Y1, Xs, Ys, Y0s, Qs,
)

def main():

    db = Database()

    db.tell(

        queens(S) <<
            let(Ns, list(range(1, 6))) &
            solution(Ns, Ns, [], S),

        solution([X|Xs], Y0s, Qs, [X/Y|S]) <<
            select(Y, Y0s, Ys) &
            noattack(X/Y, Qs) &
            solution(Xs, Ys, [X/Y|Qs], S),
        solution([], _, _, []),

        noattack(X/Y, [X1/Y1|Rest]) <<
            arithmetic_not_equal(Y, Y1) &
            arithmetic_not_equal(Y1 - Y, X1 - X) &
            arithmetic_not_equal(Y1 - Y, X - X1) &
            noattack(X/Y, Rest),
        noattack(_, []),

    )

    for subst in db.ask(queens(S)):
        print(subst[S])

if __name__ == '__main__':
    main()
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
##############################################################################
#
#    OpenERP, Open Source Management Solution
#    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as
#    published by the Free Software Foundation, either version 3 of the
#    License, or (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Affero General Public License for more details.
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################

from openerp.osv import fields, osv
from openerp.tools.translate import _

class product(osv.osv):
    _inherit = 'product.product'
    _columns = {
        'event_ok': fields.boolean('Event Subscription', help='Determine if a product needs to create automatically an event registration at the confirmation of a sales order line.'),
        'event_type_id': fields.many2one('event.type', 'Type of Event', help='Select event types so when we use this product in sales order lines, it will filter events of this type only.'),
    }

    def onchange_event_ok(self, cr, uid, ids, event_ok, context=None):
        return {'value': {'type': event_ok and 'service' or False}}

class sale_order_line(osv.osv):
    _inherit = 'sale.order.line'
    _columns = {
        'event_id': fields.many2one('event.event', 'Event', help="Choose an event and it will automatically create a registration for this event."),
        #those 2 fields are used for dynamic domains and filled by onchange
        'event_type_id': fields.related('product_id','event_type_id', type='many2one', relation="event.type", string="Event Type"),
        'event_ok': fields.related('product_id', 'event_ok', string='event_ok', type='boolean'),
    }

    def product_id_change(self, cr, uid, ids,
                          pricelist,
                          product, qty=0,
                          uom=False,
                          qty_uos=0,
                          uos=False,
                          name='',
                          partner_id=False,
                          lang=False,
                          update_tax=True,
                          date_order=False,
                          packaging=False,
                          fiscal_position=False,
                          flag=False, context=None):
        """
        check product if event type
        """
        res = super(sale_order_line,self).product_id_change(cr, uid, ids, pricelist, product, qty=qty, uom=uom, qty_uos=qty_uos, uos=uos, name=name, partner_id=partner_id, lang=lang, update_tax=update_tax, date_order=date_order, packaging=packaging, fiscal_position=fiscal_position, flag=flag, context=context)
        if product:
            product_res = self.pool.get('product.product').browse(cr, uid, product, context=context)
            if product_res.event_ok:
                res['value'].update({'event_type_id': product_res.event_type_id.id, 'event_ok':product_res.event_ok})
        return res

    def button_confirm(self, cr, uid, ids, context=None):
        '''
        create registration with sales order
        '''
        if context is None:
            context = {}
        registration_obj = self.pool.get('event.registration')
        sale_obj = self.pool.get('sale.order')
        for order_line in self.browse(cr, uid, ids, context=context):
            if order_line.event_id.id:
                dic = {
                    'name': order_line.order_id.partner_invoice_id.name,
                    'partner_id': order_line.order_id.partner_id.id,
                    'nb_register': int(order_line.product_uom_qty),
                    'email': order_line.order_id.partner_id.email,
                    'phone': order_line.order_id.partner_id.phone,
                    'origin': order_line.order_id.name,
                    'event_id': order_line.event_id.id,
                }
                message = _("The registration has been created for event %s from the Sale Order %s. ") % (order_line.event_id.name, order_line.order_id.name)
                context.update({'mail_create_nolog': True, 'message': message })
                registration_id = registration_obj.create(cr, uid, dic, context=context)
                registration_obj.message_post(cr, uid, [registration_id], body=message, context=context)
        return super(sale_order_line, self).button_confirm(cr, uid, ids, context=context)
<commit_msg>[IMP] event: remove unused parameter from context added at revision 8737<commit_after># -*- coding: utf-8 -*-
##############################################################################
#
#    OpenERP, Open Source Management Solution
#    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as
#    published by the Free Software Foundation, either version 3 of the
#    License, or (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Affero General Public License for more details.
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################

from openerp.osv import fields, osv
from openerp.tools.translate import _

class product(osv.osv):
    _inherit = 'product.product'
    _columns = {
        'event_ok': fields.boolean('Event Subscription', help='Determine if a product needs to create automatically an event registration at the confirmation of a sales order line.'),
        'event_type_id': fields.many2one('event.type', 'Type of Event', help='Select event types so when we use this product in sales order lines, it will filter events of this type only.'),
    }

    def onchange_event_ok(self, cr, uid, ids, event_ok, context=None):
        return {'value': {'type': event_ok and 'service' or False}}

class sale_order_line(osv.osv):
    _inherit = 'sale.order.line'
    _columns = {
        'event_id': fields.many2one('event.event', 'Event', help="Choose an event and it will automatically create a registration for this event."),
        #those 2 fields are used for dynamic domains and filled by onchange
        'event_type_id': fields.related('product_id','event_type_id', type='many2one', relation="event.type", string="Event Type"),
        'event_ok': fields.related('product_id', 'event_ok', string='event_ok', type='boolean'),
    }

    def product_id_change(self, cr, uid, ids,
                          pricelist,
                          product, qty=0,
                          uom=False,
                          qty_uos=0,
                          uos=False,
                          name='',
                          partner_id=False,
                          lang=False,
                          update_tax=True,
                          date_order=False,
                          packaging=False,
                          fiscal_position=False,
                          flag=False, context=None):
        """
        check product if event type
        """
        res = super(sale_order_line,self).product_id_change(cr, uid, ids, pricelist, product, qty=qty, uom=uom, qty_uos=qty_uos, uos=uos, name=name, partner_id=partner_id, lang=lang, update_tax=update_tax, date_order=date_order, packaging=packaging, fiscal_position=fiscal_position, flag=flag, context=context)
        if product:
            product_res = self.pool.get('product.product').browse(cr, uid, product, context=context)
            if product_res.event_ok:
                res['value'].update({'event_type_id': product_res.event_type_id.id, 'event_ok':product_res.event_ok})
        return res

    def button_confirm(self, cr, uid, ids, context=None):
        '''
        create registration with sales order
        '''
        if context is None:
            context = {}
        registration_obj = self.pool.get('event.registration')
        sale_obj = self.pool.get('sale.order')
        for order_line in self.browse(cr, uid, ids, context=context):
            if order_line.event_id.id:
                dic = {
                    'name': order_line.order_id.partner_invoice_id.name,
                    'partner_id': order_line.order_id.partner_id.id,
                    'nb_register': int(order_line.product_uom_qty),
                    'email': order_line.order_id.partner_id.email,
                    'phone': order_line.order_id.partner_id.phone,
                    'origin': order_line.order_id.name,
                    'event_id': order_line.event_id.id,
                }
                message = _("The registration has been created for event %s from the Sale Order %s. ") % (order_line.event_id.name, order_line.order_id.name)
                context.update({'mail_create_nolog': True})
                registration_id = registration_obj.create(cr, uid, dic, context=context)
                registration_obj.message_post(cr, uid, [registration_id], body=message, context=context)
        return super(sale_order_line, self).button_confirm(cr, uid, ids, context=context)
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from odoo import api, exceptions, fields, models, _
from odoo.exceptions import UserError
from odoo.tools import float_compare, float_round
from odoo.addons import decimal_precision as dp

class StockMoveLine(models.Model):
    _inherit = 'stock.move.line'

    workorder_id = fields.Many2one('mrp.workorder', 'Work Order')
    production_id = fields.Many2one('mrp.production', 'Production Order')
    lot_produced_id = fields.Many2one('stock.production.lot', 'Finished Lot')
    lot_produced_qty = fields.Float('Quantity Finished Product', help="Informative, not used in matching")
    done_wo = fields.Boolean('Done for Work Order', default=True, help="Technical Field which is False when temporarily filled in in work order")  # TDE FIXME: naming
    done_move = fields.Boolean('Move Done', related='move_id.is_done', store=True)  # TDE FIXME: naming

    def _get_similar_move_lines(self):
        lines = super(StockMoveLine, self)._get_similar_move_lines()
        if self.move_id.production_id:
            lines |= self.move_id.production_id.move_finished_ids.mapped('move_line_ids').filtered(lambda l: l.lot_id)
        if self.move_id.raw_material_production_id:
            lines |= self.move_id.raw_material_production_id.move_raw_ids.mapped('move_line_ids').filtered(lambda l: l.lot_id)
        if self.workorder_id:
            lines |= self.workorder_id.active_move_line_ids
        return lines

    @api.multi
    def write(self, vals):
        if 'lot_id' in vals:
            for movelot in self:
                movelot.move_id.production_id.move_raw_ids.mapped('move_line_ids')\
                    .filtered(lambda r: r.done_wo and not r.done_move and r.lot_produced_id == movelot.lot_id)\
                    .write({'lot_produced_id': vals['lot_id']})
        return super(StockMoveLine, self).write(vals)

class StockMove(models.Model):
    _inherit = 'stock.move'

    created_production_id = fields.Many2one('mrp.production', 'Created Production Order')
    production_id = fields.Many2one(
        'mrp.production', 'Production Order for finished products')
    raw_material_production_id = fields.Many2one(
        'mrp.production', 'Production Order for raw materials')
    unbuild_id = fields.Many2one(
        'mrp.unbuild', 'Unbuild Order')
    consume_unbuild_id = fields.Many2one(
        'mrp.unbuild', 'Consume Unbuild Order')
    operation_id = fields.Many2one(
        'mrp.routing.workcenter', 'Operation To Consume')  # TDE FIXME: naming
    workorder_id = fields.Many2one(
        'mrp.workorder', 'Work Order To Consume')
    # Quantities to process, in normalized UoMs
    active_move_line_ids = fields.One2many('stock.move.line', 'move_id', domain=[('done_wo', '=', True)], string='Lots')
    bom_line_id = fields.Many2one('mrp.bom.line', 'BoM Line')
    unit_factor = fields.Float('Unit Factor')
    is_done = fields.Boolean(
        'Done', compute='_compute_is_done',
        store=True,
        help='Technical Field to order moves')

    def _get_move_lines(self):
        self.ensure_one()
        if self.raw_material_production_id:
            return self.active_move_line_ids
        else:
            return super(StockMove, self)._get_move_lines()

    @api.depends('state')
    def _compute_is_done(self):
        for move in self:
            move.is_done = (move.state in ('done', 'cancel'))

    def _action_assign(self):
        res = super(StockMove, self)._action_assign()
        for move in self.filtered(lambda x: x.production_id or x.raw_material_production_id):
            if move.move_line_ids:
                move.move_line_ids.write({'production_id': move.raw_material_production_id.id,
                                               'workorder_id': move.workorder_id.id,})
        return res

    def _action_cancel(self):
        if any(move.quantity_done for move in self): #TODO: either put in stock, or check there is a production order related to it
            raise exceptions.UserError(_('You cannot cancel a manufacturing order if you have already consumed material.\
             If you want to cancel this MO, please change the consumed quantities to 0.'))
        return super(StockMove, self)._action_cancel()

    @api.multi
    # Could use split_move_operation from stock here
    def split_move_lot(self):
        ctx = dict(self.env.context)
        self.ensure_one()
        view = self.env.ref('mrp.view_stock_move_lots')
        serial = (self.has_tracking == 'serial')
        only_create = False  # Check operation type in theory
        show_reserved = any([x for x in self.move_line_ids if x.product_qty > 0.0])
        ctx.update({
            'serial': serial,
            'only_create': only_create,
            'create_lots': True,
            'state_done': self.is_done,
            'show_reserved': show_reserved,
        })
        if ctx.get('w_production'):
            action = self.env.ref('mrp.act_mrp_product_produce').read()[0]
            action['context'] = ctx
            return action
        result = {
            'name': _('Register Lots'),
            'type': 'ir.actions.act_window',
            'view_type': 'form',
            'view_mode': 'form',
            'res_model': 'stock.move',
            'views': [(view.id, 'form')],
            'view_id': view.id,
            'target': 'new',
            'res_id': self.id,
            'context': ctx,
        }
        return result

    def _action_confirm(self, merge=True):
        moves = self
        for move in self.filtered(lambda m: m.production_id):
            moves |= move.action_explode()
        # we go further with the list of ids potentially changed by action_explode
        return super(StockMove, moves)._action_confirm(merge=merge)

    def action_explode(self):
        """ Explodes pickings """
        # in order to explode a move, we must have a picking_type_id on that move because otherwise the move
        # won't be assigned to a picking and it would be weird to explode a move into several if they aren't
        # all grouped in the same picking.
        if not self.picking_type_id:
            return self
        bom = self.env['mrp.bom'].sudo()._bom_find(product=self.product_id)
        if not bom or bom.type != 'phantom':
            return self
        phantom_moves = self.env['stock.move']
        processed_moves = self.env['stock.move']
        factor = self.product_uom._compute_quantity(self.product_uom_qty, bom.product_uom_id) / bom.product_qty
        boms, lines = bom.sudo().explode(self.product_id, factor, picking_type=bom.picking_type_id)
        for bom_line, line_data in lines:
            phantom_moves += self._generate_move_phantom(bom_line, line_data['qty'])

        for new_move in phantom_moves:
            processed_moves |= new_move.action_explode()
#         if not self.split_from and self.procurement_id:
#             # Check if procurements have been made to wait for
#             moves = self.procurement_id.move_ids
#             if len(moves) == 1:
#                 self.procurement_id.write({'state': 'done'})
        if processed_moves and self.state == 'assigned':
            # Set the state of resulting moves according to 'assigned' as the original move is assigned
            processed_moves.write({'state': 'assigned'})
        # delete the move with original product which is not relevant anymore
        self.sudo().unlink()
        return processed_moves

    def _generate_move_phantom(self, bom_line, quantity):
        if bom_line.product_id.type in ['product', 'consu']:
            return self.copy(default={
                'picking_id': self.picking_id.id if self.picking_id else False,
                'product_id': bom_line.product_id.id,
                'product_uom': bom_line.product_uom_id.id,
                'product_uom_qty': quantity,
                'state': 'draft',  # will be confirmed below
                'name': self.name,
            })
        return self.env['stock.move']

class PushedFlow(models.Model):
    _inherit = "stock.location.path"

    def _prepare_move_copy_values(self, move_to_copy, new_date):
        new_move_vals = super(PushedFlow, self)._prepare_move_copy_values(move_to_copy, new_date)
        new_move_vals['production_id'] = False

        return new_move_vals
<commit_msg>[FIX] mrp: do not blindly override stock.move's _action_cancel<commit_after># -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from odoo import api, exceptions, fields, models, _
from odoo.exceptions import UserError
from odoo.tools import float_compare, float_round
from odoo.addons import decimal_precision as dp

class StockMoveLine(models.Model):
    _inherit = 'stock.move.line'

    workorder_id = fields.Many2one('mrp.workorder', 'Work Order')
    production_id = fields.Many2one('mrp.production', 'Production Order')
    lot_produced_id = fields.Many2one('stock.production.lot', 'Finished Lot')
    lot_produced_qty = fields.Float('Quantity Finished Product', help="Informative, not used in matching")
    done_wo = fields.Boolean('Done for Work Order', default=True, help="Technical Field which is False when temporarily filled in in work order")  # TDE FIXME: naming
    done_move = fields.Boolean('Move Done', related='move_id.is_done', store=True)  # TDE FIXME: naming

    def _get_similar_move_lines(self):
        lines = super(StockMoveLine, self)._get_similar_move_lines()
        if self.move_id.production_id:
            lines |= self.move_id.production_id.move_finished_ids.mapped('move_line_ids').filtered(lambda l: l.lot_id)
        if self.move_id.raw_material_production_id:
            lines |= self.move_id.raw_material_production_id.move_raw_ids.mapped('move_line_ids').filtered(lambda l: l.lot_id)
        if self.workorder_id:
            lines |= self.workorder_id.active_move_line_ids
        return lines

    @api.multi
    def write(self, vals):
        if 'lot_id' in vals:
            for movelot in self:
                movelot.move_id.production_id.move_raw_ids.mapped('move_line_ids')\
                    .filtered(lambda r: r.done_wo and not r.done_move and r.lot_produced_id == movelot.lot_id)\
                    .write({'lot_produced_id': vals['lot_id']})
        return super(StockMoveLine, self).write(vals)

class StockMove(models.Model):
    _inherit = 'stock.move'

    created_production_id = fields.Many2one('mrp.production', 'Created Production Order')
    production_id = fields.Many2one(
        'mrp.production', 'Production Order for finished products')
    raw_material_production_id = fields.Many2one(
        'mrp.production', 'Production Order for raw materials')
    unbuild_id = fields.Many2one(
        'mrp.unbuild', 'Unbuild Order')
    consume_unbuild_id = fields.Many2one(
        'mrp.unbuild', 'Consume Unbuild Order')
    operation_id = fields.Many2one(
        'mrp.routing.workcenter', 'Operation To Consume')  # TDE FIXME: naming
    workorder_id = fields.Many2one(
        'mrp.workorder', 'Work Order To Consume')
    # Quantities to process, in normalized UoMs
    active_move_line_ids = fields.One2many('stock.move.line', 'move_id', domain=[('done_wo', '=', True)], string='Lots')
    bom_line_id = fields.Many2one('mrp.bom.line', 'BoM Line')
    unit_factor = fields.Float('Unit Factor')
    is_done = fields.Boolean(
        'Done', compute='_compute_is_done',
        store=True,
        help='Technical Field to order moves')

    def _get_move_lines(self):
        self.ensure_one()
        if self.raw_material_production_id:
            return self.active_move_line_ids
        else:
            return super(StockMove, self)._get_move_lines()

    @api.depends('state')
    def _compute_is_done(self):
        for move in self:
            move.is_done = (move.state in ('done', 'cancel'))

    def _action_assign(self):
        res = super(StockMove, self)._action_assign()
        for move in self.filtered(lambda x: x.production_id or x.raw_material_production_id):
            if move.move_line_ids:
                move.move_line_ids.write({'production_id': move.raw_material_production_id.id,
                                               'workorder_id': move.workorder_id.id,})
        return res

    def _action_cancel(self):
        if any(move.quantity_done and (move.raw_material_production_id or move.production_id) for move in self):
            raise exceptions.UserError(_('You cannot cancel a manufacturing order if you have already consumed material.\
             If you want to cancel this MO, please change the consumed quantities to 0.'))
        return super(StockMove, self)._action_cancel()

    @api.multi
    # Could use split_move_operation from stock here
    def split_move_lot(self):
        ctx = dict(self.env.context)
        self.ensure_one()
        view = self.env.ref('mrp.view_stock_move_lots')
        serial = (self.has_tracking == 'serial')
        only_create = False  # Check operation type in theory
        show_reserved = any([x for x in self.move_line_ids if x.product_qty > 0.0])
        ctx.update({
            'serial': serial,
            'only_create': only_create,
            'create_lots': True,
            'state_done': self.is_done,
            'show_reserved': show_reserved,
        })
        if ctx.get('w_production'):
            action = self.env.ref('mrp.act_mrp_product_produce').read()[0]
            action['context'] = ctx
            return action
        result = {
            'name': _('Register Lots'),
            'type': 'ir.actions.act_window',
            'view_type': 'form',
            'view_mode': 'form',
            'res_model': 'stock.move',
            'views': [(view.id, 'form')],
            'view_id': view.id,
            'target': 'new',
            'res_id': self.id,
            'context': ctx,
        }
        return result

    def _action_confirm(self, merge=True):
        moves = self
        for move in self.filtered(lambda m: m.production_id):
            moves |= move.action_explode()
        # we go further with the list of ids potentially changed by action_explode
        return super(StockMove, moves)._action_confirm(merge=merge)

    def action_explode(self):
        """ Explodes pickings """
        # in order to explode a move, we must have a picking_type_id on that move because otherwise the move
        # won't be assigned to a picking and it would be weird to explode a move into several if they aren't
        # all grouped in the same picking.
        if not self.picking_type_id:
            return self
        bom = self.env['mrp.bom'].sudo()._bom_find(product=self.product_id)
        if not bom or bom.type != 'phantom':
            return self
        phantom_moves = self.env['stock.move']
        processed_moves = self.env['stock.move']
        factor = self.product_uom._compute_quantity(self.product_uom_qty, bom.product_uom_id) / bom.product_qty
        boms, lines = bom.sudo().explode(self.product_id, factor, picking_type=bom.picking_type_id)
        for bom_line, line_data in lines:
            phantom_moves += self._generate_move_phantom(bom_line, line_data['qty'])

        for new_move in phantom_moves:
            processed_moves |= new_move.action_explode()
#         if not self.split_from and self.procurement_id:
#             # Check if procurements have been made to wait for
#             moves = self.procurement_id.move_ids
#             if len(moves) == 1:
#                 self.procurement_id.write({'state': 'done'})
        if processed_moves and self.state == 'assigned':
            # Set the state of resulting moves according to 'assigned' as the original move is assigned
            processed_moves.write({'state': 'assigned'})
        # delete the move with original product which is not relevant anymore
        self.sudo().unlink()
        return processed_moves

    def _generate_move_phantom(self, bom_line, quantity):
        if bom_line.product_id.type in ['product', 'consu']:
            return self.copy(default={
                'picking_id': self.picking_id.id if self.picking_id else False,
                'product_id': bom_line.product_id.id,
                'product_uom': bom_line.product_uom_id.id,
                'product_uom_qty': quantity,
                'state': 'draft',  # will be confirmed below
                'name': self.name,
            })
        return self.env['stock.move']

class PushedFlow(models.Model):
    _inherit = "stock.location.path"

    def _prepare_move_copy_values(self, move_to_copy, new_date):
        new_move_vals = super(PushedFlow, self)._prepare_move_copy_values(move_to_copy, new_date)
        new_move_vals['production_id'] = False

        return new_move_vals
<|endoftext|>


<commit_before>## $Id$
##
## This file is part of CDS Invenio.
## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 CERN.
##
## CDS Invenio is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public License as
## published by the Free Software Foundation; either version 2 of the
## License, or (at your option) any later version.
##
## CDS Invenio is distributed in the hope that it will be useful, but
## WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
## General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with CDS Invenio; if not, write to the Free Software Foundation, Inc.,
## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.

"""CDS Invenio Submission Web Interface config file."""

__revision__ = "$Id$"

## test:
test = "FALSE"

## CC all action confirmation mails to administrator? (0 == NO; 1 == YES)
CFG_WEBSUBMIT_COPY_MAILS_TO_ADMIN = 0

## known compressed file extensions:
CFG_COMPRESSED_FILE_EXTENSIONS = ["z", "gz", "tar", "tgz", "tar",
                                  "tar.gz", "zip", "rar", "arj",
                                  "arc", "pak", "lha", "lhz", "sit",
                                  "sea", "sitx", "cpt", "hqx", "uu",
                                  "uue", "bz", "bz2", "bzip", "tbz",
                                  "tbz2", "tar.bz", "tar.bz2"]

CFG_KNOWN_FILE_EXTENSIONS = ["lis",
			     "sxi",
   		 	     "zip",
			     "kpr",
                             "xls",
                             "mov",
			     "avi",
			     "ppt",
			     "prn",
			     "pdf",
			     "tif",
			     "doc",
			     "dot",
			     "ps.gz",
			     "ps.Z",
			     "ps",
			     "eps",
			     "pps",
			     "gif",
			     "jpeg",
			     "jpg",
			     "JPG",
			     "html",
			     "htm",
			     "Download.link",
			     "link",
			     "tex",
			     "txt",
			     "ef.tif",
			     "e.tif",
			     "f.tif",
			     "ef.pdf",
			     "e.pdf",
			     "f.pdf",
			     "ef.ps.gz",
			     "e.ps.gz",
			     "f.ps.gz",
			     "ef.ps",
			     "e.ps",
			     "f.ps",
			     "e.xls",
			     "f.xls",
			     "ef.doc",
			     "e.doc",
			     "f.doc",
			     "ef.html",
			     "e.html",
			     "f.html",
			     "hpg",
			     "mpp",
			     "h",
			     "rtf",
			     "tar",
			     "tar.gz",
			     "tgz",
			     "msg",
			     "llb",
			     "ogg",
			     "mp3",
			     "wav",
			     "mpg"]

class InvenioWebSubmitFunctionError(Exception):
    """This exception should only ever be raised by WebSubmit functions.
       It will be caught and handled by the WebSubmit core itself.
       It is used to signal to WebSubmit core that one of the functions
       encountered a FATAL ERROR situation that should all further execution
       of the submission.
       The exception will carry an error message in its "value" string. This
       message will probably be displayed on the user's browser in an Invenio
       "error" box, and may be logged for the admin to examine.

       Again: If this exception is raised by a WebSubmit function, an error
              message will displayed and the submission ends in failure.

       Extends: Exception.
    """
    def __init__(self, value):
        """Set the internal "value" attribute to that of the passed "value"
           parameter.
           @param value: (string) - an error string to display to the user.
        """
        self.value = value
    def __str__(self):
        """Return oneself as a string (actually, return the contents of
           self.value).
           @return: (string)
        """
        return repr(self.value)

class InvenioWebSubmitFunctionStop(Exception):
    """This exception should only ever be raised by WebSubmit functions.
       It will be caught and handled by the WebSubmit core itself.
       It is used to signal to WebSubmit core that one of the functions
       encountered a situation that should prevent the functions that follow
       it from being executed, and that WebSubmit core should display some sort
       of message to the user. This message will be stored in the "value"
       attribute of the object.

       ***
       NOTE: In the current WebSubmit, this "value" is ususally a JavaScript
             string that redirects the user's browser back to the Web form
             phase of the submission. The use of JavaScript, however is going
             to be removed in the future, so the mechanism may change.
       ***

       Extends: Exception.
    """
    def __init__(self, value):
        """Set the internal "value" attribute to that of the passed "value"
           parameter.
           @param value: (string) - a string to display to the user.
        """
        self.value = value
    def __str__(self):
        """Return oneself as a string (actually, return the contents of
           self.value).
           @return: (string)
        """
        return repr(self.value)

class InvenioWebSubmitFunctionWarning(Exception):
    """This exception should be raised by a WebSubmit function
       when unexpected behaviour is encountered during the execution
       of the function. The unexpected behaviour should not have been
       so serious that execution had to be halted, but since the
       function was unable to perform its task, the event must be
       logged.
       Logging of the exception will be performed by WebSubmit.
    """
    def __init__(self, value):
        """Set the internal "value" attribute to that of the passed "value"
           parameter.
           @param value: (string) - a string to write to the log.
        """
        self.value = value
    def __str__(self):
        """Return oneself as a string (actually, return the contents of
           self.value).
           @return: (string)
        """
        return repr(self.value)
<commit_msg>Added png to the list of recognized extension. Are there any other extensions missing? Do we need this list?<commit_after>## $Id$
##
## This file is part of CDS Invenio.
## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 CERN.
##
## CDS Invenio is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public License as
## published by the Free Software Foundation; either version 2 of the
## License, or (at your option) any later version.
##
## CDS Invenio is distributed in the hope that it will be useful, but
## WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
## General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with CDS Invenio; if not, write to the Free Software Foundation, Inc.,
## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.

"""CDS Invenio Submission Web Interface config file."""

__revision__ = "$Id$"

## test:
test = "FALSE"

## CC all action confirmation mails to administrator? (0 == NO; 1 == YES)
CFG_WEBSUBMIT_COPY_MAILS_TO_ADMIN = 0

## known compressed file extensions:
CFG_COMPRESSED_FILE_EXTENSIONS = ["z", "gz", "tar", "tgz", "tar",
                                  "tar.gz", "zip", "rar", "arj",
                                  "arc", "pak", "lha", "lhz", "sit",
                                  "sea", "sitx", "cpt", "hqx", "uu",
                                  "uue", "bz", "bz2", "bzip", "tbz",
                                  "tbz2", "tar.bz", "tar.bz2"]

CFG_KNOWN_FILE_EXTENSIONS = ["lis",
                 "sxi",
                 "zip",
                 "kpr",
                 "xls",
                 "mov",
                 "avi",
                 "ppt",
                 "prn",
                 "pdf",
                 "tif",
                 "doc",
                 "dot",
                 "ps.gz",
                 "ps.Z",
                 "ps",
                 "eps",
                 "pps",
                 "gif",
                 "jpeg",
                 "jpg",
                 "JPG",
                 "html",
                 "htm",
                 "Download.link",
                 "link",
                 "tex",
                 "txt",
                 "ef.tif",
                 "e.tif",
                 "f.tif",
                 "ef.pdf",
                 "e.pdf",
                 "f.pdf",
                 "ef.ps.gz",
                 "e.ps.gz",
                 "f.ps.gz",
                 "ef.ps",
                 "e.ps",
                 "f.ps",
                 "e.xls",
                 "f.xls",
                 "ef.doc",
                 "e.doc",
                 "f.doc",
                 "ef.html",
                 "e.html",
                 "f.html",
                 "hpg",
                 "mpp",
                 "h",
                 "rtf",
                 "tar",
                 "tar.gz",
                 "tgz",
                 "msg",
                 "llb",
                 "ogg",
                 "mp3",
                 "wav",
                 "mpg",
                 "png"]

class InvenioWebSubmitFunctionError(Exception):
    """This exception should only ever be raised by WebSubmit functions.
       It will be caught and handled by the WebSubmit core itself.
       It is used to signal to WebSubmit core that one of the functions
       encountered a FATAL ERROR situation that should all further execution
       of the submission.
       The exception will carry an error message in its "value" string. This
       message will probably be displayed on the user's browser in an Invenio
       "error" box, and may be logged for the admin to examine.

       Again: If this exception is raised by a WebSubmit function, an error
              message will displayed and the submission ends in failure.

       Extends: Exception.
    """
    def __init__(self, value):
        """Set the internal "value" attribute to that of the passed "value"
           parameter.
           @param value: (string) - an error string to display to the user.
        """
        self.value = value
    def __str__(self):
        """Return oneself as a string (actually, return the contents of
           self.value).
           @return: (string)
        """
        return repr(self.value)

class InvenioWebSubmitFunctionStop(Exception):
    """This exception should only ever be raised by WebSubmit functions.
       It will be caught and handled by the WebSubmit core itself.
       It is used to signal to WebSubmit core that one of the functions
       encountered a situation that should prevent the functions that follow
       it from being executed, and that WebSubmit core should display some sort
       of message to the user. This message will be stored in the "value"
       attribute of the object.

       ***
       NOTE: In the current WebSubmit, this "value" is ususally a JavaScript
             string that redirects the user's browser back to the Web form
             phase of the submission. The use of JavaScript, however is going
             to be removed in the future, so the mechanism may change.
       ***

       Extends: Exception.
    """
    def __init__(self, value):
        """Set the internal "value" attribute to that of the passed "value"
           parameter.
           @param value: (string) - a string to display to the user.
        """
        self.value = value
    def __str__(self):
        """Return oneself as a string (actually, return the contents of
           self.value).
           @return: (string)
        """
        return repr(self.value)

class InvenioWebSubmitFunctionWarning(Exception):
    """This exception should be raised by a WebSubmit function
       when unexpected behaviour is encountered during the execution
       of the function. The unexpected behaviour should not have been
       so serious that execution had to be halted, but since the
       function was unable to perform its task, the event must be
       logged.
       Logging of the exception will be performed by WebSubmit.
    """
    def __init__(self, value):
        """Set the internal "value" attribute to that of the passed "value"
           parameter.
           @param value: (string) - a string to write to the log.
        """
        self.value = value
    def __str__(self):
        """Return oneself as a string (actually, return the contents of
           self.value).
           @return: (string)
        """
        return repr(self.value)
<|endoftext|>


<commit_before>import logging
from urllib2 import urlopen
from lxml import etree
from datetime import datetime, timedelta, date, time
import socket
socket.setdefaulttimeout(5)

from django.conf import settings
from django.utils.translation import ugettext_lazy as _

from molly.conf.provider import Provider, task
from molly.apps.weather.models import (
    Weather, PTYPE_OBSERVATION, PTYPE_FORECAST)

logger = logging.getLogger(__name__)

METOFFICE_OUTLOOK_CHOICES = (
    ('NA', 'unk'),
    (0, 'cs'),
    (1, 's'),
    (2, 'pc'),     # night
    (3, 'si'),
    (4, 'unk'),    # DUST ??
    (5, 'm'),
    (6, 'f'),
    (7, 'gc'),     # Medium-level cloud
    (8, 'gc'),     # Low-level cloud
    (9, 'lrs'),    # night
    (10, 'lrs'),
    (11, 'd'),
    (12, 'lr'),
    (13, 'hr'),   # Heavy rain shower (night)??
    (14, 'hr'),   # Heavy rain shower (day)??
    (15, 'hr'),
    (16, 'hr'),   # Sleet shower (night)??
    (17, 'hr'),   # Sleet shower (day)??
    (18, 'hr'),   # Sleet??
    (19, 'h'),   # Hail shower (night)
    (20, 'h'),   # Hail shower (day)
    (21, 'h'),   # Hail
    (22, 'lsn'),   # Light snow shower (night)
    (23, 'lsn'),   # Light snow shower (day)
    (24, 'lsn'),   # Light snow
    (25, 'hsn'),   # Heavy snow shower (night)
    (26, 'hsn'),   # Heavy snow shower (day)
    (27, 'hsn'),   # Heavy snow
    (28, 'tsh'),   # Thundery shower (night)
    (29, 'tsh'),   # Thundery shower (day)
    (30, 'tst'),   # Thunder storm
    (31, 'tst'),   # Tropical storm
    (32, 'unk'),   # NOT USED?
    (33, 'h'),   # Haze
    )

BASE_METOFFICE_URL = "http://partner.metoffice.gov.uk/public/val"

class MetOfficeProvider(Provider):
    """
    Scrapes MetOffice DataPoint / observations API
    Documentation is available at: http://www.metoffice.gov.uk/public/ddc/datasets-documentation.html#DailyForecast
    TODO: this class should be splitted in two (observations and forecasts).
    """

    ATTRIBUTION = {
        'title': _('MetOffice'),
        'url': _('http://www.metoffice.gov.uk/')
    }

    FRESHNESS = timedelta(hours=3)

    def __init__(self, forecasts_location_id, observations_location_id):
        self.forecasts_location_id = forecasts_location_id
        self.observations_location_id = observations_location_id

    def fetch_observation(self):
        return Weather.objects.get(location_id=self.observations_location_id,
            ptype=PTYPE_OBSERVATION)

    def fetch_forecasts(self):
        return Weather.objects.filter(
            location_id=self.forecasts_location_id, ptype=PTYPE_FORECAST,
            observed_date__gte=datetime.now().date()
        ).order_by('observed_date')

    @task(run_every=timedelta(minutes=15), default_retry_delay=2, max_retries=3)
    def import_forecasts(self, **metadata):
        api = ApiWrapper()
        forecasts, location = api.get_daily_forecasts_by_location(self.forecasts_location_id)
        for fc in forecasts:
            f = forecasts[fc]
            forecast, created = Weather.objects.get_or_create(
                    location_id=self.forecasts_location_id,
                    observed_date=fc,
                    ptype=PTYPE_FORECAST)
            forecast.name = location['name']
            forecast.min_temperature = float(f['Night']['Nm'])
            forecast.max_temperature = float(f['Day']['Dm'])
            outlooks = dict(METOFFICE_OUTLOOK_CHOICES)
            forecast.outlook = outlooks[int(f['Day']['W'])]
            forecast.observed_date = datetime.combine(fc, time(hour=0))
            forecast.save()

    @task(run_every=timedelta(minutes=15), default_retry_delay=2, max_retries=3)
    def import_observation(self, **metadata):
        api = ApiWrapper()
        observations, location = api.get_observations_by_location(self.observations_location_id)
        latest_day = sorted(observations)[-1]
        latest_hour = sorted(observations[latest_day], key=lambda x: int(x))[-1]
        latest = observations[latest_day][latest_hour]
        observation, created = Weather.objects.get_or_create(
            location_id=self.observations_location_id,
            ptype=PTYPE_OBSERVATION)
        observation.name = location['name']
        observation.temperature = float(latest['T'])
        observation.wind_speed = int(latest['S'])
        observation.wind_direction = latest['D']
        observation.pressure = int(latest['P'])
        observation.observed_date = datetime.combine(latest_day,
                time(int(latest_hour)/60))
        outlooks = dict(METOFFICE_OUTLOOK_CHOICES)
        observation.outlook = outlooks[int(latest['W'])]
        #observation.humidity = not available
        #observation.pressure_state = not available
        observation.save()

    @task(run_every=timedelta(hours=1))
    def delete_old_forecasts(self, **metadata):
        delete_until = datetime.now() - timedelta(days=2)
        logger.debug('Deleting old weather objects until %s' % delete_until)
        Weather.objects.filter(observed_date__lte=delete_until).delete()

class ApiWrapper(object):
    """
    Scrape the XML API
    """

    FORECAST_FRAGMENT_URL = '/wxfcs/all/xml'

    OBSERVATIONS_FRAGMENT_URL = '/wxobs/all/xml'

    def get_daily_forecasts_by_location(self, location_id):
        url = '{0}{1}/{2}?res=daily&key={3}'.format(BASE_METOFFICE_URL,
                self.FORECAST_FRAGMENT_URL, location_id, settings.API_KEYS['metoffice'])
        logger.debug('MetOffice: %s' % url)
        content = urlopen(url).read()
        return self.scrape_xml(content)

    def get_observations_by_location(self, location_id):
        url = '{0}{1}/{2}?res=hourly&key={3}'.format(
            BASE_METOFFICE_URL,
            self.OBSERVATIONS_FRAGMENT_URL,
            location_id,
            settings.API_KEYS['metoffice']
        )
        logger.debug('MetOffice: %s' % url)
        content = urlopen(url).read()
        return self.scrape_xml(content)

    def scrape_xml(self, content):
        """
        Scrape XML from MetOffice DataPoint API.
        Can be used to parse Forecasts/Daily, Forecasts/3hourly, Observations
        Returns a list of representations, information about the location
        """
        xml = etree.fromstring(content)
        location = xml.find('.//Location')
        l = {}
        for k in location.attrib:
            l[k] = location.attrib[k]
        periods = xml.findall('.//Period')
        p = {}
        for period in periods:
            date_val = period.get('val')
            date_parsed = date(year=int(date_val[0:4]),
                month=int(date_val[5:7]), day=int(date_val[8:10]))
            p[date_parsed] = {}
            reps = period.findall('.//Rep')
            for rep in reps:
                # rep.txt represents the number of minutes since midnight
                p[date_parsed][rep.text] = {}
                # set of attributes depends on type forecasts vs. observations,
                # but also if it's a forecast for the day or night (e.g. min temperature is
                # only available for a night forecast...
                for k in rep.attrib:
                    p[date_parsed][rep.text][k] = rep.attrib[k]
        return p, l
<commit_msg>Updated the metoffice API endpoint.<commit_after>import logging
from urllib2 import urlopen
from lxml import etree
from datetime import datetime, timedelta, date, time
import socket
socket.setdefaulttimeout(5)

from django.conf import settings
from django.utils.translation import ugettext_lazy as _

from molly.conf.provider import Provider, task
from molly.apps.weather.models import (
    Weather, PTYPE_OBSERVATION, PTYPE_FORECAST)

logger = logging.getLogger(__name__)

METOFFICE_OUTLOOK_CHOICES = (
    ('NA', 'unk'),
    (0, 'cs'),
    (1, 's'),
    (2, 'pc'),     # night
    (3, 'si'),
    (4, 'unk'),    # DUST ??
    (5, 'm'),
    (6, 'f'),
    (7, 'gc'),     # Medium-level cloud
    (8, 'gc'),     # Low-level cloud
    (9, 'lrs'),    # night
    (10, 'lrs'),
    (11, 'd'),
    (12, 'lr'),
    (13, 'hr'),   # Heavy rain shower (night)??
    (14, 'hr'),   # Heavy rain shower (day)??
    (15, 'hr'),
    (16, 'hr'),   # Sleet shower (night)??
    (17, 'hr'),   # Sleet shower (day)??
    (18, 'hr'),   # Sleet??
    (19, 'h'),   # Hail shower (night)
    (20, 'h'),   # Hail shower (day)
    (21, 'h'),   # Hail
    (22, 'lsn'),   # Light snow shower (night)
    (23, 'lsn'),   # Light snow shower (day)
    (24, 'lsn'),   # Light snow
    (25, 'hsn'),   # Heavy snow shower (night)
    (26, 'hsn'),   # Heavy snow shower (day)
    (27, 'hsn'),   # Heavy snow
    (28, 'tsh'),   # Thundery shower (night)
    (29, 'tsh'),   # Thundery shower (day)
    (30, 'tst'),   # Thunder storm
    (31, 'tst'),   # Tropical storm
    (32, 'unk'),   # NOT USED?
    (33, 'h'),   # Haze
    )

BASE_METOFFICE_URL = "http://datapoint.metoffice.gov.uk/public/data/val"

class MetOfficeProvider(Provider):
    """
    Scrapes MetOffice DataPoint / observations API
    Documentation is available at: http://www.metoffice.gov.uk/public/ddc/datasets-documentation.html#DailyForecast
    TODO: this class should be splitted in two (observations and forecasts).
    """

    ATTRIBUTION = {
        'title': _('MetOffice'),
        'url': _('http://www.metoffice.gov.uk/')
    }

    FRESHNESS = timedelta(hours=3)

    def __init__(self, forecasts_location_id, observations_location_id):
        self.forecasts_location_id = forecasts_location_id
        self.observations_location_id = observations_location_id

    def fetch_observation(self):
        return Weather.objects.get(location_id=self.observations_location_id,
            ptype=PTYPE_OBSERVATION)

    def fetch_forecasts(self):
        return Weather.objects.filter(
            location_id=self.forecasts_location_id, ptype=PTYPE_FORECAST,
            observed_date__gte=datetime.now().date()
        ).order_by('observed_date')

    @task(run_every=timedelta(minutes=15), default_retry_delay=2, max_retries=3)
    def import_forecasts(self, **metadata):
        api = ApiWrapper()
        forecasts, location = api.get_daily_forecasts_by_location(self.forecasts_location_id)
        for fc in forecasts:
            f = forecasts[fc]
            forecast, created = Weather.objects.get_or_create(
                    location_id=self.forecasts_location_id,
                    observed_date=fc,
                    ptype=PTYPE_FORECAST)
            forecast.name = location['name']
            forecast.min_temperature = float(f['Night']['Nm'])
            forecast.max_temperature = float(f['Day']['Dm'])
            outlooks = dict(METOFFICE_OUTLOOK_CHOICES)
            forecast.outlook = outlooks[int(f['Day']['W'])]
            forecast.observed_date = datetime.combine(fc, time(hour=0))
            forecast.save()

    @task(run_every=timedelta(minutes=15), default_retry_delay=2, max_retries=3)
    def import_observation(self, **metadata):
        api = ApiWrapper()
        observations, location = api.get_observations_by_location(self.observations_location_id)
        latest_day = sorted(observations)[-1]
        latest_hour = sorted(observations[latest_day], key=lambda x: int(x))[-1]
        latest = observations[latest_day][latest_hour]
        observation, created = Weather.objects.get_or_create(
            location_id=self.observations_location_id,
            ptype=PTYPE_OBSERVATION)
        observation.name = location['name']
        observation.temperature = float(latest['T'])
        observation.wind_speed = int(latest['S'])
        observation.wind_direction = latest['D']
        observation.pressure = int(latest['P'])
        observation.observed_date = datetime.combine(latest_day,
                time(int(latest_hour)/60))
        outlooks = dict(METOFFICE_OUTLOOK_CHOICES)
        observation.outlook = outlooks[int(latest['W'])]
        #observation.humidity = not available
        #observation.pressure_state = not available
        observation.save()

    @task(run_every=timedelta(hours=1))
    def delete_old_forecasts(self, **metadata):
        delete_until = datetime.now() - timedelta(days=2)
        logger.debug('Deleting old weather objects until %s' % delete_until)
        Weather.objects.filter(observed_date__lte=delete_until).delete()

class ApiWrapper(object):
    """
    Scrape the XML API
    """

    FORECAST_FRAGMENT_URL = '/wxfcs/all/xml'

    OBSERVATIONS_FRAGMENT_URL = '/wxobs/all/xml'

    def get_daily_forecasts_by_location(self, location_id):
        url = '{0}{1}/{2}?res=daily&key={3}'.format(BASE_METOFFICE_URL,
                self.FORECAST_FRAGMENT_URL, location_id, settings.API_KEYS['metoffice'])
        logger.debug('MetOffice: %s' % url)
        content = urlopen(url).read()
        return self.scrape_xml(content)

    def get_observations_by_location(self, location_id):
        url = '{0}{1}/{2}?res=hourly&key={3}'.format(
            BASE_METOFFICE_URL,
            self.OBSERVATIONS_FRAGMENT_URL,
            location_id,
            settings.API_KEYS['metoffice']
        )
        logger.debug('MetOffice: %s' % url)
        content = urlopen(url).read()
        return self.scrape_xml(content)

    def scrape_xml(self, content):
        """
        Scrape XML from MetOffice DataPoint API.
        Can be used to parse Forecasts/Daily, Forecasts/3hourly, Observations
        Returns a list of representations, information about the location
        """
        xml = etree.fromstring(content)
        location = xml.find('.//Location')
        l = {}
        for k in location.attrib:
            l[k] = location.attrib[k]
        periods = xml.findall('.//Period')
        p = {}
        for period in periods:
            date_val = period.get('val')
            date_parsed = date(year=int(date_val[0:4]),
                month=int(date_val[5:7]), day=int(date_val[8:10]))
            p[date_parsed] = {}
            reps = period.findall('.//Rep')
            for rep in reps:
                # rep.txt represents the number of minutes since midnight
                p[date_parsed][rep.text] = {}
                # set of attributes depends on type forecasts vs. observations,
                # but also if it's a forecast for the day or night (e.g. min temperature is
                # only available for a night forecast...
                for k in rep.attrib:
                    p[date_parsed][rep.text][k] = rep.attrib[k]
        return p, l
<|endoftext|>


<commit_before>from __future__ import unicode_literals

import functools
import itertools

from mopidy.models import Track
from mopidy.frontends.mpd import translator
from mopidy.frontends.mpd.exceptions import MpdNotImplemented
from mopidy.frontends.mpd.protocol import handle_request, stored_playlists

QUERY_RE = (
    r'(?P<mpd_query>("?([Aa]lbum|[Aa]rtist|[Dd]ate|[Ff]ile|[Ff]ilename|'
    r'[Tt]itle|[Tt]rack|[Aa]ny)"? "[^"]*"\s?)+)$')

def _get_field(field, search_results):
    return list(itertools.chain(*[getattr(r, field) for r in search_results]))

_get_albums = functools.partial(_get_field, 'albums')
_get_artists = functools.partial(_get_field, 'artists')
_get_tracks = functools.partial(_get_field, 'tracks')

def _album_as_track(album):
    return Track(
        uri=album.uri,
        name='Album: ' + album.name,
        artists=album.artists,
        album=album,
        date=album.date)

def _artist_as_track(artist):
    return Track(
        uri=artist.uri,
        name='Artist: ' + artist.name,
        artists=[artist])

@handle_request(r'^count ' + QUERY_RE)
def count(context, mpd_query):
    """
    *musicpd.org, music database section:*

        ``count {TAG} {NEEDLE}``

        Counts the number of songs and their total playtime in the db
        matching ``TAG`` exactly.

    *GMPC:*

    - does not add quotes around the tag argument.
    - use multiple tag-needle pairs to make more specific searches.
    """
    try:
        query = translator.query_from_mpd_search_format(mpd_query)
    except ValueError:
        return
    results = context.core.library.find_exact(**query).get()
    result_tracks = _get_tracks(results)
    return [('songs', len(result_tracks)),
            ('playtime', sum(track.length for track in result_tracks) / 1000)]

@handle_request(r'^find ' + QUERY_RE)
def find(context, mpd_query):
    """
    *musicpd.org, music database section:*

        ``find {TYPE} {WHAT}``

        Finds songs in the db that are exactly ``WHAT``. ``TYPE`` can be any
        tag supported by MPD, or one of the two special parameters - ``file``
        to search by full path (relative to database root), and ``any`` to
        match against all available tags. ``WHAT`` is what to find.

    *GMPC:*

    - does not add quotes around the field argument.
    - also uses ``find album "[ALBUM]" artist "[ARTIST]"`` to list album
      tracks.

    *ncmpc:*

    - does not add quotes around the field argument.
    - capitalizes the type argument.

    *ncmpcpp:*

    - also uses the search type "date".
    - uses "file" instead of "filename".
    """
    try:
        query = translator.query_from_mpd_search_format(mpd_query)
    except ValueError:
        return
    results = context.core.library.find_exact(**query).get()
    result_tracks = []
    if 'artist' not in query:
        result_tracks += [_artist_as_track(a) for a in _get_artists(results)]
    if 'album' not in query:
        result_tracks += [_album_as_track(a) for a in _get_albums(results)]
    result_tracks += _get_tracks(results)
    return translator.tracks_to_mpd_format(result_tracks)

@handle_request(r'^findadd ' + QUERY_RE)
def findadd(context, mpd_query):
    """
    *musicpd.org, music database section:*

        ``findadd {TYPE} {WHAT}``

        Finds songs in the db that are exactly ``WHAT`` and adds them to
        current playlist. Parameters have the same meaning as for ``find``.
    """
    try:
        query = translator.query_from_mpd_search_format(mpd_query)
    except ValueError:
        return
    results = context.core.library.find_exact(**query).get()
    context.core.tracklist.add(_get_tracks(results))

@handle_request(
    r'^list "?(?P<field>([Aa]rtist|[Aa]lbum|[Dd]ate|[Gg]enre))"?'
    r'( (?P<mpd_query>.*))?$')
def list_(context, field, mpd_query=None):
    """
    *musicpd.org, music database section:*

        ``list {TYPE} [ARTIST]``

        Lists all tags of the specified type. ``TYPE`` should be ``album``,
        ``artist``, ``date``, or ``genre``.

        ``ARTIST`` is an optional parameter when type is ``album``,
        ``date``, or ``genre``. This filters the result list by an artist.

    *Clarifications:*

        The musicpd.org documentation for ``list`` is far from complete. The
        command also supports the following variant:

        ``list {TYPE} {QUERY}``

        Where ``QUERY`` applies to all ``TYPE``. ``QUERY`` is one or more pairs
        of a field name and a value. If the ``QUERY`` consists of more than one
        pair, the pairs are AND-ed together to find the result. Examples of
        valid queries and what they should return:

        ``list "artist" "artist" "ABBA"``
            List artists where the artist name is "ABBA". Response::

                Artist: ABBA
                OK

        ``list "album" "artist" "ABBA"``
            Lists albums where the artist name is "ABBA". Response::

                Album: More ABBA Gold: More ABBA Hits
                Album: Absolute More Christmas
                Album: Gold: Greatest Hits
                OK

        ``list "artist" "album" "Gold: Greatest Hits"``
            Lists artists where the album name is "Gold: Greatest Hits".
            Response::

                Artist: ABBA
                OK

        ``list "artist" "artist" "ABBA" "artist" "TLC"``
            Lists artists where the artist name is "ABBA" *and* "TLC". Should
            never match anything. Response::

                OK

        ``list "date" "artist" "ABBA"``
            Lists dates where artist name is "ABBA". Response::

                Date:
                Date: 1992
                Date: 1993
                OK

        ``list "date" "artist" "ABBA" "album" "Gold: Greatest Hits"``
            Lists dates where artist name is "ABBA" and album name is "Gold:
            Greatest Hits". Response::

                Date: 1992
                OK

        ``list "genre" "artist" "The Rolling Stones"``
            Lists genres where artist name is "The Rolling Stones". Response::

                Genre:
                Genre: Rock
                OK

    *GMPC:*

    - does not add quotes around the field argument.

    *ncmpc:*

    - does not add quotes around the field argument.
    - capitalizes the field argument.
    """
    field = field.lower()
    try:
        query = translator.query_from_mpd_list_format(field, mpd_query)
    except ValueError:
        return
    if field == 'artist':
        return _list_artist(context, query)
    elif field == 'album':
        return _list_album(context, query)
    elif field == 'date':
        return _list_date(context, query)
    elif field == 'genre':
        pass  # TODO We don't have genre in our internal data structures yet

def _list_artist(context, query):
    artists = set()
    results = context.core.library.find_exact(**query).get()
    for track in _get_tracks(results):
        for artist in track.artists:
            if artist.name:
                artists.add(('Artist', artist.name))
    return artists

def _list_album(context, query):
    albums = set()
    results = context.core.library.find_exact(**query).get()
    for track in _get_tracks(results):
        if track.album and track.album.name:
            albums.add(('Album', track.album.name))
    return albums

def _list_date(context, query):
    dates = set()
    results = context.core.library.find_exact(**query).get()
    for track in _get_tracks(results):
        if track.date:
            dates.add(('Date', track.date))
    return dates

@handle_request(r'^listall$')
@handle_request(r'^listall "(?P<uri>[^"]+)"$')
def listall(context, uri=None):
    """
    *musicpd.org, music database section:*

        ``listall [URI]``

        Lists all songs and directories in ``URI``.
    """
    raise MpdNotImplemented  # TODO

@handle_request(r'^listallinfo$')
@handle_request(r'^listallinfo "(?P<uri>[^"]+)"$')
def listallinfo(context, uri=None):
    """
    *musicpd.org, music database section:*

        ``listallinfo [URI]``

        Same as ``listall``, except it also returns metadata info in the
        same format as ``lsinfo``.
    """
    raise MpdNotImplemented  # TODO

@handle_request(r'^lsinfo$')
@handle_request(r'^lsinfo "(?P<uri>[^"]*)"$')
def lsinfo(context, uri=None):
    """
    *musicpd.org, music database section:*

        ``lsinfo [URI]``

        Lists the contents of the directory ``URI``.

        When listing the root directory, this currently returns the list of
        stored playlists. This behavior is deprecated; use
        ``listplaylists`` instead.

    MPD returns the same result, including both playlists and the files and
    directories located at the root level, for both ``lsinfo``, ``lsinfo
    ""``, and ``lsinfo "/"``.
    """
    if uri is None or uri == '/' or uri == '':
        return stored_playlists.listplaylists(context)
    raise MpdNotImplemented  # TODO

@handle_request(r'^rescan( "(?P<uri>[^"]+)")*$')
def rescan(context, uri=None):
    """
    *musicpd.org, music database section:*

        ``rescan [URI]``

        Same as ``update``, but also rescans unmodified files.
    """
    return update(context, uri, rescan_unmodified_files=True)

@handle_request(r'^search ' + QUERY_RE)
def search(context, mpd_query):
    """
    *musicpd.org, music database section:*

        ``search {TYPE} {WHAT} [...]``

        Searches for any song that contains ``WHAT``. Parameters have the same
        meaning as for ``find``, except that search is not case sensitive.

    *GMPC:*

    - does not add quotes around the field argument.
    - uses the undocumented field ``any``.
    - searches for multiple words like this::

        search any "foo" any "bar" any "baz"

    *ncmpc:*

    - does not add quotes around the field argument.
    - capitalizes the field argument.

    *ncmpcpp:*

    - also uses the search type "date".
    - uses "file" instead of "filename".
    """
    try:
        query = translator.query_from_mpd_search_format(mpd_query)
    except ValueError:
        return
    results = context.core.library.search(**query).get()
    artists = [_artist_as_track(a) for a in _get_artists(results)]
    albums = [_album_as_track(a) for a in _get_albums(results)]
    tracks = _get_tracks(results)
    return translator.tracks_to_mpd_format(artists + albums + tracks)

@handle_request(r'^searchadd ' + QUERY_RE)
def searchadd(context, mpd_query):
    """
    *musicpd.org, music database section:*

        ``searchadd {TYPE} {WHAT} [...]``

        Searches for any song that contains ``WHAT`` in tag ``TYPE`` and adds
        them to current playlist.

        Parameters have the same meaning as for ``find``, except that search is
        not case sensitive.
    """
    try:
        query = translator.query_from_mpd_search_format(mpd_query)
    except ValueError:
        return
    results = context.core.library.search(**query).get()
    context.core.tracklist.add(_get_tracks(results))

@handle_request(r'^searchaddpl "(?P<playlist_name>[^"]+)" ' + QUERY_RE)
def searchaddpl(context, playlist_name, mpd_query):
    """
    *musicpd.org, music database section:*

        ``searchaddpl {NAME} {TYPE} {WHAT} [...]``

        Searches for any song that contains ``WHAT`` in tag ``TYPE`` and adds
        them to the playlist named ``NAME``.

        If a playlist by that name doesn't exist it is created.

        Parameters have the same meaning as for ``find``, except that search is
        not case sensitive.
    """
    try:
        query = translator.query_from_mpd_search_format(mpd_query)
    except ValueError:
        return
    results = context.core.library.search(**query).get()

    playlist = context.lookup_playlist_from_name(playlist_name)
    if not playlist:
        playlist = context.core.playlists.create(playlist_name).get()
    tracks = list(playlist.tracks) + _get_tracks(results)
    playlist = playlist.copy(tracks=tracks)
    context.core.playlists.save(playlist)

@handle_request(r'^update( "(?P<uri>[^"]+)")*$')
def update(context, uri=None, rescan_unmodified_files=False):
    """
    *musicpd.org, music database section:*

        ``update [URI]``

        Updates the music database: find new files, remove deleted files,
        update modified files.

        ``URI`` is a particular directory or song/file to update. If you do
        not specify it, everything is updated.

        Prints ``updating_db: JOBID`` where ``JOBID`` is a positive number
        identifying the update job. You can read the current job id in the
        ``status`` response.
    """
    return {'updating_db': 0}  # TODO
<commit_msg>Return MpdArgError if count parsing fails<commit_after>from __future__ import unicode_literals

import functools
import itertools

from mopidy.models import Track
from mopidy.frontends.mpd import translator
from mopidy.frontends.mpd.exceptions import MpdNotImplemented, MpdArgError
from mopidy.frontends.mpd.protocol import handle_request, stored_playlists

QUERY_RE = (
    r'(?P<mpd_query>("?([Aa]lbum|[Aa]rtist|[Dd]ate|[Ff]ile|[Ff]ilename|'
    r'[Tt]itle|[Tt]rack|[Aa]ny)"? "[^"]*"\s?)+)$')

def _get_field(field, search_results):
    return list(itertools.chain(*[getattr(r, field) for r in search_results]))

_get_albums = functools.partial(_get_field, 'albums')
_get_artists = functools.partial(_get_field, 'artists')
_get_tracks = functools.partial(_get_field, 'tracks')

def _album_as_track(album):
    return Track(
        uri=album.uri,
        name='Album: ' + album.name,
        artists=album.artists,
        album=album,
        date=album.date)

def _artist_as_track(artist):
    return Track(
        uri=artist.uri,
        name='Artist: ' + artist.name,
        artists=[artist])

@handle_request(r'^count ' + QUERY_RE)
def count(context, mpd_query):
    """
    *musicpd.org, music database section:*

        ``count {TAG} {NEEDLE}``

        Counts the number of songs and their total playtime in the db
        matching ``TAG`` exactly.

    *GMPC:*

    - does not add quotes around the tag argument.
    - use multiple tag-needle pairs to make more specific searches.
    """
    try:
        query = translator.query_from_mpd_search_format(mpd_query)
    except ValueError:
        raise MpdArgError('incorrect arguments', command='count')
    results = context.core.library.find_exact(**query).get()
    result_tracks = _get_tracks(results)
    return [('songs', len(result_tracks)),
            ('playtime', sum(track.length for track in result_tracks) / 1000)]

@handle_request(r'^find ' + QUERY_RE)
def find(context, mpd_query):
    """
    *musicpd.org, music database section:*

        ``find {TYPE} {WHAT}``

        Finds songs in the db that are exactly ``WHAT``. ``TYPE`` can be any
        tag supported by MPD, or one of the two special parameters - ``file``
        to search by full path (relative to database root), and ``any`` to
        match against all available tags. ``WHAT`` is what to find.

    *GMPC:*

    - does not add quotes around the field argument.
    - also uses ``find album "[ALBUM]" artist "[ARTIST]"`` to list album
      tracks.

    *ncmpc:*

    - does not add quotes around the field argument.
    - capitalizes the type argument.

    *ncmpcpp:*

    - also uses the search type "date".
    - uses "file" instead of "filename".
    """
    try:
        query = translator.query_from_mpd_search_format(mpd_query)
    except ValueError:
        return
    results = context.core.library.find_exact(**query).get()
    result_tracks = []
    if 'artist' not in query:
        result_tracks += [_artist_as_track(a) for a in _get_artists(results)]
    if 'album' not in query:
        result_tracks += [_album_as_track(a) for a in _get_albums(results)]
    result_tracks += _get_tracks(results)
    return translator.tracks_to_mpd_format(result_tracks)

@handle_request(r'^findadd ' + QUERY_RE)
def findadd(context, mpd_query):
    """
    *musicpd.org, music database section:*

        ``findadd {TYPE} {WHAT}``

        Finds songs in the db that are exactly ``WHAT`` and adds them to
        current playlist. Parameters have the same meaning as for ``find``.
    """
    try:
        query = translator.query_from_mpd_search_format(mpd_query)
    except ValueError:
        return
    results = context.core.library.find_exact(**query).get()
    context.core.tracklist.add(_get_tracks(results))

@handle_request(
    r'^list "?(?P<field>([Aa]rtist|[Aa]lbum|[Dd]ate|[Gg]enre))"?'
    r'( (?P<mpd_query>.*))?$')
def list_(context, field, mpd_query=None):
    """
    *musicpd.org, music database section:*

        ``list {TYPE} [ARTIST]``

        Lists all tags of the specified type. ``TYPE`` should be ``album``,
        ``artist``, ``date``, or ``genre``.

        ``ARTIST`` is an optional parameter when type is ``album``,
        ``date``, or ``genre``. This filters the result list by an artist.

    *Clarifications:*

        The musicpd.org documentation for ``list`` is far from complete. The
        command also supports the following variant:

        ``list {TYPE} {QUERY}``

        Where ``QUERY`` applies to all ``TYPE``. ``QUERY`` is one or more pairs
        of a field name and a value. If the ``QUERY`` consists of more than one
        pair, the pairs are AND-ed together to find the result. Examples of
        valid queries and what they should return:

        ``list "artist" "artist" "ABBA"``
            List artists where the artist name is "ABBA". Response::

                Artist: ABBA
                OK

        ``list "album" "artist" "ABBA"``
            Lists albums where the artist name is "ABBA". Response::

                Album: More ABBA Gold: More ABBA Hits
                Album: Absolute More Christmas
                Album: Gold: Greatest Hits
                OK

        ``list "artist" "album" "Gold: Greatest Hits"``
            Lists artists where the album name is "Gold: Greatest Hits".
            Response::

                Artist: ABBA
                OK

        ``list "artist" "artist" "ABBA" "artist" "TLC"``
            Lists artists where the artist name is "ABBA" *and* "TLC". Should
            never match anything. Response::

                OK

        ``list "date" "artist" "ABBA"``
            Lists dates where artist name is "ABBA". Response::

                Date:
                Date: 1992
                Date: 1993
                OK

        ``list "date" "artist" "ABBA" "album" "Gold: Greatest Hits"``
            Lists dates where artist name is "ABBA" and album name is "Gold:
            Greatest Hits". Response::

                Date: 1992
                OK

        ``list "genre" "artist" "The Rolling Stones"``
            Lists genres where artist name is "The Rolling Stones". Response::

                Genre:
                Genre: Rock
                OK

    *GMPC:*

    - does not add quotes around the field argument.

    *ncmpc:*

    - does not add quotes around the field argument.
    - capitalizes the field argument.
    """
    field = field.lower()
    try:
        query = translator.query_from_mpd_list_format(field, mpd_query)
    except ValueError:
        return
    if field == 'artist':
        return _list_artist(context, query)
    elif field == 'album':
        return _list_album(context, query)
    elif field == 'date':
        return _list_date(context, query)
    elif field == 'genre':
        pass  # TODO We don't have genre in our internal data structures yet

def _list_artist(context, query):
    artists = set()
    results = context.core.library.find_exact(**query).get()
    for track in _get_tracks(results):
        for artist in track.artists:
            if artist.name:
                artists.add(('Artist', artist.name))
    return artists

def _list_album(context, query):
    albums = set()
    results = context.core.library.find_exact(**query).get()
    for track in _get_tracks(results):
        if track.album and track.album.name:
            albums.add(('Album', track.album.name))
    return albums

def _list_date(context, query):
    dates = set()
    results = context.core.library.find_exact(**query).get()
    for track in _get_tracks(results):
        if track.date:
            dates.add(('Date', track.date))
    return dates

@handle_request(r'^listall$')
@handle_request(r'^listall "(?P<uri>[^"]+)"$')
def listall(context, uri=None):
    """
    *musicpd.org, music database section:*

        ``listall [URI]``

        Lists all songs and directories in ``URI``.
    """
    raise MpdNotImplemented  # TODO

@handle_request(r'^listallinfo$')
@handle_request(r'^listallinfo "(?P<uri>[^"]+)"$')
def listallinfo(context, uri=None):
    """
    *musicpd.org, music database section:*

        ``listallinfo [URI]``

        Same as ``listall``, except it also returns metadata info in the
        same format as ``lsinfo``.
    """
    raise MpdNotImplemented  # TODO

@handle_request(r'^lsinfo$')
@handle_request(r'^lsinfo "(?P<uri>[^"]*)"$')
def lsinfo(context, uri=None):
    """
    *musicpd.org, music database section:*

        ``lsinfo [URI]``

        Lists the contents of the directory ``URI``.

        When listing the root directory, this currently returns the list of
        stored playlists. This behavior is deprecated; use
        ``listplaylists`` instead.

    MPD returns the same result, including both playlists and the files and
    directories located at the root level, for both ``lsinfo``, ``lsinfo
    ""``, and ``lsinfo "/"``.
    """
    if uri is None or uri == '/' or uri == '':
        return stored_playlists.listplaylists(context)
    raise MpdNotImplemented  # TODO

@handle_request(r'^rescan( "(?P<uri>[^"]+)")*$')
def rescan(context, uri=None):
    """
    *musicpd.org, music database section:*

        ``rescan [URI]``

        Same as ``update``, but also rescans unmodified files.
    """
    return update(context, uri, rescan_unmodified_files=True)

@handle_request(r'^search ' + QUERY_RE)
def search(context, mpd_query):
    """
    *musicpd.org, music database section:*

        ``search {TYPE} {WHAT} [...]``

        Searches for any song that contains ``WHAT``. Parameters have the same
        meaning as for ``find``, except that search is not case sensitive.

    *GMPC:*

    - does not add quotes around the field argument.
    - uses the undocumented field ``any``.
    - searches for multiple words like this::

        search any "foo" any "bar" any "baz"

    *ncmpc:*

    - does not add quotes around the field argument.
    - capitalizes the field argument.

    *ncmpcpp:*

    - also uses the search type "date".
    - uses "file" instead of "filename".
    """
    try:
        query = translator.query_from_mpd_search_format(mpd_query)
    except ValueError:
        return
    results = context.core.library.search(**query).get()
    artists = [_artist_as_track(a) for a in _get_artists(results)]
    albums = [_album_as_track(a) for a in _get_albums(results)]
    tracks = _get_tracks(results)
    return translator.tracks_to_mpd_format(artists + albums + tracks)

@handle_request(r'^searchadd ' + QUERY_RE)
def searchadd(context, mpd_query):
    """
    *musicpd.org, music database section:*

        ``searchadd {TYPE} {WHAT} [...]``

        Searches for any song that contains ``WHAT`` in tag ``TYPE`` and adds
        them to current playlist.

        Parameters have the same meaning as for ``find``, except that search is
        not case sensitive.
    """
    try:
        query = translator.query_from_mpd_search_format(mpd_query)
    except ValueError:
        return
    results = context.core.library.search(**query).get()
    context.core.tracklist.add(_get_tracks(results))

@handle_request(r'^searchaddpl "(?P<playlist_name>[^"]+)" ' + QUERY_RE)
def searchaddpl(context, playlist_name, mpd_query):
    """
    *musicpd.org, music database section:*

        ``searchaddpl {NAME} {TYPE} {WHAT} [...]``

        Searches for any song that contains ``WHAT`` in tag ``TYPE`` and adds
        them to the playlist named ``NAME``.

        If a playlist by that name doesn't exist it is created.

        Parameters have the same meaning as for ``find``, except that search is
        not case sensitive.
    """
    try:
        query = translator.query_from_mpd_search_format(mpd_query)
    except ValueError:
        return
    results = context.core.library.search(**query).get()

    playlist = context.lookup_playlist_from_name(playlist_name)
    if not playlist:
        playlist = context.core.playlists.create(playlist_name).get()
    tracks = list(playlist.tracks) + _get_tracks(results)
    playlist = playlist.copy(tracks=tracks)
    context.core.playlists.save(playlist)

@handle_request(r'^update( "(?P<uri>[^"]+)")*$')
def update(context, uri=None, rescan_unmodified_files=False):
    """
    *musicpd.org, music database section:*

        ``update [URI]``

        Updates the music database: find new files, remove deleted files,
        update modified files.

        ``URI`` is a particular directory or song/file to update. If you do
        not specify it, everything is updated.

        Prints ``updating_db: JOBID`` where ``JOBID`` is a positive number
        identifying the update job. You can read the current job id in the
        ``status`` response.
    """
    return {'updating_db': 0}  # TODO
<|endoftext|>