Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

change requirements to allow running on Python 3.10 #11

Open
wants to merge 67 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
e680bae
change requirements to allow running on Python 3.10
mavel-x Mar 7, 2023
9355669
create models for orders, set up DRF
mavel-x Mar 9, 2023
767d2ce
add product validation for register_order
mavel-x Mar 9, 2023
bc97f78
extend data validation for register_order
mavel-x Mar 9, 2023
11d9530
use DRF ModelSerializer for validation
mavel-x Mar 10, 2023
0cd04a4
return order data from view
mavel-x Mar 10, 2023
a4bd5dd
list orders in manager UI
mavel-x Mar 10, 2023
b7dc9d5
show order totals in manager UI
mavel-x Mar 10, 2023
3282988
save item price for each order
mavel-x Mar 10, 2023
c6f5db8
make register_order atomic
mavel-x Mar 11, 2023
b7af883
add link to edit orders on admin site
mavel-x Mar 11, 2023
1cb5bd2
redirect back to orders page after editing order
mavel-x Mar 11, 2023
4a2c4d5
add order status to order model and manager UI
mavel-x Mar 11, 2023
fbc823c
fix duplicating orders while annotating
mavel-x Mar 11, 2023
fd4a2ea
add comments to orders
mavel-x Mar 11, 2023
340459e
add times to orders
mavel-x Mar 11, 2023
1c94521
add payment method to orders
mavel-x Mar 11, 2023
aa373a9
add restaurant to orders
mavel-x Mar 12, 2023
c713dbe
add available restaurants to orders view
mavel-x Apr 2, 2023
62bcf3f
add distance to available restaurants
mavel-x Apr 2, 2023
56e2c73
create Location model
mavel-x Apr 2, 2023
c3aa387
optimize SQL queries
mavel-x Apr 2, 2023
59443fc
merge statements
mavel-x Apr 9, 2023
1968388
rename an attribute
mavel-x Apr 9, 2023
b58b021
add qty limits to order
mavel-x Apr 9, 2023
10258ce
move serializers to dedicated file
mavel-x Apr 9, 2023
a42f3e5
fix renaming attribute
mavel-x Apr 9, 2023
5b7d640
move instance saving code into serializer
mavel-x Apr 9, 2023
e51bdc1
make fields nullable
mavel-x Apr 9, 2023
2ee9094
create empty locations on geocoder fail
mavel-x Apr 9, 2023
e22e2d8
change default value for DEBUG
mavel-x Apr 9, 2023
f361cfd
move code into querysets
mavel-x Apr 10, 2023
4047620
clean up
mavel-x Apr 10, 2023
54f2891
remove some code
mavel-x Apr 10, 2023
e400a72
add rollbar
mavel-x May 8, 2023
3be63b8
use postgres
mavel-x May 10, 2023
651ebac
fix errors in README
mavel-x May 10, 2023
8e1c710
add a deploy script
mavel-x May 11, 2023
fb752fa
update deploy script
mavel-x May 12, 2023
4518911
notify rollbar of deploys
mavel-x May 14, 2023
d72fb6e
use a full commit hash
mavel-x May 14, 2023
cdf536c
Update README.md
mavel-x May 14, 2023
99f8c55
unrequire Rollbar, update README
mavel-x May 15, 2023
64f0fbf
add no-input to script
mavel-x May 17, 2023
76eee59
prepare for docker compose deploy
mavel-x Jul 2, 2023
707c22c
fix broken commands
mavel-x Jul 2, 2023
89407b3
fix broken path
mavel-x Jul 2, 2023
581cbce
add guard checks for missing components, move script call to container
mavel-x Jul 2, 2023
4cbeaed
add a deploy script
mavel-x Jul 2, 2023
3b2899a
update README
mavel-x Jul 2, 2023
1b240a0
update link
mavel-x Jul 2, 2023
7e24679
certbot config
mavel-x Jul 5, 2023
eb079e2
allow running script
mavel-x Jul 5, 2023
6522ee0
remove hyphen in docker compose
mavel-x Jul 5, 2023
eb51ba0
remove user input check
mavel-x Jul 5, 2023
5d56553
add allow all for certbot challenge
mavel-x Jul 5, 2023
3ea9651
replace domain from example
mavel-x Jul 5, 2023
b015c32
certbot config and renewal services
mavel-x Jul 5, 2023
0c3460d
add certbot script call to first_deploy script
mavel-x Jul 5, 2023
23f9c33
fix error in variable name
mavel-x Jul 5, 2023
17a8e03
use service name instead of container name
mavel-x Jul 6, 2023
80fb803
remove existing certbot data_dir to avoid filepath problems with nginx
mavel-x Jul 6, 2023
43b8685
echo message at the end
mavel-x Jul 6, 2023
0e2b445
take domains and email from .env
mavel-x Jul 6, 2023
483c3c2
update README
mavel-x Jul 6, 2023
a5e07e0
add comments for domains
mavel-x Jul 6, 2023
cd87f75
fix an error in path joining
mavel-x Jul 6, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
venv
staticfiles
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
bundles/**
node_modules/
media/

Expand Down Expand Up @@ -142,3 +141,5 @@ dmypy.json
cython_debug/

.parcel-cache/
/data/
staticfiles/
20 changes: 20 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM node:16.20 AS parcel
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY bundles-src/ ./bundles-src/
RUN ./node_modules/.bin/parcel build bundles-src/index.js --dist-dir bundles --public-url="./"

FROM python:3.10
WORKDIR /app
COPY --from=parcel /app/bundles/ ./bundles/
RUN apt update \
&& apt install -y libpq-dev \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt ./
RUN pip install -r requirements.txt
COPY . .
RUN python manage.py collectstatic --noinput
RUN mkdir -p frontend/ \
&& cp -R bundles/* frontend/ \
&& cp -R staticfiles/* frontend/
61 changes: 52 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Сайт доставки еды Star Burger
# Сайт доставки еды [Star Burger](http://starburger.mavel.cc/)

Это сайт сети ресторанов Star Burger. Здесь можно заказать превосходные бургеры с доставкой на дом.

Expand Down Expand Up @@ -37,7 +37,7 @@ python --version
```
**Важно!** Версия Python должна быть не ниже 3.6.

Возможно, вместо команды `python` здесь и в остальных инструкциях этого README придётся использовать `python3`. Зависит это от операционной системы и от того, установлен ли у вас Python старой второй версии.
Возможно, вместо команды `python` здесь и в остальных инструкциях этого README придётся использовать `python3`. Зависит это от операционной системы и от того, установлен ли у вас Python старой второй версии.

В каталоге проекта создайте виртуальное окружение:
```sh
Expand All @@ -54,11 +54,15 @@ python -m venv venv
pip install -r requirements.txt
```

Определите переменную окружения `SECRET_KEY`. Создать файл `.env` в каталоге `star_burger/` и положите туда такой код:
Определите переменную окружения `SECRET_KEY`. Создайте файл `.env` в каталоге `star_burger/` и положите туда такой код:
```sh
SECRET_KEY=django-insecure-0if40nf4nf93n4
```

Получите API-ключ для
[Яндекс-геокодера](https://developer.tech.yandex.ru/services/) (подробнее [здесь](https://dvmn.org/encyclopedia/api-docs/yandex-geocoder-api/)).
Положите ключ в переменную `YANDEX_GEO_KEY` в файле `.env`.

Создайте файл базы данных SQLite и отмигрируйте её следующей командой:

```sh
Expand Down Expand Up @@ -136,17 +140,56 @@ Parcel будет следить за файлами в каталоге `bundle

## Как запустить prod-версию сайта

Собрать фронтенд:
### Настроить бэкенд:

```sh
./node_modules/.bin/parcel build bundles-src/index.js --dist-dir bundles --public-url="./"
```

Настроить бэкенд: создать файл `.env` в каталоге `star_burger/` со следующими настройками:
Создать файл `.env` в корневом каталоге проекта со следующими настройками:

- `DEBUG` — дебаг-режим. Поставьте `False`.
- `SECRET_KEY` — секретный ключ проекта. Он отвечает за шифрование на сайте. Например, им зашифрованы все пароли на вашем сайте.
- `ALLOWED_HOSTS` — [см. документацию Django](https://docs.djangoproject.com/en/3.1/ref/settings/#allowed-hosts)
- `POSTGRES_USER`
- `POSTGRES_PASSWORD`
- `POSTGRES_DB`

Для SSL-сертификата:
- `EMAIL`
- `CERT_DOMAINS` - список доменов в формате `example.org,www.example.org`

Убедиться, что в каталоге `star-burger/data` лежат данные, которые нужно загрузить в БД.
Убедиться, что на сервере установлен Docker.

Заменить домены в `nginx.conf` на ваши собственные.

Чтобы получать мгновенные уведомления об ошибках, подключите свой аккаунт [Rollbar](https://docs.rollbar.com/docs/setup)
и добавьте следующие переменные в `.env`:
- `ROLLBAR_TOKEN`
- `ROLLBAR_ENV` - `development`/`production`/...
- `ROLLBAR_USERNAME`

### Поднять контейнеры, получить сертификат SSL и запустить приложение:

```sh
scripts/first_deploy.sh
```

Приложение контролируется таргетом systemd и запускается автоматически при перезагрузке сервера.
В таргет включены следующие юниты:
- starburger_containers.service - запускает и останавливает контейнеры через docker compose
- starburger_cert_renewal.timer - обновляет сертификат SSL и перезагружает nginx
- starburger_clearsessions.timer - удаляет устаревшие сессии в Django

Команды, которые могут пригодиться:
```sh
systemctl stop starburger.target # остановить
systemctl start starburger.target # запустить
docker compose logs # посмотреть на stdout контейнеров
```


### Подтянуть изменения из репозитория и перезапустить сервисы:
```sh
scripts/deploy.sh
```

## Цели проекта

Expand Down
56 changes: 56 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
version: '3'

services:
db:
container_name: starburger_db
image: postgres:14
environment:
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
volumes:
- postgres:/var/lib/postgresql/data

django:
container_name: starburger_django
build: .
command: gunicorn -b 0.0.0.0:8081 --workers 3 star_burger.wsgi:application
environment:
- POSTGRES_HOST=db
volumes:
- .:/app
- frontend:/app/frontend
- media:/app/media
ports:
- "8081:8081"
depends_on:
- db

nginx:
container_name: starburger_nginx
image: nginx:1.25
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- frontend:/frontend
- media:/media
- ./data/certbot/conf:/etc/letsencrypt
- ./data/certbot/www:/var/www/certbot
depends_on:
- django

certbot:
container_name: starburger_certbot
image: certbot/certbot:v2.6.0
volumes:
- ./data/certbot/conf:/etc/letsencrypt
- ./data/certbot/www:/var/www/certbot
depends_on:
- nginx

volumes:
postgres:
frontend:
media:
57 changes: 57 additions & 0 deletions foodcartapp/admin.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
from django.conf import settings
from django.contrib import admin
from django.forms import ModelForm
from django.http import HttpResponseRedirect
from django.shortcuts import reverse
from django.templatetags.static import static
from django.utils.html import format_html
from django.utils.http import url_has_allowed_host_and_scheme

from .models import Product
from .models import ProductCategory
from .models import Restaurant
from .models import RestaurantMenuItem
from .models import Order
from .models import ProductOrder
from places.models import Location


@admin.register(Location)
class LocationAdmin(admin.ModelAdmin):
list_display = ['address', 'latitude', 'longitude', 'updated_at']
readonly_fields = ['updated_at']


class RestaurantMenuItemInline(admin.TabularInline):
Expand All @@ -30,6 +43,10 @@ class RestaurantAdmin(admin.ModelAdmin):
RestaurantMenuItemInline
]

def save_model(self, request, obj, form, change):
super().save_model(request, obj, form, change)
Location.update_by_address(obj.address)


@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
Expand Down Expand Up @@ -104,3 +121,43 @@ def get_image_list_preview(self, obj):
@admin.register(ProductCategory)
class ProductAdmin(admin.ModelAdmin):
pass


class ProductOrderInlineForm(ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['product_price'].label = 'Цена (оставьте поле пустым для стандартной цены)'


class ProductOrderInline(admin.TabularInline):
model = ProductOrder
fields = ('product', 'product_price', 'quantity')
form = ProductOrderInlineForm


@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
inlines = [ProductOrderInline]
list_display = ('firstname', 'lastname', 'phonenumber', 'created_at')
readonly_fields = ('created_at',)

def save_model(self, request, obj, form, change):
super().save_model(request, obj, form, change)
Location.update_by_address(obj.address)

def save_formset(self, request, form, formset, change):
instances = formset.save(commit=False)
for obj in formset.deleted_objects:
obj.delete()
for instance in instances:
if instance.product_price is None:
instance.product_price = instance.product.price
instance.save()
formset.save_m2m()

def response_change(self, request, obj):
response = super().response_change(request, obj)
if url_has_allowed_host_and_scheme(request.GET.get('next'), settings.ALLOWED_HOSTS):
return HttpResponseRedirect(request.GET['next'])
else:
return response
44 changes: 44 additions & 0 deletions foodcartapp/migrations/0038_auto_20230309_1702.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Generated by Django 3.2.15 on 2023-03-09 17:02

from django.db import migrations, models
import django.db.models.deletion
import phonenumber_field.modelfields


class Migration(migrations.Migration):

dependencies = [
('foodcartapp', '0037_auto_20210125_1833'),
]

operations = [
migrations.CreateModel(
name='Order',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('firstname', models.CharField(max_length=50, verbose_name='имя')),
('lastname', models.CharField(db_index=True, max_length=50, verbose_name='фамилия')),
('phonenumber', phonenumber_field.modelfields.PhoneNumberField(db_index=True, max_length=128, region=None, verbose_name='телефон')),
('address', models.CharField(max_length=200, verbose_name='адрес')),
('created_at', models.DateTimeField(auto_now=True, verbose_name='время создания')),
],
options={
'verbose_name': 'заказ',
'verbose_name_plural': 'заказы',
},
),
migrations.CreateModel(
name='ProductOrder',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('quantity', models.IntegerField(verbose_name='количество')),
('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='foodcartapp.order')),
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='foodcartapp.product')),
],
),
migrations.AddField(
model_name='order',
name='products',
field=models.ManyToManyField(related_name='order', through='foodcartapp.ProductOrder', to='foodcartapp.Product', verbose_name='товары'),
),
]
36 changes: 36 additions & 0 deletions foodcartapp/migrations/0039_auto_20230310_2031.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Generated by Django 3.2.15 on 2023-03-10 20:31

import django.core.validators
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('foodcartapp', '0038_auto_20230309_1702'),
]

operations = [
migrations.AddField(
model_name='productorder',
name='product_price',
field=models.DecimalField(decimal_places=2, default=0, max_digits=8, validators=[django.core.validators.MinValueValidator(0)], verbose_name='цена товара'),
preserve_default=False,
),
migrations.AlterField(
model_name='productorder',
name='order',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='products_ordered', to='foodcartapp.order'),
),
migrations.AlterField(
model_name='productorder',
name='product',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='orders', to='foodcartapp.product'),
),
migrations.AlterField(
model_name='productorder',
name='quantity',
field=models.PositiveIntegerField(verbose_name='количество'),
),
]
28 changes: 28 additions & 0 deletions foodcartapp/migrations/0040_auto_20230310_2031.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Generated by Django 3.2.15 on 2023-03-10 20:16

from django.db import migrations


def add_prices(apps, schema_editor):
ProductOrder = apps.get_model('foodcartapp', 'ProductOrder')
for product_order in ProductOrder.objects.prefetch_related('product').iterator():
product_order.product_price = product_order.product.price
product_order.save()


def reverse(apps, schema_editor):
ProductOrder = apps.get_model('foodcartapp', 'ProductOrder')
for product_order in ProductOrder.objects.iterator():
product_order.product_price = 0
product_order.save()


class Migration(migrations.Migration):

dependencies = [
('foodcartapp', '0039_auto_20230310_2031'),
]

operations = [
migrations.RunPython(add_prices, reverse),
]
Loading