standards app done
This commit is contained in:
parent
7d6d75b10b
commit
4dd3c3e505
@ -35,8 +35,8 @@ class UserAdmin(BaseUserAdmin):
|
||||
ordering = ['-date_joined']
|
||||
|
||||
fieldsets = (
|
||||
(None, {'fields': ('email', 'username', 'password')}),
|
||||
(_('Personal info'), {'fields': ('first_name', 'last_name', 'phone', 'employee_id')}),
|
||||
(None, {'fields': ('email', 'password')}),
|
||||
(_('Personal info'), {'fields': ('first_name', 'last_name', 'username', 'phone', 'employee_id')}),
|
||||
(_('Organization'), {'fields': ('hospital', 'department')}),
|
||||
(_('Profile'), {'fields': ('avatar', 'bio', 'language')}),
|
||||
(_('Permissions'), {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 6.0.1 on 2026-01-15 12:02
|
||||
# Generated by Django 6.0 on 2026-01-12 09:50
|
||||
|
||||
import django.utils.timezone
|
||||
import uuid
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 6.0.1 on 2026-01-15 12:02
|
||||
# Generated by Django 6.0 on 2026-01-12 09:50
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 6.0.1 on 2026-01-15 12:02
|
||||
# Generated by Django 6.0 on 2026-01-12 09:50
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 6.0.1 on 2026-01-15 12:02
|
||||
# Generated by Django 6.0 on 2026-01-12 09:50
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 6.0.1 on 2026-01-15 12:02
|
||||
# Generated by Django 6.0 on 2026-01-12 09:50
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 6.0.1 on 2026-01-15 12:02
|
||||
# Generated by Django 6.0 on 2026-01-12 09:50
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
@ -39,11 +39,11 @@ class ComplaintAdmin(admin.ModelAdmin):
|
||||
list_display = [
|
||||
'title_preview', 'patient', 'hospital', 'category',
|
||||
'severity_badge', 'status_badge', 'sla_indicator',
|
||||
'assigned_to', 'created_at'
|
||||
'created_by', 'assigned_to', 'created_at'
|
||||
]
|
||||
list_filter = [
|
||||
'status', 'severity', 'priority', 'category', 'source',
|
||||
'is_overdue', 'hospital', 'created_at'
|
||||
'is_overdue', 'hospital', 'created_by', 'created_at'
|
||||
]
|
||||
search_fields = [
|
||||
'title', 'description', 'patient__mrn',
|
||||
@ -66,6 +66,9 @@ class ComplaintAdmin(admin.ModelAdmin):
|
||||
('Classification', {
|
||||
'fields': ('priority', 'severity', 'source')
|
||||
}),
|
||||
('Creator Tracking', {
|
||||
'fields': ('created_by',)
|
||||
}),
|
||||
('Status & Assignment', {
|
||||
'fields': ('status', 'assigned_to', 'assigned_at')
|
||||
}),
|
||||
@ -94,7 +97,8 @@ class ComplaintAdmin(admin.ModelAdmin):
|
||||
qs = super().get_queryset(request)
|
||||
return qs.select_related(
|
||||
'patient', 'hospital', 'department', 'staff',
|
||||
'assigned_to', 'resolved_by', 'closed_by', 'resolution_survey'
|
||||
'assigned_to', 'resolved_by', 'closed_by', 'resolution_survey',
|
||||
'created_by'
|
||||
)
|
||||
|
||||
def title_preview(self, obj):
|
||||
@ -219,9 +223,9 @@ class InquiryAdmin(admin.ModelAdmin):
|
||||
"""Inquiry admin"""
|
||||
list_display = [
|
||||
'subject_preview', 'patient', 'contact_name',
|
||||
'hospital', 'category', 'status', 'assigned_to', 'created_at'
|
||||
'hospital', 'category', 'status', 'created_by', 'assigned_to', 'created_at'
|
||||
]
|
||||
list_filter = ['status', 'category', 'source', 'hospital', 'created_at']
|
||||
list_filter = ['status', 'category', 'source', 'hospital', 'created_by', 'created_at']
|
||||
search_fields = [
|
||||
'subject', 'message', 'contact_name', 'contact_phone',
|
||||
'patient__mrn', 'patient__first_name', 'patient__last_name'
|
||||
@ -242,6 +246,9 @@ class InquiryAdmin(admin.ModelAdmin):
|
||||
('Inquiry Details', {
|
||||
'fields': ('subject', 'message', 'category', 'source')
|
||||
}),
|
||||
('Creator Tracking', {
|
||||
'fields': ('created_by',)
|
||||
}),
|
||||
('Status & Assignment', {
|
||||
'fields': ('status', 'assigned_to')
|
||||
}),
|
||||
@ -259,7 +266,7 @@ class InquiryAdmin(admin.ModelAdmin):
|
||||
qs = super().get_queryset(request)
|
||||
return qs.select_related(
|
||||
'patient', 'hospital', 'department',
|
||||
'assigned_to', 'responded_by'
|
||||
'assigned_to', 'responded_by', 'created_by'
|
||||
)
|
||||
|
||||
def subject_preview(self, obj):
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
# Generated by Django 6.0.1 on 2026-01-15 12:02
|
||||
# Generated by Django 6.0 on 2026-01-12 09:50
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
@ -64,10 +65,6 @@ class Migration(migrations.Migration):
|
||||
('email_sent_at', models.DateTimeField(blank=True, help_text='When the explanation request email was sent', null=True)),
|
||||
('responded_at', models.DateTimeField(blank=True, help_text='When the explanation was submitted', null=True)),
|
||||
('request_message', models.TextField(blank=True, help_text='Optional message sent with the explanation request')),
|
||||
('sla_due_at', models.DateTimeField(blank=True, db_index=True, help_text='SLA deadline for staff to submit explanation', null=True)),
|
||||
('is_overdue', models.BooleanField(db_index=True, default=False, help_text='Explanation request is overdue')),
|
||||
('reminder_sent_at', models.DateTimeField(blank=True, help_text='Reminder sent to staff about overdue explanation', null=True)),
|
||||
('escalated_at', models.DateTimeField(blank=True, help_text='When explanation was escalated to manager', null=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Complaint Explanation',
|
||||
@ -168,25 +165,6 @@ class Migration(migrations.Migration):
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ExplanationSLAConfig',
|
||||
fields=[
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('response_hours', models.IntegerField(default=48, help_text='Hours staff has to submit explanation')),
|
||||
('reminder_hours_before', models.IntegerField(default=12, help_text='Send reminder X hours before deadline')),
|
||||
('auto_escalate_enabled', models.BooleanField(default=True, help_text='Automatically escalate to manager if no response')),
|
||||
('escalation_hours_overdue', models.IntegerField(default=0, help_text='Escalate X hours after overdue (0 = immediately)')),
|
||||
('max_escalation_levels', models.IntegerField(default=3, help_text='Maximum levels to escalate up staff hierarchy')),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Explanation SLA Config',
|
||||
'verbose_name_plural': 'Explanation SLA Configs',
|
||||
'ordering': ['hospital'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Inquiry',
|
||||
fields=[
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 6.0.1 on 2026-01-15 12:02
|
||||
# Generated by Django 6.0 on 2026-01-12 09:50
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 6.0.1 on 2026-01-15 12:02
|
||||
# Generated by Django 6.0 on 2026-01-12 09:50
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
@ -57,11 +57,6 @@ class Migration(migrations.Migration):
|
||||
name='complaint',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='explanations', to='complaints.complaint'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='complaintexplanation',
|
||||
name='escalated_to_manager',
|
||||
field=models.ForeignKey(blank=True, help_text="Escalated to this explanation (manager's explanation request)", null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='escalated_from_staff', to='complaints.complaintexplanation'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='complaintexplanation',
|
||||
name='requested_by',
|
||||
@ -107,11 +102,6 @@ class Migration(migrations.Migration):
|
||||
name='explanation',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='attachments', to='complaints.complaintexplanation'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='explanationslaconfig',
|
||||
name='hospital',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='explanation_sla_configs', to='organizations.hospital'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='inquiry',
|
||||
name='assigned_to',
|
||||
@ -214,10 +204,6 @@ class Migration(migrations.Migration):
|
||||
model_name='escalationrule',
|
||||
index=models.Index(fields=['hospital', 'is_active'], name='complaints__hospita_3c8bac_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='explanationslaconfig',
|
||||
index=models.Index(fields=['hospital', 'is_active'], name='complaints__hospita_fe4ec5_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='inquiry',
|
||||
index=models.Index(fields=['status', '-created_at'], name='complaints__status_3d0678_idx'),
|
||||
|
||||
@ -180,6 +180,7 @@ class Complaint(UUIDModel, TimeStampedModel):
|
||||
blank=True,
|
||||
related_name='created_complaints',
|
||||
help_text="User who created this complaint (SourceUser or Patient)"
|
||||
help_text="Source of complaint"
|
||||
)
|
||||
|
||||
# Status and workflow
|
||||
@ -760,17 +761,7 @@ class Inquiry(UUIDModel, TimeStampedModel):
|
||||
related_name="inquiries",
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Source of inquiry",
|
||||
)
|
||||
|
||||
# Creator tracking
|
||||
created_by = models.ForeignKey(
|
||||
'accounts.User',
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name='created_inquiries',
|
||||
help_text="User who created this inquiry (SourceUser or Patient)"
|
||||
help_text="Source of inquiry"
|
||||
)
|
||||
|
||||
# Status
|
||||
|
||||
@ -1029,8 +1029,13 @@ def inquiry_create(request):
|
||||
from .models import Inquiry
|
||||
from .forms import InquiryForm
|
||||
from apps.organizations.models import Patient
|
||||
|
||||
if request.method == "POST":
|
||||
|
||||
# Determine base layout based on user type
|
||||
from apps.px_sources.models import SourceUser
|
||||
source_user = SourceUser.objects.filter(user=request.user).first()
|
||||
base_layout = 'layouts/source_user_base.html' if source_user else 'layouts/base.html'
|
||||
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
# Get form data
|
||||
patient_id = request.POST.get("patient_id", None)
|
||||
@ -1086,7 +1091,9 @@ def inquiry_create(request):
|
||||
hospitals = hospitals.filter(id=request.user.hospital.id)
|
||||
|
||||
context = {
|
||||
"hospitals": hospitals,
|
||||
'hospitals': hospitals,
|
||||
'base_layout': base_layout,
|
||||
'source_user': source_user,
|
||||
}
|
||||
|
||||
return render(request, "complaints/inquiry_form.html", context)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 6.0.1 on 2026-01-15 12:02
|
||||
# Generated by Django 6.0 on 2026-01-12 09:50
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 6.0.1 on 2026-01-15 12:02
|
||||
# Generated by Django 6.0 on 2026-01-12 09:50
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 6.0.1 on 2026-01-15 12:02
|
||||
# Generated by Django 6.0 on 2026-01-12 09:50
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 6.0.1 on 2026-01-15 12:02
|
||||
# Generated by Django 6.0 on 2026-01-12 09:50
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 6.0.1 on 2026-01-15 12:02
|
||||
# Generated by Django 6.0 on 2026-01-12 09:50
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 6.0.1 on 2026-01-15 12:02
|
||||
# Generated by Django 6.0 on 2026-01-12 09:50
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
@ -0,0 +1,38 @@
|
||||
# Generated by Django 6.0.1 on 2026-01-20 12:38
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('journeys', '0002_initial'),
|
||||
('surveys', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='patientjourneystagetemplate',
|
||||
name='auto_send_survey',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='patientjourneystagetemplate',
|
||||
name='survey_delay_hours',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='patientjourneytemplate',
|
||||
name='post_discharge_survey_delay_hours',
|
||||
field=models.IntegerField(default=1, help_text='Hours after discharge to send the survey'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='patientjourneytemplate',
|
||||
name='send_post_discharge_survey',
|
||||
field=models.BooleanField(default=False, help_text='Send a comprehensive survey after patient discharge'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='patientjourneystagetemplate',
|
||||
name='survey_template',
|
||||
field=models.ForeignKey(blank=True, help_text='Survey template containing questions for this stage (merged into post-discharge survey)', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='journey_stages', to='surveys.surveytemplate'),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,37 @@
|
||||
# Generated by Django 6.0.1 on 2026-01-20 13:49
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('journeys', '0003_remove_patientjourneystagetemplate_auto_send_survey_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='patientjourneystageinstance',
|
||||
name='completed_by_event',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='patientjourneystageinstance',
|
||||
name='survey_instance',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='patientjourneystageinstance',
|
||||
name='survey_sent_at',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='patientjourneystagetemplate',
|
||||
name='description',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='patientjourneystagetemplate',
|
||||
name='requires_department',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='patientjourneystagetemplate',
|
||||
name='requires_physician',
|
||||
),
|
||||
]
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 6.0.1 on 2026-01-15 12:02
|
||||
# Generated by Django 6.0 on 2026-01-12 09:50
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 6.0.1 on 2026-01-15 12:02
|
||||
# Generated by Django 6.0 on 2026-01-12 09:50
|
||||
|
||||
import apps.observations.models
|
||||
import django.db.models.deletion
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 6.0.1 on 2026-01-15 12:02
|
||||
# Generated by Django 6.0 on 2026-01-12 09:50
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
@ -129,7 +129,6 @@ class Migration(migrations.Migration):
|
||||
('license_number', models.CharField(blank=True, max_length=100, null=True, unique=True)),
|
||||
('specialization', models.CharField(blank=True, max_length=200)),
|
||||
('email', models.EmailField(blank=True, max_length=254)),
|
||||
('phone', models.CharField(blank=True, max_length=20, verbose_name='Phone Number')),
|
||||
('employee_id', models.CharField(db_index=True, max_length=50, unique=True)),
|
||||
('name', models.CharField(blank=True, max_length=300, verbose_name='Full Name (Original)')),
|
||||
('country', models.CharField(blank=True, max_length=100, verbose_name='Country')),
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 6.0.1 on 2026-01-15 12:02
|
||||
# Generated by Django 6.0 on 2026-01-12 09:50
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 6.0.1 on 2026-01-15 12:02
|
||||
# Generated by Django 6.0 on 2026-01-12 09:50
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 6.0.1 on 2026-01-15 12:02
|
||||
# Generated by Django 6.0 on 2026-01-12 09:50
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 6.0.1 on 2026-01-15 12:02
|
||||
# Generated by Django 6.0 on 2026-01-12 09:50
|
||||
|
||||
import apps.references.models
|
||||
import django.db.models.deletion
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 6.0.1 on 2026-01-15 12:02
|
||||
# Generated by Django 6.0 on 2026-01-12 09:50
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
@ -0,0 +1,57 @@
|
||||
# Generated by Django 6.0.1 on 2026-01-20 13:49
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('surveys', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='surveyinstance',
|
||||
name='journey_stage_instance',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='surveyinstance',
|
||||
name='satisfaction_feedback_sent',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='surveyinstance',
|
||||
name='satisfaction_feedback_sent_at',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='surveyquestion',
|
||||
name='branch_logic',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='surveyquestion',
|
||||
name='help_text',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='surveyquestion',
|
||||
name='help_text_ar',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='surveyquestion',
|
||||
name='weight',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='surveyresponse',
|
||||
name='response_time_seconds',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='surveytemplate',
|
||||
name='description',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='surveytemplate',
|
||||
name='description_ar',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='surveytemplate',
|
||||
name='version',
|
||||
),
|
||||
]
|
||||
96
apps/surveys/migrations/0003_add_survey_tracking.py
Normal file
96
apps/surveys/migrations/0003_add_survey_tracking.py
Normal file
@ -0,0 +1,96 @@
|
||||
# Generated migration for survey tracking features
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('surveys', '0002_remove_surveyinstance_journey_stage_instance_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
# Add tracking fields to SurveyInstance
|
||||
migrations.AddField(
|
||||
model_name='surveyinstance',
|
||||
name='open_count',
|
||||
field=models.PositiveIntegerField(default=0, help_text='Number of times the survey link was opened'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='surveyinstance',
|
||||
name='last_opened_at',
|
||||
field=models.DateTimeField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='surveyinstance',
|
||||
name='time_spent_seconds',
|
||||
field=models.PositiveIntegerField(default=0, help_text='Total time spent on survey in seconds'),
|
||||
),
|
||||
|
||||
# Update status field choices
|
||||
migrations.AlterField(
|
||||
model_name='surveyinstance',
|
||||
name='status',
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
('sent', 'Sent'),
|
||||
('viewed', 'Viewed'),
|
||||
('in_progress', 'In Progress'),
|
||||
('completed', 'Completed'),
|
||||
('abandoned', 'Abandoned'),
|
||||
('expired', 'Expired'),
|
||||
('cancelled', 'Cancelled'),
|
||||
],
|
||||
default='sent',
|
||||
max_length=20,
|
||||
help_text='Current status of the survey instance'
|
||||
),
|
||||
),
|
||||
|
||||
# Create SurveyTracking model
|
||||
migrations.CreateModel(
|
||||
name='SurveyTracking',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('event_type', models.CharField(
|
||||
choices=[
|
||||
('page_view', 'Page View'),
|
||||
('survey_started', 'Survey Started'),
|
||||
('question_answered', 'Question Answered'),
|
||||
('survey_completed', 'Survey Completed'),
|
||||
('survey_abandoned', 'Survey Abandoned'),
|
||||
('reminder_sent', 'Reminder Sent'),
|
||||
],
|
||||
default='page_view',
|
||||
max_length=20,
|
||||
help_text='Type of tracking event'
|
||||
)),
|
||||
('time_on_page', models.PositiveIntegerField(blank=True, null=True, help_text='Time spent on current page in seconds')),
|
||||
('total_time_spent', models.PositiveIntegerField(default=0, help_text='Total time spent in survey in seconds')),
|
||||
('current_question', models.PositiveIntegerField(blank=True, null=True, help_text='Current question number being viewed')),
|
||||
('user_agent', models.TextField(blank=True, help_text='Browser user agent string')),
|
||||
('ip_address', models.GenericIPAddressField(blank=True, null=True)),
|
||||
('device_type', models.CharField(blank=True, max_length=50)),
|
||||
('browser', models.CharField(blank=True, max_length=50)),
|
||||
('country', models.CharField(blank=True, max_length=100)),
|
||||
('city', models.CharField(blank=True, max_length=100)),
|
||||
('metadata', models.JSONField(blank=True, null=True, default=dict, help_text='Additional tracking metadata')),
|
||||
('survey_instance', models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='tracking_events',
|
||||
to='surveys.surveyinstance'
|
||||
)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Survey Tracking',
|
||||
'verbose_name_plural': 'Survey Tracking Events',
|
||||
'ordering': ['-created_at'],
|
||||
'indexes': [
|
||||
models.Index(fields=['survey_instance', '-created_at'], name='idx_survey_instance_created'),
|
||||
models.Index(fields=['event_type', '-created_at'], name='idx_event_type_created'),
|
||||
models.Index(fields=['ip_address'], name='idx_ip_address'),
|
||||
],
|
||||
},
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,110 @@
|
||||
# Generated by Django 6.0.1 on 2026-01-21 13:54
|
||||
|
||||
import uuid
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('surveys', '0003_add_survey_tracking'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='surveytracking',
|
||||
options={'ordering': ['survey_instance', 'created_at']},
|
||||
),
|
||||
migrations.RemoveIndex(
|
||||
model_name='surveytracking',
|
||||
name='idx_survey_instance_created',
|
||||
),
|
||||
migrations.RemoveIndex(
|
||||
model_name='surveytracking',
|
||||
name='idx_ip_address',
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
model_name='surveytracking',
|
||||
new_name='surveys_sur_event_t_885d23_idx',
|
||||
old_name='idx_event_type_created',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='surveytracking',
|
||||
name='updated_at',
|
||||
field=models.DateTimeField(auto_now=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='surveyinstance',
|
||||
name='last_opened_at',
|
||||
field=models.DateTimeField(blank=True, help_text='Most recent time survey was opened', null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='surveyinstance',
|
||||
name='open_count',
|
||||
field=models.IntegerField(default=0, help_text='Number of times survey link was opened'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='surveyinstance',
|
||||
name='status',
|
||||
field=models.CharField(choices=[('sent', 'Sent (Not Opened)'), ('viewed', 'Viewed (Opened, Not Started)'), ('in_progress', 'In Progress (Started, Not Completed)'), ('completed', 'Completed'), ('abandoned', 'Abandoned (Started but Left)'), ('expired', 'Expired'), ('cancelled', 'Cancelled')], db_index=True, default='sent', max_length=20),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='surveyinstance',
|
||||
name='time_spent_seconds',
|
||||
field=models.IntegerField(blank=True, help_text='Total time spent on survey in seconds', null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='surveytracking',
|
||||
name='browser',
|
||||
field=models.CharField(blank=True, max_length=100),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='surveytracking',
|
||||
name='created_at',
|
||||
field=models.DateTimeField(auto_now_add=True, db_index=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='surveytracking',
|
||||
name='current_question',
|
||||
field=models.IntegerField(blank=True, help_text='Question number when event occurred', null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='surveytracking',
|
||||
name='device_type',
|
||||
field=models.CharField(blank=True, help_text='mobile, tablet, desktop', max_length=50),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='surveytracking',
|
||||
name='event_type',
|
||||
field=models.CharField(choices=[('page_view', 'Page View'), ('survey_started', 'Survey Started'), ('question_answered', 'Question Answered'), ('survey_abandoned', 'Survey Abandoned'), ('survey_completed', 'Survey Completed'), ('reminder_sent', 'Reminder Sent')], db_index=True, max_length=50),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='surveytracking',
|
||||
name='id',
|
||||
field=models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='surveytracking',
|
||||
name='metadata',
|
||||
field=models.JSONField(blank=True, default=dict),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='surveytracking',
|
||||
name='time_on_page',
|
||||
field=models.IntegerField(blank=True, help_text='Time spent on page in seconds', null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='surveytracking',
|
||||
name='total_time_spent',
|
||||
field=models.IntegerField(blank=True, help_text='Total time spent on survey so far in seconds', null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='surveytracking',
|
||||
name='user_agent',
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='surveytracking',
|
||||
index=models.Index(fields=['survey_instance', 'event_type', '-created_at'], name='surveys_sur_survey__9743a1_idx'),
|
||||
),
|
||||
]
|
||||
@ -1,4 +1,5 @@
|
||||
{% extends base_layout %}
|
||||
{% extends base_layout %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
|
||||
@ -156,6 +156,15 @@
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="col-lg-4">
|
||||
<!-- Due Date -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label">{% trans "Due Date" %}</label>
|
||||
<input type="datetime-local" name="due_date" class="form-control"
|
||||
placeholder="{% trans 'Optional due date' %}">
|
||||
<small class="form-text text-muted">
|
||||
{{ _("Leave empty for default")}}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<!-- Help Information -->
|
||||
<div class="alert alert-info">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user