Skip to content

Commit

Permalink
mapping
Browse files Browse the repository at this point in the history
  • Loading branch information
Slava committed Jun 22, 2023
1 parent 0c89218 commit a957efd
Show file tree
Hide file tree
Showing 30 changed files with 1,246 additions and 740 deletions.
10 changes: 10 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
DEBUG=True
SECRET_KEY=<Your_some_long_string>
ALLOWED_HOSTS=<Your_host>
CSRF_TRUSTED_ORIGINS=https://<Your_host>
DB_ENGINE=django.db.backends.postgresql
DB_NAME=postgres
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
DB_HOST=foodgram-db # Имя контейнера с БД в docker-compose.yml
DB_PORT=5432
60 changes: 41 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,56 +13,71 @@
Имейте ввиду, что некоторые ревьюры сами не сильно углублялись в предмет, проверку проводят сравнивая с выданным им шаблоном и иногда их претензии решаются не переписыванием кода, а объяснением Вашего решения. Не стесняйтесь спорить с ними.
***

## Tecnhologies:
## Tecnhologies

- Python 3.11
- Django 4.0
- Django REST framework 3.14
- Nginx
- Docker
- Postgres


## https://foodgram.gq


Here you can share recipes of dishes, add them to favorites and display a shopping list for cooking your favorite dishes.
To preserve order - only administrators are allowed to create tags and ingredients.

There is also an API. To view the available paths, follow the link: **https://foodgram.gq/api/**.

And the api documentation is here: **https://foodgram.gq/api/docs/**.

### To deploy this project need the next actions:
### To deploy this project need the next actions

- Download project with SSH (actually you only need the folder 'infra/')
```

```text
git clone git@github.com:Xewus/foodgram-project-react.git
```

- Connect to your server:
```

```text
ssh <server user>@<server IP>
```

- Install Docker on your server
```

```text
sudo apt install docker.io
```

- Install Docker Compose (for Linux)
```

```text
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
```

- Get permissions for docker-compose
```

```text
sudo chmod +x /usr/local/bin/docker-compose
```

- Create project directory (preferably in your home directory)
```

```text
mkdir foodgram && cd foodgram/
```

- Create env-file:
```

```text
touch .env
```

- Fill in the env-file like it:
```

```text
DEBUG=False
SECRET_KEY=<Your_some_long_string>
ALLOWED_HOSTS=<Your_host>
Expand All @@ -74,31 +89,38 @@ POSTGRES_PASSWORD=<Your_password>
DB_HOST=foodgram-db
DB_PORT=5432
```

