-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
3399 controlled access fra data files page #3430
base: develop
Are you sure you want to change the base?
Changes from 20 commits
5ffa00c
c9d2480
a8dd63d
b9f8331
2027bad
0cc815c
9f93409
e0859a7
486b19a
6f18151
75405a9
cd1a074
e1a9651
8e75ee7
05c20ce
fc8e14a
54d3b7e
dc3cd9d
4ad6999
093c734
0356b72
b353c2f
2bd0f18
a5795c5
1e672fa
2b0e4be
8edc8e0
9c47936
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,6 +37,21 @@ class Media: | |
|
||
actions = ['reparse'] | ||
|
||
def get_queryset(self, request): | ||
"""Return the queryset.""" | ||
qs = super().get_queryset(request) | ||
# return data files based on user's section | ||
FRA_SECTION_LIST = [ | ||
DataFile.Section.FRA_WORK_OUTCOME_TANF_EXITERS, | ||
DataFile.Section.FRA_SECONDRY_SCHOOL_ATTAINMENT, | ||
DataFile.Section.FRA_SUPPLEMENT_WORK_OUTCOMES | ||
] | ||
if not request.user.has_fra_access: | ||
filtered_for_fra = qs.exclude(section__in=FRA_SECTION_LIST) | ||
return filtered_for_fra | ||
else: | ||
return qs.filter(section__in=FRA_SECTION_LIST) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not sure if this is intended, but setting |
||
|
||
def reparse(self, request, queryset): | ||
"""Reparse the selected data files.""" | ||
files = queryset.values_list("id", flat=True) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
# Generated by Django 3.2.15 on 2025-01-08 13:42 | ||
|
||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
('data_files', '0016_remove_datafile_reparse_meta_models'), | ||
] | ||
|
||
operations = [ | ||
migrations.AlterField( | ||
model_name='datafile', | ||
name='section', | ||
field=models.CharField(choices=[('Tribal Closed Case Data', 'Tribal Closed Case Data'), ('Tribal Active Case Data', 'Tribal Active Case Data'), ('Tribal Aggregate Data', 'Tribal Aggregate Data'), ('Tribal Stratum Data', 'Tribal Stratum Data'), ('SSP Aggregate Data', 'Ssp Aggregate Data'), ('SSP Closed Case Data', 'Ssp Closed Case Data'), ('SSP Active Case Data', 'Ssp Active Case Data'), ('SSP Stratum Data', 'Ssp Stratum Data'), ('Active Case Data', 'Active Case Data'), ('Closed Case Data', 'Closed Case Data'), ('Aggregate Data', 'Aggregate Data'), ('Stratum Data', 'Stratum Data'), ('Work Outcomes for TANF Exiters', 'Fra Work Outcome Tanf Exiters'), ('Secondary School Attainment', 'Fra Secondry School Attainment'), ('Supplemental Work Outcomes', 'Fra Supplement Work Outcomes')], max_length=32), | ||
), | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -67,6 +67,11 @@ def assert_data_file_created(response): | |
"""Assert that the data file was created.""" | ||
assert response.status_code == status.HTTP_201_CREATED | ||
|
||
@staticmethod | ||
def assert_data_file_error(response): | ||
"""Assert that the data file was created.""" | ||
assert response.status_code == status.HTTP_400_BAD_REQUEST | ||
|
||
@staticmethod | ||
def assert_data_file_rejected(response): | ||
"""Assert that a given data file submission was rejected.""" | ||
|
@@ -154,6 +159,15 @@ def post_data_file_file(self, api_client, data_file_data): | |
format='multipart' | ||
) | ||
|
||
def post_data_file_fra(self, api_client, data_file_data): | ||
"""Submit a data file with the given data.""" | ||
data_file_data['section'] = 'Secondary School Attainment' | ||
return api_client.post( | ||
self.root_url, | ||
data_file_data, | ||
format='multipart' | ||
) | ||
|
||
def get_data_file_files(self, api_client): | ||
"""Submit a data file with the given data.""" | ||
return api_client.get( | ||
|
@@ -213,6 +227,13 @@ def test_create_data_file_file_entry(self, api_client, data_file_data, user): | |
self.assert_data_file_created(response) | ||
self.assert_data_file_exists(data_file_data, 1, user) | ||
|
||
def test_create_data_file_fra(self, api_client, data_file_data, user): | ||
"""Test ability to create data file metadata registry.""" | ||
response = self.post_data_file_fra(api_client, data_file_data) | ||
from rest_framework.exceptions import ErrorDetail | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason this lives in the function instead of at the module level? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not really, corrected it |
||
assert response.data == {'section': [ErrorDetail(string='Section cannot be FRA', code='invalid')]} | ||
self.assert_data_file_error(response) | ||
|
||
def test_data_file_file_version_increment( | ||
self, | ||
api_client, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,8 @@ | |
|
||
from .models import User | ||
|
||
import logging | ||
logger = logging.getLogger() | ||
|
||
class UserForm(forms.ModelForm): | ||
"""Customize the user admin form.""" | ||
|
@@ -19,6 +21,9 @@ class Meta: | |
exclude = ['password', 'user_permissions'] | ||
readonly_fields = ['last_login', 'date_joined', 'login_gov_uuid', 'hhs_id', 'access_request'] | ||
|
||
def __init__(self, *args, **kwargs): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this stylistic/for completeness, or was this required? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. byproduct of an approach I was taking, can be removed |
||
super().__init__(*args, **kwargs) | ||
|
||
def clean(self): | ||
"""Add extra validation for locations based on roles.""" | ||
cleaned_data = super().clean() | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
# Generated by Django 3.2.15 on 2025-01-21 04:29 | ||
|
||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
('users', '0041_users_digit_group_add_datafile_permission'), | ||
] | ||
|
||
operations = [ | ||
migrations.AddField( | ||
model_name='user', | ||
name='feature_flags', | ||
field=models.JSONField(blank=True, default=dict, help_text='Feature flags for this data file. JSON formatting example: {"fra_access": "True"}', null=True), | ||
), | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -114,6 +114,19 @@ class Meta: | |
_loaded_values = None | ||
_adding = True | ||
|
||
# Feature flag for the user to enable or disable FRA access | ||
feature_flags = models.JSONField( | ||
default=dict, | ||
help_text="Feature flags for this data file.", | ||
null=True, | ||
blank=True, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we keep There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately, there is an internal check for {} in JSONField. The only way to bypass is to override JSONField, but I decided to check for null or empty dict during cleaning which is simple There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also moved null out |
||
) | ||
|
||
@property | ||
def has_fra_access(self): | ||
"""Return whether or not the user has FRA access.""" | ||
return self.feature_flags.get('fra_access', False) | ||
|
||
def __str__(self): | ||
"""Return the username as the string representation of the object.""" | ||
return self.username | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,10 @@ | |
import pytest | ||
|
||
from tdpservice.stts.models import STT, Region | ||
from tdpservice.data_files.models import DataFile | ||
from tdpservice.data_files.test.factories import DataFileFactory | ||
from tdpservice.users.models import User | ||
from django.test import Client | ||
|
||
|
||
@pytest.mark.django_db | ||
|
@@ -70,3 +74,32 @@ def test_user_can_only_have_stt_or_region(user, stt, region): | |
|
||
user.clean() | ||
user.save() | ||
|
||
@pytest.mark.django_db | ||
def test_user_with_fra_access(client, user, stt): | ||
"""Test that a user with FRA access can only have an STT.""" | ||
user.stt = stt | ||
user.is_superuser = True | ||
user.feature_flags = {"fra_access": False} | ||
|
||
user.clean() | ||
user.save() | ||
raftmsohani marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
user = User.objects.create_superuser('admin', '[email protected]', 'password') | ||
client = Client() | ||
client.force_login(user) | ||
|
||
# Need a datafile, with a section that is not in the FRA_SECTION_LIST | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This comment is confusing since the test is using an FRA section immediately below it even though the comment says not to. |
||
datafile = DataFileFactory() | ||
datafile.section = DataFile.Section.FRA_WORK_OUTCOME_TANF_EXITERS | ||
datafile.save() | ||
|
||
response = client.get(f"/admin/data_files/datafile/{datafile.id}/change/") | ||
assert response.status_code != 200 | ||
assert f'Data file with ID “{datafile.id}” doesn’t exist. Perhaps it was deleted?' | ||
|
||
user.feature_flags = {"fra_access": True} | ||
user.save() | ||
|
||
response = client.get(f"/admin/data_files/datafile/{datafile.id}/change/") | ||
assert response.status_code == 200 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if user doesn't have FRA access or is Admin, then FRA files will be filtered