some fields validation and model cleaning

deploy_branch
Kiril Hadjiev 7 months ago
parent 06a3b105d3
commit f16ea7c748

@ -16,7 +16,7 @@
<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="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="TemplatesService">

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="dataSourceStorageLocal" created-in="PY-251.26927.74">
<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>

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

@ -5,14 +5,18 @@
</component>
<component name="ChangeListManager">
<list default="true" id="7410a44d-51b9-408b-85ad-4fa46776b372" name="Changes" comment="commit unversioned files ;)">
<change afterPath="$PROJECT_DIR$/common/fields.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/DepoT.iml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/DepoT.iml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/dataSources.local.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/dataSources.local.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/misc.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/DepoT/settings/development.py" beforeDir="false" afterPath="$PROJECT_DIR$/DepoT/settings/development.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/DepoT/settings/production.py" beforeDir="false" afterPath="$PROJECT_DIR$/DepoT/settings/production.py" 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/employee_views.py" beforeDir="false" afterPath="$PROJECT_DIR$/booking/views/employee_views.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/common/migrations/0004_populate_initial_data.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/migrations/0004_populate_initial_data.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$/common/models.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/models.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/images/web_depot.tar" beforeDir="false" afterPath="$PROJECT_DIR$/images/web_depot.tar" afterDir="false" />
<change beforePath="$PROJECT_DIR$/containers/models.py" beforeDir="false" afterPath="$PROJECT_DIR$/containers/models.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/payments/models.py" beforeDir="false" afterPath="$PROJECT_DIR$/payments/models.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/preinfo/models.py" beforeDir="false" afterPath="$PROJECT_DIR$/preinfo/models.py" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@ -67,6 +71,7 @@
"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"
},
"keyToStringList": {
@ -91,7 +96,7 @@
<recent name="C:\dev_projects\python\Django\DepoT\templates\employee" />
</key>
</component>
<component name="RunManager" selected="Django tests.Test: booking.tests.BookingViewsTestCase">
<component name="RunManager" selected="Django Server.DepoT">
<configuration name="Test: booking.tests.BookingViewsTestCase" type="DjangoTestsConfigurationType" temporary="true">
<module name="DepoT" />
<option name="ENV_FILES" value="" />
@ -101,9 +106,10 @@
<env name="PYTHONUNBUFFERED" value="1" />
<env name="DJANGO_SETTINGS_MODULE" value="DepoT.settings.development" />
</envs>
<option name="SDK_HOME" value="" />
<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="true" />
<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" />
@ -189,7 +195,7 @@
<env name="DJANGO_SETTINGS_MODULE" value="DepoT.settings.development" />
</envs>
<option name="SDK_HOME" value="" />
<option name="SDK_NAME" value="Python 3.13 (DepoT)" />
<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" />
@ -224,8 +230,8 @@
<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" />
<option value="bundled-js-predefined-d6986cc7102b-5c90d61e3bab-JavaScript-PY-242.23339.19" />
<option value="bundled-python-sdk-0029f7779945-399fe30bd8c1-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-242.23339.19" />
</set>
</attachedChunks>
</component>
@ -247,6 +253,7 @@
<workItem from="1753637487803" duration="71422000" />
<workItem from="1754046655407" duration="19056000" />
<workItem from="1754068089083" duration="32822000" />
<workItem from="1754408990802" duration="7521000" />
</task>
<task id="LOCAL-00001" summary="Add IntelliJ IDEA project configuration files&#10;&#10;This commit adds IntelliJ IDEA-specific configuration files for the project, including module setup, version control integration, inspection profiles, and workspace settings. These files facilitate development environment configuration for contributors using IntelliJ IDEA.">
<option name="closed" value="true" />
@ -368,28 +375,18 @@
</line-breakpoint>
<line-breakpoint enabled="true" suspend="THREAD" type="python-line">
<url>file://$PROJECT_DIR$/booking/tests.py</url>
<line>90</line>
<option name="timeStamp" value="109" />
<line>97</line>
<option name="timeStamp" value="127" />
</line-breakpoint>
<line-breakpoint enabled="true" suspend="THREAD" type="python-line">
<url>file://$PROJECT_DIR$/booking/tests.py</url>
<line>76</line>
<option name="timeStamp" value="117" />
</line-breakpoint>
<line-breakpoint enabled="true" suspend="THREAD" type="python-line">
<url>file://$PROJECT_DIR$/booking/views/client_views.py</url>
<line>40</line>
<option name="timeStamp" value="119" />
</line-breakpoint>
<line-breakpoint enabled="true" suspend="THREAD" type="python-line">
<url>file://$PROJECT_DIR$/booking/views/client_views.py</url>
<line>46</line>
<option name="timeStamp" value="120" />
<line>90</line>
<option name="timeStamp" value="128" />
</line-breakpoint>
<line-breakpoint enabled="true" suspend="THREAD" type="python-line">
<url>file://$PROJECT_DIR$/booking/tests.py</url>
<line>75</line>
<option name="timeStamp" value="121" />
<line>11</line>
<option name="timeStamp" value="129" />
</line-breakpoint>
<line-breakpoint enabled="true" type="javascript">
<url>file://$PROJECT_DIR$/static/js/container_validation.js</url>
@ -407,9 +404,10 @@
</breakpoint-manager>
</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.coverage" NAME="Test: booking.tests.BookingViewsTestCase Coverage Results" MODIFIED="1754414003823" 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_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$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>

@ -1,4 +1,8 @@
from django.core.exceptions import ValidationError
from django.db import models
from accounts.models import DepotUser
from common.fields import ContainerNumberField, UpperCaseCharField
from common.models import ContainerTypeModel, LinesModel, OperationModel
# Create your models here.
@ -10,15 +14,15 @@ class Booking(models.Model):
('canceled', 'Canceled'),
]
number = models.CharField(max_length=50, unique=True)
vehicles = models.CharField(blank=True, null=True)
number = UpperCaseCharField(max_length=50)
vehicles = UpperCaseCharField(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)
container_count = models.PositiveIntegerField()
container_expedited_count = models.PositiveIntegerField(default=0)
carrier = models.CharField(max_length=100, blank=True, null=True)
line = models.ForeignKey(
LinesModel,
@ -27,12 +31,12 @@ class Booking(models.Model):
)
visible = 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)
created_on = models.DateTimeField(auto_now_add=True)
created_by = models.IntegerField()
created_by = models.ForeignKey(DepotUser, related_name='booking_user', on_delete=models.CASCADE)
updated_on = models.DateTimeField(auto_now=True)
updated_by = models.IntegerField()
updated_by = models.ForeignKey(DepotUser, related_name='booking_user', on_delete=models.CASCADE)
status = models.CharField(
max_length=10,
choices=STATUS_CHOICES,
@ -45,3 +49,7 @@ class Booking(models.Model):
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.')

@ -45,8 +45,8 @@ class BookingViewsTestCase(TestCase):
line=self.line,
created_by=self.user_employee_all_rights.id,
updated_by=self.user_employee_all_rights.id,
vehicles='',
vehicles_left='',
vehicles='K1,J2,K3',
vehicles_left='K1,J2,K3',
)
@ -72,8 +72,8 @@ class BookingViewsTestCase(TestCase):
self.assertTemplateUsed(response, 'employee/booking-list.html')
self.assertContains(response, self.booking.number)
def test_booking_create_view(self):
self.assertTrue( self.user_client_all_rights.has_company_perm('can_view_booking'))
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'), {
@ -84,16 +84,17 @@ class BookingViewsTestCase(TestCase):
'vehicles': 'Truck1,Truck2',
'vehicles_left': 'Truck1,Truck2',
})
self.assertEqual(response.status_code, 200)
self.assertEqual(response.status_code, 302)
self.assertTrue(Booking.objects.filter(number='BOOK456').exists())
# def test_booking_update_view(self):
# response = self.client.post(reverse('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')
def test_booking_update_view(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')

@ -59,4 +59,4 @@ class ClientBookingUpdateView(LoginRequiredMixin, UserPassesTestMixin, LineFilte
return super().form_valid(form)
def test_func(self):
return 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'

@ -0,0 +1,29 @@
from django.core.exceptions import ValidationError
from django.db import models
class UpperCaseCharField(models.CharField):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def get_prep_value(self, value):
value = super().get_prep_value(value)
if value is not None:
return value.upper()
return value
class ContainerNumberField(UpperCaseCharField):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def get_prep_value(self, value):
value = super().get_prep_value(value)
if value is not None:
return value.upper()
return value
def validate(self, value, model_instance):
super().validate(value, model_instance)
if value and len(value) != 11:
raise ValidationError('Container number must be exactly 11 characters long.')

@ -1,22 +1,39 @@
from django.db import models
from django.core.exceptions import ValidationError
from common.fields import UpperCaseCharField
# 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)
name = UpperCaseCharField(max_length=100)
short_name = UpperCaseCharField(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']
app_label = 'common'
def __str__(self):
return self.name
def clean(self):
if not self.name:
raise ValidationError('Name cannot be empty.')
if self.short_name and len(self.short_name) > 5:
raise ValidationError('Short name cannot exceed 5 characters.')
if self.description and len(self.description) > 255:
raise ValidationError('Description cannot exceed 255 characters.')
class CompanyModel(NomenclatureBaseModel):
class Meta:
app_label = 'common'
def clean(self):
super().clean()
if CompanyModel.objects.filter(name=self.name, active=True).exclude(id=self.id).exists():
raise ValidationError(f'Company with name {self.name} already exists and is active.')
if CompanyModel.objects.filter(short_name=self.short_name, active=True).exclude(id=self.id).exists():
raise ValidationError(f'Company with short name {self.short_name} already exists and is active.')
class LinesModel(NomenclatureBaseModel):
@ -26,8 +43,13 @@ class LinesModel(NomenclatureBaseModel):
related_name='line_company'
)
class Meta:
app_label = 'common'
def clean(self):
super().clean()
if LinesModel.objects.filter(name=self.name, active=True).exclude(id=self.id).exists():
raise ValidationError(f'Line with name {self.name} already exists and is active.')
if LinesModel.objects.filter(short_name=self.short_name, active=True).exclude(id=self.id).exists():
raise ValidationError(f'Line with short name {self.short_name} already exists and is active.')
class OperationModel(NomenclatureBaseModel):
...

@ -1,11 +1,13 @@
from django.core.exceptions import ValidationError
from django.db import models
from common.fields import ContainerNumberField
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)
number = ContainerNumberField(max_length=11)
line = models.ForeignKey(
LinesModel,
on_delete=models.CASCADE,
@ -25,7 +27,7 @@ class Container(models.Model):
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)
position = models.CharField(max_length=7, blank=True, null=True)
swept = models.BooleanField(default=False)
swept_on = models.DateTimeField(blank=True, null=True)
swept_by = models.ForeignKey(
@ -67,6 +69,11 @@ class Container(models.Model):
)
expedition_vehicle = models.CharField(max_length=100, blank=True, null=True)
def clean(self):
if Container.objects.filter(number=self.number, expedited=False).exclude(id=self.id).exists():
raise ValidationError(f'Container with number {self.number} already exists in the depot.!')
if self.heavy_damaged and not self.damages.strip():
raise ValidationError('Heavy damaged containers must have damages specified.')
class ContainerHistory(Container):

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

Loading…
Cancel
Save