Моя идея состояла в том, чтобы построить простой репликабельный проект на Angular с бэкэндом на Django. Я искал и не смог найти нужных решений, пришлось во всем разбираться самому. В итоге я разобрался и решил сам написать гайд для всех, кого может заинтересовать данная проблема.
Данная статья поможет вам построить простое приложение Angular с бэкэндом на Django, организованного с помощью Webpack.
Проблема
Я хочу настроить проект на Angular 1.1.x и скормить ему данные с сервера Django. Мне бы хотелось использовать Django REST Framework (DRF), чтобы пострить RESTful API. Я также хочу сбандлить JS ассеты. Сейчас я собираюсь запустить сайт на одном сервере.
Предварительные требования
-
Python 2.x
-
Django 1.9.x
-
npm 2.15.8+
-
Webpack 1.13.x (sudo npm i -g webpack)
-
ESLint 2.13.1+ (sudo npm i -g eslint)
-
NodeJS 4.4.7+
Содержание
-
Скаффолдинг проекта. Создайте свои начальные директории.
-
Скаффолдинг проекта на Django.
-
Настрока переменных среды, нужных для запуска сервера Django.
-
Установка Django REST Framework и настройка Django с использованием переменных среды.
-
Создание API.
-
Запуск Django сервера с использованием dev settings.
-
Инициализация npm-пакета и установка front-end JS зависимостей.
-
Создание Angular entry-point и загрузка начальных зависимостей.
-
Настройка Webpack'а.
-
Дайте команду Django загрузить приложение.
-
Создайте шаблон базы приложения Angular.
-
Напишите компонент home.
-
Напишите Angular роуты, ведущие к вашему компоненту home и странице 404.
-
Добавьте директивы ангуляр-маршрутизатора к шаблону входной точки приложения.
-
Проверьте ваше REST API в приложении Angular. Шпаргалка.
Итак, начнем!
0. Настройте среду для Python.
mkvirtualenv mysite
1. Скаффолдинг проекта на Django. Создайте начальные директории.
Мы хотим сфокусироваться на модулярности в ходе разработки. Следовательно, существует множество директорий в конечном итоге использования. Мы хотим, чтобы наше дерево изначально выглядело так:
mysite
├── backend
│ ├── docs
│ ├── requirements
└── frontend
├── app
│ ├── components
│ └── shared
├── assets
│ ├── css
│ ├── img
│ ├── js
│ └── libs
├── config
├── dist
└── js
Сделайте следующее:
mkdir mysite && cd mysite
mkdir -p backend/docs/ backend/requirements/ \
frontend/app/shared/ \
frontend/app/components/ \
frontend/config \
frontend/assets/img/ frontend/assets/css/ \
frontend/assets/js/ frontend/assets/libs/ \
frontend/dist/js/
*Примечание: Структура этого проекта была навеяна опытом с несколькими другими проектами. Я считаю эту организацию идеальной, но вам не обязательно ей следовать. Но, пока вы читаете этот гайд, вы должны придерживаться этой структуры.
2. Скаффолдинг проекта на Django.
В директории backend/ создайте Django проект:
python django-admin.py startproject mysite
Также создайте requirements.txt:
pip freeze > requirements/requirements.txt
В директории (вашего проекта) backend/mysite/ произведите скаффолдинг директории, той, где будет жить ваше API:
mkdir -p applications/api/v1/
touch applications/__init__.py applications/api/__init__.py \
applications/api/v1/__init__.py applications/api/v1/routes.py \
applications/api/v1/serializers.py applications/api/v1/viewsets.py
Теперь создайте структуру директории настроек:
mkdir -p configlord/settings/
touch configlord/settings/__init__.py \
configlord/settings/base.py configlord/settings/dev.py configlord/settings/prod.py \
configlord/dev.env configlord/prod.en
3. Настройте переменные окружения, которые нужны для запуска сервера Django.
На этом этапе я предпочитаю пользоваться django-environ для работы с переменными окружения. Существует множество способов сделать это, но пакет django-environ чрезвычайно упрощает этот процесс, поэтому я использую его во всех своих проектах.
Установите django-environ:
pip install django-environ
В mysite/dev.env добавьте следующее:
DATABASE_URL=sqlite:///mysite.db
DEBUG=True
FRONTEND_ROOT=path/to/mysite/frontend/
SECRET_KEY=_some_secret_key
Мы собираемся использовать эти переменные среды в наших настройках. Выгода от использования наших переменных окружения в отдельных файлах состоит в основном в том, что такая настройка позволяет облегчить переключение между средами. В нашем случае файл the dev.env является списком переменных, которые мы бы использовали в локальной среде разработки.
*Примечание: SECRET_KEY можно взять из settings.py, который был сгенерирован django-admin.py startproject.
4. Установите Django REST Framework и настройте Django, используя переменные среды.
Установка DRF:
pip install djangorestframework
Наполните settings/base.py следующим:
Укажите, где искать переменные окружения.
import environ
project_root = environ.Path(__file__) - 3
env = environ.Env(DEBUG=(bool, False),)
CURRENT_ENV = 'dev' # 'dev' is the default environment
# read the .env file associated with the settings that're loaded
env.read_env('./mysite/{}.env'.format(CURRENT_ENV))
Установите базу данных. В данном случае мы собираемся использовать встроенные в django-environ настройки SQLite.
DATABASES = {
'default': env.db()
}
Установите SECRET_KEY ,а также debug.
SECRET_KEY = env('SECRET_KEY')
DEBUG = env('DEBUG')
Добавьте DRF в пул приложений, которые Django должен использовать.
# Application definition
INSTALLED_APPS = [
...
# Django Packages
'rest_framework',
]
Ссылки будут «жить» в специальном URL модуле, созданном с помощью базы проекта.
ROOT_URLCONF = 'mysite.urls'
Укажите Django, где искать все шаблоны и другие статические ассеты.
STATIC_URL = '/static/'
STATICFILES_FINDERS = [
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
]
STATICFILES_DIRS = [
env('FRONTEND_ROOT')
]
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [env('FRONTEND_ROOT')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
В соответствии с настройкой TEMPLATES Django должен будет искать шаблоны внутри frontend/ directory. Это то, где Angular приложение будет жить. Мы используем только Django, чтобы обслужить шаблон, внутри которого Angular приложение будет загружаться, которое будет выполнено через entry-point директиву. Если вы не знаете, о чем я, продолжайте чтение...
Наполните settings/dev.py:
from mysite.settings.base import *
CURRENT_ENV = 'dev'
Здесь мы указываем, что этот файл настроек унаследывает настройки из base.py и переопределяет строку CURRENT_ENV, найденную в base.py. Мы говорим: «Используй это значение вместо значения, найденного в наследуемом модуле».
5. Создайте API.
Нам нужно нечто, с помощью чего мы сможем протестировать службы Angular, поэтому давайте создадим небольшое API. Этот шаг можно пропустить, но я не советовал бы делать этого. Нам важно знание того, что настройки приложения Angular работают исключительно с точки зрения его потенциала, чтобы облегчить HTTP запросы.
Сгенерируйте приложение.
manage.py startapp games
Создайте модель в games/models.py.
class Game(models.model):
title = models.CharField(max_length=255)
description = models.CharField(max_length=750)
Создайте DRF сериализатор для модели игры в applications/api/v1/serializers.py.
from rest_framework.serializers import ModelSerializer
from applications.games.models import Game
class GameSerializer(ModelSerializer):
class Meta:
model = Game
Создайте DRF viewset для модели в приложениях applications/api/v1/viewsets.py.
from rest_framework import viewsets
from applications.games.models import Game
from applications.api.v1.serializers import GameSerializer
class GameViewSet(viewsets.ModelViewSet):
queryset = Game.objects.all()
serializer_class = GameSerializer
В applications/api/v1/routes.py зарегистрируйте роуты, используя DRF's router registration features.
from rest_framework import routers
from applications.api.v1.viewsets import GameViewSet
api_router = routers.SimpleRouter()
api_router.register('games', GameViewSet)
Обозначьте ссылки для зарегистрированного DRF роута внутри mysite/urls.py:
from django.contrib import admin
from django.conf.urls import include, url
from applications.api.v1.routes import api_router
urlpatterns = [
url(r'^admin/', admin.site.urls),
# API:V1
url(r'^api/v1/', include(api_router.urls)),
]
6. Запустите сервер Django, используя dev settings.
manage.py runserver --DJANGO_SETTINGS_MODULE=mysite.settings.dev
Впуская DJANGO_SETTINGS_MODULE в runserver, мы «говорим» - работать используя специфические параметры.
Если все работает, у вас появится возможность открыть localhost:8000/api/v1/games и увидеть ответ от DRF. Если все работает – самое время заняться построением приложения Angular. Если нет – направьте автору проблему. Если вы застряли на этом этапе – оставьте комментарий автору под оригиналом публикации.
7. Инициализируйте npm-пакет и установите front-end JS зависимости.
Приложение Angular не будет работать так, как мы хотим, если правильные зависимости не будут установленны. Самое время установить базовые пакеты, которые понадобятся.
Инициализируйте npm-пакет. Прямо из frontend/ запустите
npm init --yes
By passing the --yes
flag into init
, you're telling NPM to generate a package.json
using NPM defaults. Otherwise, if you don't pass that in, you'll have to answer questions... Boring.
Установите dev dependencies.
npm install --save-dev eslint eslint-loader
Установите общие зависимости.
Файл package.json file во frontend/ должен выглядеть приблизительно следующим образом:
{
"name": "my-app",
"version": "0.0.1",
"description": "This is my first angular app.",
"main": "app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"eslint": "^3.1.1",
"eslint-loader": "^1.4.1"
},
"dependencies": {
"angular": "^1.5.8",
"angular-resource": "^1.5.8",
"angular-route": "^1.5.8",
"eslint": "^3.1.1",
"eslint-loader": "^1.4.1",
"json-loader": "^0.5.4",
"lodash": "^4.13.1",
"mustache-loader": "^0.3.1"
}
}
Здесь то, что мы только что установили:
eslint – отличный линтер, благодаря которому код JavaScript будет в порядке (последователен).
eslint-loader – для запуска eslint через Webpack. Чуть позже я объясню концепцию «загрузчиков».
angular - MVC фреймворк. Если вы не знали об этом, стоит подумать о том, чтобы закрыть эту страничку прямо сейчас.
angular-resource - (Angular) HTTP библиотека выбора. Это абстракция $http.
json-loader - загрузчик (снова, используемый Webpack) для распаковки JSON из .json файлов с помощью require() во время работы нашего приложения.
mustache-loader – загрузчик, который мы будем использовать, чтобы парсить наши mustache шаблоны. Mustache шаблоны – это веселье.
Я могу спокойно предположить, что вы не знаете, как все эти пакеты заиграют вместе.
Не переживайте, братишки.
8. Создайте entry-point в Angular, объявите начальные зависимости, объявите первоначальные глобальные переменные.
В frontend/app/app.js добавьте следующее:
/* Libs */
require("angular/angular");
require("angular-route/angular-route");
require("angular-resource/angular-resource");
/* Globals */
_ = require("lodash");
_urlPrefixes = {
API: "api/v1/",
TEMPLATES: "static/app/"
};
/* Components */
/* App Dependencies */
angular.module("myApp", [
"ngResource",
"ngRoute",
]);
/* Config Vars */
// @TODO in Step 13.
/* App Config */
angular.module("myApp").config(routesConfig);
app.js это то, где Webpack будет искать модули, чтобы бандлить их вместе. Лично я ценю такую организацию и методику вызовов, но такой порядок не обязателен. Существует 6 секций:
- Libs – главные библиотеки, используемые на протяжении работы Angular приложения;
- Globals – зарезервированные глобальные переменные, которые мы можем использовать во время работы приложения;
- Components (Компоненты) – особенные модули проекта;
- App Dependencies (Зависимости приложения) – объявление входной точки приложения и его зависимостей;
- Config Vars – переменные, где хранятся настройки, такие как route config;
- App Config - вводит configs (настройки) в приложение, используя сохраненные из предыдущей секции.
Для того, чтобы globals работали, вам следует указать ESLint на то, какие из переменных - глобальные.
В config/eslint.json добавляем следующее:
{
"env": {
"node": true
},
"extends": "eslint:recommended",
"rules": {
"indent": [
"error",
2
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"double"
],
"semi": [
"error",
"always"
],
"no-console": 0
},
"globals": {
"_": true,
"_urlPrefixes": true,
"angular": true,
"inject": true,
"window": true
},
"colors": true
}
Ниже несколько переменных, о которых мы предупредили ESLint:
- _ представить lodash.
- _urlPrefixes – объект, который мы будем использовать в приложении для гиперссылок. Я расскажу об этом позже.
- angular, чтобы представить AngularJS object driving our entire application.
- inject, который будет использоваться для ввода зависимостей Angular.
- window, которая просто представляет объекты окон в JavaScript, является представителем DOM.
9. Настройка Webpack.
Теперь, когда мы выложили большинство наших зависимостей приложения, мы можем построить config file для Webpack. Webpack будет консолидировать все зависимости, а также модули для приложений, которые мы создаем в один файл. В bundle.
В frontend/webpack.config.js добавляем следующее.
module.exports = {
entry: "./app/app.js",
output: {
path: "./dist/js/",
filename: "bundle.js",
sourceMapFilename: "bundle.js.map",
},
watch: true,
// eslint config
eslint: {
configFile: './config/eslint.json'
},
module: {
preLoaders: [{
test: /\.js$/,
exclude: /node_modules/,
loader: "eslint-loader"
}],
loaders: [
{ test: /\.css$/, loader: "style!css" },
{ test: /\.html$/, loader: "mustache-loader" },
{ test: /\.json$/, loader: "json-loader" }]
},
resolve: {
extensions: ['', '.js']
}
};
Видео курсы по схожей тематике:
Для того, чтобы Webpack бандлил все наши статические зависимости, нам нужно указать ему, где их брать, какие зависимости обрабатывать и как управлять ими до банлинга.
Давайте посмотрим на то, что указывает Webpack с помощью webpack.config.js:
Entry - это путь к тому, что Webpack'у нужно для старта бандлинга. Это можеть быть полный путь или путь, относительный тому, где webpack.config.js располагается. В данном случае мы говорим о последнем варианте.
output - это объект, содержащий в себе path, который является директорией, в которую связанные зависимости будут помещаться; filename - это название бандла; и, в данном случае, мы решили использовать sourceMapFilename, чтобы обозначить, что наша() source map будет вызван(а).
watch указывает Webpack следить за изменениями в файле, пока он выполняется. Если это не настроено как true, Webpack прогонит процесс бандлинга единожды и остановится.
eslint содержит в себе специфические ESLint настройки, используемые eslint-loader.
module указывает Webpack'у, что делать с модулями, с которыми он работает.
module.preLoaders «говорит», что делать перед бандлингом. В данном случае мы хотим запустить модули (исключив модули установленные npm) через eslint.
module.loaders - это то, где указана последовательность загрузчика. В нашем случае мы просто настраиваем test и loader, где test указывает Webpack’у, какие модули запускать в загрузчике (по соответствию с паттерном regex), и loader говорит Webpack’y, какой загрузчик использовать в модулях, которые соответствуют regex паттерну в test. Каждый загрузчик указан в строке и разделен восклицательным знаком. Ex: loader!another_loader!yet_another_loader
module.preLoaders указывает, какие preLoaders'у запускать модули. Используемые настройки такие же точно, какие мы использовали в module.loaders.
Но, Грег, какая разница между preLoaders и loaders? Я рад, что ты спросил, мой дорогой друг!!
A loader указывает Webpack'у, как бандлить требуемые файлы. Loader смотрит на модуль и говорт: «Эй, так как вы упаковываете это в один файл как строку – это то, как оно должно быть преобразованно для bundle'а».
A preLoader обрабатывает код перед loaders, например, чтобы слинтить JavaScript модули.
A postLoader является плагином Webpack'а, который обрабатывает код после бандинга. Мы не специфицировали ни один postLoader ради простоты.
10. Укажите Django загрузить приложение.
Прямо сейчас все, что нужно сделать – указать Webpack’у что создавать, как создавать и что должно быть создано. (На данном этапе я бы очень удивился, если вы попробуете запустить его и он заработает без ошибок. Если так и есть, я чертов мужик.)
Так как Django использует свой собственный URL процессор в нашем приложении, мы можем быть рады тому, как любезно Django управляет всем тем, что введено в строку браузера пользователя. Как бы то ни было, мы бандлим одностраничное приложение, используя абсолютно другой фреймворк, и хотим, чтобы у приложения был полный контроль над тем, что пользователь вводит. Все, что нам нужно – обслуживать одну страничку, в которой работает SPA. Следовательно...
В backend/mysite/mysite/urls.py добавляем в список urlpatterns следующее:
# Web App Entry
url(r'^$', TemplateView.as_view(template_name="app/index.html"), name='index'),
Это значит, что когда пользователь открывает mysite.com/, env('FRONTEND_ROOT') + app/index.html будет находить STATICFILES_FINDERS в порядке рендера HTML шаблона.
11. Создайте шаблон базы приложения Angular.
frontend/app/components/app/index.html шаблон должен выглядеть как обычный шаблон Django.
В frontend/app/index.html добавляем следующее:
{% load staticfiles %}
<html ng-app="myApp">
<head>
<title>My Sitetitle>
<script src="{% static 'dist/js/bundle.js' %}">script>
head>
<body>
body>
html>
В таком случае вам удастся запустить Webpack. Если вы запустите Django сервер и откроете localhost:8000,вы увидите пустую страничку. Если нет – дайте знать автору.
12. Напишите home component.
Давайте напишем наш первый компонент. Он отобразит текст на страничке, пока пользователь открывает localhost:8000.
Создайте директорию для компонента и базовые файлы. В frontend/app/components/:
mkdir home && touch home/home-controller.js home/home.js home/home.html
В frontend/app/components/home/home.html добавляем следующее:
<div ng-controller="HomeController as ctrl">
<div>
<h1>Home!h1>
div>
div>
Теперь добавим следующее в frontend/app/components/home/home-controller.js:
function HomeController() {
var that = this;
that.foo = "Foo!";
console.log(that); // should print out the controller object
}
angular.module("Home")
.controller("HomeController", [
HomeController
])
Определение модуля Angular должно быть объявлено в home.js:
angular.module("Home", []);
require("./home-controller");
Теперь мы можем сослаться на "Home" в области зависимости определения модуля. Давайте сделаем это!
В app/app.js добавьте следующее:
/* Components */
require("./components/home/home");
/* App Dependencies */
angular.module("myApp", [
"Home", // this is our component
"ngResource",
"ngRoute"
]);
13. Пропишите пути Angular'а, ведущие к home component и страничке 404.
Нам нужно настроить первый путь. Когда пользователь попадает на localhost:8000, Angular должен взять контроль над загрузкой отрендеренного шаблона. Чтобы сделать это, нам потребуется использовать angular-router.
В frontend/app/routes.js пишем следующее:
function routesConfig($routeProvider) {
$routeProvider
.when("/", {
templateUrl: _urlPrefixes.TEMPLATES + "components/home/home.html",
label: "Home"
})
.otherwise({
templateUrl: _urlPrefixes.TEMPLATES + "404.html"
});
}
routesConfig.$inject = ["$routeProvider"];
module.exports = routesConfig;
Если мы не добавим _urlPrefixes.TEMPLATES, angular-router предположит, что components/home/home.html является действительной ссылкой, которую узнает сервер. Так как STATIC_URL в настройках предполагает неправильную работу localhost:8000/components/home/home.html.
Также, если вы еще не заметили, вы увидите otherwise({...}) в коде роутов. Это то, как будут реализованы страницы 404.
В frontend/app/404.html добавляем следующее:
<h1>NOT FOUNDh1>
И в завершении добавляем frontend/app/app.js:
/* Config Vars */
var routesConfig = require("./routes");
14. Добавьте директивы angular-router к шаблону точки входа приложения.
А теперь нам нужно указать Angular, где будет происходить переключение отображаемого, когда пользователь пользуется навигацией. Чтобы сделать это, мы используем всю силу angular-router.
В тэг
в frontend/app/index.html добавляем:
<base href="/">
Теперь в тэг
добавляем:
<div ng-view>div>
Ваш index.html теперь должен выглядеть так:
{% load staticfiles %}
<html ng-app="myApp">
<head>
<title>My Sitetitle>
<script src="{% static 'dist/js/bundle.js' %}" >script>
<base href="/">
head>
<body>
<div>
<div ng-view>div>
div>
body>
html>
Запустите Webpack. Откройте localhost:8000. Вы должны увидеть, что произошло в home/home.html. (Если ничего, отправьте эти данные автору J ).
15. Проверьте REST API в приложении Angular.
Если все сделано, у вас появится возможность написать angular службы для Django API. Давайте создадим небольшой компонент, чтобы увидеть, можем ли мы это сделать. Этот компонент должен перечислять игры. Я предполагаю, что вы уже заполнили базы данных, следовательно запрос HTTP к localhost:8000/api/v1/games вернет список игр.
Создайте скаффолд компонент в frontend/app/components/:
mkdir -p game/list/ && touch game/list/game-list-controller.js game/list/game-list-controller_test.js game/game-service.js game/game.js game/game.html
Этот компонент будет перечислять игры.
Этот компонент должен перечислять игры. Я предполагаю, что вы уже заполнили базы данных, следовательно запрос HTTP к localhost:8000/api/v1/games вернет список игр.
В game/game-service.js:
function GameService($resource) {
/**
* @name GameService
*
* @description
* A service providing game data.
*/
var that = this;
/**
* A resource for retrieving game data.
*/
that.GameResource = $resource(_urlPrefixes.API + "games/:game_id/");
/**
* A convenience method for retrieving Game objects.
* Retrieval is done via a GET request to the ../games/ endpoint.
* @param {object} params - the query string object used for a GET request to ../games/ endpoint
* @returns {object} $promise - a promise containing game-related data
*/
that.getGames = function(params) {
return that.GameResource.query(params).$promise;
};
}
angular.module("Game")
.service("GameService", ["$resource", GameService]);
Обратите внимание на ссылку $resource, которую мы используем для того, чтобы настроить механизмы HTTP в нашей службе.
Бесплатные вебинары по схожей тематике:
В game/list/game-list-controller.js:
function GameListController(GameService) {
var that = this;
/* Stored game objects. */
that.games = [];
/**
* Initialize the game list controller.
*/
that.init = function() {
return GameService.getGames().then(function(games) {
that.games = games;
});
};
}
angular.module("Game")
.controller("GameListController", [
"GameService",
GameListController
]);
В game/game.html:
<div ng-controller="GameListController as ctrl" ng-init="ctrl.init()">
<div>
<h1>Gamesh1>
<ul>
<li ng-repeat="game in ctrl.games">{{ game.title }}li>
ul>
div>
div>
В game/game.js:
angular.module("Game", []);
require("./list/game-list-controller");
require("./game-service");
Затем обратимся к компоненту в app.js:
/* Components */
require("./components/game/game");
/* App Dependencies */
angular.module("myApp", [
"Home",
"Game",
"ngResource",
"ngRoute"
]);
В конце концов, мы собираемся настроить роуты для списка игр, поэтому в frontend/app/routes.js добавьте следующее в объект $routeProvider:
.when("/game", {
templateUrl: _urlPrefixes.TEMPLATES + "components/game/list/game-list.html",
label: "Games"
})
Запустите Webpack снова. Все должно верно скомпилироваться. Если нет – дайте знать автору.
Откройте localhost:8000/#/games. Вы увидите список игр.
Сделано!
Это все.
Сомнения/Мысли
Но есть некоторые сомнения:
Глобальные переменные могут конкретно подставить вас, если вы не знаете, как с ними работать. Их локальное поведение не гарантирует того же на продакшене. Насколько я помню, их можно заставить работать, если правильно описан метод. Ваше приложение на Angular тесно связанно с Django. Поэтому ваше приложение не будет просто слиянием back- и фронтенда. Если ваш Django-RIP давно устарел, значит поменялись и маршруты, следовательно сконфигурируете ваш бэкенд согласно тому, как должны вести себя статические файлы. Так же вам будет необходимо заменить index.html с точкой входа Angular. Маленькие проекты не дадут вам особо попотеть, а вот большие явно заставят понервничать. Совет: единственное место, где должны сопрягаться приложение на Angular и Django сервер - это одна точка входа.
Деплоймент должен быть выполнен так же, как любой обычный деплоймент приложения.
Это все. Если у вас есть какие-либо вопросы и вы испытываете трудности, пожалуйста, оставьте их в комментариях в исходной статье!
Чит!
Автор пообещал выложить на гитхабе репозиторий со всем кодом.
Оригинальная статья на английском языке.
Статьи по схожей тематике