diff --git a/beat/web/common/serializers.py b/beat/web/common/serializers.py index a404df1688ff3c89ce30c4875b6194f3516da699..05344e4daf42551e3d9abad94887b866db62d9bb 100644 --- a/beat/web/common/serializers.py +++ b/beat/web/common/serializers.py @@ -25,11 +25,13 @@ # # ############################################################################### -from django.conf import settings from django.contrib.auth.models import User from django.utils import six +from django.db.models import CharField, Value as V +from django.db.models.functions import Concat + from rest_framework import serializers from ..team.models import Team @@ -295,41 +297,12 @@ class ContributionSerializer(VersionableSerializer): # ---------------------------------------------------------- -class MapDot(dict): - def __init__(self, *args, **kwargs): - super(MapDot, self).__init__(*args, **kwargs) - for arg in args: - if isinstance(arg, dict): - for k, v in arg.items(): - self[k] = v - - if kwargs: - for k, v in kwargs.items(): - self[k] = v - - def __getattr__(self, attr): - return self.get(attr) - - def __setattr__(self, key, value): - self.__setitem__(key, value) - - def __setitem__(self, key, value): - super(MapDot, self).__setitem__(key, value) - self.__dict__.update({key: value}) - - def __delattr__(self, item): - self.__delitem__(item) - - def __delitem__(self, key): - super(MapDot, self).__delitem__(key) - del self.__dict__[key] - - class ContributionCreationSerializer(serializers.ModelSerializer): declaration = JSONSerializerField(required=False) description = serializers.CharField(required=False, allow_blank=True) fork_of = serializers.JSONField(required=False) previous_version = serializers.CharField(required=False) + version = serializers.IntegerField(min_value=1) class Meta: fields = [ @@ -339,9 +312,24 @@ class ContributionCreationSerializer(serializers.ModelSerializer): "declaration", "previous_version", "fork_of", + "version", ] beat_core_class = None + def validate_fork_of(self, fork_of): + if "previous_version" in self.initial_data: + raise serializers.ValidationError( + "fork_of and previous_version cannot appear together" + ) + return fork_of + + def validate_previous_version(self, previous_version): + if "fork_of" in self.initial_data: + raise serializers.ValidationError( + "previous_version and fork_of cannot appear together" + ) + return previous_version + def validate_description(self, description): if description.find("\\") >= 0: # was escaped, unescape description = description.decode("string_escape") @@ -351,102 +339,85 @@ class ContributionCreationSerializer(serializers.ModelSerializer): user = self.context.get("user") name = self.Meta.model.sanitize_name(data["name"]) data["name"] = name + version = data.get("version") - if "previous_version" in data: - if self.Meta.beat_core_class is not None: - previous_version_asset = self.Meta.beat_core_class.Storage( - settings.PREFIX, data["previous_version"] + # If version is not one then it's necessarily a new version + # forks start at one + if version > 1 and "previous_version" not in data: + raise serializers.ValidationError( + "{} {} version {} incomplete history data posted".format( + self.Meta.model.__name__.lower(), name, version ) - if previous_version_asset.username is None: - previous_version_asset.username = user.username - else: - previous_version_asset = MapDot() - previous_version_asset["username"] = user.username - previous_version_asset["name"] = name - previous_version_asset["version"] = data["previous_version"] - data["data"] = json.dumps(data["data"]) - - else: - previous_version_asset = None + ) - if "fork_of" in data: - if self.Meta.beat_core_class is not None: - fork_of_asset = self.Meta.beat_core_class.Storage( - settings.PREFIX, data["fork_of"] + if self.Meta.model.objects.filter( + author__username__iexact=user, name=name, version=version + ).exists(): + raise serializers.ValidationError( + "{} {} version {} already exists on this account".format( + self.Meta.model.__name__.lower(), name, version ) - if fork_of_asset.username is None: - fork_of_asset.username = user.username - else: - fork_of_asset = MapDot() - fork_elem = data["fork_of"] - fork_of_asset["username"] = fork_elem["username"] - fork_of_asset["name"] = fork_elem["name"] - fork_of_asset["version"] = fork_elem["version"] - data["data"] = json.dumps(data["data"]) + ) - else: - fork_of_asset = None + previous_version = data.get("previous_version") + fork_of = data.get("fork_of") - # Retrieve the previous version (if applicable) - if previous_version_asset is not None: + if previous_version is not None: try: - previous_version = self.Meta.model.objects.get( - author__username__iexact=previous_version_asset.username, - name=previous_version_asset.name, - version=previous_version_asset.version, - ) + previous_object = self.Meta.model.objects.annotate( + fullname=Concat( + "author__username", + V("/"), + "name", + V("/"), + "version", + output_field=CharField(), + ) + ).get(fullname=previous_version) except self.Meta.model.DoesNotExist: raise serializers.ValidationError( "{} '{}' not found".format( - self.Meta.model.__name__, previous_version_asset.fullname + self.Meta.model.__name__, previous_version ) ) - - has_access, _, _ = previous_version.accessibility_for(user) - if not has_access: + accessibility_infos = previous_object.accessibility_for(user) + if not accessibility_infos.has_access: raise serializers.ValidationError("No access allowed") - data["previous_version"] = previous_version - # Retrieve the forked algorithm (if applicable) - if fork_of_asset is not None: - try: - fork_of = self.Meta.model.objects.get( - author__username__iexact=fork_of_asset.username, - name=fork_of_asset.name, - version=fork_of_asset.version, - ) - except self.Meta.model.DoesNotExist: + if version - previous_object.version != 1: raise serializers.ValidationError( - "{} '{}' not found".format( - self.Meta.model.__name__, fork_of_asset.fullname + "The requested version ({}) for this {} does not match" + "the standard increment with {}".format( + version, self.Meta.model.__name__, previous_object.version ) ) + data["previous_version"] = previous_object - has_access, _, _ = fork_of.accessibility_for(user) - if not has_access: - raise serializers.ValidationError("No access allowed") - data["fork_of"] = fork_of - - # Determine the version number - last_version = None - - if previous_version_asset is not None: - if (previous_version_asset.username == user.username) and ( - previous_version_asset.name == name - ): - last_version = self.Meta.model.objects.filter( - author=user, name=name - ).order_by("-version")[0] + elif fork_of is not None: + if version > 1: + raise serializers.ValidationError("A fork starts at 1") - if last_version is None: - if self.Meta.model.objects.filter(author=user, name=name).count() > 0: + try: + forked_of_object = self.Meta.model.objects.annotate( + fullname=Concat( + "author__username", + V("/"), + "name", + V("/"), + "version", + output_field=CharField(), + ) + ).get(fullname=fork_of) + except self.Meta.model.DoesNotExist: raise serializers.ValidationError( - "This {} name already exists on this account".format( - self.Meta.model.__name__.lower() + "{} '{}' fork origin not found".format( + self.Meta.model.__name__, fork_of ) ) - - data["version"] = last_version.version + 1 if last_version is not None else 1 + accessibility_infos = forked_of_object.accessibility_for(user) + if not accessibility_infos.has_access: + raise serializers.ValidationError("No access allowed") + data["fork_of"] = forked_of_object return data