switch from ownCloud to minIO

buttons in table are buttons and do edit and delete properly
master
kikimor 7 months ago
parent 4603953458
commit c1183c22ea

@ -5,58 +5,50 @@
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="7410a44d-51b9-408b-85ad-4fa46776b372" name="Changes" comment="commit unversioned files ;)"> <list default="true" id="7410a44d-51b9-408b-85ad-4fa46776b372" name="Changes" comment="commit unversioned files ;)">
<change afterPath="$PROJECT_DIR$/accounts/migrations/0008_populate_permissions.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/accounts/migrations/0009_create_superuser.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/booking/templatetags/__init__.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/booking/templatetags/custom_filters.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/common/migrations/0004_populate_initial_data.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/common/utils/minio_utils.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/containers/views/common.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/docker-compose.minio.yml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/payments/migrations/0004_populate_tariffs.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/static/js/container-details.js" afterDir="false" />
<change afterPath="$PROJECT_DIR$/static/styles/details.css" afterDir="false" />
<change afterPath="$PROJECT_DIR$/templates/common/base.html" afterDir="false" />
<change afterPath="$PROJECT_DIR$/templates/common/container-details.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/DepoT/settings.py" beforeDir="false" afterPath="$PROJECT_DIR$/DepoT/settings.py" afterDir="false" /> <change beforePath="$PROJECT_DIR$/DepoT/settings.py" beforeDir="false" afterPath="$PROJECT_DIR$/DepoT/settings.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/DepoT/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/DepoT/urls.py" afterDir="false" /> <change beforePath="$PROJECT_DIR$/accounts/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/accounts/urls.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/accounts/forms.py" beforeDir="false" afterPath="$PROJECT_DIR$/accounts/forms.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/accounts/views.py" beforeDir="false" afterPath="$PROJECT_DIR$/accounts/views.py" afterDir="false" /> <change beforePath="$PROJECT_DIR$/accounts/views.py" beforeDir="false" afterPath="$PROJECT_DIR$/accounts/views.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/booking/forms.py" beforeDir="false" afterPath="$PROJECT_DIR$/booking/forms.py" afterDir="false" /> <change beforePath="$PROJECT_DIR$/booking/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/booking/urls.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/common/templatetags/filters.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/templatetags/filters.py" afterDir="false" /> <change beforePath="$PROJECT_DIR$/common/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/urls.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/common/utils/utils.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/utils/utils.py" afterDir="false" /> <change beforePath="$PROJECT_DIR$/common/utils/owncloud_utls.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/utils/owncloud_utls.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/common/views/barrier_views.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/views/barrier_views.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/common_api/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/common_api/urls.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/containers/forms.py" beforeDir="false" afterPath="$PROJECT_DIR$/containers/forms.py" afterDir="false" /> <change beforePath="$PROJECT_DIR$/containers/forms.py" beforeDir="false" afterPath="$PROJECT_DIR$/containers/forms.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/containers/models.py" beforeDir="false" afterPath="$PROJECT_DIR$/containers/models.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/containers/services.py" beforeDir="false" afterPath="$PROJECT_DIR$/containers/services.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/containers/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/containers/urls.py" afterDir="false" /> <change beforePath="$PROJECT_DIR$/containers/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/containers/urls.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/containers/views/barrier_views.py" beforeDir="false" afterPath="$PROJECT_DIR$/containers/views/barrier_views.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/containers/views/employee_views.py" beforeDir="false" afterPath="$PROJECT_DIR$/containers/views/employee_views.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/damages_api/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/damages_api/urls.py" afterDir="false" /> <change beforePath="$PROJECT_DIR$/damages_api/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/damages_api/urls.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/damages_api/views.py" beforeDir="false" afterPath="$PROJECT_DIR$/damages_api/views.py" afterDir="false" /> <change beforePath="$PROJECT_DIR$/damages_api/views.py" beforeDir="false" afterPath="$PROJECT_DIR$/damages_api/views.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/docker-compose.yml" beforeDir="false" afterPath="$PROJECT_DIR$/docker-compose.yml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/docker-compose.yml" beforeDir="false" afterPath="$PROJECT_DIR$/docker-compose.yml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/dockerfile" beforeDir="false" afterPath="$PROJECT_DIR$/dockerfile" afterDir="false" /> <change beforePath="$PROJECT_DIR$/dockerfile" beforeDir="false" afterPath="$PROJECT_DIR$/dockerfile" afterDir="false" />
<change beforePath="$PROJECT_DIR$/payments/forms.py" beforeDir="false" afterPath="$PROJECT_DIR$/payments/forms.py" afterDir="false" /> <change beforePath="$PROJECT_DIR$/preinfo/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/preinfo/urls.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/payments/services.py" beforeDir="false" afterPath="$PROJECT_DIR$/payments/services.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/payments/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/payments/urls.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/payments/utils.py" beforeDir="false" afterPath="$PROJECT_DIR$/payments/utils.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/payments/views.py" beforeDir="false" afterPath="$PROJECT_DIR$/payments/views.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/populate_database.txt" beforeDir="false" afterPath="$PROJECT_DIR$/populate_database.txt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/preinfo/forms.py" beforeDir="false" afterPath="$PROJECT_DIR$/preinfo/forms.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/preinfo/views/client_views.py" beforeDir="false" afterPath="$PROJECT_DIR$/preinfo/views/client_views.py" afterDir="false" /> <change beforePath="$PROJECT_DIR$/preinfo/views/client_views.py" beforeDir="false" afterPath="$PROJECT_DIR$/preinfo/views/client_views.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/static/images/k-depot-logo.svg" beforeDir="false" afterPath="$PROJECT_DIR$/static/images/k-depot-logo.svg" afterDir="false" /> <change beforePath="$PROJECT_DIR$/requirements.txt" beforeDir="false" afterPath="$PROJECT_DIR$/requirements.txt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/static/js/container_validation.js" beforeDir="false" afterPath="$PROJECT_DIR$/static/js/container_validation.js" afterDir="false" />
<change beforePath="$PROJECT_DIR$/static/js/crud-list.js" beforeDir="false" afterPath="$PROJECT_DIR$/static/js/crud-list.js" afterDir="false" /> <change beforePath="$PROJECT_DIR$/static/js/crud-list.js" beforeDir="false" afterPath="$PROJECT_DIR$/static/js/crud-list.js" afterDir="false" />
<change beforePath="$PROJECT_DIR$/static/styles/forms.css" beforeDir="false" afterPath="$PROJECT_DIR$/static/styles/forms.css" afterDir="false" /> <change beforePath="$PROJECT_DIR$/static/styles/forms.css" beforeDir="false" afterPath="$PROJECT_DIR$/static/styles/forms.css" afterDir="false" />
<change beforePath="$PROJECT_DIR$/static/styles/styles.css" beforeDir="false" afterPath="$PROJECT_DIR$/static/styles/styles.css" afterDir="false" />
<change beforePath="$PROJECT_DIR$/static/styles/tables.css" beforeDir="false" afterPath="$PROJECT_DIR$/static/styles/tables.css" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/barrier/barrier-base.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/barrier/barrier-base.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/barrier/barrier-dashboard.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/barrier/barrier-dashboard.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/client-base.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/client-base.html" afterDir="false" /> <change beforePath="$PROJECT_DIR$/templates/client-base.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/client-base.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/client-sidebar.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/client-sidebar.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/client/booking-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/client/booking-list.html" afterDir="false" /> <change beforePath="$PROJECT_DIR$/templates/client/booking-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/client/booking-list.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/client/line-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/client/line-list.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/client/preinfo-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/client/preinfo-list.html" afterDir="false" /> <change beforePath="$PROJECT_DIR$/templates/client/preinfo-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/client/preinfo-list.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/container-expedition.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/container-expedition.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/container-photos.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/container-photos.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/container-receive.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/container-receive.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/email.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/email.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/employee-base.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee-base.html" afterDir="false" /> <change beforePath="$PROJECT_DIR$/templates/employee-base.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee-base.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/employee/container-search.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee/container-search.html" afterDir="false" /> <change beforePath="$PROJECT_DIR$/templates/employee-sidebar.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee-sidebar.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/employee/containers-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee/containers-list.html" afterDir="false" /> <change beforePath="$PROJECT_DIR$/templates/employee/booking-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee/booking-list.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/employee/payment-create.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee/payment-create.html" afterDir="false" /> <change beforePath="$PROJECT_DIR$/templates/employee/company-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee/company-list.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/employee/payment-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee/payment-list.html" afterDir="false" /> <change beforePath="$PROJECT_DIR$/templates/employee/line-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee/line-list.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/employee/preinfo-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee/preinfo-list.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/landing-page.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/landing-page.html" afterDir="false" /> <change beforePath="$PROJECT_DIR$/templates/landing-page.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/landing-page.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/list-crud.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/list-crud.html" afterDir="false" /> <change beforePath="$PROJECT_DIR$/templates/list-crud.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/list-crud.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/registration/login.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/registration/login.html" afterDir="false" /> <change beforePath="$PROJECT_DIR$/templates/registration/user-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/registration/user-list.html" afterDir="false" />
</list> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
@ -70,9 +62,9 @@
<component name="FileTemplateManagerImpl"> <component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES"> <option name="RECENT_TEMPLATES">
<list> <list>
<option value="HTML File" />
<option value="CSS File" /> <option value="CSS File" />
<option value="JavaScript File" /> <option value="JavaScript File" />
<option value="HTML File" />
<option value="Python Script" /> <option value="Python Script" />
</list> </list>
</option> </option>
@ -99,7 +91,7 @@
&quot;django.template.preview.state&quot;: &quot;SHOW_EDITOR_AND_PREVIEW&quot;, &quot;django.template.preview.state&quot;: &quot;SHOW_EDITOR_AND_PREVIEW&quot;,
&quot;git-widget-placeholder&quot;: &quot;master&quot;, &quot;git-widget-placeholder&quot;: &quot;master&quot;,
&quot;ignore.virus.scanning.warn.message&quot;: &quot;true&quot;, &quot;ignore.virus.scanning.warn.message&quot;: &quot;true&quot;,
&quot;last_opened_file_path&quot;: &quot;C:/dev_projects/python/Django/DepoT/templates/employee&quot;, &quot;last_opened_file_path&quot;: &quot;C:/dev_projects/python/Django/DepoT&quot;,
&quot;list.type.of.created.stylesheet&quot;: &quot;CSS&quot;, &quot;list.type.of.created.stylesheet&quot;: &quot;CSS&quot;,
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;, &quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;, &quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
@ -116,11 +108,11 @@
}</component> }</component>
<component name="RecentsManager"> <component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS"> <key name="CopyFile.RECENT_KEYS">
<recent name="C:\dev_projects\python\Django\DepoT" />
<recent name="C:\dev_projects\python\Django\DepoT\templates\employee" /> <recent name="C:\dev_projects\python\Django\DepoT\templates\employee" />
<recent name="C:\dev_projects\python\Django\DepoT\templates\client" /> <recent name="C:\dev_projects\python\Django\DepoT\templates\client" />
<recent name="C:\dev_projects\python\Django\DepoT\templates" /> <recent name="C:\dev_projects\python\Django\DepoT\templates" />
<recent name="C:\dev_projects\python\Django\DepoT\templates\registration" /> <recent name="C:\dev_projects\python\Django\DepoT\templates\registration" />
<recent name="C:\dev_projects\python\Django\DepoT\static\styles" />
</key> </key>
<key name="MoveFile.RECENT_KEYS"> <key name="MoveFile.RECENT_KEYS">
<recent name="C:\dev_projects\python\Django\DepoT" /> <recent name="C:\dev_projects\python\Django\DepoT" />
@ -179,7 +171,8 @@
<workItem from="1753174764499" duration="271000" /> <workItem from="1753174764499" duration="271000" />
<workItem from="1753175068058" duration="4635000" /> <workItem from="1753175068058" duration="4635000" />
<workItem from="1753179863298" duration="8357000" /> <workItem from="1753179863298" duration="8357000" />
<workItem from="1753197869497" duration="15872000" /> <workItem from="1753197869497" duration="98156000" />
<workItem from="1753637487803" duration="30673000" />
</task> </task>
<task id="LOCAL-00001" summary="Add IntelliJ IDEA project configuration files&#10;&#10;This commit adds IntelliJ IDEA-specific configuration files for the project, including module setup, version control integration, inspection profiles, and workspace settings. These files facilitate development environment configuration for contributors using IntelliJ IDEA."> <task id="LOCAL-00001" summary="Add IntelliJ IDEA project configuration files&#10;&#10;This commit adds IntelliJ IDEA-specific configuration files for the project, including module setup, version control integration, inspection profiles, and workspace settings. These files facilitate development environment configuration for contributors using IntelliJ IDEA.">
<option name="closed" value="true" /> <option name="closed" value="true" />
@ -304,11 +297,6 @@
<line>118</line> <line>118</line>
<option name="timeStamp" value="61" /> <option name="timeStamp" value="61" />
</line-breakpoint> </line-breakpoint>
<line-breakpoint enabled="true" suspend="THREAD" type="python-line">
<url>file://$PROJECT_DIR$/common/utils/owncloud_utls.py</url>
<line>29</line>
<option name="timeStamp" value="64" />
</line-breakpoint>
<line-breakpoint enabled="true" suspend="THREAD" type="python-line"> <line-breakpoint enabled="true" suspend="THREAD" type="python-line">
<url>file://$PROJECT_DIR$/payments/views.py</url> <url>file://$PROJECT_DIR$/payments/views.py</url>
<line>41</line> <line>41</line>

