Skip to content

Commit

Permalink
Merge pull request #777 from Studio-Yandex-Practicum/develop
Browse files Browse the repository at this point in the history
Release
  • Loading branch information
AntonZelinsky authored May 15, 2024
2 parents 52ee0a2 + 89034d6 commit 96669cf
Show file tree
Hide file tree
Showing 21 changed files with 217 additions and 78 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ repos:
- flake8-docstrings

- repo: https://github.com/ambv/black
rev: 22.3.0
rev: 24.4.0
hooks:
- id: black
exclude: (migrations|config/settings/)
Expand Down
4 changes: 2 additions & 2 deletions apps/articles/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ def duplicate_image(image: ImageFieldFile):

@overload
def content_block_copy(block: AbstractItemWithTitle) -> AbstractItemWithTitle:
...
pass


@overload
def content_block_copy(block: ContentUnitRichText) -> ContentUnitRichText:
...
pass


def content_block_copy(
Expand Down
15 changes: 15 additions & 0 deletions apps/core/migrations/0039_add_unaccent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Generated by Django 3.2.25 on 2024-05-11 23:05

from django.contrib.postgres.operations import UnaccentExtension
from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('core', '0038_add_yandex_disk_setting'),
]

operations = [
UnaccentExtension(),
]
19 changes: 13 additions & 6 deletions apps/core/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,15 +89,22 @@ class PreviewButtonMixin:

def change_view(self, request, object_id, form_url="", extra_context=None):
string_url = {
"BlogItem": "blog",
"NewsItem": "news",
"Project": "projects",
"Performance": "performances",
"BlogItem": "/blog",
"NewsItem": "/news",
"Project": "/projects",
"Performance": "/performances",
"Author": "",
}
link = f"/{string_url[self.model._meta.object_name]}/{object_id}"
if hasattr(self.model, "slug"):
identity = self.get_object(request, object_id).slug
else:
identity = object_id
link = f"{string_url[self.model._meta.object_name]}/{identity}"
# add hash for unpublished pages and change button name
if extra_context is None:
extra_context = {}
preview_button_context = {}
if self.model.objects.is_published(object_id):
if not hasattr(self.model.objects, "is_published") or self.model.objects.is_published(object_id):
preview_button_context["button_name"] = "Просмотр страницы"
preview_button_context["link"] = link
# FIXME: Ждем когда функционал для предпросмотра будет готов на фронтенде
Expand Down
12 changes: 2 additions & 10 deletions apps/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,6 @@ class Meta:
)
]

def save(self, *args, **kwargs):
super().save(*args, **kwargs)
# Сохранение связанного автора для обновления таблицы переадресации почты
if hasattr(self, "authors"):
self.authors.save()

def __str__(self):
return f"{self.last_name} {self.first_name}"

Expand All @@ -121,10 +115,8 @@ def reversed_full_name(self):
return f"{self.last_name} {self.first_name}".strip()

@property
def mail_forwarding_enabled(self):
if hasattr(self, "authors") and hasattr(self.authors, "virtual_email"):
return self.authors.virtual_email.enabled
return False
def has_mail_forwarding(self):
return hasattr(self, "authors") and hasattr(self.authors, "virtual_email")


