<commit_before>from datetime import datetime
import logging
from couchdbkit.exceptions import ResourceConflict
from django.conf import settings
from django.db import models
from couchdbkit.ext.django.schema import Document, StringProperty,\
    BooleanProperty, DateTimeProperty, IntegerProperty, DocumentSchema, SchemaProperty
from dimagi.utils.timezones import fields as tz_fields
from dimagi.utils.couch.database import get_db
from itertools import chain
from langcodes import langs as all_langs
from collections import defaultdict
from copy import deepcopy

lang_lookup = defaultdict(str)

for lang in all_langs:
    lang_lookup[lang['three']] = lang['names'][0] # arbitrarily using the first name if there are multiple
    if lang['two'] != '':
        lang_lookup[lang['two']] = lang['names'][0]

class DomainMigrations(DocumentSchema):
    has_migrated_permissions = BooleanProperty(default=False)

    def apply(self, domain):
        if not self.has_migrated_permissions:
            logging.info("Applying permissions migration to domain %s" % domain.name)
            from corehq.apps.users.models import UserRole, WebUser
            UserRole.init_domain_with_presets(domain.name)
            for web_user in WebUser.by_domain(domain.name):
                try:
                    web_user.save()
                except ResourceConflict:
                    # web_user has already been saved by another thread in the last few seconds
                    pass

            self.has_migrated_permissions = True
            domain.save()

class Domain(Document):
    """Domain is the highest level collection of people/stuff
       in the system.  Pretty much everything happens at the
       domain-level, including user membership, permission to
       see data, reports, charts, etc."""

    name = StringProperty()
    is_active = BooleanProperty()
    is_public = BooleanProperty(default=False)
    date_created = DateTimeProperty()
    default_timezone = StringProperty(default=getattr(settings, "TIME_ZONE", "UTC"))
    case_sharing = BooleanProperty(default=False)
    organization = StringProperty()

    # domain metadata
    city = StringProperty()
    country = StringProperty()
    region = StringProperty() # e.g. US, LAC, SA, Sub-saharn Africa, East Africa, West Africa, Southeast Asia)
    project_type = StringProperty() # e.g. MCH, HIV
    customer_type = StringProperty() # plus, full, etc.
    is_test = BooleanProperty(default=False)
    description = StringProperty()
    is_shared = BooleanProperty(default=False)

    # App Store/domain copying stuff
    original_doc = StringProperty()
    is_snapshot = BooleanProperty(default=False)
    snapshot_time = DateTimeProperty()

    migrations = SchemaProperty(DomainMigrations)

    _dirty_fields = ()

    @classmethod
    def wrap(cls, data):
        self = super(Domain, cls).wrap(data)
        if self.get_id:
            self.apply_migrations()
        return self

    @staticmethod
    def active_for_user(user, is_active=True):
        if not hasattr(user,'get_profile'):
            # this had better be an anonymous user
            return []
        from corehq.apps.users.models import CouchUser
        couch_user = CouchUser.from_django_user(user)
        if couch_user:
            domain_names = couch_user.get_domains()
            def log(json):
                doc = json['doc']
                return Domain.wrap(doc)
            return Domain.view("domain/by_status",
                                    keys=[[is_active, d] for d in domain_names],
                                    reduce=False,
                                    wrapper=log,
                                    include_docs=True).all()
        else:
            return []

    @classmethod
    def categories(cls, prefix=''):
        # unichr(0xfff8) is something close to the highest character available
        return [d['key'] for d in cls.view("domain/categories_by_prefix",
                                group=True,
                                startkey=prefix,
                                endkey="%s%c" % (prefix, unichr(0xfff8))).all()]

    def apply_migrations(self):
        self.migrations.apply(self)

    @staticmethod
    def all_for_user(user):
        if not hasattr(user,'get_profile'):
            # this had better be an anonymous user
            return []
        from corehq.apps.users.models import CouchUser
        couch_user = CouchUser.from_django_user(user)
        if couch_user:
            domain_names = couch_user.get_domains()
            return Domain.view("domain/domains",
                                    keys=domain_names,
                                    reduce=False,
                                    include_docs=True).all()
        else:
            return []

    def add(self, model_instance, is_active=True):
        """
        Add something to this domain, through the generic relation.
        Returns the created membership object
        """
        # Add membership info to Couch
        couch_user = model_instance.get_profile().get_couch_user()
        couch_user.add_domain_membership(self.name)
        couch_user.save()

    def applications(self):
        return ApplicationBase.view('app_manager/applications_brief',
                                    startkey=[self.name],
                                    endkey=[self.name, {}]).all()

    def languages(self):
        apps = self.applications()
        return set(chain.from_iterable([a.langs for a in apps]))

    def readable_languages(self):
        return ', '.join(lang_lookup[lang] or lang for lang in self.languages())

    def __unicode__(self):
        return self.name

    @classmethod
    def get_by_name(cls, name):
        result = cls.view("domain/domains",
                            key=name,
                            reduce=False,
                            include_docs=True).first()
        return result

    @classmethod
    def get_by_organization(cls, organization):
        result = cls.view("domain/by_organization",
            key=organization,
            reduce=False,
            include_docs=True)
        return result

    @classmethod
    def get_or_create_with_name(cls, name, is_active=False):
        result = cls.view("domain/domains",
            key=name,
            reduce=False,
            include_docs=True).first()
        if result:
            return result
        else:
            new_domain = Domain(name=name,
                            is_active=is_active,
                            date_created=datetime.utcnow())
            new_domain.save()
            return new_domain

    @classmethod
    def get_all(cls):
        return Domain.view("domain/domains",
                            reduce=False,
                            include_docs=True).all()

    def save_copy(self, new_domain_name=None, user=None):
        if new_domain_name is not None and Domain.get_by_name(new_domain_name):
            return None
        db = get_db()

        str_to_cls = {
            'UserRole': UserRole,
            'Application': Application,
            'RemoteApp': RemoteApp,
            }

        new_id = db.copy_doc(self.get_id)['id']
        if new_domain_name is None:
            new_domain_name = new_id
        new_domain = Domain.get(new_id)
        new_domain.name = new_domain_name
        new_domain.original_doc = self.name
        new_domain.is_snapshot = False
        new_domain.snapshot_time = None

        for field in self._dirty_fields:
            if hasattr(new_domain, field):
                delattr(new_domain, field)

        if user:
            user.add_domain_membership(new_domain_name)
            user.save()

        for res in db.view('domain/related_to_domain', key=self.name):
            json = res['value']
            doc_type = json['doc_type']
            cls = str_to_cls[doc_type]
            new_id = db.copy_doc(json['_id'])['id']

            print doc_type, new_id, json['_id']

            new_doc = cls.get(new_id)
            for field in self._dirty_fields:
                if hasattr(new_doc, field):
                    delattr(new_doc, field)

            if hasattr(cls, '_meta_fields'):
                for field in cls._meta_fields:
                    if not field.startswith('_') and hasattr(new_doc, field):
                        delattr(new_doc, field)

            new_doc.original_doc = json['_id']
            new_doc.domain = new_domain_name

            new_doc.save()

        new_domain.save()
        return new_domain

    def save_snapshot(self):
        if self.is_snapshot:
            return self
        else:
            copy = self.save_copy()
            if copy is None:
                return None
            copy.is_snapshot = True
            copy.snapshot_time = datetime.now()
            copy.save()
            return copy

    def snapshot_of(self):
        if is_snapshot:
            return Domain.get_by_name(self.copy_of)
        else:
            return None

    def snapshots(self):
        return Domain.view('domain/snapshots', key=self.name)

    def __str__(self):
        return self.name

##############################################################################################################
#
# Originally had my own hacky global storage of content type, but it turns out that contenttype.models
# wisely caches content types! No hit to the db beyond the first call - no need for us to do our own
# custom caching.
#
# See ContentType.get_for_model() code for details.

class OldDomain(models.Model):
    """Domain is the highest level collection of people/stuff
       in the system.  Pretty much everything happens at the
       domain-level, including user membership, permission to
       see data, reports, charts, etc."""

    name  = models.CharField(max_length = 64, unique=True)
    is_active = models.BooleanField(default=False)
    timezone = tz_fields.TimeZoneField()
    #description = models.CharField(max_length=255, null=True, blank=True)
    #timezone = models.CharField(max_length=64,null=True)

    class Meta():
        db_table = "domain_domain"

    # Utility function - gets active domains in which user has an active membership
    # Note that User.is_active is not checked here - we're only concerned about usable
    # domains in which the user can theoretically participate, not whether the user
    # is cleared to login.

    @staticmethod
    def active_for_user(user):
        if not hasattr(user,'get_profile'):
            # this had better be an anonymous user
            return OldDomain.objects.none()
        from corehq.apps.users.models import CouchUser
        couch_user = CouchUser.from_django_user(user)
        if couch_user:
            domain_names = couch_user.get_domains()
            return OldDomain.objects.filter(name__in=domain_names, is_active=True)
        else:
            return OldDomain.objects.none()

    @staticmethod
    def all_for_user(user):
        if not hasattr(user,'get_profile'):
            # this had better be an anonymous user
            return OldDomain.objects.none()
        from corehq.apps.users.models import CouchUser
        couch_user = CouchUser.from_django_user(user)
        if couch_user:
            domain_names = couch_user.get_domains()
            return OldDomain.objects.filter(name__in=domain_names)
        else:
            return OldDomain.objects.none()

    def add(self, model_instance, is_active=True):
        """
        Add something to this domain, through the generic relation.
        Returns the created membership object
        """
        # Add membership info to Couch
        couch_user = model_instance.get_profile().get_couch_user()
        couch_user.add_domain_membership(self.name)
        couch_user.save()

    def __unicode__(self):
        return self.name

