Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/dbca-wa/mooringlicensing in…
Browse files Browse the repository at this point in the history
…to main
  • Loading branch information
jmushtaq committed Feb 6, 2024
2 parents 150c302 + ad6d32e commit 35acaa8
Show file tree
Hide file tree
Showing 59 changed files with 2,007 additions and 677 deletions.
4 changes: 3 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@ COPY --chown=oim:oim requirements.txt ./

RUN pip install -r requirements.txt \
&& rm -rf /var/lib/{apt,dpkg,cache,log}/ /tmp/* /var/tmp/*

RUN pip install --upgrade pip
RUN wget -O /tmp/GDAL-3.8.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl https://github.com/girder/large_image_wheels/raw/wheelhouse/GDAL-3.8.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#sha256=e2fe6cfbab02d535bc52c77cdbe1e860304347f16d30a4708dc342a231412c57
RUN pip install /tmp/GDAL-3.8.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
# Install the project (ensure that frontend projects have been built prior to this step).
FROM python_libs_ml
COPY --chown=oim:oim gunicorn.ini manage_ml.py ./
Expand Down
160 changes: 137 additions & 23 deletions mooringlicensing/components/approvals/api.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import re
import traceback
from django.db.models import Q, Min, CharField, Value
from django.db.models.functions import Concat
from django.core.paginator import Paginator, EmptyPage
from confy import env
import datetime
import pytz
Expand Down Expand Up @@ -27,7 +31,7 @@
from mooringlicensing.components.proposals.email import send_create_mooring_licence_application_email_notification
from mooringlicensing.components.main.decorators import basic_exception_handler, query_debugger, timeit
from mooringlicensing.components.payments_ml.api import logger
from mooringlicensing.components.payments_ml.models import FeeSeason, FeeConstructor
from mooringlicensing.components.payments_ml.models import FeePeriod, FeeSeason, FeeConstructor
from mooringlicensing.components.payments_ml.serializers import DcvPermitSerializer, DcvAdmissionSerializer, \
DcvAdmissionArrivalSerializer, NumberOfPeopleSerializer
from mooringlicensing.components.proposals.models import Proposal, MooringLicenceApplication, ProposalType, Mooring, \
Expand Down Expand Up @@ -102,21 +106,48 @@ def get(self, request, format=None):
application_type = ApplicationType.objects.filter(code=app_code)
if application_type:
application_types.append(application_type.first())
data = [{'id': season.id, 'name': season.name} for season in FeeSeason.objects.filter(application_type__in=application_types)]
fee_seasons = FeeSeason.objects.filter(application_type__in=application_types)
else:
data = [{'id': season.id, 'name': season.name} for season in FeeSeason.objects.all()]
return Response(data)
fee_seasons = FeeSeason.objects.all()

# data = [{
# 'id': season.id,
# 'name': season.name,
# 'application_type_code': season.application_type.code,
# 'application_type_description': season.application_type.description} for season in fee_seasons]

handled = []
data = []
for fee_season in fee_seasons:
if fee_season.start_date not in handled:
handled.append(fee_season.start_date)
data.append({
'start_date': fee_season.start_date,
'name': str(fee_season.start_date.year) + ' - ' + str(fee_season.start_date.year + 1)
})
data_sorted = sorted(data, key=lambda x: x['start_date'], reverse=True) if data else []
return Response(data_sorted)


class GetSticker(views.APIView):
renderer_classes = [JSONRenderer, ]

def get(self, request, format=None):
search_term = request.GET.get('term', '')
search_term = request.GET.get('search_term', '')
page_number = request.GET.get('page_number', 1)
items_per_page = 10

if search_term:
data = Sticker.objects.filter(number__icontains=search_term)[:10]
data = Sticker.objects.filter(number__icontains=search_term)
paginator = Paginator(data, items_per_page)
try:
current_page = paginator.page(page_number)
my_objects = current_page.object_list
except EmptyPage:
my_objects = []

data_transform = []
for sticker in data:
for sticker in my_objects:
approval_history = sticker.approvalhistory_set.order_by('id').first() # Should not be None, but could be None for the data generated at the early stage of development.
if approval_history and approval_history.approval:
data_transform.append({
Expand All @@ -134,7 +165,12 @@ def get(self, request, format=None):
# Should not reach here
pass

return Response({"results": data_transform})
return Response({
"results": data_transform,
"pagination": {
"more": current_page.has_next()
}
})
return Response()


Expand Down Expand Up @@ -346,15 +382,25 @@ class ApprovalPaginatedViewSet(viewsets.ModelViewSet):

def get_queryset(self):
request_user = self.request.user
all = Approval.objects.all() # We may need to exclude the approvals created from the Waiting List Application
# exclude_approval_id = self.request.GET.get('exclude_approval_id', 0)

# target_email_user_id = int(self.request.GET.get('target_email_user_id', 0))
target_email_user_id = int(self.request.data.get('target_email_user_id', 0))
all = Approval.objects.all()
# all = Approval.objects.all().exclude(id=exclude_approval_id) # We may need to exclude the approvals created from the Waiting List Application

if is_internal(self.request):
target_email_user_id = int(self.request.GET.get('target_email_user_id', 0))
if target_email_user_id:
target_user = EmailUser.objects.get(id=target_email_user_id)
all = all.filter(Q(submitter=target_user.id))

for_swap_moorings_modal = self.request.GET.get('for_swap_moorings_modal', 'false')
for_swap_moorings_modal = True if for_swap_moorings_modal.lower() in ['true', 'yes', 'y', ] else False
if for_swap_moorings_modal:
all = all.filter(
Q(current_proposal__processing_status__in=[Proposal.PROCESSING_STATUS_APPROVED,]) &
Q(status__in=[Approval.APPROVAL_STATUS_CURRENT, Approval.APPROVAL_STATUS_SUSPENDED,])
)
logger.debug(f'{all.count()}')
return all
elif is_customer(self.request):
qs = all.filter(Q(submitter=request_user.id))
Expand Down Expand Up @@ -477,6 +523,17 @@ def get_moorings(self, request, *args, **kwargs):
})
return Response(moorings)

@detail_route(methods=['POST'], detail=True)
@renderer_classes((JSONRenderer,))
@basic_exception_handler
def swap_moorings(self, request, *args, **kwargs):
with transaction.atomic():
originated_approval = self.get_object()
target_approval_id = request.data.get('target_approval_id')
target_approval = Approval.objects.get(id=target_approval_id)
originated_approval.child_obj.swap_moorings(request, target_approval.child_obj)
return Response()

@detail_route(methods=['POST'], detail=True)
@renderer_classes((JSONRenderer,))
@basic_exception_handler
Expand Down Expand Up @@ -593,7 +650,7 @@ def approval_cancellation(self, request, *args, **kwargs):
instance = self.get_object()
serializer = ApprovalCancellationSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
instance.approval_cancellation(request,serializer.validated_data)
instance.approval_cancellation(request, serializer.validated_data)
return Response()

@detail_route(methods=['POST',], detail=True)
Expand Down Expand Up @@ -1099,6 +1156,36 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
class StickerFilterBackend(DatatablesFilterBackend):
def filter_queryset(self, request, queryset, view):
total_count = queryset.count()
search_term = request.GET.get('search[value]', '')

# Custom fullname search
pattern = re.compile(r'\S\s+')
qs_stickers1 = Sticker.objects.none()
qs_stickers2 = Sticker.objects.none()
if pattern.search(search_term):
# Only when the search term has a space after a some text(first_name), then perform custome query because we just want to perform full_name search.

# Search sticker.approval.submitter by fullname
email_user_ids = EmailUser.objects.annotate(
custom_term=Concat(
"first_name",
Value(" "),
"last_name",
output_field=CharField(),
)
).filter(custom_term__icontains=search_term).values_list('id', flat=True)
qs_stickers1 = queryset.filter(approval__in=Approval.objects.filter(submitter__in=list(email_user_ids)))

# Search sticker.approval.current_proposal.proposalapplicant by fullname
proposal_applicants = ProposalApplicant.objects.annotate(
custom_term=Concat(
"first_name",
Value(" "),
"last_name",
output_field=CharField(),
)
).filter(custom_term__icontains=search_term).values_list('id', flat=True)
qs_stickers2 = queryset.filter(approval__current_proposal__proposalapplicant__in=proposal_applicants)

# Filter by approval types (wla, aap, aup, ml)
filter_approval_type = request.GET.get('filter_approval_type')
Expand All @@ -1113,10 +1200,16 @@ def filter_queryset(self, request, queryset, view):
queryset = queryset.filter(approval__in=Approval.objects.filter(mooringlicence__in=MooringLicence.objects.all()))

# Filter Year (FeeSeason)
filter_fee_season_id = request.GET.get('filter_year')
if filter_fee_season_id and not filter_fee_season_id.lower() == 'all':
fee_season = FeeSeason.objects.get(id=filter_fee_season_id)
queryset = queryset.filter(fee_constructor__fee_season=fee_season)
# filter_fee_season_id = request.GET.get('filter_year')
# logger.debug(f'fee_season id: {filter_fee_season_id}')
# if filter_fee_season_id and not filter_fee_season_id.lower() == 'all':
# fee_season = FeeSeason.objects.get(id=filter_fee_season_id)
# queryset = queryset.filter(fee_constructor__fee_season=fee_season)
filter_year = request.GET.get('filter_year')
if filter_year and not filter_year.lower() == 'all':
filter_year = datetime.strptime(filter_year, '%Y-%m-%d').date()
fee_seasons = FeePeriod.objects.filter(start_date=filter_year).values_list('fee_season')
queryset = queryset.filter(fee_constructor__fee_season__in=fee_seasons)

# Filter sticker status
filter_sticker_status_id = request.GET.get('filter_sticker_status')
Expand All @@ -1137,6 +1230,11 @@ def filter_queryset(self, request, queryset, view):
except Exception as e:
print(e)
setattr(view, '_datatables_total_count', total_count)

# Merge with the custom search
queryset = queryset.union(qs_stickers1)
queryset = queryset.union(qs_stickers2)

return queryset


Expand Down Expand Up @@ -1197,13 +1295,16 @@ def record_lost(self, request, *args, **kwargs):
def request_replacement(self, request, *args, **kwargs):
# internal
sticker = self.get_object()
data = request.data
# data = request.data
data = {}

# Update Sticker action
data['sticker'] = sticker.id
data['action'] = 'Request replacement'
data['user'] = request.user.id
serializer = StickerActionDetailSerializer(data=request.data)
data['waive_the_fee'] = request.data.get('waive_the_fee', False)
data['reason'] = request.data.get('details', {}).get('reason', '')
serializer = StickerActionDetailSerializer(data=data)
serializer.is_valid(raise_exception=True)
details = serializer.save()

Expand Down Expand Up @@ -1234,6 +1335,18 @@ def get_queryset(self):
qs = Sticker.objects.filter(status__in=Sticker.EXPOSED_STATUS).order_by('-date_updated', '-date_created')
return qs

def list(self, request, *args, **kwargs):
"""
User is accessing /external/ page
"""
qs = self.get_queryset()
qs = self.filter_queryset(qs)

self.paginator.page_size = qs.count()
result_page = self.paginator.paginate_queryset(qs, request)
serializer = StickerSerializer(result_page, context={'request': request}, many=True)
return self.paginator.get_paginated_response(serializer.data)


class DcvAdmissionPaginatedViewSet(viewsets.ModelViewSet):
filter_backends = (DcvAdmissionFilterBackend,)
Expand Down Expand Up @@ -1291,17 +1404,18 @@ def create_mooring_licence_application(self, request, *args, **kwargs):
waiting_list_allocation=waiting_list_allocation,
date_invited=current_date,
)
waiting_list_allocation.proposal_applicant.copy_self_to_proposal(new_proposal)
logger.info(f'Offering new Mooring Site Licence application: [{new_proposal}], which has been created from the waiting list allocation: [{waiting_list_allocation}].')

# Copy applicant details to the new proposal
proposal_applicant = ProposalApplicant.objects.get(proposal=waiting_list_allocation.current_proposal)
# proposal_applicant.copy_self_to_proposal(new_proposal)
logger.info(f'ProposalApplicant: [{proposal_applicant}] has been copied from the proposal: [{waiting_list_allocation.current_proposal}] to the mooring site licence application: [{new_proposal}].')

# Copy vessel details to the new proposal
waiting_list_allocation.current_proposal.copy_vessel_details(new_proposal)
logger.info(f'Vessel details have been copied from the proposal: [{waiting_list_allocation.current_proposal}] to the mooring site licence application: [{new_proposal}].')

new_proposal.null_vessel_on_create = False
new_proposal.save()

waiting_list_allocation.log_user_action(f'Offer new Mooring Site Licence application: {new_proposal.lodgement_number}.', request)

if new_proposal:
# send email
send_create_mooring_licence_application_email_notification(request, waiting_list_allocation, new_proposal)
Expand Down
50 changes: 44 additions & 6 deletions mooringlicensing/components/approvals/email.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import datetime
import logging
import ledger_api_client
import mimetypes
import pytz
import requests
from dateutil.relativedelta import relativedelta
from django.contrib.auth.models import Group
Expand Down Expand Up @@ -245,7 +247,7 @@ def send_approval_cancelled_due_to_no_vessels_nominated_mail(approval, request=N
_log_user_email(msg, approval.submitter_obj, proposal.submitter_obj, sender=sender_user)

return msg


def send_vessel_nomination_reminder_mail(approval, request=None):
# 10
Expand Down Expand Up @@ -712,6 +714,34 @@ def send_approval_surrender_email_notification(approval, request=None, already_s
_log_user_email(msg, approval.submitter_obj, proposal.submitter_obj, sender=sender_user)


def send_swap_moorings_application_created_notification(mooring_licence, request):
email = TemplateEmailBase(
subject=f'Swap moorings application created',
html_template='mooringlicensing/emails_2/swap_moorings_application_created.html',
txt_template='mooringlicensing/emails_2/swap_moorings_application_created.txt',
)
proposal = mooring_licence.current_proposal

context = {
'dashboard_external_url': get_public_url(request),
'recipient': mooring_licence.submitter_obj,
}
all_ccs = []
all_bccs = []
attachments = []

all_bccs = proposal.assessor_recipients

msg = email.send(proposal.submitter_obj.email, cc=all_ccs, bcc=all_bccs, context=context, attachments=attachments)
if msg:
sender = request.user if request else settings.DEFAULT_FROM_EMAIL
_log_approval_email(msg, mooring_licence, sender=sender, attachments=attachments)
if mooring_licence.org_applicant:
_log_org_email(msg, mooring_licence.org_applicant, proposal.submitter_obj, sender=sender)
else:
_log_user_email(msg, mooring_licence.submitter_obj, proposal.submitter_obj, sender=sender)


def send_approval_reinstate_email_notification(approval, request):
# 31 Reinstated
# email to licence/permit holder when licence/permit is reinstated or when suspension ends
Expand Down Expand Up @@ -789,7 +819,9 @@ def send_reissue_ml_after_sale_recorded_email(approval, request, vessel_ownershi
if cc_list:
all_ccs = [cc_list]

msg = email.send(proposal.submitter_obj.email, cc=all_ccs, context=context, attachments=attachments)
all_bccs = proposal.assessor_recipients

msg = email.send(proposal.submitter_obj.email, cc=all_ccs, bcc=all_bccs, context=context, attachments=attachments)
if msg:
sender = request.user if request else settings.DEFAULT_FROM_EMAIL
_log_approval_email(msg, approval, sender=sender, attachments=attachments)
Expand Down Expand Up @@ -833,7 +865,9 @@ def send_reissue_wla_after_sale_recorded_email(approval, request, vessel_ownersh
if cc_list:
all_ccs = [cc_list]

msg = email.send(proposal.submitter_obj.email, cc=all_ccs, context=context, attachments=attachments)
all_bccs = proposal.assessor_recipients

msg = email.send(proposal.submitter_obj.email, cc=all_ccs, bcc=all_bccs, context=context, attachments=attachments)
if msg:
sender = request.user if request else settings.DEFAULT_FROM_EMAIL
_log_approval_email(msg, approval, sender=sender, attachments=attachments)
Expand Down Expand Up @@ -879,7 +913,9 @@ def send_reissue_aup_after_sale_recorded_email(approval, request, vessel_ownersh
if cc_list:
all_ccs = [cc_list]

msg = email.send(proposal.submitter_obj.email, cc=all_ccs, context=context, attachments=attachments)
all_bccs = proposal.assessor_recipients

msg = email.send(proposal.submitter_obj.email, cc=all_ccs, bcc=all_bccs, context=context, attachments=attachments)
if msg:
sender = request.user if request else settings.DEFAULT_FROM_EMAIL
_log_approval_email(msg, approval, sender=sender, attachments=attachments)
Expand Down Expand Up @@ -925,7 +961,9 @@ def send_reissue_aap_after_sale_recorded_email(approval, request, vessel_ownersh
if cc_list:
all_ccs = [cc_list]

msg = email.send(proposal.submitter_obj.email, cc=all_ccs, context=context, attachments=attachments)
all_bccs = proposal.assessor_recipients

msg = email.send(proposal.submitter_obj.email, cc=all_ccs, bcc=all_bccs, context=context, attachments=attachments)
if msg:
sender = request.user if request else settings.DEFAULT_FROM_EMAIL
_log_approval_email(msg, approval, sender=sender, attachments=attachments)
Expand Down Expand Up @@ -1049,7 +1087,7 @@ def send_aup_revoked_due_to_relinquishment_email(request, authorised_user_permit

context = {
'public_url': get_public_url(request),
'recipient': authorised_user_permit.submitter,
'recipient': authorised_user_permit.submitter_obj,
'mooring': mooring,
'stickers_to_be_returned': stickers_to_be_returned,
'dashboard_external_url': get_public_url(request),
Expand Down
Loading

0 comments on commit 35acaa8

Please sign in to comment.