django-angular3 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. django_angular3/__init__.py +5 -0
  2. django_angular3/__main__.py +4 -0
  3. django_angular3/admin.py +4 -0
  4. django_angular3/angular.py +335 -0
  5. django_angular3/apps.py +7 -0
  6. django_angular3/build.py +66 -0
  7. django_angular3/cli.py +313 -0
  8. django_angular3/config.py +110 -0
  9. django_angular3/documents.py +52 -0
  10. django_angular3/examples/01_simple_crm/django-angular3.json +10 -0
  11. django_angular3/examples/01_simple_crm/manage.py +23 -0
  12. django_angular3/examples/01_simple_crm/shop/__init__.py +0 -0
  13. django_angular3/examples/01_simple_crm/shop/admin.py +17 -0
  14. django_angular3/examples/01_simple_crm/shop/apps.py +6 -0
  15. django_angular3/examples/01_simple_crm/shop/models.py +20 -0
  16. django_angular3/examples/01_simple_crm/shop/serializers.py +15 -0
  17. django_angular3/examples/01_simple_crm/shop/tests.py +1 -0
  18. django_angular3/examples/01_simple_crm/shop/views.py +24 -0
  19. django_angular3/examples/01_simple_crm/simple_crm/__init__.py +0 -0
  20. django_angular3/examples/01_simple_crm/simple_crm/asgi.py +7 -0
  21. django_angular3/examples/01_simple_crm/simple_crm/settings.py +107 -0
  22. django_angular3/examples/01_simple_crm/simple_crm/urls.py +14 -0
  23. django_angular3/examples/01_simple_crm/simple_crm/wsgi.py +7 -0
  24. django_angular3/examples/01_simple_crm/ui.json +13 -0
  25. django_angular3/examples/__init__.py +0 -0
  26. django_angular3/management/__init__.py +1 -0
  27. django_angular3/management/commands/__init__.py +1 -0
  28. django_angular3/management/commands/_base.py +57 -0
  29. django_angular3/management/commands/build_app.py +483 -0
  30. django_angular3/management/commands/export_schema.py +106 -0
  31. django_angular3/management/commands/ng_add.py +19 -0
  32. django_angular3/management/commands/ng_build.py +6 -0
  33. django_angular3/management/commands/ng_config.py +6 -0
  34. django_angular3/management/commands/ng_gen_app.py +22 -0
  35. django_angular3/management/commands/ng_new.py +6 -0
  36. django_angular3/management/commands/ng_openapi_gen.py +6 -0
  37. django_angular3/management/commands/ng_workspace.py +6 -0
  38. django_angular3/management/commands/ng_workspace_delete.py +6 -0
  39. django_angular3/management/commands/ng_workspace_modify.py +6 -0
  40. django_angular3/migrations/__init__.py +2 -0
  41. django_angular3/models.py +2 -0
  42. django_angular3/settings.py +123 -0
  43. django_angular3/static/django_angular3/.gitkeep +0 -0
  44. django_angular3/templates/django_angular3/.gitkeep +0 -0
  45. django_angular3/tools.py +134 -0
  46. django_angular3/urls.py +6 -0
  47. django_angular3/validation.py +169 -0
  48. django_angular3/views.py +2 -0
  49. django_angular3-0.1.0.dist-info/METADATA +296 -0
  50. django_angular3-0.1.0.dist-info/RECORD +54 -0
  51. django_angular3-0.1.0.dist-info/WHEEL +5 -0
  52. django_angular3-0.1.0.dist-info/entry_points.txt +2 -0
  53. django_angular3-0.1.0.dist-info/licenses/LICENSE +21 -0
  54. django_angular3-0.1.0.dist-info/top_level.txt +1 -0
