diff --git a/.env b/.env index b9e2bf0..8d7fbd5 100644 --- a/.env +++ b/.env @@ -1,3 +1,3 @@ -DB_NAME=norahuniversity -DB_USER=norahuniversity -DB_PASSWORD=norahuniversity \ No newline at end of file +DB_NAME=haikal_db +DB_USER=faheed +DB_PASSWORD=Faheed@215 \ No newline at end of file diff --git a/NorahUniversity/settings.py b/NorahUniversity/settings.py index e2757e7..af007de 100644 --- a/NorahUniversity/settings.py +++ b/NorahUniversity/settings.py @@ -62,7 +62,7 @@ INSTALLED_APPS = [ "django_q", "widget_tweaks", "easyaudit", -# "mathfilters" + ] diff --git a/recruitment/email_service.py b/recruitment/email_service.py index 750bf12..19d68a5 100644 --- a/recruitment/email_service.py +++ b/recruitment/email_service.py @@ -242,7 +242,7 @@ def send_bulk_email(subject, message, recipient_list, request=None, attachments= Send bulk email to multiple recipients with HTML support and attachments, supporting synchronous or asynchronous dispatch. """ - + # --- 1. Categorization and Custom Message Preparation (CORRECTED) --- if not from_interview: @@ -308,19 +308,19 @@ def send_bulk_email(subject, message, recipient_list, request=None, attachments= sender_user_id=request.user.id if request and hasattr(request, 'user') and request.user.is_authenticated else None if not from_interview: # Loop through ALL final customized sends - for recipient_email, custom_message in customized_sends: - task_id = async_task( - 'recruitment.tasks.send_bulk_email_task', - subject, - custom_message, # Pass the custom message - [recipient_email], # Pass the specific recipient as a list of one - processed_attachments, - sender_user_id, - job_id, - hook='recruitment.tasks.email_success_hook', - - ) - task_ids.append(task_id) + + + task_id = async_task( + 'recruitment.tasks.send_bulk_email_task', + subject, + customized_sends, + processed_attachments, + sender_user_id, + job_id, + hook='recruitment.tasks.email_success_hook', + + ) + task_ids.append(task_id) logger.info(f"{len(task_ids)} tasks ({total_recipients} emails) queued.") diff --git a/recruitment/forms.py b/recruitment/forms.py index 1fd4580..ebca72b 100644 --- a/recruitment/forms.py +++ b/recruitment/forms.py @@ -77,7 +77,9 @@ class SourceForm(forms.ModelForm): } ), "ip_address": forms.TextInput( - attrs={"class": "form-control", "placeholder": "192.168.1.100"} + attrs={"class": "form-control", "placeholder": "192.168.1.100", + "required":True}, + ), "trusted_ips":forms.TextInput( attrs={"class": "form-control", "placeholder": "192.168.1.100","required": False} @@ -114,6 +116,13 @@ class SourceForm(forms.ModelForm): if Source.objects.filter(name=name).exclude(pk=instance.pk).exists(): raise ValidationError("A source with this name already exists.") return name + def clean_ip_address(self): + ip_address=self.cleaned_data.get('ip_address') + if not ip_address: + raise ValidationError(_("Ip address should not be empty")) + return ip_address + + class SourceAdvancedForm(forms.ModelForm): @@ -900,6 +909,7 @@ class HiringAgencyForm(forms.ModelForm): self.helper.form_class = "form-horizontal" self.helper.label_class = "col-md-3" self.helper.field_class = "col-md-9" + self.fields['email'].required=True self.helper.layout = Layout( Field("name", css_class="form-control"), @@ -949,8 +959,7 @@ class HiringAgencyForm(forms.ModelForm): # instance = self.instance email = email.lower().strip() if not instance.pk: # Creating new instance - print("created ....") - if HiringAgency.objects.filter(email=email).exists(): + if HiringAgency.objects.filter(email=email).exists() or User.objects.filter(email=email): raise ValidationError("An agency with this email already exists.") else: # Editing existing instance if ( @@ -1424,6 +1433,7 @@ class CandidateEmailForm(forms.Form): email_addresses = [] candidates=self.cleaned_data.get('to',[]) + print(f"candidates are {candidates}") if candidates: for candidate in candidates: diff --git a/recruitment/tasks.py b/recruitment/tasks.py index 8f04a1c..5b99a6f 100644 --- a/recruitment/tasks.py +++ b/recruitment/tasks.py @@ -1003,31 +1003,33 @@ def _task_send_individual_email(subject, body_message, recipient, attachments,se logger.info(f"Stored sent message ID {new_message.id} in DB.") except Exception as e: logger.error(f"Email sent to {recipient}, but failed to store in DB: {str(e)}") - + return result == 1 except Exception as e: logger.error(f"Failed to send email to {recipient}: {str(e)}", exc_info=True) -def send_bulk_email_task(subject, message, recipient_list,attachments=None,sender_user_id=None,job_id=None, hook='recruitment.tasks.email_success_hook'): +def send_bulk_email_task(subject, customized_sends,attachments=None,sender_user_id=None,job_id=None, hook='recruitment.tasks.email_success_hook'): """ Django-Q background task to send pre-formatted email to a list of recipients., Receives arguments directly from the async_task call. """ - logger.info(f"Starting bulk email task for {len(recipient_list)} recipients") + print("jhjmfhsdjhfksjhdkfjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjh") + logger.info(f"Starting bulk email task for {len(customized_sends)} recipients") successful_sends = 0 - total_recipients = len(recipient_list) + total_recipients = len(customized_sends) - if not recipient_list: + if not customized_sends: return {'success': False, 'error': 'No recipients provided to task.'} sender=get_object_or_404(User,pk=sender_user_id) job=get_object_or_404(JobPosting,pk=job_id) + # Since the async caller sends one task per recipient, total_recipients should be 1. - for recipient in recipient_list: + for recipient_email, custom_message in customized_sends: # The 'message' is the custom message specific to this recipient. - r=_task_send_individual_email(subject, message, recipient, attachments,sender,job) - print(f"Email send result for {recipient}: {r}") + r=_task_send_individual_email(subject, custom_message, recipient_email, attachments,sender,job) + print(f"Email send result for {recipient_email}: {r}") if r: successful_sends += 1 print(f"successful_sends: {successful_sends} out of {total_recipients}") diff --git a/recruitment/views.py b/recruitment/views.py index 9f31d58..628c137 100644 --- a/recruitment/views.py +++ b/recruitment/views.py @@ -1018,7 +1018,7 @@ def delete_form_template(request, template_id): @login_required @staff_or_candidate_required -def application_submit_form(request, slug): +def application_submit_form(request, template_slug): """Display the form as a step-by-step wizard""" if not request.user.is_authenticated: return redirect("application_signup",slug=slug) @@ -3328,13 +3328,13 @@ def message_create(request): if message.recipient and message.recipient.email: if request.user.user_type != "staff": - message=message.content + message=message.content else: - message=message.content.append(f"\n\n Sent by: {request.user.get_full_name()} ({request.user.email})") + body=message.content+f"\n\n Sent by: {request.user.get_full_name()} ({request.user.email})" try: email_result = async_task('recruitment.tasks._task_send_individual_email', subject=message.subject, - body_message=message, + body_message=body, recipient=message.recipient.email, attachments=None, sender=False, @@ -3960,7 +3960,7 @@ def compose_application_email(request, job_slug): candidate_ids=request.GET.getlist('candidate_ids') candidates=Application.objects.filter(id__in=candidate_ids) - + if request.method == 'POST': candidate_ids = request.POST.getlist('candidate_ids') @@ -3968,6 +3968,8 @@ def compose_application_email(request, job_slug): applications=Application.objects.filter(id__in=candidate_ids) form = CandidateEmailForm(job, applications, request.POST) + + if form.is_valid(): print("form is valid ...") # Get email addresses @@ -3990,7 +3992,7 @@ def compose_application_email(request, job_slug): subject = form.cleaned_data.get('subject') # Send emails using email service (no attachments, synchronous to avoid pickle issues) - + print(email_addresses) email_result = send_bulk_email( # subject=subject, message=message, @@ -4079,7 +4081,7 @@ def compose_application_email(request, job_slug): else: # GET request - show the form - form = CandidateEmailForm(job, candidates,request) + form = CandidateEmailForm(job, candidates) return render( request, @@ -4289,11 +4291,11 @@ def application_signup(request, slug): # gpa = form.cleaned_data["gpa"] password = form.cleaned_data["password"] gpa=form.cleaned_data["gpa"] - natiional_id=form.cleaned_data["national_id"] + national_id=form.cleaned_data["national_id"] user = User.objects.create_user( username = email,email=email,first_name=first_name,last_name=last_name,phone=phone,user_type="candidate", - gpa=gpa,natiional_id=natiional_id + ) user.set_password(password) user.save() @@ -4304,7 +4306,8 @@ def application_signup(request, slug): phone=phone, gender=gender, nationality=nationality, - # gpa=gpa, + gpa=gpa, + national_id=national_id, address=address, user = user ) @@ -4342,18 +4345,24 @@ def interview_list(request): # Get filter parameters status_filter = request.GET.get('status', '') + interview_type=request.GET.get('type') job_filter = request.GET.get('job', '') - search_query = request.GET.get('q', '') - + print(job_filter) + search_query = request.GET.get('search', '') + jobs=JobPosting.objects.filter(status='ACTIVE') + # Apply filters + if interview_type: + interviews=interviews.filter(interview__location_type=interview_type) if status_filter: interviews = interviews.filter(status=status_filter) if job_filter: - interviews = interviews.filter(job__title__icontains=job_filter) + interviews = interviews.filter(job__slug=job_filter) if search_query: interviews = interviews.filter( Q(application__person__first_name__icontains=search_query) | Q(application__person__last_name__icontains=search_query) | + Q(application__person__email=search_query)| Q(job__title__icontains=search_query) ) @@ -4368,6 +4377,7 @@ def interview_list(request): 'job_filter': job_filter, 'search_query': search_query, 'interviews': interviews, + 'jobs':jobs } return render(request, 'interviews/interview_list.html', context) @@ -4381,10 +4391,8 @@ def interview_detail(request, slug): interview = schedule.interview reschedule_form = ScheduledInterviewForm() - if interview: - reschedule_form.initial['topic'] = interview.topic - reschedule_form.initial['start_time'] = interview.start_time - reschedule_form.initial['duration'] = interview.duration + reschedule_form.initial['topic'] = interview.interview.topic + meeting=interview.interview context = { 'schedule': schedule, 'interview': interview, diff --git a/templates/includes/email_compose_form.html b/templates/includes/email_compose_form.html index 4d6f482..61b2b5d 100644 --- a/templates/includes/email_compose_form.html +++ b/templates/includes/email_compose_form.html @@ -27,23 +27,45 @@ {% csrf_token %} -
+
+ + {# --- 1. DATA LAYER: Render Hidden Inputs for ALL recipients --- #} + {# This ensures the backend receives every selected user, not just the visible one #} + {% for choice in form.to %} + + {% endfor %} + + {# --- 2. VISUAL LAYER: Show only the first one --- #} + {# We make it disabled so the user knows they can't deselect it here #} {% for choice in form.to|slice:":1" %}
- {{ choice }} + +
{% endfor %} - {% if form.to|length > 0 %} + + {# --- 3. SUMMARY: Show count of hidden recipients --- #} + {% if form.to|length > 1 %}
- {% blocktrans count total=form.to|length %}{{ total }} recipient selected{% plural %}{{ total }} recipients selected{% endblocktrans %} + {# Use simple math to show remaining count #} + {% with remaining=form.to|length|add:"-1" %} + {% blocktrans count total=remaining %} + And {{ total }} other recipient + {% plural %} + And {{ total }} other recipients + {% endblocktrans %} + {% endwith %}
{% endif %}
+ {% if form.to.errors %}
{% for error in form.to.errors %} @@ -53,7 +75,6 @@ {% endif %}
-