CBV code updates: 17th July
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user