Skip to content

Commit 547ab29

Browse files
authored
Merge pull request #249 from PROCOLLAB-github/dev
Фильтры и типизация
2 parents a8e310d + 31f6432 commit 547ab29

16 files changed

Lines changed: 541 additions & 59 deletions

core/pagination.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from rest_framework import pagination
2+
3+
4+
class Pagination(pagination.LimitOffsetPagination):
5+
default_limit = 10
6+
limit_query_param = "limit"
7+
offset_query_param = "offset"

invites/tests.py

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from users.views import UserList
88
from users.models import CustomUser
9-
from invites.views import InviteList, InviteDetail
9+
from invites.views import InviteList, InviteDetail, InviteAccept, InviteDecline
1010
from invites.models import Invite
1111
from industries.models import Industry
1212
from projects.views import ProjectList, ProjectDetail
@@ -114,6 +114,69 @@ def test_invites_creation_with_empty_data(self):
114114

115115
self.assertEqual(response.status_code, 400)
116116

117+
def test_accept_invite_by_intended_user(self):
118+
sender = self._user_create("sender@example.com")
119+
recipient = self._user_create("recipient@example.com")
120+
project = self._project_create(sender)
121+
122+
invite = self._create_invite(sender, recipient, project)
123+
124+
request = self.factory.post(f"/invites/{invite.id}/accept/")
125+
force_authenticate(request, user=recipient)
126+
accept_response = InviteAccept.as_view()(request, pk=invite.id)
127+
128+
self.assertEqual(accept_response.status_code, 200)
129+
invite.refresh_from_db()
130+
self.assertTrue(invite.is_accepted)
131+
132+
def test_decline_invite_by_intended_user(self):
133+
sender = self._user_create("sender@example.com")
134+
recipient = self._user_create("recipient@example.com")
135+
project = self._project_create(sender)
136+
137+
invite = self._create_invite(sender, recipient, project)
138+
139+
request = self.factory.post(f"/invites/{invite.id}/decline/")
140+
force_authenticate(request, user=recipient)
141+
decline_response = InviteDecline.as_view()(request, pk=invite.id)
142+
143+
self.assertEqual(decline_response.status_code, 200)
144+
invite.refresh_from_db()
145+
self.assertFalse(invite.is_accepted)
146+
147+
def test_accept_decline_invite_by_unintended_user(self):
148+
sender = self._user_create("sender@example.com")
149+
recipient = self._user_create("recipient@example.com")
150+
unintended_user = self._user_create("unintended@example.com")
151+
project = self._project_create(sender)
152+
153+
invite = self._create_invite(sender, recipient, project)
154+
155+
accept_request = self.factory.post(f"/invites/{invite.id}/accept/")
156+
force_authenticate(accept_request, user=unintended_user)
157+
accept_response = InviteAccept.as_view()(accept_request, pk=invite.id)
158+
159+
decline_request = self.factory.post(f"/invites/{invite.id}/decline/")
160+
force_authenticate(decline_request, user=unintended_user)
161+
decline_response = InviteDecline.as_view()(decline_request, pk=invite.id)
162+
163+
self.assertNotEqual(accept_response.status_code, 200)
164+
self.assertNotEqual(decline_response.status_code, 200)
165+
166+
def test_delete_invite_by_sender(self):
167+
sender = self._user_create("sender@example.com")
168+
recipient = self._user_create("recipient@example.com")
169+
project = self._project_create(sender)
170+
171+
invite = self._create_invite(sender, recipient, project)
172+
173+
request = self.factory.delete(f"/invites/{invite.id}/")
174+
force_authenticate(request, user=sender)
175+
delete_response = self.invite_detail_view(request, pk=invite.id)
176+
177+
self.assertEqual(delete_response.status_code, 204)
178+
self.assertFalse(Invite.objects.filter(pk=invite.id).exists())
179+
117180
def _project_create(self, user):
118181
request = self.factory.post("projects/", self.project_create_data)
119182
force_authenticate(request, user=user)
@@ -132,3 +195,13 @@ def _user_create(self, email):
132195
user.is_active = True
133196
user.save()
134197
return user
198+
199+
def _create_invite(self, sender, recipient, project):
200+
invite_data = self.invite_create_data.copy()
201+
invite_data.update({"project": project.id, "user": recipient.id})
202+
203+
request = self.factory.post("/invites/", invite_data, format="json")
204+
force_authenticate(request, user=sender)
205+
response = self.invite_list_view(request)
206+
207+
return Invite.objects.get(pk=response.data["id"])

mailing/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,6 @@ def get_default_mailing_schema() -> dict[str, dict[str, str]]:
77
"text": {"title": "Основной текст письма"},
88
"button_text": {"title": "Текст кнопки", "default": "Кнопка"},
99
}
10+
11+
12+
MAILING_USERS_BATCH_SIZE = 100

mailing/utils.py

Lines changed: 62 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
from typing import Dict, List, Union
2+
from .constants import MAILING_USERS_BATCH_SIZE
3+
from .models import MailingSchema
4+
from users.models import CustomUser
25

36
import django.db.models
47
from django.contrib.auth import get_user_model
@@ -9,29 +12,64 @@
912
User = get_user_model()
1013

1114

15+
def prepare_mail_data(post_data) -> dict:
16+
users = post_data.getlist("users[]")
17+
schema_id = post_data["schemas"]
18+
subject = post_data["subject"]
19+
mail_schema = MailingSchema.objects.get(pk=schema_id)
20+
context = {}
21+
for variable_name in mail_schema.schema:
22+
key_in_post = "field-" + variable_name
23+
if key_in_post in post_data:
24+
context[variable_name] = post_data[key_in_post]
25+
users_to_send = CustomUser.objects.filter(pk__in=users)
26+
data_dict = {
27+
"users_to_send": users_to_send,
28+
"subject": subject,
29+
"mail_schema_template": mail_schema.template,
30+
"context": context,
31+
}
32+
return data_dict
33+
34+
35+
def create_message_groups(messages: list) -> list[list]:
36+
grouped_messages: list[list] = [
37+
messages[message: message + MAILING_USERS_BATCH_SIZE]
38+
for message in range(0, len(messages), MAILING_USERS_BATCH_SIZE)
39+
]
40+
return grouped_messages
41+
42+
1243
def send_mail(
13-
user: User,
14-
subject: str,
15-
template_string: str,
16-
template_context: Union[
17-
Dict,
18-
List,
19-
] = None,
20-
connection=None,
44+
user: User,
45+
subject: str,
46+
template_string: str,
47+
template_context: Union[
48+
Dict,
49+
List,
50+
] = None,
51+
connection=None,
2152
):
2253
return send_mass_mail([user], subject, template_string, template_context, connection)
2354

2455

56+
def send_group_messages(messages: list) -> int:
57+
connection = mail.get_connection()
58+
num_sent = connection.send_messages(messages)
59+
connection.close()
60+
return num_sent
61+
62+
2563
def send_mass_mail(
26-
users: django.db.models.QuerySet | List[User],
27-
subject: str,
28-
template_string: str,
29-
template_context: Union[
30-
Dict,
31-
List,
32-
] = None,
33-
connection=None,
34-
) -> None:
64+
users: django.db.models.QuerySet | List[User],
65+
subject: str,
66+
template_string: str,
67+
template_context: Union[
68+
Dict,
69+
List,
70+
] = None,
71+
connection=None,
72+
) -> int:
3573
"""
3674
Begin mailing to specified users, sending rendered template with template_text arg.
3775
Throws an error if template render is unsuccessful.
@@ -45,16 +83,18 @@ def send_mass_mail(
4583
if template_context is None:
4684
template_context = {}
4785

48-
connection = connection or mail.get_connection()
4986
template = Template(template_string)
5087
messages = []
5188
for user in users:
5289
template_context["user"] = user
5390
html_msg = template.render(Context(template_context))
5491
plain_msg = template.render(Context(template_context))
55-
msg = EmailMultiAlternatives(
56-
subject, plain_msg, None, [user.email], connection=connection
57-
)
92+
msg = EmailMultiAlternatives(subject, plain_msg, None, [user.email])
5893
msg.attach_alternative(html_msg, "text/html")
5994
messages.append(msg)
60-
return connection.send_messages(messages)
95+
96+
grouped_messages = create_message_groups(messages)
97+
num_sent: int = 0
98+
for group in grouped_messages:
99+
num_sent += send_group_messages(group)
100+
return num_sent

mailing/views.py

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,18 @@
66
from users.models import CustomUser
77
from .utils import send_mass_mail
88
from .models import MailingSchema
9+
from .utils import prepare_mail_data
910

1011

1112
class SendMailView(APIView):
12-
def post(self, request):
13-
users = request.POST.getlist("users[]")
14-
schema_id = request.POST["schemas"]
15-
subject = request.POST["subject"]
16-
mail_schema = MailingSchema.objects.get(pk=schema_id)
17-
context = {}
18-
for variable_name in mail_schema.schema:
19-
key_in_post = "field-" + variable_name
20-
if key_in_post in request.POST:
21-
context[variable_name] = request.POST[key_in_post]
22-
users_to_send = CustomUser.objects.filter(pk__in=users)
23-
send_mass_mail(users_to_send, subject, mail_schema.template, context)
13+
def post(self, request) -> JsonResponse:
14+
mail_data: dict = prepare_mail_data(request.POST)
15+
send_mass_mail(
16+
mail_data["users_to_send"],
17+
mail_data["subject"],
18+
mail_data["mail_schema_template"],
19+
mail_data["context"],
20+
)
2421
return JsonResponse({"detail": "ok"})
2522

2623

mypy.ini

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[mypy]
2+
plugins =
3+
mypy_django_plugin.main
4+
mypy_drf_plugin.main
5+
6+
[mypy.plugins.django-stubs]
7+
django_settings_module = "procollab.settings"

0 commit comments

Comments
 (0)