Skip to content

Commit

Permalink
деплой перед ревью
Browse files Browse the repository at this point in the history
  • Loading branch information
ZebraHr committed Sep 23, 2023
1 parent a8db838 commit b8b452d
Show file tree
Hide file tree
Showing 16 changed files with 446 additions and 56 deletions.
1 change: 0 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,6 @@ jobs:
sudo docker compose -f docker-compose.production.yml pull
sudo docker compose -f docker-compose.production.yml down
sudo docker compose -f docker-compose.production.yml up -d
sudo docker compose -f docker-compose.production.yml exec backend python manage.py makemigrations
sudo docker compose -f docker-compose.production.yml exec backend python manage.py migrate
sudo docker compose -f docker-compose.production.yml exec backend python manage.py collectstatic
sudo docker compose -f docker-compose.production.yml exec backend cp -r /app/collected_static/. /backend_static/static/
Expand Down
96 changes: 95 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,95 @@
# praktikum_new_diplom

# Foodgram
Проект "Продуктовый помошник" (Foodgram) — сайт с рецептами.

## Технологии:
Python 3.9
Django 3.2.3
djangorestframework 3.12.4
Nginx
gunicorn
Docker

## Описание работы:
В проекте "Проуктовый помощник" любители вкусно готовить могут публиковать рецепты, добавлять чужие рецепты в избранное и подписываться на публикации других авторов. Пользователям сайта также будет доступен сервис «Список покупок». Он позволит создавать список продуктов, которые нужно купить для приготовления выбранных блюд.

## Установка проекта
- Сделайте fork репозитория, затем клонируйте его:
```
git clone https://github.com/ZebraHr/foodgram-project-reactl
```
- Для адаптации проекта на своем удаленном сервере добавьте секреты в GitHub Actions:
```
DOCKER_USERNAME # имя пользователя в DockerHub
DOCKER_PASSWORD # пароль пользователя в DockerHub
HOST # ip_address сервера
USER # имя пользователя
SSH_KEY # приватный ssh-ключ (cat ~/.ssh/id_rsa)
SSH_PASSPHRASE # кодовая фраза (пароль) для ssh-ключа
TELEGRAM_TO # id телеграм-аккаунта (можно узнать у @userinfobot, команда /start)
TELEGRAM_TOKEN # токен бота (получить токен можно у @BotFather, /token, имя бота)
```
- На удаленном сервере создайте папку foodgram/
- На удаленном сервере в папке проекта cоздайте файл .env:
```
POSTGRES_DB=<Желаемое_имя_базы_данных>
POSTGRES_USER=<Желаемое_имя_пользователя_базы_данных>
POSTGRES_PASSWORD=<Желаемый_пароль_пользователя_базы_данных>
DB_HOST=db
DB_PORT=5432
SECRET_KEY = 'ваш_secret_key'
ALLOWED_HOSTS = ip_удаленного сервера, 127.0.0.1, localhost
DEBUG = False
```
- Установка Nginx. Находясь на удалённом сервере, из любой директории выполните команду, затем запустите Nginx:
```
sudo apt install nginx -y
sudo systemctl start nginx
```
- Перейдите в файл конфигурации nginx и измените его настройки на следующие:
```
nano /etc/nginx/sites-enabled/default
```
```
server {
server_name server_name <публичный-IP-адрес> <доменное-имя>;
server_tokens_off;
client_max_body_size 30M;
location / {
proxy_set_header Host $http_host;
proxy_pass http://127.0.0.1:8000;
}
}
```
- Перезарузите Nginx:
```
sudo nginx -t
sudo systemctl reload nginx
```
- Откройте порты для фаервола и активируйте его:
```
sudo ufw allow 'Nginx Full'
sudo ufw allow OpenSSH
sudo ufw enable
```
- (Опционально) Получите SSL-сертификат для вашего доменного имени с помощью Certbot:
```
sudo apt install snapd
sudo snap install core; sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
sudo certbot --nginx
```
- Пуш в любую ветку запускает тестирование и деплой Foodgram на ваш удаленный сервер, а после успешного деплоя вам приходит оповещение в телеграм.

### Автор
Анна Победоносцева

