def validate_bulk_tasks(self, attrs, source): filters = { "project__id": attrs["project_id"], "id__in": [task["task_id"] for task in attrs[source]] } if models.Task.objects.filter(**filters).count() != len( filters["id__in"]): raise ValidationError( _("All the tasks must be from the same project")) return attrs
def validate_bulk_stories(self, attrs, source): filters = { "project__id": attrs["project_id"], "id__in": [us["us_id"] for us in attrs[source]], } if UserStory.objects.filter(**filters).count() != len(filters["id__in"]): raise ValidationError( _("All the user stories must be from the same project") ) return attrs
def validate_assigned_to(self, attrs, source): assigned_to = attrs[source] project = (attrs.get("project", None) or getattr(self.object, "project", None)) if assigned_to and project: filters = {"project_id": project.id, "user_id": assigned_to.id} if not Membership.objects.filter(**filters).exists(): raise ValidationError(_("The user must be a project member.")) return attrs
def validate_source_project_slug(self, attrs, source): if source in attrs and attrs[ source] is not None and attrs[source] != "": msg = _( "An Epic has a related story from an external project (%(project)s) and cannot be imported" ) % { "project": attrs[source] } raise ValidationError(msg) attrs.pop(source, None) return attrs
def validate_us_id(self, attrs, source): filters = {"project__id": attrs["project_id"]} if "milestone_id" in attrs: filters["milestone__id"] = attrs["milestone_id"] filters["id"] = attrs["us_id"] if not UserStory.objects.filter(**filters).exists(): raise ValidationError(_("Invalid user story id.")) return attrs
def validate_role(self, attrs, source): project = attrs.get( "project", None if self.object is None else self.object.project) if project is None: return attrs role = attrs[source] if project.roles.filter(id=role.id).count() == 0: raise ValidationError(_("Invalid role for the project")) return attrs
def field_from_native(self, data, files, field_name, into): """ Override default so that the serializer can be used as a writable nested field across relationships. """ if self.read_only: return try: value = data[field_name] except KeyError: if self.default is not None and not self.partial: # Note: partial updates shouldn't set defaults value = copy.deepcopy(self.default) else: if self.required: raise ValidationError(self.error_messages["required"]) return # Set the serializer object if it exists obj = get_component(self.parent.object, self.source or field_name) if self.parent.object else None # If we have a model manager or similar object then we need # to iterate through each instance. if (self.many and not hasattr(obj, "__iter__") and is_simple_callable(getattr(obj, "all", None))): obj = obj.all() if self.source == "*": if value: reverted_data = self.restore_fields(value, {}) if not self._errors: into.update(reverted_data) else: if value in (None, ""): into[(self.source or field_name)] = None else: kwargs = { "instance": obj, "data": value, "context": self.context, "partial": self.partial, "many": self.many, "allow_add_remove": self.allow_add_remove } serializer = self.__class__(**kwargs) if serializer.is_valid(): into[self.source or field_name] = serializer.object else: # Propagate errors up to our parent raise NestedValidationError(serializer.errors)
def from_native(self, data): if data in validators.EMPTY_VALUES: return None # UploadedFile objects should have name and size attributes. try: file_name = data.name file_size = data.size except AttributeError: raise ValidationError(self.error_messages["invalid"]) if self.max_length is not None and len(file_name) > self.max_length: error_values = {"max": self.max_length, "length": len(file_name)} raise ValidationError(self.error_messages["max_length"] % error_values) if not file_name: raise ValidationError(self.error_messages["invalid"]) if not self.allow_empty_file and not file_size: raise ValidationError(self.error_messages["empty"]) return data
def validate_bulk_stories(self, attrs, source): filters = {"project__id": attrs["project_id"]} if "milestone_id" in attrs: filters["milestone__id"] = attrs["milestone_id"] filters["id__in"] = [us["us_id"] for us in attrs[source]] if models.UserStory.objects.filter(**filters).count() != len(filters["id__in"]): raise ValidationError(_("Invalid user story ids. All stories must belong to the same project " "and, if it exists, to the same status and milestone.")) return attrs
def validate_bulk_tasks(self, attrs, source): filters = { "project__id": attrs["project_id"], "id__in": [issue["issue_id"] for issue in attrs[source]], } if models.Issue.objects.filter(**filters).count() != len( filters["id__in"]): raise ValidationError( _("All the issues must be from the same project")) return attrs
def validate_username(self, attrs, source): username = attrs.get(source) try: validate_user_email_allowed_domains(username) except InvalidEmailValidationError: # If the validation comes from a request let's check the user is a valid contact request = self.context.get("request", None) if request is not None and request.user.is_authenticated: valid_usernames = set(request.user.contacts_visible_by_user(request.user).values_list("username", flat=True)) if username not in valid_usernames: raise ValidationError(_("The user must be a valid contact")) return attrs
def validate_username(self, attrs, source): value = attrs[source] validator = core_validators.RegexValidator(re.compile(r'^[\w.-]+$'), _("invalid username"), "invalid") try: validator(value) except ValidationError: raise ValidationError( _("Required. 255 characters or fewer. Letters, numbers " "and /./-/_ characters'")) return attrs
def validate_bulk_memberships(self, attrs, source): filters = { "project__id": attrs["project_id"], "id__in": [r["role_id"] for r in attrs["bulk_memberships"]] } if Role.objects.filter(**filters).count() != len(set( filters["id__in"])): raise ValidationError( _("Invalid role ids. All roles must belong to the same project." )) return attrs
def validate(self, value): super(DecimalField, self).validate(value) if value in validators.EMPTY_VALUES: return # Check for NaN, Inf and -Inf values. We can't compare directly for NaN, # since it is never equal to itself. However, NaN is the only value that # isn't equal to itself, so we can use this to identify NaN if value != value or value == Decimal("Inf") or value == Decimal("-Inf"): raise ValidationError(self.error_messages["invalid"]) sign, digittuple, exponent = value.as_tuple() decimals = abs(exponent) # digittuple doesn't include any leading zeros. digits = len(digittuple) if decimals > digits: # We have leading zeros up to or past the decimal point. Count # everything past the decimal point as a digit. We do not count # 0 before the decimal point as a digit since that would mean # we would not allow max_digits = decimal_places. digits = decimals whole_digits = digits - decimals if self.max_digits is not None and digits > self.max_digits: raise ValidationError(self.error_messages["max_digits"] % self.max_digits) if self.decimal_places is not None and decimals > self.decimal_places: raise ValidationError( self.error_messages["max_decimal_places"] % self.decimal_places ) if ( self.max_digits is not None and self.decimal_places is not None and whole_digits > (self.max_digits - self.decimal_places) ): raise ValidationError( self.error_messages["max_whole_digits"] % (self.max_digits - self.decimal_places) ) return value
def _validate_tag_field(value): # Valid field: # - ["tag1", "tag2", "tag3"...] # - ["tag1", ["tag2", None], ["tag3", "#ccc"], [tag4, #cccccc]...] for tag in value: if isinstance(tag, str): continue if isinstance(tag, (list, tuple)) and len(tag) == 2: name = tag[0] color = tag[1] if isinstance(name, str): if color is None or color == "": continue if isinstance(color, str) and re.match('^\#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$', color): continue raise ValidationError(_("Invalid tag '{value}'. The color is not a " "valid HEX color or null.").format(value=tag)) raise ValidationError(_("Invalid tag '{value}'. it must be the name or a pair " "'[\"name\", \"hex color/\" | null]'.").format(value=tag))
def validate(self, attrs): if "ref" in attrs: if "epic" in attrs: raise ValidationError( "'epic' param is incompatible with 'ref' in the same request" ) if "us" in attrs: raise ValidationError( "'us' param is incompatible with 'ref' in the same request" ) if "task" in attrs: raise ValidationError( "'task' param is incompatible with 'ref' in the same request" ) if "issue" in attrs: raise ValidationError( "'issue' param is incompatible with 'ref' in the same request" ) if "wikipage" in attrs: raise ValidationError( "'wikipage' param is incompatible with 'ref' in the same request" ) return attrs
def from_native(self, value): """ Validates that the input is a decimal number. Returns a Decimal instance. Returns None for empty values. Ensures that there are no more than max_digits in the number, and no more than decimal_places digits after the decimal point. """ if value in validators.EMPTY_VALUES: return None value = smart_text(value).strip() try: value = Decimal(value) except DecimalException: raise ValidationError(self.error_messages["invalid"]) return value
def validate(self, data): filters = {"project__id": data["project_id"]} if "status_id" in data: filters["status__id"] = data["status_id"] if "milestone_id" in data: filters["milestone__id"] = data["milestone_id"] filters["id__in"] = [us["us_id"] for us in data["bulk_stories"]] if models.UserStory.objects.filter(**filters).count() != len( filters["id__in"]): raise ValidationError( _("Invalid user story ids. All stories must belong to the same project and, " "if it exists, to the same status and milestone.")) return data
def validate_bulk_attachments(self, attrs, source): if (attrs.get("content_type_id", None) is not None and attrs.get("object_id", None) is not None): filters = { "content_type__id": attrs["content_type_id"], "object_id": attrs["object_id"], "id__in": attrs[source] } if models.Attachment.objects.filter(**filters).count() != len( filters["id__in"]): raise ValidationError( _("Invalid attachment ids. All attachments must belong to the same " "item (epic, userstory, task, issue or wiki page).")) return attrs
def from_native(self, data): """ Checks that the file-upload field data contains a valid image (GIF, JPG, PNG, possibly others -- whatever the Python Imaging Library supports). """ f = super(ImageField, self).from_native(data) if f is None: return None # Try to import PIL in either of the two ways it can end up installed. try: from PIL import Image except ImportError: try: import Image except ImportError: Image = None assert ( Image is not None ), "Either Pillow or PIL must be installed for ImageField support." # We need to get a file object for PIL. We might have a path or we might # have to read the data into memory. if hasattr(data, "temporary_file_path"): file = data.temporary_file_path() else: if hasattr(data, "read"): file = six.BytesIO(data.read()) else: file = six.BytesIO(data["content"]) try: # load() could spot a truncated JPEG, but it loads the entire # image in memory, which is a DoS vector. See #3848 and #18520. # verify() must be called immediately after the constructor. Image.open(file).verify() except ImportError: # Under PyPy, it is possible to import PIL. However, the underlying # _imaging C module isn't available, so an ImportError will be # raised. Catch and re-raise. raise except Exception: # Python Imaging Library doesn't recognize it as an image raise ValidationError(self.error_messages["invalid_image"]) if hasattr(f, "seek") and callable(f.seek): f.seek(0) return f
def run_validators(self, value): if value in validators.EMPTY_VALUES: return errors = [] for v in self.validators: try: v(value) except ValidationError as e: if hasattr(e, "code") and e.code in self.error_messages: message = self.error_messages[e.code] if e.params: message = message % e.params errors.append(message) else: errors.extend(e.messages) if errors: raise ValidationError(errors)
def validate_after_attachment_id(self, attrs, source): if (attrs.get(source, None) is not None and attrs.get("content_type_id", None) is not None and attrs.get("object_id", None) is not None): filters = { "content_type__id": attrs["content_type_id"], "object_id": attrs["object_id"], "id": attrs[source] } if not models.Attachment.objects.filter(**filters).exists(): raise ValidationError( _("Invalid attachment id to move after. The attachment must belong " "to the same item (epic, userstory, task, issue or wiki page)." )) return attrs
def _validate_member_doesnt_exist(self, attrs, email): project = attrs.get("project", None if self.object is None else self.object.project) if project is None: return attrs qs = models.Membership.objects.all() # If self.object is not None, the serializer is in update # mode, and for it, it should exclude self. if self.object: qs = qs.exclude(pk=self.object.pk) qs = qs.filter(Q(project_id=project.id, user__email=email) | Q(project_id=project.id, email=email)) if qs.count() > 0: raise ValidationError(_("The user yet exists in the project"))
def validate_after_userstory_id(self, attrs, source): if attrs.get(source, None) is not None: filters = { "project__id": attrs["project_id"], "status__id": attrs["status_id"], "id": attrs[source] } swimlane_id = attrs.get("swimlane_id", None) if swimlane_id: filters["swimlane__id"] = swimlane_id else: filters["swimlane__isnull"] = True if not UserStory.objects.filter(**filters).exists(): raise ValidationError( _("Invalid user story id to move after. The user story must belong " "to the same project, status and swimlane.")) return attrs
def validate_bulk_tasks(self, attrs, source): filters = {"project__id": attrs["project_id"]} if "status_id" in attrs: filters["status__id"] = attrs["status_id"] if "us_id" in attrs: filters["user_story__id"] = attrs["us_id"] if "milestone_id" in attrs: filters["milestone__id"] = attrs["milestone_id"] filters["id__in"] = [t["task_id"] for t in attrs[source]] if models.Task.objects.filter(**filters).count() != len( filters["id__in"]): raise ValidationError( _("Invalid task ids. All tasks must belong to the same project and, " "if it exists, to the same status, user story and/or milestone." )) return attrs
def from_native(self, value): if value in validators.EMPTY_VALUES: return None if isinstance(value, datetime.datetime): return value if isinstance(value, datetime.date): value = datetime.datetime(value.year, value.month, value.day) if settings.USE_TZ: # For backwards compatibility, interpret naive datetimes in # local time. This won't work during DST change, but we can"t # do much about it, so we let the exceptions percolate up the # call stack. warnings.warn( "DateTimeField received a naive datetime (%s)" " while time zone support is active." % value, RuntimeWarning, ) default_timezone = timezone.get_default_timezone() value = timezone.make_aware(value, default_timezone) return value for format in self.input_formats: if format.lower() == ISO_8601: try: parsed = parse_datetime(value) except (ValueError, TypeError): pass else: if parsed is not None: return parsed else: try: parsed = datetime.datetime.strptime(value, format) except (ValueError, TypeError): pass else: return parsed msg = self.error_messages["invalid"] % readable_datetime_formats( self.input_formats ) raise ValidationError(msg)
def validate_name(self, attrs, source): """ Check the points name is not duplicated in the project on creation """ model = self.opts.model qs = None # If the object exists: if self.object and attrs.get(source, None): qs = model.objects.filter( project=self.object.project, name=attrs[source]).exclude(id=self.object.id) if not self.object and attrs.get("project", None) and attrs.get(source, None): qs = model.objects.filter(project=attrs["project"], name=attrs[source]) if qs and qs.exists(): raise ValidationError(_("Name duplicated for the project")) return attrs
def validate_email(self, attrs, source): project = attrs.get("project", None) if project is None: project = self.object.project email = attrs[source] qs = models.Membership.objects.all() # If self.object is not None, the serializer is in update # mode, and for it, it should exclude self. if self.object: qs = qs.exclude(pk=self.object.pk) qs = qs.filter(Q(project_id=project.id, user__email=email) | Q(project_id=project.id, email=email)) if qs.count() > 0: raise ValidationError(_("Email address is already taken")) return attrs
def _validate_integrity_between_project_and_name(self, attrs, source): """ Check the name is not duplicated in the project. Check when: - create a new one - update the name - update the project (move to another project) """ data_id = attrs.get("id", None) data_name = attrs.get("name", None) data_project = attrs.get("project", None) if self.object: data_id = data_id or self.object.id data_name = data_name or self.object.name data_project = data_project or self.object.project model = self.Meta.model qs = (model.objects.filter(project=data_project, name=data_name).exclude(id=data_id)) if qs.exists(): raise ValidationError(_("Already exists one with the same name.")) return attrs
def validate_username(self, attrs, source): username = attrs.get(source, None) try: validate_user_email_allowed_domains(username) except ValidationError: # If the validation comes from a request let's check the user is a valid contact request = self.context.get("request", None) if request is not None and request.user.is_authenticated: valid_usernames = request.user.contacts_visible_by_user(request.user).values_list("username", flat=True) if username not in valid_usernames: raise ValidationError(_("The user must be a valid contact")) user = User.objects.filter(Q(username=username) | Q(email=username)).first() if user is not None: email = user.email self.user = user else: email = username self.email = email self._validate_member_doesnt_exist(attrs, email) return attrs