- Copy files from 'infra/' (on your local machine) to your server:
```

```text
scp -r infra/* <server user>@<server IP>:/home/<server user>/foodgram/
```

- Run docker-compose
```

```text
sudo docker-compose up -d
```

Wait a few seconds...
Your service is work!
![Иллюстрация к проекту](https://github.com/Xewus/Foodgram/blob/master/screen.png)

## Enjoy your meal !
**Enjoy your meal!**

Oh, I'm sorry. You also need to create the first account for the admin panel using this command:
```

```text
sudo docker exec -it app python manage.py createsuperuser
```

And if you want, you can use the list of ingredients offered by us to write
recipes.
And if you want, you can use the list of ingredients offered by us to write recipes.
Upload it to the database with the following command:
```

```text
sudo docker exec -it foodgram-app python manage.py loaddata data/dump.json
```

### *Backend by:*

[Xewus](https://github.com/Xewus)
2 changes: 1 addition & 1 deletion backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ FROM python:3.11-slim
# Requirements for `psycorg2` and script "/app/run_app.sh".
RUN apt-get update &&\
apt-get upgrade -y &&\
apt-get install -y libpq-dev gcc netcat
apt-get install -y libpq-dev gcc netcat-traditional
# It also create directory `/app`.
WORKDIR /app
COPY requirements.txt ./
Expand Down
4 changes: 2 additions & 2 deletions backend/api/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@


class ApiConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'api'
default_auto_field = "django.db.models.BigAutoField"
name = "api"
75 changes: 43 additions & 32 deletions backend/api/mixins.py
Original file line number Diff line number Diff line change
@@ -1,66 +1,77 @@
"""Модуль содержит дополнительные классы
для настройки основных классов приложения.
"""
from core.enums import Tuples
from django.db.models import Model, Q
from django.db.utils import IntegrityError
from django.shortcuts import get_object_or_404
from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer
from rest_framework.status import (HTTP_201_CREATED, HTTP_204_NO_CONTENT,
HTTP_400_BAD_REQUEST)
from rest_framework.status import (
HTTP_201_CREATED,
HTTP_204_NO_CONTENT,
HTTP_400_BAD_REQUEST,
)


class AddDelViewMixin:
"""
Добавляет во Viewset дополнительные методы.
Содержит метод добавляющий/удаляющий объект связи
Содержит методы для добавления или удаления объекта связи
Many-to-Many между моделями.
Требует определения атрибута `add_serializer`.
Требует определения атрибутов `add_serializer` и `link_model`.
Example:
class ExampleViewSet(ModelViewSet, AddDelViewMixin)
...
add_serializer = ExamplSerializer
def example_func(self, request, **kwargs):
...
obj_id = ...
return self.add_del_obj(obj_id, relation.M2M)
link_model = M2M_Model
"""

add_serializer: ModelSerializer | None = None
link_model: Model | None = None

def _add_del_obj(
self,
obj_id: int | str,
m2m_model: Model,
q: Q
) -> Response:
"""Добавляет/удаляет связь M2M между пользователем и другим объектом.
def _create_relation(self, obj_id: int | str) -> Response:
"""Добавляет связь M2M между объектами.
Args:
obj_id (int | str):
`id` объекта, с которым требуется создать/удалить связь.
m2m_model (Model):
М2M модель управляющая требуемой связью.
q (Q):
Условие фильтрации объектов.
`id` объекта, с которым требуется создать связь.
Returns:
Responce: Статус подтверждающий/отклоняющий действие.
"""
obj = get_object_or_404(self.queryset, id=obj_id)
obj = get_object_or_404(self.queryset, pk=obj_id)
try:
self.link_model(None, obj.pk, self.request.user.pk).save()
except IntegrityError:
return Response(
{"error": "Действие выполнено ранее."},
status=HTTP_400_BAD_REQUEST,
)

serializer: ModelSerializer = self.add_serializer(obj)
m2m_obj = m2m_model.objects.filter(q & Q(user=self.request.user))
return Response(serializer.data, status=HTTP_201_CREATED)

if (self.request.method in Tuples.ADD_METHODS) and not m2m_obj:
# Table must have: | m2m.id | obj.id(FK) | user.id(FK) | ... |
m2m_model(None, obj.id, self.request.user.id).save()
return Response(serializer.data, status=HTTP_201_CREATED)
def _delete_relation(self, q: Q) -> Response:
"""Удаляет связь M2M между объектами.
if (self.request.method in Tuples.DEL_METHODS) and m2m_obj:
m2m_obj[0].delete()
return Response(status=HTTP_204_NO_CONTENT)
Args:
q (Q):
Условие фильтрации объектов.
Returns:
Responce: Статус подтверждающий/отклоняющий действие.
"""
deleted, _ = (
self.link_model.objects.filter(q & Q(user=self.request.user))
.first()
.delete()
)
if not deleted:
return Response(
{"error": f"{self.link_model.__name__} не существует"},
status=HTTP_400_BAD_REQUEST,
)

return Response(status=HTTP_400_BAD_REQUEST)
return Response(status=HTTP_204_NO_CONTENT)
3 changes: 2 additions & 1 deletion backend/api/paginators.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ class PageLimitPagination(PageNumberPagination):
"""Стандартный пагинатор с определением атрибута
`page_size_query_param`, для вывода запрошенного количества страниц.
"""
page_size_query_param = 'limit'

page_size_query_param = "limit"
32 changes: 10 additions & 22 deletions backend/api/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,9 @@


class BanPermission(BasePermission):
"""Базовый класс разрешений с проверкой - забанен ли пользователь.
"""
def has_permission(
self,
request: WSGIRequest,
view: APIRootView
) -> bool:
"""Базовый класс разрешений с проверкой - забанен ли пользователь."""

def has_permission(self, request: WSGIRequest, view: APIRootView) -> bool:
return bool(
request.method in SAFE_METHODS
or request.user.is_authenticated
Expand All @@ -26,20 +22,15 @@ class AuthorStaffOrReadOnly(BanPermission):
Разрешение на изменение только для служебного персонала и автора.
Остальным только чтение объекта.
"""

def has_object_permission(
self,
request: WSGIRequest,
view: APIRootView,
obj: Model
self, request: WSGIRequest, view: APIRootView, obj: Model
) -> bool:
return (
request.method in SAFE_METHODS
or request.user.is_authenticated
and request.user.is_active
and (
request.user == obj.author
or request.user.is_staff
)
and (request.user == obj.author or request.user.is_staff)
)


Expand All @@ -48,10 +39,9 @@ class AdminOrReadOnly(BanPermission):
Разрешение на создание и изменение только для админов.
Остальным только чтение объекта.
"""

def has_object_permission(
self,
request: WSGIRequest,
view: APIRootView
self, request: WSGIRequest, view: APIRootView
) -> bool:
return (
request.method in SAFE_METHODS
Expand All @@ -66,11 +56,9 @@ class OwnerUserOrReadOnly(BanPermission):
Разрешение на создание и изменение только для админа и пользователя.
Остальным только чтение объекта.
"""

def has_object_permission(
self,
request: WSGIRequest,
view: APIRootView,
obj: Model
self, request: WSGIRequest, view: APIRootView, obj: Model
) -> bool:
return (
request.method in SAFE_METHODS
Expand Down
Loading

0 comments on commit a957efd

Please sign in to comment.