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