share ticket and ticket message

This commit is contained in:
javad committed 2026-10-04 16:42:19 +03:30
1 parent 39e64854b8
commit 79fe1cddbb
35 files changed
+1742 -95

No files matched your search

+101
View File
@@ -0,0 +1,101 @@
"""Routes available only to staff, including isolated message attachment delivery."""
import mimetypes
from django.conf import settings
from django.contrib import messages
from django.core.exceptions import PermissionDenied, ValidationError
from django.http import FileResponse, JsonResponse
from django.shortcuts import get_object_or_404, redirect
from django.views.decorators.cache import never_cache
from django.views.decorators.http import require_GET, require_POST
from apps.attachments.delivery import signed_media_redirect
from apps.attachments.models import Attachment
from .models import MessageShare, Ticket
from .staff_conversation import received_messages, unread_shares, visible_messages
from .sharing import mark_share_opened, revoke_staff_share
from .views import staff_required
def received_message_share(request, share_id):
grant = get_object_or_404(MessageShare.objects.select_related("message__author__staff_profile", "granted_by__staff_profile"), public_id=share_id)
profile = getattr(request.user, "staff_profile", None)
if (grant.user_id != request.user.pk or grant.revoked_at or grant.message.deleted_at
or not request.user.is_active or not profile or not profile.is_active):
raise PermissionDenied
return grant
def submit_share_form(request, form, save, success_message):
if not form.is_valid():
messages.error(request, "گیرنده، سطح دسترسی یا توضیح اشتراک معتبر نیست.")
return
try:
save()
except ValidationError as error:
messages.error(request, " ".join(error.messages))
else:
messages.success(request, success_message)
@staff_required
@require_GET
@never_cache
def inbox(request):
from django.urls import reverse
return redirect(reverse("staff_tickets:list") + "?tab=shared")
@staff_required
@require_POST
def revoke_message(request, public_id, share_id):
grant = get_object_or_404(MessageShare.objects.select_related("message__ticket"), public_id=share_id, message__ticket__public_id=public_id)
revoke_staff_share(grant, request.user)
messages.success(request, "دسترسی به پیام اشتراکی لغو شد؛ سابقه آن حفظ شده است.")
return redirect("staff_tickets:detail", public_id=public_id)
@staff_required
@require_GET
@never_cache
def shared_message(request, share_id):
grant = received_message_share(request, share_id)
return redirect("staff_tickets:detail", public_id=grant.message.ticket.public_id)
@staff_required
@require_POST
@never_cache
def read_shared_messages(request, public_id):
ticket = get_object_or_404(Ticket, public_id=public_id)
allowed = visible_messages(ticket, request.user)
ids = request.POST.getlist("message_ids")[:100]
# Filter strings without parsing attacker-controlled UUIDs in the ORM.
visible = {str(value) for value in allowed.values_list("public_id", flat=True)}
accepted = set(ids) & visible
grants = received_messages(request.user, ticket).filter(message__public_id__in=accepted)
for grant in grants:
if grant.first_viewed_at is None:
mark_share_opened(grant, request.user)
return JsonResponse({"unread_shares": unread_shares(request.user)})
@staff_required
@require_GET
@never_cache
def unread_count(request):
return JsonResponse({"unread_shares": unread_shares(request.user)})
@staff_required
@require_GET
@never_cache
def shared_attachment(request, share_id, attachment_id, download=False):
grant = received_message_share(request, share_id)
attachment = get_object_or_404(Attachment, public_id=attachment_id, message_id=grant.message_id)
if settings.USE_NGINX_MEDIA:
return signed_media_redirect(attachment.relative_path, download=download)
content_type = attachment.mime_type or mimetypes.guess_type(attachment.original_name)[0] or "application/octet-stream"
response = FileResponse(attachment.file.open("rb"), content_type=content_type, as_attachment=download, filename=attachment.original_name)
response["X-Content-Type-Options"] = "nosniff"
return response