locations: недостающие модули области доступа + гейт reports

Делает HEAD целостным: коммит 5366799 уже импортировал эти файлы, но они
не были добавлены в git (свежий checkout падал с ImportError, а migrate не
создавал таблицу GroupLocationAccess).

- core/location_access.py: область данных (accessible_pharmacies/locations,
  scoped_queryset/employees, has_all_locations) — fail-closed
- apps/locations/models/access.py + миграция 0014: GroupLocationAccess,
  право locations.view_all_locations
- core/tests_location_access.py: 15 сценариев scoping
- apps/reports/*: снятие лишнего гейта has_all_locations с IT-отчёта отдела
  (руководитель не обязан иметь доступ ко всей сети) + тест
- apps/rro/tests: чужая аптека теперь 404 (единая область доступа), а не
  «200 с урезанными панелями» — под новую модель

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-09-21 22:59:47 +03:00
parent 536679925a
commit fa323d657c
9 changed files with 335 additions and 37 deletions
@@ -0,0 +1,31 @@
# Generated by Django 6.0.3 on 2026-09-21 17:24
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
('locations', '0013_pharmacy_edit_permission'),
]
operations = [
migrations.AlterModelOptions(
name='location',
options={'permissions': [('view_all_locations', 'Доступ к данным всех локаций портала'), ('manage_location_access', 'Назначение ответственных за локации')], 'verbose_name': 'Локация', 'verbose_name_plural': 'Локации'},
),
migrations.CreateModel(
name='GroupLocationAccess',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('group', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='location_access', to='auth.group', verbose_name='Группа')),
('locations', models.ManyToManyField(blank=True, help_text='Дополняют свою локацию и закреплённые аптеки. Доступ к разделам выдаётся отдельно.', related_name='location_access_groups', to='locations.location', verbose_name='Доступные локации')),
],
options={
'verbose_name': 'Область доступа группы',
'verbose_name_plural': 'Области доступа групп',
},
),
]
+14
View File
@@ -0,0 +1,14 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
class GroupLocationAccess(models.Model):
group = models.OneToOneField('auth.Group', on_delete=models.CASCADE, related_name='location_access', verbose_name=_('Группа'))
locations = models.ManyToManyField('locations.Location', blank=True, related_name='location_access_groups', verbose_name=_('Доступные локации'), help_text=_('Дополняют свою локацию и закреплённые аптеки. Доступ к разделам выдаётся отдельно.'))
class Meta:
verbose_name = _('Область доступа группы')
verbose_name_plural = _('Области доступа групп')
def __str__(self):
return str(self.group)