Разные проекты на Python Django./ Игра в шахматы на Django + React Typescript.

Теги: card js python

Игра в шахматы на Django + React Typescript.

Необходимые инструменты

Python 3, pip, npm

Шаг 1 Старт проекта

Виртуальное окружение и Django

python3 - m venv venv
. ./venv/bin/activate
pip install django

Заготовка.

django-admin startproject dj-chess
cd dj-chess

Создание БД

    ./manage.py migrate

Запуск сервера разработки.

    ./manage.py runserver

Шаг 2 Создание приложения

./manage.py startapp chess

Включаем в настройки проекта settings.py

INSTALLED_APPS = [
    ....
    'chess',
]

Создаем функцию-контроллер (представление)

from django.shortcuts import render

def index(request): 
    if request.method  == 'POST':
        print(request.POST['x'])

    return render(request, 'index.html')

Включаем эту функцию в роутинг urls.py.

from chess.views import index

urlpatterns = [
    path('', index),
    path('admin/', admin.site.urls),
]

Шаг 3 создание игрового пространства

Функция для формирования матрицы 2d

def make_board(x,y):
    board = []
    for x_item in range(0,x):
        tmp = [*range(0,y)]
        board.append(tmp)
    return board

Передаем доску в шаблон

def index(request): 
    board = None
    if request.method  == 'POST':
        x = int(request.POST['x'])
        y = int(request.POST['y'])
        board = make_board(x,y)
    return render(request, 'index.html', {"board": board})

В шаблоне выводим доску из расчета высоты и ширины

<div class="wrapper" style="
    width: {% widthratio board|length 1 100|add:10 %}px; 
    height: {% widthratio board|length 1 100 %}px; 
">

    {% for x in board  %}

        {% for y in x  %}
            <div class="chess-cel">{{ y }}</div>
        {% endfor %}

    {% endfor %}

</div>

Стилизируем.

<link rel="stylesheet" href="/static/chess.css" />

Настройки пути к статике settings.py.

STATICFILES_DIRS = [
    os.path.join(BASE_DIR , "static")

]

Сами стили.

.chess-cel {
    width: 100px;
    height: 100px;
    border: 1px solid silver;
}

.wrapper {
    display: flex;
    flex-wrap: wrap;
    margin: 0 auto;
}

Раскрашиваем ячейки.

Стили.

.bg-black {
    background-color: rgb(141, 138, 138);
}

.bg-white {
    background-color: rgb(247, 190, 190);
}

Меняем функцию.

def make_board(x,y):
    board = []
    for x_item in range(0,x):
        tmp = [*range(0,y)]
        tmp_objs = []
        for t in tmp:
            if x_item%2 == 0:
                if t%2 == 0 :
                    tmp_objs.append({"color": "bg-black"})
                else:
                    tmp_objs.append({"color": "bg-white"})
            else:
                if t%2 == 0 :
                    tmp_objs.append({"color": "bg-white"})
                else:
                    tmp_objs.append({"color": "bg-black"})
        board.append(tmp_objs)
    return board

Меняем шаблон.

    {% for x in board  %}

        {% for y in x  %}
            <div class="chess-cel {{ y.color }}"></div>
        {% endfor %}

    {% endfor %}

Результат.

start page

Расставляем фигуры на доске.

Делаем функцию и расставляем пешки.

def set_board(board):
    cnt = 0
    for line in board:

        if cnt == 1:
            for index, value in enumerate(line):
                board[cnt][index]["figure"] = "pone-white"

        if cnt == 6:
            for index, value in enumerate(line):
                board[cnt][index]["figure"] = "pone-black"
        cnt += 1
    return board

Расставляем остальные фигуры.

def set_board(board):
    cnt = 0
    for line in board:

        if cnt == 0:
            board[cnt][0]["figure"] = "rook-white"
            board[cnt][1]["figure"] = "knite-white"
            board[cnt][2]["figure"] = "bishop-white"
            board[cnt][3]["figure"] = "queen-white"
            board[cnt][4]["figure"] = "king-white"
            board[cnt][5]["figure"] = "bishop-white"
            board[cnt][6]["figure"] = "knite-white"
            board[cnt][7]["figure"] = "rook-white"

        if cnt == 1:
            for index, value in enumerate(line):
                board[cnt][index]["figure"] = "pone-white"

        if cnt == 6:
            for index, value in enumerate(line):
                board[cnt][index]["figure"] = "pone-black"

        if cnt == 7:
            board[cnt][0]["figure"] = "rook-black"
            board[cnt][1]["figure"] = "knite-black"
            board[cnt][2]["figure"] = "bishop-black"
            board[cnt][3]["figure"] = "queen-black"
            board[cnt][4]["figure"] = "king-black"
            board[cnt][5]["figure"] = "bishop-black"
            board[cnt][6]["figure"] = "knite-black"
            board[cnt][7]["figure"] = "rook-black"

        cnt += 1
    return board

