django-rest-frameworkbase64django-serializer

How to add multiple base64 encoded images in a request


I want to make a POST request to create a bot, while I want to transfer several photo_examples images that are encoded in base64. By default, when using Base64ImageField, only one image can be added, and I need to add several. I tried to look at other projects on the Internet, but I didn't find a solution how to implement it.

models.py

class Bot(models.Model):
    """Bot Model."""
    name = models.CharField(
        'Название',
        max_length=100
    )
    author = models.ForeignKey(
        User,
        on_delete=models.CASCADE,
        verbose_name='Автор',
    )
    description = models.TextField(
        'Описание'
    )
    categories = models.ManyToManyField(
        Category,
        through='CategoryBot',
        related_name='bots',
        verbose_name='Категории',
    )
    main_photo = models.ImageField(
        'Фото',
        upload_to='uploads/main/%Y/%m/%d/',
    )
    price = models.DecimalField(
        'Цена',
        max_digits=8,
        decimal_places=0
    )
    is_special_offer = models.BooleanField(
        'Спецпредложение',
        default=False
    )

    class Meta:
        ordering = ('name',)
        verbose_name = 'Бот'
        verbose_name_plural = 'Боты'

    def __str__(self):
        return self.name


class Photo(models.Model):
    """Model of photos of examples of the bot's work."""
    photo_examples = models.ImageField(
        'Фото(образцы)',
        upload_to='uploads/examples/%Y/%m/%d/',
    )
    bot = models.ForeignKey(
        Bot,
        on_delete=models.CASCADE,
        verbose_name='Бот',
        related_name='photo_examples'
    )

    class Meta:
        ordering = ('bot',)
        verbose_name = 'Фото(образцы)'
        verbose_name_plural = 'Фото(образцы)'

    def __str__(self):
        return f' Фото бота {self.bot}'

serializers.py

class PhotoSerializer(serializers.ModelSerializer):
    """Serializer of photo bot examples"""
    photo_examples = Base64ImageField()

    class Meta:
        model = Photo
        fields = '__all__'


class CategoryNameSerializer(serializers.PrimaryKeyRelatedField):
    def to_representation(self, value):
        return value.name


class AbstractBotSerializer(serializers.ModelSerializer):
    """
    An abstract class that defines serialization and
deserialization behavior for bot-related data.
    """
    photo_examples = PhotoSerializer(many=True)
    author = serializers.StringRelatedField(read_only=True)
    discount_author = serializers.SerializerMethodField()
    discount_category = serializers.SerializerMethodField()
    amount_discounts_sum = serializers.SerializerMethodField()
    final_price = serializers.SerializerMethodField()
    categories = CategoryNameSerializer(
        required=False,
        queryset=Category.objects.all(),
        many=True,
    )

    class Meta:
        abstract = True
        fields = '__all__'

views.py

class BotViewSet(ModelViewSet):
    """The viewset for the bot.
    With the possibility of pagination, search by name
    and description, filtering by category"""
    queryset = Bot.objects.all()
    serializer_class = BotSerializer
    pagination_class = LimitPageNumberPagination
    filter_backends = (filters.SearchFilter, DjangoFilterBackend, )
    search_fields = ('name', 'description')
    permission_classes = (IsAuthorOrReadOnly, IsAuthor,)
    filterset_fields = ('categories', )

    def get_serializer_class(self):
        if self.action == 'retrieve':
            return BotReviewRatingSerializer
        return self.serializer_class

    def perform_create(self, serializer):
        discount_author = self.request.data.get("discount_author", 0)

        bot_instance = serializer.save(author=self.request.user)

        bot_discount, created = BotDiscount.objects.get_or_create(
            bot=bot_instance, defaults={"discount": discount_author}
        )

        if not created:
            bot_discount.discount = discount_author
            bot_discount.save()

I want to transmit data in approximately this format, but if there is a way that will make it easier to implement several isobases, then you can use another way to transfer a few isobases.

POST request

{
    "name": "My Bot New",
    "description": "This is my bot",
    "main_photo": "data:image/jpeg;base64,/9j/4A....A/9k=",
    "price": 500,
    "discount_author": 35,
    "categories": [6, 4],
    "photo_examples": [
        {
        "photo_examples": "data:image/jpeg;base64,/9j/4A....A/9k="
        },
        {
        "photo_examples": "data:image/jpeg;base64,/9j/4A....A/9k="
        }
    ]
}

Now I'm getting an error:

{
    "photo_examples": [
        {
            "bot": [
                "Required field"
            ]
        },
        {
            "bot": [
                "Required field."
            ]
        }
    ]
}

Solution

  • Added to views.py in perform_create:

        # extracts the photo data from the request data
        photos_data = self.request.data.get('photo_examples', None)
        if photos_data:
            # walk through each photo
            for photo_data in photos_data:
                photo_examples_data = photo_data.get('photo_examples', None)
                if photo_examples_data:
                    # creates a new instance of a photo using a bot instance
                    photo = Photo.objects.create(bot=bot_instance)
                    #  saves photo_examples data in an instance Photo
                    photo.photo_examples.save(
                        f"{photo.pk}.jpg",
                        ContentFile(base64.b64decode(
                            photo_examples_data.split(',')[1])),
                        save=True
                    )
    

    В serializers.py redefined the field photo_examples

    photo_examples = PhotoSerializer(many=True, read_only=True)