# added after Domain is defined as per http://stackoverflow.com/questions/7199466/how-to-break-import-loop-in-python
# to prevent import loop errors (since corehq.apps.app_manager.models has to import Domain back)
from corehq.apps.app_manager.models import ApplicationBase, import_app, RemoteApp, Application
from corehq.apps.users.models import UserRole
<commit_msg>Fixing duplicate project problem<commit_after>from datetime import datetime
import logging
from couchdbkit.exceptions import ResourceConflict
from django.conf import settings
from django.db import models
from couchdbkit.ext.django.schema import Document, StringProperty,\
    BooleanProperty, DateTimeProperty, IntegerProperty, DocumentSchema, SchemaProperty
from dimagi.utils.timezones import fields as tz_fields
from dimagi.utils.couch.database import get_db
from itertools import chain
from langcodes import langs as all_langs
from collections import defaultdict
from copy import deepcopy

lang_lookup = defaultdict(str)

for lang in all_langs:
    lang_lookup[lang['three']] = lang['names'][0] # arbitrarily using the first name if there are multiple
    if lang['two'] != '':
        lang_lookup[lang['two']] = lang['names'][0]

class DomainMigrations(DocumentSchema):
    has_migrated_permissions = BooleanProperty(default=False)

    def apply(self, domain):
        if not self.has_migrated_permissions:
            logging.info("Applying permissions migration to domain %s" % domain.name)
            from corehq.apps.users.models import UserRole, WebUser
            UserRole.init_domain_with_presets(domain.name)
            for web_user in WebUser.by_domain(domain.name):
                try:
                    web_user.save()
                except ResourceConflict:
                    # web_user has already been saved by another thread in the last few seconds
                    pass

            self.has_migrated_permissions = True
            domain.save()

class Domain(Document):
    """Domain is the highest level collection of people/stuff
       in the system.  Pretty much everything happens at the
       domain-level, including user membership, permission to
       see data, reports, charts, etc."""

    name = StringProperty()
    is_active = BooleanProperty()
    is_public = BooleanProperty(default=False)
    date_created = DateTimeProperty()
    default_timezone = StringProperty(default=getattr(settings, "TIME_ZONE", "UTC"))
    case_sharing = BooleanProperty(default=False)
    organization = StringProperty()

    # domain metadata
    city = StringProperty()
    country = StringProperty()
    region = StringProperty() # e.g. US, LAC, SA, Sub-saharn Africa, East Africa, West Africa, Southeast Asia)
    project_type = StringProperty() # e.g. MCH, HIV
    customer_type = StringProperty() # plus, full, etc.
    is_test = BooleanProperty(default=False)
    description = StringProperty()
    is_shared = BooleanProperty(default=False)

    # App Store/domain copying stuff
    original_doc = StringProperty()
    is_snapshot = BooleanProperty(default=False)
    snapshot_time = DateTimeProperty()

    migrations = SchemaProperty(DomainMigrations)

    _dirty_fields = ()

    @classmethod
    def wrap(cls, data):
        self = super(Domain, cls).wrap(data)
        if self.get_id:
            self.apply_migrations()
        return self

    @staticmethod
    def active_for_user(user, is_active=True):
        if not hasattr(user,'get_profile'):
            # this had better be an anonymous user
            return []
        from corehq.apps.users.models import CouchUser
        couch_user = CouchUser.from_django_user(user)
        if couch_user:
            domain_names = couch_user.get_domains()
            def log(json):
                doc = json['doc']
                return Domain.wrap(doc)
            return Domain.view("domain/by_status",
                                    keys=[[is_active, d] for d in domain_names],
                                    reduce=False,
                                    wrapper=log,
                                    include_docs=True).all()
        else:
            return []

    @classmethod
    def categories(cls, prefix=''):
        # unichr(0xfff8) is something close to the highest character available
        return [d['key'] for d in cls.view("domain/categories_by_prefix",
                                group=True,
                                startkey=prefix,
                                endkey="%s%c" % (prefix, unichr(0xfff8))).all()]

    def apply_migrations(self):
        self.migrations.apply(self)

    @staticmethod
    def all_for_user(user):
        if not hasattr(user,'get_profile'):
            # this had better be an anonymous user
            return []
        from corehq.apps.users.models import CouchUser
        couch_user = CouchUser.from_django_user(user)
        if couch_user:
            domain_names = couch_user.get_domains()
            return Domain.view("domain/domains",
                                    keys=domain_names,
                                    reduce=False,
                                    include_docs=True).all()
        else:
            return []

    def add(self, model_instance, is_active=True):
        """
        Add something to this domain, through the generic relation.
        Returns the created membership object
        """
        # Add membership info to Couch
        couch_user = model_instance.get_profile().get_couch_user()
        couch_user.add_domain_membership(self.name)
        couch_user.save()

    def applications(self):
        return ApplicationBase.view('app_manager/applications_brief',
                                    startkey=[self.name],
                                    endkey=[self.name, {}]).all()

    def languages(self):
        apps = self.applications()
        return set(chain.from_iterable([a.langs for a in apps]))

    def readable_languages(self):
        return ', '.join(lang_lookup[lang] or lang for lang in self.languages())

    def __unicode__(self):
        return self.name

    @classmethod
    def get_by_name(cls, name):
        result = cls.view("domain/domains",
                            key=name,
                            reduce=False,
                            include_docs=True).first()
        return result

    @classmethod
    def get_by_organization(cls, organization):
        result = cls.view("domain/by_organization",
            key=organization,
            reduce=False,
            include_docs=True)
        return result

    @classmethod
    def get_or_create_with_name(cls, name, is_active=False):
        result = cls.view("domain/domains",
            key=name,
            reduce=False,
            include_docs=True).first()
        if result:
            return result
        else:
            new_domain = Domain(name=name,
                            is_active=is_active,
                            date_created=datetime.utcnow())
            new_domain.save()
            return new_domain

    @classmethod
    def get_all(cls):
        return Domain.view("domain/domains",
                            reduce=False,
                            include_docs=True).all()

    def save_copy(self, new_domain_name=None, user=None):
        if new_domain_name is not None and Domain.get_by_name(new_domain_name):
            return None
        db = get_db()

        str_to_cls = {
            'UserRole': UserRole,
            'Application': Application,
            'RemoteApp': RemoteApp,
            }

        new_id = db.copy_doc(self.get_id)['id']
        if new_domain_name is None:
            new_domain_name = new_id
        new_domain = Domain.get(new_id)
        new_domain.name = new_domain_name
        new_domain.original_doc = self.name
        new_domain.is_snapshot = False
        new_domain.snapshot_time = None

        for field in self._dirty_fields:
            if hasattr(new_domain, field):
                delattr(new_domain, field)

        for res in db.view('domain/related_to_domain', key=self.name):
            json = res['value']
            doc_type = json['doc_type']
            cls = str_to_cls[doc_type]
            new_id = db.copy_doc(json['_id'])['id']

            print doc_type, new_id, json['_id']

            new_doc = cls.get(new_id)
            for field in self._dirty_fields:
                if hasattr(new_doc, field):
                    delattr(new_doc, field)

            if hasattr(cls, '_meta_fields'):
                for field in cls._meta_fields:
                    if not field.startswith('_') and hasattr(new_doc, field):
                        delattr(new_doc, field)

            new_doc.original_doc = json['_id']
            new_doc.domain = new_domain_name

            new_doc.save()

        new_domain.save()

        if user:
            user.add_domain_membership(new_domain_name)
            user.save()

        return new_domain

    def save_snapshot(self):
        if self.is_snapshot:
            return self
        else:
            copy = self.save_copy()
            if copy is None:
                return None
            copy.is_snapshot = True
            copy.snapshot_time = datetime.now()
            copy.save()
            return copy

    def snapshot_of(self):
        if is_snapshot:
            return Domain.get_by_name(self.copy_of)
        else:
            return None

    def snapshots(self):
        return Domain.view('domain/snapshots', key=self.name)

    def __str__(self):
        return self.name

##############################################################################################################
#
# Originally had my own hacky global storage of content type, but it turns out that contenttype.models
# wisely caches content types! No hit to the db beyond the first call - no need for us to do our own
# custom caching.
#
# See ContentType.get_for_model() code for details.

class OldDomain(models.Model):
    """Domain is the highest level collection of people/stuff
       in the system.  Pretty much everything happens at the
       domain-level, including user membership, permission to
       see data, reports, charts, etc."""

    name  = models.CharField(max_length = 64, unique=True)
    is_active = models.BooleanField(default=False)
    timezone = tz_fields.TimeZoneField()
    #description = models.CharField(max_length=255, null=True, blank=True)
    #timezone = models.CharField(max_length=64,null=True)

    class Meta():
        db_table = "domain_domain"

    # Utility function - gets active domains in which user has an active membership
    # Note that User.is_active is not checked here - we're only concerned about usable
    # domains in which the user can theoretically participate, not whether the user
    # is cleared to login.

    @staticmethod
    def active_for_user(user):
        if not hasattr(user,'get_profile'):
            # this had better be an anonymous user
            return OldDomain.objects.none()
        from corehq.apps.users.models import CouchUser
        couch_user = CouchUser.from_django_user(user)
        if couch_user:
            domain_names = couch_user.get_domains()
            return OldDomain.objects.filter(name__in=domain_names, is_active=True)
        else:
            return OldDomain.objects.none()

    @staticmethod
    def all_for_user(user):
        if not hasattr(user,'get_profile'):
            # this had better be an anonymous user
            return OldDomain.objects.none()
        from corehq.apps.users.models import CouchUser
        couch_user = CouchUser.from_django_user(user)
        if couch_user:
            domain_names = couch_user.get_domains()
            return OldDomain.objects.filter(name__in=domain_names)
        else:
            return OldDomain.objects.none()

    def add(self, model_instance, is_active=True):
        """
        Add something to this domain, through the generic relation.
        Returns the created membership object
        """
        # Add membership info to Couch
        couch_user = model_instance.get_profile().get_couch_user()
        couch_user.add_domain_membership(self.name)
        couch_user.save()

    def __unicode__(self):
        return self.name