Студент Яндекс Практикума ["Python-разаботчик плюс"](https://practicum.yandex.ru/python-developer-plus/?from=catalog)

GitHub:
(https://github.com/ZebraHr)
10 changes: 5 additions & 5 deletions backend/api/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ class Meta:
fields = ('name',)


IN_NOT_IN_LIST = (
(0, 'Not_In_List'),
(1, 'In_List'),
IN_NOT_IN = (
(0, 'Not_In'),
(1, 'In'),
)


Expand All @@ -24,10 +24,10 @@ class RecipeFilter(FilterSet):
queryset=User.objects.all()
)
is_in_shopping_cart = filters.ChoiceFilter(
choices=IN_NOT_IN_LIST, method='get_is_in'
choices=IN_NOT_IN, method='get_is_in'
)
is_favorited = filters.ChoiceFilter(
choices=IN_NOT_IN_LIST,
choices=IN_NOT_IN,
method='get_is_in'
)
tags = filters.AllValuesMultipleFilter(
Expand Down
14 changes: 8 additions & 6 deletions backend/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
UserSerializer)
from rest_framework import serializers
from rest_framework.relations import SlugRelatedField
# from rest_framework.fields import IntegerField
# from rest_framework.exceptions import ValidationError

from recipes.models import (Ingredient,
Tag,
Expand All @@ -19,7 +17,9 @@
from users.models import User


class Base64ImageField(serializers.ImageField):
class Base64ImageFieldSerializer(serializers.ImageField):
"""Сериализатор для декодирования картинки.
Декодирует строку base64."""
def to_internal_value(self, data):
if isinstance(data, str) and data.startswith('data:image'):
format, imgstr = data.split(';base64,')
Expand Down Expand Up @@ -92,6 +92,7 @@ class Meta:


class IngredientInRecipeWriteSerializer(serializers.ModelSerializer):
"""Сериализатор для игредиентов в рецепте."""
id = serializers.PrimaryKeyRelatedField(queryset=Ingredient.objects.all())

class Meta:
Expand All @@ -101,7 +102,7 @@ class Meta:

class RecipeCreateSerializer(serializers.ModelSerializer):
"""Серилизатор для создания рецепта."""
image = Base64ImageField(
image = Base64ImageFieldSerializer(
required=False,
allow_null=True
)
Expand Down Expand Up @@ -168,6 +169,7 @@ def to_representation(self, instance):


class Hex2NameColor(serializers.Field):
"""Сериализатор для добавления цветов через HEX."""
def to_representation(self, value):
return value

Expand Down Expand Up @@ -210,7 +212,7 @@ def get_is_favorited(self, obj):

def get_is_in_shopping_cart(self, obj):
request = self.context.get('request')
if not request or request.user.is_anonymous:
if request.user.is_anonymous:
return False
return ShoppingCart.objects.filter(recipe=obj,
user=request.user).exists()
Expand Down Expand Up @@ -259,7 +261,7 @@ def get_recipes(self, obj):


class RecipeShortSerializer(serializers.ModelSerializer):
image = Base64ImageField()
image = Base64ImageFieldSerializer()

class Meta:
model = Recipe
Expand Down
21 changes: 11 additions & 10 deletions backend/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from rest_framework.viewsets import ReadOnlyModelViewSet
from rest_framework.permissions import (IsAuthenticatedOrReadOnly,
IsAuthenticated,
AllowAny,
# AllowAny,
SAFE_METHODS)
from rest_framework.filters import SearchFilter
from rest_framework.response import Response
Expand All @@ -15,6 +15,7 @@
from reportlab.pdfgen import canvas
from reportlab.pdfbase import pdfmetrics, ttfonts
from django.db.models import Sum
from django.conf import settings

from api.serializers import (UserCreateSerializer,
UserReadSerializer,
Expand All @@ -33,7 +34,7 @@
Subscribe,
IngredientAmount)
from users.models import User
from api.permissions import IsAmdinOrReadOnly
from api.permissions import IsAmdinOrReadOnly, IsOwnerOrReadOnly
from api.paginations import RecipePagination
from api.filters import RecipeFilter, IngredientFilter

Expand Down Expand Up @@ -121,7 +122,7 @@ class RecipeViewSet(viewsets.ModelViewSet):
"""Вьюсет рецепта.
Просмотр, создание, редактирование."""
queryset = Recipe.objects.all()
permission_classes = [AllowAny]
permission_classes = [IsOwnerOrReadOnly | IsAmdinOrReadOnly]
pagination_class = RecipePagination
filter_backends = (DjangoFilterBackend,)
filterset_class = RecipeFilter
Expand Down Expand Up @@ -207,15 +208,15 @@ def download_shopping_cart(self, request):
f' - {i["amount"]}'
for i in ingredients])
text = canvas.Canvas(buffer)
font = ttfonts.TTFont('Times-Roman', './docs/font.ttf')
font = ttfonts.TTFont('Arial', './docs/arialfont.ttf')
pdfmetrics.registerFont(font)
text.setFont('Times-Roman', 20)
text.drawString(200, 750, 'Список покупок')
text.setFont('Times-Roman', 14)
height = 700
text.setFont('Arial', settings.HEAD_FONT_SIZE)
text.drawString(settings.HEAD_INDENT, settings.HEAD_HEIGHT,
'Ваш список покупок:')
text.setFont('Arial', settings.TEXT_FONT_SIZE)
for line in shopping_list:
text.drawString(50, height, line)
height -= 25
text.drawString(settings.TEXT_INDENT, settings.TEXT_HEIGHT, line)
settings.TEXT_HEIGHT -= settings.LINE_SPACE
text.showPage()
text.save()
buffer.seek(0)
Expand Down
File renamed without changes.
Binary file added backend/docs/arialfont.ttf
Binary file not shown.
18 changes: 11 additions & 7 deletions backend/foodgram/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,6 @@
'rest_framework.authentication.TokenAuthentication',
),
'SEARCH_PARAM': 'name',
# 'DEFAULT_PAGINATION_CLASS': 'api.paginations.RecipePagination',
# 'PAGE_SIZE': 6,
}

