<commit_before>from corehq.apps.commtrack.const import RequisitionActions
from corehq.apps.domain.models import Domain
from casexml.apps.case.models import CommCareCase
from corehq.apps.locations.models import Location
from corehq.apps.commtrack import stockreport, const
from corehq.apps.sms.api import send_sms_to_verified_number
from lxml import etree
import logging
from dimagi.utils.couch.loosechange import map_reduce
from dimagi.utils.parsing import json_format_datetime
from datetime import datetime
from helpers import make_supply_point_product
from corehq.apps.commtrack.util import get_supply_point
from corehq.apps.commtrack.models import Product, CommtrackConfig

logger = logging.getLogger('commtrack.sms')

def handle(verified_contact, text):
    """top-level handler for incoming stock report messages"""
    domain = Domain.get_by_name(verified_contact.domain)
    if not domain.commtrack_enabled:
        return False

    try:
        data = StockReportHelper(domain, verified_contact).parse(text.lower())
        if not data:
            return False
    except Exception, e:
        raise
        send_sms_to_verified_number(verified_contact, 'problem with stock report: %s' % str(e))
        return True

    process(domain.name, data)
    send_confirmation(verified_contact, data)
    return True

def process(domain, data):
    logger.debug(data)
    inst_xml = to_instance(data)
    logger.debug(inst_xml)

    stockreport.process(domain, inst_xml)

class StockReportHelper(object):
    """a helper object for parsing raw stock report texts"""

    def __init__(self, domain, v):
        self.domain = domain
        self.v = v
        self.C = domain.commtrack_settings

    # TODO sms parsing could really use unit tests
    def parse(self, text, location=None):
        """take in a text and return the parsed stock transactions"""
        args = text.split()

        if args[0] in self.C.all_keywords():
            # single action sms
            # TODO: support single-action by product, as well as by action?
            action_name = self.C.all_keywords()[args[0]]
            action = self.C.all_actions_by_name[action_name]
            args = args[1:]

            if not location:
                location = self.location_from_code(args[0])
                args = args[1:]

            _tx = self.single_action_transactions(action, args)

        elif self.C.multiaction_enabled and (self.C.multiaction_keyword is None or args[0] == self.C.multiaction_keyword.lower()):
            # multiple action sms
            if self.C.multiaction_keyword:
                args = args[1:]

            if not location:
                location = self.location_from_code(args[0])
                args = args[1:]

            _tx = self.multiple_action_transactions(args)

        else:
            # initial keyword not recognized; delegate to another handler
            return None

        tx = list(_tx)
        if not tx:
            raise RuntimeError("stock report doesn't have any transactions")

        return {
            'timestamp': datetime.utcnow(),
            'user': self.v.owner,
            'phone': self.v.phone_number,
            'location': location,
            'transactions': tx,
        }

    def single_action_transactions(self, action, args):
        # special case to handle immediate stock-out reports
        if action.action_type == 'stockout':
            if all(looks_like_prod_code(arg) for arg in args):
                for prod_code in args:
                    yield mk_tx(self.product_from_code(prod_code), action.name, 0)
                return
            else:
                raise RuntimeError("can't include a quantity for stock-out action")

        grouping_allowed = (action.action_type == 'stockedoutfor')

        products = []
        for arg in args:
            if looks_like_prod_code(arg):
                products.append(self.product_from_code(arg))
            else:
                if not products:
                    raise RuntimeError('quantity "%s" doesn\'t have a product' % arg)
                if len(products) > 1 and not grouping_allowed:
                    raise RuntimeError('missing quantity for product "%s"' % products[-1].code)

                try:
                    value = int(arg)
                except:
                    raise RuntimeError('could not understand product quantity "%s"' % arg)

                for p in products:
                    yield mk_tx(p, action.name, value)
                products = []
        if products:
            raise RuntimeError('missing quantity for product "%s"' % products[-1].code)

    def multiple_action_transactions(self, args):
        action = None
        action_code = None
        product = None

        _args = iter(args)
        def next():
            return _args.next()

        found_product_for_action = True
        while True:
            try:
                keyword = next()
            except StopIteration:
                if not found_product_for_action:
                    raise RuntimeError('product expected for action "%s"' % action_code)
                break

            try:
                old_action_code = action_code
                action, action_code = self.C.keywords(multi=True)[keyword], keyword
                if not found_product_for_action:
                    raise RuntimeError('product expected for action "%s"' % old_action_code)
                found_product_for_action = False
                continue
            except KeyError:
                pass

            try:
                product = self.product_from_code(keyword)
                found_product_for_action = True
            except:
                product = None
            if product:
                if not action:
                    raise RuntimeError('need to specify an action before product')
                elif action == 'stockout':
                    value = 0
                else:
                    try:
                        value = int(next())
                    except (ValueError, StopIteration):
                        raise RuntimeError('quantity expected for product "%s"' % product.code)

                yield mk_tx(product, action, value)
                continue

            raise RuntimeError('do not recognize keyword "%s"' % keyword)

    def location_from_code(self, loc_code):
        """return the supply point case referenced by loc_code"""
        result = get_supply_point(self.domain.name, loc_code)['case']
        if not result:
            raise RuntimeError('invalid location code "%s"' % loc_code)
        return result

    def product_from_code(self, prod_code):
        """return the product doc referenced by prod_code"""
        prod_code = prod_code.lower()
        p = Product.get_by_code(self.domain.name, prod_code)
        if p is None:
            raise RuntimeError('invalid product code "%s"' % prod_code)
        return p

def mk_tx(product, action, value):
    return locals()

def looks_like_prod_code(code):
    try:
        int(code)
        return False
    except:
        return True

def product_subcases(supply_point):
    """given a supply point, return all the sub-cases for each product stocked at that supply point
    actually returns a mapping: product doc id => sub-case id
    ACTUALLY returns a dict that will create non-existent product sub-cases on demand
    """
    product_subcase_uuids = [ix.referenced_id for ix in supply_point.reverse_indices if ix.identifier == const.PARENT_CASE_REF]
    product_subcases = CommCareCase.view('_all_docs', keys=product_subcase_uuids, include_docs=True)
    product_subcase_mapping = dict((subcase.dynamic_properties().get('product'), subcase._id) for subcase in product_subcases)

    def create_product_subcase(product_uuid):
        return make_supply_point_product(supply_point, product_uuid)._id

    class DefaultDict(dict):
        """similar to collections.defaultdict(), but factory function has access
        to 'key'
        """
        def __init__(self, factory, *args, **kwargs):
            super(DefaultDict, self).__init__(*args, **kwargs)
            self.factory = factory

        def __getitem__(self, key):
            if key in self:
                val = self.get(key)
            else:
                val = self.factory(key)
                self[key] = val
            return val

    return DefaultDict(create_product_subcase, product_subcase_mapping)

def to_instance(data):
    """convert the parsed sms stock report into an instance like what would be
    submitted from a commcare phone"""
    E = stockreport.XML()
    M = stockreport.XML(stockreport.META_XMLNS, 'jrm')

    product_subcase_mapping = product_subcases(data['location'])

    def mk_xml_tx(tx):
        tx['product_id'] = tx['product']._id
        tx['case_id'] = product_subcase_mapping[tx['product']._id]
        return stockreport.tx_to_xml(tx, E)

    deviceID = ''
    if data.get('phone'):
        deviceID = 'sms:%s' % data['phone']
    timestamp = json_format_datetime(data['timestamp'])

    root = E.stock_report(
        M.meta(
            M.userID(data['user']._id),
            M.deviceID(deviceID),
            M.timeStart(timestamp),
            M.timeEnd(timestamp)
        ),
        E.location(data['location']._id),
        *(mk_xml_tx(tx) for tx in data['transactions'])
    )

    return etree.tostring(root, encoding='utf-8', pretty_print=True)

def truncate(text, maxlen, ellipsis='...'):
    if len(text) > maxlen:
        return text[:maxlen-len(ellipsis)] + ellipsis
    else:
        return text

def send_confirmation(v, data):
    C = CommtrackConfig.for_domain(v.domain)

    static_loc = Location.get(data['location'].location_[-1])
    location_name = static_loc.name

    action_to_code = dict((v, k) for k, v in C.all_keywords().iteritems())
    tx_by_action = map_reduce(lambda tx: [(tx['action'],)], data=data['transactions'], include_docs=True)
    def summarize_action(action, txs):
        def fragment(tx):
            quantity = tx['value'] if tx['value'] is not None else ''
            return '%s%s' % (tx['product'].code.lower(), quantity)
        return '%s %s' % (action_to_code[action].upper(), ' '.join(sorted(fragment(tx) for tx in txs)))

    msg = 'received stock report for %s(%s) %s' % (
        static_loc.site_code,
        truncate(location_name, 20),
        ' '.join(sorted(summarize_action(a, txs) for a, txs in tx_by_action.iteritems()))
    )

    send_sms_to_verified_number(v, msg)
<commit_msg>hotfix accidental raise<commit_after>from corehq.apps.commtrack.const import RequisitionActions
from corehq.apps.domain.models import Domain
from casexml.apps.case.models import CommCareCase
from corehq.apps.locations.models import Location
from corehq.apps.commtrack import stockreport, const
from corehq.apps.sms.api import send_sms_to_verified_number
from lxml import etree
import logging
from dimagi.utils.couch.loosechange import map_reduce
from dimagi.utils.parsing import json_format_datetime
from datetime import datetime
from helpers import make_supply_point_product
from corehq.apps.commtrack.util import get_supply_point
from corehq.apps.commtrack.models import Product, CommtrackConfig
import settings

logger = logging.getLogger('commtrack.sms')

def handle(verified_contact, text):
    """top-level handler for incoming stock report messages"""
    domain = Domain.get_by_name(verified_contact.domain)
    if not domain.commtrack_enabled:
        return False

    try:
        data = StockReportHelper(domain, verified_contact).parse(text.lower())
        if not data:
            return False
    except Exception, e:
        if settings.UNIT_TESTING:
            raise
        send_sms_to_verified_number(verified_contact, 'problem with stock report: %s' % str(e))
        return True

    process(domain.name, data)
    send_confirmation(verified_contact, data)
    return True

