├── .coveragerc
├── .github
└── workflows
│ └── main.yml
├── .gitignore
├── .pre-commit-config.yaml
├── LICENSE
├── MANIFEST
├── README.md
├── assets
├── Common
│ ├── djangologo.png
│ ├── index.html-tpl
│ ├── urls.py-tpl
│ └── views.py-tpl
├── reactjs
│ ├── App.css-tpl
│ ├── App.js-tpl
│ ├── babel.config.json-tpl
│ ├── index.js-tpl
│ ├── package.json-tpl
│ ├── reactlogo.png
│ └── webpack.config.js-tpl
└── reactts
│ ├── App.css-tpl
│ ├── App.tsx-tpl
│ ├── babel.config.json-tpl
│ ├── index.tsx-tpl
│ ├── module.d.ts-tpl
│ ├── package.json-tpl
│ ├── reactlogo.png
│ ├── tsconfig.json-tpl
│ └── webpack.config.js-tpl
├── django_webpack_dev_server
├── __init__.py
├── apps.py
├── management
│ ├── __init__.py
│ ├── commands
│ │ ├── __init__.py
│ │ └── generate.py
│ ├── constants.py
│ ├── generator.py
│ └── template_constants
│ │ ├── __init__.py
│ │ ├── development
│ │ ├── __init__.py
│ │ ├── development_constants.py
│ │ ├── reactjs_constants.py
│ │ └── reactts_constants.py
│ │ └── production
│ │ ├── __init__.py
│ │ ├── production_constants.py
│ │ ├── reactjs_constants.py
│ │ └── reactts_constants.py
└── migrations
│ └── __init__.py
├── pytest.ini
├── requirements.txt
├── requirements
├── requirements-dev.txt
├── requirements-lint.txt
└── requirements-testing.txt
├── runtests.py
├── setup.cfg
├── setup.py
├── tests
├── __init__.py
├── management
│ ├── __init__.py
│ ├── commands
│ │ ├── __init__.py
│ │ └── test_generate.py
│ └── test_generator.py
└── test_settings.py
└── tox.ini
/.coveragerc:
--------------------------------------------------------------------------------
1 | [run]
2 | source = django_webpack_dev_server
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | test:
10 | runs-on: ${{ matrix.os }}
11 |
12 | strategy:
13 | matrix:
14 | python: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13', '3.14.0-alpha.2']
15 | os: [windows-latest, ubuntu-20.04, macos-latest]
16 |
17 | steps:
18 | - uses: actions/checkout@v2
19 |
20 | - name: Setup Python
21 | uses: actions/setup-python@v2
22 | with:
23 | python-version: ${{ matrix.python }}
24 |
25 | - name: Install the Python Dependencies
26 | run: pip install tox
27 |
28 | - name: Run Tox for ${{ matrix.python }}
29 | run: tox -e py
30 |
31 | - name: Upload coverage to Codecov
32 | uses: codecov/codecov-action@v2
33 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # ignore byte compiled files created by Python
2 | __pycache__/
3 |
4 | # ignore vscode folder
5 | .vscode/
6 |
7 | # ignore the virtual environment
8 | env/
9 |
10 | # ignore the build folder produced after running setup.py
11 | build/
12 |
13 | # ignore the dist folder produced after running setup.py
14 | dist/
15 |
16 | # ignore the django mysite project used to test this package
17 | mysite/
18 |
19 | # ignore the frontend django app
20 | frontend/
21 |
22 | django_webpack_dev_server.egg-info/
23 |
24 | # ignore the html files of the coverage
25 | htmlcov/
26 |
27 | # ignore the coverage.xml file
28 | coverage.xml
29 |
30 | # ignore the todo text file
31 | Todo.txt
32 |
33 | # ignore the sqlite file
34 | db.sqlite3
35 |
36 | # ignore the .pytest_cache folder
37 | .pytest_cache/
38 |
39 | # ignore the .tox folder
40 | .tox/
41 |
42 | # ignore the environment variables file
43 | .env
44 |
45 | # ignore the .coverage file
46 | .coverage
47 |
48 | # ignore the manage.py file
49 | manage.py
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos:
2 | - repo: https://github.com/pre-commit/pre-commit-hooks
3 | rev: v5.0.0
4 | hooks:
5 | - id: check-docstring-first
6 | - id: trailing-whitespace
7 | - id: check-yaml
8 | - repo: https://github.com/psf/black
9 | rev: 25.1.0
10 | hooks:
11 | - id: black
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Jiten Sidhpura
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/MANIFEST:
--------------------------------------------------------------------------------
1 | # file GENERATED by distutils, do NOT edit
2 | README.rst
3 | setup.cfg
4 | setup.py
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # django-webpack-dev-server
2 |
3 | [](https://badge.fury.io/py/django-webpack-dev-server)
4 | [](https://codecov.io/gh/Jitensid/django-webpack-dev-server)
5 | [](https://requires.io/github/Jitensid/django-webpack-dev-server/requirements/?branch=main)
6 | [](https://github.com/Jitensid/django-webpack-dev-server/actions/workflows/main.yml)
7 | [](https://results.pre-commit.ci/latest/github/Jitensid/django-webpack-dev-server/main)
8 | [](https://github.com/psf/black)
9 | [](https://github.com/Jitensid/django-webpack-dev-server/blob/main/LICENSE)
10 |
11 | Django Webpack Dev Server is a command line Django reusable app to setup configuration files for React. It uses webpack and webpack_dev_server to bundle your frontend code.
12 |
13 | [](https://www.youtube.com/watch?v=6c-lPkKzI_E)
14 |
15 |
16 | ## Installation
17 |
18 | Install using pip
19 |
20 | `pip install django-webpack-dev-server`
21 |
22 | ## Package Supports
23 |
24 | 1. React (Javascript)
25 | 2. React (Typescript)
26 |
27 | ## Quick start
28 |
29 | 1. Add 'django_webpack_dev_server' to your INSTALLED_APPS in
30 | settings.py like this:
31 |
32 | ```python
33 | INSTALLED_APPS = [
34 | ...
35 | 'django_webpack_dev_server',
36 | ]
37 | ```
38 |
39 | 2. Default django app name is frontend and template is javascript. You can provide your name and template by running
`python manage.py generate react --app_name your_app_name --template (javascript/typescript)`
40 |
41 | 3. Run `python manage.py generate react` to create a django app with the default app_name and template.
42 |
43 | 4. Add the new django app to your INSTALLED_APPS setting like in step 1.
44 |
45 | 5. Add the path for the new django app in the urlpatterns of the project's urls.py like this:
46 |
47 | ```python
48 | from django.urls import path, include
49 |
50 | path("", include("your_app_name.urls")),
51 | ```
52 |
53 | 6. Run `python manage.py runserver` to start the django's development
54 | server.
55 |
56 | 7. `cd` into the newly created django app and run `npm start` and go to
57 | ().
58 |
59 | 8. Run `npm run build` to create a production build of your frontend code.
60 |
61 | ## Important Links
62 |
63 | 1. [Demo Video of the Package](https://www.youtube.com/watch?v=6c-lPkKzI_E)
64 |
65 | 2. [Check out Tech with Tim's Youtube Playlist for a detailed explanation in building Django and React Full Stack App.](https://www.youtube.com/watch?v=JD-age0BPVo&list=PLzMcBGfZo4-kCLWnGmK0jUBmGLaJxvi4j)
66 |
67 | 3. [Webpack Documentation](https://webpack.js.org/concepts/)
68 |
69 | ## Contributions
70 |
71 | If you find an issue or have a new feature then feel free to make a Pull Request. Your Contributions are always welcomed.
72 |
73 | ### Running the Package in Development Mode
74 |
75 | 1. You can run the package in development mode by setting the "SOFTWARE_ENVIRONMENT_MODE" environment variable equal to "development".
76 |
77 | 2. Now you can load assets from your local system and the new changes can be tested.
78 |
79 | ## License
80 |
81 | This project is provided under the [MIT License](https://github.com/Jitensid/django-webpack-dev-server/blob/main/LICENSE).
82 |
--------------------------------------------------------------------------------
/assets/Common/djangologo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jitensid/django-webpack-dev-server/c47e5208dc87ed4a9550674c7374764e97a9fb86/assets/Common/djangologo.png
--------------------------------------------------------------------------------
/assets/Common/index.html-tpl:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Django and React Web Application
8 | {% load static %}
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/assets/Common/urls.py-tpl:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 | import $app_name.views as views
3 |
4 | # All urls will point to same HTML template because it is SPA application
5 | urlpatterns = [
6 | path('', views.index, name='index'),
7 | ]
8 |
--------------------------------------------------------------------------------
/assets/Common/views.py-tpl:
--------------------------------------------------------------------------------
1 | from django.shortcuts import render
2 |
3 | # Create your views here.
4 | def index(request):
5 | return render(request, "$app_name/index.html")
6 |
--------------------------------------------------------------------------------
/assets/reactjs/App.css-tpl:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | background-color: black;
4 | min-height: 100vh;
5 | }
6 |
7 | .App-logo {
8 | height: 40vmin;
9 | animation: App-logo-spin infinite 10s linear;
10 | }
11 |
12 | .django-logo {
13 | margin-top: 20vh;
14 | height: 40vmin;
15 | }
16 |
17 | @keyframes App-logo-spin {
18 | from {
19 | transform: rotate(0deg);
20 | }
21 | to {
22 | transform: rotate(360deg);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/assets/reactjs/App.js-tpl:
--------------------------------------------------------------------------------
1 | import reactlogo from './reactlogo.png';
2 | import djangologo from './djangologo.png';
3 | import './App.css';
4 |
5 | function App() {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 |
13 | );
14 | }
15 |
16 | export default App;
17 |
--------------------------------------------------------------------------------
/assets/reactjs/babel.config.json-tpl:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["@babel/preset-env"],
4 | ["@babel/preset-react",{
5 | "runtime": "automatic"
6 | }]
7 | ],
8 | "plugins": ["@babel/plugin-proposal-class-properties"]
9 | }
10 |
--------------------------------------------------------------------------------
/assets/reactjs/index.js-tpl:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | const appDiv = document.getElementById('app');
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | appDiv
12 | );
13 |
14 | if (module.hot) {
15 | module.hot.accept();
16 | }
17 |
--------------------------------------------------------------------------------
/assets/reactjs/package.json-tpl:
--------------------------------------------------------------------------------
1 | {
2 | "name": "$app_name",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "webpack-dev-server --mode development",
8 | "build": "webpack --mode production"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "@babel/core": "^7.16.0",
14 | "@babel/preset-env": "^7.16.4",
15 | "@babel/preset-react": "^7.16.0",
16 | "@babel/plugin-proposal-class-properties": "^7.16.0",
17 | "babel-loader": "^8.2.3",
18 | "css-loader": "^6.5.1",
19 | "file-loader": "^6.2.0",
20 | "react": "^17.0.2",
21 | "react-dom": "^17.0.2",
22 | "sass": "^1.49.0",
23 | "sass-loader": "^12.3.0",
24 | "style-loader": "^3.3.1",
25 | "webpack": "^5.64.4",
26 | "webpack-cli": "^4.9.1",
27 | "webpack-dev-server": "^4.6.0"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/assets/reactjs/reactlogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jitensid/django-webpack-dev-server/c47e5208dc87ed4a9550674c7374764e97a9fb86/assets/reactjs/reactlogo.png
--------------------------------------------------------------------------------
/assets/reactjs/webpack.config.js-tpl:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 |
4 | module.exports = {
5 | entry: ['./src'],
6 | output: {
7 | path: path.resolve(__dirname, './static/$app_name'),
8 | filename: '[name].js',
9 | publicPath: '/static/$app_name/',
10 | },
11 | resolve: {
12 | alias: {
13 | 'react-dom': 'react-dom/profiling',
14 | 'scheduler/tracing': 'scheduler/tracing-profiling',
15 | },
16 | extensions: ['.jsx', '.js'],
17 | },
18 | module: {
19 | rules: [
20 | {
21 | test: /\.(js|jsx)$$/,
22 | exclude: /node_modules/,
23 | use: [
24 | {
25 | loader: 'babel-loader',
26 | },
27 | ],
28 | },
29 | {
30 | test: /\.css$$/,
31 | exclude: /node_modules/,
32 | use: [
33 | {
34 | loader: 'style-loader',
35 | },
36 | {
37 | loader: 'css-loader',
38 | },
39 | ],
40 | },
41 | {
42 | test: /\.(scss|sass)$$/,
43 | exclude: /node_modules/,
44 | use: [
45 | {
46 | loader: 'style-loader',
47 | },
48 | {
49 | loader: 'css-loader',
50 | },
51 | {
52 | loader: 'sass-loader',
53 | },
54 | ],
55 | },
56 | {
57 | test: /\.(jpe?g|png|gif|svg|mp4|mp3)$$/,
58 | use: [
59 | {
60 | loader: 'file-loader',
61 | options: {
62 | outputPath: '',
63 | },
64 | },
65 | ],
66 | },
67 | ],
68 | },
69 | optimization: {
70 | minimize: true,
71 | },
72 | devServer: {
73 | proxy: {
74 | '!/static/$app_name/**': {
75 | target: 'http://localhost:8000', // points to django dev server
76 | changeOrigin: true,
77 | },
78 | },
79 | open: true,
80 | },
81 | };
82 |
--------------------------------------------------------------------------------
/assets/reactts/App.css-tpl:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | background-color: black;
4 | min-height: 100vh;
5 | }
6 |
7 | .App-logo {
8 | height: 40vmin;
9 | animation: App-logo-spin infinite 10s linear;
10 | }
11 |
12 | .django-logo {
13 | margin-top: 20vh;
14 | height: 40vmin;
15 | }
16 |
17 | @keyframes App-logo-spin {
18 | from {
19 | transform: rotate(0deg);
20 | }
21 | to {
22 | transform: rotate(360deg);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/assets/reactts/App.tsx-tpl:
--------------------------------------------------------------------------------
1 | import './App.css';
2 | import djangologo from './djangologo.png';
3 | import reactlogo from './reactlogo.png';
4 |
5 | function App() {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 |
13 | );
14 | }
15 |
16 | export default App;
17 |
--------------------------------------------------------------------------------
/assets/reactts/babel.config.json-tpl:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-env",
4 | ["@babel/preset-react",{
5 | "runtime": "automatic"
6 | }],
7 | "@babel/preset-typescript"
8 | ],
9 | "plugins": ["@babel/plugin-proposal-class-properties"]
10 | }
11 |
--------------------------------------------------------------------------------
/assets/reactts/index.tsx-tpl:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | const appDiv = document.getElementById('app');
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | appDiv
12 | );
13 |
14 | if (module.hot) {
15 | module.hot.accept();
16 | }
--------------------------------------------------------------------------------
/assets/reactts/module.d.ts-tpl:
--------------------------------------------------------------------------------
1 | declare module '*.jpg' {
2 | const value: string;
3 | export default value;
4 | }
5 |
6 | declare module '*.jpeg' {
7 | const value: string;
8 | export default value;
9 | }
10 |
11 | declare module '*.png' {
12 | const value: string;
13 | export default value;
14 | }
15 |
16 | declare module '*.gif' {
17 | const value: string;
18 | export default value;
19 | }
20 |
21 | declare module '*.svg' {
22 | const value: string;
23 | export default value;
24 | }
25 |
26 | declare module '*.mp4' {
27 | const value: string;
28 | export default value;
29 | }
30 |
31 | declare module '*.mp3' {
32 | const value: string;
33 | export default value;
34 | }
--------------------------------------------------------------------------------
/assets/reactts/package.json-tpl:
--------------------------------------------------------------------------------
1 | {
2 | "name": "$app_name",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "webpack-dev-server --mode development",
8 | "build": "webpack --mode production"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "@babel/core": "^7.16.0",
14 | "@babel/plugin-proposal-class-properties": "^7.16.0",
15 | "@babel/preset-env": "^7.16.4",
16 | "@babel/preset-react": "^7.16.0",
17 | "@babel/preset-typescript": "^7.16.0",
18 | "@types/node": "^16.11.21",
19 | "@types/react": "^17.0.38",
20 | "@types/react-dom": "^17.0.11",
21 | "@types/webpack-env": "^1.16.3",
22 | "babel-loader": "^8.2.3",
23 | "css-loader": "^6.5.1",
24 | "file-loader": "^6.2.0",
25 | "fork-ts-checker-webpack-plugin": "^6.5.0",
26 | "react": "^17.0.2",
27 | "react-dom": "^17.0.2",
28 | "sass": "^1.49.0",
29 | "sass-loader": "^12.3.0",
30 | "style-loader": "^3.3.1",
31 | "ts-loader": "^9.2.6",
32 | "typescript": "^4.5.5",
33 | "webpack": "^5.64.4",
34 | "webpack-cli": "^4.9.1",
35 | "webpack-dev-server": "^4.6.0"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/assets/reactts/reactlogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jitensid/django-webpack-dev-server/c47e5208dc87ed4a9550674c7374764e97a9fb86/assets/reactts/reactlogo.png
--------------------------------------------------------------------------------
/assets/reactts/tsconfig.json-tpl:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": false ,
21 | "jsx": "react-jsx",
22 | },
23 | "include": [
24 | "src"
25 | ]
26 | }
--------------------------------------------------------------------------------
/assets/reactts/webpack.config.js-tpl:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 | const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
4 |
5 | module.exports = {
6 | entry: ['./src/index.tsx'],
7 | output: {
8 | path: path.resolve(__dirname, './static/$app_name'),
9 | filename: '[name].js',
10 | publicPath: '/static/$app_name/',
11 | },
12 | resolve: {
13 | alias: {
14 | 'react-dom': 'react-dom/profiling',
15 | 'scheduler/tracing': 'scheduler/tracing-profiling',
16 | },
17 | extensions: ['.tsx', '.ts', '.jsx', '.js'],
18 | },
19 | module: {
20 | rules: [
21 | {
22 | test: /\.(js|jsx)$$/,
23 | exclude: /node_modules/,
24 | use: [
25 | {
26 | loader: 'babel-loader',
27 | },
28 | ],
29 | },
30 | {
31 | test: /\.(ts|tsx)$$/,
32 | exclude: /node_modules/,
33 | use: [
34 | {
35 | loader: 'ts-loader',
36 | options: {
37 | transpileOnly: true,
38 | },
39 | },
40 | ],
41 | },
42 | {
43 | test: /\.css$$/,
44 | exclude: /node_modules/,
45 | use: [
46 | {
47 | loader: 'style-loader',
48 | },
49 | {
50 | loader: 'css-loader',
51 | },
52 | ],
53 | },
54 | {
55 | test: /\.(scss|sass)$$/,
56 | exclude: /node_modules/,
57 | use: [
58 | {
59 | loader: 'style-loader',
60 | },
61 | {
62 | loader: 'css-loader',
63 | },
64 | {
65 | loader: 'sass-loader',
66 | },
67 | ],
68 | },
69 | {
70 | test: /\.(jpe?g|png|gif|svg|mp4|mp3)$$/,
71 | use: [
72 | {
73 | loader: 'file-loader',
74 | options: {
75 | outputPath: '',
76 | },
77 | },
78 | ],
79 | },
80 | ],
81 | },
82 | optimization: {
83 | minimize: true,
84 | },
85 | devServer: {
86 | proxy: {
87 | '!/static/$app_name/**': {
88 | target: 'http://localhost:8000', // points to django dev server
89 | changeOrigin: true,
90 | },
91 | },
92 | open: true,
93 | },
94 | plugins: [
95 | new ForkTsCheckerWebpackPlugin(),
96 | ],
97 | };
98 |
--------------------------------------------------------------------------------
/django_webpack_dev_server/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jitensid/django-webpack-dev-server/c47e5208dc87ed4a9550674c7374764e97a9fb86/django_webpack_dev_server/__init__.py
--------------------------------------------------------------------------------
/django_webpack_dev_server/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class DjangoWebpackDevServerConfig(AppConfig):
5 | default_auto_field = "django.db.models.BigAutoField"
6 |
7 | name = "django_webpack_dev_server"
8 |
--------------------------------------------------------------------------------
/django_webpack_dev_server/management/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jitensid/django-webpack-dev-server/c47e5208dc87ed4a9550674c7374764e97a9fb86/django_webpack_dev_server/management/__init__.py
--------------------------------------------------------------------------------
/django_webpack_dev_server/management/commands/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jitensid/django-webpack-dev-server/c47e5208dc87ed4a9550674c7374764e97a9fb86/django_webpack_dev_server/management/commands/__init__.py
--------------------------------------------------------------------------------
/django_webpack_dev_server/management/commands/generate.py:
--------------------------------------------------------------------------------
1 | # Contains the code for the generate command
2 |
3 | from django.core.management.base import BaseCommand
4 | from django_webpack_dev_server.management.generator import Generator
5 |
6 |
7 | class Command(BaseCommand):
8 | help = "Creates a django app which has the frontend configuration"
9 |
10 | def add_arguments(self, parser):
11 | # adding subparser for react
12 | subparsers = parser.add_subparsers(
13 | help="Command to create a django app which has the frontend configuration",
14 | dest="frontend_library_or_framework",
15 | )
16 |
17 | subparsers.required = True
18 |
19 | # add a parser for react because it can be javascript or typescript
20 | react_parser = subparsers.add_parser(
21 | "react",
22 | help="Command to have django app with React for Frontend Development",
23 | )
24 |
25 | language_choices = ["javascript", "typescript"]
26 | # add the optional app_name argument with the default value as frontend
27 | react_parser.add_argument(
28 | "--app_name", help="Frontend Django App Name", default="frontend"
29 | )
30 |
31 | # add the optional template argument with the default value as javascript
32 | # template argument can be either javascript or typescript
33 | react_parser.add_argument(
34 | "--template",
35 | help="Language for the React Project",
36 | default="javascript",
37 | choices=language_choices,
38 | )
39 |
40 | def show_success_message(self, message):
41 | """
42 | :param message: message to be shown in stdout with success style
43 | """
44 | self.stdout.write(self.style.SUCCESS(message))
45 |
46 | def handle(self, *args, **options):
47 | # get the django app_name and the frontend_library_or_framework
48 | # specified by the user
49 | app_name = options["app_name"]
50 | frontend_library_or_framework = options["frontend_library_or_framework"]
51 |
52 | # if the frontend_library_or_framework is react
53 | if frontend_library_or_framework == "react":
54 | # fetch the template argument
55 | template = options["template"]
56 |
57 | # append it to the frontend_library_or_framework
58 | # react and javascript -> react_javascript for React Javascript
59 | # react and typescript -> react_typescript for React Typescript
60 | frontend_library_or_framework += "_" + template
61 |
62 | # Create a Generator object and pass app_name and frontend_library_or_framework
63 | app_generator = Generator(
64 | app_name=app_name,
65 | frontend_library_or_framework=frontend_library_or_framework,
66 | )
67 | # start building the new django app
68 | app_generator.generate()
69 |
70 | # show success messages to the user after the command is successfully
71 | # executed
72 | self.show_success_message("\n\nYour App is Ready !!!")
73 | self.show_success_message(
74 | f"Add {app_name} in the INSTALLED_APPS list in the project's settings.py file"
75 | )
76 | self.show_success_message(
77 | f"Add {app_name}.urls in urlpatterns of the project's urls.py"
78 | )
79 | self.show_success_message(
80 | "Start the Django Server and cd into your app and run npm start"
81 | )
82 |
--------------------------------------------------------------------------------
/django_webpack_dev_server/management/constants.py:
--------------------------------------------------------------------------------
1 | from django_webpack_dev_server.management.template_constants.development import (
2 | development_constants,
3 | )
4 | from django_webpack_dev_server.management.template_constants.production import (
5 | production_constants,
6 | )
7 |
8 | # To Identify if the operating system is windows based or not
9 | WINDOWS_OS_IDENTIFIER = "win32"
10 |
11 | APP_DIRECTORY_NAME = "APP"
12 | SRC_DIRECTORY_NAME = "src"
13 | TEMPLATES_DIRECTORY_NAME = "templates"
14 |
15 | COMMON_ASSETS_DIRECTORY = "Common"
16 |
17 | # Store the development template filenames with frontend library or framework as key
18 | DEVELOPMENT_TEMPLATE_FILES_DICT = {
19 | "react_javascript": development_constants.DEV_ALL_REACTJS_TEMPLATE_FILES,
20 | "react_typescript": development_constants.DEV_ALL_REACTTS_TEMPLATE_FILES,
21 | }
22 |
23 |
24 | # Store template filenames with frontend library or framework as key
25 | PROD_TEMPLATE_FILES_DICT = {
26 | "react_javascript": production_constants.PROD_ALL_REACTJS_TEMPLATE_FILES,
27 | "react_typescript": production_constants.PROD_ALL_REACTTS_TEMPLATE_FILES,
28 | }
29 |
30 | # show relevent error messages to the user
31 | COMMAND_ERROR_MESSAGES_DICT = {
32 | "INVALID_APP_NAME_ERROR_MESSAGE": "App Name should be alphanumeric only",
33 | "SYSTEM_ERROR_MESSAGE": "Seems like node or npm is not available in your system",
34 | "STATICFILES_DIRS_MISSING_ERROR_MESSAGE": "STATICFILES_DIRS attribute is missing in the django settings file",
35 | "NPM_INSTALLATION_ERROR_MESSAGE": "There were some errors while installing dependencies",
36 | }
37 |
--------------------------------------------------------------------------------
/django_webpack_dev_server/management/generator.py:
--------------------------------------------------------------------------------
1 | # necessary imports
2 | import os
3 | import queue
4 | import shlex
5 | import shutil
6 | import subprocess
7 | import sys
8 | import threading
9 | from string import Template
10 |
11 | import progressbar
12 | import requests
13 | from django.core import management
14 | from django.core.management.base import CommandError
15 |
16 | # import the constants module
17 | from django_webpack_dev_server.management import constants
18 | from dotenv import load_dotenv
19 |
20 | # load the environment variables
21 | load_dotenv()
22 |
23 |
24 | class Generator:
25 | """
26 | Base Class to create a django app with the necessary frontend configuration
27 | """
28 |
29 | # set the values of app_name, frontend_library_or_framework in the constructor
30 | def __init__(self, app_name, frontend_library_or_framework):
31 | """
32 | Constructor to set up the app_name and frontend_library_or_framework variables of the class
33 | :param app_name str: Name of the django app with frontend configuration
34 | :param frontend_library_or_framework str: Denotes the configuration to setup in the django app.
35 | """
36 |
37 | # app_name is the name of the django app which will have the frontend configuration
38 | self.app_name = app_name
39 |
40 | # frontend_library_or_framework denotes the frontend framework or library requested from the command line
41 | self.frontend_library_or_framework = frontend_library_or_framework
42 |
43 | # static_directory_name is the name of the the static directory inside the django app
44 | self.static_directory_name = "static"
45 |
46 | # set the shell_parameter according to the operating system of the user
47 | # shell_parameter = True for windows based system
48 | # shell_parameter = False for non windows based system
49 | self.shell_parameter = sys.platform == constants.WINDOWS_OS_IDENTIFIER
50 |
51 | def validate_appname(self):
52 | """
53 | Validates the app name of the django app provided by the user via commandline
54 | """
55 | # if appname contains non alphanumeric characters then raise error and inform the user
56 | if not self.app_name.isalnum():
57 | raise CommandError(
58 | constants.COMMAND_ERROR_MESSAGES_DICT["INVALID_APP_NAME_ERROR_MESSAGE"]
59 | )
60 |
61 | def check_system_requirements(self):
62 | """
63 | Checks if system has node and npm installed with the help of the
64 | subprocess module
65 | """
66 | # command to check node in system
67 | command_for_node = "node -v"
68 |
69 | # command to check npm in system
70 | command_for_npm = "npm -v"
71 |
72 | # checks whether node is installed by getting node version
73 | subprocess_node_command = subprocess.run(
74 | shlex.split(command_for_node),
75 | capture_output=True,
76 | shell=self.shell_parameter,
77 | )
78 |
79 | # checks whether npm is installed by getting npm version
80 | subprocess_npm_command = subprocess.run(
81 | shlex.split(command_for_npm),
82 | capture_output=True,
83 | shell=self.shell_parameter,
84 | )
85 |
86 | # if either of the commands fail then raise an error and inform the user
87 | if (
88 | subprocess_node_command.returncode != 0
89 | or subprocess_npm_command.returncode != 0
90 | ):
91 | raise CommandError(
92 | constants.COMMAND_ERROR_MESSAGES_DICT["SYSTEM_ERROR_MESSAGE"]
93 | )
94 |
95 | def create_required_directories(self):
96 | """
97 | Creates templates directory and static directory inside the django
98 | app which will have the frontend configuration
99 | """
100 |
101 | # create the path for the templates directory
102 | templates_directory_path = os.path.join(
103 | os.getcwd(),
104 | self.app_name,
105 | constants.TEMPLATES_DIRECTORY_NAME,
106 | self.app_name,
107 | )
108 |
109 | # create the path for the static directory
110 | static_directory_path = os.path.join(
111 | os.getcwd(), self.app_name, self.static_directory_name, self.app_name
112 | )
113 |
114 | # create the path for the src directory
115 | src_directory_path = os.path.join(
116 | os.getcwd(), self.app_name, constants.SRC_DIRECTORY_NAME
117 | )
118 |
119 | # create the necessary directories inside the newly created django app
120 | try:
121 | os.makedirs(templates_directory_path)
122 | os.makedirs(static_directory_path)
123 | os.makedirs(src_directory_path)
124 |
125 | except OSError as error:
126 | raise CommandError(error)
127 |
128 | def check_if_file_is_text_document(self, filename):
129 | """
130 | Check whether file is of form textdocument or an media file with the help of file extension
131 | :param filename str: name of the file to check
132 | :return is_text_document bool: Whether the file is text document or a media file
133 | """
134 |
135 | is_text_document = True
136 |
137 | # get the extension of the file from the filename
138 | _, file_extension = os.path.splitext(filename)
139 |
140 | # list of non text document file extensions
141 | non_text_document_file_extensions = [".png"]
142 |
143 | # if the file extension is in the extensions list then return False
144 | if file_extension in non_text_document_file_extensions:
145 | return not is_text_document
146 |
147 | # return the boolean value
148 | return is_text_document
149 |
150 | def get_target_path_of_template_file(self, filename, directory_type):
151 | """
152 | Finds the path where the file will be placed after modifications
153 | :param filename str: name of the file to get the path
154 | :param directory_type str: name of the directory where file will be stored
155 | :return target_filepath str: final filepath of the file after it is substituted with the parameters
156 | """
157 |
158 | # initialize the target_filepath as the current working directory
159 | target_filepath = os.getcwd()
160 |
161 | # append the specific path as per the directory in which the file would be stored
162 | if directory_type == constants.APP_DIRECTORY_NAME:
163 | target_filepath = os.path.join(target_filepath, self.app_name)
164 |
165 | elif directory_type == constants.SRC_DIRECTORY_NAME:
166 | target_filepath = os.path.join(
167 | target_filepath, self.app_name, constants.SRC_DIRECTORY_NAME
168 | )
169 |
170 | elif directory_type == constants.TEMPLATES_DIRECTORY_NAME:
171 | target_filepath = os.path.join(
172 | target_filepath,
173 | self.app_name,
174 | constants.TEMPLATES_DIRECTORY_NAME,
175 | self.app_name,
176 | )
177 |
178 | # append the filename into the target_filepath
179 | target_filepath = os.path.join(target_filepath, filename)
180 |
181 | # return the target_filepath
182 | return target_filepath
183 |
184 | def download_template_files(self):
185 | """
186 | Download template files from the Git Repository and substitute the
187 | necessary parameters
188 | """
189 |
190 | # get all template files as per the requirement
191 | template_files = constants.PROD_TEMPLATE_FILES_DICT.get(
192 | self.frontend_library_or_framework
193 | )
194 |
195 | # replace the parameters with appropriate values
196 | substitute_parameters = {"app_name": self.app_name}
197 |
198 | # iterate all the tempate filenames
199 | for directory_type, filename, download_url in template_files:
200 | # get the path where the file will be stored
201 | target_filepath = self.get_target_path_of_template_file(
202 | filename, directory_type
203 | )
204 |
205 | # Download the files from the Git Repository
206 | download_file = requests.get(download_url, stream=True)
207 |
208 | # write the contents of the file in the appropriate location
209 | with open(target_filepath, "wb") as target_file:
210 | try:
211 | target_file.write(download_file.content)
212 |
213 | except OSError as error:
214 | raise CommandError(error)
215 |
216 | # if file is not a type of text document then do not perform
217 | # string templating for it
218 | if not self.check_if_file_is_text_document(filename):
219 | continue
220 |
221 | # open the file and perform templating to replace the necessary parameters
222 | with open(target_filepath, "r") as target_file:
223 | # read the file contents and substitute the parameters
224 | source_file = Template(target_file.read())
225 | modified_file_contents = source_file.substitute(substitute_parameters)
226 |
227 | # write the updated file in the appropriate location
228 | with open(target_filepath, "w") as target_file:
229 | try:
230 | target_file.write(modified_file_contents)
231 |
232 | except OSError as error:
233 | raise CommandError(error)
234 |
235 | def load_assets_from_local(self):
236 | """
237 | A development only method executed when SOFTWARE_ENVIRONMENT_MODE == development
238 | It loads the assets files from local system instead of downloading from the
239 | Git Repository
240 | """
241 |
242 | # get all template files as per the requirement
243 | template_files = constants.DEVELOPMENT_TEMPLATE_FILES_DICT.get(
244 | self.frontend_library_or_framework
245 | )
246 |
247 | # replace the parameters with appropriate values
248 | substitute_parameters = {"app_name": self.app_name}
249 |
250 | # iterate all the tempate filenames
251 | for directory_type, filename, local_asset_file_path in template_files:
252 | # get the path where the file will be stored
253 | target_filepath = self.get_target_path_of_template_file(
254 | filename, directory_type
255 | )
256 |
257 | # copy the template file from the local assets directory to the target path
258 | shutil.copy(local_asset_file_path, target_filepath)
259 |
260 | # if file is not a type of text document then do not template that file
261 | if not self.check_if_file_is_text_document(filename):
262 | continue
263 |
264 | # open the files and perform templating to replace the necessary parameters
265 | with open(target_filepath, "r") as target_file:
266 | source_file = Template(target_file.read())
267 | modified_file_contents = source_file.substitute(substitute_parameters)
268 |
269 | # write the updated file in the appropriate location
270 | with open(target_filepath, "w") as target_file:
271 | try:
272 | target_file.write(modified_file_contents)
273 |
274 | except OSError as error:
275 | raise CommandError(error)
276 |
277 | def install_dependencies(self, thread_queue):
278 | """
279 | Installs the node dependencies by executing npm install command
280 | :param thread_queue Queue: Queue which will pass the status of the command
281 | to main thread
282 | """
283 | # set the current working directory to the newly created django app
284 | os.chdir(self.app_name)
285 |
286 | # command to install node dependencies
287 | command_for_npm_install = "npm install"
288 |
289 | # execute the npm install command for installing dependencies
290 | command = subprocess.Popen(
291 | shlex.split(command_for_npm_install),
292 | shell=self.shell_parameter,
293 | stdout=subprocess.PIPE,
294 | stderr=subprocess.STDOUT,
295 | )
296 |
297 | # get any data genrated by the command and display it on stdout
298 | while True:
299 | # read output lines one line at a time
300 | realtime_output = command.stdout.readline()
301 |
302 | # if output line is empty then terminate the loop
303 | if realtime_output == "" or command.poll() is not None:
304 | break
305 | elif realtime_output:
306 | # display messages in stdout
307 | print(realtime_output.strip().decode())
308 | sys.stdout.flush()
309 |
310 | # set the working directory to the original value
311 | os.chdir("../")
312 | # return the status of the installation command into the main thread
313 | thread_queue.put(command.returncode)
314 |
315 | def install_dependencies_and_show_progress_bar(self):
316 | """
317 | Install the necessary node dependencies and display a progress bar
318 | while the installation command executes
319 | """
320 | # setting the progress bar message and type
321 | widgets = ["Installing Node Dependencies ", progressbar.AnimatedMarker()]
322 |
323 | # create an indefinite progressbar because time for installing dependencies is not equal for all users
324 | bar = progressbar.ProgressBar(
325 | widgets=widgets, max_value=progressbar.UnknownLength, redirect_stdout=True
326 | )
327 | bar.start()
328 |
329 | # thread_queue is used to fetch the output of the function executed in the other thread
330 | thread_queue = queue.Queue()
331 | count = 0
332 |
333 | # create a thread which will execute the npm install command
334 | thread1 = threading.Thread(
335 | target=self.install_dependencies, args=[thread_queue], name="thread1"
336 | )
337 |
338 | # start the installation command by starting the thread
339 | thread1.start()
340 |
341 | # display the progress until the other thread is alive
342 | while thread1.is_alive():
343 | count += 1
344 | bar.update(count)
345 |
346 | # fetching status code of the command executed in other thread
347 | status = thread_queue.get()
348 |
349 | # if command failed then inform the user
350 | if status != 0:
351 | raise CommandError(
352 | constants.COMMAND_ERROR_MESSAGES_DICT["NPM_INSTALLATION_ERROR_MESSAGE"]
353 | )
354 |
355 | def generate(self):
356 | """
357 | Generates a django app with the selected frontend library or framework
358 | Driver method which will execute the other methods
359 | """
360 | self.validate_appname()
361 | self.check_system_requirements()
362 | management.call_command("startapp", self.app_name)
363 | self.create_required_directories()
364 |
365 | # In development mode the assets will be loaded from the local system
366 | if os.environ.get("SOFTWARE_ENVIRONMENT_MODE") == "development":
367 | print("Loading the Assets Locally")
368 | self.load_assets_from_local()
369 |
370 | # In production mode the assets will be downloaded from the Git Repository
371 | else:
372 | self.download_template_files()
373 |
374 | self.install_dependencies_and_show_progress_bar()
375 |
--------------------------------------------------------------------------------
/django_webpack_dev_server/management/template_constants/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jitensid/django-webpack-dev-server/c47e5208dc87ed4a9550674c7374764e97a9fb86/django_webpack_dev_server/management/template_constants/__init__.py
--------------------------------------------------------------------------------
/django_webpack_dev_server/management/template_constants/development/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jitensid/django-webpack-dev-server/c47e5208dc87ed4a9550674c7374764e97a9fb86/django_webpack_dev_server/management/template_constants/development/__init__.py
--------------------------------------------------------------------------------
/django_webpack_dev_server/management/template_constants/development/development_constants.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from django_webpack_dev_server.management.template_constants.development.reactjs_constants import (
4 | DEV_REACTJS_TEMPLATE_FILES,
5 | )
6 |
7 | from django_webpack_dev_server.management.template_constants.development.reactts_constants import (
8 | DEV_REACTTS_TEMPLATE_FILES,
9 | )
10 |
11 | DEVELOPMENT_ASSETS_DIRECTORY_PATH = os.path.join(os.getcwd(), "assets")
12 |
13 | APP_DIRECTORY_NAME = "APP"
14 | SRC_DIRECTORY_NAME = "src"
15 | TEMPLATES_DIRECTORY_NAME = "templates"
16 | COMMON_ASSETS_DIRECTORY = "Common"
17 | REACTJS = "reactjs"
18 |
19 | # Paths of the common template files which are loaded locally
20 | DEV_COMMON_TEMPLATES_PATHS_DICT = {
21 | "urls.py-tpl": os.path.join(
22 | DEVELOPMENT_ASSETS_DIRECTORY_PATH, COMMON_ASSETS_DIRECTORY, "urls.py-tpl"
23 | ),
24 | "views.py-tpl": os.path.join(
25 | DEVELOPMENT_ASSETS_DIRECTORY_PATH, COMMON_ASSETS_DIRECTORY, "views.py-tpl"
26 | ),
27 | "index.html-tpl": os.path.join(
28 | DEVELOPMENT_ASSETS_DIRECTORY_PATH, COMMON_ASSETS_DIRECTORY, "index.html-tpl"
29 | ),
30 | "djangologo.png": os.path.join(
31 | DEVELOPMENT_ASSETS_DIRECTORY_PATH, COMMON_ASSETS_DIRECTORY, "djangologo.png"
32 | ),
33 | }
34 |
35 | # Development template files which are common for any frontend library or framework
36 | DEV_COMMON_TEMPLATE_FILES = [
37 | (
38 | APP_DIRECTORY_NAME,
39 | "urls.py",
40 | os.path.join(
41 | DEVELOPMENT_ASSETS_DIRECTORY_PATH, COMMON_ASSETS_DIRECTORY, "urls.py-tpl"
42 | ),
43 | ),
44 | (
45 | APP_DIRECTORY_NAME,
46 | "views.py",
47 | os.path.join(
48 | DEVELOPMENT_ASSETS_DIRECTORY_PATH, COMMON_ASSETS_DIRECTORY, "views.py-tpl"
49 | ),
50 | ),
51 | (
52 | SRC_DIRECTORY_NAME,
53 | "djangologo.png",
54 | os.path.join(
55 | DEVELOPMENT_ASSETS_DIRECTORY_PATH, COMMON_ASSETS_DIRECTORY, "djangologo.png"
56 | ),
57 | ),
58 | (
59 | TEMPLATES_DIRECTORY_NAME,
60 | "index.html",
61 | os.path.join(
62 | DEVELOPMENT_ASSETS_DIRECTORY_PATH, COMMON_ASSETS_DIRECTORY, "index.html-tpl"
63 | ),
64 | ),
65 | ]
66 |
67 | DEV_ALL_REACTJS_TEMPLATE_FILES = DEV_COMMON_TEMPLATE_FILES + DEV_REACTJS_TEMPLATE_FILES
68 |
69 | DEV_ALL_REACTTS_TEMPLATE_FILES = DEV_COMMON_TEMPLATE_FILES + DEV_REACTTS_TEMPLATE_FILES
70 |
--------------------------------------------------------------------------------
/django_webpack_dev_server/management/template_constants/development/reactjs_constants.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | DEVELOPMENT_ASSETS_DIRECTORY_PATH = os.path.join(os.getcwd(), "assets")
4 |
5 | APP_DIRECTORY_NAME = "APP"
6 | SRC_DIRECTORY_NAME = "src"
7 | TEMPLATES_DIRECTORY_NAME = "templates"
8 | COMMON_ASSETS_DIRECTORY = "Common"
9 | REACTJS = "reactjs"
10 |
11 | # stores the paths of the reactjs template files which are loaded locally
12 | DEV_REACTJS_TEMPLATES_PATHS_DICT = {
13 | "package.json-tpl": os.path.join(
14 | DEVELOPMENT_ASSETS_DIRECTORY_PATH, REACTJS, "package.json-tpl"
15 | ),
16 | "webpack.config.js-tpl": os.path.join(
17 | DEVELOPMENT_ASSETS_DIRECTORY_PATH, REACTJS, "webpack.config.js-tpl"
18 | ),
19 | "babel.config.json-tpl": os.path.join(
20 | DEVELOPMENT_ASSETS_DIRECTORY_PATH, REACTJS, "babel.config.json-tpl"
21 | ),
22 | "App.js-tpl": os.path.join(
23 | DEVELOPMENT_ASSETS_DIRECTORY_PATH, REACTJS, "App.js-tpl"
24 | ),
25 | "index.js-tpl": os.path.join(
26 | DEVELOPMENT_ASSETS_DIRECTORY_PATH, REACTJS, "index.js-tpl"
27 | ),
28 | "App.css-tpl": os.path.join(
29 | DEVELOPMENT_ASSETS_DIRECTORY_PATH, REACTJS, "App.css-tpl"
30 | ),
31 | "reactlogo.png": os.path.join(
32 | DEVELOPMENT_ASSETS_DIRECTORY_PATH, REACTJS, "reactlogo.png"
33 | ),
34 | }
35 |
36 | # Development template files for reactjs configuration
37 | DEV_REACTJS_TEMPLATE_FILES = [
38 | (
39 | APP_DIRECTORY_NAME,
40 | "package.json",
41 | DEV_REACTJS_TEMPLATES_PATHS_DICT["package.json-tpl"],
42 | ),
43 | (
44 | APP_DIRECTORY_NAME,
45 | "webpack.config.js",
46 | DEV_REACTJS_TEMPLATES_PATHS_DICT["webpack.config.js-tpl"],
47 | ),
48 | (
49 | APP_DIRECTORY_NAME,
50 | "babel.config.json",
51 | DEV_REACTJS_TEMPLATES_PATHS_DICT["babel.config.json-tpl"],
52 | ),
53 | (SRC_DIRECTORY_NAME, "App.js", DEV_REACTJS_TEMPLATES_PATHS_DICT["App.js-tpl"]),
54 | (SRC_DIRECTORY_NAME, "index.js", DEV_REACTJS_TEMPLATES_PATHS_DICT["index.js-tpl"]),
55 | (SRC_DIRECTORY_NAME, "App.css", DEV_REACTJS_TEMPLATES_PATHS_DICT["App.css-tpl"]),
56 | (
57 | SRC_DIRECTORY_NAME,
58 | "reactlogo.png",
59 | DEV_REACTJS_TEMPLATES_PATHS_DICT["reactlogo.png"],
60 | ),
61 | ]
62 |
--------------------------------------------------------------------------------
/django_webpack_dev_server/management/template_constants/development/reactts_constants.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | DEVELOPMENT_ASSETS_DIRECTORY_PATH = os.path.join(os.getcwd(), "assets")
4 |
5 | APP_DIRECTORY_NAME = "APP"
6 | SRC_DIRECTORY_NAME = "src"
7 | TEMPLATES_DIRECTORY_NAME = "templates"
8 | COMMON_ASSETS_DIRECTORY = "Common"
9 | REACTTS = "reactts"
10 |
11 | # stores the paths of the reactjs template files which are loaded locally
12 | DEV_REACTTS_TEMPLATES_PATHS_DICT = {
13 | "package.json-tpl": os.path.join(
14 | DEVELOPMENT_ASSETS_DIRECTORY_PATH, REACTTS, "package.json-tpl"
15 | ),
16 | "webpack.config.js-tpl": os.path.join(
17 | DEVELOPMENT_ASSETS_DIRECTORY_PATH, REACTTS, "webpack.config.js-tpl"
18 | ),
19 | "babel.config.json-tpl": os.path.join(
20 | DEVELOPMENT_ASSETS_DIRECTORY_PATH, REACTTS, "babel.config.json-tpl"
21 | ),
22 | "tsconfig.json-tpl": os.path.join(
23 | DEVELOPMENT_ASSETS_DIRECTORY_PATH, REACTTS, "tsconfig.json-tpl"
24 | ),
25 | "module.d.ts-tpl": os.path.join(
26 | DEVELOPMENT_ASSETS_DIRECTORY_PATH, REACTTS, "module.d.ts-tpl"
27 | ),
28 | "App.tsx-tpl": os.path.join(
29 | DEVELOPMENT_ASSETS_DIRECTORY_PATH, REACTTS, "App.tsx-tpl"
30 | ),
31 | "index.tsx-tpl": os.path.join(
32 | DEVELOPMENT_ASSETS_DIRECTORY_PATH, REACTTS, "index.tsx-tpl"
33 | ),
34 | "App.css-tpl": os.path.join(
35 | DEVELOPMENT_ASSETS_DIRECTORY_PATH, REACTTS, "App.css-tpl"
36 | ),
37 | "reactlogo.png": os.path.join(
38 | DEVELOPMENT_ASSETS_DIRECTORY_PATH, REACTTS, "reactlogo.png"
39 | ),
40 | }
41 |
42 | # Development template files for reactjs configuration
43 | DEV_REACTTS_TEMPLATE_FILES = [
44 | (
45 | APP_DIRECTORY_NAME,
46 | "package.json",
47 | DEV_REACTTS_TEMPLATES_PATHS_DICT["package.json-tpl"],
48 | ),
49 | (
50 | APP_DIRECTORY_NAME,
51 | "webpack.config.js",
52 | DEV_REACTTS_TEMPLATES_PATHS_DICT["webpack.config.js-tpl"],
53 | ),
54 | (
55 | APP_DIRECTORY_NAME,
56 | "babel.config.json",
57 | DEV_REACTTS_TEMPLATES_PATHS_DICT["babel.config.json-tpl"],
58 | ),
59 | (
60 | APP_DIRECTORY_NAME,
61 | "tsconfig.json",
62 | DEV_REACTTS_TEMPLATES_PATHS_DICT["tsconfig.json-tpl"],
63 | ),
64 | (
65 | SRC_DIRECTORY_NAME,
66 | "module.d.ts",
67 | DEV_REACTTS_TEMPLATES_PATHS_DICT["module.d.ts-tpl"],
68 | ),
69 | (SRC_DIRECTORY_NAME, "App.tsx", DEV_REACTTS_TEMPLATES_PATHS_DICT["App.tsx-tpl"]),
70 | (
71 | SRC_DIRECTORY_NAME,
72 | "index.tsx",
73 | DEV_REACTTS_TEMPLATES_PATHS_DICT["index.tsx-tpl"],
74 | ),
75 | (SRC_DIRECTORY_NAME, "App.css", DEV_REACTTS_TEMPLATES_PATHS_DICT["App.css-tpl"]),
76 | (
77 | SRC_DIRECTORY_NAME,
78 | "reactlogo.png",
79 | DEV_REACTTS_TEMPLATES_PATHS_DICT["reactlogo.png"],
80 | ),
81 | ]
82 |
--------------------------------------------------------------------------------
/django_webpack_dev_server/management/template_constants/production/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jitensid/django-webpack-dev-server/c47e5208dc87ed4a9550674c7374764e97a9fb86/django_webpack_dev_server/management/template_constants/production/__init__.py
--------------------------------------------------------------------------------
/django_webpack_dev_server/management/template_constants/production/production_constants.py:
--------------------------------------------------------------------------------
1 | from django_webpack_dev_server.management.template_constants.production.reactjs_constants import (
2 | PROD_REACTJS_TEMPLATE_FILES,
3 | )
4 |
5 | from django_webpack_dev_server.management.template_constants.production.reactts_constants import (
6 | PROD_REACTTS_TEMPLATE_FILES,
7 | )
8 |
9 | APP_DIRECTORY_NAME = "APP"
10 | SRC_DIRECTORY_NAME = "src"
11 | TEMPLATES_DIRECTORY_NAME = "templates"
12 |
13 |
14 | COMMON_TEMPLATES_URLS_DICT = {
15 | "urls.py-tpl": "https://raw.githubusercontent.com/Jitensid/django-webpack-dev-server/main/assets/Common/urls.py-tpl",
16 | "views.py-tpl": "https://raw.githubusercontent.com/Jitensid/django-webpack-dev-server/main/assets/Common/views.py-tpl",
17 | "index.html-tpl": "https://raw.githubusercontent.com/Jitensid/django-webpack-dev-server/main/assets/Common/index.html-tpl",
18 | "djangologo.png": "https://raw.githubusercontent.com/Jitensid/django-webpack-dev-server/main/assets/Common/djangologo.png",
19 | }
20 |
21 | # Template files that will be common for any frontend library or framework
22 | COMMON_TEMPLATE_FILES = [
23 | (APP_DIRECTORY_NAME, "urls.py", COMMON_TEMPLATES_URLS_DICT["urls.py-tpl"]),
24 | (APP_DIRECTORY_NAME, "views.py", COMMON_TEMPLATES_URLS_DICT["views.py-tpl"]),
25 | (
26 | SRC_DIRECTORY_NAME,
27 | "djangologo.png",
28 | COMMON_TEMPLATES_URLS_DICT["djangologo.png"],
29 | ),
30 | (
31 | TEMPLATES_DIRECTORY_NAME,
32 | "index.html",
33 | COMMON_TEMPLATES_URLS_DICT["index.html-tpl"],
34 | ),
35 | ]
36 |
37 | PROD_ALL_REACTJS_TEMPLATE_FILES = COMMON_TEMPLATE_FILES + PROD_REACTJS_TEMPLATE_FILES
38 |
39 | PROD_ALL_REACTTS_TEMPLATE_FILES = COMMON_TEMPLATE_FILES + PROD_REACTTS_TEMPLATE_FILES
40 |
--------------------------------------------------------------------------------
/django_webpack_dev_server/management/template_constants/production/reactjs_constants.py:
--------------------------------------------------------------------------------
1 | APP_DIRECTORY_NAME = "APP"
2 | SRC_DIRECTORY_NAME = "src"
3 | TEMPLATES_DIRECTORY_NAME = "templates"
4 |
5 | # Production React Js Template files having Github Repository Links
6 | REACTJS_TEMPLATES_URLS_DICT = {
7 | "package.json-tpl": "https://raw.githubusercontent.com/Jitensid/django-webpack-dev-server/main/assets/reactjs/package.json-tpl",
8 | "webpack.config.js-tpl": "https://raw.githubusercontent.com/Jitensid/django-webpack-dev-server/main/assets/reactjs/webpack.config.js-tpl",
9 | "babel.config.json-tpl": "https://raw.githubusercontent.com/Jitensid/django-webpack-dev-server/main/assets/reactjs/babel.config.json-tpl",
10 | "App.js-tpl": "https://raw.githubusercontent.com/Jitensid/django-webpack-dev-server/main/assets/reactjs/App.js-tpl",
11 | "index.js-tpl": "https://raw.githubusercontent.com/Jitensid/django-webpack-dev-server/main/assets/reactjs/index.js-tpl",
12 | "App.css-tpl": "https://raw.githubusercontent.com/Jitensid/django-webpack-dev-server/main/assets/reactjs/App.css-tpl",
13 | "reactlogo.png": "https://raw.githubusercontent.com/Jitensid/django-webpack-dev-server/main/assets/reactjs/reactlogo.png",
14 | }
15 |
16 | # Template files specific to react js configuration
17 | PROD_REACTJS_TEMPLATE_FILES = [
18 | (
19 | APP_DIRECTORY_NAME,
20 | "package.json",
21 | REACTJS_TEMPLATES_URLS_DICT["package.json-tpl"],
22 | ),
23 | (
24 | APP_DIRECTORY_NAME,
25 | "webpack.config.js",
26 | REACTJS_TEMPLATES_URLS_DICT["webpack.config.js-tpl"],
27 | ),
28 | (
29 | APP_DIRECTORY_NAME,
30 | "babel.config.json",
31 | REACTJS_TEMPLATES_URLS_DICT["babel.config.json-tpl"],
32 | ),
33 | (SRC_DIRECTORY_NAME, "App.js", REACTJS_TEMPLATES_URLS_DICT["App.js-tpl"]),
34 | (SRC_DIRECTORY_NAME, "index.js", REACTJS_TEMPLATES_URLS_DICT["index.js-tpl"]),
35 | (SRC_DIRECTORY_NAME, "App.css", REACTJS_TEMPLATES_URLS_DICT["App.css-tpl"]),
36 | (SRC_DIRECTORY_NAME, "reactlogo.png", REACTJS_TEMPLATES_URLS_DICT["reactlogo.png"]),
37 | ]
38 |
--------------------------------------------------------------------------------
/django_webpack_dev_server/management/template_constants/production/reactts_constants.py:
--------------------------------------------------------------------------------
1 | APP_DIRECTORY_NAME = "APP"
2 | SRC_DIRECTORY_NAME = "src"
3 | TEMPLATES_DIRECTORY_NAME = "templates"
4 |
5 | # Production React Ts Template files having Github Repository Links
6 | REACTTS_TEMPLATES_URLS_DICT = {
7 | "package.json-tpl": "https://raw.githubusercontent.com/Jitensid/django-webpack-dev-server/main/assets/reactts/package.json-tpl",
8 | "webpack.config.js-tpl": "https://raw.githubusercontent.com/Jitensid/django-webpack-dev-server/main/assets/reactts/webpack.config.js-tpl",
9 | "babel.config.json-tpl": "https://raw.githubusercontent.com/Jitensid/django-webpack-dev-server/main/assets/reactts/babel.config.json-tpl",
10 | "tsconfig.json-tpl": "https://raw.githubusercontent.com/Jitensid/django-webpack-dev-server/main/assets/reactts/tsconfig.json-tpl",
11 | "module.d.ts-tpl": "https://raw.githubusercontent.com/Jitensid/django-webpack-dev-server/main/assets/reactts/module.d.ts-tpl",
12 | "App.tsx-tpl": "https://raw.githubusercontent.com/Jitensid/django-webpack-dev-server/main/assets/reactts/App.tsx-tpl",
13 | "index.tsx-tpl": "https://raw.githubusercontent.com/Jitensid/django-webpack-dev-server/main/assets/reactts/index.tsx-tpl",
14 | "App.css-tpl": "https://raw.githubusercontent.com/Jitensid/django-webpack-dev-server/main/assets/reactts/App.css-tpl",
15 | "reactlogo.png": "https://raw.githubusercontent.com/Jitensid/django-webpack-dev-server/main/assets/reactts/reactlogo.png",
16 | }
17 |
18 | # Template files specific to react ts configuration
19 | PROD_REACTTS_TEMPLATE_FILES = [
20 | (
21 | APP_DIRECTORY_NAME,
22 | "package.json",
23 | REACTTS_TEMPLATES_URLS_DICT["package.json-tpl"],
24 | ),
25 | (
26 | APP_DIRECTORY_NAME,
27 | "webpack.config.js",
28 | REACTTS_TEMPLATES_URLS_DICT["webpack.config.js-tpl"],
29 | ),
30 | (
31 | APP_DIRECTORY_NAME,
32 | "babel.config.json",
33 | REACTTS_TEMPLATES_URLS_DICT["babel.config.json-tpl"],
34 | ),
35 | (
36 | APP_DIRECTORY_NAME,
37 | "tsconfig.json",
38 | REACTTS_TEMPLATES_URLS_DICT["tsconfig.json-tpl"],
39 | ),
40 | (SRC_DIRECTORY_NAME, "module.d.ts", REACTTS_TEMPLATES_URLS_DICT["module.d.ts-tpl"]),
41 | (SRC_DIRECTORY_NAME, "App.tsx", REACTTS_TEMPLATES_URLS_DICT["App.tsx-tpl"]),
42 | (SRC_DIRECTORY_NAME, "index.tsx", REACTTS_TEMPLATES_URLS_DICT["index.tsx-tpl"]),
43 | (SRC_DIRECTORY_NAME, "App.css", REACTTS_TEMPLATES_URLS_DICT["App.css-tpl"]),
44 | (SRC_DIRECTORY_NAME, "reactlogo.png", REACTTS_TEMPLATES_URLS_DICT["reactlogo.png"]),
45 | ]
46 |
--------------------------------------------------------------------------------
/django_webpack_dev_server/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jitensid/django-webpack-dev-server/c47e5208dc87ed4a9550674c7374764e97a9fb86/django_webpack_dev_server/migrations/__init__.py
--------------------------------------------------------------------------------
/pytest.ini:
--------------------------------------------------------------------------------
1 | [pytest]
2 | DJANGO_SETTINGS_MODULE = tests.test_settings
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | django
2 | -r requirements/requirements-dev.txt
3 | -r requirements/requirements-lint.txt
4 | -r requirements/requirements-testing.txt
5 |
--------------------------------------------------------------------------------
/requirements/requirements-dev.txt:
--------------------------------------------------------------------------------
1 | python-dotenv
2 | progressbar2
3 | requests
4 | pre-commit
--------------------------------------------------------------------------------
/requirements/requirements-lint.txt:
--------------------------------------------------------------------------------
1 | black
--------------------------------------------------------------------------------
/requirements/requirements-testing.txt:
--------------------------------------------------------------------------------
1 | pytest
2 | pytest-django
3 | coverage
--------------------------------------------------------------------------------
/runtests.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 |
4 | import django
5 | from django.conf import settings
6 | from django.test.utils import get_runner
7 |
8 | if __name__ == "__main__":
9 | os.environ["DJANGO_SETTINGS_MODULE"] = "tests.test_settings"
10 | django.setup()
11 | TestRunner = get_runner(settings)
12 | test_runner = TestRunner()
13 | failures = test_runner.run_tests(["tests"])
14 | sys.exit(bool(failures))
15 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | description_file = README.md
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 |
3 | package_description = (
4 | "A Django app to setup configuration files for React in a Django Project."
5 | )
6 |
7 | # to ignore the directories while creating wheel distribution
8 | exclude_directories_for_wheel_distribution = ["tests*", "mysite"]
9 |
10 | keywords_list = [
11 | "django",
12 | "react",
13 | "webpack5",
14 | "webpack_dev_server",
15 | "command-line",
16 | "project-generator",
17 | "template-generator",
18 | "javascript",
19 | "typescript",
20 | ]
21 |
22 | setup(
23 | name="django-webpack-dev-server",
24 | version="1.0.0",
25 | packages=find_packages(exclude=exclude_directories_for_wheel_distribution),
26 | license="MIT",
27 | author="Jiten Sidhpura",
28 | author_email="jitensidhpura2000@gmail.com",
29 | description=package_description,
30 | long_description=open("README.md").read(),
31 | long_description_content_type="text/markdown",
32 | url="https://github.com/Jitensid/django-webpack-dev-server",
33 | download_url="https://github.com/Jitensid/django-webpack-dev-server/archive/refs/tags/1.0.0.tar.gz",
34 | install_requires=["requests", "progressbar2", "python-dotenv"],
35 | keywords=keywords_list,
36 | classifiers=[
37 | "Intended Audience :: Developers",
38 | "Topic :: Software Development :: Code Generators",
39 | "Framework :: Django",
40 | "License :: OSI Approved :: MIT License",
41 | "Operating System :: OS Independent",
42 | "Programming Language :: Python :: 3.6",
43 | "Programming Language :: Python :: 3.7",
44 | "Programming Language :: Python :: 3.8",
45 | "Programming Language :: Python :: 3.9",
46 | "Programming Language :: Python :: 3.10",
47 | ],
48 | )
49 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jitensid/django-webpack-dev-server/c47e5208dc87ed4a9550674c7374764e97a9fb86/tests/__init__.py
--------------------------------------------------------------------------------
/tests/management/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jitensid/django-webpack-dev-server/c47e5208dc87ed4a9550674c7374764e97a9fb86/tests/management/__init__.py
--------------------------------------------------------------------------------
/tests/management/commands/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jitensid/django-webpack-dev-server/c47e5208dc87ed4a9550674c7374764e97a9fb86/tests/management/commands/__init__.py
--------------------------------------------------------------------------------
/tests/management/commands/test_generate.py:
--------------------------------------------------------------------------------
1 | from django.core.management import call_command
2 | from unittest import mock
3 | from django_webpack_dev_server.management.generator import Generator
4 |
5 |
6 | class TestCommand:
7 | """
8 | Test Class for testing the Command Class defined in the generate.py
9 | """
10 |
11 | @mock.patch.object(Generator, "generate")
12 | def test_command(self, mocked_Generator_generate):
13 | """
14 | Function to test the methods of the Command Class
15 | """
16 | # call the management command to test
17 | call_command("generate", "react")
18 |
19 | # assert that the generate method of the Generator class is called
20 | assert mocked_Generator_generate.called == True
21 |
--------------------------------------------------------------------------------
/tests/management/test_generator.py:
--------------------------------------------------------------------------------
1 | import io
2 | import os
3 | import queue
4 | from unittest import mock
5 |
6 | import pytest
7 | from django.core.management.base import CommandError
8 | from django_webpack_dev_server.management import constants
9 | from django_webpack_dev_server.management.generator import Generator
10 |
11 |
12 | # A pytest fixture used to create a Generator object
13 | @pytest.fixture
14 | def app_generator():
15 | # Create the Generator object
16 | app_generator = Generator(
17 | app_name="frontend", frontend_library_or_framework="react_javascript"
18 | )
19 |
20 | # return the Generator object to the test
21 | return app_generator
22 |
23 |
24 | class TestGeneratorClass:
25 | """
26 | Test Class for testing the Generator Class defined in generator.py
27 | """
28 |
29 | def test_validate_app_name(self):
30 | """
31 | Function to test the validate_app_name method of the Generator Class
32 | """
33 |
34 | # Create the Generator object and pass an invalid app_name to test
35 | app_generator = Generator(
36 | app_name="frontend@@@", frontend_library_or_framework="react_javascript"
37 | )
38 |
39 | # Check if CommandError is raised when the method is called with invalid app_name
40 | with pytest.raises(CommandError) as CommandErrorException:
41 | app_generator.validate_appname()
42 |
43 | # assert the message of the exception
44 | assert (
45 | str(CommandErrorException.value)
46 | == constants.COMMAND_ERROR_MESSAGES_DICT["INVALID_APP_NAME_ERROR_MESSAGE"]
47 | )
48 |
49 | @mock.patch("subprocess.run")
50 | def test_check_system_requirements(self, mocked_subprocess_run, app_generator):
51 | """
52 | Function to test the check_system_requirements method of the Generator Class
53 | """
54 |
55 | # mock the subprocess run method and set response to 0
56 | # Response 0 means that the command ran successfully
57 | mocked_subprocess_run.return_value.returncode = 0
58 |
59 | # call the method to test
60 | app_generator.check_system_requirements()
61 |
62 | # assert that the subprocess run method was called 2 times
63 | assert mocked_subprocess_run.call_count == 2
64 |
65 | # Reset the mock
66 | mocked_subprocess_run.reset_mock()
67 |
68 | # mock the subprocess run method and set response to 1
69 | # Response 1 means that the command did not ran successfully
70 | mocked_subprocess_run.return_value.returncode = 1
71 |
72 | # Check if CommandError is raised when the method is called
73 | with pytest.raises(CommandError) as CommandErrorException:
74 | # call the method to test
75 | app_generator.check_system_requirements()
76 |
77 | # assert the message of the exception
78 | assert (
79 | str(CommandErrorException.value)
80 | == constants.COMMAND_ERROR_MESSAGES_DICT["SYSTEM_ERROR_MESSAGE"]
81 | )
82 |
83 | # assert that the subprocess run method was called 2 times
84 | assert mocked_subprocess_run.call_count == 2
85 |
86 | @mock.patch("os.makedirs")
87 | def test_create_required_directories(self, mocked_os_makedirs, app_generator):
88 | """
89 | Function to test the create_required_directories method of the Generator Class
90 | """
91 |
92 | # call the method to test
93 | app_generator.create_required_directories()
94 |
95 | # assert that the os makedirs method is called 3 times
96 | assert mocked_os_makedirs.call_count == 3
97 |
98 | # reset the mock
99 | mocked_os_makedirs.reset_mock()
100 |
101 | # Check if CommandError is raised when the method is called
102 | with pytest.raises(CommandError) as CommandErrorException:
103 | exception_message = "Error while creating directories"
104 |
105 | # Raise OSError when the os makedirs method is called to
106 | mocked_os_makedirs.side_effect = OSError(exception_message)
107 |
108 | # call the method to test
109 | app_generator.create_required_directories()
110 |
111 | # assert that the os makedirs method is called 1 time
112 | assert mocked_os_makedirs.call_count == 1
113 |
114 | # assert the message of the exception
115 | assert str(CommandErrorException.value) == exception_message
116 |
117 | def test_check_if_file_is_text_document(self, app_generator):
118 | """
119 | Function to test the check_if_file_is_text_document method of the Generator Class
120 | """
121 |
122 | # file of type text document
123 | text_document_filename = "generator.py"
124 |
125 | # file of type non text document
126 | non_text_document_filename = "reactlogo.png"
127 |
128 | # assert True if file is of text document type
129 | assert (
130 | # call the method to test
131 | app_generator.check_if_file_is_text_document(text_document_filename)
132 | == True
133 | )
134 |
135 | # assert False if file is not of text document type
136 | assert (
137 | # call the method to test
138 | app_generator.check_if_file_is_text_document(non_text_document_filename)
139 | == False
140 | )
141 |
142 | def test_get_target_path_of_template_file(self, app_generator):
143 | """
144 | Function to test the get_target_path_of_template_file method of the Generator Class
145 | """
146 | filename = "webpack.config.js-tpl"
147 |
148 | # create the path for a while that will be stored under APP directory
149 | target_app_directory_filepath = os.path.join(
150 | os.getcwd(), app_generator.app_name
151 | )
152 |
153 | # create the path for a while that will be stored under src directory
154 | target_src_directory_filepath = os.path.join(
155 | os.getcwd(), app_generator.app_name, constants.SRC_DIRECTORY_NAME
156 | )
157 |
158 | # create the path for a while that will be stored under templates directory
159 | target_templates_directory_filepath = os.path.join(
160 | os.getcwd(),
161 | app_generator.app_name,
162 | constants.TEMPLATES_DIRECTORY_NAME,
163 | app_generator.app_name,
164 | )
165 |
166 | # create the final path for a particular file
167 | target_filepath = os.path.join(target_app_directory_filepath, filename)
168 |
169 | # assert the file path created and returned by the class method
170 | assert (
171 | # call the method to test
172 | app_generator.get_target_path_of_template_file(
173 | filename, constants.APP_DIRECTORY_NAME
174 | )
175 | == target_filepath
176 | )
177 |
178 | # create the final path for a particular file
179 | target_filepath = os.path.join(target_src_directory_filepath, filename)
180 |
181 | # assert the file path created and returned by the class method
182 | assert (
183 | # call the method to test
184 | app_generator.get_target_path_of_template_file(
185 | filename, constants.SRC_DIRECTORY_NAME
186 | )
187 | == target_filepath
188 | )
189 |
190 | # create the final path for a particular file
191 | target_filepath = os.path.join(target_templates_directory_filepath, filename)
192 |
193 | # assert the file path created and returned by the class method
194 | assert (
195 | # call the method to test
196 | app_generator.get_target_path_of_template_file(
197 | filename, constants.TEMPLATES_DIRECTORY_NAME
198 | )
199 | == target_filepath
200 | )
201 |
202 | @mock.patch("requests.get")
203 | @mock.patch("builtins.open", new_callable=mock.mock_open, read_data="My data")
204 | def test_download_template_files(
205 | self, mocked_builtins_open, mocked_requests_get, app_generator
206 | ):
207 | """
208 | Function to test the download_template_files method of the Generator Class
209 | """
210 |
211 | # call the method to test
212 | app_generator.download_template_files()
213 |
214 | # assert the number of times the file open is called
215 | assert mocked_builtins_open.call_count == 29
216 |
217 | # assert the number of times the get request is made
218 | assert mocked_requests_get.call_count == 11
219 |
220 | # reset the mocks
221 | mocked_builtins_open.reset_mock()
222 | mocked_requests_get.reset_mock()
223 |
224 | # Check if CommandError is raised when the method is called
225 | with pytest.raises(CommandError) as CommandErrorException:
226 | exception_message = "Error while writing in the file"
227 |
228 | # Raise OSError during the file write operation
229 | mocked_builtins_open.return_value.write.side_effect = OSError(
230 | exception_message
231 | )
232 |
233 | # call the method to test
234 | app_generator.download_template_files()
235 |
236 | # assert the message of the exception
237 | assert str(CommandErrorException.value) == exception_message
238 |
239 | mocked_builtins_open.reset_mock()
240 | mocked_requests_get.reset_mock()
241 |
242 | # Check if CommandError is raised when the method is called
243 | with pytest.raises(CommandError) as CommandErrorException:
244 | exception_message = "Error while writing in the file"
245 |
246 | # Raise OSError during the file write operation
247 | mocked_builtins_open.return_value.write.side_effect = [
248 | "No Error for 1st call",
249 | OSError(exception_message),
250 | ]
251 |
252 | # call the method to test
253 | app_generator.download_template_files()
254 |
255 | # assert the message of the exception
256 | assert str(CommandErrorException.value) == exception_message
257 |
258 | @mock.patch("builtins.open", new_callable=mock.mock_open, read_data="My data")
259 | @mock.patch("shutil.copy")
260 | def test_load_assets_from_local(
261 | self, mocked_shutil_copy, mocked_builtins_open, app_generator
262 | ):
263 | """
264 | Function to test the load_assets_from_local method of the Generator Class
265 | """
266 |
267 | # call the method to test
268 | app_generator.load_assets_from_local()
269 |
270 | # assert that shutil copy and open functions are called
271 | assert mocked_builtins_open.called == True
272 | assert mocked_shutil_copy.called == True
273 |
274 | # reset the mocks
275 | mocked_shutil_copy.reset_mock()
276 | mocked_builtins_open.reset_mock()
277 |
278 | # Check if CommandError is raised when the method is called
279 | with pytest.raises(CommandError) as CommandErrorException:
280 | exception_message = "Error while writing in the file"
281 |
282 | # Raise OSError during the file write operation
283 | mocked_builtins_open.return_value.write.side_effect = OSError(
284 | exception_message
285 | )
286 |
287 | # call the method to test
288 | app_generator.load_assets_from_local()
289 |
290 | # assert the message of the exception
291 | assert str(CommandErrorException.value) == exception_message
292 | # assert that shutil copy and open functions are called
293 | assert mocked_builtins_open.called == True
294 | assert mocked_shutil_copy.called == True
295 |
296 | @mock.patch("subprocess.Popen")
297 | @mock.patch("os.chdir")
298 | def test_install_dependencies(
299 | self,
300 | mocked_os_chdir,
301 | mocked_subprocess_popen,
302 | app_generator,
303 | ):
304 | """
305 | Function to test the test_install_dependencies method of the Generator Class
306 | """
307 |
308 | # create a queue object which is passed to a thread
309 | thread_queue = queue.Queue()
310 |
311 | # 0 means that command ran successfully
312 | command_return_code = 0
313 |
314 | # mocked the subprocess and set the returncode value to command_return_code
315 | mocked_subprocess_popen.return_value.returncode = command_return_code
316 |
317 | # mocked the subprocess and set the stdout value to some non empty output
318 | mocked_subprocess_popen.return_value.stdout = io.BytesIO(b"Command\nOutput")
319 |
320 | # mocked the subprocess and set the return value of poll method to None and then non-empty output
321 | mocked_subprocess_popen.return_value.poll.side_effect = [
322 | None,
323 | "Non-empty Output",
324 | ]
325 |
326 | # call the method to test
327 | app_generator.install_dependencies(thread_queue)
328 |
329 | # assert that os chdir method is called 2 times
330 | assert mocked_os_chdir.call_count == 2
331 | # assert that subprocess popen method is called 1 time
332 | assert mocked_subprocess_popen.call_count == 1
333 |
334 | # assert that the queue receives the value from subprocess Popen
335 | # and value equal to the command_return_code
336 | assert thread_queue.get() == command_return_code
337 |
338 | @mock.patch("queue.Queue")
339 | @mock.patch("threading.Thread")
340 | @mock.patch("progressbar.ProgressBar.update")
341 | def test_install_dependencies_and_show_progress_bar(
342 | self,
343 | mocked_progressbar_ProgressBar_update,
344 | mocked_threading_thread,
345 | mocked_queue_Queue,
346 | app_generator,
347 | ):
348 | """
349 | Function to test the install_dependencies_and_show_progress_bar method of the Generator Class
350 | """
351 |
352 | # different values for thread is_alive method
353 | thread_is_alive_values = [True, True, True, False]
354 |
355 | # set the mocked threading thread is_alive method with the thread_is_alive_values list
356 | mocked_threading_thread.return_value.is_alive.side_effect = (
357 | thread_is_alive_values
358 | )
359 |
360 | # set the mocked queue with the value 0
361 | # 0 implies installation was successful
362 | mocked_queue_Queue.return_value.get.return_value = 0
363 |
364 | # call the method to test
365 | app_generator.install_dependencies_and_show_progress_bar()
366 |
367 | # assert that the mocked threading thread was called 1 time
368 | assert mocked_threading_thread.call_count == 1
369 |
370 | # assert that the mocked progress bar was updated to the number
371 | # of values present in the thread_is_alive_values list
372 | assert mocked_progressbar_ProgressBar_update.call_count == len(
373 | thread_is_alive_values
374 | )
375 |
376 | # Reset the mocks
377 | mocked_threading_thread.reset_mock()
378 | mocked_queue_Queue.reset_mock()
379 | mocked_progressbar_ProgressBar_update.reset_mock()
380 |
381 | # set the mocked threading thread is_alive method with the thread_is_alive_values list
382 | mocked_threading_thread.return_value.is_alive.side_effect = (
383 | thread_is_alive_values
384 | )
385 |
386 | # set the mocked queue with the value 1
387 | # 1 implies installation was not successful
388 | mocked_queue_Queue.return_value.get.return_value = 1
389 |
390 | # Check if CommandError is raised when the method is called
391 | with pytest.raises(CommandError) as CommandErrorException:
392 | # call the method to test
393 | app_generator.install_dependencies_and_show_progress_bar()
394 |
395 | # assert that the mocked threading thread was called 1 time
396 | assert mocked_threading_thread.call_count == 1
397 |
398 | # assert that the mocked progress bar was updated to the number
399 | # of values present in the thread_is_alive_values list
400 | assert mocked_progressbar_ProgressBar_update.call_count == len(
401 | thread_is_alive_values
402 | )
403 |
404 | # assert the message of the exception
405 | assert (
406 | str(CommandErrorException.value)
407 | == constants.COMMAND_ERROR_MESSAGES_DICT["NPM_INSTALLATION_ERROR_MESSAGE"]
408 | )
409 |
410 | @mock.patch.object(
411 | Generator, "install_dependencies_and_show_progress_bar", return_value=None
412 | )
413 | @mock.patch.object(Generator, "load_assets_from_local", return_value=None)
414 | @mock.patch.object(Generator, "create_required_directories", return_value=None)
415 | @mock.patch("django.core.management.call_command")
416 | @mock.patch.object(Generator, "check_system_requirements", return_value=None)
417 | def test_generate_in_development_mode(
418 | self,
419 | mocked_generator_check_system_requirements,
420 | mocked_management_call_command,
421 | mocked_create_required_directories,
422 | mocked_load_assets_from_local,
423 | mocked_install_dependencies_and_show_progress_bar,
424 | app_generator,
425 | ):
426 | """
427 | Function to test the generate method of the Generator Class
428 | """
429 |
430 | # call the method to test
431 | app_generator.generate()
432 |
433 | # assert that all the methods of the Generator class are called
434 | assert mocked_generator_check_system_requirements.called == True
435 | assert mocked_create_required_directories.called == True
436 | mocked_management_call_command.assert_called_once_with(
437 | "startapp", app_generator.app_name
438 | )
439 | assert mocked_load_assets_from_local.called == True
440 | assert mocked_install_dependencies_and_show_progress_bar.called == True
441 |
442 | @mock.patch.object(
443 | Generator, "install_dependencies_and_show_progress_bar", return_value=None
444 | )
445 | @mock.patch.object(Generator, "download_template_files", return_value=None)
446 | @mock.patch.object(Generator, "create_required_directories", return_value=None)
447 | @mock.patch("django.core.management.call_command")
448 | @mock.patch.object(Generator, "check_system_requirements", return_value=None)
449 | def test_generate_in_production_mode(
450 | self,
451 | mocked_generator_check_system_requirements,
452 | mocked_management_call_command,
453 | mocked_create_required_directories,
454 | mocked_download_template_files,
455 | mocked_install_dependencies_and_show_progress_bar,
456 | app_generator,
457 | ):
458 | """
459 | Function to test the generate method of the Generator Class
460 | """
461 |
462 | # set the environment variable SOFTWARE_ENVIRONMENT_MODE to production
463 | # to test that the download_template_files method is called or not
464 |
465 | # create the monkeypatch object
466 | monkeypatch = pytest.MonkeyPatch()
467 |
468 | # set the environment variable SOFTWARE_ENVIRONMENT_MODE to production
469 | monkeypatch.setenv("SOFTWARE_ENVIRONMENT_MODE", "production")
470 |
471 | # assert that the environment variable has changed
472 | assert os.environ["SOFTWARE_ENVIRONMENT_MODE"] == "production"
473 |
474 | # call the method to test
475 | app_generator.generate()
476 |
477 | # assert that all the methods of the Generator class are called
478 | assert mocked_generator_check_system_requirements.called == True
479 | assert mocked_create_required_directories.called == True
480 | mocked_management_call_command.assert_called_once_with(
481 | "startapp", app_generator.app_name
482 | )
483 | assert mocked_download_template_files.called == True
484 | assert mocked_install_dependencies_and_show_progress_bar.called == True
485 |
--------------------------------------------------------------------------------
/tests/test_settings.py:
--------------------------------------------------------------------------------
1 | # add the django reusable app into the installed_apps list
2 | # By this the command can be tested
3 | INSTALLED_APPS = [
4 | "django_webpack_dev_server",
5 | ]
6 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist = py36,py37,py38,py39,py310
3 | skip_missing_interpreters = true
4 |
5 | [testenv]
6 | commands =
7 | coverage run -m pytest
8 | coverage xml
9 |
10 | deps = -rrequirements.txt
11 | setenv =
12 | SOFTWARE_ENVIRONMENT_MODE = development
--------------------------------------------------------------------------------