コード例 #1
0
class FileSystemExporterSerializer(ModelSerializer):
    """
    Base serializer for FileSystemExporters.
    """
    pulp_href = DetailIdentityField()
    name = serializers.CharField(
        help_text=_("Unique name of the file system exporter."),
        validators=[
            validators.MaxLengthValidator(
                models.FileSystemExporter._meta.get_field('name').max_length,
                message=_(
                    '`name` length must be less than {} characters').format(
                        models.FileSystemExporter._meta.get_field(
                            'name').max_length)),
            UniqueValidator(queryset=models.BaseDistribution.objects.all())
        ])
    path = serializers.CharField(
        help_text=_("File system location to export to."))

    class Meta:
        model = models.FileSystemExporter
        fields = ModelSerializer.Meta.fields + (
            'path',
            'name',
        )
コード例 #2
0
class PublisherSerializer(MasterModelSerializer):
    """
    Every publisher defined by a plugin should have an Publisher 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 publisher.'),
        validators=[UniqueValidator(queryset=models.Publisher.objects.all())])
    _last_updated = serializers.DateTimeField(help_text=_(
        'Timestamp of the most recent update of the publisher configuration.'),
                                              read_only=True)
    distributions = serializers.HyperlinkedRelatedField(
        many=True,
        read_only=True,
        view_name='distributions-detail',
    )

    class Meta:
        abstract = True
        model = models.Publisher
        fields = MasterModelSerializer.Meta.fields + (
            'name',
            '_last_updated',
            'distributions',
        )
コード例 #3
0
class ImporterSerializer(ModelSerializer):
    """Base serializer for Importers."""
    pulp_href = DetailIdentityField()
    name = serializers.CharField(
        help_text=_("Unique name of the Importer."),
        validators=[UniqueValidator(queryset=models.Importer.objects.all())])

    class Meta:
        model = models.Importer
        fields = ModelSerializer.Meta.fields + ('name', )
コード例 #4
0
ファイル: importer.py プロジェクト: pavelpicka/pulpcore
class ImporterSerializer(ModelSerializer):
    """Base serializer for Importers."""

    pulp_href = DetailIdentityField(view_name_pattern=r"importer(-.*/.*)-detail")
    name = serializers.CharField(
        help_text=_("Unique name of the Importer."),
        validators=[UniqueValidator(queryset=models.Importer.objects.all())],
    )

    class Meta:
        model = models.Importer
        fields = ModelSerializer.Meta.fields + ("name",)
コード例 #5
0
class ContentGuardSerializer(ModelSerializer):
    pulp_href = DetailIdentityField()

    name = serializers.CharField(help_text=_('The unique name.'))
    description = serializers.CharField(
        help_text=_('An optional description.'),
        allow_null=True,
        required=False)

    class Meta:
        model = models.ContentGuard
        fields = ModelSerializer.Meta.fields + ('name', 'description')
コード例 #6
0
ファイル: repository.py プロジェクト: bherrin3/pulp
class ContentGuardSerializer(MasterModelSerializer):
    _href = DetailIdentityField()
    name = serializers.CharField(
        help_text=_('The unique name.'),
        validators=[
            UniqueValidator(queryset=models.ContentGuard.objects.all())
        ],
    )

    class Meta:
        abstract = True
        model = models.ContentGuard
        fields = MasterModelSerializer.Meta.fields + ('name', )
コード例 #7
0
class ContentGuardSerializer(ModelSerializer):
    pulp_href = DetailIdentityField(
        view_name_pattern=r"contentguards(-.*/.*)-detail")

    name = serializers.CharField(help_text=_("The unique name."))
    description = serializers.CharField(
        help_text=_("An optional description."),
        allow_null=True,
        required=False)

    class Meta:
        model = models.ContentGuard
        fields = ModelSerializer.Meta.fields + ("name", "description")
コード例 #8
0
ファイル: publication.py プロジェクト: fao89/pulpcore
class PublicationSerializer(MasterModelSerializer):
    _href = DetailIdentityField()
    repository_version = NestedRelatedField(
        view_name='versions-detail',
        lookup_field='number',
        parent_lookup_kwargs={'repository_pk': 'repository__pk'},
        queryset=models.RepositoryVersion.objects.all(),
        required=False,
    )
    repository = RelatedField(
        help_text=_('A URI of the repository to be published.'),
        required=False,
        label=_('Repository'),
        queryset=models.Repository.objects.all(),
        view_name='repositories-detail',
        write_only=True
    )

    def validate(self, data):
        if hasattr(self, 'initial_data'):
            validate_unknown_fields(self.initial_data, self.fields)

        repository = data.pop('repository', None)  # not an actual field on publication
        repository_version = data.get('repository_version')
        if not repository and not repository_version:
            raise serializers.ValidationError(
                _("Either the 'repository' or 'repository_version' need to be specified"))
        elif not repository and repository_version:
            return data
        elif repository and not repository_version:
            version = models.RepositoryVersion.latest(repository)
            if version:
                new_data = {'repository_version': version}
                new_data.update(data)
                return new_data
            else:
                raise serializers.ValidationError(
                    detail=_('Repository has no version available to create Publication from'))
        raise serializers.ValidationError(
            _("Either the 'repository' or 'repository_version' need to be specified "
              "but not both.")
        )

    class Meta:
        abstract = True
        model = models.Publication
        fields = MasterModelSerializer.Meta.fields + (
            'publisher',
            'repository_version',
            'repository'
        )
コード例 #9
0
ファイル: repository.py プロジェクト: pavelpicka/pulpcore
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)
    retain_repo_versions = serializers.IntegerField(
        help_text=
        _("Retain X versions of the repository. Default is null which retains all versions."
          " This is provided as a tech preview in Pulp 3 and may change in the future."
          ),
        allow_null=True,
        required=False,
        min_value=1,
    )
    remote = DetailRelatedField(
        help_text=_("An optional remote to use by default when syncing."),
        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",
            "retain_repo_versions",
            "remote",
        )
コード例 #10
0
ファイル: repository.py プロジェクト: makesse/pulpcore
class RepositorySerializer(ModelSerializer):
    pulp_href = DetailIdentityField()
    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')
コード例 #11
0
class ExporterSerializer(ModelSerializer):
    """
    Base serializer for Exporters.
    """

    pulp_href = DetailIdentityField(
        view_name_pattern=r"exporter(-.*/.*)-detail")
    name = serializers.CharField(
        help_text=_("Unique name of the file system exporter."),
        validators=[UniqueValidator(queryset=models.Exporter.objects.all())],
    )

    @staticmethod
    def validate_path(value, check_is_dir=False):
        """
        Check if path is in ALLOWED_EXPORT_PATHS.

        Args:
            value: The user-provided value path to be validated.
            check_is_dir: If true, insure path ends with a directory

        Raises:
            ValidationError: When path is not in the ALLOWED_EXPORT_PATHS setting.

        Returns:
            The validated value.
        """
        for allowed_path in settings.ALLOWED_EXPORT_PATHS:
            user_provided_realpath = os.path.realpath(value)
            if user_provided_realpath.startswith(allowed_path):
                if check_is_dir:  # fail if exists and not-directory
                    if os.path.exists(
                            user_provided_realpath
                    ) and not os.path.isdir(user_provided_realpath):
                        raise serializers.ValidationError(
                            _("Path '{}' must be a directory "
                              "path").format(value))
                return value
        raise serializers.ValidationError(
            _("Path '{}' is not an allowed export "
              "path").format(value))

    class Meta:
        model = models.Exporter
        fields = ModelSerializer.Meta.fields + ("name", )
コード例 #12
0
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',
        )
コード例 #13
0
class PublicationSerializer(ModelSerializer):
    pulp_href = DetailIdentityField(
        view_name_pattern=r"publications(-.*/.*)-detail")
    repository_version = RepositoryVersionRelatedField(required=False)
    repository = DetailRelatedField(
        help_text=_("A URI of the repository to be published."),
        required=False,
        label=_("Repository"),
        view_name_pattern=r"repositories(-.*/.*)?-detail",
        queryset=models.Repository.objects.all(),
    )

    def validate(self, data):
        if hasattr(self, "initial_data"):
            validate_unknown_fields(self.initial_data, self.fields)

        repository = data.pop("repository",
                              None)  # not an actual field on publication
        repository_version = data.get("repository_version")
        if not repository and not repository_version:
            raise serializers.ValidationError(
                _("Either the 'repository' or 'repository_version' need to be specified"
                  ))
        elif not repository and repository_version:
            return data
        elif repository and not repository_version:
            version = repository.latest_version()
            if version:
                new_data = {"repository_version": version}
                new_data.update(data)
                return new_data
            else:
                raise serializers.ValidationError(detail=_(
                    "Repository has no version available to create Publication from"
                ))
        raise serializers.ValidationError(
            _("Either the 'repository' or 'repository_version' need to be specified "
              "but not both."))

    class Meta:
        abstract = True
        model = models.Publication
        fields = ModelSerializer.Meta.fields + ("repository_version",
                                                "repository")
コード例 #14
0
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",
        )
コード例 #15
0
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",
        )
コード例 #16
0
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()
    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=[
            validators.MaxLengthValidator(
                models.BaseDistribution._meta.get_field(
                    'base_path').max_length,
                message=_('`base_path` length must be less than {} characters'
                          ).format(
                              models.BaseDistribution._meta.get_field(
                                  'base_path').max_length)),
            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.'),
        queryset=models.ContentGuard.objects.all(),
        allow_null=True)
    name = serializers.CharField(
        help_text=_('A unique name. Ex, `rawhide` and `stable`.'),
        validators=[
            validators.MaxLengthValidator(
                models.BaseDistribution._meta.get_field('name').max_length,
                message=_(
                    '`name` length must be less than {} characters').format(
                        models.BaseDistribution._meta.get_field(
                            'name').max_length)),
            UniqueValidator(queryset=models.BaseDistribution.objects.all())
        ])

    class Meta:
        abstract = True
        model = models.BaseDistribution
        fields = ModelSerializer.Meta.fields + (
            'base_path',
            'base_url',
            'content_guard',
            '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)
コード例 #17
0
ファイル: publication.py プロジェクト: jbennett7/pulpcore
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",
        )
コード例 #18
0
ファイル: publication.py プロジェクト: jbennett7/pulpcore
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
コード例 #19
0
ファイル: repository.py プロジェクト: pavelpicka/pulpcore
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,
        write_only=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://host:port",
        required=False,
        allow_null=True,
    )
    proxy_username = serializers.CharField(
        help_text="The username to authenticte to the proxy.",
        required=False,
        allow_null=True,
        write_only=True,
    )
    proxy_password = serializers.CharField(
        help_text="The password to authenticte to the proxy.",
        required=False,
        allow_null=True,
        write_only=True,
        style={"input_type": "password"},
    )
    username = serializers.CharField(
        help_text="The username to be used for authentication when syncing.",
        required=False,
        allow_null=True,
        write_only=True,
    )
    password = serializers.CharField(
        help_text="The password to be used for authentication when syncing.",
        required=False,
        allow_null=True,
        write_only=True,
        style={"input_type": "password"},
    )
    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. If not set then the default "
         "value will be used."),
        allow_null=True,
        required=False,
        min_value=1,
    )
    max_retries = serializers.IntegerField(
        help_text=
        ("Maximum number of retry attempts after a download failure. If not set then the "
         "default value (3) will be used."),
        required=False,
        allow_null=True,
    )
    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. The default is null, "
         "which will cause the default from the aiohttp library to be used."),
        min_value=0.0,
    )
    connect_timeout = serializers.FloatField(
        allow_null=True,
        required=False,
        help_text=
        ("aiohttp.ClientTimeout.connect (q.v.) for download-connections. The default is null, "
         "which will cause the default from the aiohttp library to be used."),
        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. The default is "
         "null, which will cause the default from the aiohttp library to be used."
         ),
        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. The default is "
         "null, which will cause the default from the aiohttp library to be used."
         ),
        min_value=0.0,
    )
    headers = serializers.ListField(
        child=serializers.DictField(),
        help_text=_("Headers for aiohttp.Clientsession"),
        required=False,
    )
    rate_limit = serializers.IntegerField(
        help_text=_(
            "Limits requests per second for each concurrent downloader"),
        allow_null=True,
        required=False,
    )

    def validate_url(self, url):
        """
        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:
            url: 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.
        """
        parsed_url = urlparse(url)
        if parsed_url.username or parsed_url.password:
            raise serializers.ValidationError(
                _("The remote url contains username or password. Please use remote username or "
                  "password instead."))

        if not url.lower().startswith("file://"):
            return url

        user_path = url[7:]
        if not os.path.isabs(user_path):
            raise serializers.ValidationError(
                _("The path '{}' needs to be an absolute pathname.").format(
                    user_path))

        user_provided_realpath = os.path.realpath(user_path)

        for allowed_path in settings.ALLOWED_IMPORT_PATHS:
            if user_provided_realpath.startswith(allowed_path):
                return url

        raise serializers.ValidationError(
            _("The path '{}' does not start with any of the allowed import paths"
              ).format(user_path))

    def validate_proxy_url(self, value):
        """
        Check, that the proxy_url does not contain credentials.
        """
        if value and "@" in value:
            raise serializers.ValidationError(
                _("proxy_url must not contain credentials"))
        return value

    def validate(self, data):
        """
        Check, that proxy credentials are only provided completely and if a proxy is configured.
        """
        data = super().validate(data)

        proxy_url = self.instance.proxy_url if self.partial else None
        proxy_url = data.get("proxy_url", proxy_url)
        proxy_username = self.instance.proxy_username if self.partial else None
        proxy_username = data.get("proxy_username", proxy_username)
        proxy_password = self.instance.proxy_password if self.partial else None
        proxy_password = data.get("proxy_password", proxy_password)

        if (proxy_username or proxy_password) and not proxy_url:
            raise serializers.ValidationError(
                _("proxy credentials cannot be specified without a proxy"))

        if bool(proxy_username) is not bool(proxy_password):
            raise serializers.ValidationError(
                _("proxy username and password can only be specified together")
            )

        return data

    class Meta:
        abstract = True
        model = models.Remote
        fields = ModelSerializer.Meta.fields + (
            "name",
            "url",
            "ca_cert",
            "client_cert",
            "client_key",
            "tls_validation",
            "proxy_url",
            "proxy_username",
            "proxy_password",
            "username",
            "password",
            "pulp_labels",
            "pulp_last_updated",
            "download_concurrency",
            "max_retries",
            "policy",
            "total_timeout",
            "connect_timeout",
            "sock_connect_timeout",
            "sock_read_timeout",
            "headers",
            "rate_limit",
        )
コード例 #20
0
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",
        )
コード例 #21
0
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')
コード例 #22
0
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'
        )
コード例 #23
0
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')
コード例 #24
0
ファイル: repository.py プロジェクト: t-woerner/pulp
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',
        )
コード例 #25
0
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
コード例 #26
0
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)