35955-vm/core/views.py
Flatlogic Bot 7be9310a96 v1.1
2025-11-23 01:09:28 +00:00

534 lines
22 KiB
Python

import os
import platform
from django import get_version as django_version
from django.shortcuts import render
from django.utils import timezone
from django.views.generic import ListView
from .models import Customer
def home(request):
"""Render the landing screen with loader and environment details."""
host_name = request.get_host().lower()
agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic"
now = timezone.now()
context = {
"project_name": "New Style",
"agent_brand": agent_brand,
"django_version": django_version(),
"python_version": platform.python_version(),
"current_time": now,
"host_name": host_name,
"project_description": os.getenv("PROJECT_DESCRIPTION", ""),
"project_image_url": os.getenv("PROJECT_IMAGE_URL", ""),
}
return render(request, "core/index.html", context)
def article_detail(request):
"""Render the article detail screen."""
host_name = request.get_host().lower()
agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic"
now = timezone.now()
context = {
"project_name": "New Style",
"agent_brand": agent_brand,
"django_version": django_version(),
"python_version": platform.python_version(),
"current_time": now,
"host_name": host_name,
"project_description": os.getenv("PROJECT_DESCRIPTION", ""),
"project_image__url": os.getenv("PROJECT_IMAGE_URL", ""),
}
return render(request, "core/article_detail.html", context)
from django.conf import settings
from django.core.paginator import Paginator
from django.db.models import Q, Count, Subquery, OuterRef
from .models import User, ContactHistory, Opportunity, ActivityLog
from django.shortcuts import get_object_or_404
from django.views import View
class CustomerListView(ListView):
model = Customer
template_name = 'core/customer_list.html'
context_object_name = 'customers'
paginate_by = 25 # Default page size
def get_queryset(self):
queryset = super().get_queryset().select_related('owner')
# Filter persistence
if self.request.GET.get('clear_filters'):
for key in list(self.request.session.keys()):
if key.startswith('customer_list_filter_'):
del self.request.session[key]
filter_params = {
'show_deleted': self.request.GET.get('show_deleted'),
'q': self.request.GET.get('q'),
'status': self.request.GET.get('status'),
'owner': self.request.GET.get('owner'),
}
for key, value in filter_params.items():
if value is not None:
self.request.session[f'customer_list_filter_{key}'] = value
else:
filter_params[key] = self.request.session.get(f'customer_list_filter_{key}')
# Filter by deletion status
if not filter_params['show_deleted']:
queryset = queryset.filter(is_deleted=False)
# Search
if filter_params['q']:
queryset = queryset.filter(
Q(name__icontains=filter_params['q']) |
Q(email__icontains=filter_params['q']) |
Q(id__icontains=filter_params['q'])
)
# Filter
if filter_params['status']:
queryset = queryset.filter(status=filter_params['status'])
if filter_params['owner']:
queryset = queryset.filter(owner_id=filter_params['owner'])
# Annotate with last contact and opportunity count
last_contact_subquery = ContactHistory.objects.filter(
customer=OuterRef('pk')
).order_by('-created_at').values('created_at')[:1]
queryset = queryset.annotate(
last_contact=Subquery(last_contact_subquery),
opportunity_count=Count('opportunities', distinct=True)
).order_by('name')
return queryset
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
page_size = self.request.GET.get('page_size', self.paginate_by)
paginator = Paginator(self.get_queryset(), page_size)
page_number = self.request.GET.get('page')
page_obj = paginator.get_page(page_number)
context['page_obj'] = page_obj
context['page_size'] = int(page_size)
context['paginator'] = paginator
context['customer_count'] = paginator.count
context['statuses'] = Customer.STATUS_CHOICES
context['owners'] = User.objects.filter(is_staff=True) # Assuming staff are owners
# Persist query params
context['query'] = self.request.session.get('customer_list_filter_q', '')
context['selected_status'] = self.request.session.get('customer_list_filter_status', '')
context['selected_owner'] = self.request.session.get('customer_list_filter_owner', '')
context['show_deleted'] = self.request.session.get('customer_list_filter_show_deleted', False)
is_filter_active = any([
self.request.session.get('customer_list_filter_q'),
self.request.session.get('customer_list_filter_status'),
self.request.session.get('customer_list_filter_owner'),
self.request.session.get('customer_list_filter_show_deleted'),
self.request.session.get('activity_feed_filter_activity_user'),
self.request.session.get('activity_feed_filter_activity_action'),
self.request.session.get('activity_feed_filter_activity_date_range'),
])
context['is_filter_active'] = is_filter_active
highlighted_customer_ids = []
updated_customer_id = self.request.session.pop('updated_customer_id', None)
if updated_customer_id:
highlighted_customer_ids.append(updated_customer_id)
deleted_customer_id = self.request.session.pop('deleted_customer_id', None)
if deleted_customer_id:
highlighted_customer_ids.append(deleted_customer_id)
restored_customer_id = self.request.session.pop('restored_customer_id', None)
if restored_customer_id:
highlighted_customer_ids.append(restored_customer_id)
restored_customer_ids = self.request.session.pop('restored_customer_ids', None)
if restored_customer_ids:
highlighted_customer_ids.extend([int(cid) for cid in restored_customer_ids])
context['highlighted_customer_ids'] = highlighted_customer_ids
context['undo_timeout'] = settings.UNDO_TIMEOUT
context['toast_position'] = settings.TOAST_POSITION
# Activity feed filtering
if self.request.GET.get('clear_filters'):
for key in list(self.request.session.keys()):
if key.startswith('activity_feed_filter_'):
del self.request.session[key]
activity_sort = self.request.GET.get('activity_sort', '-timestamp')
activity_page_size = self.request.GET.get('activity_page_size', 5)
activity_filter_params = {
'activity_user': self.request.GET.get('activity_user'),
'activity_action': self.request.GET.get('activity_action'),
'activity_date_range': self.request.GET.get('activity_date_range'),
'activity_sort': activity_sort,
'activity_page_size': activity_page_size,
}
for key, value in activity_filter_params.items():
if value is not None:
self.request.session[f'activity_feed_filter_{key}'] = value
else:
activity_filter_params[key] = self.request.session.get(f'activity_feed_filter_{key}')
activity_logs = ActivityLog.objects.select_related('actor').prefetch_related('content_object').all()
if activity_filter_params['activity_user']:
activity_logs = activity_logs.filter(actor_id=activity_filter_params['activity_user'])
if activity_filter_params['activity_action']:
activity_logs = activity_logs.filter(action=activity_filter_params['activity_action'])
if activity_filter_params['activity_date_range']:
try:
start_date, end_date = activity_filter_params['activity_date_range'].split(' to ')
activity_logs = activity_logs.filter(timestamp__date__gte=start_date, timestamp__date__lte=end_date)
except ValueError:
# Handle cases where the date range is not in the expected format
pass
if activity_filter_params['activity_sort']:
sort_param = activity_filter_params['activity_sort']
if sort_param == 'actor':
activity_logs = activity_logs.order_by('actor__email')
elif sort_param == '-actor':
activity_logs = activity_logs.order_by('-actor__email')
else:
activity_logs = activity_logs.order_by(sort_param)
# Activity summary
activity_summary = activity_logs.values('action').annotate(count=Count('action'))
context['activity_summary'] = {item['action']: item['count'] for item in activity_summary}
# Paginate activity logs
activity_page_size = activity_filter_params.get('activity_page_size', 5)
activity_paginator = Paginator(activity_logs, activity_page_size)
activity_page_number = self.request.GET.get('activity_page')
activity_page_obj = activity_paginator.get_page(activity_page_number)
context['activity_logs'] = activity_page_obj
context['activity_paginator'] = activity_paginator
context['activity_log_count'] = activity_paginator.count
context['activity_page_size'] = int(activity_page_size)
# Get users and actions for filter dropdowns
user_ids = ActivityLog.objects.values_list('actor_id', flat=True).distinct()
context['activity_users'] = User.objects.filter(id__in=user_ids)
context['activity_actions'] = ActivityLog.objects.values_list('action', flat=True).distinct()
context['selected_activity_user'] = self.request.session.get('activity_feed_filter_activity_user', '')
context['selected_activity_action'] = self.request.session.get('activity_feed_filter_activity_action', '')
context['selected_activity_date_range'] = self.request.session.get('activity_feed_filter_activity_date_range', '')
context['selected_activity_sort'] = self.request.session.get('activity_feed_filter_activity_sort', '-timestamp')
from datetime import timedelta
twenty_four_hours_ago = timezone.now() - timedelta(hours=24)
context['recent_activity_count'] = ActivityLog.objects.filter(timestamp__gte=twenty_four_hours_ago).count()
return context
class CustomerDetailView(View):
template_name = "core/customer_detail.html"
def get(self, request, pk):
customer = get_object_or_404(
Customer.objects.select_related('owner', 'tenant')
.prefetch_related('opportunities', 'leads', 'contacthistory_set', 'notes'),
pk=pk
)
context = {
"customer": customer,
"opportunities": customer.opportunities.all(),
"leads": customer.leads.all(),
"contact_history": customer.contacthistory_set.all(),
"notes": customer.notes.all(),
"opportunity_count": customer.opportunities.count(),
"lead_count": customer.leads.count(),
"note_count": customer.notes.count(),
}
return render(request, self.template_name, context)
from django.urls import reverse_lazy
from django.http import HttpResponseRedirect
from django.contrib import messages
from django.views.generic import DetailView
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from .forms import CustomerForm, OpportunityForm
from django.forms.models import model_to_dict
class CustomerUpdateView(UpdateView):
model = Customer
form_class = CustomerForm
template_name = 'core/customer_form.html'
def get_success_url(self):
return reverse_lazy('customer_list')
def form_valid(self, form):
# Capture previous state for changed fields only
previous_state = {key: str(getattr(self.object, key)) for key in form.changed_data}
response = super().form_valid(form)
# Capture new state for changed fields
changed_data = {key: {'old': previous_state[key], 'new': str(form.cleaned_data[key])} for key in form.changed_data}
if form.changed_data:
undo_data = {
'customer_id': self.object.pk,
'previous_state': previous_state,
'timestamp': timezone.now().isoformat(),
'session_key': self.request.session.session_key,
'action': 'update'
}
self.request.session['undo_data'] = undo_data
undo_url = reverse_lazy('customer_undo')
message = f'{{"message": "Customer updated successfully.", "undo_url": "{undo_url}"}}'
messages.success(self.request, message)
ActivityLog.objects.create(
actor=self.request.user,
action=f'updated a {self.object.__class__.__name__}',
content_object=self.object,
details=changed_data
)
return response
def form_invalid(self, form):
messages.error(self.request, 'Please correct the errors below.')
return super().form_invalid(form)
class CustomerDeleteView(DeleteView):
model = Customer
template_name = 'core/customer_confirm_delete.html'
success_url = reverse_lazy('customer_list')
def form_valid(self, form):
self.object = self.get_object()
success_url = self.get_success_url()
# Store data required for restoration
previous_state = model_to_dict(self.object, fields=['name', 'email', 'phone', 'address', 'status', 'owner'])
# model_to_dict gives owner id, which is what we want.
undo_data = {
'customer_id': self.object.pk,
'previous_state': previous_state,
'timestamp': timezone.now().isoformat(),
'session_key': self.request.session.session_key,
'action': 'delete'
}
self.request.session['undo_data'] = undo_data
self.object.delete(user=self.request.user)
undo_url = reverse_lazy('customer_undo')
message = f'{{"message": "Customer deleted successfully.", "undo_url": "{undo_url}"}}'
messages.success(self.request, message)
return HttpResponseRedirect(success_url)
class CustomerUndoView(View):
def get(self, request):
undo_data = request.session.get('undo_data')
if not undo_data or undo_data.get('session_key') != request.session.session_key:
messages.error(request, 'Invalid undo request.')
return HttpResponseRedirect(reverse_lazy('customer_list'))
try:
undo_timestamp = timezone.datetime.fromisoformat(undo_data['timestamp'])
if (timezone.now() - undo_timestamp).total_seconds() > settings.UNDO_TIMEOUT:
messages.error(request, 'The undo link has expired.')
request.session.pop('undo_data', None)
return HttpResponseRedirect(reverse_lazy('customer_list'))
except (ValueError, TypeError):
messages.error(request, 'Invalid undo timestamp.')
request.session.pop('undo_data', None)
return HttpResponseRedirect(reverse_lazy('customer_list'))
action = undo_data.get('action')
customer_id = undo_data.get('customer_id')
previous_state = undo_data.get('previous_state')
try:
if action == 'update':
customer = get_object_or_404(Customer, pk=customer_id)
for key, value in previous_state.items():
# Handle foreign keys
if key == 'owner' and value:
value = get_object_or_404(User, pk=value)
setattr(customer, key, value)
customer.save()
ActivityLog.objects.create(actor=request.user, action='undid update for', content_object=customer)
messages.success(request, '{"message": "Update undone successfully.", "icon": "success"}')
request.session['restored_customer_id'] = customer.pk
elif action == 'delete':
# Check if customer is already restored (e.g., from bulk restore)
if Customer.objects.filter(pk=customer_id, is_deleted=False).exists():
messages.warning(request, 'Customer already restored.')
else:
# If soft-deleted, restore it
if Customer.objects.filter(pk=customer_id).exists():
customer = Customer.objects.get(pk=customer_id)
customer.restore(user=request.user)
# If hard-deleted, recreate it
else:
owner_id = previous_state.pop('owner', None)
if owner_id:
previous_state['owner'] = get_object_or_404(User, pk=owner_id)
customer = Customer.objects.create(pk=customer_id, **previous_state)
ActivityLog.objects.create(actor=request.user, action='undid delete for', content_object=customer)
messages.success(request, '{"message": "1 customer restored successfully.", "icon": "success"}')
request.session['restored_customer_id'] = customer_id
except Exception as e:
messages.error(request, f"An error occurred during undo: {e}")
request.session.pop('undo_data', None)
return HttpResponseRedirect(reverse_lazy('customer_list'))
class CustomerRestoreView(View):
def get(self, request, pk):
customer = get_object_or_404(Customer, pk=pk)
customer.restore(user=request.user)
messages.success(request, '{"message": "1 customer restored successfully.", "icon": "success"}')
request.session['restored_customer_id'] = pk
return HttpResponseRedirect(reverse_lazy('customer_list'))
class CustomerBulkActionView(View):
def post(self, request):
selected_customers = request.POST.getlist('selected_customers')
action = request.POST.get('action')
if action == 'restore':
restored_count = 0
restored_ids = []
for customer_id in selected_customers:
customer = get_object_or_404(Customer, pk=customer_id)
customer.restore(user=request.user)
restored_count += 1
restored_ids.append(customer_id)
if restored_count > 0:
messages.success(request, f'{{"message": "{{restored_count}} customers restored successfully.", "icon": "success"}}')
request.session['restored_customer_ids'] = restored_ids
elif action == 'delete':
deleted_count = 0
deleted_ids = []
for customer_id in selected_customers:
customer = get_object_or_404(Customer, pk=customer_id)
customer.delete(user=request.user)
deleted_count += 1
deleted_ids.append(customer_id)
if deleted_count > 0:
request.session['bulk_deleted_customer_ids'] = deleted_ids
undo_url = reverse_lazy('customer_bulk_undo')
message = f'{{"message": "{deleted_count} customers deleted successfully.", "undo_url": "{undo_url}"}}'
messages.success(self.request, message)
return HttpResponseRedirect(reverse_lazy('customer_list'))
class CustomerBulkUndoView(View):
def get(self, request):
customer_ids = request.session.pop('bulk_deleted_customer_ids', [])
if customer_ids:
restored_count = 0
restored_ids = []
for customer_id in customer_ids:
customer = get_object_or_404(Customer, pk=customer_id)
customer.restore(user=request.user)
restored_count += 1
restored_ids.append(customer_id)
if restored_count > 0:
messages.success(request, f'{{"message": "{{restored_count}} customers restored successfully.", "icon": "success"}}')
request.session['restored_customer_ids'] = restored_ids
return HttpResponseRedirect(reverse_lazy('customer_list'))
class OpportunityListView(ListView):
model = Opportunity
template_name = 'core/opportunity_list.html'
context_object_name = 'opportunities'
paginate_by = 25
def get_queryset(self):
queryset = super().get_queryset().select_related('lead__customer', 'created_by').filter(is_deleted=False)
return queryset
class OpportunityDetailView(DetailView):
model = Opportunity
template_name = 'core/opportunity_detail.html'
context_object_name = 'opportunity'
class OpportunityCreateView(CreateView):
model = Opportunity
form_class = OpportunityForm
template_name = 'core/opportunity_form.html'
def get_success_url(self):
return reverse_lazy('opportunity_detail', kwargs={'pk': self.object.pk})
def form_valid(self, form):
form.instance.created_by = self.request.user
messages.success(self.request, 'Opportunity created successfully.')
return super().form_valid(form)
class OpportunityUpdateView(UpdateView):
model = Opportunity
form_class = OpportunityForm
template_name = 'core/opportunity_form.html'
def get_success_url(self):
return reverse_lazy('opportunity_detail', kwargs={'pk': self.object.pk})
def form_valid(self, form):
form.instance.updated_by = self.request.user
messages.success(self.request, 'Opportunity updated successfully.')
return super().form_valid(form)
class OpportunityDeleteView(DeleteView):
model = Opportunity
template_name = 'core/opportunity_confirm_delete.html'
success_url = reverse_lazy('opportunity_list')
def delete(self, request, *args, **kwargs):
self.object = self.get_object()
success_url = self.get_success_url()
self.object.delete(user=request.user)
messages.success(request, 'Opportunity deleted successfully.')
return HttpResponseRedirect(success_url)