update on the form submittion

This commit is contained in:
ismail 2025-10-06 15:12:35 +03:00
parent 1aa8b6800a
commit 1212161493
27 changed files with 5046 additions and 3334 deletions

Binary file not shown.

1495
i.html

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@ from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.admin import GroupAdmin as BaseGroupAdmin from django.contrib.auth.admin import GroupAdmin as BaseGroupAdmin
from django.contrib.auth.models import User, Group 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.forms import AdminPasswordChangeForm, UserChangeForm, UserCreationForm
from unfold.admin import ModelAdmin from unfold.admin import ModelAdmin
@ -53,4 +53,58 @@ class CandidateAdmin(ModelAdmin):
@admin.register(models.TrainingMaterial) @admin.register(models.TrainingMaterial)
class TrainingMaterialAdmin(ModelAdmin): class TrainingMaterialAdmin(ModelAdmin):
list_display = ('title', 'created_by', 'created_at') list_display = ('title', 'created_by', 'created_at')
search_fields = ('title', 'content') 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',)

View File

@ -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',
),
]

View File

@ -282,34 +282,194 @@ class ZoomMeeting(Base):
return self.topic return self.topic
class Form(models.Model): class FormTemplate(models.Model):
title = models.CharField(max_length=200) """
description = models.TextField(blank=True) Represents a complete form template with multiple stages
structure = models.JSONField(default=dict) # Stores the form schema """
created_by = models.ForeignKey(User, on_delete=models.CASCADE) 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) created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=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: class Meta:
ordering = ['-created_at'] ordering = ['-created_at']
verbose_name = 'Form Template'
verbose_name_plural = 'Form Templates'
def __str__(self): 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): 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) submitted_at = models.DateTimeField(auto_now_add=True)
ip_address = models.GenericIPAddressField(null=True, blank=True) applicant_name = models.CharField(max_length=200, blank=True, help_text="Name of the applicant")
user_agent = models.TextField(blank=True) applicant_email = models.EmailField(blank=True, help_text="Email of the applicant")
class Meta: class Meta:
ordering = ['-submitted_at'] ordering = ['-submitted_at']
verbose_name = 'Form Submission'
verbose_name_plural = 'Form Submissions'
class UploadedFile(models.Model): def __str__(self):
submission = models.ForeignKey(FormSubmission, on_delete=models.CASCADE, related_name='files') return f"Submission for {self.template.name} - {self.submitted_at.strftime('%Y-%m-%d %H:%M')}"
field_id = models.CharField(max_length=100)
file = models.FileField(upload_to='form_uploads/%Y/%m/%d/')
original_filename = models.CharField(max_length=255) class FieldResponse(models.Model):
uploaded_at = models.DateTimeField(auto_now_add=True) """
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}"

View File

@ -0,0 +1 @@
# This file makes the templatetags directory a Python package

View File

@ -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

View File

@ -46,17 +46,28 @@ urlpatterns = [
path('api/<slug:slug>/edit/', views.edit_job, name='edit_job_api'), path('api/<slug:slug>/edit/', views.edit_job, name='edit_job_api'),
# #
path('form_builder/', views.form_builder, name='form_builder'),
# Form Preview URLs # Form Preview URLs
path('forms/', views.form_list, name='form_list'), # path('forms/', views.form_list, name='form_list'),
path('forms/<int:form_id>/', views.form_preview, name='form_preview'), path('forms/builder/', views.form_builder, name='form_builder'),
path('forms/<int:form_id>/submit/', views.form_submit, name='form_submit'), path('forms/builder/<int:template_id>/', views.form_builder, name='form_builder'),
path('forms/<int:form_id>/embed/', views.form_embed, name='form_embed'), path('forms/', views.form_templates_list, name='form_templates_list'),
path('forms/<int:form_id>/submissions/', views.form_submissions, name='form_submissions'),
path('forms/<int:form_id>/edit/', views.edit_form, name='edit_form'), path('forms/form/<int:template_id>/', views.form_wizard_view, name='form_wizard'),
path('api/forms/save/', views.save_form_builder, name='save_form_builder'), path('forms/form/<int:template_id>/submit/', views.submit_form, name='submit_form'),
path('api/forms/<int:form_id>/load/', views.load_form, name='load_form'), path('forms/<int:form_id>/submissions/<int:submission_id>/', views.form_submission_details, name='form_submission_details'),
path('api/forms/<int:form_id>/update/', views.update_form_builder, name='update_form_builder'),
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/<int:template_id>/', views.load_form_template, name='load_form_template'),
path('api/templates/<int:template_id>/delete/', views.delete_form_template, name='delete_form_template'),
# path('forms/<int:form_id>/', views.form_preview, name='form_preview'),
# path('forms/<int:form_id>/submit/', views.form_submit, name='form_submit'),
# path('forms/<int:form_id>/embed/', views.form_embed, name='form_embed'),
# path('forms/<int:form_id>/submissions/', views.form_submissions, name='form_submissions'),
# path('forms/<int:form_id>/edit/', views.edit_form, name='edit_form'),
# path('api/forms/save/', views.save_form_builder, name='save_form_builder'),
# path('api/forms/<int:form_id>/load/', views.load_form, name='load_form'),
# path('api/forms/<int:form_id>/update/', views.update_form_builder, name='update_form_builder'),
] ]

View File

@ -3,7 +3,6 @@ import requests
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods from django.views.decorators.http import require_http_methods
from django.http import JsonResponse from django.http import JsonResponse
from recruitment.models import FormSubmission,Form,UploadedFile
from datetime import datetime from datetime import datetime
from django.views import View from django.views import View
from django.db.models import Q from django.db.models import Q
@ -14,11 +13,13 @@ from rest_framework import viewsets
from django.contrib import messages from django.contrib import messages
from django.core.paginator import Paginator from django.core.paginator import Paginator
from .linkedin_service import LinkedInService from .linkedin_service import LinkedInService
from .models import FormTemplate, FormStage, FormField,FieldResponse,FormSubmission
from .models import ZoomMeeting, Job, Candidate, JobPosting from .models import ZoomMeeting, Job, Candidate, JobPosting
from .serializers import JobPostingSerializer, CandidateSerializer from .serializers import JobPostingSerializer, CandidateSerializer
from django.shortcuts import get_object_or_404, render, redirect from django.shortcuts import get_object_or_404, render, redirect
from django.views.generic import CreateView,UpdateView,DetailView,ListView from django.views.generic import CreateView,UpdateView,DetailView,ListView
from .utils import create_zoom_meeting, delete_zoom_meeting, update_zoom_meeting from .utils import create_zoom_meeting, delete_zoom_meeting, update_zoom_meeting
from django.views.decorators.csrf import ensure_csrf_cookie
import logging import logging
logger=logging.getLogger(__name__) logger=logging.getLogger(__name__)
@ -334,244 +335,484 @@ def applicant_job_detail(request,slug):
job=get_object_or_404(JobPosting,slug=slug,status='ACTIVE') job=get_object_or_404(JobPosting,slug=slug,status='ACTIVE')
return render(request,'jobs/applicant_job_detail.html',{'job':job}) return render(request,'jobs/applicant_job_detail.html',{'job':job})
def form_builder(request):
return render(request,'form_builder.html')
# Form Preview Views # Form Preview Views
from django.http import JsonResponse # from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt # from django.views.decorators.csrf import csrf_exempt
from django.core.paginator import Paginator # from django.core.paginator import Paginator
from django.contrib.auth.decorators import login_required # from django.contrib.auth.decorators import login_required
import json # import json
def form_list(request): # def form_list(request):
"""Display list of all available forms""" # """Display list of all available forms"""
forms = Form.objects.filter(is_active=True).order_by('-created_at') # forms = Form.objects.filter(is_active=True).order_by('-created_at')
# Pagination # # Pagination
paginator = Paginator(forms, 12) # 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_number = request.GET.get('page')
page_obj = paginator.get_page(page_number) page_obj = paginator.get_page(page_number)
return render(request, 'forms/form_list.html', { context = {
'page_obj': page_obj '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): @require_http_methods(["DELETE"])
"""Display form preview for end users""" def delete_form_template(request, template_id):
form = get_object_or_404(Form, id=form_id, is_active=True) """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: try:
# Parse form data template = get_object_or_404(FormTemplate, id=template_id)
submission_data = {} print(template)
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 # Create form submission
submission = FormSubmission.objects.create( submission = FormSubmission.objects.create(
form=form, template=template,
submission_data=submission_data, applicant_name=request.POST.get('applicant_name', ''),
ip_address=request.META.get('REMOTE_ADDR'), applicant_email=request.POST.get('applicant_email', '')
user_agent=request.META.get('HTTP_USER_AGENT', '')
) )
# Handle file uploads # Process field responses
for field_id, file in files.items(): for field_id, value in request.POST.items():
UploadedFile.objects.create( if field_id.startswith('field_'):
submission=submission, actual_field_id = field_id.replace('field_', '')
field_id=field_id, try:
file=file, field = FormField.objects.get(id=actual_field_id, stage__template=template)
original_filename=file.name 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({ return JsonResponse({
'success': True, 'success': True,
'message': 'Form submitted successfully!', 'message': 'Form submitted successfully!',
'submission_id': submission.id 'submission_id': submission.id
}) })
except Exception as e: except Exception as e:
logger.error(f"Error submitting form {form_id}: {e}")
return JsonResponse({ return JsonResponse({
'success': False, 'success': False,
'error': 'An error occurred while submitting the form. Please try again.' 'error': str(e)
}, 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) }, 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 form_submission_details(request, form_id, submission_id):
def load_form(request, form_id): """Display detailed view of a specific form submission"""
"""Load form data for editing in builder""" # Get the form template and verify ownership
try: form = get_object_or_404(FormTemplate, id=form_id, created_by=request.user)
form = get_object_or_404(Form, id=form_id, created_by=request.user)
return JsonResponse({ # Get the specific submission
'success': True, submission = get_object_or_404(FormSubmission, id=submission_id, template=form)
'form': {
'id': form.id,
'title': form.title,
'description': form.description,
'structure': form.structure
}
})
except Exception as e: # Get all stages with their fields
logger.error(f"Error loading form {form_id}: {e}") stages = form.stages.prefetch_related('fields').order_by('order')
return JsonResponse({
'success': False,
'error': 'An error occurred while loading the form'
}, status=500)
@csrf_exempt # Get all responses for this submission, ordered by field order
def update_form_builder(request, form_id): responses = submission.responses.select_related('field').order_by('field__order')
"""Update existing form from builder"""
if request.method != 'POST':
return JsonResponse({'success': False, 'error': 'Only POST method allowed'}, status=405)
try: # Group responses by stage
form = get_object_or_404(Form, id=form_id) stage_responses = {}
for stage in stages:
# Check if user has permission to edit this form stage_responses[stage.id] = {
if form.created_by != request.user: 'stage': stage,
return JsonResponse({ 'responses': responses.filter(field__stage=stage)
'success': False, }
'error': 'You do not have permission to edit this form' # print(stages)
}, status=403) return render(request, 'forms/form_submission_details.html', {
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, 'form': form,
'page_obj': page_obj 'submission': submission,
'stages': stages,
'responses': responses,
'stage_responses': stage_responses
}) })

69
shapes/1.html Normal file
View File

@ -0,0 +1,69 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Typeform Clone</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background-color: #f5f8fa;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
overflow: hidden;
}
.floating-shapes {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
z-index: -1;
opacity: 0.5;
}
.shape {
position: absolute;
border-radius: 50%;
background: linear-gradient(135deg, rgba(99, 102, 241, 0.1), rgba(139, 92, 246, 0.1));
}
.shape-1 {
width: 300px;
height: 300px;
top: -150px;
right: -100px;
}
.shape-2 {
width: 200px;
height: 200px;
bottom: -100px;
left: -50px;
}
.shape-3 {
width: 150px;
height: 150px;
top: 50%;
left: 70%;
transform: translate(-50%, -50%);
}
</style>
</head>
<body>
<div class="floating-shapes">
<div class="shape shape-1"></div>
<div class="shape shape-2"></div>
<div class="shape shape-3"></div>
</div>
</body>
</html>

181
shapes/2.html Normal file
View File

@ -0,0 +1,181 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Typeform Clone - Nature Theme</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background-color: #f0f7f4;
overflow: hidden;
}
.nature-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(to bottom, #f0f7f4 0%, #e6f2eb 100%);
z-index: -1;
}
.leaf {
position: absolute;
opacity: 0.1;
z-index: -1;
transform-origin: center;
animation: float 15s infinite ease-in-out;
}
@keyframes float {
0%, 100% { transform: translateY(0) rotate(0deg); }
50% { transform: translateY(-20px) rotate(5deg); }
}
.leaf-1 {
top: 10%;
left: 5%;
font-size: 80px;
color: #5c9d7a;
animation-delay: 0s;
}
.leaf-2 {
bottom: 15%;
right: 10%;
font-size: 100px;
color: #5c9d7a;
animation-delay: 2s;
}
.leaf-3 {
top: 30%;
right: 5%;
font-size: 70px;
color: #5c9d7a;
animation-delay: 4s;
}
.leaf-4 {
bottom: 25%;
left: 8%;
font-size: 90px;
color: #5c9d7a;
animation-delay: 6s;
}
.organic-card {
background: rgba(255, 255, 255, 0.85);
border-radius: 24px;
box-shadow: 0 10px 30px rgba(92, 157, 122, 0.1);
overflow: hidden;
}
.form-input {
width: 100%;
padding: 14px 20px;
border: 2px solid #e0ebe8;
border-radius: 12px;
font-size: 16px;
transition: all 0.3s ease;
background-color: rgba(255, 255, 255, 0.7);
}
.form-input:focus {
outline: none;
border-color: #5c9d7a;
background-color: white;
box-shadow: 0 0 0 4px rgba(92, 157, 122, 0.1);
}
.nature-button {
background: linear-gradient(135deg, #5c9d7a 0%, #4a8b67 100%);
color: white;
padding: 14px 30px;
border-radius: 12px;
font-weight: 600;
width: 100%;
border: none;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.nature-button::before {
content: "";
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.2);
transition: left 0.5s ease;
}
.nature-button:hover::before {
left: 100%;
}
.nature-button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 15px rgba(92, 157, 122, 0.3);
}
.divider {
display: flex;
align-items: center;
margin: 25px 0;
}
.divider-line {
flex-grow: 1;
height: 1px;
background-color: #e0ebe8;
}
.divider-text {
margin: 0 15px;
color: #5c9d7a;
font-size: 14px;
}
.social-button {
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
background-color: white;
border: 2px solid #e0ebe8;
transition: all 0.3s ease;
}
.social-button:hover {
border-color: #5c9d7a;
transform: translateY(-3px);
box-shadow: 0 5px 15px rgba(92, 157, 122, 0.2);
}
</style>
</head>
<body>
<div class="nature-bg"></div>
<i class="fas fa-leaf leaf leaf-1"></i>
<i class="fas fa-leaf leaf leaf-2"></i>
<i class="fas fa-seedling leaf leaf-3"></i>
<i class="fas fa-leaf leaf leaf-4"></i>
</body>
</html>

