diff --git a/db.sqlite3 b/db.sqlite3 index 71a4820..c063fcb 100644 Binary files a/db.sqlite3 and b/db.sqlite3 differ diff --git a/i.html b/i.html deleted file mode 100644 index 9139446..0000000 --- a/i.html +++ /dev/null @@ -1,1495 +0,0 @@ - - - - - - ATS Form Builder - Resume Upload - - - - - -
-
- - - - -
-
-

Applicant Tracking System

-
- - -
-
- - -
-
-
- {{ stage.name }} - * - Default - - - - - - -
-
-
- - -
- -
-
-

- - {{ stages[currentStage].name }} Stage - * - Default -

- -
- -
-
- -

Drag form elements here to build your stage

-
- -
-
-
- - {{ field.label || field.type.charAt(0).toUpperCase() + field.type.slice(1) }} - * -
-
-
- -
-
- -
-
-
- -
- - - -
- -
- -
- -
- -
-
-
- -
-
-

Drag & drop your resume here or click to browse

-
-
-

Supported formats: PDF, DOC, DOCX (Max 5MB)

-
- - - - - -
-
- -
-
{{ field.uploadedFile.name }}
-
{{ formatFileSize(field.uploadedFile.size) }}
-
-
- -
-
-
- -
- -
- -
-
-
- - -
-
-
- -
-
-
- - -
-
-
-
-
-
-
- - -
-
-

Field Properties

- -
- -
-
- - -
- -
- - -
- -
-
- - -
-
-
- - -
-

Options

- -
-
- - -
-
- - -
- - -
-

File Settings

