Wednesday, 7 August 2019

django 64 save/read images in base64 string

default image for albums

click edit album

update logo url

error if image is not fetch from url

image fetched

album updated

postgres old album table, logo image name is saved

postgres new album table, logo image itself is saved in base64 string

#django/models

class Album(models.Model):
    artist = models.CharField(max_length=50)
    album_title = models.CharField(max_length=50)
    genre = models.CharField(max_length=50)
    album_logo = models.TextField(default='iVBORw0KGgoAAAANS...')
    date_posted = models.DateTimeField(auto_now_add=True)
    author = models.ForeignKey(User, on_delete=models.CASCADE, default=1)
    price = models.DecimalField(decimal_places=2,max_digits=5, default=10)

-------------------------------------
#django/api/serializers

class MusicSerializer(serializers.ModelSerializer):
    class Meta:
        model = Album
        fields = '__all__'

    #serializer doesn't have text field, has to change to charfield instead
    def __init__(self, *args, **kwargs):
        super(MusicSerializer, self).__init__(*args, **kwargs)
        self.fields['album_logo'] = serializers.CharField(required=True)

----------------------------------
#django/api/apiview

class AlbumList(APIView):
    def get(self, request, format=None):
        username = request.GET.get('author')
        data_length = request.GET.get('data_length')

        #filter by author
        if username==None:
            albums = Album.objects.all()
        else:
            author_id = get_object_or_404(User, username=username).pk
            albums = Album.objects.filter(author=author_id).order_by('-date_posted')

        #filter by data length
        if data_length!=None:
            try:
                int(data_length)
            except ValueError:
                return Response('data length is invvalid', status=status.HTTP_406_NOT_ACCEPTABLE)
            else:
                albums = albums[:int(data_length)]

        serializer = MusicSerializer(albums, many=True)
        return Response(serializer.data)

    def post(self, request, format=None):
        serializer = MusicSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