Добавляем разметку к доске.

Добавим метку позиции по вертикали и горизонтали (pos_x и pos_y).

def make_board(x,y):
    board = []
    for x_item in range(0,x):
        tmp = [*range(0,y)]
        tmp_objs = []
        for t in tmp:
            if x_item%2 == 0:
                if t%2 == 0 :
                    cell_obj = {"color": "bg-black"}
                else:
                    cell_obj = {"color": "bg-white"}
            else:
                if t%2 == 0 :
                    cell_obj = {"color": "bg-white"}
                else:
                    cell_obj = {"color": "bg-black"}
            cell_obj["pos_x"] = t
            cell_obj["pos_y"] = x_item
            tmp_objs.append(cell_obj)
        board.append(tmp_objs)
    return board

Выведем в шаблоне.

    <div class="wrapper" style="
        width: {% widthratio board|length 1 100|add:10 %}px; 
        height: {% widthratio board|length 1 100 %}px; 
    ">
    {% for x in board  %}
        <div class="pos-x">
            {{ x.0.pos_y }}
        </div>
    {% endfor %}

        {% for x in board  %}

            {% for y in x  %}
                {% if forloop.counter == 1 %}
                    <div class="pos-y">
                        {{ y.pos_y }}
                    </div>
                {% endif %}

                <div class="chess-cel {{ y.color }}">
                    <img class="figure-img" src="/static/images/{{ y.figure }}.png" width="100%" alt="">
                </div>

            {% endfor %}

        {% endfor %}

    </div>

Делаем функцию перемещения.

def move_figure(board,from_x=0,from_y=0,to_x=1,to_y=1):
    figure = board[from_y][from_x]["figure"]
    board[from_y][from_x]["figure"] = None;
    board[to_y][to_x]["figure"] = figure;
    return board

База данных.

Создадим 3 функции:

  1. Cоздающую файл json с доской.

  2. Чтения из базы данных

  3. Запись в базу данных.

    import os import json

    DB_PATH = os.path.join(settings.BASE_DIR,’static’,’db.json’)

    def read_db(): with open(DB_PATH, ‘r’) as f: return json.loads(f.read())

    def write_db(board): with open(DB_PATH, ‘w’) as f: return f.write(json.dumps(board))

    def create_board_db(): if not os.path.isfile(DB_PATH): board = make_board(8,8) board = set_board(board) write_db(board) else: board = read_db() return board

Используем эти функции при перемещении фигуры.

def move_figure(from_x=0,from_y=0,to_x=1,to_y=1):
    board = read_db()
    figure = board[from_y][from_x]["figure"]
    board[from_y][from_x]["figure"] = None;
    board[to_y][to_x]["figure"] = figure;
    write_db(board)
    return board

Ставим React и рисуем им доску.

Создаем проект

npx create-react-app frontend

Эджектируем его для того, чтобы исправить конфы для webpack и направить сборку в папку static на бекенде.

cd frontend
npm run eject

Меняем в config/path.js строку

const buildPath = process.env.BUILD_PATH || 'build';

на

const buildPath = '../backend/static/build';

Сборка проекта.

npm run build

Настройка бекенда под новый шаблон

Поменяем путь к шаблонам в settings.py

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR,'static','build')],

Изменим контроллер на бекенде где поменяем адреса к стилям и скриптам в шаблоне т.к. у нас они будут отличаться от реактовских.

from django.http import HttpResponse
from django.template.loader import render_to_string


def index(request): 
    board = create_board_db()
    tpl = render_to_string('index.html', {"board": board})
    tpl = tpl.replace('src="/static','src="/static/build/static')
    tpl = tpl.replace('href="/static','href="/static/build/static')
    return HttpResponse(tpl)

