Compare commits

...

9 Commits

@ -0,0 +1,33 @@
# Git
.git
.gitignore
# Docker
.dockerignore
docker-compose.yml
docker-compose.dev.yml
docker-compose-deploy.yml
docker-compose with volume.yml
dockerfile
*.tar
# Python
.venv
__pycache__
*.pyc
# Database
db.sqlite3
*.bak
# IDE
.idea
# Environment files
.env
production.env
# Other
gemini.cmd - Shortcut.lnk
images/

@ -16,7 +16,7 @@
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" /> <excludeFolder url="file://$MODULE_DIR$/.venv" />
</content> </content>
<orderEntry type="jdk" jdkName="Python 3.13 (DepoT)" jdkType="Python SDK" /> <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
<component name="TemplatesService"> <component name="TemplatesService">

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="dataSourceStorageLocal" created-in="PY-242.23339.19">
<data-source name="depot@localhost" uuid="2186be09-0cb1-4210-bad0-d279af5e6702">
<database-info product="PostgreSQL" version="17.4" jdbc-version="4.2" driver-name="PostgreSQL JDBC Driver" driver-version="42.7.3" dbms="POSTGRES" exact-version="17.4" exact-driver-version="42.7">
<identifier-quote-string>&quot;</identifier-quote-string>
</database-info>
<case-sensitivity plain-identifiers="lower" quoted-identifiers="exact" />
<secret-storage>master_key</secret-storage>
<user-name>postgres</user-name>
<schema-mapping>
<introspection-scope>
<node kind="database" qname="@">
<node kind="schema" qname="@" />
</node>
</introspection-scope>
</schema-mapping>
</data-source>
</component>
</project>

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="depot@localhost" uuid="2186be09-0cb1-4210-bad0-d279af5e6702">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://localhost:5432/depot</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

File diff suppressed because it is too large Load Diff

@ -3,4 +3,5 @@
<component name="Black"> <component name="Black">
<option name="sdkName" value="Python 3.13 (DepoT)" /> <option name="sdkName" value="Python 3.13 (DepoT)" />
</component> </component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13 (depot_django)" project-jdk-type="Python SDK" />
</project> </project>