def process(domain, data):
    logger.debug(data)
    inst_xml = to_instance(data)
    logger.debug(inst_xml)

    stockreport.process(domain, inst_xml)

class StockReportHelper(object):
    """a helper object for parsing raw stock report texts"""

    def __init__(self, domain, v):
        self.domain = domain
        self.v = v
        self.C = domain.commtrack_settings

    # TODO sms parsing could really use unit tests
    def parse(self, text, location=None):
        """take in a text and return the parsed stock transactions"""
        args = text.split()

        if args[0] in self.C.all_keywords():
            # single action sms
            # TODO: support single-action by product, as well as by action?
            action_name = self.C.all_keywords()[args[0]]
            action = self.C.all_actions_by_name[action_name]
            args = args[1:]

            if not location:
                location = self.location_from_code(args[0])
                args = args[1:]

            _tx = self.single_action_transactions(action, args)

        elif self.C.multiaction_enabled and (self.C.multiaction_keyword is None or args[0] == self.C.multiaction_keyword.lower()):
            # multiple action sms
            if self.C.multiaction_keyword:
                args = args[1:]

            if not location:
                location = self.location_from_code(args[0])
                args = args[1:]

            _tx = self.multiple_action_transactions(args)

        else:
            # initial keyword not recognized; delegate to another handler
            return None

        tx = list(_tx)
        if not tx:
            raise RuntimeError("stock report doesn't have any transactions")

        return {
            'timestamp': datetime.utcnow(),
            'user': self.v.owner,
            'phone': self.v.phone_number,
            'location': location,
            'transactions': tx,
        }

    def single_action_transactions(self, action, args):
        # special case to handle immediate stock-out reports
        if action.action_type == 'stockout':
            if all(looks_like_prod_code(arg) for arg in args):
                for prod_code in args:
                    yield mk_tx(self.product_from_code(prod_code), action.name, 0)
                return
            else:
                raise RuntimeError("can't include a quantity for stock-out action")

        grouping_allowed = (action.action_type == 'stockedoutfor')

        products = []
        for arg in args:
            if looks_like_prod_code(arg):
                products.append(self.product_from_code(arg))
            else:
                if not products:
                    raise RuntimeError('quantity "%s" doesn\'t have a product' % arg)
                if len(products) > 1 and not grouping_allowed:
                    raise RuntimeError('missing quantity for product "%s"' % products[-1].code)

                try:
                    value = int(arg)
                except:
                    raise RuntimeError('could not understand product quantity "%s"' % arg)

                for p in products:
                    yield mk_tx(p, action.name, value)
                products = []
        if products:
            raise RuntimeError('missing quantity for product "%s"' % products[-1].code)

    def multiple_action_transactions(self, args):
        action = None
        action_code = None
        product = None

        _args = iter(args)
        def next():
            return _args.next()

        found_product_for_action = True
        while True:
            try:
                keyword = next()
            except StopIteration:
                if not found_product_for_action:
                    raise RuntimeError('product expected for action "%s"' % action_code)
                break

            try:
                old_action_code = action_code
                action, action_code = self.C.keywords(multi=True)[keyword], keyword
                if not found_product_for_action:
                    raise RuntimeError('product expected for action "%s"' % old_action_code)
                found_product_for_action = False
                continue
            except KeyError:
                pass

            try:
                product = self.product_from_code(keyword)
                found_product_for_action = True
            except:
                product = None
            if product:
                if not action:
                    raise RuntimeError('need to specify an action before product')
                elif action == 'stockout':
                    value = 0
                else:
                    try:
                        value = int(next())
                    except (ValueError, StopIteration):
                        raise RuntimeError('quantity expected for product "%s"' % product.code)

                yield mk_tx(product, action, value)
                continue

            raise RuntimeError('do not recognize keyword "%s"' % keyword)

    def location_from_code(self, loc_code):
        """return the supply point case referenced by loc_code"""
        result = get_supply_point(self.domain.name, loc_code)['case']
        if not result:
            raise RuntimeError('invalid location code "%s"' % loc_code)
        return result

    def product_from_code(self, prod_code):
        """return the product doc referenced by prod_code"""
        prod_code = prod_code.lower()
        p = Product.get_by_code(self.domain.name, prod_code)
        if p is None:
            raise RuntimeError('invalid product code "%s"' % prod_code)
        return p

def mk_tx(product, action, value):
    return locals()

def looks_like_prod_code(code):
    try:
        int(code)
        return False
    except:
        return True

def product_subcases(supply_point):
    """given a supply point, return all the sub-cases for each product stocked at that supply point
    actually returns a mapping: product doc id => sub-case id
    ACTUALLY returns a dict that will create non-existent product sub-cases on demand
    """
    product_subcase_uuids = [ix.referenced_id for ix in supply_point.reverse_indices if ix.identifier == const.PARENT_CASE_REF]
    product_subcases = CommCareCase.view('_all_docs', keys=product_subcase_uuids, include_docs=True)
    product_subcase_mapping = dict((subcase.dynamic_properties().get('product'), subcase._id) for subcase in product_subcases)

    def create_product_subcase(product_uuid):
        return make_supply_point_product(supply_point, product_uuid)._id

    class DefaultDict(dict):
        """similar to collections.defaultdict(), but factory function has access
        to 'key'
        """
        def __init__(self, factory, *args, **kwargs):
            super(DefaultDict, self).__init__(*args, **kwargs)
            self.factory = factory

        def __getitem__(self, key):
            if key in self:
                val = self.get(key)
            else:
                val = self.factory(key)
                self[key] = val
            return val

    return DefaultDict(create_product_subcase, product_subcase_mapping)

def to_instance(data):
    """convert the parsed sms stock report into an instance like what would be
    submitted from a commcare phone"""
    E = stockreport.XML()
    M = stockreport.XML(stockreport.META_XMLNS, 'jrm')

    product_subcase_mapping = product_subcases(data['location'])

    def mk_xml_tx(tx):
        tx['product_id'] = tx['product']._id
        tx['case_id'] = product_subcase_mapping[tx['product']._id]
        return stockreport.tx_to_xml(tx, E)

    deviceID = ''
    if data.get('phone'):
        deviceID = 'sms:%s' % data['phone']
    timestamp = json_format_datetime(data['timestamp'])

    root = E.stock_report(
        M.meta(
            M.userID(data['user']._id),
            M.deviceID(deviceID),
            M.timeStart(timestamp),
            M.timeEnd(timestamp)
        ),
        E.location(data['location']._id),
        *(mk_xml_tx(tx) for tx in data['transactions'])
    )

    return etree.tostring(root, encoding='utf-8', pretty_print=True)

def truncate(text, maxlen, ellipsis='...'):
    if len(text) > maxlen:
        return text[:maxlen-len(ellipsis)] + ellipsis
    else:
        return text

def send_confirmation(v, data):
    C = CommtrackConfig.for_domain(v.domain)

    static_loc = Location.get(data['location'].location_[-1])
    location_name = static_loc.name

    action_to_code = dict((v, k) for k, v in C.all_keywords().iteritems())
    tx_by_action = map_reduce(lambda tx: [(tx['action'],)], data=data['transactions'], include_docs=True)
    def summarize_action(action, txs):
        def fragment(tx):
            quantity = tx['value'] if tx['value'] is not None else ''
            return '%s%s' % (tx['product'].code.lower(), quantity)
        return '%s %s' % (action_to_code[action].upper(), ' '.join(sorted(fragment(tx) for tx in txs)))

    msg = 'received stock report for %s(%s) %s' % (
        static_loc.site_code,
        truncate(location_name, 20),
        ' '.join(sorted(summarize_action(a, txs) for a, txs in tx_by_action.iteritems()))
    )

    send_sms_to_verified_number(v, msg)
<|endoftext|>


<commit_before>from datetime import datetime
from smtplib import SMTPRecipientsRefused
from celery.log import get_task_logger
from celery.schedules import crontab
from celery.decorators import periodic_task, task
from django.http import Http404
from corehq.apps.domain.models import Domain
from corehq.apps.reports.models import DailyReportNotification
from corehq.apps.users.models import CouchUser
from corehq.apps.reports.schedule import config
from corehq.apps.reports.schedule.html2text import html2text
from dimagi.utils.django.email import send_HTML_email
from corehq.apps.reports.schedule.config import ScheduledReportFactory

logging = get_task_logger()

@periodic_task(run_every=crontab(hour="*", minute="0", day_of_week="*"))
def daily_reports():
    # this should get called every hour by celery
    reps = DailyReportNotification.view("reports/daily_notifications",
                                        key=datetime.utcnow().hour,
                                        include_docs=True).all()
    _run_reports(reps)

@periodic_task(run_every=crontab(hour="*", minute="1", day_of_week="*"))
def weekly_reports():
    # this should get called every hour by celery
    now = datetime.utcnow()
    reps = DailyReportNotification.view("reports/weekly_notifications",
                                        key=[now.weekday(), now.hour],
                                        include_docs=True).all()
    _run_reports(reps)

@task
def send_report(scheduled_report, user):
    if isinstance(user, dict):
        user = CouchUser.wrap_correctly(user)
    try:
        report = ScheduledReportFactory.get_report(scheduled_report.report_slug)
        body = report.get_response(user, scheduled_report.domain)
        email = user.get_email()
        if email:
            send_HTML_email("%s [%s]" % (report.title, scheduled_report.domain), email,
                        html2text(body), body)
        else:
            raise SMTPRecipientsRefused(None)
    except Http404:
        # Scenario: user has been removed from the domain that they have scheduled reports for.
        # Do a scheduled report cleanup
        user_id = unicode(user.get_id)
        domain = Domain.get_by_name(scheduled_report.domain)
        user_ids = [user.get_id for user in domain.all_users()]
        if user_id not in user_ids:
            # remove the offending user from the scheduled report
            scheduled_report.user_ids.remove(user_id)
            if len(scheduled_report.user_ids) == 0:
                # there are no users with appropriate permissions left in the scheduled report,
                # so remove it
                scheduled_report.delete()
            else:
                scheduled_report.save()

