fix queue

This commit is contained in:
javad committed 2026-09-11 18:04:51 +03:30
1 parent 049cc41afc
commit 9a1c99cbd3
14 files changed
+237 -32

No files matched your search

+13 -1
View File
@@ -1,4 +1,16 @@
from django.urls import path
from . import views
app_name = "accounts"
urlpatterns = [path("login/", views.customer_login, name="login"),path("login/password/",views.customer_password_login,name="password_login"), path("login/otp/", views.verify_otp, name="otp"),path("profile/",views.customer_profile,name="profile"), path("logout/", views.logout_view, name="logout"), path("staff/login/", views.staff_login, name="staff_login"), path("staff/logout/", views.logout_view, name="staff_logout"), path("t/<str:token>/", views.consume_magic_link, name="magic")]
urlpatterns = [
path("", views.home, name="home"),
path("login/", views.customer_login, name="login"),
path("login/password/", views.customer_password_login, name="password_login"),
path("login/otp/", views.verify_otp, name="otp"),
path("profile/", views.customer_profile, name="profile"),
path("logout/", views.logout_view, name="logout"),
path("staff/login/", views.staff_login, name="staff_login"),
path("staff/logout/", views.logout_view, name="staff_logout"),
path("t/<str:token>/", views.consume_magic_link, name="magic"),
]
+6 -2
View File
@@ -3,8 +3,7 @@ from django.conf import settings
from django.contrib.auth import login, logout
from django.shortcuts import get_object_or_404, redirect, render
from django.utils import timezone
from django.views.decorators.http import require_http_methods
from django.views.decorators.http import require_POST
from django.views.decorators.http import require_GET, require_http_methods, require_POST
from .forms import CustomerPasswordLoginForm, CustomerProfileForm, MobileForm, OTPForm, StaffLoginForm
from apps.customers.models import CustomerProfile
from django.contrib.auth.decorators import login_required
@@ -21,6 +20,11 @@ def _authenticated_home(request):
if not request.user.is_authenticated: return None
return redirect("staff_tickets:list" if request.user.account_type==User.AccountType.STAFF else "tickets:list")
@require_GET
def home(request):
"""Send every visitor to the correct login or authenticated workspace."""
return _authenticated_home(request) or redirect("accounts:login")
@require_http_methods(["GET", "POST"])
def customer_login(request):
if response:=_authenticated_home(request): return response
@@ -8,10 +8,12 @@ from django.conf import settings
from django.core.management import call_command
from django.core.management.base import BaseCommand, CommandError
from django.db import connection, transaction
from django.db.models import F
from apps.accounts.models import User
from apps.core.models import ProductionSeedState
from apps.core.production_seed import sha256_file
from apps.tickets.models import Ticket
class Command(BaseCommand):
@@ -82,6 +84,27 @@ class Command(BaseCommand):
# the authoritative SQLite snapshot; schema/migration history remains.
call_command("flush", interactive=False, verbosity=0)
call_command("loaddata", str(bundle_dir / "data.json.gz"), verbosity=1)
# Old SQLite snapshots may predate unit-scoped queues. A
# category already belongs to exactly one unit, so it is a
# deterministic and safe source for repairing those rows.
repaired = []
for ticket in Ticket.objects.filter(
owning_unit__isnull=True,
category__unit__isnull=False,
).select_related("category"):
ticket.owning_unit_id = ticket.category.unit_id
repaired.append(ticket)
if repaired:
Ticket.objects.bulk_update(repaired, ["owning_unit"])
Ticket.objects.filter(
assigned_unit__isnull=True,
owning_unit__isnull=False,
).update(assigned_unit_id=F("owning_unit_id"))
if Ticket.objects.filter(owning_unit__isnull=True).exists():
raise CommandError(
"Seed contains tickets without an organizational unit; "
"assign their categories to units before exporting again."
)
media_root.mkdir(parents=True, exist_ok=True)
for source in sorted(staged_media.rglob("*")):
if not source.is_file():
@@ -0,0 +1,32 @@
from django.db import migrations
from django.db.models import F
def scope_legacy_tickets(apps, schema_editor):
Ticket = apps.get_model("tickets", "Ticket")
repaired = []
for ticket in Ticket.objects.filter(
owning_unit__isnull=True,
category__unit__isnull=False,
).select_related("category"):
ticket.owning_unit_id = ticket.category.unit_id
repaired.append(ticket)
if repaired:
Ticket.objects.bulk_update(repaired, ["owning_unit"])
Ticket.objects.filter(
assigned_unit__isnull=True,
owning_unit__isnull=False,
).update(assigned_unit_id=F("owning_unit_id"))
if Ticket.objects.filter(owning_unit__isnull=True).exists():
raise RuntimeError(
"Cannot create unit-scoped queues: some tickets and their categories "
"have no organizational unit."
)
class Migration(migrations.Migration):
dependencies = [("tickets", "0015_seed_customer_category_catalog")]
operations = [migrations.RunPython(scope_legacy_tickets, migrations.RunPython.noop)]
+14 -7
View File
@@ -75,7 +75,7 @@ class TicketPolicy:
scope = cls.unit_scope(user, unit)
if scope == "UNIT": return True
return bool(ticket.origin_visibility == "PUBLIC" and ticket.owner_id is None and ticket.selected_expert_id is None)
return bool(not ticket.owning_unit_id and ticket.origin_visibility == "PUBLIC" and ticket.owner_id is None and ticket.selected_expert_id is None and user.has_perm("tickets.view_ticket"))
return False
@classmethod
def viewable_q(cls, user):
"""Database equivalent of can_view; use it for every staff ticket collection."""
@@ -88,14 +88,22 @@ class TicketPolicy:
if cls.has_unit_permission(user, item.unit, "tickets.view_ticket")
}
unit_wide_ids = set(memberships.filter(ticket_scope="UNIT", unit_id__in=permitted_ids).values_list("unit_id", flat=True))
unit_wide = Q(owning_unit_id__in=unit_wide_ids) | Q(assigned_unit_id__in=unit_wide_ids)
unit_wide = Q(assigned_unit_id__in=unit_wide_ids) | Q(
assigned_unit__isnull=True,
owning_unit_id__in=unit_wide_ids,
)
limited_ids = permitted_ids - unit_wide_ids
public_unassigned = (
(Q(owning_unit_id__in=limited_ids) | Q(assigned_unit_id__in=limited_ids))
& Q(origin_visibility="PUBLIC", selected_expert__isnull=True)
(
Q(assigned_unit_id__in=limited_ids)
| Q(assigned_unit__isnull=True, owning_unit_id__in=limited_ids)
)
& Q(origin_visibility="PUBLIC", owner__isnull=True, selected_expert__isnull=True)
)
legacy = Q(owning_unit__isnull=True, origin_visibility="PUBLIC", selected_expert__isnull=True) if user.has_perm("tickets.view_ticket") else Q(pk__in=[])
return direct | unit_wide | public_unassigned | legacy
# There is deliberately no global/unitless public queue. Legacy tickets
# must be assigned to a unit by the data migration before staff can see
# them through a unit queue.
return direct | unit_wide | public_unassigned
@classmethod
def can_reply(cls,user,ticket):
if not cls.can_view(user,ticket): return False
@@ -103,7 +111,6 @@ class TicketPolicy:
if cls.has_unit_permission(user,ticket.assigned_unit or ticket.owning_unit,"messaging.add_message"): return True
if user.has_perm("tickets.view_all_tickets") or ticket.owner_id==user.id or ticket.selected_expert_id==user.id or ticket.created_by_id==user.id: return True
if ticket.owner_id is None and ticket.selected_expert_id is None and user.has_perm("messaging.add_message"): return True
if not ticket.owning_unit_id and ticket.owner_id is None and ticket.selected_expert_id is None and user.has_perm("tickets.view_ticket"): return True
return ticket.access_grants.filter(user=user,revoked_at__isnull=True,permission_level__in=[TicketAccessGrant.Level.REPLY,TicketAccessGrant.Level.FULL]).exists()
@classmethod
def can_claim(cls,user,ticket):