Skip to content
Merged

Dev #274

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
167 changes: 167 additions & 0 deletions core/migrations/0011_auto_20240128_2214.py

Large diffs are not rendered by default.

27 changes: 27 additions & 0 deletions core/migrations/0012_alter_specialization_options_and_more.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Generated by Django 4.2.3 on 2024-01-28 19:33

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
("core", "0011_auto_20240128_2214"),
]

operations = [
migrations.AlterModelOptions(
name="specialization",
options={
"verbose_name": "Специализация",
"verbose_name_plural": "Специализации",
},
),
migrations.AlterModelOptions(
name="specializationcategory",
options={
"verbose_name": "Категория специализации",
"verbose_name_plural": "Категории специализаций",
},
),
]
8 changes: 8 additions & 0 deletions core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,13 +155,21 @@ class SkillToObject(models.Model):
class SpecializationCategory(models.Model):
name = models.TextField()

class Meta:
verbose_name = "Категория специализации"
verbose_name_plural = "Категории специализаций"


class Specialization(models.Model):
name = models.TextField()
category = models.ForeignKey(
SpecializationCategory, related_name="specializations", on_delete=models.CASCADE
)

class Meta:
verbose_name = "Специализация"
verbose_name_plural = "Специализации"


class SpecializationToObject(models.Model):
specialization = models.ForeignKey(
Expand Down
Empty file added feed/__init__.py
Empty file.
6 changes: 6 additions & 0 deletions feed/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class FeedConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "feed"
20 changes: 20 additions & 0 deletions feed/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import enum

from rest_framework import serializers

from news.serializers import NewsListSerializer
from projects.serializers import ProjectListSerializer
from vacancy.serializers import VacancyDetailSerializer


class FeedItemType(enum.Enum):
PROJECT = "project"
NEWS = "news"
VACANCY = "vacancy"


FEED_SERIALIZER_MAPPING: dict[FeedItemType, serializers.Serializer] = {
FeedItemType.PROJECT.value: ProjectListSerializer,
FeedItemType.NEWS.value: NewsListSerializer,
FeedItemType.VACANCY.value: VacancyDetailSerializer,
}
69 changes: 69 additions & 0 deletions feed/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import random
import typing

from feed import constants
from feed.serializers import FeedItemSerializer
from news.models import News
from projects.models import Project
from vacancy.models import Vacancy


def collect_feed() -> list:
# да, это ужасно
n_random_projects = get_n_random_projects(3)
n_latest_created_projects = get_n_latest_created_projects(3)
n_latest_created_news = get_n_latest_created_news(3)
n_latest_created_vacancies = get_n_latest_created_vacancies(3)

feed = (
to_feed_items(
constants.FeedItemType.PROJECT.value,
set(n_random_projects + n_latest_created_projects),
)
+ to_feed_items(constants.FeedItemType.NEWS.value, n_latest_created_news)
+ to_feed_items(constants.FeedItemType.VACANCY.value, n_latest_created_vacancies)
)

random.shuffle(feed)
return feed


def to_feed_items(type_: constants.FeedItemType, items: typing.Iterable) -> list[dict]:
feed_items = []
for item in items:
serializer = to_feed_item(type_, item)
serializer.is_valid()
feed_items += [serializer.data]
return feed_items


def get_n_random_projects(num: int) -> list[Project]:
tries = 3
projects = set()

while len(projects) < num and tries > 0:
project = Project.objects.filter(draft=False).order_by("?").first()

if project not in projects:
projects.add(project)
else:
tries -= 1
return list(projects)


def get_n_latest_created_projects(num: int) -> list[Project]:
return list(Project.objects.filter(draft=False).order_by("-datetime_created")[:num])


def get_n_latest_created_news(num: int) -> list[Project]:
return list(News.objects.order_by("-datetime_created")[:num])


def get_n_latest_created_vacancies(num: int) -> list[Project]:
return list(Vacancy.objects.order_by("-datetime_created")[:num])


def to_feed_item(type_: constants.FeedItemType, data):
serializer = constants.FEED_SERIALIZER_MAPPING[type_](data)

return FeedItemSerializer(data={"type": type_, "content": serializer.data})
Empty file added feed/migrations/__init__.py
Empty file.
7 changes: 7 additions & 0 deletions feed/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from rest_framework import serializers
from feed import constants


class FeedItemSerializer(serializers.Serializer):
type = serializers.ChoiceField(choices=constants.FeedItemType, required=True)
content = serializers.JSONField(required=True)
9 changes: 9 additions & 0 deletions feed/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from django.urls import path

from feed.views import FeedList

app_name = "feed"

urlpatterns = [
path("", FeedList.as_view()),
]
31 changes: 31 additions & 0 deletions feed/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework import status
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView

from feed.helpers import collect_feed


class FeedList(APIView):
@swagger_auto_schema(
responses={
200: openapi.Response(
description="List of some news: new projects, vacancies, project, users and program news",
schema=openapi.Schema(
type=openapi.TYPE_ARRAY,
items=openapi.Schema(
type=openapi.TYPE_OBJECT,
description="Feed item",
properties={
"type": openapi.TYPE_STRING,
"content": openapi.TYPE_OBJECT,
},
),
),
)
}
)
def get(self, request: Request, *args, **kwargs) -> Response:
return Response(status=status.HTTP_200_OK, data=collect_feed())
3 changes: 2 additions & 1 deletion procollab/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,14 @@
"events.apps.EventsConfig",
"partner_programs.apps.PartnerProgramsConfig",
"mailing.apps.MailingConfig",
"feed.apps.FeedConfig",
"project_rates.apps.ProjectRatesConfig",
# Rest framework
"rest_framework",
"rest_framework_simplejwt",
"rest_framework_simplejwt.token_blacklist",
"django_cleanup.apps.CleanupConfig",
"django_rest_passwordreset",
# "rest_framework.authtoken",
# Plugins
"corsheaders",
"django_filters",
Expand Down
2 changes: 1 addition & 1 deletion procollab/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,12 @@
path("chats/", include("chats.urls", namespace="chats")),
path("events/", include("events.urls", namespace="events")),
path("programs/", include("partner_programs.urls", namespace="partner_programs")),
path("feed/", include("feed.urls", namespace="feed")),
path("api/token/", TokenObtainPairView.as_view(), name="token_obtain_pair"),
path("api/token/refresh/", TokenRefreshView.as_view(), name="token_refresh"),
path("api/token/verify/", TokenVerifyView.as_view(), name="token_verify"),
path("", include("metrics.urls", namespace="metrics")),
path("django_prometheus/", include("django_prometheus.urls")),
path("mailing_test/", include("mailing.urls")), # todo Убрать
]

