Django Fundamentals
Django is a high-level Python web framework that encourages rapid development and clean, pragmatic design. It follows the Model-View-Template (MVT) architectural pattern, which is a variant of the traditional Model-View-Controller (MVC) pattern. Django provides a comprehensive set of tools and features that make it easier to build robust and scalable web applications.
Architecture
Section titled “Architecture”Django’s architecture is based on the MVT pattern, which consists of three main components:
-
Model: The Model is responsible for managing the data of the application. It defines the structure of the database and provides an interface for interacting with the data. In Django, models are defined as Python classes that inherit from
django.db.models.Model. -
View: The View is responsible for handling user requests and returning responses. It processes the data from the Model and renders it using templates. In Django, views are defined as Python functions or classes that take a web request and return a web response.
-
Template: The Template is responsible for presenting the data to the user. It defines the structure and layout of the HTML pages that are rendered by the views. In Django, templates are defined using a simple templating language that allows for dynamic content generation.
Django Folder Structure
Section titled “Django Folder Structure”When you create a new Django project, it generates a specific folder structure that organizes the different components of the application. The main folders and files in a Django project include:
myproject/├── manage.py├── myproject/│ ├── __init__.py│ ├── settings.py│ ├── urls.py│ ├── asgi.py│ └── wsgi.py└── app1/ ├── migrations/ │ ├── __init__.py │ └── 0001_initial.py ├── __init__.py ├── admin.py ├── apps.py ├── models.py ├── form.py ├── tests.py └── views.pymanage.py: A command-line utility that allows you to interact with your Django project. You can use it to run the development server, create database migrations, and perform other administrative tasks.myproject/: The main project folder that contains the settings and configuration for the entire project.app1/: A Django application folder that contains the code for a specific functionality of the project. You can have multiple applications within a Django project, each responsible for a different aspect of the application.migrations/: A folder that contains database migration files, which are used to manage changes to the database schema over time.models.py: A file where you define the data models for your application.views.py: A file where you define the views that handle user requests and return responses.admin.py: A file where you can register your models with the Django admin site, allowing you to manage your data through a web interface.settings.py: A file that contains the configuration settings for your Django project, such as database settings, installed applications, and middleware.urls.py: A file where you define the URL patterns for your application, mapping URLs to views.asgi.pyandwsgi.py: Files that serve as entry points for ASGI and WSGI servers, respectively, allowing your Django application to communicate with web servers.
Django Settings
Section titled “Django Settings”This section shows how to write a production-ready settings.py for a Django project named myproject.
It uses environment variables, safe defaults, and clear production checks.
Base config and environment variable
Section titled “Base config and environment variable”Using These environment variables allows you to keep sensitive information out of your codebase and easily switch between development and production settings without changing the code.
Install python-dotenv to load environment variables from a .env file in development:
uv add python-dotenvIn the settings.py, load environment variables and set up basic settings:
from pathlib import Pathimport osimport sysimport dotenvfrom django.core.exceptions import ImproperlyConfigured
BASE_DIR = Path(__file__).resolve().parent.parent
# Load values from .env if file existsenv_path = BASE_DIR / ".env"if env_path.exists(): dotenv.load_dotenv(env_path)
SECRET_KEY = os.getenv("SECRET_KEY")if not SECRET_KEY: raise ImproperlyConfigured("SECRET_KEY not set")
# Keep this exact pattern for clean environment controlDEBUG = os.getenv("DEBUG", "False").lower() == "true"PRODUCTION = "production"ENVIRONMENT = os.getenv("ENVIRONMENT", PRODUCTION).lower()Why this check is needed:
DEBUGlets you avoid dev behavior in production.ENVIRONMENTlets you switch safely between production and non-production.
Generating a SECRET_KEY
Section titled “Generating a SECRET_KEY”This is a critical setting for security. In production, always set SECRET_KEY as an environment variable. For local development, you can generate one with this code:
from django.core.management.utils import get_random_secret_key
print(get_random_secret_key())Allowed Hosts
Section titled “Allowed Hosts”Using Allowed Hosts is a security risk. Always set ALLOWED_HOSTS in production to the specific domains your app will serve.
ALLOWED_HOSTS = [ host.strip().lower() for host in os.getenv("ALLOWED_HOSTS", "localhost,127.0.0.1").split(",")]App is a Django component that encapsulates a specific functionality of the project. It can be reused across different projects and can be easily plugged into the main project. In settings.py, you need to list all the apps that are part of your project in the INSTALLED_APPS setting. This includes both built-in Django apps and any custom apps you create.
INSTALLED_APPS = [ "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", "accounts.apps.AccountsConfig", "third_party_app", # add your third-party apps here "add_custom_apps_here", # add your custom apps here]Middleware
Section titled “Middleware”Middleware is a way to process requests and responses globally before they reach the view or after the view has processed them. In settings.py, you need to list all the middleware components that your project will use in the MIDDLEWARE setting. This includes both built-in Django middleware and any custom middleware you create.
MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "whitenoise.middleware.WhiteNoiseMiddleware", # serve static files efficiently "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware",]Routing Configuration
Section titled “Routing Configuration”Route configuration defines what should return when a user accesses a specific URL. In settings.py, you need to specify the root URL configuration module in the ROOT_URLCONF setting. This module contains the URL patterns that map URLs to views.
ROOT_URLCONF = "myproject.urls"WSGI and ASGI
Section titled “WSGI and ASGI”WSGI (Web Server Gateway Interface) and ASGI (Asynchronous Server Gateway Interface) are interfaces that allow your Django application to communicate with web servers. WSGI is the traditional interface for synchronous applications, while ASGI is designed for asynchronous applications and supports features like WebSockets.
WSGI_APPLICATION = "myproject.wsgi.application"Templates
Section titled “Templates”Templates are used to render HTML pages in Django. In settings.py, you need to configure the template settings in the TEMPLATES setting. This includes specifying the template engine, directories where templates are located, and context processors.
TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", "DIRS": [BASE_DIR / "templates"], "APP_DIRS": True, "OPTIONS": { "context_processors": [ "django.template.context_processors.debug", "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", ], }, },]Database
Section titled “Database”Database Configration help us to connect our Django application to a database. In settings.py, you need to configure the database settings in the DATABASES setting. This includes specifying the database engine, name, user, password, host, and port.
Use if/else so one settings file works for local and production:
- local dev can use SQLite quickly
- production can use MySQL for better scaling
DB_ENGINE = os.getenv("DB_ENGINE", "sqlite")
if DB_ENGINE == "mysql": DATABASES = { "default": { "ENGINE": "django.db.backends.mysql", "NAME": os.getenv("DB_NAME"), "USER": os.getenv("DB_USER"), "PASSWORD": os.getenv("DB_PASSWORD"), "HOST": os.getenv("DB_HOST"), "PORT": os.getenv("DB_PORT", "3306"), "OPTIONS": {"charset": "utf8mb4"}, } }else: DATA_DIR = BASE_DIR / "_data" DATA_DIR.mkdir(exist_ok=True) DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": DATA_DIR / "db.sqlite3", } }Static Files
Section titled “Static Files”Static files are files that are served directly to the client, such as CSS, JavaScript, images, and fonts. In settings.py, you need to configure the static file settings in the STATIC_URL, STATIC_ROOT, and STATICFILES_DIRS settings. This includes specifying the URL prefix for static files, the directory where static files will be collected for production, and any additional directories where static files are located.
Install WhiteNoise:
uv add whitenoiseUse WhiteNoise to serve static files efficiently in production. Add it to the MIDDLEWARE setting after SecurityMiddleware.
MIDDLEWARE = [ # other middleware ... "django.middleware.security.SecurityMiddleware", "whitenoise.middleware.WhiteNoiseMiddleware", # whitenoise must be after SecurityMiddleware # other middleware ...]
STATIC_URL = "/static/"STATIC_ROOT = BASE_DIR / "staticfiles"STATICFILES_DIRS = [BASE_DIR / "static"]STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"Why we use WhiteNoise:
- serves static files directly from Django app
- no separate static server needed for simple deployments
- supports compressed and hashed files for faster loading and cache safety
Also run this before deployment:
uv run python manage.py collectstatic --noinputMedia files are user-uploaded files that need to be served separately from static files. In settings.py, you need to configure the media file settings in the MEDIA_URL and MEDIA_ROOT settings. This includes specifying the URL prefix for media files and the directory where media files will be stored.
MEDIA_URL = "/media/"MEDIA_ROOT = BASE_DIR / "media"Auth and user model settings
Section titled “Auth and user model settings”By the help of these setting we can customize the authentication system of our Django application. In settings.py, you can specify the login URL, redirect URLs after login and logout, and the custom user model if you have one.
LOGIN_URL = "/accounts/login/" #(not needed in DRF)LOGIN_REDIRECT_URL = "/" #(not needed in DRF)LOGOUT_REDIRECT_URL = "/accounts/login/" #(not needed in DRF)
AUTH_USER_MODEL = "appname.User"Production security settings
Section titled “Production security settings”These are turned on only in real production. This keeps development easy while ensuring production is secure by default. By the help of these settings we can enhance the security of our Django application in production environments. In settings.py, you can specify various security settings such as CSRF protection, secure cookies, HTTP headers, and SSL settings.
if not DEBUG and ENVIRONMENT == PRODUCTION: CSRF_TRUSTED_ORIGINS = [ origin for origin in os.getenv("CSRF_TRUSTED_ORIGINS", "").split(",") if origin ] CSRF_COOKIE_SECURE = True SECURE_BROWSER_XSS_FILTER = True SECURE_CONTENT_TYPE_NOSNIFF = True SECURE_HSTS_SECONDS = 31536000 # HTTPS for 1 year SECURE_HSTS_INCLUDE_SUBDOMAINS = True SECURE_HSTS_PRELOAD = True SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") SECURE_SSL_REDIRECT = True SESSION_COOKIE_HTTPONLY = True SESSION_COOKIE_SECURE = True SESSION_EXPIRE_AT_BROWSER_CLOSE = True SESSION_SAVE_EVERY_REQUEST = True X_FRAME_OPTIONS = "DENY"Logging
Section titled “Logging”Logging is an important part of building and maintaining a Django application. It helps you see what your app is doing, diagnose problems quickly, and keep a history of important events. In settings.py, Django lets you control logging through the LOGGING setting.
With LOGGING, you can decide:
- which messages should be recorded
- where those messages should be sent
- how each message should look
Django logging is usually built from three parts:
- a log level, which decides how important a message must be before it is recorded
- a log handler, which decides where the message goes
- a formatter, which decides how the message is displayed
Common handlers include StreamHandler, which writes messages to the console, and FileHandler, which writes messages to a file. You can also create custom handlers when your project needs more advanced behavior.
StreamHandleris best for local development because it shows log messages immediately in the terminal or server console.FileHandleris useful in production because it stores logs in a file that you can review later for debugging, auditing, or troubleshooting.
Django also supports several log levels, including DEBUG, INFO, WARNING, ERROR, and CRITICAL. A lower level records more detail, while a higher level records only more serious events. This helps you control how much information is collected in different environments.
LOG_DIR = BASE_DIR / "logs"LOG_DIR.mkdir(exist_ok=True)
LOGGING = { "version": 1, "disable_existing_loggers": False, "handlers": { "console": { "class": "logging.StreamHandler", "formatter": "simple", }, "file": { "class": "logging.FileHandler", "filename": LOG_DIR / "app.log", "formatter": "verbose", }, "error_file": { "class": "logging.FileHandler", "filename": LOG_DIR / "error.log", "formatter": "verbose", "level": "ERROR", }, }, "loggers": { "": { "handlers": ["console", "file", "error_file"], "level": os.getenv.get("DJANGO_LOG_LEVEL", "INFO").upper(), }, # otionally add app-specific loggers for more granular control "appname": { "handlers": ["console", "file", "error_file"], "level": os.getenv.get("DJANGO_LOG_LEVEL", "INFO").upper()," }, }, "formatters": { "verbose": { # docs: https://docs.python.org/3/library/logging.html#logrecord-attributes # this docs page lists all the available attributes you can use in log formatting "format": "{asctime} ({levelname}) - {name}: {message}", "style": "{", # `{` use str.format() style, `%` use old printf style, `$` use string.Template style }, "simple": { "format": "{levelname} - {message}", "style": "{", }, },}version: This is the schema version for the logging configuration. Django expects this to be set to1.disable_existing_loggers: When set toTrue, Django turns off loggers that were already configured elsewhere. Keeping itFalseis usually safer because it preserves existing logging behavior.handlers: This section defines where log messages go. In this example, there are three handlers:console,file, anderror_file.console: Sends log output to the console so you can watch application activity while developing.class: Tells Django which handler implementation to use. Here,logging.StreamHandlerwrites messages to standard output.formatter: Specifies which formatter to use for this handler. In this case, it uses thesimpleformatter defined later in the configuration.
file: Saves general log messages to a file.class: Useslogging.FileHandlerto write messages to disk.filename: Points to the file that will store the logs. Here,LOG_DIR / "app.log"means the file will be created inside thelogsfolder asapp.log.formatter: Uses theverboseformatter for more detailed log messages.
error_file: Stores only error-level messages and more severe problems in a separate file.class: Also useslogging.FileHandler.filename: Points to the dedicated error log file. In this example, it isLOG_DIR / "error.log".formatter: Uses theverboseformatter for more detailed error messages.level: This setting means that only messages with a severity ofERRORor higher will be recorded by this handler. This helps keep the error log focused on critical issues without being cluttered by less important messages.
loggers: This section defines which handlers should be used for different loggers. The example includes the root logger and an application-specific logger namedappname.""(root logger): This is the default logger. It captures log messages that do not belong to a more specific logger and sends them to the console and both log files.appname: This logger is useful when you want separate logging behavior for one part of your project. You can give it its own handlers or level if needed.level: This sets the minimum severity a message must have before the logger records it. The example reads the level from theDJANGO_LOG_LEVELenvironment variable, and falls back toINFOwhen the variable is not set.
formatters: This optional section controls how each log message is displayed.verbose: This formatter creates a detailed message format that includes the time, log level, logger name, and the actual message. Thestylevalue of{means the format string uses Python’sstr.format()syntax.simple: This formatter creates a more concise message format that includes only the log level and the message. It also usesstr.format()syntax.
Project URL Configuration
Section titled “Project URL Configuration”This is the main urls.py pattern for a Django project.
For example, if your project folder is myproject, then write myproject.views.
from django.conf import settingsfrom django.conf.urls.static import staticfrom django.contrib import adminfrom django.urls import include, path
from . import views
urlpatterns = [ path("admin/", admin.site.urls), path("", include("appname.urls")),]
handler400 = "appname.views.error_400_view"handler403 = "appname.views.error_403_view"handler404 = "appname.views.error_404_view"handler500 = "appname.views.error_500_view"
if settings.ENVIRONMENT != settings.PRODUCTION: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)Notes:
- Replace
appnamewith your Django project name. - Keep custom error handlers in the main project
urls.py. - Serve media files with
static()only in development. - Do not use this media setup for production servers.
URL converters and URL formatting
Section titled “URL converters and URL formatting”Path converters let Django validate URL segments before your view runs. This makes routes safer, cleaner, and easier to maintain.
<converter:variable-name>i.e. int, str, slug, uuid, etc. are called path converters. They ensure the URL segment matches the expected format before calling the view. And variable-name is the name of the parameter passed to the view likeid,slug, etc.
| Converter | Type | Examples |
|---|---|---|
<int:id> | Integer | 5, 100, 999 |
<str:name> | String | hello, product-name |
<slug:slug> | URL-safe | hello-world |
<uuid:code> | UUID | 550e8400-… |
Example usage in urls.py:
from django.urls import path
from . import views
urlpatterns = [ path("users/<int:id>/", views.user_detail, name="user-detail"), path("categories/<str:name>/", views.category_detail, name="category-detail"), path("posts/<slug:slug>/", views.post_detail, name="post-detail"), path("invoices/<uuid:code>/", views.invoice_detail, name="invoice-detail"),]Django Views
Section titled “Django Views”In Django, a view is the code that receives an HTTP request and returns an HTTP response. Views are where request handling decisions happen, such as:
- reading URL parameters
- validating request method (
GET,POST, etc.) - fetching data from models
- returning HTML, JSON, redirects, or errors
Django supports two main styles:
- Function-Based Views (FBV)
- Class-Based Views (CBV)
Both are production-ready. Use the style that keeps the code easiest to read and maintain for your team.
Reading data from GET and POST requests
Section titled “Reading data from GET and POST requests”This is one of the most important basics in Django views.
- Use
request.GETfor query string data (URL params after?) - Use
request.POSTfor form body data sent with POST requests
Example URL:
/products/?search=phone&category=electronics&page=2
def product_list(request): search = request.GET.get("search", "") category = request.GET.get("category") page = request.GET.get("page", "1")
# Example: /products/?tag=python&tag=django tags = request.GET.getlist("tag")
return JsonResponse( { "search": search, "category": category, "page": page, "tags": tags, } )For POST form submission:
from django.shortcuts import redirect, render
def contact_submit(request): if request.method == "POST": name = request.POST.get("name", "").strip() email = request.POST.get("email", "").strip().lower() message = request.POST.get("message", "").strip()
if not name or not email: return render(request, "contact.html", {"error": "Name and email are required."})
# save/send data return redirect("contact-success")
return render(request, "contact.html")Best practices:
- Use
.get("key", default)to avoidKeyError - Use
.getlist("key")for repeated params - Treat
request.GETandrequest.POSTas untrusted input - For real forms, prefer Django Forms and
cleaned_dataafteris_valid()
Function-Based Views
Section titled “Function-Based Views”Function-Based Views are plain Python functions. They are usually the best choice when logic is custom and straightforward.
from django.contrib.auth.decorators import login_requiredfrom django.shortcuts import get_object_or_404, redirect, renderfrom django.http import JsonResponse
from .models import Article
@login_requireddef article_detail(request, slug): article = get_object_or_404(Article, slug=slug)
if request.method == "POST": # Example: update a counter or process form data article.views_count += 1 article.save(update_fields=["views_count"]) return redirect("article-detail", slug=article.slug)
context = {"article": article} return render(request, "articles/detail.html", context)
def article_api(request): data = { "total_articles": Article.objects.count(), "status": "ok", } return JsonResponse(data, status=200)How to think about this example:
@login_requiredprotects the page so only logged-in users can access it.get_object_or_404()returns the article or an automatic 404 response.request.method == "POST"keeps write logic explicit and easy to audit.redirect()prevents form re-submission on browser refresh.JsonResponseis clean for simple API-style endpoints.
Use FBV when:
- logic is short or highly custom
- you want full control over each method branch
- clarity is more important than abstraction
Production notes for FBV:
- Keep one view focused on one job.
- Check methods explicitly for write operations.
- Apply auth and permission decorators close to the view.
- Move repeated logic into helper functions or services.
Class-Based Views
Section titled “Class-Based Views”Class-Based Views group related behavior in classes and provide reusable generic views. They reduce repeated code for common CRUD pages.
Basic custom CBV
Section titled “Basic custom CBV”from django.http import HttpResponsefrom django.views import View
class HelloView(View): def get(self, request): return HttpResponse("Hello from GET")
def post(self, request): return HttpResponse("Hello from POST")This pattern maps HTTP methods (get, post, and so on) to class methods.
Generic CBV examples
Section titled “Generic CBV examples”from django.contrib.auth.mixins import LoginRequiredMixinfrom django.urls import reverse_lazyfrom django.views.generic import CreateView, DetailView, ListView
from .models import Article
class ArticleListView(ListView): model = Article template_name = "articles/list.html" context_object_name = "articles" paginate_by = 10
class ArticleDetailView(DetailView): model = Article template_name = "articles/detail.html" context_object_name = "article" slug_field = "slug" slug_url_kwarg = "slug"
class ArticleCreateView(LoginRequiredMixin, CreateView): model = Article fields = ["title", "slug", "content"] template_name = "articles/form.html" success_url = reverse_lazy("article-list")How to think about these classes:
ListViewhandles listing and pagination quickly.DetailViewhandles single-object lookup byslug.CreateViewhandles form display, validation, and object creation.LoginRequiredMixinprotects create actions without repeating auth code.
Use CBV when:
- many pages follow similar CRUD patterns
- you want to reuse behavior through mixins
- you want less boilerplate around forms and object lookup
Production notes for CBV:
- Put auth mixins before the generic view class in inheritance order.
- Override only what you need (
get_queryset,form_valid, etc.). - Keep business logic out of templates and large view methods.
Decorators
Section titled “Decorators”@login_required
Section titled “@login_required”Use this when only authenticated users should access a view. It is the first protection layer for private pages.
from django.contrib.auth.decorators import login_required
@login_required(login_url="/accounts/login/")def dashboard(request): return render(request, "accounts/dashboard.html")@csrf_exempt
Section titled “@csrf_exempt”This disables CSRF protection for a view. Use it only when absolutely required, such as a trusted webhook endpoint you cannot protect with CSRF tokens.
from django.views.decorators.csrf import csrf_exempt
@csrf_exemptdef webhook_receiver(request): return JsonResponse({"received": True})Production note:
- Avoid
@csrf_exemptfor regular browser form endpoints. - For APIs, prefer token/session strategies designed for your client type.
Other useful decorators:
@require_http_methods(["GET", "POST"])@require_POST@permission_required("app_label.permission_name")@cache_page(60 * 5)
Helper functions
Section titled “Helper functions”render(request, template_name, context=None, status=200)
Section titled “render(request, template_name, context=None, status=200)”- Combines a template with context and returns
HttpResponse - Standard choice for server-rendered HTML pages
from django.shortcuts import render
def profile(request): return render(request, "accounts/profile.html", {"user": request.user})redirect(to, *args, **kwargs)
Section titled “redirect(to, *args, **kwargs)”- Returns an HTTP redirect (
302by default) tocan be a URL name, model object, or absolute path
from django.shortcuts import redirect
def go_home(request): return redirect("home")JsonResponse(data, status=200, safe=True)
Section titled “JsonResponse(data, status=200, safe=True)”- Returns JSON output, commonly for API endpoints
safe=Truemeans the top-level value must be a dictionary
from django.http import JsonResponse
def health_check(request): return JsonResponse({"ok": True, "service": "django-app"})get_object_or_404(Model, **lookup)
Section titled “get_object_or_404(Model, **lookup)”- Fetches one object or raises
Http404 - Cleaner and safer than manual
try/exceptfor missing objects
from django.shortcuts import get_object_or_404
article = get_object_or_404(Article, slug=slug)URL mapping for FBV and CBV
Section titled “URL mapping for FBV and CBV”from django.urls import path
from .views import ArticleDetailView, ArticleListView, article_detail
urlpatterns = [ path("articles/", ArticleListView.as_view(), name="article-list"), path("articles/<slug:slug>/", ArticleDetailView.as_view(), name="article-detail"), path("legacy-article/<slug:slug>/", article_detail, name="legacy-article-detail"),]Remember:
- CBV must be added with
.as_view()inurls.py - FBV is passed directly as a function
Production notes for URL design:
- Use clear and stable path names (
name="article-detail") for reverse URL lookup. - Keep URL names consistent across templates, redirects, and tests.
- Prefer slug or UUID routes for public resources instead of database IDs when possible.
Security
Section titled “Security”CORS (Cross-Origin Resource Sharing)
Section titled “CORS (Cross-Origin Resource Sharing)”CORS is a security feature implemented by browsers to restrict web pages from making requests to a different domain than the one that served the web page. This is important to prevent malicious websites from accessing sensitive data on another site without permission.
To enable CORS in a Django application, you can use the django-cors-headers package. This package allows you to specify which origins are allowed to make cross-origin requests to your Django application.
-
Install the package using uv:
Terminal window uv add django-cors-headers -
Then, add it to your
INSTALLED_APPSandMIDDLEWAREinsettings.py:INSTALLED_APPS = [# other apps"corsheaders",]MIDDLEWARE = ["corsheaders.middleware.CorsMiddleware", # must be high in the list# other middleware] -
You can configure allowed origins in
settings.py:CORS_ALLOWED_ORIGINS = ["http://localhost:3000","http://127.0.0.1:3000",]