initial rasaddam backend project

This commit is contained in:
2025-04-29 15:43:53 +03:30
parent 4e757b1776
commit 2e17d0cd44
67 changed files with 1634 additions and 0 deletions

0
.dockerignore Normal file
View File

15
.env.example Normal file
View File

@@ -0,0 +1,15 @@
# Django secrets
SECRET_KEY=super-insecure-django-key
DEBUG=True
ALLOWED_HOSTS=*
ENV_NAME=DEV
# Datbase secrets
DB_HOST=localhost
DB_PORT=5432
DB_NAME=example_db
DB_USERNAME=postgres
DB_PASSWORD=12345678
# Super user information
SUPERUSER_EMAIL=superuser@example.com

66
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,66 @@
default_language_version:
python: python3.11
default_stages: [commit]
fail_fast: true
repos:
# Basic pre-commit hooks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.3.0
hooks:
- id: check-xml
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace
- id: check-added-large-files
- id: check-ast
- id: debug-statements
- id: no-commit-to-branch
args: ['--branch', 'development', '--branch', 'staging', '--branch', 'production']
# Dependencies checker
- repo: https://github.com/Lucas-C/pre-commit-hooks-safety
rev: v1.3.2
hooks:
- id: python-safety-dependencies-check
files: common.txt, local.txt
# isort For sorting imports
- repo: https://github.com/pycqa/isort
rev: 5.12.0
hooks:
- id: isort
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.4.4
hooks:
- id: ruff
name: ruff
entry: ruff check --fix
language: python
- id: ruff-format
name: ruff-format
entry: ruff format
language: python
# Codespell for spell checking
# - repo: https://github.com/codespell-project/codespell
# rev: v2.2.4
# hooks:
# - id: codespell
# exclude: >
# (?x)^(
# .*\test_*.py
# )$
# Pytest for testing
# - repo: local
# hooks:
# - id: pytest-check
# stages: [commit]
# types: [python]
# name: pytest-check
# entry: pytest
# language: system
# pass_filenames: false
# always_run: true

217
README.md Normal file
View File