117
shapes/3.html Normal file
View File

@ -0,0 +1,117 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Typeform Clone - Dark Theme</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background-color: #0f0f0f;
color: #ffffff;
overflow: hidden;
}
.neon-grid {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
linear-gradient(rgba(255, 0, 255, 0.1) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 0, 255, 0.1) 1px, transparent 1px);
background-size: 50px 50px;
z-index: -1;
}
.neon-border {
border: 1px solid rgba(255, 0, 255, 0.5);
position: relative;
}
.neon-border::before {
content: "";
position: absolute;
top: -2px;
left: -2px;
right: -2px;
bottom: -2px;
background: linear-gradient(45deg, #ff00ff, #00ffff, #ff00ff);
z-index: -1;
opacity: 0.7;
filter: blur(5px);
animation: neon-pulse 2s infinite alternate;
}
@keyframes neon-pulse {
0% { opacity: 0.5; }
100% { opacity: 1; }
}
.glow-text {
text-shadow: 0 0 10px rgba(255, 0, 255, 0.7);
}
.neon-button {
background: transparent;
color: #ffffff;
border: 1px solid #ff00ff;
position: relative;
overflow: hidden;
transition: all 0.3s ease;
}
.neon-button::before {
content: "";
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 0, 255, 0.3), transparent);
transition: left 0.5s ease;
}
.neon-button:hover::before {
left: 100%;
}
.neon-button:hover {
background: rgba(255, 0, 255, 0.1);
box-shadow: 0 0 15px rgba(255, 0, 255, 0.7);
transform: translateY(-2px);
}
.floating-orb {
position: absolute;
border-radius: 50%;
background: radial-gradient(circle, rgba(255, 0, 255, 0.7) 0%, rgba(0, 255, 255, 0.7) 100%);
filter: blur(40px);
z-index: -1;
opacity: 0.6;
animation: float 10s infinite ease-in-out alternate;
}
@keyframes float {
0% { transform: translate(0, 0) scale(1); }
100% { transform: translate(30px, -30px) scale(1.1); }
}
</style>
</head>
<body class="min-h-screen flex items-center justify-center p-4">
<div class="neon-grid"></div>
<div class="floating-orb" style="width: 300px; height: 300px; top: 10%; left: 10%;"></div>
<div class="floating-orb" style="width: 200px; height: 200px; bottom: 20%; right: 15%; animation-delay: 2s;"</div>
</body>
</html>

