fix queue
This commit is contained in:
1 parent
049cc41afc
commit
9a1c99cbd3
14 files changed
+237
-32
No files matched your search
+13
-1
@@ -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"),
|
||||
]
|
||||
@@ -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)]
|
||||
@@ -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):
|
||||
|
||||
Reference in new issue
Block a user