Skip to content
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

3106 reparse django action #3167

Open
wants to merge 55 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 50 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
2753507
initial commit - added filter
raftmsohani Aug 14, 2024
f912d6b
Merge branch 'develop' into 3076-Admin-Filter-Enhancements-Data-Files…
raftmsohani Aug 15, 2024
b79cbb1
3076 added test
raftmsohani Aug 15, 2024
11b411e
Merge branch 'develop' into 3076-Admin-Filter-Enhancements-Data-Files…
raftmsohani Aug 16, 2024
1ca33a6
linting
raftmsohani Aug 23, 2024
e5b9371
Merge branch '3076-Admin-Filter-Enhancements-Data-Files-Page' of gith…
raftmsohani Aug 23, 2024
4a3a169
move import to top
raftmsohani Aug 23, 2024
f810633
lint
raftmsohani Aug 23, 2024
bc050cf
3076 correction
raftmsohani Aug 27, 2024
16bfbce
3076 Fixed today filter
raftmsohani Aug 28, 2024
3fa6d7a
linting
raftmsohani Aug 28, 2024
f91fc46
Merge branch 'develop' into 3076-Admin-Filter-Enhancements-Data-Files…
raftmsohani Aug 28, 2024
6c0cc70
Merge branch 'develop' into 3076-Admin-Filter-Enhancements-Data-Files…
raftmsohani Aug 29, 2024
3f7690c
Merge branch '3076-Admin-Filter-Enhancements-Data-Files-Page' of gith…
raftmsohani Aug 29, 2024
fb6ecb7
3076 applied a11y review changes
raftmsohani Aug 30, 2024
198b98a
3076 fix failing test
raftmsohani Aug 30, 2024
c61b298
3106 first draft
raftmsohani Sep 3, 2024
b3504b9
Merge branch 'develop' into 3106-reparse-django-action
raftmsohani Sep 5, 2024
e87808c
Merge branch 'develop' into 3106-reparse-django-action
raftmsohani Sep 5, 2024
16ecfdb
Merge branch '3106-reparse-django-action' of github.com:raft-tech/TAN…
raftmsohani Sep 5, 2024
d59af03
clean up
raftmsohani Sep 5, 2024
e1a3bbc
clean up
raftmsohani Sep 5, 2024
d3fd407
clean admin
raftmsohani Sep 5, 2024
2237686
clean admin
raftmsohani Sep 5, 2024
778f706
parsing command
raftmsohani Sep 9, 2024
a5737b4
delete unwanted imports
raftmsohani Sep 9, 2024
8e94085
Merge branch 'develop' into 3106-reparse-django-action
raftmsohani Sep 9, 2024
7fa47a8
added modal form
raftmsohani Sep 11, 2024
dd6d85e
Merge branch '3106-reparse-django-action' of github.com:raft-tech/TAN…
raftmsohani Sep 11, 2024
1463ea4
added styling to confirmation page
raftmsohani Sep 11, 2024
152d439
changes
raftmsohani Sep 11, 2024
d3deddf
3106 middle page works
raftmsohani Sep 13, 2024
f78e5ae
Merge branch 'develop' into 3106-reparse-django-action
raftmsohani Sep 13, 2024
a54f14a
3106 can add files for reparsing
raftmsohani Sep 13, 2024
2b75af8
3106 Correct linting
raftmsohani Sep 13, 2024
626087f
bypassing status check to get this deployed for testing
Sep 17, 2024
38d50f5
injected working js
raftmsohani Sep 18, 2024
5e6c89c
remove some of the comments
raftmsohani Sep 18, 2024
ed965ac
revert changes
raftmsohani Sep 19, 2024
95b6ebd
linting and corrections
raftmsohani Sep 19, 2024
1c19e87
lint
raftmsohani Sep 19, 2024
98bd857
redirect to meta model
raftmsohani Sep 20, 2024
e09ce5d
Merge branch 'develop' into 3106-reparse-django-action
raftmsohani Sep 20, 2024
e100f6d
Merge branch 'develop' into 3106-reparse-django-action
raftmsohani Sep 23, 2024
3daebc5
3106 replying comments
raftmsohani Sep 25, 2024
4f28656
Merge branch '3106-reparse-django-action' of github.com:raft-tech/TAN…
raftmsohani Sep 25, 2024
f1c22f4
move imports to the top
raftmsohani Sep 25, 2024
6ca4da4
Merge branch 'develop' into 3106-reparse-django-action
raftmsohani Sep 25, 2024
f3a3959
Update admin.py: renamed reparse command
raftmsohani Sep 30, 2024
4945d32
Merge branch 'develop' into 3106-reparse-django-action
raftmsohani Sep 30, 2024
d1fb4d6
3106 removed unused function
raftmsohani Sep 30, 2024
6e723e0
3106 fixed reparsing command issues
raftmsohani Sep 30, 2024
bf2d288
Merge branch 'develop' into 3106-reparse-django-action
raftmsohani Oct 2, 2024
a1c4631
resolve merge conflict errors
raftmsohani Oct 2, 2024
4d6dcb4
Merge branch 'develop' into 3106-reparse-django-action
andrew-jameson Oct 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 36 additions & 2 deletions tdrs-backend/tdpservice/data_files/admin/admin.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
"""Admin class for DataFile objects."""
from django.contrib import admin
from tdpservice.core.utils import ReadOnlyAdminMixin
# from tdpservice.core.filters import custom_filter_title
from tdpservice.data_files.models import DataFile, LegacyFileTransfer
from tdpservice.parsers.models import DataFileSummary, ParserError
from tdpservice.data_files.admin.filters import DataFileSummaryPrgTypeFilter, LatestReparseEvent, VersionFilter
from django.conf import settings
from django.utils.html import format_html
from datetime import datetime, timedelta, timezone
from django.shortcuts import redirect
from django.core.management import call_command
from django.utils.translation import ngettext
from django.contrib import messages

