Skip to content

Commit 8b76ce7

Browse files
authored
Merge pull request #9 from MaxBakshaev/dev
Dev
2 parents daaea45 + acd3095 commit 8b76ce7

File tree

185 files changed

+41151
-14
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

185 files changed

+41151
-14
lines changed

.github/workflows/CI.yml

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,50 @@ jobs:
2626
2727
- name: Lint application
2828
run: |
29-
make lint
29+
make lint
30+
31+
postgres:
32+
needs: lint
33+
runs-on: ubuntu-latest
34+
35+
steps:
36+
- name: Checkout code
37+
uses: actions/checkout@v4
38+
39+
- name: Set up Docker Buildx
40+
uses: docker/setup-buildx-action@v3
41+
42+
- name: Set up Docker Compose
43+
run: |
44+
sudo apt-get update
45+
sudo apt-get install -y docker-compose
46+
47+
- name: Create .env
48+
run: |
49+
echo "SECRET_KEY=${{ secrets.SECRET_KEY }}" >> .env
50+
echo "POSTGRES_DB=${{ secrets.POSTGRES_DB }}" >> .env
51+
echo "POSTGRES_USER=${{ secrets.POSTGRES_USER }}" >> .env
52+
echo "POSTGRES_PASSWORD=${{ secrets.POSTGRES_PASSWORD }}" >> .env
53+
echo "DATABASE_URL=${{ secrets.DATABASE_URL }}" >> .env
54+
echo "CELERY_BROKER_URL=${{ secrets.CELERY_BROKER_URL }}" >> .env
55+
echo "TOKEN=${{ secrets.TOKEN }}" >> .env
56+
echo "API_URL=${{ secrets.API_URL }}" >> .env
57+
58+
- name: Build services
59+
run: |
60+
make build
61+
62+
- name: Start containers
63+
run: |
64+
make bup
65+
66+
- name: Wait for PostgreSQL to be ready
67+
run: |
68+
POSTGRES_USER=$(grep POSTGRES_USER .env | cut -d '=' -f2)
69+
70+
timeout 20 bash -c "
71+
until docker-compose -f docker/docker-compose.yml exec -T postgres pg_isready -U $POSTGRES_USER; do
72+
echo 'Waiting for PostgreSQL...'
73+
sleep 2
74+
done
75+
"

Makefile

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,70 @@
11
.SILENT:
22

3+
# Переменная с вызовом docker-compose
4+
DC = docker-compose -f docker/docker-compose.yml
5+
6+
# 🐳 Команды с docker-compose:
7+
8+
# Запуск docker-compose без аргументов
9+
dc:
10+
$(DC)
11+
12+
# Запуск контейнеров
13+
start:
14+
$(DC) start
15+
16+
# Поднятие с пересборкой в фоновом режиме и удалением образов <none>
17+
restart:
18+
$(DC) down && $(DC) up --build -d && docker image prune -f
19+
20+
# Остановка контейнеров
21+
stop:
22+
$(DC) stop
23+
24+
# Поднятие контейнеров в фоновом режиме
25+
up:
26+
$(DC) up -d
27+
28+
# Поднятие контейнеров с логами
29+
uplog:
30+
$(DC) up
31+
32+
# Сборка образов
33+
build:
34+
$(DC) build
35+
36+
# Поднятие с пересборкой в фоновом режиме
37+
bup:
38+
$(DC) up --build -d
39+
40+
# Поднятие с пересборкой и логами
41+
buplog:
42+
$(DC) up --build
43+
44+
# Остановка и удаление контейнеров
45+
down:
46+
$(DC) down
47+
48+
# Остановка и удаление контейнеров и томов
49+
downv:
50+
$(DC) down -v
51+
52+
# Удаление всех контейнеров, сетей, томов и образов
53+
clean:
54+
$(DC) down --volumes --rmi all
55+
56+
# Удаление образов <none>
57+
none:
58+
docker image prune -f
59+
60+
# Просмотр логов в реальном времени
61+
logs:
62+
$(DC) logs -f
63+
64+
# Просмотр запущенных контейнеров
65+
ps:
66+
$(DC) ps
67+
368
# 🐍 Проверка кода по flake8
469
lint:
570
flake8 core --max-line-length=79 --exclude=migrations && \

