class Migration(migrations.Migration): dependencies = [ ('projects', '0018_merge_20170106_1627'), ] operations = [ migrations.AlterField( model_name='project', name='currencies', field=multiselectfield.MultiSelectField(choices=[(b'EUR', 'Euro')], default=[], max_length=100), ), migrations.AlterField( model_name='project', name='payout_status', field=models.CharField(blank=True, choices=[(b'needs_approval', 'Needs approval'), (b'approved', 'Approved'), (b'created', 'Created'), (b'in_progress', 'In progress'), (b'partial', 'Partial'), (b'success', 'Success'), (b'failed', 'Failed')], max_length=50, null=True), ), ]
class Migration(migrations.Migration): dependencies = [ ('projects', '0071_merge_20180412_1133'), ] operations = [ migrations.AddField( model_name='projectplatformsettings', name='facebook_at_work_url', field=models.URLField(max_length=100, null=True), ), migrations.AddField( model_name='projectplatformsettings', name='share_options', field=multiselectfield.MultiSelectField(choices=[ (b'twitter', 'Twitter'), (b'facebook', 'Facebook'), (b'facebookAtWork', 'Facebook at Work'), (b'linkedin', 'LinkedIn'), (b'whatsapp', 'Whatsapp'), (b'email', 'Email') ], default=[], max_length=100), preserve_default=False, ), ]
class Migration(migrations.Migration): dependencies = [ ('projects', '0077_auto_20180518_1050'), ] operations = [ migrations.AlterField( model_name='projectplatformsettings', name='share_options', field=multiselectfield.MultiSelectField(blank=True, choices=[(b'twitter', 'Twitter'), (b'facebook', 'Facebook'), (b'facebookAtWork', 'Facebook at Work'), (b'linkedin', 'LinkedIn'), (b'whatsapp', 'Whatsapp'), (b'email', 'Email')], max_length=100), ), ]
class Migration(migrations.Migration): dependencies = [ ('projects', '0014_auto_20161115_1601'), ] operations = [ migrations.AlterField( model_name='project', name='currencies', field=multiselectfield.MultiSelectField(choices=[(b'EUR', 'Euro')], default=[], max_length=100), ), ]
class Reminder(models.Model, model_utils.FieldList): REQUIRED = 'r' TEXT = 't' EMAIL = 'e' CALL = 'c' COMPLETED = 'o' DELETED = 'd' FOLLOW_UP_TYPES = ( (REQUIRED, 'Required'), (TEXT, 'Text'), (EMAIL, 'Email'), (CALL, 'Call'), (COMPLETED, 'Completed'), (DELETED, 'Deleted'), ) follow_up = multiselectfield.MultiSelectField(choices=FOLLOW_UP_TYPES, default=REQUIRED) NO_ANSWER = 'n' VOICEMAIL = 'v' RESULTS = ( (NO_ANSWER, 'No answer'), (VOICEMAIL, 'Voicemail'), ) result = models.CharField(max_length=1, choices=RESULTS, blank=True) created = models.DateField() class Meta: abstract = True def get_all_fields(self): fields = super().get_all_fields() fields.pop('id') # hide detail/update/delete in generic template modal_btn_pseudo = model_utils.FieldList.PseudoBtn('Update') modal_btn_pseudo.type = 'modal' modal_btn = self.Field(modal_btn_pseudo, '') fields.update({'modal_btn': modal_btn}) fields.move_to_end('modal_btn', last=False) new_value = fields['follow_up'].value.replace(' ', '<br />') follow_up_pseudo = model_utils.FieldList.PseudoField('Follow up') follow_up = self.Field(follow_up_pseudo, safestring.mark_safe(new_value)) fields.update({'follow_up': follow_up}) return fields
class Migration(migrations.Migration): dependencies = [ ('projects', '0013_merge'), ] operations = [ migrations.AlterField( model_name='project', name='currencies', field=multiselectfield.MultiSelectField(choices=[(b'EUR', 'Euro')], default=[], max_length=100, null=True), ), ]
class Migration(migrations.Migration): dependencies = [ ('projects', '0047_auto_20171024_1016'), ] operations = [ migrations.AddField( model_name='projectplatformsettings', name='contact_types', field=multiselectfield.MultiSelectField(choices=[ (b'organization', 'Organization'), (b'personal', 'Personal') ], default=['organization'], max_length=100), preserve_default=False, ), ]
class Migration(migrations.Migration): dependencies = [ ('projects', '0042_merge_20170920_1332'), ] operations = [ migrations.CreateModel( name='ProjectPlatformSettings', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('update', models.DateTimeField(auto_now=True)), ('project_create_types', multiselectfield.MultiSelectField(choices=[(b'sourcing', 'Sourcing'), (b'funding', 'Funding')], max_length=100)), ('project_create_flow', models.CharField(choices=[(b'combined', 'Combined'), (b'choice', 'Choice')], max_length=100)), ('project_suggestions', models.BooleanField(default=True)), ('project_contact_method', models.CharField(choices=[(b'mail', 'E-mail'), (b'phone', 'Choose')], max_length=100)), ], options={ 'verbose_name': 'Project Settings', 'verbose_name_plural': 'Project Settings', }, ), migrations.CreateModel( name='ProjectSearchFilter', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(choices=[(b'location', 'Location'), (b'theme', 'Theme'), (b'skill', 'Skill'), (b'date', 'Date'), (b'status', 'Status'), (b'type', 'Type')], max_length=100)), ('default', models.CharField(blank=True, max_length=100, null=True)), ('values', models.CharField(blank=True, help_text='Comma separated list of possible values', max_length=500, null=True)), ], ), migrations.AddField( model_name='projectplatformsettings', name='search_filters', field=models.ManyToManyField(to='projects.ProjectSearchFilter'), ), ]
class StudyAction(models.Model): """ A model for the data that we're saving in CSV form in production. We translate the data there into a database model that we can then use for data processing however we wish to. This should never be populated with the `dropbox_to_db` and `db_to_dropbox` scripts. Instead, prefer the use of another script that converts CSV data into rows amenable to this model """ user = models.ForeignKey(User, on_delete=models.CASCADE) start_timestamp = models.DateTimeField( ) # This is the timestamp one row above in the CSV end_timestamp = models.DateTimeField( ) # This is the timestamp on the same row start_state = models.CharField(max_length=80) diagnoses = multiselectfield.MultiSelectField( choices=tuple(constants.DIAGNOSES.items())) diagnosis_certainty = models.IntegerField() action = models.CharField(max_length=20, choices=tuple(constants.ACTIONS.items())) next_state = models.CharField(max_length=80, null=True, blank=True) video_loaded_time = models.DateTimeField() video_stop_time = models.DateTimeField() dx_selected_time = models.DateTimeField() dx_confirmed_time = models.DateTimeField() ax_selected_time = models.DateTimeField() # Other data not part of the JSON action information browser_refreshed = models.BooleanField(default=False) corrupted_dx_suggestions = models.BooleanField(default=False) corrupted_ax_suggestions = models.BooleanField(default=False) dx_suggestions = multiselectfield.MultiSelectField(choices=tuple( constants.DIAGNOSES.items()), blank=True, null=True) ax_suggestions = multiselectfield.MultiSelectField(choices=tuple( constants.ACTIONS.items()), blank=True, null=True) # Cached property _action_idx = None # Fields that should not be part of the JSON action information NOT_CSV_HEADER_FIELDS = [ 'id', 'user', 'start_timestamp', 'end_timestamp', 'browser_refreshed', 'dx_suggestions', 'ax_suggestions', # The corrupted flags should be populated post-processing, but they # are now forever part of the CSV header ] class Meta: verbose_name = _('study action') verbose_name_plural = _('study actions') def __str__(self): return f"{self.user.username}, {self.action_idx}" @staticmethod def get_csv_headers(): """ Headers corresponding to the fields that should be present in the CSV. The returned value is used by the `utils` code to figure out how to structure the CSV """ return [ x.name for x in StudyAction._meta.get_fields() if x.name not in StudyAction.NOT_CSV_HEADER_FIELDS ] @property def action_idx(self): """Get the action index for the given user""" if self._action_idx is None: self._action_idx = StudyAction.objects.filter( user=self.user, start_timestamp__lt=self.start_timestamp).count() return self._action_idx @property def duration(self): return ( self.end_timestamp - self.start_timestamp) if self.start_timestamp is not None else None @property def dx_decision_duration(self): return ( self.dx_selected_time - self.video_stop_time) if self.video_stop_time is not None else None @property def ax_decision_duration(self): return (self.ax_selected_time - self.dx_confirmed_time ) if self.dx_confirmed_time is not None else None @property def decision_duration(self): return ( self.ax_selected_time - self.video_stop_time) if self.video_stop_time is not None else None @property def chose_dx_suggestion(self): if self.diagnoses is None or self.dx_suggestions is None: return None return len(set(self.diagnoses) & set(self.dx_suggestions)) > 0 @property def chose_ax_suggestion(self): # None of the data will hit this. So instead we return 0 (not NA) when # there are no AX suggestions if self.action is None or self.ax_suggestions is None: return None return self.action in self.ax_suggestions @property def chose_dx_optimal(self): if self.start_state is None or self.diagnoses is None: return None state = State(eval(self.start_state)) optimal_dx = Suggestions().ordered_diagnoses(state, None, accumulate=True) return len(set(self.diagnoses) & set(optimal_dx)) > 0 @property def chose_ax_optimal(self): if self.start_state is None or self.action is None: return None state = State(eval(self.start_state)) optimal_ax = Suggestions().optimal_action(state, None) return self.action in optimal_ax
class Service(models.Model): METHOD_CHOICES = ( (u'delete', u'delete'), (u'get', u'get'), (u'post', u'post'), (u'patch', u'patch'), (u'put', u'put'), ) codes = sorted( list( set([ a if isinstance(a, int) else 100 for a in requests.codes.__dict__.values() ]))) ACCEPTED_CODE_CHOICES = [] for code in codes: if code >= 400: ACCEPTED_CODE_CHOICES.append((code, code)) REJECTED_CODE_CHOICES = [] for code in codes: if code < 400: REJECTED_CODE_CHOICES.append((code, code)) name = models.CharField(max_length=200, default='', db_index=True) url = models.URLField(max_length=200, default='') method = models.CharField(max_length=20, choices=METHOD_CHOICES, default='get') timeout = models.IntegerField(blank=True, null=True, default=None) verify = models.BooleanField(default=True) headers = models.TextField(blank=True, default='') parameters = models.TextField(blank=True, default='') service_failover = models.ManyToManyField( "self", through='ServiceFailover', symmetrical=False, related_name='service_failover_relation') accepted_codes = multiselectfield.MultiSelectField( choices=ACCEPTED_CODE_CHOICES, blank=True, default=None) rejected_codes = multiselectfield.MultiSelectField( choices=REJECTED_CODE_CHOICES, blank=True, default=None) def __str__(self): return self.name def accept_code(self, code): res = True if code < 400 and str(code) in self.rejected_codes: res = False elif code >= 400 and str(code) not in self.accepted_codes: res = False return res async def request_async_task(self, max_retry=10, retry_interval=1, header_data=None, get_data=None, url_data=None, parse_data=None, parameters=None): service_response = self.request_recursive(header_data=header_data, get_data=get_data, url_data=url_data, parse_data=parse_data, parameters=parameters) current_try = 1 while not service_response['success'] and (max_retry == 0 or max_retry < current_try): time.sleep(retry_interval) if max_retry != 0: current_try += 1 service_response = self.request_recursive(header_data=header_data, get_data=get_data, url_data=url_data, parse_data=parse_data, parameters=parameters) def request_async(self, max_retry=10, retry_interval=1, header_data=None, get_data=None, url_data=None, parse_data=None, parameters=None): loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) loop = asyncio.get_event_loop() loop.run_until_complete( self.request_async_task(max_retry=max_retry, retry_interval=retry_interval, header_data=header_data, get_data=get_data, url_data=url_data, parse_data=parse_data, parameters=parameters)) loop.close() def request_recursive(self, header_data=None, get_data=None, url_data=None, parse_data=None, parameters=None): response = self.request(header_data=header_data, get_data=get_data, url_data=url_data, parameters=parameters) if response.get('result') == 'OK': return response elif self.pk and self.service_failover.count() != 0: response_failover_stack = [] for service_for_failover in self.service_failover.all(): response_failover, result_failover = service_for_failover.get_service_data( header_data=header_data, get_data=get_data, url_data=url_data, parse_data=parse_data, parameters=parameters) if response_failover.get('result') == 'OK': return response_failover, result_failover response_failover_stack.append(response_failover) response.update({'failovers': response_failover_stack}) return response def request(self, header_data=None, get_data=None, url_data=None, parameters=None): headers = {} service_headers = self.headers if service_headers: headers = json.loads(service_headers) for headers_key in headers: if header_data and headers[headers_key] and headers[ headers_key] in header_data: headers[headers_key] = header_data[headers[headers_key]] data = {} service_parameters = self.parameters if service_parameters: data = json.loads(service_parameters) for data_key in data: if data[data_key] and get_data and data[data_key] in get_data: data[data_key] = get_data[data[data_key]] elif parameters: data = parameters service_url = self.url if url_data: for url_data_key in url_data: service_url = service_url.replace(url_data_key, url_data[url_data_key]) response = self.get_from_service(service_url, headers, data) return response def get_from_service(self, url, headers, data): if self.timeout: timeout = self.timeout else: timeout = 10 try: if self.method == 'post': result = requests.post(url, data=json.dumps(data), headers=headers, timeout=timeout, verify=self.verify) elif self.method == 'get': data = urllib.parse.urlencode(data) if data: url = url + '?' + data result = requests.get(url, headers=headers, timeout=timeout, verify=self.verify) elif self.method == 'patch': result = requests.patch(url, data=json.dumps(data), headers=headers, timeout=timeout, verify=self.verify) elif self.method == 'put': result = requests.put(url, data=json.dumps(data), headers=headers, timeout=timeout, verify=self.verify) elif self.method == 'delete': data = urllib.parse.urlencode(data) if data: url = url + '?' + data result = requests.delete(url, headers=headers, timeout=timeout, verify=self.verify) try: response = result.json() except: response = {} if not self.accept_code(result.status_code): response = { "result": "error", "success": False, "content": response, "code": result.status_code, "elapsed": result.elapsed.seconds + result.elapsed.microseconds / 1000000.0 } else: response = { "result": "ok", "success": True, "content": response, "code": result.status_code, "elapsed": result.elapsed.seconds + result.elapsed.microseconds / 1000000.0 } except requests.exceptions.Timeout: response = { "result": "error", "success": False, "content": 'service timeout', "elapsed": timeout, "code": 408 } except requests.exceptions.ConnectionError: response = { "result": "error", "success": False, "content": 'connection error', "code": 503 } return response