From 78d570ad4ff7ed297b90532d7a3230154419957a Mon Sep 17 00:00:00 2001 From: kikimor Date: Thu, 3 Jul 2025 18:43:27 +0300 Subject: [PATCH] Add IntelliJ IDEA project configuration files This commit adds IntelliJ IDEA-specific configuration files for the project, including module setup, version control integration, inspection profiles, and workspace settings. These files facilitate development environment configuration for contributors using IntelliJ IDEA. --- .idea/workspace.xml | 105 ++++++++++-- accounts/admin.py | 10 +- accounts/forms.py | 13 +- accounts/models.py | 72 +++++++- accounts/views.py | 61 ++++++- common/urls.py | 12 +- common/views.py | 31 +++- static/styles/forms.css | 149 ++++++++++++++++ templates/client-base.html | 2 + templates/client-booking-content.html | 130 +++++++++----- templates/client-sidebar.html | 9 +- templates/employee-base.html | 196 ++++++++++++++++++++++ templates/employee-dashboard-content.html | 162 ++++++++++++++++++ templates/employee-sidebar.html | 64 +++++++ templates/recent.html | 40 +++++ templates/registration/login.html | 2 +- templates/registration/register.html | 61 +++++-- 17 files changed, 1023 insertions(+), 96 deletions(-) create mode 100644 static/styles/forms.css create mode 100644 templates/employee-base.html create mode 100644 templates/employee-dashboard-content.html create mode 100644 templates/employee-sidebar.html create mode 100644 templates/recent.html diff --git a/.idea/workspace.xml b/.idea/workspace.xml index c97e951..83266d6 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -5,26 +5,39 @@ - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -48,17 +61,24 @@ "RunOnceActivity.ShowReadmeOnStart": "true", "django.template.preview.state": "SHOW_EDITOR_AND_PREVIEW", "git-widget-placeholder": "master", - "last_opened_file_path": "C:/dev_projects/python/Django/DepoT/templates", + "last_opened_file_path": "C:/dev_projects/python/Django/DepoT/templates/registration", + "list.type.of.created.stylesheet": "CSS", "node.js.detected.package.eslint": "true", "node.js.detected.package.tslint": "true", "node.js.selected.package.eslint": "(autodetect)", "node.js.selected.package.tslint": "(autodetect)", "nodejs_package_manager_path": "npm", "vue.rearranger.settings.migration": "true" + }, + "keyToStringList": { + "DatabaseDriversLRU": [ + "postgresql" + ] } }]]> + @@ -106,7 +126,7 @@ @@ -138,11 +206,18 @@ - file://$PROJECT_DIR$/common/views.py - 22 - + + + + + + \ No newline at end of file diff --git a/accounts/admin.py b/accounts/admin.py index 4a111d9..4183c35 100644 --- a/accounts/admin.py +++ b/accounts/admin.py @@ -12,13 +12,14 @@ class DepotUserAdmin(UserAdmin): 'company', 'line', 'company_permissions', - 'new_field1', - 'is_company_admin', + 'user_type', )}), ) # Add fields to display in list view - list_display = UserAdmin.list_display + ('phone_number', 'company', 'line', 'is_company_admin') + list_display = ('username', 'email', 'user_type', 'company', 'line', 'is_active', 'is_staff', 'is_superuser') + search_fields = ('username', 'email') + list_filter = ('user_type', 'is_active', 'is_staff', 'is_superuser') # Add fields to the add form add_fieldsets = UserAdmin.add_fieldsets + ( @@ -28,7 +29,6 @@ class DepotUserAdmin(UserAdmin): 'company', 'line', 'company_permissions', - 'new_field1', - 'is_company_admin', + 'user_type', )}), ) \ No newline at end of file diff --git a/accounts/forms.py b/accounts/forms.py index 05f2585..f2d127f 100644 --- a/accounts/forms.py +++ b/accounts/forms.py @@ -16,18 +16,25 @@ class RegisterForm(ModelForm): password1 = CharField(label='Password', widget=PasswordInput) password2 = CharField(label='Confirm Password', widget=PasswordInput) - field_order = ['username', 'email', 'password1', 'password2', 'phone_number', 'line', 'company_permissions'] + field_order = ['username', 'email', 'password1', 'password2', 'phone_number', 'company', 'line', 'user_type', 'company_permissions', 'employee_permissions', 'is_active'] class Meta: model = get_user_model() - fields = ['username', 'email', 'password1', 'password2', 'phone_number', 'line', 'company_permissions'] + fields = ['username', 'email', 'password1', 'password2', 'phone_number', 'company', 'line', 'user_type', 'company_permissions', 'employee_permissions', 'is_active'] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields['password1'].widget.attrs.update({'autocomplete': 'new-password'}) self.fields['password2'].widget.attrs.update({'autocomplete': 'new-password'}) - + self.fields['user_type'].widget.attrs['disabled'] = True + self.fields['company_permissions'].widget.attrs['disabled'] = True + self.fields['employee_permissions'].widget.attrs['disabled'] = True + + if self.data.get('user_type') in ( 'EM', 'BS'): + self.fields['company_permissions'].required = False + if self.data.get('user_type') in ('CL', 'CA', 'BS'): + self.fields['company_permissions'].required = False def clean(self): cleaned_data = super().clean() diff --git a/accounts/models.py b/accounts/models.py index 0f8329a..5b1d27c 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -1,3 +1,4 @@ +from django.contrib.auth.base_user import BaseUserManager from django.contrib.auth.models import AbstractUser from django.db import models @@ -13,11 +14,69 @@ class ClientPermission(models.Model): ('can_view_bookings', 'Can view bookings'), ('can_manage_company_users', 'Can manage company users'), ) + + + def __str__(self): + return self.name + +class EmployeePermission(models.Model): + codename = models.CharField(max_length=100, default='') + name = models.CharField(max_length=255, default='') + + class Meta: + managed = True + default_permissions = () + permissions = ( + ('can_manage_containers', 'Can manage containers'), + ('can_view_reports', 'Can view reports'), + ('can_handle_operations', 'Can handle operations'), + ) + def __str__(self): return self.name + +class CustomUserManager(BaseUserManager): + def create_user(self, email, password=None, **extra_fields): + if not email: + raise ValueError('Users must have an email address') + email = self.normalize_email(email) + user = self.model(email=email, **extra_fields) + user.set_password(password) + user.save(using=self._db) + return user + + def create_superuser(self, email, password=None, **extra_fields): + extra_fields.setdefault('is_staff', True) + extra_fields.setdefault('is_superuser', True) + extra_fields.setdefault('is_active', True) + extra_fields.setdefault('user_type', 'AD') # Set user_type to admin + + if extra_fields.get('is_staff') is not True: + raise ValueError('Superuser must have is_staff=True.') + if extra_fields.get('is_superuser') is not True: + raise ValueError('Superuser must have is_superuser=True.') + + return self.create_user(email, password, **extra_fields) + + class DepotUser(AbstractUser): + class UserType(models.TextChoices): + BARRIER_STAFF = 'BS', 'Barrier Staff' + COMPANY_ADMIN = 'CA', 'Company Admin' + EMPLOYEE = 'EM', 'Employee' + CLIENT = 'CL', 'Client', + ADMIN = 'AD', 'Admin' + + objects = CustomUserManager() + + user_type = models.CharField( + max_length=2, + choices=UserType.choices, + default=UserType.CLIENT + ) + phone_number = models.CharField(max_length=15, blank=True, null=True) email = models.EmailField(unique=True, blank=False, null=False) company = models.ForeignKey( @@ -36,13 +95,14 @@ class DepotUser(AbstractUser): ) company_permissions = models.ManyToManyField('ClientPermission') - new_field1 = models.BooleanField(default=False) - is_company_admin = models.BooleanField(default=False) + employee_permissions = models.ManyToManyField('EmployeePermission', blank=True) def has_company_perm(self, perm_codename): if self.is_superuser: return True - return self.company_permissions.filter( - codename=perm_codename, - is_client_permission=self.company - ).exists() \ No newline at end of file + return self.company_permissions.filter(codename=perm_codename).exists() + + def has_employee_perm(self, perm_codename): + if self.is_superuser: + return True + return self.employee_permissions.filter(codename=perm_codename).exists() \ No newline at end of file diff --git a/accounts/views.py b/accounts/views.py index 6881e58..46a5866 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -2,12 +2,17 @@ from django.contrib.auth import get_user_model from django.contrib.auth.views import LoginView from django.shortcuts import render from django.urls import reverse_lazy +from django.utils.decorators import method_decorator from django.views.generic import TemplateView, FormView, ListView, UpdateView from accounts.forms import LoginForm, RegisterForm +from accounts.models import DepotUser - +from django.contrib.auth.decorators import login_required, user_passes_test +from django.contrib.auth.mixins import AccessMixin # Create your views here. + + class DepotLoginView(LoginView): template_name = 'registration/login.html' # success_url = reverse_lazy('dashboard') @@ -15,19 +20,71 @@ class DepotLoginView(LoginView): next_page = reverse_lazy('dashboard') -class RegisterView(FormView): +def is_company_admin(user): + return user.is_authenticated and user.is_company_admin + +@method_decorator(login_required, name='dispatch') +class RegisterView(AccessMixin, FormView): template_name = 'registration/register.html' form_class = RegisterForm # model = get_user_model() success_url = reverse_lazy('dashboard') + def dispatch(self, request, *args, **kwargs): + user: DepotUser = request.user + + + if not (user.is_superuser or user.user_type == DepotUser.UserType.COMPANY_ADMIN): + return self.handle_no_permission() + return super().dispatch(request, *args, **kwargs) + def form_valid(self, form): # Create user from form data user = form.save(commit=False) + user_type = form.cleaned_data['user_type'] + + # Clear irrelevant permissions based on user type + if user_type == DepotUser.UserType.CLIENT: + user.employee_permissions.clear() + elif user_type == DepotUser.UserType.EMPLOYEE: + user.company_permissions.clear() + # user.set_password(form.cleaned_data['password']) user.save() return super().form_valid(form) + def get_form(self, form_class = None): + form = super().get_form(form_class) + user: DepotUser = self.request.user + + if user.is_superuser: + # Superuser can manage all permissions and user types + form.fields['user_type'].widget.attrs['disabled'] = False + form.fields['company_permissions'].widget.attrs['disabled'] = False + form.fields['employee_permissions'].widget.attrs['disabled'] = False + + # Show relevant permissions based on selected user type + if form.initial.get('user_type') == DepotUser.UserType.CLIENT: + form.fields['employee_permissions'].widget.attrs['disabled'] = True + elif form.initial.get('user_type') == DepotUser.UserType.EMPLOYEE: + form.fields['company_permissions'].widget.attrs['disabled'] = True + + elif user.user_type == DepotUser.UserType.COMPANY_ADMIN: + form.fields['company'].queryset = form.fields['company'].queryset.filter(pk=user.company.pk) + form.fields['company'].initial = user.company + form.fields['company'].widget.readonly = True # form.fields['line'].widget.attrs['disabled'] = True + form.fields['line'].queryset = form.fields['line'].queryset.filter(company=user.company.pk) + + form.fields['user_type'].choices = [ + (DepotUser.UserType.CLIENT, 'Client') + ] + form.fields['user_type'].initial = DepotUser.UserType.CLIENT + + form.fields['company_permissions'].widget.attrs['disabled'] = False + form.fields['employee_permissions'].widget.attrs['disabled'] = True + + return form + class UserListView(ListView): template_name = 'registration/register.html' # form_class = RegisterForm diff --git a/common/urls.py b/common/urls.py index 965ba40..e02e93f 100644 --- a/common/urls.py +++ b/common/urls.py @@ -1,10 +1,12 @@ -from django.urls import path +from django.urls import path, include from common import views urlpatterns = [ path('', views.IndexView.as_view(), name='index'), - path('client/dashboard/', views.ClientDashboardView.as_view(), name='client_dashboard'), - path('barrier/dashboard/', views.BarrierDashboardView.as_view(), name='barrier_dashboard'), - - path('dashboard/', views.DashboardRedirectView.as_view(), name='dashboard'), + path('dashboard/', include([ + path('', views.DashboardRedirectView.as_view(), name='dashboard'), + path('client/', views.ClientDashboardView.as_view(), name='client_dashboard'), + path('barrier/', views.BarrierDashboardView.as_view(), name='barrier_dashboard'), + path('employee/', views.EmployeeDashboardView.as_view(), name='employee_dashboard'), + ])) ] diff --git a/common/views.py b/common/views.py index 5b35048..aebee98 100644 --- a/common/views.py +++ b/common/views.py @@ -2,6 +2,11 @@ from django.urls import reverse_lazy from django.views.generic import TemplateView, RedirectView from django.shortcuts import render +from accounts.models import DepotUser +from booking.models import Booking +from containers.models import Container +from preinfo.models import Preinfo + # Create your views here. class IndexView(TemplateView): @@ -21,12 +26,16 @@ class DashboardRedirectView(RedirectView): def get_redirect_url(self, *args, **kwargs): # if self.request.user.is_authenticated: - if self.request.user.username == 'client': + if self.request.user.user_type == DepotUser.UserType.COMPANY_ADMIN: return reverse_lazy('client_dashboard') - elif self.request.user.username == 'clientadmin': + elif self.request.user.user_type == DepotUser.UserType.CLIENT: return reverse_lazy('client_dashboard') - elif self.request.user.username == 'barrier': + elif self.request.user.user_type == DepotUser.UserType.BARRIER_STAFF: return reverse_lazy('barrier_dashboard') + elif self.request.user.user_type == DepotUser.UserType.EMPLOYEE: + return reverse_lazy('employee_dashboard') + elif self.request.user.user_type == DepotUser.UserType.ADMIN: + return reverse_lazy('employee_dashboard') return reverse_lazy('index') @@ -47,10 +56,26 @@ class BarrierDashboardView(TemplateView): 'description': 'This is the client dashboard page.', } + def get(self, request, *args, **kwargs): return render(request, self.template_name, self.extra_context) +class EmployeeDashboardView(TemplateView): + template_name = 'employee-dashboard-content.html' + extra_context = { + 'title': 'Employee Dashboard', + 'description': 'This is the depot employee dashboard page.', + } + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + containers = Container.objects.filter(expedited=False).count() + preinfos = Preinfo.objects.filter(received=False).count() + bookings = Booking.objects.filter(status='active').count() + context['containers'] = containers + context['preinfos'] = preinfos + context['bookings'] = bookings + return context # class ClientPreinfoView(TemplateView): # template_name = 'client-preinfo-content.html' diff --git a/static/styles/forms.css b/static/styles/forms.css new file mode 100644 index 0000000..ac610c3 --- /dev/null +++ b/static/styles/forms.css @@ -0,0 +1,149 @@ +/* Basic form styling */ +form { + max-width: 600px; + margin: 20px auto; + padding: 20px; +} + +/* Label styling */ +label { + display: block; + margin-bottom: 2px; + font-weight: 500; + color: #a57d52; +} + +/* Input field styling */ +input[type="text"], +input[type="email"], +input[type="password"], +input[type="date"], +input[type="number"], +select, +textarea { + width: 100%; + padding: 8px; + margin-bottom: 10px; + border: 1px solid #a57d52; + border-radius: 4px; + box-sizing: border-box; +} + +/* Submit button styling */ +button[type="submit"] { + background-color: #a57d52; + color: white; + padding: 10px 20px; + border: none; + border-radius: 4px; + cursor: pointer; + /*margin-top: 20px;*/ +} + +button[type="submit"]:hover { + background-color: #a67744; +} + +/* Error messages */ +.errorlist { + color: #dc3545; + list-style: none; + padding: 0; + margin: 5px 0; + font-size: 0.9em; +} + +/* Help text */ +.helptext { + color: #666; + font-size: 0.9em; + margin-bottom: 10px; + display: block; +} + + +.card { + margin-top: 24px; + background-color: white; + border-radius: 8px; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); +} + +.card-header { + padding: 16px 24px; + border-bottom: 1px solid #ddd; +} + +.card-header h3 { + margin: 0; + font-size: 18px; + font-weight: bold; + color: #333; +} + +.card-body { + padding: 24px; +} + +.table-container { + overflow-x: auto; +} + +table { + width: 100%; + border-collapse: collapse; +} + +thead { + background-color: #f9f9f9; +} + +th, td { + padding: 12px 16px; + text-align: left; + font-size: 14px; +} + +th { + font-weight: bold; + text-transform: uppercase; + color: #666; + border-bottom: 1px solid #ddd; +} + +tbody tr { + border-bottom: 1px solid #eee; +} + +.status { + display: inline-block; + padding: 4px 8px; + font-size: 12px; + font-weight: bold; + border-radius: 12px; + background-color: #e0f0ff; + color: #0066cc; +} + +.actions button { + background: none; + border: none; + color: #0066cc; + font-size: 14px; + cursor: pointer; + margin-right: 10px; +} + +.actions button:hover { + color: #004999; +} + +.actions button:last-child { + color: #cc0000; + margin-right: 0; +} + +.actions button:last-child:hover { + color: #990000; +} + diff --git a/templates/client-base.html b/templates/client-base.html index 7b379df..582410f 100644 --- a/templates/client-base.html +++ b/templates/client-base.html @@ -6,6 +6,7 @@ Line Operator Dashboard | Container Depot + + diff --git a/templates/client-booking-content.html b/templates/client-booking-content.html index d9e708d..cdd5166 100644 --- a/templates/client-booking-content.html +++ b/templates/client-booking-content.html @@ -22,49 +22,93 @@ -
-
-

