Compare commits

...

No commits in common. 'master' and 'main' have entirely different histories.
master ... main

41
.env

@ -1,41 +0,0 @@
DB_USER="postgres"
DB_PASSWORD="admin"
DB_HOST="localhost"
DB_PORT=5432
DB_NAME="depot"
TEST_DB_NAME="depot_test"
SECRET_KEY='7b112605-9074-4195-98e9-7add8d57942d'
CONFIG_ENV='config.DevelopmentConfig'
JWT_EXPIRATION_DELTA=2
EPAY_SECURITY_KEY='VX6FIDVFI5MSP39UYMDWI47DX37T7H8ZT8AAC4UYS1IGCT9YFMJWJ98AH4YHJ3VY'
EPAY_CLIENT_ID='D056898459'
INVOICE_EXPIRE_PERIOD=3
OWNCLOUD_URL='https://cloud.kikimor.com'
OWNCLOUD_USER='kikimor'
OWNCLOUD_PASSWORD='ShuShunka1!'
OWNCLOUD_DAMAGES_FOLDER='damages/'
EMAIL_HOST="smtp.gmail.com"
EMAIL_PORT=587
EMAIL_HOST_USER="kikimor.eood@gmail.com"
EMAIL_HOST_PASSWORD="mjra fwsq iyaq yzai"
EMAIL_USE_TLS=True
ADMIN_USER_EMAIL="kikimor@gmail.com"
ADMIN_USER_NAME="kikimor"
ADMIN_USER_PASSWORD="shushunka1"
POSTGRES_USER=$DB_USER
POSTGRES_PASSWORD=$DB_PASSWORD
POSTGRES_DB=$DB_NAME
MINIO_ENDPOINT="localhost:9000"
MINIO_ACCESS_KEY="kikimor"
MINIO_SECRET_KEY="shushunka1"
MINIO_BUCKET_NAME="damages"
AWS_S3_CUSTOM_DOMAIN='localhost:9000' # For browser acce
AWS_S3_URL_PROTOCOL='http'
MINIO_SERVER_URL="http://localhost:9000"

162
.gitignore vendored

@ -0,0 +1,162 @@
# ---> Python
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="FacetManager">
<facet type="django" name="Django">
<configuration>
<option name="rootFolder" value="$MODULE_DIR$" />
<option name="settingsModule" value="DepoT/settings.py" />
<option name="manageScript" value="$MODULE_DIR$/manage.py" />
<option name="environment" value="&lt;map/&gt;" />
<option name="doNotUseTestRunner" value="false" />
<option name="trackFilePattern" value="migrations" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.13 (DepoT)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="TemplatesService">
<option name="TEMPLATE_CONFIGURATION" value="Django" />
<option name="TEMPLATE_FOLDERS">
<list>
<option value="$MODULE_DIR$/../DepoT\templates" />
</list>
</option>
</component>
</module>

@ -1,12 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyUnresolvedReferencesInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredIdentifiers">
<list>
<option value="object.*" />
</list>
</option>
</inspection_tool>
</profile>
</component>

@ -1,6 +0,0 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.13 (DepoT)" />
</component>
</project>

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/DepoT.iml" filepath="$PROJECT_DIR$/.idea/DepoT.iml" />
</modules>
</component>
</project>

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

@ -1,314 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="7410a44d-51b9-408b-85ad-4fa46776b372" name="Changes" comment="commit unversioned files ;)">
<change afterPath="$PROJECT_DIR$/accounts/migrations/0001_initial.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/accounts/migrations/0002_clientpermission_codename_clientpermission_name.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/accounts/migrations/0003_remove_depotuser_is_company_admin_and_more.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/accounts/migrations/0004_employeepermission_depotuser_employee_permissions.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/accounts/migrations/0005_alter_depotuser_managers_alter_depotuser_user_type.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/accounts/migrations/0006_alter_clientpermission_options.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/accounts/migrations/0007_auto_20250725_1920.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/booking/migrations/0001_initial.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/booking/migrations/0002_rename_bookingmodel_booking.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/booking/migrations/0003_booking_container_expedited_count.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/booking/migrations/0004_booking_status.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/booking/migrations/0005_alter_booking_vehicles_left.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/booking/views/common.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/common/migrations/0001_initial.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/common/migrations/0002_alter_companymodel_short_name_and_more.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/common/migrations/0003_auto_20250725_1920.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/common/migrations/0005_companymodel_active_containerkindmodel_active_and_more.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/containers/migrations/0001_initial.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/containers/migrations/0002_rename_receive_vehicles_container_receive_vehicle.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/containers/migrations/0003_alter_container_booking.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/containers/migrations/0004_rename_line_id_container_line.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/containers/migrations/0005_alter_container_expedited_by_and_more.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/containers/migrations/0006_containerphotos.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/containers/migrations/0007_container_preinfo.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/damages_api/__init__.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/payments/migrations/0001_initial.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/payments/migrations/0002_additionalfees_containertariffperiod.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/payments/migrations/0003_auto_20250725_1920.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/payments/migrations/__init__.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/preinfo/migrations/0001_initial.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/preinfo/migrations/0002_alter_preinfomodel_deleted_by_and_more.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/preinfo/migrations/0003_rename_preinfomodel_preinfo.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="ChangesViewManager" show_ignored="true" />
<component name="DjangoConsoleOptions" custom-start-script="import sys; print('Python %s on %s' % (sys.version, sys.platform))&#10;import django; print('Django %s' % django.get_version())&#10;sys.path.extend([WORKING_DIR_AND_PYTHON_PATHS])&#10;if 'setup' in dir(django): django.setup()&#10;import django_manage_shell; django_manage_shell.run(PROJECT_ROOT)">
<option name="myCustomStartScript" value="import sys; print('Python %s on %s' % (sys.version, sys.platform))&#10;import django; print('Django %s' % django.get_version())&#10;sys.path.extend([WORKING_DIR_AND_PYTHON_PATHS])&#10;if 'setup' in dir(django): django.setup()&#10;import django_manage_shell; django_manage_shell.run(PROJECT_ROOT)" />
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="CSS File" />
<option value="JavaScript File" />
<option value="HTML File" />
<option value="Python Script" />
</list>
</option>
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="ProjectColorInfo">{
&quot;associatedIndex&quot;: 6
}</component>
<component name="ProjectId" id="2yxmkOyUBM7hzsgEZWENDFkS6jw" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;DefaultHtmlFileTemplate&quot;: &quot;HTML File&quot;,
&quot;Django Server.DepoT.executor&quot;: &quot;Debug&quot;,
&quot;RunOnceActivity.OpenDjangoStructureViewOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
&quot;RunOnceActivity.pycharm.django.structure.promotion.once.per.project&quot;: &quot;true&quot;,
&quot;django.template.preview.state&quot;: &quot;SHOW_EDITOR_AND_PREVIEW&quot;,
&quot;git-widget-placeholder&quot;: &quot;master&quot;,
&quot;ignore.virus.scanning.warn.message&quot;: &quot;true&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;node.js.detected.package.eslint&quot;: &quot;true&quot;,
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
},
&quot;keyToStringList&quot;: {
&quot;DatabaseDriversLRU&quot;: [
&quot;postgresql&quot;
]
}
}</component>
<component name="RecentsManager">
<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\client" />
<recent name="C:\dev_projects\python\Django\DepoT\templates" />
<recent name="C:\dev_projects\python\Django\DepoT\templates\registration" />
</key>
<key name="MoveFile.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\client" />
<recent name="C:\dev_projects\python\Django\DepoT\common\views" />
<recent name="C:\dev_projects\python\Django\DepoT\booking\views" />
</key>
</component>
<component name="RunManager">
<configuration name="DepoT" type="Python.DjangoServer" factoryName="Django server">
<module name="DepoT" />
<option name="ENV_FILES" value="" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
<env name="DJANGO_SETTINGS_MODULE" value="DepoT.settings" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="IS_MODULE_SDK" value="false" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<option name="launchJavascriptDebuger" value="false" />
<option name="port" value="8000" />
<option name="host" value="" />
<option name="additionalOptions" value="" />
<option name="browserUrl" value="" />
<option name="runTestServer" value="false" />
<option name="runNoReload" value="false" />
<option name="useCustomRunCommand" value="false" />
<option name="customRunCommand" value="" />
<method v="2" />
</configuration>
</component>
<component name="SharedIndexes">
<attachedChunks>
<set>
<option value="bundled-js-predefined-d6986cc7102b-09060db00ec0-JavaScript-PY-251.26927.74" />
<option value="bundled-python-sdk-657d8234b839-64d779b69b7a-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-251.26927.74" />
</set>
</attachedChunks>
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="7410a44d-51b9-408b-85ad-4fa46776b372" name="Changes" comment="" />
<created>1750784740296</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1750784740296</updated>
<workItem from="1750784741367" duration="240087000" />
<workItem from="1752078951079" duration="106000" />
<workItem from="1752079161329" duration="189445000" />
<workItem from="1753174764499" duration="271000" />
<workItem from="1753175068058" duration="4635000" />
<workItem from="1753179863298" duration="8357000" />
<workItem from="1753197869497" duration="98156000" />
<workItem from="1753637487803" duration="47474000" />
</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.">
<option name="closed" value="true" />
<created>1750855057151</created>
<option name="number" value="00001" />
<option name="presentableId" value="LOCAL-00001" />
<option name="project" value="LOCAL" />
<updated>1750855057151</updated>
</task>
<task id="LOCAL-00002" 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" />
<created>1750855273832</created>
<option name="number" value="00002" />
<option name="presentableId" value="LOCAL-00002" />
<option name="project" value="LOCAL" />
<updated>1750855273832</updated>
</task>
<task id="LOCAL-00003" 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" />
<created>1750942491703</created>
<option name="number" value="00003" />
<option name="presentableId" value="LOCAL-00003" />
<option name="project" value="LOCAL" />
<updated>1750942491703</updated>
</task>
<task id="LOCAL-00004" 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" />
<created>1750942543055</created>
<option name="number" value="00004" />
<option name="presentableId" value="LOCAL-00004" />
<option name="project" value="LOCAL" />
<updated>1750942543055</updated>
</task>
<task id="LOCAL-00005" 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" />
<created>1750942560128</created>
<option name="number" value="00005" />
<option name="presentableId" value="LOCAL-00005" />
<option name="project" value="LOCAL" />
<updated>1750942560128</updated>
</task>
<task id="LOCAL-00006" 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" />
<created>1751295996186</created>
<option name="number" value="00006" />
<option name="presentableId" value="LOCAL-00006" />
<option name="project" value="LOCAL" />
<updated>1751295996186</updated>
</task>
<task id="LOCAL-00007" 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" />
<created>1751359397230</created>
<option name="number" value="00007" />
<option name="presentableId" value="LOCAL-00007" />
<option name="project" value="LOCAL" />
<updated>1751359397230</updated>
</task>
<task id="LOCAL-00008" 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" />
<created>1751443902965</created>
<option name="number" value="00008" />
<option name="presentableId" value="LOCAL-00008" />
<option name="project" value="LOCAL" />
<updated>1751443902965</updated>
</task>
<task id="LOCAL-00009" 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" />
<created>1751557411675</created>
<option name="number" value="00009" />
<option name="presentableId" value="LOCAL-00009" />
<option name="project" value="LOCAL" />
<updated>1751557411675</updated>
</task>
<task id="LOCAL-00010" summary="commit unversioned files ;)">
<option name="closed" value="true" />
<created>1751618076902</created>
<option name="number" value="00010" />
<option name="presentableId" value="LOCAL-00010" />
<option name="project" value="LOCAL" />
<updated>1751618076902</updated>
</task>
<task id="LOCAL-00011" summary="commit unversioned files ;)">
<option name="closed" value="true" />
<created>1751877829547</created>
<option name="number" value="00011" />
<option name="presentableId" value="LOCAL-00011" />
<option name="project" value="LOCAL" />
<updated>1751877829547</updated>
</task>
<option name="localTasksCounter" value="12" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
<component name="Vcs.Log.Tabs.Properties">
<option name="TAB_STATES">
<map>
<entry key="MAIN">
<value>
<State />
</value>
</entry>
</map>
</option>
</component>
<component name="VcsManagerConfiguration">
<MESSAGE value="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." />
<MESSAGE value="commit unversioned files ;)" />
<option name="LAST_COMMIT_MESSAGE" value="commit unversioned files ;)" />
</component>
<component name="XDebuggerManager">
<breakpoint-manager>
<breakpoints>
<line-breakpoint enabled="true" suspend="THREAD" type="python-line">
<url>file://$PROJECT_DIR$/DepoT/mixins/crudListViewMixin.py</url>
<line>7</line>
<option name="timeStamp" value="55" />
</line-breakpoint>
<line-breakpoint enabled="true" suspend="THREAD" type="python-line">
<url>file://$PROJECT_DIR$/common/views/client_views.py</url>
<line>118</line>
<option name="timeStamp" value="61" />
</line-breakpoint>
<line-breakpoint enabled="true" suspend="THREAD" type="python-line">
<url>file://$PROJECT_DIR$/payments/views.py</url>
<line>41</line>
<option name="timeStamp" value="98" />
</line-breakpoint>
<line-breakpoint enabled="true" suspend="THREAD" type="python-line">
<url>file://$PROJECT_DIR$/payments/views.py</url>
<line>56</line>
<option name="timeStamp" value="99" />
</line-breakpoint>
<line-breakpoint enabled="true" type="javascript">
<url>file://$PROJECT_DIR$/static/js/container_validation.js</url>
<line>4</line>
<option name="timeStamp" value="95" />
</line-breakpoint>
</breakpoints>
<default-breakpoints>
<breakpoint type="python-exception">
<properties notifyOnTerminate="true" exception="BaseException">
<option name="notifyOnTerminate" value="true" />
</properties>
</breakpoint>
</default-breakpoints>
</breakpoint-manager>
</component>
</project>

