patch oko teksta PDFa i UI tablica
This commit is contained in:
202
backend/core/users/management/commands/import_users_csv.py
Normal file
202
backend/core/users/management/commands/import_users_csv.py
Normal file
@@ -0,0 +1,202 @@
|
||||
import csv
|
||||
from pathlib import Path
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db import transaction
|
||||
|
||||
from modules.crm.models import Client
|
||||
|
||||
|
||||
def _to_bool(value, field_name):
|
||||
if value is None or value == "":
|
||||
return None
|
||||
normalized = str(value).strip().lower()
|
||||
if normalized in {"1", "true", "t", "yes", "y", "da"}:
|
||||
return True
|
||||
if normalized in {"0", "false", "f", "no", "n", "ne"}:
|
||||
return False
|
||||
raise CommandError(f"Neispravna boolean vrijednost za '{field_name}': {value}")
|
||||
|
||||
|
||||
def _to_int(value, field_name):
|
||||
if value is None or value == "":
|
||||
return None
|
||||
try:
|
||||
return int(str(value).strip())
|
||||
except (TypeError, ValueError) as exc:
|
||||
raise CommandError(f"Neispravna brojčana vrijednost za '{field_name}': {value}") from exc
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Uvozi korisnike (radnike) iz CSV datoteke u CustomUser model."
|
||||
|
||||
expected_columns = [
|
||||
"email",
|
||||
"first_name",
|
||||
"last_name",
|
||||
"password",
|
||||
"is_serviser",
|
||||
"is_kupac",
|
||||
"is_team_member",
|
||||
"is_staff",
|
||||
"is_superuser",
|
||||
"is_active",
|
||||
"telefon",
|
||||
"oib",
|
||||
"occupation",
|
||||
"residence",
|
||||
"work_position",
|
||||
"licence_number",
|
||||
"is_verified",
|
||||
"client_id",
|
||||
"client_tax_id",
|
||||
]
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument("csv_path", type=str, help="Putanja do CSV datoteke.")
|
||||
parser.add_argument(
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="Validira i simulira import bez spremanja u bazu.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--create-only",
|
||||
action="store_true",
|
||||
help="Kreira samo nove korisnike; postojeće preskače.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--default-password",
|
||||
type=str,
|
||||
default="",
|
||||
help="Fallback lozinka za nove korisnike ako polje 'password' nije popunjeno.",
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
csv_path = Path(options["csv_path"]).expanduser().resolve()
|
||||
dry_run = options["dry_run"]
|
||||
create_only = options["create_only"]
|
||||
default_password = options["default_password"]
|
||||
|
||||
if not csv_path.exists():
|
||||
raise CommandError(f"CSV datoteka ne postoji: {csv_path}")
|
||||
|
||||
User = get_user_model()
|
||||
created_count = 0
|
||||
updated_count = 0
|
||||
skipped_count = 0
|
||||
|
||||
with csv_path.open("r", encoding="utf-8-sig", newline="") as handle:
|
||||
reader = csv.DictReader(handle)
|
||||
if not reader.fieldnames:
|
||||
raise CommandError("CSV nema zaglavlje (header).")
|
||||
|
||||
normalized_headers = [str(h).strip() for h in reader.fieldnames]
|
||||
missing = [column for column in self.expected_columns if column not in normalized_headers]
|
||||
if missing:
|
||||
raise CommandError("CSV nema obavezna polja: " + ", ".join(missing))
|
||||
|
||||
row_number = 1
|
||||
with transaction.atomic():
|
||||
for row in reader:
|
||||
row_number += 1
|
||||
if not any((value or "").strip() for value in row.values()):
|
||||
continue
|
||||
|
||||
email = (row.get("email") or "").strip().lower()
|
||||
if not email:
|
||||
raise CommandError(f"Red {row_number}: email je obavezan.")
|
||||
|
||||
user = User.objects.filter(email__iexact=email).first()
|
||||
if user and create_only:
|
||||
skipped_count += 1
|
||||
self.stdout.write(
|
||||
self.style.WARNING(
|
||||
f"Red {row_number}: preskočeno (korisnik već postoji) {email}"
|
||||
)
|
||||
)
|
||||
continue
|
||||
|
||||
is_create = user is None
|
||||
if is_create:
|
||||
password = (row.get("password") or "").strip() or default_password
|
||||
if not password:
|
||||
raise CommandError(
|
||||
f"Red {row_number}: password je obavezan za nove korisnike "
|
||||
"(ili koristite --default-password)."
|
||||
)
|
||||
user = User(email=email)
|
||||
user.set_password(password)
|
||||
else:
|
||||
password = (row.get("password") or "").strip()
|
||||
if password:
|
||||
user.set_password(password)
|
||||
|
||||
client = self._resolve_client(row=row, row_number=row_number)
|
||||
|
||||
user.first_name = (row.get("first_name") or "").strip()
|
||||
user.last_name = (row.get("last_name") or "").strip()
|
||||
user.telefon = (row.get("telefon") or "").strip()
|
||||
user.oib = (row.get("oib") or "").strip() or None
|
||||
user.occupation = (row.get("occupation") or "").strip()
|
||||
user.residence = (row.get("residence") or "").strip()
|
||||
user.work_position = (row.get("work_position") or "").strip()
|
||||
user.licence_number = (row.get("licence_number") or "").strip() or None
|
||||
|
||||
for bool_field in [
|
||||
"is_serviser",
|
||||
"is_kupac",
|
||||
"is_team_member",
|
||||
"is_staff",
|
||||
"is_superuser",
|
||||
"is_active",
|
||||
"is_verified",
|
||||
]:
|
||||
parsed = _to_bool(row.get(bool_field), bool_field)
|
||||
if parsed is not None:
|
||||
setattr(user, bool_field, parsed)
|
||||
|
||||
user.client_profile = client
|
||||
user.username = None
|
||||
|
||||
user.full_clean()
|
||||
if not dry_run:
|
||||
user.save()
|
||||
|
||||
if is_create:
|
||||
created_count += 1
|
||||
else:
|
||||
updated_count += 1
|
||||
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
f"Red {row_number}: {'kreiran' if is_create else 'ažuriran'} korisnik {email}"
|
||||
)
|
||||
)
|
||||
|
||||
if dry_run:
|
||||
transaction.set_rollback(True)
|
||||
|
||||
summary = (
|
||||
f"Import korisnika završen. created={created_count}, updated={updated_count}, "
|
||||
f"skipped={skipped_count}, dry_run={dry_run}"
|
||||
)
|
||||
self.stdout.write(self.style.SUCCESS(summary))
|
||||
|
||||
def _resolve_client(self, *, row, row_number):
|
||||
client_id = _to_int(row.get("client_id"), "client_id")
|
||||
client_tax_id = (row.get("client_tax_id") or "").strip()
|
||||
|
||||
if client_id:
|
||||
client = Client.objects.filter(id=client_id).first()
|
||||
if not client:
|
||||
raise CommandError(f"Red {row_number}: klijent s ID={client_id} ne postoji.")
|
||||
return client
|
||||
|
||||
if client_tax_id:
|
||||
client = Client.objects.filter(tax_id=client_tax_id).first()
|
||||
if not client:
|
||||
raise CommandError(f"Red {row_number}: klijent s tax_id={client_tax_id} ne postoji.")
|
||||
return client
|
||||
|
||||
return None
|
||||
Reference in New Issue
Block a user