Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 14 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,22 @@ SECRET_KEY=

DEBUG=

# Настройка БД
NAME=
USER=
PASSWORD=
HOST=
PORT=

# Настройка почты
EMAIL_HOST_USER=
EMAIL_HOST_PASSWORD=
EMAIL_PORT=
EMAIL_HOST=
EMAIL_USE_TLS=
EMAIL_USE_SSL=
DEFAULT_FROM_EMAIL=

# Включение кэширования
CACHE_ENABLE=
LOCATION=
4 changes: 4 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[flake8]
max-line-length = 119
ignore = E203,W503
exclude = .git,__pycache__,env,.env,venv,.venv,migrations
Binary file modified README.md
Binary file not shown.
49 changes: 20 additions & 29 deletions config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@

WSGI_APPLICATION = 'config.wsgi.application'


# Database
DATABASES = {
'default': {
Expand Down Expand Up @@ -100,39 +99,31 @@
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

# AUTH_USER_MODEL = 'YourAppName.YourClassName'
# AUTH_USER_MODEL = "users.User"
AUTH_USER_MODEL = "users.User"

LOGIN_REDIRECT_URL = "/"
LOGOUT_REDIRECT_URL = "/"

#Подключение почты Яндекс
# Подключение почты Яндекс
# Адрес почтового сервера — smtp.yandex.ru.
# Защита соединения — SSL.
# Порт — 465. Если почтовый клиент начинает соединение без шифрования — 587.
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.yandex.ru'
EMAIL_PORT = 465
EMAIL_USE_TLS = False
EMAIL_USE_SSL = True
EMAIL_HOST_USER = 'iVasya2033@yandex.ru'
EMAIL_HOST_PASSWORD = 'znwlirhdwkdnopwb'
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
# EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
# EMAIL_HOST = os.getenv("EMAIL_HOST")
# EMAIL_PORT = os.getenv("EMAIL_PORT")
# EMAIL_USE_TLS = os.getenv("EMAIL_USE_TLS", False) == "True"
# EMAIL_USE_SSL = os.getenv("EMAIL_USE_SSL", False) == "True"
# EMAIL_HOST_USER = os.getenv("EMAIL_HOST_USER")
# EMAIL_HOST_PASSWORD = os.getenv("EMAIL_HOST_PASSWORD")
# DEFAULT_FROM_EMAIL = os.getenv("DEFAULT_FROM_EMAIL")

#REDIS
# CACHE_ENABLE = os.getenv("CACHE_ENABLE", False) == "True"
#
# if CACHE_ENABLE:
# CACHES = {
# "default": {
# "BACKEND": "django.core.cache.backends.redis.RedisCache",
# "LOCATION": os.getenv("LOCATION"),
# }
# }
EMAIL_HOST = os.getenv("EMAIL_HOST")
EMAIL_PORT = os.getenv("EMAIL_PORT")
EMAIL_USE_TLS = os.getenv("EMAIL_USE_TLS", False) == "True"
EMAIL_USE_SSL = os.getenv("EMAIL_USE_SSL", False) == "True"
EMAIL_HOST_USER = os.getenv("EMAIL_HOST_USER")
EMAIL_HOST_PASSWORD = os.getenv("EMAIL_HOST_PASSWORD")
DEFAULT_FROM_EMAIL = os.getenv("DEFAULT_FROM_EMAIL")

# REDIS
CACHE_ENABLE = os.getenv("CACHE_ENABLE", False) == "True"

if CACHE_ENABLE:
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.redis.RedisCache",
"LOCATION": os.getenv("LOCATION"),
}
}
31 changes: 13 additions & 18 deletions config/urls.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
"""
URL configuration for config project.

The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/5.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path
from django.urls import include, path

from config import settings

urlpatterns = [
path('admin/', admin.site.urls),
path("admin/", admin.site.urls),
path("", include("mailing.urls", namespace="mailing")),
path("users/", include("users.urls", namespace="users")),
]

if settings.DEBUG:
urlpatterns += static(
settings.MEDIA_URL,
document_root=settings.MEDIA_ROOT
)
1 change: 1 addition & 0 deletions groups.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"model": "auth.permission", "pk": 1, "fields": {"name": "Can add log entry", "content_type": 1, "codename": "add_logentry"}}, {"model": "auth.permission", "pk": 2, "fields": {"name": "Can change log entry", "content_type": 1, "codename": "change_logentry"}}, {"model": "auth.permission", "pk": 3, "fields": {"name": "Can delete log entry", "content_type": 1, "codename": "delete_logentry"}}, {"model": "auth.permission", "pk": 4, "fields": {"name": "Can view log entry", "content_type": 1, "codename": "view_logentry"}}, {"model": "auth.permission", "pk": 5, "fields": {"name": "Can add permission", "content_type": 2, "codename": "add_permission"}}, {"model": "auth.permission", "pk": 6, "fields": {"name": "Can change permission", "content_type": 2, "codename": "change_permission"}}, {"model": "auth.permission", "pk": 7, "fields": {"name": "Can delete permission", "content_type": 2, "codename": "delete_permission"}}, {"model": "auth.permission", "pk": 8, "fields": {"name": "Can view permission", "content_type": 2, "codename": "view_permission"}}, {"model": "auth.permission", "pk": 9, "fields": {"name": "Can add group", "content_type": 3, "codename": "add_group"}}, {"model": "auth.permission", "pk": 10, "fields": {"name": "Can change group", "content_type": 3, "codename": "change_group"}}, {"model": "auth.permission", "pk": 11, "fields": {"name": "Can delete group", "content_type": 3, "codename": "delete_group"}}, {"model": "auth.permission", "pk": 12, "fields": {"name": "Can view group", "content_type": 3, "codename": "view_group"}}, {"model": "auth.permission", "pk": 13, "fields": {"name": "Can add content type", "content_type": 5, "codename": "add_contenttype"}}, {"model": "auth.permission", "pk": 14, "fields": {"name": "Can change content type", "content_type": 5, "codename": "change_contenttype"}}, {"model": "auth.permission", "pk": 15, "fields": {"name": "Can delete content type", "content_type": 5, "codename": "delete_contenttype"}}, {"model": "auth.permission", "pk": 16, "fields": {"name": "Can view content type", "content_type": 5, "codename": "view_contenttype"}}, {"model": "auth.permission", "pk": 17, "fields": {"name": "Can add session", "content_type": 6, "codename": "add_session"}}, {"model": "auth.permission", "pk": 18, "fields": {"name": "Can change session", "content_type": 6, "codename": "change_session"}}, {"model": "auth.permission", "pk": 19, "fields": {"name": "Can delete session", "content_type": 6, "codename": "delete_session"}}, {"model": "auth.permission", "pk": 20, "fields": {"name": "Can view session", "content_type": 6, "codename": "view_session"}}, {"model": "auth.permission", "pk": 21, "fields": {"name": "Can add ╤ююс∙хэшх", "content_type": 7, "codename": "add_message"}}, {"model": "auth.permission", "pk": 22, "fields": {"name": "Can change ╤ююс∙хэшх", "content_type": 7, "codename": "change_message"}}, {"model": "auth.permission", "pk": 23, "fields": {"name": "Can delete ╤ююс∙хэшх", "content_type": 7, "codename": "delete_message"}}, {"model": "auth.permission", "pk": 24, "fields": {"name": "Can view ╤ююс∙хэшх", "content_type": 7, "codename": "view_message"}}, {"model": "auth.permission", "pk": 25, "fields": {"name": "Can add ╧юыєўрЄхы№ Ёрёё√ыъш", "content_type": 8, "codename": "add_recipientmailing"}}, {"model": "auth.permission", "pk": 26, "fields": {"name": "Can change ╧юыєўрЄхы№ Ёрёё√ыъш", "content_type": 8, "codename": "change_recipientmailing"}}, {"model": "auth.permission", "pk": 27, "fields": {"name": "Can delete ╧юыєўрЄхы№ Ёрёё√ыъш", "content_type": 8, "codename": "delete_recipientmailing"}}, {"model": "auth.permission", "pk": 28, "fields": {"name": "Can view ╧юыєўрЄхы№ Ёрёё√ыъш", "content_type": 8, "codename": "view_recipientmailing"}}, {"model": "auth.permission", "pk": 29, "fields": {"name": "Can add ╨рёё√ыър", "content_type": 9, "codename": "add_mailing"}}, {"model": "auth.permission", "pk": 30, "fields": {"name": "Can change ╨рёё√ыър", "content_type": 9, "codename": "change_mailing"}}, {"model": "auth.permission", "pk": 31, "fields": {"name": "Can delete ╨рёё√ыър", "content_type": 9, "codename": "delete_mailing"}}, {"model": "auth.permission", "pk": 32, "fields": {"name": "Can view ╨рёё√ыър", "content_type": 9, "codename": "view_mailing"}}, {"model": "auth.permission", "pk": 33, "fields": {"name": "Can add ╧юя√Єър", "content_type": 10, "codename": "add_mailingattempt"}}, {"model": "auth.permission", "pk": 34, "fields": {"name": "Can change ╧юя√Єър", "content_type": 10, "codename": "change_mailingattempt"}}, {"model": "auth.permission", "pk": 35, "fields": {"name": "Can delete ╧юя√Єър", "content_type": 10, "codename": "delete_mailingattempt"}}, {"model": "auth.permission", "pk": 36, "fields": {"name": "Can view ╧юя√Єър", "content_type": 10, "codename": "view_mailingattempt"}}, {"model": "auth.permission", "pk": 37, "fields": {"name": "Can add ╧юы№чютрЄхы№", "content_type": 11, "codename": "add_user"}}, {"model": "auth.permission", "pk": 38, "fields": {"name": "Can change ╧юы№чютрЄхы№", "content_type": 11, "codename": "change_user"}}, {"model": "auth.permission", "pk": 39, "fields": {"name": "Can delete ╧юы№чютрЄхы№", "content_type": 11, "codename": "delete_user"}}, {"model": "auth.permission", "pk": 40, "fields": {"name": "Can view ╧юы№чютрЄхы№", "content_type": 11, "codename": "view_user"}}, {"model": "auth.permission", "pk": 41, "fields": {"name": "┬ючьюцэюёЄ№ юЄъы■ўхэш  Ёрёё√ыъш", "content_type": 9, "codename": "can_disable_mailing"}}, {"model": "auth.permission", "pk": 42, "fields": {"name": "┬ючьюцэюёЄ№ сыюъшЁютъш яюы№чютрЄхы ", "content_type": 11, "codename": "can_block_user"}}, {"model": "auth.group", "pk": 1, "fields": {"name": "╧юы№чютрЄхыш", "permissions": [29, 30, 31, 32, 36, 25, 26, 27, 28]}}, {"model": "auth.group", "pk": 2, "fields": {"name": "╠хэхфцхЁ√", "permissions": [41, 30, 32, 28, 42, 38, 40]}}]
71 changes: 70 additions & 1 deletion mailing/admin.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,72 @@
from django.contrib import admin

# Register your models here.
from mailing.models import (
Mailing,
MailingAttempt,
Message,
RecipientMailing)
from users.models import User


@admin.register(RecipientMailing)
class RecipientMailingAdmin(admin.ModelAdmin):
list_display = ("id", "fio", "email", "comment", "owner")
list_filter = ("fio",)
search_fields = (
"fio",
"email",
)


@admin.register(Message)
class MessageAdmin(admin.ModelAdmin):
list_display = (
"id",
"subject",
"content",
"owner",
)
search_fields = ("subject",)
list_filter = ("subject",)


@admin.register(Mailing)
class MailingAdmin(admin.ModelAdmin):
list_display = (
"id",
"first_sending",
"end_sending",
"status",
"message",
"owner"
)
search_fields = ("status",)
list_filter = ("status",)


@admin.register(User)
class UserAdmin(admin.ModelAdmin):
list_display = (
"id",
"avatar",
"email",
"first_name",
"last_name",
"middle_name",
"phone_number",
"country",
)
search_fields = ("email",)
list_filter = ("email",)


@admin.register(MailingAttempt)
class MailingAttemptAdmin(admin.ModelAdmin):
list_display = (
"id",
"owner",
"date_attempt",
"status",
)
search_fields = ("owner",)
list_filter = ("owner",)
53 changes: 53 additions & 0 deletions mailing/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from django.forms import BooleanField, ImageField, ModelForm
from django.urls import reverse_lazy

from mailing.models import Mailing, Message, RecipientMailing


class StyleFormMixin:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for fild_name, fild in self.fields.items():
if isinstance(fild, BooleanField):
fild.widget.attrs["class"] = "form-check-input"
elif isinstance(fild, ImageField):
fild.widget.attrs["class"] = "form-control-file"
else:
fild.widget.attrs["class"] = "form-control"


class MailingForm(StyleFormMixin, ModelForm):
class Meta:
model = Mailing
fields = "__all__"
exclude = ("can_disable_mailing", "owner")
success_url = reverse_lazy("mailing:mailing_list")


class MailingModeratorForm(StyleFormMixin, ModelForm):
class Meta:
model = Mailing
fields = "__all__"
success_url = reverse_lazy("mailing:mailing_list")


class RecipientForm(StyleFormMixin, ModelForm):
class Meta:
model = RecipientMailing
fields = "__all__"
exclude = ("can_blocking_client", "owner")
success_url = reverse_lazy("mailing:recipientmailing_list")


class RecipientModeratorForm(StyleFormMixin, ModelForm):
class Meta:
model = RecipientMailing
fields = "__all__"
success_url = reverse_lazy("mailing:recipientmailing_list")


class MessageForm(StyleFormMixin, ModelForm):
class Meta:
model = Message
fields = "__all__"
success_url = reverse_lazy("mailing:message_list")
Empty file added mailing/management/__init__.py
Empty file.
Empty file.
41 changes: 41 additions & 0 deletions mailing/management/commands/send_mailings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from django.core.mail import send_mail
from django.core.management import BaseCommand
from django.utils import timezone

from config.settings import EMAIL_HOST_USER
from mailing.models import Mailing, MailingAttempt


class Command(BaseCommand):
help = "Отправка почтовых отправлений получателям"

def handle(self, *args, **kwargs):
mailings = Mailing.objects.filter(status__in=[Mailing.CREATED, Mailing.LAUNCHED])
for mailing in mailings:
for recipient in mailing.recipients.all():
try:
send_mail(
mailing.message.subject,
mailing.message.content,
from_email=EMAIL_HOST_USER,
recipient_list=[recipient.email],
fail_silently=False,
)
MailingAttempt.objects.create(
date_attempt=timezone.now(),
status=MailingAttempt.STATUS_OK,
server_response="Email отправлен",
mailing=mailing,
)
print(
f"Сообщение {mailing.message.subject}"
f"успешно отправлено на {recipient.email}")
except Exception as e:
MailingAttempt.objects.create(
date_attempt=timezone.now(),
status=MailingAttempt.STATUS_NOK,
server_response=str(e),
mailing=mailing,
)
print(str(e))
mailing.save()
73 changes: 73 additions & 0 deletions mailing/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Generated by Django 5.2.4 on 2025-07-08 20:24

from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = [
]

operations = [
migrations.CreateModel(
name='Mailing',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('first_sending', models.DateTimeField(verbose_name='Дата и время первого отправления')),
('end_sending', models.DateTimeField(verbose_name='Дата и время окончания отправки')),
('status', models.CharField(choices=[('Создана', 'Создана'), ('Запущена', 'Запущена'), ('Завершена', 'Завершена')], default='Создана', max_length=10, verbose_name='Статус рассылки')),
('is_active', models.BooleanField(default=True, verbose_name='активна')),
],
options={
'verbose_name': 'Рассылка',
'verbose_name_plural': 'Рассылки',
'ordering': ['first_sending'],
'permissions': [('can_disable_mailing', 'Возможность отключения рассылки')],
},
),
migrations.CreateModel(
name='MailingAttempt',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date_attempt', models.DateTimeField(verbose_name='Дата и время попытки')),
('status', models.CharField(choices=[('Успешно', 'Успешно'), ('Не успешно', 'Не успешно')], max_length=15, verbose_name='Статус попытки')),
('server_response', models.TextField(verbose_name='Ответ почтового сервера')),
],
options={
'verbose_name': 'Попытка',
'verbose_name_plural': 'Попытки',
'ordering': ['date_attempt', 'status'],
},
),
migrations.CreateModel(
name='Message',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('subject', models.CharField(max_length=255, verbose_name='Тема сообщения')),
('content', models.TextField(verbose_name='Содержание сообщения')),
],
options={
'verbose_name': 'Сообщение',
'verbose_name_plural': 'Сообщения',
'ordering': ['subject'],
},
),
migrations.CreateModel(
name='RecipientMailing',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('email', models.EmailField(max_length=255, unique=True, verbose_name='E-mail')),
('fio', models.CharField(max_length=255, verbose_name='ФИО')),
('comment', models.TextField(blank=True, null=True, verbose_name='Комментарий')),
('avatar', models.ImageField(blank=True, null=True, upload_to='mailing/avatars', verbose_name='Аватар')),
('is_active', models.BooleanField(default=True, verbose_name='активность')),
],
options={
'verbose_name': 'Получатель рассылки',
'verbose_name_plural': 'Получатели рассылки',
'ordering': ['fio'],
},
),
]
Loading