From 203d9854636cbf31531945811ade42ecb6f70270 Mon Sep 17 00:00:00 2001 From: irfanuddinahmad Date: Thu, 26 Sep 2024 19:31:27 +0500 Subject: [PATCH] feat: Added job app and models --- .annotation_safe_list.yml | 6 + .../apps/api_client/constants.py | 1 + .../apps/api_client/discovery.py | 64 +++++++++ enterprise_catalog/apps/jobs/__init__.py | 3 + enterprise_catalog/apps/jobs/admin.py | 34 +++++ enterprise_catalog/apps/jobs/apps.py | 9 ++ .../apps/jobs/management/__init__.py | 0 .../apps/jobs/management/commands/__init__.py | 0 .../management/commands/fetch_jobs_skills.py | 65 +++++++++ .../commands/process_enterprise_jobs.py | 71 +++++++++ .../apps/jobs/migrations/0001_initial.py | 136 ++++++++++++++++++ .../apps/jobs/migrations/__init__.py | 0 enterprise_catalog/apps/jobs/models.py | 110 ++++++++++++++ .../apps/jobs/tests/__init__.py | 0 .../apps/jobs/tests/factories.py | 39 +++++ enterprise_catalog/settings/base.py | 1 + 16 files changed, 539 insertions(+) create mode 100644 enterprise_catalog/apps/jobs/__init__.py create mode 100644 enterprise_catalog/apps/jobs/admin.py create mode 100644 enterprise_catalog/apps/jobs/apps.py create mode 100644 enterprise_catalog/apps/jobs/management/__init__.py create mode 100644 enterprise_catalog/apps/jobs/management/commands/__init__.py create mode 100644 enterprise_catalog/apps/jobs/management/commands/fetch_jobs_skills.py create mode 100644 enterprise_catalog/apps/jobs/management/commands/process_enterprise_jobs.py create mode 100644 enterprise_catalog/apps/jobs/migrations/0001_initial.py create mode 100644 enterprise_catalog/apps/jobs/migrations/__init__.py create mode 100644 enterprise_catalog/apps/jobs/models.py create mode 100644 enterprise_catalog/apps/jobs/tests/__init__.py create mode 100644 enterprise_catalog/apps/jobs/tests/factories.py diff --git a/.annotation_safe_list.yml b/.annotation_safe_list.yml index 2a09ce7cb..1b1e62400 100644 --- a/.annotation_safe_list.yml +++ b/.annotation_safe_list.yml @@ -63,3 +63,9 @@ video_catalog.HistoricalVideoTranscriptSummary: ".. no_pii:": "This model has no PII" video_catalog.HistoricalVideoSkill: ".. no_pii:": "This model has no PII" +jobs.HistoricalJob: + ".. no_pii:": "This model has no PII" +jobs.HistoricalJobEnterprise: + ".. no_pii:": "This model has no PII" +jobs.HistoricalJobSkill: + ".. no_pii:": "This model has no PII" diff --git a/enterprise_catalog/apps/api_client/constants.py b/enterprise_catalog/apps/api_client/constants.py index 563eaa56c..fca560c2b 100644 --- a/enterprise_catalog/apps/api_client/constants.py +++ b/enterprise_catalog/apps/api_client/constants.py @@ -12,6 +12,7 @@ DISCOVERY_PROGRAMS_ENDPOINT = urljoin(settings.DISCOVERY_SERVICE_API_URL, 'programs/') DISCOVERY_COURSE_REVIEWS_ENDPOINT = urljoin(settings.DISCOVERY_SERVICE_API_URL, 'course_review/') DISCOVERY_VIDEO_SKILLS_ENDPOINT = urljoin(settings.DISCOVERY_SERVICE_URL, 'taxonomy/api/v1/xblocks/') +DISCOVERY_JOBS_SKILLS_ENDPOINT = urljoin(settings.DISCOVERY_SERVICE_URL, 'taxonomy/api/v1/jobs/') DISCOVERY_OFFSET_SIZE = 200 DISCOVERY_CATALOG_QUERY_CACHE_KEY_TPL = 'catalog_query:{id}' DISCOVERY_AVERAGE_COURSE_REVIEW_CACHE_KEY = 'average_course_review' diff --git a/enterprise_catalog/apps/api_client/discovery.py b/enterprise_catalog/apps/api_client/discovery.py index 658e62ea8..e7d4a4334 100644 --- a/enterprise_catalog/apps/api_client/discovery.py +++ b/enterprise_catalog/apps/api_client/discovery.py @@ -21,6 +21,7 @@ from .constants import ( DISCOVERY_COURSE_REVIEWS_ENDPOINT, DISCOVERY_COURSES_ENDPOINT, + DISCOVERY_JOBS_SKILLS_ENDPOINT, DISCOVERY_OFFSET_SIZE, DISCOVERY_PROGRAMS_ENDPOINT, DISCOVERY_SEARCH_ALL_ENDPOINT, @@ -287,6 +288,69 @@ def get_video_skills(self, video_usage_key): return video_skills + def _retrieve_jobs_skills(self, request_params): + """ + Makes a request to discovery's taxonomy/api/v1/jobs paginated endpoint + """ + page = request_params.get('page', 1) + LOGGER.info(f'Retrieving video skills from course-discovery for page {page}...') + attempts = 0 + while True: + attempts = attempts + 1 + successful = True + exception = None + try: + response = self.client.get( + DISCOVERY_JOBS_SKILLS_ENDPOINT, + params=request_params, + timeout=self.HTTP_TIMEOUT, + ) + successful = response.status_code < 400 + elapsed_seconds = response.elapsed.total_seconds() + LOGGER.info( + f'Retrieved jobs skills results from course-discovery for page {page} in ' + f'retrieve_jobs_skills_seconds={elapsed_seconds} seconds.' + ) + except requests.exceptions.RequestException as err: + exception = err + LOGGER.exception(f'Error while retrieving jobs skills results from course-discovery for page {page}') + successful = False + if attempts <= self.MAX_RETRIES and not successful: + sleep_seconds = self._calculate_backoff(attempts) + LOGGER.warning( + f'failed request detected from {DISCOVERY_JOBS_SKILLS_ENDPOINT}, ' + 'backing-off before retrying, ' + f'sleeping {sleep_seconds} seconds...' + ) + time.sleep(sleep_seconds) + else: + if exception: + raise exception + break + try: + return response.json() + except requests.exceptions.JSONDecodeError as err: + LOGGER.exception( + f'Invalid JSON while retrieving jobs skills results from course-discovery for page {page}, ' + f'resonse status code: {response.status_code}, ' + f'response body: {response.text}' + ) + raise err + + def get_jobs_skills(self, page=1): + """ + Return results from the discovery service's taxonomy/api/v1/jobs endpoint + """ + results = [] + request_params = {'page': page} + try: + response = self._retrieve_jobs_skills(request_params) + results = response.get('results', []) + return results, response.get('next') + except Exception as exc: + LOGGER.exception(f'Could not retrieve jobs and skills from course-discovery (page {page}) {exc}') + raise exc + def get_metadata_by_query(self, catalog_query, extra_query_params=None): """ Return results from the discovery service's search/all endpoint. diff --git a/enterprise_catalog/apps/jobs/__init__.py b/enterprise_catalog/apps/jobs/__init__.py new file mode 100644 index 000000000..a156c411b --- /dev/null +++ b/enterprise_catalog/apps/jobs/__init__.py @@ -0,0 +1,3 @@ +""" +Job app - implementation for enterprise-job relationship. +""" diff --git a/enterprise_catalog/apps/jobs/admin.py b/enterprise_catalog/apps/jobs/admin.py new file mode 100644 index 000000000..7d5f1d2ab --- /dev/null +++ b/enterprise_catalog/apps/jobs/admin.py @@ -0,0 +1,34 @@ +""" +Admin for jobs models. +""" +from django.contrib import admin +from simple_history.admin import SimpleHistoryAdmin + +from enterprise_catalog.apps.jobs.models import Job, JobEnterprise, JobSkill + + +@admin.register(Job) +class JobAdmin(admin.ModelAdmin): + """ + Django admin for Jobs. + """ + list_display = ('job_id', 'external_id', 'title', 'description', ) + search_fields = ('job_id', 'title', 'description', ) + + +@admin.register(JobEnterprise) +class JobEnterpriseAdmin(SimpleHistoryAdmin): + """ + Django admin for Enterprise Jobs. + """ + list_display = ('enterprise_uuid', 'created', 'modified', ) + search_fields = ('enterprise_uuid', ) + + +@admin.register(JobSkill) +class JobSkillAdmin(SimpleHistoryAdmin): + """ + Django admin for Job Skills. + """ + list_display = ('skill_id', 'name', 'significance', 'created', 'modified',) + search_fields = ('name', ) diff --git a/enterprise_catalog/apps/jobs/apps.py b/enterprise_catalog/apps/jobs/apps.py new file mode 100644 index 000000000..981efdd4c --- /dev/null +++ b/enterprise_catalog/apps/jobs/apps.py @@ -0,0 +1,9 @@ +""" +Job app - implementation for enterprise-job relationship. +""" +from django.apps import AppConfig + + +class JobConfig(AppConfig): + name = 'jobs' + default = False diff --git a/enterprise_catalog/apps/jobs/management/__init__.py b/enterprise_catalog/apps/jobs/management/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/enterprise_catalog/apps/jobs/management/commands/__init__.py b/enterprise_catalog/apps/jobs/management/commands/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/enterprise_catalog/apps/jobs/management/commands/fetch_jobs_skills.py b/enterprise_catalog/apps/jobs/management/commands/fetch_jobs_skills.py new file mode 100644 index 000000000..feffac420 --- /dev/null +++ b/enterprise_catalog/apps/jobs/management/commands/fetch_jobs_skills.py @@ -0,0 +1,65 @@ +""" +Management command for fetching jobs skills from taxonomy connector +""" +import logging + +from django.core.management.base import BaseCommand + +from enterprise_catalog.apps.api_client.discovery import DiscoveryApiClient +from enterprise_catalog.apps.jobs.models import Job, JobSkill + + +logger = logging.getLogger(__name__) + + +class Command(BaseCommand): + """ + Management command for fetching job skills from taxonomy connector + + Example Usage: + >> python manage.py fetch_jobs_skills + """ + help = ( + 'Fetch the skills associated with jobs from taxonomy connector' + ) + + def _process_job_skills(self, results): + """ + Process the job skills fetched from taxonomy connector. + """ + for result in results: + try: + job, _ = Job.objects.update_or_create( + job_id=result.get('id'), + title=result.get('name'), + description=result.get('description'), + external_id=result.get('external_id') + ) + job_skills = result.get('skills') + for item in job_skills: + skill = item.get('skill') + JobSkill.objects.update_or_create( + job=job, + skill_id=skill.get('id'), + name=skill.get('name'), + significance=item.get('significance') + ) + except Exception as exc: # pylint: disable=broad-exception-caught + job_id = result.get('id') + logger.exception(f'Could not store job skills. job id {job_id} {exc}') + + def handle(self, *args, **options): + """ + Fetch the skills associated with jobs from taxonomy connector. + """ + page = 1 + try: + results, has_next = DiscoveryApiClient().get_jobs_skills(page=page) + self._process_job_skills(results) + + while has_next: + page += 1 + results, has_next = DiscoveryApiClient().get_jobs_skills(page=page) + self._process_job_skills(results) + except Exception as exc: # pylint: disable=broad-exception-caught + logger.exception(f'Could not retrieve job skills for page {page} {exc}') diff --git a/enterprise_catalog/apps/jobs/management/commands/process_enterprise_jobs.py b/enterprise_catalog/apps/jobs/management/commands/process_enterprise_jobs.py new file mode 100644 index 000000000..cc9a4f28f --- /dev/null +++ b/enterprise_catalog/apps/jobs/management/commands/process_enterprise_jobs.py @@ -0,0 +1,71 @@ +""" +Management command for associating jobs with enterprises +""" +import logging + +from django.core.management.base import BaseCommand + +from enterprise_catalog.apps.catalog.algolia_utils import ( + get_initialized_algolia_client, +) +from enterprise_catalog.apps.catalog.models import EnterpriseCatalog +from enterprise_catalog.apps.jobs.models import Job, JobEnterprise, JobSkill + + +logger = logging.getLogger(__name__) + + +class Command(BaseCommand): + """ + Management command for associating jobs with enterprises via common skills + + Example Usage: + >> python manage.py process_enterprise_jobs + """ + help = ( + 'Associate jobs with enterprises via common skills' + ) + + def handle(self, *args, **options): + """ + Associate jobs with enterprises via common skills + """ + logger.info("Generating enterprise job association...") + algolia_client = get_initialized_algolia_client() + enterprise_uuids = set() + enterprise_catalogs = EnterpriseCatalog.objects.all() + for enterprise_catalog in enterprise_catalogs: + enterprise_uuids.add(enterprise_catalog.enterprise_uuid) + + jobs = Job.objects.all() + associated_jobs = set() + for enterprise_uuid in enterprise_uuids: + for job in jobs: + try: + job_skills = JobSkill.objects.filter(job=job).order_by('-significance')[:3] # Get top 3 skills + search_query = { + 'filters': f'(skill_names:{job_skills[0].name} OR \ + skill_names:{job_skills[1].name} OR \ + skill_names:{job_skills[2]}.name) AND \ + enterprise_customer_uuids:{enterprise_uuid}', + 'maxFacetHits': 50 + } + response = algolia_client.algolia_index.search_for_facet_values('skill_names', '', search_query) + for hit in response.get('facetHits', []): + if hit.get('count') > 1: + JobEnterprise.objects.update_or_create( + job=job, + enterprise_uuid=enterprise_uuid + ) + associated_jobs.add(job.job_id) + break + except Exception: # pylint: disable=broad-exception-caught + logger.error( + '[PROCESS_ENTERPRISE_JOBS] Failure in processing \ + enterprise "%s" and job: "%s".', + enterprise_uuid, + job.job_id, + exc_info=True + ) + else: + JobEnterprise.objects.all().exclude(job_id__in=associated_jobs).delete() diff --git a/enterprise_catalog/apps/jobs/migrations/0001_initial.py b/enterprise_catalog/apps/jobs/migrations/0001_initial.py new file mode 100644 index 000000000..a607de312 --- /dev/null +++ b/enterprise_catalog/apps/jobs/migrations/0001_initial.py @@ -0,0 +1,136 @@ +# Generated by Django 4.2.13 on 2024-10-09 07:32 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone +import model_utils.fields +import simple_history.models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Job', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('job_id', models.IntegerField(help_text='The job ID received from API.', unique=True)), + ('external_id', models.CharField(help_text='The external identifier for the job received from API.', max_length=255, unique=True)), + ('title', models.CharField(help_text='Job title', max_length=255)), + ('description', models.TextField(help_text='Job description.')), + ], + options={ + 'verbose_name': 'Job', + 'verbose_name_plural': 'Jobs', + }, + ), + migrations.CreateModel( + name='JobSkill', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('skill_id', models.CharField(help_text='Skill id', max_length=255)), + ('name', models.CharField(help_text='Skill name', max_length=255)), + ('significance', models.FloatField(help_text='The significance of skill for the job.')), + ('job', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='skills', to='jobs.job')), + ], + options={ + 'verbose_name': 'Job Skill', + 'verbose_name_plural': 'Job Skills', + }, + ), + migrations.CreateModel( + name='JobEnterprise', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('enterprise_uuid', models.UUIDField()), + ('job', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='enterprises', to='jobs.job')), + ], + options={ + 'verbose_name': 'Job Enterprise', + 'verbose_name_plural': 'Job Enterprises', + }, + ), + migrations.CreateModel( + name='HistoricalJobSkill', + fields=[ + ('id', models.IntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('skill_id', models.CharField(help_text='Skill id', max_length=255)), + ('name', models.CharField(help_text='Skill name', max_length=255)), + ('significance', models.FloatField(help_text='The significance of skill for the job.')), + ('history_id', models.AutoField(primary_key=True, serialize=False)), + ('history_date', models.DateTimeField(db_index=True)), + ('history_change_reason', models.CharField(max_length=100, null=True)), + ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), + ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), + ('job', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='jobs.job')), + ], + options={ + 'verbose_name': 'historical Job Skill', + 'verbose_name_plural': 'historical Job Skills', + 'ordering': ('-history_date', '-history_id'), + 'get_latest_by': ('history_date', 'history_id'), + }, + bases=(simple_history.models.HistoricalChanges, models.Model), + ), + migrations.CreateModel( + name='HistoricalJobEnterprise', + fields=[ + ('id', models.IntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('enterprise_uuid', models.UUIDField()), + ('history_id', models.AutoField(primary_key=True, serialize=False)), + ('history_date', models.DateTimeField(db_index=True)), + ('history_change_reason', models.CharField(max_length=100, null=True)), + ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), + ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), + ('job', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='jobs.job')), + ], + options={ + 'verbose_name': 'historical Job Enterprise', + 'verbose_name_plural': 'historical Job Enterprises', + 'ordering': ('-history_date', '-history_id'), + 'get_latest_by': ('history_date', 'history_id'), + }, + bases=(simple_history.models.HistoricalChanges, models.Model), + ), + migrations.CreateModel( + name='HistoricalJob', + fields=[ + ('id', models.IntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('job_id', models.IntegerField(db_index=True, help_text='The job ID received from API.')), + ('external_id', models.CharField(db_index=True, help_text='The external identifier for the job received from API.', max_length=255)), + ('title', models.CharField(help_text='Job title', max_length=255)), + ('description', models.TextField(help_text='Job description.')), + ('history_id', models.AutoField(primary_key=True, serialize=False)), + ('history_date', models.DateTimeField(db_index=True)), + ('history_change_reason', models.CharField(max_length=100, null=True)), + ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), + ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'historical Job', + 'verbose_name_plural': 'historical Jobs', + 'ordering': ('-history_date', '-history_id'), + 'get_latest_by': ('history_date', 'history_id'), + }, + bases=(simple_history.models.HistoricalChanges, models.Model), + ), + ] diff --git a/enterprise_catalog/apps/jobs/migrations/__init__.py b/enterprise_catalog/apps/jobs/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/enterprise_catalog/apps/jobs/models.py b/enterprise_catalog/apps/jobs/models.py new file mode 100644 index 000000000..9b318ce07 --- /dev/null +++ b/enterprise_catalog/apps/jobs/models.py @@ -0,0 +1,110 @@ +""" +Models for job app. +""" +from django.db import models +from django.utils.translation import gettext as _ +from model_utils.models import TimeStampedModel +from simple_history.models import HistoricalRecords + + +class Job(TimeStampedModel): + """ + Model for storing job related information. + + .. no_pii: + """ + job_id = models.IntegerField(unique=True, help_text=_('The job ID received from API.')) + external_id = models.CharField( + max_length=255, + unique=True, + help_text=_( + 'The external identifier for the job received from API.' + ) + ) + title = models.CharField(max_length=255, help_text=_('Job title')) + description = models.TextField(help_text=_('Job description.')) + + history = HistoricalRecords() + + class Meta: + verbose_name = _('Job') + verbose_name_plural = _('Jobs') + app_label = 'jobs' + + def __str__(self): + """ + Return human-readable string representation. + """ + + return f'' + + +class JobSkill(TimeStampedModel): + """ + Stores the skills associated with a Job. + + .. no_pii: + """ + job = models.ForeignKey( + Job, + related_name='skills', + on_delete=models.CASCADE, + ) + skill_id = models.CharField(max_length=255, help_text=_('Skill id')) + name = models.CharField(max_length=255, help_text=_('Skill name')) + significance = models.FloatField( + blank=False, + help_text=_( + 'The significance of skill for the job.' + ) + ) + + history = HistoricalRecords() + + class Meta: + verbose_name = _("Job Skill") + verbose_name_plural = _("Job Skills") + app_label = 'jobs' + + def __str__(self): + """ + Return human-readable string representation. + """ + return ( + "".format( + job_id=str(self.job), + skill_id=self.skill_id + ) + ) + + +class JobEnterprise(TimeStampedModel): + """ + Stores the enterprises associated with a Job. + + .. no_pii: + """ + job = models.ForeignKey( + Job, + related_name='enterprises', + on_delete=models.CASCADE, + ) + enterprise_uuid = models.UUIDField() + + history = HistoricalRecords() + + class Meta: + verbose_name = _("Job Enterprise") + verbose_name_plural = _("Job Enterprises") + app_label = 'jobs' + + def __str__(self): + """ + Return human-readable string representation. + """ + return ( + "".format( + job_id=str(self.job), + enterprise_id=str(self.enterprise_uuid) + ) + ) diff --git a/enterprise_catalog/apps/jobs/tests/__init__.py b/enterprise_catalog/apps/jobs/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/enterprise_catalog/apps/jobs/tests/factories.py b/enterprise_catalog/apps/jobs/tests/factories.py new file mode 100644 index 000000000..3a4129ab6 --- /dev/null +++ b/enterprise_catalog/apps/jobs/tests/factories.py @@ -0,0 +1,39 @@ +from uuid import uuid4 + +import factory +from factory.fuzzy import FuzzyInteger, FuzzyText + +from enterprise_catalog.apps.jobs.models import Job, JobEnterprise, JobSkill + + +class JobFactory(factory.django.DjangoModelFactory): + """ + Test factory for the `Job` model + """ + class Meta: + model = Job + + title = FuzzyText(length=32) + description = FuzzyText(length=255) + + +class JobEnterpriseFactory(factory.django.DjangoModelFactory): + """ + Test factory for the `JobEnterprise` model + """ + class Meta: + model = JobEnterprise + + enterprise_uuid = factory.LazyFunction(uuid4) + + +class JobSkillFactory(factory.django.DjangoModelFactory): + """ + Test factory for the `JobSkill` model + """ + class Meta: + model = JobSkill + + skill_id = FuzzyText(length=32) + name = FuzzyText(length=32) + significance = FuzzyInteger(0, 100) diff --git a/enterprise_catalog/settings/base.py b/enterprise_catalog/settings/base.py index 6da799cb7..119758118 100644 --- a/enterprise_catalog/settings/base.py +++ b/enterprise_catalog/settings/base.py @@ -70,6 +70,7 @@ 'enterprise_catalog.apps.academy', 'enterprise_catalog.apps.ai_curation', 'enterprise_catalog.apps.video_catalog', + 'enterprise_catalog.apps.jobs', ) INSTALLED_APPS += THIRD_PARTY_APPS