diff --git a/.dockerignore b/.dockerignore index 9a64f5b..dfc0ff3 100644 --- a/.dockerignore +++ b/.dockerignore @@ -29,3 +29,5 @@ production.env # Other gemini.cmd - Shortcut.lnk + +images/ \ No newline at end of file diff --git a/.env b/.env index 0a18fa1..2db9de7 100644 --- a/.env +++ b/.env @@ -36,7 +36,6 @@ MINIO_ENDPOINT="localhost:9000" MINIO_ACCESS_KEY="kikimor" MINIO_SECRET_KEY="shushunka1" MINIO_BUCKET_NAME="damages" -#MINIO_STATIC_BUCKET_NAME=depot-static -#AWS_S3_CUSTOM_DOMAIN='localhost:9000' # For browser acce -#AWS_S3_URL_PROTOCOL='http' -#MINIO_SERVER_URL="http://localhost:9000" \ No newline at end of file +AWS_S3_CUSTOM_DOMAIN='localhost:9000' # For browser acce +AWS_S3_URL_PROTOCOL='http' +MINIO_SERVER_URL="http://localhost:9000" \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 31fa4eb..f564935 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -5,19 +5,34 @@ + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + + + - - - - + + + + + + + + + + + + + @@ -263,11 +311,6 @@ 7 + + + \ No newline at end of file diff --git a/DepoT/__pycache__/settings.cpython-311.pyc b/DepoT/__pycache__/settings.cpython-311.pyc deleted file mode 100644 index 6862117..0000000 Binary files a/DepoT/__pycache__/settings.cpython-311.pyc and /dev/null differ diff --git a/DepoT/__pycache__/settings.cpython-313.pyc b/DepoT/__pycache__/settings.cpython-313.pyc deleted file mode 100644 index d76232a..0000000 Binary files a/DepoT/__pycache__/settings.cpython-313.pyc and /dev/null differ diff --git a/DepoT/settings/__init__.py b/DepoT/settings/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/DepoT/settings/base.py b/DepoT/settings/base.py new file mode 100644 index 0000000..e69de29 diff --git a/DepoT/settings/development.py b/DepoT/settings/development.py new file mode 100644 index 0000000..73c3455 --- /dev/null +++ b/DepoT/settings/development.py @@ -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.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 \ No newline at end of file diff --git a/DepoT/settings/production.py b/DepoT/settings/production.py new file mode 100644 index 0000000..92c0fc8 --- /dev/null +++ b/DepoT/settings/production.py @@ -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() +environ.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")}/' diff --git a/accounts/__pycache__/models.cpython-313.pyc b/accounts/__pycache__/models.cpython-313.pyc index 5ac3fed..d8757d1 100644 Binary files a/accounts/__pycache__/models.cpython-313.pyc and b/accounts/__pycache__/models.cpython-313.pyc differ diff --git a/booking/__pycache__/models.cpython-313.pyc b/booking/__pycache__/models.cpython-313.pyc index 03801b4..9ad9cc0 100644 Binary files a/booking/__pycache__/models.cpython-313.pyc and b/booking/__pycache__/models.cpython-313.pyc differ diff --git a/booking/models.py b/booking/models.py index 76d0fa9..0f55f47 100644 --- a/booking/models.py +++ b/booking/models.py @@ -41,4 +41,7 @@ class Booking(models.Model): @property def containers_left(self): - return self.container_count - (self.container_expedited_count or 0) \ No newline at end of file + return self.container_count - (self.container_expedited_count or 0) + + class Meta: + app_label = 'booking' \ No newline at end of file diff --git a/booking/tests.py b/booking/tests.py index 7ce503c..d6f2ff2 100644 --- a/booking/tests.py +++ b/booking/tests.py @@ -1,3 +1,49 @@ -from django.test import TestCase +from django.test import TestCase, Client +from django.urls import reverse +from django.contrib.auth import get_user_model +from booking.models import Booking +from common.models import LinesModel, ContainerTypeModel -# Create your tests here. +class BookingViewsTestCase(TestCase): + def setUp(self): + self.client = Client() + DepotUser = get_user_model() + self.user = DepotUser.objects.create_user(username='testuser', password='password', user_type='EM') + self.client.login(username='testuser', password='password') + self.line = LinesModel.objects.create(name='Test Line') + self.container_type = ContainerTypeModel.objects.create(name='20ft') + self.booking = Booking.objects.create( + number='BOOK123', + container_type=self.container_type, + container_count=10, + line=self.line, + created_by=self.user.id, + updated_by=self.user.id + ) + + def test_booking_list_view(self): + response = self.client.get(reverse('booking-list')) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'booking/booking-list.html') + self.assertContains(response, self.booking.number) + + def test_booking_create_view(self): + response = self.client.post(reverse('booking-create'), { + 'number': 'BOOK456', + 'container_type': self.container_type.id, + 'container_count': 5, + 'line': self.line.id, + }) + 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') \ No newline at end of file diff --git a/booking/views/client_views.py b/booking/views/client_views.py index c182da7..b61bf76 100644 --- a/booking/views/client_views.py +++ b/booking/views/client_views.py @@ -44,7 +44,7 @@ class CreateBookingView(LoginRequiredMixin, UserPassesTestMixin, LineFilterFormM return super().form_valid(form) def test_func(self): - 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): diff --git a/common/__pycache__/models.cpython-313.pyc b/common/__pycache__/models.cpython-313.pyc index ea73d8d..63bcfc9 100644 Binary files a/common/__pycache__/models.cpython-313.pyc and b/common/__pycache__/models.cpython-313.pyc differ diff --git a/common/models.py b/common/models.py index abf922c..12482f4 100644 --- a/common/models.py +++ b/common/models.py @@ -15,7 +15,8 @@ class NomenclatureBaseModel(models.Model): class CompanyModel(NomenclatureBaseModel): - ... + class Meta: + app_label = 'common' class LinesModel(NomenclatureBaseModel): @@ -25,8 +26,12 @@ class LinesModel(NomenclatureBaseModel): related_name='line_company' ) + class Meta: + app_label = 'common' + class OperationModel(NomenclatureBaseModel): - ... + class Meta: + app_label = 'common' class ContainerKindModel(NomenclatureBaseModel): ... diff --git a/containers/tests.py b/containers/tests.py index 7ce503c..60ebdf7 100644 --- a/containers/tests.py +++ b/containers/tests.py @@ -1,3 +1,48 @@ -from django.test import TestCase +from django.test import TestCase, Client +from django.urls import reverse +from django.contrib.auth import get_user_model +from containers.models import Container +from common.models import LinesModel, ContainerTypeModel +from preinfo.models import Preinfo -# Create your tests here. +class ContainerViewsTestCase(TestCase): + def setUp(self): + self.client = Client() + DepotUser = get_user_model() + self.user = DepotUser.objects.create_user(username='testuser', password='password', user_type='E') + self.client.login(username='testuser', password='password') + self.line = LinesModel.objects.create(name='Test Line') + self.container_type = ContainerTypeModel.objects.create(name='20ft') + self.preinfo = Preinfo.objects.create(number='PRE123', line=self.line, container_type=self.container_type) + self.container = Container.objects.create( + number='TEST1234567', + line=self.line, + container_type=self.container_type, + received_by=self.user, + preinfo=self.preinfo + ) + + def test_container_list_view(self): + response = self.client.get(reverse('container-list')) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'containers/container-list.html') + self.assertContains(response, self.container.number) + + def test_container_receive_view(self): + response = self.client.post(reverse('container-receive'), { + 'number': 'TEST7654321', + 'line': self.line.id, + 'container_type': self.container_type.id, + 'preinfo': self.preinfo.id, + }) + self.assertEqual(response.status_code, 302) + self.assertTrue(Container.objects.filter(number='TEST7654321').exists()) + + def test_container_expedition_view(self): + response = self.client.post(reverse('container-expedition', args=[self.container.id]), { + 'expedition_vehicle': 'TRUCK123', + }) + self.assertEqual(response.status_code, 302) + self.container.refresh_from_db() + self.assertTrue(self.container.expedited) + self.assertEqual(self.container.expedition_vehicle, 'TRUCK123') \ No newline at end of file diff --git a/damages_api/tests.py b/damages_api/tests.py index 7ce503c..3440085 100644 --- a/damages_api/tests.py +++ b/damages_api/tests.py @@ -1,3 +1,49 @@ -from django.test import TestCase +import base64 +from unittest.mock import patch +from django.test import TestCase, Client +from django.urls import reverse +from rest_framework import status +from django.contrib.auth import get_user_model +from containers.models import Container, ContainerPhotos +from common.models import LinesModel, ContainerTypeModel +from preinfo.models import Preinfo -# Create your tests here. +class DamagesAPITestCase(TestCase): + def setUp(self): + self.client = Client() + DepotUser = get_user_model() + self.user = DepotUser.objects.create_user(username='testuser', password='password', user_type='E') + self.client.login(username='testuser', password='password') + self.line = LinesModel.objects.create(name='Test Line') + self.container_type = ContainerTypeModel.objects.create(name='20ft') + self.preinfo = Preinfo.objects.create(number='PRE123', line=self.line, container_type=self.container_type) + self.container = Container.objects.create( + number='TEST1234567', + line=self.line, + container_type=self.container_type, + received_by=self.user, + preinfo=self.preinfo + ) + + @patch('damages_api.views.upload_damage_photo') + def test_damage_photo_upload(self, mock_upload): + mock_upload.return_value = 'http://mock-url.com/photo.jpg' + photo_data = base64.b64encode(b'test photo data').decode('utf-8') + response = self.client.post(reverse('damages-api', args=[self.container.id]), { + 'photo': photo_data, + 'photo_extension': 'jpg', + }, content_type='application/json') + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertTrue(ContainerPhotos.objects.filter(container=self.container).exists()) + + @patch('damages_api.views.boto3.client') + def test_damage_photo_retrieval(self, mock_boto_client): + mock_s3 = mock_boto_client.return_value + mock_s3.list_objects_v2.return_value = { + 'Contents': [ + {'Key': f'{self.container.id}/test.jpg'} + ] + } + response = self.client.get(reverse('damages-api', args=[self.container.id])) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIn('url', response.data[0]) \ No newline at end of file diff --git a/docker-compose-deploy.yml b/docker-compose-deploy.yml index bf34a59..6c97453 100644 --- a/docker-compose-deploy.yml +++ b/docker-compose-deploy.yml @@ -38,8 +38,7 @@ services: networks: - app-network command: > - bash -c "sleep 10 &&\ - python manage.py collectstatic --noinput --verbosity 3 &&\ + bash -c "python manage.py collectstatic --noinput --verbosity 3 &&\ python manage.py migrate &&\ gunicorn DepoT.wsgi:application --bind 0.0.0.0:8000 --workers 3" @@ -70,7 +69,6 @@ services: condition: service_healthy entrypoint: > /bin/sh -c " - sleep 10; mc alias set myminio http://minio:9000 kikimor shushunka1; mc mb myminio/damages; mc mb myminio/static; diff --git a/dockerfile b/dockerfile index c4a5b18..6e73e67 100644 --- a/dockerfile +++ b/dockerfile @@ -2,6 +2,7 @@ FROM python:3.11-slim ENV PYTHONUNBUFFERED=1 WORKDIR /DepoT ENV PYTHONPATH="${PYTHONPATH}:/DepoT:/." +ENV DJANGO_SETTINGS_MODULE=DepoT.settings.production COPY requirements.txt . RUN pip install -r requirements.txt COPY . . diff --git a/createbuckets_depot.tar b/images/createbuckets_depot.tar similarity index 100% rename from createbuckets_depot.tar rename to images/createbuckets_depot.tar diff --git a/depot-web.tar b/images/depot-web.tar similarity index 100% rename from depot-web.tar rename to images/depot-web.tar diff --git a/depot_image.tar b/images/depot_image.tar similarity index 100% rename from depot_image.tar rename to images/depot_image.tar diff --git a/minio.tar b/images/minio.tar similarity index 100% rename from minio.tar rename to images/minio.tar diff --git a/postgres.tar b/images/postgres.tar similarity index 100% rename from postgres.tar rename to images/postgres.tar diff --git a/web_depot.tar b/images/web_depot.tar similarity index 91% rename from web_depot.tar rename to images/web_depot.tar index 6c36f62..80245d5 100644 Binary files a/web_depot.tar and b/images/web_depot.tar differ diff --git a/manage.py b/manage.py index 5bf2659..fe147b1 100644 --- a/manage.py +++ b/manage.py @@ -6,7 +6,7 @@ import sys def main(): """Run administrative tasks.""" - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "DepoT.settings") + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "DepoT.settings.development") try: from django.core.management import execute_from_command_line except ImportError as exc: diff --git a/minio_backend/__pycache__/__init__.cpython-313.pyc b/minio_backend/__pycache__/__init__.cpython-313.pyc index e40c124..add708a 100644 Binary files a/minio_backend/__pycache__/__init__.cpython-313.pyc and b/minio_backend/__pycache__/__init__.cpython-313.pyc differ diff --git a/minio_backend/__pycache__/storage.cpython-313.pyc b/minio_backend/__pycache__/storage.cpython-313.pyc index da7b73f..d01d116 100644 Binary files a/minio_backend/__pycache__/storage.cpython-313.pyc and b/minio_backend/__pycache__/storage.cpython-313.pyc differ diff --git a/payments/services.py b/payments/services.py index 6ea1990..156724f 100644 --- a/payments/services.py +++ b/payments/services.py @@ -5,9 +5,10 @@ import urllib.parse from datetime import datetime, timedelta import requests +import environ -from DepoT.settings import env - +# from DepoT.settings import env +env = environ.Env() class EPay: payment_types = { @@ -33,8 +34,8 @@ class EPay: datetime.today() + timedelta(days=int(env("INVOICE_EXPIRE_PERIOD"))) ).strftime("%d.%m.%Y") - if amount == 0: - amount = 1000 + # if amount == 0: + # amount = 1000 # for message = f"MIN={client_id}\nINVOICE={invoice}\nAMOUNT={amount}\nCURRENCY={currency}\nEXP_TIME={expiry}\nDESCR={description}" encoded_data, checksum = EPay.encode_message(message) diff --git a/templates/client/booking-list.html b/templates/client/booking-list.html index a67b757..5afe303 100644 --- a/templates/client/booking-list.html +++ b/templates/client/booking-list.html @@ -1,6 +1,7 @@ {% extends 'list-crud.html' %} {% load static %} {% load filters %} +{% load custom_filters %} {% block filter %}
@@ -32,8 +33,8 @@ {{ object.container_number }} {{ object.container_count }} {{ object.containers_left }} - {{ object.vehicles }} - {{ object.vehicles_left }} + {{ object.vehicles|distinct_vehicles }} + {{ object.vehicles_left|distinct_vehicles }} {{ object.created_on|bg_date }} {{ object.created_by.username }} {{ object.status }} diff --git a/templates/common/allowed-vehicles.html b/templates/common/allowed-vehicles.html index 198e719..cdbfc3d 100644 --- a/templates/common/allowed-vehicles.html +++ b/templates/common/allowed-vehicles.html @@ -7,12 +7,14 @@ body { font-family: Arial, sans-serif; margin: 40px; - background-color: black; - color: white; + background-color: black; + color: white; + font-size: 2em; } h1 { font-size: 2.5em; margin-bottom: 20px; + text-align: center; } .table-container { width: 90%; @@ -21,14 +23,22 @@ table { width: 100%; border-collapse: collapse; + margin-bottom: 30px; } th, td { text-align: left; padding: 12px; border: 1px solid #ddd; } + th { - background-color: #f5f5f5; + font-size: 2em; + background-color: white; + color: black; + } + td { + font-size: 2em; + color: white; } @@ -36,20 +46,22 @@

Vehicles with bookings

-
- - - - - - - - - - - -
Line name
BB1234TT, TT1234BB
-
+ {% for obj in objects %} +
+ + + + + + + + + + + +
{{ obj.line }}
{{ obj.vehicles|upper}}
+
+ {% endfor %} \ No newline at end of file diff --git a/templates/common/base.html b/templates/common/base.html index 3c91ceb..ac63b32 100644 --- a/templates/common/base.html +++ b/templates/common/base.html @@ -48,6 +48,10 @@ {% endblock extra_js %} + {% block custom_js %} + {% endblock custom_js %} + + diff --git a/templates/employee/unpaid-list.html b/templates/employee/unpaid-list.html index bffcbe4..5b44015 100644 --- a/templates/employee/unpaid-list.html +++ b/templates/employee/unpaid-list.html @@ -43,7 +43,7 @@ {% endblock table_data %} {% block buttons %} - + {% endblock buttons %} {% block create_modal_header %} @@ -74,5 +74,6 @@ document.getElementById('createPaymentBtn').addEventListener('click', function() window.location.href = '{% url "payments_create" %}?' + params.toString(); } }); + {% endblock custom_js %} \ No newline at end of file diff --git a/DepoT/settings.py b/unused templates/settings.py similarity index 100% rename from DepoT/settings.py rename to unused templates/settings.py