backend_users_equipment_interventions

This commit is contained in:
2026-08-18 16:14:45 +03:00
parent 3715c7e296
commit 75bcef3095
30 changed files with 1401 additions and 38 deletions
+20 -1
View File
@@ -25,5 +25,24 @@ $ git commit -m "conf_initial"
$ docker compose up -d $ docker compose up -d
$ docker compose exec web python3.13 manage.py createsuperuser docker compose exec web python manage.py migrate
$ docker compose exec web python manage.py createsuperuser
docker compose exec web python manage.py startapp users
docker compose exec web python manage.py startapp equipment
docker compose exec web python manage.py startapp interventions
sudo chown -R $USER: .
docker compose exec web python manage.py makemigrations users
docker compose exec web python manage.py migrate
docker compose exec web python -m pip install django-simple-history
docker compose exec web python manage.py makemigrations
docker compose exec web python manage.py migrate
docker compose exec web python manage.py makemigrations equipment
docker compose exec web python manage.py migrate
View File
+110
View File
@@ -0,0 +1,110 @@
from django.contrib import admin
from simple_history.admin import SimpleHistoryAdmin
from .models import (
Computer,
Monitor,
Peripheral,
DigitalCertificate,
MaintenanceRecord,
DecommissionRecord,
)
# --- Inlines for Computer ---
class MaintenanceInlineComputer(admin.TabularInline):
model = MaintenanceRecord
fk_name = "computer"
extra = 0
class DecommissionInlineComputer(admin.StackedInline):
model = DecommissionRecord
fk_name = "computer"
extra = 0
max_num = 1
# --- Inlines for Monitor ---
class MaintenanceInlineMonitor(admin.TabularInline):
model = MaintenanceRecord
fk_name = "monitor"
extra = 0
class DecommissionInlineMonitor(admin.StackedInline):
model = DecommissionRecord
fk_name = "monitor"
extra = 0
max_num = 1
# --- Inlines for Peripheral ---
class MaintenanceInlinePeripheral(admin.TabularInline):
model = MaintenanceRecord
fk_name = "peripheral"
extra = 0
class DecommissionInlinePeripheral(admin.StackedInline):
model = DecommissionRecord
fk_name = "peripheral"
extra = 0
max_num = 1
@admin.register(Computer)
class ComputerAdmin(SimpleHistoryAdmin):
list_display = ("equipment_code", "oem_name", "status", "assigned_to", "entry_date")
list_filter = ("status", "entry_date", "assigned_to")
search_fields = ("equipment_code", "oem_name", "mac_address", "observations")
inlines = [MaintenanceInlineComputer, DecommissionInlineComputer]
@admin.register(Monitor)
class MonitorAdmin(SimpleHistoryAdmin):
list_display = ("equipment_code", "name", "status", "assigned_to", "entry_date")
list_filter = ("status", "entry_date", "assigned_to")
search_fields = ("equipment_code", "name", "serial_number")
inlines = [MaintenanceInlineMonitor, DecommissionInlineMonitor]
@admin.register(Peripheral)
class PeripheralAdmin(SimpleHistoryAdmin):
list_display = (
"equipment_code",
"name",
"peripheral_type",
"status",
"assigned_to",
)
list_filter = ("status", "peripheral_type", "entry_date", "assigned_to")
search_fields = ("equipment_code", "name", "serial_number")
inlines = [MaintenanceInlinePeripheral, DecommissionInlinePeripheral]
@admin.register(DigitalCertificate)
class DigitalCertificateAdmin(SimpleHistoryAdmin):
list_display = (
"get_certificate_type_display",
"assigned_to",
"issue_date",
"expiration_date",
"is_canceled",
)
list_filter = ("certificate_type", "is_canceled", "issue_date")
search_fields = ("assigned_to__name", "assigned_to__username")
# Optionally register the records independently so they can be searched globally
@admin.register(MaintenanceRecord)
class MaintenanceRecordAdmin(SimpleHistoryAdmin):
list_display = ("date", "operation_type", "performed_by")
list_filter = ("date", "operation_type")
search_fields = ("description", "performed_by")
@admin.register(DecommissionRecord)
class DecommissionRecordAdmin(SimpleHistoryAdmin):
list_display = ("document_number", "date", "reason")
list_filter = ("date",)
search_fields = ("document_number", "reason")
+5
View File
@@ -0,0 +1,5 @@
from django.apps import AppConfig
class EquipmentConfig(AppConfig):
name = 'equipment'
+196
View File
@@ -0,0 +1,196 @@
# Generated by Django 6.1 on 2026-08-18 12:20
import datetime
import django.db.models.deletion
import simple_history.models
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('users', '0002_historicalcompartiment_historicaldirectie_and_more'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Computer',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('equipment_code', models.CharField(blank=True, max_length=50, unique=True, verbose_name='Cod Echipament')),
('entry_date', models.DateField(default=datetime.date.today, verbose_name='Data intrării')),
('oem_name', models.CharField(max_length=255, verbose_name='Producător (OEM)')),
('motherboard', models.CharField(max_length=255, verbose_name='Placă de bază')),
('cpu', models.CharField(max_length=255, verbose_name='Procesor (CPU)')),
('ram', models.CharField(max_length=100, verbose_name='Memorie RAM')),
('storage', models.CharField(max_length=100, verbose_name='Stocare (HDD/SSD)')),
('optical_drive', models.CharField(blank=True, max_length=100, null=True, verbose_name='Unitate optică')),
('gpu', models.CharField(max_length=255, verbose_name='Placă video (GPU)')),
('mac_address', models.CharField(max_length=50, verbose_name='Adresă MAC')),
('os_license', models.CharField(max_length=255, verbose_name='Licență OS')),
('additional_licenses', models.TextField(blank=True, null=True, verbose_name='Licențe adiționale')),
('observations', models.TextField(blank=True, null=True, verbose_name='Observații')),
('assigned_to', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='computers', to='users.employee', verbose_name='Atribuit către')),
],
options={
'verbose_name': 'Computer / Laptop',
'verbose_name_plural': 'Computere / Laptopuri',
},
),
migrations.CreateModel(
name='DigitalCertificate',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('certificate_type', models.CharField(choices=[('cloud', 'Cloud'), ('usb', 'USB Token')], max_length=50, verbose_name='Tip Certificat')),
('issue_date', models.DateField(verbose_name='Data emiterii')),
('expiration_date', models.DateField(verbose_name='Data expirării')),
('is_canceled', models.BooleanField(default=False, verbose_name='Anulat?')),
('assigned_to', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='digital_certificates', to='users.employee', verbose_name='Titular Certificat')),
],
options={
'verbose_name': 'Certificat Digital',
'verbose_name_plural': 'Certificate Digitale',
},
),
migrations.CreateModel(
name='HistoricalComputer',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('equipment_code', models.CharField(blank=True, db_index=True, max_length=50, verbose_name='Cod Echipament')),
('entry_date', models.DateField(default=datetime.date.today, verbose_name='Data intrării')),
('oem_name', models.CharField(max_length=255, verbose_name='Producător (OEM)')),
('motherboard', models.CharField(max_length=255, verbose_name='Placă de bază')),
('cpu', models.CharField(max_length=255, verbose_name='Procesor (CPU)')),
('ram', models.CharField(max_length=100, verbose_name='Memorie RAM')),
('storage', models.CharField(max_length=100, verbose_name='Stocare (HDD/SSD)')),
('optical_drive', models.CharField(blank=True, max_length=100, null=True, verbose_name='Unitate optică')),
('gpu', models.CharField(max_length=255, verbose_name='Placă video (GPU)')),
('mac_address', models.CharField(max_length=50, verbose_name='Adresă MAC')),
('os_license', models.CharField(max_length=255, verbose_name='Licență OS')),
('additional_licenses', models.TextField(blank=True, null=True, verbose_name='Licențe adiționale')),
('observations', models.TextField(blank=True, null=True, verbose_name='Observații')),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('assigned_to', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='users.employee', verbose_name='Atribuit către')),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'historical Computer / Laptop',
'verbose_name_plural': 'historical Computere / Laptopuri',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='HistoricalDigitalCertificate',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('certificate_type', models.CharField(choices=[('cloud', 'Cloud'), ('usb', 'USB Token')], max_length=50, verbose_name='Tip Certificat')),
('issue_date', models.DateField(verbose_name='Data emiterii')),
('expiration_date', models.DateField(verbose_name='Data expirării')),
('is_canceled', models.BooleanField(default=False, verbose_name='Anulat?')),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('assigned_to', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='users.employee', verbose_name='Titular Certificat')),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'historical Certificat Digital',
'verbose_name_plural': 'historical Certificate Digitale',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='HistoricalMonitor',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('equipment_code', models.CharField(blank=True, db_index=True, max_length=50, verbose_name='Cod Echipament')),
('entry_date', models.DateField(default=datetime.date.today, verbose_name='Data intrării')),
('name', models.CharField(max_length=255, verbose_name='Nume Monitor')),
('serial_number', models.CharField(max_length=100, verbose_name='Număr Serial')),
('screen_diagonal', models.CharField(max_length=50, verbose_name='Diagonală (inch)')),
('observations', models.TextField(blank=True, null=True, verbose_name='Observații')),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('assigned_to', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='users.employee', verbose_name='Atribuit către')),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'historical Monitor',
'verbose_name_plural': 'historical Monitoare',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='HistoricalPeripheral',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('equipment_code', models.CharField(blank=True, db_index=True, max_length=50, verbose_name='Cod Echipament')),
('entry_date', models.DateField(default=datetime.date.today, verbose_name='Data intrării')),
('peripheral_type', models.CharField(choices=[('printer', 'Imprimantă'), ('ups', 'UPS'), ('scanner', 'Scanner'), ('copier', 'Copiator'), ('ethernet', 'Adaptor Ethernet'), ('audio', 'Boxe Audio'), ('hdd_ext', 'HDD Extern'), ('webcam', 'Cameră Web'), ('other', 'Altul')], max_length=50, verbose_name='Tip Periferic')),
('name', models.CharField(max_length=255, verbose_name='Nume Echipament')),
('serial_number', models.CharField(max_length=100, verbose_name='Număr Serial')),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('assigned_to', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='users.employee', verbose_name='Atribuit către')),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'historical Periferic',
'verbose_name_plural': 'historical Periferice',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='Monitor',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('equipment_code', models.CharField(blank=True, max_length=50, unique=True, verbose_name='Cod Echipament')),
('entry_date', models.DateField(default=datetime.date.today, verbose_name='Data intrării')),
('name', models.CharField(max_length=255, verbose_name='Nume Monitor')),
('serial_number', models.CharField(max_length=100, verbose_name='Număr Serial')),
('screen_diagonal', models.CharField(max_length=50, verbose_name='Diagonală (inch)')),
('observations', models.TextField(blank=True, null=True, verbose_name='Observații')),
('assigned_to', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='monitors', to='users.employee', verbose_name='Atribuit către')),
],
options={
'verbose_name': 'Monitor',
'verbose_name_plural': 'Monitoare',
},
),
migrations.CreateModel(
name='Peripheral',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('equipment_code', models.CharField(blank=True, max_length=50, unique=True, verbose_name='Cod Echipament')),
('entry_date', models.DateField(default=datetime.date.today, verbose_name='Data intrării')),
('peripheral_type', models.CharField(choices=[('printer', 'Imprimantă'), ('ups', 'UPS'), ('scanner', 'Scanner'), ('copier', 'Copiator'), ('ethernet', 'Adaptor Ethernet'), ('audio', 'Boxe Audio'), ('hdd_ext', 'HDD Extern'), ('webcam', 'Cameră Web'), ('other', 'Altul')], max_length=50, verbose_name='Tip Periferic')),
('name', models.CharField(max_length=255, verbose_name='Nume Echipament')),
('serial_number', models.CharField(max_length=100, verbose_name='Număr Serial')),
('assigned_to', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='peripherals', to='users.employee', verbose_name='Atribuit către')),
],
options={
'verbose_name': 'Periferic',
'verbose_name_plural': 'Periferice',
},
),
]
@@ -0,0 +1,63 @@
# Generated by Django 6.1 on 2026-08-18 12:45
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('equipment', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='computer',
name='equipment_code',
field=models.CharField(max_length=50, unique=True, verbose_name='Cod Echipament'),
),
migrations.AlterField(
model_name='computer',
name='gpu',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Placă video (GPU)'),
),
migrations.AlterField(
model_name='computer',
name='oem_name',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Producător (OEM)'),
),
migrations.AlterField(
model_name='historicalcomputer',
name='equipment_code',
field=models.CharField(db_index=True, max_length=50, verbose_name='Cod Echipament'),
),
migrations.AlterField(
model_name='historicalcomputer',
name='gpu',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Placă video (GPU)'),
),
migrations.AlterField(
model_name='historicalcomputer',
name='oem_name',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Producător (OEM)'),
),
migrations.AlterField(
model_name='historicalmonitor',
name='equipment_code',
field=models.CharField(db_index=True, max_length=50, verbose_name='Cod Echipament'),
),
migrations.AlterField(
model_name='historicalperipheral',
name='equipment_code',
field=models.CharField(db_index=True, max_length=50, verbose_name='Cod Echipament'),
),
migrations.AlterField(
model_name='monitor',
name='equipment_code',
field=models.CharField(max_length=50, unique=True, verbose_name='Cod Echipament'),
),
migrations.AlterField(
model_name='peripheral',
name='equipment_code',
field=models.CharField(max_length=50, unique=True, verbose_name='Cod Echipament'),
),
]
@@ -0,0 +1,130 @@
# Generated by Django 6.1 on 2026-08-18 12:55
import datetime
import django.db.models.deletion
import simple_history.models
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('equipment', '0002_alter_computer_equipment_code_alter_computer_gpu_and_more'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddField(
model_name='computer',
name='status',
field=models.CharField(choices=[('activ', 'Activ'), ('casat', 'Casat (Decommissioned)')], default='activ', max_length=20, verbose_name='Stare'),
),
migrations.AddField(
model_name='historicalcomputer',
name='status',
field=models.CharField(choices=[('activ', 'Activ'), ('casat', 'Casat (Decommissioned)')], default='activ', max_length=20, verbose_name='Stare'),
),
migrations.AddField(
model_name='historicalmonitor',
name='status',
field=models.CharField(choices=[('activ', 'Activ'), ('casat', 'Casat (Decommissioned)')], default='activ', max_length=20, verbose_name='Stare'),
),
migrations.AddField(
model_name='historicalperipheral',
name='status',
field=models.CharField(choices=[('activ', 'Activ'), ('casat', 'Casat (Decommissioned)')], default='activ', max_length=20, verbose_name='Stare'),
),
migrations.AddField(
model_name='monitor',
name='status',
field=models.CharField(choices=[('activ', 'Activ'), ('casat', 'Casat (Decommissioned)')], default='activ', max_length=20, verbose_name='Stare'),
),
migrations.AddField(
model_name='peripheral',
name='status',
field=models.CharField(choices=[('activ', 'Activ'), ('casat', 'Casat (Decommissioned)')], default='activ', max_length=20, verbose_name='Stare'),
),
migrations.CreateModel(
name='DecommissionRecord',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date', models.DateField(default=datetime.date.today, verbose_name='Data casării')),
('document_number', models.CharField(max_length=100, verbose_name='Număr document (Proces Verbal)')),
('reason', models.TextField(verbose_name='Motivul casării (Defect, Uzat moral, etc.)')),
('computer', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='decommission_log', to='equipment.computer')),
('monitor', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='decommission_log', to='equipment.monitor')),
('peripheral', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='decommission_log', to='equipment.peripheral')),
],
options={
'verbose_name': 'Înregistrare Casare',
'verbose_name_plural': 'Înregistrări Casare',
},
),
migrations.CreateModel(
name='HistoricalDecommissionRecord',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('date', models.DateField(default=datetime.date.today, verbose_name='Data casării')),
('document_number', models.CharField(max_length=100, verbose_name='Număr document (Proces Verbal)')),
('reason', models.TextField(verbose_name='Motivul casării (Defect, Uzat moral, etc.)')),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('computer', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='equipment.computer')),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
('monitor', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='equipment.monitor')),
('peripheral', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='equipment.peripheral')),
],
options={
'verbose_name': 'historical Înregistrare Casare',
'verbose_name_plural': 'historical Înregistrări Casare',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='HistoricalMaintenanceRecord',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('date', models.DateField(default=datetime.date.today, verbose_name='Data intervenției')),
('operation_type', models.CharField(max_length=150, verbose_name='Tip operațiune (Curățare, Upgrade, etc.)')),
('description', models.TextField(verbose_name='Descriere detaliată')),
('performed_by', models.CharField(max_length=100, verbose_name='Efectuat de')),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('computer', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='equipment.computer')),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
('monitor', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='equipment.monitor')),
('peripheral', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='equipment.peripheral')),
],
options={
'verbose_name': 'historical Foaie de Mentenanță',
'verbose_name_plural': 'historical Foi de Mentenanță',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='MaintenanceRecord',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date', models.DateField(default=datetime.date.today, verbose_name='Data intervenției')),
('operation_type', models.CharField(max_length=150, verbose_name='Tip operațiune (Curățare, Upgrade, etc.)')),
('description', models.TextField(verbose_name='Descriere detaliată')),
('performed_by', models.CharField(max_length=100, verbose_name='Efectuat de')),
('computer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='maintenance_logs', to='equipment.computer')),
('monitor', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='maintenance_logs', to='equipment.monitor')),
('peripheral', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='maintenance_logs', to='equipment.peripheral')),
],
options={
'verbose_name': 'Foaie de Mentenanță',
'verbose_name_plural': 'Foi de Mentenanță',
},
),
]
View File
+291
View File
@@ -0,0 +1,291 @@
from django.db import models
from datetime import date
from simple_history.models import HistoricalRecords
from users.models import Employee
from django.core.exceptions import ValidationError
EQUIPMENT_STATUS = [
("activ", "Activ"),
("casat", "Casat (Decommissioned)"),
]
class Computer(models.Model):
# Unique ID and Entry
equipment_code = models.CharField(
max_length=50, unique=True, verbose_name="Cod Echipament"
)
entry_date = models.DateField(default=date.today, verbose_name="Data intrării")
# Hardware Specs
oem_name = models.CharField(
max_length=255, null=True, blank=True, verbose_name="Producător (OEM)"
)
motherboard = models.CharField(max_length=255, verbose_name="Placă de bază")
cpu = models.CharField(max_length=255, verbose_name="Procesor (CPU)")
ram = models.CharField(max_length=100, verbose_name="Memorie RAM")
storage = models.CharField(max_length=100, verbose_name="Stocare (HDD/SSD)")
optical_drive = models.CharField(
max_length=100, blank=True, null=True, verbose_name="Unitate optică"
)
gpu = models.CharField(
max_length=255, null=True, blank=True, verbose_name="Placă video (GPU)"
)
mac_address = models.CharField(max_length=50, verbose_name="Adresă MAC")
# Software & Misc
os_license = models.CharField(max_length=255, verbose_name="Licență OS")
additional_licenses = models.TextField(
blank=True, null=True, verbose_name="Licențe adiționale"
)
observations = models.TextField(blank=True, null=True, verbose_name="Observații")
# Custody Assignment
assigned_to = models.ForeignKey(
Employee,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="computers",
verbose_name="Atribuit către",
)
status = models.CharField(
max_length=20, choices=EQUIPMENT_STATUS, default="activ", verbose_name="Stare"
)
history = HistoricalRecords()
def __str__(self):
return f"{self.equipment_code} - {self.oem_name or 'Sistem Asamblat'}"
class Meta:
verbose_name = "Computer / Laptop"
verbose_name_plural = "Computere / Laptopuri"
class Monitor(models.Model):
equipment_code = models.CharField(
max_length=50, unique=True, verbose_name="Cod Echipament"
)
entry_date = models.DateField(default=date.today, verbose_name="Data intrării")
name = models.CharField(max_length=255, verbose_name="Nume Monitor")
serial_number = models.CharField(max_length=100, verbose_name="Număr Serial")
screen_diagonal = models.CharField(max_length=50, verbose_name="Diagonală (inch)")
observations = models.TextField(blank=True, null=True, verbose_name="Observații")
assigned_to = models.ForeignKey(
Employee,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="monitors",
verbose_name="Atribuit către",
)
status = models.CharField(
max_length=20, choices=EQUIPMENT_STATUS, default="activ", verbose_name="Stare"
)
history = HistoricalRecords()
def __str__(self):
return f"{self.equipment_code} - {self.name}"
class Meta:
verbose_name = "Monitor"
verbose_name_plural = "Monitoare"
class Peripheral(models.Model):
PERIPHERAL_TYPES = [
("printer", "Imprimantă"),
("ups", "UPS"),
("scanner", "Scanner"),
("copier", "Copiator"),
("ethernet", "Adaptor Ethernet"),
("audio", "Boxe Audio"),
("hdd_ext", "HDD Extern"),
("webcam", "Cameră Web"),
("other", "Altul"),
]
equipment_code = models.CharField(
max_length=50, unique=True, verbose_name="Cod Echipament"
)
entry_date = models.DateField(default=date.today, verbose_name="Data intrării")
peripheral_type = models.CharField(
max_length=50, choices=PERIPHERAL_TYPES, verbose_name="Tip Periferic"
)
name = models.CharField(max_length=255, verbose_name="Nume Echipament")
serial_number = models.CharField(max_length=100, verbose_name="Număr Serial")
assigned_to = models.ForeignKey(
Employee,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="peripherals",
verbose_name="Atribuit către",
)
status = models.CharField(
max_length=20, choices=EQUIPMENT_STATUS, default="activ", verbose_name="Stare"
)
history = HistoricalRecords()
def __str__(self):
return f"{self.equipment_code} - {self.name} ({self.get_peripheral_type_display()})"
class Meta:
verbose_name = "Periferic"
verbose_name_plural = "Periferice"
class DigitalCertificate(models.Model):
CERT_TYPES = [
("cloud", "Cloud"),
("usb", "USB Token"),
]
certificate_type = models.CharField(
max_length=50, choices=CERT_TYPES, verbose_name="Tip Certificat"
)
issue_date = models.DateField(verbose_name="Data emiterii")
expiration_date = models.DateField(verbose_name="Data expirării")
is_canceled = models.BooleanField(default=False, verbose_name="Anulat?")
# Links directly to the employee
assigned_to = models.ForeignKey(
Employee,
on_delete=models.CASCADE,
related_name="digital_certificates",
verbose_name="Titular Certificat",
)
history = HistoricalRecords()
def __str__(self):
return f"Certificat {self.get_certificate_type_display()} - {self.assigned_to.name}"
class Meta:
verbose_name = "Certificat Digital"
verbose_name_plural = "Certificate Digitale"
class MaintenanceRecord(models.Model):
date = models.DateField(default=date.today, verbose_name="Data intervenției")
operation_type = models.CharField(
max_length=150, verbose_name="Tip operațiune (Curățare, Upgrade, etc.)"
)
description = models.TextField(verbose_name="Descriere detaliată")
performed_by = models.CharField(max_length=100, verbose_name="Efectuat de")
# Links to equipment (only one should be populated)
computer = models.ForeignKey(
Computer,
on_delete=models.CASCADE,
null=True,
blank=True,
related_name="maintenance_logs",
)
monitor = models.ForeignKey(
Monitor,
on_delete=models.CASCADE,
null=True,
blank=True,
related_name="maintenance_logs",
)
peripheral = models.ForeignKey(
Peripheral,
on_delete=models.CASCADE,
null=True,
blank=True,
related_name="maintenance_logs",
)
history = HistoricalRecords()
def clean(self):
equipment_count = sum(
[bool(self.computer), bool(self.monitor), bool(self.peripheral)]
)
if equipment_count != 1:
raise ValidationError(
"O înregistrare de mentenanță trebuie să fie legată de exact un singur echipament."
)
def save(self, *args, **kwargs):
self.clean()
super().save(*args, **kwargs)
def __str__(self):
return f"{self.date} - {self.operation_type}"
class Meta:
verbose_name = "Foaie de Mentenanță"
verbose_name_plural = "Foi de Mentenanță"
class DecommissionRecord(models.Model):
date = models.DateField(default=date.today, verbose_name="Data casării")
document_number = models.CharField(
max_length=100, verbose_name="Număr document (Proces Verbal)"
)
reason = models.TextField(verbose_name="Motivul casării (Defect, Uzat moral, etc.)")
# Links to equipment
computer = models.OneToOneField(
Computer,
on_delete=models.CASCADE,
null=True,
blank=True,
related_name="decommission_log",
)
monitor = models.OneToOneField(
Monitor,
on_delete=models.CASCADE,
null=True,
blank=True,
related_name="decommission_log",
)
peripheral = models.OneToOneField(
Peripheral,
on_delete=models.CASCADE,
null=True,
blank=True,
related_name="decommission_log",
)
history = HistoricalRecords()
def clean(self):
equipment_count = sum(
[bool(self.computer), bool(self.monitor), bool(self.peripheral)]
)
if equipment_count != 1:
raise ValidationError(
"Un proces verbal de casare trebuie să fie legat de exact un singur echipament."
)
def save(self, *args, **kwargs):
self.clean()
super().save(*args, **kwargs)
# Automatically update the status of the linked equipment to 'casat'
if self.computer:
self.computer.status = "casat"
self.computer.save()
elif self.monitor:
self.monitor.status = "casat"
self.monitor.save()
elif self.peripheral:
self.peripheral.status = "casat"
self.peripheral.save()
def __str__(self):
return f"Casare {self.document_number} din {self.date}"
class Meta:
verbose_name = "Înregistrare Casare"
verbose_name_plural = "Înregistrări Casare"
+3
View File
@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.
+3
View File
@@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.
+50 -37
View File
@@ -11,6 +11,7 @@ https://docs.djangoproject.com/en/6.1/ref/settings/
""" """
from pathlib import Path from pathlib import Path
import os
# Build paths inside the project like this: BASE_DIR / 'subdir'. # Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent BASE_DIR = Path(__file__).resolve().parent.parent
@@ -20,62 +21,74 @@ BASE_DIR = Path(__file__).resolve().parent.parent
# See https://docs.djangoproject.com/en/6.1/howto/deployment/checklist/ # See https://docs.djangoproject.com/en/6.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret! # SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-z(p$%wh6x^_9!1cgt%!s$w8k39(fox-^!iujxai42_wncja4%s' SECRET_KEY = "django-insecure-z(p$%wh6x^_9!1cgt%!s$w8k39(fox-^!iujxai42_wncja4%s"
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True DEBUG = True
ALLOWED_HOSTS = [] ALLOWED_HOSTS = ["localhost", "127.0.0.1", "0.0.0.0"]
# Application definition # Application definition
INSTALLED_APPS = [ INSTALLED_APPS = [
'django.contrib.admin', "django.contrib.admin",
'django.contrib.auth', "django.contrib.auth",
'django.contrib.contenttypes', "django.contrib.contenttypes",
'django.contrib.sessions', "django.contrib.sessions",
'django.contrib.messages', "django.contrib.messages",
'django.contrib.staticfiles', "django.contrib.staticfiles",
# third-party apps:
"simple_history",
# custom apps
"users",
"equipment",
"interventions",
] ]
MIDDLEWARE = [ MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', "django.middleware.security.SecurityMiddleware",
'django.contrib.sessions.middleware.SessionMiddleware', "django.contrib.sessions.middleware.SessionMiddleware",
'django.middleware.common.CommonMiddleware', "django.middleware.common.CommonMiddleware",
'django.middleware.csrf.CsrfViewMiddleware', "django.middleware.csrf.CsrfViewMiddleware",
'django.contrib.auth.middleware.AuthenticationMiddleware', "django.contrib.auth.middleware.AuthenticationMiddleware",
'django.contrib.messages.middleware.MessageMiddleware', "django.contrib.messages.middleware.MessageMiddleware",
'django.middleware.clickjacking.XFrameOptionsMiddleware', "django.middleware.clickjacking.XFrameOptionsMiddleware",
# Add this line to track the user making the changes:
"simple_history.middleware.HistoryRequestMiddleware",
] ]
ROOT_URLCONF = 'eumi__v4_project.urls' ROOT_URLCONF = "eumi__v4_project.urls"
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', "BACKEND": "django.template.backends.django.DjangoTemplates",
'DIRS': [], "DIRS": [],
'APP_DIRS': True, "APP_DIRS": True,
'OPTIONS': { "OPTIONS": {
'context_processors': [ "context_processors": [
'django.template.context_processors.request', "django.template.context_processors.request",
'django.contrib.auth.context_processors.auth', "django.contrib.auth.context_processors.auth",
'django.contrib.messages.context_processors.messages', "django.contrib.messages.context_processors.messages",
], ],
}, },
}, },
] ]
WSGI_APPLICATION = 'eumi__v4_project.wsgi.application' WSGI_APPLICATION = "eumi__v4_project.wsgi.application"
# Database # Database
# https://docs.djangoproject.com/en/6.1/ref/settings/#databases # https://docs.djangoproject.com/en/6.1/ref/settings/#databases
DATABASES = { DATABASES = {
'default': { "default": {
'ENGINE': 'django.db.backends.sqlite3', "ENGINE": "django.db.backends.postgresql",
'NAME': BASE_DIR / 'db.sqlite3', "NAME": os.environ.get("DB_NAME", "djangodb"),
"USER": os.environ.get("DB_USER", "djangouser"),
"PASSWORD": os.environ.get("DB_PASSWORD", "djangopass"),
"HOST": os.environ.get("DB_HOST", "db"),
"PORT": os.environ.get("DB_PORT", "5432"),
} }
} }
@@ -85,16 +98,16 @@ DATABASES = {
AUTH_PASSWORD_VALIDATORS = [ AUTH_PASSWORD_VALIDATORS = [
{ {
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
}, },
{ {
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
}, },
{ {
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
}, },
{ {
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
}, },
] ]
@@ -102,9 +115,9 @@ AUTH_PASSWORD_VALIDATORS = [
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/6.1/topics/i18n/ # https://docs.djangoproject.com/en/6.1/topics/i18n/
LANGUAGE_CODE = 'en-us' LANGUAGE_CODE = "ro"
TIME_ZONE = 'UTC' TIME_ZONE = "Europe/Bucharest"
USE_I18N = True USE_I18N = True
@@ -114,14 +127,14 @@ USE_TZ = True
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/6.1/howto/static-files/ # https://docs.djangoproject.com/en/6.1/howto/static-files/
STATIC_URL = 'static/' STATIC_URL = "static/"
# Email # Email
# https://docs.djangoproject.com/en/6.1/topics/email/#topic-email-configuration # https://docs.djangoproject.com/en/6.1/topics/email/#topic-email-configuration
MAILERS = { MAILERS = {
'default': { "default": {
'BACKEND': 'django.core.mail.backends.console.EmailBackend', "BACKEND": "django.core.mail.backends.console.EmailBackend",
}, },
} }
View File
+31
View File
@@ -0,0 +1,31 @@
from django.contrib import admin
from simple_history.admin import SimpleHistoryAdmin
from .models import Intervention
@admin.register(Intervention)
class InterventionAdmin(SimpleHistoryAdmin):
list_display = (
"id",
"title",
"status",
"priority",
"reported_by",
"assigned_to_it",
"created_at",
)
list_filter = ("status", "priority", "created_at", "assigned_to_it")
search_fields = ("title", "description", "reported_by__name", "assigned_to_it")
readonly_fields = ("created_at", "updated_at")
fieldsets = (
(
"Detalii Tichet",
{"fields": ("title", "description", "reported_by", "priority", "status")},
),
(
"Asignare și Rezolvare",
{"fields": ("assigned_to_it", "resolution_notes", "resolved_at")},
),
("Timpi", {"fields": ("created_at", "updated_at"), "classes": ("collapse",)}),
)
+5
View File
@@ -0,0 +1,5 @@
from django.apps import AppConfig
class InterventionsConfig(AppConfig):
name = 'interventions'
+68
View File
@@ -0,0 +1,68 @@
# Generated by Django 6.1 on 2026-08-18 13:10
import django.db.models.deletion
import simple_history.models
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('users', '0002_historicalcompartiment_historicaldirectie_and_more'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='HistoricalIntervention',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('title', models.CharField(max_length=255, verbose_name='Titlu scurt / Subiect')),
('description', models.TextField(verbose_name='Descrierea problemei')),
('created_at', models.DateTimeField(blank=True, editable=False, verbose_name='Data raportării')),
('updated_at', models.DateTimeField(blank=True, editable=False, verbose_name='Ultima actualizare')),
('resolved_at', models.DateTimeField(blank=True, null=True, verbose_name='Data soluționării')),
('status', models.CharField(choices=[('nou', 'Nou (Deschis)'), ('in_lucru', 'În lucru'), ('in_asteptare', 'În așteptare'), ('solutionat', 'Soluționat'), ('inchis', 'Închis')], default='nou', max_length=20, verbose_name='Stare')),
('priority', models.CharField(choices=[('scazuta', 'Scăzută'), ('medie', 'Medie'), ('ridicata', 'Ridicată'), ('critica', 'Critică (Urgență)')], default='medie', max_length=20, verbose_name='Prioritate')),
('assigned_to_it', models.CharField(blank=True, max_length=150, null=True, verbose_name='Atribuit către (IT)')),
('resolution_notes', models.TextField(blank=True, null=True, verbose_name='Note de soluționare')),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
('reported_by', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='users.employee', verbose_name='Raportat de (Utilizator)')),
],
options={
'verbose_name': 'historical Intervenție Tehnică',
'verbose_name_plural': 'historical Intervenții Tehnice',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='Intervention',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=255, verbose_name='Titlu scurt / Subiect')),
('description', models.TextField(verbose_name='Descrierea problemei')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Data raportării')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Ultima actualizare')),
('resolved_at', models.DateTimeField(blank=True, null=True, verbose_name='Data soluționării')),
('status', models.CharField(choices=[('nou', 'Nou (Deschis)'), ('in_lucru', 'În lucru'), ('in_asteptare', 'În așteptare'), ('solutionat', 'Soluționat'), ('inchis', 'Închis')], default='nou', max_length=20, verbose_name='Stare')),
('priority', models.CharField(choices=[('scazuta', 'Scăzută'), ('medie', 'Medie'), ('ridicata', 'Ridicată'), ('critica', 'Critică (Urgență)')], default='medie', max_length=20, verbose_name='Prioritate')),
('assigned_to_it', models.CharField(blank=True, max_length=150, null=True, verbose_name='Atribuit către (IT)')),
('resolution_notes', models.TextField(blank=True, null=True, verbose_name='Note de soluționare')),
('reported_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reported_interventions', to='users.employee', verbose_name='Raportat de (Utilizator)')),
],
options={
'verbose_name': 'Intervenție Tehnică',
'verbose_name_plural': 'Intervenții Tehnice',
'ordering': ['-created_at'],
},
),
]
+65
View File
@@ -0,0 +1,65 @@
from django.db import models
from simple_history.models import HistoricalRecords
from users.models import Employee
class Intervention(models.Model):
STATUS_CHOICES = [
("nou", "Nou (Deschis)"),
("in_lucru", "În lucru"),
("in_asteptare", "În așteptare"),
("solutionat", "Soluționat"),
("inchis", "Închis"),
]
PRIORITY_CHOICES = [
("scazuta", "Scăzută"),
("medie", "Medie"),
("ridicata", "Ridicată"),
("critica", "Critică (Urgență)"),
]
title = models.CharField(max_length=255, verbose_name="Titlu scurt / Subiect")
description = models.TextField(verbose_name="Descrierea problemei")
# Tracking the timeline
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Data raportării")
updated_at = models.DateTimeField(auto_now=True, verbose_name="Ultima actualizare")
resolved_at = models.DateTimeField(
null=True, blank=True, verbose_name="Data soluționării"
)
status = models.CharField(
max_length=20, choices=STATUS_CHOICES, default="nou", verbose_name="Stare"
)
priority = models.CharField(
max_length=20,
choices=PRIORITY_CHOICES,
default="medie",
verbose_name="Prioritate",
)
# People involved
reported_by = models.ForeignKey(
Employee,
on_delete=models.CASCADE,
related_name="reported_interventions",
verbose_name="Raportat de (Utilizator)",
)
assigned_to_it = models.CharField(
max_length=150, blank=True, null=True, verbose_name="Atribuit către (IT)"
)
resolution_notes = models.TextField(
blank=True, null=True, verbose_name="Note de soluționare"
)
history = HistoricalRecords()
def __str__(self):
return f"Ticket #{self.id} - {self.title} ({self.get_status_display()})"
class Meta:
verbose_name = "Intervenție Tehnică"
verbose_name_plural = "Intervenții Tehnice"
ordering = ["-created_at"]
+3
View File
@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.
+3
View File
@@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.
+2
View File
@@ -2,3 +2,5 @@ asgiref==3.12.1
Django==6.1 Django==6.1
psycopg2-binary==2.9.12 psycopg2-binary==2.9.12
sqlparse==0.6.0 sqlparse==0.6.0
django-simple-history
View File
+38
View File
@@ -0,0 +1,38 @@
from django.contrib import admin
from simple_history.admin import SimpleHistoryAdmin
from .models import Directie, Serviciu, Compartiment, Employee
@admin.register(Directie)
class DirectieAdmin(SimpleHistoryAdmin):
list_display = ("name",)
search_fields = ("name",)
@admin.register(Serviciu)
class ServiciuAdmin(SimpleHistoryAdmin):
list_display = ("name", "directie")
list_filter = ("directie",)
search_fields = ("name",)
@admin.register(Compartiment)
class CompartimentAdmin(SimpleHistoryAdmin):
list_display = ("name", "directie", "serviciu")
list_filter = ("directie", "serviciu")
search_fields = ("name",)
@admin.register(Employee)
class EmployeeAdmin(SimpleHistoryAdmin):
list_display = (
"name",
"username",
"role",
"email",
"directie",
"serviciu",
"compartiment",
)
list_filter = ("directie", "serviciu", "compartiment")
search_fields = ("name", "username", "email")
+5
View File
@@ -0,0 +1,5 @@
from django.apps import AppConfig
class UsersConfig(AppConfig):
name = 'users'
+74
View File
@@ -0,0 +1,74 @@
# Generated by Django 6.1 on 2026-08-18 11:35
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Directie',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Nume Direcție')),
],
options={
'verbose_name': 'Direcție',
'verbose_name_plural': 'Direcții',
},
),
migrations.CreateModel(
name='Compartiment',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Nume Compartiment')),
('directie', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='compartimente_directe', to='users.directie', verbose_name='Direcție (Direct)')),
],
options={
'verbose_name': 'Compartiment',
'verbose_name_plural': 'Compartimente',
},
),
migrations.CreateModel(
name='Serviciu',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Nume Serviciu')),
('directie', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='servicii', to='users.directie', verbose_name='Direcție')),
],
options={
'verbose_name': 'Serviciu',
'verbose_name_plural': 'Servicii',
},
),
migrations.CreateModel(
name='Employee',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Name')),
('username', models.CharField(max_length=150, unique=True, verbose_name='User name')),
('role', models.CharField(max_length=255, verbose_name='Function/role')),
('office_number', models.CharField(max_length=50, verbose_name='Office number')),
('email', models.EmailField(max_length=254, verbose_name='Email address')),
('observations', models.TextField(blank=True, null=True, verbose_name='Observations')),
('compartiment', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='employees', to='users.compartiment', verbose_name='Compartiment')),
('directie', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='employees', to='users.directie', verbose_name='Direcție')),
('serviciu', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='employees', to='users.serviciu', verbose_name='Serviciu')),
],
options={
'verbose_name': 'Angajat / Utilizator',
'verbose_name_plural': 'Angajați / Utilizatori',
},
),
migrations.AddField(
model_name='compartiment',
name='serviciu',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='compartimente_subordonate', to='users.serviciu', verbose_name='Serviciu'),
),
]
@@ -0,0 +1,104 @@
# Generated by Django 6.1 on 2026-08-18 11:43
import django.db.models.deletion
import simple_history.models
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='HistoricalCompartiment',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Nume Compartiment')),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('directie', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='users.directie', verbose_name='Direcție (Direct)')),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
('serviciu', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='users.serviciu', verbose_name='Serviciu')),
],
options={
'verbose_name': 'historical Compartiment',
'verbose_name_plural': 'historical Compartimente',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='HistoricalDirectie',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Nume Direcție')),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'historical Direcție',
'verbose_name_plural': 'historical Direcții',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='HistoricalEmployee',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Name')),
('username', models.CharField(db_index=True, max_length=150, verbose_name='User name')),
('role', models.CharField(max_length=255, verbose_name='Function/role')),
('office_number', models.CharField(max_length=50, verbose_name='Office number')),
('email', models.EmailField(max_length=254, verbose_name='Email address')),
('observations', models.TextField(blank=True, null=True, verbose_name='Observations')),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('compartiment', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='users.compartiment', verbose_name='Compartiment')),
('directie', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='users.directie', verbose_name='Direcție')),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
('serviciu', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='users.serviciu', verbose_name='Serviciu')),
],
options={
'verbose_name': 'historical Angajat / Utilizator',
'verbose_name_plural': 'historical Angajați / Utilizatori',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='HistoricalServiciu',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Nume Serviciu')),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('directie', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='users.directie', verbose_name='Direcție')),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'historical Serviciu',
'verbose_name_plural': 'historical Servicii',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
]
View File
+126
View File
@@ -0,0 +1,126 @@
from django.db import models
from django.core.exceptions import ValidationError
from simple_history.models import HistoricalRecords
class Directie(models.Model):
name = models.CharField(max_length=255, verbose_name="Nume Direcție")
history = HistoricalRecords()
def __str__(self):
return self.name
class Meta:
verbose_name = "Direcție"
verbose_name_plural = "Direcții"
class Serviciu(models.Model):
name = models.CharField(max_length=255, verbose_name="Nume Serviciu")
# A Serviciu is directly assigned to a Direcție
directie = models.ForeignKey(
Directie,
on_delete=models.CASCADE,
related_name="servicii",
verbose_name="Direcție",
)
history = HistoricalRecords()
def __str__(self):
return self.name
class Meta:
verbose_name = "Serviciu"
verbose_name_plural = "Servicii"
class Compartiment(models.Model):
name = models.CharField(max_length=255, verbose_name="Nume Compartiment")
# A Compartiment can be part of a Serviciu or directly included in a Direcție
directie = models.ForeignKey(
Directie,
on_delete=models.CASCADE,
null=True,
blank=True,
related_name="compartimente_directe",
verbose_name="Direcție (Direct)",
)
serviciu = models.ForeignKey(
Serviciu,
on_delete=models.CASCADE,
null=True,
blank=True,
related_name="compartimente_subordonate",
verbose_name="Serviciu",
)
history = HistoricalRecords()
def clean(self):
# Validation to ensure the Compartiment belongs to either a Direcție OR a Serviciu, but not both or neither.
if not self.directie and not self.serviciu:
raise ValidationError(
"Un compartiment trebuie să aparțină de o Direcție sau de un Serviciu."
)
if self.directie and self.serviciu:
raise ValidationError(
"Un compartiment nu poate aparține simultan și de o Direcție și de un Serviciu."
)
def save(self, *args, **kwargs):
self.clean()
super().save(*args, **kwargs)
def __str__(self):
return self.name
class Meta:
verbose_name = "Compartiment"
verbose_name_plural = "Compartimente"
class Employee(models.Model):
"""
Represents the 'Users' from the project specification who will hold equipment.
Named 'Employee' to avoid conflicts with Django's built-in auth.User model.
"""
name = models.CharField(max_length=255, verbose_name="Name")
username = models.CharField(max_length=150, unique=True, verbose_name="User name")
role = models.CharField(max_length=255, verbose_name="Function/role")
office_number = models.CharField(max_length=50, verbose_name="Office number")
email = models.EmailField(verbose_name="Email address")
observations = models.TextField(null=True, blank=True, verbose_name="Observations")
# Organizational Placement - Employees can be placed at any of the three levels
directie = models.ForeignKey(
Directie,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="employees",
verbose_name="Direcție",
)
serviciu = models.ForeignKey(
Serviciu,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="employees",
verbose_name="Serviciu",
)
compartiment = models.ForeignKey(
Compartiment,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="employees",
verbose_name="Compartiment",
)
history = HistoricalRecords()
def __str__(self):
return f"{self.name} ({self.username})"
class Meta:
verbose_name = "Angajat / Utilizator"
verbose_name_plural = "Angajați / Utilizatori"
+3
View File
@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.
+3
View File
@@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.