def validate(self, data): """Validate the absence data. An absence should not be created on a public holiday or a weekend. :returns: The validated data :rtype: dict """ location = Employment.objects.for_user( data.get('user'), data.get('date') ).location if PublicHoliday.objects.filter( location_id=location.id, date=data.get('date') ).exists(): raise ValidationError( 'You can\'t create an absence on a public holiday' ) workdays = [int(day) for day in location.workdays] if data.get('date').isoweekday() not in workdays: raise ValidationError('You can\'t create an absence on a weekend') return data
def validate(self, data): """ Validate that verified by is only set by reviewer or superuser. Additionally make sure a report is cannot be verified_by if is still needs review. """ user = self.context["request"].user current_verified_by = self.instance and self.instance.verified_by new_verified_by = data.get("verified_by") task = data.get("task") or self.instance.task review = data.get("review") if new_verified_by != current_verified_by: is_reviewer = ( user.is_superuser or task.project.reviewers.filter(id=user.id).exists() ) if not is_reviewer: raise ValidationError(_("Only reviewer may verify reports.")) if new_verified_by is not None and new_verified_by != user: raise ValidationError(_("You may only verifiy with your own user")) if new_verified_by and review: raise ValidationError( _("Report can't both be set as `review` and `verified`.") ) return data
def validate(self, data): """Validate the employment as a whole. Ensure the end date is after the start date and there is only one active employment per user and there are no overlapping employments. :throws: django.core.exceptions.ValidationError :return: validated data :rtype: dict """ instance = self.instance start_date = data.get("start_date", instance and instance.start_date) end_date = data.get("end_date", instance and instance.end_date) if end_date and start_date >= end_date: raise ValidationError( _("The end date must be after the start date")) user = data.get("user", instance and instance.user) employments = models.Employment.objects.filter(user=user) # end date not set means employment is ending today end_date = end_date or date.today() employments = employments.annotate( end=Coalesce("end_date", Value(date.today()))) if instance: employments = employments.exclude(id=instance.id) if any([ e.start_date <= end_date and start_date <= e.end for e in employments ]): raise ValidationError( _("A user can't have multiple employments at the same time")) return data
def validate(self, data): """Validate the absence data. An absence should not be created on a public holiday or a weekend. :returns: The validated data :rtype: dict """ instance = self.instance user = data.get("user", instance and instance.user) try: location = Employment.objects.get_at(user, data.get("date")).location except Employment.DoesNotExist: raise ValidationError( _("You can't create an absence on an unemployed day.")) if PublicHoliday.objects.filter(location_id=location.id, date=data.get("date")).exists(): raise ValidationError( _("You can't create an absence on a public holiday")) workdays = [int(day) for day in location.workdays] if data.get("date").isoweekday() not in workdays: raise ValidationError( _("You can't create an absence on a weekend")) return data
def validate(self, data): """Validate the activity block. Ensure that a user can only have one activity which doesn't end before it started. """ instance = self.instance from_time = data.get("from_time", instance and instance.from_time) to_time = data.get("to_time", instance and instance.to_time) user = instance and instance.user or data["user"] def validate_running_activity(): if activity.filter(to_time__isnull=True).exists(): raise ValidationError( _("A user can only have one active activity")) # validate that there is only one active activity activity = models.Activity.objects.filter(user=user) # if the request creates a new activity if instance is None and to_time is None: validate_running_activity() # if the request mutates an existsting activity if instance and instance.to_time and to_time is None: validate_running_activity() # validate that to is not before from if to_time is not None and to_time < from_time: raise ValidationError( _("An activity block may not end before it starts.")) return data
def _validate_owner_only(self, value, field): if self.instance is not None: user = self.context["request"].user owner = self.instance.user if getattr(self.instance, field) != value and user != owner: raise ValidationError(_(f"Only owner may change {field}")) return value
def reward_amount_matches(data): """ Validates that the reward activity is the same as the donation activity """ if data.get('reward') and data['reward'].amount > data['amount']: raise ValidationError( _('The amount must be higher or equal to the amount of the reward.') )
def validate_type(self, value): """Only owner is allowed to change type.""" if self.instance is not None: user = self.context["request"].user owner = self.instance.user if self.instance.date != value and user != owner: raise ValidationError(_("Only owner may change absence type")) return value
def __call__(self, data): activity = data.get('activity') or self.activity for field in self.fields: if ( activity.target and field in data and data[field].currency != activity.target.currency ): raise ValidationError(self.message)
def validate_duration(self, value): """Only owner is allowed to change duration.""" if self.instance is not None: user = self.context['request'].user owner = self.instance.user if self.instance.duration != value and user != owner: raise ValidationError('Only owner may change duration') return value
def validate(self, data): """Validate that verified by is only set by reviewer or superuser.""" user = self.context['request'].user current_verified_by = self.instance and self.instance.verified_by new_verified_by = data.get('verified_by') task = data.get('task') or self.instance.task if new_verified_by != current_verified_by: is_reviewer = (user.is_superuser or task.project.reviewers.filter(id=user.id).exists()) if not is_reviewer: raise ValidationError(_('Only reviewer may verify reports.')) if new_verified_by is not None and new_verified_by != user: raise ValidationError( _('You may only verifiy with your own user')) return data
def validate(self, data): """Validate the activity block. Ensure that a user can only have one activity with an active block which doesn't end before it started. """ instance = self.instance from_time = data.get('from_time', instance and instance.from_time) to_time = data.get('to_time', instance and instance.to_time) user = instance and instance.activity.user or data.get('activity').user # validate that there is only one active activity blocks = models.ActivityBlock.objects.filter(activity__user=user) if blocks.filter(to_time__isnull=True) and to_time is None: raise ValidationError( _('A user can only have one active activity')) # validate that to is not before from if to_time is not None and to_time < from_time: raise ValidationError( _('An activity block may not end before it starts.')) return data
def validate_verified_by(self, value): user = self.context['request'].user current_verified_by = self.instance and self.instance.verified_by if value == current_verified_by: # no validation needed when nothing has changed return value if value is None and user.is_staff: # staff is allowed to reset verified by return value if value is not None and user.is_staff and value == user: # staff user is allowed to set it's own user as verified by return value raise ValidationError(_('Only staff user may verify reports.'))
def filter_state(queryset, _, value): if not isinstance(value, list): value = [value] enum_values = [] for item in value: try: enum_value = TransitState[item.upper()] except KeyError: raise ValidationError('Invalid shipment state supplied.') enum_values.append(enum_value.value) if issubclass(queryset.model, Shipment): queryset = queryset.filter(state__in=enum_values) else: queryset = queryset.filter(shipment__state__in=enum_values) return queryset
def validate(self, data): """Validate the activity block. Ensure that a user can only have one activity with an active block. """ if self.instance: user = self.instance.activity.user to_datetime = data.get('to_datetime', self.instance.to_datetime) else: user = data.get('activity').user to_datetime = data.get('to_datetime', None) blocks = models.ActivityBlock.objects.filter(activity__user=user) if (blocks.filter(to_datetime__isnull=True) and to_datetime is None): raise ValidationError('A user can only have one active activity') return data
def __call__(self, data): if data.get(self.field) and not data[self.field].activity == data['activity']: raise ValidationError(self.message)
def validate_running_activity(): if activity.filter(to_time__isnull=True).exists(): raise ValidationError( _("A user can only have one active activity"))
def __call__(self, data): if data.get('user') and data['user'].is_authenticated and self.user and self.user != data['user']: raise ValidationError(self.message)
def test_shipment_imports(self): url = reverse('import-shipments-list', kwargs={'version': 'v1'}) csv_path = './tests/tmp/test.csv' xls_path = './tests/tmp/test.xls' xlsx_path = './tests/tmp/test.xlsx' self.make_shipment_file(csv_path) self.make_shipment_file(xls_path) self.make_shipment_file(xlsx_path) csv_file_data = { 'name': 'Test csv file', 'description': 'Auto generated file for test purposes', 'file_type': 'csv', 'storage_credentials_id': STORAGE_CRED_ID, 'shipper_wallet_id': SHIPPER_WALLET_ID, 'carrier_wallet_id': CARRIER_WALLET_ID } xls_file_data = copy.deepcopy(csv_file_data) xls_file_data['name'] = 'Test xls file' xls_file_data['file_type'] = 'Xls' xls_file_data['upload_status'] = 'complete' xlsx_file_data = copy.deepcopy(csv_file_data) xlsx_file_data['name'] = 'Test xlsx file' xlsx_file_data['file_type'] = '2' xlsx_file_data['processing_status'] = 'failed' # Unauthenticated user should fail response = self.client.post(url, csv_file_data) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.set_user(self.user_1) with mock.patch('apps.imports.serializers.ShipmentImportCreateSerializer.validate_shipper_wallet_id') as mock_wallet_validation, \ mock.patch('apps.imports.serializers.ShipmentImportCreateSerializer.validate_storage_credentials_id') as mock_storage_validation: mock_wallet_validation.return_value = SHIPPER_WALLET_ID mock_storage_validation.return_value = STORAGE_CRED_ID # --------------------- Upload csv file --------------------# # Authenticated request should succeed response = self.client.post(url, csv_file_data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) data = response.json()['data'] self.assertEqual(data['attributes']['upload_status'], 'PENDING') csv_obj = ShipmentImport.objects.get(id=data['id']) fields = data['meta']['presigned_s3']['fields'] # csv file upload put_url = data['meta']['presigned_s3']['url'] with open(csv_path, 'rb') as csv: res = requests.post(put_url, data=fields, files={'file': csv}) self.assertEqual(res.status_code, status.HTTP_204_NO_CONTENT) self.assertTrue(self.s3_object_exists(csv_obj.s3_key)) # --------------------- Upload xls file --------------------# response = self.client.post(url, xls_file_data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) data = response.json()['data'] # upload_status field is not configurable at creation self.assertEqual(data['attributes']['upload_status'], 'PENDING') self.assertEqual(data['attributes']['processing_status'], 'PENDING') # Wallets and storage should be present in response self.assertEqual(data['attributes'].get('storage_credentials_id'), STORAGE_CRED_ID) self.assertEqual(data['attributes'].get('shipper_wallet_id'), SHIPPER_WALLET_ID) self.assertEqual(data['attributes'].get('carrier_wallet_id'), CARRIER_WALLET_ID) xls_obj = ShipmentImport.objects.get(id=data['id']) fields = data['meta']['presigned_s3']['fields'] # xls file upload put_url = data['meta']['presigned_s3']['url'] with open(xls_path, 'rb') as xls: res = requests.post(put_url, data=fields, files={'file': xls}) self.assertEqual(res.status_code, status.HTTP_204_NO_CONTENT) self.assertTrue(self.s3_object_exists(xls_obj.s3_key)) # --------------------- Upload xlsx file --------------------# response = self.client.post(url, xlsx_file_data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) data = response.json()['data'] self.assertEqual(data['attributes']['upload_status'], 'PENDING') # processing_status field is not configurable at creation self.assertEqual(data['attributes']['processing_status'], 'PENDING') xlsx_obj = ShipmentImport.objects.get(id=data['id']) fields = data['meta']['presigned_s3']['fields'] # xlsx file upload put_url = data['meta']['presigned_s3']['url'] with open(xlsx_path, 'rb') as xlsx: res = requests.post(put_url, data=fields, files={'file': xlsx}) self.assertEqual(res.status_code, status.HTTP_204_NO_CONTENT) self.assertTrue(self.s3_object_exists(xlsx_obj.s3_key)) # -------------------- test patch object -------------------# patch_csv_data = { 'name': 'Can update name', 'file_type': 'XLS', # Cannot be modified 'owner_id': 'new_owner_id', # Cannot be modified 'masquerade_id': 'new_masquerade_id', # Cannot be modified "upload_status": "Complete", "processing_status": "Complete", "report": { "success": 15, "failed": 0 } } csv_patch_url = f'{url}/{csv_obj.id}/' response = self.client.patch(csv_patch_url, data=patch_csv_data) self.assertEqual(response.status_code, status.HTTP_200_OK) data = response.json()['data'] # The file_type cannot change on patch self.assertEqual(data['attributes']['file_type'], 'CSV') self.assertEqual(data['attributes']['report'], patch_csv_data['report']) self.assertEqual(data['attributes']['processing_status'], 'COMPLETE') # Wallets and storage, should be present in the response object and are non modifiable self.assertEqual(data['attributes'].get('storage_credentials_id'), STORAGE_CRED_ID) self.assertEqual(data['attributes'].get('shipper_wallet_id'), SHIPPER_WALLET_ID) self.assertEqual(data['attributes'].get('carrier_wallet_id'), CARRIER_WALLET_ID) # owner_id and masquerade_id shouldn't be present in the response object self.assertIsNone(data['attributes'].get('owner_id')) self.assertIsNone(data['attributes'].get('masquerade_id')) # wallet and storage are non modifiable fields new_wallet_id = 'Wallet_is_Non_Modifiable' mock_wallet_validation.reset_mock() mock_wallet_validation.return_value = new_wallet_id patch_csv_data['shipper_wallet_id'] = new_wallet_id response = self.client.patch(csv_patch_url, data=patch_csv_data) self.assertEqual(mock_wallet_validation.call_count, 0) self.assertEqual(response.status_code, status.HTTP_200_OK) data = response.json()['data'] # The shipper_wallet_id attribute cannot be patched self.assertEqual(data['attributes']['shipper_wallet_id'], SHIPPER_WALLET_ID) csv_obj.refresh_from_db() self.assertEqual(csv_obj.shipper_wallet_id, SHIPPER_WALLET_ID) # ------------------ permissions test -----------------------# self.set_user(self.user_2) # user_2 can create an xls file object response = self.client.post(url, xlsx_file_data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) # user_2 cannot access a document object not owned user_1_xlsx_url = f'{url}/{xlsx_obj.id}/' response = self.client.get(user_1_xlsx_url) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) # user_2 cannot modify a document object not owned user_1_xlsx_url = f'{url}/{xlsx_obj.id}/' response = self.client.patch(user_1_xlsx_url, data=patch_csv_data) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) # user_2 can list only document owned response = self.client.get(url) self.assertEqual(response.status_code, status.HTTP_200_OK) data = response.json()['data'] self.assertEqual(len(data), 1) user_2_csv_document = ShipmentImport.objects.get(id=data[0]['id']) self.assertEqual(user_2_csv_document.masquerade_id, self.user_2.id) mock_wallet_validation.reset_mock() # Trying to upload a document without a shipper wallet should fail csv_file_data_without_shipper_wallet = copy.deepcopy(csv_file_data) csv_file_data_without_shipper_wallet.pop('shipper_wallet_id') response = self.client.post(url, csv_file_data_without_shipper_wallet) self.assertEqual(mock_wallet_validation.call_count, 0) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) # Trying to upload a document with a wallet / storage not owned by the requester should fail error_message = 'User does not have access to this wallet in ShipChain Profiles' mock_wallet_validation.side_effect = ValidationError(error_message) mock_wallet_validation.return_value = None csv_file_data_with_invalid_shipper_wallet = copy.deepcopy( csv_file_data) csv_file_data_with_invalid_shipper_wallet[ 'shipper_wallet_id'] = 'Non_Accessible_Wallet' response = self.client.post( url, csv_file_data_with_invalid_shipper_wallet) self.assertEqual(mock_wallet_validation.call_count, 1) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) data = response.json()['errors'][0] self.assertEqual(data['detail'], error_message) # ------------------ filtering test -----------------------# self.set_user(self.user_1) # User_1 owns 3 ShipmentImport objects respectively of one file type response = self.client.get(f'{url}/?file_type=XLS') self.assertEqual(response.status_code, status.HTTP_200_OK) data = response.json()['data'] self.assertEqual(len(data), 1) self.assertEqual(data[0]['attributes']['file_type'], FileType.XLS.name) # User_1 only has the CSV file with upload status complete response = self.client.get(f'{url}/?upload_status=Complete') self.assertEqual(response.status_code, status.HTTP_200_OK) data = response.json()['data'] self.assertEqual(len(data), 1) self.assertEqual(data[0]['attributes']['file_type'], FileType.CSV.name) self.assertEqual(data[0]['attributes']['upload_status'], UploadStatus.COMPLETE.name) # user_1 only has one shipment import with name: 'Test xlsx file' shipment_import_name = 'Test xlsx file' response = self.client.get(f'{url}/?name__contains=XLsx') self.assertEqual(response.status_code, status.HTTP_200_OK) data = response.json()['data'] self.assertEqual(len(data), 1) self.assertEqual(data[0]['attributes']['file_type'], FileType.XLSX.name) self.assertEqual(data[0]['attributes']['name'], shipment_import_name) # User_1 only has the CSV file with processing status complete response = self.client.get(f'{url}/?processing_status=COMPLETE') self.assertEqual(response.status_code, status.HTTP_200_OK) data = response.json()['data'] self.assertEqual(len(data), 1) self.assertEqual(data[0]['attributes']['file_type'], FileType.CSV.name) self.assertEqual(data[0]['attributes']['processing_status'], ProcessingStatus.COMPLETE.name)