- -
- - -
- -
- - -
-
-
-
-
-
- - - -
- - - - \ No newline at end of file diff --git a/recruitment/__pycache__/admin.cpython-313.pyc b/recruitment/__pycache__/admin.cpython-313.pyc index cea40a7..33016b7 100644 Binary files a/recruitment/__pycache__/admin.cpython-313.pyc and b/recruitment/__pycache__/admin.cpython-313.pyc differ diff --git a/recruitment/__pycache__/models.cpython-313.pyc b/recruitment/__pycache__/models.cpython-313.pyc index 5b91c76..3e2ca59 100644 Binary files a/recruitment/__pycache__/models.cpython-313.pyc and b/recruitment/__pycache__/models.cpython-313.pyc differ diff --git a/recruitment/__pycache__/urls.cpython-313.pyc b/recruitment/__pycache__/urls.cpython-313.pyc index fc5fa74..9dc2251 100644 Binary files a/recruitment/__pycache__/urls.cpython-313.pyc and b/recruitment/__pycache__/urls.cpython-313.pyc differ diff --git a/recruitment/__pycache__/views.cpython-313.pyc b/recruitment/__pycache__/views.cpython-313.pyc index 64c651f..ee93565 100644 Binary files a/recruitment/__pycache__/views.cpython-313.pyc and b/recruitment/__pycache__/views.cpython-313.pyc differ diff --git a/recruitment/admin.py b/recruitment/admin.py index 35b771e..a84bdde 100644 --- a/recruitment/admin.py +++ b/recruitment/admin.py @@ -6,7 +6,7 @@ from django.contrib import admin from django.contrib.auth.admin import UserAdmin as BaseUserAdmin from django.contrib.auth.admin import GroupAdmin as BaseGroupAdmin from django.contrib.auth.models import User, Group - +from .models import FormTemplate, FormStage, FormField, FormSubmission, FieldResponse from unfold.forms import AdminPasswordChangeForm, UserChangeForm, UserCreationForm from unfold.admin import ModelAdmin @@ -53,4 +53,58 @@ class CandidateAdmin(ModelAdmin): @admin.register(models.TrainingMaterial) class TrainingMaterialAdmin(ModelAdmin): list_display = ('title', 'created_by', 'created_at') - search_fields = ('title', 'content') \ No newline at end of file + search_fields = ('title', 'content') + + +class FormFieldInline(admin.TabularInline): + model = FormField + extra = 0 + fields = ('label', 'field_type', 'required', 'order', 'is_predefined') + ordering = ('order',) + +class FormStageInline(admin.TabularInline): + model = FormStage + extra = 0 + fields = ('name', 'order', 'is_predefined') + ordering = ('order',) + inlines = [FormFieldInline] + +@admin.register(FormTemplate) +class FormTemplateAdmin(admin.ModelAdmin): + list_display = ('name', 'created_by', 'created_at', 'is_active', 'get_stage_count') + list_filter = ('is_active', 'created_at', 'created_by') + search_fields = ('name', 'description', 'created_by__username') + inlines = [FormStageInline] + readonly_fields = ('created_at', 'updated_at') + + def get_stage_count(self, obj): + return obj.get_stage_count() + get_stage_count.short_description = 'Stages' + +@admin.register(FormStage) +class FormStageAdmin(admin.ModelAdmin): + list_display = ('name', 'template', 'order', 'is_predefined') + list_filter = ('is_predefined', 'template') + search_fields = ('name', 'template__name') + ordering = ('template', 'order') + +@admin.register(FormField) +class FormFieldAdmin(admin.ModelAdmin): + list_display = ('label', 'field_type', 'stage', 'required', 'order', 'is_predefined') + list_filter = ('field_type', 'required', 'is_predefined', 'stage__template') + search_fields = ('label', 'stage__name', 'stage__template__name') + ordering = ('stage', 'order') + +@admin.register(FormSubmission) +class FormSubmissionAdmin(admin.ModelAdmin): + list_display = ('template', 'applicant_name', 'applicant_email', 'submitted_at') + list_filter = ('submitted_at', 'template') + search_fields = ('applicant_name', 'applicant_email', 'template__name') + readonly_fields = ('submitted_at',) + +@admin.register(FieldResponse) +class FieldResponseAdmin(admin.ModelAdmin): + list_display = ('field', 'submission', 'display_value') + list_filter = ('field__field_type', 'submission__template') + search_fields = ('field__label', 'submission__applicant_name') + readonly_fields = ('display_value',) \ No newline at end of file diff --git a/recruitment/migrations/0013_formfield_formstage_remove_formsubmission_form_and_more.py b/recruitment/migrations/0013_formfield_formstage_remove_formsubmission_form_and_more.py new file mode 100644 index 0000000..9157a8b --- /dev/null +++ b/recruitment/migrations/0013_formfield_formstage_remove_formsubmission_form_and_more.py @@ -0,0 +1,156 @@ +# Generated by Django 5.2.6 on 2025-10-05 09:50 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('recruitment', '0012_form_formsubmission_uploadedfile'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='FormField', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('label', models.CharField(help_text='Label for the field', max_length=200)), + ('field_type', models.CharField(choices=[('text', 'Text Input'), ('email', 'Email'), ('phone', 'Phone'), ('textarea', 'Text Area'), ('file', 'File Upload'), ('date', 'Date Picker'), ('select', 'Dropdown'), ('radio', 'Radio Buttons'), ('checkbox', 'Checkboxes')], help_text='Type of the field', max_length=20)), + ('placeholder', models.CharField(blank=True, help_text='Placeholder text', max_length=200)), + ('required', models.BooleanField(default=False, help_text='Whether the field is required')), + ('order', models.PositiveIntegerField(default=0, help_text='Order of the field in the stage')), + ('is_predefined', models.BooleanField(default=False, help_text='Whether this is a default field')), + ('options', models.JSONField(blank=True, default=list, help_text='Options for selection fields (stored as JSON array)')), + ('file_types', models.CharField(blank=True, help_text="Allowed file types (comma-separated, e.g., '.pdf,.doc,.docx')", max_length=200)), + ('max_file_size', models.PositiveIntegerField(default=5, help_text='Maximum file size in MB (default: 5MB)')), + ], + options={ + 'verbose_name': 'Form Field', + 'verbose_name_plural': 'Form Fields', + 'ordering': ['order'], + }, + ), + migrations.CreateModel( + name='FormStage', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(help_text='Name of the stage', max_length=200)), + ('order', models.PositiveIntegerField(default=0, help_text='Order of the stage in the form')), + ('is_predefined', models.BooleanField(default=False, help_text='Whether this is a default resume stage')), + ], + options={ + 'verbose_name': 'Form Stage', + 'verbose_name_plural': 'Form Stages', + 'ordering': ['order'], + }, + ), + migrations.RemoveField( + model_name='formsubmission', + name='form', + ), + migrations.RemoveField( + model_name='uploadedfile', + name='submission', + ), + migrations.AlterModelOptions( + name='formsubmission', + options={'ordering': ['-submitted_at'], 'verbose_name': 'Form Submission', 'verbose_name_plural': 'Form Submissions'}, + ), + migrations.RemoveField( + model_name='formsubmission', + name='ip_address', + ), + migrations.RemoveField( + model_name='formsubmission', + name='submission_data', + ), + migrations.RemoveField( + model_name='formsubmission', + name='user_agent', + ), + migrations.AddField( + model_name='formsubmission', + name='applicant_email', + field=models.EmailField(blank=True, help_text='Email of the applicant', max_length=254), + ), + migrations.AddField( + model_name='formsubmission', + name='applicant_name', + field=models.CharField(blank=True, help_text='Name of the applicant', max_length=200), + ), + migrations.AddField( + model_name='formsubmission', + name='submitted_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='form_submissions', to=settings.AUTH_USER_MODEL), + ), + migrations.CreateModel( + name='FieldResponse', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('value', models.JSONField(blank=True, help_text='Response value (stored as JSON)', null=True)), + ('uploaded_file', models.FileField(blank=True, null=True, upload_to='form_uploads/')), + ('submission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='responses', to='recruitment.formsubmission')), + ('field', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='responses', to='recruitment.formfield')), + ], + options={ + 'verbose_name': 'Field Response', + 'verbose_name_plural': 'Field Responses', + }, + ), + migrations.AddField( + model_name='formfield', + name='stage', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='fields', to='recruitment.formstage'), + ), + migrations.CreateModel( + name='FormTemplate', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(help_text='Name of the form template', max_length=200)), + ('description', models.TextField(blank=True, help_text='Description of the form template')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('is_active', models.BooleanField(default=True, help_text='Whether this template is active')), + ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='form_templates', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'Form Template', + 'verbose_name_plural': 'Form Templates', + 'ordering': ['-created_at'], + }, + ), + migrations.AddField( + model_name='formstage', + name='template', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='stages', to='recruitment.formtemplate'), + ), + migrations.AddField( + model_name='formsubmission', + name='template', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='submissions', to='recruitment.formtemplate'), + preserve_default=False, + ), + migrations.CreateModel( + name='SharedFormTemplate', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('is_public', models.BooleanField(default=False, help_text='Whether this template is publicly available')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('shared_with', models.ManyToManyField(blank=True, related_name='shared_templates', to=settings.AUTH_USER_MODEL)), + ('template', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='recruitment.formtemplate')), + ], + options={ + 'verbose_name': 'Shared Form Template', + 'verbose_name_plural': 'Shared Form Templates', + }, + ), + migrations.DeleteModel( + name='Form', + ), + migrations.DeleteModel( + name='UploadedFile', + ), + ] diff --git a/recruitment/migrations/__pycache__/0013_formfield_formstage_remove_formsubmission_form_and_more.cpython-313.pyc b/recruitment/migrations/__pycache__/0013_formfield_formstage_remove_formsubmission_form_and_more.cpython-313.pyc new file mode 100644 index 0000000..e488061 Binary files /dev/null and b/recruitment/migrations/__pycache__/0013_formfield_formstage_remove_formsubmission_form_and_more.cpython-313.pyc differ diff --git a/recruitment/models.py b/recruitment/models.py index 3d86193..3fad9b8 100644 --- a/recruitment/models.py +++ b/recruitment/models.py @@ -282,34 +282,194 @@ class ZoomMeeting(Base): return self.topic -class Form(models.Model): - title = models.CharField(max_length=200) - description = models.TextField(blank=True) - structure = models.JSONField(default=dict) # Stores the form schema - created_by = models.ForeignKey(User, on_delete=models.CASCADE) +class FormTemplate(models.Model): + """ + Represents a complete form template with multiple stages + """ + name = models.CharField(max_length=200, help_text="Name of the form template") + description = models.TextField(blank=True, help_text="Description of the form template") + created_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name='form_templates') created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) - is_active = models.BooleanField(default=True) + is_active = models.BooleanField(default=True, help_text="Whether this template is active") class Meta: ordering = ['-created_at'] + verbose_name = 'Form Template' + verbose_name_plural = 'Form Templates' def __str__(self): - return self.title + return self.name + + def get_stage_count(self): + return self.stages.count() + + def get_field_count(self): + return sum(stage.fields.count() for stage in self.stages.all()) + + +class FormStage(models.Model): + """ + Represents a stage/section within a form template + """ + template = models.ForeignKey(FormTemplate, on_delete=models.CASCADE, related_name='stages') + name = models.CharField(max_length=200, help_text="Name of the stage") + order = models.PositiveIntegerField(default=0, help_text="Order of the stage in the form") + is_predefined = models.BooleanField(default=False, help_text="Whether this is a default resume stage") + + class Meta: + ordering = ['order'] + verbose_name = 'Form Stage' + verbose_name_plural = 'Form Stages' + + def __str__(self): + return f"{self.template.name} - {self.name}" + + def clean(self): + if self.order < 0: + raise ValidationError("Order must be a positive integer") + + +class FormField(models.Model): + """ + Represents a single field within a form stage + """ + FIELD_TYPES = [ + ('text', 'Text Input'), + ('email', 'Email'), + ('phone', 'Phone'), + ('textarea', 'Text Area'), + ('file', 'File Upload'), + ('date', 'Date Picker'), + ('select', 'Dropdown'), + ('radio', 'Radio Buttons'), + ('checkbox', 'Checkboxes'), + ] + + stage = models.ForeignKey(FormStage, on_delete=models.CASCADE, related_name='fields') + label = models.CharField(max_length=200, help_text="Label for the field") + field_type = models.CharField(max_length=20, choices=FIELD_TYPES, help_text="Type of the field") + placeholder = models.CharField(max_length=200, blank=True, help_text="Placeholder text") + required = models.BooleanField(default=False, help_text="Whether the field is required") + order = models.PositiveIntegerField(default=0, help_text="Order of the field in the stage") + is_predefined = models.BooleanField(default=False, help_text="Whether this is a default field") + + # For selection fields (select, radio, checkbox) + options = models.JSONField( + default=list, + blank=True, + help_text="Options for selection fields (stored as JSON array)" + ) + + # For file upload fields + file_types = models.CharField( + max_length=200, + blank=True, + help_text="Allowed file types (comma-separated, e.g., '.pdf,.doc,.docx')" + ) + max_file_size = models.PositiveIntegerField( + default=5, + help_text="Maximum file size in MB (default: 5MB)" + ) + + class Meta: + ordering = ['order'] + verbose_name = 'Form Field' + verbose_name_plural = 'Form Fields' + + def __str__(self): + return f"{self.stage.name} - {self.label}" + + def clean(self): + # Validate options for selection fields + if self.field_type in ['select', 'radio', 'checkbox']: + if not isinstance(self.options, list): + raise ValidationError("Options must be a list for selection fields") + else: + # Clear options for non-selection fields + if self.options: + self.options = [] + + # Validate file settings for file fields + if self.field_type == 'file': + if not self.file_types: + self.file_types = '.pdf,.doc,.docx' + if self.max_file_size <= 0: + raise ValidationError("Max file size must be greater than 0") + else: + # Clear file settings for non-file fields + self.file_types = '' + self.max_file_size = 0 + + if self.order < 0: + raise ValidationError("Order must be a positive integer") + class FormSubmission(models.Model): - form = models.ForeignKey(Form, on_delete=models.CASCADE, related_name='submissions') - submission_data = models.JSONField(default=dict) # Stores form responses + """ + Represents a completed form submission by an applicant + """ + template = models.ForeignKey(FormTemplate, on_delete=models.CASCADE, related_name='submissions') + submitted_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='form_submissions') submitted_at = models.DateTimeField(auto_now_add=True) - ip_address = models.GenericIPAddressField(null=True, blank=True) - user_agent = models.TextField(blank=True) + applicant_name = models.CharField(max_length=200, blank=True, help_text="Name of the applicant") + applicant_email = models.EmailField(blank=True, help_text="Email of the applicant") class Meta: ordering = ['-submitted_at'] + verbose_name = 'Form Submission' + verbose_name_plural = 'Form Submissions' -class UploadedFile(models.Model): - submission = models.ForeignKey(FormSubmission, on_delete=models.CASCADE, related_name='files') - field_id = models.CharField(max_length=100) - file = models.FileField(upload_to='form_uploads/%Y/%m/%d/') - original_filename = models.CharField(max_length=255) - uploaded_at = models.DateTimeField(auto_now_add=True) \ No newline at end of file + def __str__(self): + return f"Submission for {self.template.name} - {self.submitted_at.strftime('%Y-%m-%d %H:%M')}" + + +class FieldResponse(models.Model): + """ + Represents a response to a specific field in a form submission + """ + submission = models.ForeignKey(FormSubmission, on_delete=models.CASCADE, related_name='responses') + field = models.ForeignKey(FormField, on_delete=models.CASCADE, related_name='responses') + + # Store the response value as JSON to handle different data types + value = models.JSONField(null=True, blank=True, help_text="Response value (stored as JSON)") + + # For file uploads, store the file path + uploaded_file = models.FileField(upload_to='form_uploads/', null=True, blank=True) + + class Meta: + verbose_name = 'Field Response' + verbose_name_plural = 'Field Responses' + + def __str__(self): + return f"Response to {self.field.label} in {self.submission}" + + @property + def display_value(self): + """Return a human-readable representation of the response value""" + if self.uploaded_file: + return f"File: {self.uploaded_file.name}" + elif self.value is None: + return "" + elif isinstance(self.value, list): + return ", ".join(str(v) for v in self.value) + else: + return str(self.value) + + +# Optional: Create a model for form templates that can be shared across organizations +class SharedFormTemplate(models.Model): + """ + Represents a form template that can be shared across different organizations/users + """ + template = models.OneToOneField(FormTemplate, on_delete=models.CASCADE) + is_public = models.BooleanField(default=False, help_text="Whether this template is publicly available") + shared_with = models.ManyToManyField(User, blank=True, related_name='shared_templates') + created_at = models.DateTimeField(auto_now_add=True) + + class Meta: + verbose_name = 'Shared Form Template' + verbose_name_plural = 'Shared Form Templates' + + def __str__(self): + return f"Shared: {self.template.name}" \ No newline at end of file diff --git a/recruitment/templatetags/__init__.py b/recruitment/templatetags/__init__.py new file mode 100644 index 0000000..30aef66 --- /dev/null +++ b/recruitment/templatetags/__init__.py @@ -0,0 +1 @@ +# This file makes the templatetags directory a Python package diff --git a/recruitment/templatetags/__pycache__/__init__.cpython-313.pyc b/recruitment/templatetags/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..9242944 Binary files /dev/null and b/recruitment/templatetags/__pycache__/__init__.cpython-313.pyc differ diff --git a/recruitment/templatetags/__pycache__/form_filters.cpython-313.pyc b/recruitment/templatetags/__pycache__/form_filters.cpython-313.pyc new file mode 100644 index 0000000..76ea8d4 Binary files /dev/null and b/recruitment/templatetags/__pycache__/form_filters.cpython-313.pyc differ diff --git a/recruitment/templatetags/form_filters.py b/recruitment/templatetags/form_filters.py new file mode 100644 index 0000000..6cbf01e --- /dev/null +++ b/recruitment/templatetags/form_filters.py @@ -0,0 +1,52 @@ +from django import template +from rich import print + +register = template.Library() + +@register.simple_tag +def get_stage_responses(stage_responses, stage_id): + """ + Template tag to get responses for a specific stage. + Usage: {% get_stage_responses stage_responses stage.id as stage_data %} + """ + + if stage_responses and stage_id in stage_responses: + return stage_responses[stage_id] + return [] + +@register.simple_tag +def get_all_responses_flat(stage_responses): + """ + Template tag to get all responses flattened for table display. + Usage: {% get_all_responses_flat stage_responses as all_responses %} + """ + all_responses = [] + if stage_responses: + print(stage_responses.get(9).get("responses")[0].value) + for stage_id, responses in stage_responses.items(): + for response in responses: + # Check if response is an object or string + if hasattr(response, 'stage') and hasattr(response, 'field'): + stage_name = response.stage.name if hasattr(response.stage, 'name') else f"Stage {stage_id}" + field_label = response.field.label if hasattr(response.field, 'label') else "Unknown Field" + field_type = response.field.get_field_type_display() if hasattr(response.field, 'get_field_type_display') else "Unknown Type" + required = response.field.required if hasattr(response.field, 'required') else False + value = response.value if hasattr(response, 'value') else response + uploaded_file = response.uploaded_file if hasattr(response, 'uploaded_file') else None + else: + stage_name = f"Stage {stage_id}" + field_label = "Unknown Field" + field_type = "Text" + required = False + value = response + uploaded_file = None + + all_responses.append({ + 'stage_name': stage_name, + 'field_label': field_label, + 'field_type': field_type, + 'required': required, + 'value': value, + 'uploaded_file': uploaded_file + }) + return all_responses diff --git a/recruitment/urls.py b/recruitment/urls.py index a4b87f4..599e36f 100644 --- a/recruitment/urls.py +++ b/recruitment/urls.py @@ -46,17 +46,28 @@ urlpatterns = [ path('api//edit/', views.edit_job, name='edit_job_api'), # - path('form_builder/', views.form_builder, name='form_builder'), # Form Preview URLs - path('forms/', views.form_list, name='form_list'), - path('forms//', views.form_preview, name='form_preview'), - path('forms//submit/', views.form_submit, name='form_submit'), - path('forms//embed/', views.form_embed, name='form_embed'), - path('forms//submissions/', views.form_submissions, name='form_submissions'), - path('forms//edit/', views.edit_form, name='edit_form'), - path('api/forms/save/', views.save_form_builder, name='save_form_builder'), - path('api/forms//load/', views.load_form, name='load_form'), - path('api/forms//update/', views.update_form_builder, name='update_form_builder'), + # path('forms/', views.form_list, name='form_list'), + path('forms/builder/', views.form_builder, name='form_builder'), + path('forms/builder//', views.form_builder, name='form_builder'), + path('forms/', views.form_templates_list, name='form_templates_list'), + + path('forms/form//', views.form_wizard_view, name='form_wizard'), + path('forms/form//submit/', views.submit_form, name='submit_form'), + path('forms//submissions//', views.form_submission_details, name='form_submission_details'), + + path('api/templates/', views.list_form_templates, name='list_form_templates'), + path('api/templates/save/', views.save_form_template, name='save_form_template'), + path('api/templates//', views.load_form_template, name='load_form_template'), + path('api/templates//delete/', views.delete_form_template, name='delete_form_template'), + # path('forms//', views.form_preview, name='form_preview'), + # path('forms//submit/', views.form_submit, name='form_submit'), + # path('forms//embed/', views.form_embed, name='form_embed'), + # path('forms//submissions/', views.form_submissions, name='form_submissions'), + # path('forms//edit/', views.edit_form, name='edit_form'), + # path('api/forms/save/', views.save_form_builder, name='save_form_builder'), + # path('api/forms//load/', views.load_form, name='load_form'), + # path('api/forms//update/', views.update_form_builder, name='update_form_builder'), ] diff --git a/recruitment/views.py b/recruitment/views.py index 1577934..fe0e444 100644 --- a/recruitment/views.py +++ b/recruitment/views.py @@ -3,7 +3,6 @@ import requests from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_http_methods from django.http import JsonResponse -from recruitment.models import FormSubmission,Form,UploadedFile from datetime import datetime from django.views import View from django.db.models import Q @@ -14,11 +13,13 @@ from rest_framework import viewsets from django.contrib import messages from django.core.paginator import Paginator from .linkedin_service import LinkedInService +from .models import FormTemplate, FormStage, FormField,FieldResponse,FormSubmission from .models import ZoomMeeting, Job, Candidate, JobPosting from .serializers import JobPostingSerializer, CandidateSerializer from django.shortcuts import get_object_or_404, render, redirect from django.views.generic import CreateView,UpdateView,DetailView,ListView from .utils import create_zoom_meeting, delete_zoom_meeting, update_zoom_meeting +from django.views.decorators.csrf import ensure_csrf_cookie import logging logger=logging.getLogger(__name__) @@ -334,244 +335,484 @@ def applicant_job_detail(request,slug): job=get_object_or_404(JobPosting,slug=slug,status='ACTIVE') return render(request,'jobs/applicant_job_detail.html',{'job':job}) -def form_builder(request): - return render(request,'form_builder.html') - # Form Preview Views -from django.http import JsonResponse -from django.views.decorators.csrf import csrf_exempt -from django.core.paginator import Paginator -from django.contrib.auth.decorators import login_required -import json +# from django.http import JsonResponse +# from django.views.decorators.csrf import csrf_exempt +# from django.core.paginator import Paginator +# from django.contrib.auth.decorators import login_required +# import json -def form_list(request): - """Display list of all available forms""" - forms = Form.objects.filter(is_active=True).order_by('-created_at') +# def form_list(request): +# """Display list of all available forms""" +# forms = Form.objects.filter(is_active=True).order_by('-created_at') - # Pagination - paginator = Paginator(forms, 12) +# # Pagination +# paginator = Paginator(forms, 12) +# page_number = request.GET.get('page') +# page_obj = paginator.get_page(page_number) + +# return render(request, 'forms/form_list.html', { +# 'page_obj': page_obj +# }) + +# def form_preview(request, form_id): +# """Display form preview for end users""" +# form = get_object_or_404(Form, id=form_id, is_active=True) + +# # Get submission count for analytics +# submission_count = form.submissions.count() + +# return render(request, 'forms/form_preview.html', { +# 'form': form, +# 'submission_count': submission_count, +# 'is_embed': request.GET.get('embed', 'false') == 'true' +# }) + +# @csrf_exempt +# def form_submit(request, form_id): +# """Handle form submission via AJAX""" +# if request.method != 'POST': +# return JsonResponse({'success': False, 'error': 'Only POST method allowed'}, status=405) + +# form = get_object_or_404(Form, id=form_id, is_active=True) + +# try: +# # Parse form data +# submission_data = {} +# files = {} + +# # Process regular form fields +# for key, value in request.POST.items(): +# if key != 'csrfmiddlewaretoken': +# submission_data[key] = value + +# # Process file uploads +# for key, file in request.FILES.items(): +# if file: +# files[key] = file + +# # Create form submission +# submission = FormSubmission.objects.create( +# form=form, +# submission_data=submission_data, +# ip_address=request.META.get('REMOTE_ADDR'), +# user_agent=request.META.get('HTTP_USER_AGENT', '') +# ) + +# # Handle file uploads +# for field_id, file in files.items(): +# UploadedFile.objects.create( +# submission=submission, +# field_id=field_id, +# file=file, +# original_filename=file.name +# ) + +# # TODO: Send email notification if configured + +# return JsonResponse({ +# 'success': True, +# 'message': 'Form submitted successfully!', +# 'submission_id': submission.id +# }) + +# except Exception as e: +# logger.error(f"Error submitting form {form_id}: {e}") +# return JsonResponse({ +# 'success': False, +# 'error': 'An error occurred while submitting the form. Please try again.' +# }, status=500) + +# def form_embed(request, form_id): +# """Display embeddable version of form""" +# form = get_object_or_404(Form, id=form_id, is_active=True) + +# return render(request, 'forms/form_embed.html', { +# 'form': form, +# 'is_embed': True +# }) + +# @login_required +# def save_form_builder(request): +# """Save form from builder to database""" +# if request.method != 'POST': +# return JsonResponse({'success': False, 'error': 'Only POST method allowed'}, status=405) + +# try: +# data = json.loads(request.body) +# form_data = data.get('form', {}) + +# # Check if this is an update or create +# form_id = data.get('form_id') + +# if form_id: +# # Update existing form +# form = Form.objects.get(id=form_id, created_by=request.user) +# form.title = form_data.get('title', 'Untitled Form') +# form.description = form_data.get('description', '') +# form.structure = form_data +# form.save() +# else: +# # Create new form +# form = Form.objects.create( +# title=form_data.get('title', 'Untitled Form'), +# description=form_data.get('description', ''), +# structure=form_data, +# created_by=request.user +# ) + +# return JsonResponse({ +# 'success': True, +# 'form_id': form.id, +# 'message': 'Form saved successfully!' +# }) + +# except json.JSONDecodeError: +# return JsonResponse({ +# 'success': False, +# 'error': 'Invalid JSON data' +# }, status=400) +# except Exception as e: +# logger.error(f"Error saving form: {e}") +# return JsonResponse({ +# 'success': False, +# 'error': 'An error occurred while saving the form' +# }, status=500) + +# @login_required +# def load_form(request, form_id): +# """Load form data for editing in builder""" +# try: +# form = get_object_or_404(Form, id=form_id, created_by=request.user) + +# return JsonResponse({ +# 'success': True, +# 'form': { +# 'id': form.id, +# 'title': form.title, +# 'description': form.description, +# 'structure': form.structure +# } +# }) + +# except Exception as e: +# logger.error(f"Error loading form {form_id}: {e}") +# return JsonResponse({ +# 'success': False, +# 'error': 'An error occurred while loading the form' +# }, status=500) + +# @csrf_exempt +# def update_form_builder(request, form_id): +# """Update existing form from builder""" +# if request.method != 'POST': +# return JsonResponse({'success': False, 'error': 'Only POST method allowed'}, status=405) + +# try: +# form = get_object_or_404(Form, id=form_id) + +# # Check if user has permission to edit this form +# if form.created_by != request.user: +# return JsonResponse({ +# 'success': False, +# 'error': 'You do not have permission to edit this form' +# }, status=403) + +# data = json.loads(request.body) +# form_data = data.get('form', {}) + +# # Update form +# form.title = form_data.get('title', 'Untitled Form') +# form.description = form_data.get('description', '') +# form.structure = form_data +# form.save() + +# return JsonResponse({ +# 'success': True, +# 'form_id': form.id, +# 'message': 'Form updated successfully!' +# }) + +# except json.JSONDecodeError: +# return JsonResponse({ +# 'success': False, +# 'error': 'Invalid JSON data' +# }, status=400) +# except Exception as e: +# logger.error(f"Error updating form {form_id}: {e}") +# return JsonResponse({ +# 'success': False, +# 'error': 'An error occurred while updating the form' +# }, status=500) + +# def edit_form(request, form_id): +# """Display form edit page""" +# form = get_object_or_404(Form, id=form_id) + +# # Check if user has permission to edit this form +# if form.created_by != request.user: +# messages.error(request, 'You do not have permission to edit this form.') +# return redirect('form_list') + +# return render(request, 'forms/edit_form.html', { +# 'form': form +# }) + +# def form_submissions(request, form_id): +# """View submissions for a specific form""" +# form = get_object_or_404(Form, id=form_id, created_by=request.user) +# submissions = form.submissions.all().order_by('-submitted_at') + +# # Pagination +# paginator = Paginator(submissions, 20) +# page_number = request.GET.get('page') +# page_obj = paginator.get_page(page_number) + +# return render(request, 'forms/form_submissions.html', { +# 'form': form, +# 'page_obj': page_obj +# }) + + +@ensure_csrf_cookie +def form_builder(request, template_id=None): + """Render the form builder interface""" + context = {} + if template_id: + template = get_object_or_404(FormTemplate, id=template_id, created_by=request.user) + context['template_id'] = template.id + context['template_name'] = template.name + return render(request,'forms/form_builder.html',context) + + +@csrf_exempt +@require_http_methods(["POST"]) +def save_form_template(request): + """Save a new or existing form template""" + try: + data = json.loads(request.body) + template_name = data.get('name', 'Untitled Form') + stages_data = data.get('stages', []) + template_id = data.get('template_id') + + if template_id: + # Update existing template + template = get_object_or_404(FormTemplate, id=template_id, created_by=request.user) + template.name = template_name + template.save() + # Clear existing stages and fields + template.stages.all().delete() + else: + # Create new template + template = FormTemplate.objects.create( + name=template_name, + created_by=request.user + ) + + # Create stages and fields + for stage_order, stage_data in enumerate(stages_data): + stage = FormStage.objects.create( + template=template, + name=stage_data['name'], + order=stage_order, + is_predefined=stage_data.get('predefined', False) + ) + + for field_order, field_data in enumerate(stage_data['fields']): + options = field_data.get('options', []) + if not isinstance(options, list): + options = [] + + file_types = field_data.get('fileTypes', '') + max_file_size = field_data.get('maxFileSize', 5) + + FormField.objects.create( + stage=stage, + label=field_data.get('label', ''), + field_type=field_data.get('type', 'text'), + placeholder=field_data.get('placeholder', ''), + required=field_data.get('required', False), + order=field_order, + is_predefined=field_data.get('predefined', False), + options=options, + file_types=file_types, + max_file_size=max_file_size + ) + + return JsonResponse({ + 'success': True, + 'template_id': template.id, + 'message': 'Form template saved successfully!' + }) + except Exception as e: + return JsonResponse({ + 'success': False, + 'error': str(e) + }, status=400) + + +@require_http_methods(["GET"]) +def load_form_template(request, template_id): + """Load an existing form template""" + template = get_object_or_404(FormTemplate, id=template_id, created_by=request.user) + + stages = [] + for stage in template.stages.all(): + fields = [] + for field in stage.fields.all(): + fields.append({ + 'id': field.id, + 'type': field.field_type, + 'label': field.label, + 'placeholder': field.placeholder, + 'required': field.required, + 'options': field.options, + 'fileTypes': field.file_types, + 'maxFileSize': field.max_file_size, + 'predefined': field.is_predefined + }) + stages.append({ + 'id': stage.id, + 'name': stage.name, + 'predefined': stage.is_predefined, + 'fields': fields + }) + + return JsonResponse({ + 'success': True, + 'template': { + 'id': template.id, + 'name': template.name, + 'description': template.description, + 'stages': stages + } + }) + +def form_templates_list(request): + """List all form templates for the current user""" + query = request.GET.get('q', '') + templates = FormTemplate.objects.filter(created_by=request.user) + + if query: + templates = templates.filter( + Q(name__icontains=query) | Q(description__icontains=query) + ) + + templates = templates.order_by('-created_at') + paginator = Paginator(templates, 10) # Show 10 templates per page page_number = request.GET.get('page') page_obj = paginator.get_page(page_number) - return render(request, 'forms/form_list.html', { - 'page_obj': page_obj + context = { + 'templates': page_obj, + 'query': query, + } + return render(request, 'forms/form_templates_list.html', context) + +@require_http_methods(["GET"]) +def list_form_templates(request): + """List all form templates for the current user""" + templates = FormTemplate.objects.filter(created_by=request.user).values( + 'id', 'name', 'description', 'created_at', 'updated_at' + ) + return JsonResponse({ + 'success': True, + 'templates': list(templates) }) -def form_preview(request, form_id): - """Display form preview for end users""" - form = get_object_or_404(Form, id=form_id, is_active=True) +@require_http_methods(["DELETE"]) +def delete_form_template(request, template_id): + """Delete a form template""" + template = get_object_or_404(FormTemplate, id=template_id, created_by=request.user) + template.delete() + return JsonResponse({'success': True, 'message': 'Form template deleted successfully!'}) - # Get submission count for analytics - submission_count = form.submissions.count() - return render(request, 'forms/form_preview.html', { - 'form': form, - 'submission_count': submission_count, - 'is_embed': request.GET.get('embed', 'false') == 'true' - }) - -@csrf_exempt -def form_submit(request, form_id): - """Handle form submission via AJAX""" - if request.method != 'POST': - return JsonResponse({'success': False, 'error': 'Only POST method allowed'}, status=405) - - form = get_object_or_404(Form, id=form_id, is_active=True) +def form_wizard_view(request, template_id): + """Display the form as a step-by-step wizard""" + template = get_object_or_404(FormTemplate, id=template_id, is_active=True) + return render(request, 'forms/form_wizard.html', {'template_id': template_id}) +@require_http_methods(["POST"]) +def submit_form(request, template_id): + """Handle form submission""" try: - # Parse form data - submission_data = {} - files = {} - - # Process regular form fields - for key, value in request.POST.items(): - if key != 'csrfmiddlewaretoken': - submission_data[key] = value - - # Process file uploads - for key, file in request.FILES.items(): - if file: - files[key] = file + template = get_object_or_404(FormTemplate, id=template_id) + print(template) # Create form submission submission = FormSubmission.objects.create( - form=form, - submission_data=submission_data, - ip_address=request.META.get('REMOTE_ADDR'), - user_agent=request.META.get('HTTP_USER_AGENT', '') + template=template, + applicant_name=request.POST.get('applicant_name', ''), + applicant_email=request.POST.get('applicant_email', '') ) - # Handle file uploads - for field_id, file in files.items(): - UploadedFile.objects.create( - submission=submission, - field_id=field_id, - file=file, - original_filename=file.name - ) + # Process field responses + for field_id, value in request.POST.items(): + if field_id.startswith('field_'): + actual_field_id = field_id.replace('field_', '') + try: + field = FormField.objects.get(id=actual_field_id, stage__template=template) + FieldResponse.objects.create( + submission=submission, + field=field, + value=value if value else None + ) + except FormField.DoesNotExist: + continue - # TODO: Send email notification if configured + # Handle file uploads + for field_id, uploaded_file in request.FILES.items(): + if field_id.startswith('field_'): + actual_field_id = field_id.replace('field_', '') + try: + field = FormField.objects.get(id=actual_field_id, stage__template=template) + FieldResponse.objects.create( + submission=submission, + field=field, + uploaded_file=uploaded_file + ) + except FormField.DoesNotExist: + continue return JsonResponse({ 'success': True, 'message': 'Form submitted successfully!', 'submission_id': submission.id }) - except Exception as e: - logger.error(f"Error submitting form {form_id}: {e}") return JsonResponse({ 'success': False, - 'error': 'An error occurred while submitting the form. Please try again.' - }, status=500) - -def form_embed(request, form_id): - """Display embeddable version of form""" - form = get_object_or_404(Form, id=form_id, is_active=True) - - return render(request, 'forms/form_embed.html', { - 'form': form, - 'is_embed': True - }) - -@login_required -def save_form_builder(request): - """Save form from builder to database""" - if request.method != 'POST': - return JsonResponse({'success': False, 'error': 'Only POST method allowed'}, status=405) - - try: - data = json.loads(request.body) - form_data = data.get('form', {}) - - # Check if this is an update or create - form_id = data.get('form_id') - - if form_id: - # Update existing form - form = Form.objects.get(id=form_id, created_by=request.user) - form.title = form_data.get('title', 'Untitled Form') - form.description = form_data.get('description', '') - form.structure = form_data - form.save() - else: - # Create new form - form = Form.objects.create( - title=form_data.get('title', 'Untitled Form'), - description=form_data.get('description', ''), - structure=form_data, - created_by=request.user - ) - - return JsonResponse({ - 'success': True, - 'form_id': form.id, - 'message': 'Form saved successfully!' - }) - - except json.JSONDecodeError: - return JsonResponse({ - 'success': False, - 'error': 'Invalid JSON data' + 'error': str(e) }, status=400) - except Exception as e: - logger.error(f"Error saving form: {e}") - return JsonResponse({ - 'success': False, - 'error': 'An error occurred while saving the form' - }, status=500) -@login_required -def load_form(request, form_id): - """Load form data for editing in builder""" - try: - form = get_object_or_404(Form, id=form_id, created_by=request.user) +def form_submission_details(request, form_id, submission_id): + """Display detailed view of a specific form submission""" + # Get the form template and verify ownership + form = get_object_or_404(FormTemplate, id=form_id, created_by=request.user) - return JsonResponse({ - 'success': True, - 'form': { - 'id': form.id, - 'title': form.title, - 'description': form.description, - 'structure': form.structure - } - }) + # Get the specific submission + submission = get_object_or_404(FormSubmission, id=submission_id, template=form) - except Exception as e: - logger.error(f"Error loading form {form_id}: {e}") - return JsonResponse({ - 'success': False, - 'error': 'An error occurred while loading the form' - }, status=500) + # Get all stages with their fields + stages = form.stages.prefetch_related('fields').order_by('order') -@csrf_exempt -def update_form_builder(request, form_id): - """Update existing form from builder""" - if request.method != 'POST': - return JsonResponse({'success': False, 'error': 'Only POST method allowed'}, status=405) + # Get all responses for this submission, ordered by field order + responses = submission.responses.select_related('field').order_by('field__order') - try: - form = get_object_or_404(Form, id=form_id) - - # Check if user has permission to edit this form - if form.created_by != request.user: - return JsonResponse({ - 'success': False, - 'error': 'You do not have permission to edit this form' - }, status=403) - - data = json.loads(request.body) - form_data = data.get('form', {}) - - # Update form - form.title = form_data.get('title', 'Untitled Form') - form.description = form_data.get('description', '') - form.structure = form_data - form.save() - - return JsonResponse({ - 'success': True, - 'form_id': form.id, - 'message': 'Form updated successfully!' - }) - - except json.JSONDecodeError: - return JsonResponse({ - 'success': False, - 'error': 'Invalid JSON data' - }, status=400) - except Exception as e: - logger.error(f"Error updating form {form_id}: {e}") - return JsonResponse({ - 'success': False, - 'error': 'An error occurred while updating the form' - }, status=500) - -def edit_form(request, form_id): - """Display form edit page""" - form = get_object_or_404(Form, id=form_id) - - # Check if user has permission to edit this form - if form.created_by != request.user: - messages.error(request, 'You do not have permission to edit this form.') - return redirect('form_list') - - return render(request, 'forms/edit_form.html', { - 'form': form - }) - -def form_submissions(request, form_id): - """View submissions for a specific form""" - form = get_object_or_404(Form, id=form_id, created_by=request.user) - submissions = form.submissions.all().order_by('-submitted_at') - - # Pagination - paginator = Paginator(submissions, 20) - page_number = request.GET.get('page') - page_obj = paginator.get_page(page_number) - - return render(request, 'forms/form_submissions.html', { + # Group responses by stage + stage_responses = {} + for stage in stages: + stage_responses[stage.id] = { + 'stage': stage, + 'responses': responses.filter(field__stage=stage) + } + # print(stages) + return render(request, 'forms/form_submission_details.html', { 'form': form, - 'page_obj': page_obj + 'submission': submission, + 'stages': stages, + 'responses': responses, + 'stage_responses': stage_responses }) diff --git a/shapes/1.html b/shapes/1.html new file mode 100644 index 0000000..cde791b --- /dev/null +++ b/shapes/1.html @@ -0,0 +1,69 @@ + + + + + + Typeform Clone + + + + + +
+
+
+
+
+ + \ No newline at end of file diff --git a/shapes/2.html b/shapes/2.html new file mode 100644 index 0000000..df14861 --- /dev/null +++ b/shapes/2.html @@ -0,0 +1,181 @@ + + + + + Typeform Clone - Nature Theme + + + + + +
+ + + + + + + + + \ No newline at end of file diff --git a/shapes/3.html b/shapes/3.html new file mode 100644 index 0000000..bae51a3 --- /dev/null +++ b/shapes/3.html @@ -0,0 +1,117 @@ + + + + + + Typeform Clone - Dark Theme + + + + + +
+ +
+
+ + + \ No newline at end of file diff --git a/shapes/4.html b/shapes/4.html new file mode 100644 index 0000000..185c008 --- /dev/null +++ b/shapes/4.html @@ -0,0 +1,154 @@ + + + + + + Typeform Clone - Minimalist + + + + + +
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/static/media/form_uploads/Abdullah_Bakhsh_-_2025.pdf b/static/media/form_uploads/Abdullah_Bakhsh_-_2025.pdf new file mode 100644 index 0000000..852169f Binary files /dev/null and b/static/media/form_uploads/Abdullah_Bakhsh_-_2025.pdf differ diff --git a/static/media/form_uploads/Summary-QVP-598973.pdf b/static/media/form_uploads/Summary-QVP-598973.pdf new file mode 100644 index 0000000..3c4fcfc Binary files /dev/null and b/static/media/form_uploads/Summary-QVP-598973.pdf differ diff --git a/templates/form_builder.html b/templates/form_builder.html deleted file mode 100644 index 5710c79..0000000 --- a/templates/form_builder.html +++ /dev/null @@ -1,1604 +0,0 @@ - - - - - - Dynamic Form Builder - - - - - - - - - - - - - - - - - - - - - -
- - - - diff --git a/templates/forms/form_builder.html b/templates/forms/form_builder.html new file mode 100644 index 0000000..dfa8cf5 --- /dev/null +++ b/templates/forms/form_builder.html @@ -0,0 +1,1738 @@ + + + + + + ATS Form Builder - Vanilla JS + + + + + + +
+ + + +
+
+

