def freeze_scores(sample, includes=None, excludes=None, collected_by=None, created_at=None, segment_path=None): #pylint:disable=too-many-arguments,disable=too-many-locals # This function must be executed in a `transaction.atomic` block. LOGGER.info("freeze scores for %s based of sample %s", sample.account, sample.slug) created_at = datetime_or_now(created_at) if not segment_path: segment_path = '/' score_sample = Sample.objects.create( created_at=created_at, campaign=sample.campaign, account=sample.account, extra=sample.extra, is_frozen=True) # Copy the actual answers score_metric_id = Metric.objects.get(slug='score').pk for answer in Answer.objects.filter( sample=sample, question__path__startswith=segment_path).exclude( metric_id=score_metric_id): answer.pk = None answer.created_at = created_at answer.sample = score_sample answer.save() LOGGER.debug("save(created_at=%s, question_id=%s, metric_id=%s,"\ " measured=%s, denominator=%s, collected_by=%s,"\ " sample=%s)", answer.created_at, answer.question_id, answer.metric_id, answer.measured, answer.denominator, answer.collected_by, answer.sample) # Create frozen scores for answers we can derive a score from # (i.e. assessment). assessment_metric_id = Metric.objects.get(slug='assessment').pk calculator = get_score_calculator(segment_path) scored_answers = calculator.get_scores( sample.campaign, assessment_metric_id, prefix=segment_path, includes=includes, excludes=excludes) for decorated_answer in scored_answers: if (decorated_answer.answer_id and decorated_answer.is_planned == sample.extra): numerator = decorated_answer.numerator denominator = decorated_answer.denominator LOGGER.debug("create(created_at=%s, question_id=%s,"\ " metric_id=%s, measured=%s, denominator=%s,"\ " collected_by=%s, sample=%s)", created_at, decorated_answer.id, score_metric_id, numerator, denominator, collected_by, score_sample) _ = Answer.objects.create( created_at=created_at, question_id=decorated_answer.id, metric_id=score_metric_id, measured=numerator, denominator=denominator, collected_by=collected_by, sample=score_sample) sample.created_at = datetime_or_now() sample.save() return score_sample
def ends_at(self): if not hasattr(self, '_ends_at'): self._ends_at = self.request.GET.get('ends_at', None) if self._ends_at: self._ends_at = self._ends_at.strip('"') try: self._ends_at = datetime_or_now(self._ends_at) except ValueError: self._ends_at = datetime_or_now() return self._ends_at
def paginate_queryset(self, queryset, request, view=None): expired_at = datetime_or_now() - relativedelta(year=1) self.no_assessment = 0 self.abandoned = 0 self.expired = 0 self.assessment_phase = 0 self.improvement_phase = 0 self.completed = 0 for account in queryset: last_activity_at = account.get('last_activity_at', None) if last_activity_at: if account.get('assessment_completed', False): if account.get('improvement_completed', False): if last_activity_at < expired_at: self.expired += 1 else: self.completed += 1 else: if last_activity_at < expired_at: self.abandoned += 1 else: self.improvement_phase += 1 else: if last_activity_at < expired_at: self.abandoned += 1 else: self.assessment_phase += 1 else: self.no_assessment += 1 return super(CompletionSummaryPagination, self).paginate_queryset( queryset, request, view=view)
def populate_historical_scores(self, organization): if not isinstance(organization, Organization): organization = Organization.objects.get(slug=organization) assessment_sample = Sample.objects.filter( extra__isnull=True, survey=self.survey, account=organization).order_by('-created_at').first() # Backup current answers backups = {} for answer in Answer.objects.filter(sample=assessment_sample, metric_id=1): backups[answer.pk] = answer.measured today = datetime_or_now() for months in [6, 12, 24]: for answer in Answer.objects.filter(sample=assessment_sample, metric_id=1): choices = [1, 2, 3, 4] answer.measured = random.choice(choices[(answer.measured - 1):]) answer.save() created_at = today - relativedelta(months=months) score_sample = freeze_scores(assessment_sample, includes=[assessment_sample.pk], excludes=get_testing_accounts(), created_at=created_at) # XXX Sample.created_at is using `auto_now_add` score_sample.created_at = created_at score_sample.save() # Restore backup for answer in Answer.objects.filter(sample=assessment_sample, metric_id=1): answer.measured = backups[answer.pk] answer.save()
def migrate_completion_status(): created_at = datetime_or_now() with transaction.atomic(): scores_api = SuppliersAPIView() rollup_tree = scores_api.rollup_scores() for account in get_account_model().objects.all(): accounts = rollup_tree[0].get('accounts', {}) if account.pk in accounts: scores = accounts.get(account.pk, None) if scores: normalized_score = scores.get('normalized_score', None) if normalized_score is not None: assess_api = AssessmentAPIView() sample = Sample.objects.filter( extra__isnull=True, survey__title=ReportMixin.report_title, account=account).order_by( '-created_at').first() assess_api.freeze_scores( sample, includes=[sample], excludes=settings.TESTING_RESPONSE_IDS, created_at=created_at) improvement_score = scores.get('improvement_score', None) if improvement_score is not None: for sample in Sample.objects.filter( extra='is_planned', survey__title=ReportMixin.report_title, account=account): sample.is_frozen = True sample.save()
def get_context_data(self, **kwargs): context = super(SuppliersView, self).get_context_data(**kwargs) root, trail = self.breadcrumbs update_context_urls(context, { 'api_suppliers': reverse('api_suppliers', args=(self.account, root)), 'api_accessibles': site_prefixed( "/api/profile/%(account)s/plans/%(account)s-report/"\ "subscriptions/" % {'account': self.account}), 'api_organizations': site_prefixed("/api/profile/"), 'api_organization_profile': site_prefixed( "/api/profile/%(account)s/" % {'account': self.account}), 'download': reverse('reporting_organization_download', args=(self.account, root)), 'improvements_download': reverse( 'reporting_organization_improvements_download', args=(self.account, root)) }) try: extra = json.loads(self.account.extra) except (IndexError, TypeError, ValueError) as err: extra = {} start_at = extra.get('start_at', None) context.update({ 'score_toggle': True, 'account_extra': self.account.extra, 'date_range': { 'start_at': start_at, 'ends_at': (datetime_or_now() + relativedelta(days=1) ).isoformat(), } }) return context
def ends_at(self): if not hasattr(self, '_ends_at'): if self.sample.is_frozen: self._ends_at = self.sample.created_at else: self._ends_at = datetime_or_now() return self._ends_at
def get_supplier_managers(account): ends_at = datetime_or_now() queryset = Subscription.objects.filter( ends_at__gt=ends_at, organization=account).select_related('plan__organization').values_list( 'plan__organization__slug', 'plan__organization__full_name') supplier_managers = [{ 'slug': supplier_manager[0], 'printable_name': supplier_manager[1] } for supplier_manager in queryset] return supplier_managers
def get_context_data(self, **kwargs): context = {'base_url': self.get_base_url()} organization = self.kwargs.get('organization', None) if organization: for accessible in self.get_accessibles(self.request): if accessible['slug'] == organization: context.update({'organization': accessible}) break from_root, trail = self.breadcrumbs root = None if trail: root = self._build_tree(trail[-1][0], from_root, cut=TransparentCut()) # Flatten icons and practices (i.e. Energy Efficiency) to produce # the list of charts. for element in six.itervalues(root[1]): for chart in self.score_charts: # We use `score_charts`, not `get_printable_charts` because # not all top level icons might show up in the benchmark # graphs, yet we need to display the scores under the icons. if element[0]['slug'] == chart['slug']: if 'normalized_score' in chart: element[0][ 'normalized_score'] = "%s%%" % chart.get( 'normalized_score') else: element[0]['normalized_score'] = "N/A" element[0]['score_weight'] = chart.get( 'score_weight', "N/A") break charts = self.get_printable_charts() for chart in charts: if chart['slug'] == 'totals': context.update({ 'total_chart': chart, 'nb_respondents': chart.get('nb_respondents', "N/A") }) break context.update({ 'charts': [chart for chart in charts if chart['slug'] != 'totals'], 'breadcrumbs': trail, 'root': root, 'at_time': datetime_or_now() }) return context
def post(self, request, *args, **kwargs): """ Uploads a static asset file. **Examples .. code-block:: http POST /api/assets/ HTTP/1.1 """ #pylint: disable=unused-argument,too-many-locals uploaded_file = request.data['file'] if self.content_type: # We optionally force the content_type because S3Store uses # mimetypes.guess and surprisingly it doesn't get it correct # for 'text/css'. uploaded_file.content_type = self.content_type sha1 = hashlib.sha1(uploaded_file.read()).hexdigest() # Store filenames with forward slashes, even on Windows filename = force_text(uploaded_file.name.replace('\\', '/')) sha1_filename = sha1 + os.path.splitext(filename)[1] storage = get_default_storage(self.request, self.account) stored_filename = sha1_filename if self.store_hash else filename prefix = request.data.get('prefix', None) if prefix is not None: stored_filename = urljoin(prefix, stored_filename) result = {} if storage.exists(stored_filename): if self.replace_stored: storage.delete(stored_filename) storage.save(stored_filename, uploaded_file) response_status = status.HTTP_201_CREATED else: result = { "message": "%s is already in the gallery." % filename } response_status = status.HTTP_200_OK else: storage.save(stored_filename, uploaded_file) response_status = status.HTTP_201_CREATED result.update({ 'location': storage.url(stored_filename), 'updated_at': datetime_or_now(), 'tags': [] }) return Response(self.get_serializer().to_representation(result), status=response_status)
def freeze_scores(sample, includes=None, excludes=None, collected_by=None, created_at=None): LOGGER.info("freeze scores for %s", sample.account) created_at = datetime_or_now(created_at) scored_answers = get_scored_answers( population=Consumption.objects.get_active_by_accounts( excludes=excludes), includes=includes) score_sample = Sample.objects.create(created_at=created_at, survey=sample.survey, account=sample.account, extra='completed', is_frozen=True) with connection.cursor() as cursor: cursor.execute(scored_answers, params=None) col_headers = cursor.description decorated_answer_tuple = namedtuple( 'DecoratedAnswerTuple', [col[0] for col in col_headers]) for decorated_answer in cursor.fetchall(): decorated_answer = decorated_answer_tuple(*decorated_answer) if decorated_answer.answer_id: numerator = decorated_answer.numerator denominator = decorated_answer.denominator _ = Answer.objects.create(created_at=created_at, question_id=decorated_answer.id, metric_id=2, measured=numerator, denominator=denominator, collected_by=collected_by, sample=score_sample, rank=decorated_answer.rank) sample.created_at = datetime_or_now() sample.save() return score_sample
def post(self, request, *args, **kwargs): """ Uploads a static asset file. **Examples .. code-block:: http POST /api/assets/ HTTP/1.1 """ #pylint: disable=unused-argument,too-many-locals uploaded_file = request.data['file'] if self.content_type: # We optionally force the content_type because S3Store uses # mimetypes.guess and surprisingly it doesn't get it correct # for 'text/css'. uploaded_file.content_type = self.content_type sha1 = hashlib.sha1(uploaded_file.read()).hexdigest() # Store filenames with forward slashes, even on Windows filename = force_text(uploaded_file.name.replace('\\', '/')) sha1_filename = sha1 + os.path.splitext(filename)[1] storage = get_default_storage(self.request, self.account) stored_filename = sha1_filename if self.store_hash else filename prefix = request.data.get('prefix', None) if prefix is not None: stored_filename = urljoin(prefix, stored_filename) result = {} if storage.exists(stored_filename): if self.replace_stored: storage.delete(stored_filename) storage.save(stored_filename, uploaded_file) response_status = status.HTTP_201_CREATED else: result = { "message": "%s is already in the gallery." % filename} response_status = status.HTTP_200_OK else: storage.save(stored_filename, uploaded_file) response_status = status.HTTP_201_CREATED result.update({ 'location': storage.url(stored_filename), 'updated_at': datetime_or_now(), 'tags': [] }) return Response(self.get_serializer().to_representation(result), status=response_status)
def get_supplier_managers(account, ends_at=None): ends_at = datetime_or_now(ends_at) queryset = Subscription.objects.filter(ends_at__gt=ends_at, organization=account) if is_testing(account): queryset = queryset.filter( plan__organization__extra__contains='testing') else: queryset = queryset.exclude( plan__organization__extra__contains='testing') queryset = queryset.select_related('plan__organization').values_list( 'plan__organization__slug', 'plan__organization__full_name') supplier_managers = [{ 'slug': supplier_manager[0], 'printable_name': supplier_manager[1] } for supplier_manager in queryset] return supplier_managers
def get_filename(self): return datetime_or_now().strftime(self.basename + '-%Y%m%d.xlsx')
def perform_create(self, serializer): if not is_authenticated(self.request): raise PermissionDenied() serializer.save(created_at=datetime_or_now(), element=self.element, user=self.request.user)
def create(self, request, *args, **kwargs): #pylint:disable=too-many-locals,too-many-statements if request.data: serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) supplier_managers = [serializer.validated_data] else: supplier_managers = get_supplier_managers(self.account) last_activity_at = Answer.objects.filter( sample=self.assessment_sample).aggregate(Max('created_at')).get( 'created_at__max', None) if not last_activity_at: raise ValidationError({'detail': "You cannot share a scorecard"\ " before completing the assessment."}) last_scored_assessment = Sample.objects.filter( is_frozen=True, extra__isnull=True, survey=self.survey, account=self.account).order_by('-created_at').first() if (not last_scored_assessment or last_scored_assessment.created_at < last_activity_at): # New activity since last record, let's freeze the assessment # and planning. with transaction.atomic(): last_scored_assessment = freeze_scores( self.assessment_sample, includes=self.get_included_samples(), excludes=self._get_filter_out_testing(), collected_by=self.request.user) if self.improvement_sample: freeze_scores(self.improvement_sample, includes=self.get_included_samples(), excludes=self._get_filter_out_testing(), collected_by=self.request.user) # send assessment updated and invite notifications data = supplier_managers status_code = None for supplier_manager in supplier_managers: supplier_manager_slug = supplier_manager.get('slug', None) if supplier_manager_slug: try: matrix = Matrix.objects.filter( account__slug=supplier_manager_slug, metric__slug='totals').select_related('account').get() # Supplier manager already has a dashboard LOGGER.info("%s shared %s assessment (%s) with %s", self.request.user, self.account, last_scored_assessment, supplier_manager_slug) # Update or create dashboard entry ends_at = datetime_or_now() + relativedelta(years=1) subscription_query = Subscription.objects.filter( organization=self.account, plan=Plan.objects.get(organization=matrix.account)) if subscription_query.exists(): # The Subscription already exists. The metadata ( # either requested by supplier manager or pro-actively # shared) was set on creation. We thus just need to # extend the end date and clear the grant_key. subscription_query.update(grant_key=None, ends_at=ends_at) else: # Create the subscription with a request_key, and # a extra tag to keep track of originator. Subscription.objects.create( organization=self.account, plan=Plan.objects.get(organization=matrix.account), ends_at=ends_at, extra='{"originator":"supplier"}') # send assessment updated. reason = supplier_manager.get('message', None) if reason: reason = force_text(reason) signals.assessment_completed.send( sender=__name__, assessment=last_scored_assessment, path=self.kwargs.get('path'), notified=matrix.account, reason=reason, request=self.request) if status_code is None: status_code = status.HTTP_201_CREATED except Matrix.DoesNotExist: # Registered supplier manager but no dashboard # XXX send hint to get paid version. LOGGER.error("%s shared %s assessment (%s) with matrix %s", self.request.user, self.account, last_scored_assessment, supplier_manager_slug) # XXX Should technically add all managers # of `supplier_manager_slug` account_model = get_account_model() try: dashboard_account = account_model.objects.get( slug=supplier_manager_slug) data = {} data.update(supplier_manager) data.update({ 'slug': dashboard_account.email, 'email': dashboard_account.email }) if status_code is None: status_code = status.HTTP_404_NOT_FOUND except account_model.DoesNotExist: raise ValidationError({ 'detail': _("Cannot find account '%s'") % supplier_manager_slug }) else: # Organization profile cannot be found. contact_email = supplier_manager.get('email', None) LOGGER.error("%s shared %s assessment (%s) with %s", self.request.user, self.account, last_scored_assessment, contact_email) data = {} data.update(supplier_manager) data.update({'slug': contact_email}) if status_code is None: status_code = status.HTTP_404_NOT_FOUND return Response(data, status=status_code)