Tuesday, 18 June 2019

django 47 react album detail

click detail button on album will display songs in album

#django models

from django.db import models
from django.urls import reverse
from django.contrib.auth.models import User

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.FileField()
    date_posted = models.DateTimeField(auto_now_add=True)
    author = models.ForeignKey(User, on_delete=models.CASCADE, default=1)

    #form submitted without action redirect to detail
    def get_absolute_url(self):
        return reverse('music:detail', kwargs={'pk': self.pk})

    #query album.objects.get(pk=1)
    def __str__(self):
        return self.album_title + ' - ' + self.artist

class Song(models.Model):
    album = models.ForeignKey(Album, on_delete=models.CASCADE)
    file_type = models.CharField(max_length=50)
    song_title = models.CharField(max_length=50)
    is_favorite = models.BooleanField(default=False)

    def __str__(self):
        return self.song_title

        # form submitted without action redirect to detail
    def get_absolute_url(self):
        return reverse('music:detail', kwargs={'pk': self.album.id})

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

from rest_framework import serializers
from music.models import Album, Song
from django.contrib.auth.models import User

class MusicSerializer(serializers.ModelSerializer):
    class Meta:
        model = Album
        fields = ('id', 'artist', 'album_title', 'genre', 'album_logo', 'date_posted', 'author')

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ('id', 'username')

class SongSerializer(serializers.ModelSerializer):
    class Meta:
        model = Song
        fields = ('id', 'album', 'file_type', 'song_title', 'is_favorite')

-----------------------------
#django api/apiview
...
class SongList(APIView):
    def get(self, request, format=None):
        songs = Song.objects.all()
        serializer = SongSerializer(songs, many=True)
        return Response(serializer.data)

---------------------------------
#django urls

from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from music.api import apiview, mixins
from rest_framework.authtoken import views

app_name = 'musicAPI'

urlpatterns = [
    path('album_list/', apiview.AlbumList.as_view(), name='AlbumList'),
    path('user_list/', apiview.UserList.as_view(), name='UserList'),
    path('song_list/', apiview.SongList.as_view(), name='SongList'),
    path('api-token-auth/', views.obtain_auth_token, name='AuthToken'),
]

urlpatterns = format_suffix_patterns(urlpatterns)

------------------------------------
//partials/albumDetail

import React, { Component } from 'react';
import '../App.css';
import { Redirect } from 'react-router-dom';
import { connect } from 'react-redux';
import { getAlbums } from '../redux/actions/getAlbums';
import { getUsers } from '../redux/actions/getUser';
import { getSongs } from '../redux/actions/getSongs';
import { FaHeart, FaPlus } from "react-icons/fa";
import { MdEdit, MdDelete } from "react-icons/md";

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

        this.state = {
        };
    }

    componentDidMount() {

        //load database if not already loaded
        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));
            }
            if (!this.props.gotSongs) {
                this.props.dispatch(getSongs(this.props.token));
            }
        }
    }

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

        const pathName = window.location.pathname.split('/');
        const albumId = parseInt(pathName[pathName.length - 1]);
        const album = this.props.albums.filter(album => album.id == albumId)[0];
        const albumTitle = album.album_title;
        const songs = this.props.songs.filter(song => song.album == albumId)
        const songList = []
        songs.map((item, index) => {
            const songItem =
                <div>
                    <div style={{ display: 'flex', justifyContent: 'space-between', width: '80%' }} key={index}>
                        <span>
                            <b>{item.song_title}</b>{' '}
                            {item.is_favorite ? <FaHeart /> : ''}
                        </span>
                        <div>
                            <MdEdit style={{ marginRight: '10px', cursor: 'pointer' }} />
                            <MdDelete style={{ cursor: 'pointer' }} />
                        </div>
                    </div>
                    <hr />
                </div>
            songList.push(songItem);
        })
        const imgSrc = 'http://127.0.0.1:8000' + album.album_logo;
        const artist = album.artist;

        return (
            <div style={{ padding: '10px', marginTop: '10px' }}>
                {this.props.errorAlbum} {this.props.errorSong} {this.props.errorUser}

                {this.props.gotAlbums && this.props.gotSongs ?
                    <div>
                        <div style={{ width: '80%', display: 'flex', justifyContent: 'space-between' }}>
                            <h4>{albumTitle}</h4>
                            <img src={imgSrc} style={{ height: '30px', width: '40px' }}></img>
                        </div>
                        <hr />
                        <div style={{ width: '80%', display: 'flex', justifyContent: 'space-between' }}>
                            <span style={{ fontStyle: 'italic' }}>by {artist}</span>
                            <FaPlus style={{ cursor: 'pointer' }} />
                        </div>
                        <hr />
                        {songList}
                    </div>
                    :
                    <p>loading database</p>
                }
            </div>
        );
    }
}