Resume Application Form

+
+ + + +
+
+ +
+
+
+ +
+ +
+
+

+ + Contact Information Stage + + +

+ +
+
+
+ +

Drag form elements here to build your stage

+
+
+
+ + +
+
+
+ + + + + + + \ No newline at end of file diff --git a/templates/forms/form_submission_details.html b/templates/forms/form_submission_details.html new file mode 100644 index 0000000..a2fd33f --- /dev/null +++ b/templates/forms/form_submission_details.html @@ -0,0 +1,422 @@ +{% extends "base.html" %} +{% load form_filters %} + +{% block title %}{{ form.name }} - Submission Details{% endblock %} + +{% block content %} +
+
+
+
+
+

Submission Details

+

{{ form.name }}

+
+ +
+
+
+ + +
+
+
+
+
+
+

{{ submission.id }}

+ Submission ID +
+
+ +
+
+
+
+
+
+
+
+
+
+

{{ submission.submitted_at|date:"M d, Y" }}

+ Submitted +
+
+ +
+
+
+
+
+
+
+
+
+
+

{{ responses|length }}

+ Fields Completed +
+
+ +
+
+
+
+
+
+ + +
+
+
Submission Information
+
+
+
+
+ + + + + + + + + + {% if submission.submitted_by %} + + + + + {% endif %} +
Form:{{ form.name }}
Submitted:{{ submission.submitted_at|date:"F d, Y H:i" }}
Submitted By:{{ submission.submitted_by.get_full_name|default:submission.submitted_by.username }}
+
+
+ {% if submission.applicant_name %} + + + + + + {% endif %} + {% if submission.applicant_email %} + + + + + {% endif %} +
Applicant Name:{{ submission.applicant_name }}
Email:{{ submission.applicant_email }}
+
+
+
+
+ + +
+
+
Submitted Responses
+
+
+ {% for stage in stages %} +
+
+
+
+ {{ stage.name }} +
+
+
+ {% get_stage_responses stage_responses stage.id as stage_data %} + {% if stage_data %} +
+ + + + + + + + + + + {% for response in stage_data %} + + + + + + + {% endfor %} + +
Field LabelField TypeResponse ValueFile
+ {{ response.field.label }} + {% if response.field.required %} + * + {% endif %} + {{ response.field.get_field_type_display }} + {% if response.uploaded_file %} + File: {{ response.uploaded_file.name }} + {% elif response.value %} + {% if response.field.field_type == 'checkbox' and response.value|length > 0 %} +
    + {% for val in response.value %} +
  • {{ val }}
  • + {% endfor %} +