Сделаем отслеживание изменяемых файлов и автоматическую сборку после изменений.

Установка библиотеки.

npm i --save-dev npm-watch

Добавляем в package.json строки

  "scripts": {
    ...
    "watch": "npm-watch",
   ...
  },
  "watch": {
    "build": "src/"
  },

Запуск вочера.

npm start watch

Устанавливаем Material UI и делаем роутинг.

Установка material-ui тем и цветов

npm install @material-ui/core --save
yarn add @material-ui/styles

Устанавливаем иконки.

npm install @material-ui/icons --save

Использование.

Вставляем шрифт в public/index.html

<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />

Вставляем кнопку

import Button from "@material-ui/core/Button";

<Button variant="contained" color="secondary">Button</Button>

Делаем панель навигации.

import './App.css';

import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import Typography from '@material-ui/core/Typography';
import Button from '@material-ui/core/Button';
import IconButton from '@material-ui/core/IconButton';
import MenuIcon from '@material-ui/icons/Menu';

import { makeStyles } from '@material-ui/core/styles';


const useStyles = makeStyles((theme) => ({
  root: {
    flexGrow: 1,
  },
  menuButton: {
    marginRight: theme.spacing(2),
  },
  title: {
    flexGrow: 1,
  },
}));


function App() {
  const classes = useStyles();
  return (
    <div className="App" >
        <AppBar position="static">
          <Toolbar>
            <IconButton edge="start" className={classes.menuButton} color="inherit" aria-label="menu">
              <MenuIcon />
            </IconButton>
            <Typography variant="h6" className={classes.title}>
              Chess game
            </Typography>
            <Button color="inherit">Login</Button>
          </Toolbar>
        </AppBar>

    </div>
  );
}

export default App;

Роутинг

Установка

npm install --save react-router-dom

Вынесем навигацию в отдельный компонент.

import React from 'react';

import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import Typography from '@material-ui/core/Typography';
import Button from '@material-ui/core/Button';
import IconButton from '@material-ui/core/IconButton';
import MenuIcon from '@material-ui/icons/Menu';
import { Link, BrowserRouter } from "react-router-dom";
import { makeStyles } from '@material-ui/core/styles';


const useStyles = makeStyles((theme) => ({
  root: {
    flexGrow: 1,
  },
  menuButton: {
    marginRight: theme.spacing(2),
  },
  title: {
    flexGrow: 1,
  },
}));

function Navbar() {
    const classes = useStyles();
    return (
        <AppBar position="static">
        <Toolbar>
            <IconButton edge="start" className={classes.menuButton} color="inherit" aria-label="menu">
            <MenuIcon />
            </IconButton>
            <Typography variant="h6" className={classes.title}>
            Chess game
            </Typography>

            <Button 
                variant="contained" 
                color="primary" 
                component={Link} 
                to={'/chess'} >
                    Chess
                </Button>

                <Button 
                variant="contained" 
                color="secondary" 
                component={Link} 
                to={'/login'} >
                    Login
                </Button>


        </Toolbar>
        </AppBar>
    )
}

export default Navbar;

Реализуем роутинг в App.js.

import './App.css';

import Chess from './pages/Chess';
import Login from './pages/Login';
import Navbar from './components/Navbar';

import {
  BrowserRouter as Router,
  Switch,
  Route
} from "react-router-dom";


function App() {

  return (
    <div className="App" >
          <Router>
            <Navbar />
            <Switch>

              <Route path="/login" component={Login} />
              <Route path="/chess" component={Chess} />
              <Route path="/" component={Chess} />
            </Switch>
          </Router>
    </div>
  );
}

export default App;

Компоненты логина

import React from 'react';

function Login() {
  console.log('Login comp');
  return (
    <div className="Chess" >
       <h1>Login</h1>
    </div>
  );
}

export default Login;

и игры

import React from 'react';

function Chess() {

  return (
    <div className="Chess" >
       <h1>Chess</h1>
    </div>
  );
}

export default Chess;

Результат.

start page

Устанавливаем axios и делаем запрос на сервер.

npm install axios --save

Создадим класс для запросов Request.js.

import axios from "axios";


class Request {

    async get(url) {
        let response = await axios.get(`${url}`)
        return response.data;
    }

}

export default Request;

Используем в хуке при монтировании компонента.