@ -91,15 +91,13 @@ WSGI_APPLICATION = "DepoT.wsgi.application"
DATABASES = { DATABASES = {
"default": { "default": {
"ENGINE": "django.db.backends.postgresql", "ENGINE": "django.db.backends.postgresql",
"NAME": os.environ.get("DB_NAME", env("DB_NAME")), "NAME": env("DB_NAME"),
"USER": os.environ.get("DB_USER", env("DB_USER")), "USER": env("DB_USER"),
"PASSWORD": os.environ.get("DB_PASSWORD", env("DB_PASSWORD")), "PASSWORD": env("DB_PASSWORD"),
"HOST": os.environ.get("DB_HOST", env("DB_HOST")), "HOST": env("DB_HOST"),
"PORT": os.environ.get("DB_PORT", env("DB_PORT")), "PORT": env("DB_PORT"),
} }
} }
# Password validation # Password validation
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators # https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
@ -167,8 +165,9 @@ EMAIL_HOST_PASSWORD = env("EMAIL_HOST_PASSWORD", cast=str, default=None)
EMAIL_USE_TLS = env("EMAIL_USE_TLS", cast=bool, default=True) # Use EMAIL_PORT 587 for TLS EMAIL_USE_TLS = env("EMAIL_USE_TLS", cast=bool, default=True) # Use EMAIL_PORT 587 for TLS
EMAIL_USE_SSL = env("EMAIL_USE_SSL", cast=bool, default=False) # EUse MAIL_PORT 465 for SSL EMAIL_USE_SSL = env("EMAIL_USE_SSL", cast=bool, default=False) # EUse MAIL_PORT 465 for SSL
ADMIN_USER_NAME=env("ADMIN_USER_NAME", default="Admin user") ADMIN_USER_NAME=env("ADMIN_USER_NAME")
ADMIN_USER_EMAIL=env("ADMIN_USER_EMAIL", default=None) ADMIN_USER_PASSWORD=env("ADMIN_USER_PASSWORD")
ADMIN_USER_EMAIL=env("ADMIN_USER_EMAIL")
MANAGERS=[] MANAGERS=[]
ADMINS=[] ADMINS=[]
@ -177,3 +176,13 @@ if all([ADMIN_USER_NAME, ADMIN_USER_EMAIL]):
(f'{ADMIN_USER_NAME}', f'{ADMIN_USER_EMAIL}') (f'{ADMIN_USER_NAME}', f'{ADMIN_USER_EMAIL}')
] ]
MANAGERS=ADMINS MANAGERS=ADMINS
MINIO_ENDPOINT = env('MINIO_ENDPOINT')
AWS_S3_CUSTOM_DOMAIN = env('AWS_S3_CUSTOM_DOMAIN')
MINIO_SERVER_URL = env('MINIO_SERVER_URL')
AWS_S3_URL_PROTOCOL = env('AWS_S3_URL_PROTOCOL')
MINIO_ACCESS_KEY = env('MINIO_ACCESS_KEY')
MINIO_SECRET_KEY = env('MINIO_SECRET_KEY')
MINIO_BUCKET_NAME = env('MINIO_BUCKET_NAME')
MINIO_SECURE = False # Set to True if using HTTPS

@ -9,5 +9,6 @@ urlpatterns = [
path('', views.UserListView.as_view(), name='user_list'), path('', views.UserListView.as_view(), name='user_list'),
path('register/', views.RegisterView.as_view(), name='user_register'), path('register/', views.RegisterView.as_view(), name='user_register'),
path('<int:pk>/update/', views.UserUpdateView.as_view(), name='user_update'), path('<int:pk>/update/', views.UserUpdateView.as_view(), name='user_update'),
path('<int:pk>/active/', views.UserActiveView.as_view(), name='user_active'),
])), ])),
] ]

@ -1,15 +1,20 @@
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.views import LoginView from django.contrib.auth.views import LoginView
from django.http import HttpResponseForbidden, JsonResponse
from django.shortcuts import render from django.shortcuts import render
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.views import View
from django.views.generic import TemplateView, FormView, ListView, UpdateView from django.views.generic import TemplateView, FormView, ListView, UpdateView
from rest_framework.generics import get_object_or_404
from accounts.forms import LoginForm, RegisterForm from accounts.forms import LoginForm, RegisterForm
from accounts.models import DepotUser from accounts.models import DepotUser
from django.contrib.auth.decorators import login_required, user_passes_test from django.contrib.auth.decorators import login_required, user_passes_test
from django.contrib.auth.mixins import AccessMixin from django.contrib.auth.mixins import AccessMixin, LoginRequiredMixin
# Create your views here. # Create your views here.
@ -165,5 +170,18 @@ class UserUpdateView(UpdateView):
return form return form
class UserActiveView(LoginRequiredMixin, View):
success_url = reverse_lazy('user_list')
def post(self, request, pk, *args, **kwargs):
user = request.user
if not (user.is_superuser or getattr(user, 'user_type', None) == DepotUser.UserType.COMPANY_ADMIN):
return HttpResponseForbidden("You do not have permission to perform this action.")
target_user = get_object_or_404(get_user_model(), pk=pk)
if target_user == user:
return HttpResponseForbidden("You cannot change your own active status.")
target_user.is_active = not target_user.is_active
target_user.save()
return JsonResponse({'success': True, 'is_active': target_user.is_active})

