├── .github
└── workflows
│ └── label.yml
├── .gitignore
├── README.md
├── client
├── .gitignore
├── babel.config.js
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ └── index.html
├── src
│ ├── App.vue
│ ├── assets
│ │ └── logo.png
│ ├── components
│ │ ├── AppSubForm.vue
│ │ ├── FieldSubForm.vue
│ │ ├── FlexWrapper.vue
│ │ ├── Form.vue
│ │ ├── ModelSubForm.vue
│ │ ├── ProjectSubForm.vue
│ │ └── fields
│ │ │ ├── BooleanFieldSubForm.vue
│ │ │ ├── CharFieldSubForm.vue
│ │ │ ├── DateFieldSubForm.vue
│ │ │ ├── DateTimeFieldSubForm.vue
│ │ │ ├── DecimalFieldSubForm.vue
│ │ │ ├── EmailFieldSubForm.vue
│ │ │ ├── FileFieldSubForm.vue
│ │ │ ├── ForeignKeyFieldSubForm.vue
│ │ │ ├── NoArgFieldSubForm.vue
│ │ │ ├── NumberFieldSubForm.vue
│ │ │ ├── SlugFieldSubForm.vue
│ │ │ ├── TextFieldSubForm.vue
│ │ │ ├── TimeFieldSubForm.vue
│ │ │ └── URLFieldSubForm.vue
│ ├── main.js
│ ├── quasar.js
│ └── styles
│ │ ├── quasar.sass
│ │ └── quasar.variables.sass
└── vue.config.js
├── images
├── 1.png
├── 2.png
├── 3.png
└── 4.png
└── server
├── .dockerignore
├── .gitignore
├── Dockerfile
├── dist
├── css
│ ├── app.e820ce6b.css
│ └── chunk-vendors.c6f8963b.css
├── favicon.ico
├── fonts
│ ├── flUhRq6tzZclQEJ-Vdg-IuiaDsNa.463cfa6b.woff
│ └── flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.8ff0ce02.woff2
├── index.html
└── js
│ ├── app.012999eb.js
│ ├── app.012999eb.js.map
│ ├── chunk-vendors.3ebf6a6f.js
│ └── chunk-vendors.3ebf6a6f.js.map
├── nodemon.json
├── package.json
├── src
├── __test__
│ └── helper.test.ts
├── app.ts
├── bash-helper.model.ts
├── helper.ts
├── index.ts
├── models
│ ├── __test__
│ │ ├── app.model.test.ts
│ │ └── field.model.test.ts
│ ├── app.model.ts
│ ├── field.model.ts
│ ├── fields
│ │ ├── __test__
│ │ │ ├── no-arg-field.model.test.ts
│ │ │ └── number-field.model.test.ts
│ │ ├── boolean-field.model.ts
│ │ ├── decimal-field.model.ts
│ │ ├── dt-field.model.ts
│ │ ├── file-field.model.ts
│ │ ├── foreign-key-field.model.ts
│ │ ├── index.ts
│ │ ├── no-arg-field.model.ts
│ │ ├── number-field.model.ts
│ │ └── string-field.model.ts
│ ├── model.model.ts
│ └── project.model.ts
├── routes
│ ├── project.ts
│ └── status.ts
└── templates
│ ├── django-admin.ts
│ ├── django-api.ts
│ ├── django-app-urls.ts
│ ├── django-project-urls.ts
│ ├── django-requirements.ts
│ ├── django-serializers.ts
│ ├── django-settings.ts
│ ├── django-utils.ts
│ ├── docker-compose.ts
│ ├── docker.ts
│ └── index.ts
└── tsconfig.json
/.github/workflows/label.yml:
--------------------------------------------------------------------------------
1 | # This workflow will triage pull requests and apply a label based on the
2 | # paths that are modified in the pull request.
3 | #
4 | # To use this workflow, you will need to set up a .github/labeler.yml
5 | # file with configuration. For more information, see:
6 | # https://github.com/actions/labeler
7 |
8 | name: Labeler
9 | on: [pull_request]
10 |
11 | jobs:
12 | label:
13 |
14 | runs-on: ubuntu-latest
15 |
16 | steps:
17 | - uses: actions/labeler@v2
18 | with:
19 | repo-token: "${{ secrets.GITHUB_TOKEN }}"
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Django CRUD REST API Generator
2 |
3 | This is a simple tool that generates a Django REST API with the given models.
4 |
5 | Specs:
6 | - Authentication,
7 | - DRF generic views,
8 | - Routes,
9 | - PEP8,
10 | - Dockerized.
11 |
12 | You can test it out [here](https://django-apigen.herokuapp.com/).
13 |
14 | ### To run the generated Django apps
15 |
16 | Docker and docker-compose are required to run the generated apps.
17 |
18 | ```sh
19 | # Unzip the project at 'Downloads' or 'server/projects'.
20 | cd django_project # Go into the generated app
21 | docker-compose up # Run the containers
22 | ```
23 |
24 | ## Run local version
25 |
26 | Run the server.
27 | ```sh
28 | cd server
29 | npm i
30 | npm run dev
31 | ```
32 |
33 | Run the client.
34 | ```sh
35 | cd client
36 | npm i
37 | npm run serve
38 | ```
39 |
40 | ## Screenshots
41 | 1) Choose project and app parameters.
42 |
43 | 
44 |
45 | 2) Create app models with the form served by Vue.
46 |
47 | 
48 |
49 | 3) Review your models structure.
50 |
51 | 
52 |
53 | 4) All these will be generated by the app and can be downloaded.
54 |
55 | 
56 |
--------------------------------------------------------------------------------
/client/.gitignore:
--------------------------------------------------------------------------------
1 | dist
2 | node_modules
3 |
--------------------------------------------------------------------------------
/client/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "presets": [
3 | "@vue/cli-plugin-babel/preset"
4 | ],
5 | "plugins": [
6 | [
7 | "transform-imports",
8 | {
9 | "quasar": {
10 | "transform": "quasar/dist/babel-transforms/imports.js",
11 | "preventFullImport": true
12 | }
13 | }
14 | ]
15 | ]
16 | }
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "api-generator-vue-client",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "lint": "vue-cli-service lint",
9 | "test:unit": "vue-cli-service test:unit"
10 | },
11 | "dependencies": {
12 | "@quasar/extras": "^1.0.0",
13 | "@vue/test-utils": "^1.0.3",
14 | "axios": "^0.19.2",
15 | "core-js": "^3.6.4",
16 | "quasar": "^1.0.0",
17 | "vue": "^2.6.11",
18 | "vue-github-button": "^1.2.0"
19 | },
20 | "devDependencies": {
21 | "@vue/cli-plugin-babel": "~4.3.0",
22 | "@vue/cli-plugin-eslint": "~4.3.0",
23 | "@vue/cli-service": "~4.3.0",
24 | "babel-eslint": "^10.1.0",
25 | "babel-plugin-transform-imports": "1.5.0",
26 | "eslint": "^6.7.2",
27 | "eslint-plugin-vue": "^6.2.2",
28 | "node-sass": "^4.13.0",
29 | "sass-loader": "^8.0.0",
30 | "vue-cli-plugin-quasar": "~2.0.2",
31 | "vue-template-compiler": "^2.6.11"
32 | },
33 | "eslintConfig": {
34 | "root": true,
35 | "env": {
36 | "node": true
37 | },
38 | "extends": [
39 | "plugin:vue/essential",
40 | "eslint:recommended"
41 | ],
42 | "parserOptions": {
43 | "parser": "babel-eslint"
44 | },
45 | "rules": {}
46 | },
47 | "browserslist": [
48 | "> 1%",
49 | "last 2 versions",
50 | "not dead"
51 | ]
52 | }
53 |
--------------------------------------------------------------------------------
/client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mehmetalpsumer/django-rest-api-generator/9d0ee5266be57dc2a4460bf72097ae78e8cdbe7a/client/public/favicon.ico
--------------------------------------------------------------------------------
/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Django API Generator
8 |
9 |
10 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/client/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Django CRUD REST API Generator
7 |
8 |
9 |
10 | Star
19 |
20 | Issue
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/client/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mehmetalpsumer/django-rest-api-generator/9d0ee5266be57dc2a4460bf72097ae78e8cdbe7a/client/src/assets/logo.png
--------------------------------------------------------------------------------
/client/src/components/AppSubForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
14 |
15 |
16 |
17 |
18 |
19 |
36 |
--------------------------------------------------------------------------------
/client/src/components/FieldSubForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 | Field
10 |
11 |
12 |
19 |
20 |
21 |
22 |
30 |
31 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
50 |
54 |
58 |
59 |
60 |
64 |
68 |
69 |
70 |
76 |
77 |
78 |
84 |
90 |
96 |
102 |
103 |
107 |
108 |
109 |
113 |
114 |
118 |
119 |
123 |
124 |
128 |
132 |
133 |
137 |
138 |
139 |
140 |
220 |
--------------------------------------------------------------------------------
/client/src/components/FlexWrapper.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
18 |
--------------------------------------------------------------------------------
/client/src/components/Form.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
31 |
32 |
33 |
34 |
35 |
36 |
43 |
44 |
45 |
46 |
53 |
60 |
61 |
62 |
63 |
64 |
65 | Review your project settings.
66 |
67 |
68 |
73 |
74 |
75 |
76 |
83 |
90 |
91 |
92 |
93 |
94 |
95 | You can check the
96 | README on GitHub
101 | for the instructions.
102 |
103 |
104 |
105 |
106 |
244 |
--------------------------------------------------------------------------------
/client/src/components/ModelSubForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Model
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
20 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
76 |
--------------------------------------------------------------------------------
/client/src/components/ProjectSubForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
14 |
15 |
27 |
28 |
29 |
30 |
31 |
32 |
50 |
--------------------------------------------------------------------------------
/client/src/components/fields/BooleanFieldSubForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
14 |
15 |
16 |
17 |
18 |
36 |
--------------------------------------------------------------------------------
/client/src/components/fields/CharFieldSubForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
15 |
16 |
28 |
29 |
30 |
31 |
32 |
50 |
--------------------------------------------------------------------------------
/client/src/components/fields/DateFieldSubForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
29 |
--------------------------------------------------------------------------------
/client/src/components/fields/DateTimeFieldSubForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
29 |
--------------------------------------------------------------------------------
/client/src/components/fields/DecimalFieldSubForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
19 |
20 |
33 |
34 |
54 |
55 |
56 |
57 |
58 |
77 |
--------------------------------------------------------------------------------
/client/src/components/fields/EmailFieldSubForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
20 |
21 |
27 |
28 |
29 |
30 |
31 |
52 |
--------------------------------------------------------------------------------
/client/src/components/fields/FileFieldSubForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
22 |
23 |
24 |
25 |
26 |
43 |
--------------------------------------------------------------------------------
/client/src/components/fields/ForeignKeyFieldSubForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
13 |
14 |
15 |
16 |
56 |
--------------------------------------------------------------------------------
/client/src/components/fields/NoArgFieldSubForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
14 |
--------------------------------------------------------------------------------
/client/src/components/fields/NumberFieldSubForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
21 |
22 |
23 |
24 |
25 |
43 |
--------------------------------------------------------------------------------
/client/src/components/fields/SlugFieldSubForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
17 |
18 |
29 |
30 |
31 |
32 |
33 |
54 |
--------------------------------------------------------------------------------
/client/src/components/fields/TextFieldSubForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
14 |
15 |
16 |
17 |
18 |
35 |
--------------------------------------------------------------------------------
/client/src/components/fields/TimeFieldSubForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
29 |
--------------------------------------------------------------------------------
/client/src/components/fields/URLFieldSubForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
17 |
18 |
24 |
25 |
26 |
27 |
28 |
49 |
--------------------------------------------------------------------------------
/client/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 | import './quasar'
4 |
5 | Vue.config.productionTip = false
6 |
7 | new Vue({
8 | render: h => h(App),
9 | }).$mount('#app')
10 |
--------------------------------------------------------------------------------
/client/src/quasar.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | import './styles/quasar.sass'
4 | import '@quasar/extras/material-icons/material-icons.css'
5 | import { Quasar } from 'quasar'
6 |
7 | Vue.use(Quasar, {
8 | config: {},
9 | components: { /* not needed if importStrategy is not 'manual' */ },
10 | directives: { /* not needed if importStrategy is not 'manual' */ },
11 | plugins: {
12 | }
13 | })
--------------------------------------------------------------------------------
/client/src/styles/quasar.sass:
--------------------------------------------------------------------------------
1 | @import './quasar.variables.sass'
2 | @import '~quasar-styl'
3 | // @import '~quasar-addon-styl'
4 |
--------------------------------------------------------------------------------
/client/src/styles/quasar.variables.sass:
--------------------------------------------------------------------------------
1 | // It's highly recommended to change the default colors
2 | // to match your app's branding.
3 |
4 | $primary : #027BE3
5 | $secondary : #26A69A
6 | $accent : #9C27B0
7 |
8 | $dark : #1D1D1D
9 |
10 | $positive : #21BA45
11 | $negative : #C10015
12 | $info : #31CCEC
13 | $warning : #F2C037
14 |
15 | @import '~quasar-variables-styl'
16 |
--------------------------------------------------------------------------------
/client/vue.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | pluginOptions: {
3 | quasar: {
4 | importStrategy: 'kebab',
5 | rtlSupport: false
6 | }
7 | },
8 | transpileDependencies: [
9 | 'quasar'
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/images/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mehmetalpsumer/django-rest-api-generator/9d0ee5266be57dc2a4460bf72097ae78e8cdbe7a/images/1.png
--------------------------------------------------------------------------------
/images/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mehmetalpsumer/django-rest-api-generator/9d0ee5266be57dc2a4460bf72097ae78e8cdbe7a/images/2.png
--------------------------------------------------------------------------------
/images/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mehmetalpsumer/django-rest-api-generator/9d0ee5266be57dc2a4460bf72097ae78e8cdbe7a/images/3.png
--------------------------------------------------------------------------------
/images/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mehmetalpsumer/django-rest-api-generator/9d0ee5266be57dc2a4460bf72097ae78e8cdbe7a/images/4.png
--------------------------------------------------------------------------------
/server/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/server/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | node_modules
3 | build
4 | package-lock.json
5 | projects
6 |
--------------------------------------------------------------------------------
/server/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:12
2 |
3 | RUN apt-get update && apt-get install -y python python-pip zip && pip install Django && pip install autopep8
4 |
5 | WORKDIR /app
6 | COPY package.json /app
7 | RUN npm install
8 | COPY . /app
9 | RUN npm run build
10 |
11 | CMD ["node", "build/index.js"]
--------------------------------------------------------------------------------
/server/dist/css/chunk-vendors.c6f8963b.css:
--------------------------------------------------------------------------------
1 | @font-face{font-family:Material Icons;font-style:normal;font-weight:400;font-display:block;src:url(../fonts/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.8ff0ce02.woff2) format("woff2"),url(../fonts/flUhRq6tzZclQEJ-Vdg-IuiaDsNa.463cfa6b.woff) format("woff")}.material-icons{font-family:Material Icons;font-weight:400;font-style:normal;display:inline-block;line-height:1;text-transform:none;letter-spacing:normal;word-wrap:normal;white-space:nowrap;direction:ltr;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;-moz-osx-font-smoothing:grayscale;font-feature-settings:"liga"}
--------------------------------------------------------------------------------
/server/dist/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mehmetalpsumer/django-rest-api-generator/9d0ee5266be57dc2a4460bf72097ae78e8cdbe7a/server/dist/favicon.ico
--------------------------------------------------------------------------------
/server/dist/fonts/flUhRq6tzZclQEJ-Vdg-IuiaDsNa.463cfa6b.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mehmetalpsumer/django-rest-api-generator/9d0ee5266be57dc2a4460bf72097ae78e8cdbe7a/server/dist/fonts/flUhRq6tzZclQEJ-Vdg-IuiaDsNa.463cfa6b.woff
--------------------------------------------------------------------------------
/server/dist/fonts/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.8ff0ce02.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mehmetalpsumer/django-rest-api-generator/9d0ee5266be57dc2a4460bf72097ae78e8cdbe7a/server/dist/fonts/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.8ff0ce02.woff2
--------------------------------------------------------------------------------
/server/dist/index.html:
--------------------------------------------------------------------------------
1 | Django API Generator
--------------------------------------------------------------------------------
/server/dist/js/app.012999eb.js:
--------------------------------------------------------------------------------
1 | (function(e){function t(t){for(var a,o,s=t[0],r=t[1],u=t[2],c=0,m=[];c1}},[l("ProjectSubForm",{ref:"projectSubForm"}),l("q-stepper-navigation",[l("q-btn",{attrs:{color:"primary",label:"Continue"},on:{click:e.toNextStep}})],1)],1),l("q-step",{attrs:{name:2,title:"App",icon:"create_new_folder",done:e.step>2}},[l("AppSubForm",{ref:"appSubForm"}),l("q-stepper-navigation",[l("q-btn",{attrs:{color:"primary",label:"Continue"},on:{click:e.toNextStep}}),l("q-btn",{staticClass:"q-ml-sm",attrs:{flat:"",color:"primary",label:"Back"},on:{click:e.toPrevStep}})],1)],1),l("q-step",{attrs:{name:3,title:"Models",icon:"assignment",done:e.step>3}},[e._l(e.models,(function(t){return l("ModelSubForm",{key:t,ref:"modelSubForms",refInFor:!0,attrs:{id:t},on:{removeModelClicked:function(l){return e.removeModel(t)}}})})),l("q-stepper-navigation",[l("q-btn",{attrs:{color:"primary",label:"Continue"},on:{click:e.toNextStep}}),l("q-btn",{staticClass:"q-ml-sm",attrs:{flat:"",color:"primary",label:"Add model"},on:{click:e.addModel}}),l("q-btn",{staticClass:"q-ml-sm",attrs:{flat:"",color:"primary",label:"Back"},on:{click:e.toPrevStep}})],1)],2),l("q-step",{attrs:{name:4,title:"Review & Complete",icon:"add_comment"}},[e._v(" Review your project settings. "),l("div",{staticClass:"q-pa-md q-gutter-sm"},[l("q-tree",{attrs:{"default-expand-all":!0,nodes:e.treeData,"node-key":"label"}})],1),l("q-stepper-navigation",[l("q-btn",{attrs:{color:"primary",label:"Finish",loading:e.submitted,disable:e.submitted},on:{click:e.submit}}),l("q-btn",{staticClass:"q-ml-sm",attrs:{flat:"",color:"primary",label:"Back"},on:{click:function(t){e.step=3}}})],1)],1)],1),e._m(0)],1)},u=[function(){var e=this,t=e.$createElement,l=e._self._c||t;return l("p",[e._v(" You can check the "),l("a",{attrs:{href:"https://github.com/mehmetalpsumer/django-rest-api-generator/blob/master/README.md",target:"_blank"}},[e._v("README on GitHub")]),e._v(" for the instructions. ")])}],d=(l("99af"),l("c975"),l("d81d"),l("a434"),l("b0c0"),l("d3b7"),l("3ca3"),l("ddb0"),l("2b3d"),l("96cf"),l("1da1")),c=l("bc3a"),m=l.n(c),f=function(){var e=this,t=e.$createElement,l=e._self._c||t;return l("div",{staticClass:"q-pa-md",staticStyle:{"max-width":"400px"}},[l("q-form",{staticClass:"q-gutter-md"},[l("q-input",{attrs:{filled:"",label:"Project name",hint:"Name of the Django project","lazy-rules":"",rules:[function(e){return e&&e.length>0||"Please type something"}]},model:{value:e.project.name,callback:function(t){e.$set(e.project,"name",t)},expression:"project.name"}}),l("q-input",{attrs:{filled:"",type:"number",label:"Port",hint:"Port that Django will run on","lazy-rules":"",rules:[function(e){return null!==e&&""!==e||"Please type something"},function(e){return e>1024&&e<65535||"Please type a valid port number"}]},model:{value:e.project.port,callback:function(t){e.$set(e.project,"port",t)},expression:"project.port"}})],1)],1)},p=[],b={data:function(){return{project:{name:"my_project",port:8e3}}},methods:{getData:function(){return this.project}}},h=b,F=l("2877"),g=l("fe09"),v=Object(F["a"])(h,f,p,!1,null,null,null),x=v.exports;v.options.components=Object.assign(Object.create(v.options.components||null),v.options.components||{},{QForm:g["d"],QInput:g["f"]});var y=function(){var e=this,t=e.$createElement,l=e._self._c||t;return l("div",{staticClass:"q-pa-md",staticStyle:{"max-width":"400px"}},[l("q-form",{staticClass:"q-gutter-md"},[l("q-input",{attrs:{filled:"",label:"App name",hint:"Name of the Django app","lazy-rules":"",rules:[function(e){return e&&e.length>0||"Please type something"}]},model:{value:e.app.name,callback:function(t){e.$set(e.app,"name",t)},expression:"app.name"}})],1)],1)},D=[],q={data:function(){return{app:{name:"my_app"}}},methods:{getData:function(){return this.app}}},k=q,j=Object(F["a"])(k,y,D,!1,null,null,null),S=j.exports;j.options.components=Object.assign(Object.create(j.options.components||null),j.options.components||{},{QForm:g["d"],QInput:g["f"]});var _=function(){var e=this,t=e.$createElement,l=e._self._c||t;return l("div",{staticStyle:{"margin-bottom":"30px"}},[l("q-card-section",[l("div",{staticClass:"text-h6"},[l("div",{staticClass:"row justify-between"},[l("div",{staticClass:"col-4"},[e._v(" Model ")]),l("div",{staticClass:"col-8"},[l("q-btn",{staticStyle:{float:"right"},attrs:{size:"sm",flat:"",icon:"close",label:"Remove model"},on:{click:e.onRemoveModel}}),l("q-btn",{staticStyle:{float:"right"},attrs:{size:"sm",flat:"",icon:"add",label:"Add field"},on:{click:e.addField}})],1)])])]),l("q-card-section",{staticClass:"q-pt-none"},[l("q-form",{staticClass:"q-gutter-md"},[l("q-input",{attrs:{filled:"",label:"Model name","lazy-rules":"",hint:"Recomended to separate words with spaces.",rules:[function(e){return e&&e.length>0||"Please type something"}]},model:{value:e.model.name,callback:function(t){e.$set(e.model,"name",t)},expression:"model.name"}})],1)],1),l("q-separator",{attrs:{inset:""}}),e._l(e.fields,(function(t){return l("FieldSubForm",{key:t,ref:"fields",refInFor:!0,attrs:{id:t},on:{removeFieldClicked:function(l){return e.removeField(t)}}})}))],2)},O=[],C=function(){var e=this,t=e.$createElement,l=e._self._c||t;return l("q-form",{staticClass:"q-gutter-md",staticStyle:{"margin-left":"50px","margin-top":"10px","margin-right":"10px"}},[l("div",{staticClass:"text-h6"},[l("div",{staticClass:"row justify-between"},[l("div",{staticClass:"col-4"},[e._v(" Field ")]),l("div",{staticClass:"col-4"},[l("q-btn",{staticStyle:{float:"right"},attrs:{flat:"",size:"sm",icon:"close"},on:{click:e.onRemoveField}})],1)])]),l("q-input",{attrs:{filled:"",label:"Field name",hint:"Recommended to use snake_case.","lazy-rules":"",rules:[function(e){return e&&e.length>0||"Please type something"}]},model:{value:e.field.name,callback:function(t){e.$set(e.field,"name",t)},expression:"field.name"}}),l("q-select",{attrs:{options:e.fieldTypes,label:"Field Type"},model:{value:e.field.fieldType,callback:function(t){e.$set(e.field,"fieldType",t)},expression:"field.fieldType"}}),l("div",{staticClass:"q-gutter-sm"},[l("q-checkbox",{attrs:{label:"Can be null"},model:{value:e.field.isNull,callback:function(t){e.$set(e.field,"isNull",t)},expression:"field.isNull"}}),l("q-checkbox",{attrs:{label:"Can be blank"},model:{value:e.field.isBlank,callback:function(t){e.$set(e.field,"isBlank",t)},expression:"field.isBlank"}}),l("q-checkbox",{attrs:{label:"Is unique"},model:{value:e.field.isUnique,callback:function(t){e.$set(e.field,"isUnique",t)},expression:"field.isUnique"}}),l("q-checkbox",{attrs:{label:"Is PK"},model:{value:e.field.isPrimaryKey,callback:function(t){e.$set(e.field,"isPrimaryKey",t)},expression:"field.isPrimaryKey"}})],1),"AutoField"===e.field.fieldType?l("NoArgFieldSubForm",{ref:"AutoField"}):e._e(),"BigAutoField"===e.field.fieldType?l("NoArgFieldSubForm",{ref:"BigAutoField"}):e._e(),"DurationField"===e.field.fieldType?l("NoArgFieldSubForm",{ref:"DurationField"}):"SmallAutoField"===e.field.fieldType?l("NoArgFieldSubForm",{ref:"SmallAutoField"}):e._e(),"DateField"===e.field.fieldType?l("DateFieldSubForm",{ref:"DateField"}):e._e(),"DateTimeField"===e.field.fieldType?l("DateFieldSubForm",{ref:"DateTimeField"}):"TimeField"===e.field.fieldType?l("DateFieldSubForm",{ref:"TimeField"}):"BigIntegerField"===e.field.fieldType?l("NumberFieldSubForm",{ref:"BigIntegerField",attrs:{min:"-9223372036854775808",max:"9223372036854775807"}}):"IntegerField"===e.field.fieldType?l("NumberFieldSubForm",{ref:"IntegerField",attrs:{min:"-2147483648",max:"2147483647"}}):"PositiveIntegerField"===e.field.fieldType?l("NumberFieldSubForm",{ref:"PositiveIntegerField",attrs:{min:"0",max:"2147483647"}}):"PositiveSmallIntegerField"===e.field.fieldType?l("NumberFieldSubForm",{ref:"PositiveSmallIntegerField",attrs:{min:"0",max:"32767"}}):"SmallIntegerField"===e.field.fieldType?l("NumberFieldSubForm",{ref:"SmallIntegerField",attrs:{min:"-32768",max:"32767"}}):"BooleanField"===e.field.fieldType?l("BooleanFieldSubForm",{ref:"BooleanField"}):e._e(),"CharField"===e.field.fieldType?l("CharFieldSubForm",{ref:"CharField"}):e._e(),"DecimalField"===e.field.fieldType?l("DecimalFieldSubForm",{ref:"DecimalField"}):e._e(),"EmailField"===e.field.fieldType?l("EmailFieldSubForm",{ref:"EmailField"}):e._e(),"FileField"===e.field.fieldType?l("FileFieldSubForm",{ref:"FileField"}):"ForeignKeyField"===e.field.fieldType?l("ForeignKeyFieldSubForm",{ref:"ForeignKeyField"}):"SlugField"===e.field.fieldType?l("SlugFieldSubForm",{ref:"SlugField"}):"TextField"===e.field.fieldType?l("TextFieldSubForm",{ref:"TextField"}):"URLField"===e.field.fieldType?l("URLFieldSubForm",{ref:"URLField"}):e._e()],1)},$=[],T=l("5530"),w=function(){var e=this,t=e.$createElement,l=e._self._c||t;return l("div",[l("q-form",{staticClass:"q-gutter-md"},[l("div",{staticClass:"q-gutter-sm"},[l("q-checkbox",{attrs:{label:"Has default?"},model:{value:e.booleanField.hasDefault,callback:function(t){e.$set(e.booleanField,"hasDefault",t)},expression:"booleanField.hasDefault"}})],1),l("q-select",{attrs:{options:e.boolOptions,disable:!e.booleanField.hasDefault,label:"Default value"},model:{value:e.booleanField.defaultValue,callback:function(t){e.$set(e.booleanField,"defaultValue",t)},expression:"booleanField.defaultValue"}})],1)],1)},Q=[],P={data:function(){return{booleanField:{defaultValue:"True",hasDefault:!1},boolOptions:["True","False"]}},methods:{getData:function(){return this.booleanField}}},M=P,E=Object(F["a"])(M,w,Q,!1,null,null,null),L=E.exports;E.options.components=Object.assign(Object.create(E.options.components||null),E.options.components||{},{QForm:g["d"],QCheckbox:g["c"],QSelect:g["j"]});var I=function(){var e=this,t=e.$createElement,l=e._self._c||t;return l("div",[l("q-form",{staticClass:"q-gutter-md"},[l("div",{staticClass:"q-gutter-sm"},[l("q-checkbox",{attrs:{label:"Has default?"},model:{value:e.charField.hasDefault,callback:function(t){e.$set(e.charField,"hasDefault",t)},expression:"charField.hasDefault"}})],1),l("q-input",{attrs:{filled:"",label:"Default value",disable:!e.charField.hasDefault,maxlength:e.charField.maxLength},model:{value:e.charField.defaultValue,callback:function(t){e.$set(e.charField,"defaultValue",t)},expression:"charField.defaultValue"}}),l("q-input",{attrs:{filled:"",type:"number",label:"Max length","lazy-rules":"",rules:[function(e){return null!==e&&""!==e||"Max length cannot be blank."},function(e){return e>0||"Max length cannot be negative."}]},model:{value:e.charField.maxLength,callback:function(t){e.$set(e.charField,"maxLength",t)},expression:"charField.maxLength"}})],1)],1)},A=[],N={data:function(){return{charField:{maxLength:32,defaultValue:"",hasDefault:!1}}},methods:{getData:function(){return this.charField}}},V=N,R=Object(F["a"])(V,I,A,!1,null,null,null),B=R.exports;R.options.components=Object.assign(Object.create(R.options.components||null),R.options.components||{},{QForm:g["d"],QCheckbox:g["c"],QInput:g["f"]});var z=function(){var e=this,t=e.$createElement,l=e._self._c||t;return l("div",[l("q-form",{staticClass:"q-gutter-md"},[l("div",{staticClass:"q-gutter-sm"},[l("q-checkbox",{attrs:{label:"auto_now"},model:{value:e.dateField.autoNow,callback:function(t){e.$set(e.dateField,"autoNow",t)},expression:"dateField.autoNow"}}),l("q-checkbox",{attrs:{label:"auto_now_add"},model:{value:e.dateField.autoNowAdd,callback:function(t){e.$set(e.dateField,"autoNowAdd",t)},expression:"dateField.autoNowAdd"}})],1)])],1)},U=[],K={data:function(){return{dateField:{autoNow:!1,autoNowAdd:!1}}},methods:{getData:function(){return this.dateField}}},H=K,G=Object(F["a"])(H,z,U,!1,null,null,null),J=G.exports;G.options.components=Object.assign(Object.create(G.options.components||null),G.options.components||{},{QForm:g["d"],QCheckbox:g["c"]});var W=function(){var e=this,t=e.$createElement,l=e._self._c||t;return l("div",[l("q-form",{staticClass:"q-gutter-md"},[l("div",{staticClass:"q-gutter-sm"},[l("q-checkbox",{attrs:{label:"Has default?"},model:{value:e.decimalField.hasDefault,callback:function(t){e.$set(e.decimalField,"hasDefault",t)},expression:"decimalField.hasDefault"}})],1),l("q-input",{attrs:{filled:"",type:"number",label:"Max digits","lazy-rules":"",rules:[function(e){return null!==e&&""!==e||"Max digits cannot be blank."},function(e){return e>0||"Max digits cannot be negative."}]},model:{value:e.decimalField.maxDigits,callback:function(t){e.$set(e.decimalField,"maxDigits",t)},expression:"decimalField.maxDigits"}}),l("q-input",{attrs:{filled:"",type:"number",label:"Decimal places","lazy-rules":"",rules:[function(e){return null!==e&&""!==e||"Decimal places cannot be blank."},function(e){return e>0||"Decimal places cannot be negative."},function(t){return t0||"Max length cannot be negative."}]},model:{value:e.fileField.maxLength,callback:function(t){e.$set(e.fileField,"maxLength",t)},expression:"fileField.maxLength"}})],1)],1)},ue=[],de={data:function(){return{fileField:{maxLength:100,uploadTo:"uploads/"}}},methods:{getData:function(){return this.fileField}}},ce=de,me=Object(F["a"])(ce,re,ue,!1,null,null,null),fe=me.exports;me.options.components=Object.assign(Object.create(me.options.components||null),me.options.components||{},{QForm:g["d"],QInput:g["f"]});var pe=function(){var e=this,t=e.$createElement,l=e._self._c||t;return l("q-form",[l("q-select",{attrs:{options:e.otherModels,label:"Foreign key to"},model:{value:e.foreignKeyField.model,callback:function(t){e.$set(e.foreignKeyField,"model",t)},expression:"foreignKeyField.model"}}),l("q-select",{attrs:{options:e.onDeleteOptions,label:"On delete"},model:{value:e.foreignKeyField.onDelete,callback:function(t){e.$set(e.foreignKeyField,"onDelete",t)},expression:"foreignKeyField.onDelete"}})],1)},be=[],he={data:function(){return{foreignKeyField:{model:"",onDelete:"models.SET_NULL"},onDeleteOptions:["models.SET_NULL","models.SET_DEFAULT","models.CASCADE"]}},computed:{otherModels:function(){var e=this.getParent("MainForm");return e?e.getModelNames():[]}},methods:{getParent:function(e){var t=this.$parent;while("undefined"!==typeof t){if(t.$options.name===e)return t;t=t.$parent}return!1},getData:function(){return this.foreignKeyField}}},Fe=he,ge=Object(F["a"])(Fe,pe,be,!1,null,null,null),ve=ge.exports;ge.options.components=Object.assign(Object.create(ge.options.components||null),ge.options.components||{},{QForm:g["d"],QSelect:g["j"]});var xe=function(){var e=this,t=e.$createElement,l=e._self._c||t;return l("div",[l("q-form",{staticClass:"q-gutter-md"},[l("div",{staticClass:"q-gutter-sm"},[l("q-checkbox",{attrs:{label:"Has default?"},model:{value:e.slugField.hasDefault,callback:function(t){e.$set(e.slugField,"hasDefault",t)},expression:"slugField.hasDefault"}})],1),l("q-input",{attrs:{filled:"",disable:!e.slugField.hasDefault,maxlength:e.slugField.maxLength,label:"Default value","lazy-rules":"",rules:[function(t){return e.isSlug(t)||"Please enter a valid slug"}]},model:{value:e.slugField.defaultValue,callback:function(t){e.$set(e.slugField,"defaultValue",t)},expression:"slugField.defaultValue"}}),l("q-input",{attrs:{filled:"",type:"number",label:"Max length","lazy-rules":"",rules:[function(e){return null!==e&&""!==e||"Please type something"},function(e){return e>0||"Please type a valid length"}]},model:{value:e.slugField.maxLength,callback:function(t){e.$set(e.slugField,"maxLength",t)},expression:"slugField.maxLength"}})],1)],1)},ye=[],De=(l("4d63"),l("ac1f"),l("25f0"),{data:function(){return{slugField:{maxLength:50,defaultValue:"",hasDefault:!1}}},methods:{getData:function(){return this.slugField},isSlug:function(e){return new RegExp("[a-zA-Z0-9_-]").test(e)}}}),qe=De,ke=Object(F["a"])(qe,xe,ye,!1,null,null,null),je=ke.exports;ke.options.components=Object.assign(Object.create(ke.options.components||null),ke.options.components||{},{QForm:g["d"],QCheckbox:g["c"],QInput:g["f"]});var Se=function(){var e=this,t=e.$createElement,l=e._self._c||t;return l("div",[l("q-form",{staticClass:"q-gutter-md"},[l("div",{staticClass:"q-gutter-sm"},[l("q-checkbox",{attrs:{label:"Has default?"},model:{value:e.textField.hasDefault,callback:function(t){e.$set(e.textField,"hasDefault",t)},expression:"textField.hasDefault"}})],1),l("q-input",{attrs:{filled:"",disable:!e.textField.hasDefault,label:"Default value"},model:{value:e.textField.defaultValue,callback:function(t){e.$set(e.textField,"defaultValue",t)},expression:"textField.defaultValue"}})],1)],1)},_e=[],Oe={data:function(){return{textField:{defaultValue:"",hasDefault:!1}}},methods:{getData:function(){return this.textField}}},Ce=Oe,$e=Object(F["a"])(Ce,Se,_e,!1,null,null,null),Te=$e.exports;$e.options.components=Object.assign(Object.create($e.options.components||null),$e.options.components||{},{QForm:g["d"],QCheckbox:g["c"],QInput:g["f"]});var we=function(){var e=this,t=e.$createElement,l=e._self._c||t;return l("div",[l("q-form",{staticClass:"q-gutter-md"},[l("div",{staticClass:"q-gutter-sm"},[l("q-checkbox",{attrs:{label:"Has default?"},model:{value:e.urlField.hasDefault,callback:function(t){e.$set(e.urlField,"hasDefault",t)},expression:"urlField.hasDefault"}})],1),l("q-input",{attrs:{filled:"",disable:!e.urlField.hasDefault,maxlength:e.urlField.maxLength,label:"Default value","lazy-rules":"",rules:[function(t){return e.isUrl(t)||"Default value must be a valid URL."}]},model:{value:e.urlField.defaultValue,callback:function(t){e.$set(e.urlField,"defaultValue",t)},expression:"urlField.defaultValue"}}),l("q-input",{attrs:{filled:"",type:"number",label:"Max length"},model:{value:e.urlField.maxLength,callback:function(t){e.$set(e.urlField,"maxLength",t)},expression:"urlField.maxLength"}})],1)],1)},Qe=[],Pe={data:function(){return{urlField:{maxLength:200,defaultValue:"",hasDefault:!1}}},methods:{getData:function(){return this.urlField},isUrl:function(e){return/^(ftp|http|https):\/\/[^ "]+$/.test(e)}}},Me=Pe,Ee=Object(F["a"])(Me,we,Qe,!1,null,null,null),Le=Ee.exports;Ee.options.components=Object.assign(Object.create(Ee.options.components||null),Ee.options.components||{},{QForm:g["d"],QCheckbox:g["c"],QInput:g["f"]});var Ie=function(){var e=this,t=e.$createElement,l=e._self._c||t;return l("div")},Ae=[],Ne={methods:{getData:function(){return{}}}},Ve=Ne,Re=Object(F["a"])(Ve,Ie,Ae,!1,null,null,null),Be=Re.exports,ze=function(){var e=this,t=e.$createElement,l=e._self._c||t;return l("div",[l("q-form",{staticClass:"q-gutter-md"},[l("div",{staticClass:"q-gutter-sm"},[l("q-checkbox",{attrs:{label:"Has default?"},model:{value:e.numberField.hasDefault,callback:function(t){e.$set(e.numberField,"hasDefault",t)},expression:"numberField.hasDefault"}})],1),l("q-input",{attrs:{disable:!e.numberField.hasDefault,filled:"",type:"number",label:"Default value","lazy-rules":"",hint:"Values must be between "+e.min+" and "+e.max+".",rules:[function(t){return t>=e.min||"This number is too small!"},function(t){return t<=e.max||"This number is too big!"}]},model:{value:e.numberField.defaultValue,callback:function(t){e.$set(e.numberField,"defaultValue",t)},expression:"numberField.defaultValue"}})],1)],1)},Ue=[],Ke={props:["min","max"],data:function(){return{numberField:{defaultValue:null,hasDefault:!1}}},methods:{getData:function(){return this.numberField}}},He=Ke,Ge=Object(F["a"])(He,ze,Ue,!1,null,null,null),Je=Ge.exports;Ge.options.components=Object.assign(Object.create(Ge.options.components||null),Ge.options.components||{},{QForm:g["d"],QCheckbox:g["c"],QInput:g["f"]});var We={components:{BooleanFieldSubForm:L,CharFieldSubForm:B,DecimalFieldSubForm:te,EmailFieldSubForm:se,FileFieldSubForm:fe,ForeignKeyFieldSubForm:ve,SlugFieldSubForm:je,TextFieldSubForm:Te,URLFieldSubForm:Le,NoArgFieldSubForm:Be,DateFieldSubForm:J,NumberFieldSubForm:Je},props:["id"],data:function(){return{field:{name:"my_field",fieldType:"AutoField",isUnique:!1,isNull:!1,isBlank:!1,isPrimaryKey:!1},fieldTypes:["AutoField","BigAutoField","BigIntegerField","BooleanField","CharField","DateField","DateTimeField","DecimalField","DurationField","EmailField","FileField","ForeignKeyField","IntegerField","PositiveIntegerField","PositiveSmallIntegerField","SlugField","SmallAutoField","SmallIntegerField","TextField","TimeField","URLField"]}},methods:{onRemoveField:function(){this.$emit("removeFieldClicked",this.id)},getData:function(){var e=this.field.fieldType;if(this.$refs[e])return Object(T["a"])({commonOptions:this.field},this.$refs[e].getData())}}},Ye=We,Ze=Object(F["a"])(Ye,C,$,!1,null,null,null),Xe=Ze.exports;Ze.options.components=Object.assign(Object.create(Ze.options.components||null),Ze.options.components||{},{QForm:g["d"],QBtn:g["a"],QInput:g["f"],QSelect:g["j"],QCheckbox:g["c"]});var et={components:{FieldSubForm:Xe},props:["id"],data:function(){return{model:{name:"My Model"},fields:[],fieldCounter:0}},methods:{addField:function(){this.fields.push(this.fieldCounter++)},removeField:function(e){var t=this.fields.indexOf(e);this.fields.splice(t,1)},onRemoveModel:function(){this.$emit("removeModelClicked",this.id)},getData:function(){var e=this.model;return e["fields"]=this.$refs.fields.map((function(e){return e.getData()})),e}},mounted:function(){this.addField()}},tt=et,lt=Object(F["a"])(tt,_,O,!1,null,null,null),at=lt.exports;lt.options.components=Object.assign(Object.create(lt.options.components||null),lt.options.components||{},{QCardSection:g["b"],QBtn:g["a"],QForm:g["d"],QInput:g["f"],QSeparator:g["k"]}),console.log();var nt={name:"MainForm",data:function(){return{host:"https://django-apigen.herokuapp.com",submitted:!1,step:1,models:[],modelCounter:0,formData:{project:{},app:{},models:[]},treeData:[]}},components:{ProjectSubForm:x,AppSubForm:S,ModelSubForm:at},methods:{toNextStep:function(){this.updateData(),this.step=Math.min(++this.step,4)},toPrevStep:function(){this.updateData(),this.step=Math.max(--this.step,1)},updateData:function(){var e;switch(this.step){case 1:e=this.$refs.projectSubForm,this.formData.project=e.getData();break;case 2:e=this.$refs.appSubForm,this.formData.app=e.getData();break;case 3:e=this.$refs.modelSubForms,this.formData.models=e.map((function(e){return e.getData()}));break;default:break}this.updateTreeData()},updateTreeData:function(){var e=this.getJsonData();this.treeData=[{label:e.name,children:[{label:e.app.name,children:e.app.models.map((function(e){return{label:e.name,children:e.fields.map((function(e){return{label:"".concat(e.commonOptions.name," [").concat(e.commonOptions.fieldType,"]")}}))}}))}]}]},addModel:function(){this.models.push(this.modelCounter++)},removeModel:function(e){var t=this.models.indexOf(e);this.models.splice(t,1)},getModelNames:function(){var e=this.$refs.modelSubForms;return e.map((function(e){return e.getData().name}))},getJsonData:function(){var e=this.formData.project;if(e)return e.app=this.formData.app,e.app.models=this.formData.models,e},submit:function(){var e=Object(d["a"])(regeneratorRuntime.mark((function e(){var t,l,a,n,i,o;return regeneratorRuntime.wrap((function(e){while(1)switch(e.prev=e.next){case 0:return t=this.getJsonData(),this.submitted=!0,e.prev=2,l=this.host+"/api/project",e.next=6,m()({method:"POST",url:l,data:t,responseType:"blob"});case 6:a=e.sent,n=window.URL.createObjectURL(new Blob([a.data])),i=document.createElement("a"),i.href=n,i.setAttribute("download","project.zip"),document.body.appendChild(i),i.click(),this.submitted=!1,e.next=22;break;case 16:e.prev=16,e.t0=e["catch"](2),this.submitted=!1,o="\n An error occurred! :(\n\n Please consider submitting a GitHub issue, thanks.\n\n ".concat(e.t0),alert(o),console.error(e.t0);case 22:case"end":return e.stop()}}),e,this,[[2,16]])})));function t(){return e.apply(this,arguments)}return t}()},mounted:function(){this.addModel()}},it=nt,ot=Object(F["a"])(it,r,u,!1,null,null,null),st=ot.exports;ot.options.components=Object.assign(Object.create(ot.options.components||null),ot.options.components||{},{QStepper:g["m"],QStep:g["l"],QStepperNavigation:g["n"],QBtn:g["a"],QTree:g["q"]});var rt={name:"FlexWrapper",components:{Form:st}},ut=rt,dt=Object(F["a"])(ut,o,s,!1,null,null,null),ct=dt.exports;dt.options.components=Object.assign(Object.create(dt.options.components||null),dt.options.components||{},{QPage:g["h"]});var mt=l("e878"),ft={name:"LayoutDefault",components:{GithubButton:mt["a"],FlexWrapper:ct}},pt=ft,bt=Object(F["a"])(pt,n,i,!1,null,null,null),ht=bt.exports;bt.options.components=Object.assign(Object.create(bt.options.components||null),bt.options.components||{},{QLayout:g["g"],QHeader:g["e"],QToolbar:g["o"],QToolbarTitle:g["p"],QPageContainer:g["i"]});l("c867"),l("e54f");var Ft=l("b05d");a["a"].use(Ft["a"],{config:{},components:{},directives:{},plugins:{}}),a["a"].config.productionTip=!1,new a["a"]({render:function(e){return e(ht)}}).$mount("#app")},c867:function(e,t,l){}});
2 | //# sourceMappingURL=app.012999eb.js.map
--------------------------------------------------------------------------------
/server/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "watch": ["src"],
3 | "ext": "ts",
4 | "exec": "ts-node ./src/index.ts"
5 | }
6 |
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "zreportapi",
3 | "version": "1.0.0",
4 | "description": "API for Django API generator",
5 | "main": "index.js",
6 | "scripts": {
7 | "dev": "nodemon",
8 | "start": "ts-node src/index.ts",
9 | "build": "tsc",
10 | "test": "jest --watchAll --no-cache",
11 | "postinstall": "mkdir -p projects"
12 | },
13 | "jest": {
14 | "preset": "ts-jest",
15 | "testEnvironment": "node",
16 | "setupFilesAfterEnv": []
17 | },
18 | "author": "Mehmet Alp Sümer",
19 | "license": "ISC",
20 | "dependencies": {
21 | "@types/shelljs": "^0.8.8",
22 | "body-parser": "^1.19.0",
23 | "cors": "^2.8.5",
24 | "express": "^4.17.1",
25 | "shelljs": "^0.8.4"
26 | },
27 | "devDependencies": {
28 | "@types/body-parser": "^1.19.0",
29 | "@types/cors": "^2.8.6",
30 | "@types/express": "^4.17.6",
31 | "@types/jest": "^26.0.4",
32 | "@types/node": "^13.11.1",
33 | "@types/supertest": "^2.0.10",
34 | "jest": "^26.1.0",
35 | "nodemon": "^2.0.3",
36 | "supertest": "^4.0.2",
37 | "ts-jest": "^26.1.2",
38 | "ts-node": "^8.10.2",
39 | "typescript": "^3.8.3"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/server/src/__test__/helper.test.ts:
--------------------------------------------------------------------------------
1 | import { slugify, execCmd } from '../helper';
2 |
3 | it("slugifies strings correctly", () => {
4 | const str = "To ße Sluğified";
5 | const expectedSlug = "to_sse_slugified";
6 | const slug = slugify(str);
7 |
8 | expect(slug).toEqual(expectedSlug);
9 | });
10 |
11 | it("throws an error for invalid cmd", async () => {
12 | const invalidCmd = "Invalid Cmd";
13 | await expect(execCmd([invalidCmd], "This will not run")).rejects.toThrow();
14 | });
15 |
--------------------------------------------------------------------------------
/server/src/app.ts:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import bodyParser from "body-parser";
3 | import cors from "cors";
4 | import { projectRouter } from './routes/project';
5 | import { statusRouter } from './routes/status';
6 |
7 | const app = express();
8 |
9 | app.use(bodyParser.json({ limit: "50mb" }));
10 | app.use(bodyParser.urlencoded({ limit: "50mb", extended: true }));
11 | app.use(cors());
12 | app.use(express.static("dist"));
13 | app.use(express.static("projects"));
14 |
15 | // Routes
16 | app.use(projectRouter);
17 | app.use(statusRouter);
18 |
19 | app.all("*", async (req, res) => {
20 | try {
21 | res.sendFile("../dist/index.html");
22 | } catch(error) {
23 | res.status(404).send({ error: error, message: 'Not found.' });
24 | }
25 |
26 | });
27 |
28 | export { app };
29 |
--------------------------------------------------------------------------------
/server/src/bash-helper.model.ts:
--------------------------------------------------------------------------------
1 | import * as sh from "shelljs";
2 | import { writeFileSync, copyFileSync, readFileSync } from "fs";
3 |
4 | export class BashHelper {
5 | /**
6 | * Executes a bash command, and returns executed command.
7 | * @param cmd Command to be executed
8 | * @param description Description of the task to be executed
9 | */
10 | private static async execCmd(cmd: string[], description: string = "") {
11 | console.log(`=> Action: ${description}...`);
12 | const runCmd = sh.exec(cmd.join(" "));
13 |
14 | if (!runCmd.stderr) {
15 | console.log(`=> Completed: ${description}!`);
16 | return cmd.join(" ");
17 | } else {
18 | console.log(`=> Error: ${description}`);
19 | throw new Error(description);
20 | }
21 | }
22 |
23 | /**
24 | * Creates a directory
25 | * @param directory Path of the directory to be created
26 | * @param createNested If selected, all nested dirs will be created
27 | */
28 | public static async createDirectory(
29 | directory: string,
30 | createNested: boolean = false
31 | ) {
32 | const cmd = ["mkdir", ...(createNested ? ["-p"] : []), directory];
33 | const action = `Create directory: ${directory}`;
34 | await BashHelper.execCmd(cmd, action);
35 | }
36 |
37 | /**
38 | * Creates an empty file
39 | * @param directory Directory where the file will be placed
40 | * @param fileName Name of the file with the extension
41 | */
42 | public static async createFile(directory: string, fileName: string) {
43 | const fileDir = `${directory}/${fileName}`;
44 | const touchCmd = ["touch", fileDir];
45 | const touchAction = `Create file: ${fileDir}`;
46 | await BashHelper.execCmd(touchCmd, touchAction);
47 | }
48 |
49 | /**
50 | * Writes a string to a file
51 | * @param filePath Path of the file
52 | * @param content Content to be written
53 | * @param append Append or overwrite
54 | */
55 | public static async writeToFile(filePath: string, content: string, append: boolean = false) {
56 | if (append) {
57 | content = readFileSync(filePath) + content;
58 | }
59 | writeFileSync(filePath, content);
60 | }
61 |
62 | public static async copyFile(sourceDir: string, targetDir: string) {
63 | copyFileSync(sourceDir, targetDir);
64 | }
65 |
66 | /**
67 | * Creates a new Django project
68 | * @param projectDir Directory where the Django project will be placed
69 | * @param projectName Name of the Django project
70 | */
71 | public static async createDjangoProject(
72 | projectDir: string,
73 | projectName: string
74 | ) {
75 | const cmd = ["django-admin", "startproject", projectName, projectDir];
76 | const action = `Create Django project: ${projectDir} ${projectName}`;
77 | await BashHelper.execCmd(cmd, action);
78 | }
79 |
80 | /**
81 | * Creates a new Django app
82 | * @param projectDir Project directory where the Django app will be created
83 | * @param appName Name of the Django app
84 | */
85 | public static async createDjangoApp(projectDir: string, appName: string) {
86 | const cmd = ["django-admin", "startapp", appName, projectDir];
87 | const action = `Create Django app: ${projectDir} ${appName}`;
88 | await BashHelper.execCmd(cmd, action);
89 | }
90 |
91 | /**
92 | * Formats Python project with PEP8 standards
93 | * @param projectDir Path of the proejct to be formatted
94 | */
95 | public static async formatProjectWithAutopep8(projectDir: string) {
96 | const cmd = ["autopep8", "--in-place", "--recursive", projectDir];
97 | const action = `Format project files: ${projectDir}`;
98 | await BashHelper.execCmd(cmd, action);
99 | }
100 |
101 | /**
102 | * Compress a directory into a ZIP file
103 | * @param directory Directory to be compressed
104 | */
105 | public static async compressDirectory(directory: string) {
106 | const cmd = ["zip", "-r", `${directory}.zip`, directory];
107 | const action = `Compress directory: ${directory}`;
108 | await BashHelper.execCmd(cmd, action);
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/server/src/helper.ts:
--------------------------------------------------------------------------------
1 | import * as sh from "shelljs";
2 | import { Project, ProjectJson } from "./models/project.model";
3 | import { App } from "./models/app.model";
4 | import { Model } from "./models/model.model";
5 | import {
6 | StringField,
7 | NumberField,
8 | DtField,
9 | BooleanField,
10 | DecimalField,
11 | FileField,
12 | ForeignKeyField,
13 | NoArgField
14 | } from "./models/fields/index";
15 |
16 |
17 | const parseJsonProject = (projectJson: ProjectJson): Project => {
18 | console.log(projectJson);
19 | const project = new Project(projectJson.name, projectJson.port);
20 | const appJson = projectJson.app;
21 | const app = new App(appJson.name);
22 |
23 | for (let j = 0; j < appJson.models.length; j++) {
24 | const modelJson = appJson.models[j];
25 | const model = new Model(modelJson.name);
26 |
27 | for (let k = 0; k < modelJson.fields.length; k++) {
28 | const fieldJson = modelJson.fields[k];
29 |
30 | switch (fieldJson.commonOptions.fieldType) {
31 | case "AutoField":
32 | case "BigAutoField":
33 | case "DurationField":
34 | case "SmallAutoField":
35 | model.fields.push(new NoArgField(fieldJson.commonOptions));
36 | break;
37 | case "BigIntegerField":
38 | case "IntegerField":
39 | case "PositiveIntegerField":
40 | case "PositiveSmallIntegerField":
41 | case "SmallIntegerField":
42 | model.fields.push(
43 | new NumberField(fieldJson.commonOptions, fieldJson.defaultValue, fieldJson.hasDefault!)
44 | );
45 | break;
46 | case "CharField":
47 | case "EmailField":
48 | case "SlugField":
49 | case "TextField":
50 | case "URLField":
51 | model.fields.push(
52 | new StringField(
53 | fieldJson.commonOptions,
54 | fieldJson.maxLength,
55 | fieldJson.defaultValue,
56 | fieldJson.hasDefault
57 | )
58 | );
59 | break;
60 | case "DateField":
61 | case "DateTimeField":
62 | case "TimeField":
63 | model.fields.push(
64 | new DtField(
65 | fieldJson.commonOptions,
66 | fieldJson.autoNow || false,
67 | fieldJson.autoNowAdd || false
68 | )
69 | );
70 | break;
71 | case "DecimalField":
72 | model.fields.push(
73 | new DecimalField(
74 | fieldJson.commonOptions,
75 | fieldJson.maxDigits,
76 | fieldJson.decimalPlaces,
77 | fieldJson.defaultValue,
78 | fieldJson.hasDefault!
79 | )
80 | );
81 | break;
82 | case "BooleanField":
83 | model.fields.push(
84 | new BooleanField(
85 | fieldJson.commonOptions,
86 | fieldJson.defaultValue,
87 | fieldJson.hasDefault!
88 | )
89 | );
90 | break;
91 | case "FileField":
92 | model.fields.push(
93 | new FileField(
94 | fieldJson.commonOptions,
95 | fieldJson.maxLength,
96 | fieldJson.uploadTo
97 | )
98 | );
99 | break;
100 | case "ForeignKeyField":
101 | model.fields.push(
102 | new ForeignKeyField(
103 | fieldJson.commonOptions,
104 | fieldJson.model!,
105 | fieldJson.onDelete!
106 | )
107 | );
108 | break;
109 | default:
110 | break;
111 | }
112 | }
113 |
114 | app.models.push(model);
115 | }
116 | project.app = app;
117 |
118 | return project;
119 | };
120 |
121 | const execCmd = async (cmd: string[], action: string) => {
122 | console.log(`=> Action: ${action}...`);
123 | const runCmd = sh.exec(cmd.join(" "));
124 |
125 | if (!runCmd.stderr) {
126 | console.log(`=> Completed: ${action}!`);
127 | } else {
128 | console.log(`=> Error: ${action}`);
129 | throw new Error(action);
130 | }
131 | };
132 |
133 | const slugify = (text: string, separator: string = "_") => {
134 | let slug = text.toString().toLowerCase().trim();
135 |
136 | const sets = [
137 | { to: "a", from: "[ÀÁÂÃÅÆĀĂĄẠẢẤẦẨẪẬẮẰẲẴẶ]" },
138 | { to: "ae", from: "[Ä]" },
139 | { to: "c", from: "[ÇĆĈČ]" },
140 | { to: "d", from: "[ÐĎĐÞ]" },
141 | { to: "e", from: "[ÈÉÊËĒĔĖĘĚẸẺẼẾỀỂỄỆ]" },
142 | { to: "g", from: "[ĜĞĢǴ]" },
143 | { to: "h", from: "[ĤḦ]" },
144 | { to: "i", from: "[ÌÍÎÏĨĪĮİỈỊ]" },
145 | { to: "j", from: "[Ĵ]" },
146 | { to: "ij", from: "[IJ]" },
147 | { to: "k", from: "[Ķ]" },
148 | { to: "l", from: "[ĹĻĽŁ]" },
149 | { to: "m", from: "[Ḿ]" },
150 | { to: "n", from: "[ÑŃŅŇ]" },
151 | { to: "o", from: "[ÒÓÔÕØŌŎŐỌỎỐỒỔỖỘỚỜỞỠỢǪǬƠ]" },
152 | { to: "oe", from: "[΅]" },
153 | { to: "p", from: "[ṕ]" },
154 | { to: "r", from: "[ŔŖŘ]" },
155 | { to: "s", from: "[ŚŜŞŠ]" },
156 | { to: "ss", from: "[ß]" },
157 | { to: "t", from: "[ŢŤ]" },
158 | { to: "u", from: "[ÙÚÛŨŪŬŮŰŲỤỦỨỪỬỮỰƯ]" },
159 | { to: "ue", from: "[Ü]" },
160 | { to: "w", from: "[ẂŴẀẄ]" },
161 | { to: "x", from: "[ẍ]" },
162 | { to: "y", from: "[ÝŶŸỲỴỶỸ]" },
163 | { to: "z", from: "[ŹŻŽ]" },
164 | { to: "-", from: "[·/_,:;']" },
165 | ];
166 |
167 | sets.forEach((set) => {
168 | slug = slug.replace(new RegExp(set.from, "gi"), set.to);
169 | });
170 |
171 | slug = slug
172 | .toString()
173 | .toLowerCase()
174 | .replace(/\s+/g, "-") // Replace spaces with -
175 | .replace(/&/g, "-and-") // Replace & with 'and'
176 | .replace(/[^\w\-]+/g, "") // Remove all non-word chars
177 | .replace(/\--+/g, "-") // Replace multiple - with single -
178 | .replace(/^-+/, "") // Trim - from start of text
179 | .replace(/-+$/, ""); // Trim - from end of text
180 |
181 | if (typeof separator !== "undefined" && separator !== "-") {
182 | slug = slug.replace(/-/g, separator);
183 | }
184 |
185 | return slug;
186 | }
187 |
188 | export { parseJsonProject, execCmd, slugify };
189 |
--------------------------------------------------------------------------------
/server/src/index.ts:
--------------------------------------------------------------------------------
1 | import { app } from "./app";
2 |
3 | const port = process.env.PORT || 3000;
4 | const start = async () => {
5 | app.listen(port, () => {
6 | console.log("Listening on " + port);
7 | });
8 | };
9 |
10 | start();
11 |
--------------------------------------------------------------------------------
/server/src/models/__test__/app.model.test.ts:
--------------------------------------------------------------------------------
1 | import { App } from '../app.model';
2 |
3 | it("should convert app name to slug", () => {
4 | const appName1 = "My App 1";
5 | const formattedAppName1 = "my_app_1";
6 |
7 | const appName2 = "*1App";
8 | const formattedAppName2 = "1app";
9 |
10 | const appName3 = "This App Haş Weird Çharacterş";
11 | const formattedAppName3 = "this_app_has_weird_characters";
12 |
13 | const app1 = new App(appName1);
14 | const app2 = new App(appName2);
15 | const app3 = new App(appName3);
16 |
17 | expect(app1.name).toEqual(formattedAppName1);
18 | expect(app2.name).toEqual(formattedAppName2);
19 | expect(app3.name).toEqual(formattedAppName3);
20 | });
21 |
--------------------------------------------------------------------------------
/server/src/models/__test__/field.model.test.ts:
--------------------------------------------------------------------------------
1 | import { Field } from '../field.model';
2 |
3 | it("formats field name with snake_case", () => {
4 | const name1 = "Field Name";
5 | const formattedName1 = "field_name";
6 |
7 | const name2 = "fieldName";
8 | const formattedName2 = "fieldname";
9 |
10 | const name3 = "field Name";
11 | const formattedName3 = "field_name";
12 |
13 | const field1 = new Field({ name: name1, fieldType: 'CharField' });
14 | const field2 = new Field({ name: name2, fieldType: 'CharField' });
15 | const field3 = new Field({ name: name3, fieldType: "CharField" });
16 |
17 | expect(field1.formatFieldName()).toEqual(formattedName1);
18 | expect(field2.formatFieldName()).toEqual(formattedName2);
19 | expect(field3.formatFieldName()).toEqual(formattedName3);
20 | });
21 |
22 | it("converts common options to string correctly", () => {
23 | const commonOptionsStr =
24 | "null=False,blank=False,unique=False,primary_key=False";
25 | const field = new Field({ name: "field", fieldType: "CharField" });
26 | expect(field.commonOptionsToString()).toEqual(commonOptionsStr);
27 | });
28 |
29 | it("converts boolean values to Python equivalent", () => {
30 | const field = new Field({ name: "field", fieldType: "CharField" });
31 |
32 | expect(field.boolToString(true)).toEqual("True");
33 | expect(field.boolToString(false)).toEqual("False");
34 | expect(field.boolToString(undefined)).toEqual("False");
35 | });
36 |
--------------------------------------------------------------------------------
/server/src/models/app.model.ts:
--------------------------------------------------------------------------------
1 | import { slugify } from "../helper";
2 | import { Model } from "./model.model";
3 |
4 | class App {
5 | name: string;
6 | models: Model[];
7 |
8 | constructor(name: string, models: Model[] = []) {
9 | this.name = slugify(name);
10 | this.models = models;
11 | }
12 | }
13 |
14 | export { App };
15 |
--------------------------------------------------------------------------------
/server/src/models/field.model.ts:
--------------------------------------------------------------------------------
1 | interface CommonFieldOptions {
2 | name: string;
3 | fieldType: string;
4 | isNull?: boolean;
5 | isBlank?: boolean;
6 | editable?: boolean;
7 | helpText?: string;
8 | isPrimaryKey?: boolean;
9 | isUnique?: boolean;
10 | }
11 |
12 | const defaultOptions: CommonFieldOptions = {
13 | name: "default_field",
14 | fieldType: "TextField",
15 | isNull: false,
16 | isBlank: false,
17 | isPrimaryKey: false,
18 | isUnique: false,
19 | };
20 |
21 | class Field {
22 | fieldType: string;
23 | name: string;
24 | isNull?: boolean;
25 | isBlank?: boolean;
26 | isRequired?: boolean;
27 | isUnique?: boolean;
28 | isPrimaryKey?: boolean;
29 |
30 | constructor(commonFields: CommonFieldOptions) {
31 | this.fieldType = commonFields.fieldType;
32 | this.name = commonFields.name;
33 | this.isNull = commonFields.isNull || defaultOptions.isNull;
34 | this.isBlank = commonFields.isBlank || defaultOptions.isBlank;
35 | this.isUnique = commonFields.isUnique || defaultOptions.isUnique;
36 | this.isPrimaryKey =
37 | commonFields.isPrimaryKey || defaultOptions.isPrimaryKey;
38 | }
39 |
40 | public formatFieldName(): string {
41 | return this.name
42 | .toLowerCase()
43 | .replace(" ", "_")
44 | .replace(/[^a-z|_]/gi, "");
45 | }
46 |
47 | public commonOptionsToString(): string {
48 | const options = [
49 | `null=${this.boolToString(this.isNull)}`,
50 | `blank=${this.boolToString(this.isBlank)}`,
51 | `unique=${this.boolToString(this.isUnique)}`,
52 | `primary_key=${this.boolToString(this.isPrimaryKey)}`,
53 | ];
54 | return options.join(",");
55 | }
56 |
57 | public boolToString(bool: boolean | undefined | null) {
58 | if (!bool) return "False";
59 | return bool == true ? "True" : "False";
60 | }
61 |
62 | public createArgString(name: string, value: any, qutoes: boolean): string | null {
63 | if (!name || !value) {
64 | return null;
65 | }
66 |
67 | return qutoes ? `${name}='${value}'` : `${name}=${value}`;
68 | }
69 | }
70 |
71 | export { Field, CommonFieldOptions };
72 |
--------------------------------------------------------------------------------
/server/src/models/fields/__test__/no-arg-field.model.test.ts:
--------------------------------------------------------------------------------
1 | import { NoArgField } from '../no-arg-field.model';
2 | import { CommonFieldOptions } from '../../field.model';
3 |
4 |
5 | it("creates duration field string correctly with no args", () => {
6 | const commonFields: CommonFieldOptions = {
7 | name: 'no_arg_field',
8 | fieldType: 'DurationField'
9 | };
10 |
11 | const durationField = new NoArgField(commonFields);
12 | const commonOptionsStr =
13 | "null=False,blank=False,unique=False,primary_key=False";
14 | const fieldStr = `${commonFields.name} = models.${commonFields.fieldType}(${commonOptionsStr})`;
15 | expect(durationField.toString()).toEqual(fieldStr);
16 | });
17 |
18 | it("creates auto field string correctly with no args", () => {
19 | const commonFields: CommonFieldOptions = {
20 | name: "no_arg_field",
21 | fieldType: "AutoField",
22 | };
23 |
24 | const autoField = new NoArgField(commonFields);
25 | const commonOptionsStr =
26 | "null=False,blank=False,unique=False,primary_key=False";
27 | const fieldStr = `${commonFields.name} = models.${commonFields.fieldType}(${commonOptionsStr})`;
28 | expect(autoField.toString()).toEqual(fieldStr);
29 | });
30 |
31 | it("creates big auto field string correctly with no args", () => {
32 | const commonFields: CommonFieldOptions = {
33 | name: "no_arg_field",
34 | fieldType: "BigAutoField",
35 | };
36 |
37 | const autoField = new NoArgField(commonFields);
38 | const commonOptionsStr =
39 | "null=False,blank=False,unique=False,primary_key=False";
40 | const fieldStr = `${commonFields.name} = models.${commonFields.fieldType}(${commonOptionsStr})`;
41 | expect(autoField.toString()).toEqual(fieldStr);
42 | });
43 |
44 | it("creates small auto field string correctly with no args", () => {
45 | const commonFields: CommonFieldOptions = {
46 | name: "no_arg_field",
47 | fieldType: "SmallAutoField",
48 | };
49 |
50 | const autoField = new NoArgField(commonFields);
51 | const commonOptionsStr =
52 | "null=False,blank=False,unique=False,primary_key=False";
53 | const fieldStr = `${commonFields.name} = models.${commonFields.fieldType}(${commonOptionsStr})`;
54 | expect(autoField.toString()).toEqual(fieldStr);
55 | });
56 |
57 | it("creates no arg field string correctly with args", () => {
58 | const commonFields: CommonFieldOptions = {
59 | name: "no_arg_field",
60 | fieldType: "SmallAutoField",
61 | isNull: true,
62 | isPrimaryKey: true
63 | };
64 |
65 | const autoField = new NoArgField(commonFields);
66 | const commonOptionsStr =
67 | "null=True,blank=False,unique=False,primary_key=True";
68 | const fieldStr = `${commonFields.name} = models.${commonFields.fieldType}(${commonOptionsStr})`;
69 | expect(autoField.toString()).toEqual(fieldStr);
70 | });
71 |
--------------------------------------------------------------------------------
/server/src/models/fields/__test__/number-field.model.test.ts:
--------------------------------------------------------------------------------
1 | import { NumberField } from '../number-field.model';
2 |
3 | it("doesnt write default value when it is null", () => {
4 | const commonFields = {
5 | name: "number_field",
6 | fieldType: "IntegerField"
7 | };
8 |
9 | const intField = new NumberField(commonFields, null, false);
10 |
11 | expect(intField.toString()).not.toContain(`default=null`);
12 | });
13 |
--------------------------------------------------------------------------------
/server/src/models/fields/boolean-field.model.ts:
--------------------------------------------------------------------------------
1 | import { Field, CommonFieldOptions } from "../field.model";
2 |
3 | export class BooleanField extends Field {
4 | defaultValue: boolean;
5 | hasDefault: boolean;
6 |
7 | constructor(commonFields: CommonFieldOptions, defaultValue: boolean, hasDefault: boolean) {
8 | super(commonFields);
9 | this.defaultValue = defaultValue;
10 | this.hasDefault = hasDefault;
11 | }
12 |
13 | public toString(): string {
14 | const fieldName = this.formatFieldName();
15 | const args = [
16 | this.commonOptionsToString()
17 | ];
18 |
19 | const defaultValueStr = this.createArgString(
20 | "default",
21 | this.boolToString(this.defaultValue),
22 | false
23 | );
24 |
25 | if (this.hasDefault && defaultValueStr) {
26 | args.push(defaultValueStr);
27 | }
28 |
29 | return `${fieldName} = models.BooleanField(${args.join(",")})`;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/server/src/models/fields/decimal-field.model.ts:
--------------------------------------------------------------------------------
1 | import { Field, CommonFieldOptions } from "../field.model";
2 |
3 | export class DecimalField extends Field {
4 | maxDigits?: number | null;
5 | decimalPlaces?: number | null;
6 | defaultValue: string;
7 | hasDefault: boolean;
8 |
9 | constructor(
10 | commonFields: CommonFieldOptions,
11 | maxDigits: number | undefined,
12 | decimalPlaces: number | undefined,
13 | defaultValue: string,
14 | hasDefault: boolean,
15 | ) {
16 | super(commonFields);
17 | this.maxDigits = maxDigits;
18 | this.decimalPlaces = decimalPlaces;
19 | this.defaultValue = defaultValue ? defaultValue.toString() : "";
20 | this.hasDefault = hasDefault;
21 | }
22 |
23 | public toString(): string {
24 | const fieldName = this.formatFieldName();
25 |
26 | const args = [
27 | this.createArgString("max_digits", this.maxDigits, false),
28 | this.createArgString("decimal_places", this.decimalPlaces, false),
29 | this.commonOptionsToString(),
30 | ];
31 |
32 | const defaultValueStr = this.createArgString(
33 | "default",
34 | this.defaultValue,
35 | true
36 | );
37 | if (defaultValueStr && this.hasDefault) {
38 | args.push(defaultValueStr);
39 | }
40 |
41 | return `${fieldName} = models.DecimalField(${args.join(",")})`;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/server/src/models/fields/dt-field.model.ts:
--------------------------------------------------------------------------------
1 | import { Field, CommonFieldOptions } from "../field.model";
2 |
3 | export class DtField extends Field {
4 | autoNow: boolean;
5 | autoNowAdd: boolean;
6 |
7 | constructor(
8 | commonFields: CommonFieldOptions,
9 | autoNow: boolean,
10 | autoNowAdd: boolean
11 | ) {
12 | super(commonFields);
13 | this.autoNow = autoNow;
14 | this.autoNowAdd = autoNowAdd;
15 | }
16 |
17 | public toString(): string {
18 | const fieldName = this.formatFieldName();
19 | const args = [
20 | this.createArgString("auto_now", this.boolToString(this.autoNow), false),
21 | this.createArgString(
22 | "auto_now_add",
23 | this.boolToString(this.autoNowAdd),
24 | false
25 | ),
26 | super.commonOptionsToString(),
27 | ];
28 | return `${fieldName} = models.${this.fieldType}(${args.join(",")})`;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/server/src/models/fields/file-field.model.ts:
--------------------------------------------------------------------------------
1 | import { Field, CommonFieldOptions } from "../field.model";
2 |
3 | export class FileField extends Field {
4 | maxLength: number | undefined;
5 | uploadTo?: string | undefined;
6 |
7 | constructor(
8 | commonFields: CommonFieldOptions,
9 | maxLength: number | undefined,
10 | uploadTo: string | undefined
11 | ) {
12 | super(commonFields);
13 | this.maxLength = maxLength;
14 | this.uploadTo = uploadTo;
15 | }
16 |
17 | public toString(): string {
18 | const fieldName = this.formatFieldName();
19 | const args = [
20 | this.createArgString("max_length", this.maxLength, false),
21 | this.createArgString("upload_to", this.uploadTo, true),
22 | this.commonOptionsToString()
23 | ];
24 | return `${fieldName} = models.FileField(${args.join(",")})`;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/server/src/models/fields/foreign-key-field.model.ts:
--------------------------------------------------------------------------------
1 | import { Field, CommonFieldOptions } from '../field.model';
2 |
3 | export class ForeignKeyField extends Field {
4 | fkModel: string;
5 | onDelete: string;
6 |
7 | constructor(
8 | commonFields: CommonFieldOptions,
9 | fkModel: string,
10 | onDelete: string
11 | ) {
12 | super(commonFields);
13 | this.fkModel = fkModel;
14 | this.onDelete = onDelete;
15 | }
16 |
17 | public toString(): string {
18 | const fieldName = this.formatFieldName();
19 | const args = [
20 | `'${this.fkModel}'`,
21 | this.createArgString("on_delete", this.onDelete, true),
22 | this.commonOptionsToString(),
23 | ];
24 |
25 | return `${fieldName} = models.ForeignKey(${args.join(",")})`;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/server/src/models/fields/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./boolean-field.model";
2 | export * from "./decimal-field.model";
3 | export * from "./file-field.model";
4 | export * from "./foreign-key-field.model";
5 |
6 | export * from "./string-field.model";
7 | export * from "./number-field.model";
8 | export * from "./no-arg-field.model";
9 | export * from "./dt-field.model";
10 |
--------------------------------------------------------------------------------
/server/src/models/fields/no-arg-field.model.ts:
--------------------------------------------------------------------------------
1 | import { Field, CommonFieldOptions } from "../field.model";
2 |
3 | export class NoArgField extends Field {
4 | constructor(commonFields: CommonFieldOptions) {
5 | super(commonFields);
6 | }
7 |
8 | public toString(): string {
9 | const fieldName = this.formatFieldName();
10 | const fieldType = this.fieldType;
11 | const args = [
12 | this.commonOptionsToString()
13 | ];
14 |
15 | return `${fieldName} = models.${fieldType}(${args.join(",")})`;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/server/src/models/fields/number-field.model.ts:
--------------------------------------------------------------------------------
1 | import { Field, CommonFieldOptions } from "../field.model";
2 |
3 | export class NumberField extends Field {
4 | defaultValue?: number | null;
5 | hasDefault: boolean;
6 |
7 | constructor(commonFields: CommonFieldOptions, defaultValue: number | null, hasDefault: boolean) {
8 | super(commonFields);
9 | this.defaultValue = defaultValue ? defaultValue : null;
10 | this.hasDefault = hasDefault;
11 | }
12 |
13 | public toString(): string {
14 | const fieldName = this.formatFieldName();
15 | const fieldType = this.fieldType;
16 | const defaultValueStr = this.createArgString("default", this.defaultValue, false);
17 |
18 | const args = [
19 | this.commonOptionsToString(),
20 | ];
21 |
22 | if (defaultValueStr && this.hasDefault) {
23 | args.push(defaultValueStr);
24 | }
25 | return `${fieldName} = models.${fieldType}(${args./*filter(Boolean).*/join(",")})`;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/server/src/models/fields/string-field.model.ts:
--------------------------------------------------------------------------------
1 | import { Field, CommonFieldOptions } from "../field.model";
2 |
3 | export class StringField extends Field {
4 | maxLength: number | undefined;
5 | defaultValue?: string;
6 | hasDefault?: boolean;
7 |
8 | constructor(
9 | commonFields: CommonFieldOptions,
10 | maxLength: number | undefined,
11 | defaultValue: string | undefined,
12 | hasDefault?: boolean | undefined
13 | ) {
14 | super(commonFields);
15 | this.maxLength = maxLength;
16 | this.defaultValue = defaultValue ? defaultValue.toString() : "";
17 | this.hasDefault = hasDefault;
18 | }
19 |
20 | public toString(): string {
21 | const fieldName = this.formatFieldName();
22 | const fieldType = this.fieldType;
23 | const defaultValue = this.createArgString("default", this.defaultValue, true);
24 |
25 | const args = [
26 | this.commonOptionsToString(),
27 | ];
28 |
29 | if (this.hasDefault && defaultValue) {
30 | args.push(defaultValue);
31 | }
32 |
33 | if (fieldType !== "TextField") {
34 | args.push(this.createArgString("max_length", this.maxLength, false)!);
35 | }
36 |
37 | return `${fieldName} = models.${fieldType}(${args.join(',')})`;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/server/src/models/model.model.ts:
--------------------------------------------------------------------------------
1 | import { Field } from './field.model';
2 |
3 | export class Model {
4 | name: string;
5 | depth: number;
6 | fields: Field[];
7 |
8 | constructor(name: string, fields: Field[] = [], depth: number = 0) {
9 | this.name = name;
10 | this.fields = fields;
11 | this.depth = depth;
12 | }
13 |
14 | public fieldsToString(): string {
15 | return this.fields.map((f) => "\t" + f.toString()).join("\n");
16 | }
17 |
18 | public formatModelName(modelName: string): string {
19 | return modelName
20 | .replace(/\w\S*/g, function (txt) {
21 | return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
22 | })
23 | .replace(/[^a-z]/gi, "");
24 | }
25 |
26 | public getSerializerName(): string {
27 | const modelName = this.formatModelName(this.name);
28 | return `${modelName}Serializer`;
29 | }
30 |
31 | public getListApiName(): string {
32 | const modelName = this.formatModelName(this.name);
33 | return `${modelName}List`;
34 | }
35 |
36 | public getDetailApiName(): string {
37 | const modelName = this.formatModelName(this.name);
38 | return `${modelName}Detail`;
39 | }
40 |
41 | public getPath(): string {
42 | return this.name
43 | .toString()
44 | .toLowerCase()
45 | .replace(/\s+/g, "-") // Replace spaces with -
46 | .replace(/[^\w\-]+/g, "") // Remove all non-word chars
47 | .replace(/\-\-+/g, "-") // Replace multiple - with single -
48 | .replace(/^-+/, "") // Trim - from start of text
49 | .replace(/-+$/, ""); // Trim - from end of text
50 | }
51 |
52 | public toString(): string {
53 | const modelName = this.formatModelName(this.name);
54 | const fields = this.fieldsToString();
55 | return `\nclass ${modelName}(models.Model):\n${fields}\n`;
56 | }
57 |
58 | public toSerializerString(): string {
59 | const modelName = this.formatModelName(this.name);
60 |
61 | return `class ${this.getSerializerName()}(serializers.ModelSerializer):
62 | class Meta:
63 | model = models.${modelName}
64 | fields = '__all__'
65 | depth = ${this.depth}`;
66 | }
67 |
68 | public toListApiString(): string {
69 | const modelName = this.formatModelName(this.name);
70 |
71 | return `class ${this.getListApiName()}(generics.ListCreateAPIView):
72 | queryset = models.${modelName}.objects.all()
73 | serializer_class = serializers.${this.getSerializerName()}
74 | filter_backends = [filters.SearchFilter, filters.OrderingFilter]
75 | search_fields = [${this.fields
76 | .map((field) => `'${field.formatFieldName()}'`)
77 | .join(",")}]`;
78 | }
79 |
80 | public toDetailApiString(): string {
81 | const modelName = this.formatModelName(this.name);
82 |
83 | return `class ${this.getDetailApiName()}(generics.RetrieveUpdateDestroyAPIView):
84 | queryset = models.${modelName}.objects.all()
85 | serializer_class = serializers.${this.getSerializerName()}
86 | lookup_url_kwarg = 'id'`;
87 | }
88 |
89 | public getListApiPath(): string {
90 | return `path('${this.getPath()}/', api.${this.getListApiName()}.as_view()),`;
91 | }
92 |
93 | public getDetailApiPath(): string {
94 | return `path('${this.getPath()}//', api.${this.getDetailApiName()}.as_view()),`;
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/server/src/models/project.model.ts:
--------------------------------------------------------------------------------
1 | import { App } from "./app.model";
2 | import { CommonFieldOptions } from "./field.model";
3 | import { slugify } from "../helper";
4 |
5 | const projectsRoot = "./projects";
6 |
7 | class Project {
8 | name: string;
9 | app: App;
10 | port: number;
11 | dbUser: string;
12 | dbPassword: string;
13 | timestamp: number;
14 |
15 | constructor(
16 | name: string,
17 | port: number = 7080,
18 | dbUser: string = "postgres",
19 | dbPassword: string = "postgres"
20 | ) {
21 | this.name = slugify(name);
22 | this.port = port;
23 | this.dbUser = dbUser;
24 | this.dbPassword = dbPassword;
25 | this.app = new App("dummy");
26 | this.timestamp = new Date().getTime();
27 | }
28 |
29 |
30 | public get projectName() {
31 | return `${this.name}_${this.timestamp}`;
32 | }
33 |
34 | public get projectDir() {
35 | return `${projectsRoot}/${this.projectName}`;
36 | }
37 | }
38 |
39 | interface ProjectJson {
40 | name: string;
41 | port: number;
42 | app: {
43 | name: string;
44 | models: {
45 | name: string;
46 | fields: {
47 | commonOptions: CommonFieldOptions;
48 | hasDefault?: boolean;
49 | defaultValue?: any;
50 | maxLength?: number;
51 | model?: string;
52 | onDelete?: string;
53 | autoNow?: boolean;
54 | autoNowAdd?: boolean;
55 | maxDigits?: number;
56 | decimalPlaces?: number;
57 | uploadTo?: string;
58 | }[];
59 | }[];
60 | };
61 | }
62 |
63 | export { Project, ProjectJson, projectsRoot };
64 |
--------------------------------------------------------------------------------
/server/src/routes/project.ts:
--------------------------------------------------------------------------------
1 | import path from "path";
2 | import express, { Request, Response } from "express";
3 |
4 | import { Project } from "../models/project.model";
5 | import { parseJsonProject } from "../helper";
6 | import { BashHelper } from "../bash-helper.model";
7 | import {
8 | djangoRequirements,
9 | dockerComposeContent,
10 | dockerfileContent,
11 | djangoAdmin,
12 | djangoSerializers,
13 | djangoApi,
14 | djangoAppUrls,
15 | djangoProjectUrls,
16 | djangoSettings,
17 | djangoUtils,
18 | } from "../templates";
19 |
20 | const router = express.Router();
21 |
22 | router.post("/api/project", async (req: Request, res: Response) => {
23 | let project: Project = new Project(""); // avoid TS 'non-defined'
24 | try {
25 | project = parseJsonProject(req.body);
26 | } catch (err) {
27 | res.status(500).send({ message: "Error while parsing JSON." });
28 | }
29 |
30 | // Project create
31 | await BashHelper.createDirectory(project.projectDir, true);
32 | await BashHelper.createDjangoProject(project.projectDir, project.name);
33 |
34 | // requirements.txt
35 | await BashHelper.createFile(project.projectDir, "requirements.txt");
36 | await BashHelper.writeToFile(
37 | project.projectDir + "/requirements.txt",
38 | djangoRequirements
39 | );
40 |
41 | // Dockerfile
42 | await BashHelper.createFile(project.projectDir, "Dockerfile");
43 | await BashHelper.writeToFile(
44 | project.projectDir + "/Dockerfile",
45 | dockerfileContent(project.port)
46 | );
47 |
48 | // docker-compose.yml
49 | await BashHelper.createFile(project.projectDir, "docker-compose.yml");
50 | await BashHelper.writeToFile(
51 | project.projectDir + "/docker-compose.yml",
52 | dockerComposeContent(project.port)
53 | );
54 |
55 | // settings.py
56 | const settingsFileName = "settings.py";
57 | await BashHelper.createFile(
58 | project.projectDir + "/" + project.name,
59 | settingsFileName
60 | );
61 | await BashHelper.writeToFile(
62 | project.projectDir + "/" + project.name + "/" + settingsFileName,
63 | djangoSettings(project.name, project.app.name)
64 | );
65 |
66 | // urls.py
67 | const projectUrlsFileName = "urls.py";
68 | await BashHelper.createFile(
69 | project.projectDir + "/" + project.name,
70 | projectUrlsFileName
71 | );
72 | await BashHelper.writeToFile(
73 | project.projectDir + "/" + project.name + "/" + projectUrlsFileName,
74 | djangoProjectUrls(project.app.name)
75 | );
76 |
77 | /* App */
78 | const djangoApp = project.app;
79 | const djangoAppDir = project.projectDir + "/" + djangoApp.name;
80 | await BashHelper.createDirectory(djangoAppDir);
81 | await BashHelper.createDjangoApp(djangoAppDir, djangoApp.name);
82 |
83 | // admin.py
84 | await BashHelper.createFile(djangoAppDir, "admin.py");
85 | await BashHelper.writeToFile(
86 | djangoAppDir + "/admin.py",
87 | djangoAdmin(djangoApp.models)
88 | );
89 |
90 | // serializers.py
91 | const serializersFileName = "serializers.py";
92 | await BashHelper.createFile(djangoAppDir, serializersFileName);
93 | await BashHelper.writeToFile(
94 | djangoAppDir + "/" + serializersFileName,
95 | djangoSerializers(djangoApp.models)
96 | );
97 |
98 | // api.py
99 | const apiFileName = "api.py";
100 | await BashHelper.createFile(djangoAppDir, apiFileName);
101 | await BashHelper.writeToFile(
102 | djangoAppDir + "/" + apiFileName,
103 | djangoApi(djangoApp.models)
104 | );
105 |
106 | // urls.py
107 | const urlsFileName = "urls.py";
108 | await BashHelper.createFile(djangoAppDir, urlsFileName);
109 | await BashHelper.writeToFile(
110 | djangoAppDir + "/" + urlsFileName,
111 | djangoAppUrls(djangoApp.models)
112 | );
113 |
114 | // JWT
115 | const utilsFileName = "utils.py";
116 | await BashHelper.createFile(djangoAppDir, utilsFileName);
117 | await BashHelper.writeToFile(djangoAppDir + "/" + utilsFileName, djangoUtils);
118 |
119 | /* Models */
120 | const modelsFilePath = djangoAppDir + "/models.py";
121 | djangoApp.models.forEach(async (model) => {
122 | await BashHelper.writeToFile(modelsFilePath, model.toString(), true);
123 | });
124 |
125 | // format PEP8
126 | await BashHelper.formatProjectWithAutopep8(project.projectDir);
127 |
128 | // compress
129 | await BashHelper.compressDirectory(project.projectDir);
130 |
131 | try {
132 | res.status(201).sendFile(`${project.projectName}.zip`, {
133 | root: path.join(__dirname, "../../projects"),
134 | dotfiles: "deny",
135 | headers: {
136 | "x-timestamp": Date.now(),
137 | "x-sent": true,
138 | },
139 | });
140 | } catch (err) {
141 | res.status(500).send({ message: "Error while sending the zip file." });
142 | }
143 | });
144 |
145 | router.get("/api/project", async (req: Request, res: Response) => {
146 | res.send({ message: "GET on this route is not allowed." });
147 | });
148 |
149 | export { router as projectRouter };
150 |
--------------------------------------------------------------------------------
/server/src/routes/status.ts:
--------------------------------------------------------------------------------
1 | import express, { Request, Response } from "express";
2 |
3 | const router = express.Router();
4 |
5 | router.get("/api/status", async (req: Request, res: Response) => {
6 | res.status(200).send({ ok: true });
7 | });
8 |
9 | export { router as statusRouter };
10 |
--------------------------------------------------------------------------------
/server/src/templates/django-admin.ts:
--------------------------------------------------------------------------------
1 | import { Model } from '../models/model.model';
2 |
3 | const djangoAdmin = function (models: Model[]) {
4 | return `
5 | from django.contrib import admin
6 | from . import models
7 |
8 | ${models
9 | .map((model) => model.formatModelName(model.name))
10 | .map((name) => `admin.site.register(models.${name})`)
11 | .join("\n")}
12 | `;
13 | };
14 |
15 | export { djangoAdmin };
16 |
--------------------------------------------------------------------------------
/server/src/templates/django-api.ts:
--------------------------------------------------------------------------------
1 | import { Model } from "../models/model.model";
2 |
3 | const djangoApi = function (models: Model[]) {
4 | return `
5 | from django.http import JsonResponse
6 | from django.db import transaction
7 |
8 | from rest_framework import generics, filters
9 | from rest_framework.response import Response
10 | from django_filters.rest_framework import DjangoFilterBackend
11 |
12 | from . import models
13 | from . import serializers
14 |
15 | ${models.map((model) => model.toListApiString()).join("\n")}
16 |
17 | ${models.map((model) => model.toDetailApiString()).join("\n")}
18 | `;
19 | };
20 |
21 | export { djangoApi };
22 |
--------------------------------------------------------------------------------
/server/src/templates/django-app-urls.ts:
--------------------------------------------------------------------------------
1 | import { Model } from "../models/model.model";
2 |
3 | const djangoAppUrls = function (models: Model[]) {
4 | return `
5 | from django.urls import path
6 | from rest_framework_jwt.views import obtain_jwt_token, refresh_jwt_token
7 |
8 | from . import views
9 | from . import api
10 |
11 | urlpatterns = [
12 | # Auth
13 | path('token-auth', obtain_jwt_token),
14 | path('token-refresh', refresh_jwt_token),
15 |
16 | # API
17 | ${models.map((model) => model.getListApiPath()).join("\n")}
18 | ${models.map((model) => model.getDetailApiPath()).join("\n")}
19 | ]
20 | `;
21 | };
22 |
23 | export { djangoAppUrls };
24 |
--------------------------------------------------------------------------------
/server/src/templates/django-project-urls.ts:
--------------------------------------------------------------------------------
1 | const djangoProjectUrls = function(appName: string) {
2 | return `
3 | from django.conf.urls import url, include
4 | from django.contrib import admin
5 | from rest_framework_swagger.views import get_swagger_view
6 |
7 | schema_view = get_swagger_view(title='Swagger')
8 |
9 | urlpatterns = [
10 | url(r'^admin/', admin.site.urls),
11 | url(r'^api/', include('${appName}.urls')),
12 | url(r'^swagger/', schema_view),
13 | ]
14 | `;
15 | }
16 |
17 | export { djangoProjectUrls };
--------------------------------------------------------------------------------
/server/src/templates/django-requirements.ts:
--------------------------------------------------------------------------------
1 | const djangoRequirements = `
2 | Django==2.2.12
3 | djangorestframework==3.9.0
4 | django-cors-headers==2.4.0
5 | djangorestframework-jwt
6 | psycopg2
7 | xlrd
8 | Pillow
9 | django-filter
10 | django_rest_swagger==2.2.0
11 | `;
12 |
13 | export { djangoRequirements };
14 |
--------------------------------------------------------------------------------
/server/src/templates/django-serializers.ts:
--------------------------------------------------------------------------------
1 | import { Model } from "../models/model.model";
2 |
3 | const djangoSerializers = function (models: Model[]) {
4 | return `
5 | from rest_framework import serializers
6 | from django.contrib.auth.models import User
7 | from . import models
8 |
9 | ${models
10 | .map((model) => model.toSerializerString())
11 | .join("\n")}
12 | `;
13 | };
14 |
15 | export { djangoSerializers };
16 |
--------------------------------------------------------------------------------
/server/src/templates/django-settings.ts:
--------------------------------------------------------------------------------
1 | const djangoSettings = function (
2 | projectName: string,
3 | appName: string,
4 | corsWhitelist: string[] = [],
5 | pageSize: number = 25,
6 | jwtExpiration: number = 3600 * 12
7 | ) {
8 | return `
9 | import os
10 | import datetime
11 |
12 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
13 |
14 | SECRET_KEY = 'qh9s+kq4ef_y3mv9x_&6ik=2&s-=l=!8*m^r$+7sd^7awsfy^@'
15 |
16 | # SECURITY WARNING: don't run with debug turned on in production!
17 | DEBUG = True
18 | ALLOWED_HOSTS = ["*"]
19 |
20 |
21 | # Application definition
22 |
23 | INSTALLED_APPS = [
24 | 'django.contrib.admin',
25 | 'django.contrib.auth',
26 | 'django.contrib.contenttypes',
27 | 'django.contrib.sessions',
28 | 'django.contrib.messages',
29 | 'django.contrib.staticfiles',
30 | 'django_filters',
31 | 'rest_framework',
32 | 'rest_framework.authtoken',
33 | 'corsheaders',
34 | 'rest_framework_swagger',
35 | '${appName}'
36 | ]
37 |
38 | MIDDLEWARE = [
39 | 'corsheaders.middleware.CorsMiddleware',
40 | 'django.middleware.security.SecurityMiddleware',
41 | 'django.contrib.sessions.middleware.SessionMiddleware',
42 | 'django.middleware.common.CommonMiddleware',
43 | 'django.middleware.csrf.CsrfViewMiddleware',
44 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
45 | 'django.contrib.messages.middleware.MessageMiddleware',
46 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
47 | ]
48 |
49 | ROOT_URLCONF = '${projectName}.urls'
50 |
51 | TEMPLATES = [
52 | {
53 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
54 | 'DIRS': [],
55 | 'APP_DIRS': True,
56 | 'OPTIONS': {
57 | 'context_processors': [
58 | 'django.template.context_processors.debug',
59 | 'django.template.context_processors.request',
60 | 'django.contrib.auth.context_processors.auth',
61 | 'django.contrib.messages.context_processors.messages',
62 | ],
63 | },
64 | },
65 | ]
66 |
67 | WSGI_APPLICATION = '${projectName}.wsgi.application'
68 |
69 |
70 | # Database
71 | # https://docs.djangoproject.com/en/2.1/ref/settings/#databases
72 |
73 | DATABASES = {
74 | 'default': {
75 | 'ENGINE': 'django.db.backends.postgresql',
76 | 'NAME': 'postgres',
77 | 'USER': os.environ['POSTGRES_USER'],
78 | 'HOST': 'db',
79 | 'PORT': 5432,
80 | 'PASSWORD': os.environ['POSTGRES_PASSWORD']
81 | }
82 | }
83 |
84 |
85 | # Password validation
86 | # https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators
87 |
88 | AUTH_PASSWORD_VALIDATORS = [
89 | {
90 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
91 | },
92 | {
93 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
94 | },
95 | {
96 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
97 | },
98 | {
99 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
100 | },
101 | ]
102 |
103 | # Internationalization
104 | # https://docs.djangoproject.com/en/2.1/topics/i18n/
105 |
106 | LANGUAGE_CODE = 'en-US'
107 |
108 | TIME_ZONE = 'Europe/Istanbul'
109 |
110 | USE_I18N = True
111 |
112 | USE_L10N = True
113 |
114 | USE_TZ = True
115 |
116 |
117 | # Static files (CSS, JavaScript, Images)
118 | # https://docs.djangoproject.com/en/2.1/howto/static-files/
119 |
120 | STATIC_URL = '/static/'
121 |
122 | STATIC_URL = '/static/'
123 | STATIC_ROOT = '/var/www/yonetim.shop-wash.co/staticfiles/'
124 |
125 | MEDIA_URL = '/media/'
126 | MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
127 |
128 | # Cors Rules
129 |
130 | CORS_ORIGIN_WHITELIST = (
131 | ${corsWhitelist.map((adr) => `'${adr}'`).join(",")}
132 | )
133 |
134 | # REST settings
135 |
136 | REST_FRAMEWORK = {
137 | 'DEFAULT_AUTHENTICATION_CLASSES': (
138 | 'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
139 | 'rest_framework.authentication.SessionAuthentication',
140 | 'rest_framework.authentication.BasicAuthentication',
141 | ),
142 | 'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'],
143 | 'PAGE_SIZE': ${pageSize},
144 | 'TEST_REQUEST_DEFAULT_FORMAT': 'json'
145 | }
146 |
147 | # API Auth
148 | JWT_AUTH = {
149 | 'JWT_ALLOW_REFRESH': True,
150 | 'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=${jwtExpiration}),
151 | 'JWT_PAYLOAD_HANDLER':
152 | '${appName}.utils.jwt_payload_handler',
153 | }
154 | `;
155 | };
156 |
157 | export { djangoSettings };
158 |
--------------------------------------------------------------------------------
/server/src/templates/django-utils.ts:
--------------------------------------------------------------------------------
1 | const djangoUtils = `
2 | import warnings
3 | from calendar import timegm
4 | from datetime import datetime
5 |
6 | from rest_framework_jwt.compat import get_username, get_username_field
7 | from rest_framework_jwt.settings import api_settings
8 |
9 |
10 | def jwt_payload_handler(user):
11 | username_field = get_username_field()
12 | username = get_username(user)
13 |
14 | warnings.warn(
15 | 'The following fields will be removed in the future: '
16 | '\`email\` and \`user_id\`. ',
17 | DeprecationWarning
18 | )
19 |
20 | payload = {'id': user.pk, 'email': user.email, 'name': user.name, 'exp': datetime.utcnow() + api_settings.JWT_EXPIRATION_DELTA, 'admin': user.is_staff, 'active': user.is_active}
21 |
22 | # Include original issued at time for a brand new token,
23 | # to allow token refresh
24 | if api_settings.JWT_ALLOW_REFRESH:
25 | payload['orig_iat'] = timegm(
26 | datetime.utcnow().utctimetuple()
27 | )
28 |
29 | if api_settings.JWT_AUDIENCE is not None:
30 | payload['aud'] = api_settings.JWT_AUDIENCE
31 |
32 | if api_settings.JWT_ISSUER is not None:
33 | payload['iss'] = api_settings.JWT_ISSUER
34 |
35 | return payload
36 |
37 | `;
38 |
39 | export { djangoUtils }
40 |
--------------------------------------------------------------------------------
/server/src/templates/docker-compose.ts:
--------------------------------------------------------------------------------
1 | const dockerComposeContent = function (port: number = 8080, dbUser: string = "postgres", dbPassword: string = "postgres") {
2 | return `
3 | version: "3"
4 |
5 | services:
6 | db:
7 | image: postgres
8 | environment:
9 | POSTGRES_USER: ${dbUser}
10 | POSTGRES_PASSWORD: ${dbPassword}
11 | dj:
12 | build: .
13 | environment:
14 | POSTGRES_USER: ${dbUser}
15 | POSTGRES_PASSWORD: ${dbPassword}
16 | command: bash -c "python manage.py makemigrations && python manage.py migrate && python manage.py runserver 0.0.0.0:${port}"
17 | volumes:
18 | - ./:/code
19 | ports:
20 | - "${port}:${port}"
21 | depends_on:
22 | - db
23 | `;
24 | };
25 |
26 | export { dockerComposeContent };
27 |
--------------------------------------------------------------------------------
/server/src/templates/docker.ts:
--------------------------------------------------------------------------------
1 | const dockerfileContent = function (port: number = 8080) {
2 | return `
3 | FROM python:3
4 | ENV PYTHONUNBUFFERED 1
5 | COPY requirements.txt /
6 | RUN pip install -r /requirements.txt
7 | RUN mkdir /code
8 | WORKDIR /code
9 | COPY . /code/
10 | EXPOSE ${port}
11 | `;
12 | };
13 |
14 | export { dockerfileContent };
15 |
--------------------------------------------------------------------------------
/server/src/templates/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./django-admin";
2 | export * from "./django-api";
3 | export * from "./django-app-urls";
4 | export * from "./django-project-urls";
5 | export * from "./django-requirements";
6 | export * from "./django-serializers";
7 | export * from "./django-settings";
8 | export * from "./django-utils";
9 | export * from "./docker";
10 | export * from "./docker-compose";
11 |
--------------------------------------------------------------------------------
/server/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Basic Options */
4 | // "incremental": true, /* Enable incremental compilation */
5 | "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
6 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
7 | // "lib": [], /* Specify library files to be included in the compilation. */
8 | // "allowJs": true, /* Allow javascript files to be compiled. */
9 | // "checkJs": true, /* Report errors in .js files. */
10 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
11 | // "declaration": true, /* Generates corresponding '.d.ts' file. */
12 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
13 | "sourceMap": true, /* Generates corresponding '.map' file. */
14 | // "outFile": "./dist", /* Concatenate and emit output to single file. */
15 | "outDir": "build", /* Redirect output structure to the directory. */
16 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
17 | // "composite": true, /* Enable project compilation */
18 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
19 | // "removeComments": true, /* Do not emit comments to output. */
20 | // "noEmit": true, /* Do not emit outputs. */
21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */
22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
24 |
25 | /* Strict Type-Checking Options */
26 | "strict": true, /* Enable all strict type-checking options. */
27 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
28 | // "strictNullChecks": true, /* Enable strict null checks. */
29 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */
30 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
31 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
32 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
33 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
34 |
35 | /* Additional Checks */
36 | // "noUnusedLocals": true, /* Report errors on unused locals. */
37 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
38 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
39 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
40 |
41 | /* Module Resolution Options */
42 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
43 | "baseUrl": ".", /* Base directory to resolve non-absolute module names. */
44 | "paths": {
45 | "*": [
46 | "node_modules/*"
47 | ]
48 | }, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
49 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
50 | // "typeRoots": [], /* List of folders to include type definitions from. */
51 | // "types": [], /* Type declaration files to be included in compilation. */
52 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
53 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
54 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
55 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
56 |
57 | /* Source Map Options */
58 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
60 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
61 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
62 |
63 | /* Experimental Options */
64 | "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
65 | "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
66 |
67 | /* Advanced Options */
68 | "forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */
69 | },
70 | "include": [
71 | "src/**/*"
72 | ],
73 | }
74 |
--------------------------------------------------------------------------------