diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index 1741cb2..c2c75e8 100644 Binary files a/core/__pycache__/admin.cpython-311.pyc and b/core/__pycache__/admin.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 1999a5f..5c1f0c4 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/management/__init__.py b/core/management/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/core/management/__init__.py @@ -0,0 +1 @@ + diff --git a/core/management/__pycache__/__init__.cpython-311.pyc b/core/management/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..7a3b8c3 Binary files /dev/null and b/core/management/__pycache__/__init__.cpython-311.pyc differ diff --git a/core/management/commands/__init__.py b/core/management/commands/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/core/management/commands/__init__.py @@ -0,0 +1 @@ + diff --git a/core/management/commands/__pycache__/__init__.cpython-311.pyc b/core/management/commands/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..22b56f7 Binary files /dev/null and b/core/management/commands/__pycache__/__init__.cpython-311.pyc differ diff --git a/core/management/commands/__pycache__/seed_demo_data.cpython-311.pyc b/core/management/commands/__pycache__/seed_demo_data.cpython-311.pyc new file mode 100644 index 0000000..dad4f27 Binary files /dev/null and b/core/management/commands/__pycache__/seed_demo_data.cpython-311.pyc differ diff --git a/core/management/commands/seed_demo_data.py b/core/management/commands/seed_demo_data.py new file mode 100644 index 0000000..ddc0a6d --- /dev/null +++ b/core/management/commands/seed_demo_data.py @@ -0,0 +1,212 @@ +from datetime import timedelta + +from django.core.management.base import BaseCommand +from django.utils import timezone + +from core.models import JobPosting, JobSource + + +class Command(BaseCommand): + help = "Seed realistic demo sources and job postings for local MVP reviews." + + def add_arguments(self, parser): + parser.add_argument( + "--reset", + action="store_true", + help="Delete existing JobSource and JobPosting records before seeding.", + ) + + def handle(self, *args, **options): + if options["reset"]: + JobPosting.objects.all().delete() + JobSource.objects.all().delete() + self.stdout.write(self.style.WARNING("Existing sources and jobs deleted.")) + + today = timezone.localdate() + now = timezone.now() + + sources_payload = [ + { + "name": "France Travail – Dijon", + "family": JobSource.Family.PORTAL, + "url": "https://candidat.francetravail.fr/offres/recherche?lieux=21D", + "status": JobSource.Status.ACTIVE, + "owner": "Ops Team", + "notes": "Primary public feed for Dijon area listings.", + "last_checked_at": now - timedelta(minutes=22), + }, + { + "name": "Apec Bourgogne-Franche-Comté", + "family": JobSource.Family.PORTAL, + "url": "https://www.apec.fr/candidat/recherche-emploi.html", + "status": JobSource.Status.ACTIVE, + "owner": "Ops Team", + "notes": "Executive and white-collar roles.", + "last_checked_at": now - timedelta(hours=2), + }, + { + "name": "Adecco Dijon Tertiaire", + "family": JobSource.Family.AGENCY, + "url": "https://www.adecco.fr/offres-emploi/?k=dijon", + "status": JobSource.Status.PAUSED, + "owner": "Data Partner", + "notes": "Paused while waiting on extraction rule update.", + "last_checked_at": now - timedelta(days=2), + }, + { + "name": "Urgo Group Careers", + "family": JobSource.Family.COMPANY, + "url": "https://careers.urgo-group.com/", + "status": JobSource.Status.ACTIVE, + "owner": "Ops Team", + "notes": "Direct company careers feed.", + "last_checked_at": now - timedelta(minutes=55), + }, + { + "name": "SEB Selongey Careers", + "family": JobSource.Family.COMPANY, + "url": "https://www.groupe-seb.com/fr/carrieres", + "status": JobSource.Status.ERROR, + "owner": "Connector Squad", + "notes": "Blocked by anti-bot response, requires parser fallback.", + "last_checked_at": now - timedelta(days=5), + }, + ] + + source_map = {} + for payload in sources_payload: + source, _ = JobSource.objects.update_or_create(url=payload["url"], defaults=payload) + source_map[source.name] = source + + jobs_payload = [ + { + "source": "France Travail – Dijon", + "title": "Développeur Python Django (H/F)", + "company": "Noveo Digital", + "location": "Dijon", + "contract_type": JobPosting.ContractType.CDI, + "remote": True, + "salary": "38k€–45k€", + "apply_url": "https://example.com/jobs/python-django-dijon", + "published_at": today - timedelta(days=1), + "description": "Concevoir des APIs Django et maintenir un back-office métier orienté data.", + "is_active": True, + "duplicate_score": 4.10, + }, + { + "source": "France Travail – Dijon", + "title": "Intégrateur Front-end React", + "company": "Pixel Nordic", + "location": "Dijon", + "contract_type": JobPosting.ContractType.CDD, + "remote": False, + "salary": "34k€", + "apply_url": "https://example.com/jobs/react-integrator", + "published_at": today - timedelta(days=2), + "description": "Intégrer des interfaces performantes et accessibles au sein d'une équipe produit.", + "is_active": True, + "duplicate_score": 1.90, + }, + { + "source": "Apec Bourgogne-Franche-Comté", + "title": "Product Owner CMS", + "company": "Cobalt Studio", + "location": "Dijon", + "contract_type": JobPosting.ContractType.CDI, + "remote": True, + "salary": "45k€–52k€", + "apply_url": "https://example.com/jobs/product-owner-cms", + "published_at": today - timedelta(days=3), + "description": "Piloter la roadmap d'une plateforme CMS B2B et animer les rituels produit.", + "is_active": True, + "duplicate_score": 0.50, + }, + { + "source": "Adecco Dijon Tertiaire", + "title": "Technicien support applicatif", + "company": "Helix Services", + "location": "Chenôve", + "contract_type": JobPosting.ContractType.INTERIM, + "remote": False, + "salary": "13,50€/h", + "apply_url": "https://example.com/jobs/support-applicatif", + "published_at": today - timedelta(days=4), + "description": "Support N1/N2 sur une suite SaaS et suivi d'incidents applicatifs.", + "is_active": True, + "duplicate_score": 6.75, + }, + { + "source": "Urgo Group Careers", + "title": "Data Analyst RH", + "company": "Urgo Group", + "location": "Chenôve", + "contract_type": JobPosting.ContractType.CDI, + "remote": True, + "salary": "40k€", + "apply_url": "https://example.com/jobs/data-analyst-rh", + "published_at": today - timedelta(days=5), + "description": "Structurer les tableaux de bord RH et fiabiliser les flux de reporting.", + "is_active": True, + "duplicate_score": 0.30, + }, + { + "source": "SEB Selongey Careers", + "title": "Ingénieur QA Automatisation", + "company": "Groupe SEB", + "location": "Selongey", + "contract_type": JobPosting.ContractType.CDI, + "remote": False, + "salary": "42k€–48k€", + "apply_url": "https://example.com/jobs/qa-automation", + "published_at": today - timedelta(days=6), + "description": "Automatiser les scénarios de validation et renforcer la non-régression continue.", + "is_active": False, + "duplicate_score": 3.20, + }, + { + "source": "Apec Bourgogne-Franche-Comté", + "title": "Chef de projet digital", + "company": "Mutualité Bourgogne", + "location": "Dijon", + "contract_type": JobPosting.ContractType.CDD, + "remote": False, + "salary": "39k€", + "apply_url": "https://example.com/jobs/chef-projet-digital", + "published_at": today - timedelta(days=7), + "description": "Coordonner la refonte d'outils internes et piloter les prestataires externes.", + "is_active": True, + "duplicate_score": 2.40, + }, + { + "source": "France Travail – Dijon", + "title": "Développeur Full Stack Junior", + "company": "BFC Cloud", + "location": "Dijon", + "contract_type": JobPosting.ContractType.APPRENTICESHIP, + "remote": True, + "salary": "Selon grille alternance", + "apply_url": "https://example.com/jobs/fullstack-junior", + "published_at": today - timedelta(days=8), + "description": "Participer au développement d'un CMS métier avec Django et Vue.", + "is_active": True, + "duplicate_score": 5.60, + }, + ] + + seeded_jobs = 0 + for payload in jobs_payload: + source = source_map[payload.pop("source")] + _, created = JobPosting.objects.update_or_create( + source=source, + title=payload["title"], + company=payload["company"], + defaults=payload, + ) + if created: + seeded_jobs += 1 + + self.stdout.write( + self.style.SUCCESS( + f"Demo seed complete: {len(source_map)} sources available, {JobPosting.objects.count()} total job postings ({seeded_jobs} new)." + ) + ) diff --git a/core/templates/core/job_list.html b/core/templates/core/job_list.html index 7e04596..a8e6f80 100644 --- a/core/templates/core/job_list.html +++ b/core/templates/core/job_list.html @@ -34,6 +34,13 @@ {% for value,label in family_choices %}{% endfor %} +