DOMAIN = settings.FRONTEND_BASE_URL

Expand All @@ -23,11 +26,42 @@ def has_change_permission(self, request, obj=None):
"""Read only permissions."""
return False


@admin.register(DataFile)
class DataFileAdmin(ReadOnlyAdminMixin, admin.ModelAdmin):
"""Admin class for DataFile models."""

class Media:
"""Media class for DataFileAdmin."""

js = ('admin/js/admin/admin_datafile_model.js',)

actions = ['reparse']

def reparse(self, request, queryset):
"""Reparse the selected data files."""
files = queryset.values_list("id", flat=True)
file_ids = ",".join(map(str, files))
call_command("clean_and_reparse", f"-f {file_ids}")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this works, but my preference would be the re-implement the logic of the management command here

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one reason for this - i was testing the sequential reparse scenario and was confronted with this error

image

i couldn't find the actual exception in the logs (the parse was still active, and putting out tons of logs), but if we re-implemented the command logic, that error could be caught and displayed to the user

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be able to catch the exception/log message in the admin console LogEntries. It should make a new entry indicating it wont reparse when not sequential

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also agree with Jan that as a first pass calling the command works, but it would be nice to share the logic between this and the command to allow for a little more fine grain control.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so are we ok to repeat the steps?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think they need to have code duplicates. I bet we can factor out parametrized functions from the command and put them into a shared module for both the command and the action to import. That will be a good amount of work though which I think could make sense to move into a follow on ticket.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can make a util file and create util functions and share them and I agree this can be a new ticket

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created a new issue #3205

self.message_user(
request,
ngettext(
"%d file successfully submitted for reparsing.",
"%d files successfully submitted for reparsing.",
files.count(),
)
% files.count(),
messages.SUCCESS,
)
return redirect("/admin/search_indexes/reparsemeta/")

def get_actions(self, request):
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: if not admin, then the reparse command is not shown

"""Return the actions."""
actions = super().get_actions(request)
if not request.user.groups.filter(name__in=["OFA System Admin", "OFA Admin"]).exists():
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

user.is_in_group() or user.is_ofa_sys_admin (modified to accept both)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is then it will do two queries instead of one

if "reparse" in actions:
del actions["reparse"]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of another if statement can we just make it actions.pop("reparse", None)?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately no since actions is a list and .pop only works with int indexes

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If actions is a list how are you indexing into it with a string?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are right, it is a dictionary and although my original test failed, I tested again and it passed. so will do the change

return actions

def status(self, obj):
"""Return the status of the data file summary."""
return DataFileSummary.objects.get(datafile=obj).status
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
$(window).on('load', function() {
//your code here
console.log('loaded');
var submitBtn=document.querySelector('button[type=submit]'); // add the first listener
var theForm = submitBtn.parentNode.parentNode;

for (var i = 0; i < theForm.childNodes.length; i++) {
console.log(theForm.childNodes[i].className)
if (theForm.childNodes[i].className == "actions") {
form_header = theForm.childNodes[i];
break;
}
}
for (var i = 0; i < form_header.childNodes.length; i++) {
console.log(form_header.childNodes[i].className)
if (form_header.childNodes[i].className == "action-counter") {
number_of_files = form_header.childNodes[i];
break;
}
}
submitBtn.addEventListener('click', function(e) {
e.preventDefault();
disableFields();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getting an error when clicking the submit button

image

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be fixed now

if (confirm("You are about to re-parse " + number_of_files.innerHTML.split(/(\s+)/)[0] + " files. Are you sure you want to continue?")) {
console.log('submitting');
theForm.submit();
} else {
console.log('not submitting');
};
});


});
9 changes: 9 additions & 0 deletions tdrs-backend/tdpservice/data_files/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
from tdpservice.data_files.s3_client import S3Client
from tdpservice.parsers.models import ParserError
from tdpservice.parsers.serializers import ParsingErrorSerializer
from rest_framework.parsers import JSONParser, FormParser
from django.core.management import call_command

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -151,6 +153,13 @@ def download_error_report(self, request, pk=None):
serializer = ParsingErrorSerializer(parser_errors, many=True, context=self.get_serializer_context())
return Response(get_xls_serialized_file(serializer.data))

