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): """ 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)
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 DistributionSerializer(ModelSerializer): _href = IdentityField( view_name='distributions-detail' ) name = serializers.CharField( help_text=_('A unique distribution name. Ex, `rawhide` and `stable`.'), validators=[validators.MaxLengthValidator( models.Distribution._meta.get_field('name').max_length, message=_('Distribution name length must be less than {} characters').format( models.Distribution._meta.get_field('name').max_length )), UniqueValidator(queryset=models.Distribution.objects.all())] ) 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.Distribution._meta.get_field('base_path').max_length, message=_('Distribution base_path length must be less than {} characters').format( models.Distribution._meta.get_field('base_path').max_length )), UniqueValidator(queryset=models.Distribution.objects.all()), ] ) publisher = DetailRelatedField( required=False, help_text=_('Publications created by this publisher and repository are automatically' 'served as defined by this distribution'), queryset=models.Publisher.objects.all(), allow_null=True ) content_guard = DetailRelatedField( required=False, help_text=_('An optional content-guard.'), queryset=models.ContentGuard.objects.all(), allow_null=True ) publication = RelatedField( required=False, help_text=_('The publication being served as defined by this distribution'), queryset=models.Publication.objects.exclude(complete=False), view_name='publications-detail', allow_null=True ) repository = serializers.HyperlinkedRelatedField( required=False, help_text=_('Publications created by this repository and publisher are automatically' 'served as defined by this distribution'), queryset=models.Repository.objects.all(), view_name='repositories-detail', allow_null=True ) base_url = BaseURLField( source='base_path', read_only=True, help_text=_('The URL for accessing the publication as defined by this distribution.') ) class Meta: model = models.Distribution fields = ModelSerializer.Meta.fields + ( 'name', 'base_path', 'publisher', 'publication', 'base_url', 'repository', 'content_guard', ) 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) if 'publisher' in data: publisher = data['publisher'] elif self.instance: publisher = self.instance.publisher else: publisher = None if 'repository' in data: repository = data['repository'] elif self.instance: repository = self.instance.repository else: repository = None if publisher and not repository: raise serializers.ValidationError({'repository': _("Repository must be set if " "publisher is set.")}) if repository and not publisher: raise serializers.ValidationError({'publisher': _("Publisher must be set if " "repository is set.")}) return data