154
shapes/4.html Normal file
View File

@ -0,0 +1,154 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Typeform Clone - Minimalist</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background-color: #f5f5f7;
overflow: hidden;
}
.geometric-shapes {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
z-index: -1;
}
.shape {
position: absolute;
opacity: 0.05;
}
.triangle {
width: 0;
height: 0;
border-left: 100px solid transparent;
border-right: 100px solid transparent;
border-bottom: 173px solid #000;
}
.square {
width: 120px;
height: 120px;
background-color: #000;
}
.circle {
width: 140px;
height: 140px;
border-radius: 50%;
background-color: #000;
}
.hexagon {
width: 120px;
height: 69.28px;
background-color: #000;
position: relative;
margin: 34.64px 0;
}
.hexagon:before,
.hexagon:after {
content: "";
position: absolute;
width: 0;
border-left: 60px solid transparent;
border-right: 60px solid transparent;
}
.hexagon:before {
bottom: 100%;
border-bottom: 34.64px solid #000;
}
.hexagon:after {
top: 100%;
border-top: 34.64px solid #000;
}
.form-container {
max-width: 450px;
margin: 0 auto;
padding: 40px 20px;
}
.input-field {
width: 100%;
padding: 15px 20px;
margin-bottom: 15px;
border: 1px solid #e0e0e0;
border-radius: 8px;
font-size: 16px;
transition: all 0.3s ease;
}
.input-field:focus {
outline: none;
border-color: #007aff;
box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.1);
}
.btn-primary {
background: #007aff;
color: white;
padding: 14px 30px;
border-radius: 8px;
font-weight: 600;
width: 100%;
border: none;
cursor: pointer;
transition: all 0.3s ease;
}
.btn-primary:hover {
background: #005ecb;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 122, 255, 0.3);
}
.divider {
display: flex;
align-items: center;
margin: 25px 0;
}
.divider-line {
flex-grow: 1;
height: 1px;
background-color: #e0e0e0;
}
.divider-text {
margin: 0 15px;
color: #8e8e93;
font-size: 14px;
}
</style>
</head>
<body>
<div class="geometric-shapes">
<div class="shape triangle" style="top: 10%; left: 5%;"></div>
<div class="shape square" style="top: 20%; right: 10%;"></div>
<div class="shape circle" style="bottom: 15%; left: 8%;"></div>
<div class="shape hexagon" style="bottom: 25%; right: 15%;"></div>
</div>
</body>
</html>

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,422 @@
{% extends "base.html" %}
{% load form_filters %}
{% block title %}{{ form.name }} - Submission Details{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h1><i class="fas fa-file-alt"></i> Submission Details</h1>
<p class="text-muted mb-0">{{ form.name }}</p>
</div>
<div>
<a href="" class="btn btn-outline-primary me-2">
<i class="fas fa-arrow-left"></i> Back to Submissions
</a>
<a href="" class="btn btn-outline-primary me-2" target="_blank">
<i class="fas fa-eye"></i> Preview Form
</a>
<button class="btn btn-success" onclick="exportSubmission()">
<i class="fas fa-download"></i> Export
</button>
</div>
</div>
</div>
</div>
<!-- Submission Summary -->
<div class="row mb-4">
<div class="col-md-4">
<div class="card bg-primary text-white">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h4 class="mb-0">{{ submission.id }}</h4>
<small>Submission ID</small>
</div>
<div class="align-self-center">
<i class="fas fa-hashtag fa-2x opacity-75"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card bg-success text-white">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h4 class="mb-0">{{ submission.submitted_at|date:"M d, Y" }}</h4>
<small>Submitted</small>
</div>
<div class="align-self-center">
<i class="fas fa-calendar-check fa-2x opacity-75"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card bg-info text-white">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h4 class="mb-0">{{ responses|length }}</h4>
<small>Fields Completed</small>
</div>
<div class="align-self-center">
<i class="fas fa-tasks fa-2x opacity-75"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Submission Information -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0"><i class="fas fa-info-circle"></i> Submission Information</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<table class="table table-sm">
<tr>
<td><strong>Form:</strong></td>
<td>{{ form.name }}</td>
</tr>
<tr>
<td><strong>Submitted:</strong></td>
<td>{{ submission.submitted_at|date:"F d, Y H:i" }}</td>
</tr>
{% if submission.submitted_by %}
<tr>
<td><strong>Submitted By:</strong></td>
<td>{{ submission.submitted_by.get_full_name|default:submission.submitted_by.username }}</td>
</tr>
{% endif %}
</table>
</div>
<div class="col-md-6">
{% if submission.applicant_name %}
<table class="table table-sm">
<tr>
<td><strong>Applicant Name:</strong></td>
<td>{{ submission.applicant_name }}</td>
</tr>
{% endif %}
{% if submission.applicant_email %}
<tr>
<td><strong>Email:</strong></td>
<td>{{ submission.applicant_email }}</td>
</tr>
{% endif %}
</table>
</div>
</div>
</div>
</div>
<!-- Form Responses by Stage -->
<div class="card">
<div class="card-header">
<h5 class="mb-0"><i class="fas fa-clipboard-list"></i> Submitted Responses</h5>
</div>
<div class="card-body">
{% for stage in stages %}
<div class="mb-4">
<div class="card border-primary">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">
<i class="fas fa-layer-group"></i> {{ stage.name }}
</h5>
</div>
<div class="card-body">
{% get_stage_responses stage_responses stage.id as stage_data %}
{% if stage_data %}
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead class="table-dark">
<tr>
<th>Field Label</th>
<th>Field Type</th>
<th>Response Value</th>
<th>File</th>
</tr>
</thead>
<tbody>
{% for response in stage_data %}
<tr>
<td>
{{ response.field.label }}
{% if response.field.required %}
<span class="text-danger">*</span>
{% endif %}
</td>
<td>{{ response.field.get_field_type_display }}</td>
<td>
{% if response.uploaded_file %}
<span class="text-primary">File: {{ response.uploaded_file.name }}</span>
{% elif response.value %}
{% if response.field.field_type == 'checkbox' and response.value|length > 0 %}
<ul class="list-unstyled mb-0">
{% for val in response.value %}
<li><i class="fas fa-check text-success"></i> {{ val }}</li>
{% endfor %}
</ul>
{% elif response.field.field_type == 'radio' %}
<span class="badge bg-info">{{ response.value }}</span>
{% elif response.field.field_type == 'select' %}
<span class="badge bg-secondary">{{ response.value }}</span>
{% else %}
<p class="mb-0">{{ response.value|linebreaksbr }}</p>
{% endif %}
{% else %}
<span class="text-muted">Not provided</span>
{% endif %}
</td>
<td>
{% if response.uploaded_file %}
<a href="{{ response.uploaded_file.url }}" class="btn btn-sm btn-outline-primary" target="_blank">
<i class="fas fa-download"></i> Download
</a>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="text-center text-muted py-3">
<i class="fas fa-inbox fa-2x mb-2"></i>
<p>No responses submitted for this stage.</p>
</div>
{% endif %}
</div>
</div>
</div>
{% if not forloop.last %}
<hr>
{% endif %}
{% empty %}
<div class="text-center text-muted py-5">
<i class="fas fa-inbox fa-3x mb-3"></i>
<h4>No stages found</h4>
<p>This form doesn't have any stages defined.</p>
</div>
{% endfor %}
</div>
</div>
<!-- Raw Data Table -->
<div class="card mt-4">
<div class="card-header">
<h5 class="mb-0"><i class="fas fa-table"></i> All Responses (Raw Data)</h5>
</div>
<div class="card-body">
{% get_all_responses_flat stage_responses as all_responses %}
{% if all_responses %}
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead class="table-dark">
<tr>
<th>Stage</th>
<th>Field Label</th>
<th>Field Type</th>
<th>Required</th>
<th>Response Value</th>
<th>File</th>
</tr>
</thead>
<tbody>
{% for response in all_responses %}
<tr>
<td>{{ response.stage_name }}</td>
<td>
{{ response.field_label }}
{% if response.required %}
<span class="text-danger">*</span>
{% endif %}
</td>
<td>{{ response.field_type }}</td>
<td>
{% if response.required %}
<span class="badge bg-danger">Yes</span>
{% else %}
<span class="badge bg-secondary">No</span>
{% endif %}
</td>
<td>
{% if response.uploaded_file %}
<span class="text-primary">File: {{ response.uploaded_file.name }}</span>
{% elif response.value %}
{% if response.field_type == 'checkbox' and response.value|length > 0 %}
<ul class="list-unstyled mb-0">
{% for val in response.value %}
<li><i class="fas fa-check text-success"></i> {{ val }}</li>
{% endfor %}
</ul>
{% elif response.field_type == 'radio' %}
<span class="badge bg-info">{{ response.value }}</span>
{% elif response.field_type == 'select' %}
<span class="badge bg-secondary">{{ response.value }}</span>
{% else %}
<p class="mb-0">{{ response.value|linebreaksbr }}</p>
{% endif %}
{% else %}
<span class="text-muted">Not provided</span>
{% endif %}
</td>
<td>
{% if response.uploaded_file %}
<a href="{{ response.uploaded_file.url }}" class="btn btn-sm btn-outline-primary" target="_blank">
<i class="fas fa-download"></i> Download
</a>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="text-center text-muted py-3">
<i class="fas fa-inbox fa-2x mb-2"></i>
<p>No responses found for this submission.</p>
</div>
{% endif %}
</div>
</div>
</div>
<!-- Delete Confirmation Modal -->
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="deleteModalLabel">
<i class="fas fa-exclamation-triangle text-danger"></i> Confirm Delete
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>Are you sure you want to delete this submission? This action cannot be undone.</p>
<div class="alert alert-warning">
<strong>Submission ID:</strong> {{ submission.id }}<br>
<strong>Submitted:</strong> {{ submission.submitted_at|date:"M d, Y H:i" }}
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger" id="confirmDeleteBtn">
<i class="fas fa-trash"></i> Delete Submission
</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_css %}
<style>
.response-value {
max-height: 200px;
overflow-y: auto;
}
.response-value ul {
margin: 0;
padding-left: 1.5rem;
}
.card {
border: 1px solid #e9ecef;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
transition: box-shadow 0.15s ease-in-out;
}
.card:hover {
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
}
.card-title {
font-size: 0.9rem;
font-weight: 600;
color: #333;
}
</style>
{% endblock %}
{% block extra_js %}
<script>
function exportSubmission() {
// Create export options modal or direct download
const format = prompt('Export format (csv, json, pdf):', 'csv');
if (format) {
window.open(`/recruitment/api/forms/{{ form.id }}/submissions/{{ submission.id }}/export/?format=${format}`, '_blank');
}
}
// Handle delete confirmation
document.addEventListener('DOMContentLoaded', function() {
const deleteBtn = document.querySelector('a[href*="delete"]');
if (deleteBtn) {
deleteBtn.addEventListener('click', function(e) {
e.preventDefault();
const modal = new bootstrap.Modal(document.getElementById('deleteModal'));
modal.show();
});
}
// Handle confirm delete
const confirmDeleteBtn = document.getElementById('confirmDeleteBtn');
if (confirmDeleteBtn) {
confirmDeleteBtn.addEventListener('click', function() {
fetch(`/recruitment/api/forms/{{ form.id }}/submissions/{{ submission.id }}/delete/`, {
method: 'DELETE',
headers: {
'X-CSRFToken': getCsrfToken(),
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Submission deleted successfully!');
} else {
alert('Error deleting submission: ' + data.error);
}
})
.catch(error => {
console.error('Error:', error);
alert('Error deleting submission');
});
});
}
});
function getCsrfToken() {
const cookie = document.cookie.split(';').find(c => c.trim().startsWith('csrftoken='));
return cookie ? cookie.split('=')[1] : '';
}
// Print functionality
function printSubmission() {
window.print();
}
// Add print button to header
document.addEventListener('DOMContentLoaded', function() {
const headerActions = document.querySelector('.d-flex.justify-content-between.align-items-center.mb-4');
if (headerActions) {
const printBtn = document.createElement('button');
printBtn.className = 'btn btn-outline-secondary me-2';
printBtn.innerHTML = '<i class="fas fa-print"></i> Print';
printBtn.onclick = printSubmission;
headerActions.insertBefore(printBtn, headerActions.firstChild);
}
});
</script>
{% endblock %}

