Hide peer logins on profiles and restrict view_all to published reports.
Login stays visible only to self/manager/HR; draft report docs stay department-head only. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+23
-11
@@ -29,22 +29,25 @@ def user_can_open_reports_hub(user) -> bool:
|
||||
def user_can_view_report_document(user, doc: ReportDocument) -> bool:
|
||||
if not user_can_open_reports_hub(user):
|
||||
return False
|
||||
# Черновики — только руководителю своего отдела.
|
||||
if user_can_manage_it_monthly_report(user) and getattr(user, 'department_id', None) == doc.department_id:
|
||||
return True
|
||||
# view_all и обычные сотрудники — только опубликованные.
|
||||
if not doc.is_published:
|
||||
return False
|
||||
if user_can_view_all_department_reports(user):
|
||||
return True
|
||||
if getattr(user, 'department_id', None) != doc.department_id:
|
||||
return False
|
||||
if user_can_manage_it_monthly_report(user):
|
||||
return True
|
||||
return doc.is_published
|
||||
return getattr(user, 'department_id', None) == doc.department_id
|
||||
|
||||
|
||||
def user_can_edit_report_document(user, doc: ReportDocument) -> bool:
|
||||
if not user_can_open_reports_hub(user):
|
||||
return False
|
||||
if user_can_view_all_department_reports(user):
|
||||
if user_can_manage_it_monthly_report(user) and getattr(user, 'department_id', None) == doc.department_id:
|
||||
return True
|
||||
if user_can_manage_it_monthly_report(user):
|
||||
return getattr(user, 'department_id', None) == doc.department_id
|
||||
# view_all может править/снимать только уже опубликованные (черновики чужих отделов не видит).
|
||||
if user_can_view_all_department_reports(user):
|
||||
return doc.is_published
|
||||
return False
|
||||
|
||||
|
||||
@@ -65,13 +68,22 @@ def visible_report_documents(user):
|
||||
if not user_can_open_reports_hub(user):
|
||||
return ReportDocument.objects.none()
|
||||
qs = ReportDocument.objects.select_related('department', 'created_by', 'published_by')
|
||||
if user_can_view_all_department_reports(user):
|
||||
return qs
|
||||
dept_id = getattr(user, 'department_id', None)
|
||||
manage_own = user_can_manage_it_monthly_report(user) and bool(dept_id)
|
||||
|
||||
if user_can_view_all_department_reports(user):
|
||||
# Все отделы — только опубликованные; свои черновики — если ещё и руководитель.
|
||||
if manage_own:
|
||||
from django.db.models import Q
|
||||
return qs.filter(
|
||||
Q(status=ReportDocument.STATUS_PUBLISHED) | Q(department_id=dept_id)
|
||||
).distinct()
|
||||
return qs.filter(status=ReportDocument.STATUS_PUBLISHED)
|
||||
|
||||
if not dept_id:
|
||||
return ReportDocument.objects.none()
|
||||
qs = qs.filter(department_id=dept_id)
|
||||
if user_can_manage_it_monthly_report(user):
|
||||
if manage_own:
|
||||
return qs
|
||||
return qs.filter(status=ReportDocument.STATUS_PUBLISHED)
|
||||
|
||||
|
||||
@@ -643,3 +643,39 @@ class ReportDocumentTests(TestCase):
|
||||
fake = SimpleUploadedFile('x.docx', b'x')
|
||||
r = self.client.post('/reports/documents/upload/', {'file': fake})
|
||||
self.assertEqual(r.status_code, 403)
|
||||
|
||||
def test_view_all_sees_only_published(self):
|
||||
from django.contrib.auth.models import Permission
|
||||
from apps.reports.documents import visible_report_documents
|
||||
from apps.reports.models import ReportDocument
|
||||
|
||||
other_dept_type, _ = DirectoryType.objects.get_or_create(
|
||||
code='department', defaults={'name': 'Відділи', 'hierarchy_type': 'strict'},
|
||||
)
|
||||
other = Directory.objects.create(
|
||||
directory_type=other_dept_type, code='hr-docs', name='HR-docs',
|
||||
)
|
||||
draft = ReportDocument.objects.create(
|
||||
department=other, title='Черновик HR', filename='draft.docx',
|
||||
kind=ReportDocument.KIND_EXTRA, status=ReportDocument.STATUS_DRAFT,
|
||||
created_by=self.head,
|
||||
)
|
||||
published = ReportDocument.objects.create(
|
||||
department=other, title='Опубликован HR', filename='pub.docx',
|
||||
kind=ReportDocument.KIND_EXTRA, status=ReportDocument.STATUS_PUBLISHED,
|
||||
created_by=self.head,
|
||||
)
|
||||
viewer = make_employee(
|
||||
last_name='Все', first_name='Отделы', username='view.all.docs',
|
||||
department=self.dept, user_location_type='office',
|
||||
)
|
||||
it_group = Group.objects.get(name='IT')
|
||||
viewer.groups.add(it_group)
|
||||
perm = Permission.objects.get(
|
||||
codename='view_all_departments', content_type__app_label='reports',
|
||||
)
|
||||
viewer.user_permissions.add(perm)
|
||||
|
||||
titles = set(visible_report_documents(viewer).values_list('title', flat=True))
|
||||
self.assertIn(published.title, titles)
|
||||
self.assertNotIn(draft.title, titles)
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ def can_delete_task(user, task) -> bool:
|
||||
def _employee_dict(emp):
|
||||
return {
|
||||
'pk': emp.pk,
|
||||
'label': emp.get_full_name() or emp.username,
|
||||
'label': emp.get_full_name() or '—',
|
||||
'position': emp.post.name if emp.post_id else '',
|
||||
'photo': emp.photo.url if emp.photo else '',
|
||||
}
|
||||
|
||||
@@ -146,6 +146,7 @@ class EmployeeDetailTests(TestCase):
|
||||
self.assertNotIn('Личный телефон', html)
|
||||
self.assertNotIn('Интеграции', html)
|
||||
self.assertNotIn(self.employee.personal_phone, html)
|
||||
self.assertNotIn(self.employee.username, html)
|
||||
|
||||
def test_manager_chain_and_subordinates_shown(self):
|
||||
login_2fa(self.client, self.staff)
|
||||
|
||||
@@ -46,11 +46,11 @@
|
||||
</div>
|
||||
|
||||
<div style="border-top:1px solid var(--color-border); padding-top:0.875rem; margin-bottom:1rem; font-size:0.8125rem;">
|
||||
{% if can_view_pii or is_own_profile %}
|
||||
<div style="display:flex; justify-content:space-between; gap:0.5rem; margin-bottom:0.375rem;">
|
||||
<span style="color:var(--color-muted-foreground);">{% trans "Логин" %}</span>
|
||||
<span style="font-weight:500; text-align:right; word-break:break-all;">{{ employee.username }}</span>
|
||||
</div>
|
||||
{% if can_view_pii or is_own_profile %}
|
||||
{% if employee.email %}
|
||||
<div style="display:flex; justify-content:space-between; gap:0.5rem; margin-bottom:0.375rem;">
|
||||
<span style="color:var(--color-muted-foreground);">Email</span>
|
||||
|
||||
Reference in New Issue
Block a user