def _run_reports(reps):
    for rep in reps:
        for user_id in rep.user_ids:
            user = CouchUser.get(user_id)
            send_report.delay(rep, user.to_json())
<commit_msg>add task to run saved exports<commit_after>from datetime import datetime
from smtplib import SMTPRecipientsRefused
from celery.log import get_task_logger
from celery.schedules import crontab
from celery.decorators import periodic_task, task
from django.http import Http404
from corehq.apps.domain.models import Domain
from corehq.apps.reports.models import DailyReportNotification,\
    HQGroupExportConfiguration
from corehq.apps.users.models import CouchUser
from corehq.apps.reports.schedule.html2text import html2text
from dimagi.utils.django.email import send_HTML_email
from corehq.apps.reports.schedule.config import ScheduledReportFactory
from couchexport.groupexports import export_for_group

logging = get_task_logger()

@periodic_task(run_every=crontab(hour="*", minute="0", day_of_week="*"))
def daily_reports():
    # this should get called every hour by celery
    reps = DailyReportNotification.view("reports/daily_notifications",
                                        key=datetime.utcnow().hour,
                                        include_docs=True).all()
    _run_reports(reps)

@periodic_task(run_every=crontab(hour="*", minute="1", day_of_week="*"))
def weekly_reports():
    # this should get called every hour by celery
    now = datetime.utcnow()
    reps = DailyReportNotification.view("reports/weekly_notifications",
                                        key=[now.weekday(), now.hour],
                                        include_docs=True).all()
    _run_reports(reps)

@task
def send_report(scheduled_report, user):
    if isinstance(user, dict):
        user = CouchUser.wrap_correctly(user)
    try:
        report = ScheduledReportFactory.get_report(scheduled_report.report_slug)
        body = report.get_response(user, scheduled_report.domain)
        email = user.get_email()
        if email:
            send_HTML_email("%s [%s]" % (report.title, scheduled_report.domain), email,
                        html2text(body), body)
        else:
            raise SMTPRecipientsRefused(None)
    except Http404:
        # Scenario: user has been removed from the domain that they have scheduled reports for.
        # Do a scheduled report cleanup
        user_id = unicode(user.get_id)
        domain = Domain.get_by_name(scheduled_report.domain)
        user_ids = [user.get_id for user in domain.all_users()]
        if user_id not in user_ids:
            # remove the offending user from the scheduled report
            scheduled_report.user_ids.remove(user_id)
            if len(scheduled_report.user_ids) == 0:
                # there are no users with appropriate permissions left in the scheduled report,
                # so remove it
                scheduled_report.delete()
            else:
                scheduled_report.save()

@periodic_task(run_every=crontab(hour=[0,12], minute="0", day_of_week="*"))
def saved_exports():
    for row in HQGroupExportConfiguration.view("groupexport/by_domain", reduce=False).all():
        export_for_group(row["id"], "couch")

def _run_reports(reps):
    for rep in reps:
        for user_id in rep.user_ids:
            user = CouchUser.get(user_id)
            send_report.delay(rep, user.to_json())
<|endoftext|>


<commit_before>'''Data module

'''

__author__ = 'R Devon Hjelm'
__author_email__ = 'erroneus@gmail.com'

import logging

from .data_handler import DataHandler
from ..config import CONFIG

logger = logging.getLogger('cortex.data')

DATA_HANDLER = DataHandler()
_PLUGINS = {}

def setup(source: str=None, batch_size=64, n_workers: int=4,
          skip_last_batch: bool=False, DataLoader=None,
          copy_to_local: bool=False, data_args={}, shuffle: bool=True):
    '''Dataset entrypoint.

    Args:
        source: Dataset source or list of sources.
        batch_size: Batch size or dict of batch sizes.
        noise_variables: Dict of noise variables.
        n_workers: Number of workers for DataLoader class.
        skip_last_batch: Whether to skip the last batch if the size
        is smaller than batch_size.
        DataLoader: Optional user-defined DataLoader.
        copy_to_local: Copy the data to a local path.
        data_args: Arguments for dataset plugin.
        shuffle: Shuffle the dataset.

    '''
    global DATA_HANDLER

    if source and not isinstance(source, (list, tuple)):
        sources = [source]
    else:
        sources = source

    DATA_HANDLER.set_batch_size(batch_size, skip_last_batch=skip_last_batch)

    if sources:
        for source in sources:
            plugin = _PLUGINS.get(source, None)
            if plugin is None:
                raise KeyError('Dataset plugin for `{}` not found'
                               .format(source))

            plugin.handle(source, copy_to_local=copy_to_local, **data_args)
            DATA_HANDLER.add_dataset(source, plugin, n_workers=n_workers,
                                     shuffle=shuffle, DataLoader=DataLoader)
    else:
        raise ValueError('No source provided. Use `--d.source`')

def register(plugin):
    global _PLUGINS
    plugin = plugin()

    for k in plugin.sources:
        if k in _PLUGINS:
            raise KeyError('`{}` already registered in a plugin. '
                           'Try using a different name.'.format(k))
        _PLUGINS[k] = plugin

class DatasetPluginBase():
    def __init__(self):

        if len(self.sources) == 0:
            raise ValueError('No sources found for dataset entry point.')

        self._datasets = {}
        self._dims = {}
        self._input_names = None
        self._scale = None
        if CONFIG.data_paths is None:
            raise ValueError('`data_paths` not set in config.')
        self._paths = CONFIG.data_paths
<commit_msg>fixing an indentation for a naughty push<commit_after>'''Data module

'''

__author__ = 'R Devon Hjelm'
__author_email__ = 'erroneus@gmail.com'

import logging

from .data_handler import DataHandler
from ..config import CONFIG

logger = logging.getLogger('cortex.data')

DATA_HANDLER = DataHandler()
_PLUGINS = {}

def setup(source: str=None, batch_size=64, n_workers: int=4,
          skip_last_batch: bool=False, DataLoader=None,
          copy_to_local: bool=False, data_args={}, shuffle: bool=True):
    '''Dataset entrypoint.

    Args:
        source: Dataset source or list of sources.
        batch_size: Batch size or dict of batch sizes.
        noise_variables: Dict of noise variables.
        n_workers: Number of workers for DataLoader class.
        skip_last_batch: Whether to skip the last batch if the size
            is smaller than batch_size.
        DataLoader: Optional user-defined DataLoader.
        copy_to_local: Copy the data to a local path.
        data_args: Arguments for dataset plugin.
        shuffle: Shuffle the dataset.

    '''
    global DATA_HANDLER

    if source and not isinstance(source, (list, tuple)):
        sources = [source]
    else:
        sources = source

    DATA_HANDLER.set_batch_size(batch_size, skip_last_batch=skip_last_batch)

    if sources:
        for source in sources:
            plugin = _PLUGINS.get(source, None)
            if plugin is None:
                raise KeyError('Dataset plugin for `{}` not found'
                               .format(source))

            plugin.handle(source, copy_to_local=copy_to_local, **data_args)
            DATA_HANDLER.add_dataset(source, plugin, n_workers=n_workers,
                                     shuffle=shuffle, DataLoader=DataLoader)
    else:
        raise ValueError('No source provided. Use `--d.source`')

def register(plugin):
    global _PLUGINS
    plugin = plugin()

    for k in plugin.sources:
        if k in _PLUGINS:
            raise KeyError('`{}` already registered in a plugin. '
                           'Try using a different name.'.format(k))
        _PLUGINS[k] = plugin

class DatasetPluginBase():
    def __init__(self):

        if len(self.sources) == 0:
            raise ValueError('No sources found for dataset entry point.')

        self._datasets = {}
        self._dims = {}
        self._input_names = None
        self._scale = None
        if CONFIG.data_paths is None:
            raise ValueError('`data_paths` not set in config.')
        self._paths = CONFIG.data_paths
<|endoftext|>


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

import sys
import couchdb
import unittest

sys.path.append('../lib')
from patXML import *

basedir = os.path.join(os.curdir, '../test')
testdir = os.path.join(basedir, 'fixtures/xml/')

class TestCouchPatent(unittest.TestCase):

    def setUp(self):
        #setup CouchDB
        self.couch = couchdb.Server()
        self.db = None
        if 'patents' in self.couch:
            self.db = self.couch['patents']
        else:
            self.db = self.couch.create('patents')
        self.assertTrue('patents' in self.couch)
        #establish test xml file
        self.testfile = XMLPatentBase(open(testdir+'ipg120327.one.xml').read())
        self.assertTrue(self.testfile)

unittest.main()
<commit_msg>add import for parse.py<commit_after>#!/usr/bin/env python

import sys
import couchdb
import unittest

sys.path.append('../lib')
from patXML import *

sys.path.append('..')
import parse

basedir = os.path.join(os.curdir, '../test')
testdir = os.path.join(basedir, 'fixtures/xml/')

class TestCouchPatent(unittest.TestCase):

    def setUp(self):
        #setup CouchDB
        self.couch = couchdb.Server()
        self.db = None
        if 'patents' in self.couch:
            self.db = self.couch['patents']
        else:
            self.db = self.couch.create('patents')
        self.assertTrue('patents' in self.couch)
        #establish test xml file
        self.testfile = XMLPatentBase(open(testdir+'ipg120327.one.xml').read())
        self.assertTrue(self.testfile)

unittest.main()
<|endoftext|>


<commit_before>from django.test import TestCase

from coupons.forms import CouponGenerationForm, CouponForm
from coupons.models import Coupon

class CouponGenerationFormTestCase(TestCase):
    def test_form(self):
        form_data = {'quantity': 23, 'value': 42, 'type': 'monetary'}
        form = CouponGenerationForm(data=form_data)
        self.assertTrue(form.is_valid())

class CouponFormTestCase(TestCase):
    def setUp(self):
        self.coupon = Coupon.objects.create_coupon('monetary', 100)

    def test_wrong_code(self):
        form_data = {'code': 'foo'}
        form = CouponForm(data=form_data)
        self.assertFalse(form.is_valid())

    def test_right_code(self):
        form_data = {'code': self.coupon.code}
        form = CouponForm(data=form_data)
        self.assertTrue(form.is_valid())

    def test_types(self):
        form_data = {'code': self.coupon.code}
        form = CouponForm(data=form_data, types=('percentage',))
        self.assertFalse(form.is_valid())