# added after Domain is defined as per http://stackoverflow.com/questions/7199466/how-to-break-import-loop-in-python
# to prevent import loop errors (since corehq.apps.app_manager.models has to import Domain back)
from corehq.apps.app_manager.models import ApplicationBase, import_app, RemoteApp, Application
from corehq.apps.users.models import UserRole
<|endoftext|>


<commit_before># Copyright (c) 2007 The Hewlett-Packard Development Company
# All rights reserved.
#
# Redistribution and use of this software in source and binary forms,
# with or without modification, are permitted provided that the
# following conditions are met:
#
# The software must be used only for Non-Commercial Use which means any
# use which is NOT directed to receiving any direct monetary
# compensation for, or commercial advantage from such use.  Illustrative
# examples of non-commercial use are academic research, personal study,
# teaching, education and corporate research & development.
# Illustrative examples of commercial use are distributing products for
# commercial advantage and providing services using the software for
# commercial advantage.
#
# If you wish to use this software or functionality therein that may be
# covered by patents for commercial use, please contact:
#     Director of Intellectual Property Licensing
#     Office of Strategy and Technology
#     Hewlett-Packard Company
#     1501 Page Mill Road
#     Palo Alto, California  94304
#
# Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.  Redistributions
# in binary form must reproduce the above copyright notice, this list of
# conditions and the following disclaimer in the documentation and/or
# other materials provided with the distribution.  Neither the name of
# the COPYRIGHT HOLDER(s), HEWLETT-PACKARD COMPANY, nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.  No right of
# sublicense is granted herewith.  Derivatives of the software and
# output created using the software may be prepared, but only for
# Non-Commercial Uses.  Derivatives of the software may be shared with
# others provided: (i) the others agree to abide by the list of
# conditions herein which includes the Non-Commercial Use restrictions;
# and (ii) such Derivatives of the software include the above copyright
# notice to acknowledge the contribution from this software where
# applicable, this list of conditions and the disclaimer below.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# Authors: Gabe Black

microcode = '''
def macroop POP_R {
    # Make the default data size of pops 64 bits in 64 bit mode
    .adjust_env oszIn64Override

    ld reg, ss, [0, t0, rsp]
    addi rsp, rsp, dsz
};

def macroop POP_M {
    # Make the default data size of pops 64 bits in 64 bit mode
    .adjust_env oszIn64Override

    ld t1, ss, [0, t0, rsp]
    addi rsp, rsp, dsz
    st t1, ds, [scale, index, base], disp
};

def macroop POP_P {
    # Make the default data size of pops 64 bits in 64 bit mode
    .adjust_env oszIn64Override

    rdip t7
    ld t1, ss, [0, t0, rsp]
    addi rsp, rsp, dsz
    st t1, ds, [0, t0, t7]
};

def macroop PUSH_R {
    # Make the default data size of pops 64 bits in 64 bit mode
    .adjust_env oszIn64Override

    # This needs to work slightly differently from the other versions of push
    # because the -original- version of the stack pointer is what gets pushed
    st reg, ss, [0, t0, rsp], "-env.dataSize"
    subi rsp, rsp, dsz
};

def macroop PUSH_I {
    # Make the default data size of pops 64 bits in 64 bit mode
    .adjust_env oszIn64Override

    limm t1, imm
    subi rsp, rsp, dsz
    st t1, ss, [0, t0, rsp]
};

def macroop PUSH_M {
    # Make the default data size of pops 64 bits in 64 bit mode
    .adjust_env oszIn64Override

    ld t1, ds, [scale, index, base], disp
    subi rsp, rsp, dsz
    st t1, ss, [0, t0, rsp]
};

def macroop PUSH_P {
    # Make the default data size of pops 64 bits in 64 bit mode
    .adjust_env oszIn64Override

    rdip t7
    ld t1, ds, [0, t0, t7], disp
    subi rsp, rsp, dsz
    st t1, ss, [0, t0, rsp]
};

def macroop PUSHA {
    st rax, ss, [0, t0, rsp], "-0 * env.dataSize"
    st rcx, ss, [0, t0, rsp], "-1 * env.dataSize"
    st rdx, ss, [0, t0, rsp], "-2 * env.dataSize"
    st rbx, ss, [0, t0, rsp], "-3 * env.dataSize"
    st rsp, ss, [0, t0, rsp], "-4 * env.dataSize"
    st rbp, ss, [0, t0, rsp], "-5 * env.dataSize"
    st rsi, ss, [0, t0, rsp], "-6 * env.dataSize"
    st rdi, ss, [0, t0, rsp], "-7 * env.dataSize"
    subi rsp, rsp, "8 * env.dataSize"
};

def macroop POPA {
    ld rdi, ss, [0, t0, rsp], "0 * env.dataSize"
    ld rsi, ss, [0, t0, rsp], "1 * env.dataSize"
    ld rbp, ss, [0, t0, rsp], "2 * env.dataSize"
    ld rbx, ss, [0, t0, rsp], "4 * env.dataSize"
    ld rdx, ss, [0, t0, rsp], "5 * env.dataSize"
    ld rcx, ss, [0, t0, rsp], "6 * env.dataSize"
    ld rax, ss, [0, t0, rsp], "7 * env.dataSize"
    addi rsp, rsp, "8 * env.dataSize"
};
'''
#let {{
#    class ENTER(Inst):
#	"GenFault ${new UnimpInstFault}"
#    class LEAVE(Inst):
#	"GenFault ${new UnimpInstFault}"
#}};
<commit_msg>X86: Implement LEAVE<commit_after># Copyright (c) 2007 The Hewlett-Packard Development Company
# All rights reserved.
#
# Redistribution and use of this software in source and binary forms,
# with or without modification, are permitted provided that the
# following conditions are met:
#
# The software must be used only for Non-Commercial Use which means any
# use which is NOT directed to receiving any direct monetary
# compensation for, or commercial advantage from such use.  Illustrative
# examples of non-commercial use are academic research, personal study,
# teaching, education and corporate research & development.
# Illustrative examples of commercial use are distributing products for
# commercial advantage and providing services using the software for
# commercial advantage.
#
# If you wish to use this software or functionality therein that may be
# covered by patents for commercial use, please contact:
#     Director of Intellectual Property Licensing
#     Office of Strategy and Technology
#     Hewlett-Packard Company
#     1501 Page Mill Road
#     Palo Alto, California  94304
#
# Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.  Redistributions
# in binary form must reproduce the above copyright notice, this list of
# conditions and the following disclaimer in the documentation and/or
# other materials provided with the distribution.  Neither the name of
# the COPYRIGHT HOLDER(s), HEWLETT-PACKARD COMPANY, nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.  No right of
# sublicense is granted herewith.  Derivatives of the software and
# output created using the software may be prepared, but only for
# Non-Commercial Uses.  Derivatives of the software may be shared with
# others provided: (i) the others agree to abide by the list of
# conditions herein which includes the Non-Commercial Use restrictions;
# and (ii) such Derivatives of the software include the above copyright
# notice to acknowledge the contribution from this software where
# applicable, this list of conditions and the disclaimer below.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# Authors: Gabe Black

microcode = '''
def macroop POP_R {
    # Make the default data size of pops 64 bits in 64 bit mode
    .adjust_env oszIn64Override

    ld reg, ss, [0, t0, rsp]
    addi rsp, rsp, dsz
};

def macroop POP_M {
    # Make the default data size of pops 64 bits in 64 bit mode
    .adjust_env oszIn64Override

    ld t1, ss, [0, t0, rsp]
    addi rsp, rsp, dsz
    st t1, ds, [scale, index, base], disp
};

def macroop POP_P {
    # Make the default data size of pops 64 bits in 64 bit mode
    .adjust_env oszIn64Override

    rdip t7
    ld t1, ss, [0, t0, rsp]
    addi rsp, rsp, dsz
    st t1, ds, [0, t0, t7]
};

def macroop PUSH_R {
    # Make the default data size of pops 64 bits in 64 bit mode
    .adjust_env oszIn64Override

    # This needs to work slightly differently from the other versions of push
    # because the -original- version of the stack pointer is what gets pushed
    st reg, ss, [0, t0, rsp], "-env.dataSize"
    subi rsp, rsp, dsz
};

def macroop PUSH_I {
    # Make the default data size of pops 64 bits in 64 bit mode
    .adjust_env oszIn64Override

    limm t1, imm
    subi rsp, rsp, dsz
    st t1, ss, [0, t0, rsp]
};

def macroop PUSH_M {
    # Make the default data size of pops 64 bits in 64 bit mode
    .adjust_env oszIn64Override

    ld t1, ds, [scale, index, base], disp
    subi rsp, rsp, dsz
    st t1, ss, [0, t0, rsp]
};

def macroop PUSH_P {
    # Make the default data size of pops 64 bits in 64 bit mode
    .adjust_env oszIn64Override

    rdip t7
    ld t1, ds, [0, t0, t7], disp
    subi rsp, rsp, dsz
    st t1, ss, [0, t0, rsp]
};

def macroop PUSHA {
    st rax, ss, [0, t0, rsp], "-0 * env.dataSize"
    st rcx, ss, [0, t0, rsp], "-1 * env.dataSize"
    st rdx, ss, [0, t0, rsp], "-2 * env.dataSize"
    st rbx, ss, [0, t0, rsp], "-3 * env.dataSize"
    st rsp, ss, [0, t0, rsp], "-4 * env.dataSize"
    st rbp, ss, [0, t0, rsp], "-5 * env.dataSize"
    st rsi, ss, [0, t0, rsp], "-6 * env.dataSize"
    st rdi, ss, [0, t0, rsp], "-7 * env.dataSize"
    subi rsp, rsp, "8 * env.dataSize"
};

def macroop POPA {
    ld rdi, ss, [0, t0, rsp], "0 * env.dataSize"
    ld rsi, ss, [0, t0, rsp], "1 * env.dataSize"
    ld rbp, ss, [0, t0, rsp], "2 * env.dataSize"
    ld rbx, ss, [0, t0, rsp], "4 * env.dataSize"
    ld rdx, ss, [0, t0, rsp], "5 * env.dataSize"
    ld rcx, ss, [0, t0, rsp], "6 * env.dataSize"
    ld rax, ss, [0, t0, rsp], "7 * env.dataSize"
    addi rsp, rsp, "8 * env.dataSize"
};

def macroop LEAVE {
    # Make the default data size of pops 64 bits in 64 bit mode
    .adjust_env oszIn64Override

    mov rsp, rsp, rbp
    ld rbp, ss, [0, t0, rsp]
    addi rsp, rsp, dsz
};
'''
#let {{
#    class ENTER(Inst):
#	"GenFault ${new UnimpInstFault}"
#    class LEAVE(Inst):
#	"GenFault ${new UnimpInstFault}"
#}};
<|endoftext|>


