Compare commits
No commits in common. 'main' and 'master' have entirely different histories.
@ -0,0 +1,41 @@
|
||||
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"
|
||||
@ -1,162 +0,0 @@
|
||||
# ---> 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/
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
<?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="<map/>" />
|
||||
<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>
|
||||
@ -0,0 +1,12 @@
|
||||
<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>
|
||||
@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.13 (DepoT)" />
|
||||
</component>
|
||||
</project>
|
||||
@ -0,0 +1,8 @@
|
||||
<?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>
|
||||
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@ -0,0 +1,314 @@
|
||||
<?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)) import django; print('Django %s' % django.get_version()) sys.path.extend([WORKING_DIR_AND_PYTHON_PATHS]) if 'setup' in dir(django): django.setup() 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)) import django; print('Django %s' % django.get_version()) sys.path.extend([WORKING_DIR_AND_PYTHON_PATHS]) if 'setup' in dir(django): django.setup() 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">{
|
||||
"associatedIndex": 6
|
||||
}</component>
|
||||
<component name="ProjectId" id="2yxmkOyUBM7hzsgEZWENDFkS6jw" />
|
||||
<component name="ProjectViewState">
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent">{
|
||||
"keyToString": {
|
||||
"DefaultHtmlFileTemplate": "HTML File",
|
||||
"Django Server.DepoT.executor": "Debug",
|
||||
"RunOnceActivity.OpenDjangoStructureViewOnStart": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"RunOnceActivity.git.unshallow": "true",
|
||||
"RunOnceActivity.pycharm.django.structure.promotion.once.per.project": "true",
|
||||
"django.template.preview.state": "SHOW_EDITOR_AND_PREVIEW",
|
||||
"git-widget-placeholder": "master",
|
||||
"ignore.virus.scanning.warn.message": "true",
|
||||
"last_opened_file_path": "C:/dev_projects/python/Django/DepoT",
|
||||
"list.type.of.created.stylesheet": "CSS",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.detected.package.tslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
"node.js.selected.package.tslint": "(autodetect)",
|
||||
"nodejs_package_manager_path": "npm",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
},
|
||||
"keyToStringList": {
|
||||
"DatabaseDriversLRU": [
|
||||
"postgresql"
|
||||
]
|
||||
}
|
||||
}</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 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 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 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 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 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 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 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 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 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 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>
|
||||
@ -0,0 +1,16 @@
|
||||
"""
|
||||
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()
|
||||
@ -0,0 +1,15 @@
|
||||
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
|
||||
@ -0,0 +1,21 @@
|
||||
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
|
||||
@ -0,0 +1,188 @@
|
||||
"""
|
||||
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
|
||||
@ -0,0 +1,31 @@
|
||||
"""
|
||||
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')),
|
||||
]
|
||||
@ -0,0 +1,16 @@
|
||||
"""
|
||||
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,34 @@
|
||||
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',
|
||||
)}),
|
||||
)
|
||||
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class UsersConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "accounts"
|
||||
@ -0,0 +1,16 @@
|
||||
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
|
||||
@ -0,0 +1,96 @@
|
||||
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())
|
||||
@ -0,0 +1,182 @@
|
||||
# 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()),
|
||||
],
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,23 @@
|
||||
# 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),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,35 @@
|
||||
# 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,
|
||||
),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,43 @@
|
||||
# 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"),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,32 @@
|
||||
# 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,
|
||||
),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,29 @@
|
||||
# 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"),
|
||||
),
|
||||
},
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,12 @@
|
||||
# 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 = []
|
||||
@ -0,0 +1,44 @@
|
||||
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),
|
||||
]
|
||||
@ -0,0 +1,29 @@
|
||||
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),
|
||||
]
|
||||
@ -0,0 +1,115 @@
|
||||
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()
|
||||
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@ -0,0 +1,15 @@
|
||||
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'),
|
||||
])),
|
||||
]
|
||||
@ -0,0 +1,198 @@
|
||||
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')
|
||||
@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class BookingConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "booking"
|
||||
@ -0,0 +1,36 @@
|
||||
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'
|
||||
})
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
# 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",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,19 @@
|
||||
# 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",
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,18 @@
|
||||
# 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),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,26 @@
|
||||
# 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,
|
||||
),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,18 @@
|
||||
# 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),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,44 @@
|
||||
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)
|
||||
@ -0,0 +1,11 @@
|
||||
from django import template
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@register.filter
|
||||
def distinct_vehicles(vehicles):
|
||||
vehicles_set = set(vehicles.split(','))
|
||||
vehicles_str = ', '.join(sorted(vehicles_set))
|
||||
return vehicles_str
|
||||
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@ -0,0 +1,14 @@
|
||||
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'),
|
||||
]
|
||||
@ -0,0 +1,62 @@
|
||||
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'
|
||||
@ -0,0 +1,27 @@
|
||||
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
|
||||
@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class CommonConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "common"
|
||||
@ -0,0 +1,17 @@
|
||||
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):
|
||||
...
|
||||
@ -0,0 +1,17 @@
|
||||
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):
|
||||
...
|
||||
@ -0,0 +1,132 @@
|
||||
# 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,
|
||||
},
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,33 @@
|
||||
# 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),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,12 @@
|
||||
# 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 = []
|
||||
@ -0,0 +1,91 @@
|
||||
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),
|
||||
]
|
||||
@ -0,0 +1,33 @@
|
||||
# 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),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,46 @@
|
||||
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
|
||||
@ -0,0 +1,20 @@
|
||||
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 ''
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@ -0,0 +1,39 @@
|
||||
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'),
|
||||
])),
|
||||
]
|
||||
@ -0,0 +1,73 @@
|
||||
from minio import Minio
|
||||
from django.conf import settings
|
||||
from datetime import datetime, timedelta
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
import boto3
|
||||
from botocore.client import Config
|
||||
|
||||
client = Minio(
|
||||
settings.MINIO_ENDPOINT,
|
||||
access_key=settings.MINIO_ACCESS_KEY,
|
||||
secret_key=settings.MINIO_SECRET_KEY,
|
||||
secure=settings.MINIO_SECURE
|
||||
)
|
||||
|
||||
|
||||
def upload_damage_photo(photo_file, depot_id, content_type):
|
||||
try:
|
||||
s3_client = boto3.client('s3',
|
||||
endpoint_url=f'http://{settings.MINIO_ENDPOINT}',
|
||||
aws_access_key_id=settings.MINIO_ACCESS_KEY,
|
||||
aws_secret_access_key=settings.MINIO_SECRET_KEY,
|
||||
config=Config(signature_version='s3v4'),
|
||||
region_name='us-east-1'
|
||||
)
|
||||
|
||||
# Use the full filename as the key, which includes the depot_id folder
|
||||
s3_client.upload_fileobj(
|
||||
photo_file,
|
||||
settings.MINIO_BUCKET_NAME,
|
||||
photo_file.name,
|
||||
ExtraArgs={'ContentType': content_type}
|
||||
)
|
||||
|
||||
# Generate a presigned URL for the uploaded object
|
||||
url = s3_client.generate_presigned_url('get_object',
|
||||
Params={
|
||||
'Bucket': settings.MINIO_BUCKET_NAME,
|
||||
'Key': photo_file.name
|
||||
},
|
||||
ExpiresIn=3600
|
||||
)
|
||||
|
||||
return url
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error uploading to MinIO: {str(e)}")
|
||||
raise
|
||||
|
||||
|
||||
def get_damages(depot_id):
|
||||
try:
|
||||
objects = client.list_objects(
|
||||
settings.MINIO_BUCKET_NAME,
|
||||
prefix=f"{depot_id}/"
|
||||
)
|
||||
|
||||
damages = []
|
||||
for obj in objects:
|
||||
url = client.presigned_get_object(
|
||||
settings.MINIO_BUCKET_NAME,
|
||||
obj.object_name,
|
||||
expires=timedelta(days=7)
|
||||
)
|
||||
damages.append({
|
||||
"url": url,
|
||||
"filename": obj.object_name.split('/')[-1]
|
||||
})
|
||||
|
||||
return Response(damages, status=status.HTTP_200_OK)
|
||||
except Exception as e:
|
||||
print(f"Error listing objects from MinIO: {str(e)}")
|
||||
return Response([], status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
@ -0,0 +1,77 @@
|
||||
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)
|
||||
@ -0,0 +1,30 @@
|
||||
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
|
||||
@ -0,0 +1,89 @@
|
||||
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()
|
||||
@ -0,0 +1,17 @@
|
||||
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())
|
||||
@ -0,0 +1,140 @@
|
||||
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)
|
||||
@ -0,0 +1,140 @@
|
||||
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)
|
||||
@ -0,0 +1,54 @@
|
||||
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)
|
||||
@ -0,0 +1,7 @@
|
||||
from django.urls import path
|
||||
from .views import validate_container
|
||||
|
||||
urlpatterns = [
|
||||
# ... your other urls
|
||||
path('validate-container/<str:number>/', validate_container, name='validate_container'),
|
||||
]
|
||||
@ -0,0 +1,6 @@
|
||||
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})
|
||||
@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ContainersConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "containers"
|
||||
@ -0,0 +1,65 @@
|
||||
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'
|
||||
})
|
||||
)
|
||||
@ -0,0 +1,112 @@
|
||||
# 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",),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,18 @@
|
||||
# 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",
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,26 @@
|
||||
# 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",
|
||||
),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,18 @@
|
||||
# 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",
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,58 @@
|
||||
# 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,
|
||||
),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,48 @@
|
||||
# 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,26 @@
|
||||
# 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,
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,97 @@
|
||||
|
||||
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',
|
||||
)
|
||||
|
||||
@ -0,0 +1,17 @@
|
||||
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()
|
||||
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@ -0,0 +1,20 @@
|
||||
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…
Reference in New Issue