<commit_msg>test user restriction<commit_after>from django.contrib.auth.models import User
from django.test import TestCase

from coupons.forms import CouponGenerationForm, CouponForm
from coupons.models import Coupon

class CouponGenerationFormTestCase(TestCase):
    def test_form(self):
        form_data = {'quantity': 23, 'value': 42, 'type': 'monetary'}
        form = CouponGenerationForm(data=form_data)
        self.assertTrue(form.is_valid())

class CouponFormTestCase(TestCase):
    def setUp(self):
        self.user = User(username="user1")
        self.user.save()
        self.coupon = Coupon.objects.create_coupon('monetary', 100, self.user)

    def test_wrong_code(self):
        form_data = {'code': 'foo'}
        form = CouponForm(data=form_data)
        self.assertFalse(form.is_valid())

    def test_right_code(self):
        form_data = {'code': self.coupon.code}
        form = CouponForm(data=form_data, user=self.user)
        self.assertTrue(form.is_valid())

    def test_types(self):
        form_data = {'code': self.coupon.code}
        form = CouponForm(data=form_data, user=self.user, types=('percentage',))
        self.assertFalse(form.is_valid())

    def test_user(self):
        other_user = User(username="user2")
        other_user.save()
        form_data = {'code': self.coupon.code}
        form = CouponForm(data=form_data, user=other_user)
        self.assertFalse(form.is_valid())

<|endoftext|>


<commit_before>from datetime import timedelta
import re

from django.utils import timezone
from django.test import TestCase

from coupons.models import Coupon
from coupons.settings import CODE_LENGTH, CODE_CHARS

class CouponTestCase(TestCase):
    def test_generate_code(self):
        self.assertIsNotNone(re.match("^[%s]{%d}" % (CODE_CHARS, CODE_LENGTH,), Coupon.generate_code()))

    def test_save(self):
        coupon = Coupon(type='monetary', value=100)
        coupon.save()
        self.assertTrue(coupon.pk)

    def test_create_coupon(self):
        coupon = Coupon.objects.create_coupon('monetary', 100)
        self.assertTrue(coupon.pk)

    def test_create_coupons(self):
        coupons = Coupon.objects.create_coupons(50, 'monetary', 100)
        for coupon in coupons:
            self.assertTrue(coupon.pk)

    def test_redeem(self):
        coupon = Coupon.objects.create_coupon('monetary', 100)
        coupon.redeem()
        self.assertIsNotNone(coupon.redeemed_at)

    def test_expired(self):
        coupon = Coupon.objects.create_coupon('monetary', 100)
        self.assertFalse(coupon.expired())
        coupon.valid_until = timezone.now()-timedelta(1)
        self.assertTrue(coupon.expired())

    def test_str(self):
        coupon = Coupon.objects.create_coupon('monetary', 100)
        self.assertEqual(coupon.code, str(coupon))
<commit_msg>add test case for segmented code generation<commit_after>from datetime import timedelta
import re

from django.utils import timezone
from django.test import TestCase

from coupons.models import Coupon
from coupons.settings import (
    CODE_LENGTH,
    CODE_CHARS,
    SEGMENT_LENGTH,
    SEGMENT_SEPARATOR,
)

class CouponTestCase(TestCase):
    def test_generate_code(self):
        self.assertIsNotNone(re.match("^[%s]{%d}" % (CODE_CHARS, CODE_LENGTH,), Coupon.generate_code()))

    def test_generate_code_segmented(self):
        num_segments = CODE_LENGTH // SEGMENT_LENGTH  # full ones
        num_rest = CODE_LENGTH - num_segments * SEGMENT_LENGTH
        self.assertIsNotNone(
            re.match(
                "^([{chars}]{{{sl}}}{sep}){{{ns}}}[{chars}]{{{nr}}}$".format(
                    chars=CODE_CHARS,
                    sep=SEGMENT_SEPARATOR,
                    sl=SEGMENT_LENGTH,
                    ns=num_segments,
                    nr=num_rest),
                Coupon.generate_code(True)
            )
        )

    def test_save(self):
        coupon = Coupon(type='monetary', value=100)
        coupon.save()
        self.assertTrue(coupon.pk)

    def test_create_coupon(self):
        coupon = Coupon.objects.create_coupon('monetary', 100)
        self.assertTrue(coupon.pk)

    def test_create_coupons(self):
        coupons = Coupon.objects.create_coupons(50, 'monetary', 100)
        for coupon in coupons:
            self.assertTrue(coupon.pk)

    def test_redeem(self):
        coupon = Coupon.objects.create_coupon('monetary', 100)
        coupon.redeem()
        self.assertIsNotNone(coupon.redeemed_at)

    def test_expired(self):
        coupon = Coupon.objects.create_coupon('monetary', 100)
        self.assertFalse(coupon.expired())
        coupon.valid_until = timezone.now()-timedelta(1)
        self.assertTrue(coupon.expired())

    def test_str(self):
        coupon = Coupon.objects.create_coupon('monetary', 100)
        self.assertEqual(coupon.code, str(coupon))
<|endoftext|>


<commit_before>from django.db import models
from resources.models import Resource

class LearningOutcome(models.Model):
    #  Auto-incrementing 'id' field is automatically set by Django
    slug = models.SlugField(unique=True)
    text = models.CharField(max_length=200, unique=True)

    def __str__(self):
        return self.text

class CurriculumLink(models.Model):
    #  Auto-incrementing 'id' field is automatically set by Django
    name = models.CharField(max_length=100, unique=True)

    def __str__(self):
        return self.name

class ClassroomResource(models.Model):
    #  Auto-incrementing 'id' field is automatically set by Django
    text = models.CharField(max_length=300, unique=True)

    def __str__(self):
        return self.text

class Age(models.Model):
#  Auto-incrementing 'id' field is automatically set by Django
    age = models.PositiveSmallIntegerField()

    def __str__(self):
        return self.age

class Topic(models.Model):
    #  Auto-incrementing 'id' field is automatically set by Django
    slug = models.SlugField(unique=True)
    name = models.CharField(max_length=100)
    content = models.TextField()
    other_resources = models.TextField()
    icon = models.CharField(max_length=100, null=True)

    def __str__(self):
        return self.name

class UnitPlan(models.Model):
    #  Auto-incrementing 'id' field is automatically set by Django
    topic = models.ForeignKey(
        Topic,
        on_delete=models.CASCADE,
        related_name='topic_unit_plans'
    )
    slug = models.SlugField()
    name = models.CharField(max_length=100)
    content = models.TextField()

    def __str__(self):
        return self.name

class Lesson(models.Model):
    #  Auto-incrementing 'id' field is automatically set by Django
    topic = models.ForeignKey(
        Topic,
        on_delete=models.CASCADE,
        related_name='topic_lessons'
    )
    unit_plan = models.ForeignKey(
        UnitPlan,
        on_delete=models.CASCADE,
        related_name='unit_plan_lessons'
    )
    slug = models.SlugField()
    name = models.CharField(max_length=100)
    number = models.IntegerField()
    content = models.TextField()
    ages = models.ManyToManyField(
        Age,
        related_name='lesson_ages'
    )
    learning_outcomes = models.ManyToManyField(
        LearningOutcome,
        related_name='lesson_learning_outcomes'
    )
    curriculum_links = models.ManyToManyField(
        CurriculumLink,
        related_name='lesson_curriculum_links'
    )
    classroom_resources = models.ManyToManyField(
        ClassroomResource,
        related_name='lesson_classroom_resources'
    )
    generated_resources = models.ManyToManyField(
        Resource,
        through='ConnectedGeneratedResource',
        related_name='lesson_generated_resources'
    )

    def __str__(self):
        return self.name

class FollowUpActivity(models.Model):
    #  Auto-incrementing 'id' field is automatically set by Django
    topic = models.ForeignKey(
        Topic,
        on_delete=models.CASCADE,
        related_name='topic_follow_up_activities'
    )
    slug = models.SlugField()
    name = models.CharField(max_length=200)
    content = models.TextField()
    curriculum_links = models.ManyToManyField(
        CurriculumLink,
        related_name='follow_up_activity_curriculum_links'
    )

    def __str__(self):
        return self.name

class ProgrammingExerciseDifficulty(models.Model):
    #  Auto-incrementing 'id' field is automatically set by Django
    level = models.PositiveSmallIntegerField(unique=True)
    name = models.CharField(max_length=100, unique=True)

    def __str__(self):
        return self.name

class ProgrammingExercise(models.Model):
    #  Auto-incrementing 'id' field is automatically set by Django
    topic = models.ForeignKey(
        Topic,
        on_delete=models.CASCADE,
        related_name='topic_programming_exercises'
    )
    slug = models.SlugField()
    name = models.CharField(max_length=200)
    exercise_number = models.IntegerField()
    content = models.TextField()
    scratch_hints = models.TextField()
    scratch_solution = models.TextField()
    python_hints = models.TextField()
    python_solution = models.TextField()
    learning_outcomes = models.ManyToManyField(
        LearningOutcome,
        related_name='programming_exercise_learning_outcomes'
    )
    difficulty = models.ForeignKey(
        ProgrammingExerciseDifficulty,
        on_delete=models.CASCADE,
        related_name='difficulty_programming_exercises'
    )

    def __str__(self):
        return self.name

class ConnectedGeneratedResource(models.Model):
    #  Auto-incrementing 'id' field is automatically set by Django
    resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
    lesson = models.ForeignKey(Lesson, on_delete=models.CASCADE)
    description = models.CharField(max_length=300)
<commit_msg>Fix indentation of comment<commit_after>from django.db import models
from resources.models import Resource

class LearningOutcome(models.Model):
    #  Auto-incrementing 'id' field is automatically set by Django
    slug = models.SlugField(unique=True)
    text = models.CharField(max_length=200, unique=True)

    def __str__(self):
        return self.text