@ -0,0 +1,11 @@
from django import template
register = template.Library()
@register.filter
def distinct_vehicles(vehicles):
vehicles_set = set(vehicles.split(','))
vehicles_str = ', '.join(sorted(vehicles_set))
return vehicles_str

@ -7,7 +7,8 @@ urlpatterns = [
path('client/', include([ path('client/', include([
path('', ClientBookingView.as_view(), name='client_booking'), path('', ClientBookingView.as_view(), name='client_booking'),
path('create/', CreateBookingView.as_view(), name='client_booking_create'), path('create/', CreateBookingView.as_view(), name='client_booking_create'),
path('update/<int:pk>/', ClientBookingUpdateView.as_view(), name='client_booking_update'), path('<int:pk>/update/', ClientBookingUpdateView.as_view(), name='client_booking_update'),
path('<int:pk>/active/', ClientBookingUpdateView.as_view(), name='client_booking_active'),
])), ])),
path('employee/', BookingListView.as_view(), name='employee_bookings'), path('employee/', BookingListView.as_view(), name='employee_bookings'),
] ]

@ -22,15 +22,18 @@ urlpatterns = [
path('', ClientLineListView.as_view(), name='client_line'), path('', ClientLineListView.as_view(), name='client_line'),
path('create/', ClientLineCreateView.as_view(), name='client_line_create'), path('create/', ClientLineCreateView.as_view(), name='client_line_create'),
path('<int:pk>/edit/', ClientLineUpdateView.as_view(), name='client_line_update'), path('<int:pk>/edit/', ClientLineUpdateView.as_view(), name='client_line_update'),
path('<int:pk>/active/', ClientLineUpdateView.as_view(), name='client_line_active'),
])), ])),
path('employee/company/', include([ path('employee/company/', include([
path('', EmployeeCompanyListView.as_view(), name='employee_company'), path('', EmployeeCompanyListView.as_view(), name='employee_company'),
path('create/', EmployeeCompanyCreateView.as_view(), name='employee_company_create'), path('create/', EmployeeCompanyCreateView.as_view(), name='employee_company_create'),
path('<int:pk>/edit/', EmployeeCompanyUpdateView.as_view(), name='employee_company_update'), path('<int:pk>/edit/', EmployeeCompanyUpdateView.as_view(), name='employee_company_update'),
path('<int:pk>/active/', EmployeeCompanyUpdateView.as_view(), name='employee_company_active'),
])), ])),
path('employee/line/', include([ path('employee/line/', include([
path('', EmployeeLineListView.as_view(), name='employee_line'), path('', EmployeeLineListView.as_view(), name='employee_line'),
path('create/', EmployeeLineCreateView.as_view(), name='employee_line_create'), path('create/', EmployeeLineCreateView.as_view(), name='employee_line_create'),
path('<int:pk>/edit/', EmployeeLineUpdateView.as_view(), name='employee_line_update'), path('<int:pk>/edit/', EmployeeLineUpdateView.as_view(), name='employee_line_update'),
path('<int:pk>/active/', EmployeeLineUpdateView.as_view(), name='employee_line_active'),
])), ])),
] ]

@ -0,0 +1,73 @@
from minio import Minio
from django.conf import settings
from datetime import datetime, timedelta
from rest_framework.response import Response
from rest_framework import status
import boto3
from botocore.client import Config
client = Minio(
settings.MINIO_ENDPOINT,
access_key=settings.MINIO_ACCESS_KEY,
secret_key=settings.MINIO_SECRET_KEY,
secure=settings.MINIO_SECURE
)
def upload_damage_photo(photo_file, depot_id, content_type):
try:
s3_client = boto3.client('s3',
endpoint_url=f'http://{settings.MINIO_ENDPOINT}',
aws_access_key_id=settings.MINIO_ACCESS_KEY,
aws_secret_access_key=settings.MINIO_SECRET_KEY,
config=Config(signature_version='s3v4'),
region_name='us-east-1'
)
# Use the full filename as the key, which includes the depot_id folder
s3_client.upload_fileobj(
photo_file,
settings.MINIO_BUCKET_NAME,
photo_file.name,
ExtraArgs={'ContentType': content_type}
)
# Generate a presigned URL for the uploaded object
url = s3_client.generate_presigned_url('get_object',
Params={
'Bucket': settings.MINIO_BUCKET_NAME,
'Key': photo_file.name
},
ExpiresIn=3600
)
return url
except Exception as e:
print(f"Error uploading to MinIO: {str(e)}")
raise
def get_damages(depot_id):
try:
objects = client.list_objects(
settings.MINIO_BUCKET_NAME,
prefix=f"{depot_id}/"
)
damages = []
for obj in objects:
url = client.presigned_get_object(
settings.MINIO_BUCKET_NAME,
obj.object_name,
expires=timedelta(days=7)
)
damages.append({
"url": url,
"filename": obj.object_name.split('/')[-1]
})
return Response(damages, status=status.HTTP_200_OK)
except Exception as e:
print(f"Error listing objects from MinIO: {str(e)}")
return Response([], status=status.HTTP_500_INTERNAL_SERVER_ERROR)

@ -2,11 +2,15 @@ import os
import uuid import uuid
from datetime import datetime from datetime import datetime
from django.conf import settings from django.conf import settings
from django.http import HttpResponse
from rest_framework import status from rest_framework import status
from rest_framework.response import Response from rest_framework.response import Response
import owncloud import owncloud
from owncloud import (HTTPResponseError) from owncloud import (HTTPResponseError)
import xml.etree.ElementTree as ET
import requests
class Owncloud: class Owncloud:
@staticmethod @staticmethod
@ -33,8 +37,41 @@ class Owncloud:
damages = [] damages = []
for file_info in files: for file_info in files:
if file_info.is_dir(): if file_info.is_dir():
continue # Пропускаме поддиректории continue
# Създаване на публичен линк за всеки файл
link_info = oc.share_file_with_link(file_info.path) link_info = oc.share_file_with_link(file_info.path)
damages.append(link_info.get_link()) damages.append(link_info.get_link())
return Response(damages, status=status.HTTP_200_OK) return Response(damages, status=status.HTTP_200_OK)
@staticmethod
def get_damages_webdav(depot_id):
oc = owncloud.Client(settings.OWNCLOUD_URL)
oc.login(settings.OWNCLOUD_USER, settings.OWNCLOUD_PASSWORD)
path = f"{settings.OWNCLOUD_DAMAGES_FOLDER}{str(depot_id)}"
files = oc.list(path)
damages = []
for file_info in files:
if file_info.is_dir():
continue
original_filename = os.path.basename(file_info.path)
link_info = oc.share_file_with_link(file_info.path)
share_url = link_info.get_link()
token = share_url.rstrip('/').split('/')[-1]
damages.append({
"token": token,
"filename": original_filename
})
return Response(damages, status=status.HTTP_200_OK)
def proxy_owncloud_image(request, token, filename):
url = f"{settings.OWNCLOUD_URL}/public.php/webdav/{filename}"
r = requests.get(url, auth=(token, ''), stream=True)
if r.status_code == 200:
content_type = r.headers.get('Content-Type', 'image/jpeg')
return HttpResponse(r.content, content_type=content_type)
return HttpResponse("Not found", status=404)

@ -1,4 +1,4 @@
from django.forms import ModelForm, TextInput from django.forms import ModelForm, TextInput, CharField, Form
from containers.models import Container from containers.models import Container
@ -52,3 +52,14 @@ class ContainerExpeditionForm(ContainerBaseForm):
for field in readonly_fields: for field in readonly_fields:
self.fields[field].widget.attrs['readonly'] = True self.fields[field].widget.attrs['readonly'] = True
# self.fields[field].disabled = True # self.fields[field].disabled = True
class ContainerSearchForm(Form):
container_number = CharField(
max_length=11,
required=True,
widget=TextInput(attrs={
'class': 'form-control',
'placeholder': 'Enter container number'
})
)