@ -5,39 +5,12 @@
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="7410a44d-51b9-408b-85ad-4fa46776b372" name="Changes" comment="commit unversioned files ;)"> <list default="true" id="7410a44d-51b9-408b-85ad-4fa46776b372" name="Changes" comment="commit unversioned files ;)">
<change afterPath="$PROJECT_DIR$/accounts/migrations/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" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/booking/models.py" beforeDir="false" afterPath="$PROJECT_DIR$/booking/models.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/booking/tests.py" beforeDir="false" afterPath="$PROJECT_DIR$/booking/tests.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/booking/views/client_views.py" beforeDir="false" afterPath="$PROJECT_DIR$/booking/views/client_views.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/containers/tests.py" beforeDir="false" afterPath="$PROJECT_DIR$/containers/tests.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/readme.md" beforeDir="false" afterPath="$PROJECT_DIR$/readme.md" afterDir="false" />
</list> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
@ -69,49 +42,167 @@
<option name="hideEmptyMiddlePackages" value="true" /> <option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" /> <option name="showLibraryContents" value="true" />
</component> </component>
<component name="PropertiesComponent">{ <component name="PropertiesComponent"><![CDATA[{
&quot;keyToString&quot;: { "keyToString": {
&quot;DefaultHtmlFileTemplate&quot;: &quot;HTML File&quot;, "ASKED_MARK_IGNORED_FILES_AS_EXCLUDED": "true",
&quot;Django Server.DepoT.executor&quot;: &quot;Debug&quot;, "DefaultHtmlFileTemplate": "HTML File",
&quot;RunOnceActivity.OpenDjangoStructureViewOnStart&quot;: &quot;true&quot;, "Django Server.DepoT.executor": "Run",
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;, "Django tests.Test: booking.tests.BookingViewsTestCase.executor": "Run",
&quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;, "Django tests.Test: booking.tests.BookingViewsTestCase.test_booking_list_view.executor": "Debug",
&quot;RunOnceActivity.pycharm.django.structure.promotion.once.per.project&quot;: &quot;true&quot;, "Django tests.Test: booking.tests.BookingViewsTestCase.test_booking_list_view__user_all_rights__expect_OK.executor": "Profiler",
&quot;django.template.preview.state&quot;: &quot;SHOW_EDITOR_AND_PREVIEW&quot;, "Django tests.Test: booking.tests.BookingViewsTestCase.test_booking_list_view__user_no_rights__expect_forbidden.executor": "Debug",
&quot;git-widget-placeholder&quot;: &quot;master&quot;, "Django tests.Test: booking.tests.BookingViewsTestCase.test_booking_update_view.executor": "Debug",
&quot;ignore.virus.scanning.warn.message&quot;: &quot;true&quot;, "Django tests.Test: preinfo.test_views.PreinfoViewsTest.executor": "Run",
&quot;last_opened_file_path&quot;: &quot;C:/dev_projects/python/Django/DepoT&quot;, "RunOnceActivity.OpenDjangoStructureViewOnStart": "true",
&quot;list.type.of.created.stylesheet&quot;: &quot;CSS&quot;, "RunOnceActivity.ShowReadmeOnStart": "true",
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;, "RunOnceActivity.git.unshallow": "true",
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;, "RunOnceActivity.pycharm.django.structure.promotion.once.per.project": "true",
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;, "django.template.preview.state": "SHOW_EDITOR",
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;, "git-widget-placeholder": "deploy__branch",
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;, "ignore.virus.scanning.warn.message": "true",
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot; "last_opened_file_path": "C:/dev_projects/python/django/depot_django/containers",
"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",
"settings.editor.selected.configurable": "preferences.editor",
"vue.rearranger.settings.migration": "true"
}, },
&quot;keyToStringList&quot;: { "keyToStringList": {
&quot;DatabaseDriversLRU&quot;: [ "DatabaseDriversLRU": [
&quot;postgresql&quot; "postgresql"
] ]
} }
}</component> }]]></component>
<component name="RecentsManager"> <component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS"> <key name="CopyFile.RECENT_KEYS">
<recent name="C:\dev_projects\python\django\depot_django\containers" />
<recent name="C:\dev_projects\python\Django\DepoT\DepoT\settings" />
<recent name="C:\dev_projects\python\Django\DepoT" /> <recent name="C:\dev_projects\python\Django\DepoT" />
<recent name="C:\dev_projects\python\Django\DepoT\minio_backend" />
<recent name="C:\dev_projects\python\Django\DepoT\templates\employee" /> <recent name="C:\dev_projects\python\Django\DepoT\templates\employee" />
<recent name="C:\dev_projects\python\Django\DepoT\templates\client" />
<recent name="C:\dev_projects\python\Django\DepoT\templates" />
<recent name="C:\dev_projects\python\Django\DepoT\templates\registration" />
</key> </key>
<key name="MoveFile.RECENT_KEYS"> <key name="MoveFile.RECENT_KEYS">
<recent name="C:\dev_projects\python\Django\DepoT\images" />
<recent name="C:\dev_projects\python\Django\DepoT\unused templates" />
<recent name="C:\dev_projects\python\Django\DepoT\DepoT" />
<recent name="C:\dev_projects\python\Django\DepoT" /> <recent name="C:\dev_projects\python\Django\DepoT" />
<recent name="C:\dev_projects\python\Django\DepoT\templates\employee" /> <recent name="C:\dev_projects\python\Django\DepoT\templates\employee" />
<recent name="C:\dev_projects\python\Django\DepoT\templates\client" />
<recent name="C:\dev_projects\python\Django\DepoT\common\views" />
<recent name="C:\dev_projects\python\Django\DepoT\booking\views" />
</key> </key>
</component> </component>
<component name="RunManager"> <component name="RunManager" selected="Django tests.Test: preinfo.test_views.PreinfoViewsTest">
<configuration name="Test: booking.tests.BookingViewsTestCase" type="DjangoTestsConfigurationType" temporary="true">
<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.development" />
</envs>
<option name="SDK_HOME" value="$PROJECT_DIR$/.venv/Scripts/python.exe" />
<option name="SDK_NAME" value="Python 3.13 (depot_django)" />
<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" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="TARGET" value="booking.tests.BookingViewsTestCase" />
<option name="SETTINGS_FILE" value="" />
<option name="CUSTOM_SETTINGS" value="false" />
<option name="USE_OPTIONS" value="false" />
<option name="OPTIONS" value="" />
<method v="2" />
</configuration>
<configuration name="Test: booking.tests.BookingViewsTestCase.test_booking_list_view__user_all_rights__expect_OK" type="DjangoTestsConfigurationType" temporary="true">
<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" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="TARGET" value="booking.tests.BookingViewsTestCase.test_booking_list_view__user_all_rights__expect_OK" />
<option name="SETTINGS_FILE" value="" />
<option name="CUSTOM_SETTINGS" value="false" />
<option name="USE_OPTIONS" value="false" />
<option name="OPTIONS" value="" />
<method v="2" />
</configuration>
<configuration name="Test: booking.tests.BookingViewsTestCase.test_booking_list_view__user_no_rights__expect_forbidden" type="DjangoTestsConfigurationType" temporary="true">
<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.development" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="TARGET" value="booking.tests.BookingViewsTestCase.test_booking_list_view__user_no_rights__expect_forbidden" />
<option name="SETTINGS_FILE" value="" />
<option name="CUSTOM_SETTINGS" value="false" />
<option name="USE_OPTIONS" value="false" />
<option name="OPTIONS" value="" />
<method v="2" />
</configuration>
<configuration name="Test: booking.tests.BookingViewsTestCase.test_booking_update_view" type="DjangoTestsConfigurationType" temporary="true">
<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.development" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="TARGET" value="booking.tests.BookingViewsTestCase.test_booking_update_view" />
<option name="SETTINGS_FILE" value="" />
<option name="CUSTOM_SETTINGS" value="false" />
<option name="USE_OPTIONS" value="false" />
<option name="OPTIONS" value="" />
<method v="2" />
</configuration>
<configuration name="Test: preinfo.test_views.PreinfoViewsTest" type="DjangoTestsConfigurationType" temporary="true">
<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.development" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="TARGET" value="preinfo.test_views.PreinfoViewsTest" />
<option name="SETTINGS_FILE" value="" />
<option name="CUSTOM_SETTINGS" value="false" />
<option name="USE_OPTIONS" value="false" />
<option name="OPTIONS" value="" />
<method v="2" />
</configuration>
<configuration name="DepoT" type="Python.DjangoServer" factoryName="Django server"> <configuration name="DepoT" type="Python.DjangoServer" factoryName="Django server">
<module name="DepoT" /> <module name="DepoT" />
<option name="ENV_FILES" value="" /> <option name="ENV_FILES" value="" />
@ -119,9 +210,10 @@
<option name="PARENT_ENVS" value="true" /> <option name="PARENT_ENVS" value="true" />
<envs> <envs>
<env name="PYTHONUNBUFFERED" value="1" /> <env name="PYTHONUNBUFFERED" value="1" />
<env name="DJANGO_SETTINGS_MODULE" value="DepoT.settings" /> <env name="DJANGO_SETTINGS_MODULE" value="DepoT.settings.development" />
</envs> </envs>
<option name="SDK_HOME" value="" /> <option name="SDK_HOME" value="" />
<option name="SDK_NAME" value="Python 3.13 (depot_django)" />
<option name="WORKING_DIRECTORY" value="" /> <option name="WORKING_DIRECTORY" value="" />
<option name="IS_MODULE_SDK" value="false" /> <option name="IS_MODULE_SDK" value="false" />
<option name="ADD_CONTENT_ROOTS" value="true" /> <option name="ADD_CONTENT_ROOTS" value="true" />
@ -137,12 +229,29 @@
<option name="customRunCommand" value="" /> <option name="customRunCommand" value="" />
<method v="2" /> <method v="2" />
</configuration> </configuration>
<list>
<item itemvalue="Django Server.DepoT" />
<item itemvalue="Django tests.Test: preinfo.test_views.PreinfoViewsTest" />
<item itemvalue="Django tests.Test: booking.tests.BookingViewsTestCase.test_booking_update_view" />
<item itemvalue="Django tests.Test: booking.tests.BookingViewsTestCase.test_booking_list_view__user_all_rights__expect_OK" />
<item itemvalue="Django tests.Test: booking.tests.BookingViewsTestCase.test_booking_list_view__user_no_rights__expect_forbidden" />
<item itemvalue="Django tests.Test: booking.tests.BookingViewsTestCase" />
</list>
<recent_temporary>
<list>
<item itemvalue="Django tests.Test: preinfo.test_views.PreinfoViewsTest" />
<item itemvalue="Django tests.Test: booking.tests.BookingViewsTestCase" />
<item itemvalue="Django tests.Test: booking.tests.BookingViewsTestCase.test_booking_update_view" />
<item itemvalue="Django tests.Test: booking.tests.BookingViewsTestCase.test_booking_list_view__user_all_rights__expect_OK" />
<item itemvalue="Django tests.Test: booking.tests.BookingViewsTestCase.test_booking_list_view__user_no_rights__expect_forbidden" />
</list>
</recent_temporary>
</component> </component>
<component name="SharedIndexes"> <component name="SharedIndexes">
<attachedChunks> <attachedChunks>
<set> <set>
<option value="bundled-js-predefined-d6986cc7102b-09060db00ec0-JavaScript-PY-251.26927.74" /> <option value="bundled-js-predefined-d6986cc7102b-5c90d61e3bab-JavaScript-PY-242.23339.19" />
<option value="bundled-python-sdk-657d8234b839-64d779b69b7a-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-251.26927.74" /> <option value="bundled-python-sdk-0029f7779945-399fe30bd8c1-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-242.23339.19" />
</set> </set>
</attachedChunks> </attachedChunks>
</component> </component>
@ -161,7 +270,10 @@
<workItem from="1753175068058" duration="4635000" /> <workItem from="1753175068058" duration="4635000" />
<workItem from="1753179863298" duration="8357000" /> <workItem from="1753179863298" duration="8357000" />
<workItem from="1753197869497" duration="98156000" /> <workItem from="1753197869497" duration="98156000" />
<workItem from="1753637487803" duration="47474000" /> <workItem from="1753637487803" duration="71422000" />
<workItem from="1754046655407" duration="19056000" />
<workItem from="1754068089083" duration="32822000" />
<workItem from="1754408990802" duration="13021000" />
</task> </task>
<task id="LOCAL-00001" summary="Add IntelliJ IDEA project configuration files&#10;&#10;This commit adds IntelliJ IDEA-specific configuration files for the project, including module setup, version control integration, inspection profiles, and workspace settings. These files facilitate development environment configuration for contributors using IntelliJ IDEA."> <task id="LOCAL-00001" summary="Add IntelliJ IDEA project configuration files&#10;&#10;This commit adds IntelliJ IDEA-specific configuration files for the project, including module setup, version control integration, inspection profiles, and workspace settings. These files facilitate development environment configuration for contributors using IntelliJ IDEA.">
<option name="closed" value="true" /> <option name="closed" value="true" />
@ -282,19 +394,34 @@
<option name="timeStamp" value="55" /> <option name="timeStamp" value="55" />
</line-breakpoint> </line-breakpoint>
<line-breakpoint enabled="true" suspend="THREAD" type="python-line"> <line-breakpoint enabled="true" suspend="THREAD" type="python-line">
<url>file://$PROJECT_DIR$/common/views/client_views.py</url> <url>file://$PROJECT_DIR$/preinfo/test_views.py</url>
<line>118</line> <line>50</line>
<option name="timeStamp" value="61" /> <option name="timeStamp" value="130" />
</line-breakpoint> </line-breakpoint>
<line-breakpoint enabled="true" suspend="THREAD" type="python-line"> <line-breakpoint enabled="true" suspend="THREAD" type="python-line">
<url>file://$PROJECT_DIR$/payments/views.py</url> <url>file://$PROJECT_DIR$/preinfo/test_views.py</url>
<line>41</line> <line>94</line>
<option name="timeStamp" value="98" /> <option name="timeStamp" value="138" />
</line-breakpoint> </line-breakpoint>
<line-breakpoint enabled="true" suspend="THREAD" type="python-line"> <line-breakpoint enabled="true" suspend="THREAD" type="python-line">
<url>file://$PROJECT_DIR$/payments/views.py</url> <url>file://$PROJECT_DIR$/preinfo/test_views.py</url>
<line>56</line> <line>101</line>
<option name="timeStamp" value="99" /> <option name="timeStamp" value="139" />
</line-breakpoint>
<line-breakpoint enabled="true" suspend="THREAD" type="python-line">
<url>file://$PROJECT_DIR$/preinfo/test_views.py</url>
<line>107</line>
<option name="timeStamp" value="140" />
</line-breakpoint>
<line-breakpoint enabled="true" suspend="THREAD" type="python-line">
<url>file://$PROJECT_DIR$/preinfo/test_views.py</url>
<line>111</line>
<option name="timeStamp" value="141" />
</line-breakpoint>
<line-breakpoint enabled="true" suspend="THREAD" type="python-line">
<url>file://$PROJECT_DIR$/preinfo/test_views.py</url>
<line>117</line>
<option name="timeStamp" value="142" />
</line-breakpoint> </line-breakpoint>
<line-breakpoint enabled="true" type="javascript"> <line-breakpoint enabled="true" type="javascript">
<url>file://$PROJECT_DIR$/static/js/container_validation.js</url> <url>file://$PROJECT_DIR$/static/js/container_validation.js</url>
@ -311,4 +438,13 @@
</default-breakpoints> </default-breakpoints>
</breakpoint-manager> </breakpoint-manager>
</component> </component>
<component name="com.intellij.coverage.CoverageDataManagerImpl">
<SUITE FILE_PATH="coverage/DepoT$Test__booking_tests_BookingViewsTestCase_test_booking_list_view__user_no_rights__expect_forbidden.coverage" NAME="Test: booking.tests.BookingViewsTestCase.test_booking_list_view__user_no_rights__expect_forbidden Coverage Results" MODIFIED="1754372596969" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="" />
<SUITE FILE_PATH="coverage/depot_django$Test__booking_tests_BookingViewsTestCase_test_booking_update_view.coverage" NAME="Test: booking.tests.BookingViewsTestCase.test_booking_update_view Coverage Results" MODIFIED="1754420837053" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="" />
<SUITE FILE_PATH="coverage/depot_django$Test__booking_tests_BookingViewsTestCase.coverage" NAME="Test: booking.tests.BookingViewsTestCase Coverage Results" MODIFIED="1754420864513" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="" />
<SUITE FILE_PATH="coverage/depot_django$Test__preinfo_test_views_PreinfoViewsTest.coverage" NAME="Test: preinfo.test_views.PreinfoViewsTest Coverage Results" MODIFIED="1754425958620" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="" />
<SUITE FILE_PATH="coverage/DepoT$Test__booking_tests_BookingViewsTestCase_test_booking_list_view.coverage" NAME="Test: booking.tests.BookingViewsTestCase.test_booking_list_view Coverage Results" MODIFIED="1754333059580" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="" />
<SUITE FILE_PATH="coverage/DepoT$Test__booking_tests_BookingViewsTestCase.coverage" NAME="Test: booking.tests.BookingViewsTestCase Coverage Results" MODIFIED="1754386070100" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="" />
<SUITE FILE_PATH="coverage/DepoT$Test__booking_tests_BookingViewsTestCase_test_booking_list_view__user_all_rights__expect_OK.coverage" NAME="Test: booking.tests.BookingViewsTestCase.test_booking_list_view__user_all_rights__expect_OK Coverage Results" MODIFIED="1754372722226" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="" />
</component>
</project> </project>