<commit_before>import logging
from corehq.apps.sms.mixin import SMSBackend, SMSLoadBalancingMixin
from corehq.apps.sms.util import clean_phone_number
from corehq.apps.twilio.forms import TwilioBackendForm
from couchdbkit.ext.django.schema import *
from twilio.rest import TwilioRestClient
from django.conf import settings

class TwilioBackend(SMSBackend, SMSLoadBalancingMixin):
    account_sid = StringProperty()
    auth_token = StringProperty()

    @classmethod
    def get_api_id(cls):
        return "TWILIO"

    @classmethod
    def get_generic_name(cls):
        return "Twilio"

    @classmethod
    def get_template(cls):
        return "twilio/backend.html"

    @classmethod
    def get_form_class(cls):
        return TwilioBackendForm

    def get_load_balancing_interval(self):
        # Twilio automatically rate limits at 1 sms/sec, but we'll also
        # balance the sms load evenly between the phone numbers used by
        # this backend over the last 60 seconds.
        return 60

    @property
    def phone_numbers(self):
        """
        Prior to introduction load balancing, the Twilio backend only had
        one phone number, so need to handle old Twilio backends which don't
        have the _phone_numbers property set.
        """
        if self.x_phone_numbers:
            return self.x_phone_numbers
        else:
            return [self.phone_number]

    def send(self, msg, *args, **kwargs):
        orig_phone_number = kwargs.get("orig_phone_number")
        msg.system_phone_number = orig_phone_number
        client = TwilioRestClient(self.account_sid, self.auth_token,
            timeout=settings.SMS_GATEWAY_TIMEOUT)
        to = msg.phone_number
        from_ = orig_phone_number
        body = msg.text
        message = client.sms.messages.create(
            body=body,
            to=to,
            from_=from_
        )
        msg.backend_message_id = message.sid
        msg.save()

<commit_msg>Use different api to allow sending messages with more than 160 characters<commit_after>import logging
from corehq.apps.sms.mixin import SMSBackend, SMSLoadBalancingMixin
from corehq.apps.sms.util import clean_phone_number
from corehq.apps.twilio.forms import TwilioBackendForm
from couchdbkit.ext.django.schema import *
from twilio.rest import TwilioRestClient
from django.conf import settings

class TwilioBackend(SMSBackend, SMSLoadBalancingMixin):
    account_sid = StringProperty()
    auth_token = StringProperty()

    @classmethod
    def get_api_id(cls):
        return "TWILIO"

    @classmethod
    def get_generic_name(cls):
        return "Twilio"

    @classmethod
    def get_template(cls):
        return "twilio/backend.html"

    @classmethod
    def get_form_class(cls):
        return TwilioBackendForm

    def get_load_balancing_interval(self):
        # Twilio automatically rate limits at 1 sms/sec, but we'll also
        # balance the sms load evenly between the phone numbers used by
        # this backend over the last 60 seconds.
        return 60

    @property
    def phone_numbers(self):
        """
        Prior to introduction load balancing, the Twilio backend only had
        one phone number, so need to handle old Twilio backends which don't
        have the _phone_numbers property set.
        """
        if self.x_phone_numbers:
            return self.x_phone_numbers
        else:
            return [self.phone_number]

    def send(self, msg, *args, **kwargs):
        orig_phone_number = kwargs.get("orig_phone_number")
        msg.system_phone_number = orig_phone_number
        client = TwilioRestClient(self.account_sid, self.auth_token,
            timeout=settings.SMS_GATEWAY_TIMEOUT)
        to = msg.phone_number
        from_ = orig_phone_number
        body = msg.text
        message = client.messages.create(
            body=body,
            to=to,
            from_=from_
        )
        msg.backend_message_id = message.sid
        msg.save()

<|endoftext|>


<commit_before>from typing import Any

from django import forms
from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
from django.urls import reverse
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _

from crispy_forms import bootstrap as twbscrispy
from crispy_forms import layout as crispy
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit

from corehq.apps.hqwebapp import crispy as hqcrispy
from corehq.apps.userreports.dbaccessors import get_report_configs_for_domain
from corehq.apps.userreports.ui.fields import JsonField
from corehq.motech.models import ConnectionSettings

from .const import (
    DHIS2_UID_MESSAGE,
    DHIS2_UID_RE,
    SEND_FREQUENCY_CHOICES,
    SEND_FREQUENCY_MONTHLY,
    SEND_FREQUENCY_QUARTERLY,
    SEND_FREQUENCY_WEEKLY,
)
from .models import SQLDataSetMap, SQLDataValueMap

class DataSetMapForm(forms.ModelForm):
    description = forms.CharField(
        label=_('Description'),
    )
    frequency = forms.ChoiceField(
        label=_('Frequency'),
        choices=SEND_FREQUENCY_CHOICES,
        initial=SEND_FREQUENCY_MONTHLY,
    )
    day_to_send = forms.CharField(
        label=_('Day to send data'),
        help_text=_('Day of the month if "Frequency" is monthly or quarterly. '
                    'Day of the week if "Frequency" is weekly, where Monday '
                    'is 1 and Sunday is 7.'),
    )
    data_set_id = forms.CharField(
        label=_('DataSetID'),
        help_text=_('Set DataSetID if this UCR adds values to an existing '
                    'DHIS2 DataSet'),
        validators=[RegexValidator(DHIS2_UID_RE, DHIS2_UID_MESSAGE)],
        required=False,
    )
    org_unit_id = forms.CharField(
        label=_('OrgUnitID¹'),
        validators=[RegexValidator(DHIS2_UID_RE, DHIS2_UID_MESSAGE)],
        required=False,
    )
    org_unit_column = forms.CharField(
        label=_('OrgUnitID column¹'),
        help_text=_('¹ Please set either a fixed value for "OrgUnitID", or '
                    'specify an "OrgUnitID column" where a DHIS2 Organisation '
                    'Unit ID will be found.'),
        required=False,
    )
    period = forms.CharField(
        label=_('Period²'),
        help_text=_('Week periods use the format yyyyWn (e.g. "2004W10" for '
                    'week 10, 2004). Month periods use the format yyyyMM '
                    '(e.g. "200403" for March 2004). Quarter periods use the '
                    'format yyyyQn (e.g. "2004Q1" for January-March 2004).'),
        required=False,
    )
    period_column = forms.CharField(
        label=_('Period column²'),
        help_text=_('² Please set a fixed value for "Period", or specify a '
                    '"Period column" where a period will be found, or if the '
                    'UCR has a date filter then leave both fields blank to '
                    'filter the UCR by the date range of the previous '
                    'period.'),
        required=False,
    )
    attribute_option_combo_id = forms.CharField(
        label=_('AttributeOptionComboID'),
        validators=[RegexValidator(DHIS2_UID_RE, DHIS2_UID_MESSAGE)],
        required=False,
    )
    complete_date = forms.DateField(
        label=_('CompleteDate'),
        required=False,
    )

    class Meta:
        model = SQLDataSetMap
        fields = [
            'description',
            'connection_settings',
            'ucr_id',
            'frequency',
            'day_to_send',
            'data_set_id',
            'org_unit_id',
            'org_unit_column',
            'period',
            'period_column',
            'attribute_option_combo_id',
            'complete_date',
        ]

    def __init__(self, domain, *args, **kwargs):
        from corehq.motech.dhis2.views import DataSetMapListView

        super().__init__(*args, **kwargs)
        self.domain = domain
        self.fields['connection_settings'] = get_connection_settings_field(domain)
        self.fields['ucr_id'] = get_ucr_field(domain)

        self.helper = hqcrispy.HQFormHelper()
        self.helper.layout = crispy.Layout(
            crispy.Fieldset(
                _('DataSet Details'),
                crispy.Field('description'),
                crispy.Field('connection_settings'),
                crispy.Field('ucr_id'),
                crispy.Field('frequency'),
                crispy.Field('day_to_send'),
                crispy.Field('data_set_id'),
                crispy.Field('org_unit_id'),
                crispy.Field('org_unit_column'),
                crispy.Field('period'),
                crispy.Field('period_column'),
                crispy.Field('attribute_option_combo_id'),
                crispy.Field('complete_date', css_class='date-picker'),
            ),
            hqcrispy.FormActions(
                twbscrispy.StrictButton(
                    _("Save"),
                    type="submit",
                    css_class="btn btn-primary",
                ),
                hqcrispy.LinkButton(
                    _("Cancel"),
                    reverse(
                        DataSetMapListView.urlname,
                        kwargs={'domain': self.domain},
                    ),
                    css_class="btn btn-default",
                ),
            ),
        )

    def save(self, commit=True):
        self.instance.domain = self.domain
        return super().save(commit)

    def clean(self):
        cleaned_data = super().clean()

        if not (
            cleaned_data.get('org_unit_id')
            or cleaned_data.get('org_unit_column')
        ):
            self.add_error('org_unit_column', _(
                'Either "OrgUnitID" or "OrgUnitID column" is required.'
            ))

        if (
            cleaned_data.get('org_unit_id')
            and cleaned_data.get('org_unit_column')
        ):
            self.add_error('org_unit_column', _(
                'Either "OrgUnitID" or "OrgUnitID column" is required, but '
                'not both.'
            ))

        if (
            cleaned_data.get('period')
            and cleaned_data.get('period_column')
        ):
            self.add_error('period_column', _(
                'Either "Period" or "Period column" is required, but not '
                'both. Alternatively, leave both fields blank to use the '
                "UCR's date filter."
            ))

        if (
            cleaned_data.get('frequency') == SEND_FREQUENCY_WEEKLY
            and not _int_in_range(cleaned_data.get('day_to_send'), 1, 7)
        ):
            self.add_error('day_to_send', _(
                'Enter a day of the week, where Monday is 1 and Sunday is 7.'
            ))

        elif (
            cleaned_data.get('frequency') != SEND_FREQUENCY_WEEKLY
            and not _int_in_range(cleaned_data.get('day_to_send'), 1, 28)
        ):
            self.add_error('day_to_send', _(
                'Enter a day of the month that occurs in every month (i.e. '
                'from 1 to 28).'
            ))

        if cleaned_data.get('period'):
            period_validators = {
                SEND_FREQUENCY_WEEKLY: RegexValidator(r'^\d{4}W\d{1,2}$', _(
                    '"Frequency" is set to "Weekly". Please enter a valid '
                    'week period in the format yyyyWn (e.g. "2004W10" for '
                    'week 10, 2004).'
                )),
                SEND_FREQUENCY_MONTHLY: RegexValidator(r'^\d{6}$', _(
                    '"Frequency" is set to "Monthly". Please enter a valid '
                    'month period in the format yyyyMM (e.g. "200403" for '
                    'March 2004).'
                )),
                SEND_FREQUENCY_QUARTERLY: RegexValidator(r'^\d{4}Q\d$', _(
                    '"Frequency" is set to "Quarterly". Please enter a valid '
                    'quarter period in the format yyyyQn (e.g. "2004Q1" for '
                    'January-March 2004).'
                )),
            }
            validate_period = period_validators[cleaned_data.get('frequency')]
            try:
                validate_period(cleaned_data.get('period'))
            except ValidationError:
                self.add_error('period', validate_period.message)

        return self.cleaned_data