+ {% elif response.field.field_type == 'radio' %} + {{ response.value }} + {% elif response.field.field_type == 'select' %} + {{ response.value }} + {% else %} +

{{ response.value|linebreaksbr }}

+ {% endif %} + {% else %} + Not provided + {% endif %} +
+ {% if response.uploaded_file %} + + Download + + {% endif %} +
+
+ {% else %} +
+ +

No responses submitted for this stage.

+
+ {% endif %} +
+
+
+ {% if not forloop.last %} +
+ {% endif %} + {% empty %} +
+ +

No stages found

+

This form doesn't have any stages defined.

+
+ {% endfor %} +
+
+ + +
+
+
All Responses (Raw Data)
+
+
+ {% get_all_responses_flat stage_responses as all_responses %} + {% if all_responses %} +
+ + + + + + + + + + + + + {% for response in all_responses %} + + + + + + + + + {% endfor %} + +
StageField LabelField TypeRequiredResponse ValueFile
{{ response.stage_name }} + {{ response.field_label }} + {% if response.required %} + * + {% endif %} + {{ response.field_type }} + {% if response.required %} + Yes + {% else %} + No + {% endif %} + + {% if response.uploaded_file %} + File: {{ response.uploaded_file.name }} + {% elif response.value %} + {% if response.field_type == 'checkbox' and response.value|length > 0 %} +
    + {% for val in response.value %} +
  • {{ val }}
  • + {% endfor %} +
