Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added thread score system #7

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
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
1 change: 1 addition & 0 deletions app/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from fixtures import * # noqa:F403,F401
3 changes: 3 additions & 0 deletions app/fixtures/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from fixtures.candidates import * # noqa: F401, F403
from fixtures.job import * # noqa: F401, F403
from fixtures.messages import * # noqa: F401, F403
16 changes: 16 additions & 0 deletions app/fixtures/candidates.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from typing import Callable

import pytest
from model_bakery import baker

from sandbox.models import Candidate

__all__ = ['candidate_factory']


@pytest.fixture
def candidate_factory() -> Callable[..., Candidate]:
def _candidate(**kwargs) -> Candidate:
return baker.make('sandbox.Candidate', **kwargs)

return _candidate
25 changes: 25 additions & 0 deletions app/fixtures/job.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from typing import Callable

import pytest
from model_bakery import baker

from sandbox.models import Recruiter, JobPosting

__all__ = ['recruiter_factory', 'job_posting_factory']


@pytest.fixture
def recruiter_factory() -> Callable[..., Recruiter]:
def _recruiter(**kwargs) -> Recruiter:
return baker.make('sandbox.Recruiter', **kwargs)

return _recruiter


@pytest.fixture
def job_posting_factory(recruiter_factory) -> Callable[..., JobPosting]:
def _job_posting(**kwargs) -> JobPosting:
kwargs['recruiter'] = kwargs.pop('recruiter', recruiter_factory())
return baker.make('sandbox.JobPosting', **kwargs)

return _job_posting
19 changes: 19 additions & 0 deletions app/fixtures/messages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from typing import Callable

import pytest
from model_bakery import baker

from sandbox.models import MessageThread

__all__ = ['message_thread_factory']


@pytest.fixture
def message_thread_factory(recruiter_factory, job_posting_factory, candidate_factory) -> Callable[..., MessageThread]:
def _message_thread(**kwargs) -> MessageThread:
kwargs['recruiter_factory'] = kwargs.pop('recruiter_factory', recruiter_factory())
kwargs['job_posting'] = kwargs.pop('job_posting', job_posting_factory())
kwargs['candidate_factory'] = kwargs.pop('candidate_factory', candidate_factory())
return baker.make('sandbox.MessageThread', **kwargs)

return _message_thread
3 changes: 3 additions & 0 deletions app/project/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .celery import celery_app # noqa ABS101

__all__ = ('celery_app',)
9 changes: 9 additions & 0 deletions app/project/celery.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import os

from celery import Celery

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project.settings')

celery_app = Celery('project')
celery_app.config_from_object('django.conf:settings', namespace='CELERY')
celery_app.autodiscover_tasks()
36 changes: 26 additions & 10 deletions app/project/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@
from pathlib import Path
import pathlib

from dotenv import load_dotenv

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/

load_dotenv()

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-cm7kmad02=)e($jgj(m=ut$1p(1t25thew6i2=rx1300i^s75b'

Expand All @@ -29,7 +32,6 @@

ALLOWED_HOSTS = ['localhost', '127.0.0.1', '0.0.0.0', '[::1]']


# Application definition

INSTALLED_APPS = [
Expand All @@ -39,8 +41,8 @@
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'sandbox.apps.SandboxConfig',
"django_jinja",
'sandbox.apps.SandboxConfig',
"django_jinja",
]

MIDDLEWARE = [
Expand Down Expand Up @@ -74,14 +76,13 @@
'APP_DIRS': True,
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'OPTIONS': {
"match_extension": (".html"),
"match_extension": ".html",
},
},
]

WSGI_APPLICATION = 'project.wsgi.application'


# Database
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases

Expand All @@ -91,12 +92,11 @@
'NAME': os.getenv('POSTGRES_DB'),
'USER': os.getenv('POSTGRES_USER'),
'PASSWORD': os.getenv('POSTGRES_PASSWORD'),
'HOST': 'db', # Use the service name from docker-compose.yml
'HOST': os.getenv('POSTGRES_HOST'), # Use the service name from docker-compose.yml
'PORT': '5432',
}
}


# Password validation
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators

Expand All @@ -115,7 +115,6 @@
},
]


# Internationalization
# https://docs.djangoproject.com/en/3.2/topics/i18n/

Expand All @@ -129,7 +128,6 @@

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.2/howto/static-files/
STATIC_URL = '/static/'
Expand All @@ -138,3 +136,21 @@
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

REDIS_URL = os.getenv('REDIS_URL', 'redis://localhost:6379')

CELERY_BROKER_URL = f'{REDIS_URL}/3'
CELERY_RESULT_BACKEND = f'{REDIS_URL}/4'
CELERY_ACCEPT_CONTENT = ['application/json']
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TASK_SERIALIZER = 'json'
CELERY_TIMEZONE = 'Europe/Kyiv'