bot/bot.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import os
2+
import asyncio
3+
from aiogram import Bot, Dispatcher
4+
from aiogram.types import Message
5+
from aiogram.filters import Command
6+
from dotenv import load_dotenv
7+
8+
load_dotenv()
9+
10+
API_URL = os.getenv("API_URL")
11+
BOT_TOKEN = os.getenv("TOKEN")
12+
13+
bot = Bot(token=BOT_TOKEN)
14+
dp = Dispatcher()
15+
16+
17+
@dp.message(Command(commands=["start"]))
18+
async def start(message: Message):
19+
await message.answer("Привет!")
20+
21+
22+
async def main():
23+
await dp.start_polling(bot)
24+
25+
26+
if __name__ == "__main__":
27+
asyncio.run(main())

core/apps/tasks/admin.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class CategoryAdmin(admin.ModelAdmin):
1313

1414
# Поля, отображаемые в списке
1515
list_display = [
16+
"creation_date",
1617
"name",
1718
]
1819

@@ -21,6 +22,14 @@ class CategoryAdmin(admin.ModelAdmin):
2122
"name",
2223
]
2324

25+
def get_fields(self, request, obj=None):
26+
"""Убирает поле 'creation_date' из редактирования задачи"""
27+
28+
fields = super().get_fields(request, obj)
29+
if obj and "creation_date" in fields:
30+
fields.remove("creation_date")
31+
return fields
32+
2433

2534
@admin.register(Task)
2635
class TaskAdmin(admin.ModelAdmin):
@@ -62,3 +71,11 @@ class TaskAdmin(admin.ModelAdmin):
6271
"category",
6372
"user",
6473
]
74+
75+
def get_fields(self, request, obj=None):
76+
"""Убирает поле 'creation_date' из редактирования задачи"""
77+
78+
fields = super().get_fields(request, obj)
79+
if obj and "creation_date" in fields:
80+
fields.remove("creation_date")
81+
return fields

core/apps/tasks/migrations/0001_initial.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Generated by Django 5.2.7 on 2025-10-12 10:10
1+
# Generated by Django 5.2.7 on 2025-10-12 14:47
22

