From d1dda003d6c396bdc8998f5fb1aa183cfec3beaf Mon Sep 17 00:00:00 2001 From: Faheed Date: Mon, 27 Oct 2025 17:43:46 +0300 Subject: [PATCH] small fix for the form template marked active when the job is made active --- .../__pycache__/urls.cpython-312.pyc | Bin 2656 -> 2656 bytes .../linkedin_service.cpython-312.pyc | Bin 17100 -> 12067 bytes .../__pycache__/models.cpython-312.pyc | Bin 60814 -> 60904 bytes .../__pycache__/signals.cpython-312.pyc | Bin 4406 -> 4406 bytes recruitment/__pycache__/utils.cpython-312.pyc | Bin 20414 -> 20414 bytes recruitment/__pycache__/views.cpython-312.pyc | Bin 92181 -> 92506 bytes recruitment/linkedin_service.py | 174 +++++++++--------- ..._jobposting_linkedin_post_formated_data.py | 18 ++ ..._jobposting_linkedin_post_formated_data.py | 18 ++ ..._jobposting_linkedin_post_formated_data.py | 18 ++ recruitment/models.py | 1 + recruitment/signals.py | 11 +- recruitment/tasks.py | 63 +++++-- recruitment/utils.py | 6 +- recruitment/views.py | 9 + templates/jobs/job_detail.html | 5 + 16 files changed, 220 insertions(+), 103 deletions(-) create mode 100644 recruitment/migrations/0003_jobposting_linkedin_post_formated_data.py create mode 100644 recruitment/migrations/0004_alter_jobposting_linkedin_post_formated_data.py create mode 100644 recruitment/migrations/0005_alter_jobposting_linkedin_post_formated_data.py diff --git a/NorahUniversity/__pycache__/urls.cpython-312.pyc b/NorahUniversity/__pycache__/urls.cpython-312.pyc index 3a455bcac85223e3e61d1dac31fcc69ef96e0329..7ad3c1f00585bcc31d862feff56d2e93f71b0b0b 100644 GIT binary patch delta 20 acmaDL@<4?9G%qg~0}wbA{@=(Q$^`&FiUqC! delta 20 acmaDL@<4?9G%qg~0}!aO{oKeM$^`&CGzB97 diff --git a/recruitment/__pycache__/linkedin_service.cpython-312.pyc b/recruitment/__pycache__/linkedin_service.cpython-312.pyc index 19d53fabe8d1e18fec443a727cba79f0d4da1bfc..7875609da8184188ab8e384a0044253f8317cbe9 100644 GIT binary patch delta 2552 zcmZ`)TWnlM89sBayLV$N;xFtdQ8dEBo$4n7L!yfretYRqWUF? z8rS7JXXy=M#Xi8zl10Lp?*xHcUiB?G)V^7(1Wp*QG0!QND%9E3Mu$d*MnZjbWc1L; zR4I}W;T>_(t*vcci|pYS zW0PdC`9|z{xxEh#Uz0=QjAK)W`D}nMoBc~!ymt^`Kf(b73*iBT2N8whLLa> zVFY0mz(;orIKaDKn3H6tdE0Eru?Zks3LEE_(i3Eozm+}$rv4{=@c1Vw5*F-g)1%>lmn+6Jh;43b=bo|V$=MK z*oCc-g~Y)24;se`z>8xvp>iA{h!zf!)}xX`HD8(zhxQF!{{ zrf&d<@L+&o9p?FPF3)>&`^i&$A@_5r12g|`jL&oGQy9 z**IsjGRU(MQKnBIG2yP;tIo=4*K^oWB%~rjDT?ZDC4>VBKd@P_A~endYg8N9Chj;7 z6Qt=c-HYU6bD-xPqAZm({zENS8i2QPMXJkd!29vh&;q4Jt81;!K*`y!freOJqcT;x zC8}-$Q$wpOR69EfCo0t&3Y8lwH5xjN)%neR`7-W&0pPCH?l<2T|^F{5d|^Kb{B z%bO(e!gfwx>p^iddudmzEjG-$$uhJ%=xxcXo2{U|B+;Iil`^4SjU>IVZq}16gN)v5 z>JyT+kN;}))qUhgO^9EpP*PefT&mm7#O&BElBJHh`su85@MU zB^z%;L-NAJ3EQhV3yf75`>rI7N$fkw8MW2uzJvFLIDFVbl7CM_ou2wUS>~6gE=1nQ zye*$=_I{>c(L%+qo#Kl#gF1Fic7oSthO=i;f$~uU`5QBRqavu7LH8dJNMJaRSMW<= zNVPIi-bMePLh1pYob6kD5|?KXzK5`zee@$VR-M2O0tSWztktkED$svfMRcpkGk$LN zfo}Yygfjnsy3t#++k9FdW0>T)Au+w>))5OYF?ac`b2ktAhgletkQt{b@hF4jI+^;el zj-V{o?DA@52zW^)_V^>g*h|KPXC3OD)5ANC%#-`URwB|Xp>=ihG tEJ!}4{6mEh@}6YACuQE1((g*A&}cH@5%) delta 6680 zcmbt2TWlLwcJtzM_>e_XZ(AeFl1$2?WI47RTb8WI^4s!5ek5AQ1jQLiBU7X@Gqf#+ z484zH6(Cv1ncdpiO<-+OqYj*Qm3kL(fV8pE1lvUm?9gIC?_`5@whI(#QNVE)ZPTJi z&zTV=*_-{z4#2~E?|I&P&b{~C^Tw%v`ZLn~k8ZbPkyLS$!jF`CqcE z7Fo&>7!u0VxUGitso%9VvCBu_v8{*EPih4rDAJIF>2o7?II7|$au%7PSpHAWzngrxEPBnVlvS_Atw`WGSVhMq+R{Vgd!xA zwxiQig47K|(l#|v{tMQamND%X_0#fmx#NHe+9YFzo?4 zu)Vr%DmoocMtO6^nt@Zc0Wek!*9_WvKoZ78SrMd&I2j!iBB`l(QoZL3l2O&^{}6&g z^Zy@RhvrK-ISG9UYin4H62Y0DR&6Hx)Ya8fzU?rOX2aYOL6N2*;=__ly;}WL&X~x< z=Ie|iP70DAMGPQ77wC+b7)=^Mp3Qi@X#odb3kjqLLCZH1-0i89jKqEUX3mfg|6j*+u4V{CmwEl8RC4tvY}8E!ZcQ zhzu!{(`1IsQp&;!L8uyCy)i>6Aj146m{M!$|1IYF!sx@C6`L(Q{q>oxyzT2V+j+;= zXLkN6npxiU^)7jwz6@PD@?mtDYb(5PBhVQ}IkL8PD+I znP87jI@6BsxcH4zJx_;vQW#>tJ^wnFwsg10#hx_N-9FWmrku{SwR=Qr@9jz3x{|; z{)LSNI~V4tJ7aMnnuuUqMiX!+nMX>Ei7}X9`Vl!96Ql9SR8m&d&zgKWgYT=bS3!(S zfFI5ipLlEU`U3a7wKL4`twdS*g)`5e(JI>KDsQg&VC3hMwwjn*46dlUbo}D+%X_a;*LKVY z*3UR`Y}Mt7`9N@{+31)AgV%P>?VS(w76SwMz`%T9*NpR?w_3ATOVw)BQtkgwSP|p= zVa6csd7T`Bvq=5S?^o+voMk(R3vf=25i#oNMuNyvsyDETk;xbQ*r>Zmz0kxESvpH) z=w*~O24#DoGPfA}5|yD644z0oB3`3_udc)=FR6vlk5MH2veemStss+bU)T;@EIBzD zZIcB|F%*I4#-sAM0^!VIFeS!tkHq1s3DRjt<8CoGHZDj5eA)(WM5n!t#}Xp!D_K;g z_a;YVom{7rjX@fM5w253dIkg2JjrF;V5R7Eqn-#Vihv@Wh8BbSgTy*?^)mnOI7|na|R|9+Iy?bvTy%~E+zVzJ1=VoJt%Fso6 zu3@f!ZsVL|OTO&!TM)yVckd@&-+}_KZ-)Kiftx5hM1G-r_sV5pzz+j`>zTQ70JrV+ zeVZ8bYI1f%@&nm0$y4g#H5INOLF)S&JqGT92+7laN~%}E%QRzl%K*>3w&-$_KxEKT zI{rvv3_>MCPGDm5$S1AN(CYd?z+weFo`f|Z>@{kM6*L@j`_(U-?QlPwhAg4PLED#5 zf5`LA7C%#>v^YSv_p4<;QwemdOdDt?NQ8m%M=YSF4LBh;yUZ#|{jw#XCMp~dwutn) zmLZ9WfJ(0QQxg@S)Tf@Rv{|!EhLQY8>2qh8M--6wM4+U0M6;eHLD;9gU=*MqHHw(1 zHFeaLbTC;9kb-xt(t5tkY*G{T>ftJ%Zv|godS9UKtTkg%$*O?cm$8<{&3RZ{{ECT2 zknF2n^j{i$wqz9VGVzUzPRiPNzk;co@hL5()zw-U7TnLUL zt`Aa7r6I(f5Pp096-nUY$>eD~(cpFieM%JGdU)VXNy$+#3}m96Np2zu2|+Ri`V}EM z8BSr|asSH2=ebbGXoo@^bZ2xjl&*Dd`(vLo1-Jhn*dl8tqXNYTWYpj#pJ0mV`Zr^`a@ z#UN+GHPpS!UVstse_VPFX&j3;m2Q&+!zsE|Ovn&iI)lNY(_%uAMv&HXpzq1Zp@Tz5 zBmKMkjtuEE#F+Ug7685Q5YkzkB_>u7jk9N{K9Vb+@t8A`9Ylx4EGr=7!SCqGv#CbR;fBz@{-}Tz>Q+h4%X*BPlV?qj5Q< zBr4tb=$*SnwMdi)cKKWI@H$W2cM;xG-T%=XQmE;e`Po4d89Z8r~Vt9x?=_x5}CK+#^8x7TSaHx%rT z-M11|H8ZZeY(iV zvvmbmc&X+L-Tbz=V2^D_Lha#il~v&H(2`TC8;`fd68Z8u}LI_K+mYO8i>wYz8b-*x#f9DM%Z z<-W@qP`Bu(%SUJX|8n<_cVByQe$A%*%1s4(*S)H`D~?wjvu*QL9pHN_QQQ0%vA5Rz zB=~0Vdh2}iCatMUtJ*x{{#|YGt^PUp{JNfeZI5QJ>i-?bg;4%_i-SNVm( z=LfageFfM4yLCY=)T^~_y&287_G)$8Z*9!i?VdSs&s%pnoA<8M?CYdz2uSsB>niPx zSq&gC178C#%7qsrT}O3`j9PWiiL%ZLczL zGvkxoB9b7)`6?xnSv%w*ZbKwo#1ix}cHWLg@GRDh;AKi0GTkul;k_csmw_CI;xOeC z!~|~0(x?80v{A?>hsv7X@p5#y7D7GYir{+97H5XPgt| z>YOX!U6wq;Sinfu;6is!UE)XW|RzI&_ zZINVXtF>G^xmVrCowH(^BOOtH!EIvj270;S$tOM16&(Dd^|U#}F4h1Ifo6@(D03N= zNpxS(ZI+*&MDjlep#F11v(+q2s^sd5mZuP5lqdfhTR#CHTCtA>ns zE}U?_%3^saUmm))b-ui7#&+ZC>g^?m=|7{ca!U^aJ}_K`=_Rn5;rweW|{J+wti0$TkzxYl|cIZmi- zsJ;AkXhPv|nJw!6(3(zTwQ&8#-@}3c!CxUuI|3XHdQGpj;lLII>(tjnYifqEDI$0m z!IJNOrrrzrt1%=E^PvU`Ng0+14aGZDcWbC-17cD7AKYB(;nt1h#EtK^+9(?uJP1|x zAG9@{tU`_k1dkyw3>m=Ik{y@@7}vZRYq*Z4Z684EO~N=ujQWU#`_uqfPnUP8ws0`_ zU_ZIiQuCmPwfG;@^jO+H-$`w?I3HxlfW`TF$7YMi||HT}# MlAZ?yLPO#I1NbJxng9R* diff --git a/recruitment/__pycache__/models.cpython-312.pyc b/recruitment/__pycache__/models.cpython-312.pyc index 5f501401d576ea8ed98e2a7f8e990e91a6309a18..c1360f1b6b31b7882f741961f5f5236148c0f3bd 100644 GIT binary patch delta 5099 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 CX{$H@ delta 4753 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 diff --git a/recruitment/__pycache__/utils.cpython-312.pyc b/recruitment/__pycache__/utils.cpython-312.pyc index 7a31e4973e50e67b7327ae821a8caa349cdb5838..3c1ee409550c0176ae9a28a22dd7bb6a211ad2db 100644 GIT binary patch delta 24 ecmdltpK;%OM&8rByj%=GU?2KFWAaAcjs5^)qX-26 delta 24 ecmdltpK;%OM&8rByj%=G@OJl)jJ}P$8~p)mrwI)J diff --git a/recruitment/__pycache__/views.cpython-312.pyc b/recruitment/__pycache__/views.cpython-312.pyc index 5eb571ddafaa0b3c5096352488049b73781ffb20..f6c5db4f7fb3431fede1d79ca57b443b96c44e6b 100644 GIT binary patch delta 7529 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 SUBnX!rd+J`RM}yW?!N(%)c-30 diff --git a/recruitment/linkedin_service.py b/recruitment/linkedin_service.py index e275f2d..3f645a4 100644 --- a/recruitment/linkedin_service.py +++ b/recruitment/linkedin_service.py @@ -1,19 +1,18 @@ # jobs/linkedin_service.py import uuid -import re -from html import unescape -from urllib.parse import quote, urlencode + import requests import logging import time from django.conf import settings +from urllib.parse import quote, urlencode logger = logging.getLogger(__name__) # Define constants LINKEDIN_API_VERSION = '2.0.0' LINKEDIN_VERSION = '202409' -MAX_POST_CHARS = 3000 # LinkedIn's maximum character limit for shareCommentary + class LinkedInService: def __init__(self): @@ -162,113 +161,114 @@ class LinkedInService: # ---------------- POSTING UTILITIES ---------------- - def clean_html_for_social_post(self, html_content): - """Converts safe HTML to plain text with basic formatting.""" - if not html_content: - return "" + # def clean_html_for_social_post(self, html_content): + # """Converts safe HTML to plain text with basic formatting.""" + # if not html_content: + # return "" - text = html_content + # text = html_content - # 1. Convert Bolding tags to *Markdown* - text = re.sub(r'(.*?)', r'*\1*', text, flags=re.IGNORECASE) - text = re.sub(r'(.*?)', r'*\1*', text, flags=re.IGNORECASE) + # # 1. Convert Bolding tags to *Markdown* + # text = re.sub(r'(.*?)', r'*\1*', text, flags=re.IGNORECASE) + # text = re.sub(r'(.*?)', r'*\1*', text, flags=re.IGNORECASE) - # 2. Handle Lists: Convert
  • tags into a bullet point - text = re.sub(r'', '\n', text, flags=re.IGNORECASE) - text = re.sub(r']*>', 'β€’ ', text, flags=re.IGNORECASE) - text = re.sub(r'
  • ', '\n', text, flags=re.IGNORECASE) + # # 2. Handle Lists: Convert
  • tags into a bullet point + # text = re.sub(r'', '\n', text, flags=re.IGNORECASE) + # text = re.sub(r']*>', 'β€’ ', text, flags=re.IGNORECASE) + # text = re.sub(r'
  • ', '\n', text, flags=re.IGNORECASE) - # 3. Handle Paragraphs and Line Breaks - text = re.sub(r'

    ', '\n\n', text, flags=re.IGNORECASE) - text = re.sub(r'
    ', '\n', text, flags=re.IGNORECASE) + # # 3. Handle Paragraphs and Line Breaks + # text = re.sub(r'

    ', '\n\n', text, flags=re.IGNORECASE) + # text = re.sub(r'
    ', '\n', text, flags=re.IGNORECASE) - # 4. Strip all remaining, unsupported HTML tags - clean_text = re.sub(r'<[^>]+>', '', text) + # # 4. Strip all remaining, unsupported HTML tags + # clean_text = re.sub(r'<[^>]+>', '', text) - # 5. Unescape HTML entities - clean_text = unescape(clean_text) + # # 5. Unescape HTML entities + # clean_text = unescape(clean_text) - # 6. Clean up excessive whitespace/newlines - clean_text = re.sub(r'(\n\s*){3,}', '\n\n', clean_text).strip() + # # 6. Clean up excessive whitespace/newlines + # clean_text = re.sub(r'(\n\s*){3,}', '\n\n', clean_text).strip() - return clean_text + # return clean_text - def hashtags_list(self, hash_tags_str): - """Convert comma-separated hashtags string to list""" - if not hash_tags_str: - return ["#HigherEd", "#Hiring", "#UniversityJobs"] + # def hashtags_list(self, hash_tags_str): + # """Convert comma-separated hashtags string to list""" + # if not hash_tags_str: + # return ["#HigherEd", "#Hiring", "#UniversityJobs"] - tags = [tag.strip() for tag in hash_tags_str.split(',') if tag.strip()] - tags = [tag if tag.startswith('#') else f'#{tag}' for tag in tags] + # tags = [tag.strip() for tag in hash_tags_str.split(',') if tag.strip()] + # tags = [tag if tag.startswith('#') else f'#{tag}' for tag in tags] - if not tags: - return ["#HigherEd", "#Hiring", "#UniversityJobs"] + # if not tags: + # return ["#HigherEd", "#Hiring", "#UniversityJobs"] - return tags + # return tags - def _build_post_message(self, job_posting): - """ - Constructs the final text message. - Includes a unique suffix for duplicate content prevention (422 fix). - """ - message_parts = [ - f"πŸ”₯ *Job Alert!* We’re looking for a talented professional to join our team.", - f"πŸ‘‰ **{job_posting.title}** πŸ‘ˆ", - ] + # def _build_post_message(self, job_posting): + # """ + # Constructs the final text message. + # Includes a unique suffix for duplicate content prevention (422 fix). + # """ + # message_parts = [ + # f"πŸ”₯ *Job Alert!* We’re looking for a talented professional to join our team.", + # f"πŸ‘‰ **{job_posting.title}** πŸ‘ˆ", + # ] - if job_posting.department: - message_parts.append(f"*{job_posting.department}*") + # if job_posting.department: + # message_parts.append(f"*{job_posting.department}*") - message_parts.append("\n" + "=" * 25 + "\n") + # message_parts.append("\n" + "=" * 25 + "\n") - # KEY DETAILS SECTION - details_list = [] - if job_posting.job_type: - details_list.append(f"πŸ’Ό Type: {job_posting.get_job_type_display()}") - if job_posting.get_location_display() != 'Not specified': - details_list.append(f"πŸ“ Location: {job_posting.get_location_display()}") - if job_posting.workplace_type: - details_list.append(f"🏠 Workplace: {job_posting.get_workplace_type_display()}") - if job_posting.salary_range: - details_list.append(f"πŸ’° Salary: {job_posting.salary_range}") + # # KEY DETAILS SECTION + # details_list = [] + # if job_posting.job_type: + # details_list.append(f"πŸ’Ό Type: {job_posting.get_job_type_display()}") + # if job_posting.get_location_display() != 'Not specified': + # details_list.append(f"πŸ“ Location: {job_posting.get_location_display()}") + # if job_posting.workplace_type: + # details_list.append(f"🏠 Workplace: {job_posting.get_workplace_type_display()}") + # if job_posting.salary_range: + # details_list.append(f"πŸ’° Salary: {job_posting.salary_range}") - if details_list: - message_parts.append("*Key Information*:") - message_parts.extend(details_list) - message_parts.append("\n") + # if details_list: + # message_parts.append("*Key Information*:") + # message_parts.extend(details_list) + # message_parts.append("\n") - # DESCRIPTION SECTION - clean_description = self.clean_html_for_social_post(job_posting.description) - if clean_description: - message_parts.append(f"πŸ”Ž *About the Role:*\n{clean_description}") + # # DESCRIPTION SECTION + # clean_description = self.clean_html_for_social_post(job_posting.description) + # if clean_description: + # message_parts.append(f"πŸ”Ž *About the Role:*\n{clean_description}") + # clean_ - # CALL TO ACTION - if job_posting.application_url: - message_parts.append(f"\n\n---") - # CRITICAL: Include the URL explicitly in the text body. - # When media_category is NONE, LinkedIn often makes these URLs clickable. - message_parts.append(f"πŸ”— **APPLY NOW:** {job_posting.application_url}") + # # CALL TO ACTION + # if job_posting.application_url: + # message_parts.append(f"\n\n---") + # # CRITICAL: Include the URL explicitly in the text body. + # # When media_category is NONE, LinkedIn often makes these URLs clickable. + # message_parts.append(f"πŸ”— **APPLY NOW:** {job_posting.application_url}") - # HASHTAGS - hashtags = self.hashtags_list(job_posting.hash_tags) - if job_posting.department: - dept_hashtag = f"#{job_posting.department.replace(' ', '')}" - hashtags.insert(0, dept_hashtag) + # # HASHTAGS + # hashtags = self.hashtags_list(job_posting.hash_tags) + # if job_posting.department: + # dept_hashtag = f"#{job_posting.department.replace(' ', '')}" + # hashtags.insert(0, dept_hashtag) - message_parts.append("\n" + " ".join(hashtags)) + # message_parts.append("\n" + " ".join(hashtags)) - final_message = "\n".join(message_parts) + # final_message = "\n".join(message_parts) - # --- FIX: ADD UNIQUE SUFFIX AND HANDLE LENGTH (422 fix) --- - unique_suffix = f"\n\n| Ref: {int(time.time())}" + # # --- FIX: ADD UNIQUE SUFFIX AND HANDLE LENGTH (422 fix) --- + # unique_suffix = f"\n\n| Ref: {int(time.time())}" - available_length = MAX_POST_CHARS - len(unique_suffix) + # available_length = MAX_POST_CHARS - len(unique_suffix) - if len(final_message) > available_length: - logger.warning("Post message truncated due to character limit.") - final_message = final_message[:available_length - 3] + "..." + # if len(final_message) > available_length: + # logger.warning("Post message truncated due to character limit.") + # final_message = final_message[:available_length - 3] + "..." - return final_message + unique_suffix + # return final_message + unique_suffix # ---------------- MAIN POSTING METHODS ---------------- @@ -279,7 +279,9 @@ class LinkedInService: CRITICAL FIX: Avoids ARTICLE category if not using an image to prevent 402 errors. """ - message = self._build_post_message(job_posting) + message = job_posting.linkedin_post_formated_data + if len(message)>=3000: + message=message[:2900]+"...." # --- FIX FOR 402: Force NONE if no image is present. --- if media_category != "IMAGE": diff --git a/recruitment/migrations/0003_jobposting_linkedin_post_formated_data.py b/recruitment/migrations/0003_jobposting_linkedin_post_formated_data.py new file mode 100644 index 0000000..5e92015 --- /dev/null +++ b/recruitment/migrations/0003_jobposting_linkedin_post_formated_data.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.7 on 2025-10-27 10:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('recruitment', '0002_candidate_retry'), + ] + + operations = [ + migrations.AddField( + model_name='jobposting', + name='linkedin_post_formated_data', + field=models.JSONField(blank=True, null=True), + ), + ] diff --git a/recruitment/migrations/0004_alter_jobposting_linkedin_post_formated_data.py b/recruitment/migrations/0004_alter_jobposting_linkedin_post_formated_data.py new file mode 100644 index 0000000..d821d60 --- /dev/null +++ b/recruitment/migrations/0004_alter_jobposting_linkedin_post_formated_data.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.7 on 2025-10-27 11:29 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('recruitment', '0003_jobposting_linkedin_post_formated_data'), + ] + + operations = [ + migrations.AlterField( + model_name='jobposting', + name='linkedin_post_formated_data', + field=models.CharField(blank=True, null=True), + ), + ] diff --git a/recruitment/migrations/0005_alter_jobposting_linkedin_post_formated_data.py b/recruitment/migrations/0005_alter_jobposting_linkedin_post_formated_data.py new file mode 100644 index 0000000..6bd9476 --- /dev/null +++ b/recruitment/migrations/0005_alter_jobposting_linkedin_post_formated_data.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.7 on 2025-10-27 11:58 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('recruitment', '0004_alter_jobposting_linkedin_post_formated_data'), + ] + + operations = [ + migrations.AlterField( + model_name='jobposting', + name='linkedin_post_formated_data', + field=models.TextField(blank=True, null=True), + ), + ] diff --git a/recruitment/models.py b/recruitment/models.py index 4d545ec..f651e57 100644 --- a/recruitment/models.py +++ b/recruitment/models.py @@ -129,6 +129,7 @@ class JobPosting(Base): max_length=50, blank=True, help_text="Status of LinkedIn posting" ) linkedin_posted_at = models.DateTimeField(null=True, blank=True) + linkedin_post_formated_data=models.TextField(null=True,blank=True) published_at = models.DateTimeField(db_index=True, null=True, blank=True) # Added index # University Specific Fields diff --git a/recruitment/signals.py b/recruitment/signals.py index 5f6b623..590aed2 100644 --- a/recruitment/signals.py +++ b/recruitment/signals.py @@ -45,6 +45,15 @@ def format_job(sender, instance, created, **kwargs): # If the instance is no longer active, delete the scheduled task existing_schedule.delete() +# @receiver(post_save, sender=JobPosting) +# def update_form_template_status(sender, instance, created, **kwargs): +# if not created: +# if instance.status == "Active": +# instance.form_template.is_active = True +# else: +# instance.form_template.is_active = False +# instance.save() + @receiver(post_save, sender=Candidate) def score_candidate_resume(sender, instance, created, **kwargs): if not instance.is_resume_parsed: @@ -351,4 +360,4 @@ def create_default_stages(sender, instance, created, **kwargs): # required=False, # order=3, # is_predefined=True - # ) \ No newline at end of file + # ) diff --git a/recruitment/tasks.py b/recruitment/tasks.py index d975b10..7edffd7 100644 --- a/recruitment/tasks.py +++ b/recruitment/tasks.py @@ -116,25 +116,55 @@ def format_job_description(pk): job_posting = JobPosting.objects.get(pk=pk) print(job_posting) prompt = f""" - Can you please organize and format this unformatted job description and qualifications into clear, readable sections using headings and bullet points? - Format the Content: You need to convert the clear, formatted job description and qualifications into a 2 blocks of HTML code. - **JOB DESCRIPTION:** - {job_posting.description} + You are a dual-purpose AI assistant specializing in content formatting and social media copywriting for job announcements. - **QUALIFICATIONS:** - {job_posting.qualifications} + **JOB POSTING DATA (Raw Input):** + --- + **JOB DESCRIPTION:** + {job_posting.description} - **STRICT JSON OUTPUT INSTRUCTIONS:** - Output a single, valid JSON object with ONLY the following two top-level keys: + **QUALIFICATIONS:** + {job_posting.qualifications} - 'job_description': 'A HTML containing the formatted job description', - 'job_qualifications': 'A HTML containing the formatted job qualifications', + **BENEFITS:** + {job_posting.benefits} + **APPLICATION INSTRUCTIONS:** + {job_posting.application_instructions} - Do not include any other text except for the JSON output. + **APPLICATION DEADLINE:** + {job_posting.application_deadline} + + **HASHTAGS: for search and reach:** + {job_posting.hash_tags} + --- + + **TASK 1: HTML Formatting (Two Blocks)** + 1. **Format the Job Description:** Organize and format the raw JOB DESCRIPTION and BENEFITS data into clear, readable sections using `

    ` headings and `
      `/`
    • ` bullet points. Encapsulate the entire formatted block within a single `
      `. + 2. **Format the Qualifications:** Organize and format the raw QUALIFICATIONS data into clear, readable sections using `

      ` headings and `
        `/`
      • ` bullet points. Encapsulate the entire formatted block within a single `
        `. + 2. **Format the Requirements:** Organize and format the raw Requirements data into clear, readable sections using `

        ` headings and `
          `/`
        • ` bullet points. Encapsulate the entire formatted block within a single `
          `. + + **TASK 2: LinkedIn Post Creation** + 1. **Write the Post:** Create an engaging, professional, and concise LinkedIn post (maximum 1300 characters) summarizing the opportunity. + 2. **Encourage Action:** The post must have a strong call-to-action (CTA) encouraging applications. + 3. **Use Hashtags:** Integrate relevant industry, role, and company hashtags (including any provided in the raw input) naturally at the end of the post. + + **STRICT JSON OUTPUT INSTRUCTIONS:** + Output a **single, valid JSON object** with **ONLY** the following three top-level key-value pairs. + + * The values for `html_job_description` and `html_qualifications` MUST be the complete, formatted HTML strings (including all tags). + * The value for `linkedin_post` MUST be the complete, final LinkedIn post as a single string not greater than 3000 characters. + + **Output Keys:** + 1. `html_job_description` + 2. `html_qualifications` + 3 `html_job_requirements` + 4. `linkedin_post_data` + + **Do not include any other text, explanation, or markdown outside of the final JSON object.** """ result = ai_handler(prompt) - + print(f"REsults: {result}") if result['status'] == 'error': logger.error(f"AI handler returned error for candidate {job_posting.pk}") print(f"AI handler returned error for candidate {job_posting.pk}") @@ -144,9 +174,10 @@ def format_job_description(pk): data = json.loads(data) print(data) - job_posting.description = data.get('job_description') - job_posting.qualifications = data.get('job_qualifications') - job_posting.save(update_fields=['description', 'qualifications']) + job_posting.description = data.get('html_job_description') + job_posting.qualifications = data.get('html_qualifications') + job_posting.linkedin_post_formated_data=data.get('linkedin_post_data') + job_posting.save(update_fields=['description', 'qualifications','linkedin_post_formated_data']) def ai_handler(prompt): @@ -400,6 +431,8 @@ def handle_reume_parsing_and_scoring(pk): logger.info(f"Successfully scored and saved analysis for candidate {instance.id}") print(f"Successfully scored and saved analysis for candidate {instance.id}") + + def create_interview_and_meeting( candidate_id, job_id, diff --git a/recruitment/utils.py b/recruitment/utils.py index 4b24c1a..af759df 100644 --- a/recruitment/utils.py +++ b/recruitment/utils.py @@ -612,4 +612,8 @@ def update_meeting(instance, updated_data): return {"status": "success", "message": "Zoom meeting updated successfully."} logger.warning(f"Failed to update Zoom meeting {instance.meeting_id}. Error: {result.get('message', 'Unknown error')}") - return {"status": "error", "message": result.get("message", "Zoom meeting update failed.")} \ No newline at end of file + return {"status": "error", "message": result.get("message", "Zoom meeting update failed.")} + + + + diff --git a/recruitment/views.py b/recruitment/views.py index 9d535fa..28c99da 100644 --- a/recruitment/views.py +++ b/recruitment/views.py @@ -357,6 +357,15 @@ def job_detail(request, slug): status_form = JobPostingStatusForm(request.POST, instance=job) if status_form.is_valid(): + job_status=status_form.cleaned_data['status'] + form_template=job.form_template + if job_status=='ACTIVE': + form_template.is_active=True + form_template.save(update_fields=['is_active']) + else: + form_template.is_active=False + form_template.save(update_fields=['is_active']) + status_form.save() # Add a success message diff --git a/templates/jobs/job_detail.html b/templates/jobs/job_detail.html index 25b27f2..952af61 100644 --- a/templates/jobs/job_detail.html +++ b/templates/jobs/job_detail.html @@ -347,9 +347,14 @@ {% trans "Create New Form Template" %} {% else %} + {% if job.form_template.is_active %} {% trans "View Form Template" %} + {% else %} +

          {% trans "This job status is not active, the form will appear once the job is made active"%}

          + {% endif %} + {% endif %}