refactor playlist duplicate error structure
- use non_field_errors struct when writing duplicate track errors - generalize frontend error handler and update frontend error parsing
This commit is contained in:
parent
31d990499d
commit
22f0235045
9 changed files with 285 additions and 22 deletions
|
|
@ -70,7 +70,7 @@ class Playlist(models.Model):
|
|||
return self.name
|
||||
|
||||
@transaction.atomic
|
||||
def insert(self, plt, index=None):
|
||||
def insert(self, plt, index=None, allow_duplicates=True):
|
||||
"""
|
||||
Given a PlaylistTrack, insert it at the correct index in the playlist,
|
||||
and update other tracks index if necessary.
|
||||
|
|
@ -96,6 +96,10 @@ class Playlist(models.Model):
|
|||
if index < 0:
|
||||
raise exceptions.ValidationError("Index must be zero or positive")
|
||||
|
||||
if not allow_duplicates:
|
||||
existing_without_current_plt = existing.exclude(pk=plt.pk)
|
||||
self._check_duplicate_add(existing_without_current_plt, [plt.track])
|
||||
|
||||
if move:
|
||||
# we remove the index temporarily, to avoid integrity errors
|
||||
plt.index = None
|
||||
|
|
@ -125,7 +129,7 @@ class Playlist(models.Model):
|
|||
return to_update.update(index=models.F("index") - 1)
|
||||
|
||||
@transaction.atomic
|
||||
def insert_many(self, tracks):
|
||||
def insert_many(self, tracks, allow_duplicates=True):
|
||||
existing = self.playlist_tracks.select_for_update()
|
||||
now = timezone.now()
|
||||
total = existing.filter(index__isnull=False).count()
|
||||
|
|
@ -134,6 +138,10 @@ class Playlist(models.Model):
|
|||
raise exceptions.ValidationError(
|
||||
"Playlist would reach the maximum of {} tracks".format(max_tracks)
|
||||
)
|
||||
|
||||
if not allow_duplicates:
|
||||
self._check_duplicate_add(existing, tracks)
|
||||
|
||||
self.save(update_fields=["modification_date"])
|
||||
start = total
|
||||
plts = [
|
||||
|
|
@ -144,6 +152,26 @@ class Playlist(models.Model):
|
|||
]
|
||||
return PlaylistTrack.objects.bulk_create(plts)
|
||||
|
||||
def _check_duplicate_add(self, existing_playlist_tracks, tracks_to_add):
|
||||
track_ids = [t.pk for t in tracks_to_add]
|
||||
|
||||
duplicates = existing_playlist_tracks.filter(
|
||||
track__pk__in=track_ids
|
||||
).values_list("track__pk", flat=True)
|
||||
if duplicates:
|
||||
duplicate_tracks = [t for t in tracks_to_add if t.pk in duplicates]
|
||||
raise exceptions.ValidationError(
|
||||
{
|
||||
"non_field_errors": [
|
||||
{
|
||||
"tracks": duplicate_tracks,
|
||||
"playlist_name": self.name,
|
||||
"code": "tracks_already_exist_in_playlist",
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class PlaylistTrackQuerySet(models.QuerySet):
|
||||
def for_nested_serialization(self, actor=None):
|
||||
|
|
|
|||
|
|
@ -24,10 +24,11 @@ class PlaylistTrackSerializer(serializers.ModelSerializer):
|
|||
|
||||
class PlaylistTrackWriteSerializer(serializers.ModelSerializer):
|
||||
index = serializers.IntegerField(required=False, min_value=0, allow_null=True)
|
||||
allow_duplicates = serializers.BooleanField(required=False)
|
||||
|
||||
class Meta:
|
||||
model = models.PlaylistTrack
|
||||
fields = ("id", "track", "playlist", "index")
|
||||
fields = ("id", "track", "playlist", "index", "allow_duplicates")
|
||||
|
||||
def validate_playlist(self, value):
|
||||
if self.context.get("request"):
|
||||
|
|
@ -47,17 +48,21 @@ class PlaylistTrackWriteSerializer(serializers.ModelSerializer):
|
|||
@transaction.atomic
|
||||
def create(self, validated_data):
|
||||
index = validated_data.pop("index", None)
|
||||
allow_duplicates = validated_data.pop("allow_duplicates", True)
|
||||
instance = super().create(validated_data)
|
||||
instance.playlist.insert(instance, index)
|
||||
|
||||
instance.playlist.insert(instance, index, allow_duplicates)
|
||||
return instance
|
||||
|
||||
@transaction.atomic
|
||||
def update(self, instance, validated_data):
|
||||
update_index = "index" in validated_data
|
||||
index = validated_data.pop("index", None)
|
||||
allow_duplicates = validated_data.pop("allow_duplicates", True)
|
||||
super().update(instance, validated_data)
|
||||
if update_index:
|
||||
instance.playlist.insert(instance, index)
|
||||
instance.playlist.insert(instance, index, allow_duplicates)
|
||||
|
||||
return instance
|
||||
|
||||
def get_unique_together_validators(self):
|
||||
|
|
@ -151,3 +156,7 @@ class PlaylistAddManySerializer(serializers.Serializer):
|
|||
tracks = serializers.PrimaryKeyRelatedField(
|
||||
many=True, queryset=Track.objects.for_nested_serialization()
|
||||
)
|
||||
allow_duplicates = serializers.BooleanField(required=False)
|
||||
|
||||
class Meta:
|
||||
fields = "allow_duplicates"
|
||||
|
|
|
|||
|
|
@ -55,7 +55,10 @@ class PlaylistViewSet(
|
|||
serializer = serializers.PlaylistAddManySerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
try:
|
||||
plts = playlist.insert_many(serializer.validated_data["tracks"])
|
||||
plts = playlist.insert_many(
|
||||
serializer.validated_data["tracks"],
|
||||
serializer.validated_data["allow_duplicates"],
|
||||
)
|
||||
except exceptions.ValidationError as e:
|
||||
payload = {"playlist": e.detail}
|
||||
return Response(payload, status=400)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue