Skip to content

Commit fccbe87

Browse files
authored
Merge branch 'master' into dev-to-master
2 parents 5e5e941 + 9268298 commit fccbe87

15 files changed

Lines changed: 250 additions & 29 deletions

File tree

.github/workflows/release-ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ name: 'Build and Deploy server'
33
on:
44
release:
55
types: [ published ]
6+
workflow_dispatch:
67

78
jobs:
89
test:

chats/views.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,9 @@ def get(self, request, *args, **kwargs) -> Response:
9494
request.user.id == user1_id or request.user.id == user2_id
9595
), "current user id is not present in raw id"
9696

97-
user1 = User.objects.get(pk=user1_id)
98-
user2 = User.objects.get(pk=user2_id)
97+
users = User.objects.filter(pk__in=[user1_id, user2_id])
98+
user1 = users.get(pk=user1_id)
99+
user2 = users.get(pk=user2_id)
99100

100101
if user1 == request.user:
101102
opponent = user2
@@ -117,7 +118,9 @@ def get(self, request, *args, **kwargs) -> Response:
117118
except ValueError:
118119
return Response(
119120
status=status.HTTP_400_BAD_REQUEST,
120-
data={"detail": "processed id must contain two integers separated by underscore"},
121+
data={
122+
"detail": "processed id must contain two integers separated by underscore"
123+
},
121124
)
122125
except AssertionError as e:
123126
return Response(status=status.HTTP_400_BAD_REQUEST, data={"detail": str(e)})
@@ -223,10 +226,16 @@ def get(self, request, *args, **kwargs):
223226
project_chats = user.get_project_chats().prefetch_related("messages")
224227

225228
has_direct_messages_unread = (
226-
direct_messages.filter(messages__is_read=False).distinct().exists()
229+
direct_messages.filter(messages__is_read=False)
230+
.exclude(messages__is_deleted=True)
231+
.distinct()
232+
.exists()
227233
)
228234
has_project_messages_unread = (
229-
project_chats.filter(messages__is_read=False).distinct().exists()
235+
project_chats.filter(messages__is_read=False)
236+
.exclude(messages__is_deleted=True)
237+
.distinct()
238+
.exists()
230239
)
231240
return Response(
232241
{"has_unreads": has_direct_messages_unread or has_project_messages_unread}

feed/serializers.py

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,68 @@
11
from rest_framework import serializers
2-
from feed import constants
32

3+
from core.services import get_views_count, get_likes_count
4+
from feed.mapping import CONTENT_OBJECT_MAPPING, CONTENT_OBJECT_SERIALIZER_MAPPING
5+
from files.serializers import UserFileSerializer
6+
from news.mapping import NewsMapping
7+
from news.models import News
8+
from projects.models import Project
9+
from users.models import CustomUser
410

5-
class FeedItemSerializer(serializers.Serializer):
6-
type_model = serializers.ChoiceField(choices=constants.FeedItemType, required=True)
7-
content = serializers.JSONField(required=True)
11+
12+
class NewsFeedListSerializer(serializers.ModelSerializer):
13+
name = serializers.SerializerMethodField()
14+
image_address = serializers.SerializerMethodField()
15+
is_user_liked = serializers.SerializerMethodField()
16+
files = UserFileSerializer(many=True)
17+
views_count = serializers.SerializerMethodField()
18+
likes_count = serializers.SerializerMethodField()
19+
content_object = serializers.SerializerMethodField()
20+
type_model = serializers.SerializerMethodField()
21+
22+
def get_type_model(self, obj) -> str:
23+
model_type = CONTENT_OBJECT_MAPPING[obj.content_type.model]
24+
if obj.text != "" and model_type == "project":
25+
return "news"
26+
return model_type
27+
28+
def get_content_object(self, obj) -> dict:
29+
type_model = obj.content_type.model
30+
if obj.text != "" and self.get_type_model(obj) == "project":
31+
type_model = "news"
32+
serializer = CONTENT_OBJECT_SERIALIZER_MAPPING[type_model](obj.content_object)
33+
return serializer.data
34+
35+
def get_views_count(self, obj):
36+
return get_views_count(obj)
37+
38+
def get_likes_count(self, obj):
39+
return get_likes_count(obj)
40+
41+
def get_name(self, obj):
42+
if obj.content_type.model == CustomUser.__name__.lower():
43+
return f"{obj.content_object.first_name} {obj.content_object.last_name}"
44+
elif obj.text != "" and obj.content_type.model == Project.__name__.lower():
45+
return f"{obj.content_object.name}"
46+
47+
def get_image_address(self, obj):
48+
return NewsMapping.get_image_address(obj.content_object)
49+
50+
def get_is_user_liked(self, obj):
51+
return obj.id in self.context.get("liked_news")
52+
53+
class Meta:
54+
model = News
55+
fields = [
56+
"id",
57+
"name",
58+
"image_address",
59+
"text",
60+
"datetime_created",
61+
"views_count",
62+
"likes_count",
63+
"files",
64+
"is_user_liked",
65+
"content_object",
66+
"type_model",
67+
]
68+
read_only_fields = ["views_count", "likes_count", "type_model"]

feed/services.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,29 @@
11
from django.contrib.contenttypes.models import ContentType
22

3+
from core.models import Like
34
from feed.constants import SIGNALS_MODELS
45
from news.models import News
6+
from users.models import CustomUser
57

68

9+
def get_liked_news(user: CustomUser, queryset: list[News]) -> list[int]:
10+
obj_type = ContentType.objects.get_for_model(News)
11+
liked_news = Like.objects.filter(
12+
content_type=obj_type, object_id__in=[news.id for news in queryset], user=user
13+
).values_list("object_id", flat=True)
14+
return liked_news
15+
16+
17+
# signals services
718
def delete_news_for_model(instance: SIGNALS_MODELS):
819
content_type = ContentType.objects.get_for_model(instance)
9-
obj = News.objects.filter(content_type=content_type, object_id=instance.id).first()
20+
obj = News.objects.filter(
21+
text="", content_type=content_type, object_id=instance.id
22+
).first()
1023
if obj:
1124
obj.delete()
1225

1326

1427
def create_news_for_model(instance: SIGNALS_MODELS):
1528
content_type = ContentType.objects.get_for_model(instance)
16-
News.objects.get_or_create(content_type=content_type, object_id=instance.id)
29+
News.objects.get_or_create(text="", content_type=content_type, object_id=instance.id)

feed/views.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
from rest_framework.views import APIView
55

66
from feed.pagination import FeedPagination
7+
from feed.services import get_liked_news
78

89
from news.models import News
9-
from news.serializers import NewsFeedListSerializer
10+
from .serializers import NewsFeedListSerializer
1011
from projects.models import Project
1112
from vacancy.models import Vacancy
1213

@@ -38,7 +39,12 @@ def get(self, *args, **kwargs):
3839
paginator = self.pagination_class()
3940
paginated_data = paginator.paginate_queryset(self.get_queryset(), self.request)
4041
serializer = NewsFeedListSerializer(
41-
paginated_data, context={"user": self.request.user}, many=True
42+
paginated_data,
43+
context={
44+
"user": self.request.user,
45+
"liked_news": get_liked_news(self.request.user, paginated_data),
46+
},
47+
many=True,
4248
)
4349

4450
new_data = []

news/serializers.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,10 @@
22
from rest_framework import serializers
33

44
from core.services import is_fan, get_likes_count, get_views_count
5-
from feed.mapping import CONTENT_OBJECT_MAPPING, CONTENT_OBJECT_SERIALIZER_MAPPING
65
from files.serializers import UserFileSerializer
76
from news.mapping import NewsMapping
87
from news.models import News
98

10-
from projects.models import Project
11-
from users.models import CustomUser
129

1310
User = get_user_model()
1411

@@ -63,7 +60,8 @@ class Meta:
6360
]
6461

6562

66-
class NewsFeedListSerializer(serializers.ModelSerializer[News]):
63+
64+
class NewsFeedListSerializer(serializers.ModelSerializer):
6765
name = serializers.SerializerMethodField()
6866
image_address = serializers.SerializerMethodField()
6967
is_user_liked = serializers.SerializerMethodField()
@@ -125,7 +123,9 @@ class Meta:
125123
read_only_fields = ["views_count", "likes_count", "type_model"]
126124

127125

128-
class NewsDetailSerializer(serializers.ModelSerializer[News]):
126+
127+
128+
class NewsDetailSerializer(serializers.ModelSerializer):
129129
views_count = serializers.SerializerMethodField()
130130
likes_count = serializers.SerializerMethodField()
131131
name = serializers.SerializerMethodField()

projects/signals.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
from django.dispatch import receiver
33

44
from chats.models import ProjectChat
5+
from feed.services import delete_news_for_model, create_news_for_model
56
from projects.models import Collaborator, Project
7+
from vacancy.models import Vacancy
68

79

810
@receiver(post_save, sender=Project)
@@ -18,3 +20,21 @@ def create_project(sender, instance, created, **kwargs):
1820
Collaborator.objects.create(
1921
user=instance.leader, project=instance, role="Основатель"
2022
)
23+
24+
25+
@receiver(post_save, sender=Project)
26+
def update_vacancy(sender, instance, created, **kwargs):
27+
vacancies = Vacancy.objects.filter(project=instance)
28+
old_values = vacancies.values_list("is_active", flat=True)
29+
vacancies.update(is_active=False if instance.draft else True)
30+
new_values = vacancies.values_list("is_active", flat=True)
31+
32+
vacancies_list = list(vacancies)
33+
34+
for i in range(len(new_values)):
35+
old = old_values[i]
36+
new = new_values[i]
37+
if old != new and new is False:
38+
delete_news_for_model(vacancies_list[i])
39+
elif old != new and new is True:
40+
create_news_for_model(vacancies_list[i])
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{% extends "admin/change_list.html" %}
2+
{% load i18n %}
3+
4+
{% block object-tools-items %}
5+
{{ block.super }}
6+
<a href="#" class="addlink" previewlistener="true" onclick="get_users()">
7+
Выгрузка ФИО|Email всех пользователей
8+
</a>
9+
10+
<script>
11+
function get_users() {
12+
window.open("{% url 'admin:users_email_excel' %}", '_blank').focus();
13+
}
14+
</script>
15+
{% endblock %}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{% extends "admin/change_list.html" %}
2+
{% load i18n %}
3+
4+
{% block object-tools-items %}
5+
{{ block.super }}
6+
<a href="#" class="addlink" previewlistener="true" onclick="get_emails_vacancies()">
7+
Emal лидеров у проектов с вакансиями
8+
</a>
9+
10+
<script>
11+
function get_emails_vacancies() {
12+
window.open("{% url 'admin:vacancy_leaders_email' %}", '_blank').focus();
13+
}
14+
</script>
15+
{% endblock %}

users/admin.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import tablib
12
from django.conf import settings
23
from django.contrib import admin
4+
from django.http import HttpResponse
35
from django.shortcuts import redirect
46
from django.urls import path
57

@@ -119,6 +121,7 @@ class CustomUserAdmin(admin.ModelAdmin):
119121

120122
readonly_fields = ("ordering_score",)
121123
change_form_template = "users/admin/users_change_form.html"
124+
change_list_template = "users/admin/users_change_list.html"
122125

123126
def save_model(self, request, obj, form, change):
124127
# if user_type changed, then delete all related fields
@@ -177,13 +180,22 @@ def get_urls(self):
177180
self.admin_site.admin_view(self.mass_mail),
178181
name="user_mass_mail",
179182
),
183+
path(
184+
"all-users-email-excel/",
185+
self.admin_site.admin_view(self.all_users_email_excel),
186+
name="users_email_excel",
187+
),
180188
]
181189
return custom_urls + default_urls
182190

183191
def mass_mail(self, request):
184192
users = CustomUser.objects.all()
185193
return MailingTemplateRender().render_template(request, None, users, None)
186194

195+
def all_users_email_excel(self, request):
196+
users = CustomUser.objects.only("first_name", "last_name", "email").iterator()
197+
return self.get_export_users_emails(users)
198+
187199
def mailing(self, request, user_object):
188200
user = CustomUser.objects.get(pk=user_object)
189201
users = [user]
@@ -194,6 +206,21 @@ def force_verify(self, request, object_id):
194206
force_verify_user(user)
195207
return redirect("admin:users_customuser_change", object_id)
196208

209+
def get_export_users_emails(self, users):
210+
response_data = tablib.Dataset(headers=["ФИО", "Email"])
211+
212+
for user in users:
213+
response_data.append([user.first_name + " " + user.last_name, user.email])
214+
215+
binary_data = response_data.export("xlsx")
216+
file_name = "all_users_names_emails"
217+
response = HttpResponse(
218+
content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
219+
headers={"Content-Disposition": f'attachment; filename="{file_name}.xlsx"'},
220+
)
221+
response.write(binary_data)
222+
return response
223+
197224

198225
@admin.register(UserAchievement)
199226
class UserAchievementAdmin(admin.ModelAdmin):

0 commit comments

Comments
 (0)