Recent Preinfo Submissions

-
-
-
- - - - - - - - +{#
#} +{#
#} +{#

Recent Preinfo Submissions

#} +{#
#} +{#
#} +{#
#} +{#
Booking №Container №TypeContainer countContainers left
#} +{# #} +{# #} +{# #} +{# #} +{# #} +{# #} +{# #} {# #} - - - - - - - {% for booking in recent %} - - - - - - - - - - - - {% endfor %} - -
Booking №Container №TypeContainer countContainers leftVehiclesVehiclesStatusActions
{{ booking.number }}{{ booking.container_number }}{{ booking.container_type }}{{ booking.container_count }}{{ booking.containers_left }}{{ booking.vehicles_left }} - {{ booking.status }} - - - -
-
-
-
+{# Vehicles#} +{# Status#} +{# Actions#} +{# #} +{# #} +{# #} +{# {% for booking in recent %}#} +{# #} +{# {{ booking.number }}#} +{# {{ booking.container_number }}#} +{# {{ booking.container_type }}#} +{# {{ booking.container_count }}#} +{# {{ booking.containers_left }}#} +{# {{ booking.vehicles_left }}#} +{# #} +{# #} +{# {{ booking.status }}#} +{# #} +{# #} +{# #} +{# #} +{# #} +{# #} +{# {% endfor %}#} +{# #} +{# #} +{# #} +{# #} +{# #} + + +
+
+

Recent Preinfo Submissions

+
+
+
+ + + + + + + + + + + + + + + {% for booking in recent %} + + + + + + + + + + + {% endfor %} + +
Booking №Container №TypeContainer countContainers leftVehiclesStatusActions
{{ booking.number }}{{ booking.container_number }}{{ booking.container_type }}{{ booking.container_count }}{{ booking.containers_left }}{{ booking.vehicles_left }}{{ booking.status }} + + +
+
+
+
+ + {% endblock content %} \ No newline at end of file diff --git a/templates/client-sidebar.html b/templates/client-sidebar.html index d43a281..ad9801a 100644 --- a/templates/client-sidebar.html +++ b/templates/client-sidebar.html @@ -31,15 +31,18 @@ Reports - + + {% if request.user.UserType.COMPANY_ADMIN %} +
Account
- + Settings + {% endif %}
@@ -51,7 +54,7 @@

{{ request.user }}

{{ request.user.company }}

- + diff --git a/templates/employee-base.html b/templates/employee-base.html new file mode 100644 index 0000000..6dfaa5a --- /dev/null +++ b/templates/employee-base.html @@ -0,0 +1,196 @@ +{% load static %} + + + + + + Employee Dashboard | Container Depot + + + + + + + + {% include 'employee-sidebar.html' %} + +
+ +
+
+
+

Dashboard

+
+
+ + +
+
+
+ + +
+ {% block content %} +{# {% include 'client-dashboard-content.html' %}#} +{# {% include 'client-preinfo-content.html' %}#} +{# {% include 'client-orders-content.html' %}#} + {% endblock content %} +
+
+ +{# #} +{##} + + diff --git a/templates/employee-dashboard-content.html b/templates/employee-dashboard-content.html new file mode 100644 index 0000000..94daad4 --- /dev/null +++ b/templates/employee-dashboard-content.html @@ -0,0 +1,162 @@ +{% extends 'employee-base.html' %} +{% block content %} + +
+
+
+
+
+ + + +
+
+

Active Containers

+

{{ containers }}

+

+3 since last week

+
+
+
+ +
+
+
+ + + +
+
+

Preinfo Sent

+

{{ preinfos }}

+

+5 since last week

+
+
+
+ +
+
+
+ + + +
+
+

Bookings

+

{{ bookings }}

+

2 require attention

+
+
+
+
+ +
+
+
+

Recent Container Activity

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ContainerTypeStatusDate
MSCU123456740HC + Received + 2023-06-15
MSCU765432120DV + Preinfo + 2023-06-14
MSCU246813540DV + Order + 2023-06-13
MSCU135792420RF + Expedited + 2023-06-12
+
+
+
+ +
+
+

Payment Status

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
InvoiceAmountStatusDue Date
INV-2023-0042$1,250.00 + Paid + 2023-06-10
INV-2023-0041$875.50 + Pending + 2023-06-20
INV-2023-0040$2,100.00 + Overdue + 2023-06-05
INV-2023-0039$950.25 + Paid + 2023-05-28
+
+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/templates/employee-sidebar.html b/templates/employee-sidebar.html new file mode 100644 index 0000000..1e4e996 --- /dev/null +++ b/templates/employee-sidebar.html @@ -0,0 +1,64 @@ +{% load static %} +
diff --git a/templates/recent.html b/templates/recent.html new file mode 100644 index 0000000..c5c01fc --- /dev/null +++ b/templates/recent.html @@ -0,0 +1,40 @@ +
+
+

Recent Preinfo Submissions

+
+
+
+ + + + + + + + + + + + + + + {% for booking in recent %} + + + + + + + + + + + {% endfor %} + +
Booking №Container №TypeContainer countContainers leftVehiclesStatusActions
{{ booking.number }}{{ booking.container_number }}{{ booking.container_type }}{{ booking.container_count }}{{ booking.containers_left }}{{ booking.vehicles_left }}{{ booking.status }} + + +
+
+
+
diff --git a/templates/registration/login.html b/templates/registration/login.html index c39460b..209f169 100644 --- a/templates/registration/login.html +++ b/templates/registration/login.html @@ -5,7 +5,7 @@ Container Depot Management System - + {# {{ title|default:"Container Depot Management System" }} {{ description|default:"Login to the Container Depot Management System" }}#} diff --git a/templates/registration/register.html b/templates/registration/register.html index d5e6989..7830ed4 100644 --- a/templates/registration/register.html +++ b/templates/registration/register.html @@ -1,14 +1,55 @@ - - - - - Register User - - +{% extends 'client-base.html' %} +{% block content %} +
{% csrf_token %} - {{ form.as_p }} + {{ form }}
- - \ No newline at end of file + + + +{% endblock content %} \ No newline at end of file