diff --git a/asset/forms.py b/asset/forms.py index e85d5a20b..321200ad0 100644 --- a/asset/forms.py +++ b/asset/forms.py @@ -49,6 +49,8 @@ class AssetForm(ModelForm): return_status__isnull=True ): raise ValidationError('Asset in use you can"t change the status') + if Asset.objects.filter(asset_tracking_id=self.data["asset_tracking_id"]).exclude(id=instance.pk).exists(): + raise ValidationError({"asset_tracking_id":'Already asset with this tracking id exists.'}) class AssetCategoryForm(ModelForm): class Meta: model = AssetCategory diff --git a/asset/templates/asset/asset_list.html b/asset/templates/asset/asset_list.html index 21bbbf012..d70bd1dab 100644 --- a/asset/templates/asset/asset_list.html +++ b/asset/templates/asset/asset_list.html @@ -58,9 +58,10 @@ id="oh-btn-asset-update-modal"> - @@ -70,15 +71,15 @@ class="oh-btn oh-btn--light-bkg w-100 " data-toggle="oh-modal-toggle" data-target="#AssetCategoryModal" - hx-get="{% url 'asset-update' id=asset.id %}?{{pg}}" + hx-get="{% url 'asset-update' asset.id %}?{{pg}}" title="{% trans 'Update' %}" hx-target="#AssetModal" id="oh-btn-asset-update-modal"> - diff --git a/asset/templates/category/asset_category.html b/asset/templates/category/asset_category.html index fd02da80a..4658861a1 100644 --- a/asset/templates/category/asset_category.html +++ b/asset/templates/category/asset_category.html @@ -47,19 +47,18 @@ diff --git a/asset/urls.py b/asset/urls.py index bb27a1604..48c27ea68 100644 --- a/asset/urls.py +++ b/asset/urls.py @@ -4,39 +4,92 @@ from . import views urlpatterns = [ - - path('asset-creation//', views.asset_creation,name='asset-creation'), - path('asset-list/', views.asset_list,name='asset-list'), - path('asset-update//', views.asset_update,name='asset-update'), - path('asset-delete//', views.asset_delete,name='asset-delete'), - path('asset-information//', views.asset_information,name='asset-information'), - - path('asset-category-view', views.asset_category_view,name='asset-category-view'), - path('asset-category-view-search-filter', views.asset_category_view_search_filter,name='asset-category-view-search-filter'), - path('asset-category-creation', views.asset_category_creation,name='asset-category-creation'), - path('asset-category-update/', views.asset_category_update,name='asset-category-update'), - path('asset-category-delete/', views.asset_category_delete,name='asset-category-delete'), - - path('asset-request-creation', views.asset_request_creation,name='asset-request-creation'), - path('asset-request-allocation-view', views.asset_request_alloaction_view,name='asset-request-allocation-view'), - path('asset-request-allocation-view-search-filter', views.asset_request_alloaction_view_search_filter,name='asset-request-allocation-view-search-filter'), - path('asset-request-approve//', views.asset_request_approve,name='asset-request-approve'), - path('asset-request-reject//', views.asset_request_reject,name='asset-request-reject'), - - path('asset-allocate-creation', views.asset_allocate_creation,name='asset-allocate-creation'), - path('asset-allocate-return//',views.asset_allocate_return,name='asset-allocate-return'), - path('asset-excel', views.asset_excel, name='asset-excel'), - - path('asset-import', views.asset_import, name='asset-import'), - path('asset-export-excel', views.asset_export_excel, name='asset-export-excel'), - - path('asset-batch-number-creation', views.asset_batch_number_creation, name='asset-batch-number-creation'), - path('asset-batch-view', views.asset_batch_view, name='asset-batch-view'), - path('asset-batch-number-search', views.asset_batch_number_search, name='asset-batch-number-search'), - path('asset-batch-update/', views.asset_batch_update, name='asset-batch-update'), - path('asset-batch-number-delete/', views.asset_batch_number_delete, name='asset-batch-number-delete'), - - path('asset-count-update', views.asset_count_update, name='asset-count-update'), - - + path("asset-creation//", views.asset_creation, name="asset-creation"), + path("asset-list/", views.asset_list, name="asset-list"), + path("asset-update//", views.asset_update, name="asset-update"), + path("asset-delete//", views.asset_delete, name="asset-delete"), + path( + "asset-information//", views.asset_information, name="asset-information" + ), + path("asset-category-view", views.asset_category_view, name="asset-category-view"), + path( + "asset-category-view-search-filter", + views.asset_category_view_search_filter, + name="asset-category-view-search-filter", + ), + path( + "asset-category-creation", + views.asset_category_creation, + name="asset-category-creation", + ), + path( + "asset-category-update/", + views.asset_category_update, + name="asset-category-update", + ), + path( + "asset-category-delete/", + views.delete_asset_category, + name="asset-category-delete", + ), + path( + "asset-request-creation", + views.asset_request_creation, + name="asset-request-creation", + ), + path( + "asset-request-allocation-view", + views.asset_request_alloaction_view, + name="asset-request-allocation-view", + ), + path( + "asset-request-allocation-view-search-filter", + views.asset_request_alloaction_view_search_filter, + name="asset-request-allocation-view-search-filter", + ), + path( + "asset-request-approve//", + views.asset_request_approve, + name="asset-request-approve", + ), + path( + "asset-request-reject//", + views.asset_request_reject, + name="asset-request-reject", + ), + path( + "asset-allocate-creation", + views.asset_allocate_creation, + name="asset-allocate-creation", + ), + path( + "asset-allocate-return//", + views.asset_allocate_return, + name="asset-allocate-return", + ), + path("asset-excel", views.asset_excel, name="asset-excel"), + path("asset-import", views.asset_import, name="asset-import"), + path("asset-export-excel", views.asset_export_excel, name="asset-export-excel"), + path( + "asset-batch-number-creation", + views.asset_batch_number_creation, + name="asset-batch-number-creation", + ), + path("asset-batch-view", views.asset_batch_view, name="asset-batch-view"), + path( + "asset-batch-number-search", + views.asset_batch_number_search, + name="asset-batch-number-search", + ), + path( + "asset-batch-update/", + views.asset_batch_update, + name="asset-batch-update", + ), + path( + "asset-batch-number-delete/", + views.asset_batch_number_delete, + name="asset-batch-number-delete", + ), + path("asset-count-update", views.asset_count_update, name="asset-count-update"), ] diff --git a/asset/views.py b/asset/views.py index b315673da..b6974bf5e 100644 --- a/asset/views.py +++ b/asset/views.py @@ -1,54 +1,76 @@ -from django.shortcuts import render, redirect -from .forms import AssetBatchForm, AssetForm, AssetRequestForm, AssetAllocationForm, AssetCategoryForm, AssetReturnForm -from .models import Asset, AssetRequest, AssetAssignment, AssetCategory, AssetLot -from django.http import HttpResponse, HttpResponseRedirect +"""" +asset.py + +This module is used to """ import pandas as pd -from .filters import AssetExportFilter, AssetFilter +from django.shortcuts import render, redirect +from django.http import HttpResponse, HttpResponseRedirect from django.contrib import messages from django.core.paginator import Paginator -from .filters import AssetAllocationFilter, AssetExportFilter, AssetRequestFilter, CustomAssetFilter, AssetCategoryFilter -from horilla.decorators import login_required,hx_request_required -from horilla.decorators import permission_required from django.utils.translation import gettext_lazy as _ from notifications.signals import notify -from employee.models import Employee +from horilla.decorators import login_required, hx_request_required +from horilla.decorators import permission_required +from asset.models import Asset, AssetRequest, AssetAssignment, AssetCategory, AssetLot +from asset.forms import ( + AssetBatchForm, + AssetForm, + AssetRequestForm, + AssetAllocationForm, + AssetCategoryForm, + AssetReturnForm, +) +from asset.filters import ( + AssetAllocationFilter, + AssetRequestFilter, + CustomAssetFilter, + AssetCategoryFilter, + AssetExportFilter, + AssetFilter, +) + @login_required @hx_request_required -@permission_required('asset.add_asset') +@permission_required("asset.add_asset") def asset_creation(request, id): """ View function for creating a new asset object. Args: - request (HttpRequest): A Django HttpRequest object that contains information about the current request. - id (int): An integer representing the ID of the asset category for which the asset is being created. + request (HttpRequest): A Django HttpRequest object that contains information + about the current request. + id (int): An integer representing the ID of the asset category for which + the asset is being created. Returns: - If the request method is 'POST' and the form is valid, the function saves the new asset object to the database + If the request method is 'POST' and the form is valid, the function saves the + new asset object to the database and redirects to the asset creation page with a success message. - If the form is not valid, the function returns the asset creation page with the form containing the invalid data. - If the request method is not 'POST', the function renders the asset creation page with the form initialized with + If the form is not valid, the function returns the asset creation page with the + form containing the invalid data. + If the request method is not 'POST', the function renders the asset creation + page with the form initialized with the ID of the asset category for which the asset is being created. Raises: None """ - initial_data = {'asset_category_id': id} + initial_data = {"asset_category_id": id} form = AssetForm(initial=initial_data) - context = {'asset_creation_form': form} - if request.method == 'POST': + context = {"asset_creation_form": form} + if request.method == "POST": form = AssetForm(request.POST, initial=initial_data) if form.is_valid(): form.save() - messages.success(request, _('Asset created successfully')) - return redirect('asset-creation', id=id) + messages.success(request, _("Asset created successfully")) + return redirect("asset-creation", id=id) else: - context['asset_creation_form'] = form - return render(request, 'asset/asset_creation.html', context) + context["asset_creation_form"] = form + return render(request, "asset/asset_creation.html", context) @login_required @hx_request_required -@permission_required('asset.delete_asset') +@permission_required("asset.delete_asset") def asset_update(request, id): """ Updates an asset with the given ID. @@ -64,29 +86,31 @@ def asset_update(request, id): - If the request method is POST and the form is valid, a redirect to the asset list view for the asset's category. """ - if request.method == 'GET': - # modal form get - asset_under = request.GET.get('asset_under') - elif request.method == 'POST': + if request.method == "GET": + # modal form get + asset_under = request.GET.get("asset_under") + elif request.method == "POST": # modal form post - asset_under = request.POST.get('asset_under') + asset_under = request.POST.get("asset_under") if not asset_under: # if asset there is no asset_under data that means the request is form the category list - asset_under = 'asset_category' + asset_under = "asset_category" instance = Asset.objects.get(id=id) asset_form = AssetForm(instance=instance) - previous_data = request.environ['QUERY_STRING'] - context = {'asset_form': asset_form, - 'asset_under':asset_under, - 'pg':previous_data} - if request.method == 'POST': + previous_data = request.environ["QUERY_STRING"] + context = { + "asset_form": asset_form, + "asset_under": asset_under, + "pg": previous_data, + } + if request.method == "POST": asset_form = AssetForm(request.POST, instance=instance) if asset_form.is_valid(): asset_form.save() - messages.success(request,_('Asset Updated')) - context['asset_form'] = asset_form - return render(request, 'asset/asset_update.html', context) + messages.success(request, _("Asset Updated")) + context["asset_form"] = asset_form + return render(request, "asset/asset_update.html", context) @login_required @@ -102,91 +126,105 @@ def asset_information(request, id): """ asset = Asset.objects.get(id=id) - context = {'asset': asset} - return render(request, 'asset/asset_information.html', context) + context = {"asset": asset} + return render(request, "asset/asset_information.html", context) @login_required -@permission_required('asset.delete_asset') +@permission_required("asset.delete_asset") def asset_delete(request, id): """Delete the asset with the given id. - If the asset is currently in use, display an info message and redirect to the asset list. + If the asset is currently in use, display an info message and + redirect to the asset list. Otherwise, delete the asset and display a success message. Args: request: HttpRequest object representing the current request. id: int representing the id of the asset to be deleted. Returns: - If the asset is currently in use or the asset list filter is applied, render the asset list template + If the asset is currently in use or the asset list filter is + applied, render the asset list template with the corresponding context. - Otherwise, redirect to the asset list view for the asset category of the deleted asset. + Otherwise, redirect to the asset list view for the asset + category of the deleted asset. """ asset = Asset.objects.get(id=id) status = asset.asset_status - asset_list_filter = request.GET.get('asset_list') + asset_list_filter = request.GET.get("asset_list") asset_allocation = AssetAssignment.objects.filter(asset_id=asset).first() - if asset_list_filter : - # if the asset deleted is from the filterd list of asset + if asset_list_filter: + # if the asset deleted is from the filterd list of asset asset_under = "asset_filter" assets = Asset.objects.all() - previous_data = request.environ['QUERY_STRING'] + previous_data = request.environ["QUERY_STRING"] asset_filtered = AssetFilter(request.GET, queryset=assets) asset_list = asset_filtered.qs paginator = Paginator(asset_list, 20) - page_number = request.GET.get('page') + page_number = request.GET.get("page") page_obj = paginator.get_page(page_number) - context = { "assets": page_obj, - "pg": previous_data, - "asset_category_id": asset.asset_category_id.id, - "asset_under":asset_under - } - if status =='In use': - messages.info(request,_('Asset is in use')) - return render(request, 'asset/asset_list.html', context) + context = { + "assets": page_obj, + "pg": previous_data, + "asset_category_id": asset.asset_category_id.id, + "asset_under": asset_under, + } + if status == "In use": + messages.info(request, _("Asset is in use")) + return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/")) + elif asset_allocation: # if this asset is used in any allocation - messages.error(request,_('Asset is used in allocation!.')) - return render(request, 'asset/asset_list.html', context) + messages.error(request, _("Asset is used in allocation!.")) + return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/")) + asset.delete() - messages.success(request,_('Asset deleted successfully')) - return render(request, 'asset/asset_list.html', context) + messages.success(request, _("Asset deleted successfully")) + return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/")) + else: # if the asset is deleted under the category - if status =='In use': - #if asset under the category - messages.info(request,_('Asset is in use')) - return redirect('asset-list',id=asset.asset_category_id.id) + if status == "In use": + # if asset under the category + messages.info(request, _("Asset is in use")) + return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/")) + elif asset_allocation: # if this asset is used in any allocation - messages.error(request,_('Asset is used in allocation!.')) - return redirect('asset-list',id=asset.asset_category_id.id) + messages.error(request, _("Asset is used in allocation!.")) + return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/")) + asset.delete() - messages.success(request,_('Asset deleted successfully')) - return redirect('asset-list',id=asset.asset_category_id.id) + messages.success(request, _("Asset deleted successfully")) + return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/")) + @login_required @hx_request_required -def asset_list(request, id): +def asset_list(request, id): """ - View function is used as asset list inside a category and also in filterd asset list + View function is used as asset list inside a category and also in + filterd asset list Args: - request (HttpRequest): A Django HttpRequest object that contains information about the current request. - id (int): An integer representing the id of the asset category to list assets for. + request (HttpRequest): A Django HttpRequest object that contains + information about the current request. + id (int): An integer representing the id of the asset category + to list assets for. Returns: - A rendered HTML template that displays a paginated list of assets in the given asset category. + A rendered HTML template that displays a paginated list of assets in the given + asset category. Raises: None """ - asset_list_filter = request.GET.get('asset_list') + asset_list_filter = request.GET.get("asset_list") context = {} if asset_list_filter: # if the data is present means that it is for asset filtered list - query = request.GET.get('query') + query = request.GET.get("query") asset_under = "asset_filter" if query: - assets_in_category = Asset.objects.filter(asset_name__icontains = query) + assets_in_category = Asset.objects.filter(asset_name__icontains=query) else: assets_in_category = Asset.objects.all() else: @@ -195,26 +233,27 @@ def asset_list(request, id): asset_category = AssetCategory.objects.get(id=id) assets_in_category = Asset.objects.filter(asset_category_id=asset_category) - previous_data = request.environ['QUERY_STRING'] + previous_data = request.environ["QUERY_STRING"] asset_filtered = AssetFilter(request.GET, queryset=assets_in_category) asset_list = asset_filtered.qs # Change 20 to the desired number of items per page paginator = Paginator(asset_list, 20) - page_number = request.GET.get('page') + page_number = request.GET.get("page") page_obj = paginator.get_page(page_number) - context = {"assets": page_obj, - "pg": previous_data, - "asset_category_id": id, - "asset_under": asset_under, - "asset_count": len(assets_in_category) or None} - return render(request, 'asset/asset_list.html', context) - + context = { + "assets": page_obj, + "pg": previous_data, + "asset_category_id": id, + "asset_under": asset_under, + "asset_count": len(assets_in_category) or None, + } + return render(request, "asset/asset_list.html", context) @login_required @hx_request_required -@permission_required('asset.add_assetcategory') +@permission_required("asset.add_assetcategory") def asset_category_creation(request): """ Allow a user to create a new AssetCategory object using a form. @@ -226,19 +265,19 @@ def asset_category_creation(request): asset_category_form = AssetCategoryForm() context = {"asset_category_form": asset_category_form} - if request.method == 'POST': + if request.method == "POST": asset_category_form = AssetCategoryForm(request.POST) if asset_category_form.is_valid(): asset_category_form.save() - messages.success(request, _('Asset category created successfully')) + messages.success(request, _("Asset category created successfully")) else: - context['asset_category_form'] = asset_category_form - return render(request, 'category/asset_category_creation.html', context) + context["asset_category_form"] = asset_category_form + return render(request, "category/asset_category_creation.html", context) @login_required @hx_request_required -@permission_required('asset.change_assetcategory') +@permission_required("asset.change_assetcategory") def asset_category_update(request, id): """ This view is used to update an existing asset category. @@ -249,25 +288,22 @@ def asset_category_update(request, id): Rendered HTML template. """ - previous_data = request.environ['QUERY_STRING'] + previous_data = request.environ["QUERY_STRING"] asset_category = AssetCategory.objects.get(id=id) asset_category_form = AssetCategoryForm(instance=asset_category) - context = { - 'asset_category_update_form': asset_category_form, - 'pg': previous_data - } - if request.method == 'POST': + context = {"asset_category_update_form": asset_category_form, "pg": previous_data} + if request.method == "POST": asset_category_form = AssetCategoryForm(request.POST, instance=asset_category) if asset_category_form.is_valid(): asset_category_form.save() - messages.success(request, _('Asset category updated successfully')) + messages.success(request, _("Asset category updated successfully")) else: - context['asset_category_form'] = asset_category_form + context["asset_category_form"] = asset_category_form - return render(request, 'category/asset_category_update.html', context) + return render(request, "category/asset_category_update.html", context) -@permission_required('asset.delete_assetcategory') +@permission_required("asset.delete_assetcategory") def asset_category_delete(request, id): """ Deletes an asset category and redirects to the asset category view. @@ -280,28 +316,38 @@ def asset_category_delete(request, id): None. """ asset_category = AssetCategory.objects.get(id=id) - asset_status = Asset.objects.filter(asset_category_id=asset_category).filter(asset_status='In use') + asset_status = Asset.objects.filter(asset_category_id=asset_category).filter( + asset_status="In use" + ) if asset_status: - messages.info(request, _('There are assets in use in the %(asset_category)s category.') % {'asset_category':asset_category}) + messages.info( + request, + _("There are assets in use in the %(asset_category)s category.") + % {"asset_category": asset_category}, + ) return redirect(asset_category_view_search_filter) asset_category.delete() - messages.success(request, _('Asset Category Deleted')) + messages.success(request, _("Asset Category Deleted")) return redirect(asset_category_view_search_filter) def filter_pagination_asset_category(request): - search = request.GET.get('search') + search = request.GET.get("search") if search is None: search = "" - previous_data = request.environ['QUERY_STRING'] - asset_category_queryset = AssetCategory.objects.all().filter(asset_category_name__icontains=search) - asset_category_filtered = AssetCategoryFilter(request.GET, queryset=asset_category_queryset) + previous_data = request.environ["QUERY_STRING"] + asset_category_queryset = AssetCategory.objects.all().filter( + asset_category_name__icontains=search + ) + asset_category_filtered = AssetCategoryFilter( + request.GET, queryset=asset_category_queryset + ) asset_export_filter = AssetExportFilter(request.GET, queryset=Asset.objects.all()) asset_category_paginator = Paginator(asset_category_filtered.qs, 20) - page_number = request.GET.get('page') + page_number = request.GET.get("page") asset_categorys = asset_category_paginator.get_page(page_number) asset_creation_form = AssetForm() @@ -312,7 +358,6 @@ def filter_pagination_asset_category(request): "asset_category_form": asset_category_form, "asset_export_filter": asset_export_filter, "asset_categorys": asset_categorys, - "asset_category_filter_form": asset_category_filtered.form, "asset_filter_form": asset_filter_form.form, "pg": previous_data, @@ -320,41 +365,44 @@ def filter_pagination_asset_category(request): @login_required -@permission_required('asset.view_assetcategory') +@permission_required("asset.view_assetcategory") def asset_category_view(request): """ View function for rendering a paginated list of asset categories. Args: - request (HttpRequest): A Django HttpRequest object that contains information about the current request. + request (HttpRequest): A Django HttpRequest object that contains information + about the current request. Returns: A rendered HTML template that displays a paginated list of asset categories. Raises: None """ context = filter_pagination_asset_category(request) - return render(request, 'category/asset_category_view.html', context) + return render(request, "category/asset_category_view.html", context) @login_required -@permission_required('asset.view_assetcategory') +@permission_required("asset.view_assetcategory") def asset_category_view_search_filter(request): """ View function for rendering a paginated list of asset categories with search and filter options. Args: - request (HttpRequest): A Django HttpRequest object that contains information about the current request. + request (HttpRequest): A Django HttpRequest object that contains information + about the current request. Returns: - A rendered HTML template that displays a paginated list of asset categories with search and filter options. + A rendered HTML template that displays a paginated list of asset + categories with search and filter options. Raises: None """ - - search_type = request.GET.get('type') - query = request.GET.get('search') - if search_type =='asset': + + search_type = request.GET.get("type") + query = request.GET.get("search") + if search_type == "asset": # searching asset will redirect to asset list and pass the query - return redirect(f'/asset/asset-list/0?asset_list=asset&query={query}') + return redirect(f"/asset/asset-list/0?asset_list=asset&query={query}") context = filter_pagination_asset_category(request) - return render(request, 'category/asset_category.html', context) + return render(request, "category/asset_category.html", context) @login_required @@ -362,27 +410,30 @@ def asset_request_creation(request): """ Creates a new AssetRequest object and saves it to the database. Renders the asset_request_creation.html template if the request method is GET. - If the request method is POST and the form data is valid, the new AssetRequest is saved to the database and + If the request method is POST and the form data is valid, the new + AssetRequest is saved to the database and the user is redirected to the asset_request_view_search_filter view. - If the form data is invalid, or if the request method is POST but the form data is not present, the user is - presented with the asset_request_creation.html template with error messages displayed. + If the form data is invalid, or if the request method is POST but the + form data is not present, the user is + presented with the asset_request_creation.html template with error + messages displayed. """ # intitial = {'requested_employee_id':request.user.employee_get} form = AssetRequestForm(user=request.user) - context = {'asset_request_form':form} - if request.method == 'POST': - form = AssetRequestForm(request.POST,user=request.user) + context = {"asset_request_form": form} + if request.method == "POST": + form = AssetRequestForm(request.POST, user=request.user) if form.is_valid(): form.save() - messages.success(request,_('Asset request created!')) - return HttpResponse('') - context['asset_request_form'] = form - - return render(request,'request_allocation/asset_request_creation.html',context) + messages.success(request, _("Asset request created!")) + return HttpResponse("") + context["asset_request_form"] = form + + return render(request, "request_allocation/asset_request_creation.html", context) @login_required -@permission_required('asset.add_asset') +@permission_required("asset.add_asset") def asset_request_approve(request, id): """ Approves an asset request with the given ID and updates the corresponding asset record @@ -394,39 +445,50 @@ def asset_request_approve(request, id): A redirect response to the asset request allocation view, or an error message if the request with the given ID cannot be found or its asset has already been allocated. """ - + asset_request = AssetRequest.objects.filter(id=id).first() asset_category = asset_request.asset_category_id - assets = Asset.objects.filter(asset_category_id=asset_category).filter(asset_status='Available') - - form = AssetAllocationForm(initial={'asset_id':assets}) - context = {'asset_allocation_form':form, - 'id':id} - if request.method == 'POST': + assets = asset_category.asset_set.all() + form = AssetAllocationForm() + form.fields["asset_id"].queryset = assets + context = {"asset_allocation_form": form, "id": id} + if request.method == "POST": post_data = request.POST.dict() # Add additional fields to the dictionary - post_data['assigned_to_employee_id'] = asset_request.requested_employee_id - post_data['assigned_by_employee_id'] = request.user.employee_get + post_data["assigned_to_employee_id"] = asset_request.requested_employee_id + post_data["assigned_by_employee_id"] = request.user.employee_get form = AssetAllocationForm(post_data) if form.is_valid(): asset = form.instance.asset_id.id - asset =Asset.objects.filter(id=asset).first() - asset.asset_status = 'In use' + asset = Asset.objects.filter(id=asset).first() + asset.asset_status = "In use" asset.save() form = form.save() - asset_request.asset_request_status = 'Approved' + asset_request.asset_request_status = "Approved" asset_request.save() - messages.success(request,_('Asset request approved successfully!.')) - notify.send(request.user.employee_get, recipient=form.assigned_to_employee_id.employee_user_id, verb='Your asset request approved!.',redirect='/asset/asset-request-allocation-view',icon='bag-check') - response = render(request,'request_allocation/asset_approve.html',{'asset_allocation_form':form,'id':id}) - return HttpResponse(response.content.decode('utf-8') +'') - context['asset_allocation_form'] = form - - return render(request,'request_allocation/asset_approve.html',context) - + messages.success(request, _("Asset request approved successfully!.")) + notify.send( + request.user.employee_get, + recipient=form.assigned_to_employee_id.employee_user_id, + verb="Your asset request approved!.", + redirect="/asset/asset-request-allocation-view", + icon="bag-check", + ) + response = render( + request, + "request_allocation/asset_approve.html", + {"asset_allocation_form": form, "id": id}, + ) + return HttpResponse( + response.content.decode("utf-8") + "" + ) + context["asset_allocation_form"] = form + + return render(request, "request_allocation/asset_approve.html", context) + @login_required -@permission_required('asset.add_asset') +@permission_required("asset.add_asset") def asset_request_reject(request, id): """ View function to reject an asset request. @@ -435,40 +497,49 @@ def asset_request_reject(request, id): id (int): the id of the AssetRequest object to reject Returns: - HttpResponse: a redirect to the asset request list view with a success message if the asset request is rejected successfully, or a redirect to the asset request detail view with an error message if the asset request is not found or already rejected + HttpResponse: a redirect to the asset request list view with a success m + essage if the asset request is rejected successfully, or a redirect to the + asset request detail view with an error message if the asset request is not + found or already rejected """ asset_request = AssetRequest.objects.get(id=id) - asset_request.asset_request_status = 'Rejected' + asset_request.asset_request_status = "Rejected" asset_request.save() - messages.info(request,_('Asset request rejected')) - notify.send(request.user.employee_get, recipient=asset_request.requested_employee_id.employee_user_id, verb='Your asset request rejected!.',redirect='/asset/asset-request-allocation-view',icon='bag-check') - return HttpResponseRedirect(request. META. get('HTTP_REFERER', '/')) + messages.info(request, _("Asset request rejected")) + notify.send( + request.user.employee_get, + recipient=asset_request.requested_employee_id.employee_user_id, + verb="Your asset request rejected!.", + redirect="/asset/asset-request-allocation-view", + icon="bag-check", + ) + return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/")) @login_required -@permission_required('asset.add_asset') +@permission_required("asset.add_asset") def asset_allocate_creation(request): """ View function to create asset allocation. Returns: - to allocated view. """ - + form = AssetAllocationForm() - context = {'asset_allocation_form':form} - if request.method == 'POST': + context = {"asset_allocation_form": form} + if request.method == "POST": form = AssetAllocationForm(request.POST) if form.is_valid(): asset = form.instance.asset_id.id - asset =Asset.objects.filter(id=asset).first() - asset.asset_status = 'In use' + asset = Asset.objects.filter(id=asset).first() + asset.asset_status = "In use" asset.save() form.save() - messages.success(request,_('Asset allocated successfully!.')) - return HttpResponse('') - context['asset_allocation_form'] = form - - return render(request,'request_allocation/asset_allocation_creation.html',context) + messages.success(request, _("Asset allocated successfully!.")) + return HttpResponse("") + context["asset_allocation_form"] = form + + return render(request, "request_allocation/asset_allocation_creation.html", context) @login_required @@ -481,58 +552,80 @@ def asset_allocate_return(request, id): - message of the return """ - context = {'asset_return_form' : AssetReturnForm(), - 'asset_id':id} - if request.method == 'POST': + context = {"asset_return_form": AssetReturnForm(), "asset_id": id} + if request.method == "POST": asset = Asset.objects.filter(id=id).first() - asset_return_status = request.POST.get('return_status') - asset_return_date = request.POST.get('return_date') - asset_return_condition = request.POST.get('return_condition') - if asset_return_status == 'Healthy': - asset_allocation = AssetAssignment.objects.filter(asset_id=id,return_status__isnull=True).first() + asset_return_status = request.POST.get("return_status") + asset_return_date = request.POST.get("return_date") + asset_return_condition = request.POST.get("return_condition") + if asset_return_status == "Healthy": + asset_allocation = AssetAssignment.objects.filter( + asset_id=id, return_status__isnull=True + ).first() asset_allocation.return_date = asset_return_date asset_allocation.return_status = asset_return_status asset_allocation.return_condition = asset_return_condition asset_allocation.save() - asset.asset_status = 'Available' + asset.asset_status = "Available" asset.save() - messages.info(request,_('Asset Return Successful !.')) - return HttpResponseRedirect(request. META. get('HTTP_REFERER', '/')) - asset.asset_status = 'Not-Available' + messages.info(request, _("Asset Return Successful !.")) + return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/")) + asset.asset_status = "Not-Available" asset.save() - asset_allocation = AssetAssignment.objects.filter(asset_id=id,return_status__isnull=True).first() + asset_allocation = AssetAssignment.objects.filter( + asset_id=id, return_status__isnull=True + ).first() asset_allocation.return_date = asset_return_date asset_allocation.return_status = asset_return_status asset_allocation.return_condition = asset_return_condition asset_allocation.save() - messages.info(request,_('Asset Return Successful!.')) - return HttpResponseRedirect(request. META. get('HTTP_REFERER', '/')) - return render(request,'asset/asset_return_form.html',context) + messages.info(request, _("Asset Return Successful!.")) + return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/")) + return render(request, "asset/asset_return_form.html", context) def filter_pagination_asset_request_allocation(request): - - asset_request_alloaction_search = request.GET.get('search') + asset_request_alloaction_search = request.GET.get("search") if asset_request_alloaction_search is None: asset_request_alloaction_search = "" employee = request.user.employee_get - - assets = AssetAssignment.objects.filter(assigned_to_employee_id=employee).exclude(return_status__isnull=False).filter(asset_id__asset_name__icontains=asset_request_alloaction_search) - if request.user.has_perm(('asset.view_assetrequest','asset.view_assetassignment')): - asset_allocations_queryset = AssetAssignment.objects.all().filter(assigned_to_employee_id__employee_first_name__icontains=asset_request_alloaction_search) - asset_requests_queryset = AssetRequest.objects.all().filter(requested_employee_id__employee_first_name__icontains=asset_request_alloaction_search) - else: - asset_allocations_queryset = AssetAssignment.objects.filter(assigned_to_employee_id=employee).filter(assigned_to_employee_id__employee_first_name__icontains=asset_request_alloaction_search) - asset_requests_queryset = AssetRequest.objects.filter(requested_employee_id=employee).filter(requested_employee_id__employee_first_name__icontains=asset_request_alloaction_search) - previous_data = request.environ['QUERY_STRING'] + assets = ( + AssetAssignment.objects.filter(assigned_to_employee_id=employee) + .exclude(return_status__isnull=False) + .filter(asset_id__asset_name__icontains=asset_request_alloaction_search) + ) + if request.user.has_perm(("asset.view_assetrequest", "asset.view_assetassignment")): + asset_allocations_queryset = AssetAssignment.objects.all().filter( + assigned_to_employee_id__employee_first_name__icontains=asset_request_alloaction_search + ) + asset_requests_queryset = AssetRequest.objects.all().filter( + requested_employee_id__employee_first_name__icontains=asset_request_alloaction_search + ) + else: + asset_allocations_queryset = AssetAssignment.objects.filter( + assigned_to_employee_id=employee + ).filter( + assigned_to_employee_id__employee_first_name__icontains=asset_request_alloaction_search + ) + asset_requests_queryset = AssetRequest.objects.filter( + requested_employee_id=employee + ).filter( + requested_employee_id__employee_first_name__icontains=asset_request_alloaction_search + ) + + previous_data = request.environ["QUERY_STRING"] assets_filtered = CustomAssetFilter(request.GET, queryset=assets) - asset_request_filtered = AssetRequestFilter(request.GET, queryset=asset_requests_queryset) - asset_allocation_filtered = AssetAllocationFilter(request.GET, queryset=asset_allocations_queryset) + asset_request_filtered = AssetRequestFilter( + request.GET, queryset=asset_requests_queryset + ) + asset_allocation_filtered = AssetAllocationFilter( + request.GET, queryset=asset_allocations_queryset + ) asset_paginator = Paginator(assets_filtered.qs, 20) asset_request_paginator = Paginator(asset_request_filtered.qs, 20) asset_allocation_paginator = Paginator(asset_allocation_filtered.qs, 20) - page_number = request.GET.get('page') + page_number = request.GET.get("page") assets = asset_paginator.get_page(page_number) asset_requests = asset_request_paginator.get_page(page_number) asset_allocations = asset_allocation_paginator.get_page(page_number) @@ -558,7 +651,9 @@ def asset_request_alloaction_view(request): HttpResponse: The HTTP response object with the rendered HTML template. """ context = filter_pagination_asset_request_allocation(request) - return render(request, 'request_allocation/asset_request_allocation_view.html', context) + return render( + request, "request_allocation/asset_request_allocation_view.html", context + ) def asset_request_alloaction_view_search_filter(request): @@ -570,7 +665,9 @@ def asset_request_alloaction_view_search_filter(request): Rendered HTTP response with the filtered and paginated asset request allocation list. """ context = filter_pagination_asset_request_allocation(request) - return render(request, 'request_allocation/asset_request_allocation_list.html', context) + return render( + request, "request_allocation/asset_request_allocation_list.html", context + ) def convert_nan(val): @@ -578,46 +675,58 @@ def convert_nan(val): return None else: return val - + + @login_required -@permission_required('asset.add_asset') +@permission_required("asset.add_asset") def asset_import(request): - """ asset import view""" + """asset import view""" try: - if request.method == 'POST': - file = request.FILES.get('asset_import') + if request.method == "POST": + file = request.FILES.get("asset_import") if file is not None: try: df = pd.read_excel(file) except KeyError as e: - messages.error(request, f'{e}') + messages.error(request, f"{e}") return redirect(asset_category_view) # Create Asset objects from the DataFrame and save them to the database for index, row in df.iterrows(): - asset_name = convert_nan(row['Asset name']) - asset_description = convert_nan(row['Description']) - asset_tracking_id = convert_nan(row['Tracking id']) - purchase_date = convert_nan(row['Purchase date']) - purchase_cost = convert_nan(row['Purchase cost']) - category_name = convert_nan(row['Category']) - lot_number = convert_nan(row['lot number']) - status = convert_nan(row['Status']) - - asset_category, create = AssetCategory.objects.get_or_create(asset_category_name=category_name) - asset_lot_number, create = AssetLot.objects.get_or_create(lot_number=lot_number) - Asset.objects.create(asset_name=asset_name, asset_description=asset_description, asset_tracking_id=asset_tracking_id, - asset_purchase_date=purchase_date, asset_purchase_cost=purchase_cost, - asset_category_id=asset_category, asset_status=status, asset_lot_number_id=asset_lot_number) + asset_name = convert_nan(row["Asset name"]) + asset_description = convert_nan(row["Description"]) + asset_tracking_id = convert_nan(row["Tracking id"]) + purchase_date = convert_nan(row["Purchase date"]) + purchase_cost = convert_nan(row["Purchase cost"]) + category_name = convert_nan(row["Category"]) + lot_number = convert_nan(row["lot number"]) + status = convert_nan(row["Status"]) - messages.success(request, _('Successfully imported Assets')) + asset_category, create = AssetCategory.objects.get_or_create( + asset_category_name=category_name + ) + asset_lot_number, create = AssetLot.objects.get_or_create( + lot_number=lot_number + ) + Asset.objects.create( + asset_name=asset_name, + asset_description=asset_description, + asset_tracking_id=asset_tracking_id, + asset_purchase_date=purchase_date, + asset_purchase_cost=purchase_cost, + asset_category_id=asset_category, + asset_status=status, + asset_lot_number_id=asset_lot_number, + ) + + messages.success(request, _("Successfully imported Assets")) return redirect(asset_category_view) - messages.error(request, _('File Error')) + messages.error(request, _("File Error")) return redirect(asset_category_view) except Exception as e: - messages.error(request, f'{e}') + messages.error(request, f"{e}") return redirect(asset_category_view) @@ -626,13 +735,21 @@ def asset_excel(request): """asset excel download view""" try: - columns = ['Asset name', 'Description', 'Tracking id', - 'Purchase date', 'Purchase cost', 'Category', 'Status', 'lot number'] + columns = [ + "Asset name", + "Description", + "Tracking id", + "Purchase date", + "Purchase cost", + "Category", + "Status", + "lot number", + ] # Create a pandas DataFrame with columns but no data df = pd.DataFrame(columns=columns) # Write the DataFrame to an Excel file - response = HttpResponse(content_type='application/ms-excel') - response['Content-Disposition'] = 'attachment; filename="my_excel_file.xlsx"' + response = HttpResponse(content_type="application/ms-excel") + response["Content-Disposition"] = 'attachment; filename="my_excel_file.xlsx"' df.to_excel(response, index=False) return response except Exception as e: @@ -640,141 +757,130 @@ def asset_excel(request): @login_required -@permission_required('asset.add_asset') +@permission_required("asset.add_asset") def asset_export_excel(request): - """asset export view """ + """asset export view""" queryset_all = Asset.objects.all() if not queryset_all: - messages.warning(request, _('There are no assets to export.')) - return redirect('asset-category-view') # or some other URL + messages.warning(request, _("There are no assets to export.")) + return redirect("asset-category-view") # or some other URL queryset = AssetExportFilter(request.POST, queryset=queryset_all).qs - + # Convert the queryset to a Pandas DataFrame data = { - 'asset_name': [], - 'asset_description': [], - 'asset_tracking_id': [], - 'asset_purchase_date': [], - 'asset_purchase_cost': [], - 'asset_category_id': [], - 'asset_status': [], - 'asset_lot_number_id': [], + "asset_name": [], + "asset_description": [], + "asset_tracking_id": [], + "asset_purchase_date": [], + "asset_purchase_cost": [], + "asset_category_id": [], + "asset_status": [], + "asset_lot_number_id": [], } + + fields_to_check = [ + "asset_name", + "asset_description", + "asset_tracking_id", + "asset_purchase_date", + "asset_purchase_cost", + "asset_category_id", + "asset_status", + "asset_lot_number_id", + ] for asset in queryset: - try: - data['asset_name'].append(asset.asset_name) - except AttributeError: - data['asset_name'].append(None) - - try: - data['asset_description'].append(asset.asset_description) - except AttributeError: - data['asset_description'].append(None) - - try: - data['asset_tracking_id'].append(asset.asset_tracking_id) - except AttributeError: - data['asset_tracking_id'].append(None) - - try: - data['asset_purchase_date'].append(asset.asset_purchase_date) - except AttributeError: - data['asset_purchase_date'].append(None) - - try: - data['asset_purchase_cost'].append(asset.asset_purchase_cost) - except AttributeError: - data['asset_purchase_cost'].append(None) - - try: - data['asset_category_id'].append(asset.asset_category_id.asset_category_name) - except AttributeError: - data['asset_category_id'].append(None) - - try: - data['asset_status'].append(asset.asset_status) - except AttributeError: - data['asset_status'].append(None) - - try: - data['asset_lot_number_id'].append(asset.asset_lot_number_id.lot_number) - except AttributeError: - data['asset_lot_number_id'].append(None) + for field in fields_to_check: + # Get the value of the field for the current asset + value = getattr(asset, field) + + # Append the value if it exists, or append None if it's None + data[field].append(value if value is not None else None) - df = pd.DataFrame(data) + # Fill any missing values with None + for key in data: + data[key] = data[key] + [None] * (len(queryset) - len(data[key])) + + # Convert the data dictionary to a Pandas DataFrame + df = pd.DataFrame(data) # Convert any date fields to the desired format # Rename the columns as needed - df = df.rename(columns={ - 'asset_name': 'Asset name', - 'asset_description': 'Description', - 'asset_tracking_id': 'Tracking id', - 'asset_purchase_date': 'Purchase date', - 'asset_purchase_cost': 'Purchase cost', - 'asset_category_id': 'Category', - 'asset_status': 'Status', - 'asset_lot_number_id': 'lot number', - }) + df = df.rename( + columns={ + "asset_name": "Asset name", + "asset_description": "Description", + "asset_tracking_id": "Tracking id", + "asset_purchase_date": "Purchase date", + "asset_purchase_cost": "Purchase cost", + "asset_category_id": "Category", + "asset_status": "Status", + "asset_lot_number_id": "lot number", + } + ) # Write the DataFrame to an Excel file - response = HttpResponse(content_type='application/vnd.ms-excel') - response['Content-Disposition'] = 'attachment; filename="assets.xlsx"' + response = HttpResponse(content_type="application/vnd.ms-excel") + response["Content-Disposition"] = 'attachment; filename="assets.xlsx"' df.to_excel(response, index=False) return response @login_required def asset_batch_number_creation(request): - """asset batch number creation view """ + """asset batch number creation view""" asset_batch_form = AssetBatchForm() context = { - 'asset_batch_form': asset_batch_form, - } - if request.method == 'POST': + "asset_batch_form": asset_batch_form, + } + if request.method == "POST": asset_batch_form = AssetBatchForm(request.POST) if asset_batch_form.is_valid(): asset_batch_form.save() - messages.success(request,_('Batch number created successfully.')) - response = render(request,'batch/asset_batch_number_creation.html',context) - return HttpResponse(response.content.decode('utf-8') +'') + messages.success(request, _("Batch number created successfully.")) + response = render( + request, "batch/asset_batch_number_creation.html", context + ) + return HttpResponse( + response.content.decode("utf-8") + "" + ) else: context = { - 'asset_batch_form': asset_batch_form, + "asset_batch_form": asset_batch_form, } - return render(request,'batch/asset_batch_number_creation.html',context) - return render(request,'batch/asset_batch_number_creation.html',context) + return render(request, "batch/asset_batch_number_creation.html", context) + return render(request, "batch/asset_batch_number_creation.html", context) @login_required -@permission_required('asset.add_assetlot') +@permission_required("asset.add_assetlot") def asset_batch_view(request): """ View function to display details of all batch numbers. - + Returns: - all asset batch numbers based on page """ - + asset_batchs = AssetLot.objects.all() - previous_data = request.environ['QUERY_STRING'] + previous_data = request.environ["QUERY_STRING"] asset_batch_numbers_search_paginator = Paginator(asset_batchs, 20) - page_number = request.GET.get('page') - asset_batch_numbers = asset_batch_numbers_search_paginator.get_page( - page_number) + page_number = request.GET.get("page") + asset_batch_numbers = asset_batch_numbers_search_paginator.get_page(page_number) asset_batch_form = AssetBatchForm() context = { - 'batch_numbers': asset_batch_numbers, - 'asset_batch_form': asset_batch_form, - 'pg': previous_data} - return render(request, 'batch/asset_batch_number_view.html', context) + "batch_numbers": asset_batch_numbers, + "asset_batch_form": asset_batch_form, + "pg": previous_data, + } + return render(request, "batch/asset_batch_number_view.html", context) @login_required -@permission_required('asset.change_assetlot') +@permission_required("asset.change_assetlot") def asset_batch_update(request, id): """ View function to return asset. @@ -787,28 +893,31 @@ def asset_batch_update(request, id): asset_batch = AssetLot.objects.get(id=id) asset_batch_form = AssetBatchForm(instance=asset_batch) context = { - 'asset_batch_update_form': asset_batch_form, + "asset_batch_update_form": asset_batch_form, } - assigned_batch_number = Asset.objects.filter( - asset_lot_number_id=asset_batch_number) + assigned_batch_number = Asset.objects.filter(asset_lot_number_id=asset_batch_number) if assigned_batch_number: asset_batch_form = AssetBatchForm(instance=asset_batch) - asset_batch_form['lot_number'].field.widget.attrs.update({'readonly': 'readonly'}) - context['asset_batch_update_form'] = asset_batch_form - context['in_use_message'] = 'This batch number is already in-use' - if request.method == 'POST': + asset_batch_form["lot_number"].field.widget.attrs.update( + {"readonly": "readonly"} + ) + context["asset_batch_update_form"] = asset_batch_form + context["in_use_message"] = "This batch number is already in-use" + if request.method == "POST": asset_batch_form = AssetBatchForm(request.POST, instance=asset_batch_number) if asset_batch_form.is_valid(): asset_batch_form.save() - messages.info(request,_('Batch updated successfully.')) - response = render(request, 'batch/asset_batch_number_update.html', context) - return HttpResponse(response.content.decode('utf-8') +'') - context['asset_batch_update_form'] = asset_batch_form - return render(request, 'batch/asset_batch_number_update.html', context) + messages.info(request, _("Batch updated successfully.")) + response = render(request, "batch/asset_batch_number_update.html", context) + return HttpResponse( + response.content.decode("utf-8") + "" + ) + context["asset_batch_update_form"] = asset_batch_form + return render(request, "batch/asset_batch_number_update.html", context) @login_required -@permission_required('asset.delete_assetlot') +@permission_required("asset.delete_assetlot") def asset_batch_number_delete(request, id): """ View function to return asset. @@ -820,15 +929,16 @@ def asset_batch_number_delete(request, id): asset_batch_number = AssetLot.objects.get(id=id) assigned_batch_number = Asset.objects.filter(asset_lot_number_id=asset_batch_number) if assigned_batch_number: - messages.error(request,_('Batch number in-use')) + messages.error(request, _("Batch number in-use")) return redirect(asset_batch_view) asset_batch_number.delete() - messages.success(request,_('Batch number deleted')) + messages.success(request, _("Batch number deleted")) return redirect(asset_batch_view) + @login_required @hx_request_required -@permission_required('asset.delete_assetlot') +@permission_required("asset.delete_assetlot") def asset_batch_number_search(request): """ View function to return search data of asset batch number. @@ -839,14 +949,16 @@ def asset_batch_number_search(request): Returns: - message of the return """ - asset_batch_number_search = request.GET.get('search') + asset_batch_number_search = request.GET.get("search") if asset_batch_number_search is None: asset_batch_number_search = "" - asset_batchs = AssetLot.objects.all().filter(lot_number__icontains=asset_batch_number_search) - previous_data = request.environ['QUERY_STRING'] + asset_batchs = AssetLot.objects.all().filter( + lot_number__icontains=asset_batch_number_search + ) + previous_data = request.environ["QUERY_STRING"] asset_batch_numbers_search_paginator = Paginator(asset_batchs, 20) - page_number = request.GET.get('page') + page_number = request.GET.get("page") asset_batch_numbers = asset_batch_numbers_search_paginator.get_page(page_number) context = { @@ -854,7 +966,8 @@ def asset_batch_number_search(request): "pg": previous_data, } - return render(request, 'batch/asset_batch_number_list.html', context) + return render(request, "batch/asset_batch_number_list.html", context) + @login_required def asset_count_update(request): @@ -865,11 +978,24 @@ def asset_count_update(request): Returns: - count of asset inside the category """ - if request.method=='POST': - category_id = request.POST.get('asset_category_id') + if request.method == "POST": + category_id = request.POST.get("asset_category_id") if category_id is not None: category = AssetCategory.objects.get(id=category_id) asset_count = category.asset_set.count() return HttpResponse(asset_count) - return HttpResponse('error') - \ No newline at end of file + return HttpResponse("error") + + +@login_required +@permission_required("asset.delete_assetcategory") +def delete_asset_category(request, cat_id): + """ + This method is used to delete asset category + """ + try: + AssetCategory.objects.get(id=cat_id).delete() + messages.success(request, "Asset category deleted.") + except: + messages.error(request, "Something went wrong!") + return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/")) diff --git a/base/forms.py b/base/forms.py index 3b738c35d..a72b3e73a 100644 --- a/base/forms.py +++ b/base/forms.py @@ -1,44 +1,60 @@ +""" +forms.py +This module is used to register forms for base module +""" +import calendar +from typing import Any, Dict +import uuid import datetime -from typing import Any -from django.forms import widgets -from django.core.exceptions import ValidationError +from datetime import timedelta from django import forms from django.contrib.auth.models import Group, Permission -from base.models import Company, Department, JobPosition, JobRole, WorkType, EmployeeType, EmployeeShift, EmployeeShiftSchedule, RotatingShift, RotatingShiftAssign, RotatingWorkType, RotatingWorkTypeAssign, WorkTypeRequest, ShiftRequest, EmployeeShiftDay from django.forms import DateInput from django.core.exceptions import ValidationError -from employee.models import Employee -import uuid -import re -import calendar -from notifications.signals import notify -from datetime import timedelta from django.utils.translation import gettext as _ +from employee.models import Employee +from base.models import ( + Company, + Department, + JobPosition, + JobRole, + WorkType, + EmployeeType, + EmployeeShift, + EmployeeShiftSchedule, + RotatingShift, + RotatingShiftAssign, + RotatingWorkType, + RotatingWorkTypeAssign, + WorkTypeRequest, + ShiftRequest, + EmployeeShiftDay, +) # your form here def validate_time_format(value): - ''' + """ this method is used to validate the format of duration like fields. - ''' + """ if len(value) > 6: raise ValidationError("Invalid format, it should be HH:MM format") - try: + try: hour, minute = value.split(":") hour = int(hour) minute = int(minute) if len(str(hour)) > 3 or minute not in range(60): - raise ValidationError("Invalid time") - except ValueError as e: - raise ValidationError("Invalid format") from e + raise ValidationError("Invalid format, it should be HH:MM format") + except ValueError as error: + raise ValidationError("Invalid format, it should be HH:MM format") from error BASED_ON = [ - ('after', 'After'), - ('weekly', 'Weekend'), - ('monthly', 'Monthly'), + ("after", "After"), + ("weekly", "Weekend"), + ("monthly", "Monthly"), ] @@ -52,7 +68,7 @@ def get_next_week_date(target_day, start_date): Returns: datetime.date: The date of the next occurrence of the target day within the next week. -""" + """ if start_date.weekday() == target_day: return start_date days_until_target_day = (target_day - start_date.weekday()) % 7 @@ -63,24 +79,27 @@ def get_next_week_date(target_day, start_date): def get_next_monthly_date(start_date, rotate_every): """ - Given a start date and a rotation day (specified as an integer between 1 and 31, or the string 'last'), calculates the - next rotation date for a monthly rotation schedule. + Given a start date and a rotation day (specified as an integer between 1 and 31, or + the string 'last'),calculates the next rotation date for a monthly rotation schedule. - If the rotation day has not yet occurred in the current month, the next rotation date will be on the rotation day - of the current month. If the rotation day has already occurred in the current month, the next rotation date will be on - the rotation day of the next month. + If the rotation day has not yet occurred in the current month, the next rotation date + will be on the rotation day of the current month. If the rotation day has already + occurred in the current month, the next rotation date will be on the rotation day of + the next month. - If 'last' is specified as the rotation day, the next rotation date will be on the last day of the current month. + If 'last' is specified as the rotation day, the next rotation date will be on the + last day of the current month. Parameters: - start_date: The start date of the rotation schedule, as a datetime.date object. - - rotate_every: The rotation day, specified as an integer between 1 and 31, or the string 'last'. + - rotate_every: The rotation day, specified as an integer between 1 and 31, or the + string 'last'. Returns: - A datetime.date object representing the next rotation date. """ - if rotate_every == 'last': + if rotate_every == "last": # Set rotate_every to the last day of the current month last_day = calendar.monthrange(start_date.year, start_date.month)[1] rotate_every = str(last_day) @@ -88,223 +107,373 @@ def get_next_monthly_date(start_date, rotate_every): # Calculate the next change date if start_date.day <= rotate_every or rotate_every == 0: - # If the rotation day has not occurred yet this month, or if it's the last day of the month, set the next change date to the rotation day of this month + # If the rotation day has not occurred yet this month, or if it's the last- + # day of the month, set the next change date to the rotation day of this month try: - next_change = datetime.date( - start_date.year, start_date.month, rotate_every) + next_change = datetime.date(start_date.year, start_date.month, rotate_every) except ValueError: next_change = datetime.date( - start_date.year, start_date.month + 1, 1) # Advance to next month + start_date.year, start_date.month + 1, 1 + ) # Advance to next month # Set day to rotate_every next_change = datetime.date( - next_change.year, next_change.month, rotate_every) + next_change.year, next_change.month, rotate_every + ) else: - # If the rotation day has already occurred this month, set the next change date to the rotation day of the next month + # If the rotation day has already occurred this month, set the next change + # date to the rotation day of the next month last_day = calendar.monthrange(start_date.year, start_date.month)[1] next_month_start = start_date.replace(day=last_day) + timedelta(days=1) try: next_change = next_month_start.replace(day=rotate_every) except ValueError: - next_change = (next_month_start.replace( - month=next_month_start.month + 1) + timedelta(days=1)).replace(day=rotate_every) + next_change = ( + next_month_start.replace(month=next_month_start.month + 1) + + timedelta(days=1) + ).replace(day=rotate_every) return next_change class ModelForm(forms.ModelForm): + """ + Override django model's form to add initial styling + """ + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for field_name, field in self.fields.items(): widget = field.widget - if isinstance(widget,(forms.NumberInput, forms.EmailInput, forms.TextInput, forms.FileInput)): + if isinstance( + widget, + (forms.NumberInput, forms.EmailInput, forms.TextInput, forms.FileInput), + ): if field.label is not None: label = _(field.label.title()) field.widget.attrs.update( - {"class": "oh-input w-100", "placeholder": label}) + {"class": "oh-input w-100", "placeholder": label} + ) elif isinstance(widget, (forms.Select,)): label = "" if field.label is not None: label = _(field.label) field.empty_label = _("---Choose {label}---").format(label=label) field.widget.attrs.update( - {"class": "oh-select oh-select-2 select2-hidden-accessible"}) + {"class": "oh-select oh-select-2 select2-hidden-accessible"} + ) elif isinstance(widget, (forms.Textarea)): - field.widget.attrs.update({"class": "oh-input w-100","placeholder": _(field.label),"rows": 2,"cols": 40,}) - elif isinstance(widget,(forms.CheckboxInput,forms.CheckboxSelectMultiple,)): + field.widget.attrs.update( + { + "class": "oh-input w-100", + "placeholder": _(field.label), + "rows": 2, + "cols": 40, + } + ) + elif isinstance( + widget, + ( + forms.CheckboxInput, + forms.CheckboxSelectMultiple, + ), + ): field.widget.attrs.update({"class": "oh-switch__checkbox"}) class Form(forms.Form): + """ + Overrides to add initial styling to the django Form instance + """ + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for field_name, field in self.fields.items(): widget = field.widget - if isinstance(widget, (forms.NumberInput, forms.EmailInput, forms.TextInput)): - label = _(field.label) - field.widget.attrs.update({"class": "oh-input w-100", "placeholder": label}) + if isinstance( + widget, (forms.NumberInput, forms.EmailInput, forms.TextInput) + ): + if field.label is not None: + label = _(field.label) + field.widget.attrs.update( + {"class": "oh-input w-100", "placeholder": label} + ) elif isinstance(widget, (forms.Select,)): label = "" if field.label is not None: label = field.label.replace("id", " ") field.empty_label = _("---Choose {label}---").format(label=label) - field.widget.attrs.update({"class": "oh-select oh-select-2 select2-hidden-accessible"}) + field.widget.attrs.update( + {"class": "oh-select oh-select-2 select2-hidden-accessible"} + ) elif isinstance(widget, (forms.Textarea)): label = _(field.label) - field.widget.attrs.update({"class": "oh-input w-100","placeholder": label,"rows": 2,"cols": 40,}) - elif isinstance(widget,(forms.CheckboxInput,forms.CheckboxSelectMultiple,)): + field.widget.attrs.update( + { + "class": "oh-input w-100", + "placeholder": label, + "rows": 2, + "cols": 40, + } + ) + elif isinstance( + widget, + ( + forms.CheckboxInput, + forms.CheckboxSelectMultiple, + ), + ): field.widget.attrs.update({"class": "oh-switch__checkbox"}) - class UserGroupForm(ModelForm): + """ + Django user groups form + """ + class Meta: + """ + Meta class for additional options + """ + model = Group - fields = '__all__' + fields = "__all__" class AssignUserGroup(Form): + """ + Form to assign groups + """ + employee = forms.ModelMultipleChoiceField(queryset=Employee.objects.all()) group = forms.ModelMultipleChoiceField(queryset=Group.objects.all()) def save(self): - employees = self.cleaned_data['employee'] - group = self.cleaned_data['group'] + """ + Save method to assign group to employees + """ + employees = self.cleaned_data["employee"] + group = self.cleaned_data["group"] for employee in employees: employee.employee_user_id.groups.add(*group) - return group class AssignPermission(Form): + """ + Forms to assign user permision + """ + employee = forms.ModelMultipleChoiceField(queryset=Employee.objects.all()) - permission = forms.ModelMultipleChoiceField( - queryset=Permission.objects.all()) + permission = forms.ModelMultipleChoiceField(queryset=Permission.objects.all()) def save(self): - employees = self.cleaned_data['employee'] - permissions = self.cleaned_data['permission'] + """ + Save method to assign permission to employee + """ + employees = self.cleaned_data["employee"] + permissions = self.cleaned_data["permission"] for emp in employees: user = emp.employee_user_id user.user_permissions.add(*permissions) - return + return self class CompanyForm(ModelForm): - class Meta: - model = Company - fields = '__all__' + """ + Company model's form + """ - def __init__(self, *args, **kwargs): - super(CompanyForm, self).__init__(*args, **kwargs) + class Meta: + """ + Meta class for additional options + """ + + model = Company + fields = "__all__" class DepartmentForm(ModelForm): - class Meta: - model = Department - fields = '__all__' + """ + Department model's form + """ - def __init__(self, *args, **kwargs): - super(DepartmentForm, self).__init__(*args, **kwargs) + class Meta: + """ + Meta class for additional options + """ + + model = Department + fields = "__all__" class JobPositionForm(ModelForm): - class Meta: - model = JobPosition - fields = '__all__' + """ + JobPosition model's form + """ - def __init__(self, *args, **kwargs): - super(JobPositionForm, self).__init__(*args, **kwargs) + class Meta: + """ + Meta class for additional options + """ + + model = JobPosition + fields = "__all__" class JobRoleForm(ModelForm): - class Meta: - model = JobRole - fields = '__all__' + """ + JobRole model's form + """ - def __init__(self, *args, **kwargs): - super(JobRoleForm, self).__init__(*args, **kwargs) + class Meta: + """ + Meta class for additional options + """ + + model = JobRole + fields = "__all__" class WorkTypeForm(ModelForm): - class Meta: - model = WorkType - fields = '__all__' + """ + WorkType model's form + """ - def __init__(self, *args, **kwargs): - super(WorkTypeForm, self).__init__(*args, **kwargs) + class Meta: + """ + Meta class for additional options + """ + + model = WorkType + fields = "__all__" class RotatingWorkTypeForm(ModelForm): - class Meta: - model = RotatingWorkType - fields = '__all__' - exclude = ('employee_id',) - widgets = { - 'start_date': DateInput(attrs={'type': 'date'}), - } + """ + RotatingWorkType model's form + """ - def __init__(self, *args, **kwargs): - super(RotatingWorkTypeForm, self).__init__(*args, **kwargs) + class Meta: + """ + Meta class for additional options + """ + + model = RotatingWorkType + fields = "__all__" + exclude = ("employee_id",) + widgets = { + "start_date": DateInput(attrs={"type": "date"}), + } class RotatingWorkTypeAssignForm(forms.ModelForm): + """ + RotatingWorkTypeAssign model's form + """ + employee_id = forms.ModelMultipleChoiceField( - label="Employee", queryset=Employee.objects.filter(employee_work_info__isnull=False)) - based_on = forms.ChoiceField(choices=BASED_ON, initial='daily') - rotate_after_day = forms.IntegerField(initial=5,) - start_date = forms.DateField( - initial=datetime.date.today, widget=forms.DateInput) + label="Employee", + queryset=Employee.objects.filter(employee_work_info__isnull=False), + ) + based_on = forms.ChoiceField(choices=BASED_ON, initial="daily") + rotate_after_day = forms.IntegerField( + initial=5, + ) + start_date = forms.DateField(initial=datetime.date.today, widget=forms.DateInput) class Meta: + """ + Meta class for additional options + """ + model = RotatingWorkTypeAssign - fields = '__all__' - exclude = ('next_change_date', 'current_work_type', 'next_work_type') + fields = "__all__" + exclude = ("next_change_date", "current_work_type", "next_work_type") widgets = { - 'start_date': DateInput(attrs={'type': 'date'}), + "start_date": DateInput(attrs={"type": "date"}), } labels = { - 'rotating_work_type_id': 'Rotating work type', + "rotating_work_type_id": "Rotating work type", } def __init__(self, *args, **kwargs): - super(RotatingWorkTypeAssignForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) - self.fields['rotate_every_weekend'].widget.attrs.update( - {'class': 'w-100', 'style': 'display:none; height:50px; border-radius:0;border:1px solid hsl(213deg,22%,84%);', 'data-hidden': True}) - self.fields['rotate_every'].widget.attrs.update( - {'class': 'w-100', 'style': 'display:none; height:50px; border-radius:0;border:1px solid hsl(213deg,22%,84%);', 'data-hidden': True}) - self.fields['rotate_after_day'].widget.attrs.update( - {'class': 'w-100 oh-input', 'style': ' height:50px; border-radius:0;', }) - self.fields['based_on'].widget.attrs.update( - {'class': 'w-100', 'style': ' height:50px; border-radius:0;border:1px solid hsl(213deg,22%,84%);', }) - self.fields['start_date'].widget = forms.DateInput( - attrs={'class': 'w-100 oh-input', 'type': 'date', 'style': ' height:50px; border-radius:0;', }) - self.fields['rotating_work_type_id'].widget.attrs.update( - {'class': 'oh-select oh-select-2', }) - self.fields['employee_id'].widget.attrs.update( - {'class': 'oh-select oh-select-2', }) + self.fields["rotate_every_weekend"].widget.attrs.update( + { + "class": "w-100", + "style": "display:none; height:50px; border-radius:0;border:1px \ + solid hsl(213deg,22%,84%);", + "data-hidden": True, + } + ) + self.fields["rotate_every"].widget.attrs.update( + { + "class": "w-100", + "style": "display:none; height:50px; border-radius:0;border:1px \ + solid hsl(213deg,22%,84%);", + "data-hidden": True, + } + ) + self.fields["rotate_after_day"].widget.attrs.update( + { + "class": "w-100 oh-input", + "style": " height:50px; border-radius:0;", + } + ) + self.fields["based_on"].widget.attrs.update( + { + "class": "w-100", + "style": " height:50px; border-radius:0;border:1px solid hsl(213deg,22%,84%);", + } + ) + self.fields["start_date"].widget = forms.DateInput( + attrs={ + "class": "w-100 oh-input", + "type": "date", + "style": " height:50px; border-radius:0;", + } + ) + self.fields["rotating_work_type_id"].widget.attrs.update( + { + "class": "oh-select oh-select-2", + } + ) + self.fields["employee_id"].widget.attrs.update( + { + "class": "oh-select oh-select-2", + } + ) def clean_employee_id(self): - employee_ids = self.cleaned_data.get('employee_id') + employee_ids = self.cleaned_data.get("employee_id") if employee_ids: return employee_ids[0] else: - return ValidationError('This field is required') + return ValidationError("This field is required") def clean(self): cleaned_data = super().clean() - if 'rotate_after_day' in self.errors: - del self.errors['rotate_after_day'] + if "rotate_after_day" in self.errors: + del self.errors["rotate_after_day"] return cleaned_data def save(self, commit=False, manager=None): - employee_ids = self.data.getlist('employee_id') + employee_ids = self.data.getlist("employee_id") rotating_work_type = RotatingWorkType.objects.get( - id=self.data['rotating_work_type_id']) + id=self.data["rotating_work_type_id"] + ) - day_name = self.cleaned_data['rotate_every_weekend'] - day_names = ["monday", "tuesday", "wednesday", - "thursday", "friday", "saturday", "sunday"] + day_name = self.cleaned_data["rotate_every_weekend"] + day_names = [ + "monday", + "tuesday", + "wednesday", + "thursday", + "friday", + "saturday", + "sunday", + ] target_day = day_names.index(day_name.lower()) for employee_id in employee_ids: @@ -312,71 +481,122 @@ class RotatingWorkTypeAssignForm(forms.ModelForm): rotating_work_type_assign = RotatingWorkTypeAssign() rotating_work_type_assign.rotating_work_type_id = rotating_work_type rotating_work_type_assign.employee_id = employee - rotating_work_type_assign.based_on = self.cleaned_data['based_on'] - rotating_work_type_assign.start_date = self.cleaned_data['start_date'] - rotating_work_type_assign.next_change_date = self.cleaned_data['start_date'] + rotating_work_type_assign.based_on = self.cleaned_data["based_on"] + rotating_work_type_assign.start_date = self.cleaned_data["start_date"] + rotating_work_type_assign.next_change_date = self.cleaned_data["start_date"] rotating_work_type_assign.rotate_after_day = self.data.get( - 'rotate_after_day') - rotating_work_type_assign.rotate_every = self.cleaned_data['rotate_every'] + "rotate_after_day" + ) + rotating_work_type_assign.rotate_every = self.cleaned_data["rotate_every"] rotating_work_type_assign.rotate_every_weekend = self.cleaned_data[ - 'rotate_every_weekend'] - rotating_work_type_assign.next_change_date = self.cleaned_data['start_date'] - rotating_work_type_assign.current_work_type = employee.employee_work_info.work_type_id + "rotate_every_weekend" + ] + rotating_work_type_assign.next_change_date = self.cleaned_data["start_date"] + rotating_work_type_assign.current_work_type = ( + employee.employee_work_info.work_type_id + ) rotating_work_type_assign.next_work_type = rotating_work_type.work_type2 - based_on = self.cleaned_data['based_on'] - start_date = self.cleaned_data['start_date'] + based_on = self.cleaned_data["based_on"] + start_date = self.cleaned_data["start_date"] if based_on == "weekly": next_date = get_next_week_date(target_day, start_date) rotating_work_type_assign.next_change_date = next_date elif based_on == "monthly": # 0, 1, 2, ..., 31, or "last" - rotate_every = self.cleaned_data['rotate_every'] - start_date = self.cleaned_data['start_date'] + rotate_every = self.cleaned_data["rotate_every"] + start_date = self.cleaned_data["start_date"] next_date = get_next_monthly_date(start_date, rotate_every) rotating_work_type_assign.next_change_date = next_date elif based_on == "after": - rotating_work_type_assign.next_change_date = rotating_work_type_assign.start_date + \ - datetime.timedelta( - days=int(self.data.get('rotate_after_day'))) + rotating_work_type_assign.next_change_date = ( + rotating_work_type_assign.start_date + + datetime.timedelta(days=int(self.data.get("rotate_after_day"))) + ) rotating_work_type_assign.save() class RotatingWorkTypeAssignUpdateForm(forms.ModelForm): + """ + RotatingWorkTypeAssign model's form + """ + class Meta: + """ + Meta class for additional options + """ + model = RotatingWorkTypeAssign - fields = '__all__' - exclude = ('next_change_date', 'current_work_type', 'next_work_type') + fields = "__all__" + exclude = ("next_change_date", "current_work_type", "next_work_type") widgets = { - 'start_date': DateInput(attrs={'type': 'date'}), + "start_date": DateInput(attrs={"type": "date"}), } def __init__(self, *args, **kwargs): - super(RotatingWorkTypeAssignUpdateForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) - self.fields['rotate_every_weekend'].widget.attrs.update( - {'class': 'w-100', 'style': 'display:none; height:50px; border-radius:0;border:1px solid hsl(213deg,22%,84%);', 'data-hidden': True}) - self.fields['rotate_every'].widget.attrs.update( - {'class': 'w-100', 'style': 'display:none; height:50px; border-radius:0;border:1px solid hsl(213deg,22%,84%);', 'data-hidden': True}) - self.fields['rotate_after_day'].widget.attrs.update( - {'class': 'w-100 oh-input', 'style': ' height:50px; border-radius:0;', }) - self.fields['based_on'].widget.attrs.update( - {'class': 'w-100', 'style': ' height:50px; border-radius:0; border:1px solid hsl(213deg,22%,84%);', }) - self.fields['start_date'].widget = forms.DateInput( - attrs={'class': 'w-100 oh-input', 'type': 'date', 'style': ' height:50px; border-radius:0;', }) - self.fields['rotating_work_type_id'].widget.attrs.update( - {'class': 'oh-select oh-select-2', }) - self.fields['employee_id'].widget.attrs.update( - {'class': 'oh-select oh-select-2', }) + self.fields["rotate_every_weekend"].widget.attrs.update( + { + "class": "w-100", + "style": "display:none; height:50px; border-radius:0;border:1px\ + solid hsl(213deg,22%,84%);", + "data-hidden": True, + } + ) + self.fields["rotate_every"].widget.attrs.update( + { + "class": "w-100", + "style": "display:none; height:50px; border-radius:0;border:1px \ + solid hsl(213deg,22%,84%);", + "data-hidden": True, + } + ) + self.fields["rotate_after_day"].widget.attrs.update( + { + "class": "w-100 oh-input", + "style": " height:50px; border-radius:0;", + } + ) + self.fields["based_on"].widget.attrs.update( + { + "class": "w-100", + "style": " height:50px; border-radius:0; border:1px solid \ + hsl(213deg,22%,84%);", + } + ) + self.fields["start_date"].widget = forms.DateInput( + attrs={ + "class": "w-100 oh-input", + "type": "date", + "style": " height:50px; border-radius:0;", + } + ) + self.fields["rotating_work_type_id"].widget.attrs.update( + { + "class": "oh-select oh-select-2", + } + ) + self.fields["employee_id"].widget.attrs.update( + { + "class": "oh-select oh-select-2", + } + ) def save(self, *args, **kwargs): - - day_name = self.cleaned_data['rotate_every_weekend'] - day_names = ["monday", "tuesday", "wednesday", - "thursday", "friday", "saturday", "sunday"] + day_name = self.cleaned_data["rotate_every_weekend"] + day_names = [ + "monday", + "tuesday", + "wednesday", + "thursday", + "friday", + "saturday", + "sunday", + ] target_day = day_names.index(day_name.lower()) - based_on = self.cleaned_data['based_on'] + based_on = self.cleaned_data["based_on"] start_date = self.instance.start_date if based_on == "weekly": next_date = get_next_week_date(target_day, start_date) @@ -387,184 +607,272 @@ class RotatingWorkTypeAssignUpdateForm(forms.ModelForm): next_date = get_next_monthly_date(start_date, rotate_every) self.instance.next_change_date = next_date elif based_on == "after": - self.instance.next_change_date = self.instance.start_date + \ - datetime.timedelta( - days=int(self.data.get('rotate_after_day'))) + self.instance.next_change_date = ( + self.instance.start_date + + datetime.timedelta(days=int(self.data.get("rotate_after_day"))) + ) return super().save() class EmployeeTypeForm(ModelForm): - class Meta: - model = EmployeeType - fields = '__all__' + """ + EmployeeType form + """ - def __init__(self, *args, **kwargs): - super(EmployeeTypeForm, self).__init__(*args, **kwargs) + class Meta: + """ + Meta class for additional options + """ + + model = EmployeeType + fields = "__all__" class EmployeeShiftForm(ModelForm): + """ + EmployeeShift Form + """ + class Meta: + """ + Meta class for additional options + """ + model = EmployeeShift - fields = '__all__' - exclude = ('days',) + fields = "__all__" + exclude = ("days",) - def __init__(self, *args, **kwargs): - super(EmployeeShiftForm, self).__init__(*args, **kwargs) - - def clean_full_time(self): - full_time = self.cleaned_data['full_time'] + def clean(self) -> Dict[str, Any]: + full_time = self.data["full_time"] validate_time_format(full_time) - return full_time - + full_time = self.data["weekly_full_time"] + validate_time_format(full_time) + return super().clean() class EmployeeShiftScheduleUpdateForm(ModelForm): + """ + EmployeeShiftSchedule model's form + """ class Meta: - fields = '__all__' - widgets = { - 'start_time': DateInput(attrs={'type': 'time'}), - 'end_time': DateInput(attrs={'type': 'time'}), + """ + Meta class for additional options + """ + fields = "__all__" + widgets = { + "start_time": DateInput(attrs={"type": "time"}), + "end_time": DateInput(attrs={"type": "time"}), } model = EmployeeShiftSchedule def __init__(self, *args, **kwargs): - if instance := kwargs.get('instance'): - ''' - django forms not showing value inside the date, time html element. - so here overriding default forms instance method to set initial value - ''' + if instance := kwargs.get("instance"): + # """ + # django forms not showing value inside the date, time html element. + # so here overriding default forms instance method to set initial value + # """ initial = { - 'start_time': instance.start_time.strftime('%H:%M'), - 'end_time': instance.end_time.strftime('%H:%M'), + "start_time": instance.start_time.strftime("%H:%M"), + "end_time": instance.end_time.strftime("%H:%M"), } - kwargs['initial'] = initial + kwargs["initial"] = initial super().__init__(*args, **kwargs) class EmployeeShiftScheduleForm(ModelForm): + """ + EmployeeShiftSchedule model's form + """ + day = forms.ModelMultipleChoiceField( - queryset=EmployeeShiftDay.objects.all(),) + queryset=EmployeeShiftDay.objects.all(), + ) class Meta: + """ + Meta class for additional options + """ model = EmployeeShiftSchedule - fields = '__all__' + fields = "__all__" widgets = { - 'start_time': DateInput(attrs={'type': 'time'}), - 'end_time': DateInput(attrs={'type': 'time'}), - + "start_time": DateInput(attrs={"type": "time"}), + "end_time": DateInput(attrs={"type": "time"}), } def __init__(self, *args, **kwargs): - if instance := kwargs.get('instance'): - ''' - django forms not showing value inside the date, time html element. - so here overriding default forms instance method to set initial value - ''' + if instance := kwargs.get("instance"): + # """ + # django forms not showing value inside the date, time html element. + # so here overriding default forms instance method to set initial value + # """ initial = { - 'start_time': instance.start_time.strftime('%H:%M'), - 'end_time': instance.end_time.strftime('%H:%M'), + "start_time": instance.start_time.strftime("%H:%M"), + "end_time": instance.end_time.strftime("%H:%M"), } - kwargs['initial'] = initial - super(EmployeeShiftScheduleForm, self).__init__(*args, **kwargs) - self.fields['day'].widget.attrs.update({'id': str(uuid.uuid4())}) - self.fields['shift_id'].widget.attrs.update({'id': str(uuid.uuid4())}) + kwargs["initial"] = initial + super().__init__(*args, **kwargs) + self.fields["day"].widget.attrs.update({"id": str(uuid.uuid4())}) + self.fields["shift_id"].widget.attrs.update({"id": str(uuid.uuid4())}) def save(self, commit=True): instance = super().save(commit=False) - for day in self.data.getlist('day'): + for day in self.data.getlist("day"): if int(day) != int(instance.day.id): data_copy = self.data.copy() - data_copy.update({'day': str(day)}) - shift_schedule = EmployeeShiftScheduleUpdateForm( - data_copy).save(commit=False) + data_copy.update({"day": str(day)}) + shift_schedule = EmployeeShiftScheduleUpdateForm(data_copy).save( + commit=False + ) shift_schedule.save() if commit: instance.save() return instance def clean_day(self): - days = self.cleaned_data['day'] + """ + Validation to day field + """ + days = self.cleaned_data["day"] for day in days: attendance = EmployeeShiftSchedule.objects.filter( - day=day, shift_id=self.data['shift_id']).first() + day=day, shift_id=self.data["shift_id"] + ).first() if attendance is not None: - raise ValidationError( - f'Shift schedule is already exist for {day}') + raise ValidationError(f"Shift schedule is already exist for {day}") if days.first() is None: - raise ValidationError('Employee not chosen') + raise ValidationError("Employee not chosen") return days.first() class RotatingShiftForm(ModelForm): - class Meta: - model = RotatingShift - fields = '__all__' - exclude = ('employee_id',) + """ + RotatingShift model's form + """ - def __init__(self, *args, **kwargs): - super(RotatingShiftForm, self).__init__(*args, **kwargs) + class Meta: + """ + Meta class for additional options + """ + + model = RotatingShift + fields = "__all__" + exclude = ("employee_id",) class RotatingShiftAssignForm(forms.ModelForm): + """ + RotatingShiftAssign model's form + """ + employee_id = forms.ModelMultipleChoiceField( - label="Employee", queryset=Employee.objects.filter(employee_work_info__isnull=False)) - based_on = forms.ChoiceField(choices=BASED_ON, initial='daily') - rotate_after_day = forms.IntegerField(initial=5,) - start_date = forms.DateField( - initial=datetime.date.today, widget=forms.DateInput) + label="Employee", + queryset=Employee.objects.filter(employee_work_info__isnull=False), + ) + based_on = forms.ChoiceField(choices=BASED_ON, initial="daily") + rotate_after_day = forms.IntegerField( + initial=5, + ) + start_date = forms.DateField(initial=datetime.date.today, widget=forms.DateInput) class Meta: + """ + Meta class for additional options + """ + model = RotatingShiftAssign - fields = '__all__' - exclude = ('next_change_date', 'current_shift', 'next_shift') + fields = "__all__" + exclude = ("next_change_date", "current_shift", "next_shift") widgets = { - 'start_date': DateInput(attrs={'type': 'date'}), + "start_date": DateInput(attrs={"type": "date"}), } labels = { - 'rotating_shift_id': 'Rotating shift', + "rotating_shift_id": "Rotating shift", } def __init__(self, *args, **kwargs): - super(RotatingShiftAssignForm, self).__init__(*args, **kwargs) - self.fields['rotate_every_weekend'].widget.attrs.update( - {'class': 'w-100 ', 'style': 'display:none; height:50px; border-radius:0;border:1px solid hsl(213deg,22%,84%);', 'data-hidden': True}) - self.fields['rotate_every'].widget.attrs.update( - {'class': 'w-100 ', 'style': 'display:none; height:50px; border-radius:0;border:1px solid hsl(213deg,22%,84%);', 'data-hidden': True}) - self.fields['rotate_after_day'].widget.attrs.update( - {'class': 'w-100 oh-input', 'style': ' height:50px; border-radius:0;', }) - self.fields['based_on'].widget.attrs.update( - {'class': 'w-100', 'style': ' height:50px; border-radius:0;border:1px solid hsl(213deg,22%,84%);', }) - self.fields['start_date'].widget = forms.DateInput( - attrs={'class': 'w-100 oh-input', 'type': 'date', 'style': ' height:50px; border-radius:0;', }) - self.fields['rotating_shift_id'].widget.attrs.update( - {'class': 'oh-select oh-select-2', }) - self.fields['employee_id'].widget.attrs.update( - {'class': 'oh-select oh-select-2', }) + super().__init__(*args, **kwargs) + self.fields["rotate_every_weekend"].widget.attrs.update( + { + "class": "w-100 ", + "style": "display:none; height:50px; border-radius:0;border:1px \ + solid hsl(213deg,22%,84%);", + "data-hidden": True, + } + ) + self.fields["rotate_every"].widget.attrs.update( + { + "class": "w-100 ", + "style": "display:none; height:50px; border-radius:0;border:1px \ + solid hsl(213deg,22%,84%);", + "data-hidden": True, + } + ) + self.fields["rotate_after_day"].widget.attrs.update( + { + "class": "w-100 oh-input", + "style": " height:50px; border-radius:0;", + } + ) + self.fields["based_on"].widget.attrs.update( + { + "class": "w-100", + "style": " height:50px; border-radius:0;border:1px solid hsl(213deg,22%,84%);", + } + ) + self.fields["start_date"].widget = forms.DateInput( + attrs={ + "class": "w-100 oh-input", + "type": "date", + "style": " height:50px; border-radius:0;", + } + ) + self.fields["rotating_shift_id"].widget.attrs.update( + { + "class": "oh-select oh-select-2", + } + ) + self.fields["employee_id"].widget.attrs.update( + { + "class": "oh-select oh-select-2", + } + ) def clean_employee_id(self): - employee_ids = self.cleaned_data.get('employee_id') + """ + Validation to employee_id field + """ + employee_ids = self.cleaned_data.get("employee_id") if employee_ids: return employee_ids[0] else: - return ValidationError('This field is required') + return ValidationError("This field is required") def clean(self): cleaned_data = super().clean() - if 'rotate_after_day' in self.errors: - del self.errors['rotate_after_day'] + if "rotate_after_day" in self.errors: + del self.errors["rotate_after_day"] return cleaned_data - def save(self, commit=False,): - employee_ids = self.data.getlist('employee_id') - rotating_shift = RotatingShift.objects.get( - id=self.data['rotating_shift_id']) + def save( + self, + commit=False, + ): + employee_ids = self.data.getlist("employee_id") + rotating_shift = RotatingShift.objects.get(id=self.data["rotating_shift_id"]) - day_name = self.cleaned_data['rotate_every_weekend'] - day_names = ["monday", "tuesday", "wednesday", - "thursday", "friday", "saturday", "sunday"] + day_name = self.cleaned_data["rotate_every_weekend"] + day_names = [ + "monday", + "tuesday", + "wednesday", + "thursday", + "friday", + "saturday", + "sunday", + ] target_day = day_names.index(day_name.lower()) for employee_id in employee_ids: @@ -572,70 +880,115 @@ class RotatingShiftAssignForm(forms.ModelForm): rotating_shift_assign = RotatingShiftAssign() rotating_shift_assign.rotating_shift_id = rotating_shift rotating_shift_assign.employee_id = employee - rotating_shift_assign.based_on = self.cleaned_data['based_on'] - rotating_shift_assign.start_date = self.cleaned_data['start_date'] - rotating_shift_assign.next_change_date = self.cleaned_data['start_date'] - rotating_shift_assign.rotate_after_day = self.data.get( - 'rotate_after_day') - rotating_shift_assign.rotate_every = self.cleaned_data['rotate_every'] - rotating_shift_assign.rotate_every_weekend = self.cleaned_data['rotate_every_weekend'] - rotating_shift_assign.next_change_date = self.cleaned_data['start_date'] + rotating_shift_assign.based_on = self.cleaned_data["based_on"] + rotating_shift_assign.start_date = self.cleaned_data["start_date"] + rotating_shift_assign.next_change_date = self.cleaned_data["start_date"] + rotating_shift_assign.rotate_after_day = self.data.get("rotate_after_day") + rotating_shift_assign.rotate_every = self.cleaned_data["rotate_every"] + rotating_shift_assign.rotate_every_weekend = self.cleaned_data[ + "rotate_every_weekend" + ] + rotating_shift_assign.next_change_date = self.cleaned_data["start_date"] rotating_shift_assign.current_shift = employee.employee_work_info.shift_id rotating_shift_assign.next_shift = rotating_shift.shift2 - based_on = self.cleaned_data['based_on'] - start_date = self.cleaned_data['start_date'] + based_on = self.cleaned_data["based_on"] + start_date = self.cleaned_data["start_date"] if based_on == "weekly": next_date = get_next_week_date(target_day, start_date) rotating_shift_assign.next_change_date = next_date elif based_on == "monthly": # 0, 1, 2, ..., 31, or "last" - rotate_every = self.cleaned_data['rotate_every'] - start_date = self.cleaned_data['start_date'] + rotate_every = self.cleaned_data["rotate_every"] + start_date = self.cleaned_data["start_date"] next_date = get_next_monthly_date(start_date, rotate_every) rotating_shift_assign.next_change_date = next_date elif based_on == "after": - rotating_shift_assign.next_change_date = rotating_shift_assign.start_date + \ - datetime.timedelta( - days=int(self.data.get('rotate_after_day'))) - + rotating_shift_assign.next_change_date = ( + rotating_shift_assign.start_date + + datetime.timedelta(days=int(self.data.get("rotate_after_day"))) + ) rotating_shift_assign.save() class RotatingShiftAssignUpdateForm(forms.ModelForm): + """ + RotatingShiftAssign model's form + """ class Meta: + """ + Meta class for additional options + """ + model = RotatingShiftAssign - fields = '__all__' - exclude = ('next_change_date', 'current_shift', 'next_shift') + fields = "__all__" + exclude = ("next_change_date", "current_shift", "next_shift") widgets = { - 'start_date': DateInput(attrs={'type': 'date'}), + "start_date": DateInput(attrs={"type": "date"}), } def __init__(self, *args, **kwargs): - super(RotatingShiftAssignUpdateForm, self).__init__(*args, **kwargs) - self.fields['rotate_every_weekend'].widget.attrs.update( - {'class': 'w-100 ', 'style': 'display:none; height:50px; border-radius:0; border:1px solid hsl(213deg,22%,84%);', 'data-hidden': True}) - self.fields['rotate_every'].widget.attrs.update( - {'class': 'w-100 ', 'style': 'display:none; height:50px; border-radius:0; border:1px solid hsl(213deg,22%,84%);', 'data-hidden': True}) - self.fields['rotate_after_day'].widget.attrs.update( - {'class': 'w-100 oh-input', 'style': ' height:50px; border-radius:0;', }) - self.fields['based_on'].widget.attrs.update( - {'class': 'w-100', 'style': ' height:50px; border-radius:0; border:1px solid hsl(213deg,22%,84%);', }) - self.fields['start_date'].widget = forms.DateInput( - attrs={'class': 'w-100 oh-input', 'type': 'date', 'style': ' height:50px; border-radius:0;', }) - self.fields['rotating_shift_id'].widget.attrs.update( - {'class': 'oh-select oh-select-2', }) - self.fields['employee_id'].widget.attrs.update( - {'class': 'oh-select oh-select-2', }) + super().__init__(*args, **kwargs) + self.fields["rotate_every_weekend"].widget.attrs.update( + { + "class": "w-100 ", + "style": "display:none; height:50px; border-radius:0; border:1px \ + solid hsl(213deg,22%,84%);", + "data-hidden": True, + } + ) + self.fields["rotate_every"].widget.attrs.update( + { + "class": "w-100 ", + "style": "display:none; height:50px; border-radius:0; border:1px \ + solid hsl(213deg,22%,84%);", + "data-hidden": True, + } + ) + self.fields["rotate_after_day"].widget.attrs.update( + { + "class": "w-100 oh-input", + "style": " height:50px; border-radius:0;", + } + ) + self.fields["based_on"].widget.attrs.update( + { + "class": "w-100", + "style": " height:50px; border-radius:0; border:1px solid hsl(213deg,22%,84%);", + } + ) + self.fields["start_date"].widget = forms.DateInput( + attrs={ + "class": "w-100 oh-input", + "type": "date", + "style": " height:50px; border-radius:0;", + } + ) + self.fields["rotating_shift_id"].widget.attrs.update( + { + "class": "oh-select oh-select-2", + } + ) + self.fields["employee_id"].widget.attrs.update( + { + "class": "oh-select oh-select-2", + } + ) def save(self, *args, **kwargs): - - day_name = self.cleaned_data['rotate_every_weekend'] - day_names = ["monday", "tuesday", "wednesday", - "thursday", "friday", "saturday", "sunday"] + day_name = self.cleaned_data["rotate_every_weekend"] + day_names = [ + "monday", + "tuesday", + "wednesday", + "thursday", + "friday", + "saturday", + "sunday", + ] target_day = day_names.index(day_name.lower()) - based_on = self.cleaned_data['based_on'] + based_on = self.cleaned_data["based_on"] start_date = self.instance.start_date if based_on == "weekly": next_date = get_next_week_date(target_day, start_date) @@ -646,27 +999,37 @@ class RotatingShiftAssignUpdateForm(forms.ModelForm): next_date = get_next_monthly_date(start_date, rotate_every) self.instance.next_change_date = next_date elif based_on == "after": - self.instance.next_change_date = self.instance.start_date + \ - datetime.timedelta( - days=int(self.data.get('rotate_after_day'))) + self.instance.next_change_date = ( + self.instance.start_date + + datetime.timedelta(days=int(self.data.get("rotate_after_day"))) + ) return super().save() class ShiftRequestForm(ModelForm): + """ + ShiftRequest model's form + """ class Meta: + """ + Meta class for additional options + """ + model = ShiftRequest - fields = '__all__' - exclude = ('approved', 'canceled', 'previous_shift_id', - 'is_active', 'shift_changed') + fields = "__all__" + exclude = ( + "approved", + "canceled", + "previous_shift_id", + "is_active", + "shift_changed", + ) widgets = { - 'requested_date': DateInput(attrs={'type': 'date'}), - 'requested_till': DateInput(attrs={'type': 'date'}), - } - labels = { - 'employee_id': 'Employee', - 'shift_id': 'Shift' + "requested_date": DateInput(attrs={"type": "date"}), + "requested_till": DateInput(attrs={"type": "date"}), } + labels = {"employee_id": "Employee", "shift_id": "Shift"} def save(self, commit: bool = ...): if not self.instance.approved: @@ -678,77 +1041,110 @@ class ShiftRequestForm(ModelForm): class WorkTypeRequestForm(ModelForm): + """ + WorkTypeRequest model's form + """ + class Meta: + """ + Meta class for additional options + """ + model = WorkTypeRequest - fields = '__all__' - exclude = ('approved', 'canceled', 'previous_work_type_id', - 'is_active', 'work_type_changed') + fields = "__all__" + exclude = ( + "approved", + "canceled", + "previous_work_type_id", + "is_active", + "work_type_changed", + ) widgets = { - 'requested_date': DateInput(attrs={'type': 'date'}), - 'requested_till': DateInput(attrs={'type': 'date'}), - } - labels = { - 'employee_id': 'Employee', - 'work_type_id': 'Work type' + "requested_date": DateInput(attrs={"type": "date"}), + "requested_till": DateInput(attrs={"type": "date"}), } + labels = {"employee_id": "Employee", "work_type_id": "Work type"} def save(self, commit: bool = ...): if not self.instance.approved: employee = self.instance.employee_id - self.instance.previous_work_type_id = employee.employee_work_info.work_type_id + self.instance.previous_work_type_id = ( + employee.employee_work_info.work_type_id + ) return super().save(commit) class ResetPasswordForm(forms.Form): + """ + ResetPasswordForm + """ + password = forms.CharField( label="New password", strip=False, - widget=forms.PasswordInput(attrs={'autocomplete': 'new-password', - 'placeholder': 'Enter Strong Password', 'class': 'oh-input oh-input--password w-100 mb-2'}), + widget=forms.PasswordInput( + attrs={ + "autocomplete": "new-password", + "placeholder": "Enter Strong Password", + "class": "oh-input oh-input--password w-100 mb-2", + } + ), help_text="Enter your new password.", ) confirm_password = forms.CharField( label="New password confirmation", strip=False, - widget=forms.PasswordInput(attrs={'autocomplete': 'new-password', - 'placeholder': 'Re-Enter Password', 'class': 'oh-input oh-input--password w-100 mb-2'}), + widget=forms.PasswordInput( + attrs={ + "autocomplete": "new-password", + "placeholder": "Re-Enter Password", + "class": "oh-input oh-input--password w-100 mb-2", + } + ), help_text="Enter the same password as before, for verification.", ) def clean_password(self): - password = self.cleaned_data.get('password') + """ + Validation to password field""" + password = self.cleaned_data.get("password") try: - if len(password) < 7: - raise ValidationError( - 'Password must contain at least 8 characters.') + raise ValidationError("Password must contain at least 8 characters.") elif not any(char.isupper() for char in password): raise ValidationError( - 'Password must contain at least one uppercase letter.') + "Password must contain at least one uppercase letter." + ) elif not any(char.islower() for char in password): raise ValidationError( - 'Password must contain at least one lowercase letter.') + "Password must contain at least one lowercase letter." + ) elif not any(char.isdigit() for char in password): - raise ValidationError( - 'Password must contain at least one digit.') + raise ValidationError("Password must contain at least one digit.") elif all( - char not in '!@#$%^&*()_+-=[]{}|;:,.<>?\'\"`~\\/' - for char in password + char not in "!@#$%^&*()_+-=[]{}|;:,.<>?'\"`~\\/" for char in password ): raise ValidationError( - 'Password must contain at least one special character.') - except ValidationError as e: - raise forms.ValidationError(list(e)[0]) + "Password must contain at least one special character." + ) + except ValidationError as error: + raise forms.ValidationError(list(error)[0]) return password def clean_confirm_password(self): - password = self.cleaned_data.get('password') - confirm_password = self.cleaned_data.get('confirm_password') + """ + validation method for confirm password field + """ + password = self.cleaned_data.get("password") + confirm_password = self.cleaned_data.get("confirm_password") if password == confirm_password: return confirm_password - raise forms.ValidationError('Password must be same.') + raise forms.ValidationError("Password must be same.") def save(self, *args, user=None, **kwargs): + """ + Save method to ResetPasswordForm + """ if user is not None: - user.set_password(self.data['password']) + user.set_password(self.data["password"]) user.save() diff --git a/base/models.py b/base/models.py index 0a71ab7e2..901e96098 100644 --- a/base/models.py +++ b/base/models.py @@ -1,15 +1,21 @@ +""" +models.py + +This module is used to register django models +""" import django -from django.db import models -from simple_history.models import HistoricalRecords from django.core.exceptions import ValidationError from django.utils.translation import gettext_lazy as _ +from django.db import models +from simple_history.models import HistoricalRecords # Create your models here. + def validate_time_format(value): - ''' + """ this method is used to validate the format of duration like fields. - ''' + """ if len(value) > 6: raise ValidationError(_("Invalid format, it should be HH:MM format")) try: @@ -17,13 +23,16 @@ def validate_time_format(value): hour = int(hour) minute = int(minute) if len(str(hour)) > 3 or minute not in range(60): - raise ValidationError(_("Invalid time")) + raise ValidationError(_("Invalid time, excepted HH:MM")) except ValueError as e: - raise ValidationError(_("Invalid format")) from e - + raise ValidationError(_("Invalid format, excepted HH:MM")) from e class Company(models.Model): + """ + Company model + """ + company = models.CharField(max_length=50) hq = models.BooleanField(default=False) address = models.TextField() @@ -31,239 +40,471 @@ class Company(models.Model): state = models.CharField(max_length=50) city = models.CharField(max_length=50) zip = models.CharField(max_length=20) - icon = models.FileField(upload_to='base/icon',null=True,) + icon = models.FileField( + upload_to="base/icon", + null=True, + ) + objects = models.Manager() + class Meta: - unique_together = ['company','address'] + """ + Meta class to add additional options + """ + + unique_together = ["company", "address"] def __str__(self) -> str: - return self.company + return str(self.company) class Department(models.Model): - department = models.CharField(max_length=50, blank=False,unique=True) + """ + Department model + """ + + department = models.CharField(max_length=50, blank=False, unique=True) history = HistoricalRecords() + objects = models.Manager() def __str__(self): - return self.department + return str(self.department) class JobPosition(models.Model): + """ + JobPosition model + """ + job_position = models.CharField(max_length=50, blank=False, null=False, unique=True) department_id = models.ForeignKey( - Department, on_delete=models.CASCADE, blank=True, related_name='job_position', verbose_name='Department') + Department, + on_delete=models.CASCADE, + blank=True, + related_name="job_position", + verbose_name="Department", + ) + objects = models.Manager() + def __str__(self): - return self.job_position + return str(self.job_position) class JobRole(models.Model): - job_position_id = models.ForeignKey(JobPosition, on_delete=models.CASCADE, verbose_name='Job Position') - job_role = models.CharField(max_length=50,blank=False,null=True) + """JobRole model""" + + job_position_id = models.ForeignKey( + JobPosition, on_delete=models.CASCADE, verbose_name="Job Position" + ) + job_role = models.CharField(max_length=50, blank=False, null=True) + objects = models.Manager() + class Meta: - unique_together = ('job_position_id','job_role') + """ + Meta class to add additional options + """ + + unique_together = ("job_position_id", "job_role") + def __str__(self): - return f'{self.job_role} - {self.job_position_id.job_position}' + return f"{self.job_role} - {self.job_position_id.job_position}" + class WorkType(models.Model): + """ + WorkType model + """ + work_type = models.CharField(max_length=50) + objects = models.Manager() + def __str__(self) -> str: - return self.work_type + return str(self.work_type) + class RotatingWorkType(models.Model): + """ + RotatingWorkType model + """ + name = models.CharField(max_length=50) - work_type1 = models.ForeignKey(WorkType,on_delete=models.CASCADE,related_name='work_type1',verbose_name=_('Work Type 1')) - work_type2 = models.ForeignKey(WorkType,on_delete=models.CASCADE,related_name='work_type2',verbose_name=_('Work Type 2')) - employee_id = models.ManyToManyField('employee.Employee', through='RotatingWorkTypeAssign',verbose_name="Employee") + work_type1 = models.ForeignKey( + WorkType, + on_delete=models.CASCADE, + related_name="work_type1", + verbose_name=_("Work Type 1"), + ) + work_type2 = models.ForeignKey( + WorkType, + on_delete=models.CASCADE, + related_name="work_type2", + verbose_name=_("Work Type 2"), + ) + employee_id = models.ManyToManyField( + "employee.Employee", through="RotatingWorkTypeAssign", verbose_name="Employee" + ) + objects = models.Manager() def __str__(self) -> str: - return self.name - + return str(self.name) + def clean(self): if self.work_type1 == self.work_type2: - raise ValidationError(_('Choose different work type')) + raise ValidationError(_("Choose different work type")) DAY_DATE = [(str(i), str(i)) for i in range(1, 32)] -DAY_DATE.append(('last', _('Last Day'))) -DAY = [ - ('monday', _('Monday')), - ('tuesday', _('Tuesday')), - ('wednesday', _('Wednesday')), - ('thursday', _('Thursday')), - ('friday', _('Friday')), - ('saturday', _('Saturday')), - ('sunday', _('Sunday')), +DAY_DATE.append(("last", _("Last Day"))) +DAY = [ + ("monday", _("Monday")), + ("tuesday", _("Tuesday")), + ("wednesday", _("Wednesday")), + ("thursday", _("Thursday")), + ("friday", _("Friday")), + ("saturday", _("Saturday")), + ("sunday", _("Sunday")), ] BASED_ON = [ - ('after',_('After')), - ('weekly',_('Weekend')), - ('monthly',_('Monthly')), + ("after", _("After")), + ("weekly", _("Weekend")), + ("monthly", _("Monthly")), ] + class RotatingWorkTypeAssign(models.Model): - - employee_id = models.ForeignKey('employee.Employee',on_delete=models.CASCADE,null=True,verbose_name="Employee") - rotating_work_type_id = models.ForeignKey(RotatingWorkType,on_delete=models.CASCADE,verbose_name='Rotating Work Type') + """ + RotatingWorkTypeAssign model + """ + + employee_id = models.ForeignKey( + "employee.Employee", + on_delete=models.CASCADE, + null=True, + verbose_name="Employee", + ) + rotating_work_type_id = models.ForeignKey( + RotatingWorkType, on_delete=models.CASCADE, verbose_name="Rotating Work Type" + ) next_change_date = models.DateField(null=True) - start_date = models.DateField(default= django.utils.timezone.now) - based_on = models.CharField(max_length=10,choices=BASED_ON,null=False,blank=False) + start_date = models.DateField(default=django.utils.timezone.now) + based_on = models.CharField( + max_length=10, choices=BASED_ON, null=False, blank=False + ) rotate_after_day = models.IntegerField(default=7) - rotate_every_weekend = models.CharField(max_length=10,default='monday',choices=DAY,blank=True,null=True) - rotate_every = models.CharField(max_length=10,default='1',choices=DAY_DATE) - current_work_type = models.ForeignKey(WorkType,null=True,on_delete=models.DO_NOTHING,related_name='current_work_type') - next_work_type = models.ForeignKey(WorkType,null=True,on_delete=models.DO_NOTHING,related_name='next_work_type') + rotate_every_weekend = models.CharField( + max_length=10, default="monday", choices=DAY, blank=True, null=True + ) + rotate_every = models.CharField(max_length=10, default="1", choices=DAY_DATE) + current_work_type = models.ForeignKey( + WorkType, + null=True, + on_delete=models.DO_NOTHING, + related_name="current_work_type", + ) + next_work_type = models.ForeignKey( + WorkType, null=True, on_delete=models.DO_NOTHING, related_name="next_work_type" + ) is_active = models.BooleanField(default=True) - + objects = models.Manager() + class Meta: - ordering = ['-next_change_date','-employee_id__employee_first_name'] + """ + Meta class to add additional options + """ + + ordering = ["-next_change_date", "-employee_id__employee_first_name"] def clean(self): if self.is_active and self.employee_id is not None: # Check if any other active record with the same parent already exists - siblings = RotatingWorkTypeAssign.objects.filter(is_active=True, employee_id=self.employee_id) + siblings = RotatingWorkTypeAssign.objects.filter( + is_active=True, employee_id=self.employee_id + ) if siblings.exists() and siblings.first().id != self.id: - raise ValidationError(_('Only one active record allowed per employee')) + raise ValidationError(_("Only one active record allowed per employee")) if self.start_date < django.utils.timezone.now().date(): - raise ValidationError(_('Date must be greater than or equal to today')) + raise ValidationError(_("Date must be greater than or equal to today")) class EmployeeType(models.Model): + """ + EmployeeType model + """ + employee_type = models.CharField(max_length=50) + objects = models.Manager() def __str__(self) -> str: - return self.employee_type + return str(self.employee_type) + class EmployeeShiftDay(models.Model): + """ + EmployeeShiftDay model + """ day = models.CharField(max_length=20, choices=DAY) + objects = models.Manager() + def __str__(self) -> str: - return self.day + return str(self.day) + class EmployeeShift(models.Model): - employee_shift = models.CharField(max_length=50, null=False, blank=False,) - days = models.ManyToManyField( - EmployeeShiftDay, through='EmployeeShiftSchedule') - weekly_full_time = models.CharField(max_length=6,default='40:00',null=True,blank=True) - full_time = models.CharField(max_length=6,default='200:00',validators=[validate_time_format]) + """ + EmployeeShift model + """ + employee_shift = models.CharField( + max_length=50, + null=False, + blank=False, + ) + days = models.ManyToManyField(EmployeeShiftDay, through="EmployeeShiftSchedule") + weekly_full_time = models.CharField( + max_length=6, default="40:00", null=True, blank=True,validators=[validate_time_format] + ) + full_time = models.CharField( + max_length=6, default="200:00", validators=[validate_time_format] + ) + objects = models.Manager() def __str__(self) -> str: - return self.employee_shift + return str(self.employee_shift) + class EmployeeShiftSchedule(models.Model): - day = models.ForeignKey(EmployeeShiftDay, - on_delete=models.CASCADE,related_name='day_schedule') + """ + EmployeeShiftSchedule model + """ + + day = models.ForeignKey( + EmployeeShiftDay, on_delete=models.CASCADE, related_name="day_schedule" + ) shift_id = models.ForeignKey( - EmployeeShift, on_delete=models.CASCADE,verbose_name='Shift') - minimum_working_hour = models.CharField(default='08:15',max_length=5,validators=[validate_time_format]) + EmployeeShift, on_delete=models.CASCADE, verbose_name="Shift" + ) + minimum_working_hour = models.CharField( + default="08:15", max_length=5, validators=[validate_time_format] + ) start_time = models.TimeField(null=True) - end_time = models.TimeField(null=True) + end_time = models.TimeField(null=True) + objects = models.Manager() class Meta: - unique_together = [ - ['shift_id', 'day'] - ] + """ + Meta class to add additional options + """ + + unique_together = [["shift_id", "day"]] def __str__(self) -> str: - return f'{self.shift_id.employee_shift} {self.day}' + return f"{self.shift_id.employee_shift} {self.day}" + class RotatingShift(models.Model): - name =models.CharField(max_length=50) - employee_id = models.ManyToManyField('employee.Employee',through='RotatingShiftAssign',verbose_name='Employee') - shift1 = models.ForeignKey(EmployeeShift,related_name='shift1',on_delete=models.CASCADE,verbose_name=_('Shift 1')) - shift2 = models.ForeignKey(EmployeeShift,related_name='shift2',on_delete=models.CASCADE,verbose_name=_('Shift 2')) + """ + RotatingShift model + """ + + name = models.CharField(max_length=50) + employee_id = models.ManyToManyField( + "employee.Employee", through="RotatingShiftAssign", verbose_name="Employee" + ) + shift1 = models.ForeignKey( + EmployeeShift, + related_name="shift1", + on_delete=models.CASCADE, + verbose_name=_("Shift 1"), + ) + shift2 = models.ForeignKey( + EmployeeShift, + related_name="shift2", + on_delete=models.CASCADE, + verbose_name=_("Shift 2"), + ) + objects = models.Manager() def __str__(self) -> str: - return self.name + return str(self.name) + def clean(self): if self.shift1 == self.shift2: - raise ValidationError(_('Choose different shifts')) + raise ValidationError(_("Choose different shifts")) class RotatingShiftAssign(models.Model): + """ + RotatingShiftAssign model + """ - # employee_id = models.OneToOneField('employee.Employee',on_delete=models.CASCADE) - employee_id = models.ForeignKey('employee.Employee',on_delete=models.CASCADE,verbose_name='Employee') - rotating_shift_id = models.ForeignKey(RotatingShift,on_delete=models.CASCADE,verbose_name='Rotating Shift') + employee_id = models.ForeignKey( + "employee.Employee", on_delete=models.CASCADE, verbose_name="Employee" + ) + rotating_shift_id = models.ForeignKey( + RotatingShift, on_delete=models.CASCADE, verbose_name="Rotating Shift" + ) next_change_date = models.DateField(null=True) start_date = models.DateField(default=django.utils.timezone.now) - based_on = models.CharField(max_length=10,choices=BASED_ON,null=False,blank=False) - rotate_after_day = models.IntegerField(null=True,blank=True,default=7) - rotate_every_weekend = models.CharField(max_length=10,default='monday',choices=DAY,blank=True,null=True) - rotate_every = models.CharField(max_length=10,blank=True,null=True,default='1',choices=DAY_DATE) - current_shift = models.ForeignKey(EmployeeShift,on_delete=models.DO_NOTHING,null=True,related_name='current_shift') - next_shift = models.ForeignKey(EmployeeShift,on_delete=models.DO_NOTHING,null=True,related_name='next_shift') - is_active=models.BooleanField(default=True) + based_on = models.CharField( + max_length=10, choices=BASED_ON, null=False, blank=False + ) + rotate_after_day = models.IntegerField(null=True, blank=True, default=7) + rotate_every_weekend = models.CharField( + max_length=10, default="monday", choices=DAY, blank=True, null=True + ) + rotate_every = models.CharField( + max_length=10, blank=True, null=True, default="1", choices=DAY_DATE + ) + current_shift = models.ForeignKey( + EmployeeShift, + on_delete=models.DO_NOTHING, + null=True, + related_name="current_shift", + ) + next_shift = models.ForeignKey( + EmployeeShift, on_delete=models.DO_NOTHING, null=True, related_name="next_shift" + ) + is_active = models.BooleanField(default=True) + objects = models.Manager() class Meta: - ordering = ['-next_change_date','-employee_id__employee_first_name'] - + """ + Meta class to add additional options + """ + + ordering = ["-next_change_date", "-employee_id__employee_first_name"] + def clean(self): if self.is_active and self.employee_id is not None: # Check if any other active record with the same parent already exists - siblings = RotatingShiftAssign.objects.filter(is_active=True, employee_id=self.employee_id) + siblings = RotatingShiftAssign.objects.filter( + is_active=True, employee_id=self.employee_id + ) if siblings.exists() and siblings.first().id != self.id: - raise ValidationError(_('Only one active record allowed per employee')) + raise ValidationError(_("Only one active record allowed per employee")) if self.start_date < django.utils.timezone.now().date(): - raise ValidationError(_('Date must be greater than or equal to today')) + raise ValidationError(_("Date must be greater than or equal to today")) class WorkTypeRequest(models.Model): + """ + WorkTypeRequest model + """ - def save(self, *args, **kwargs): - - super(WorkTypeRequest,self).save(*args, **kwargs) - - employee_id = models.ForeignKey('employee.Employee',on_delete=models.CASCADE,null=True,related_name='work_type_request',verbose_name='Employee') - requested_date = models.DateField(null=True,default=django.utils.timezone.now) - requested_till = models.DateField(null=True,blank=True,default=django.utils.timezone.now) - work_type_id = models.ForeignKey(WorkType,on_delete=models.CASCADE,related_name='requested_work_type',verbose_name='Work Type') - previous_work_type_id = models.ForeignKey(WorkType,on_delete=models.DO_NOTHING,null=True,blank=True,related_name='previous_work_type') + employee_id = models.ForeignKey( + "employee.Employee", + on_delete=models.CASCADE, + null=True, + related_name="work_type_request", + verbose_name="Employee", + ) + requested_date = models.DateField(null=True, default=django.utils.timezone.now) + requested_till = models.DateField( + null=True, blank=True, default=django.utils.timezone.now + ) + work_type_id = models.ForeignKey( + WorkType, + on_delete=models.CASCADE, + related_name="requested_work_type", + verbose_name="Work Type", + ) + previous_work_type_id = models.ForeignKey( + WorkType, + on_delete=models.DO_NOTHING, + null=True, + blank=True, + related_name="previous_work_type", + ) description = models.TextField(null=True) approved = models.BooleanField(default=False) canceled = models.BooleanField(default=False) work_type_changed = models.BooleanField(default=False) - is_active= models.BooleanField(default=True) + is_active = models.BooleanField(default=True) + objects = models.Manager() class Meta: - permissions = (('approve_worktyperequest','Approve Work Type Request'),('cancel_worktyperequest','Cancel Work Type Request')) - ordering = ['requested_date',] - unique_together = ['employee_id','requested_date'] - + """ + Meta class to add additional options + """ + + permissions = ( + ("approve_worktyperequest", "Approve Work Type Request"), + ("cancel_worktyperequest", "Cancel Work Type Request"), + ) + ordering = [ + "requested_date", + ] + unique_together = ["employee_id", "requested_date"] + def clean(self): if self.requested_date < django.utils.timezone.now().date(): - raise ValidationError(_('Date must be greater than or equal to today')) + raise ValidationError(_("Date must be greater than or equal to today")) if self.requested_till and self.requested_till < self.requested_date: - raise ValidationError(_('End date must be greater than or equal to start date')) + raise ValidationError( + _("End date must be greater than or equal to start date") + ) class ShiftRequest(models.Model): + """ + ShiftRequest model + """ - employee_id = models.ForeignKey('employee.Employee',on_delete=models.CASCADE,null=True,related_name='shift_request',verbose_name='Employee') - requested_date = models.DateField(null=True,default=django.utils.timezone.now) - requested_till = models.DateField(null=True,blank=True,default=django.utils.timezone.now) - shift_id = models.ForeignKey(EmployeeShift,on_delete=models.CASCADE,related_name='requested_shift',verbose_name='Shift') - previous_shift_id = models.ForeignKey(EmployeeShift,on_delete=models.DO_NOTHING,null=True,blank=True,related_name='previous_shift') + employee_id = models.ForeignKey( + "employee.Employee", + on_delete=models.CASCADE, + null=True, + related_name="shift_request", + verbose_name="Employee", + ) + requested_date = models.DateField(null=True, default=django.utils.timezone.now) + requested_till = models.DateField( + null=True, blank=True, default=django.utils.timezone.now + ) + shift_id = models.ForeignKey( + EmployeeShift, + on_delete=models.CASCADE, + related_name="requested_shift", + verbose_name="Shift", + ) + previous_shift_id = models.ForeignKey( + EmployeeShift, + on_delete=models.DO_NOTHING, + null=True, + blank=True, + related_name="previous_shift", + ) description = models.TextField(null=True) approved = models.BooleanField(default=False) canceled = models.BooleanField(default=False) shift_changed = models.BooleanField(default=False) - is_active= models.BooleanField(default=True) + is_active = models.BooleanField(default=True) + objects = models.Manager() class Meta: - permissions = (('approve_shiftrequest','Approve Shift Request'),('cancel_shiftrequest','Cancel Shift Request')) - ordering = ['requested_date',] - unique_together = ['employee_id','requested_date'] + """ + Meta class to add additional options + """ + + permissions = ( + ("approve_shiftrequest", "Approve Shift Request"), + ("cancel_shiftrequest", "Cancel Shift Request"), + ) + ordering = [ + "requested_date", + ] + unique_together = ["employee_id", "requested_date"] def clean(self): if self.requested_date < django.utils.timezone.now().date(): - raise ValidationError(_('Date must be greater than or equal to today')) + raise ValidationError(_("Date must be greater than or equal to today")) if self.requested_till and self.requested_till < self.requested_date: - raise ValidationError(_('End date must be greater than or equal to start date')) - + raise ValidationError( + _("End date must be greater than or equal to start date") + ) def save(self, *args, **kwargs): - - super(ShiftRequest,self).save(*args, **kwargs) + super().save(*args, **kwargs) def __str__(self): - return f"{self.employee_id}" \ No newline at end of file + return f"{self.employee_id}" diff --git a/base/templates/shift_request/shift_request_nav.html b/base/templates/shift_request/shift_request_nav.html index 0351d0b3d..aa91399f1 100644 --- a/base/templates/shift_request/shift_request_nav.html +++ b/base/templates/shift_request/shift_request_nav.html @@ -42,6 +42,8 @@ x-data="{searchShow: false}" />
+ {% if 'employee' in perms or request.user|is_reportingmanager %} +
+ {% endif %}
+ {% if 'employee' in perms or request.user|is_reportingmanager %}
+ {% endif %}
{{own_objective.employee_id.employee_first_name|upper}} + >{{own_objective.employee_id.employee_first_name|title}} {{own_objective.employee_id.employee_last_name}}
@@ -257,7 +257,7 @@ />
{{all_objective.employee_id.employee_first_name|upper}} + >{{all_objective.employee_id.employee_first_name|title}} {{all_objective.employee_id.employee_last_name}} diff --git a/pms/views.py b/pms/views.py index 749eb35a2..09483b74a 100644 --- a/pms/views.py +++ b/pms/views.py @@ -1739,7 +1739,7 @@ def dashboard_objective_status(request): ).count() # if not objectives_count: - data.setdefault("objective_label", []).append(status[0]) + data.setdefault("objective_label", []).append(status[1]) data.setdefault("objective_value", []).append(objectives_count) return JsonResponse(data) @@ -1756,7 +1756,7 @@ def dashboard_key_result_status(request): key_results_count = filtersubordinates( request, queryset=key_results, perm="pms.view_employeekeyresult" ).count() - data.setdefault("key_result_label", []).append(i[0]) + data.setdefault("key_result_label", []).append(i[1]) data.setdefault("key_result_value", []).append(key_results_count) return JsonResponse(data) @@ -1773,7 +1773,7 @@ def dashboard_feedback_status(request): feedback_count = filtersubordinates( request, queryset=feedbacks, perm="pms.view_feedback" ).count() - data.setdefault("feedback_label", []).append(i[0]) + data.setdefault("feedback_label", []).append(i[1]) data.setdefault("feedback_value", []).append(feedback_count) return JsonResponse(data)