@@ -0,0 +1,217 @@
[![Python 3.12.3](https://img.shields.io/badge/python-3.12.3-blue.svg)](https://www.python.org/downloads/release/python-3123/)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
[![Pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://pre-commit.com/)
![Django](https://img.shields.io/badge/django-%23092E20.svg?style=for-the-badge&logo=django&logoColor=white)
![DjangoREST](https://img.shields.io/badge/DJANGO-REST-ff1709?style=for-the-badge&logo=django&logoColor=white&color=ff1709&labelColor=gray)
![Postgres](https://img.shields.io/badge/postgres-%23316192.svg?style=for-the-badge&logo=postgresql&logoColor=white)
![Swagger](https://img.shields.io/badge/-Swagger-%23Clojure?style=for-the-badge&logo=swagger&logoColor=white)
# Django Project Structure
This is a template/project structure for developing django-based applications -
using `django-rest-framework` along with `django`.
The project is meant to be easily clone-able, and used as the starter template
for the next big thing you develop. Note, this is a folder structure only, not
“best practices”.
## Some batteries included:
* [Django Storages](https://django-storages.readthedocs.io/en/stable/) - To integrate with different types of storages
* [Django Rest Framework](https://www.django-rest-framework.org/) - For API development
* [Django CORS Headers](https://github.com/adamchainz/django-cors-headers) - To allow requests from other origins
* [Sentry](https://docs.sentry.io/platforms/python/) - For crashes
* [Gunicorn](https://gunicorn.org/) - As a web server
## Getting Started
1. Since this is a template repository, simply hit "Use this template" on GitHub
and follow the instructions. Otherwise, you can just clone the repo, remove/add
anything you see fit.
1. Run the project using `python manage.py runserver` and you should see the
default success page provided by Django at
[http://127.0.0.1:8000/](http://127.0.0.1:8000/).
1. [Optional] If you want to configure database, in the `DATABASE` section of
`settings.py` we have added `postgresql` as the default `DATABASE` (As most of
the application are using it). You can roll back to the `sqlite` by adding the
following code snippet, removing the current one.
```bash
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
```
### Creating an App
1. Create a folder with the app name in `apps`. For example: `poll`
1. Run `python manage.py startapp poll apps/poll` from the root directory of the
project
## Project Tree
``` bash
.
├── apps/
│ └── example/ # A django rest app
│ ├── api/
│ │ ├── v1/ # Only the "presentation" layer exists here.
│ │ │ ├── __init__.py
│ │ │ ├── serializers.py
│ │ │ ├── urls.py
│ │ │ └── views.py
│ │ ├── v2 # Only the "presentation" layer exists here.
│ │ │ ├── __init__.py
│ │ │ ├── serializers.py
│ │ │ ├── urls.py
│ │ │ └── views.py
│ │ └── __init__.py
│ ├── fixtures/ # Constant "seeders" to populate your database
│ ├── management/
│ │ ├── commands/ # Try and write some database seeders here
│ │ │ └── command.py
│ │ └── __init__.py
│ ├── migrations/
│ │ └── __init__.py
│ ├── templates/ # App-specific templates go here
│ ├── tests/ # All your integration and unit tests for an app go here.
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── models.py
│ ├── services.py # Your business logic and data abstractions go here.
│ ├── urls.py
│ └── views.py
├── common/ # An optional folder containing common "stuff" for the entire project
├── config/
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── deployments/ # Isolate Dockerfiles and docker-compose files here.
├── docs/
│ ├── CHANGELOG.md
│ ├── CONTRIBUTING.md
│ ├── deployment.md
│ ├── local-development.md
│ └── swagger.yaml
├── requirements/
│ ├── common.txt # Same for all environments
│ ├── development.txt # Only for a development server
│ ├── local.txt # Only for a local server (example: docs, performance testing, etc.)
│ └── production.txt # Production only
├── static/ # Your static files
├── .env.example # An example of your .env configurations. Add necessary comments.
├── .gitignore # https://github.com/github/gitignore/blob/main/Python.gitignore
├── entrypoint.sh # Any bootstrapping necessary for your application
├── manage.py
├── pytest.ini
└── README.md
```
## Rationale
Each `app` should be designed in way to be plug-able, that is, dragged and dropped
into any other project and itll work independently.
### `apps` Folder
* A mother-folder containing all apps for our project. Congruent to any
JS-framework's `src` folder. If you really wanted to, you could even call it the
`src` folder. Again, it's up to you.
* An app can be a django template project, or a rest framework API.
### `services`
* Well be writing business logic in services instead of anywhere else.
* There's a common argument: "Why not just use model managers?", and honestly,
that's a fair point. However, for our use case, we've often noticed that a single
service can leverage more zero to many models. Either way, managers or services,
both work towards the same goal - isolating business logic away from views, and
brings it closer to the data.
### `api` Folder
* We like to place all our API components into a package within an app called
`api`. For example, in this repository it's the `example/api` folder. That
allows us to isolate our API components in a consistent location. If
we were to put it in the root of our app, then we would end up with a huge list
of API-specific modules in the general area of the app. That's without getting
into the mess of API versioning.
For projects with a lot of small, interconnecting apps, it can be hard to hunt
down where a particular API view lives. In contrast to placing all API code
within each relevant app, sometimes it makes more sense to build an app
specifically for the API. This is where all the serializers, renderers, and views
are placed. Therefore, the name of the app should reflect its API version
#### API Versioning
It might often be necessary to support multiple versions of an API throughout
the lifetime of a project. Therefore, we're adding in support right from the
start.
For different API versions, we're assuming the following will change:
- Serializers: That is, how the data is presented to a consumer
- Views: That is, how the data is accessed and modified by a consumer
- URLs: That is, where the consumer access the data
`model`s and `service`s can be thought of as shared between versions. Therefore,
migrating changes should be versioned carefully without breaking different
versions of the API. After all, your API version is simply a presentation of how
data is handled and managed within your application.
Sufficient unit tests and integration tests should wrap services and API
endpoints to ensure full compatibility.
#### What's `v2` of an API?
Currently, we're proposing that major changes to the following constitutes a new
API version:
1. Representation of data, either for submission or retrieval
1. Major optimizations
1. Major code reorganization and code refactor
1. Usually, in a Django project, you won't need to worry about API versioning
### `config`
* Contains project configuration files, including the primary URL file
* ~~Contains settings split into `base`, `local`, `production` and `development`.~~.
Update: As environment specific variables will be handled using environment
variables, we've deemed it unnecessary to have separate settings files for now.
### `deployments`
* Contains Docker, Docker-Compose and nginx specific files for deploying in
different environments.
### Exception handling
You should probably add a custom exception handler to your project based on
who consumes your APIs. To learn how to create a custom exception handler,
you can check out the Django Rest Framework documentation at:
https://www.django-rest-framework.org/api-guide/exceptions/#custom-exception-handling
## FAQ
> Why not just make a cookiecutter out of this?
Honestly, GitHub templates are so much easier. It's a one-click solution and
you're good to go. If we want to turn this into a cookiecutter, we'd have to also
start deciding sensible defaults, for instance, sentry, DRF version, formatters,
linters, etc. And that's something better left to the developer. Although, I am
playing around with the idea of having a cookiecutter with those sensible
defaults, but let's hope we have time to work on it on the `cookiecutter` branch.
## References
- [Two Scoops of Django by Daniel and Audrey Feldroy](https://www.feldroy.com/books/two-scoops-of-django-3-x)
- [Django Best Practices](https://django-best-practices.readthedocs.io/en/latest/index.html)
- [Cookiecutter Django](https://github.com/cookiecutter/cookiecutter-django)
- [HackSoft Django Style Guide](https://github.com/HackSoftware/Django-Styleguide)
- [Radoslav Georgiev - Django Structure for Scale and Longevity](https://www.youtube.com/watch?v=yG3ZdxBb1oo)
- [Build APIs You Won't Hate](https://apisyouwonthate.com/books/build-apis-you-wont-hate/)
- [Tuxedo Style Guides](https://github.com/saqibur/tuxedo)
- [Django Anti Patterns](https://www.django-antipatterns.com/)

View File

16
Rasaddam_Backend/asgi.py Normal file
View File

@@ -0,0 +1,16 @@
"""
ASGI config for Rasaddam_Backend project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Rasaddam_Backend.settings')
application = get_asgi_application()

View File

@@ -0,0 +1,122 @@
"""
Django settings for Rasaddam_Backend project.
Generated by 'django-admin startproject' using Django 5.2.
For more information on this file, see
https://docs.djangoproject.com/en/5.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.2/ref/settings/
"""
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-@0apn-lk85pfw=z00x2ib$w9#rwz8%2v4i_n^^9jz-m9b+y55*'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'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',
]
ROOT_URLCONF = 'Rasaddam_Backend.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'Rasaddam_Backend.wsgi.application'
# Database
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Password validation
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/5.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.2/howto/static-files/
STATIC_URL = 'static/'
# Default primary key field type
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

22
Rasaddam_Backend/urls.py Normal file
View File

@@ -0,0 +1,22 @@
"""
URL configuration for Rasaddam_Backend project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/5.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
urlpatterns = [
path('admin/', admin.site.urls),
]

16
Rasaddam_Backend/wsgi.py Normal file
View File

@@ -0,0 +1,16 @@
"""
WSGI config for Rasaddam_Backend project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.2/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Rasaddam_Backend.settings')
application = get_wsgi_application()

0
apps/core/__init__.py Normal file
View File

6
apps/core/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class CoreConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "apps.core"

1
apps/core/constants.py Normal file
View File

@@ -0,0 +1 @@
# Add your app constants here.

6
apps/core/exceptions.py Normal file
View File

@@ -0,0 +1,6 @@
"""
You should probably add a custom exception handler to your project based on
who consumes your APIs. To learn how to create a custom exception handler,
you can check out the Django Rest Framework documentation at:
https://www.django-rest-framework.org/api-guide/exceptions/#custom-exception-handling
"""

0
apps/core/helpers.py Normal file
View File

0
apps/core/models.py Normal file
View File

3
apps/core/urls.py Normal file
View File

@@ -0,0 +1,3 @@
app_name = "core"
urlpatterns = []

0
apps/example/__init__.py Normal file
View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

0
apps/example/apps.py Normal file
View File

View File

View File

View File

@@ -0,0 +1 @@
# Your custom management commands go here.

View File

0
apps/example/models.py Normal file
View File

1
apps/example/services.py Normal file
View File

@@ -0,0 +1 @@
# Your services go here

1
apps/example/urls.py Normal file
View File

@@ -0,0 +1 @@
# Your urls go here

1
common/constants.py Normal file
View File

@@ -0,0 +1 @@
# Add your common constants here that are common for all other apps.

0
common/generics.py Normal file
View File

0
common/helpers.py Normal file
View File

0
common/mixins.py Normal file
View File

84
common/models.py Normal file
View File

@@ -0,0 +1,84 @@
# Standard library imports
import uuid
# Django imports
from django.conf import settings
from django.db import models
from django.utils.translation import gettext_lazy as _
# Django Rest Framework imports
# Third party imports
# Local imports
user_model = settings.AUTH_USER_MODEL
class IsDeletedManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(is_deleted=False)
class BaseModel(models.Model):
"""
Tracks instance creations, updates, and (soft) deletions.
"""
uuid = models.UUIDField(
verbose_name=_("UUID"), unique=True, default=uuid.uuid4, editable=False
)
created_by = models.ForeignKey(
to=user_model,
verbose_name=_("Created by"),
null=True,
blank=True,
related_name="%(class)s_created",
on_delete=models.SET_NULL,
)
created_at = models.DateTimeField(
verbose_name=_("Created at"),
auto_now_add=True,
editable=False,
db_index=True,
)
updated_by = models.ForeignKey(
to=user_model,
verbose_name=_("Updated by"),
null=True,
blank=True,
related_name="%(class)s_updated",
on_delete=models.SET_NULL,
)
updated_at = models.DateTimeField(
verbose_name=_("Updated at"),
auto_now=True,
null=True,
blank=True,
)
deleted_by = models.ForeignKey(
to=user_model,
verbose_name=_("Deleted by"),
null=True,
blank=True,
related_name="%(class)s_deleted",
on_delete=models.SET_NULL,
)
deleted_at = models.DateTimeField(
verbose_name=_("Deleted at"), null=True, blank=True, default=None
)
is_deleted = models.BooleanField(verbose_name=_("Is deleted"), default=False)
objects = IsDeletedManager()
objects_all = models.Manager()
class Meta:
abstract = True

43
common/serializers.py Normal file
View File

@@ -0,0 +1,43 @@
from typing import List
from rest_framework.request import Request
from rest_framework.serializers import ModelSerializer, Serializer
class DynamicFieldsModelSerializer(ModelSerializer):
"""
A ModelSerializer that takes an additional `fields` argument that
controls which fields should be displayed.
"""
def __init__(self, *args, **kwargs):
# Don't pass the 'fields' arg up to the superclass
fields = kwargs.pop("fields", None)
# Instantiate the superclass normally
super().__init__(*args, **kwargs)
if fields is not None:
# Drop any fields that are not specified in the `fields` argument.
allowed = set(fields)
existing = set(self.fields)
for field_name in existing - allowed:
self.fields.pop(field_name)
def create_validated_instance(serializer: Serializer, request: Request):
serializer = serializer(data=request.data, context={"request": request})
serializer.is_valid(raise_exception=True)
return serializer.save(), serializer.validated_data
def get_validated_data(
serializer: Serializer, request: Request, fields: List[str] = None
):
if fields and issubclass(serializer, DynamicFieldsModelSerializer):
serializer = serializer(fields=fields, data=request.data)
else:
serializer = serializer(data=request.data)
serializer.is_valid(raise_exception=True)
return serializer.validated_data

0
config/__init__.py Normal file
View File

16
config/asgi.py Normal file
View File

@@ -0,0 +1,16 @@
"""
ASGI config for config project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
application = get_asgi_application()

151
config/settings.py Normal file
View File

@@ -0,0 +1,151 @@
"""
Django settings for config project.
Generated by 'django-admin startproject' using Django 4.0.1.
For more information on this file, see
https://docs.djangoproject.com/en/4.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.0/ref/settings/
"""
import os
from pathlib import Path
import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = "django-insecure-$227hjjmuq2e!)o^@2&#2v#+(-=@$v362o@8g#s9!2)tjn1)1a"
ALLOWED_HOSTS = []
DEFAULT_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
]
THIRD_PARTY_APPS = [
"rest_framework",
"corsheaders",
"storages",
"django_filters",
]
SELF_APPS = [
"apps.core",
]
INSTALLED_APPS = DEFAULT_APPS + THIRD_PARTY_APPS + SELF_APPS
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"corsheaders.middleware.CorsMiddleware", # Third-Party Middleware
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
ROOT_URLCONF = "config.urls"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"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",
],
},
},
]
WSGI_APPLICATION = "config.wsgi.application"
# Database
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql_psycopg2",
"NAME": "your-db-name",
"USER": "your-db-user",
"PASSWORD": "your-db-user-password",
"HOST": "your-db-host",
"PORT": "your-db-port",
}
}
# Password validation
# https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
{
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
]
# Internationalization
# https://docs.djangoproject.com/en/4.0/topics/i18n/
LANGUAGE_CODE = "en-us"
TIME_ZONE = "UTC"
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.0/howto/static-files/
STATIC_URL = "static/"
# Default primary key field type
# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
DEBUG = True
### --- SENTRY SETTINGS --- ###
if not DEBUG:
sentry_sdk.init(
dsn=os.environ("SENTRY_DSN"),
integrations=[
DjangoIntegration(),
],
traces_sample_rate=0.5,
send_default_pii=False,
)

8
config/urls.py Normal file
View File

@@ -0,0 +1,8 @@
from django.urls import (
include,
path,
)
urlpatterns = [
path("", include("apps.core.urls")),
]

16
config/wsgi.py Normal file
View File

@@ -0,0 +1,16 @@
"""
WSGI config for config project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
application = get_wsgi_application()

View File

View File

View File

View File

@@ -0,0 +1,33 @@
# Gunicorn server
upstream django {
server domain.com:9000;
}
# Redirect all requests on the www subdomain to the root domain
server {
listen 80;
server_name www.domain.com;
rewrite ^/(.*) http://domain.com/$1 permanent;
}
# Serve static files and redirect any other request to Gunicorn
server {
listen 80;
server_name domain.com;
root /var/www/domain.com/;
access_log /var/log/nginx/domain.com.access.log;
error_log /var/log/nginx/domain.com.error.log;
# Check if a file exists at /var/www/domain/ for the incoming request.
# If it doesn't proxy to Gunicorn/Django.
try_files $uri @django;
# Setup named location for Django requests and handle proxy details
location @django {
proxy_pass http://django;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}

0
docs/CHANGELOG.md Normal file
View File

0
docs/CONTRIBUTING.md Normal file
View File

0
docs/deployment.md Normal file
View File

728
docs/swagger.yaml Normal file
View File

@@ -0,0 +1,728 @@
openapi: 3.0.1
info:
title: Swagger Petstore
description: 'This is a sample server Petstore server. You can find out more about Swagger
at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For
this sample, you can use the api key `special-key` to test the authorization filters.'
termsOfService: http://swagger.io/terms/
contact:
email: apiteam@swagger.io
license:
name: Apache 2.0
url: http://www.apache.org/licenses/LICENSE-2.0.html
version: 1.0.0
externalDocs:
description: Find out more about Swagger
url: http://swagger.io
servers:
- url: https://petstore.swagger.io/v2
- url: http://petstore.swagger.io/v2
tags:
- name: pet
description: Everything about your Pets
externalDocs:
description: Find out more
url: http://swagger.io
- name: store
description: Access to Petstore orders
- name: user
description: Operations about user
externalDocs:
description: Find out more about our store
url: http://swagger.io
paths:
/pet:
put:
tags:
- pet
summary: Update an existing pet
operationId: updatePet
requestBody:
description: Pet object that needs to be added to the store
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
application/xml:
schema:
$ref: '#/components/schemas/Pet'
required: true
responses:
400:
description: Invalid ID supplied
content: {}
404:
description: Pet not found
content: {}
405:
description: Validation exception
content: {}
security:
- petstore_auth:
- write:pets
- read:pets
x-codegen-request-body-name: body
post:
tags:
- pet
summary: Add a new pet to the store
operationId: addPet
requestBody:
description: Pet object that needs to be added to the store
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
application/xml:
schema:
$ref: '#/components/schemas/Pet'
required: true
responses:
405:
description: Invalid input
content: {}
security:
- petstore_auth:
- write:pets
- read:pets
x-codegen-request-body-name: body
/pet/findByStatus:
get:
tags:
- pet
summary: Finds Pets by status
description: Multiple status values can be provided with comma separated strings
operationId: findPetsByStatus
parameters:
- name: status
in: query
description: Status values that need to be considered for filter
required: true
style: form
explode: true
schema:
type: array
items:
type: string
default: available
enum:
- available
- pending
- sold
responses:
200:
description: successful operation
content:
application/xml:
schema:
type: array
items:
$ref: '#/components/schemas/Pet'
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Pet'
400:
description: Invalid status value
content: {}
security:
- petstore_auth:
- write:pets
- read:pets
/pet/findByTags:
get:
tags:
- pet
summary: Finds Pets by tags
description: Muliple tags can be provided with comma separated strings. Use tag1,
tag2, tag3 for testing.
operationId: findPetsByTags
parameters:
- name: tags
in: query
description: Tags to filter by
required: true
style: form
explode: true
schema:
type: array
items:
type: string
responses:
200:
description: successful operation
content:
application/xml:
schema:
type: array
items:
$ref: '#/components/schemas/Pet'
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Pet'
400:
description: Invalid tag value
content: {}
deprecated: true
security:
- petstore_auth:
- write:pets
- read:pets
/pet/{petId}:
get:
tags:
- pet
summary: Find pet by ID
description: Returns a single pet
operationId: getPetById
parameters:
- name: petId
in: path
description: ID of pet to return
required: true
schema:
type: integer
format: int64
responses:
200:
description: successful operation
content:
application/xml:
schema:
$ref: '#/components/schemas/Pet'
application/json:
schema:
$ref: '#/components/schemas/Pet'
400:
description: Invalid ID supplied
content: {}
404:
description: Pet not found
content: {}
security:
- api_key: []
post:
tags:
- pet
summary: Updates a pet in the store with form data
operationId: updatePetWithForm
parameters:
- name: petId
in: path
description: ID of pet that needs to be updated
required: true
schema:
type: integer
format: int64
requestBody:
content:
application/x-www-form-urlencoded:
schema:
properties:
name:
type: string
description: Updated name of the pet
status:
type: string
description: Updated status of the pet
responses:
405:
description: Invalid input
content: {}
security:
- petstore_auth:
- write:pets
- read:pets
delete:
tags:
- pet
summary: Deletes a pet
operationId: deletePet
parameters:
- name: api_key
in: header
schema:
type: string
- name: petId
in: path
description: Pet id to delete
required: true
schema:
type: integer
format: int64
responses:
400:
description: Invalid ID supplied
content: {}
404:
description: Pet not found
content: {}
security:
- petstore_auth:
- write:pets
- read:pets
/pet/{petId}/uploadImage:
post:
tags:
- pet
summary: uploads an image
operationId: uploadFile
parameters:
- name: petId
in: path
description: ID of pet to update
required: true
schema:
type: integer
format: int64
requestBody:
content:
multipart/form-data:
schema:
properties:
additionalMetadata:
type: string
description: Additional data to pass to server
file:
type: string
description: file to upload
format: binary
responses:
200:
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/ApiResponse'
security:
- petstore_auth:
- write:pets
- read:pets
/store/inventory:
get:
tags:
- store
summary: Returns pet inventories by status
description: Returns a map of status codes to quantities
operationId: getInventory
responses:
200:
description: successful operation
content:
application/json:
schema:
type: object
additionalProperties:
type: integer
format: int32
security:
- api_key: []
/store/order:
post:
tags:
- store
summary: Place an order for a pet
operationId: placeOrder
requestBody:
description: order placed for purchasing the pet
content:
'*/*':
schema:
$ref: '#/components/schemas/Order'
required: true
responses:
200:
description: successful operation
content:
application/xml:
schema:
$ref: '#/components/schemas/Order'
application/json:
schema:
$ref: '#/components/schemas/Order'
400:
description: Invalid Order
content: {}
x-codegen-request-body-name: body
/store/order/{orderId}:
get:
tags:
- store
summary: Find purchase order by ID
description: For valid response try integer IDs with value >= 1 and <= 10. Other
values will generated exceptions
operationId: getOrderById
parameters:
- name: orderId
in: path
description: ID of pet that needs to be fetched
required: true
schema:
maximum: 10.0
minimum: 1.0
type: integer
format: int64
responses:
200:
description: successful operation
content:
application/xml:
schema:
$ref: '#/components/schemas/Order'
application/json:
schema:
$ref: '#/components/schemas/Order'
400:
description: Invalid ID supplied
content: {}
404:
description: Order not found
content: {}
delete:
tags:
- store
summary: Delete purchase order by ID
description: For valid response try integer IDs with positive integer value. Negative
or non-integer values will generate API errors
operationId: deleteOrder
parameters:
- name: orderId
in: path
description: ID of the order that needs to be deleted
required: true
schema:
minimum: 1.0
type: integer
format: int64
responses:
400:
description: Invalid ID supplied
content: {}
404:
description: Order not found
content: {}
/user:
post:
tags:
- user
summary: Create user
description: This can only be done by the logged in user.
operationId: createUser
requestBody:
description: Created user object
content:
'*/*':
schema:
$ref: '#/components/schemas/User'
required: true
responses:
default:
description: successful operation
content: {}
x-codegen-request-body-name: body
/user/createWithArray:
post:
tags:
- user
summary: Creates list of users with given input array
operationId: createUsersWithArrayInput
requestBody:
description: List of user object
content:
'*/*':
schema:
type: array
items:
$ref: '#/components/schemas/User'
required: true
responses:
default:
description: successful operation
content: {}
x-codegen-request-body-name: body
/user/createWithList:
post:
tags:
- user
summary: Creates list of users with given input array
operationId: createUsersWithListInput
requestBody:
description: List of user object
content:
'*/*':
schema:
type: array
items:
$ref: '#/components/schemas/User'
required: true
responses:
default:
description: successful operation
content: {}
x-codegen-request-body-name: body
/user/login:
get:
tags:
- user
summary: Logs user into the system
operationId: loginUser
parameters:
- name: username
in: query
description: The user name for login
required: true
schema:
type: string
- name: password
in: query
description: The password for login in clear text
required: true
schema:
type: string
responses:
200:
description: successful operation
headers:
X-Rate-Limit:
description: calls per hour allowed by the user
schema:
type: integer
format: int32
X-Expires-After:
description: date in UTC when token expires
schema:
type: string
format: date-time
content:
application/xml:
schema:
type: string
application/json:
schema:
type: string
400:
description: Invalid username/password supplied
content: {}
/user/logout:
get:
tags:
- user
summary: Logs out current logged in user session
operationId: logoutUser
responses:
default:
description: successful operation
content: {}
/user/{username}:
get:
tags:
- user
summary: Get user by user name
operationId: getUserByName
parameters:
- name: username
in: path
description: 'The name that needs to be fetched. Use user1 for testing. '
required: true
schema:
type: string
responses:
200:
description: successful operation
content:
application/xml:
schema:
$ref: '#/components/schemas/User'
application/json:
schema:
$ref: '#/components/schemas/User'
400:
description: Invalid username supplied
content: {}
404:
description: User not found
content: {}
put:
tags:
- user
summary: Updated user
description: This can only be done by the logged in user.
operationId: updateUser
parameters:
- name: username
in: path
description: name that need to be updated
required: true
schema:
type: string
requestBody:
description: Updated user object
content:
'*/*':
schema:
$ref: '#/components/schemas/User'
required: true
responses:
400:
description: Invalid user supplied
content: {}
404:
description: User not found
content: {}
x-codegen-request-body-name: body
delete:
tags:
- user
summary: Delete user
description: This can only be done by the logged in user.
operationId: deleteUser
parameters:
- name: username
in: path
description: The name that needs to be deleted
required: true
schema:
type: string
responses:
400:
description: Invalid username supplied
content: {}
404:
description: User not found
content: {}
components:
schemas:
Order:
type: object
properties:
id:
type: integer
format: int64
petId:
type: integer
format: int64
quantity:
type: integer
format: int32
shipDate:
type: string
format: date-time
status:
type: string
description: Order Status
enum:
- placed
- approved
- delivered
complete:
type: boolean
default: false
xml:
name: Order
Category:
type: object
properties:
id:
type: integer
format: int64
name:
type: string
xml:
name: Category
User:
type: object
properties:
id:
type: integer
format: int64
username:
type: string
firstName:
type: string
lastName:
type: string
email:
type: string
password:
type: string
phone:
type: string
userStatus:
type: integer
description: User Status
format: int32
xml:
name: User
Tag:
type: object
properties:
id:
type: integer
format: int64
name:
type: string
xml:
name: Tag
Pet:
required:
- name
- photoUrls
type: object
properties:
id:
type: integer
format: int64
category:
$ref: '#/components/schemas/Category'
name:
type: string
example: doggie
photoUrls:
type: array
xml:
name: photoUrl
wrapped: true
items:
type: string
tags:
type: array
xml:
name: tag
wrapped: true
items:
$ref: '#/components/schemas/Tag'
status:
type: string
description: pet status in the store
enum:
- available
- pending
- sold
xml:
name: Pet
ApiResponse:
type: object
properties:
code:
type: integer
format: int32
type:
type: string
message:
type: string
securitySchemes:
petstore_auth:
type: oauth2
flows:
implicit:
authorizationUrl: http://petstore.swagger.io/oauth/dialog
scopes:
write:pets: modify pets in your account
read:pets: read your pets
api_key:
type: apiKey
name: api_key
in: header

6
entrypoint.sh Normal file
View File

@@ -0,0 +1,6 @@
#!/bin/sh
set -e
python manage.py migrate --noinput
python manage.py collectstatic --noinput
exec "$@"

0
locale/.gitkeep Normal file
View File

0
logs/.gitkeep Normal file
View File

22
manage.py Normal file
View File

@@ -0,0 +1,22 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Rasaddam_Backend.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

0
media/.gitkeep Normal file
View File

16
pyproject.toml Normal file
View File

@@ -0,0 +1,16 @@
[tool.black]
line-length = 79
target-version = ['py312']
include = '\.pyi?$'
extend-exclude = 'migrations/*'
[tool.ruff.lint.isort]
case-sensitive = true
force-single-line = true
section-order = ["future", "standard-library", "first-party", "django", "rest_framework", "third-party", "local-folder"]
[tool.ruff.lint.isort.sections]
"django" = ["django"]
"rest_framework" = ["rest_framework"]

3
pytest.ini Normal file
View File

@@ -0,0 +1,3 @@
[pytest]
DJANGO_SETTINGS_MODULE = config.settings
python_files = tests.py test_*.py *_tests.py

9
requirements/common.txt Normal file
View File

@@ -0,0 +1,9 @@
Django
djangorestframework
django-cors-headers
django-filter
django-storages
psycopg2-binary==2.9.9
Pillow
sentry-sdk
gunicorn

4
requirements/local.txt Normal file
View File

@@ -0,0 +1,4 @@
-r common.txt
ruff
pre-commit
pytest-django

View File

@@ -0,0 +1 @@
-r common.txt

0
scripts/.gitkeep Normal file
View File

0
static/.gitkeep Normal file
View File