def get_connection_settings_field(domain):
    from corehq.motech.views import ConnectionSettingsListView

    connection_settings = ConnectionSettings.objects.filter(domain=domain)
    url = reverse(ConnectionSettingsListView.urlname,
                  kwargs={'domain': domain})
    return forms.ModelChoiceField(
        label=_("Connection Settings"),
        queryset=connection_settings,
        required=True,
        help_text=_(f'Add/Edit Connections Settings')
    )

def get_ucr_field(domain):
    from corehq.apps.userreports.views import UserConfigReportsHomeView

    ucrs = get_report_configs_for_domain(domain)
    url = reverse(UserConfigReportsHomeView.urlname,
                  kwargs={'domain': domain})
    return forms.ChoiceField(
        label=_("User Configurable Report"),
        choices=[(ucr.get_id, ucr.title) for ucr in ucrs],
        required=True,
        help_text=_(
            'DataSet Maps map CommCare UCRs to DHIS2 DataSets. '
            'More info. '
            f'Go to Configurable Reports to define a UCR.')
    )

class DataValueMapBaseForm(forms.ModelForm):
    column = forms.CharField(
        label=_('Column'),
        required=True,
    )
    data_element_id = forms.CharField(
        label=_('Data element ID'),
        required=True,
        validators=[RegexValidator(DHIS2_UID_RE, DHIS2_UID_MESSAGE)],
    )
    category_option_combo_id = forms.CharField(
        label=_('Category option combo ID'),
        required=False,
        validators=[RegexValidator(DHIS2_UID_RE, DHIS2_UID_MESSAGE)],
    )
    comment = forms.CharField(
        label=_('Comment'),
        required=False,
    )

    class Meta:
        model = SQLDataValueMap
        fields = [
            'column',
            'data_element_id',
            'category_option_combo_id',
            'comment',
        ]

    def __init__(self, dataset_map, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.dataset_map = dataset_map
        self.helper = self.get_form_helper()

    def get_form_helper(self):
        raise NotImplementedError

    def save(self, commit=True):
        self.instance.dataset_map = self.dataset_map
        return super().save(commit)

class DataValueMapCreateForm(DataValueMapBaseForm):

    def get_form_helper(self):
        helper = FormHelper()
        helper.form_style = 'inline'
        helper.form_show_labels = False
        helper.layout = crispy.Layout(
            twbscrispy.InlineField('column'),
            twbscrispy.InlineField('data_element_id'),
            twbscrispy.InlineField('category_option_combo_id'),
            twbscrispy.InlineField('comment'),
            twbscrispy.StrictButton(
                mark_safe(f' {_("Add")}'),  # nosec: no user input
                css_class='btn-primary',
                type='submit',
            )
        )
        return helper

class DataValueMapUpdateForm(DataValueMapBaseForm):

    id = forms.CharField(widget=forms.HiddenInput())

    class Meta:
        model = SQLDataValueMap
        fields = [
            'id',
            'column',
            'data_element_id',
            'category_option_combo_id',
            'comment',
        ]

    def get_form_helper(self):
        helper = FormHelper()
        helper.form_style = 'default'
        helper.form_show_labels = True
        helper.layout = crispy.Layout(
            crispy.Div(
                crispy.Field('id'),
                crispy.Field('column'),
                crispy.Field('data_element_id'),
                crispy.Field('category_option_combo_id'),
                crispy.Field('comment'),
                css_class='modal-body',
            ),
            twbscrispy.FormActions(
                twbscrispy.StrictButton(
                    "Update DataValue Map",
                    css_class='btn btn-primary',
                    type='submit',
                ),
                crispy.HTML('<button type="button" '
                            '        class="btn btn-default" '
                            '        data-dismiss="modal">'
                            'Cancel'
                            '</button>'),
                css_class='modal-footer',
            )
        )
        return helper

class Dhis2ConfigForm(forms.Form):
    form_configs = JsonField(expected_type=list)

    def __init__(self, *args, **kwargs):
        super(Dhis2ConfigForm, self).__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.add_input(Submit('submit', _('Save Changes')))

    def clean_form_configs(self):
        errors = _validate_form_configs(self.cleaned_data['form_configs'])
        if errors:
            raise ValidationError(errors)
        return self.cleaned_data['form_configs']

class Dhis2EntityConfigForm(forms.Form):
    """
    Dhis2EntityConfig.case_configs is a list. Dhis2EntityConfigForm has
    one case_config, and is used in a formset.
    """
    case_config = JsonField()

    def clean_case_config(self):
        errors = []
        case_config = self.cleaned_data['case_config']
        if not isinstance(case_config, dict):
            raise ValidationError(
                _('The "case_type" property is a dictionary, not a "%(data_type)s".'),
                params={'data_type': type(case_config).__name__}
            )
        if not case_config.get('case_type'):
            errors.append(ValidationError(
                _('The "%(property)s" property is required.'),
                params={'property': 'case_type'},
                code='required_property',
            ))
        if 'form_configs' not in case_config:
            errors.append(ValidationError(
                _('The "%(property)s" property is required.'),
                params={'property': 'form_configs'},
                code='required_property',
            ))
        elif not isinstance(case_config['form_configs'], list):
            raise ValidationError(
                _('The "form_configs" property is a dictionary, not a "%(data_type)s".'),
                params={'data_type': type(case_config['form_configs']).__name__}
            )
        else:
            errors.extend(_validate_form_configs(case_config['form_configs']))
        if errors:
            raise ValidationError(errors)
        return self.cleaned_data['case_config']

def _validate_form_configs(form_configs):
    errors = []
    for form_config in form_configs:
        if form_config.get('xmlns'):
            required_msg = _('The "%(property)s" property is required for '
                             'form "{}".').format(form_config['xmlns'])
        else:
            required_msg = _('The "%(property)s" property is required.')

        if not form_config.get('xmlns'):
            errors.append(ValidationError(
                '{} {}'.format(required_msg, _('Please specify the XMLNS of the form.')),
                params={'property': 'xmlns'},
                code='required_property',
            ))
        if not form_config.get('program_id'):
            errors.append(ValidationError(
                '{} {}'.format(required_msg, _('Please specify the DHIS2 Program of the event.')),
                params={'property': 'program_id'},
                code='required_property',
            ))
        if not form_config.get('datavalue_maps'):
            errors.append(ValidationError(
                '{} {}'.format(required_msg, _('Please map CommCare values to DHIS2 data elements.')),
                params={'property': 'datavalue_maps'},
                code='required_property',
            ))
    return errors

def _int_in_range(value: Any, lower: int, upper: int) -> bool:
    """
    Casts as int and returns True if lower <= value <= upper

    >>> _int_in_range(5, 1, 7)
    True

    """
    try:
        value_int = int(value)
    except (TypeError, ValueError):
        return False
    return lower <= value_int <= upper
<commit_msg>Make DataSetID a required input<commit_after>from typing import Any

from django import forms
from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
from django.urls import reverse
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _

from crispy_forms import bootstrap as twbscrispy
from crispy_forms import layout as crispy
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit

from corehq.apps.hqwebapp import crispy as hqcrispy
from corehq.apps.userreports.dbaccessors import get_report_configs_for_domain
from corehq.apps.userreports.ui.fields import JsonField
from corehq.motech.models import ConnectionSettings

from .const import (
    DHIS2_UID_MESSAGE,
    DHIS2_UID_RE,
    SEND_FREQUENCY_CHOICES,
    SEND_FREQUENCY_MONTHLY,
    SEND_FREQUENCY_QUARTERLY,
    SEND_FREQUENCY_WEEKLY,
)
from .models import SQLDataSetMap, SQLDataValueMap

class DataSetMapForm(forms.ModelForm):
    description = forms.CharField(
        label=_('Description'),
    )
    frequency = forms.ChoiceField(
        label=_('Frequency'),
        choices=SEND_FREQUENCY_CHOICES,
        initial=SEND_FREQUENCY_MONTHLY,
    )
    day_to_send = forms.CharField(
        label=_('Day to send data'),
        help_text=_('Day of the month if "Frequency" is monthly or quarterly. '
                    'Day of the week if "Frequency" is weekly, where Monday '
                    'is 1 and Sunday is 7.'),
    )
    data_set_id = forms.CharField(
        label=_('DataSetID'),
        help_text=_('The DHIS2 DataSet ID to which this UCR adds value'),
        validators=[RegexValidator(DHIS2_UID_RE, DHIS2_UID_MESSAGE)],
        required=True,
    )
    org_unit_id = forms.CharField(
        label=_('OrgUnitID¹'),
        validators=[RegexValidator(DHIS2_UID_RE, DHIS2_UID_MESSAGE)],
        required=False,
    )
    org_unit_column = forms.CharField(
        label=_('OrgUnitID column¹'),
        help_text=_('¹ Please set either a fixed value for "OrgUnitID", or '
                    'specify an "OrgUnitID column" where a DHIS2 Organisation '
                    'Unit ID will be found.'),
        required=False,
    )
    period = forms.CharField(
        label=_('Period²'),
        help_text=_('Week periods use the format yyyyWn (e.g. "2004W10" for '
                    'week 10, 2004). Month periods use the format yyyyMM '
                    '(e.g. "200403" for March 2004). Quarter periods use the '
                    'format yyyyQn (e.g. "2004Q1" for January-March 2004).'),
        required=False,
    )
    period_column = forms.CharField(
        label=_('Period column²'),
        help_text=_('² Please set a fixed value for "Period", or specify a '
                    '"Period column" where a period will be found, or if the '
                    'UCR has a date filter then leave both fields blank to '
                    'filter the UCR by the date range of the previous '
                    'period.'),
        required=False,
    )
    attribute_option_combo_id = forms.CharField(
        label=_('AttributeOptionComboID'),
        validators=[RegexValidator(DHIS2_UID_RE, DHIS2_UID_MESSAGE)],
        required=False,
    )
    complete_date = forms.DateField(
        label=_('CompleteDate'),
        required=False,
    )

    class Meta:
        model = SQLDataSetMap
        fields = [
            'description',
            'connection_settings',
            'ucr_id',
            'frequency',
            'day_to_send',
            'data_set_id',
            'org_unit_id',
            'org_unit_column',
            'period',
            'period_column',
            'attribute_option_combo_id',
            'complete_date',
        ]

    def __init__(self, domain, *args, **kwargs):
        from corehq.motech.dhis2.views import DataSetMapListView

        super().__init__(*args, **kwargs)
        self.domain = domain
        self.fields['connection_settings'] = get_connection_settings_field(domain)
        self.fields['ucr_id'] = get_ucr_field(domain)

        self.helper = hqcrispy.HQFormHelper()
        self.helper.layout = crispy.Layout(
            crispy.Fieldset(
                _('DataSet Details'),
                crispy.Field('description'),
                crispy.Field('connection_settings'),
                crispy.Field('ucr_id'),
                crispy.Field('frequency'),
                crispy.Field('day_to_send'),
                crispy.Field('data_set_id'),
                crispy.Field('org_unit_id'),
                crispy.Field('org_unit_column'),
                crispy.Field('period'),
                crispy.Field('period_column'),
                crispy.Field('attribute_option_combo_id'),
                crispy.Field('complete_date', css_class='date-picker'),
            ),
            hqcrispy.FormActions(
                twbscrispy.StrictButton(
                    _("Save"),
                    type="submit",
                    css_class="btn btn-primary",
                ),
                hqcrispy.LinkButton(
                    _("Cancel"),
                    reverse(
                        DataSetMapListView.urlname,
                        kwargs={'domain': self.domain},
                    ),
                    css_class="btn btn-default",
                ),
            ),
        )

    def save(self, commit=True):
        self.instance.domain = self.domain
        return super().save(commit)

    def clean(self):
        cleaned_data = super().clean()

        if not (
            cleaned_data.get('org_unit_id')
            or cleaned_data.get('org_unit_column')
        ):
            self.add_error('org_unit_column', _(
                'Either "OrgUnitID" or "OrgUnitID column" is required.'
            ))

        if (
            cleaned_data.get('org_unit_id')
            and cleaned_data.get('org_unit_column')
        ):
            self.add_error('org_unit_column', _(
                'Either "OrgUnitID" or "OrgUnitID column" is required, but '
                'not both.'
            ))

        if (
            cleaned_data.get('period')
            and cleaned_data.get('period_column')
        ):
            self.add_error('period_column', _(
                'Either "Period" or "Period column" is required, but not '
                'both. Alternatively, leave both fields blank to use the '
                "UCR's date filter."
            ))

        if (
            cleaned_data.get('frequency') == SEND_FREQUENCY_WEEKLY
            and not _int_in_range(cleaned_data.get('day_to_send'), 1, 7)
        ):
            self.add_error('day_to_send', _(
                'Enter a day of the week, where Monday is 1 and Sunday is 7.'
            ))

        elif (
            cleaned_data.get('frequency') != SEND_FREQUENCY_WEEKLY
            and not _int_in_range(cleaned_data.get('day_to_send'), 1, 28)
        ):
            self.add_error('day_to_send', _(
                'Enter a day of the month that occurs in every month (i.e. '
                'from 1 to 28).'
            ))

        if cleaned_data.get('period'):
            period_validators = {
                SEND_FREQUENCY_WEEKLY: RegexValidator(r'^\d{4}W\d{1,2}$', _(
                    '"Frequency" is set to "Weekly". Please enter a valid '
                    'week period in the format yyyyWn (e.g. "2004W10" for '
                    'week 10, 2004).'
                )),
                SEND_FREQUENCY_MONTHLY: RegexValidator(r'^\d{6}$', _(
                    '"Frequency" is set to "Monthly". Please enter a valid '
                    'month period in the format yyyyMM (e.g. "200403" for '
                    'March 2004).'
                )),
                SEND_FREQUENCY_QUARTERLY: RegexValidator(r'^\d{4}Q\d$', _(
                    '"Frequency" is set to "Quarterly". Please enter a valid '
                    'quarter period in the format yyyyQn (e.g. "2004Q1" for '
                    'January-March 2004).'
                )),
            }
            validate_period = period_validators[cleaned_data.get('frequency')]
            try:
                validate_period(cleaned_data.get('period'))
            except ValidationError:
                self.add_error('period', validate_period.message)

        return self.cleaned_data

def get_connection_settings_field(domain):
    from corehq.motech.views import ConnectionSettingsListView

    connection_settings = ConnectionSettings.objects.filter(domain=domain)
    url = reverse(ConnectionSettingsListView.urlname,
                  kwargs={'domain': domain})
    return forms.ModelChoiceField(
        label=_("Connection Settings"),
        queryset=connection_settings,
        required=True,
        help_text=_(f'Add/Edit Connections Settings')
    )

def get_ucr_field(domain):
    from corehq.apps.userreports.views import UserConfigReportsHomeView

    ucrs = get_report_configs_for_domain(domain)
    url = reverse(UserConfigReportsHomeView.urlname,
                  kwargs={'domain': domain})
    return forms.ChoiceField(
        label=_("User Configurable Report"),
        choices=[(ucr.get_id, ucr.title) for ucr in ucrs],
        required=True,
        help_text=_(
            'DataSet Maps map CommCare UCRs to DHIS2 DataSets. '
            'More info. '
            f'Go to Configurable Reports to define a UCR.')
    )

class DataValueMapBaseForm(forms.ModelForm):
    column = forms.CharField(
        label=_('Column'),
        required=True,
    )
    data_element_id = forms.CharField(
        label=_('Data element ID'),
        required=True,
        validators=[RegexValidator(DHIS2_UID_RE, DHIS2_UID_MESSAGE)],
    )
    category_option_combo_id = forms.CharField(
        label=_('Category option combo ID'),
        required=False,
        validators=[RegexValidator(DHIS2_UID_RE, DHIS2_UID_MESSAGE)],
    )
    comment = forms.CharField(
        label=_('Comment'),
        required=False,
    )

    class Meta:
        model = SQLDataValueMap
        fields = [
            'column',
            'data_element_id',
            'category_option_combo_id',
            'comment',
        ]

    def __init__(self, dataset_map, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.dataset_map = dataset_map
        self.helper = self.get_form_helper()

    def get_form_helper(self):
        raise NotImplementedError

    def save(self, commit=True):
        self.instance.dataset_map = self.dataset_map
        return super().save(commit)

class DataValueMapCreateForm(DataValueMapBaseForm):

    def get_form_helper(self):
        helper = FormHelper()
        helper.form_style = 'inline'
        helper.form_show_labels = False
        helper.layout = crispy.Layout(
            twbscrispy.InlineField('column'),
            twbscrispy.InlineField('data_element_id'),
            twbscrispy.InlineField('category_option_combo_id'),
            twbscrispy.InlineField('comment'),
            twbscrispy.StrictButton(
                mark_safe(f' {_("Add")}'),  # nosec: no user input
                css_class='btn-primary',
                type='submit',
            )
        )
        return helper

class DataValueMapUpdateForm(DataValueMapBaseForm):

    id = forms.CharField(widget=forms.HiddenInput())

    class Meta:
        model = SQLDataValueMap
        fields = [
            'id',
            'column',
            'data_element_id',
            'category_option_combo_id',
            'comment',
        ]

    def get_form_helper(self):
        helper = FormHelper()
        helper.form_style = 'default'
        helper.form_show_labels = True
        helper.layout = crispy.Layout(
            crispy.Div(
                crispy.Field('id'),
                crispy.Field('column'),
                crispy.Field('data_element_id'),
                crispy.Field('category_option_combo_id'),
                crispy.Field('comment'),
                css_class='modal-body',
            ),
            twbscrispy.FormActions(
                twbscrispy.StrictButton(
                    "Update DataValue Map",
                    css_class='btn btn-primary',
                    type='submit',
                ),
                crispy.HTML('<button type="button" '
                            '        class="btn btn-default" '
                            '        data-dismiss="modal">'
                            'Cancel'
                            '</button>'),
                css_class='modal-footer',
            )
        )
        return helper

class Dhis2ConfigForm(forms.Form):
    form_configs = JsonField(expected_type=list)

    def __init__(self, *args, **kwargs):
        super(Dhis2ConfigForm, self).__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.add_input(Submit('submit', _('Save Changes')))

    def clean_form_configs(self):
        errors = _validate_form_configs(self.cleaned_data['form_configs'])
        if errors:
            raise ValidationError(errors)
        return self.cleaned_data['form_configs']

class Dhis2EntityConfigForm(forms.Form):
    """
    Dhis2EntityConfig.case_configs is a list. Dhis2EntityConfigForm has
    one case_config, and is used in a formset.
    """
    case_config = JsonField()

    def clean_case_config(self):
        errors = []
        case_config = self.cleaned_data['case_config']
        if not isinstance(case_config, dict):
            raise ValidationError(
                _('The "case_type" property is a dictionary, not a "%(data_type)s".'),
                params={'data_type': type(case_config).__name__}
            )
        if not case_config.get('case_type'):
            errors.append(ValidationError(
                _('The "%(property)s" property is required.'),
                params={'property': 'case_type'},
                code='required_property',
            ))
        if 'form_configs' not in case_config:
            errors.append(ValidationError(
                _('The "%(property)s" property is required.'),
                params={'property': 'form_configs'},
                code='required_property',
            ))
        elif not isinstance(case_config['form_configs'], list):
            raise ValidationError(
                _('The "form_configs" property is a dictionary, not a "%(data_type)s".'),
                params={'data_type': type(case_config['form_configs']).__name__}
            )
        else:
            errors.extend(_validate_form_configs(case_config['form_configs']))
        if errors:
            raise ValidationError(errors)
        return self.cleaned_data['case_config']

def _validate_form_configs(form_configs):
    errors = []
    for form_config in form_configs:
        if form_config.get('xmlns'):
            required_msg = _('The "%(property)s" property is required for '
                             'form "{}".').format(form_config['xmlns'])
        else:
            required_msg = _('The "%(property)s" property is required.')

        if not form_config.get('xmlns'):
            errors.append(ValidationError(
                '{} {}'.format(required_msg, _('Please specify the XMLNS of the form.')),
                params={'property': 'xmlns'},
                code='required_property',
            ))
        if not form_config.get('program_id'):
            errors.append(ValidationError(
                '{} {}'.format(required_msg, _('Please specify the DHIS2 Program of the event.')),
                params={'property': 'program_id'},
                code='required_property',
            ))
        if not form_config.get('datavalue_maps'):
            errors.append(ValidationError(
                '{} {}'.format(required_msg, _('Please map CommCare values to DHIS2 data elements.')),
                params={'property': 'datavalue_maps'},
                code='required_property',
            ))
    return errors

def _int_in_range(value: Any, lower: int, upper: int) -> bool:
    """
    Casts as int and returns True if lower <= value <= upper

    >>> _int_in_range(5, 1, 7)
    True

    """
    try:
        value_int = int(value)
    except (TypeError, ValueError):
        return False
    return lower <= value_int <= upper
<|endoftext|>


<commit_before>from contextlib import contextmanager

from django.conf import settings
from django.core import signals
from django.db import DEFAULT_DB_ALIAS

import sqlalchemy
from memoized import memoized
from urllib.parse import urlencode
from sqlalchemy.orm.scoping import scoped_session
from sqlalchemy.orm.session import sessionmaker
from sqlalchemy.pool import NullPool

from corehq.util.test_utils import unit_testing_only

from .util import select_db_for_read

DEFAULT_ENGINE_ID = DEFAULT_DB_ALIAS
UCR_ENGINE_ID = 'ucr'

def get_db_alias_or_none(enigne_id):
    try:
        return connection_manager.get_django_db_alias(enigne_id)
    except KeyError:
        return None

def create_engine(connection_url: str, connect_args: dict = None):
    # paramstyle='format' allows you to use column names that include the ')' character
    # otherwise queries will sometimes be misformated/error when formatting
    # https://github.com/zzzeek/sqlalchemy/blob/ff20903/lib/sqlalchemy/dialects/postgresql/psycopg2.py#L173
    # Turn off sqlalchemy connection pooling since we use pgbouncer for connection pooling already
    connect_args = connect_args or {}
    return sqlalchemy.create_engine(
        connection_url,
        paramstyle='format',
        pool_class=NullPool,
        connect_args=connect_args
    )

class SessionHelper(object):
    """
    Shim class helper for a single connection/session factory
    """

    def __init__(self, connection_url: str, connect_args: dict = None):
        self.url = connection_url
        self.engine = create_engine(connection_url, connect_args)
        self._session_factory = sessionmaker(bind=self.engine)
        # Session is the actual constructor object
        self.Session = scoped_session(self._session_factory)

    @property
    def session_context(self):
        @contextmanager
        def session_scope():
            """Provide a transactional scope around a series of operations."""
            session = self.Session()
            try:
                yield session
                session.commit()
            except:
                session.rollback()
                raise
            finally:
                session.close()

        return session_scope

class ConnectionManager(object):
    """
    Object for dealing with sqlalchemy engines and sessions.

    Maintains a mapping of `engine_id` to Django db alias as well as load balancing configuration
    from the `REPORTING_DATABASES` setting.
    """

    def __init__(self):
        self._session_helpers = {}
        self.read_database_mapping = {}
        self.engine_id_django_db_map = {}
        self._populate_connection_map()

    def _get_or_create_helper(self, connection_url: str):
        if connection_url not in self._session_helpers:
            self._session_helpers[connection_url] = SessionHelper(connection_url)

        return self._session_helpers[connection_url]

    def get_django_db_alias(self, engine_id):
        return self.engine_id_django_db_map[engine_id]

    def engine_id_is_available(self, engine_id):
        return engine_id in self.engine_id_django_db_map

    def get_session_helper(self, engine_id=DEFAULT_ENGINE_ID, readonly=False):
        """
        Returns the SessionHelper object associated with this engine id
        """
        # making this separate from _get_or_create in case we ever want to fail differently here
        if readonly:
            engine_id = self.get_load_balanced_read_db_alias(engine_id)
        return self._get_or_create_helper(self.get_connection_string(engine_id))

    def get_scoped_session(self, engine_id=DEFAULT_ENGINE_ID):
        """
        This returns a handle to a thread-locally scoped session object.
        """
        return self.get_session_helper(engine_id).Session

    def get_engine(self, engine_id=DEFAULT_ENGINE_ID):
        """
        Get an engine by ID. This should be a unique field identifying the connection,
        e.g. "report-db-1"
        """
        return self.get_session_helper(engine_id).engine

    def get_load_balanced_read_db_alias(self, engine_id):
        """
        returns the load balanced read db alias based on list of read databases
            and their weights obtained from settings.REPORTING_DATABASES

            If a suitable db is not found returns the `default` or `engine_id` itself
        """
        read_dbs = self.read_database_mapping.get(engine_id, [])
        load_balanced_db = select_db_for_read(read_dbs)

        return load_balanced_db or self.get_django_db_alias(engine_id)

    def close_scoped_sessions(self):
        for helper in self._session_helpers.values():
            helper.Session.remove()

    def dispose_engine(self, engine_id):
        """
        If found, closes the active sessions associated with an an engine and disposes it.
        Also removes it from the session manager.
        If not found, does nothing.
        """
        self._dispose_engine(self.get_connection_string(engine_id))

    def _dispose_engine(self, connection_url):
        helper = self._session_helpers.pop(connection_url, None)
        if helper:
            helper.Session.remove()
            helper.engine.dispose()

    def dispose_all(self):
        """
        Dispose all engines associated with this. Useful for tests.
        """
        for session_key in list(self._session_helpers):
            self._dispose_engine(session_key)

    def get_connection_string(self, engine_id_or_db_alias):
        db_alias = self.engine_id_django_db_map.get(engine_id_or_db_alias, engine_id_or_db_alias)
        return self._connection_string_from_django(db_alias)

    def _populate_connection_map(self):
        reporting_db_config = settings.REPORTING_DATABASES
        if reporting_db_config:
            for engine_id, db_config in reporting_db_config.items():
                write_db = db_config
                if not isinstance(db_config, dict):
                    self.engine_id_django_db_map[engine_id] = write_db
                    continue

                write_db = db_config['WRITE']
                self.engine_id_django_db_map[engine_id] = write_db
                weighted_read_dbs = db_config['READ']
                dbs = [db for db, weight in weighted_read_dbs]
                assert set(dbs).issubset(set(settings.DATABASES))

                if weighted_read_dbs:
                    self.read_database_mapping[engine_id] = weighted_read_dbs

        if DEFAULT_ENGINE_ID not in self.engine_id_django_db_map:
            self.engine_id_django_db_map[DEFAULT_ENGINE_ID] = DEFAULT_DB_ALIAS
        if UCR_ENGINE_ID not in self.engine_id_django_db_map:
            self.engine_id_django_db_map[UCR_ENGINE_ID] = DEFAULT_DB_ALIAS

    @memoized
    def _connection_string_from_django(self, django_alias):
        db_settings = settings.DATABASES[django_alias].copy()
        db_settings['PORT'] = db_settings.get('PORT', '5432')
        options = db_settings.get('OPTIONS')
        db_settings['OPTIONS'] = '?{}'.format(urlencode(options)) if options else ''

        return "postgresql+psycopg2://{USER}:{PASSWORD}@{HOST}:{PORT}/{NAME}{OPTIONS}".format(
            **db_settings
        )

    def resolves_to_unique_dbs(self, engine_ids):
        # return True if all in the list of engine_ids point to a different database
        return len(engine_ids) == len({connection_manager.get_django_db_alias(e) for e in engine_ids})

connection_manager = ConnectionManager()
Session = connection_manager.get_scoped_session(DEFAULT_ENGINE_ID)

# Register an event that closes the database connection
# when a Django request is finished.
# This will rollback any open transactions.
def _close_connections(**kwargs):
    Session.remove()  # todo: unclear whether this is necessary
    connection_manager.close_scoped_sessions()

signals.request_finished.connect(_close_connections)

@unit_testing_only
@contextmanager
def override_engine(engine_id, connection_url, db_alias=None):
    get_connection_string = connection_manager.get_connection_string

    def _get_conn_string(e_id):
        if e_id in (engine_id, db_alias):
            return connection_url
        else:
            return get_connection_string(e_id)

    connection_manager.get_connection_string = _get_conn_string
    original_alias = connection_manager.engine_id_django_db_map.get(engine_id, None)
    if db_alias:
        connection_manager.engine_id_django_db_map[engine_id] = db_alias
    try:
        yield
    finally:
        connection_manager.dispose_engine(engine_id)
        connection_manager.get_connection_string = get_connection_string
        connection_manager.engine_id_django_db_map[engine_id] = original_alias
<commit_msg>use correct argument name<commit_after>from contextlib import contextmanager

from django.conf import settings
from django.core import signals
from django.db import DEFAULT_DB_ALIAS

import sqlalchemy
from memoized import memoized
from urllib.parse import urlencode
from sqlalchemy.orm.scoping import scoped_session
from sqlalchemy.orm.session import sessionmaker
from sqlalchemy.pool import NullPool

from corehq.util.test_utils import unit_testing_only

from .util import select_db_for_read

DEFAULT_ENGINE_ID = DEFAULT_DB_ALIAS
UCR_ENGINE_ID = 'ucr'

def get_db_alias_or_none(enigne_id):
    try:
        return connection_manager.get_django_db_alias(enigne_id)
    except KeyError:
        return None

def create_engine(connection_url: str, connect_args: dict = None):
    # paramstyle='format' allows you to use column names that include the ')' character
    # otherwise queries will sometimes be misformated/error when formatting
    # https://github.com/zzzeek/sqlalchemy/blob/ff20903/lib/sqlalchemy/dialects/postgresql/psycopg2.py#L173
    # Turn off sqlalchemy connection pooling since we use pgbouncer for connection pooling already
    connect_args = connect_args or {}
    return sqlalchemy.create_engine(
        connection_url,
        paramstyle='format',
        poolclass=NullPool,
        connect_args=connect_args
    )

class SessionHelper(object):
    """
    Shim class helper for a single connection/session factory
    """

    def __init__(self, connection_url: str, connect_args: dict = None):
        self.url = connection_url
        self.engine = create_engine(connection_url, connect_args)
        self._session_factory = sessionmaker(bind=self.engine)
        # Session is the actual constructor object
        self.Session = scoped_session(self._session_factory)

    @property
    def session_context(self):
        @contextmanager
        def session_scope():
            """Provide a transactional scope around a series of operations."""
            session = self.Session()
            try:
                yield session
                session.commit()
            except:
                session.rollback()
                raise
            finally:
                session.close()

        return session_scope

class ConnectionManager(object):
    """
    Object for dealing with sqlalchemy engines and sessions.

    Maintains a mapping of `engine_id` to Django db alias as well as load balancing configuration
    from the `REPORTING_DATABASES` setting.
    """

    def __init__(self):
        self._session_helpers = {}
        self.read_database_mapping = {}
        self.engine_id_django_db_map = {}
        self._populate_connection_map()

    def _get_or_create_helper(self, connection_url: str):
        if connection_url not in self._session_helpers:
            self._session_helpers[connection_url] = SessionHelper(connection_url)

        return self._session_helpers[connection_url]

    def get_django_db_alias(self, engine_id):
        return self.engine_id_django_db_map[engine_id]

    def engine_id_is_available(self, engine_id):
        return engine_id in self.engine_id_django_db_map

    def get_session_helper(self, engine_id=DEFAULT_ENGINE_ID, readonly=False):
        """
        Returns the SessionHelper object associated with this engine id
        """
        # making this separate from _get_or_create in case we ever want to fail differently here
        if readonly:
            engine_id = self.get_load_balanced_read_db_alias(engine_id)
        return self._get_or_create_helper(self.get_connection_string(engine_id))

    def get_scoped_session(self, engine_id=DEFAULT_ENGINE_ID):
        """
        This returns a handle to a thread-locally scoped session object.
        """
        return self.get_session_helper(engine_id).Session

    def get_engine(self, engine_id=DEFAULT_ENGINE_ID):
        """
        Get an engine by ID. This should be a unique field identifying the connection,
        e.g. "report-db-1"
        """
        return self.get_session_helper(engine_id).engine

    def get_load_balanced_read_db_alias(self, engine_id):
        """
        returns the load balanced read db alias based on list of read databases
            and their weights obtained from settings.REPORTING_DATABASES

            If a suitable db is not found returns the `default` or `engine_id` itself
        """
        read_dbs = self.read_database_mapping.get(engine_id, [])
        load_balanced_db = select_db_for_read(read_dbs)

        return load_balanced_db or self.get_django_db_alias(engine_id)

    def close_scoped_sessions(self):
        for helper in self._session_helpers.values():
            helper.Session.remove()

    def dispose_engine(self, engine_id):
        """
        If found, closes the active sessions associated with an an engine and disposes it.
        Also removes it from the session manager.
        If not found, does nothing.
        """
        self._dispose_engine(self.get_connection_string(engine_id))

    def _dispose_engine(self, connection_url):
        helper = self._session_helpers.pop(connection_url, None)
        if helper:
            helper.Session.remove()
            helper.engine.dispose()

    def dispose_all(self):
        """
        Dispose all engines associated with this. Useful for tests.
        """
        for session_key in list(self._session_helpers):
            self._dispose_engine(session_key)

    def get_connection_string(self, engine_id_or_db_alias):
        db_alias = self.engine_id_django_db_map.get(engine_id_or_db_alias, engine_id_or_db_alias)
        return self._connection_string_from_django(db_alias)

    def _populate_connection_map(self):
        reporting_db_config = settings.REPORTING_DATABASES
        if reporting_db_config:
            for engine_id, db_config in reporting_db_config.items():
                write_db = db_config
                if not isinstance(db_config, dict):
                    self.engine_id_django_db_map[engine_id] = write_db
                    continue

                write_db = db_config['WRITE']
                self.engine_id_django_db_map[engine_id] = write_db
                weighted_read_dbs = db_config['READ']
                dbs = [db for db, weight in weighted_read_dbs]
                assert set(dbs).issubset(set(settings.DATABASES))

                if weighted_read_dbs:
                    self.read_database_mapping[engine_id] = weighted_read_dbs

        if DEFAULT_ENGINE_ID not in self.engine_id_django_db_map:
            self.engine_id_django_db_map[DEFAULT_ENGINE_ID] = DEFAULT_DB_ALIAS
        if UCR_ENGINE_ID not in self.engine_id_django_db_map:
            self.engine_id_django_db_map[UCR_ENGINE_ID] = DEFAULT_DB_ALIAS

    @memoized
    def _connection_string_from_django(self, django_alias):
        db_settings = settings.DATABASES[django_alias].copy()
        db_settings['PORT'] = db_settings.get('PORT', '5432')
        options = db_settings.get('OPTIONS')
        db_settings['OPTIONS'] = '?{}'.format(urlencode(options)) if options else ''

        return "postgresql+psycopg2://{USER}:{PASSWORD}@{HOST}:{PORT}/{NAME}{OPTIONS}".format(
            **db_settings
        )

    def resolves_to_unique_dbs(self, engine_ids):
        # return True if all in the list of engine_ids point to a different database
        return len(engine_ids) == len({connection_manager.get_django_db_alias(e) for e in engine_ids})

connection_manager = ConnectionManager()
Session = connection_manager.get_scoped_session(DEFAULT_ENGINE_ID)

# Register an event that closes the database connection
# when a Django request is finished.
# This will rollback any open transactions.
def _close_connections(**kwargs):
    Session.remove()  # todo: unclear whether this is necessary
    connection_manager.close_scoped_sessions()

signals.request_finished.connect(_close_connections)

@unit_testing_only
@contextmanager
def override_engine(engine_id, connection_url, db_alias=None):
    get_connection_string = connection_manager.get_connection_string

    def _get_conn_string(e_id):
        if e_id in (engine_id, db_alias):
            return connection_url
        else:
            return get_connection_string(e_id)

    connection_manager.get_connection_string = _get_conn_string
    original_alias = connection_manager.engine_id_django_db_map.get(engine_id, None)
    if db_alias:
        connection_manager.engine_id_django_db_map[engine_id] = db_alias
    try:
        yield
    finally:
        connection_manager.dispose_engine(engine_id)
        connection_manager.get_connection_string = get_connection_string
        connection_manager.engine_id_django_db_map[engine_id] = original_alias
<|endoftext|>