View File

@ -0,0 +1,312 @@
<!-- templates/form_templates_list.html -->
{% extends 'base.html' %}
{% block title %}Form Templates - ATS{% endblock %}
{% block extra_css %}
<style>
.template-card {
transition: transform 0.2s, box-shadow 0.2s;
height: 100%;
}
.template-card:hover {
transform: translateY(-5px);
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;
}
.stat-value {
font-size: 1.25rem;
font-weight: 700;
color: var(--bs-primary);
}
.stat-label {
font-size: 0.875rem;
color: var(--bs-gray-600);
}
.card-description {
min-height: 60px;
}
.empty-state {
text-align: center;
padding: 3rem 1rem;
color: var(--bs-gray-600);
}
.empty-state i {
font-size: 3rem;
margin-bottom: 1.5rem;
color: var(--bs-gray-400);
}
.action-btn {
min-width: 90px;
}
@media (max-width: 768px) {
.card-actions .btn {
width: 100%;
margin-bottom: 0.5rem;
}
}
.toast-container {
position: fixed;
top: 20px;
right: 20px;
z-index: 9999;
}
</style>
{% endblock %}
{% block content %}
<div class="container py-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3 mb-0"><i class="fas fa-file-alt me-2"></i>Form Templates</h1>
<a href="{% url 'form_builder' %}" class="btn btn-primary">
<i class="fas fa-plus me-1"></i> Create New Template
</a>
</div>
<div class="row mb-4">
<div class="col-md-6">
<div class="input-group">
<input type="text" class="form-control" id="searchInput" placeholder="Search templates..." value="{{ query }}">
<button class="btn btn-outline-secondary" type="button" id="searchBtn">
<i class="fas fa-search"></i>
</button>
</div>
</div>
</div>
{% if templates %}
<div class="row g-4">
{% for template in templates %}
<div class="col-lg-4 col-md-6">
<div class="card template-card h-100">
<div class="card-header bg-light">
<h3 class="h5 mb-2">{{ template.name }}</h3>
<div class="d-flex justify-content-between text-muted small">
<span><i class="fas fa-calendar me-1"></i> {{ template.created_at|date:"M d, Y" }}</span>
<span><i class="fas fa-sync-alt me-1"></i> {{ template.updated_at|timesince }} ago</span>
</div>
</div>
<div class="card-body d-flex flex-column">
<div class="row text-center mb-3">
<div class="col-6">
<div class="stat-value">{{ template.get_stage_count }}</div>
<div class="stat-label">Stages</div>
</div>
<div class="col-6">
<div class="stat-value">{{ template.get_field_count }}</div>
<div class="stat-label">Fields</div>
</div>
</div>
<p class="card-text card-description flex-grow-1">
{% if template.description %}
{{ template.description|truncatewords:20 }}
{% else %}
<em class="text-muted">No description provided</em>
{% endif %}
</p>
<div class="card-actions d-grid gap-2 d-md-flex justify-content-md-end">
<a href="{% url 'form_builder' template.id %}" class="btn btn-outline-primary btn-sm action-btn">
<i class="fas fa-edit me-1"></i> Edit
</a>
<button class="btn btn-outline-danger btn-sm action-btn delete"
data-template-id="{{ template.id }}"
data-template-name="{{ template.name }}">
<i class="fas fa-trash me-1"></i> Delete
</button>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
{% if templates.has_other_pages %}
<div class="d-flex justify-content-center mt-4">
<nav aria-label="Page navigation">
<ul class="pagination mb-0">
{% if templates.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ templates.previous_page_number }}{% if query %}&q={{ query }}{% endif %}" aria-label="Previous">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
{% endif %}
{% for num in templates.paginator.page_range %}
{% if templates.number == num %}
<li class="page-item active"><span class="page-link">{{ num }}</span></li>
{% elif num > templates.number|add:'-3' and num < templates.number|add:'3' %}
<li class="page-item">
<a class="page-link" href="?page={{ num }}{% if query %}&q={{ query }}{% endif %}">{{ num }}</a>
</li>
{% endif %}
{% endfor %}
{% if templates.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ templates.next_page_number }}{% if query %}&q={{ query }}{% endif %}" aria-label="Next">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
{% endif %}
</ul>
</nav>
</div>
{% endif %}
{% else %}
<div class="empty-state">
<i class="fas fa-file-contract"></i>
<h3 class="h4 mb-3">No Form Templates Found</h3>
<p class="mb-4">
{% if query %}No templates match your search "{{ query }}".{% else %}You haven't created any form templates yet.{% endif %}
</p>
<a href="{% url 'form_builder' %}" class="btn btn-primary">
<i class="fas fa-plus me-1"></i> Create Your First Template
</a>
</div>
{% endif %}
</div>
<!-- Delete Confirmation Modal -->
{% include 'includes/delete_modal.html' %}
{% endblock %}
{% block extra_js %}
<script>
// Initialize Bootstrap modal
const deleteModal = new bootstrap.Modal(document.getElementById('deleteModal'));
// CSRF Token for AJAX requests
// Create toast container if it doesn't exist
let toastContainer = document.querySelector('.toast-container');
if (!toastContainer) {
toastContainer = document.createElement('div');
toastContainer.className = 'toast-container';
document.body.appendChild(toastContainer);
}
// Function to create toast notification
function createToast(message, type = 'success') {
const toastId = 'toast-' + Date.now();
const toastHtml = `
<div id="${toastId}" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header">
<i class="fas fa-${type === 'success' ? 'check-circle text-success' : 'exclamation-circle text-danger'} me-2"></i>
<strong class="me-auto">${type === 'success' ? 'Success' : 'Error'}</strong>
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
<div class="toast-body">
${message}
</div>
</div>
`;
toastContainer.insertAdjacentHTML('beforeend', toastHtml);
const toastElement = document.getElementById(toastId);
const toast = new bootstrap.Toast(toastElement);
toast.show();
// Remove toast element after it's hidden
toastElement.addEventListener('hidden.bs.toast', () => {
toastElement.remove();
});
}
// Search functionality
document.getElementById('searchBtn').addEventListener('click', function() {
const query = document.getElementById('searchInput').value;
window.location.href = query ? `?q=${encodeURIComponent(query)}` : '?';
});
document.getElementById('searchInput').addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
document.getElementById('searchBtn').click();
}
});
// Delete modal functionality
let templateToDelete = null;
document.querySelectorAll('.delete').forEach(button => {
button.addEventListener('click', function() {
const templateId = this.dataset.templateId;
const templateName = this.dataset.templateName;
templateToDelete = templateId;
document.getElementById('templateNameToDelete').textContent = templateName;
deleteModal.show();
});
});
// Handle form submission in delete modal
document.getElementById('deleteForm').addEventListener('submit', async function(e) {
e.preventDefault();
if (!templateToDelete) return;
try {
const response = await fetch(`/api/templates/${templateToDelete}/delete/`, {
method: 'DELETE',
headers: {
'X-CSRFToken': csrfToken,
'X-Requested-With': 'XMLHttpRequest'
}
});
const result = await response.json();
if (result.success) {
// Show success toast
createToast(result.message);
// Hide the modal
deleteModal.hide();
// Remove the template card from the DOM after a short delay
setTimeout(() => {
// Find and remove the card with matching template ID
const cardToRemove = document.querySelector(`[data-template-id="${templateToDelete}"]`).closest('.col-lg-4');
if (cardToRemove) {
cardToRemove.style.transition = 'opacity 0.3s ease-out, transform 0.3s ease-out';
cardToRemove.style.opacity = '0';
cardToRemove.style.transform = 'scale(0.8)';
setTimeout(() => {
cardToRemove.remove();
// Check if any templates remain
const remainingCards = document.querySelectorAll('.col-lg-4');
if (remainingCards.length === 0) {
// Redirect to empty state
window.location.reload();
}
}, 300);
}
}, 1000);
} else {
// Show error toast
createToast('Error: ' + result.error, 'error');
}
} catch (error) {
console.error('Error:', error);
createToast('An error occurred while deleting the template.', 'error');
}
templateToDelete = null;
});
// Handle modal close event
document.getElementById('deleteModal').addEventListener('hidden.bs.modal', function() {
templateToDelete = null;
});
</script>
{% endblock %}

File diff suppressed because it is too large Load Diff