class CurriculumLink(models.Model):
    #  Auto-incrementing 'id' field is automatically set by Django
    name = models.CharField(max_length=100, unique=True)

    def __str__(self):
        return self.name

class ClassroomResource(models.Model):
    #  Auto-incrementing 'id' field is automatically set by Django
    text = models.CharField(max_length=300, unique=True)

    def __str__(self):
        return self.text

class Age(models.Model):
    #  Auto-incrementing 'id' field is automatically set by Django
    age = models.PositiveSmallIntegerField()

    def __str__(self):
        return self.age

class Topic(models.Model):
    #  Auto-incrementing 'id' field is automatically set by Django
    slug = models.SlugField(unique=True)
    name = models.CharField(max_length=100)
    content = models.TextField()
    other_resources = models.TextField()
    icon = models.CharField(max_length=100, null=True)

    def __str__(self):
        return self.name

class UnitPlan(models.Model):
    #  Auto-incrementing 'id' field is automatically set by Django
    topic = models.ForeignKey(
        Topic,
        on_delete=models.CASCADE,
        related_name='topic_unit_plans'
    )
    slug = models.SlugField()
    name = models.CharField(max_length=100)
    content = models.TextField()

    def __str__(self):
        return self.name

class Lesson(models.Model):
    #  Auto-incrementing 'id' field is automatically set by Django
    topic = models.ForeignKey(
        Topic,
        on_delete=models.CASCADE,
        related_name='topic_lessons'
    )
    unit_plan = models.ForeignKey(
        UnitPlan,
        on_delete=models.CASCADE,
        related_name='unit_plan_lessons'
    )
    slug = models.SlugField()
    name = models.CharField(max_length=100)
    number = models.IntegerField()
    content = models.TextField()
    ages = models.ManyToManyField(
        Age,
        related_name='lesson_ages'
    )
    learning_outcomes = models.ManyToManyField(
        LearningOutcome,
        related_name='lesson_learning_outcomes'
    )
    curriculum_links = models.ManyToManyField(
        CurriculumLink,
        related_name='lesson_curriculum_links'
    )
    classroom_resources = models.ManyToManyField(
        ClassroomResource,
        related_name='lesson_classroom_resources'
    )
    generated_resources = models.ManyToManyField(
        Resource,
        through='ConnectedGeneratedResource',
        related_name='lesson_generated_resources'
    )

    def __str__(self):
        return self.name

class FollowUpActivity(models.Model):
    #  Auto-incrementing 'id' field is automatically set by Django
    topic = models.ForeignKey(
        Topic,
        on_delete=models.CASCADE,
        related_name='topic_follow_up_activities'
    )
    slug = models.SlugField()
    name = models.CharField(max_length=200)
    content = models.TextField()
    curriculum_links = models.ManyToManyField(
        CurriculumLink,
        related_name='follow_up_activity_curriculum_links'
    )

    def __str__(self):
        return self.name

class ProgrammingExerciseDifficulty(models.Model):
    #  Auto-incrementing 'id' field is automatically set by Django
    level = models.PositiveSmallIntegerField(unique=True)
    name = models.CharField(max_length=100, unique=True)

    def __str__(self):
        return self.name

class ProgrammingExercise(models.Model):
    #  Auto-incrementing 'id' field is automatically set by Django
    topic = models.ForeignKey(
        Topic,
        on_delete=models.CASCADE,
        related_name='topic_programming_exercises'
    )
    slug = models.SlugField()
    name = models.CharField(max_length=200)
    exercise_number = models.IntegerField()
    content = models.TextField()
    scratch_hints = models.TextField()
    scratch_solution = models.TextField()
    python_hints = models.TextField()
    python_solution = models.TextField()
    learning_outcomes = models.ManyToManyField(
        LearningOutcome,
        related_name='programming_exercise_learning_outcomes'
    )
    difficulty = models.ForeignKey(
        ProgrammingExerciseDifficulty,
        on_delete=models.CASCADE,
        related_name='difficulty_programming_exercises'
    )

    def __str__(self):
        return self.name

class ConnectedGeneratedResource(models.Model):
    #  Auto-incrementing 'id' field is automatically set by Django
    resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
    lesson = models.ForeignKey(Lesson, on_delete=models.CASCADE)
    description = models.CharField(max_length=300)
<|endoftext|>


<commit_before># Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.

from typing import Any

from PyQt5.QtCore import pyqtProperty, pyqtSlot, pyqtSignal

from UM.Decorators import override

from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
from UM.Settings.ContainerStack import ContainerStack, InvalidContainerStackError
from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.DefinitionContainer import DefinitionContainer
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.Interfaces import ContainerInterface

from . import Exceptions

class GlobalStack(ContainerStack):
    def __init__(self, container_id: str, *args, **kwargs):
        super().__init__(container_id, *args, **kwargs)

        self._empty_instance_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()

        self._containers = [self._empty_instance_container for i in range(len(_ContainerIndexes.IndexTypeMap))]
        self._extruders = []

        self.containersChanged.connect(self._onContainersChanged)

    pyqtContainersChanged = pyqtSignal()

    def setUserChanges(self, new_user_changes: InstanceContainer) -> None:
        self.replaceContainer(_ContainerIndexes.UserChanges, new_user_changes)

    @pyqtProperty(InstanceContainer, fset = setUserChanges, notify = pyqtContainersChanged)
    def userChanges(self) -> InstanceContainer:
        return self._containers[_ContainerIndexes.UserChanges]

    def setQualityChanges(self, new_quality_changes: InstanceContainer) -> None:
        self.replaceContainer(_ContainerIndexes.QualityChanges, new_quality_changes)

    def setQualityChangesById(self, new_quality_changes_id: str) -> None:
        quality_changes = ContainerRegistry.getInstance().findInstanceContainers(id = new_quality_changes_id)
        if quality_changes:
            self.setQualityChanges(quality_changes[0])
        else:
            raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_quality_changes_id))

    @pyqtProperty(InstanceContainer, fset = setQualityChanges, notify = pyqtContainersChanged)
    def qualityChanges(self) -> InstanceContainer:
        return self._containers[_ContainerIndexes.QualityChanges]

    def setQuality(self, new_quality: InstanceContainer) -> None:
        self.replaceContainer(_ContainerIndexes.Quality, new_quality)

    def setQualityById(self, new_quality_id: str) -> None:
        quality = ContainerRegistry.getInstance().findInstanceContainers(id = new_quality_id)
        if quality:
            self.setQuality(quality[0])
        else:
            raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_quality_id))

    @pyqtProperty(InstanceContainer, fset = setQuality, notify = pyqtContainersChanged)
    def quality(self) -> InstanceContainer:
        return self._containers[_ContainerIndexes.Quality]

    def setMaterial(self, new_material: InstanceContainer) -> None:
        self.replaceContainer(_ContainerIndexes.Material, new_material)

    def setMaterialById(self, new_material_id: str) -> None:
        material = ContainerRegistry.getInstance().findInstanceContainers(id = new_material_id)
        if material:
            self.setMaterial(material[0])
        else:
            raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_material_id))

    @pyqtProperty(InstanceContainer, fset = setMaterial, notify = pyqtContainersChanged)
    def material(self) -> InstanceContainer:
        return self._containers[_ContainerIndexes.Material]

    def setVariant(self, new_variant: InstanceContainer) -> None:
        self.replaceContainer(_ContainerIndexes.Variant, new_variant)

    def setVariantById(self, new_variant_id: str) -> None:
        variant = ContainerRegistry.getInstance().findInstanceContainers(id = new_variant_id)
        if variant:
            self.setVariant(variant[0])
        else:
            raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_variant_id))

    @pyqtProperty(InstanceContainer, fset = setVariant, notify = pyqtContainersChanged)
    def variant(self) -> InstanceContainer:
        return self._containers[_ContainerIndexes.Variant]

    def setDefinitionChanges(self, new_definition_changes: InstanceContainer) -> None:
        self.replaceContainer(_ContainerIndexes.DefinitionChanges, new_definition_changes)

    def setDefinitionChangesById(self, new_definition_changes_id: str) -> None:
        new_definition_changes = ContainerRegistry.getInstance().findInstanceContainers(id = new_definition_changes_id)
        if new_definition_changes:
            self.setDefinitionChanges(new_definition_changes[0])
        else:
            raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_definition_changes_id))

    @pyqtProperty(InstanceContainer, fset = setDefinitionChanges, notify = pyqtContainersChanged)
    def definitionChanges(self) -> InstanceContainer:
        return self._containers[_ContainerIndexes.DefinitionChanges]

    @pyqtProperty(DefinitionContainer)
    def definition(self) -> DefinitionContainer:
        return self._containers[_ContainerIndexes.Definition]

    @pyqtProperty("QVariantList")
    def extruders(self) -> list:
        return self._extruders

    def addExtruder(self, extruder):
        extruder_count = self.getProperty("machine_extruder_count", "value")
        if len(self._extruders) + 1 > extruder_count:
            raise Exceptions.TooManyExtrudersError("Tried to add extruder to {id} but its extruder count is {count}".format(id = self.id, count = extruder_count))

        self._extruders.append(extruder)

    ##  Check whether the specified setting has a 'user' value.
    #
    #   A user value here is defined as the setting having a value in either
    #   the UserChanges or QualityChanges container.
    #
    #   \return True if the setting has a user value, False if not.
    @pyqtSlot(str, result = bool)
    def hasUserValue(self, key: str) -> bool:
        if self._containers[_ContainerIndexes.UserChanges].hasProperty(key, "value"):
            return True

        if self._containers[_ContainerIndexes.QualityChanges].hasProperty(key, "value"):
            return True

        return False

    ##  Overridden from ContainerStack
    @override(ContainerStack)
    def getProperty(self, key: str, property_name: str) -> Any:
        if property_name == "value":
            if not self.hasUserValue(key):
                resolve = super().getProperty(key, "resolve")
                if resolve:
                    return resolve

        return super().getProperty(key, property_name)

    def setProperty(self, key: str, property_name: str, new_value: Any, target_container: str = "user") -> None:
        container_index = _ContainerIndexes.indexForType(target_container)
        if container_index != -1:
            self._containers[container_index].setProperty(key, property_name, new_value)
        else:
            raise IndexError("Invalid target container {type}".format(type = target_container))

    ##  Overridden from ContainerStack
    @override(ContainerStack)
    def setNextStack(self, next_stack: ContainerStack) -> None:
        raise Exceptions.InvalidOperationError("Global stack cannot have a next stack!")

    ##  Overridden from ContainerStack
    #
    #   Since we have a fixed order of containers in the stack, we want to enforce this.
    @override(ContainerStack)
    def addContainer(self, container: ContainerInterface) -> None:
        raise Exceptions.InvalidOperationError("Cannot add a container to Global stack")

    ##  Overridden from ContainerStack
    @override(ContainerStack)
    def insertContainer(self, index: int, container: ContainerInterface) -> None:
        raise Exceptions.InvalidOperationError("Cannot insert a container into Global stack")

    ##  Overridden from ContainerStack
    @override(ContainerStack)
    def removeContainer(self, index: int) -> None:
        raise Exceptions.InvalidOperationError("Cannot remove a container from Global stack")

    ##  Overridden from ContainerStack
    @override(ContainerStack)
    def replaceContainer(self, index: int, container: ContainerInterface, postpone_emit: bool = False) -> None:
        expected_type = _ContainerIndexes.IndexTypeMap[index]
        if expected_type == "definition":
            if not isinstance(container, DefinitionContainer):
                raise Exceptions.InvalidContainerError("Cannot replace container at index {index} with a container that is not a DefinitionContainer".format(index = index))
        elif container != self._empty_instance_container and container.getMetaDataEntry("type") != expected_type:
            raise Exceptions.InvalidContainerError("Cannot replace container at index {index} with a container that is not of {type} type".format(index = index, type = expected_type))

        super().replaceContainer(index, container, postpone_emit)

    ##  Overridden from ContainerStack
    @override(ContainerStack)
    def deserialize(self, contents: str) -> None:
        super().deserialize(contents)

        new_containers = self._containers.copy()
        print("containers before", new_containers)
        while(len(new_containers) < len(_ContainerIndexes.IndexTypeMap)):
            new_containers.append(self._empty_instance_container)

        # Validate and ensure the list of containers matches with what we expect
        for index, type_name in _ContainerIndexes.IndexTypeMap.items():
            try:
                container = new_containers[index]
            except IndexError:
                container = None

            if type_name == "definition":
                if not container or not isinstance(container, DefinitionContainer):
                    definition = self.findContainer(container_type = DefinitionContainer, category = "*")
                    if not definition:
                        raise InvalidContainerStackError("Stack {id} does not have a definition!".format(id = self._id))

                    new_containers[index] = definition
                continue

            if not container or container.getMetaDataEntry("type") != type_name:
                actual_container = self.findContainer(type = type_name)
                if actual_container:
                    new_containers[index] = actual_container

        print("containers after", new_containers)
        self._containers = new_containers

    def _onContainersChanged(self, container):
        self.pyqtContainersChanged.emit()