if settings.DEBUG:
Expand Down
Empty file added project_rates/__init__.py
Empty file.
7 changes: 7 additions & 0 deletions project_rates/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from django.apps import AppConfig


class ProjectRatesConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "project_rates"
verbose_name = "Оценка проектов"
6 changes: 6 additions & 0 deletions project_rates/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
VERBOSE_TYPES = (
("str", "Текст"),
("int", "Целочисленное число"),
("float", "Число с плавающей точкой"),
("bool", "Да или нет"),
)
136 changes: 136 additions & 0 deletions project_rates/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# Generated by Django 4.2.3 on 2024-02-10 10:49

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

initial = True

dependencies = [
("partner_programs", "0004_auto_20231230_0002"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("projects", "0021_project_subscribers"),
]

operations = [
migrations.CreateModel(
name="Criteria",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=50, verbose_name="Название")),
(
"description",
models.TextField(blank=True, null=True, verbose_name="Описание"),
),
(
"type",
models.CharField(
choices=[
("str", "Текст"),
("int", "Целочисленное число"),
("float", "Число с плавающей точкой"),
("bool", "Да или нет"),
],
max_length=8,
verbose_name="Тип",
),
),
(
"min_value",
models.FloatField(
blank=True,
help_text="(если есть)",
null=True,
verbose_name="Минимально допустимое числовое значение",
),
),
(
"max_value",
models.FloatField(
blank=True,
help_text="(если есть)",
null=True,
verbose_name="Максимально допустимое числовое значение",
),
),
(
"partner_program",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="criterias",
to="partner_programs.partnerprogram",
),
),
],
options={
"verbose_name": "Критерий оценки проекта",
"verbose_name_plural": "Критерии оценки проектов",
},
),
migrations.CreateModel(
name="ProjectScore",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"value",
models.CharField(
blank=True, max_length=50, null=True, verbose_name="Значение"
),
),
(
"comment",
models.CharField(
blank=True, max_length=100, null=True, verbose_name="Комментарий"
),
),
(
"criteria",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="scores",
to="project_rates.criteria",
),
),
(
"project",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="scores",
to="projects.project",
),
),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="scores",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"verbose_name": "Оценка проекта",
"verbose_name_plural": "Оценки проектов",
"unique_together": {("criteria", "user", "project")},
},
),
]
Empty file.
Loading