@ -1,16 +0,0 @@
"""
ASGI config for DepoT project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "DepoT.settings")
application = get_asgi_application()

@ -1,15 +0,0 @@
from common.models import LinesModel
class LineFilterFormMixin:
def get_form(self, form_class=None):
form = super().get_form(form_class)
user = self.request.user
if user.line:
form.fields['line'].queryset = form.fields['line'].queryset.filter(pk=user.line.pk)
form.fields['line'].initial = user.line
form.fields['line'].widget.attrs['readonly'] = True
else:
form.fields['line'].queryset = LinesModel.objects.filter(company=user.company)
form.fields['line'].widget.attrs['readonly'] = False
return form

@ -1,21 +0,0 @@
from django.http import JsonResponse
from django.shortcuts import get_object_or_404
class CRUDListViewMixin:
...
# def post(self, request, *args, **kwargs):
# if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
# object_id = request.POST.get('object_id')
# obj = get_object_or_404(self.model, id=object_id)
# return JsonResponse(self.get_object_data(obj))
# else:
# return self.handle_form_submission(request, *args, **kwargs)
# def get_object_data(self, obj):
# """Override this method in child views to specify which data to return"""
# raise NotImplementedError
#
# def handle_form_submission(self, request, *args, **kwargs):
# """Override this method in child views to handle form submission"""
# raise NotImplementedError

@ -1,188 +0,0 @@
"""
Django settings for DepoT project.
Generated by 'django-admin startproject' using Django 5.2.3.
For more information on this file, see
https://docs.djangoproject.com/en/5.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.2/ref/settings/
"""
from pathlib import Path
import os
import environ
BASE_DIR = Path(__file__).resolve().parent.parent
env = environ.Env()
environ.Env.read_env(os.path.join(BASE_DIR, '.env'))
SECRET_KEY = "django-insecure-g%187p84o9^rr)3#9@r3n^o2v1i%@6=+puxm7hlodg+kbsk%n#"
DEBUG = True
ALLOWED_HOSTS = ['192.168.24.43', '127.0.0.1', 'localhost', ]
PROJECT_APPS = [
'accounts',
"booking",
"common",
"containers",
'preinfo',
'payments',
]
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"rest_framework",
"damages_api",
] + PROJECT_APPS
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
ROOT_URLCONF = "DepoT.urls"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [BASE_DIR / 'templates']
,
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]
WSGI_APPLICATION = "DepoT.wsgi.application"
# Database
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
# DATABASES = {
# "default": {
# "ENGINE": "django.db.backends.sqlite3",
# "NAME": BASE_DIR / "db.sqlite3",
# }
# }
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": env("DB_NAME"),
"USER": env("DB_USER"),
"PASSWORD": env("DB_PASSWORD"),
"HOST": env("DB_HOST"),
"PORT": env("DB_PORT"),
}
}
# Password validation
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
AUTH_USER_MODEL = 'accounts.DepotUser'
LOGIN_URL = '/user/login/'
AUTH_PASSWORD_VALIDATORS = [
{
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
{
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
]
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
]
# Internationalization
# https://docs.djangoproject.com/en/5.2/topics/i18n/
LANGUAGE_CODE = "en-us"
TIME_ZONE = "UTC"
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.2/howto/static-files/
STATIC_URL = "static/"
STATICFILES_DIRS = [
BASE_DIR / 'static'
]
TEMP_FILE_FOLDER = "/tmp/damages_photos"
# Default primary key field type
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
OWNCLOUD_URL = env('OWNCLOUD_URL')
OWNCLOUD_USER = env('OWNCLOUD_USER')
OWNCLOUD_PASSWORD = env('OWNCLOUD_PASSWORD')
OWNCLOUD_DAMAGES_FOLDER = env('OWNCLOUD_DAMAGES_FOLDER')
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = env("EMAIL_HOST", cast=str, default=None)
EMAIL_PORT = env("EMAIL_PORT", cast=str, default='587') # Recommended
EMAIL_HOST_USER = env("EMAIL_HOST_USER", cast=str, default=None)
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_SSL = env("EMAIL_USE_SSL", cast=bool, default=False) # EUse MAIL_PORT 465 for SSL
ADMIN_USER_NAME=env("ADMIN_USER_NAME")
ADMIN_USER_PASSWORD=env("ADMIN_USER_PASSWORD")
ADMIN_USER_EMAIL=env("ADMIN_USER_EMAIL")
MANAGERS=[]
ADMINS=[]
if all([ADMIN_USER_NAME, ADMIN_USER_EMAIL]):
ADMINS +=[
(f'{ADMIN_USER_NAME}', f'{ADMIN_USER_EMAIL}')
]
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

@ -1,31 +0,0 @@
"""
URL configuration for DepoT project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/5.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
# from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path('', include('common.urls')),
path('user/', include('accounts.urls')),
path('preinfo/', include('preinfo.urls')),
path('container/', include('containers.urls')),
path('booking/', include('booking.urls')),
path('payment/', include('payments.urls')),
path('api/damages/', include('damages_api.urls')),
path('api/common/', include('common_api.urls')),
]

