CBV code updates: 17th July

This commit is contained in:
Horilla
2025-07-17 15:19:44 +05:30
parent 9412f5ff13
commit 71ba49b6c5
7 changed files with 188 additions and 193 deletions

View File

@@ -34,7 +34,7 @@ from django import forms
from django.apps import apps
from django.core.exceptions import ValidationError
from django.db.models.query import QuerySet
from django.forms import DateTimeInput
from django.forms import DateInput, DateTimeInput, TimeInput
from django.template.loader import render_to_string
from django.utils.html import format_html
from django.utils.text import capfirst
@@ -661,6 +661,14 @@ class AttendanceRequestForm(BaseModelForm):
"batch_attendance_id",
]
widgets = {
"attendance_clock_in": TimeInput(attrs={"type": "time"}),
"attendance_clock_out": TimeInput(attrs={"type": "time"}),
"attendance_clock_out_date": DateInput(attrs={"type": "date"}),
"attendance_date": DateInput(attrs={"type": "date"}),
"attendance_clock_in_date": DateInput(attrs={"type": "date"}),
}
def as_p(self, *args, **kwargs):
"""
Render the form fields as HTML table rows with Bootstrap styling.
@@ -1076,12 +1084,16 @@ class BulkAttendanceRequestForm(BaseModelForm):
from_date = forms.DateField(
required=False,
label=_("From Date"),
widget=forms.DateInput(attrs={"type": "date", "class": "form-control"}),
widget=forms.DateInput(
attrs={"type": "date", "class": "form-control oh-input w-100"}
),
)
to_date = forms.DateField(
required=False,
label=_("To Date"),
widget=forms.DateInput(attrs={"type": "date", "class": "form-control"}),
widget=forms.DateInput(
attrs={"type": "date", "class": "form-control oh-input w-100"}
),
)
batch_attendance_id = forms.ModelChoiceField(
queryset=BatchAttendance.objects.all(),
@@ -1110,6 +1122,11 @@ class BulkAttendanceRequestForm(BaseModelForm):
"request_description",
)
widgets = {
"attendance_clock_in": TimeInput(attrs={"type": "time"}),
"attendance_clock_out": TimeInput(attrs={"type": "time"}),
}
def update_worked_hour_hx_fields(self, field_name):
"""Update the widget attributes for worked hour fields."""
self.fields[field_name].widget.attrs.update(

View File

@@ -103,7 +103,7 @@
role="dialog"
aria-labelledby="validateAttendanceRequest"
aria-hidden="true"
style="z-index: 35;"
style="z-index: 1025;"
>
<div class="oh-modal__dialog">
<div class="oh-modal__dialog-header">
@@ -127,7 +127,7 @@
role="dialog"
aria-labelledby="genericModalEdit"
aria-hidden="true"
style="z-index: 45;"
style="z-index: 1025;"
>
<div class="oh-modal__dialog" id="genericModalEditBody"></div>
</div>

View File

@@ -433,11 +433,11 @@ def validate_attendance_request(request, attendance_id):
first_dict = empty_data
else:
other_dict = json.loads(attendance.requested_data)
requests_ids_json = request.GET.get("requests_ids")
requests_ids_json = request.session.get("ordered_ids_attendance", [])
previous_instance_id = next_instance_id = attendance.pk
if requests_ids_json:
previous_instance_id, next_instance_id = closest_numbers(
json.loads(requests_ids_json), attendance_id
requests_ids_json, attendance_id
)
return render(
request,

View File

@@ -78,173 +78,140 @@
</div>
</span>
<div class="oh-accordion-meta__actions" onclick="event.stopPropagation()">
<div class="oh-dropdown" x-data="{open: false}">
<button class="oh-btn oh-stop-prop oh-accordion-meta__btn" @click="open = !open" @click.outside="open = false" title="Action">
{% trans "Actions" %}
<ion-icon class="ms-2 oh-accordion-meta__btn-icon md hydrated" name="caret-down-outline" role="img" aria-label="caret down outline"></ion-icon>
</button>
<div class="oh-dropdown__menu oh-dropdown__menu--right" x-show="open" style="display: none;">
<ul class="oh-dropdown__items">
<li class="oh-dropdown__item">
<a class="oh-dropdown__link"
data-toggle="oh-modal-toggle"
data-target="#objectCreateModal"
hx-get="{% url 'document-request-update' document_list.list.0.document_request_id.id %}"
hx-target="#objectCreateModalTarget">{% trans "Edit" %}</a>
</li>
<li class="oh-dropdown__item">
<a
hx-get="{% url 'generic-delete' %}?model=horilla_documents.DocumentRequest&pk={{document_list.list.0.document_request_id.id}}"
hx-target="#deleteConfirmationBody" data-toggle="oh-modal-toggle"
data-target="#deleteConfirmation">
<button
type="submit"
class="oh-dropdown__link oh-dropdown__link--danger"
title="{% trans 'Delete' %}"
>
{% trans "Delete" %}
</button>
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
<div class="oh-accordion-meta__body d-none">
<div class="oh-sticky-table oh-sticky-table--no-overflow mb-5">
<div class="oh-card p-4">
{% for document in document_list.list %}
<div
class="oh-user_permission-list_item accordion exclude-accordion-style " hx-get='{% url "view-file" document.id %}'
hx-target="#viewFile" data-toggle="oh-modal-toggle" id="requestDocument{{document.id}}"
data-target="#viewFileModal"
>
<div class="oh-user_permission-list_profile ps-2 {% if document.status == "approved" %}row-status--yellow {% elif document.status == 'rejected' %}row-status--red {% elif document.status == 'requested' %}row-status--blue{% endif %}">
<input type="checkbox" id="{{ document.id }}" onchange="highlightRow($(this))"
class="oh-input payslip-checkbox oh-input__checkbox all-documents-row"
onclick="event.stopPropagation()"
>
<div class="oh-navbar__user-photo oh-user_permission--profile">
{% if document.document %}
{% if document.status == "approved" %}
<span
class="oh-badge oh-badge--secondary oh-badge--small oh-badge--round ms-2 mr-2 file-upload"
title="{% trans 'Approved' %}"
>
<ion-icon name="checkmark"></ion-icon>
{% elif document.status == 'rejected' %}
<span
class="oh-badge oh-badge--secondary oh-badge--small oh-badge--round ms-2 mr-2 file-upload"
title="{% trans 'Rejected' %}"
>
<ion-icon name="alert"></ion-icon>
</span>
<div
class="oh-user_permission-list_item accordion exclude-accordion-style " hx-get='{% url "view-file" document.id %}'
hx-target="#viewFile" data-toggle="oh-modal-toggle" id="requestDocument{{document.id}}"
data-target="#viewFileModal"
>
<div class="oh-user_permission-list_profile ps-2 {% if document.status == "approved" %}row-status--yellow {% elif document.status == 'rejected' %}row-status--red {% elif document.status == 'requested' %}row-status--blue{% endif %}">
<input type="checkbox" id="{{ document.id }}" onchange="highlightRow($(this))"
class="oh-input payslip-checkbox oh-input__checkbox all-documents-row"
onclick="event.stopPropagation()"
>
<div class="oh-navbar__user-photo oh-user_permission--profile">
{% if document.document %}
{% if document.status == "approved" %}
<span
class="oh-badge oh-badge--secondary oh-badge--small oh-badge--round ms-2 mr-2 file-upload"
title="{% trans 'Approved' %}"
>
<ion-icon name="checkmark"></ion-icon>
{% elif document.status == 'rejected' %}
<span
class="oh-badge oh-badge--secondary oh-badge--small oh-badge--round ms-2 mr-2 file-upload"
title="{% trans 'Rejected' %}"
>
<ion-icon name="alert"></ion-icon>
</span>
{% else %}
<span
class="oh-badge oh-badge--secondary oh-badge--small oh-badge--round ms-2 mr-2 file-upload"
title="{% trans 'File Uploaded' %}"
>
<ion-icon name="image-outline"></ion-icon>
</span>
{% endif %}
{% else %}
<span
class="oh-badge oh-badge--secondary oh-badge--small oh-badge--round ms-2 mr-2 file-upload"
title="{% trans 'File Uploaded' %}"
>
<ion-icon name="image-outline"></ion-icon>
</span>
<span
class="oh-badge oh-badge--secondary oh-badge--small oh-badge--round ms-2 mr-2 file-upload"
hx-get="{% url 'file-upload' document.id %}"
hx-target="#genericModalBody"
data-document-id="{{ document.id }}"
data-toggle="oh-modal-toggle"
data-target="#genericModal"
onclick="event.stopPropagation()"
title="{% trans 'Upload' %}"
>
<ion-icon
class="md hydrated m-0"
name="add-outline"
role="img"
aria-label="add outline"
></ion-icon>
</span>
{% endif %}
{% else %}
</div>
<div class="oh-feedback-card__name-container ms-1">
<span class="oh-card__title oh-card__title--sm fw-bold me-1"
>{{document.title}} -- {{document.employee_id.get_full_name}}
</span>
<span
class="oh-badge oh-badge--secondary oh-badge--small oh-badge--round ms-2 mr-2 file-upload"
hx-get="{% url 'file-upload' document.id %}"
hx-target="#genericModalBody"
data-document-id="{{ document.id }}"
data-toggle="oh-modal-toggle"
data-target="#genericModal"
onclick="event.stopPropagation()"
title="{% trans 'Upload' %}"
>
class="oh-user_permission_list-text oh-text--light"
title="{{document.document_request_id.description}}"
>
{{document.document_request_id.description|truncatechars:20}}
</span>
</div>
</div>
<div class="oh-btn-group">
{% if perms.horilla_document.change_documentrequest %}
{% if document.status == "approved" or not document.document %}
<a class="oh-btn oh-btn--success w-100 oh-btn--disabled" onclick="event.stopPropagation()">
<ion-icon class="me-1" name="checkmark-outline"></ion-icon>
</a>
{% else %}
<a
type="submit"
hx-confirm="{% trans 'Do you want to approve this request' %}"
hx-get="{% url 'document-approve' document.id %}"
hx-target="#viewFile"
title="{% trans 'Approve' %}"
class="oh-btn oh-btn--success w-100"
onclick="event.stopPropagation()"
>
<ion-icon class="me-1" name="checkmark-outline"></ion-icon>
</a>
{% endif %}
{% if document.status == 'rejected' or not document.document %}
<a class="oh-btn oh-btn--danger w-100 oh-btn--disabled" onclick="event.stopPropagation()">
<ion-icon class="me-1" name="close-circle-outline"></ion-icon>
</a>
{% else %}
<a
type="submit"
hx-get="{% url 'document-reject' document.id %}"
{% comment %} hx-confirm="{% trans 'Do you want to reject this request' %}" {% endcomment %}
hx-target="#genericModalBody"
data-toggle="oh-modal-toggle"
data-target="#rejectFileModal"
title="{% trans 'Reject' %}"
class="oh-btn oh-btn--danger w-100"
onclick="event.stopPropagation()"
>
<ion-icon class="me-1" name="close-circle-outline"></ion-icon>
</a>
{% endif %}
{% endif %}
<form
hx-confirm="{% trans 'Are you sure you want to delete this Document Request?' %}"
hx-post="{% url 'document-delete' document.id %}"
hx-target="#requestDocument{{document.id}}"
method='post'
onclick="event.stopPropagation()"
>
{% csrf_token %}
<button
type="submit"
class="oh-btn oh-btn--secondary"
title="{% trans 'Delete' %}"
>
<ion-icon
class="md hydrated m-0"
name="add-outline"
class="me-1 md hydrated"
name="trash-outline"
role="img"
aria-label="add outline"
></ion-icon>
</span>
{% endif %}
</div>
<div class="oh-feedback-card__name-container ms-1">
<span class="oh-card__title oh-card__title--sm fw-bold me-1"
>{{document.title}} -- {{document.employee_id.get_full_name}}
</span>
<span
class="oh-user_permission_list-text oh-text--light"
title="{{document.document_request_id.description}}"
>
{{document.document_request_id.description|truncatechars:20}}
</span>
aria-label="trash outline"
></ion-icon
>
</button>
</form>
</div>
</div>
<div class="oh-btn-group">
{% if perms.horilla_document.change_documentrequest %}
{% if document.status == "approved" or not document.document %}
<a class="oh-btn oh-btn--success w-100 oh-btn--disabled" onclick="event.stopPropagation()">
<ion-icon class="me-1" name="checkmark-outline"></ion-icon>
</a>
{% else %}
<a
type="submit"
hx-confirm="{% trans 'Do you want to approve this request' %}"
hx-get="{% url 'document-approve' document.id %}"
hx-target="#viewFile"
title="{% trans 'Approve' %}"
class="oh-btn oh-btn--success w-100"
onclick="event.stopPropagation()"
>
<ion-icon class="me-1" name="checkmark-outline"></ion-icon>
</a>
{% endif %}
{% if document.status == 'rejected' or not document.document %}
<a class="oh-btn oh-btn--danger w-100 oh-btn--disabled" onclick="event.stopPropagation()">
<ion-icon class="me-1" name="close-circle-outline"></ion-icon>
</a>
{% else %}
<a
type="submit"
hx-get="{% url 'document-reject' document.id %}"
{% comment %} hx-confirm="{% trans 'Do you want to reject this request' %}" {% endcomment %}
hx-target="#genericModalBody"
data-toggle="oh-modal-toggle"
data-target="#rejectFileModal"
title="{% trans 'Reject' %}"
class="oh-btn oh-btn--danger w-100"
onclick="event.stopPropagation()"
>
<ion-icon class="me-1" name="close-circle-outline"></ion-icon>
</a>
{% endif %}
{% endif %}
<form
hx-confirm="{% trans 'Are you sure you want to delete this Document Request?' %}"
hx-post="{% url 'document-delete' document.id %}"
hx-target="#requestDocument{{document.id}}"
method='post'
onclick="event.stopPropagation()"
>
{% csrf_token %}
<button
type="submit"
class="oh-btn oh-btn--secondary"
title="{% trans 'Delete' %}"
>
<ion-icon
class="me-1 md hydrated"
name="trash-outline"
role="img"
aria-label="trash outline"
></ion-icon
>
</button>
</form>
</div>
</div>
{% endfor %}
<div class="oh-pagination">
<span class="oh-pagination__page">
{% trans "Page" %} {{ document_list.list.number }}

View File

@@ -21,6 +21,7 @@
>
</div>
{% if nav_url %}
<div
hx-get="{{nav_url}}?{{request.GET.urlencode}}"
hx-trigger="load"
@@ -31,7 +32,9 @@
>
</div>
</div>
{% endif %}
{% if view_url %}
<div
class="oh-wrapper"
hx-get="{{view_url}}?{{request.GET.urlencode}}"
@@ -44,6 +47,7 @@
>
</div>
</div>
{% endif %}
{% endblock content %}

View File

@@ -2,9 +2,10 @@ from django.apps import AppConfig
class PgGitBackupConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'pg_backup'
default_auto_field = "django.db.models.BigAutoField"
name = "pg_backup"
def ready(self):
from pg_backup import scheduler
return super().ready()

View File

@@ -37,44 +37,43 @@ Backups can also be triggered manually by calling `backup_postgres()`.
"""
import os
import environ
import datetime
import subprocess
import shutil
import logging
import logging.config
import os
import shutil
import subprocess
from pathlib import Path
import environ
from apscheduler.schedulers.background import BackgroundScheduler
from django.conf import settings
# === Logging Configuration ===
LOGGING_CONFIG = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'standard': {
'format': '[%(asctime)s] [%(levelname)s] %(name)s: %(message)s'
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"standard": {"format": "[%(asctime)s] [%(levelname)s] %(name)s: %(message)s"},
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"formatter": "standard",
},
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'formatter': 'standard',
"loggers": {
"pg_backup": {
"handlers": ["console"],
"level": "INFO",
"propagate": False,
},
},
'loggers': {
'pg_backup': {
'handlers': ['console'],
'level': 'INFO',
'propagate': False,
},
}
}
logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger('pg_backup')
logger = logging.getLogger("pg_backup")
# === Configuration ===
@@ -90,7 +89,9 @@ db = settings.DATABASES["default"]
DB_ENGINE = db["ENGINE"]
if "postgresql" not in DB_ENGINE:
logger.warning("Skipping backup scheduler: not a PostgreSQL database (engine: %s)", DB_ENGINE)
logger.warning(
"Skipping backup scheduler: not a PostgreSQL database (engine: %s)", DB_ENGINE
)
else:
DB_NAME = db["NAME"]
DB_USER = db["USER"]
@@ -110,13 +111,18 @@ else:
command = [
shutil.which("pg_dump"),
"-h", DB_HOST,
"-p", DB_PORT,
"-U", DB_USER,
"-F", "c",
"-h",
DB_HOST,
"-p",
DB_PORT,
"-U",
DB_USER,
"-F",
"c",
"-b",
"-v",
"-f", str(backup_file),
"-f",
str(backup_file),
DB_NAME,
]
@@ -145,11 +151,11 @@ else:
hour, minute = map(int, time_str.split(":"))
scheduler.add_job(
backup_postgres,
'cron',
"cron",
hour=hour,
minute=minute,
id=f"backup_{hour}_{minute}",
replace_existing=True
replace_existing=True,
)
logger.info("Backup scheduled at %02d:%02d", hour, minute)
except ValueError: