1

I've just gone through the Django-Rest-Framework (DRF) tutorial and tried to adapt it to a simple photos app I am working on with models Album and Photo in a M-2-M relationship: an album can have many photos, and a photo can be in many albums. The db has tables myapp_album, myapp_photo and myapp_album_photos, as you'd expect.

But what I want is to be able to create albums and photos independent of each other and then to create the relations between them. I can do that easily in the shell with the photo.add(album), etc. but it's not clear to me how to do this via the generated DRF routes.

The problem is that using the terse DRF abstractions that I gleaned from the tutorial (using ModelSerializer, viewsets, etc.), I only end up with routes to /albums and /photos, and it's not clear to me how to create a route (or how otherwise) to associate existing photos with existing albums.

Could someone please help clarify the situation? Thanks

### models.py
class Photo(models.Model):
    title = models.CharField(max_length=100, blank=True)
    caption = models.TextField(blank=True)
    datetime = models.DateTimeField(auto_now_add=False, blank=False)
    filename = models.CharField(max_length=100, blank=False)

class Album(models.Model):
    name = models.CharField(max_length=100, blank=False, default='')
    description = models.TextField()
    photos = models.ManyToManyField(Photo)


### serializers.py
class AlbumSerializer(serializers.ModelSerializer):
    class Meta:
        model = Album
        fields = ['id', 'name', 'description']

class PhotoSerializer(serializers.ModelSerializer):
    class Meta:
        model = Photo
        fields = ['id', 'title', 'caption', 'datetime', 'filename']

### views.py
class AlbumViewSet(viewsets.ModelViewSet):
    queryset = Album.objects.all()
    serializer_class = AlbumSerializer

class PhotoViewSet(viewsets.ModelViewSet):
    queryset = Photo.objects.all()
    serializer_class = PhotoSerializer
Magnus
  • 3,086
  • 2
  • 29
  • 51
  • Have you tried updating `to_representation` of `PhotoSerializer` ? also check this: https://stackoverflow.com/questions/61537923/update-manytomany-relationship-in-django-rest-framework – Ahtisham Jan 30 '22 at 05:22

1 Answers1

2

You'll have to construct this functionality by hand.

  1. You can create an AlbumPhoto model that represents a relationship between a photo and an album. Then, you can use the default serializers and view sets for this model. By setting the through and through_fields settings of ManyToManyField, you should be able to use the new model without changing your existing model code.

    ### models.py
    class Photo(models.Model):
        title = models.CharField(max_length=100, blank=True)
        caption = models.TextField(blank=True)
        datetime = models.DateTimeField(auto_now_add=False, blank=False)
        filename = models.CharField(max_length=100, blank=False)
    
    class AlbumPhoto(models.Model):
        album = models.ForeignKey(Album, on_delete=models.CASCADE)
        photo = models.ForeignKey(Photo, on_delete=models.CASCADE)
        # add any extra information about this relationship
        # such as the order number of this photo in the album
    
    class Album(models.Model):
        name = models.CharField(max_length=100, blank=False, default='')
        description = models.TextField()
        photos = models.ManyToManyField(Photo,
            through='AlbumPhoto',
            through_fields=('album', 'photo'))
    
    ### serializers.py
    class AlbumPhotoSerializer(serializers.ModelSerializer):
        class Meta:
            model = AlbumPhoto
            fields = ['id', 'album', 'photo']
    
    ### views.py
    class AlbumPhotoViewSet(viewsets.ModelViewSet):
        queryset = AlbumPhoto.objects.all()
        serializer_class = AlbumPhotoSerializer
    

    To add a photo to an album, issue this POST request to /albumphoto/:

    {
        "album": 1,
        "photo": 1
    }
    

    This example works with Django 4.0.1, Django REST Framework 3.13.1, and Python 3.9.6.

  2. You can use a writable nested serializer, overriding the create() and update() methods to accept a list of related items when updating your photos or albums list. Here's an untested update() function that lets you add a list of Album IDs to a Photo model:

    class PhotoSerializer(serializers.ModelSerializer):
        class Meta:
            model = Photo
            fields = ['id', 'title', 'caption', 'datetime', 'filename', 'albums']
    
        def update(self, instance, validated_data):
            # Use pop() to remove the many-to-many fields
            # for custom processing.
            albums = validated_data.pop('albums')
            # Add each album to the photo instance.
            for album in albums:
                instance.albums.add(album)
            # Let the base class update() function
            # do the rest of the work.
            return super().update(instance, validated_data)
    
  3. You can create your own views for adding relationships between existing Photo and Album objects. If you're going to go to all the trouble, I think option 1 is better.

Jeff Booth
  • 461
  • 2
  • 5
  • 1
    Thanks very much for this -- very helpful. I am still researching the options and will let you know what I end up doing. – Magnus Jan 31 '22 at 01:02