def validate_asset_types(self, key, value): """Validate if values for asset_types are correct.""" max_types = 1 if len(value) > max_types: raise ValidationError(message='Invalid number of type of assets', name=key) members = AssetTypes.__members__ for item in value: if item not in members: raise ValidationError(message='Invalid type of asset', name=key) return value
def validate_requirement_items(self, key: str, values: t.Sequence) -> t.Sequence: """Validate if requirement_items payload is in the correct format. :param key: Attribute name. :param values: Requirement items payload. :return: Validated payload. """ request = self.request user_id = str(request.user.id) if request else None current_value = list( self.requirement_items) if self.requirement_items else [] if values: for item in values: if not item.get('created_by') and user_id: item['created_by'] = user_id if not item.get('created_at'): item['created_at'] = datetime_utcnow().isoformat() if values or current_value: requirements_schema = RequirementItems() try: values = requirements_schema.deserialize(values) except colander.Invalid as exc: raise ValidationError( message='Invalid payload for requirement_items', name=key) return values
def validate_add_order_roles(self, key, value): """Validate if values for add_order_roles are correct.""" all_groups = [item.value for item in Groups] for item in value: if item not in all_groups: raise ValidationError(message='Invalid role', name=key) return value
def create_sorting_from_query_params( query_params: dict, allowed_fields: t.Sequence[str], default: str = '', default_direction: int = 1) -> t.Sequence[Sort]: """Process a query parameters dictionary and return a list of Sort objects. :param query_params: Dictionary containing query_params for a request. :param allowed_fields: List of fields that support sorting. :param default: Default field for sorting. :param default_direction: Default direction for sorting. :return: list of Sort objects. """ specified = query_params.get('_sort', '').split(',') sorting = [] for field in specified: field = field.strip() m = re.match(r'^([\-+]?)([\.\w]+)$', field) if m: order, field = m.groups() if field not in allowed_fields: raise ValidationError( message=f'Unknown sort field \'{field}\'', location='querystring', name=field) direction = -1 if order == '-' else 1 sorting.append(Sort(field, direction)) if not sorting and (default and default_direction): sorting.append(Sort(default, default_direction)) return sorting
def availability(self, value: list): """Set availabilities for an Order.""" project = self.project timezone = self.timezone if isinstance(timezone, str): timezone = pytz.timezone(timezone) user = self.workflow.context not_pm = 'g:briefy_pm' not in user.groups if user else True validated_value = [] if value and len(value) != len(set(value)): msg = 'Availability dates should be different.' raise ValidationError(message=msg, name='availability') if value and timezone and project: if not_pm: availability_window = project.availability_window else: # allow less than 24hs for PMs but not in the past availability_window = 0 now = datetime.now(tz=timezone) for availability in value: if isinstance(availability, str): availability = parse(availability) tz_availability = availability.astimezone(timezone) date_diff = tz_availability - now if date_diff.days < availability_window: msg = 'Both availability dates must be at least {window} days from now.' msg = msg.format(window=availability_window) raise ValidationError(message=msg, name='availability') validated_value.append(availability.isoformat()) elif value: logger.warn( 'Could not check availability dates. Order {id}'.format( id=self.id)) self._availability = validated_value if validated_value else value
def order_charges_update(current_value: t.Sequence, new_value: t.Sequence) -> t.Sequence: """Function to handle OrderCharges modification. :param current_value: Current OrderCharges. :param new_value: New OrderCharges. :return: OrderCharges to be persisted. """ processed = [] current_dict = {line['id']: line for line in current_value} to_update = {line['id']: line for line in new_value if line.get('id') in current_dict} to_delete = {k: v for k, v in current_dict.items() if k not in to_update} # Validate if we can delete for key in to_delete: line = to_delete[key] invoice_number = line.get('invoice_number') invoice_date = line.get('invoice_date') if invoice_date and invoice_number: raise ValidationError( 'Not possible to delete an already invoiced item.', name='additional_charges' ) for line in new_value: line_id = line.get('id') invoice_date = line.get('invoice_date') if isinstance(invoice_date, date): line['invoice_date'] = invoice_date.isoformat() if line_id not in to_update: line['created_at'] = utc_now_serialized() if not line_id: line['id'] = str(uuid4()) current_item = line else: current_item = current_dict[line_id] for field in _FIELDS: current_item[field] = line[field] processed.append(current_item) return processed
def validate_additional_charges(self, key: str, value: t.Sequence) -> t.Sequence: """Validate if additional_charges payload is in the correct format. :param key: Attribute name. :param value: Additional charges payload. :return: Validated payload """ current_value = list( self.additional_charges) if self.additional_charges else [] if value or current_value: charges_schema = OrderCharges() try: new_value = charges_schema.deserialize(value) except colander.Invalid as e: raise ValidationError( message='Invalid payload for additional_charges', name=key) value = order_charges_update(current_value, new_value) # Force total_order_price recalculation flag_modified(self, 'actual_order_price') flag_modified(self, 'additional_charges') return value
def raise_validation_exception(message: str, key: str): """Raise a ValidationException.""" raise ValidationError(message, key)
def filter_query(self, query: Query, query_params: t.Optional[dict] = None) -> Query: """Apply request filters to a query.""" raw_filters = filter.create_filter_from_query_params( query_params, self.filter_allowed_fields) for raw_filter in raw_filters: with_transformation = False mapper = None key = raw_filter.field value = raw_filter.value op = raw_filter.operator.value query, column, sub_key = self.get_column_from_key(query, key) if not column: raise ValidationError( message=f'Unknown filter field \'{key}\'', location='querystring', name=key) if value == 'null': value = None possible_names = [ name.format(op=op) for name in ['{op}', '{op}_', '__{op}__'] ] if isinstance(column, AssociationProxy) and '.' in key: mapper = getattr(column.remote_attr.prop, 'mapper', None) if mapper: remote_class = mapper.class_ dest_column = getattr(remote_class, sub_key) attrs = [ getattr(dest_column, name) for name in possible_names if hasattr(dest_column, name) ] else: attrs = [ getattr(column, name) for name in possible_names if hasattr(column, name) ] elif isinstance(column, InstrumentedAttribute) and '.' in key: mapper = getattr(column.property, 'mapper', None) dest_column = getattr(mapper.c, sub_key, None) # try to get the original field starting with underscore if dest_column is None: dest_column = getattr(mapper.c, f'_{sub_key}', None) attrs = [ getattr(dest_column, name) for name in possible_names if hasattr(dest_column, name) ] else: if isinstance(column, AssociationProxy): remote_attr = column.remote_attr if isinstance(column.remote_attr.comparator, BaseComparator): with_transformation = True attrs = [ getattr(remote_attr, name) for name in possible_names if hasattr(remote_attr, name) ] else: attrs = [ getattr(column, name) for name in possible_names if hasattr(column, name) ] if not attrs: attrs = [ getattr(remote_attr, name) for name in possible_names if hasattr(remote_attr, name) ] else: if isinstance(column.comparator, BaseComparator): with_transformation = True attrs = [ getattr(column, name) for name in possible_names if hasattr(column, name) ] # validate before try to create the filter if not attrs: error_details = { 'location': 'querystring', 'description': f'Invalid filter operator: \'{op}\'', 'name': key, 'value': value } return self.raise_invalid(**error_details) expression = attrs[0](value) is_proxy = isinstance(column, AssociationProxy) is_instrumented = isinstance(column, InstrumentedAttribute) if is_proxy or is_instrumented and '.' in key: if is_proxy and not column.scalar: filt = column.any(expression) elif is_instrumented and column.property.uselist: filt = column.any(expression) elif is_instrumented or mapper: filt = column.has(expression) else: filt = expression else: filt = expression if with_transformation: query = query.with_transformation(filt) else: query = query.filter(filt) return query
def create_filter_from_query_params( query_params: dict, allowed_fields: t.Sequence[str]) -> t.Sequence[Filter]: """Process a query parameters dictionary and return a list of Filter objects. :param query_params: Dictionary containing query_params for a request. :param allowed_fields: List of fields that support sorting. :return: list of Filter objects. """ filters = [] for param, param_value in query_params.items(): param = param.strip() # Ignore specific fields if param.startswith('_') and param not in ('_since', '_to', '_before'): continue # Handle the _since specific filter. if param in ('_since', '_to', '_before'): try: value = int(param_value) except ValueError as exc: raise ValueError( f'Parameter "{param}" is not a valid integer.') operators = { '_since': COMPARISON.GT, '_to': COMPARISON.MAX, '_before': COMPARISON.LT, } operator = operators[param] filters.append(Filter(UPDATED_AT, value, operator)) continue m = re.match(r'^(min|max|not|lt|gt|in|exclude|like|ilike)_([\.\w]+)$', param) if m: keyword, field = m.groups() operator = getattr(COMPARISON, keyword.upper()) else: operator, field = COMPARISON.EQ, param if field not in allowed_fields: raise ValidationError(message=f'Unknown filter field \'{field}\'', location='querystring', name=field) value = data.native_value(param_value, field) if operator in (COMPARISON.IN, COMPARISON.EXCLUDE): value = set( [data.native_value(v, field) for v in param_value.split(',')]) elif operator in ( COMPARISON.LIKE, COMPARISON.ILIKE, ): value = value.replace('%', '') value = f'%{value}%' filters.append(Filter(field, value, operator)) return filters