import React , { useState, useEffect } from 'react';
import Request from '../Request';


function Chess() {
  useEffect(() => { 
    var r = new Request();
    r.get('/static/db.json')
    .then((payload) => {
      console.log(payload);
    });
  }, [])
  return (
    <div className="Chess" >
       <h1>Chess</h1>
    </div>
  );
}

export default Chess;

Добавляем данные в состояние.

  const [board, setBoard] = useState([[]]);
  useEffect(() => { 
    var r = new Request();
    r.get('/static/db.json')
    .then((payload) => {
       setBoard(payload);
    });
  }, [])

Передаем состояние в компонент доски.

  return (
    <div className="Chess" >
       <h1>Chess</h1>
       <Board id="board" board={board} />  
    </div>
  );

Создаем компонент доски.

import './Board.css';
import React from 'react';
import Cell from './Cell';


function Board(props) {
  console.log(props);
  return (
    <div className="Board" >
       {props.board.map((el,index) => el.map((e,index) => <Cell cell={e} />)
       )}
    </div>
  );
}

export default Board;

Создаем компонент ячейки.

import React from 'react';
import './Cell.css';

function Cell(props) {
  return (
    <div className={`cell ${props.cell.color}`} >
      {props.cell.figure}
    </div>
  );
}

export default Cell;

Устанавливаем библиотеки.

djangorestframework==3.12.4
django-cors-headers==3.7.0
drf-yasg==1.20.0
djangorestframework-oauth==1.1.0

Добавляем роутинг.

from drf_yasg.views import get_schema_view
from drf_yasg import openapi
from rest_framework import permissions

from rest_framework import routers
router = routers.DefaultRouter()

schema_view = get_schema_view(
   openapi.Info(
      title="Chess game API",
      default_version='v1',
      description=''' 
          Documentation `ReDoc` view can be found [here](/doc).

          Admin interfase  [here](/admin).

          Authors: zdimon77@gmail.com
      ''',
      license=openapi.License(name="BSD License"),
   ),
   public=True,
   permission_classes=(permissions.AllowAny,),
)



urlpatterns = [
    ...
    path('v1/',include([
        path('chess/',include('chess.urls')),
        path('o/', include('oauth2_provider.urls', namespace='oauth2_provider')),
    ])),

]

Модели для провиля и фактов авторизации через соц. сети.

from django.db import models
from django.contrib.auth.models import User
# Create your models here.

class UserProfile(User):
    publicname = models.CharField(default='', max_length=250)


class SocialAuth(models.Model):
    type = models.CharField(max_length=50)
    email =  models.CharField(max_length=50)
    secret =  models.CharField(max_length=250)
    user = models.ForeignKey(UserProfile, on_delete=models.CASCADE)

Делаем контролер для логина через гугл.

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import AllowAny
from drf_yasg.utils import swagger_auto_schema

from chess.serializers import GoogleAuthRequestSerializer, UserSerializer

from rest_framework.authtoken.models import Token
from django.contrib.auth.models import User
from chess.models import UserProfile, SocialAuth

class GoogleView(APIView):
    '''
    Google authorization.

    _______________________

    '''

    permission_classes = (AllowAny,)
    @swagger_auto_schema( 
        request_body = GoogleAuthRequestSerializer \
        )
    def post(self, request, format=None):
        try:
            u = User.objects.get(username=request.data['email'])
            token = Token.objects.get(user=user)
            user = u.userprofile
        except Exception as e:
            print(str(e))
            user = UserProfile()
            user.username = request.data['email']
            user.publicname = '%s %s' % (request.data['name'],request.data['firstName'])
            user.is_active = True
            user.set_password = '123'
            user.save()
            token = Token.objects.create(user=user)
            s = SocialAuth()
            s.type = 'GOOGLE'
            s.email = request.data['email']
            s.user = user
            s.save()

        return Response({
            'token': token.key,
            'agent': request.META['HTTP_USER_AGENT'],
            'user': UserSerializer(user).data
        })

Устанавливаем библиотеку.

npm install socket.io-client --save

Создание соединения и обработка входящих сообщений.

