class RepositorySerializer(ModelSerializer): pulp_href = DetailIdentityField( view_name_pattern=r"repositories(-.*/.*)-detail", ) versions_href = RepositoryVersionsIdentityFromRepositoryField() latest_version_href = LatestVersionField() name = serializers.CharField( help_text=_("A unique name for this repository."), validators=[UniqueValidator(queryset=models.Repository.objects.all())], ) description = serializers.CharField( help_text=_("An optional description."), required=False, allow_null=True) class Meta: model = models.Repository fields = ModelSerializer.Meta.fields + ( "versions_href", "latest_version_href", "name", "description", )
class RepositorySerializer(ModelSerializer): pulp_href = DetailIdentityField( view_name_pattern=r"repositories(-.*/.*)-detail") pulp_labels = LabelsField(required=False) versions_href = RepositoryVersionsIdentityFromRepositoryField() latest_version_href = LatestVersionField() name = serializers.CharField( help_text=_("A unique name for this repository."), validators=[UniqueValidator(queryset=models.Repository.objects.all())], ) description = serializers.CharField( help_text=_("An optional description."), required=False, allow_null=True) remote = DetailRelatedField( view_name_pattern=r"remotes(-.*/.*)-detail", queryset=models.Remote.objects.all(), required=False, allow_null=True, ) def validate_remote(self, value): if value and type(value) not in self.Meta.model.REMOTE_TYPES: raise serializers.ValidationError( detail=_("Type for Remote '{}' does not match Repository." ).format(value.name)) return value class Meta: model = models.Repository fields = ModelSerializer.Meta.fields + ( "versions_href", "pulp_labels", "latest_version_href", "name", "description", "remote", )
class ExporterSerializer(MasterModelSerializer): _href = DetailIdentityField() name = serializers.CharField( help_text=_('The exporter unique name.'), validators=[UniqueValidator(queryset=models.Exporter.objects.all())] ) last_updated = serializers.DateTimeField( help_text=_('Timestamp of the last update.'), read_only=True ) last_export = serializers.DateTimeField( help_text=_('Timestamp of the last export.'), read_only=True ) class Meta: abstract = True model = models.Exporter fields = MasterModelSerializer.Meta.fields + ( 'name', 'last_updated', 'last_export', )
class RemoteSerializer(MasterModelSerializer): """ Every remote defined by a plugin should have a Remote serializer that inherits from this class. Please import from `pulpcore.plugin.serializers` rather than from this module directly. """ _href = DetailIdentityField() name = serializers.CharField( help_text=_('A unique name for this remote.'), validators=[UniqueValidator(queryset=models.Remote.objects.all())], ) url = serializers.CharField( help_text='The URL of an external content source.', ) validate = serializers.BooleanField( help_text='If True, the plugin will validate imported artifacts.', required=False, ) ssl_ca_certificate = serializers.FileField( help_text='A PEM encoded CA certificate used to validate the server ' 'certificate presented by the remote server.', write_only=True, required=False, ) ssl_client_certificate = serializers.FileField( help_text='A PEM encoded client certificate used for authentication.', write_only=True, required=False, ) ssl_client_key = serializers.FileField( help_text='A PEM encoded private key used for authentication.', write_only=True, required=False, ) ssl_validation = serializers.BooleanField( help_text='If True, SSL peer validation must be performed.', required=False, ) proxy_url = serializers.CharField( help_text='The proxy URL. Format: scheme://user:password@host:port', required=False, allow_blank=True, ) username = serializers.CharField( help_text='The username to be used for authentication when syncing.', write_only=True, required=False, allow_blank=True, ) password = serializers.CharField( help_text='The password to be used for authentication when syncing.', write_only=True, required=False, allow_blank=True, ) _last_updated = serializers.DateTimeField( help_text='Timestamp of the most recent update of the remote.', read_only=True) download_concurrency = serializers.IntegerField( help_text='Total number of simultaneous connections.', required=False, min_value=1) policy = serializers.ChoiceField( help_text= "The policy to use when downloading content. The possible values include: " "'immediate', 'on_demand', and 'cache_only'. 'immediate' is the default.", choices=models.Remote.POLICY_CHOICES, default=models.Remote.IMMEDIATE) class Meta: abstract = True model = models.Remote fields = MasterModelSerializer.Meta.fields + ( 'name', 'url', 'validate', 'ssl_ca_certificate', 'ssl_client_certificate', 'ssl_client_key', 'ssl_validation', 'proxy_url', 'username', 'password', '_last_updated', 'download_concurrency', 'policy')
class RemoteSerializer(ModelSerializer): """ Every remote defined by a plugin should have a Remote serializer that inherits from this class. Please import from `pulpcore.plugin.serializers` rather than from this module directly. """ pulp_href = DetailIdentityField() name = serializers.CharField( help_text=_('A unique name for this remote.'), validators=[UniqueValidator(queryset=models.Remote.objects.all())], ) url = serializers.CharField( help_text='The URL of an external content source.', ) ssl_ca_certificate = SecretCharField( help_text= 'A string containing the PEM encoded CA certificate used to validate the server ' 'certificate presented by the remote server. All new line characters must be ' 'escaped. Returns SHA256 sum on GET.', write_only=False, required=False, allow_null=True, ) ssl_client_certificate = SecretCharField( help_text= 'A string containing the PEM encoded client certificate used for authentication. ' 'All new line characters must be escaped. Returns SHA256 sum on GET.', write_only=False, required=False, allow_null=True, ) ssl_client_key = SecretCharField( help_text= 'A PEM encoded private key used for authentication. Returns SHA256 sum on GET.', write_only=False, required=False, allow_null=True, ) ssl_validation = serializers.BooleanField( help_text='If True, SSL peer validation must be performed.', required=False, ) proxy_url = serializers.CharField( help_text='The proxy URL. Format: scheme://user:password@host:port', required=False, allow_null=True, ) username = serializers.CharField( help_text='The username to be used for authentication when syncing.', write_only=True, required=False, allow_null=True, ) password = serializers.CharField( help_text='The password to be used for authentication when syncing.', write_only=True, required=False, allow_null=True, ) pulp_last_updated = serializers.DateTimeField( help_text='Timestamp of the most recent update of the remote.', read_only=True) download_concurrency = serializers.IntegerField( help_text='Total number of simultaneous connections.', required=False, min_value=1) policy = serializers.ChoiceField( help_text="The policy to use when downloading content.", choices=((models.Remote.IMMEDIATE, 'When syncing, download all metadata and content now.')), default=models.Remote.IMMEDIATE) class Meta: abstract = True model = models.Remote fields = ModelSerializer.Meta.fields + ( 'name', 'url', 'ssl_ca_certificate', 'ssl_client_certificate', 'ssl_client_key', 'ssl_validation', 'proxy_url', 'username', 'password', 'pulp_last_updated', 'download_concurrency', 'policy')
class RemoteSerializer(MasterModelSerializer): """ Every remote defined by a plugin should have an Remote serializer that inherits from this class. Please import from `pulpcore.plugin.serializers` rather than from this module directly. """ _href = DetailIdentityField() name = serializers.CharField( help_text=_('A unique name for this remote.'), validators=[UniqueValidator(queryset=models.Remote.objects.all())]) url = serializers.CharField( help_text='The URL of an external content source.', ) validate = serializers.BooleanField( help_text='If True, the plugin will validate imported artifacts.', required=False, ) ssl_ca_certificate = serializers.FileField( help_text='A PEM encoded CA certificate used to validate the server ' 'certificate presented by the remote server.', write_only=True, required=False, ) ssl_client_certificate = serializers.FileField( help_text='A PEM encoded client certificate used for authentication.', write_only=True, required=False, ) ssl_client_key = serializers.FileField( help_text='A PEM encoded private key used for authentication.', write_only=True, required=False, ) ssl_validation = serializers.BooleanField( help_text='If True, SSL peer validation must be performed.', required=False, ) proxy_url = serializers.CharField( help_text='The proxy URL. Format: scheme://user:password@host:port', required=False, ) username = serializers.CharField( help_text='The username to be used for authentication when syncing.', write_only=True, required=False, ) password = serializers.CharField( help_text='The password to be used for authentication when syncing.', write_only=True, required=False, ) last_synced = serializers.DateTimeField( help_text='Timestamp of the most recent successful sync.', read_only=True) last_updated = serializers.DateTimeField( help_text='Timestamp of the most recent update of the remote.', read_only=True) class Meta: abstract = True model = models.Remote fields = MasterModelSerializer.Meta.fields + ( 'name', 'url', 'validate', 'ssl_ca_certificate', 'ssl_client_certificate', 'ssl_client_key', 'ssl_validation', 'proxy_url', 'username', 'password', 'last_synced', 'last_updated', )
class DistributionSerializer(ModelSerializer): """ The Serializer for the Distribution model. The serializer deliberately omits the the `publication` and `repository_version` field due to plugins typically requiring one or the other but not both. To include the ``publication`` field, it is recommended plugins define the field:: publication = DetailRelatedField( required=False, help_text=_("Publication to be served"), view_name_pattern=r"publications(-.*/.*)?-detail", queryset=models.Publication.objects.exclude(complete=False), allow_null=True, ) To include the ``repository_version`` field, it is recommended plugins define the field:: repository_version = RepositoryVersionRelatedField( required=False, help_text=_("RepositoryVersion to be served"), allow_null=True ) Additionally, the serializer omits the ``remote`` field, which is used for pull-through caching feature and only by plugins which use publications. Plugins implementing a pull-through caching should define the field in their derived serializer class like this:: remote = DetailRelatedField( required=False, help_text=_('Remote that can be used to fetch content when using pull-through caching.'), queryset=models.Remote.objects.all(), allow_null=True ) """ pulp_href = DetailIdentityField( view_name_pattern=r"distributions(-.*/.*)-detail") pulp_labels = LabelsField(required=False) base_path = serializers.CharField( help_text= _('The base (relative) path component of the published url. Avoid paths that \ overlap with other distribution base paths (e.g. "foo" and "foo/bar")' ), validators=[ UniqueValidator(queryset=models.Distribution.objects.all()) ], ) base_url = BaseURLField( source="base_path", read_only=True, help_text= _("The URL for accessing the publication as defined by this distribution." ), ) content_guard = DetailRelatedField( required=False, help_text=_("An optional content-guard."), view_name_pattern=r"contentguards(-.*/.*)?-detail", queryset=models.ContentGuard.objects.all(), allow_null=True, ) name = serializers.CharField( help_text=_("A unique name. Ex, `rawhide` and `stable`."), validators=[ UniqueValidator(queryset=models.Distribution.objects.all()), ], ) repository = DetailRelatedField( required=False, help_text=_( "The latest RepositoryVersion for this Repository will be served." ), view_name_pattern=r"repositories(-.*/.*)?-detail", queryset=models.Repository.objects.all(), allow_null=True, ) class Meta: abstract = True model = models.Distribution fields = ModelSerializer.Meta.fields + ( "base_path", "base_url", "content_guard", "pulp_labels", "name", "repository", ) def _validate_path_overlap(self, path): # look for any base paths nested in path search = path.split("/")[0] q = Q(base_path=search) for subdir in path.split("/")[1:]: search = "/".join((search, subdir)) q |= Q(base_path=search) # look for any base paths that nest path q |= Q(base_path__startswith="{}/".format(path)) qs = models.Distribution.objects.filter(q) if self.instance is not None: qs = qs.exclude(pk=self.instance.pk) match = qs.first() if match: raise serializers.ValidationError(detail=_( "Overlaps with existing distribution '{}'").format(match.name)) return path def validate_base_path(self, path): self._validate_relative_path(path) return self._validate_path_overlap(path) def validate(self, data): super().validate(data) repository_provided = data.get( "repository", (self.instance and self.instance.repository) or None) repository_version_provided = data.get( "repository_version", (self.instance and self.instance.repository_version) or None) publication_provided = data.get( "publication", (self.instance and self.instance.publication) or None) if publication_provided and repository_version_provided: raise serializers.ValidationError( _("Only one of the attributes 'publication' and 'repository_version' " "may be used simultaneously.")) elif repository_provided and repository_version_provided: raise serializers.ValidationError( _("Only one of the attributes 'repository' and 'repository_version' " "may be used simultaneously.")) elif repository_provided and publication_provided: raise serializers.ValidationError( _("Only one of the attributes 'repository' and 'publication' " "may be used simultaneously.")) return data
class RemoteSerializer(ModelSerializer): """ Every remote defined by a plugin should have a Remote serializer that inherits from this class. Please import from `pulpcore.plugin.serializers` rather than from this module directly. """ pulp_href = DetailIdentityField() name = serializers.CharField( help_text=_('A unique name for this remote.'), validators=[UniqueValidator(queryset=models.Remote.objects.all())], ) url = serializers.CharField( help_text='The URL of an external content source.', ) ca_cert = SecretCharField( help_text='A string containing the PEM encoded CA certificate used to validate the server ' 'certificate presented by the remote server. All new line characters must be ' 'escaped. Returns SHA256 checksum of the certificate file on GET.', write_only=False, required=False, allow_null=True, ) client_cert = SecretCharField( help_text='A string containing the PEM encoded client certificate used for authentication. ' 'All new line characters must be escaped. Returns SHA256 checksum of the ' 'certificate file on GET.', write_only=False, required=False, allow_null=True, ) client_key = SecretCharField( help_text='A PEM encoded private key used for authentication. Returns SHA256 checksum of ' 'the certificate file on GET.', write_only=False, required=False, allow_null=True, ) tls_validation = serializers.BooleanField( help_text='If True, TLS peer validation must be performed.', required=False, ) proxy_url = serializers.CharField( help_text='The proxy URL. Format: scheme://user:password@host:port', required=False, allow_null=True, ) username = serializers.CharField( help_text='The username to be used for authentication when syncing.', write_only=True, required=False, allow_null=True, ) password = serializers.CharField( help_text='The password to be used for authentication when syncing.', write_only=True, required=False, allow_null=True, ) pulp_last_updated = serializers.DateTimeField( help_text='Timestamp of the most recent update of the remote.', read_only=True ) download_concurrency = serializers.IntegerField( help_text='Total number of simultaneous connections.', required=False, min_value=1 ) policy = serializers.ChoiceField( help_text="The policy to use when downloading content.", choices=( (models.Remote.IMMEDIATE, 'When syncing, download all metadata and content now.') ), default=models.Remote.IMMEDIATE ) def validate_url(self, value): """ Check if the 'url' is a ``file://`` path, and if so, ensure it's an ALLOWED_IMPORT_PATH. The ALLOWED_IMPORT_PATH is specified as a Pulp setting. Args: value: The user-provided value for 'url' to be validated. Raises: ValidationError: When the url starts with `file://`, but is not a subfolder of a path in the ALLOWED_IMPORT_PATH setting. Returns: The validated value. """ if not value.lower().startswith('file://'): return value user_path = value[7:] for allowed_path in settings.ALLOWED_IMPORT_PATHS: user_provided_realpath = os.path.realpath(user_path) if user_provided_realpath.startswith(allowed_path): return value raise serializers.ValidationError(_("url '{}' is not an allowed import path").format(value)) class Meta: abstract = True model = models.Remote fields = ModelSerializer.Meta.fields + ( 'name', 'url', 'ca_cert', 'client_cert', 'client_key', 'tls_validation', 'proxy_url', 'username', 'password', 'pulp_last_updated', 'download_concurrency', 'policy' )
class RemoteSerializer(ModelSerializer): """ Every remote defined by a plugin should have a Remote serializer that inherits from this class. Please import from `pulpcore.plugin.serializers` rather than from this module directly. """ pulp_href = DetailIdentityField(view_name_pattern=r"remotes(-.*/.*)-detail") pulp_labels = LabelsField(required=False) name = serializers.CharField( help_text=_("A unique name for this remote."), validators=[UniqueValidator(queryset=models.Remote.objects.all())], ) url = serializers.CharField(help_text="The URL of an external content source.") ca_cert = serializers.CharField( help_text="A PEM encoded CA certificate used to validate the server " "certificate presented by the remote server.", required=False, allow_null=True, ) client_cert = serializers.CharField( help_text="A PEM encoded client certificate used for authentication.", required=False, allow_null=True, ) client_key = serializers.CharField( help_text="A PEM encoded private key used for authentication.", required=False, allow_null=True, ) tls_validation = serializers.BooleanField( help_text="If True, TLS peer validation must be performed.", required=False ) proxy_url = serializers.CharField( help_text="The proxy URL. Format: scheme://user:password@host:port", required=False, allow_null=True, ) username = serializers.CharField( help_text="The username to be used for authentication when syncing.", required=False, allow_null=True, ) password = serializers.CharField( help_text="The password to be used for authentication when syncing.", required=False, allow_null=True, ) pulp_last_updated = serializers.DateTimeField( help_text="Timestamp of the most recent update of the remote.", read_only=True ) download_concurrency = serializers.IntegerField( help_text="Total number of simultaneous connections.", required=False, min_value=1 ) policy = serializers.ChoiceField( help_text="The policy to use when downloading content.", choices=((models.Remote.IMMEDIATE, "When syncing, download all metadata and content now.")), default=models.Remote.IMMEDIATE, ) total_timeout = serializers.FloatField( allow_null=True, required=False, help_text="aiohttp.ClientTimeout.total (q.v.) for download-connections.", min_value=0.0, ) connect_timeout = serializers.FloatField( allow_null=True, required=False, help_text="aiohttp.ClientTimeout.connect (q.v.) for download-connections.", min_value=0.0, ) sock_connect_timeout = serializers.FloatField( allow_null=True, required=False, help_text="aiohttp.ClientTimeout.sock_connect (q.v.) for download-connections.", min_value=0.0, ) sock_read_timeout = serializers.FloatField( allow_null=True, required=False, help_text="aiohttp.ClientTimeout.sock_read (q.v.) for download-connections.", min_value=0.0, ) headers = serializers.ListField( child=serializers.DictField(), help_text=_("Headers for aiohttp.Clientsession"), ) rate_limit = serializers.IntegerField( help_text=_("Limits total download rate in requests per second"), allow_null=True, required=False, ) def validate_url(self, value): """ Check if the 'url' is a ``file://`` path, and if so, ensure it's an ALLOWED_IMPORT_PATH. The ALLOWED_IMPORT_PATH is specified as a Pulp setting. Args: value: The user-provided value for 'url' to be validated. Raises: ValidationError: When the url starts with `file://`, but is not a subfolder of a path in the ALLOWED_IMPORT_PATH setting. Returns: The validated value. """ if not value.lower().startswith("file://"): return value user_path = value[7:] for allowed_path in settings.ALLOWED_IMPORT_PATHS: user_provided_realpath = os.path.realpath(user_path) if user_provided_realpath.startswith(allowed_path): return value raise serializers.ValidationError(_("url '{}' is not an allowed import path").format(value)) class Meta: abstract = True model = models.Remote fields = ModelSerializer.Meta.fields + ( "name", "url", "ca_cert", "client_cert", "client_key", "tls_validation", "proxy_url", "username", "password", "pulp_labels", "pulp_last_updated", "download_concurrency", "policy", "total_timeout", "connect_timeout", "sock_connect_timeout", "sock_read_timeout", "rate_limit", )
class DistributionSerializer(ModelSerializer, BasePathOverlapMixin): """ The Serializer for the Distribution model. The serializer deliberately omits the the `publication` and `repository_version` field due to plugins typically requiring one or the other but not both. To include the ``publication`` field, it is recommended plugins define the field:: publication = DetailRelatedField( required=False, help_text=_("Publication to be served"), view_name_pattern=r"publications(-.*/.*)?-detail", queryset=models.Publication.objects.exclude(complete=False), allow_null=True, ) To include the ``repository_version`` field, it is recommended plugins define the field:: repository_version = RepositoryVersionRelatedField( required=False, help_text=_("RepositoryVersion to be served"), allow_null=True ) Additionally, the serializer omits the ``remote`` field, which is used for pull-through caching feature and only by plugins which use publications. Plugins implementing a pull-through caching should define the field in their derived serializer class like this:: remote = DetailRelatedField( required=False, help_text=_('Remote that can be used to fetch content when using pull-through caching.'), queryset=models.Remote.objects.all(), allow_null=True ) """ pulp_href = DetailIdentityField( view_name_pattern=r"distributions(-.*/.*)-detail") pulp_labels = LabelsField(required=False) base_path = serializers.CharField(help_text=_( 'The base (relative) path component of the published url. Avoid paths that \ overlap with other distribution base paths (e.g. "foo" and "foo/bar")' ), ) base_url = BaseURLField( source="base_path", read_only=True, help_text= _("The URL for accessing the publication as defined by this distribution." ), ) content_guard = DetailRelatedField( required=False, help_text=_("An optional content-guard."), view_name_pattern=r"contentguards(-.*/.*)?-detail", queryset=models.ContentGuard.objects.all(), allow_null=True, ) name = serializers.CharField( help_text=_("A unique name. Ex, `rawhide` and `stable`."), validators=[ UniqueValidator(queryset=models.BaseDistribution.objects.all()), UniqueValidator(queryset=models.Distribution.objects.all()), ], ) repository = DetailRelatedField( required=False, help_text=_( "The latest RepositoryVersion for this Repository will be served." ), view_name_pattern=r"repositories(-.*/.*)?-detail", queryset=models.Repository.objects.all(), allow_null=True, ) class Meta: abstract = True model = models.BaseDistribution fields = ModelSerializer.Meta.fields + ( "base_path", "base_url", "content_guard", "pulp_labels", "name", "repository", ) def validate(self, data): super().validate(data) publication_in_data = "publication" in data repository_version_in_data = "repository_version" in data publication_in_instance = self.instance.publication if self.instance else None repository_version_in_instance = self.instance.repository_version if self.instance else None if publication_in_data and repository_version_in_data: error = True elif publication_in_data and repository_version_in_instance: error = True elif publication_in_instance and repository_version_in_data: error = True else: error = False if error: msg = _( "Only one of the attributes 'publication' and 'repository_version' may be used " "simultaneously.") raise serializers.ValidationError(msg) return data
class BaseDistributionSerializer(ModelSerializer, BasePathOverlapMixin): """ The Serializer for the BaseDistribution model. The serializer deliberately omits the "remote" field, which is used for pull-through caching only. Plugins implementing pull-through caching will have to add the field in their derived serializer class like this:: remote = DetailRelatedField( required=False, help_text=_('Remote that can be used to fetch content when using pull-through caching.'), queryset=models.Remote.objects.all(), allow_null=True ) """ pulp_href = DetailIdentityField( view_name_pattern=r"distributions(-.*/.*)-detail") pulp_labels = LabelsField(required=False) base_path = serializers.CharField( help_text= _('The base (relative) path component of the published url. Avoid paths that \ overlap with other distribution base paths (e.g. "foo" and "foo/bar")' ), validators=[ UniqueValidator(queryset=models.BaseDistribution.objects.all()) ], ) base_url = BaseURLField( source="base_path", read_only=True, help_text= _("The URL for accessing the publication as defined by this distribution." ), ) content_guard = DetailRelatedField( required=False, help_text=_("An optional content-guard."), view_name_pattern=r"contentguards(-.*/.*)?-detail", queryset=models.ContentGuard.objects.all(), allow_null=True, ) name = serializers.CharField( help_text=_("A unique name. Ex, `rawhide` and `stable`."), validators=[ UniqueValidator(queryset=models.BaseDistribution.objects.all()), UniqueValidator(queryset=models.Distribution.objects.all()), ], ) def __init__(self, *args, **kwargs): """Initialize a BaseDistributionSerializer and emit deprecation warnings""" deprecation_logger.warn( _("BaseDistributionSerializer is deprecated and could be removed as early as " "pulpcore==3.13; use pulpcore.plugin.serializers.DistributionSerializer instead." )) return super().__init__(*args, **kwargs) class Meta: abstract = True model = models.BaseDistribution fields = ModelSerializer.Meta.fields + ( "base_path", "base_url", "content_guard", "pulp_labels", "name", )
class BaseDistributionSerializer(ModelSerializer): """ The Serializer for the BaseDistribution model. The serializer deliberately omits the "remote" field, which is used for pull-through caching only. Plugins implementing pull-through caching will have to add the field in their derived serializer class like this:: remote = DetailRelatedField( required=False, help_text=_('Remote that can be used to fetch content when using pull-through caching.'), queryset=models.Remote.objects.all(), allow_null=True ) """ pulp_href = DetailIdentityField( view_name_pattern=r"distributions(-.*/.*)-detail") pulp_labels = LabelsField(required=False) base_path = serializers.CharField( help_text= _('The base (relative) path component of the published url. Avoid paths that \ overlap with other distribution base paths (e.g. "foo" and "foo/bar")' ), validators=[ UniqueValidator(queryset=models.BaseDistribution.objects.all()) ], ) base_url = BaseURLField( source="base_path", read_only=True, help_text= _("The URL for accessing the publication as defined by this distribution." ), ) content_guard = DetailRelatedField( required=False, help_text=_("An optional content-guard."), view_name_pattern=r"contentguards(-.*/.*)?-detail", queryset=models.ContentGuard.objects.all(), allow_null=True, ) name = serializers.CharField( help_text=_("A unique name. Ex, `rawhide` and `stable`."), validators=[ UniqueValidator(queryset=models.BaseDistribution.objects.all()) ], ) class Meta: abstract = True model = models.BaseDistribution fields = ModelSerializer.Meta.fields + ( "base_path", "base_url", "content_guard", "pulp_labels", "name", ) def _validate_path_overlap(self, path): # look for any base paths nested in path search = path.split("/")[0] q = Q(base_path=search) for subdir in path.split("/")[1:]: search = "/".join((search, subdir)) q |= Q(base_path=search) # look for any base paths that nest path q |= Q(base_path__startswith="{}/".format(path)) qs = models.BaseDistribution.objects.filter(q) if self.instance is not None: qs = qs.exclude(pk=self.instance.pk) match = qs.first() if match: raise serializers.ValidationError(detail=_( "Overlaps with existing distribution '{}'").format(match.name)) return path def validate_base_path(self, path): self._validate_relative_path(path) return self._validate_path_overlap(path)