33
import django.db.models.deletion
44
import django.utils.timezone
@@ -18,7 +18,8 @@ class Migration(migrations.Migration):
1818
migrations.CreateModel(
1919
name='Category',
2020
fields=[
21-
('name', models.CharField(max_length=32, primary_key=True, serialize=False, unique=True, verbose_name='Категория')),
21+
('creation_date', models.DateTimeField(default=django.utils.timezone.now, primary_key=True, serialize=False, verbose_name='Дата создания')),
22+
('name', models.CharField(max_length=32, unique=True, verbose_name='Категория')),
2223
],
2324
options={
2425
'verbose_name': 'Категорию',
@@ -28,11 +29,11 @@ class Migration(migrations.Migration):
2829
migrations.CreateModel(
2930
name='Task',
3031
fields=[
31-
('name', models.CharField(max_length=128, primary_key=True, serialize=False, unique=True, verbose_name='Название')),
32+
('name', models.CharField(max_length=128, unique=True, verbose_name='Название')),
3233
('description', models.TextField(verbose_name='Описание')),
33-
('creation_date', models.DateTimeField(default=django.utils.timezone.now, verbose_name='Дата создания')),
34+
('creation_date', models.DateTimeField(default=django.utils.timezone.now, primary_key=True, serialize=False, verbose_name='Дата создания')),
3435
('end_date', models.DateTimeField(default=django.utils.timezone.now, verbose_name='Дата завершения')),
35-
('category', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tasks', to='tasks.category', verbose_name='Категория')),
36+
('category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tasks', to='tasks.category', verbose_name='Категория')),
3637
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tasks', to=settings.AUTH_USER_MODEL, verbose_name='Пользователь')),
3738
],
3839
options={

core/apps/tasks/models.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,30 @@
1111
class Category(models.Model):
1212
"""Категория."""
1313

14+
creation_date = models.DateTimeField(
15+
default=timezone.now,
16+
verbose_name="Дата создания",
17+
primary_key=True,
18+
)
1419
name = models.CharField(
1520
verbose_name="Категория",
1621
max_length=32,
17-
primary_key=True,
1822
unique=True,
1923
)
2024

2125
class Meta:
2226
verbose_name = "Категорию"
2327
verbose_name_plural = "Категории"
2428

29+
def save(self, *args, **kwargs):
30+
"""Не обновляет creation_date для существующей задачи."""
31+
32+
if self.pk:
33+
self.creation_date = self._meta.get_field(
34+
"creation_date"
35+
).value_from_object(self)
36+
super().save(*args, **kwargs)
37+
2538
def __str__(self) -> str:
2639
return self.name
2740

@@ -32,7 +45,6 @@ class Task(models.Model):
3245
name = models.CharField(
3346
verbose_name="Название",
3447
max_length=128,
35-
primary_key=True,
3648
unique=True,
3749
)
3850
description = models.TextField(
@@ -41,6 +53,7 @@ class Task(models.Model):
4153
creation_date = models.DateTimeField(
4254
default=timezone.now,
4355
verbose_name="Дата создания",
56+
primary_key=True,
4457
)
4558
end_date = models.DateTimeField(
4659
default=timezone.now,
@@ -53,6 +66,7 @@ class Task(models.Model):
5366
# нет жесткой привязки к категории
5467
on_delete=models.SET_NULL,
5568
null=True,
69+
blank=True,
5670
)
5771
user = models.ForeignKey(
5872
User,
@@ -67,6 +81,15 @@ class Meta:
6781
verbose_name_plural = "Задачи"
6882
ordering = ("-creation_date",)
6983

84+
def save(self, *args, **kwargs):
85+
"""Не обновляет creation_date для существующей задачи."""
86+
87+
if self.pk:
88+
self.creation_date = self._meta.get_field(
89+
"creation_date"
90+
).value_from_object(self)
91+
super().save(*args, **kwargs)
92+
7093
def __str__(self):
7194
"""
7295
Возвращает строковое представление задачи,

core/project/settings.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
https://docs.djangoproject.com/en/5.2/ref/settings/
1111
"""
1212

13+
import dj_database_url
1314
import os
1415
from pathlib import Path
1516
import sys
@@ -86,10 +87,7 @@
8687
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
8788

8889
DATABASES = {
89-
"default": {
90-
"ENGINE": "django.db.backends.sqlite3",
91-
"NAME": BASE_DIR / "db.sqlite3",
92-
}
90+
"default": dj_database_url.config(default=os.getenv("DATABASE_URL")),
9391
}
9492

9593

@@ -111,6 +109,10 @@
111109
},
112110
]
113111

112+
CELERY_BROKER_URL = os.getenv("CELERY_BROKER_URL")
113+
CELERY_ACCEPT_CONTENT = ["json"]
114+
CELERY_TASK_SERIALIZER = "json"
115+
CELERY_RESULT_BACKEND = CELERY_BROKER_URL
114116

115117
# Internationalization
116118
# https://docs.djangoproject.com/en/5.2/topics/i18n/
@@ -128,6 +130,7 @@
128130
# https://docs.djangoproject.com/en/5.2/howto/static-files/
129131

130132
STATIC_URL = "static/"
133+
STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")
131134

132135
# Default primary key field type
133136
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field

core/project/urls.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
1616
"""
1717

18+
from django.conf import settings
19+
from django.conf.urls.static import static
1820
from django.contrib import admin
1921
from django.urls import include, path
2022

@@ -29,3 +31,7 @@
2931
import debug_toolbar
3032

3133
urlpatterns += [path("__debug__/", include(debug_toolbar.urls))]
34+
urlpatterns += static(
35+
settings.STATIC_URL,
36+
document_root=settings.STATIC_ROOT,
37+
)

docker/Dockerfile.bot

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
FROM python:3.12.10
2+
3+
WORKDIR /app
4+
5+
COPY requirements.txt .
6+
RUN pip install --no-cache-dir -r requirements.txt
7+
8+
COPY bot bot
9+
COPY .env .
10+
11+
ENV PYTHONUNBUFFERED=1
12+
13+
CMD ["python", "bot/bot.py"]

docker/Dockerfile.django

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
FROM python:3.12.10
2+
3+
RUN apt-get update && apt-get install -y postgresql-client
4+
5+
WORKDIR /app
6+
7+
COPY requirements.txt .
8+
RUN pip install --no-cache-dir -r requirements.txt
9+
10+
COPY . .
11+
RUN chmod 755 .
12+
13+
COPY docker/wait-for-it.sh /wait-for-it.sh
14+
RUN chmod +x /wait-for-it.sh
15+
16+
ENV PYTHONUNBUFFERED=1
17+
18+
CMD ["/wait-for-it.sh", "postgres:5432", "--", "gunicorn", "core.project.wsgi:application", "--bind", "0.0.0.0:8000"]

0 commit comments

Comments
 (0)