From a9465600af7af5e4c9232eaec6c40e166d0a9d37 Mon Sep 17 00:00:00 2001 From: Patrick Arminio Date: Wed, 3 Jul 2024 12:10:09 +0200 Subject: [PATCH] Add tests --- docs/en/docs/advanced/django-orm.md | 88 ++++++------------- docs_src/django_orm/mysite/__init__.py | 0 docs_src/django_orm/mysite/settings.py | 10 +++ docs_src/django_orm/mysite/urls.py | 1 + docs_src/django_orm/polls/__init__.py | 0 .../polls/migrations/0001_initial.py | 36 ++++++++ .../django_orm/polls/migrations/__init__.py | 0 docs_src/django_orm/polls/models.py | 29 ++++++ docs_src/django_orm/tutorial001.py | 37 ++++++++ .../test_tutorial/test_django_orm/__init__.py | 0 .../test_django_orm/test_tutorial001.py | 42 +++++++++ 11 files changed, 182 insertions(+), 61 deletions(-) create mode 100644 docs_src/django_orm/mysite/__init__.py create mode 100644 docs_src/django_orm/mysite/settings.py create mode 100644 docs_src/django_orm/mysite/urls.py create mode 100644 docs_src/django_orm/polls/__init__.py create mode 100644 docs_src/django_orm/polls/migrations/0001_initial.py create mode 100644 docs_src/django_orm/polls/migrations/__init__.py create mode 100644 docs_src/django_orm/polls/models.py create mode 100644 docs_src/django_orm/tutorial001.py create mode 100644 tests/test_tutorial/test_django_orm/__init__.py create mode 100644 tests/test_tutorial/test_django_orm/test_tutorial001.py diff --git a/docs/en/docs/advanced/django-orm.md b/docs/en/docs/advanced/django-orm.md index c6713b0ca..9c14e04cd 100644 --- a/docs/en/docs/advanced/django-orm.md +++ b/docs/en/docs/advanced/django-orm.md @@ -22,41 +22,29 @@ pip install fastapi ## Step 2: Set up a basic FastAPI application -Create a `main.py` file: +Let's create a basic FastAPI application in a new file called `main.py`: -```python -from fastapi import FastAPI +=== "Python 3.8+" -app = FastAPI() -``` + ```Python + {!> ../../../docs_src/django_orm/tutorial001.py[ln:5,13]!} + ``` In the next steps we'll import the `Question` model from Django, and create a FastAPI endpoint to list all questions. -## Step 3: Import Django models - -In your `main.py` file, let's import the `Question` model: - -```python -from polls.models import Question -``` - -Make sure to replace `polls` with the name of your Django app. +## Step 3: Import and use Django models -## Step 4: Create a FastAPI endpoint +In your `main.py` file, let's import the `Question` model and create a FastAPI endpoint to list all questions: -Now let's create a FastAPI endpoint to list all questions: +=== "Python 3.8+" -```python -@app.get("/questions") -def get_questions(): - questions = Question.objects.all() + ```Python + {!> ../../../docs_src/django_orm/tutorial001.py[ln:10,16-20]!} + ``` - return [{"question": question.question_text} for question in questions] -``` - -## Step 5: Run the FastAPI application +## Step 4: Run the FastAPI application -No we can run the FastAPI application: +Now, let's run the FastAPI application: ```bash fastapi dev main.py @@ -64,62 +52,40 @@ fastapi dev main.py If you go to `http://localhost:8000/questions` we should see the list of questions, right? 🤔 -Unfortunately, we'll get an error: +Unfortunately, we'll get this error: ```text django.core.exceptions.ImproperlyConfigured: Requested setting INSTALLED_APPS, but settings are not configured. You must either define the environment variable DJANGO_SETTINGS_MODULE or call settings.configure() before accessing settings. ``` -This error happens because Django settings are not configured before importing the Django models. +This error happens because Django needs to be configured before importing the models. -## Step 6: Configure Django settings +## Step 5: Configure Django settings To fix this error, we need to configure Django settings before importing the Django models. In the `main.py` add the following code **before** importing the Django models: -```python -import os -import django +=== "Python 3.8+" -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") -django.setup() -``` + ```Python + {!> ../../../docs_src/django_orm/tutorial001.py[ln:1,7-10]!} + ``` -This will configure Django settings before importing the Django models. +Now, if you run the FastAPI application again, you should see the list of questions at `http://localhost:8000/questions`! 🎉 -## Step 7: Run the FastAPI application (again) +## Conclusion -Now you can run the FastAPI application: - -```bash -fastapi dev main.py -``` - -And now if we go to `http://localhost:8000/questions` we should see a list of questions! 🎉 +In this guide, we learned how to use Django's ORM with FastAPI. This can be extremely useful when migrating from Django to FastAPI, as you can reuse your existing Django models and queries. ## Using the ORM in async routes Django's support for async is currently limited, if you need to do run any query in an async route (or function), you need to either use the async equivalent of the query or use `sync_to_async` from `asgiref.sync` to run the query: -```python -from asgiref.sync import sync_to_async - -@app.get("/questions") -async def get_questions(): - def _fetch_questions(): - return list(Question.objects.all()) - - questions = await sync_to_async(_fetch_questions)() +=== "Python 3.8+" - return [{"question": question.question_text} for question in questions] - - -@app.get("/questions/{question_id}") -async def get_question(question_id: int): - question = await Question.objects.filter(id=question_id).afirst() - - return {"question": question.question_text} -``` + ```Python + {!> ../../../docs_src/django_orm/tutorial001.py[ln:23-37]!} + ``` diff --git a/docs_src/django_orm/mysite/__init__.py b/docs_src/django_orm/mysite/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/docs_src/django_orm/mysite/settings.py b/docs_src/django_orm/mysite/settings.py new file mode 100644 index 000000000..fbaada55c --- /dev/null +++ b/docs_src/django_orm/mysite/settings.py @@ -0,0 +1,10 @@ +INSTALLED_APPS = ["polls"] + +ROOT_URLCONF = "mysite.urls" + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": ":memory:", + }, +} diff --git a/docs_src/django_orm/mysite/urls.py b/docs_src/django_orm/mysite/urls.py new file mode 100644 index 000000000..637600f58 --- /dev/null +++ b/docs_src/django_orm/mysite/urls.py @@ -0,0 +1 @@ +urlpatterns = [] diff --git a/docs_src/django_orm/polls/__init__.py b/docs_src/django_orm/polls/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/docs_src/django_orm/polls/migrations/0001_initial.py b/docs_src/django_orm/polls/migrations/0001_initial.py new file mode 100644 index 000000000..9d6420237 --- /dev/null +++ b/docs_src/django_orm/polls/migrations/0001_initial.py @@ -0,0 +1,36 @@ +# Generated by Django 2.1 on 2018-08-08 21:45 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Choice', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('choice_text', models.CharField(max_length=200)), + ('votes', models.IntegerField(default=0)), + ], + ), + migrations.CreateModel( + name='Question', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('question_text', models.CharField(max_length=200)), + ('pub_date', models.DateTimeField(verbose_name='date published')), + ], + ), + migrations.AddField( + model_name='choice', + name='question', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='polls.Question'), + ), + ] diff --git a/docs_src/django_orm/polls/migrations/__init__.py b/docs_src/django_orm/polls/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/docs_src/django_orm/polls/models.py b/docs_src/django_orm/polls/models.py new file mode 100644 index 000000000..70f5466a0 --- /dev/null +++ b/docs_src/django_orm/polls/models.py @@ -0,0 +1,29 @@ +import datetime + +from django.db import models +from django.utils import timezone + + +class Question(models.Model): + question_text = models.CharField(max_length=200) + pub_date = models.DateTimeField('date published') + + def __str__(self): + return self.question_text + + def was_published_recently(self): + now = timezone.now() + return now - datetime.timedelta(days=1) <= self.pub_date <= now + + was_published_recently.admin_order_field = 'pub_date' + was_published_recently.boolean = True + was_published_recently.short_description = 'Published recently?' + + +class Choice(models.Model): + question = models.ForeignKey(Question, on_delete=models.CASCADE) + choice_text = models.CharField(max_length=200) + votes = models.IntegerField(default=0) + + def __str__(self): + return self.choice_text diff --git a/docs_src/django_orm/tutorial001.py b/docs_src/django_orm/tutorial001.py new file mode 100644 index 000000000..be9ba6919 --- /dev/null +++ b/docs_src/django_orm/tutorial001.py @@ -0,0 +1,37 @@ +import os + +import django +from asgiref.sync import sync_to_async +from fastapi import FastAPI + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") +django.setup() + +from polls.models import Question # noqa: I001 + + +app = FastAPI() + + +@app.get("/questions") +def get_questions(): + questions = Question.objects.all() + + return [{"question": question.question_text} for question in questions] + + +@app.get("/questions-async") +async def get_questions_async(): + def _fetch_questions(): + return list(Question.objects.all()) + + questions = await sync_to_async(_fetch_questions)() + + return [{"question": question.question_text} for question in questions] + + +@app.get("/questions/{question_id}") +async def get_question(question_id: int): + question = await Question.objects.filter(id=question_id).afirst() + + return {"question": question.question_text} diff --git a/tests/test_tutorial/test_django_orm/__init__.py b/tests/test_tutorial/test_django_orm/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/test_tutorial/test_django_orm/test_tutorial001.py b/tests/test_tutorial/test_django_orm/test_tutorial001.py new file mode 100644 index 000000000..c9a6c1264 --- /dev/null +++ b/tests/test_tutorial/test_django_orm/test_tutorial001.py @@ -0,0 +1,42 @@ +import pytest +from django.core.management.color import no_style +from django.core.management.sql import sql_flush +from django.db import connection +from django.utils import timezone +from fastapi.testclient import TestClient + +from docs_src.django_orm.tutorial001 import Question, app + +client = TestClient(app) + + +@pytest.fixture(scope="session", autouse=True) +def django_db_setup(): + connection.creation.create_test_db(verbosity=0, autoclobber=True) + + yield + + connection.creation.destroy_test_db("default", verbosity=0) + + +@pytest.fixture(autouse=True) +def flush_db(): + sql_list = sql_flush(no_style(), connection, allow_cascade=False) + + connection.ops.execute_sql_flush(sql_list) + + +def test_get_questions(): + Question.objects.create(question_text="there goes my hero", pub_date=timezone.now()) + + response = client.get("/questions") + + assert response.status_code == 200, response.text + assert response.json() == [{"question": "there goes my hero"}] + + +def test_question_empty(): + response = client.get("/questions") + + assert response.status_code == 200, response.text + assert response.json() == []