## private:
global_stack_mime = MimeType(
    name = "application/x-cura-globalstack",
    comment = "Cura Global Stack",
    suffixes = ["global.cfg"]
)

MimeTypeDatabase.addMimeType(global_stack_mime)
ContainerRegistry.addContainerTypeByName(GlobalStack, "global_stack", global_stack_mime.name)

class _ContainerIndexes:
    UserChanges = 0
    QualityChanges = 1
    Quality = 2
    Material = 3
    Variant = 4
    DefinitionChanges = 5
    Definition = 6

    # Simple hash map to map from index to "type" metadata entry
    IndexTypeMap = {
        UserChanges: "user",
        QualityChanges: "quality_changes",
        Quality: "quality",
        Material: "material",
        Variant: "variant",
        DefinitionChanges: "definition_changes",
        Definition: "definition",
    }

    @classmethod
    def indexForType(cls, type_name: str) -> int:
        for key, value in cls.IndexTypeMap.items():
            if value == type_name:
                return key

        return -1
<commit_msg>Allow setting Definition and setting Definition by ID<commit_after># Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.

from typing import Any

from PyQt5.QtCore import pyqtProperty, pyqtSlot, pyqtSignal

from UM.Decorators import override

from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
from UM.Settings.ContainerStack import ContainerStack, InvalidContainerStackError
from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.DefinitionContainer import DefinitionContainer
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.Interfaces import ContainerInterface

from . import Exceptions

class GlobalStack(ContainerStack):
    def __init__(self, container_id: str, *args, **kwargs):
        super().__init__(container_id, *args, **kwargs)

        self._empty_instance_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()

        self._containers = [self._empty_instance_container for i in range(len(_ContainerIndexes.IndexTypeMap))]
        self._extruders = []

        self.containersChanged.connect(self._onContainersChanged)

    pyqtContainersChanged = pyqtSignal()

    def setUserChanges(self, new_user_changes: InstanceContainer) -> None:
        self.replaceContainer(_ContainerIndexes.UserChanges, new_user_changes)

    @pyqtProperty(InstanceContainer, fset = setUserChanges, notify = pyqtContainersChanged)
    def userChanges(self) -> InstanceContainer:
        return self._containers[_ContainerIndexes.UserChanges]

    def setQualityChanges(self, new_quality_changes: InstanceContainer) -> None:
        self.replaceContainer(_ContainerIndexes.QualityChanges, new_quality_changes)

    def setQualityChangesById(self, new_quality_changes_id: str) -> None:
        quality_changes = ContainerRegistry.getInstance().findInstanceContainers(id = new_quality_changes_id)
        if quality_changes:
            self.setQualityChanges(quality_changes[0])
        else:
            raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_quality_changes_id))

    @pyqtProperty(InstanceContainer, fset = setQualityChanges, notify = pyqtContainersChanged)
    def qualityChanges(self) -> InstanceContainer:
        return self._containers[_ContainerIndexes.QualityChanges]

    def setQuality(self, new_quality: InstanceContainer) -> None:
        self.replaceContainer(_ContainerIndexes.Quality, new_quality)

    def setQualityById(self, new_quality_id: str) -> None:
        quality = ContainerRegistry.getInstance().findInstanceContainers(id = new_quality_id)
        if quality:
            self.setQuality(quality[0])
        else:
            raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_quality_id))

    @pyqtProperty(InstanceContainer, fset = setQuality, notify = pyqtContainersChanged)
    def quality(self) -> InstanceContainer:
        return self._containers[_ContainerIndexes.Quality]

    def setMaterial(self, new_material: InstanceContainer) -> None:
        self.replaceContainer(_ContainerIndexes.Material, new_material)

    def setMaterialById(self, new_material_id: str) -> None:
        material = ContainerRegistry.getInstance().findInstanceContainers(id = new_material_id)
        if material:
            self.setMaterial(material[0])
        else:
            raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_material_id))

    @pyqtProperty(InstanceContainer, fset = setMaterial, notify = pyqtContainersChanged)
    def material(self) -> InstanceContainer:
        return self._containers[_ContainerIndexes.Material]

    def setVariant(self, new_variant: InstanceContainer) -> None:
        self.replaceContainer(_ContainerIndexes.Variant, new_variant)

    def setVariantById(self, new_variant_id: str) -> None:
        variant = ContainerRegistry.getInstance().findInstanceContainers(id = new_variant_id)
        if variant:
            self.setVariant(variant[0])
        else:
            raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_variant_id))

    @pyqtProperty(InstanceContainer, fset = setVariant, notify = pyqtContainersChanged)
    def variant(self) -> InstanceContainer:
        return self._containers[_ContainerIndexes.Variant]

    def setDefinitionChanges(self, new_definition_changes: InstanceContainer) -> None:
        self.replaceContainer(_ContainerIndexes.DefinitionChanges, new_definition_changes)

    def setDefinitionChangesById(self, new_definition_changes_id: str) -> None:
        new_definition_changes = ContainerRegistry.getInstance().findInstanceContainers(id = new_definition_changes_id)
        if new_definition_changes:
            self.setDefinitionChanges(new_definition_changes[0])
        else:
            raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_definition_changes_id))

    @pyqtProperty(InstanceContainer, fset = setDefinitionChanges, notify = pyqtContainersChanged)
    def definitionChanges(self) -> InstanceContainer:
        return self._containers[_ContainerIndexes.DefinitionChanges]

    def setDefinition(self, new_definition: DefinitionContainer) -> None:
        self.replaceContainer(_ContainerIndexes.Definition, new_definition)

    def setDefinitionById(self, new_definition_id: str) -> None:
        new_definition = ContainerRegistry.getInstance().findDefinitionContainers(id = new_definition_id)
        if new_definition:
            self.setDefinition(new_definition[0])
        else:
            raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_definition_id))

    @pyqtProperty(DefinitionContainer, fset = setDefinition, notify = pyqtContainersChanged)
    def definition(self) -> DefinitionContainer:
        return self._containers[_ContainerIndexes.Definition]

    @pyqtProperty("QVariantList")
    def extruders(self) -> list:
        return self._extruders

    def addExtruder(self, extruder):
        extruder_count = self.getProperty("machine_extruder_count", "value")
        if len(self._extruders) + 1 > extruder_count:
            raise Exceptions.TooManyExtrudersError("Tried to add extruder to {id} but its extruder count is {count}".format(id = self.id, count = extruder_count))

        self._extruders.append(extruder)

    ##  Check whether the specified setting has a 'user' value.
    #
    #   A user value here is defined as the setting having a value in either
    #   the UserChanges or QualityChanges container.
    #
    #   \return True if the setting has a user value, False if not.
    @pyqtSlot(str, result = bool)
    def hasUserValue(self, key: str) -> bool:
        if self._containers[_ContainerIndexes.UserChanges].hasProperty(key, "value"):
            return True

        if self._containers[_ContainerIndexes.QualityChanges].hasProperty(key, "value"):
            return True

        return False

    ##  Overridden from ContainerStack
    @override(ContainerStack)
    def getProperty(self, key: str, property_name: str) -> Any:
        if property_name == "value":
            if not self.hasUserValue(key):
                resolve = super().getProperty(key, "resolve")
                if resolve:
                    return resolve

        return super().getProperty(key, property_name)

    def setProperty(self, key: str, property_name: str, new_value: Any, target_container: str = "user") -> None:
        container_index = _ContainerIndexes.indexForType(target_container)
        if container_index != -1:
            self._containers[container_index].setProperty(key, property_name, new_value)
        else:
            raise IndexError("Invalid target container {type}".format(type = target_container))

    ##  Overridden from ContainerStack
    @override(ContainerStack)
    def setNextStack(self, next_stack: ContainerStack) -> None:
        raise Exceptions.InvalidOperationError("Global stack cannot have a next stack!")

    ##  Overridden from ContainerStack
    #
    #   Since we have a fixed order of containers in the stack, we want to enforce this.
    @override(ContainerStack)
    def addContainer(self, container: ContainerInterface) -> None:
        raise Exceptions.InvalidOperationError("Cannot add a container to Global stack")

    ##  Overridden from ContainerStack
    @override(ContainerStack)
    def insertContainer(self, index: int, container: ContainerInterface) -> None:
        raise Exceptions.InvalidOperationError("Cannot insert a container into Global stack")

    ##  Overridden from ContainerStack
    @override(ContainerStack)
    def removeContainer(self, index: int) -> None:
        raise Exceptions.InvalidOperationError("Cannot remove a container from Global stack")

    ##  Overridden from ContainerStack
    @override(ContainerStack)
    def replaceContainer(self, index: int, container: ContainerInterface, postpone_emit: bool = False) -> None:
        expected_type = _ContainerIndexes.IndexTypeMap[index]
        if expected_type == "definition":
            if not isinstance(container, DefinitionContainer):
                raise Exceptions.InvalidContainerError("Cannot replace container at index {index} with a container that is not a DefinitionContainer".format(index = index))
        elif container != self._empty_instance_container and container.getMetaDataEntry("type") != expected_type:
            raise Exceptions.InvalidContainerError("Cannot replace container at index {index} with a container that is not of {type} type".format(index = index, type = expected_type))

        super().replaceContainer(index, container, postpone_emit)

    ##  Overridden from ContainerStack
    @override(ContainerStack)
    def deserialize(self, contents: str) -> None:
        super().deserialize(contents)

        new_containers = self._containers.copy()
        print("containers before", new_containers)
        while(len(new_containers) < len(_ContainerIndexes.IndexTypeMap)):
            new_containers.append(self._empty_instance_container)

        # Validate and ensure the list of containers matches with what we expect
        for index, type_name in _ContainerIndexes.IndexTypeMap.items():
            try:
                container = new_containers[index]
            except IndexError:
                container = None

            if type_name == "definition":
                if not container or not isinstance(container, DefinitionContainer):
                    definition = self.findContainer(container_type = DefinitionContainer, category = "*")
                    if not definition:
                        raise InvalidContainerStackError("Stack {id} does not have a definition!".format(id = self._id))

                    new_containers[index] = definition
                continue

            if not container or container.getMetaDataEntry("type") != type_name:
                actual_container = self.findContainer(type = type_name)
                if actual_container:
                    new_containers[index] = actual_container

        print("containers after", new_containers)
        self._containers = new_containers

    def _onContainersChanged(self, container):
        self.pyqtContainersChanged.emit()