export default connect(
    (store) => {
        return {
            token: store.login.token,
            loggedin: store.login.fetched,
            errorAlbum: store.albums.error,
            errorSong: store.songs.error,
            errorUser: store.users.error,
            albums: store.albums.albums,
            gotAlbums: store.albums.fetched,
            songs: store.songs.songs,
            gotSongs: store.songs.fetched,
            users: store.users.users,
            gotUsers: store.users.fetched,
        };
    }
)(AlbumDetail);

-----------------------------------
//partials/flippyCard

import React, { Component } from 'react';
import '../App.css';
import { FaPenNib } from "react-icons/fa";
import { Button } from 'reactstrap';
import { MdEdit, MdDelete, MdFlip } from "react-icons/md";
import { Link } from 'react-router-dom';

export default class FlippyCard extends Component {
    constructor(props) {
        super(props);
        this.myRef = React.createRef();
    }

    flipClick = () => {
        this.myRef.current.classList.toggle('manu-flip');
        console.log('Album: ', this.props.data.album_title, ' DOM class: ', this.myRef.current.classList);
    }

    render() {
        const imgSrc = 'http://127.0.0.1:8000' + this.props.data.album_logo;
        const detailLink = '/albumDetail/' + this.props.data.id;

        return (
            <div className="flip-container" ref={this.myRef} onTouchStart={() => this.flipClick()} style={{ margin: '10px' }}>
                <div className="flipper">
                    <div className="front" style={{ backgroundColor: 'white' }}>
                        <img src={imgSrc}
                            style={{ height: '150px', width: '100%' }}></img>
                        <div style={{ height: '60px' }}>
                            <h6 style={{ textAlign: 'center', marginTop: '10px' }}>{this.props.data.album_title}</h6>
                        </div>
                        <div style={{ backgroundColor: '#F4F6F6', height: '30px', paddingLeft: '5px' }}>
                            <FaPenNib /> <span>{this.props.data.author}</span>
                        </div>
                        <MdFlip className='flip-button' onClick={() => this.flipClick()} />
                    </div>
                    <div className="back" style={{ backgroundColor: 'white' }}>
                        <div style={{ textAlign: 'center', height: '220px', paddingTop: '10px' }}>
                            <p>Artist: {this.props.data.artist}</p>
                            <p>Genre: {this.props.data.genre}</p>
                            <div style={{ display: 'flex', justifyContent: 'space-around', marginLeft: '5px', marginRight: '5px' }}>
                                <Button color='primary' size='sm'><Link to={detailLink} style={{ color: 'white' }}>View Detail</Link></Button>
                                <Button outline size='sm'><MdEdit /></Button>
                                <Button outline size='sm'><MdDelete /></Button>
                            </div>
                        </div>
                        <div style={{ backgroundColor: '#F4F6F6', height: '30px', paddingLeft: '5px' }}>
                            <FaPenNib /> <span>{this.props.data.date_posted.split('.')[0].replace('T', ' ')}</span>
                        </div>
                        <MdFlip className='flip-button' onClick={() => this.flipClick()} />
                    </div>
                </div>
            </div>
        );
    }
}

-----------------------------
//partials/body

import React, { Component } from 'react';
import '../App.css';
import { Switch, Route } from 'react-router-dom';
import Albums from '../pages/albums';
import Login from '../pages/login';
import Logout from '../pages/logout';
import AlbumDetail from '../partials/albumDetail';

export default class Body extends Component {

    render() {
        return (
            <main className='Body-Background'>
                <Switch>
                    <Route exact path='/' component={Albums} />
                    <Route path='/login/' component={Login} />
                    <Route path='/logout/' component={Logout} />
                    <Route path='/albumDetail/' component={AlbumDetail} />
                </Switch>
            </main>
        );
    }
}

reference:
http://chuanshuoge2.blogspot.com/2019/06/django-46-react-toggle-flippy-card.html

No comments:

Post a Comment