+ {% elif response.field_type == 'radio' %} + {{ response.value }} + {% elif response.field_type == 'select' %} + {{ response.value }} + {% else %} +

{{ response.value|linebreaksbr }}

+ {% endif %} + {% else %} + Not provided + {% endif %} +
+ {% if response.uploaded_file %} + + Download + + {% endif %} +
+
+ {% else %} +
+ +

No responses found for this submission.

+
+ {% endif %} +
+
+
+ + + +{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block extra_js %} + +{% endblock %} diff --git a/templates/forms/form_templates_list.html b/templates/forms/form_templates_list.html new file mode 100644 index 0000000..2d240a5 --- /dev/null +++ b/templates/forms/form_templates_list.html @@ -0,0 +1,312 @@ + +{% extends 'base.html' %} + +{% block title %}Form Templates - ATS{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block content %} +
+
+

Form Templates

+ + Create New Template + +
+ +
+
+
+ + +
+
+
+ + {% if templates %} +
+ {% for template in templates %} +
+
+
+

{{ template.name }}

+
+ {{ template.created_at|date:"M d, Y" }} + {{ template.updated_at|timesince }} ago +
+
+
+
+
+
{{ template.get_stage_count }}
+
Stages
+
+
+
{{ template.get_field_count }}
+
Fields
+
+
+

+ {% if template.description %} + {{ template.description|truncatewords:20 }} + {% else %} + No description provided + {% endif %} +

+
+ + Edit + + +
+
+
+
+ {% endfor %} +
+ + {% if templates.has_other_pages %} +
+ +
+ {% endif %} + {% else %} +
+ +

No Form Templates Found

+

+ {% if query %}No templates match your search "{{ query }}".{% else %}You haven't created any form templates yet.{% endif %} +

+ + Create Your First Template + +
+ {% endif %} +
+ + +{% include 'includes/delete_modal.html' %} +{% endblock %} + +{% block extra_js %} + +{% endblock %} diff --git a/templates/forms/form_wizard.html b/templates/forms/form_wizard.html new file mode 100644 index 0000000..f547b3c --- /dev/null +++ b/templates/forms/form_wizard.html @@ -0,0 +1,1143 @@ + + + + + + + Application Form + + + + + +
+ +
+
+
+ +
+ +
1 of 1
+
+ +
+
+ +
+ + +
+ + +
+ + + + \ No newline at end of file