Skip to content

Commit 3aa280e

Browse files
committed
Added code sample and fixed failing tests
1 parent 9a98ed0 commit 3aa280e

File tree

31 files changed

+611
-27
lines changed

31 files changed

+611
-27
lines changed

.github/workflows/test_full.yml

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,8 @@ jobs:
1111
strategy:
1212
matrix:
1313
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
14-
django-version: ['<3.2', '<3.3', '<4.2', '<4.3', '<5.1']
14+
django-version: ['<3.2', '<3.3', '<4.2', '<4.3']
1515
exclude:
16-
- python-version: '3.8'
17-
django-version: '<5.1'
18-
- python-version: '3.9'
19-
django-version: '<5.1'
2016
- python-version: '3.12'
2117
django-version: '<3.2'
2218
- python-version: '3.12'

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ MIDDLEWARE = [
205205
"whitenoise.middleware.WhiteNoiseMiddleware",
206206
...
207207
]
208+
WHITENOISE_STATIC_PREFIX = '/static/'
208209
...
209210
```
210211
So when you visit [http://localhost:8000/-django-example/admin](http://localhost:8000/-django-example/admin) again, the page should be functional.

ellar_django/commands.py

Lines changed: 144 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,156 @@
11
import sys
2+
import typing as t
23

3-
import click
4+
import django
5+
import ellar_cli.click as click
46

5-
HELP_MESSAGE = """
6-
Ellar will always intercept and command with '--help'.
77

8-
So if you want to get help on any django command,
9-
simply wrap the command and --help in quotes
8+
class _CommandItem(t.NamedTuple):
9+
name: str
10+
description: str
1011

11-
For example: ellar django 'migrate --help' or python manage.py django 'migrate --help'
12-
"""
1312

13+
_django_support_commands: t.List[_CommandItem] = [
14+
_CommandItem(
15+
name="migrate",
16+
description="Synchronizes the database state with the current set of models and migrations",
17+
),
18+
_CommandItem(
19+
name="makemigrations",
20+
description="Creates new migrations based on the changes detected to your models.",
21+
),
22+
_CommandItem(
23+
name="check",
24+
description="inspects the entire Django project for common problems",
25+
),
26+
_CommandItem(
27+
name="createcachetable",
28+
description="Creates the cache tables for use with the database cache backend",
29+
),
30+
_CommandItem(
31+
name="dbshell",
32+
description="Runs the command-line client for the database engine",
33+
),
34+
_CommandItem(
35+
name="diffsettings",
36+
description="Displays differences between the current settings and default settings",
37+
),
38+
_CommandItem(
39+
name="dumpdata",
40+
description="Outputs to standard output all data in the database",
41+
),
42+
_CommandItem(
43+
name="flush",
44+
description="Removes all data from the database and re-executes any post-synchronization handlers",
45+
),
46+
_CommandItem(
47+
name="inspectdb", description="Introspects the database tables in the database"
48+
),
49+
_CommandItem(
50+
name="loaddata",
51+
description="Searches for and loads the contents into the database.",
52+
),
53+
_CommandItem(
54+
name="optimizemigration",
55+
description="Optimizes the operations for the named migration and overrides the existing file",
56+
),
57+
_CommandItem(
58+
name="showmigrations", description="Shows all migrations in a project."
59+
),
60+
_CommandItem(
61+
name="sqlflush",
62+
description="Prints the SQL statements that would be executed for the flush command",
63+
),
64+
_CommandItem(
65+
name="sqlmigrate", description="Prints the SQL for the named migration."
66+
),
67+
_CommandItem(
68+
name="sqlsequencereset",
69+
description="Prints the SQL statements for resetting sequences for the given app name(s)",
70+
),
71+
_CommandItem(
72+
name="squashmigrations", description="Squashes the migrations for app_label"
73+
),
74+
_CommandItem(
75+
name="startapp", description="Creates a Django app directory structure"
76+
),
77+
_CommandItem(
78+
name="changepassword",
79+
description="This command is only available if Django’s authentication system",
80+
),
81+
_CommandItem(
82+
name="createsuperuser",
83+
description="Creates a superuser account (a user who has all permissions)",
84+
),
85+
_CommandItem(
86+
name="collectstatic", description="Expose static files to STATIC_ROOT folder"
87+
),
88+
_CommandItem(name="findstatic", description="Search for a static file location"),
89+
_CommandItem(
90+
name="clearsessions",
91+
description="Can be run as a cron job or directly to clean out expired sessions.",
92+
),
93+
]
1494

15-
@click.command(
95+
96+
def version_callback(ctx: click.Context, _: t.Any, value: bool) -> None:
97+
if value:
98+
click.echo(f"Django Version: {django.__version__}")
99+
raise click.Exit(0)
100+
101+
102+
@click.group(
16103
name="django",
17-
context_settings={"ignore_unknown_options": True, "allow_extra_args": True},
18-
help=HELP_MESSAGE,
104+
help="- Ellar Django Commands",
19105
)
20-
def django_command_wrapper() -> None:
21-
from django.core.management import execute_from_command_line
106+
@click.option(
107+
"-v",
108+
"--version",
109+
callback=version_callback,
110+
help="Show the version and exit.",
111+
is_flag=True,
112+
expose_value=False,
113+
is_eager=True,
114+
)
115+
@click.pass_context
116+
def django_command(ctx: click.Context) -> None:
117+
pass
118+
119+
120+
def _add_django_command(command_item: _CommandItem) -> None:
121+
def help_callback(ctx: click.Context, _: t.Any, value: bool) -> None:
122+
from django.core.management import execute_from_command_line
123+
124+
if value:
125+
args = ["manage.py", command_item.name, "--help"]
126+
127+
execute_from_command_line(args)
128+
raise click.Exit(0)
129+
130+
@django_command.command(
131+
name=command_item.name,
132+
help=command_item.description,
133+
context_settings={"ignore_unknown_options": True, "allow_extra_args": True},
134+
add_help_option=False,
135+
)
136+
@click.option(
137+
"-h",
138+
"--help",
139+
callback=help_callback,
140+
help="Show the version and exit.",
141+
is_flag=True,
142+
expose_value=False,
143+
is_eager=True,
144+
)
145+
@click.with_app_context
146+
def _command() -> None:
147+
from django.core.management import execute_from_command_line
148+
149+
args = ["manage.py", command_item.name]
22150

23-
args = []
24-
skip = 1
151+
for item in sys.argv[3:]:
152+
args.extend(item.split(" "))
153+
execute_from_command_line(args)
25154

26-
if "manage.py" in sys.argv: # pragma: no cover
27-
skip = 2
28155

29-
for item in sys.argv[skip:]:
30-
args.extend(item.split(" "))
31-
execute_from_command_line(args)
156+
list(map(_add_django_command, _django_support_commands))

ellar_django/module.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from starlette.responses import RedirectResponse
88
from starlette.routing import Mount
99

10-
from .commands import django_command_wrapper
10+
from .commands import django_command
1111
from .middleware import DjangoAdminRedirectMiddleware
1212

1313
_router = ModuleRouter()
@@ -18,7 +18,7 @@ async def _redirect_route(req: Request) -> RedirectResponse:
1818
return RedirectResponse(url=str(req.base_url))
1919

2020

21-
@Module(commands=[django_command_wrapper])
21+
@Module(commands=[django_command])
2222
class DjangoModule(IModuleSetup):
2323
@classmethod
2424
def setup(cls, settings_module: str, path_prefix: str = "/dj") -> "DynamicModule":
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
## Introduction
2+
This is an illustration of using DjangoORM within your Ellar Project.
3+
4+
## Project setup
5+
```
6+
pip install -r requirements.txt
7+
```
8+
9+
## Apply Migrations
10+
```shell
11+
python manage.py django migrate
12+
```
13+
14+
## Development Server
15+
```
16+
python manage.py runserver --reload
17+
```

sample/ellar-and-django-orm/ellar_and_django_orm/__init__.py

Whitespace-only changes.

sample/ellar-and-django-orm/ellar_and_django_orm/apps/__init__.py

Whitespace-only changes.

sample/ellar-and-django-orm/ellar_and_django_orm/apps/event/__init__.py

Whitespace-only changes.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""
2+
Define endpoints routes in python class-based fashion
3+
example:
4+
5+
@Controller("/dogs", tag="Dogs", description="Dogs Resources")
6+
class MyController(ControllerBase):
7+
@get('/')
8+
def index(self):
9+
return {'detail': "Welcome Dog's Resources"}
10+
"""
11+
12+
import typing as t
13+
14+
from ellar.common import Controller, ControllerBase, get, post
15+
16+
from ...interfaces.events_repository import IEventRepository
17+
from .schemas import EventSchema, EventSchemaOut
18+
19+
20+
@Controller("/event")
21+
class EventController(ControllerBase):
22+
def __init__(self, event_repo: IEventRepository):
23+
self.event_repo = event_repo
24+
25+
@post("/", response={201: EventSchemaOut})
26+
def create_event(self, event: EventSchema):
27+
event = self.event_repo.create_event(**event.dict())
28+
return 201, event
29+
30+
@get("/", response=t.List[EventSchema], name="event-list")
31+
def list_events(self):
32+
return list(self.event_repo.list_events())
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
"""
2+
@Module(
3+
controllers=[MyController],
4+
providers=[
5+
YourService,
6+
ProviderConfig(IService, use_class=AService),
7+
ProviderConfig(IFoo, use_value=FooService()),
8+
],
9+
routers=(routerA, routerB)
10+
statics='statics',
11+
template='template_folder',
12+
# base_directory -> default is the `event` folder
13+
)
14+
class MyModule(ModuleBase):
15+
def register_providers(self, container: Container) -> None:
16+
# for more complicated provider registrations
17+
pass
18+
19+
"""
20+
21+
from ellar.common import Module
22+
from ellar.core import ModuleBase
23+
from ellar.di import Container
24+
25+
from .controllers import EventController
26+
27+
28+
@Module(
29+
controllers=[EventController],
30+
providers=[],
31+
routers=[],
32+
)
33+
class EventModule(ModuleBase):
34+
"""
35+
Event Module
36+
"""
37+
38+
def register_providers(self, container: Container) -> None:
39+
"""for more complicated provider registrations, use container.register_instance(...)"""

0 commit comments

Comments
 (0)