class Role(BaseModel):
Expand Down
41 changes: 41 additions & 0 deletions apps/core/services/mail_forwarding.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""Управление записями переадресации электронной почты."""

from typing import Callable, Optional

from apps.library.models.author import Author
from apps.postfix.models import Virtual

MAIL_CREATED = "Создан или обновлен виртуальный адрес '{virtual_email}'"
MAIL_DELETED = "Виртуальный адрес '{virtual_email}' был удален"


def create_forwarding(author: Author) -> Virtual:
virtual_email: Optional[Virtual] = getattr(author, "virtual_email", None)
if not virtual_email:
virtual_email = Virtual(author=author, mailbox=author.slug)
virtual_email.save()
else:
virtual_email.mailbox = author.slug
virtual_email.recipients.all().delete()
virtual_email.recipients.create(email=author.person.email)
author.virtual_email = virtual_email
return virtual_email


def delete_forwarding(author: Author) -> Optional[Virtual]:
virtual_email: Optional[Virtual] = getattr(author, "virtual_email", None)
if virtual_email:
virtual_email.delete()
author.virtual_email = None
return virtual_email


def on_change(instance: Author, create: bool, message: Callable[[str], None]) -> Optional[Virtual]:
if create:
virtual_email = create_forwarding(instance)
message(MAIL_CREATED.format(virtual_email=virtual_email))
else:
virtual_email = delete_forwarding(instance)
if virtual_email:
message(MAIL_DELETED.format(virtual_email=virtual_email))
return virtual_email
24 changes: 24 additions & 0 deletions apps/feedback/migrations/0008_auto_20240427_0051.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Generated by Django 3.2.25 on 2024-04-26 21:51

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


class Migration(migrations.Migration):

dependencies = [
('feedback', '0007_participationapplicationfestival_nickname'),
]

operations = [
migrations.AlterField(
model_name='participationapplicationfestival',
name='city',
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='Город проживания'),
),
migrations.AlterField(
model_name='participationapplicationfestival',
name='phone_number',
field=phonenumber_field.modelfields.PhoneNumberField(blank=True, help_text='Номер телефона указывается в формате +7', max_length=128, null=True, region=None, verbose_name='Номер телефона'),
),
]
4 changes: 4 additions & 0 deletions apps/feedback/models/participation_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,13 @@ class ParticipationApplicationFestival(BaseModel):
city = models.CharField(
max_length=50,
verbose_name="Город проживания",
null=True,
blank=True,
)
phone_number = PhoneNumberField(
verbose_name="Номер телефона",
null=True,
blank=True,
help_text="Номер телефона указывается в формате +7",
)
email = models.EmailField(
Expand Down
2 changes: 2 additions & 0 deletions apps/feedback/services/participation_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ def _mail_send_export(self, instance, file_link):
if send_email_success:
instance.sent_to_email = True
instance.save()
# Отправка подтверждения участнику
send_email(from_email, (instance.email,), template_id, context, attach_file=True)

def export_application(self, instance, file_link):
"""Функция, объединяющая экспорт на диск, в таблицу и отправку на почту."""
Expand Down
20 changes: 18 additions & 2 deletions apps/info/admin/people.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
from functools import partial
from typing import Any

from adminsortable2.admin import SortableAdminMixin
from django.contrib import admin
from django.contrib import admin, messages
from django.http import HttpResponseRedirect
from django.http.request import HttpRequest
from django.urls import reverse
from django.utils.html import format_html

from apps.core.mixins import AdminImagePreview, HideOnNavPanelAdminModelMixin
from apps.core.models import Person
from apps.core.services.mail_forwarding import on_change
from apps.info.filters import HasReviewAdminFilter, PartnerTypeFilter
from apps.info.models import Partner, Selector, Sponsor, Volunteer
from apps.info.models.people import Review
Expand Down Expand Up @@ -112,10 +116,22 @@ def get_search_results(self, request, queryset, search_term):
def get_readonly_fields(self, request: HttpRequest, person: Person) -> tuple[str]:
return tuple(super().get_readonly_fields(request, person)) + (
("email",)
if not request.user.has_perm("postfix.change_virtual") and person and person.mail_forwarding_enabled
if not request.user.has_perms(("postfix.change_virtual", "postfix.delete_virtual", "postfix.add_virtual"))
and person
and person.has_mail_forwarding
else ()
)

def save_related(self, request: Any, form: Any, formsets: Any, change: Any) -> None:
person: Person = form.instance
if "email" in form.changed_data and person.has_mail_forwarding:
on_change(
instance=person.authors,
create=bool(person.email),
message=partial(messages.add_message, request, messages.INFO),
)
super().save_related(request, form, formsets, change)


@admin.register(Volunteer)
class VolunteerAdmin(admin.ModelAdmin):
Expand Down
51 changes: 32 additions & 19 deletions apps/library/admin/author.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
from typing import Any
from functools import partial
from typing import Any, Optional, Union

from adminsortable2.admin import SortableInlineAdminMixin
from django import forms
from django.contrib import admin
from django.contrib import admin, messages
from django.contrib.postgres.aggregates import StringAgg
from django.db.models import Count
from django.forms import ModelForm, ValidationError
from django.forms.fields import Field
from django.http.request import HttpRequest

from apps.core.mixins import PreviewButtonMixin
from apps.core.services.mail_forwarding import on_change
from apps.library.forms import OtherLinkForm
from apps.library.models import Author, AuthorPlay, OtherLink, SocialNetworkLink
from apps.postfix.models import Virtual
Expand Down Expand Up @@ -126,23 +130,15 @@ def get_initial_for_field(self, field: Field, field_name: str) -> Any:
return False
return super().get_initial_for_field(field, field_name)

def save(self, commit: bool = True) -> Any:
author = self.instance

if hasattr(author, "virtual_email"):
author.virtual_email.mailbox = self.cleaned_data.get("slug")
author.virtual_email.enabled = self.cleaned_data.get("enable_email", False)

if self.cleaned_data.get("enable_email", False) and not hasattr(author, "virtual_email"):
virtual = Virtual(author=author, mailbox=self.cleaned_data.get("slug"))
author.virtual_email = virtual

return super().save(commit=commit)

def clean(self):
cleaned_data = super().clean()
enable_email = cleaned_data.get("enable_email")
person = cleaned_data.get("person")
slug = cleaned_data.get("slug")
if enable_email and Virtual.objects.filter(mailbox=slug).exists():
raise ValidationError(
"Такой почтовый ящик уже существует. Измените транслит фамилии или отключите переадресацию."
)
if enable_email and person:
if not person.email:
raise ValidationError(
Expand All @@ -151,7 +147,7 @@ def clean(self):


@admin.register(Author)
class AuthorAdmin(admin.ModelAdmin):
class AuthorAdmin(PreviewButtonMixin, admin.ModelAdmin):
# form = AuthorAdminForm
list_display = (
"person",
Expand All @@ -175,15 +171,21 @@ class AuthorAdmin(admin.ModelAdmin):
search_fields = (
"biography",
"slug",
"person__first_name",
"person__last_name",
"person__middle_name",
"person__first_name__unaccent",
"person__last_name__unaccent",
"person__middle_name__unaccent",
"person__email",
"plays__name",
)
autocomplete_fields = ("person",)
empty_value_display = "-пусто-"

def get_readonly_fields(self, request: HttpRequest, obj: Optional[Any] = ...) -> Union[list[str], tuple[Any, ...]]:
fields = super().get_readonly_fields(request, obj)
if hasattr(obj, "virtual_email") and not request.user.has_perm("postfix.change_virtual"):
fields = list(fields) + ["person", "slug"]
return fields

def get_form(self, request, obj=None, change=False, **kwargs) -> Any:
if request.user.has_perm("postfix.add_virtual") and request.user.has_perm("postfix.change_virtual"):
kwargs["form"] = AuthorAdminForm
Expand All @@ -196,6 +198,17 @@ def get_queryset(self, request):
)
return queryset

def save_related(self, request: Any, form: Any, formsets: Any, change: Any) -> None:
author: Author = form.instance
has_changes = any(field_name in form.changed_data for field_name in ["enable_email", "person", "slug"])
if has_changes:
on_change(
author,
create=form.cleaned_data.get("enable_email", False),
message=partial(messages.add_message, request, messages.INFO),
)
return super().save_related(request, form, formsets, change)

@admin.display(description="Количество пьес")
def plays_count(self, obj):
return obj._plays_count
Expand Down
2 changes: 1 addition & 1 deletion apps/library/filters/authors.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ def filter_by_name_predicate(queryset, name, value):
"""Фамилии начинающиеся не с кирилицы выдается под знаком '#'."""
if value == "#":
return queryset.exclude(Q(person__last_name__regex="^[а-яА-Я]"))
return queryset.filter(Q(person__last_name__istartswith=value))
return queryset.filter(Q(person__last_name__unaccent__istartswith=value))


class AuthorFilter(django_filters.FilterSet):
Expand Down
2 changes: 1 addition & 1 deletion apps/library/views/search_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def get_querylist(self):
if q:
plays_queryset = Play.objects.filter(other_play=False, published=True).filter(name__icontains=q)
authors_queryset = Author.objects.filter(
Q(person__first_name__icontains=q) | Q(person__last_name__icontains=q)
Q(person__first_name__unaccent__icontains=q) | Q(person__last_name__unaccent__icontains=q)
)
else:
plays_queryset = Play.objects.none()
Expand Down
3 changes: 2 additions & 1 deletion apps/postfix/models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from django.conf import settings
from django.db import models

from apps.core.models import BaseModel
Expand Down Expand Up @@ -28,7 +29,7 @@ class Meta:
verbose_name_plural = "виртуальные адреса"

def __str__(self):
return self.mailbox
return f"{self.mailbox}@{settings.POSTFIX_MAIL_DOMAIN}"


class Recipient(BaseModel):
Expand Down
1 change: 1 addition & 0 deletions config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"django.contrib.postgres",
]
THIRD_PARTY_APPS = [
"corsheaders",
Expand Down
Loading

0 comments on commit 96669cf

Please sign in to comment.