var socket = io(`${config.backendURL}`)
socket.on('messages', (message) => {
  console.log(message);
}

Игровые поля.

Создадим модель где будем хранить все активные игровые доски.

Модель для фигур.

class Figure(models.Model):

    COLOR = (
        ("black", "Black"),
        ("white", "White")
    )
    name = models.CharField(max_length=90)
    image = models.ImageField(upload_to='figures')
    color = models.CharField(max_length=90, choices=COLOR,
                  default="white")


    @property
    def image_tag(self):
        try:
            return mark_safe(f'<img width="100" src="{self.image.url}" />')
        except:
            return 'No image'

Команда для загрузки начальных данных в таблицу.

from django.core.management.base import BaseCommand
from chess.models import Figure
from django.core.files import File
from django.conf import settings
import os


FIGURES = [
    'pone',
    'king',
    'queen',
    'knite',
    'bishop',
    'rook'
]

class Command(BaseCommand):

    def handle(self, *args, **options):
        print('Loading figures')
        Figure.objects.all().delete()
        for fn in FIGURES:
            f = Figure()
            f.name = fn
            f.color = 'white'
            f.save()
            path = os.path.join(settings.BASE_DIR,'static','images',f'{fn}-white.svg')
            with open(path, 'rb') as doc_file:
                f.image.save(f'{fn}-white.svg', File(doc_file), save=True)
        for fn in FIGURES:
            f = Figure()
            f.name = fn
            f.color = 'black'
            f.save()
            path = os.path.join(settings.BASE_DIR,'static','images',f'{fn}-black.svg')
            with open(path, 'rb') as doc_file:
                f.image.save(f'{fn}-black.svg', File(doc_file), save=True)

Связь пользователя, фигуры и доски.

class User2Figure(models.Model):
    user = models.ForeignKey(UserProfile,on_delete=models.CASCADE)
    figure = models.ForeignKey(Figure,on_delete=models.CASCADE)
    board = models.ForeignKey(Board,on_delete=models.CASCADE)

Модель доски.

class Board(models.Model):
    uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    owner = models.ForeignKey(UserProfile,on_delete=models.SET_NULL, null=True, blank=True)

Модель ячейки.

class Cell(models.Model):
    board = models.ForeignKey(Board,on_delete=models.CASCADE)
    figure = models.ForeignKey(User2Figure,on_delete=models.SET_NULL, null=True, blank=True)
    color = models.CharField(max_length=90, choices=COLOR,
              default="white")

Создадим представление (запрос API) для создания игровой доски.

Так как он будет авторизованным добавим классы для аутентификации в настройки.

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework.authentication.SessionAuthentication',
    ],
}

Функция создания или получения доски.

from chess.models import Board


def get_or_create_board(user):
    print('Creating board!')
    try:
        board = Board.objects.get(owner=user)
    except:
        board = Board()
        board.owner = user
        board.save()
    return board

Серилизатор для доски.

from rest_framework import serializers
from chess.models import Board



class BoardSerializer(serializers.ModelSerializer):


    class Meta:
        model = Board
        fields = [   
                    'owner', 
                    'uuid'
                    ]

Класс запроса.

from rest_framework.views import APIView
from rest_framework.response import Response
from drf_yasg.utils import swagger_auto_schema
from rest_framework.permissions import IsAuthenticated
from chess.serializers import BoardSerializer
from chess.utils import get_or_create_board

class CreateBoardView(APIView):
    '''
    Create or get board.

    _______________________

    '''

    permission_classes = (IsAuthenticated,)
    @swagger_auto_schema( 
        responses={200: BoardSerializer }
        )
    def get(self, request, format=None):
        return Response(BoardSerializer(get_or_create_board(request.user.userprofile)).data)

Дополним функцию создания доски раздачей фигур пользователю.

def give_figures(board):
    for f in Figure.objects.filter(color="white"):
        if f.name == 'pone':
            for cnt in range(1,9):
                u2f = User2Figure()
                u2f.figure = f
                u2f.user = board.owner
                u2f.board = board
                u2f.save()
        elif f.name == 'rook' or f.name == 'bishop' or f.name == 'knite':
            for cnt in range(1,3):
                u2f = User2Figure()
                u2f.figure = f
                u2f.user = board.owner
                u2f.board = board
                u2f.save()
        else:
            u2f = User2Figure()
            u2f.figure = f
            u2f.user = board.owner
            u2f.board = board
            u2f.save()

Добавим соперника в модель доски.