DJOSER = {
Expand All @@ -174,10 +172,16 @@
'user': 'api.serializers.UserReadSerializer',
'current_user': 'api.serializers.UserReadSerializer',
},

# 'PERMISSIONS': {
# 'user': ['djoser.permissions.CurrentUserOrAdminOrReadOnly'],
# 'user_list': ['rest_framework.permissions.IsAuthenticatedOrReadOnly'],
# },
'HIDE_USERS': False,
}


# download_shopping_cart

HEAD_FONT_SIZE = 18
TEXT_FONT_SIZE = 14
HEAD_HEIGHT = 800
HEAD_INDENT = 200
TEXT_HEIGHT = 750
TEXT_INDENT = 50
LINE_SPACE = 20
Binary file removed backend/media/recipes/photo_2023-08-23_14-02-41.jpg
Binary file not shown.
30 changes: 18 additions & 12 deletions backend/recipes/admin.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from import_export.admin import ImportExportActionModelAdmin
from django.contrib import admin
from django.contrib.admin import display

from recipes.models import (Ingredient,
Tag,
Expand All @@ -8,20 +9,28 @@
ShoppingCart,
Subscribe,
IngredientAmount,
RecipeTag,
)
from users.models import User


@admin.register(Ingredient)
@admin.register(Tag)
class RecipeIngredientAdmin(ImportExportActionModelAdmin):
list_display = ('name', 'measurement_unit')
list_display = ('name',)
list_filter = ('name',)
search_fields = ('name',)


@admin.register(Tag)
@admin.register(Ingredient)
class RecipeTagAdmin(ImportExportActionModelAdmin):
list_display = ('name', 'measurement_unit')
list_filter = ('name',)
search_fields = ('name',)


class TagInRecipeAdmin(admin.TabularInline):
model = RecipeTag
autocomplete_fields = ('tag', )


class IngredientAmountAdmin(admin.TabularInline):
Expand All @@ -30,16 +39,15 @@ class IngredientAmountAdmin(admin.TabularInline):


class RecipeAdmin(admin.ModelAdmin):
inlines = (IngredientAmountAdmin,)
list_display = ('id', 'name', 'text', 'author', )
inlines = (IngredientAmountAdmin, TagInRecipeAdmin)
list_display = ('id', 'name', 'text', 'author', 'is_in_favorites')
readonly_fields = ('is_in_favorites',)
list_filter = ('pub_date', 'author', 'name', 'tags',)
empty_value_display = '-пусто-'


# class IngredientAdmin(admin.ModelAdmin):
# list_display = ('name', 'measurement_unit',)
# list_filter = ('name',)
# search_fields = ('name',)
@display(description='Добавлено в избранное')
def is_in_favorites(self, obj):
return obj.favorites_recipes.count()


class UserAdmin(admin.ModelAdmin):
Expand All @@ -48,8 +56,6 @@ class UserAdmin(admin.ModelAdmin):


admin.site.register(Recipe, RecipeAdmin)
# admin.site.register(Ingredient, IngredientAdmin)
# admin.site.register(Tag)
admin.site.register(Favorite)
admin.site.register(ShoppingCart)
admin.site.register(User, UserAdmin)
Expand Down
Loading

0 comments on commit b8b452d

Please sign in to comment.