<commit_before>from __future__ import absolute_import, unicode_literals

from collections import Counter, OrderedDict
from itertools import chain, groupby
import json
from operator import itemgetter

import pytz

from django.conf import settings
from django.db import models, transaction
from django.db.models import Count, Q
from django.utils import timezone
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _

from tracpro.contacts.models import Contact

from . import rules
from .tasks import pollrun_start
from .utils import extract_words, natural_sort_key


class PollQuerySet(models.QuerySet):

    def active(self):
        return self.filter(is_active=True)

    def by_org(self, org):
        return self.filter(org=org)


class PollManager(models.Manager.from_queryset(PollQuerySet)):

    def from_temba(self, org, temba_poll):
        """Create new or update existing Poll from RapidPro data."""
        poll, _ = self.get_or_create(org=org, flow_uuid=temba_poll.uuid)

        if poll.name == poll.rapidpro_name:
            # Name is tracking RapidPro name so we must update both.
            poll.name = poll.rapidpro_name = temba_poll.name
        else:
            # Custom name will be maintained despite update of RapidPro name.
            poll.rapidpro_name = temba_poll.name

        poll.save()

        return poll

    @transaction.atomic
    def set_active_for_org(self, org, uuids):
        """Set matching org Polls to be active, and all others to be inactive.

        If an invalid UUID is given, a ValueError is raised and the transaction
        is rolled back.
        """
        active_count = org.polls.filter(flow_uuid__in=uuids).update(is_active=True)
        if active_count != len(uuids):
            invalid_uuids = set(uuids) - set(org.polls.values_list('flow_uuid', flat=True))
            raise ValueError(
                "No Poll for {} matching these UUIDS: {}".format(
                    org.name, invalid_uuids))
        org.polls.exclude(flow_uuid__in=uuids).update(is_active=False)

    def sync(self, org):
        """Update the org's Polls and Questions from RapidPro."""
        # Retrieve current Polls known to RapidPro.
        temba_polls = org.get_temba_client().get_flows(archived=False)
        temba_polls = {p.uuid: p for p in temba_polls}

        # Remove Polls that are no longer on RapidPro.
        org.polls.exclude(flow_uuid__in=temba_polls.keys()).delete()

        # Create new or update existing Polls to match RapidPro data.
        for temba_poll in temba_polls.values():
            poll = Poll.objects.from_temba(org, temba_poll)

            temba_questions = OrderedDict((r.uuid, r) for r in temba_poll.rulesets)

            # Remove questions that are no longer on RapidPro.
            poll.questions.exclude(ruleset_uuid__in=temba_questions.keys()).delete()

            # Create new or update existing Questions to match RapidPro data.
            for order, temba_question in enumerate(temba_questions.values(), 1):
                Question.objects.from_temba(poll, temba_question, order)


@python_2_unicode_compatible
class Poll(models.Model):
    """Corresponds to a RapidPro flow.

    Keeping track of contact responses to a flow is data-intensive, so Tracpro
    only tracks flows that the user has selected. Selected flows are managed
    as Polls.
    """
    flow_uuid = models.CharField(max_length=36)
    org = models.ForeignKey(
        'orgs.Org', related_name='polls', verbose_name=_('org'))
    rapidpro_name = models.CharField(
        max_length=64, verbose_name=_('RapidPro name'))
    name = models.CharField(
        max_length=64, blank=True, verbose_name=_('name'))

    # Set this to False rather than deleting a Poll. If the user should
    # re-select the corresponding flow later, we can avoid re-importing
    # existing data.<commit_msg>Move question sync to Poll.from_temba<commit_after>from __future__ import absolute_import, unicode_literals

from collections import Counter, OrderedDict
from itertools import chain, groupby
import json
from operator import itemgetter

import pytz

from django.conf import settings
from django.db import models, transaction
from django.db.models import Count, Q
from django.utils import timezone
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _

from tracpro.contacts.models import Contact

from . import rules
from .tasks import pollrun_start
from .utils import extract_words, natural_sort_key


class PollQuerySet(models.QuerySet):

    def active(self):
        return self.filter(is_active=True)

    def by_org(self, org):
        return self.filter(org=org)


class PollManager(models.Manager.from_queryset(PollQuerySet)):

    def from_temba(self, org, temba_poll):
        """Create new or update existing Poll from RapidPro data."""
        poll, _ = self.get_or_create(org=org, flow_uuid=temba_poll.uuid)

        if poll.name == poll.rapidpro_name:
            # Name is tracking RapidPro name so we must update both.
            poll.name = poll.rapidpro_name = temba_poll.name
        else:
            # Custom name will be maintained despite update of RapidPro name.
            poll.rapidpro_name = temba_poll.name

        poll.save()

        # Sync related Questions, and maintain question order.
        temba_questions = temba_polls.rulesets
        temba_questions = OrderedDict((r.uuid, r) for r in temba_poll.rulesets)

        # Remove Questions that are no longer on RapidPro.
        poll.questions.exclude(ruleset_uuid__in=temba_questions.keys()).delete()

        # Create new or update existing Questions to match RapidPro data.
        for order, temba_question in enumerate(temba_questions.values(), 1):
            Question.objects.from_temba(poll, temba_question, order)

        return poll

    @transaction.atomic
    def set_active_for_org(self, org, uuids):
        """Set matching org Polls to be active, and all others to be inactive.

        If an invalid UUID is given, a ValueError is raised and the transaction
        is rolled back.
        """
        active_count = org.polls.filter(flow_uuid__in=uuids).update(is_active=True)
        if active_count != len(uuids):
            invalid_uuids = set(uuids) - set(org.polls.values_list('flow_uuid', flat=True))
            raise ValueError(
                "No Poll for {} matching these UUIDS: {}".format(
                    org.name, invalid_uuids))
        org.polls.exclude(flow_uuid__in=uuids).update(is_active=False)

    def sync(self, org):
        """Update the org's Polls and Questions from RapidPro."""
        # Retrieve current Polls known to RapidPro.
        temba_polls = org.get_temba_client().get_flows(archived=False)
        temba_polls = {p.uuid: p for p in temba_polls}

        # Remove Polls that are no longer on RapidPro.
        org.polls.exclude(flow_uuid__in=temba_polls.keys()).delete()

        # Create new or update existing Polls to match RapidPro data.
        for temba_poll in temba_polls.values():
            Poll.objects.from_temba(org, temba_poll)


@python_2_unicode_compatible
class Poll(models.Model):
    """Corresponds to a RapidPro flow.

    Keeping track of contact responses to a flow is data-intensive, so Tracpro
    only tracks flows that the user has selected. Selected flows are managed
    as Polls.
    """
    flow_uuid = models.CharField(max_length=36)
    org = models.ForeignKey(
        'orgs.Org', related_name='polls', verbose_name=_('org'))
    rapidpro_name = models.CharField(
        max_length=64, verbose_name=_('RapidPro name'))
    name = models.CharField(
        max_length=64, blank=True, verbose_name=_('name'))

    # Set this to False rather than deleting a Poll. If the user should
    # re-select the corresponding flow later, we can avoid re-importing
    # existing data.
{'fraction_non_alphanumeric': 0.08099256833354326, 'fraction_numerical': 0.002267288071545535, 'mean_word_length': 3.099122354155911, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2815355', 'n_tokens_mistral': 2385, 'n_tokens_neox': 2281, 'n_words': 699}