@ -1,11 +1,13 @@
from django.urls import include, path from django.urls import include, path
from containers.views.common import ContainerDetails
from containers.views.employee_views import ContainersListView, ReportContainersUnpaidListView from containers.views.employee_views import ContainersListView, ReportContainersUnpaidListView
from containers.views.barrier_views import ContainerReceiveView, ContainerExpedition, ContainerSearchView, \ from containers.views.barrier_views import ContainerReceiveView, ContainerExpedition, ContainerSearchView, \
ContainerPhotosView ContainerPhotosView
urlpatterns = [ urlpatterns = [
path('container-search/', ContainerSearchView.as_view(), name='container_search'), path('container-search/', ContainerSearchView.as_view(), name='container_search'),
path('container-details/', ContainerDetails.as_view(), name='container_details'),
path('container-search/', ContainerSearchView.as_view(), name='barrier_photos'), path('container-search/', ContainerSearchView.as_view(), name='barrier_photos'),
# path('search/', ContainerSearchView.as_view(), name='container_search'), # path('search/', ContainerSearchView.as_view(), name='container_search'),
path('employee/', ContainersListView.as_view(), name='employee_containers'), path('employee/', ContainersListView.as_view(), name='employee_containers'),

@ -0,0 +1,129 @@
from django.shortcuts import render
from django.views.generic import FormView, ListView
from django.views.generic.base import TemplateView
from common.utils.utils import filter_queryset_by_user
from containers.forms import ContainerSearchForm
from containers.models import Container
# class ContainerDetails(FormView):
# template_name = 'common/container-details.html'
# form_class = ContainerSearchForm
# model = Container
# base_template = 'employee-base.html'
# context_object_name = 'objects'
# paginate_by = 10
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context['base_template'] = self.base_template
# return context
#
# def get(self, request, *args, **kwargs):
#
# referrer = request.META.get('HTTP_REFERER', '')
# if 'container-details' not in referrer:
# request.session.pop('container_number', None)
#
#
# context = self.get_context_data()
# selected_id = request.GET.get('selected')
# container_number = request.session.get('container_number')
#
# if container_number:
# containers = Container.objects.filter(number=container_number).order_by('-received_on')
# if containers.exists():
# if selected_id:
# try:
# selected_container = containers.get(id=selected_id)
# except Container.DoesNotExist:
# selected_container = containers.first()
# else:
# selected_container = containers.first()
#
# context.update({
# 'form': self.form_class(initial={'container_number': container_number}),
# 'container': selected_container,
# 'objects': containers, # For list-crud.html compatibility
# 'show_results': True
# })
# return render(request, self.template_name, context)
#
# context.update({
# 'form': self.form_class(),
# 'show_results': False
# })
# return render(request, self.template_name, context)
#
# def form_valid(self, form):
# container_number = form.cleaned_data['container_number']
# self.request.session['container_number'] = container_number # Store in session
# context = self.get_context_data()
# containers = Container.objects.filter(number=container_number).order_by('-received_on')
# if containers.exists():
# context.update({
# 'form': form,
# 'container': containers.first(),
# 'objects': containers, # For list-crud.html compatibility
# 'show_results': True
# })
# else:
# context.update({
# 'form': form,
# 'error': 'Container not found',
# 'show_results': False
# })
# return render(self.request, self.template_name, context)
class ContainerDetails(ListView):
template_name = 'common/container-details.html'
model = Container
base_template = 'employee-base.html'
context_object_name = 'objects'
paginate_by = 10
def get_queryset(self):
container_number = self.request.session.get('container_number')
if container_number:
query = Container.objects.filter(number=container_number).order_by('-received_on')
query = filter_queryset_by_user(query, self.request.user)
return query
return Container.objects.none()
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['base_template'] = self.base_template
context['form'] = ContainerSearchForm(
initial={'container_number': self.request.session.get('container_number')}
)
full_queryset = self.get_queryset()
if full_queryset.exists():
selected_id = self.request.GET.get('selected')
if selected_id:
try:
context['container'] = full_queryset.get(id=selected_id)
except Container.DoesNotExist:
context['container'] = full_queryset.first()
else:
context['container'] = full_queryset.first()
context['show_results'] = True
else:
context['show_results'] = False
return context
def post(self, request, *args, **kwargs):
form = ContainerSearchForm(request.POST)
if form.is_valid():
container_number = form.cleaned_data['container_number']
request.session['container_number'] = container_number
return self.get(request, *args, **kwargs)
context = self.get_context_data()
context['form'] = form
context['show_results'] = False
return render(request, self.template_name, context)

@ -1,5 +1,6 @@
from django.urls import path from django.urls import path
from common.utils.owncloud_utls import proxy_owncloud_image
from damages_api.views import Damages from damages_api.views import Damages
urlpatterns = [ urlpatterns = [

@ -1,52 +1,85 @@
import base64 import base64
import mimetypes
import os import os
import tempfile import tempfile
from datetime import datetime from datetime import datetime
from django.conf import settings
import boto3
from botocore.client import Config
from django.core.exceptions import BadRequest from django.core.exceptions import BadRequest
from django.core.files.base import ContentFile
from rest_framework import status from rest_framework import status
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from common.utils.owncloud_utls import Owncloud # from common.utils.owncloud_utls import Owncloud
from containers.models import ContainerPhotos, Container from containers.models import ContainerPhotos, Container
from common.utils.minio_utils import get_damages, upload_damage_photo
class Damages(APIView): class Damages(APIView):
def post(self, request, depot_id): def post(self, request, depot_id):
photo = request.data.get("photo") photo = request.data.get("photo")
extension = request.data.get("photo_extension") extension = request.data.get("photo_extension")
with tempfile.NamedTemporaryFile(suffix=f'.{extension}', delete=False) as temp_file:
try: try:
temp_file.write(base64.b64decode(photo.encode("utf-8"))) # Decode base64 and create a ContentFile
temp_file.flush() photo_data = base64.b64decode(photo.encode("utf-8"))
temp_file.close() # Close the file before uploading
# Generate a unique filename with proper folder structure
own_filename = Owncloud.upload_damage_photo(temp_file.name, depot_id) import uuid
filename = f"{depot_id}/{datetime.today().strftime('%Y%m%d')}_{uuid.uuid4()}.{extension}"
container_photo = ContainerPhotos() photo_file = ContentFile(photo_data, name=filename)
container_photo.container = Container.objects.get(pk=depot_id)
container_photo.photo = own_filename # Set content type based on file extension
container_photo.uploaded_on = datetime.now() content_type = mimetypes.guess_type(filename)[0] or 'application/octet-stream'
container_photo.uploaded_by = request.user
container_photo.save() # Upload to MinIO and get URL
url = upload_damage_photo(photo_file, depot_id, content_type)
# Save to database
container_photo = ContainerPhotos.objects.create(
container=Container.objects.get(pk=depot_id),
photo=url,
uploaded_by=request.user
)
return Response(status=status.HTTP_201_CREATED) return Response(status=status.HTTP_201_CREATED)
except Exception as ex: except Exception as ex:
print(f"Error in damage photo upload: {str(ex)}")
return Response( return Response(
{"error": "Failed to process photo"}, {"error": "Failed to process photo"},
status=status.HTTP_400_BAD_REQUEST status=status.HTTP_400_BAD_REQUEST
) )
finally:
if temp_file:
try:
os.unlink(temp_file.name)
except OSError:
pass # Ignore deletion errors
def get(self, request, depot_id): def get(self, request, depot_id):
return Owncloud.get_damages(depot_id) try:
s3_client = boto3.client('s3',
endpoint_url=f"http://{settings.MINIO_ENDPOINT}",
aws_access_key_id=settings.MINIO_ACCESS_KEY,
aws_secret_access_key=settings.MINIO_SECRET_KEY,
config=Config(signature_version='s3v4')
)
prefix = f"{depot_id}/"
response = s3_client.list_objects_v2(
Bucket=settings.MINIO_BUCKET_NAME,
Prefix=prefix
)
photos = []
if 'Contents' in response:
for obj in response['Contents']:
url = f"{settings.MINIO_SERVER_URL}/{settings.MINIO_BUCKET_NAME}/{obj['Key']}"
photos.append({'url': url})
return Response(photos, status=status.HTTP_200_OK)
except Exception as e:
print(f"MinIO Error: {str(e)}")
return Response(
{"error": "Failed to retrieve photos"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)

@ -0,0 +1,70 @@
version: '3.8'
services:
database:
image: postgres
environment:
- POSTGRES_DB=depot # Matches DB_NAME in production.env
- POSTGRES_USER=postgres # Matches DB_USER in production.env
- POSTGRES_PASSWORD=admin # Matches DB_PASSWORD in production.env
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d depot"] # Check specific database
interval: 5s
timeout: 5s
retries: 5
networks:
- app-network
minio:
image: minio/minio:latest
ports:
- "9000:9000" # API Port
- "9001:9001" # Console Port
environment:
MINIO_ROOT_USER: kikimor # Change this
MINIO_ROOT_PASSWORD: shushunka1
MINIO_SERVER_URL: http://localhost:9000
MINIO_ADDRESS: "0.0.0.0:9000"
MINIO_BROWSER_REDIRECT_URL: "http://localhost:9001" # Console URL
volumes:
- minio_data:/data
command: server --console-address ":9001" /data
healthcheck:
test: ["CMD", "mc", "ready", "local"]
interval: 30s
timeout: 20s
retries: 3
networks:
- app-network
# Optional: Create buckets and users on startup
createbuckets:
image: minio/mc
depends_on:
- minio
entrypoint: >
/bin/sh -c "
sleep 10;
mc alias set myminio http://minio:9000 kikimor shushunka1;
mc mb myminio/damages;
mc anonymous set download myminio/damages;
mc policy set public myminio/damages;
echo '{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:GetObject\"],\"Resource\":[\"arn:aws:s3:::damages/*\"]}]}' > /tmp/policy.json;
mc admin policy add myminio getonly /tmp/policy.json;
mc admin policy set myminio getonly user=kikimor;
exit 0;
"
networks:
- app-network
volumes:
postgres_data:
minio_data:
networks:
app-network:
driver: bridge

@ -1,11 +1,84 @@
version: '3.8' version: '3.8'
services: services:
database:
image: postgres
environment:
- POSTGRES_DB=depot # Matches DB_NAME in production.env
- POSTGRES_USER=postgres # Matches DB_USER in production.env
- POSTGRES_PASSWORD=admin # Matches DB_PASSWORD in production.env
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d depot"] # Check specific database
interval: 5s
timeout: 5s
retries: 5
networks:
- app-network
web: web:
build: . build: .
ports: ports:
- "8000:8000" - "8000:8000"
env_file: env_file:
- development.env - production.env
depends_on:
database:
condition: service_healthy
networks:
- app-network
command: >
bash -c "python manage.py migrate &&
python manage.py runserver 0.0.0.0:8000"
minio:
image: minio/minio:latest
ports:
- "9000:9000" # API Port
- "9001:9001" # Console Port
environment:
MINIO_ROOT_USER: kikimor # Change this
MINIO_ROOT_PASSWORD: shushunka1
MINIO_SERVER_URL: http://localhost:9000
MINIO_ADDRESS: "0.0.0.0:9000"
MINIO_BROWSER_REDIRECT_URL: "http://localhost:9001" # Console URL
volumes: volumes:
- .:/DepoT - minio_data:/data
command: server --console-address ":9001" /data
healthcheck:
test: ["CMD", "mc", "ready", "local"]
interval: 30s
timeout: 20s
retries: 3
networks:
- app-network
# Optional: Create buckets and users on startup
createbuckets:
image: minio/mc
depends_on:
- minio
entrypoint: >
/bin/sh -c "
sleep 10;
mc alias set myminio http://minio:9000 kikimor shushunka1;
mc mb myminio/damages;
mc anonymous set download myminio/damages;
mc policy set public myminio/damages;
echo '{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:GetObject\"],\"Resource\":[\"arn:aws:s3:::damages/*\"]}]}' > /tmp/policy.json;
mc admin policy add myminio getonly /tmp/policy.json;
mc admin policy set myminio getonly user=kikimor;
exit 0;
"
networks:
- app-network
volumes:
postgres_data:
minio_data:
networks:
app-network:
driver: bridge

@ -4,4 +4,4 @@ WORKDIR /DepoT
COPY requirements.txt . COPY requirements.txt .
RUN pip install -r requirements.txt RUN pip install -r requirements.txt
COPY . . COPY . .
CMD python manage.py migrate && python manage.py runserver 0.0.0.0:8000 CMD ["bash", "-c", "python manage.py migrate && python manage.py runserver 0.0.0.0:8000"]

@ -1,12 +1,13 @@
from django.urls import path, include from django.urls import path, include
from preinfo.views.client_views import (ClientPreinfoView, check_preinfo, PreinfoSearchView, ClientPreinfoCreateView, ClientPreinfoUpdateView) from preinfo.views.client_views import (ClientPreinfoView, check_preinfo, PreinfoSearchView, ClientPreinfoCreateView,
ClientPreinfoUpdateView, ClientPreinfoDeleteView, )
from preinfo.views.employee_views import EmployeePreinfoView from preinfo.views.employee_views import EmployeePreinfoView
urlpatterns = [ urlpatterns = [
path('client/', include([ path('client/', include([
path('', ClientPreinfoView.as_view(), name='client_preinfo'), path('', ClientPreinfoView.as_view(), name='client_preinfo'),
path('create/', ClientPreinfoCreateView.as_view(), name='client_preinfo_create'), path('create/', ClientPreinfoCreateView.as_view(), name='client_preinfo_create'),
path('<int:pk>/update/', ClientPreinfoUpdateView.as_view(), name='client_preinfo_update'), path('<int:pk>/delete/', ClientPreinfoDeleteView.as_view(), name='client_preinfo_delete'),
])), ])),
path('check-preinfo/', check_preinfo, name='check_preinfo'), path('check-preinfo/', check_preinfo, name='check_preinfo'),
path('preinfo-search/', PreinfoSearchView.as_view(), name='preinfo_search'), path('preinfo-search/', PreinfoSearchView.as_view(), name='preinfo_search'),

@ -1,9 +1,13 @@
from datetime import timezone
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.http import JsonResponse from django.http import JsonResponse
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.timezone import now
from django.views import View from django.views import View
from django.views.generic import CreateView, ListView, UpdateView from django.views.generic import CreateView, ListView, UpdateView
from rest_framework.generics import get_object_or_404
from DepoT.mixins.LineFiltweFormMixin import LineFilterFormMixin from DepoT.mixins.LineFiltweFormMixin import LineFilterFormMixin
from common.utils.utils import filter_queryset_by_user, get_preinfo_by_number, send_test_email from common.utils.utils import filter_queryset_by_user, get_preinfo_by_number, send_test_email
@ -98,3 +102,20 @@ class PreinfoSearchView(View):
return render(request, self.template_name, {'error': 'Not found'}) return render(request, self.template_name, {'error': 'Not found'})
class ClientPreinfoDeleteView(LoginRequiredMixin, UserPassesTestMixin, View):
success_url = reverse_lazy('client_preinfo')
def test_func(self):
return self.request.user.has_company_perm('can_manage_preinfo') or self.request.user.user_type == 'CA'
def post(self, request, pk, *args, **kwargs):
target_preinfo = get_object_or_404(Preinfo, pk=pk)
if target_preinfo.received:
return JsonResponse({'success': False, 'error': 'Cannot delete completed preinfo.'})
if target_preinfo.deleted:
return JsonResponse({'success': False, 'error': 'Preinfo already deleted.'})
target_preinfo.deleted = True
target_preinfo.deleted_on = now()
target_preinfo.deleted_by = request.user
target_preinfo.save()
return JsonResponse({'success': True, 'is_active': target_preinfo.active})

Binary file not shown.

@ -0,0 +1,23 @@
document.addEventListener('DOMContentLoaded', function() {
const table = document.getElementById('objectTable');
if (!table) return;
table.addEventListener('click', function(e) {
const row = e.target.closest('.selectable-row');
if (!row) return;
// Remove selection from all rows
table.querySelectorAll('.selected-row').forEach(selectedRow => {
selectedRow.classList.remove('selected-row');
});
// Add selection to clicked row
row.classList.add('selected-row');
// Get container ID and redirect to show its details
const containerId = row.dataset.id;
const currentUrl = new URL(window.location.href);
currentUrl.searchParams.set('selected', containerId);
window.location.href = currentUrl.toString();
});
});

@ -4,6 +4,8 @@ document.addEventListener('DOMContentLoaded', function() {
const selectionMode = table.dataset.selectionMode || 'single'; const selectionMode = table.dataset.selectionMode || 'single';
const toggleSelectAllBtn = document.getElementById('toggleSelectAllBtn'); const toggleSelectAllBtn = document.getElementById('toggleSelectAllBtn');
const editBtn = document.getElementById('editBtn');
const deleteBtn = document.getElementById('deleteBtn');
if (table.dataset.selectionMode === 'multiple') { if (table.dataset.selectionMode === 'multiple') {
toggleSelectAllBtn.style.display = 'inline-block'; toggleSelectAllBtn.style.display = 'inline-block';
@ -26,19 +28,10 @@ document.addEventListener('DOMContentLoaded', function() {
row.classList.toggle('selected-row'); row.classList.toggle('selected-row');
checkbox.checked = !checkbox.checked; checkbox.checked = !checkbox.checked;
const selectedRows = table.querySelectorAll('.selected-row'); const selectedRows = table.querySelectorAll('.selected-row');
document.querySelectorAll('[data-requires-selection]').forEach(button => { document.querySelectorAll('[data-requires-selection]').forEach(button => {
button.disabled = selectedRows.length === 0; button.disabled = selectedRows.length === 0;
}); });
const objectId = row.dataset.id;
const editBtn = document.getElementById('editBtn');
const deleteBtn = document.getElementById('deleteBtn');
if (editBtn) {
editBtn.removeAttribute('disabled');
editBtn.href = editBtn.dataset.url?.replace('0', objectId);
}
if (deleteBtn) {
deleteBtn.removeAttribute('disabled');
}
}); });
let allSelected = false; let allSelected = false;
@ -56,4 +49,81 @@ document.addEventListener('DOMContentLoaded', function() {
this.textContent = allSelected ? 'Unselect All' : 'Select All'; this.textContent = allSelected ? 'Unselect All' : 'Select All';
}); });
// Edit button click handler
if (editBtn) {
editBtn.addEventListener('click', function() {
const selectedRow = table.querySelector('.selected-row');
if (selectedRow) {
const objectId = selectedRow.dataset.id;
const editUrl = editBtn.dataset.url.replace('0', objectId);
window.location.href = editUrl;
}
});
}
// Delete modal logic
const deleteModal = document.getElementById('deleteModal');
const cancelDelete = document.getElementById('cancelDelete');
const confirmDelete = document.getElementById('confirmDelete');
let selectedId = null;
if (deleteBtn) {
deleteBtn.addEventListener('click', function() {
const selectedRow = table.querySelector('.selected-row');
if (selectedRow) {
selectedId = selectedRow.dataset.id;
deleteModal.style.display = 'block';
}
});
}
cancelDelete.addEventListener('click', function() {
deleteModal.style.display = 'none';
});
confirmDelete.addEventListener('click', function() {
if (selectedId) {
const deleteUrl = deleteBtn.dataset.url.replace('0', selectedId);
fetch(deleteUrl, {
method: 'POST',
headers: {
'X-CSRFToken': getCookie('csrftoken')
},
})
.then(response => {
if (response.ok) {
window.location.reload(); // This reloads the page after success
} else {
alert('Error deleting item');
}
})
.catch(error => {
console.error('Error:', error);
alert('Error deleting item');
});
}
deleteModal.style.display = 'none';
});
window.addEventListener('click', function(event) {
if (event.target === deleteModal) {
deleteModal.style.display = 'none';
}
});
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
}); });

@ -0,0 +1,97 @@
.search-card {
background: #EDDECB;
border: 1px solid #E1C6A8;
border-radius: 8px;
margin-bottom: 1.5rem;
}
.search-body {
padding: 1rem;
}
.search-group {
display: flex;
gap: 0.5rem;
}
.search-button {
flex-shrink: 0;
}
/*.btn-primary {*/
/* background: #EDDECB;*/
/* color: white;*/
/* border: none;*/
/* padding: 0.5rem 1rem;*/
/* border-radius: 8px;*/
/* cursor: pointer;*/
/*}*/
/*.btn-primary:hover {*/
/* background: #0056b3;*/
/*}*/
.details-card {
background: #EDDECB;
border: 1px solid #E1C6A8;
border-radius: 8px;
margin-bottom: 1.5rem;
}
.card-header {
background: #E1C6A8;
padding: 1rem;
border-bottom: 1px solid #E1C6A8;
}
.card-header h5 {
margin: 0;
font-size: 1.1rem;
}
.details-body {
padding: 1rem;
}
.details-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
}
.details-column {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.detail-row {
display: flex;
align-items: center;
gap: 1rem;
}
.detail-label {
flex: 0 0 45%;
text-align: right;
font-weight: bold;
color: #555;
}
.detail-value {
flex: 1;
}
/* Form styling */
input[type="text"] {
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 4px;
width: 100%;
}
input[type="text"]:focus {
outline: none;
border-color: #007bff;
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
}

@ -84,6 +84,15 @@ button.btn-secondary {
border: 1px solid #a57d52; border: 1px solid #a57d52;
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
/*background-color: #702222;*/
/*padding: 6px 20px; !* Reduced from 10px to 6px *!*/
/*border: 1px solid #ae3975;*/
/*border-radius: 4px;*/
/*cursor: pointer;*/
/*height: 32px; !* Add explicit height *!*/
/*line-height: 1.2; !* Add line height for better text alignment *!*/
} }
button[type="submit"]:hover, button[type="submit"]:hover,

@ -1,46 +1,5 @@
{% load static %} {% extends "common/base.html" %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Line Operator Dashboard | Container Depot</title>
<link rel="stylesheet" href="{% static 'styles/tables.css' %}">
<link rel="stylesheet" href="{% static 'styles/forms.css' %}">
<link rel="stylesheet" href="{% static 'styles/sidebar.css' %}">
<link rel="stylesheet" href="{% static 'styles/base.css' %}">
<link rel="stylesheet" href="{% static 'styles/dashboard-content.css' %}">
</head>
{% block aside %}
<body>
<aside class="sidebar">
{% include 'client-sidebar.html' %} {% include 'client-sidebar.html' %}
</aside> {% endblock aside %}
<main class="content-area">
<div class="content">
{% block content %}
{% endblock content %}
</div>
</main>
{% block custom_styles %}
{% endblock custom_styles %}
{% block extra_js %}
<script>
const validateContainerUrl = "{% url 'validate_container' 'placeholder' %}".replace('placeholder/', '');
</script>
<script src="{% static 'js/container_validation.js' %}"></script>
{% endblock extra_js %}
{% block crud_js %}
{% endblock crud_js %}
</body>
</html>

@ -31,7 +31,12 @@
{% endblock %} {% endblock %}
{% block buttons %} {% block buttons %}
<a href="{% url 'client_booking_create' %}" class="btn btn-primary">Create Preinfo</a> {# <a href="{% url 'client_booking_create' %}" class="btn btn-primary">Create booking</a>#}
<a href="#" id="editBtn" data-url="{% url 'client_booking_update' pk=0 %}" class="btn btn-primary" disabled>Edit Preinfo</a> {# <a href="#" id="editBtn" data-url="{% url 'client_booking_update' pk=0 %}" class="btn btn-primary" disabled>Edit Preinfo</a>#}
<button id="deleteButton" class="btn btn-danger">Delete Preinfo</button> {# <button id="deleteButton" class="btn btn-danger">Delete Preinfo</button>#}
{# #}
<button class="btn btn-primary" type="button" onclick="window.location.href='{% url 'client_booking_create' %}'">Create booking</button>
<button class="btn btn-primary" type="button" id="editBtn" data-url="{% url 'client_booking_update' pk=0 %}" data-requires-selection disabled>Edit booking</button>
<button class="btn btn-primary" type="button" id="deleteBtn" data-url="{% url 'client_booking_active' pk=0 %}" data-requires-selection disabled>Delete booking</button>
{% endblock buttons %} {% endblock buttons %}

@ -15,15 +15,12 @@
{% endblock %} {% endblock %}
{% block buttons %} {% block buttons %}
<a href="{% url 'client_line_create' %}" class="btn btn-primary">Create line</a> {# <a href="{% url 'client_line_create' %}" class="btn btn-primary">Create line</a>#}
<a href="#" id="editBtn" data-url="{% url 'client_line_update' pk=0 %}" class="btn btn-primary" disabled>Edit Preinfo</a> {# <a href="#" id="editBtn" data-url="{% url 'client_line_update' pk=0 %}" class="btn btn-primary" disabled>Edit Preinfo</a>#}
<button id="deleteButton" class="btn btn-danger">Delete Preinfo</button> {# <button id="deleteButton" class="btn btn-danger">Delete Preinfo</button>#}
{% endblock buttons %}
{% block create_modal_header %} <button class="btn btn-primary" type="button" onclick="window.location.href='{% url 'client_line_create' %}'">Create line</button>
<h2>Create line</h2> <button class="btn btn-primary" type="button" id="editBtn" data-url="{% url 'client_line_update' pk=0 %}" data-requires-selection disabled>Edit line</button>
{% endblock %} <button class="btn btn-primary" type="button" id="deleteBtn" data-url="{% url 'client_line_active' pk=0 %}" data-requires-selection disabled>Delete line</button>
{% endblock buttons %}
{% block modal_header %}
<h2>edit line</h2>
{% endblock modal_header %}

@ -1,6 +1,10 @@
{% extends 'list-crud.html' %} {% extends 'list-crud.html' %}
{% load static %} {% load static %}
{% block filter %}
{% endblock filter %}
{% block table_header %} {% block table_header %}
<th style="display: none;">Select</th> <th style="display: none;">Select</th>
<th>Preinfo №</th> <th>Preinfo №</th>
@ -23,9 +27,12 @@
{% endblock %} {% endblock %}
{% block buttons %} {% block buttons %}
<a href="{% url 'client_preinfo_create' %}" class="btn btn-primary">Create Preinfo</a> {# <a href="{% url 'client_preinfo_create' %}" class="btn btn-primary">Create Preinfo</a>#}
<a href="#" id="editBtn" data-url="{% url 'client_preinfo_update' pk=0 %}" class="btn btn-primary" disabled>Edit Preinfo</a> {# <a href="#" id="editBtn" data-url="{% url 'client_preinfo_update' pk=0 %}" class="btn btn-primary" disabled>Edit Preinfo</a> #}
<button id="deleteButton" class="btn btn-danger">Delete Preinfo</button> {# <button id="deleteButton" class="btn btn-danger">Delete Preinfo</button>#}
<button class="btn btn-primary" type="button" onclick="window.location.href='{% url 'client_preinfo_create' %}'">Create Preinfo</button>
<button class="btn btn-primary" type="button" id="deleteBtn" data-url="{% url 'client_preinfo_delete' pk=0 %}" data-requires-selection disabled>Delete Preinfo</button>
{% endblock buttons %} {% endblock buttons %}
{% block create_modal_header %} {% block create_modal_header %}

@ -0,0 +1,48 @@
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Line Operator Dashboard | Container Depot</title>
<link rel="stylesheet" href="{% static 'styles/tables.css' %}">
<link rel="stylesheet" href="{% static 'styles/forms.css' %}">
<link rel="stylesheet" href="{% static 'styles/sidebar.css' %}">
<link rel="stylesheet" href="{% static 'styles/base.css' %}">
<link rel="stylesheet" href="{% static 'styles/dashboard-content.css' %}">
{% block extra_styles %}
{% endblock extra_styles %}
</head>
<body>
<aside class="sidebar">
{% block aside %}
{% endblock aside %}
</aside>
<main class="content-area">
<div class="content">
{% block content_header %}
{% endblock content_header%}
{% block content %}
{% endblock content %}
{% block content_footer %}
{% endblock content_footer%}
</div>
</main>
{% block custom_styles %}
{% endblock custom_styles %}
{% block extra_js %}
<script>
const validateContainerUrl = "{% url 'validate_container' 'placeholder' %}".replace('placeholder/', '');
</script>
<script src="{% static 'js/container_validation.js' %}"></script>
{% endblock extra_js %}
</body>
</html>

@ -0,0 +1,303 @@
{% extends 'list-crud.html' %}
{% load static %}
{% block extra_styles %}
<link rel="stylesheet" href="{% static 'styles/details.css' %}">
<style>
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.9);
}
.modal-content {
position: relative;
margin: auto;
padding: 20px;
width: 90%;
max-width: 1200px;
height: 90vh;
display: flex;
flex-direction: column;
}
.close-modal {
color: #fff;
position: absolute;
right: 25px;
top: 10px;
font-size: 35px;
cursor: pointer;
}
.gallery-item iframe {
width: 100%;
height: 100%;
border: none;
background: white;
}
.gallery-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1rem;
padding: 1rem;
overflow-y: auto;
background: rgba(255, 255, 255, 0.1);
border-radius: 8px;
}
.gallery-item {
position: relative;
aspect-ratio: 1;
background: white;
border-radius: 4px;
overflow: hidden;
}
.gallery-item img {
width: 100%;
height: 100%;
object-fit: contain;
background: white;
}
.photo-list {
display: flex;
flex-direction: column;
gap: 1rem;
padding: 1rem;
background: rgba(255, 255, 255, 0.1);
border-radius: 8px;
}
.photo-link {
background: #E1C6A8;
padding: 1rem;
border-radius: 4px;
text-decoration: none;
color: #000;
display: flex;
align-items: center;
justify-content: space-between;
}
.photo-link:hover {
background: #d4b08c;
}
</style>
{% endblock %}
{% block content_header %}
<div class="search-card">
<div class="search-body">
<form method="post">
{% csrf_token %}
<div class="search-group">
{{ form.container_number }}
<div class="search-button">
<button type="submit" class="btn-primary">Search</button>
</div>
</div>
</form>
</div>
</div>
{% if show_results and container %}
<div class="details-card">
<div class="card-header">
<h5>Container Details</h5>
{% if container.photos.exists %}
<button type="button" class="btn-primary" onclick="openGallery()">
<i class="fas fa-images"></i> View Photos
</button>
{% endif %}
</div>
<div class="details-body">
<div class="details-grid">
<div class="details-column">
<div class="detail-row">
<div class="detail-label">Container Type:</div>
<div class="detail-value">{{ container.container_type }}</div>
</div>
<div class="detail-row">
<div class="detail-label">Line:</div>
<div class="detail-value">{{ container.line }}</div>
</div>
<div class="detail-row">
<div class="detail-label">Preinfo:</div>
<div class="detail-value">{{ container.preinfo }}</div>
</div>
<div class="detail-row">
<div class="detail-label">Received On:</div>
<div class="detail-value">{{ container.received_on|date:"Y-m-d H:i" }}</div>
</div>
<div class="detail-row">
<div class="detail-label">Receive Vehicle:</div>
<div class="detail-value">{{ container.receive_vehicle }}</div>
</div>
</div>
<div class="details-column">
<div class="detail-row">
<div class="detail-label">Swept:</div>
<div class="detail-value">{{ container.swept|yesno:"Yes,No" }}</div>
</div>
<div class="detail-row">
<div class="detail-label">Washed:</div>
<div class="detail-value">{{ container.washed|yesno:"Yes,No" }}</div>
</div>
<div class="detail-row">
<div class="detail-label">Booking:</div>
<div class="detail-value">
{% if container.booking %}
{{ container.booking.name }}
{% else %}
-
{% endif %}
</div>
</div>
<div class="detail-row">
<div class="detail-label">Expedited On:</div>
<div class="detail-value">{{ container.expedited_on|date:"Y-m-d H:i"|default:"-" }}</div>
</div>
<div class="detail-row">
<div class="detail-label">Expedition Vehicle:</div>
<div class="detail-value">{{ container.expedition_vehicle|default:"-" }}</div>
</div>
<div class="detail-row">
<div class="detail-label">Damages:</div>
<div class="detail-value">{{ container.damages|default:"-" }}</div>
</div>
<div class="detail-row">
<div class="detail-label">Heavy Damaged:</div>
<div class="detail-value">{{ container.heavy_damaged|yesno:"Yes,No" }}</div>
</div>
</div>
</div>
</div>
</div>
<div id="galleryModal" class="modal">
<div class="modal-content">
<span class="close-modal" onclick="closeGallery()">&times;</span>
<h3 style="color: white; margin-bottom: 1rem;">Photos for Container {{ container.number }}</h3>
<div class="gallery-grid">
</div>
</div>
</div>
{% endif %}
{% endblock content_header %}
{% block table_header %}
<th style="display: none;">Select</th>
<th>Company name</th>
<th>Company short name</th>
<th>Description</th>
{% endblock table_header %}
{% block table_data %}
<td>{{ object.number }}</td>
<td>{{ object.number }}</td>
<td>{{ object.number }}</td>
{% endblock %}
{% block buttons %}
{% endblock buttons %}
{% block crud_js %}
<script src="{% static 'js/container-details.js' %}"></script>
<script>
function loadPhotos(containerId) {
const photoGrid = document.querySelector('.gallery-grid');
photoGrid.innerHTML = '<div class="loading">Loading photos...</div>';
fetch(`/api/damages/${containerId}/`)
.then(response => response.json())
.then(data => {
photoGrid.innerHTML = '';
if (data.length === 0) {
photoGrid.innerHTML = '<div style="color: white;">No photos available</div>';
return;
}
data.forEach(photo => {
const photoDiv = document.createElement('div');
photoDiv.className = 'gallery-item';
const img = document.createElement('img');
img.src = photo.url; // Use the presigned URL directly
img.alt = `Container photo`;
img.onerror = function() {
console.error('Failed to load image:', img.src);
this.style.display = 'none';
photoDiv.innerHTML += '<div style="color: red;">Failed to load image</div>';
};
photoDiv.appendChild(img);
photoGrid.appendChild(photoDiv);
});
})
.catch(error => {
console.error('Error loading photos:', error);
photoGrid.innerHTML = '<div style="color: white;">Error loading photos</div>';
});
}
function openGallery() {
const container = {{ container.id|default:'null' }};
if (container) {
loadPhotos(container);
}
document.getElementById('galleryModal').style.display = 'block';
document.body.style.overflow = 'hidden';
}
function closeGallery() {
document.getElementById('galleryModal').style.display = 'none';
document.body.style.overflow = 'auto';
document.querySelector('.gallery-grid').innerHTML = '';
}
// Close modal when clicking outside
window.onclick = function(event) {
const modal = document.getElementById('galleryModal');
if (event.target === modal) {
closeGallery();
}
}
// Close modal on escape key
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape') {
closeGallery();
}
});
// Add loading indicator styles
const style = document.createElement('style');
style.textContent = `
.loading {
color: white;
text-align: center;
padding: 2rem;
}
`;
document.head.appendChild(style);
</script>
{% endblock %}

@ -1,38 +1,5 @@
{% load static %} {% extends "common/base.html" %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Depot Employee Dashboard | Container Depot</title>
<link rel="stylesheet" href="{% static 'styles/tables.css' %}">
<link rel="stylesheet" href="{% static 'styles/forms.css' %}">
<link rel="stylesheet" href="{% static 'styles/sidebar.css' %}">
<link rel="stylesheet" href="{% static 'styles/base.css' %}">
<link rel="stylesheet" href="{% static 'styles/dashboard-content.css' %}">
</head>
{% block aside %}
<body>
<aside class="sidebar">
{% include 'employee-sidebar.html' %} {% include 'employee-sidebar.html' %}
</aside> {% endblock aside %}
<main class="content-area">
<div class="content">
{% block content %}
{% endblock content %}
</div>
</main>
{% block extra_js %}
<script>
const validateContainerUrl = "{% url 'validate_container' 'placeholder' %}".replace('placeholder/', '');
</script>
<script src="{% static 'js/container_validation.js' %}"></script>
{% endblock %}
</body>
</html>

@ -6,6 +6,7 @@
</div> </div>
{% url 'employee_dashboard' as dashboard_url %} {% url 'employee_dashboard' as dashboard_url %}
{% url 'employee_containers' as employee_containers_url %} {% url 'employee_containers' as employee_containers_url %}
{% url 'container_details' as container_details_url %}
{% url 'employee_bookings' as employee_bookings_url %} {% url 'employee_bookings' as employee_bookings_url %}
{% url 'employee_preinfo' as employee_preinfo_url %} {% url 'employee_preinfo' as employee_preinfo_url %}
{% url 'register' as register_url %} {% url 'register' as register_url %}
@ -28,6 +29,12 @@
</svg> </svg>
Containers Containers
</a> </a>
<a href="{{ container_details_url }}" class="nav-item {% if request.path == container_details_url %}active{% endif %}" id="preinfoNav">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
</svg>
Container details
</a>
<a href="{{ employee_preinfo_url }}" class="nav-item {% if request.path == employee_preinfo_url %}active{% endif %}" id="preinfoNav"> <a href="{{ employee_preinfo_url }}" class="nav-item {% if request.path == employee_preinfo_url %}active{% endif %}" id="preinfoNav">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />

@ -1,4 +1,5 @@
{% extends 'list-crud.html' %} {% extends 'list-crud.html' %}
{% load custom_filters %}
{% load static %} {% load static %}
{% block table_header %} {% block table_header %}
@ -13,6 +14,7 @@
<th>Vehicles left</th> <th>Vehicles left</th>
<th>Created on</th> <th>Created on</th>
<th>Created by</th> <th>Created by</th>
<th>Status</th>
{% endblock table_header %} {% endblock table_header %}
{% block table_data %} {% block table_data %}
@ -22,10 +24,11 @@
<td>{{ object.container_number }}</td> <td>{{ object.container_number }}</td>
<td>{{ object.container_count }}</td> <td>{{ object.container_count }}</td>
<td>{{ object.containers_left }}</td> <td>{{ object.containers_left }}</td>
<td>{{ object.vehicles }}</td> <td>{{ object.vehicles|distinct_vehicles }}</td>
<td>{{ object.vehicles_left }}</td> <td>{{ object.vehicles_left|distinct_vehicles }}</td>
<td>{{ object.created_on }}</td> <td>{{ object.created_on }}</td>
<td>{{ object.created_by.username }}</td> <td>{{ object.created_by.username }}</td>
<td>{{ object.status }}</td>
{% endblock %} {% endblock %}
{% block buttons %} {% block buttons %}
@ -34,10 +37,3 @@
{# <button id="deleteButton" class="btn btn-danger">Delete Preinfo</button>#} {# <button id="deleteButton" class="btn btn-danger">Delete Preinfo</button>#}
{% endblock buttons %} {% endblock buttons %}
{% block create_modal_header %}
<h2>Create Booking</h2>
{% endblock %}
{% block modal_header %}
<h2>Edit Booking</h2>
{% endblock modal_header %}

@ -15,9 +15,14 @@
{% endblock %} {% endblock %}
{% block buttons %} {% block buttons %}
<a href="{% url 'employee_company_create' %}" class="btn btn-primary">Create company</a> {# <a href="{% url 'employee_company_create' %}" class="btn btn-primary">Create company</a>#}
<a href="#" id="editBtn" data-url="{% url 'employee_company_update' pk=0 %}" class="btn btn-primary" disabled>Edit Preinfo</a> {# <a href="#" id="editBtn" data-url="{% url 'employee_company_update' pk=0 %}" class="btn btn-primary" disabled>Edit Preinfo</a>#}
<button id="deleteButton" class="btn btn-danger">Delete Preinfo</button> {# <button id="deleteButton" class="btn btn-danger">Delete company</button>#}
<button class="btn btn-primary" type="button" onclick="window.location.href='{% url 'employee_company_create' %}'">Create company</button>
<button class="btn btn-primary" type="button" id="editBtn" data-url="{% url 'employee_company_update' pk=0 %}" data-requires-selection disabled>Edit company</button>
<button class="btn btn-primary" type="button" id="deleteBtn" data-url="{% url 'employee_company_active' pk=0 %}" data-requires-selection disabled>Delete company</button>
{% endblock buttons %} {% endblock buttons %}
{% block create_modal_header %} {% block create_modal_header %}

@ -17,9 +17,14 @@
{% endblock %} {% endblock %}
{% block buttons %} {% block buttons %}
<a href="{% url 'employee_line_create' %}" class="btn btn-primary">Create line</a> {# <a href="{% url 'employee_line_create' %}" class="btn btn-primary">Create line</a>#}
<a href="#" id="editBtn" data-url="{% url 'employee_line_update' pk=0 %}" class="btn btn-primary" disabled>Edit Preinfo</a> {# <a href="#" id="editBtn" data-url="{% url 'employee_line_update' pk=0 %}" class="btn btn-primary" disabled>Edit Preinfo</a>#}
<button id="deleteButton" class="btn btn-danger">Delete Preinfo</button> {# <button id="deleteButton" class="btn btn-danger">Delete Preinfo</button>#}
<button class="btn btn-primary" type="button" onclick="window.location.href='{% url 'employee_line_create' %}'">Create line</button>
<button class="btn btn-primary" type="button" id="editBtn" data-url="{% url 'employee_line_update' pk=0 %}" data-requires-selection disabled>Edit line</button>
<button class="btn btn-primary" type="button" id="deleteBtn" data-url="{% url 'employee_line_active' pk=0 %}" data-requires-selection disabled>Delete line</button>
{% endblock buttons %} {% endblock buttons %}
{% block create_modal_header %} {% block create_modal_header %}

@ -9,6 +9,7 @@
<th>Line</th> <th>Line</th>
<th>Created on</th> <th>Created on</th>
<th>Created by</th> <th>Created by</th>
<th>Status</th>
{% endblock table_header %} {% endblock table_header %}
{% block table_data %} {% block table_data %}
@ -18,8 +19,6 @@
<td>{{ object.line.short_name }}</td> <td>{{ object.line.short_name }}</td>
<td>{{ object.created_on }}</td> <td>{{ object.created_on }}</td>
<td>{{ object.created_by.username }}</td> <td>{{ object.created_by.username }}</td>
<td>{{ object.received|yesno:"Received,Pending" }}</td>
{% endblock %} {% endblock %}
{% block modal_header %}
<h2>Edit Preinfo</h2>
{% endblock modal_header %}

@ -128,27 +128,22 @@ header {
&copy; 2025 Kikimor EOOD | K-DepoT - All rights reserved. &copy; 2025 Kikimor EOOD | K-DepoT - All rights reserved.
</footer> </footer>
<script>
<script>
function initMap() { function initMap() {
const map = new google.maps.Map(document.getElementById("map"), { const depot = { lat: 43.2121, lng: 27.9204 };
center: { lat: 43.2121, lng: 27.9204 }, const map = new google.maps.Map(document.getElementById('map'), {
zoom: 15 zoom: 15,
center: depot,
}); });
const marker = new google.maps.Marker({
new google.maps.Marker({ position: depot,
position: { lat: 43.2121, lng: 27.9204 },
map: map, map: map,
title: "K-DepoT - Kikimor EOOD" title: 'K-DepoT'
}); });
} }
</script> </script>
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyBboGDgiCrc9yp2uSLmVcfVVIVK-kOQqc4&callback=initMap" async defer></script>
<script
src="https://maps.googleapis.com/maps/api/js?key=AIzaSyBboGDgiCrc9yp2uSLmVcfVVIVK-kOQqc4&callback=initMap"
async
defer>
</script>
</body> </body>
</html> </html>

@ -83,6 +83,51 @@
<script src="{% static 'js/crud-list.js' %}"></script> <script src="{% static 'js/crud-list.js' %}"></script>
{% endblock crud_js %} {% endblock crud_js %}
<div id="deleteModal" class="modal" style="display: none;">
<div class="modal-content">
<h3>Confirm Delete</h3>
<p>Are you sure you want to delete this item?</p>
<div class="modal-buttons">
<button class="btn btn-secondary" id="cancelDelete">Cancel</button>
<button class="btn btn-danger" id="confirmDelete">Delete</button>
</div>
</div>
</div>
{% block modal_styles %}
<style>
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
}
.modal-content {
position: relative;
background-color: #EDDECB;
margin: 15% auto;
padding: 20px;
border: 1px solid #a57d52;
border-radius: 8px;
width: 80%;
max-width: 500px;
}
.modal-buttons {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 20px;
}
</style>
{% endblock modal_styles %}
{% endblock content %} {% endblock content %}

@ -24,9 +24,12 @@
{% endblock %} {% endblock %}
{% block buttons %} {% block buttons %}
<a href="{% url 'user_register' %}" class="btn btn-primary" type="button">Create user</a> {# <a href="{% url 'user_register' %}" class="btn btn-primary" type="button">Create user</a>#}
<a href="#" id="editBtn" data-url="{% url 'user_update' pk=0 %}" class="btn btn-primary" type="button" disabled>Edit user</a> {# <a href="#" id="editBtn" data-url="{% url 'user_update' pk=0 %}" class="btn btn-primary" type="button" disabled>Edit user</a>#}
<button id="deleteButton" class="btn btn-danger">Delete user</button> {# <button id="deleteButton" class="btn btn-danger">Delete user</button>#}
<button class="btn btn-primary" type="button" onclick="window.location.href='{% url 'user_register' %}'">Create user</button>
<button class="btn btn-primary" type="button" id="editBtn" data-url="{% url 'user_update' pk=0 %}" data-requires-selection disabled>Edit user</button>
<button class="btn btn-primary" type="button" id="deleteBtn" data-url="{% url 'user_active' pk=0 %}" data-requires-selection disabled>Delete user</button>
{% endblock buttons %} {% endblock buttons %}
{% block create_modal_header %} {% block create_modal_header %}

Loading…
Cancel
Save