@ -1,16 +0,0 @@
"""
WSGI config for DepoT project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.2/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "DepoT.settings")
application = get_wsgi_application()

@ -0,0 +1,2 @@
# depot_django

@ -1,34 +0,0 @@
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import DepotUser
@admin.register(DepotUser)
class DepotUserAdmin(UserAdmin):
# Add your custom fields to the fieldsets
fieldsets = UserAdmin.fieldsets + (
('Additional Info', {'fields': (
'phone_number',
'company',
'line',
'company_permissions',
'user_type',
)}),
)
# Add fields to display in list view
list_display = ('username', 'email', 'user_type', 'company', 'line', 'is_active', 'is_staff', 'is_superuser')
search_fields = ('username', 'email')
list_filter = ('user_type', 'is_active', 'is_staff', 'is_superuser')
# Add fields to the add form
add_fieldsets = UserAdmin.add_fieldsets + (
('Additional Info', {'fields': (
'email',
'phone_number',
'company',
'line',
'company_permissions',
'user_type',
)}),
)

@ -1,6 +0,0 @@
from django.apps import AppConfig
class UsersConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "accounts"

@ -1,16 +0,0 @@
from django.contrib.auth.backends import ModelBackend
class CompanyUserBackend(ModelBackend):
def get_user_permissions(self, user_obj, obj=None):
if not user_obj.is_active or user_obj.is_anonymous:
return set()
if user_obj.is_superuser:
return super().get_user_permissions(user_obj, obj)
perms = super().get_user_permissions(user_obj, obj)
if user_obj.company and user_obj.company.is_client:
# Filter permissions based on client company context
perms = {p for p in perms if p.startswith('accounts.client_')}
return perms

@ -1,96 +0,0 @@
from django.contrib.auth import get_user_model
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.views import PasswordChangeView
from django.core.exceptions import ValidationError
from django.forms import CharField
from django.forms.models import ModelForm
from django.forms.widgets import PasswordInput
class LoginForm(AuthenticationForm):
field_order = ['username', 'password']
class Meta:
model = get_user_model()
class RegisterForm(ModelForm):
password1 = CharField(label='Password', widget=PasswordInput)
password2 = CharField(label='Confirm Password', widget=PasswordInput)
field_order = ['username', 'email', 'password1', 'password2', 'phone_number', 'company', 'line', 'user_type', 'company_permissions', 'employee_permissions', 'is_active']
class Meta:
model = get_user_model()
fields = ['username', 'email', 'password1', 'password2', 'phone_number', 'company', 'line', 'user_type', 'company_permissions', 'employee_permissions', 'is_active']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['password1'].widget.attrs.update({'autocomplete': 'new-password'})
self.fields['password2'].widget.attrs.update({'autocomplete': 'new-password'})
self.fields['user_type'].widget.attrs['readonly'] = True
self.fields['company_permissions'].widget.attrs['disabled'] = True
self.fields['employee_permissions'].widget.attrs['disabled'] = True
if self.data.get('user_type') in ( 'EM', 'BS'):
self.fields['company_permissions'].required = False
if self.data.get('user_type') in ('CL', 'CA', 'BS'):
self.fields['company_permissions'].required = False
def clean(self):
cleaned_data = super().clean()
password1 = cleaned_data.get('password1')
password2 = cleaned_data.get('password2')
if password1 and password2 and password1 != password2:
raise ValidationError("Passwords don't match")
return cleaned_data
def save(self, commit=True):
user = super().save(commit=False)
user.set_password(self.cleaned_data['password1'])
if commit:
user.save()
return user
class UserEditForm(ModelForm):
password1 = CharField(required=False, label='Password', widget=PasswordInput)
password2 = CharField(required=False, label='Confirm Password', widget=PasswordInput)
field_order = ['username', 'email', 'password1', 'password2', 'phone_number', 'company', 'line', 'user_type', 'company_permissions', 'employee_permissions', 'is_active']
class Meta:
model = get_user_model()
fields = ['username', 'email', 'password1', 'password2', 'phone_number', 'company', 'line', 'user_type', 'company_permissions', 'employee_permissions', 'is_active']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['user_type'].widget.attrs['readonly'] = True
self.fields['company_permissions'].widget.attrs['disabled'] = True
self.fields['employee_permissions'].widget.attrs['disabled'] = True
if self.data.get('user_type') in ( 'EM', 'BS'):
self.fields['company_permissions'].required = False
if self.data.get('user_type') in ('CL', 'CA', 'BS'):
self.fields['company_permissions'].required = False
def clean(self):
cleaned_data = super().clean()
password1 = cleaned_data.get('password1')
password2 = cleaned_data.get('password2')
if password1 and password2 and password1 != password2:
raise ValidationError("Passwords don't match")
return cleaned_data
def save(self, commit=True):
user = super().save(commit=False)
user.set_password(self.cleaned_data['password1'])
if commit:
user.save()
return user
class UserChangePasswordForm(PasswordChangeView):
old_password = CharField(widget=PasswordInput())
new_password = CharField(widget=PasswordInput())
confirm_password = CharField(widget=PasswordInput())

@ -1,182 +0,0 @@
# Generated by Django 5.2.3 on 2025-06-27 11:42
import django.contrib.auth.models
import django.contrib.auth.validators
import django.db.models.deletion
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
("auth", "0012_alter_user_first_name_max_length"),
("common", "0001_initial"),
]
operations = [
migrations.CreateModel(
name="ClientPermission",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
],
options={
"permissions": (
("can_book_container", "Can book container"),
("can_view_bookings", "Can view bookings"),
("can_manage_company_users", "Can manage company users"),
),
"managed": True,
"default_permissions": (),
},
),
migrations.CreateModel(
name="DepotUser",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("password", models.CharField(max_length=128, verbose_name="password")),
(
"last_login",
models.DateTimeField(
blank=True, null=True, verbose_name="last login"
),
),
(
"is_superuser",
models.BooleanField(
default=False,
help_text="Designates that this user has all permissions without explicitly assigning them.",
verbose_name="superuser status",
),
),
(
"username",
models.CharField(
error_messages={
"unique": "A user with that username already exists."
},
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
max_length=150,
unique=True,
validators=[
django.contrib.auth.validators.UnicodeUsernameValidator()
],
verbose_name="username",
),
),
(
"first_name",
models.CharField(
blank=True, max_length=150, verbose_name="first name"
),
),
(
"last_name",
models.CharField(
blank=True, max_length=150, verbose_name="last name"
),
),
(
"is_staff",
models.BooleanField(
default=False,
help_text="Designates whether the user can log into this admin site.",
verbose_name="staff status",
),
),
(
"is_active",
models.BooleanField(
default=True,
help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
verbose_name="active",
),
),
(
"date_joined",
models.DateTimeField(
default=django.utils.timezone.now, verbose_name="date joined"
),
),
(
"phone_number",
models.CharField(blank=True, max_length=15, null=True),
),
("email", models.EmailField(max_length=254, unique=True)),
("new_field1", models.BooleanField(default=False)),
("is_company_admin", models.BooleanField(default=False)),
(
"company",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="user_lines",
to="common.companymodel",
),
),
(
"groups",
models.ManyToManyField(
blank=True,
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
related_name="user_set",
related_query_name="user",
to="auth.group",
verbose_name="groups",
),
),
(
"line",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="user_lines",
to="common.linesmodel",
),
),
(
"user_permissions",
models.ManyToManyField(
blank=True,
help_text="Specific permissions for this user.",
related_name="user_set",
related_query_name="user",
to="auth.permission",
verbose_name="user permissions",
),
),
(
"company_permissions",
models.ManyToManyField(to="accounts.clientpermission"),
),
],
options={
"verbose_name": "user",
"verbose_name_plural": "users",
"abstract": False,
},
managers=[
("objects", django.contrib.auth.models.UserManager()),
],
),
]

@ -1,23 +0,0 @@
# Generated by Django 5.2.3 on 2025-07-02 08:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("accounts", "0001_initial"),
]
operations = [
migrations.AddField(
model_name="clientpermission",
name="codename",
field=models.CharField(default="", max_length=100),
),
migrations.AddField(
model_name="clientpermission",
name="name",
field=models.CharField(default="", max_length=255),
),
]

@ -1,35 +0,0 @@
# Generated by Django 5.2.3 on 2025-07-02 12:04
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("accounts", "0002_clientpermission_codename_clientpermission_name"),
]
operations = [
migrations.RemoveField(
model_name="depotuser",
name="is_company_admin",
),
migrations.RemoveField(
model_name="depotuser",
name="new_field1",
),
migrations.AddField(
model_name="depotuser",
name="user_type",
field=models.CharField(
choices=[
("BS", "Barrier Staff"),
("CA", "Company Admin"),
("EM", "Employee"),
("CL", "Client"),
],
default="CL",
max_length=2,
),
),
]

@ -1,43 +0,0 @@
# Generated by Django 5.2.3 on 2025-07-03 08:11
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("accounts", "0003_remove_depotuser_is_company_admin_and_more"),
]
operations = [
migrations.CreateModel(
name="EmployeePermission",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("codename", models.CharField(default="", max_length=100)),
("name", models.CharField(default="", max_length=255)),
],
options={
"permissions": (
("can_manage_containers", "Can manage containers"),
("can_view_reports", "Can view reports"),
("can_handle_operations", "Can handle operations"),
),
"managed": True,
"default_permissions": (),
},
),
migrations.AddField(
model_name="depotuser",
name="employee_permissions",
field=models.ManyToManyField(blank=True, to="accounts.employeepermission"),
),
]

@ -1,32 +0,0 @@
# Generated by Django 5.2.3 on 2025-07-03 14:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("accounts", "0004_employeepermission_depotuser_employee_permissions"),
]
operations = [
migrations.AlterModelManagers(
name="depotuser",
managers=[],
),
migrations.AlterField(
model_name="depotuser",
name="user_type",
field=models.CharField(
choices=[
("BS", "Barrier Staff"),
("CA", "Company Admin"),
("EM", "Employee"),
("CL", "Client"),
("AD", "Admin"),
],
default="CL",
max_length=2,
),
),
]

@ -1,29 +0,0 @@
# Generated by Django 5.2.3 on 2025-07-09 12:51
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("accounts", "0005_alter_depotuser_managers_alter_depotuser_user_type"),
]
operations = [
migrations.AlterModelOptions(
name="clientpermission",
options={
"default_permissions": (),
"managed": True,
"permissions": (
("can_view_booking", "Can view booking"),
("can_manage_booking", "Can book container"),
("can_view_preinfo", "Can view preinfo"),
("can_manage_preinfo", "Can manage preinfo"),
("can_view_payment", "Can view payment"),
("can_manage_payment", "Can manage payment"),
("can_manage_company_users", "Can manage company users"),
),
},
),
]

@ -1,12 +0,0 @@
# Generated by Django 5.2.3 on 2025-07-25 16:20
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("accounts", "0006_alter_clientpermission_options"),
]
operations = []

@ -1,44 +0,0 @@
from django.db import migrations
def create_permissions(apps, schema_editor):
EmployeePermission = apps.get_model('accounts', 'EmployeePermission')
ClientPermission = apps.get_model('accounts', 'ClientPermission')
employee_permissions = [
(1, 'can_manage_containers', 'Can manage containers'),
(2, 'can_view_reports', 'Can view reports'),
(3, 'can_handle_operations', 'Can handle operations'),
]
for _id, codename, name in employee_permissions:
EmployeePermission.objects.create(id=_id, codename=codename, name=name)
client_permissions = [
(1, 'can_view_booking', 'Can view booking'),
(2, 'can_manage_booking', 'Can book container'),
(3, 'can_view_preinfo', 'Can view preinfo'),
(4, 'can_manage_preinfo', 'Can manage preinfo'),
(5, 'can_view_payment', 'Can view payment'),
(6, 'can_manage_payment', 'Can manage payment'),
(7, 'can_manage_company_users', 'Can manage company users'),
]
for _id, codename, name in client_permissions:
ClientPermission.objects.create(id=_id, codename=codename, name=name)
def reverse_permissions(apps, schema_editor):
EmployeePermission = apps.get_model('accounts', 'EmployeePermission')
ClientPermission = apps.get_model('accounts', 'ClientPermission')
EmployeePermission.objects.all().delete()
ClientPermission.objects.all().delete()
class Migration(migrations.Migration):
dependencies = [
('accounts', '0007_auto_20250725_1920'), # Adjust this to your last migration
]
operations = [
migrations.RunPython(create_permissions, reverse_permissions),
]

@ -1,29 +0,0 @@
from django.db import migrations
from django.conf import settings
from django.contrib.auth.hashers import make_password
def create_superuser(apps, schema_editor):
User = apps.get_model('accounts', 'DepotUser')
if not User.objects.filter(email=settings.ADMIN_USER_EMAIL).exists():
user = User.objects.create(
email=settings.ADMIN_USER_EMAIL,
username=settings.ADMIN_USER_NAME,
is_staff=True,
is_superuser=True,
is_active=True,
user_type='AD',
password=make_password(settings.ADMIN_USER_PASSWORD)
)
def reverse_superuser(apps, schema_editor):
User = apps.get_model('accounts', 'DepotUser')
User.objects.filter(email=settings.ADMIN_USER_EMAIL).delete()
class Migration(migrations.Migration):
dependencies = [
('accounts', '0008_populate_permissions'),
]
operations = [
migrations.RunPython(create_superuser, reverse_superuser),
]

@ -1,115 +0,0 @@
from django.contrib.auth.base_user import BaseUserManager
from django.contrib.auth.models import AbstractUser
from django.db import models
class ClientPermission(models.Model):
codename = models.CharField(max_length=100, default='')
name = models.CharField(max_length=255, default='')
class Meta:
managed = True
default_permissions = ()
permissions = (
('can_view_booking', 'Can view booking'),
('can_manage_booking', 'Can book container'),
('can_view_preinfo', 'Can view preinfo'),
('can_manage_preinfo', 'Can manage preinfo'),
('can_view_payment', 'Can view payment'),
('can_manage_payment', 'Can manage payment'),
('can_manage_company_users', 'Can manage company users'),
)
def __str__(self):
return self.name
class EmployeePermission(models.Model):
codename = models.CharField(max_length=100, default='')
name = models.CharField(max_length=255, default='')
class Meta:
managed = True
default_permissions = ()
permissions = (
('can_manage_containers', 'Can manage containers'),
('can_view_reports', 'Can view reports'),
('can_handle_operations', 'Can handle operations'),
)
def __str__(self):
return self.name
class CustomUserManager(BaseUserManager):
def create_user(self, email, password=None, **extra_fields):
if not email:
raise ValueError('Users must have an email address')
email = self.normalize_email(email)
user = self.model(email=email, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, password=None, **extra_fields):
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
extra_fields.setdefault('is_active', True)
extra_fields.setdefault('user_type', 'AD') # Set user_type to admin
if extra_fields.get('is_staff') is not True:
raise ValueError('Superuser must have is_staff=True.')
if extra_fields.get('is_superuser') is not True:
raise ValueError('Superuser must have is_superuser=True.')
return self.create_user(email, password, **extra_fields)
class DepotUser(AbstractUser):
class UserType(models.TextChoices):
BARRIER_STAFF = 'BS', 'Barrier Staff'
COMPANY_ADMIN = 'CA', 'Company Admin'
EMPLOYEE = 'EM', 'Employee'
CLIENT = 'CL', 'Client',
ADMIN = 'AD', 'Admin'
objects = CustomUserManager()
user_type = models.CharField(
max_length=2,
choices=UserType.choices,
default=UserType.CLIENT
)
phone_number = models.CharField(max_length=15, blank=True, null=True)
email = models.EmailField(unique=True, blank=False, null=False)
company = models.ForeignKey(
'common.CompanyModel',
on_delete=models.CASCADE,
related_name='user_lines',
blank=True,
null=True
)
line = models.ForeignKey(
'common.LinesModel',
on_delete=models.CASCADE,
related_name='user_lines',
blank=True,
null=True
)
company_permissions = models.ManyToManyField('ClientPermission')
employee_permissions = models.ManyToManyField('EmployeePermission', blank=True)
def has_company_perm(self, perm_codename):
if self.is_superuser:
return True
return self.company_permissions.filter(codename=perm_codename).exists()
def has_employee_perm(self, perm_codename):
if self.is_superuser:
return True
return self.employee_permissions.filter(codename=perm_codename).exists()

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

@ -1,15 +0,0 @@
from django.urls import path, include
from django.contrib.auth import views as auth_views
from accounts import views
urlpatterns = [
path('', include([
path('', views.UserListView.as_view(), name='user_list'),
path('register/', views.RegisterView.as_view(), name='user_register'),
path('login/', views.DepotLoginView.as_view(), name='login'),
path('relogin/', auth_views.logout_then_login, name='relogin'),
path('change-password/', views.UserChangePasswordView.as_view(), name='change_password'),
path('<int:pk>/update/', views.UserUpdateView.as_view(), name='user_update'),
path('<int:pk>/active/', views.UserActiveView.as_view(), name='user_active'),
])),
]

@ -1,198 +0,0 @@
from django.contrib.auth import get_user_model
from django.contrib.auth.views import LoginView
from django.http import HttpResponseForbidden, JsonResponse
from django.shortcuts import render
from django.urls import reverse_lazy
from django.utils.decorators import method_decorator
from django.views import View
from django.views.generic import TemplateView, FormView, ListView, UpdateView
from rest_framework.generics import get_object_or_404
from accounts.forms import LoginForm, RegisterForm, UserChangePasswordForm
from accounts.models import DepotUser
from django.contrib.auth.decorators import login_required, user_passes_test
from django.contrib.auth.mixins import AccessMixin, LoginRequiredMixin
# Create your views here.
class DepotLoginView(LoginView):
template_name = 'registration/login.html'
# success_url = reverse_lazy('dashboard')
form_class = LoginForm
next_page = reverse_lazy('dashboard')
def is_company_admin(user):
return user.is_authenticated and user.is_company_admin
@method_decorator(login_required, name='dispatch')
class RegisterView(AccessMixin, FormView):
template_name = 'registration/register.html'
form_class = RegisterForm
# model = get_user_model()
success_url = reverse_lazy('dashboard')
def dispatch(self, request, *args, **kwargs):
user: DepotUser = request.user
if not (user.is_superuser or user.user_type == DepotUser.UserType.COMPANY_ADMIN):
return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)
def form_valid(self, form):
# Create user from form data
user = form.save(commit=False)
user_type = form.cleaned_data['user_type']
user.save()
# Clear irrelevant permissions based on user type
if user_type == DepotUser.UserType.CLIENT:
user.employee_permissions.clear()
user.company_permissions.set(form.cleaned_data['company_permissions'])
elif user_type == DepotUser.UserType.EMPLOYEE:
user.company_permissions.clear()
user.employee_permissions.set(form.cleaned_data['employee_permissions'])
return super().form_valid(form)
def get_form(self, form_class = None):
form = super().get_form(form_class)
user: DepotUser = self.request.user
if user.is_superuser:
# Superuser can manage all permissions and user types
form.fields['user_type'].widget.attrs['disabled'] = False
form.fields['company_permissions'].widget.attrs['disabled'] = False
form.fields['employee_permissions'].widget.attrs['disabled'] = False
# Show relevant permissions based on selected user type
if form.initial.get('user_type') == DepotUser.UserType.CLIENT:
form.fields['employee_permissions'].widget.attrs['disabled'] = True
elif form.initial.get('user_type') == DepotUser.UserType.EMPLOYEE:
form.fields['company_permissions'].widget.attrs['disabled'] = True
elif user.user_type == DepotUser.UserType.COMPANY_ADMIN:
form.fields['company'].queryset = form.fields['company'].queryset.filter(pk=user.company.pk)
form.fields['company'].initial = user.company
form.fields['company'].widget.readonly = True # form.fields['line'].widget.attrs['disabled'] = True
form.fields['line'].queryset = form.fields['line'].queryset.filter(company=user.company.pk)
form.fields['user_type'].choices = [
(DepotUser.UserType.CLIENT, 'Client')
]
form.fields['user_type'].initial = DepotUser.UserType.CLIENT
form.fields['company_permissions'].widget.attrs['disabled'] = False
form.fields['employee_permissions'].widget.attrs['disabled'] = True
return form
class UserListView(ListView):
template_name = 'registration/user-list.html'
model = get_user_model()
context_object_name = 'objects'
paginate_by = 30 # Number of containers per page
# base_template = 'employee-base.html'
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context['base_template'] = self.base_template
# return context
def get_queryset(self):
queryset = super().get_queryset()
user = self.request.user
data_filter = self.request.GET.get('filter')
if data_filter != 'all':
queryset = queryset.filter(is_active=True)
# Filter users based on permissions
if user.is_superuser:
return queryset.all()
elif user.user_type == DepotUser.UserType.COMPANY_ADMIN:
return queryset.filter(company=user.company)
else:
return queryset.none()
class UserUpdateView(UpdateView):
template_name = 'registration/register.html'
form_class = RegisterForm
model = get_user_model()
success_url = reverse_lazy('user_list')
def dispatch(self, request, *args, **kwargs):
user: DepotUser = request.user
if not (user.is_superuser or user.user_type == DepotUser.UserType.COMPANY_ADMIN):
return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)
def form_valid(self, form):
user = form.save(commit=False)
user_type = form.cleaned_data['user_type']
user.save()
# Clear irrelevant permissions based on user type
if user_type == DepotUser.UserType.CLIENT:
user.employee_permissions.clear()
user.company_permissions.set(form.cleaned_data['company_permissions'])
elif user_type == DepotUser.UserType.EMPLOYEE:
user.company_permissions.clear()
user.employee_permissions.set(form.cleaned_data['employee_permissions'])
return super().form_valid(form)
def get_form(self, form_class = None):
form = super().get_form(form_class)
user: DepotUser = self.request.user
if user.is_superuser:
# Superuser can manage all permissions and user types
form.fields['user_type'].widget.attrs['disabled'] = False
form.fields['company_permissions'].widget.attrs['disabled'] = False
form.fields['employee_permissions'].widget.attrs['disabled'] = False
# Show relevant permissions based on selected user type
if form.initial.get('user_type') == DepotUser.UserType.CLIENT:
form.fields['employee_permissions'].widget.attrs['disabled'] = True
elif form.initial.get('user_type') == DepotUser.UserType.EMPLOYEE:
form.fields['company_permissions'].widget.attrs['disabled'] = True
elif user.user_type == DepotUser.UserType.COMPANY_ADMIN:
form.fields['company'].queryset = form.fields['company'].queryset.filter(pk=user.company.pk)
form.fields['company'].initial = user.company
form.fields['company'].widget.readonly = True
form.fields['line'].queryset = form.fields['line'].queryset.filter(company=user.company.pk)
form.fields['user_type'].choices = [
(DepotUser.UserType.CLIENT, 'Client')
]
form.fields['user_type'].initial = DepotUser.UserType.CLIENT
form.fields['company_permissions'].widget.attrs['disabled'] = False
form.fields['employee_permissions'].widget.attrs['disabled'] = True
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})
class UserChangePasswordView(LoginRequiredMixin, View):
template_name = 'registration/change_password.html'
form_class = UserChangePasswordForm
success_url = reverse_lazy('home')

@ -1,3 +0,0 @@
from django.contrib import admin
# Register your models here.

@ -1,6 +0,0 @@
from django.apps import AppConfig
class BookingConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "booking"

@ -1,36 +0,0 @@
from django import forms
from booking.models import Booking
class BookingBaseForm(forms.ModelForm):
"""
Base form for booking-related forms.
This can be extended by other booking forms.
"""
class Meta:
fields = '__all__'
model = Booking
class BookingCreateForm(BookingBaseForm):
class Meta(BookingBaseForm.Meta):
fields = ['number', 'vehicles', 'container_type', 'container_count', 'carrier', 'line', 'container_number']
widgets = {
'container_number': forms.TextInput(attrs={
'oninput': 'validateContainerNumber(this)',
'class': 'form-control'
})
}
class BookingUpdateForm(BookingBaseForm):
class Meta(BookingBaseForm.Meta):
fields = ['number', 'vehicles', 'container_type', 'container_count', 'carrier', 'line', 'container_number']
widgets = {
'container_number': forms.TextInput(attrs={
'oninput': 'validateContainerNumber(this)',
'class': 'form-control'
})
}

@ -1,61 +0,0 @@
# Generated by Django 5.2.3 on 2025-06-27 10:20
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
("common", "0001_initial"),
]
operations = [
migrations.CreateModel(
name="BookingModel",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("number", models.CharField(max_length=50, unique=True)),
("vehicles", models.CharField(blank=True, null=True)),
("container_count", models.IntegerField()),
("carrier", models.CharField(blank=True, max_length=100, null=True)),
("visible", models.BooleanField(default=True)),
("is_new", models.BooleanField(default=True)),
(
"container_number",
models.CharField(blank=True, max_length=11, null=True),
),
("vehicles_left", models.IntegerField(blank=True, null=True)),
("created_on", models.DateTimeField(auto_now_add=True)),
("created_by", models.IntegerField()),
("updated_on", models.DateTimeField(auto_now=True)),
("updated_by", models.IntegerField()),
(
"container_type",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="booking_container_types",
to="common.containertypemodel",
),
),
(
"line",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="booking_lines",
to="common.linesmodel",
),
),
],
),
]

@ -1,19 +0,0 @@
# Generated by Django 5.2.3 on 2025-06-30 15:11
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("booking", "0001_initial"),
("common", "0002_alter_companymodel_short_name_and_more"),
("containers", "0005_alter_container_expedited_by_and_more"),
]
operations = [
migrations.RenameModel(
old_name="BookingModel",
new_name="Booking",
),
]

@ -1,18 +0,0 @@
# Generated by Django 5.2.3 on 2025-07-01 08:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("booking", "0002_rename_bookingmodel_booking"),
]
operations = [
migrations.AddField(
model_name="booking",
name="container_expedited_count",
field=models.IntegerField(default=0),
),
]

@ -1,26 +0,0 @@
# Generated by Django 5.2.3 on 2025-07-01 09:08
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("booking", "0003_booking_container_expedited_count"),
]
operations = [
migrations.AddField(
model_name="booking",
name="status",
field=models.CharField(
choices=[
("active", "Active"),
("finished", "Finished"),
("canceled", "Canceled"),
],
default="active",
max_length=10,
),
),
]

@ -1,18 +0,0 @@
# Generated by Django 5.2.3 on 2025-07-01 09:31
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("booking", "0004_booking_status"),
]
operations = [
migrations.AlterField(
model_name="booking",
name="vehicles_left",
field=models.CharField(blank=True, null=True),
),
]

@ -1,44 +0,0 @@
from django.db import models
from common.models import ContainerTypeModel, LinesModel, OperationModel
# Create your models here.
class Booking(models.Model):
STATUS_CHOICES = [
('active', 'Active'),
('finished', 'Finished'),
('canceled', 'Canceled'),
]
number = models.CharField(max_length=50, unique=True)
vehicles = models.CharField(blank=True, null=True)
container_type = models.ForeignKey(
ContainerTypeModel,
on_delete=models.CASCADE,
related_name='booking_container_types',
)
container_count = models.IntegerField()
container_expedited_count = models.IntegerField(default=0)
carrier = models.CharField(max_length=100, blank=True, null=True)
line = models.ForeignKey(
LinesModel,
on_delete=models.CASCADE,
related_name='booking_lines',
)
visible = models.BooleanField(default=True)
is_new = models.BooleanField(default=True)
container_number = models.CharField(max_length=11, blank=True, null=True)
vehicles_left = models.CharField(blank=True, null=True)
created_on = models.DateTimeField(auto_now_add=True)
created_by = models.IntegerField()
updated_on = models.DateTimeField(auto_now=True)
updated_by = models.IntegerField()
status = models.CharField(
max_length=10,
choices=STATUS_CHOICES,
default='active'
)
@property
def containers_left(self):
return self.container_count - (self.container_expedited_count or 0)

@ -1,11 +0,0 @@
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

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

@ -1,14 +0,0 @@
from django.urls import path, include
from booking.views.client_views import CreateBookingView, ClientBookingView, ClientBookingUpdateView
from booking.views.employee_views import BookingListView
urlpatterns = [
path('client/', include([
path('', ClientBookingView.as_view(), name='client_booking'),
path('create/', CreateBookingView.as_view(), name='client_booking_create'),
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'),
]

@ -1,62 +0,0 @@
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.urls import reverse_lazy
from django.views.generic import CreateView, ListView
from DepoT.mixins.LineFiltweFormMixin import LineFilterFormMixin
from booking.forms import BookingCreateForm, BookingUpdateForm
from booking.models import Booking
from common.utils.utils import filter_queryset_by_user
class ClientBookingView(LoginRequiredMixin, UserPassesTestMixin, ListView):
model = Booking
template_name = 'client/booking-list.html'
paginate_by = 4
context_object_name = 'objects'
# base_template = 'client-base.html'
def test_func(self):
return self.request.user.has_company_perm('can_view_booking') or self.request.user.user_type == 'CA'
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context['base_template'] = self.base_template
# return context
def get_queryset(self):
queryset = super().get_queryset()
user = self.request.user
result = filter_queryset_by_user(queryset, user)
return result
class CreateBookingView(LoginRequiredMixin, UserPassesTestMixin, LineFilterFormMixin, CreateView):
template_name = 'client/booking-create.html'
model = Booking
form_class = BookingCreateForm
success_url = reverse_lazy('client_booking')
def form_valid(self, form):
# todo more validation
form.instance.created_by = self.request.user.id
form.instance.updated_by = self.request.user.id
form.instance.vehicles_left = form.cleaned_data.get('vehicles')
return super().form_valid(form)
def test_func(self):
return True # self.request.user.has_company_perm('can_edit_preinfo') or self.request.user.user_type == 'CA'
class ClientBookingUpdateView(LoginRequiredMixin, UserPassesTestMixin, LineFilterFormMixin, CreateView):
template_name = 'client/booking-edit.html'
model = Booking
form_class = BookingUpdateForm
success_url = reverse_lazy('client_booking')
def form_valid(self, form):
# todo more validation
form.instance.updated_by = self.request.user.id
return super().form_valid(form)
def test_func(self):
return True # self.request.user.has_company_perm('can_edit_preinfo') or self.request.user.user_type == 'CA'

@ -1,27 +0,0 @@
from django.views.generic import ListView
from booking.models import Booking
class BookingListView(ListView):
template_name = 'employee/booking-list.html'
model = Booking
context_object_name = 'objects'
paginate_by = 30 # Number of containers per page
# base_template = 'employee-base.html'
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context['base_template'] = self.base_template
# return context
def get_queryset(self):
queryset = super().get_queryset()
data_filter = self.request.GET.get('filter')
if data_filter != 'all':
queryset = queryset.filter(status='active')
queryset = queryset.order_by('-created_on')
return queryset

@ -1,3 +0,0 @@
from django.contrib import admin
# Register your models here.

@ -1,6 +0,0 @@
from django.apps import AppConfig
class CommonConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "common"

@ -1,17 +0,0 @@
from django.forms import ModelForm
from common.models import CompanyModel
class CompanyBaseForm(ModelForm):
class Meta:
model = CompanyModel
fields = '__all__'
class CompanyCreateForm(CompanyBaseForm):
...
class CompanyUpdateForm(CompanyBaseForm):
...

@ -1,17 +0,0 @@
from django.forms import ModelForm
from common.models import LinesModel
class LineBaseForm(ModelForm):
class Meta:
model = LinesModel
fields = '__all__'
class LineCreateForm(LineBaseForm):
...
class LineUpdateForm(LineBaseForm):
...

@ -1,132 +0,0 @@
# Generated by Django 5.2.3 on 2025-06-27 10:20
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="CompanyModel",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=100, unique=True)),
("short_name", models.CharField(max_length=5, unique=True)),
("description", models.TextField(blank=True, null=True)),
],
options={
"ordering": ["name"],
"abstract": False,
},
),
migrations.CreateModel(
name="ContainerKindModel",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=100, unique=True)),
("short_name", models.CharField(max_length=5, unique=True)),
("description", models.TextField(blank=True, null=True)),
],
options={
"ordering": ["name"],
"abstract": False,
},
),
migrations.CreateModel(
name="OperationModel",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=100, unique=True)),
("short_name", models.CharField(max_length=5, unique=True)),
("description", models.TextField(blank=True, null=True)),
],
options={
"ordering": ["name"],
"abstract": False,
},
),
migrations.CreateModel(
name="ContainerTypeModel",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=6, unique=True)),
("length", models.IntegerField()),
("height", models.BooleanField()),
("deleted", models.BooleanField(default=False)),
(
"container_type",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="container_type_container_kinds",
to="common.containerkindmodel",
),
),
],
),
migrations.CreateModel(
name="LinesModel",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=100, unique=True)),
("short_name", models.CharField(max_length=5, unique=True)),
("description", models.TextField(blank=True, null=True)),
(
"company",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="line_company",
to="common.companymodel",
),
),
],
options={
"ordering": ["name"],
"abstract": False,
},
),
]

@ -1,33 +0,0 @@
# Generated by Django 5.2.3 on 2025-06-27 15:12
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("common", "0001_initial"),
]
operations = [
migrations.AlterField(
model_name="companymodel",
name="short_name",
field=models.CharField(blank=True, max_length=5, null=True),
),
migrations.AlterField(
model_name="containerkindmodel",
name="short_name",
field=models.CharField(blank=True, max_length=5, null=True),
),
migrations.AlterField(
model_name="linesmodel",
name="short_name",
field=models.CharField(blank=True, max_length=5, null=True),
),
migrations.AlterField(
model_name="operationmodel",
name="short_name",
field=models.CharField(blank=True, max_length=5, null=True),
),
]

@ -1,12 +0,0 @@
# Generated by Django 5.2.3 on 2025-07-25 16:20
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("common", "0002_alter_companymodel_short_name_and_more"),
]
operations = []

@ -1,91 +0,0 @@
from django.db import migrations
def create_initial_data(apps, schema_editor):
ContainerKindModel = apps.get_model('common', 'ContainerKindModel')
ContainerTypeModel = apps.get_model('common', 'ContainerTypeModel')
CompanyModel = apps.get_model('common', 'CompanyModel')
LinesModel = apps.get_model('common', 'LinesModel')
# Container Kinds
container_kinds = [
(1, 'Standart', '', ''),
(2, 'Reefer', '', ''),
(3, 'Opentop', '', ''),
(4, 'FlatRack', '', ''),
(5, 'Tank', '', ''),
(6, 'Platform', '', ''),
]
for _id, name, *rest in container_kinds:
ContainerKindModel.objects.create(id=_id, name=name)
# Container Types
container_types = [
(1, '20GP', 20, False, 1, False),
(2, '40GP', 40, False, 1, False),
(3, '40HC', 40, True, 1, False),
(4, '20RF', 20, False, 2, False),
(5, '40RF', 40, False, 2, False),
(6, '20OT', 20, False, 3, False),
(7, '40OT', 40, False, 3, False),
(8, '20FR', 20, False, 4, False),
(9, '20FRPL', 20, False, 6, False),
(10, '45HC', 45, True, 1, False),
(11, '40FRPL', 40, False, 6, False),
(12, '40HR', 40, True, 2, False),
]
for _id, name, length, height, container_type_id, deleted in container_types:
ContainerTypeModel.objects.create(
id=_id,
name=name,
length=length,
height=height,
container_type_id=container_type_id,
deleted=deleted
)
# Companies and Lines
companies = [
(1, 'GMS', 'GMS', 'Global Maritime Servises'),
(2, 'TO', 'TO', 'Терминален оператор'),
]
for _id, name, short_name, description in companies:
CompanyModel.objects.create(
id=_id,
name=name,
short_name=short_name,
description=description
)
lines = [
(1, 'GMS', 'GMS', 'GMS line', 1),
(2, 'HPG', 'HPG', 'Hapag Lloyds line', 1),
(3, 'TO', 'TO', 'Терминален оператор line', 2),
]
for _id, name, short_name, description, company_id in lines:
LinesModel.objects.create(
id=_id,
name=name,
short_name=short_name,
description=description,
company_id=company_id
)
def reverse_initial_data(apps, schema_editor):
ContainerKindModel = apps.get_model('common', 'ContainerKindModel')
ContainerTypeModel = apps.get_model('common', 'ContainerTypeModel')
CompanyModel = apps.get_model('common', 'CompanyModel')
LinesModel = apps.get_model('common', 'LinesModel')
ContainerKindModel.objects.all().delete()
ContainerTypeModel.objects.all().delete()
CompanyModel.objects.all().delete()
LinesModel.objects.all().delete()
class Migration(migrations.Migration):
dependencies = [
('common', '0003_auto_20250725_1920'), # Adjust this to your last migration
]
operations = [
migrations.RunPython(create_initial_data, reverse_initial_data),
]

@ -1,33 +0,0 @@
# Generated by Django 5.2.3 on 2025-07-30 14:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("common", "0004_populate_initial_data"),
]
operations = [
migrations.AddField(
model_name="companymodel",
name="active",
field=models.BooleanField(default=True),
),
migrations.AddField(
model_name="containerkindmodel",
name="active",
field=models.BooleanField(default=True),
),
migrations.AddField(
model_name="linesmodel",
name="active",
field=models.BooleanField(default=True),
),
migrations.AddField(
model_name="operationmodel",
name="active",
field=models.BooleanField(default=True),
),
]

@ -1,46 +0,0 @@
from django.db import models
# Create your models here.
class NomenclatureBaseModel(models.Model):
name = models.CharField(max_length=100, unique=True)
short_name = models.CharField(max_length=5, null=True, blank=True)
description = models.TextField(blank=True, null=True)
active = models.BooleanField(default=True)
class Meta:
abstract = True
ordering = ['name']
def __str__(self):
return self.name
class CompanyModel(NomenclatureBaseModel):
...
class LinesModel(NomenclatureBaseModel):
company = models.ForeignKey(
'common.CompanyModel',
on_delete=models.CASCADE,
related_name='line_company'
)
class OperationModel(NomenclatureBaseModel):
...
class ContainerKindModel(NomenclatureBaseModel):
...
class ContainerTypeModel(models.Model):
name = models.CharField(max_length=6, unique=True)
length = models.IntegerField()
height = models.BooleanField()
container_type = models.ForeignKey(
'common.ContainerKindModel',
on_delete=models.CASCADE,
related_name='container_type_container_kinds'
)
deleted = models.BooleanField(default=False)
def __str__(self):
return self.name

@ -1,20 +0,0 @@
from django import template
from django.template.defaultfilters import date
from common.utils.utils import check_container_number_validity
register = template.Library()
@register.filter
def bg_date(value):
if value:
return date(value, "y.m.d h:m")
return ""
@register.simple_tag
def container_validity_class(container_number):
if not check_container_number_validity(container_number):
return 'invalid-container'
return ''

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

@ -1,39 +0,0 @@
from django.urls import path, include
from common.views.barrier_views import BarrierDashboardView
from common.views.employee_views import EmployeeDashboardView, EmployeeCompanyListView, EmployeeCompanyCreateView, \
EmployeeCompanyUpdateView, EmployeeLineListView, EmployeeLineCreateView, EmployeeLineUpdateView
from common.views.client_views import ClientDashboardView, ClientCompanyListView, ClientCompanyCreateView, \
ClientCompanyUpdateView, ClientLineListView, ClientLineCreateView, ClientLineUpdateView
from common.views.shared_views import IndexView, DashboardRedirectView
urlpatterns = [
path('', IndexView.as_view(), name='index'),
path('dashboard/', include([
path('', DashboardRedirectView.as_view(), name='dashboard'),
path('client/', ClientDashboardView.as_view(), name='client_dashboard'),
path('barrier/', BarrierDashboardView.as_view(), name='barrier_dashboard'),
path('employee/', EmployeeDashboardView.as_view(), name='employee_dashboard'),
])),
path('client/company/', include([
path('', ClientCompanyListView.as_view(), name='client_company'),
path('create/', ClientCompanyCreateView.as_view(), name='client_company_create'),
path('<int:pk>/edit/', ClientCompanyUpdateView.as_view(), name='client_company_edit'),
])),
path('client/line/', include([
path('', ClientLineListView.as_view(), name='client_line'),
path('create/', ClientLineCreateView.as_view(), name='client_line_create'),
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('', EmployeeCompanyListView.as_view(), name='employee_company'),
path('create/', EmployeeCompanyCreateView.as_view(), name='employee_company_create'),
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('', EmployeeLineListView.as_view(), name='employee_line'),
path('create/', EmployeeLineCreateView.as_view(), name='employee_line_create'),
path('<int:pk>/edit/', EmployeeLineUpdateView.as_view(), name='employee_line_update'),
path('<int:pk>/active/', EmployeeLineUpdateView.as_view(), name='employee_line_active'),
])),
]

@ -1,73 +0,0 @@
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)

@ -1,77 +0,0 @@
import os
import uuid
from datetime import datetime
from django.conf import settings
from django.http import HttpResponse
from rest_framework import status
from rest_framework.response import Response
import owncloud
from owncloud import (HTTPResponseError)
import xml.etree.ElementTree as ET
import requests
class Owncloud:
@staticmethod
def upload_damage_photo(filename, depot_id):
_, ext = os.path.splitext(filename)
oc = owncloud.Client(settings.OWNCLOUD_URL)
oc.login(settings.OWNCLOUD_USER, settings.OWNCLOUD_PASSWORD)
path = f"{settings.OWNCLOUD_DAMAGES_FOLDER}{str(depot_id)}"
try:
oc.file_info(path)
except HTTPResponseError:
_ = oc.mkdir(path)
owncloud_filename = f"{datetime.today().strftime('%Y%m%d')}_{uuid.uuid4()}{ext}"
owncloud_fullpath = path + "/" + owncloud_filename
oc.put_file(owncloud_fullpath, filename)
file_info = oc.share_file_with_link(owncloud_fullpath)
return file_info.get_link()
@staticmethod
def get_damages(depot_id):
oc = owncloud.Client(settings.OWNCLOUD_URL)
oc.login(settings.OWNCLOUD_USER, settings.OWNCLOUD_PASSWORD)
files = oc.list(f"{settings.OWNCLOUD_DAMAGES_FOLDER}{str(depot_id)}")
damages = []
for file_info in files:
if file_info.is_dir():
continue
link_info = oc.share_file_with_link(file_info.path)
damages.append(link_info.get_link())
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,30 +0,0 @@
import datetime
from models.enums import ContainerLength, ContainerType
PAYMENT_SCHEMA = {
"standart": [[0, 5, 25], [5, 20, 40], [40, 1000000, 75]],
"reefer": [[0, 7, 35], [7, 30, 60], [30, 1000000, 115]],
}
def calc_storage_amount(depot, container):
result = (depot.expedited_on - depot.received_on + datetime.timedelta(days=1)).days
if container.container_type == ContainerType.ct_reefer:
data = PAYMENT_SCHEMA["reefer"]
result -= 1
else:
data = PAYMENT_SCHEMA["standart"]
for i in data:
start, end, price = i
if start <= result < end:
price_per_day = price
break
if container.container_length in (
ContainerLength.cl_40_ft,
ContainerLength.cl_45_ft,
):
result *= 2
result = result * price_per_day
return result

@ -1,89 +0,0 @@
import re
from django.template.loader import render_to_string
from django.utils.html import strip_tags
from containers.models import Container
from preinfo.models import Preinfo
from django.conf import settings
from django.core.mail import send_mail, EmailMessage
def filter_queryset_by_user(queryset, user):
"""
Filters the queryset based on the user's line or company.
If the user has a line, it filters by that line.
If the user has a company, it filters by all lines associated with that company.
"""
print(f'user: {user}, user company: {user.company}, user line: {user.line}, user type: {user.user_type}')
if user.line:
filtered = queryset.filter(line=user.line)
print(f"Filtering by line: {user.line.id}, count: {filtered.count()}")
return filtered
elif user.company:
company_lines = user.company.line_company.all()
filtered = queryset.filter(line__in=company_lines)
print(f"Filtering by company: {user.company.id}, count: {filtered.count()}")
return filtered
return queryset
def get_preinfo_by_number(number):
"""
Retrieves a PreinfoModel instance by its container number.
Returns None if no matching PreinfoModel is found.
"""
try:
return Preinfo.objects.get(container_number=number, received=False)
except Preinfo.DoesNotExist:
return None
def get_container_by_number(number):
"""
Retrieves a Container instance by its number.
Returns None if no matching Container is found.
"""
try:
return Container.objects.get(number=number, expedited=False)
except Container.DoesNotExist:
return None
CALC_STRING = "0123456789A.BCDEFGHIJK.LMNOPQRSTU.VWXYZ"
def check_container_number_validity(container_number):
if not re.search(r"[A-Z]{4}[0-9]{7}", container_number):
return False
subtotal = 0
delta = 1
for i in range(0, 10):
subtotal = subtotal + CALC_STRING.find(container_number[i]) * delta
delta = delta * 2
subtotal = (subtotal % 11) % 10
if not subtotal == CALC_STRING.find(container_number[10]):
return False
return True
ADMIN_USER_EMAIL= settings.ADMIN_USER_EMAIL
EMAIL_HOST_USER = settings.EMAIL_HOST_USER
def send_test_email(user_email, urls):
subject='Welcome to Our Platform'
html_message = render_to_string('email.html', {
'site_name': 'Our Platform',
'user_email': user_email,
'link1': urls['pay_epay'],
'link2': urls['pay_credit_card'],
})
plain_message = strip_tags(html_message)
email = EmailMessage(
subject=subject,
body=html_message,
from_email=EMAIL_HOST_USER,
to=[user_email],
)
email.content_subtype = 'html' # This tells Django to send HTML email
email.send()

@ -1,17 +0,0 @@
from django.shortcuts import render
from django.views.generic import TemplateView
from containers.models import Container
class BarrierDashboardView(TemplateView):
template_name = 'barrier/barrier-dashboard.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
recent_containers = Container.objects.select_related('line', 'booking').order_by('-expedited_on', '-received_on')[:10]
context['recent_containers'] = recent_containers
return context
def get(self, request, *args, **kwargs):
return render(request, self.template_name, self.get_context_data())

@ -1,140 +0,0 @@
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.shortcuts import render
from django.urls import reverse_lazy
from django.views.generic import TemplateView, ListView, CreateView, UpdateView
from DepoT.mixins.LineFiltweFormMixin import LineFilterFormMixin
from common.forms.company import CompanyCreateForm, CompanyUpdateForm
from common.forms.line import LineCreateForm, LineUpdateForm
from common.models import CompanyModel, LinesModel
from common.utils.utils import filter_queryset_by_user
class ClientDashboardView(TemplateView):
template_name = 'client-dashboard-content.html'
extra_context = {
'title': 'Client Dashboard',
'description': 'This is the client dashboard page.',
}
def get(self, request, *args, **kwargs):
return render(request, self.template_name, self.extra_context)
class ClientCompanyListView(LoginRequiredMixin, UserPassesTestMixin, ListView):
model = CompanyModel
template_name = 'common/../../templates/employee/company-list.html'
context_object_name = 'objects'
paginate_by = 3
# base_template = 'client-base.html'
def test_func(self):
return True # self.request.user.has_employee_perm('can_view_preinfo') or self.request.user.user_type == 'CA'
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context['base_template'] = self.base_template
# return context
def get_queryset(self):
queryset = super().get_queryset()
user = self.request.user
result = filter_queryset_by_user(queryset, user)
return result
class ClientCompanyCreateView(LoginRequiredMixin, UserPassesTestMixin, CreateView):
model = CompanyModel
template_name = 'common/../../templates/employee/company-create.html'
form_class = CompanyCreateForm
success_url = reverse_lazy('client-company')
def test_func(self):
return True # self.request.user.has_company_perm('can_create_preinfo') or self.request.user.user_type == 'CA'
# def form_valid(self, form):
# form.instance.created_by = self.request.user
# form.instance.updated_by = self.request.user
# return super().form_valid(form)
class ClientCompanyUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = CompanyModel
template_name = 'common/../../templates/employee/company-update.html'
form_class = CompanyUpdateForm
success_url = reverse_lazy('client-company')
def test_func(self):
return True # self.request.user.ha.s_company_perm('can_edit_preinfo') or self.request.user.user_type == 'CA'
# def form_valid(self, form):
# form.instance.updated_by = self.request.user
# return super().form_valid(form)
class ClientLineListView(LoginRequiredMixin, UserPassesTestMixin, ListView):
model = LinesModel
template_name = 'client/line-list.html'
context_object_name = 'objects'
paginate_by = 3
# base_template = 'client-base.html'
def test_func(self):
return True # self.request.user.has_employee_perm('can_view_preinfo') or self.request.user.user_type == 'CA'
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context['base_template'] = self.base_template
# return context
def get_queryset(self):
queryset = super().get_queryset()
user = self.request.user
result = queryset.filter(company=user.company)
return result
class ClientLineCreateView(LoginRequiredMixin, UserPassesTestMixin, CreateView):
model = LinesModel
template_name = 'client/line-create.html'
form_class = LineCreateForm
success_url = reverse_lazy('client-line')
def test_func(self):
return True # self.request.user.has_company_perm('can_create_preinfo') or self.request.user.user_type == 'CA'
def get_form(self, form_class=None):
form = super().get_form(form_class)
form.fields['company'].queryset = CompanyModel.objects.filter(id=self.request.user.company.id)
form.fields['company'].initial = self.request.user.company.id
return form
def form_valid(self, form):
# todo more validation
form.instance.company = self.request.user.company
return super().form_valid(form)
class ClientLineUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = LinesModel
template_name = 'client/line-update.html'
form_class = LineUpdateForm
success_url = reverse_lazy('client-company')
def test_func(self):
return True # self.request.user.ha.s_company_perm('can_edit_preinfo') or self.request.user.user_type == 'CA'
def get_form(self, form_class=None):
form = super().get_form(form_class)
form.fields['company'].queryset = CompanyModel.objects.filter(id=self.request.user.company.id)
form.fields['company'].initial = self.request.user.company.id
return form
def form_valid(self, form):
# todo more validation
form.instance.company = self.request.user.company
return super().form_valid(form)

@ -1,140 +0,0 @@
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.urls import reverse_lazy
from django.views.generic import TemplateView, ListView, CreateView, UpdateView
from booking.models import Booking
from common.forms.company import CompanyCreateForm, CompanyUpdateForm
from common.forms.line import LineCreateForm, LineUpdateForm
from common.models import CompanyModel, LinesModel
from containers.models import Container
from preinfo.models import Preinfo
class EmployeeDashboardView(TemplateView):
template_name = 'employee-dashboard-content.html'
extra_context = {
'title': 'Employee Dashboard',
'description': 'This is the depot employee dashboard page.',
}
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
containers = Container.objects.filter(expedited=False).count()
preinfos = Preinfo.objects.filter(received=False).count()
bookings = Booking.objects.filter(status='active').count()
context['containers'] = containers
context['preinfos'] = preinfos
context['bookings'] = bookings
return context
class EmployeeCompanyListView(LoginRequiredMixin, UserPassesTestMixin, ListView):
model = CompanyModel
template_name = 'common/../../templates/employee/company-list.html'
context_object_name = 'objects'
paginate_by = 3
# base_template = 'employee-base.html'
def test_func(self):
return True # self.request.user.has_employee_perm('can_view_preinfo') or self.request.user.user_type == 'CA'
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context['base_template'] = self.base_template
# return context
def get_queryset(self):
queryset = super().get_queryset()
data_filter = self.request.GET.get('filter')
if data_filter != 'all':
queryset = queryset.filter(active=True)
queryset = queryset.order_by('name')
return queryset
class EmployeeCompanyCreateView(LoginRequiredMixin, UserPassesTestMixin, CreateView):
model = CompanyModel
template_name = 'common/../../templates/employee/company-create.html'
form_class = CompanyCreateForm
success_url = reverse_lazy('employee_company')
def test_func(self):
return True # self.request.user.has_company_perm('can_create_preinfo') or self.request.user.user_type == 'CA'
# def form_valid(self, form):
# form.instance.created_by = self.request.user
# form.instance.updated_by = self.request.user
# return super().form_valid(form)
class EmployeeCompanyUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = CompanyModel
template_name = 'common/../../templates/employee/company-update.html'
form_class = CompanyUpdateForm
success_url = reverse_lazy('employee_company')
def test_func(self):
return True # self.request.user.ha.s_company_perm('can_edit_preinfo') or self.request.user.user_type == 'CA'
# def form_valid(self, form):
# form.instance.updated_by = self.request.user
# return super().form_valid(form)
class EmployeeLineListView(LoginRequiredMixin, UserPassesTestMixin, ListView):
model = LinesModel
template_name = 'employee/line-list.html'
context_object_name = 'objects'
paginate_by = 3
# base_template = 'employee-base.html'
def test_func(self):
return True # self.request.user.has_employee_perm('can_view_preinfo') or self.request.user.user_type == 'CA'
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context['base_template'] = self.base_template
# return context
def get_queryset(self):
queryset = super().get_queryset()
data_filter = self.request.GET.get('filter')
if data_filter != 'all':
queryset = queryset.filter(active=True)
queryset = queryset.order_by('name')
return queryset
class EmployeeLineCreateView(LoginRequiredMixin, UserPassesTestMixin, CreateView):
model = LinesModel
template_name = 'employee/company-create.html'
form_class = LineCreateForm
success_url = reverse_lazy('employee_line')
def test_func(self):
return True # self.request.user.has_company_perm('can_create_preinfo') or self.request.user.user_type == 'CA'
# def form_valid(self, form):
# form.instance.created_by = self.request.user
# form.instance.updated_by = self.request.user
# return super().form_valid(form)
class EmployeeLineUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = LinesModel
template_name = 'employee/company-update.html'
form_class = LineUpdateForm
success_url = reverse_lazy('employee_line')
def test_func(self):
return True # self.request.user.ha.s_company_perm('can_edit_preinfo') or self.request.user.user_type == 'CA'
# def form_valid(self, form):
# form.instance.updated_by = self.request.user
# return super().form_valid(form)

@ -1,54 +0,0 @@
from django.urls import reverse_lazy
from django.views.generic import TemplateView, RedirectView
from django.shortcuts import render
from accounts.models import DepotUser
from booking.models import Booking
from containers.models import Container
from preinfo.models import Preinfo
# Create your views here.
class IndexView(TemplateView):
template_name = 'landing-page.html'
extra_context = {
'title': 'Home',
'description': 'This is the home page of the container application.',
}
def get(self, request, *args, **kwargs):
return render(request, self.template_name, self.extra_context)
class DashboardRedirectView(RedirectView):
is_permanent = False
def get_redirect_url(self, *args, **kwargs):
# if self.request.user.is_authenticated:
if self.request.user.user_type == DepotUser.UserType.COMPANY_ADMIN:
return reverse_lazy('client_dashboard')
elif self.request.user.user_type == DepotUser.UserType.CLIENT:
return reverse_lazy('client_dashboard')
elif self.request.user.user_type == DepotUser.UserType.BARRIER_STAFF:
return reverse_lazy('barrier_dashboard')
elif self.request.user.user_type == DepotUser.UserType.EMPLOYEE:
return reverse_lazy('employee_dashboard')
elif self.request.user.user_type == DepotUser.UserType.ADMIN:
return reverse_lazy('employee_dashboard')
return reverse_lazy('index')
# class ClientPreinfoView(TemplateView):
# template_name = 'client-preinfo-content.html'
#
#
#
# def get(self, request, *args, **kwargs):
# return render(request, self.template_name, self.extra_context)

@ -1,7 +0,0 @@
from django.urls import path
from .views import validate_container
urlpatterns = [
# ... your other urls
path('validate-container/<str:number>/', validate_container, name='validate_container'),
]

@ -1,6 +0,0 @@
from django.http import JsonResponse
from common.utils.utils import check_container_number_validity
def validate_container(request, number):
is_valid = check_container_number_validity(number)
return JsonResponse({'valid': is_valid})

@ -1,3 +0,0 @@
from django.contrib import admin
# Register your models here.

@ -1,6 +0,0 @@
from django.apps import AppConfig
class ContainersConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "containers"

@ -1,65 +0,0 @@
from django.forms import ModelForm, TextInput, CharField, Form
from containers.models import Container
class ContainerBaseForm(ModelForm):
class Meta:
model = Container
fields = '__all__'
widgets = {
'number': TextInput(attrs={
'oninput': 'validateContainerNumber(this)',
'class': 'form-control'
})
}
class ContainerReceiveForm(ContainerBaseForm):
"""
Form for creating a new Container instance.
Inherits from ContainerBaseForm.
"""
class Meta(ContainerBaseForm.Meta):
fields = ['number', 'receive_vehicle', 'damages', 'heavy_damaged', 'position',]
def __init__(self, *args, preinfo=None, **kwargs):
super().__init__(*args, **kwargs)
# Add form-control class to all fields
for field in self.fields.values():
field.widget.attrs.update({'class': 'form-control'})
# Checkbox specific styling
self.fields['heavy_damaged'].widget.attrs.update({'class': 'form-check-input'})
# If preinfo is provided, set initial values and make fields readonly
if preinfo:
...
# self.fields['preinfo'].initial = preinfo.container_number
# self.fields['number'].initial = preinfo.number
# self.fields['container_number'].widget.attrs['readonly'] = True
# self.fields['number'].widget.attrs['readonly'] = True
# self.fields['booking'].disabled = True
# self.fields['number'].disabled = True
class ContainerExpeditionForm(ContainerBaseForm):
class Meta(ContainerBaseForm.Meta):
fields = ['number', 'expedition_vehicle', 'position', 'line', 'container_type', 'damages', 'heavy_damaged']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
readonly_fields = ['number', 'position', 'line', 'container_type', 'damages', 'heavy_damaged']
for field in readonly_fields:
self.fields[field].widget.attrs['readonly'] = 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,112 +0,0 @@
# Generated by Django 5.2.3 on 2025-06-27 10:20
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
("booking", "0001_initial"),
("common", "0001_initial"),
]
operations = [
migrations.CreateModel(
name="Container",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("number", models.CharField(max_length=11)),
("received_on", models.DateTimeField(auto_now_add=True)),
("received_by", models.IntegerField()),
(
"receive_vehicles",
models.CharField(blank=True, max_length=100, null=True),
),
("damages", models.TextField(blank=True, null=True)),
("heavy_damaged", models.BooleanField(default=False)),
("position", models.CharField(blank=True, max_length=100, null=True)),
("swept", models.BooleanField(default=False)),
("swept_on", models.DateTimeField(blank=True, null=True)),
("swept_by", models.IntegerField(blank=True, null=True)),
("washed", models.BooleanField(default=False)),
("washed_on", models.DateTimeField(blank=True, null=True)),
("washed_by", models.IntegerField(blank=True, null=True)),
("expedited", models.BooleanField(default=False)),
("expedited_on", models.DateTimeField(blank=True, null=True)),
("expedited_by", models.IntegerField(blank=True, null=True)),
(
"expedition_vehicle",
models.CharField(blank=True, max_length=100, null=True),
),
(
"booking",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="container_bookings",
to="booking.bookingmodel",
),
),
(
"container_type",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="container_container_types",
to="common.containertypemodel",
),
),
(
"line_id",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="container_lines",
to="common.linesmodel",
),
),
],
),
migrations.CreateModel(
name="ContainerHistory",
fields=[
(
"container_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="containers.container",
),
),
("operation_date", models.DateTimeField(auto_now_add=True)),
(
"container",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="history_containers",
to="containers.container",
),
),
(
"operation",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="history_operations",
to="common.operationmodel",
),
),
],
bases=("containers.container",),
),
]

@ -1,18 +0,0 @@
# Generated by Django 5.2.3 on 2025-06-28 06:15
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("containers", "0001_initial"),
]
operations = [
migrations.RenameField(
model_name="container",
old_name="receive_vehicles",
new_name="receive_vehicle",
),
]

@ -1,26 +0,0 @@
# Generated by Django 5.2.3 on 2025-06-28 08:36
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("booking", "0001_initial"),
("containers", "0002_rename_receive_vehicles_container_receive_vehicle"),
]
operations = [
migrations.AlterField(
model_name="container",
name="booking",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="container_bookings",
to="booking.bookingmodel",
),
),
]

@ -1,18 +0,0 @@
# Generated by Django 5.2.3 on 2025-06-28 12:23
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("containers", "0003_alter_container_booking"),
]
operations = [
migrations.RenameField(
model_name="container",
old_name="line_id",
new_name="line",
),
]

@ -1,58 +0,0 @@
# Generated by Django 5.2.3 on 2025-06-28 12:30
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("containers", "0004_rename_line_id_container_line"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AlterField(
model_name="container",
name="expedited_by",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="container_expedited_by",
to=settings.AUTH_USER_MODEL,
),
),
migrations.AlterField(
model_name="container",
name="received_by",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="container_received_by",
to=settings.AUTH_USER_MODEL,
),
),
migrations.AlterField(
model_name="container",
name="swept_by",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="container_swept_by",
to=settings.AUTH_USER_MODEL,
),
),
migrations.AlterField(
model_name="container",
name="washed_by",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="container_washed_by",
to=settings.AUTH_USER_MODEL,
),
),
]

@ -1,48 +0,0 @@
# Generated by Django 5.2.3 on 2025-07-11 17:43
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("containers", "0005_alter_container_expedited_by_and_more"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name="ContainerPhotos",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("photo", models.TextField(blank=True, null=True)),
("uploaded_on", models.DateTimeField(auto_now_add=True)),
(
"container",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="photos",
to="containers.container",
),
),
(
"uploaded_by",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="container_photos_uploaded_by",
to=settings.AUTH_USER_MODEL,
),
),
],
),
]

@ -1,26 +0,0 @@
# Generated by Django 5.2.3 on 2025-07-18 07:05
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("containers", "0006_containerphotos"),
("preinfo", "0003_rename_preinfomodel_preinfo"),
]
operations = [
migrations.AddField(
model_name="container",
name="preinfo",
field=models.ForeignKey(
default=1,
on_delete=django.db.models.deletion.CASCADE,
related_name="container_preinfo",
to="preinfo.preinfo",
),
preserve_default=False,
),
]

@ -1,97 +0,0 @@
from django.db import models
from common.models import LinesModel, ContainerTypeModel
# from payments.models import ContainerTariffPeriod, AdditionalFees
# Create your models here.
class Container(models.Model):
number = models.CharField(max_length=11)
line = models.ForeignKey(
LinesModel,
on_delete=models.CASCADE,
related_name='container_lines',
)
container_type = models.ForeignKey(
ContainerTypeModel,
on_delete=models.CASCADE,
related_name='container_container_types',
)
received_on = models.DateTimeField(auto_now_add=True)
received_by = models.ForeignKey(
'accounts.DepotUser',
on_delete=models.CASCADE,
related_name='container_received_by',
)
receive_vehicle = models.CharField(max_length=100, blank=True, null=True)
damages = models.TextField(blank=True, null=True)
heavy_damaged = models.BooleanField(default=False)
position = models.CharField(max_length=100, blank=True, null=True)
swept = models.BooleanField(default=False)
swept_on = models.DateTimeField(blank=True, null=True)
swept_by = models.ForeignKey(
'accounts.DepotUser',
on_delete=models.CASCADE,
related_name='container_swept_by',
blank=True,
null=True
)
washed = models.BooleanField(default=False)
washed_on = models.DateTimeField(blank=True, null=True)
washed_by = models.ForeignKey(
'accounts.DepotUser',
on_delete=models.CASCADE,
related_name='container_washed_by',
blank=True,
null=True
)
preinfo = models.ForeignKey(
'preinfo.Preinfo',
on_delete=models.CASCADE,
related_name='container_preinfo',
)
booking = models.ForeignKey(
'booking.Booking',
on_delete=models.CASCADE,
related_name='container_bookings',
blank = True,
null = True
)
expedited = models.BooleanField(default=False)
expedited_on = models.DateTimeField(blank=True, null=True)
expedited_by = models.ForeignKey(
'accounts.DepotUser',
on_delete=models.CASCADE,
related_name='container_expedited_by',
blank=True,
null=True
)
expedition_vehicle = models.CharField(max_length=100, blank=True, null=True)
class ContainerHistory(Container):
operation = models.ForeignKey(
'common.OperationModel',
on_delete=models.CASCADE,
related_name='history_operations',
)
operation_date = models.DateTimeField(auto_now_add=True)
container = models.ForeignKey(
Container,
on_delete=models.CASCADE,
related_name='history_containers'
)
class ContainerPhotos(models.Model):
container = models.ForeignKey(
Container,
on_delete=models.CASCADE,
related_name='photos',
)
photo = models.TextField(blank=True, null=True)
uploaded_on = models.DateTimeField(auto_now_add=True)
uploaded_by = models.ForeignKey(
'accounts.DepotUser',
on_delete=models.CASCADE,
related_name='container_photos_uploaded_by',
)

@ -1,17 +0,0 @@
from booking.models import Booking
from containers.models import Container
def get_container_for_booking(booking):
filters = {
'expedited': False,
}
# booking = Booking.objects.get(number=booking_number)
if not booking:
return None
if booking.container_number:
filters['container_number'] = booking.container_number
else:
filters['container_type'] = booking.container_type
filters['line'] = booking.line
return Container.objects.filter(**filters).order_by('received_on').first()

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

@ -1,20 +0,0 @@
from django.urls import include, path
from containers.views.common import ContainerDetails
from containers.views.employee_views import ContainersListView, ReportContainersUnpaidListView
from containers.views.barrier_views import ContainerReceiveView, ContainerExpedition, ContainerSearchView, \
ContainerPhotosView
urlpatterns = [
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('search/', ContainerSearchView.as_view(), name='container_search'),
path('employee/', ContainersListView.as_view(), name='employee_containers'),
path('not-paid', ReportContainersUnpaidListView.as_view(), name='not_paid'),
path('barrier/receive/', ContainerReceiveView.as_view(), name='container_receive'),
path('barrier/expedition/', ContainerExpedition.as_view(), name='container_expedition'),
path('barrier/<int:pk>/', include([
path('photos/', ContainerPhotosView.as_view(), name='container_photos'),
])),
]

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save