@ -14,10 +14,10 @@ from pathlib import Path
import os import os
import environ import environ
BASE_DIR = Path(__file__).resolve().parent.parent BASE_DIR = Path(__file__).resolve().parent.parent.parent
env = environ.Env() env = environ.Env()
environ.Env.read_env(os.path.join(BASE_DIR, '.env')) env.read_env(os.path.join(BASE_DIR, '.env'))
SECRET_KEY = "django-insecure-g%187p84o9^rr)3#9@r3n^o2v1i%@6=+puxm7hlodg+kbsk%n#" SECRET_KEY = "django-insecure-g%187p84o9^rr)3#9@r3n^o2v1i%@6=+puxm7hlodg+kbsk%n#"

@ -0,0 +1,247 @@
"""
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
from minio_backend.storage import MinioStaticStorage, MinioMediaStorage
BASE_DIR = Path(__file__).resolve().parent.parent.parent
env = environ.Env()
env.read_env(os.path.join(BASE_DIR, 'production.env'))
SECRET_KEY = "django-insecure-g%187p84o9^rr)3#9@r3n^o2v1i%@6=+puxm7hlodg+kbsk%n#"
DEBUG = False
ALLOWED_HOSTS = ['192.168.24.43', '127.0.0.1', 'localhost', 'depot.kikimor.com', ]
CSRF_TRUSTED_ORIGINS = ['https://depot.kikimor.com']
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
USE_X_FORWARDED_HOST = True
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",
"django_minio_backend",
"minio_backend",
] + 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'
]
STATIC_ROOT = BASE_DIR / 'staticfiles'
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')
MINIO_EXTERNAL_ENDPOINT = AWS_S3_CUSTOM_DOMAIN
MINIO_EXTERNAL_ENDPOINT_USE_HTTPS = True
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
MINIO_USE_HTTPS = False # Add this line
MINIO_STATIC_BUCKET = env('MINIO_STATIC_BUCKET_NAME')
# django-minio-backend settings
MINIO_STORAGE_ENDPOINT = AWS_S3_CUSTOM_DOMAIN
MINIO_STORAGE_PORT = 443 if MINIO_SECURE else 80 # Add this line
MINIO_STORAGE_ACCESS_KEY = env('MINIO_ACCESS_KEY')
MINIO_STORAGE_SECRET_KEY = env('MINIO_SECRET_KEY')
MINIO_STORAGE_USE_HTTPS = True
MINIO_STORAGE_MEDIA_BUCKET_NAME = env('MINIO_BUCKET_NAME') # For user-uploaded media
MINIO_STORAGE_STATIC_BUCKET_NAME = env('MINIO_STATIC_BUCKET_NAME') # For static files
MINIO_STORAGE_AUTO_CREATE_MEDIA_BUCKET = True
MINIO_STORAGE_AUTO_CREATE_STATIC_BUCKET = True
MINIO_STORAGE_AUTO_CREATE_POLICY = True
MINIO_STORAGE_MEDIA_BASE_URL = f'{AWS_S3_URL_PROTOCOL}://{AWS_S3_CUSTOM_DOMAIN}/{env("MINIO_BUCKET_NAME")}/'
MINIO_STORAGE_STATIC_BASE_URL = f'{AWS_S3_URL_PROTOCOL}://{AWS_S3_CUSTOM_DOMAIN}/{env("MINIO_STATIC_BUCKET_NAME")}/'
MINIO_PRIVATE_BUCKETS = [] # If you have any private buckets
MINIO_PUBLIC_BUCKETS = [
env('MINIO_BUCKET_NAME'), # Just the bucket name as a string
env('MINIO_STATIC_BUCKET_NAME') # Just the bucket name as a string
]
MINIO_POLICY_ACTIONS = {
'GET': ['get_object'],
'PUT': ['put_object'],
'DELETE': ['delete_object'],
'LIST': ['list_multipart_uploads',
'list_parts',
'list_objects'],
}
STORAGES = {
"default": {
"BACKEND": "minio_backend.storage.MinioMediaStorage",
},
"staticfiles": {
"BACKEND": "minio_backend.storage.MinioStaticStorage",
},
}
DEFAULT_FILE_STORAGE = 'minio_backend.storage.MinioMediaStorage'
STATICFILES_STORAGE = 'minio_backend.storage.MinioStaticStorage'
STATIC_URL = f'{AWS_S3_URL_PROTOCOL}://{AWS_S3_CUSTOM_DOMAIN}/{env("MINIO_STATIC_BUCKET_NAME")}/'
STATICFILES_LOCATION = 'static'
MEDIA_ROOT = BASE_DIR / 'mediafiles'
MEDIA_URL = f'{AWS_S3_URL_PROTOCOL}://{AWS_S3_CUSTOM_DOMAIN}/{env("MINIO_BUCKET_NAME")}/'

@ -0,0 +1,5 @@
FROM minio/mc:latest
RUN test -f init_minio_buckets.sh && echo "init_minio_buckets.sh found in build context!" || (echo "init_minio_buckets.sh NOT found in build context!" && exit 1)
COPY init_minio_buckets.sh /usr/local/bin/init_minio_buckets.sh
RUN chmod +x /usr/local/bin/init_minio_buckets.sh
ENTRYPOINT ["/usr/local/bin/init_minio_buckets.sh"]

@ -0,0 +1,5 @@
FROM alpine/git
WORKDIR /build_context
COPY . .
RUN ls -la
CMD ["echo", "Build context listed."]

@ -5,7 +5,6 @@ from .models import DepotUser
@admin.register(DepotUser) @admin.register(DepotUser)
class DepotUserAdmin(UserAdmin): class DepotUserAdmin(UserAdmin):
# Add your custom fields to the fieldsets
fieldsets = UserAdmin.fieldsets + ( fieldsets = UserAdmin.fieldsets + (
('Additional Info', {'fields': ( ('Additional Info', {'fields': (
'phone_number', 'phone_number',
@ -16,12 +15,10 @@ class DepotUserAdmin(UserAdmin):
)}), )}),
) )
# Add fields to display in list view
list_display = ('username', 'email', 'user_type', 'company', 'line', 'is_active', 'is_staff', 'is_superuser') list_display = ('username', 'email', 'user_type', 'company', 'line', 'is_active', 'is_staff', 'is_superuser')
search_fields = ('username', 'email') search_fields = ('username', 'email')
list_filter = ('user_type', 'is_active', 'is_staff', 'is_superuser') list_filter = ('user_type', 'is_active', 'is_staff', 'is_superuser')
# Add fields to the add form
add_fieldsets = UserAdmin.add_fieldsets + ( add_fieldsets = UserAdmin.add_fieldsets + (
('Additional Info', {'fields': ( ('Additional Info', {'fields': (
'email', 'email',

@ -11,6 +11,5 @@ class CompanyUserBackend(ModelBackend):
perms = 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: 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_')} perms = {p for p in perms if p.startswith('accounts.client_')}
return perms return perms

@ -9,6 +9,8 @@ def create_permissions(apps, schema_editor):
(1, 'can_manage_containers', 'Can manage containers'), (1, 'can_manage_containers', 'Can manage containers'),
(2, 'can_view_reports', 'Can view reports'), (2, 'can_view_reports', 'Can view reports'),
(3, 'can_handle_operations', 'Can handle operations'), (3, 'can_handle_operations', 'Can handle operations'),
(4, 'can_view_payments', 'Can view payments'),
(5, 'can_manage_payments', 'Can Manage payments'),
] ]
for _id, codename, name in employee_permissions: for _id, codename, name in employee_permissions:
EmployeePermission.objects.create(id=_id, codename=codename, name=name) EmployeePermission.objects.create(id=_id, codename=codename, name=name)

@ -37,6 +37,8 @@ class EmployeePermission(models.Model):
('can_manage_containers', 'Can manage containers'), ('can_manage_containers', 'Can manage containers'),
('can_view_reports', 'Can view reports'), ('can_view_reports', 'Can view reports'),
('can_handle_operations', 'Can handle operations'), ('can_handle_operations', 'Can handle operations'),
('can_view_payments', 'Can view payments'),
('can_manage_payments', 'Can manage payments'),
) )
def __str__(self): def __str__(self):

@ -0,0 +1,11 @@
from django import template
register = template.Library()
@register.filter
def has_company_perm(user, perm_codename):
return user.has_company_perm(perm_codename)
@register.filter
def has_employee_perm(user, perm_codename):
return user.has_employee_perm(perm_codename)

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

@ -1,5 +1,5 @@
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.views import LoginView from django.contrib.auth.views import LoginView, PasswordChangeView
from django.http import HttpResponseForbidden, JsonResponse from django.http import HttpResponseForbidden, JsonResponse
from django.shortcuts import render from django.shortcuts import render
from django.urls import reverse_lazy from django.urls import reverse_lazy
@ -8,11 +8,11 @@ from django.views import View
from django.views.generic import TemplateView, FormView, ListView, UpdateView from django.views.generic import TemplateView, FormView, ListView, UpdateView
from rest_framework.generics import get_object_or_404 from rest_framework.generics import get_object_or_404
from accounts.forms import LoginForm, RegisterForm, UserChangePasswordForm from accounts.forms import LoginForm, RegisterForm, UserChangePasswordForm, UserEditForm
from accounts.models import DepotUser from accounts.models import DepotUser
from django.contrib.auth.decorators import login_required, user_passes_test from django.contrib.auth.decorators import login_required, user_passes_test
from django.contrib.auth.mixins import AccessMixin, LoginRequiredMixin from django.contrib.auth.mixins import AccessMixin, LoginRequiredMixin, UserPassesTestMixin
# Create your views here. # Create your views here.
@ -28,27 +28,21 @@ class DepotLoginView(LoginView):
def is_company_admin(user): def is_company_admin(user):
return user.is_authenticated and user.is_company_admin return user.is_authenticated and user.is_company_admin
@method_decorator(login_required, name='dispatch')
class RegisterView(AccessMixin, FormView): class RegisterView(LoginRequiredMixin, UserPassesTestMixin, FormView):
template_name = 'registration/register.html' template_name = 'registration/register.html'
form_class = RegisterForm form_class = RegisterForm
# model = get_user_model()
success_url = reverse_lazy('dashboard') success_url = reverse_lazy('dashboard')
def dispatch(self, request, *args, **kwargs): def test_func(self):
user: DepotUser = request.user user = self.request.user
return user.is_superuser or user.user_type == DepotUser.UserType.COMPANY_ADMIN
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): def form_valid(self, form):
# Create user from form data
user = form.save(commit=False) user = form.save(commit=False)
user_type = form.cleaned_data['user_type'] user_type = form.cleaned_data['user_type']
user.save() user.save()
# Clear irrelevant permissions based on user type
if user_type == DepotUser.UserType.CLIENT: if user_type == DepotUser.UserType.CLIENT:
user.employee_permissions.clear() user.employee_permissions.clear()
user.company_permissions.set(form.cleaned_data['company_permissions']) user.company_permissions.set(form.cleaned_data['company_permissions'])
@ -90,17 +84,15 @@ class RegisterView(AccessMixin, FormView):
return form return form
class UserListView(ListView): class UserListView(LoginRequiredMixin, UserPassesTestMixin, ListView):
template_name = 'registration/user-list.html' template_name = 'registration/user-list.html'
model = get_user_model() model = get_user_model()
context_object_name = 'objects' context_object_name = 'objects'
paginate_by = 30 # Number of containers per page paginate_by = 20
# base_template = 'employee-base.html'
# def get_context_data(self, **kwargs): def test_func(self):
# context = super().get_context_data(**kwargs) user = self.request.user
# context['base_template'] = self.base_template return user.is_superuser or user.user_type == DepotUser.UserType.COMPANY_ADMIN
# return context
def get_queryset(self): def get_queryset(self):
queryset = super().get_queryset() queryset = super().get_queryset()
@ -111,7 +103,6 @@ class UserListView(ListView):
if data_filter != 'all': if data_filter != 'all':
queryset = queryset.filter(is_active=True) queryset = queryset.filter(is_active=True)
# Filter users based on permissions
if user.is_superuser: if user.is_superuser:
return queryset.all() return queryset.all()
elif user.user_type == DepotUser.UserType.COMPANY_ADMIN: elif user.user_type == DepotUser.UserType.COMPANY_ADMIN:
@ -119,18 +110,15 @@ class UserListView(ListView):
else: else:
return queryset.none() return queryset.none()
class UserUpdateView(UpdateView): class UserUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
template_name = 'registration/register.html' template_name = 'registration/register.html'
form_class = RegisterForm form_class = UserEditForm
model = get_user_model() model = get_user_model()
success_url = reverse_lazy('user_list') success_url = reverse_lazy('user_list')
def dispatch(self, request, *args, **kwargs): def test_func(self):
user: DepotUser = request.user user = self.request.user
return user.is_superuser or user.user_type == DepotUser.UserType.COMPANY_ADMIN
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): def form_valid(self, form):
user = form.save(commit=False) user = form.save(commit=False)
@ -175,14 +163,16 @@ class UserUpdateView(UpdateView):
return form return form
class UserActiveView(LoginRequiredMixin, View): class UserActiveView(LoginRequiredMixin, UserPassesTestMixin, View):
success_url = reverse_lazy('user_list') success_url = reverse_lazy('user_list')
def test_func(self):
user = self.request.user
return user.is_superuser or user.user_type == DepotUser.UserType.COMPANY_ADMIN
def post(self, request, pk, *args, **kwargs): def post(self, request, pk, *args, **kwargs):
user = request.user 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) target_user = get_object_or_404(get_user_model(), pk=pk)
if target_user == user: if target_user == user:
return HttpResponseForbidden("You cannot change your own active status.") return HttpResponseForbidden("You cannot change your own active status.")
@ -192,7 +182,9 @@ class UserActiveView(LoginRequiredMixin, View):
return JsonResponse({'success': True, 'is_active': target_user.is_active}) return JsonResponse({'success': True, 'is_active': target_user.is_active})
class UserChangePasswordView(LoginRequiredMixin, View): class CustomPasswordChangeView(LoginRequiredMixin, PasswordChangeView):
template_name = 'registration/change_password.html' template_name = 'registration/change_password.html'
form_class = UserChangePasswordForm
success_url = reverse_lazy('home') def get_success_url(self):
next_url = self.request.GET.get('next') or self.request.POST.get('next')
return next_url or reverse_lazy('dashboard')

@ -1,4 +1,8 @@
from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from accounts.models import DepotUser
from common.fields import ContainerNumberField, UpperCaseCharField
from common.models import ContainerTypeModel, LinesModel, OperationModel from common.models import ContainerTypeModel, LinesModel, OperationModel
# Create your models here. # Create your models here.
@ -10,15 +14,15 @@ class Booking(models.Model):
('canceled', 'Canceled'), ('canceled', 'Canceled'),
] ]
number = models.CharField(max_length=50, unique=True) number = UpperCaseCharField(max_length=50)
vehicles = models.CharField(blank=True, null=True) vehicles = UpperCaseCharField(blank=True, null=True)
container_type = models.ForeignKey( container_type = models.ForeignKey(
ContainerTypeModel, ContainerTypeModel,
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='booking_container_types', related_name='booking_container_types',
) )
container_count = models.IntegerField() container_count = models.PositiveIntegerField()
container_expedited_count = models.IntegerField(default=0) container_expedited_count = models.PositiveIntegerField(default=0)
carrier = models.CharField(max_length=100, blank=True, null=True) carrier = models.CharField(max_length=100, blank=True, null=True)
line = models.ForeignKey( line = models.ForeignKey(
LinesModel, LinesModel,
@ -27,12 +31,12 @@ class Booking(models.Model):
) )
visible = models.BooleanField(default=True) visible = models.BooleanField(default=True)
is_new = models.BooleanField(default=True) is_new = models.BooleanField(default=True)
container_number = models.CharField(max_length=11, blank=True, null=True) container_number = ContainerNumberField(max_length=11, blank=True, null=True)
vehicles_left = models.CharField(blank=True, null=True) vehicles_left = models.CharField(blank=True, null=True)
created_on = models.DateTimeField(auto_now_add=True) created_on = models.DateTimeField(auto_now_add=True)
created_by = models.IntegerField() created_by = models.ForeignKey(DepotUser, related_name='booking_created_user', on_delete=models.CASCADE)
updated_on = models.DateTimeField(auto_now=True) updated_on = models.DateTimeField(auto_now=True)
updated_by = models.IntegerField() updated_by = models.ForeignKey(DepotUser, related_name='booking_updated_user', on_delete=models.CASCADE)
status = models.CharField( status = models.CharField(
max_length=10, max_length=10,
choices=STATUS_CHOICES, choices=STATUS_CHOICES,
@ -42,3 +46,10 @@ class Booking(models.Model):
@property @property
def containers_left(self): def containers_left(self):
return self.container_count - (self.container_expedited_count or 0) return self.container_count - (self.container_expedited_count or 0)
class Meta:
app_label = 'booking'
def clean(self):
if Booking.objects.filter(number=self.number, status='active').exclude(id=self.id).exists():
raise ValidationError(f'Booking with number {self.number} already exists and is active.')

@ -1,3 +1,99 @@
from django.test import TestCase from django.test import TestCase, Client
from django.urls import reverse, reverse_lazy
from django.contrib.auth import get_user_model
# Create your tests here. from accounts.models import ClientPermission
from booking.models import Booking
from common.models import LinesModel, ContainerTypeModel, CompanyModel
class BookingViewsTestCase(TestCase):
def setUp(self):
self.company = CompanyModel.objects.create(name='Test Company')
self.line = LinesModel.objects.create(name='Test Line', company_id=self.company.id)
self.container_type = ContainerTypeModel.objects.get(name='20GP')
self.user_password = 'password'
self.client = Client()
DepotUser = get_user_model()
self.user_client_no_rights = DepotUser.objects.create_user(username='user_client_no_rights', password=self.user_password, user_type='CL', email='user_client_no_rights@gmail.com')
self.user_client_all_rights = DepotUser.objects.create_user(username='user_client_all_rights', password=self.user_password, user_type='CL', email='user_client_all_rights@gmail.com', line=self.line, company=self.company)
self.user_client_all_rights.company_permissions.add(ClientPermission.objects.get(codename='can_view_booking').id)
self.user_client_all_rights.company_permissions.add(ClientPermission.objects.get(codename='can_manage_booking').id)
self.user_client_all_rights.company_permissions.add(ClientPermission.objects.get(codename='can_view_preinfo').id)
self.user_client_all_rights.company_permissions.add(ClientPermission.objects.get(codename='can_manage_preinfo').id)
self.user_client_all_rights.company_permissions.add(ClientPermission.objects.get(codename='can_view_payment').id)
self.user_client_all_rights.company_permissions.add(ClientPermission.objects.get(codename='can_manage_payment').id)
self.user_client_all_rights.company_permissions.add(ClientPermission.objects.get(codename='can_manage_company_users').id)
self.user_employee_no_rights = DepotUser.objects.create_user(username='user_employee_no_rights', password=self.user_password, user_type='EM', email='user_employee_no_rights@gmail.com')
self.user_employee_all_rights = DepotUser.objects.create_user(username='user_employee_all_rights', password=self.user_password, user_type='EM', email='user_employee_all_rights@gmail.com')
self.user_employee_all_rights.employee_permissions.add(1)
self.user_employee_all_rights.employee_permissions.add(2)
self.user_employee_all_rights.employee_permissions.add(3)
self.user_employee_all_rights.employee_permissions.add(4)
self.user_employee_all_rights.employee_permissions.add(5)
self.booking = Booking.objects.create(
number='BOOK123',
container_type=self.container_type,
container_count=10,
line=self.line,
created_by=self.user_client_all_rights,
updated_by=self.user_client_all_rights,
vehicles='K1,J2,K3',
vehicles_left='K1,J2,K3',
)
def test_employee_booking_list_view__anonymouse_user__expect_redirect_302(self):
response = self.client.get(reverse_lazy('employee_bookings'))
self.assertEqual(response.status_code, 302)
def test_employee_booking_list_view__client_user_login__expect_forbidden_403(self):
self.client.login(username=self.user_client_no_rights.username, password=self.user_password)
response = self.client.get(reverse_lazy('employee_bookings'))
self.assertEqual(response.status_code, 403)
def test_employee_booking_list_view__user_login__expect_redirect_200(self):
self.client.login(username=self.user_employee_no_rights.username, password=self.user_password)
response = self.client.get(reverse_lazy('employee_bookings'))
self.assertEqual(response.status_code, 200)
def test_booking_list_view__user_all_rights__expect_OK(self):
self.client.login(username=self.user_employee_all_rights.username, password=self.user_password)
response = self.client.get(reverse_lazy('employee_bookings'))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'employee/booking-list.html')
self.assertContains(response, self.booking.number)
def test_client_create_booking__expect_redirect_to_booking_list_and_booking_in_db(self):
self.assertTrue( self.user_client_all_rights.has_company_perm('can_manage_booking'))
self.client.login(username=self.user_client_all_rights.username, password=self.user_password)
response = self.client.post(reverse('client_booking_create'), {
'number': 'BOOK456',
'container_type': self.container_type.id,
'container_count': 5,
'line': self.line.id,
'vehicles': 'Truck1,Truck2',
'vehicles_left': 'Truck1,Truck2',
})
self.assertEqual(response.status_code, 302)
self.assertTrue(Booking.objects.filter(number='BOOK456').exists())
def test_booking_update_view__expect_redirect_and_booking_updated_in_db(self):
self.client.login(username=self.user_client_all_rights.username, password=self.user_password)
response = self.client.post(reverse('client_booking_update', args=[self.booking.id]), {
'number': 'BOOK123-updated',
'container_type': self.container_type.id,
'container_count': 15,
'line': self.line.id,
})
self.assertEqual(response.status_code, 302)
self.booking.refresh_from_db()
self.assertEqual(self.booking.number, 'BOOK123-UPDATED')

@ -1,6 +1,6 @@
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.views.generic import CreateView, ListView from django.views.generic import CreateView, ListView, UpdateView
from DepoT.mixins.LineFiltweFormMixin import LineFilterFormMixin from DepoT.mixins.LineFiltweFormMixin import LineFilterFormMixin
from booking.forms import BookingCreateForm, BookingUpdateForm from booking.forms import BookingCreateForm, BookingUpdateForm
@ -11,23 +11,23 @@ from common.utils.utils import filter_queryset_by_user
class ClientBookingView(LoginRequiredMixin, UserPassesTestMixin, ListView): class ClientBookingView(LoginRequiredMixin, UserPassesTestMixin, ListView):
model = Booking model = Booking
template_name = 'client/booking-list.html' template_name = 'client/booking-list.html'
paginate_by = 4 paginate_by = 20
context_object_name = 'objects' context_object_name = 'objects'
# base_template = 'client-base.html'
def test_func(self): def test_func(self):
return self.request.user.has_company_perm('can_view_booking') or self.request.user.user_type == 'CA' 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): def get_queryset(self):
queryset = super().get_queryset() queryset = super().get_queryset()
user = self.request.user user = self.request.user
result = filter_queryset_by_user(queryset, user) queryset = filter_queryset_by_user(queryset, user)
return result
data_filter = self.request.GET.get('filter')
if data_filter != 'all':
queryset = queryset.filter(status='active')
queryset = queryset.order_by('-created_on')
return queryset
class CreateBookingView(LoginRequiredMixin, UserPassesTestMixin, LineFilterFormMixin, CreateView): class CreateBookingView(LoginRequiredMixin, UserPassesTestMixin, LineFilterFormMixin, CreateView):
@ -38,16 +38,16 @@ class CreateBookingView(LoginRequiredMixin, UserPassesTestMixin, LineFilterFormM
def form_valid(self, form): def form_valid(self, form):
# todo more validation # todo more validation
form.instance.created_by = self.request.user.id form.instance.created_by = self.request.user
form.instance.updated_by = self.request.user.id form.instance.updated_by = self.request.user
form.instance.vehicles_left = form.cleaned_data.get('vehicles') form.instance.vehicles_left = form.cleaned_data.get('vehicles')
return super().form_valid(form) return super().form_valid(form)
def test_func(self): def test_func(self):
return True # self.request.user.has_company_perm('can_edit_preinfo') or self.request.user.user_type == 'CA' return self.request.user.has_company_perm('can_manage_booking') or self.request.user.user_type == 'CA'
class ClientBookingUpdateView(LoginRequiredMixin, UserPassesTestMixin, LineFilterFormMixin, CreateView): class ClientBookingUpdateView(LoginRequiredMixin, UserPassesTestMixin, LineFilterFormMixin, UpdateView):
template_name = 'client/booking-edit.html' template_name = 'client/booking-edit.html'
model = Booking model = Booking
form_class = BookingUpdateForm form_class = BookingUpdateForm
@ -55,8 +55,8 @@ class ClientBookingUpdateView(LoginRequiredMixin, UserPassesTestMixin, LineFilte
def form_valid(self, form): def form_valid(self, form):
# todo more validation # todo more validation
form.instance.updated_by = self.request.user.id form.instance.updated_by = self.request.user
return super().form_valid(form) return super().form_valid(form)
def test_func(self): def test_func(self):
return True # self.request.user.has_company_perm('can_edit_preinfo') or self.request.user.user_type == 'CA' return self.request.user.has_company_perm('can_manage_booking') or self.request.user.user_type == 'CA'

@ -1,19 +1,18 @@
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.views.generic import ListView from django.views.generic import ListView
from booking.models import Booking from booking.models import Booking
class BookingListView(ListView): class BookingListView(LoginRequiredMixin, UserPassesTestMixin, ListView):
template_name = 'employee/booking-list.html' template_name = 'employee/booking-list.html'
model = Booking model = Booking
context_object_name = 'objects' context_object_name = 'objects'
paginate_by = 30 # Number of containers per page paginate_by = 20
# base_template = 'employee-base.html'
# def get_context_data(self, **kwargs): def test_func(self):
# context = super().get_context_data(**kwargs) user = self.request.user
# context['base_template'] = self.base_template return self.request.user.user_type == 'EM' or user.is_superuser
# return context
def get_queryset(self): def get_queryset(self):
queryset = super().get_queryset() queryset = super().get_queryset()

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

Loading…
Cancel
Save