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:
Qasim Ali 2019-04-24 11:31:46 +02:00 committed by Eliot Berriot
commit 22f0235045
9 changed files with 285 additions and 22 deletions

View file

@ -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):

View file

@ -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"

View file

@ -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)