## private:
global_stack_mime = MimeType(
    name = "application/x-cura-globalstack",
    comment = "Cura Global Stack",
    suffixes = ["global.cfg"]
)

MimeTypeDatabase.addMimeType(global_stack_mime)
ContainerRegistry.addContainerTypeByName(GlobalStack, "global_stack", global_stack_mime.name)

class _ContainerIndexes:
    UserChanges = 0
    QualityChanges = 1
    Quality = 2
    Material = 3
    Variant = 4
    DefinitionChanges = 5
    Definition = 6

    # Simple hash map to map from index to "type" metadata entry
    IndexTypeMap = {
        UserChanges: "user",
        QualityChanges: "quality_changes",
        Quality: "quality",
        Material: "material",
        Variant: "variant",
        DefinitionChanges: "definition_changes",
        Definition: "definition",
    }

    @classmethod
    def indexForType(cls, type_name: str) -> int:
        for key, value in cls.IndexTypeMap.items():
            if value == type_name:
                return key

        return -1
<|endoftext|>


<commit_before>import datetime, pytz

from celery.task import periodic_task
from celery.schedules import crontab
from django.conf import settings
from xml.etree import ElementTree

from casexml.apps.case.models import CommCareCase
from casexml.apps.case.mock import CaseBlock
from casexml.apps.case.xml import V2
from corehq.apps.domain.models import Domain
from corehq.apps.groups.models import Group
from corehq.apps.hqcase.utils import submit_case_blocks, get_cases_in_domain
from dimagi.utils.decorators.memoized import memoized

DOMAINS = ["hsph-dev", "hsph-betterbirth"]
PAST_N_DAYS = 21
GROUPS_TO_CHECK = ["cati", "cati-tl"]
GROUP_SHOULD_BE = "fida"
TYPE = "birth"
OWNER_FIELD_MAPPINGS = {
        "cati": "cati_assignment",
        "fida": "field_follow_up_assignment"
    }
INDEXED_GROUPS = dict((domain, {}) for domain in DOMAINS)

@memoized
def indexed_facilities():
    facility_index = {}
    for domain in DOMAINS:
        current_domain_index = {}
        facilities = get_cases_in_domain(domain, type="facility")
        for facility in facilities:
            current_domain_index[facility.facility_id] = {
                "cati": facility.cati_user,
                "fida": facility.fida_user
            }
        facility_index[domain] = current_domain_index
    return facility_index

def get_owner_username(domain, owner_type, facility_id):
    if not owner_type:
        return ''
    facility_index_by_domain = indexed_facilities()
    try:
        return facility_index_by_domain[domain][facility_id][owner_type]
    except KeyError:
        return None

def update_groups_index(domain):
    groups = Group.by_domain(domain)
    for group in groups:
        if group.case_sharing and group.metadata.get("main_user", None):
            INDEXED_GROUPS[domain][group.metadata["main_user"]] = group

def get_group_id(domain, owner_type, facility_id):
    owner_username = get_owner_username(domain, owner_type, facility_id)
    try:
        return INDEXED_GROUPS[domain][owner_username]._id
    except KeyError:
        return None

past_x_date = lambda time_zone, past_x_days: (datetime.datetime.now(time_zone) - datetime.timedelta(past_x_days)).date()
get_none_or_value = lambda _object, _attribute: getattr(_object, _attribute) if (hasattr(_object, _attribute)) else ''

@periodic_task(
    run_every=crontab(minute=1, hour="*/6"),
    queue=getattr(settings, 'CELERY_PERIODIC_QUEUE', 'celery')
)
def new_update_case_properties():
    _domain = Domain.get_by_name(DOMAINS[0])
    if _domain is None:
        return
    time_zone = _domain.default_timezone
    time_zone = pytz.timezone(time_zone)
    past_21_date = past_x_date(time_zone, 21)
    past_42_date = past_x_date(time_zone, 42)
    for domain in DOMAINS:
        update_groups_index(domain)
        case_list = get_cases_in_domain(domain, type=TYPE)
        cases_to_modify = []
        for case in case_list:
            if case.closed:
                continue
            if not get_none_or_value(case, "owner_id") or not get_none_or_value(case, "date_admission") or not get_none_or_value(case, "facility_id"):
                continue
            curr_assignment = get_none_or_value(case, "current_assignment")
            next_assignment = get_none_or_value(case, "next_assignment")
            facility_id = get_none_or_value(case, "facility_id")
            fida_group = get_group_id(domain, "fida", facility_id)
            cati_owner_username = get_owner_username(domain, "cati", facility_id)

            ## Assignment Directly from Registration ##
            # Assign Cases to Call Center
            if case.date_admission >= past_21_date and (not curr_assignment) and (not next_assignment):
                owner_id = get_group_id(domain, "cati", facility_id)
                if not owner_id:
                    continue
                update = {
                    "owner_id": owner_id,
                    "current_assignment": "cati"
                }
                cases_to_modify.append({
                    "case_id": case._id,
                    "update": update,
                    "close": False,
                })
            # Assign Cases Directly To Field
            elif (case.date_admission >= past_42_date) and (case.date_admission < past_21_date) and (not curr_assignment) and (not next_assignment):
                if not fida_group:
                    continue
                update = {
                    "current_assignment": "fida",
                    "owner_id": fida_group,
                    "cati_status": 'skipped',
                }
                cases_to_modify.append(
                    {
                        "case_id": case._id,
                        "update": update,
                        "close": False,
                    }
                )
            # Assign Cases Directly to Lost to Follow Up
            elif case.date_admission < past_42_date and (not curr_assignment) and (not next_assignment):
                update = {
                    "cati_status": 'skipped',
                    "last_assignment": '',
                    "closed_status": "timed_out_lost_to_follow_up",
                }
                cases_to_modify.append(
                    {
                        "case_id": case._id,
                        "update": update,
                        "close": True,
                    }
                )

            ## Assignment from Call Center ##
            # Assign Cases to Field (manually by call center)
            elif (case.date_admission >= past_42_date) and next_assignment == "fida":
                if not cati_owner_username or not fida_group:
                    continue
                update = {
                    "last_cati_user": cati_owner_username,
                    "current_assignment": "fida",
                    "next_assignment": '',
                    "owner_id": fida_group,
                    "cati_status": 'manually_assigned_to_field'
                }
                cases_to_modify.append(
                    {
                        "case_id": case._id,
                        "update": update,
                        "close": False,
                    }
                )
            # Assign cases to field (automatically)
            elif (case.date_admission >= past_42_date) and (case.date_admission < past_21_date) and (curr_assignment == "cati" or curr_assignment == "cati_tl"):
                if not cati_owner_username or not fida_group:
                    continue
                update = {
                    "last_cati_assignment": curr_assignment,
                    "last_cati_user": cati_owner_username,
                    "cati_status": 'timed_out',
                    "current_assignment": "fida",
                    "next_assignment": '',
                    "owner_id": fida_group
                }
                cases_to_modify.append(
                    {
                        "case_id": case._id,
                        "update": update,
                        "close": False,
                    }
                )
            # Assign Cases to Lost to Follow Up
            elif case.date_admission < past_42_date and (curr_assignment == "cati" or curr_assignment == "cati_tl"):
                if not get_owner_username(domain, curr_assignment, facility_id) or not cati_owner_username:
                    continue
                update = {
                    "last_cati_assignment": curr_assignment,
                    "last_cati_user": cati_owner_username,
                    "last_user": get_owner_username(domain, curr_assignment, facility_id),
                    "cati_status": 'timed_out',
                    "last_assignment": curr_assignment,
                    "current_assignment": '',
                    "closed_status": "timed_out_lost_to_follow_up",
                    "next_assignment": ''
                }
                cases_to_modify.append(
                    {
                        "case_id": case._id,
                        "update": update,
                        "close": True,
                    }
                )

            ## Assignment from Field ##
            # Assign Cases to Lost to Follow Up
            elif case.date_admission < past_42_date and (curr_assignment == "fida" or curr_assignment == "fida_tl"):
                if not get_owner_username(domain, curr_assignment, facility_id):
                    continue
                update = {
                    "last_user": get_owner_username(domain, curr_assignment, facility_id),
                    "last_assignment": curr_assignment,
                    "current_assignment": '',
                    "closed_status": "timed_out_lost_to_follow_up",
                    "next_assignment": '',
                }
                cases_to_modify.append(
                    {
                        "case_id": case._id,
                        "update": update,
                        "close": True,
                    }
                )
        case_blocks = [ElementTree.tostring(CaseBlock(
            create=False,
            case_id=case["case_id"],
            update=case["update"],
            close=case["close"],
            version=V2,
            ).as_xml()) for case in cases_to_modify
        ]
        submit_case_blocks(case_blocks, domain)<commit_msg>get cati/fida user data from group than facility-case<commit_after>import datetime, pytz

from celery.task import periodic_task
from celery.schedules import crontab
from django.conf import settings
from xml.etree import ElementTree

from casexml.apps.case.models import CommCareCase
from casexml.apps.case.mock import CaseBlock
from casexml.apps.case.xml import V2
from corehq.apps.domain.models import Domain
from corehq.apps.groups.models import Group
from corehq.apps.hqcase.utils import submit_case_blocks, get_cases_in_domain
from dimagi.utils.decorators.memoized import memoized

DOMAINS = ["hsph-dev", "hsph-betterbirth"]
PAST_N_DAYS = 21
GROUPS_TO_CHECK = ["cati", "cati-tl"]
GROUP_SHOULD_BE = "fida"
TYPE = "birth"
OWNER_FIELD_MAPPINGS = {
        "cati": "cati_assignment",
        "fida": "field_follow_up_assignment"
    }
INDEXED_GROUPS = dict((domain, {}) for domain in DOMAINS)
GROUPS_BY_ID = dict((domain, {}) for domain in DOMAINS)

@memoized
def indexed_facilities():
    facility_index = {}
    for domain in DOMAINS:
        current_domain_index = {}
        facilities = get_cases_in_domain(domain, type="facility")
        for facility in facilities:
            case_sharing_group = GROUPS_BY_ID[domain].get(facility.owner_id, None)
            if case_sharing_group is None:
                continue
            cati_user = case_sharing_group.metadata.get('cati_user', None)
            fida_user = case_sharing_group.metadata.get('fida_user', None)
            current_domain_index[facility.facility_id] = {
                "cati": cati_user,
                "fida": fida_user
            }
        facility_index[domain] = current_domain_index
    return facility_index

def get_owner_username(domain, owner_type, facility_id):
    if not owner_type:
        return ''
    facility_index_by_domain = indexed_facilities()
    try:
        return facility_index_by_domain[domain][facility_id][owner_type]
    except KeyError:
        return None

def update_groups_index(domain):
    groups = Group.by_domain(domain)
    for group in groups:
        GROUPS_BY_ID[domain][group._id] = group
        if group.case_sharing and group.metadata.get("main_user", None):
            INDEXED_GROUPS[domain][group.metadata["main_user"]] = group

def get_group_id(domain, owner_type, facility_id):
    owner_username = get_owner_username(domain, owner_type, facility_id)
    try:
        return INDEXED_GROUPS[domain][owner_username]._id
    except KeyError:
        return None

past_x_date = lambda time_zone, past_x_days: (datetime.datetime.now(time_zone) - datetime.timedelta(past_x_days)).date()
get_none_or_value = lambda _object, _attribute: getattr(_object, _attribute) if (hasattr(_object, _attribute)) else ''

@periodic_task(
    run_every=crontab(minute=1, hour="*/6"),
    queue=getattr(settings, 'CELERY_PERIODIC_QUEUE', 'celery')
)
def new_update_case_properties():
    _domain = Domain.get_by_name(DOMAINS[0])
    if _domain is None:
        return
    time_zone = _domain.default_timezone
    time_zone = pytz.timezone(time_zone)
    past_21_date = past_x_date(time_zone, 21)
    past_42_date = past_x_date(time_zone, 42)
    for domain in DOMAINS:
        update_groups_index(domain)
        case_list = get_cases_in_domain(domain, type=TYPE)
        cases_to_modify = []
        for case in case_list:
            if case.closed:
                continue
            if not get_none_or_value(case, "owner_id") or not get_none_or_value(case, "date_admission") or not get_none_or_value(case, "facility_id"):
                continue
            curr_assignment = get_none_or_value(case, "current_assignment")
            next_assignment = get_none_or_value(case, "next_assignment")
            facility_id = get_none_or_value(case, "facility_id")
            fida_group = get_group_id(domain, "fida", facility_id)
            cati_owner_username = get_owner_username(domain, "cati", facility_id)

            ## Assignment Directly from Registration ##
            # Assign Cases to Call Center
            if case.date_admission >= past_21_date and (not curr_assignment) and (not next_assignment):
                owner_id = get_group_id(domain, "cati", facility_id)
                if not owner_id:
                    continue
                update = {
                    "owner_id": owner_id,
                    "current_assignment": "cati"
                }
                cases_to_modify.append({
                    "case_id": case._id,
                    "update": update,
                    "close": False,
                })
            # Assign Cases Directly To Field
            elif (case.date_admission >= past_42_date) and (case.date_admission < past_21_date) and (not curr_assignment) and (not next_assignment):
                if not fida_group:
                    continue
                update = {
                    "current_assignment": "fida",
                    "owner_id": fida_group,
                    "cati_status": 'skipped',
                }
                cases_to_modify.append(
                    {
                        "case_id": case._id,
                        "update": update,
                        "close": False,
                    }
                )
            # Assign Cases Directly to Lost to Follow Up
            elif case.date_admission < past_42_date and (not curr_assignment) and (not next_assignment):
                update = {
                    "cati_status": 'skipped',
                    "last_assignment": '',
                    "closed_status": "timed_out_lost_to_follow_up",
                }
                cases_to_modify.append(
                    {
                        "case_id": case._id,
                        "update": update,
                        "close": True,
                    }
                )

            ## Assignment from Call Center ##
            # Assign Cases to Field (manually by call center)
            elif (case.date_admission >= past_42_date) and next_assignment == "fida":
                if not cati_owner_username or not fida_group:
                    continue
                update = {
                    "last_cati_user": cati_owner_username,
                    "current_assignment": "fida",
                    "next_assignment": '',
                    "owner_id": fida_group,
                    "cati_status": 'manually_assigned_to_field'
                }
                cases_to_modify.append(
                    {
                        "case_id": case._id,
                        "update": update,
                        "close": False,
                    }
                )
            # Assign cases to field (automatically)
            elif (case.date_admission >= past_42_date) and (case.date_admission < past_21_date) and (curr_assignment == "cati" or curr_assignment == "cati_tl"):
                if not cati_owner_username or not fida_group:
                    continue
                update = {
                    "last_cati_assignment": curr_assignment,
                    "last_cati_user": cati_owner_username,
                    "cati_status": 'timed_out',
                    "current_assignment": "fida",
                    "next_assignment": '',
                    "owner_id": fida_group
                }
                cases_to_modify.append(
                    {
                        "case_id": case._id,
                        "update": update,
                        "close": False,
                    }
                )
            # Assign Cases to Lost to Follow Up
            elif case.date_admission < past_42_date and (curr_assignment == "cati" or curr_assignment == "cati_tl"):
                if not get_owner_username(domain, curr_assignment, facility_id) or not cati_owner_username:
                    continue
                update = {
                    "last_cati_assignment": curr_assignment,
                    "last_cati_user": cati_owner_username,
                    "last_user": get_owner_username(domain, curr_assignment, facility_id),
                    "cati_status": 'timed_out',
                    "last_assignment": curr_assignment,
                    "current_assignment": '',
                    "closed_status": "timed_out_lost_to_follow_up",
                    "next_assignment": ''
                }
                cases_to_modify.append(
                    {
                        "case_id": case._id,
                        "update": update,
                        "close": True,
                    }
                )

            ## Assignment from Field ##
            # Assign Cases to Lost to Follow Up
            elif case.date_admission < past_42_date and (curr_assignment == "fida" or curr_assignment == "fida_tl"):
                if not get_owner_username(domain, curr_assignment, facility_id):
                    continue
                update = {
                    "last_user": get_owner_username(domain, curr_assignment, facility_id),
                    "last_assignment": curr_assignment,
                    "current_assignment": '',
                    "closed_status": "timed_out_lost_to_follow_up",
                    "next_assignment": '',
                }
                cases_to_modify.append(
                    {
                        "case_id": case._id,
                        "update": update,
                        "close": True,
                    }
                )
        case_blocks = [ElementTree.tostring(CaseBlock(
            create=False,
            case_id=case["case_id"],
            update=case["update"],
            close=case["close"],
            version=V2,
            ).as_xml()) for case in cases_to_modify
        ]
        submit_case_blocks(case_blocks, domain)<|endoftext|>