class Board(models.Model):
    uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    owner = models.ForeignKey(UserProfile,on_delete=models.SET_NULL, null=True, blank=True, related_name='owner')
    agressor = models.ForeignKey(UserProfile,on_delete=models.SET_NULL, null=True, blank=True, related_name='agressor')

Зоздадим запрос на добавление соперника.

from rest_framework.views import APIView
from rest_framework.response import Response
from drf_yasg.utils import swagger_auto_schema
from rest_framework.permissions import IsAuthenticated
from chess.serializers import BoardSerializer
from chess.utils import get_or_create_board

class AddAgressorBoardView(APIView):
    '''
    Add a rival.

    _______________________

    '''

    permission_classes = (IsAuthenticated,)
    @swagger_auto_schema( 
        responses={200: BoardSerializer }
        )
    def get(self, request, board_id, format=None):
        return Response({"message": "ok"})

Функцию для добавления соперника.

def add_agressor(board, user):
    board.agressor = user
    board.save()
    return board

Контроллер

from chess.models import Board
from rest_framework.views import APIView
from rest_framework.response import Response
from drf_yasg.utils import swagger_auto_schema
from rest_framework.permissions import IsAuthenticated
from chess.serializers import BoardSerializer
from chess.utils import add_agressor

class AddAgressorBoardView(APIView):
    '''
    Add a rival.

    _______________________

    '''

    permission_classes = (IsAuthenticated,)
    @swagger_auto_schema( 
        responses={200: BoardSerializer }
        )
    def get(self, request, board_id, format=None):
        board = Board.objects.get(uuid=board_id)
        return Response(BoardSerializer(add_agressor(board,request.user.userprofile)).data)

start page

Создадим функцию создания ячеек доски.

def make_cells(board):
    for x_item in range(1,9):
        tmp = [*range(1,9)]
        for t in tmp:
            c = Cell()
            c.board = board
            if x_item%2 == 0:
                if t%2 == 0 :
                    c.color = 'black'
                else:
                    c.color = 'white'
            else:
                if t%2 == 0 :
                    c.color = 'white'
                else:
                    c.color = 'black'
            c.save()

Вызовем ее при создании доски.

def get_or_create_board(user):
    print('Creating board!')
    try:
        board = Board.objects.get(owner=user)
    except:
        board = Board()
        board.owner = user
        board.save()
        give_figures(board)
        make_cells(board)
    return board

Создадим серилизатор для ячейки.

Дополним серилизатор доски ячейками.

from rest_framework import serializers
from chess.models import Board, Cell
from .cell_serializer import CellSerializer


class BoardSerializer(serializers.ModelSerializer):
    cells = serializers.SerializerMethodField()

    def get_cells(self, obj=None):
        cells = []
        for c in Cell.objects.filter(board=obj):
            cells.append(CellSerializer(c).data)
        return cells

    class Meta:
        model = Board
        fields = [   
                    'owner', 
                    'agressor',
                    'uuid',
                    'cells'
                    ]

start page

Фронтенд

Создадим кнопку для создания игрового поля.

Для этого определим переменную активной доски.

const [is_active_board, setIsActiveBoard] = useState(false);

И выведем кнопку в случае ее ложности.

   {
     is_active_board?
     <Board id="board" board={board} />
     :
     <div className="create-game-div">
     <Button 
     variant="contained" 
     color="primary" 
     onClick={doCreate}
     >
       Create a new game board.
     </Button>
     </div>
   }