@action(methods=["post"], detail=False, parser_classes=[JSONParser, MultiPartParser, FormParser])
def run_action_reparse_cmd(self, request, pk=None):
"""Run the reparse command."""
# Need to add the reparsing file ids to the request object
call_command("clean_and_reparse", f"-f {request.data['file_ids']}")
return Response({'status': 'success'})


class GetYearList(APIView):
"""Get list of years for which there are data_files."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def add_arguments(self, parser):
parser.add_argument("-y", "--fiscal_year", type=int, help="Reparse all files in the fiscal year, e.g. 2021.")
parser.add_argument("-a", "--all", action='store_true', help="Clean and reparse all datafiles. If selected, "
"fiscal_year/quarter aren't necessary.")
parser.add_argument("-f", "--files", nargs='+', type=str, help="Re-parse specific datafiles by datafile id")
raftmsohani marked this conversation as resolved.
Show resolved Hide resolved

def __get_log_context(self, system_user):
"""Return logger context."""
Expand Down Expand Up @@ -223,28 +224,20 @@ def __calculate_timeout(self, num_files, num_records):
logger.info(f"Setting timeout for the reparse event to be {delta} seconds from meta model creation date.")
return delta

def handle(self, *args, **options):
"""Delete and reparse datafiles matching a query."""
fiscal_year = options.get('fiscal_year', None)
fiscal_quarter = options.get('fiscal_quarter', None)
reparse_all = options.get('all', False)
new_indices = reparse_all is True

args_passed = fiscal_year is not None or fiscal_quarter is not None or reparse_all

if not args_passed:
logger.warn("No arguments supplied.")
self.print_help("manage.py", "clean_and_parse")
return

def get_files_to_reparse(case, fiscal_year, fiscal_quarter, selected_files, reparse_all):
"""Get the files to reparse."""
backup_file_name = "/tmp/reparsing_backup"
files = DataFile.objects.all()
continue_msg = "You have selected to reparse datafiles for FY {fy} and {q}. The reparsed files "
if selected_files:
files = files.filter(id__in=selected_files)
backup_file_name += "_selected_files"
continue_msg = continue_msg.format(fy="All", q="Q1-4")
if reparse_all:
backup_file_name += "_FY_All_Q1-4"
continue_msg = continue_msg.format(fy="All", q="Q1-4")
else:
if not fiscal_year and not fiscal_quarter:
if not fiscal_year and not fiscal_quarter and not selected_files:
print(
'Options --fiscal_year and --fiscal_quarter not set. '
'Provide either option to continue, or --all to wipe all submissions.'
Expand All @@ -262,6 +255,33 @@ def handle(self, *args, **options):
files = files.filter(quarter=fiscal_quarter)
backup_file_name += f"_FY_All_{fiscal_quarter}"
continue_msg = continue_msg.format(fy="All", q=fiscal_quarter)
return files, backup_file_name, continue_msg

def handle(self, *args, **options):
"""Delete and reparse datafiles matching a query."""
fiscal_year = options.get('fiscal_year', None)
fiscal_quarter = options.get('fiscal_quarter', None)
reparse_all = options.get('all', False)
selected_files = options.get('files', None)
selected_files = [int(file) for file in selected_files[0].split(',')] if selected_files else None
new_indices = reparse_all is True

# have to check if the selected_files is not None
args_passed = fiscal_year is not None or fiscal_quarter is not None or reparse_all or selected_files

if not args_passed:
logger.warning("No arguments supplied.")
self.print_help("manage.py", "clean_and_parse")
return

# Set up the backup file name and continue message
files, backup_file_name, continue_msg = self.get_files_to_reparse(
fiscal_year,
fiscal_quarter,
selected_files,
reparse_all)

# end of the if statement

fmt_str = "be" if new_indices else "NOT be"
continue_msg += "will {new_index} stored in new indices and the old indices ".format(new_index=fmt_str)
Expand All @@ -270,10 +290,11 @@ def handle(self, *args, **options):
fmt_str = f"ALL ({num_files})" if reparse_all else f"({num_files})"
continue_msg += "\nThese options will delete and reparse {0} datafiles.".format(fmt_str)

c = str(input(f'\n{continue_msg}\nContinue [y/n]? ')).lower()
if c not in ['y', 'yes']:
print('Cancelled.')
return
if not selected_files:
c = str(input(f'\n{continue_msg}\nContinue [y/n]? ')).lower()
if c not in ['y', 'yes']:
print('Cancelled.')
return

system_user, created = User.objects.get_or_create(username='system')
if created:
Expand Down
Loading