class AlbumDetail(APIView):

    def get_object(self, pk):
        try:
            return Album.objects.get(pk=pk)
        except Album.DoesNotExist:
            raise Http404

    def get(self, request, pk, format=None):
        album = self.get_object(pk)
        serializer = MusicSerializer(album)
        return Response(serializer.data)

    def put(self, request, pk, format=None):
        album = self.get_object(pk)

        #only owner can edit
        if album.author != request.user:
            return Response({"detail": "You do not have permission to perform this action."},
                            status= status.HTTP_403_FORBIDDEN)

        serializer = MusicSerializer(album, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def delete(self, request, pk, format=None):
        album = self.get_object(pk)

        # only owner can delete
        if album.author != request.user:
            return Response({"detail": "You do not have permission to perform this action."},
                            status=status.HTTP_403_FORBIDDEN)

        album.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

---------------------------------
#django/api/urls

path('album_list/', apiview.AlbumList.as_view(), name='AlbumList'),
path('album_detail/<int:pk>/', apiview.AlbumDetail.as_view(), name='AlbumDetail'),

----------------------------------
//cmd

npm install base64-to-image

------------------------------------
//react/pages/albumForm

import React, { Component } from 'react';
import '../App.css';
import { Input } from 'antd';
import { FaOpencart, FaLyft, FaSignature, FaDollarSign, FaRegShareSquare } from "react-icons/fa";
import { Button } from 'reactstrap';
import { connect } from 'react-redux';
import { postAlbum } from '../redux/actions/postAlbum';
import { putAlbum } from '../redux/actions/putAlbum';
import { Redirect } from 'react-router-dom';
import { getAlbums } from '../redux/actions/getAlbums';
import { getUsers } from '../redux/actions/getUser';
import { message } from 'antd';
const image2base64 = require('image-to-base64');

class albumForm extends Component {
    constructor(props) {
        super(props);

        this.state = {
            artist: '',
            album_title: '',
            genre: '',
            album_logo: null,
            mode: 'add',
            price: 0,
            imgSize: null,
        };
    }

    componentDidMount() {
        if (this.props.loggedin) {
            if (!this.props.gotUsers) {
                this.props.dispatch(getUsers(this.props.token));
            }
            if (!this.props.gotAlbums) {
                this.props.dispatch(getAlbums(this.props.token));
            }
        }

        //detecting if add or update
        const pathName = window.location.pathname.split('/');
        const albumId = pathName[pathName.length - 1];

        if (albumId == 'addAlbum') {
            //add
            this.setState({ mode: 'add' })
        } else {
            //update
            const album_id = parseInt(albumId);

            const waitGetAlbums = setInterval(() => {
                if (this.props.gotAlbums) {
                    const album = this.props.albums.filter(album => album.id == album_id)[0];

                    this.setState({
                        mode: 'update',
                        artist: album.artist,
                        album_title: album.album_title,
                        genre: album.genre,
                        album_logo: album.album_logo,
                        price: album.price,
                    })
                    clearInterval(waitGetAlbums);
                }
            }, 100)
        }
    }

    inputChange = (e, p) => {
        //album_logo save img in base64
        if (p === 'album_logo') {
            image2base64(e.target.value) // you can also to use url
                .then(
                    (response) => {
                        this.setState({ imgSize: response.length, album_logo: response }) //cGF0aC90by9maWxlLmpwZw==
                    }
                )
                .catch(
                    (error) => {
                        message.error(error.toString()) //Exepection error....
                    }
                )
        }
        else {
            this.setState({ [p]: e.target.value });
        }
    }

    fileChange = (e) => {
        console.log(e.target.files);
        this.setState({ album_logo: e.target.files[0] })
    }

    formSubmit = (e) => {
        e.preventDefault();

        const formData = new FormData();
        formData.set('artist', this.state.artist);
        formData.set('album_title', this.state.album_title);
        formData.set('genre', this.state.genre);
        formData.set('author', this.props.users.filter(user => user.username == this.props.username)[0].id);
        formData.set('album_logo', this.state.album_logo);
        formData.set('price', this.state.price);

        if (this.state.mode == 'add') {
            this.props.dispatch(postAlbum(this.props.token, formData))

            //wait for 5sec, check every sec to see if post successful
            let i = 0;
            const waitPost = setInterval(() => {
                if (this.props.added) {
                    message.success('post successful')
                    clearInterval(waitPost);
                }
                if (i == 50) {
                    message.error('post timeout')
                    clearInterval(waitPost);
                }
                i++;
            }, 100)
        }
        else {
            //update
            const pathName = window.location.pathname.split('/');
            const albumId = parseInt(pathName[pathName.length - 1]);

            this.props.dispatch(putAlbum(this.props.token, albumId, formData));

            let i = 0;
            const waitPost = setInterval(() => {
                if (this.props.updated) {
                    message.success('post updated')
                    clearInterval(waitPost);
                }
                if (i == 50) {
                    message.error('update timeout')
                    clearInterval(waitPost);
                }
                i++;
            }, 100)
        }
    }

    render() {
        if (!this.props.loggedin) {
            return <Redirect to='/login' />
        }

        return (
            <form style={{ marginLeft: '10px', width: '300px' }}
                onSubmit={(e) => this.formSubmit(e)}>
                <legend>Album Form</legend>
                <hr />
                <p style={{ color: 'red' }}>{this.props.error}</p>

                <Input placeholder={this.state.mode == 'add' ? "artist" : this.state.artist}
                    required={this.state.mode == 'add' ? true : false}
                    prefix={<FaOpencart style={{ color: 'rgba(0,0,0,.25)' }} />}
                    onChange={(e) => this.inputChange(e, 'artist')}
                    style={{ marginTop: '5px' }}
                />
                <Input placeholder={this.state.mode == 'add' ? "album title" : this.state.album_title}
                    required={this.state.mode == 'add' ? true : false}
                    prefix={<FaSignature style={{ color: 'rgba(0,0,0,.25)' }} />}
                    onChange={(e) => this.inputChange(e, 'album_title')}
                    style={{ marginTop: '15px' }}
                />
                <Input placeholder={this.state.mode == 'add' ? "genre" : this.state.genre}
                    required={this.state.mode == 'add' ? true : false}
                    prefix={<FaLyft style={{ color: 'rgba(0,0,0,.25)' }} />}
                    onChange={(e) => this.inputChange(e, 'genre')}
                    style={{ marginTop: '15px' }}
                />
                <Input type='number' placeholder={this.state.mode == 'add' ? "price" : this.state.price}
                    required={this.state.mode == 'add' ? true : false}
                    prefix={<FaDollarSign style={{ color: 'rgba(0,0,0,.25)' }} />}
                    onChange={(e) => this.inputChange(e, 'price')}
                    style={{ marginTop: '15px' }}
                />
                <Input type='url' placeholder={this.state.mode == 'add' ? "logo url(https://example.com/img.png)" : 'https://example.com/img.png'}
                    required={this.state.mode == 'add' ? true : false}
                    prefix={<FaRegShareSquare style={{ color: 'rgba(0,0,0,.25)' }} />}
                    onBlur={(e) => this.inputChange(e, 'album_logo')}
                    style={{ marginTop: '15px' }}
                />
                {this.state.album_logo ? <div style={{ marginTop: '15px' }}>
                    <img src={`data:image/jpeg;base64,${this.state.album_logo}`}
                        style={{ height: '75px', width: '100px' }} />
                    {this.state.imgSize ? <span style={{ marginLeft: '10px' }}>size: {this.state.imgSize} byte</span> : null}
                </div>
                    : null}
                <Button color="success" type='submit' size='sm'
                    style={{ marginTop: '15px' }}
                >Submit</Button>
            </form>
        );
    }
}

export default connect(
    (store) => {
        return {
            token: store.login.token,
            loggedin: store.login.fetched,
            added: store.albums.added,
            gotAlbums: store.albums.fetched,
            albums: store.albums.albums,
            gotUsers: store.users.fetched,
            users: store.users.users,
            username: store.login.username,
            error: store.albums.error,
            updated: store.albums.updated,
        };
    }
)(albumForm);

reference:
http://chuanshuoge2.blogspot.com/2019/08/django-64-saveread-images-in-base64.html

No comments:

Post a Comment