def test_choices_from_list(self): choices = get_choices_from_list(self.values_list.index) self.assertCountEqual(choices, [ ('test1', 'test1 - Test 1'), ('test2', 'test2 - Test 2'), ('test3', 'test3 - Test 3'), ])
def update_schedule_section(document, metadata, revision, rewrite_schedule=True, **kwargs): """Update the "actual" dates of the schedule section. The "<status> Actual Date" fields must be updated automatically depending on the different revisions' statuses and created date. See #172 """ if not rewrite_schedule: return if not isinstance(metadata, ScheduleMixin): return list_index = revision._meta.get_field('status').list_index statuses = get_choices_from_list(list_index) for status, _ in statuses: field = 'status_{}_actual_date'.format(status).lower() setattr(metadata, field, None) revisions = document.get_all_revisions() for rev in revisions.reverse(): if rev.status: field = 'status_{}_actual_date'.format(rev.status).lower() if not getattr(metadata, field, None): setattr(metadata, field, rev.created_on) metadata.save()
def test_choices_from_list(self): choices = get_choices_from_list(self.values_list.index) self.assertItemsEqual(choices, [ (u'test1', u'test1 - Test 1'), (u'test2', u'test2 - Test 2'), (u'test3', u'test3 - Test 3'), ])
def fetch_documents_behind_schedule(self, category): """Fetch documents behind schedule. Documents behind schedule are documents that were meant to reach a certain status at a certain date (forecast), and that date has already passed whereas the document still has not reached that status. It technical terms, it means: - in it's "schedule" table, the document has one line with a `forecast` value that is < today and an `actual` value that is null. - that lines concerns a status that is higher than the current status in the document workflow. """ # Get the list of existing statuses for this category's document class Metadata = category.document_class() Revision = Metadata.get_revision_class() list_index = Revision._meta.get_field('status').list_index statuses = [ status.lower() for status, _ in get_choices_from_list(list_index) ] today = timezone.now().date() # Create a first coarse filter to get all documents that MAY be # behind schedule. # Get all documents with any X status with a past forecast date AND # not actual date. # # However, that does not mean that this document is behind schedule, # because statuses can be skipped. E.g a document with a forecast date # for status A that goes directly to the next status B will have an # empty value for the `status_A_actual_date` but is still not # behind schedule. # # The reason we don't filter everything in a single query is because it # would make the said query ridiculously complex. conditions = [] for status in statuses: forecast_field = 'status_{}_forecast_date'.format(status) actual_field = 'status_{}_actual_date'.format(status) # Let's check that the actual schedule fields corresponding to # this status exists try: Metadata._meta.get_field(forecast_field) Metadata._meta.get_field(actual_field) except FieldDoesNotExist: continue forecast_condition = '{}__lt'.format(forecast_field) actual_condition = '{}__isnull'.format(actual_field) conditions.append( Q(**{forecast_condition: today}) & Q(**{actual_condition: True})) coarse_filter = reduce(operator.or_, conditions) documents = Metadata.objects \ .filter(document__category=category) \ .filter(coarse_filter) \ .select_related('document', 'latest_revision') def is_behind_schedule(document): """Tells if a single document is actually behind schedule. Check for a "behind schedule" condition, but only for statuses that the document has not reached yet. """ current_status = document.status.lower() # statuses are sorted by chronological order current_status_index = statuses.index(current_status) for status_index, status in enumerate(statuses): if status_index <= current_status_index: # The document has already passed this status, ignore # this schedule line. continue forecast_field = 'status_{}_forecast_date'.format(status) actual_field = 'status_{}_actual_date'.format(status) if getattr(document, forecast_field, None) is None: continue forecast_date = getattr(document, forecast_field) actual_date = getattr(document, actual_field) if forecast_date < today and actual_date is None: return True return False # Here, we filter the queryset to remove false positives. behind_schedule_documents = filter(is_behind_schedule, documents) return list(behind_schedule_documents)