EUROPEAN_COUNTRIES = {
'ALB', 'AND', 'ARM', 'AUT', 'AZE', 'BLR', 'BEL', 'BIH', 'BGR', 'HRV',
'CYP', 'CZE', 'DNK', 'EST', 'FRO', 'FIN', 'FRA', 'GEO', 'DEU', 'GRC',
'HUN', 'ISL', 'IRL', 'IMN', 'ITA', 'KAZ', 'XKX', 'LVA', 'LIE', 'LTU',
'LUX', 'MLT', 'MDA', 'MCO', 'MNE', 'NLD', 'MKD', 'NOR', 'POL', 'PRT',
'ROU', 'RUS', 'SMR', 'SRB', 'SVK', 'SVN', 'ESP', 'SWE', 'CHE', 'TUR',
'UKR', 'GBR', 'VAT'
}
3 changes: 3 additions & 0 deletions app/pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[pytest]
DJANGO_SETTINGS_MODULE=project.settings
addopts = --no-migrations
3 changes: 3 additions & 0 deletions app/sandbox/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
class SandboxConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'sandbox'

def ready(self):
import sandbox.signals # noqa
Empty file.
Empty file.
20 changes: 20 additions & 0 deletions app/sandbox/management/commands/update_threads.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from django.core.management.base import BaseCommand, CommandError

from sandbox.models import MessageThread
from sandbox.services.threads_service import ThreadsService


class Command(BaseCommand):
help = "Recalculates the score for every thread in the database."

def handle(self, *args, **options):
for instance in MessageThread.objects.all().iterator():
try:
ThreadsService.update_message_thread_scoring(message_thread=instance)
except Exception as e:
raise CommandError(f'Error updating thread {instance.id}: {e}')

count_threads = MessageThread.objects.count()
self.stdout.write(
self.style.SUCCESS(f'Successfully recalculated score for {count_threads} threads.')
)
22 changes: 22 additions & 0 deletions app/sandbox/migrations/0002_auto_20231107_2024.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Generated by Django 3.2.23 on 2023-11-07 20:24

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
('sandbox', '0001_initial'),
]

operations = [
migrations.AddField(
model_name='messagethread',
name='score',
field=models.FloatField(blank=True, db_index=True, default=0.0),
),
migrations.AddField(
model_name='messagethread',
name='score_description',
field=models.JSONField(blank=True, default=dict),
),
]
10 changes: 9 additions & 1 deletion app/sandbox/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
key=lambda c: c[1],
)


class LegacyUACity(models.TextChoices):
"""used in jobs/candidate"""
KYIV = "Київ", _("Kyiv")
Expand All @@ -27,6 +28,7 @@ class LegacyUACity(models.TextChoices):
CHERNIVTSI = "Чернівці", _("Chernivtsi")
UZHHOROD = "Ужгород", _("Uzhhorod")


class EnglishLevel(models.TextChoices):
NONE = ("no_english", "No English")
BASIC = ("basic", "Beginner/Elementary")
Expand All @@ -35,6 +37,7 @@ class EnglishLevel(models.TextChoices):
UPPER = ("upper", "Upper-Intermediate")
FLUENT = ("fluent", "Advanced/Fluent")


class Candidate(models.Model):
USERTYPE = "candidate"

Expand Down Expand Up @@ -135,7 +138,7 @@ class RelocateType(models.TextChoices):
NO_RELOCATE = "no_relocate", _("No relocation")
CANDIDATE_PAID = "candidate_paid", _("Covered by candidate")
COMPANY_PAID = "company_paid", _("Covered by company")

class AcceptRegion(models.TextChoices):
EUROPE = "europe", _("Ukraine + Europe")
EUROPE_ONLY = "europe_only", _("Only Europe")
Expand Down Expand Up @@ -187,6 +190,7 @@ class AcceptRegion(models.TextChoices):
published = models.DateTimeField(blank=True, null=True, db_index=True)
created = models.DateTimeField(auto_now_add=True, db_index=True)


class Action(str, enum.Enum):
MESSAGE = ''

Expand All @@ -209,6 +213,7 @@ class Action(models.TextChoices):
POKE = "poke"
SHADOW_POKE = "shadowpoke"


class Bucket(str, enum.Enum):
"""Bucket is the current state of the message thread"""
ARCHIVE = 'archive'
Expand All @@ -218,6 +223,7 @@ class Bucket(str, enum.Enum):
SHORTLIST = 'shortlist' # TODO: looks deprecated by recruiter_favorite & candidate_favorite
UNREAD = 'unread' # TODO: for recruiter we use INBOX bucket with `last_seen_recruiter`


class Message(models.Model):
class Sender(models.TextChoices):
CANDIDATE = "candidate", _("Candidate")
Expand Down Expand Up @@ -279,6 +285,8 @@ class MatchReason(models.TextChoices):
last_seen_recruiter = models.DateTimeField(null=True)
last_seen_candidate = models.DateTimeField(null=True)
created = models.DateTimeField(auto_now_add=True)
score = models.FloatField(blank=True, default=0.0, db_index=True)
score_description = models.JSONField(blank=True, default=dict)

@property
def last_message(self):
Expand Down
Empty file.
Loading