Далее отправим запрос и положим данные в стейт (преременную состояния компонента).

  const doCreate = () => {
     var r = new Request();
     r.get('chess/create/board')
     .then((payload) => {
      setBoard(payload.cells);
      setIsActiveBoard(true);
    })

Компонент доски.

import './Board.css';
import React from 'react';
import Cell from './Cell';


function Board(props) {
  console.log(props);

  return (
    <div className="Board" >
       {
         props.board? 
         props.board.map((el,index) => <Cell cell={el} />)
         :
         <>
      }       
    </div>  
  );
}

export default Board;

Отрисовка ячеек.

import React from 'react';
import './Cell.css';

function Cell(props) {
  return (
    <div className={`cell bg-${props.cell.color}`} >
      { props.cell.figure ? <img class="chess-figure" src={`/static/images/${props.cell.figure}.svg`} />: ''}

    </div>  
  );
}

export default Cell;

Вставка ссылки на игру.

const [link, setLink] = useState('');


  const doCreate = () => {
     var r = new Request();
     r.get('chess/create/board')
     .then((payload) => {
      setBoard(payload.cells);
      setIsActiveBoard(true);
      setLink(`${config.siteURL}board/${payload.uuid}`);
    })

  }

Бекенд

Запрос на регистрацию по емейлу.

class EmailAuthView(APIView):
    '''
    Email authorization.

    _______________________

    '''

    permission_classes = ()
    authentication_classes = ()
    @swagger_auto_schema( 
        request_body = EmailSerializer, \
    )
    def post(self, request, format=None):
        data = json.loads(request.body)
        try:
            user = UserProfile.objects.get(username=data["email"])
            return Response({"status": 1, "message": "This email existst!"})
        except:
            print("Creating user")
            u = UserProfile()
            u.username = data["email"]
            u.set_password('123')
            u.is_active = True
            u.save()
            token, created = Token.objects.get_or_create(user=u)
            return Response({"status": 0, "token": token.key, "username": data["email"]})

Серилизатор.

from rest_framework import serializers


class EmailSerializer(serializers.Serializer):
    email = serializers.CharField()

Получение информации о игровой доске.

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import AllowAny
from drf_yasg.utils import swagger_auto_schema
from django.conf import settings
from chess.models import Board
from chess.serializers import BoardSerializer


class GetBoardView(APIView):
    '''
    Get board.

    _______________________

    '''

    permission_classes = ()
    authentication_classes = ()
    @swagger_auto_schema( 
            responses={200: BoardSerializer }
        )
    def get(self, request, board_id, format=None):
        board = Board.objects.get(uuid=board_id)
        return Response(BoardSerializer(board).data)

Фронтенд.

Запрашиваем информацию о доске.

    if(localStorage.getItem('board')){
      var r = new Request();
      r.get(`chess/get/board/${localStorage.getItem('board')}`)
      .then((payload) => {
       setBoard(payload.cells);
       setIsActiveBoard(true);
     })
    }

Расставляем фигуры.

Запрос на получение фигур игрока.

Серилизаторы.

from rest_framework import serializers
from chess.models import User2Figure

class FigureDetailSerializer(serializers.ModelSerializer):


    class Meta:
        model = Figure
        fields = [   
                    'name', 
                    'image',
                    'color'
                    ]


class FigureSerializer(serializers.ModelSerializer):
    figure = FigureDetailSerializer()
    class Meta:
        model = User2Figure
        fields = [   
                    'board', 
                    'figure',
                    'user'
                    ]

Запрос.

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from drf_yasg.utils import swagger_auto_schema
from django.conf import settings
from chess.serializers import FigureSerializer
from chess.models import User2Figure

class GetFiguresView(APIView):
    '''
    Get figures.

    _______________________

    '''

    permission_classes = (IsAuthenticated,)
    @swagger_auto_schema(

    )
    def get(self, request, format=None):
        figures = []
        for f in User2Figure.objects.filter(user=request.user):
            figures.append(FigureSerializer(f).data)
        return Response(figures)

Результат.

start page

Фронтенд.

Создаем запрос на получение фигур и раскладываем их в отдельном компоненте.

Компонент фигур.

import Request from '../Request';
import React , { useState, useEffect } from 'react';
import './Figures.css';



function Figures() {
  const [figures, setFigures] = useState([]);
  useEffect(() => {

    var r = new Request();
    r.get('chess/get/figures') 
    .then((payload) => {
      setFigures(payload);
   })

  },[]);

  return (
    <div className="figures-wrapper" >

        {
          figures.map((el) => { 
            return (
              <div className="figure"> 
                <img src={el.figure.get_image_absolute_url} />
              </div>
            )
          })
        }
    </div>  
  );
}

export default Figures;

Вывод компонента в гридах.

...
import Grid from '@material-ui/core/Grid';

function Chess() {
  ...
  return (
    <div className="Chess" >
       {
         is_active_board?
         <Grid container>
           <Grid item xs={4}>
            <Figures />
           </Grid>
           <Grid item xs={8}>
            <Board id="board" board={board} />
           </Grid>
         </Grid>
...

Результат.

start page

Делаем активной фигуру при клике.

Задать вопрос, прокомментировать.