├── backend ├── eos_tracker │ ├── __init__.py │ ├── asgi.py │ ├── wsgi.py │ ├── urls.py │ └── settings.py ├── satellites │ ├── __init__.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── admin.py │ ├── tests.py │ ├── urls.py │ ├── apps.py │ └── views.py ├── .python-version ├── manage.py └── pyproject.toml ├── img └── header.png ├── README.md └── .gitignore /backend/eos_tracker/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/satellites/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/.python-version: -------------------------------------------------------------------------------- 1 | 3.13.0 2 | -------------------------------------------------------------------------------- /backend/satellites/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlosfab/eos-globe-tracker/main/img/header.png -------------------------------------------------------------------------------- /backend/satellites/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /backend/satellites/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /backend/satellites/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /backend/satellites/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from .views import fetch_satellites 3 | 4 | urlpatterns = [ 5 | path('satellites/', fetch_satellites, name='fetch_satellites'), 6 | ] -------------------------------------------------------------------------------- /backend/satellites/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class SatellitesConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "satellites" 7 | -------------------------------------------------------------------------------- /backend/eos_tracker/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for eos_tracker project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "eos_tracker.settings") 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /backend/eos_tracker/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for eos_tracker project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/5.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "eos_tracker.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /backend/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "eos_tracker.settings") 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == "__main__": 22 | main() 23 | -------------------------------------------------------------------------------- /backend/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "eos-globe-tracker" 3 | version = "0.1.0" 4 | description = "A web app for visualizing Earth Observation System (EOS) satellite trajectories on an interactive 3D Earth globe, built with Django, React, and react-globe.gl." 5 | authors = ["Carlos Melo "] 6 | license = "MIT" 7 | readme = "README.md" 8 | 9 | [tool.poetry.dependencies] 10 | python = "^3.13" 11 | django = "^5.0" 12 | djangorestframework = "^3.15" 13 | requests = "^2.31" 14 | skyfield = "^1.48" 15 | python-dotenv = "^1.1.0" 16 | django-cors-headers = "^4.7.0" 17 | 18 | [tool.poetry.group.dev.dependencies] 19 | pytest = "^8.3.3" 20 | black = "^24.10.0" 21 | flake8 = "^7.1.1" 22 | 23 | [build-system] 24 | requires = ["poetry-core"] 25 | build-backend = "poetry.core.masonry.api" 26 | -------------------------------------------------------------------------------- /backend/eos_tracker/urls.py: -------------------------------------------------------------------------------- 1 | """ 2 | URL configuration for eos_tracker project. 3 | 4 | The `urlpatterns` list routes URLs to views. For more information please see: 5 | https://docs.djangoproject.com/en/5.2/topics/http/urls/ 6 | Examples: 7 | Function views 8 | 1. Add an import: from my_app import views 9 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 10 | Class-based views 11 | 1. Add an import: from other_app.views import Home 12 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 13 | Including another URLconf 14 | 1. Import the include() function: from django.urls import include, path 15 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 16 | """ 17 | 18 | from django.contrib import admin 19 | from django.urls import path, include 20 | 21 | urlpatterns = [ 22 | path('api/', include('satellites.urls')), 23 | ] 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EOS Globe Tracker 2 | 3 | 4 | 5 | 6 | ![Banner](img/header.png) 7 | 8 | > **Note**: This project is currently a work in progress. 9 | 10 | A web app for visualizing Earth Observation System (EOS) satellite trajectories on an interactive 3D Earth globe, built with Django, React, and react-globe.gl. 11 | 12 | ## Overview 13 | This project tracks Earth Resources satellites (e.g., SCD 1, TECHSAT 1B, DLR-TUBSAT) using Two-Line Element (TLE) data from Celestrak. The backend fetches and processes satellite positions, while the frontend (to be implemented) will display these satellites on a 3D globe with their orbits and trailing trajectories. 14 | 15 | ## Current Status 16 | - **Backend**: Fully implemented using Django and Django REST Framework. 17 | - Fetches TLE data from Celestrak (`GROUP=resource`). 18 | - Computes satellite positions, orbits, and trails using Skyfield. 19 | - Exposes an API endpoint at `/api/satellites/`. 20 | - **Frontend**: Not yet implemented (planned for React with `react-globe.gl`). 21 | 22 | ## Project Structure 23 | - `backend/`: Django backend for fetching and processing satellite data. 24 | - `pyproject.toml`: Poetry configuration for dependencies. 25 | - `eos_tracker/`: Django project directory. 26 | - `satellites/`: Django app for satellite tracking API. 27 | - `frontend/`: React frontend (to be implemented). 28 | - `README.md`: Project documentation. 29 | 30 | ## Setup 31 | 32 | ### Prerequisites 33 | - Python 3.13 34 | - Node.js 18+ 35 | - Poetry (for Python dependency management) 36 | 37 | ### Backend Setup 38 | 1. Navigate to the backend directory: 39 | ```bash 40 | cd backend 41 | ``` 42 | 2. Install dependencies using Poetry: 43 | ```bash 44 | poetry install 45 | ``` 46 | 3. Activate the virtual environment: 47 | ```bash 48 | poetry shell 49 | ``` 50 | 4. Apply Django migrations: 51 | ```bash 52 | python manage.py migrate 53 | ``` 54 | 5. Run the Django development server: 55 | ```bash 56 | python manage.py runserver 57 | ``` 58 | 6. Access the API at `http://localhost:8000/api/satellites/`. 59 | 60 | ### Frontend Setup 61 | The frontend is not yet implemented. See the `roadmap.md` for next steps. 62 | 63 | ## API Endpoints 64 | - **GET /api/satellites/**: Returns a list of Earth Resources satellites with their positions, orbits, and trails. 65 | - Example Response: 66 | ```json 67 | { 68 | "satellites": [ 69 | { 70 | "name": "SCD 1", 71 | "latitude": 10.234, 72 | "longitude": -50.678, 73 | "altitude": 750.2, 74 | "velocity": 7.3, 75 | "orbit": [[10.234, -50.678], ...], 76 | "trail": [[10.200, -50.650], ...] 77 | }, 78 | ... 79 | ] 80 | } 81 | ``` 82 | 83 | ## Dependencies 84 | - **Backend**: 85 | - Django 5.0 86 | - Django REST Framework 3.15 87 | - Requests 2.31 88 | - Skyfield 1.48 89 | - Django CORS Headers (for frontend-backend communication) 90 | 91 | ## License 92 | MIT License (see `LICENSE` file for details). 93 | 94 | ## Author 95 | Carlos Melo -------------------------------------------------------------------------------- /backend/satellites/views.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from django.core.cache import cache 3 | from django.conf import settings 4 | from rest_framework.decorators import api_view 5 | from rest_framework.response import Response 6 | from skyfield.api import EarthSatellite, load, wgs84 7 | 8 | @api_view(['GET']) 9 | def fetch_satellites(request): 10 | tle_cache_key = "celestrak_tle_resource" 11 | tle_cache_timeout = 3600 12 | 13 | tle_lines = cache.get(tle_cache_key) 14 | if tle_lines is None: 15 | url = settings.CELESTRAK_TLE_URL 16 | headers = { 17 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" 18 | } 19 | try: 20 | response = requests.get(url, headers=headers, timeout=10) 21 | response.raise_for_status() 22 | tle_lines = response.text.splitlines() 23 | cache.set(tle_cache_key, tle_lines, tle_cache_timeout) 24 | print("Fetched new TLE data from Celestrak") 25 | except requests.exceptions.RequestException as e: 26 | print("Request Error:", str(e)) 27 | return Response({"error": str(e)}, status=500) 28 | else: 29 | print("Using cached TLE data") 30 | 31 | print("TLE Lines:", tle_lines) 32 | print("Number of TLE Lines:", len(tle_lines)) 33 | 34 | if len(tle_lines) < 3: 35 | return Response({"error": "No valid TLE data returned from Celestrak"}, status=400) 36 | 37 | ts = load.timescale() 38 | t = ts.now() 39 | satellites = [] 40 | for i in range(0, len(tle_lines), 3): 41 | if i + 2 < len(tle_lines): 42 | if not (tle_lines[i].strip() and tle_lines[i + 1].startswith('1 ') and tle_lines[i + 2].startswith('2 ')): 43 | print(f"Invalid TLE format at index {i}: {tle_lines[i:i+3]}") 44 | continue 45 | 46 | try: 47 | sat = EarthSatellite(tle_lines[i + 1], tle_lines[i + 2], tle_lines[i], ts) 48 | geocentric = sat.at(t) 49 | subpoint = wgs84.subpoint(geocentric) 50 | mean_motion = sat.model.no_kozai 51 | period_minutes = (2 * 3.14159 / mean_motion) 52 | times = ts.utc(t.utc_datetime().year, t.utc_datetime().month, t.utc_datetime().day, 53 | t.utc_datetime().hour, t.utc_datetime().minute, 54 | range(int(t.utc_datetime().second), int(t.utc_datetime().second + period_minutes * 60), 60)) 55 | path = [] 56 | for ti in times: 57 | geo = sat.at(ti) 58 | sub = wgs84.subpoint(geo) 59 | path.append([float(sub.latitude.degrees), float(sub.longitude.degrees)]) 60 | trail_times = ts.utc(t.utc_datetime().year, t.utc_datetime().month, t.utc_datetime().day, 61 | t.utc_datetime().hour, t.utc_datetime().minute, 62 | range(int(t.utc_datetime().second - 300), int(t.utc_datetime().second), 60)) 63 | trail = [] 64 | for ti in trail_times: 65 | geo = sat.at(ti) 66 | sub = wgs84.subpoint(geo) 67 | trail.append([float(sub.latitude.degrees), float(sub.longitude.degrees)]) 68 | satellites.append({ 69 | "name": sat.name, 70 | "latitude": float(subpoint.latitude.degrees), 71 | "longitude": float(subpoint.longitude.degrees), 72 | "altitude": float(subpoint.elevation.km), 73 | "velocity": float(sum(v ** 2 for v in geocentric.velocity.km_per_s) ** 0.5), 74 | "orbit": path, 75 | "trail": trail 76 | }) 77 | except Exception as e: 78 | print(f"Error processing satellite at index {i}: {str(e)}") 79 | continue 80 | 81 | print("Satellites List:", satellites) 82 | if not satellites: 83 | return Response({"error": "No valid Earth Resources satellites found in TLE data"}, status=400) 84 | 85 | return Response({"satellites": satellites[:10]}) -------------------------------------------------------------------------------- /backend/eos_tracker/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for eos_tracker project. 3 | 4 | Generated by 'django-admin startproject' using Django 5.2.1. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/5.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/5.2/ref/settings/ 11 | """ 12 | 13 | import os 14 | from pathlib import Path 15 | from dotenv import load_dotenv 16 | 17 | # Load environment variables from .env file 18 | load_dotenv() 19 | 20 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 21 | BASE_DIR = Path(__file__).resolve().parent.parent 22 | 23 | # Celestrak Configuration 24 | CELESTRAK_TLE_URL = os.getenv("CELESTRAK_TLE_URL", "https://celestrak.org/NORAD/elements/gp.php?GROUP=resource&FORMAT=tle") 25 | 26 | # SECURITY WARNING: keep the secret key used in production secret! 27 | # Load SECRET_KEY from environment variable 28 | SECRET_KEY = os.getenv("DJANGO_SECRET_KEY") 29 | if not SECRET_KEY: 30 | raise ValueError("DJANGO_SECRET_KEY environment variable not set!") 31 | 32 | # SECURITY WARNING: don't run with debug turned on in production! 33 | DEBUG = os.getenv("DJANGO_DEBUG", "True").lower() == "true" 34 | 35 | ALLOWED_HOSTS = ['localhost', '127.0.0.1'] 36 | 37 | # Application definition 38 | 39 | INSTALLED_APPS = [ 40 | "django.contrib.admin", 41 | "django.contrib.auth", 42 | "django.contrib.contenttypes", 43 | "django.contrib.sessions", 44 | "django.contrib.messages", 45 | "django.contrib.staticfiles", 46 | "rest_framework", 47 | "corsheaders", 48 | "satellites", 49 | ] 50 | 51 | MIDDLEWARE = [ 52 | "django.middleware.security.SecurityMiddleware", 53 | "django.contrib.sessions.middleware.SessionMiddleware", 54 | "corsheaders.middleware.CorsMiddleware", 55 | "django.middleware.common.CommonMiddleware", 56 | "django.middleware.csrf.CsrfViewMiddleware", 57 | "django.contrib.auth.middleware.AuthenticationMiddleware", 58 | "django.contrib.messages.middleware.MessageMiddleware", 59 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 60 | ] 61 | 62 | # CORS settings for development (React frontend on localhost:3000) 63 | CORS_ALLOWED_ORIGINS = [ 64 | "http://localhost:3000", 65 | "http://127.0.0.1:3000", 66 | ] 67 | 68 | # For development, allow all origins if needed (less secure, comment out in production) 69 | CORS_ALLOW_ALL_ORIGINS = True 70 | 71 | ROOT_URLCONF = "eos_tracker.urls" 72 | 73 | TEMPLATES = [ 74 | { 75 | "BACKEND": "django.template.backends.django.DjangoTemplates", 76 | "DIRS": [], 77 | "APP_DIRS": True, 78 | "OPTIONS": { 79 | "context_processors": [ 80 | "django.template.context_processors.request", 81 | "django.contrib.auth.context_processors.auth", 82 | "django.contrib.messages.context_processors.messages", 83 | ], 84 | }, 85 | }, 86 | ] 87 | 88 | WSGI_APPLICATION = "eos_tracker.wsgi.application" 89 | 90 | # Database 91 | # https://docs.djangoproject.com/en/5.2/ref/settings/#databases 92 | 93 | DATABASES = { 94 | "default": { 95 | "ENGINE": "django.db.backends.sqlite3", 96 | "NAME": BASE_DIR / "db.sqlite3", 97 | } 98 | } 99 | 100 | # Password validation 101 | # https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators 102 | 103 | AUTH_PASSWORD_VALIDATORS = [ 104 | { 105 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", 106 | }, 107 | { 108 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", 109 | }, 110 | { 111 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", 112 | }, 113 | { 114 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", 115 | }, 116 | ] 117 | 118 | # Internationalization 119 | # https://docs.djangoproject.com/en/5.2/topics/i18n/ 120 | 121 | LANGUAGE_CODE = "en-us" 122 | 123 | TIME_ZONE = "America/New_York" 124 | 125 | USE_I18N = True 126 | 127 | USE_TZ = True 128 | 129 | # Static files (CSS, JavaScript, Images) 130 | # https://docs.djangoproject.com/en/5.2/howto/static-files/ 131 | 132 | STATIC_URL = "static/" 133 | 134 | # Default primary key field type 135 | # https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field 136 | 137 | DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" 138 | 139 | # Caching configuration 140 | CACHES = { 141 | 'default': { 142 | 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 143 | 'LOCATION': 'unique-snowflake', 144 | } 145 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | .venv/ 6 | .env 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | share/python-wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .nox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | *.py,cover 52 | .hypothesis/ 53 | .pytest_cache/ 54 | cover/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | .pybuilder/ 78 | target/ 79 | 80 | # Jupyter Notebook 81 | .ipynb_checkpoints 82 | 83 | # IPython 84 | profile_default/ 85 | ipython_config.py 86 | 87 | # pyenv 88 | # For a library or package, you might want to ignore these files since the code is 89 | # intended to run in multiple environments; otherwise, check them in: 90 | # .python-version 91 | 92 | # pipenv 93 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 94 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 95 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 96 | # install all needed dependencies. 97 | #Pipfile.lock 98 | 99 | # UV 100 | # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. 101 | # This is especially recommended for binary packages to ensure reproducibility, and is more 102 | # commonly ignored for libraries. 103 | #uv.lock 104 | 105 | # poetry 106 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 107 | # This is especially recommended for binary packages to ensure reproducibility, and is more 108 | # commonly ignored for libraries. 109 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 110 | #poetry.lock 111 | 112 | # pdm 113 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 114 | #pdm.lock 115 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 116 | # in version control. 117 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 118 | .pdm.toml 119 | .pdm-python 120 | .pdm-build/ 121 | 122 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 123 | __pypackages__/ 124 | 125 | # Celery stuff 126 | celerybeat-schedule 127 | celerybeat.pid 128 | 129 | # SageMath parsed files 130 | *.sage.py 131 | 132 | # Environments 133 | .env 134 | .venv 135 | env/ 136 | venv/ 137 | ENV/ 138 | env.bak/ 139 | venv.bak/ 140 | 141 | # Spyder project settings 142 | .spyderproject 143 | .spyproject 144 | 145 | # Rope project settings 146 | .ropeproject 147 | 148 | # mkdocs documentation 149 | /site 150 | 151 | # mypy 152 | .mypy_cache/ 153 | .dmypy.json 154 | dmypy.json 155 | 156 | # Pyre type checker 157 | .pyre/ 158 | 159 | # pytype static type analyzer 160 | .pytype/ 161 | 162 | # Cython debug symbols 163 | cython_debug/ 164 | 165 | # PyCharm 166 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 167 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 168 | # and can be added to the global gitignore or merged into this file. For a more nuclear 169 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 170 | #.idea/ 171 | 172 | # Ruff stuff: 173 | .ruff_cache/ 174 | 175 | # PyPI configuration file 176 | .pypirc 177 | 178 | # VS Code 179 | .vscode/ 180 | 181 | # Visual Studio Code settings 182 | pycache/ *.pyc *.pyo *.pyd .Python *.egg-info/ dist/ build/ .venv/ venv/ env/ 183 | 184 | # VS Code settings 185 | *.log *.pot *.pyc local_settings.py db.sqlite3 186 | 187 | # Logs 188 | node_modules/ npm-debug.log package-lock.json 189 | 190 | # macOS Finder 191 | .DS_Store 192 | .idea/ *.swp *~ 193 | backend/manage.py 194 | backend/poetry.lock 195 | --------------------------------------------------------------------------------