django_angular3/cli.py ADDED
@@ -0,0 +1,313 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import json
5
+ import sys
6
+ from collections.abc import Sequence
7
+ from pathlib import Path
8
+
9
+ from .angular import (
10
+ AngularCommandError,
11
+ execute_invocations,
12
+ format_invocations,
13
+ resolve_angular_command,
14
+ )
15
+ from .build import create_build_plan, write_build_plan
16
+ from .config import ConfigError, load_project_config
17
+ from .validation import validate_openapi_file, validate_project_config, validate_ui_file
18
+
19
+
20
+ def build_parser() -> argparse.ArgumentParser:
21
+ parser = argparse.ArgumentParser(prog="django-angular3")
22
+ subparsers = parser.add_subparsers(dest="command", required=True)
23
+
24
+ validate_openapi = subparsers.add_parser(
25
+ "validate-openapi", help="Validate an OpenAPI source document."
26
+ )
27
+ validate_openapi.add_argument("path", help="Path to the OpenAPI document.")
28
+
29
+ validate_ui = subparsers.add_parser(
30
+ "validate-ui", help="Validate a UI definition document."
31
+ )
32
+ validate_ui.add_argument("path", help="Path to the UI definition document.")
33
+
34
+ validate_project = subparsers.add_parser(
35
+ "validate-project", help="Validate a django-angular3 project configuration."
36
+ )
37
+ validate_project.add_argument(
38
+ "path",
39
+ nargs="?",
40
+ default="django-angular3.json",
41
+ help="Path to the project config.",
42
+ )
43
+
44
+ build = subparsers.add_parser(
45
+ "build", help="Validate a project and emit a deterministic build plan."
46
+ )
47
+ build.add_argument(
48
+ "path", nargs="?", default="django-angular3.json", help="Path to the config."
49
+ )
50
+ build.add_argument(
51
+ "--output",
52
+ default="build",
53
+ help="Directory where the build plan should be written.",
54
+ )
55
+ build.add_argument(
56
+ "--dry-run",
57
+ action="store_true",
58
+ help="Print the build plan instead of writing it to disk.",
59
+ )
60
+
61
+ ng_new = subparsers.add_parser("ng_new", help="Create an empty Angular workspace.")
62
+ ng_new.add_argument(
63
+ "path", nargs="?", default=None, help="Path to the project config."
64
+ )
65
+ ng_new.add_argument(
66
+ "--dry-run",
67
+ action="store_true",
68
+ help=(
69
+ "Print the resolved Angular subprocess call list instead of "
70
+ "invoking Angular tooling."
71
+ ),
72
+ )
73
+
74
+ ng_workspace = subparsers.add_parser(
75
+ "ng_workspace",
76
+ help="Bootstrap the configured Angular workspace with angular-django2.",
77
+ )
78
+ ng_workspace.add_argument(
79
+ "path", nargs="?", default=None, help="Path to the project config."
80
+ )
81
+ ng_workspace.add_argument(
82
+ "--dry-run",
83
+ action="store_true",
84
+ help=(
85
+ "Print the resolved Angular subprocess call list instead of "
86
+ "invoking Angular tooling."
87
+ ),
88
+ )
89
+
90
+ ng_config = subparsers.add_parser(
91
+ "ng_config", help="Configure Angular workspace defaults."
92
+ )
93
+ ng_config.add_argument(
94
+ "path", nargs="?", default=None, help="Path to the project config."
95
+ )
96
+ ng_config.add_argument(
97
+ "--dry-run",
98
+ action="store_true",
99
+ help=(
100
+ "Print the resolved Angular subprocess call list instead of "
101
+ "invoking Angular tooling."
102
+ ),
103
+ )
104
+
105
+ ng_build = subparsers.add_parser(
106
+ "ng_build", help="Build the configured Angular application."
107
+ )
108
+ ng_build.add_argument(
109
+ "path", nargs="?", default=None, help="Path to the project config."
110
+ )
111
+ ng_build.add_argument(
112
+ "--dry-run",
113
+ action="store_true",
114
+ help=(
115
+ "Print the resolved Angular subprocess call list instead of "
116
+ "invoking Angular tooling."
117
+ ),
118
+ )
119
+
120
+ ng_gen_app = subparsers.add_parser(
121
+ "ng_gen_app",
122
+ help="Generate an Angular application in the configured workspace.",
123
+ )
124
+ ng_gen_app.add_argument(
125
+ "path", nargs="?", default=None, help="Path to the project config."
126
+ )
127
+ ng_gen_app.add_argument(
128
+ "--app-name", default=None, help="Optional Angular application name."
129
+ )
130
+ ng_gen_app.add_argument(
131
+ "--dry-run",
132
+ action="store_true",
133
+ help=(
134
+ "Print the resolved Angular subprocess call list instead of "
135
+ "invoking Angular tooling."
136
+ ),
137
+ )
138
+
139
+ ng_openapi_gen = subparsers.add_parser(
140
+ "ng_openapi_gen", help="Run ng-openapi-gen for the configured OpenAPI source."
141
+ )
142
+ ng_openapi_gen.add_argument(
143
+ "path", nargs="?", default=None, help="Path to the project config."
144
+ )
145
+ ng_openapi_gen.add_argument(
146
+ "--dry-run",
147
+ action="store_true",
148
+ help=(
149
+ "Print the resolved Angular subprocess call list instead of "
150
+ "invoking Angular tooling."
151
+ ),
152
+ )
153
+
154
+ ng_add = subparsers.add_parser("ng_add", help="Run ng add for an Angular package.")
155
+ ng_add.add_argument(
156
+ "path", nargs="?", default=None, help="Path to the project config."
157
+ )
158
+ ng_add.add_argument(
159
+ "--package",
160
+ default=None,
161
+ help="Package to add (defaults to setting: ng_add_package).",
162
+ )
163
+ ng_add.add_argument(
164
+ "--dry-run",
165
+ action="store_true",
166
+ help=(
167
+ "Print the resolved Angular subprocess call list instead of "
168
+ "invoking Angular tooling."
169
+ ),
170
+ )
171
+
172
+ install_tutorial = subparsers.add_parser(
173
+ "install-tutorial",
174
+ help="Install the simple_crm tutorial project to a local directory.",
175
+ )
176
+ install_tutorial.add_argument(
177
+ "dest",
178
+ nargs="?",
179
+ default="simple_crm",
180
+ help="Destination directory (default: simple_crm).",
181
+ )
182
+
183
+ return parser
184
+
185
+
186
+ def main(argv: Sequence[str] | None = None) -> int:
187
+ parser = build_parser()
188
+ args = parser.parse_args(argv)
189
+
190
+ if args.command == "validate-openapi":
191
+ return _run_validation(
192
+ validate_openapi_file(args.path), f"OpenAPI document {args.path}"
193
+ )
194
+
195
+ if args.command == "validate-ui":
196
+ return _run_validation(validate_ui_file(args.path), f"UI document {args.path}")
197
+
198
+ if args.command == "validate-project":
199
+ try:
200
+ config = load_project_config(args.path)
201
+ except ConfigError as exc:
202
+ print(f"Configuration error: {exc}", file=sys.stderr)
203
+ return 1
204
+ return _run_validation(
205
+ validate_project_config(config), f"Project configuration {Path(args.path)}"
206
+ )
207
+
208
+ if args.command == "build":
209
+ try:
210
+ config = load_project_config(args.path)
211
+ except ConfigError as exc:
212
+ print(f"Configuration error: {exc}", file=sys.stderr)
213
+ return 1
214
+
215
+ errors = validate_project_config(config)
216
+ if errors:
217
+ return _run_validation(errors, f"Project configuration {Path(args.path)}")
218
+
219
+ plan = create_build_plan(config)
220
+ if args.dry_run:
221
+ print(json.dumps(plan.to_dict(), indent=2))
222
+ return 0
223
+
224
+ plan_path = write_build_plan(plan, args.output)
225
+ print(f"Wrote build plan to {plan_path}")
226
+ return 0
227
+
228
+ if args.command == "install-tutorial":
229
+ return _run_install_tutorial(args.dest)
230
+
231
+ if args.command in {
232
+ "ng_new",
233
+ "ng_workspace",
234
+ "ng_config",
235
+ "ng_build",
236
+ "ng_gen_app",
237
+ "ng_openapi_gen",
238
+ "ng_add",
239
+ }:
240
+ plan_options = {}
241
+ if args.command == "ng_gen_app":
242
+ plan_options["app_name"] = args.app_name
243
+ if args.command == "ng_add":
244
+ plan_options["package"] = args.package
245
+ return _run_angular_command(
246
+ args.command, args.path, dry_run=args.dry_run, **plan_options
247
+ )
248
+
249
+ parser.error("Unknown command")
250
+ return 2
251
+
252
+
253
+ def _run_validation(errors: list[str], label: str) -> int:
254
+ if errors:
255
+ print(f"{label} is invalid.", file=sys.stderr)
256
+ for error in errors:
257
+ print(f" - {error}", file=sys.stderr)
258
+ return 1
259
+
260
+ print(f"{label} is valid.")
261
+ return 0
262
+
263
+
264
+ def _run_angular_command(
265
+ command_name: str, path: str | Path | None, *, dry_run: bool, **options: str | None
266
+ ) -> int:
267
+ try:
268
+ invocations = resolve_angular_command(command_name, path, **options)
269
+ except (AngularCommandError, ConfigError, TypeError, ValueError) as exc:
270
+ print(exc, file=sys.stderr)
271
+ return 1
272
+
273
+ if dry_run:
274
+ print(format_invocations(invocations))
275
+ return 0
276
+
277
+ try:
278
+ execute_invocations(invocations)
279
+ except AngularCommandError as exc:
280
+ print(exc, file=sys.stderr)
281
+ return 1
282
+
283
+ print(f"Executed {len(invocations)} command(s).")
284
+ return 0
285
+
286
+
287
+ def _run_install_tutorial(dest: str) -> int:
288
+ import shutil
289
+
290
+ src = Path(__file__).parent / "examples" / "01_simple_crm"
291
+ dest_path = Path(dest)
292
+
293
+ if dest_path.exists():
294
+ print(f"Error: destination '{dest_path}' already exists.", file=sys.stderr)
295
+ return 1
296
+
297
+ shutil.copytree(
298
+ src,
299
+ dest_path,
300
+ ignore=shutil.ignore_patterns("__pycache__", "*.pyc", "*.pyo"),
301
+ )
302
+ print(f"Tutorial project installed to '{dest_path}'.")
303
+ print()
304
+ print("Next steps:")
305
+ print(f" cd {dest_path}")
306
+ print(" python manage.py migrate")
307
+ print(" python manage.py createsuperuser")
308
+ print(" python manage.py runserver")
309
+ return 0
310
+
311
+
312
+ if __name__ == "__main__":
313
+ raise SystemExit(main())
@@ -0,0 +1,110 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ from .documents import DocumentError, load_document
8
+
9
+
10
+ class ConfigError(ValueError):
11
+ """Raised when the project configuration is invalid."""
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class ProjectConfig:
16
+ config_path: Path
17
+ project_name: str
18
+ openapi_source: Path
19
+ ui_source: Path
20
+ angular_output: Path
21
+ openapi_generator_config: Path | None = None
22
+ ng_openapi_gen_config: Path | None = None
23
+
24
+
25
+ def load_project_config(path: str | Path) -> ProjectConfig:
26
+ config_path = Path(path).resolve()
27
+ try:
28
+ document: dict[str, Any] | None = load_document(config_path)
29
+ except DocumentError as exc:
30
+ raise ConfigError(str(exc)) from exc
31
+
32
+ if not isinstance(document, dict):
33
+ raise ConfigError("Project configuration must be a mapping.")
34
+
35
+ project: dict[str, Any] = _require_mapping(document, "project")
36
+ openapi: dict[str, Any] = _require_mapping(document, "openapi")
37
+ ui: dict[str, Any] = _require_mapping(document, "ui")
38
+ angular: dict[str, Any] = _require_mapping(document, "angular")
39
+
40
+ root = config_path.parent
41
+ project_name = _require_string(project, "name", section="project")
42
+ openapi_source = _resolve_path(
43
+ root, _require_string(openapi, "source", section="openapi")
44
+ )
45
+ ui_source = _resolve_path(root, _require_string(ui, "source", section="ui"))
46
+ angular_output_value = angular.get("output", angular.get("package"))
47
+ if not isinstance(angular_output_value, str) or not angular_output_value.strip():
48
+ raise ConfigError(
49
+ "Configuration value 'angular.output' must be a non-empty string."
50
+ )
51
+ angular_output = _resolve_path(root, angular_output_value)
52
+
53
+ openapi_generator_config = _optional_path(
54
+ root, openapi.get("openapiGeneratorConfig"), "openapi.openapiGeneratorConfig"
55
+ )
56
+ ng_openapi_gen_config = _optional_path(
57
+ root, openapi.get("ngOpenApiGenConfig"), "openapi.ngOpenApiGenConfig"
58
+ )
59
+
60
+ return ProjectConfig(
61
+ config_path=config_path,
62
+ project_name=project_name,
63
+ openapi_source=openapi_source,
64
+ ui_source=ui_source,
65
+ angular_output=angular_output,
66
+ openapi_generator_config=openapi_generator_config,
67
+ ng_openapi_gen_config=ng_openapi_gen_config,
68
+ )
69
+
70
+
71
+ def _require_mapping(document: dict[str, Any], key: str) -> dict[str, Any]:
72
+ value: dict[str, Any] | None = document.get(key)
73
+ if not isinstance(value, dict):
74
+ raise ConfigError(f"Configuration section '{key}' must be a mapping.")
75
+ return value
76
+
77
+
78
+ def _require_string(document: dict[str, Any], key: str, *, section: str) -> str:
79
+ value = document.get(key)
80
+ if not isinstance(value, str) or not value.strip():
81
+ raise ConfigError(
82
+ f"Configuration value '{section}.{key}' must be a non-empty string."
83
+ )
84
+ return value
85
+
86
+
87
+ def _resolve_path(root: Path, raw_path: str) -> Path:
88
+ return (root / raw_path).resolve()
89
+
90
+
91
+ def _optional_path(root: Path, value: Any, label: str) -> Path | None:
92
+ if value is None:
93
+ return None
94
+ if not isinstance(value, str) or not value.strip():
95
+ raise ConfigError(f"Configuration value '{label}' must be a non-empty string.")
96
+ return (root / value).resolve()
97
+
98
+
99
+ def get_previous_schema_path(source: Path) -> Path:
100
+ """Return the conventional path for the previous schema artifact.
101
+
102
+ The previous schema is stored alongside the current schema with
103
+ ``.previous`` inserted before the file extension. For example::
104
+
105
+ spec/openapi/source/api.json → spec/openapi/source/api.previous.json
106
+
107
+ This path is written by ``export_schema`` before the current schema is
108
+ overwritten, and consumed by ``build_app`` for change detection.
109
+ """
110
+ return source.parent / (source.stem + ".previous" + source.suffix)
@@ -0,0 +1,52 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import tomllib
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ try:
9
+ import yaml # type: ignore
10
+ except ImportError: # pragma: no cover - exercised when optional dep absent
11
+ yaml = None
12
+
13
+
14
+ class DocumentError(ValueError):
15
+ """Raised when a structured input document cannot be loaded."""
16
+
17
+
18
+ def load_document(path: str | Path) -> Any:
19
+ file_path = Path(path)
20
+ if not file_path.exists():
21
+ raise DocumentError(f"Document does not exist: {file_path}")
22
+
23
+ suffix = file_path.suffix.lower()
24
+ text = file_path.read_text(encoding="utf-8")
25
+
26
+ if suffix == ".json":
27
+ try:
28
+ return json.loads(text)
29
+ except json.JSONDecodeError as exc:
30
+ raise DocumentError(f"Invalid JSON in {file_path}: {exc}") from exc
31
+
32
+ if suffix == ".toml":
33
+ try:
34
+ return tomllib.loads(text)
35
+ except tomllib.TOMLDecodeError as exc:
36
+ raise DocumentError(f"Invalid TOML in {file_path}: {exc}") from exc
37
+
38
+ if suffix in {".yaml", ".yml"}:
39
+ if yaml is None:
40
+ raise DocumentError(
41
+ "YAML support requires the optional 'yaml' extra. "
42
+ "Install with `pip install -e .[yaml]` or use JSON/TOML."
43
+ )
44
+ try:
45
+ return yaml.safe_load(text)
46
+ except yaml.YAMLError as exc:
47
+ raise DocumentError(f"Invalid YAML in {file_path}: {exc}") from exc
48
+
49
+ raise DocumentError(
50
+ f"Unsupported document format for {file_path}. "
51
+ "Use .json, .toml, .yaml, or .yml."
52
+ )
@@ -0,0 +1,10 @@
1
+ {
2
+ "project": { "name": "simple_crm" },
3
+ "app": { "name": "shop" },
4
+ "openapi": { "source": "schema.yaml" },
5
+ "ui": { "source": "ui.json" },
6
+ "angular": {
7
+ "output": "build/angular",
8
+ "workspace": { "packageManager": "pnpm", "style": "scss", "routing": true }
9
+ }
10
+ }
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env python
2
+ """Django's command-line utility for administrative tasks."""
3
+
4
+ import os
5
+ import sys
6
+
7
+
8
+ def main():
9
+ """Run administrative tasks."""
10
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "simple_crm.settings")
11
+ try:
12
+ from django.core.management import execute_from_command_line
13
+ except ImportError as exc:
14
+ raise ImportError(
15
+ "Couldn't import Django. Are you sure it's installed and "
16
+ "available on your PYTHONPATH environment variable? Did you "
17
+ "forget to activate a virtual environment?"
18
+ ) from exc
19
+ execute_from_command_line(sys.argv)
20
+
21
+
22
+ if __name__ == "__main__":
23
+ main()
@@ -0,0 +1,17 @@
1
+ from django.contrib import admin
2
+
3
+ from .models import Customer, Product
4
+
5
+
6
+ class CustomerAdmin(admin.ModelAdmin):
7
+ list_display = ["id", "name", "email", "active"]
8
+ list_editable = ["name", "email", "active"]
9
+
10
+
11
+ class ProductAdmin(admin.ModelAdmin):
12
+ list_display = ["id", "name", "price", "sku"]
13
+ list_editable = ["name", "price"]
14
+
15
+
16
+ admin.site.register(Customer, CustomerAdmin)
17
+ admin.site.register(Product, ProductAdmin)
@@ -0,0 +1,6 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class ShopConfig(AppConfig):
5
+ default_auto_field = "django.db.models.BigAutoField"
6
+ name = "shop"
@@ -0,0 +1,20 @@
1
+ from django.db import models
2
+
3
+
4
+ class Customer(models.Model):
5
+ name = models.CharField(max_length=120)
6
+ email = models.EmailField()
7
+ phone = models.CharField(max_length=50, blank=True)
8
+ active = models.BooleanField(default=True)
9
+
10
+ class Meta:
11
+ ordering = ["id"]
12
+
13
+
14
+ class Product(models.Model):
15
+ name = models.CharField(max_length=200)
16
+ price = models.FloatField()
17
+ sku = models.CharField(max_length=100, blank=True)
18
+
19
+ class Meta:
20
+ ordering = ["id"]
@@ -0,0 +1,15 @@
1
+ from rest_framework import serializers
2
+
3
+ from .models import Customer, Product
4
+
5
+
6
+ class CustomerSerializer(serializers.ModelSerializer):
7
+ class Meta:
8
+ model = Customer
9
+ fields = ["id", "name", "email", "phone", "active"]
10
+
11
+
12
+ class ProductSerializer(serializers.ModelSerializer):
13
+ class Meta:
14
+ model = Product
15
+ fields = ["id", "name", "price", "sku"]
@@ -0,0 +1 @@
1
+ # Create your tests here.
@@ -0,0 +1,24 @@
1
+ from rest_framework import mixins, permissions, viewsets
2
+
3
+ from .models import Customer, Product
4
+ from .serializers import CustomerSerializer, ProductSerializer
5
+
6
+
7
+ class CustomerViewSet(
8
+ mixins.ListModelMixin,
9
+ mixins.CreateModelMixin,
10
+ mixins.RetrieveModelMixin,
11
+ mixins.UpdateModelMixin,
12
+ mixins.DestroyModelMixin,
13
+ viewsets.GenericViewSet,
14
+ ):
15
+ queryset = Customer.objects.all()
16
+ serializer_class = CustomerSerializer
17
+ permission_classes = [permissions.AllowAny]
18
+ http_method_names = ["get", "post", "patch", "delete", "head", "options"]
19
+
20
+
21
+ class ProductViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
22
+ queryset = Product.objects.all()
23
+ serializer_class = ProductSerializer
24
+ permission_classes = [permissions.AllowAny]
@@ -0,0 +1,7 @@
1
+ import os
2
+
3
+ from django.core.asgi import get_asgi_application
4
+
5
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "simple_crm.settings")
6
+
7
+ application = get_asgi_application()