diff --git a/README.md b/README.md
new file mode 100644
index 0000000..4dcefba
--- /dev/null
+++ b/README.md
@@ -0,0 +1,104 @@
+
+# MVP CRM-системы для Амбассадоров Яндекс Практикума.
+
+## О проекте
+
+Проект команды №10 в хакатоне Яндекс-Практикума.
+
+## Команда разработчиков
+
+- [Рагимов Шериф](https://github.com/ragimov700)
+- [Зеленчук Михаил](https://github.com/qwertttyyy)
+- [Земцов Антон](https://github.com/antonata-c)
+- [Гуржий Вадим](https://github.com/VadimGurzhy)
+
+## Технологии
+[![Django](https://img.shields.io/badge/Django-092E20?style=for-the-badge&logo=django&logoColor=white)](https://www.djangoproject.com/)
+[![DjangoRESTFramework](https://img.shields.io/badge/Django%20REST%20Framework-092E20?style=for-the-badge&logo=django&logoColor=white)](https://www.django-rest-framework.org/)
+[![PostgreSQL](https://img.shields.io/badge/PostgreSQL-316192?style=for-the-badge&logo=postgresql&logoColor=white)](https://www.postgresql.org)
+[![Docker](https://img.shields.io/badge/Docker-0db7ed?style=for-the-badge&logo=docker&logoColor=white)](https://www.docker.com)
+[![Nginx](https://img.shields.io/badge/nginx-009639?style=for-the-badge&logo=nginx&logoColor=white)](https://www.nginx.org)
+![CI/CD](https://img.shields.io/badge/CI%2FCD-2088FF?style=for-the-badge&logo=GitHub-Actions&logoColor=white)
+
+
+## Начало работы
+
+Эти инструкции позволят вам запустить копию проекта на вашем локальном компьютере для разработки и тестирования.
+
+
+Запуск с использованием Docker
+
+### Предварительные требования
+
+Убедитесь, что у вас установлены Docker и Docker Compose. Это можно сделать, следуя официальной документации Docker: https://docs.docker.com/get-docker/ и https://docs.docker.com/compose/install/
+
+### Установка и запуск
+
+1. Клонируйте репозиторий на локальный компьютер:
+ ```
+ git clone git@github.com:Tenth-Team/backend.git
+ cd backend/infra
+ ```
+
+2. Запустите контейнеры с помощью Docker Compose:
+ ```
+ docker-compose up --build
+ ```
+
+ Теперь приложение должно быть доступно по адресу:
+
+ http://localhost:8000
+
+ А документация доступна по адресу:
+
+ http://localhost:8000/api/v1/swagger/
+
+
+
+
+Локальный запуск через pip
+
+### Предварительные требования
+
+Убедитесь, что у вас установлен Python и pip. Рекомендуется использовать виртуальное окружение для изоляции зависимостей проекта.
+
+### Установка и запуск
+
+1. Клонируйте репозиторий на локальный компьютер:
+ ```
+ git clone git@github.com:Tenth-Team/backend.git
+ cd backend/backend
+ ```
+
+2. Создайте и активируйте виртуальное окружение:
+ ```
+ python -m venv venv
+ source venv/bin/activate # На Windows используйте `venv\Scripts\activate`
+ ```
+
+3. Установите зависимости:
+ ```
+ pip install -r requirements.txt
+ ```
+
+4. Запустите проект (пример для Django):
+ ```
+ python manage.py migrate
+ python manage.py runserver
+ ```
+
+ Теперь приложение должно быть доступно по адресу:
+
+ http://localhost:8000
+
+ А документация доступна по адресу:
+
+ http://localhost:8000/api/v1/swagger/
+
+
+
+
+### Ссылка на скриншот документации:
+
+[![Static Badge](https://img.shields.io/badge/Документация_Swagger-Google_Drive-blue?style=for-the-badge)](https://drive.google.com/file/d/1ySTNXQUQZt4djonFki1h1biCSBdJHsIO/view)
+
diff --git a/backend/ambassadors/admin.py b/backend/ambassadors/admin.py
index 2496210..beaea7f 100644
--- a/backend/ambassadors/admin.py
+++ b/backend/ambassadors/admin.py
@@ -15,26 +15,41 @@
@admin.register(Country)
class CountryAdmin(admin.ModelAdmin):
+ """
+ Конфигурация админки для модели стран.
+ """
list_display = ('name',)
@admin.register(City)
class CityAdmin(admin.ModelAdmin):
+ """
+ Конфигурация админки для модели города.
+ """
list_display = ('name',)
@admin.register(TrainingProgram)
class TrainingProgramAdmin(admin.ModelAdmin):
+ """
+ Конфигурация админки для модели программ обучения.
+ """
list_display = ('name',)
@admin.register(AmbassadorGoal)
class AmbassadorGoalAdmin(admin.ModelAdmin):
+ """
+ Конфигурация админки для модели цели амбассадорства.
+ """
list_display = ('name',)
@admin.register(Ambassador)
class AmbassadorAdmin(admin.ModelAdmin):
+ """
+ Конфигурация админки для модели амбассадора.
+ """
list_display = (
'full_name',
'gender',
@@ -50,6 +65,9 @@ class AmbassadorAdmin(admin.ModelAdmin):
@admin.register(Content)
class ContentAdmin(admin.ModelAdmin):
+ """
+ Конфигурация админки для модели контента.
+ """
list_display = (
'id',
'full_name',
diff --git a/backend/ambassadors/factories.py b/backend/ambassadors/factories.py
index 002a4f8..8bf0577 100644
--- a/backend/ambassadors/factories.py
+++ b/backend/ambassadors/factories.py
@@ -23,10 +23,11 @@
TrainingProgram,
)
-fake = Faker(['en-US', 'en_US', 'en_US', 'en-US'])
-
class CityFactory(DjangoModelFactory):
+ """
+ Фабрика для модели городов.
+ """
class Meta:
model = City
@@ -34,6 +35,9 @@ class Meta:
class CountryFactory(DjangoModelFactory):
+ """
+ Фабрика для модели стран.
+ """
class Meta:
model = Country
@@ -41,6 +45,9 @@ class Meta:
class TrainingProgramFactory(DjangoModelFactory):
+ """
+ Фабрика для модели программ обучения.
+ """
class Meta:
model = TrainingProgram
@@ -48,6 +55,9 @@ class Meta:
class AmbassadorGoalFactory(DjangoModelFactory):
+ """
+ Фабрика для модели целей амбассадорства.
+ """
class Meta:
model = AmbassadorGoal
@@ -55,6 +65,9 @@ class Meta:
class AmbassadorFactory(DjangoModelFactory):
+ """
+ Фабрика для модели амбассадора.
+ """
class Meta:
model = Ambassador
@@ -89,6 +102,9 @@ class Meta:
class ContentFactory(DjangoModelFactory):
+ """
+ Фабрика для модели контента.
+ """
class Meta:
model = Content
@@ -105,6 +121,9 @@ class Meta:
class PromoCodeFactory(DjangoModelFactory):
+ """
+ Фабрика для модели промокодов.
+ """
class Meta:
model = PromoCode
@@ -117,6 +136,9 @@ class Meta:
class MerchandiseFactory(DjangoModelFactory):
+ """
+ Фабрика для модели мерча.
+ """
class Meta:
model = Merchandise
@@ -131,6 +153,9 @@ class Meta:
class MerchandiseShippingRequestFactory(DjangoModelFactory):
+ """
+ Фабрика для модели заявок на мерч.
+ """
class Meta:
model = MerchandiseShippingRequest
diff --git a/backend/ambassadors/signals.py b/backend/ambassadors/signals.py
index a0c0452..3be38f5 100644
--- a/backend/ambassadors/signals.py
+++ b/backend/ambassadors/signals.py
@@ -5,6 +5,10 @@
@receiver(pre_save, sender=PromoCode)
def make_other_promo_codes_inactive(sender, instance, **kwargs):
+ """
+ Сигнал, который деактивирует все остальные промокоды
+ при определении активного.
+ """
if instance.status == 'active':
PromoCode.objects.filter(
ambassador=instance.ambassador
diff --git a/backend/api/v1/filters.py b/backend/api/v1/filters.py
index 315a864..d362ffb 100644
--- a/backend/api/v1/filters.py
+++ b/backend/api/v1/filters.py
@@ -14,8 +14,9 @@
class ContentFilter(FilterSet):
- """Класс для фильтрации Контента."""
-
+ """
+ Фильтры для контента.
+ """
status = ChoiceFilter(choices=CONTENT_STATUS_CHOICES)
full_name = CharFilter(field_name='full_name', lookup_expr='icontains')
ya_edu = CharFilter(
diff --git a/backend/api/v1/pagination.py b/backend/api/v1/pagination.py
index 314c83e..961d1e8 100644
--- a/backend/api/v1/pagination.py
+++ b/backend/api/v1/pagination.py
@@ -2,9 +2,15 @@
class AmbassadorPagination(LimitOffsetPagination):
+ """
+ Пагинация для амбассадоров.
+ """
default_limit = 30
max_limit = 100
class ContentPagination(AmbassadorPagination):
+ """
+ Пагинация для контента.
+ """
pass
diff --git a/backend/api/v1/permissions.py b/backend/api/v1/permissions.py
index fc11f52..b3e7db3 100644
--- a/backend/api/v1/permissions.py
+++ b/backend/api/v1/permissions.py
@@ -3,6 +3,10 @@
class IsAuthenticatedOrYandexForms(permissions.BasePermission):
+ """
+ Пермишен, который разрешает действия только для авторизованных
+ пользователей или Яндекс формам.
+ """
def has_permission(self, request, view):
api_key = request.headers.get('Authorization')
return (request.user.is_authenticated
diff --git a/backend/api/v1/serializers.py b/backend/api/v1/serializers.py
index 2020d9a..da3403b 100644
--- a/backend/api/v1/serializers.py
+++ b/backend/api/v1/serializers.py
@@ -221,6 +221,9 @@ class Meta:
class AmbassadorUpdateSerializer(serializers.ModelSerializer):
+ """
+ Сериализатор для изменения амбассадора.
+ """
city = serializers.CharField(required=False)
country = serializers.CharField(required=False)
diff --git a/backend/api/v1/views.py b/backend/api/v1/views.py
index 7eb3380..03879c6 100644
--- a/backend/api/v1/views.py
+++ b/backend/api/v1/views.py
@@ -197,6 +197,9 @@ class ContentViewSet(viewsets.ModelViewSet):
@extend_schema(tags=["Заявки"])
@extend_schema_view(**merch_schema)
class MerchandiseShippingRequestViewSet(viewsets.ModelViewSet):
+ """
+ Viewset для модели заявки на мерч.
+ """
queryset = MerchandiseShippingRequest.objects.all()
serializer_class = MerchandiseShippingRequestSerializer
http_method_names = (
@@ -220,8 +223,10 @@ def download(self, request):
@extend_schema(tags=["Программы и цели"], **loyalty_schema)
class AmbassadorLoyaltyViewSet(ListAPIView):
- """Viewset для получения данных для страницы лояльности.
- Возвращает список амбассадоров."""
+ """
+ Viewset для получения данных для страницы лояльности.
+ Возвращает список амбассадоров.
+ """
shipped_merch_prefetch = Prefetch(
'merch_shipping_requests',
diff --git a/backend/config/settings.py b/backend/config/settings.py
index d65f85a..ad32a08 100644
--- a/backend/config/settings.py
+++ b/backend/config/settings.py
@@ -8,7 +8,7 @@
DEBUG = os.environ.get('DEBUG', 'True').lower() == 'true'
ALLOWED_HOSTS = os.getenv("ALLOWED_HOSTS", '*').split()
-CSRF_TRUSTED_ORIGINS = ['https://crm.ragimov700.ru']
+CSRF_TRUSTED_ORIGINS = ['https://crm.ragimov700.ru', 'http://127.0.0.1:8000']
INSTALLED_APPS = [
'django.contrib.admin',
diff --git a/infra/.env.example b/infra/.env.example
new file mode 100644
index 0000000..4e197b8
--- /dev/null
+++ b/infra/.env.example
@@ -0,0 +1,9 @@
+DEBUG=True
+SECRET_KEY=django-insecure-ssksrb!2)bv^nd%9*=^w=^0h$=0_r-^mb6xo@9yi!)u1d2%)uv
+ALLOWED_HOSTS=*
+
+POSTGRES_DB='postgres'
+POSTGRES_USER='admin'
+POSTGRES_PASSWORD='password'
+DB_HOST='db'
+DB_PORT=5432
\ No newline at end of file
diff --git a/infra/docker-compose.local.yml b/infra/docker-compose.local.yml
index 6879e97..57ce661 100644
--- a/infra/docker-compose.local.yml
+++ b/infra/docker-compose.local.yml
@@ -1,16 +1,37 @@
version: '3.3'
services:
+ db:
+ image: postgres:13-alpine
+ volumes:
+ - postgres:/var/lib/postgresql/data
+ env_file:
+ - .env.example
+ restart: always
backend:
build: ../backend
- env_file: ../.env
+ env_file:
+ - .env.example
volumes:
- ../backend/:/app/
+ - backend_static:/app/static
+ command: >
+ sh -c "python manage.py collectstatic --no-input &&
+ sleep 3 &&
+ python manage.py migrate &&
+ python manage.py runserver 0.0.0.0:8000"
+ depends_on:
+ - db
nginx:
image: nginx:1.19.3
volumes:
- - ./nginx.conf:/etc/nginx/conf.d/default.conf
+ - backend_static:/backend_static
+ - ./nginx.conf:/etc/nginx/conf.d/default.conf
ports:
- - "80:80"
+ - "8000:80"
depends_on:
- backend
+
+volumes:
+ postgres:
+ backend_static:
diff --git a/infra/docker-compose.production.yml b/infra/docker-compose.production.yml
index fc0cd7d..85dac3d 100644
--- a/infra/docker-compose.production.yml
+++ b/infra/docker-compose.production.yml
@@ -2,20 +2,36 @@ version: '3.3'
volumes:
backend_static:
+ postgres_data:
services:
+ db:
+ image: postgres:13-alpine
+ volumes:
+ - postgres_data:/var/lib/postgresql/data
+ env_file:
+ - .env.example
+ restart: always
+
backend:
image: tenthteam/backend
- command: daphne -b 0.0.0.0 -p 8000 config.asgi:application
- env_file: ./.env
+ command: sh -c "daphne -b 0.0.0.0 -p 8000 config.asgi:application"
+ env_file: .env.example
volumes:
- backend_static:/app/static
+ depends_on:
+ - db
+ frontend:
+ image: tenthteam/frontend
+ volumes:
+ - ../frontend/:/app/result_build/
nginx:
image: nginx:1.19.3
volumes:
- backend_static:/backend_static
- ./nginx.conf:/etc/nginx/conf.d/default.conf
+ - ../frontend/dist:/usr/share/nginx/html/
ports:
- - "80:80"
+ - "8000:80"
depends_on:
- - backend
+ - backend
\ No newline at end of file
diff --git a/infra/nginx.conf b/infra/nginx.conf
index 548fd4f..a08bee2 100644
--- a/infra/nginx.conf
+++ b/infra/nginx.conf
@@ -1,18 +1,39 @@
server {
listen 80;
-
+ server_name 80.90.186.109;
server_tokens off;
location /backend_static/ {
alias /backend_static/;
}
- location / {
+ location /api/ {
proxy_pass http://backend:8000;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_redirect off;
}
-}
\ No newline at end of file
+ location /admin/ {
+ proxy_pass http://backend:8000/admin/;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ }
+
+ location / {
+ root /usr/share/nginx/html;
+ index index.html index.htm;
+ try_files $uri /index.html;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ }
+ error_page 500 502 503 504 /50x.html;
+ location = /50x.html {
+ root /var/html/frontend/;
+ }
+}