From d54bf8a44c7c38399e32182889d01d8300fd80d4 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Fri, 6 Feb 2026 07:07:50 +0000 Subject: [PATCH] Ver 12 Working --- core/__pycache__/models.cpython-311.pyc | Bin 12056 -> 14496 bytes .../update_permission_names.cpython-311.pyc | Bin 0 -> 6336 bytes .../commands/update_permission_names.py | 103 ++++++++++++++++++ ..._alter_expenselineitem_options_and_more.py | 53 +++++++++ ...elineitem_options_and_more.cpython-311.pyc | Bin 0 -> 1904 bytes core/models.py | 40 +++++++ 6 files changed, 196 insertions(+) create mode 100644 core/management/commands/__pycache__/update_permission_names.cpython-311.pyc create mode 100644 core/management/commands/update_permission_names.py create mode 100644 core/migrations/0010_alter_expenselineitem_options_and_more.py create mode 100644 core/migrations/__pycache__/0010_alter_expenselineitem_options_and_more.cpython-311.pyc diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index d8999040e5a95506751d968068aa40e012327bfa..34dac78a1a7c9bcd0ebfa9c70bfffe17edc6653e 100644 GIT binary patch delta 6117 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 diff --git a/core/management/commands/__pycache__/update_permission_names.cpython-311.pyc b/core/management/commands/__pycache__/update_permission_names.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6bdad8dc65a587ac2e716f8801f172d1c7f55544 GIT binary patch literal 6336 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 New Name + # Or just (codename) -> New Name if unique enough, but (app, model) is safer. + + # We can also map just the model's permissions generically if we want. + + renames = { + # Core App - Business Logic + ('core', 'project', 'add_project'): 'Manage: Create New Project', + ('core', 'project', 'change_project'): 'Manage: Edit Project Details', + ('core', 'project', 'delete_project'): 'Manage: Delete Project', + ('core', 'project', 'view_project'): 'Manage: View Projects (Dashboard Access)', + + ('core', 'worker', 'add_worker'): 'Manage: Add New Worker', + ('core', 'worker', 'change_worker'): 'Manage: Edit Worker Details', + ('core', 'worker', 'delete_worker'): 'Manage: Delete Worker', + ('core', 'worker', 'view_worker'): 'Manage: View Worker Profiles', + + ('core', 'team', 'add_team'): 'Manage: Create Team', + ('core', 'team', 'change_team'): 'Manage: Edit Team Structure', + ('core', 'team', 'delete_team'): 'Manage: Delete Team', + ('core', 'team', 'view_team'): 'Manage: View Teams', + + ('core', 'worklog', 'add_worklog'): 'Log Work: Add Attendance Entry', + ('core', 'worklog', 'change_worklog'): 'Log Work: Edit Attendance Entry', + ('core', 'worklog', 'delete_worklog'): 'Log Work: Delete Attendance Entry', + ('core', 'worklog', 'view_worklog'): 'Log Work: View Attendance History', + + ('core', 'payrollrecord', 'add_payrollrecord'): 'Payroll: Generate Payment Record', + ('core', 'payrollrecord', 'change_payrollrecord'): 'Payroll: Edit Payment Record', + ('core', 'payrollrecord', 'delete_payrollrecord'): 'Payroll: Delete Payment Record', + ('core', 'payrollrecord', 'view_payrollrecord'): 'Payroll: View Payment History', + + ('core', 'loan', 'add_loan'): 'Loans: Create New Loan', + ('core', 'loan', 'change_loan'): 'Loans: Edit Loan Details', + ('core', 'loan', 'delete_loan'): 'Loans: Delete Loan', + ('core', 'loan', 'view_loan'): 'Loans: View Loan Registry', + + ('core', 'expensereceipt', 'add_expensereceipt'): 'Receipts: Add Expense Receipt', + ('core', 'expensereceipt', 'change_expensereceipt'): 'Receipts: Edit Expense Receipt', + ('core', 'expensereceipt', 'delete_expensereceipt'): 'Receipts: Delete Expense Receipt', + ('core', 'expensereceipt', 'view_expensereceipt'): 'Receipts: View Expense History', + + # Auth App + ('auth', 'user', 'add_user'): 'Admin: Create User Accounts', + ('auth', 'user', 'change_user'): 'Admin: Edit User Accounts', + ('auth', 'user', 'delete_user'): 'Admin: Delete User Accounts', + ('auth', 'user', 'view_user'): 'Admin: View User Accounts', + + ('auth', 'group', 'add_group'): 'Admin: Create Permission Groups', + ('auth', 'group', 'change_group'): 'Admin: Edit Permission Groups', + ('auth', 'group', 'delete_group'): 'Admin: Delete Permission Groups', + ('auth', 'group', 'view_group'): 'Admin: View Permission Groups', + + # System / Technical (Marking as Technical to help user ignore them) + ('sessions', 'session', 'add_session'): 'System (Tech): Add Session', + ('sessions', 'session', 'change_session'): 'System (Tech): Manage Sessions', + ('sessions', 'session', 'delete_session'): 'System (Tech): Clear Sessions', + ('sessions', 'session', 'view_session'): 'System (Tech): View Sessions', + + ('admin', 'logentry', 'add_logentry'): 'System (Tech): Add Admin Log', + ('admin', 'logentry', 'change_logentry'): 'System (Tech): Edit Admin Log', + ('admin', 'logentry', 'delete_logentry'): 'System (Tech): Clear Admin Log', + ('admin', 'logentry', 'view_logentry'): 'System (Tech): View Admin Log', + + ('contenttypes', 'contenttype', 'add_contenttype'): 'System (Tech): Add Content Type', + ('contenttypes', 'contenttype', 'change_contenttype'): 'System (Tech): Edit Content Type', + ('contenttypes', 'contenttype', 'delete_contenttype'): 'System (Tech): Delete Content Type', + ('contenttypes', 'contenttype', 'view_contenttype'): 'System (Tech): View Content Types', + } + + count = 0 + for (app_label, model, codename), new_name in renames.items(): + try: + # We need to find the content type first to be safe, or filter by codename + content_type__app_label + # but direct filter is easiest if unique. + # However, codenames can be shared across models (though rare for standard CRUD). + # Safer to lookup ContentType. + + ct = ContentType.objects.filter(app_label=app_label, model=model).first() + if not ct: + self.stdout.write(self.style.WARNING(f"Model {app_label}.{model} not found, skipping.")) + continue + + perm = Permission.objects.filter(content_type=ct, codename=codename).first() + if perm: + perm.name = new_name + perm.save() + self.stdout.write(f"Renamed {codename} -> {new_name}") + count += 1 + else: + self.stdout.write(self.style.WARNING(f"Permission {codename} for {app_label}.{model} not found.")) + except Exception as e: + self.stdout.write(self.style.ERROR(f"Error renaming {codename}: {e}")) + + self.stdout.write(self.style.SUCCESS(f"Successfully updated {count} permission names.")) diff --git a/core/migrations/0010_alter_expenselineitem_options_and_more.py b/core/migrations/0010_alter_expenselineitem_options_and_more.py new file mode 100644 index 0000000..e03777e --- /dev/null +++ b/core/migrations/0010_alter_expenselineitem_options_and_more.py @@ -0,0 +1,53 @@ +# Generated by Django 5.2.7 on 2026-02-04 20:45 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0009_worker_date_of_employment_worker_id_photo_and_more'), + ] + + operations = [ + migrations.AlterModelOptions( + name='expenselineitem', + options={'verbose_name': 'Expense Line Item', 'verbose_name_plural': 'Expense Line Items'}, + ), + migrations.AlterModelOptions( + name='expensereceipt', + options={'verbose_name': 'Expense Receipt', 'verbose_name_plural': 'Expense Receipts'}, + ), + migrations.AlterModelOptions( + name='loan', + options={'verbose_name': 'Loan', 'verbose_name_plural': 'Loans'}, + ), + migrations.AlterModelOptions( + name='payrolladjustment', + options={'verbose_name': 'Payroll Adjustment', 'verbose_name_plural': 'Payroll Adjustments'}, + ), + migrations.AlterModelOptions( + name='payrollrecord', + options={'verbose_name': 'Payroll Record', 'verbose_name_plural': 'Payroll Records'}, + ), + migrations.AlterModelOptions( + name='project', + options={'verbose_name': 'Project', 'verbose_name_plural': 'Projects'}, + ), + migrations.AlterModelOptions( + name='team', + options={'verbose_name': 'Team', 'verbose_name_plural': 'Teams'}, + ), + migrations.AlterModelOptions( + name='userprofile', + options={'verbose_name': 'User Profile', 'verbose_name_plural': 'User Profiles'}, + ), + migrations.AlterModelOptions( + name='worker', + options={'verbose_name': 'Worker', 'verbose_name_plural': 'Workers'}, + ), + migrations.AlterModelOptions( + name='worklog', + options={'verbose_name': 'Work Log / Attendance', 'verbose_name_plural': 'Work Logs / Attendance'}, + ), + ] diff --git a/core/migrations/__pycache__/0010_alter_expenselineitem_options_and_more.cpython-311.pyc b/core/migrations/__pycache__/0010_alter_expenselineitem_options_and_more.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c0da053d649fd9ec84ae52ea27ad4864d041b839 GIT binary patch literal 1904 zcma)6&u<$=6rS~uwY`nwIN7$eK-rWm!m1h@A)u8iq#RHMxJd|9NEYgJ<2P~E?CvbP zYiN9m1P6}XxNt6@BKQ|_;J|SYJy>()#LaDb>WMe&-6Tya%d^jK-}~m9eb4%y=GSUf zBeZX>bnaX5dv}9%?i27N8D`Gw_x`hx0S=F2F^2Wd`0QxD2n( zz6-+zGcBS z4;l3=mx-m58TFk(U^%k8WV6KmWctK>S}kB*X8X~wmVJ3E1r|;Lp(r@q@RYZ0?tQTRgoqV%CSHwamPM|Nz74d?D+Vjw-qswcBhs1vb-`eh=TC{5m2QEoWA zH#{#9qs@Cp(^!il=7HsP+3-RJhcDpkjglzSL<~xWMp0fEI1J-CO1~Lcj#NTsz))~t zUe{(}Any!OUvnZB+~L5SjZ`R&`z*aOekX_Wo_Kz*xyxPF9BdD~XwYO2Sa%Tdpeefz zeXGlwauA!xS533kdZ$G#`DvQUo(&mI=iz7!SN**}-ZhC+F$n*}D{24e8LOnyR>t|9 zuI{(T8mX8+YLCk+qx#j6c_aRe#rh7~xR|eKPYAka(v8cjjxUZ=5sA8iWn^2l607{>Y*+7#N1-CY@(@BXt9 z>tCTw`P!wCdHukS^-Z)XH0C$}?tdbmz{~_(Z{$S<`j*$@tFWES z*hJkG$w+8#Vf4IwYciuB25)1Ef|GC)-8io(3M#HF$HX{kW3{QMV{+F0q&;3(c_KHS Jy<|73_&==L5L5sF literal 0 HcmV?d00001 diff --git a/core/models.py b/core/models.py index c43ef4b..43ea234 100644 --- a/core/models.py +++ b/core/models.py @@ -9,6 +9,10 @@ class UserProfile(models.Model): pin = models.CharField(max_length=4, help_text="4-digit PIN for login") is_admin = models.BooleanField(default=False) + class Meta: + verbose_name = "User Profile" + verbose_name_plural = "User Profiles" + def __str__(self): return f"{self.user.username}'s profile" @@ -19,6 +23,10 @@ class Project(models.Model): created_at = models.DateTimeField(auto_now_add=True) is_active = models.BooleanField(default=True) + class Meta: + verbose_name = "Project" + verbose_name_plural = "Projects" + def __str__(self): return self.name @@ -37,6 +45,10 @@ class Worker(models.Model): created_at = models.DateTimeField(auto_now_add=True) is_active = models.BooleanField(default=True) + class Meta: + verbose_name = "Worker" + verbose_name_plural = "Workers" + @property def day_rate(self): return self.monthly_salary / Decimal('20.0') @@ -56,6 +68,10 @@ class Team(models.Model): created_at = models.DateTimeField(auto_now_add=True) is_active = models.BooleanField(default=True) + class Meta: + verbose_name = "Team" + verbose_name_plural = "Teams" + def __str__(self): return self.name @@ -66,6 +82,10 @@ class WorkLog(models.Model): supervisor = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True) notes = models.TextField(blank=True) + class Meta: + verbose_name = "Work Log / Attendance" + verbose_name_plural = "Work Logs / Attendance" + def __str__(self): return f"{self.date} - {self.project.name}" @@ -76,6 +96,10 @@ class PayrollRecord(models.Model): work_logs = models.ManyToManyField(WorkLog, related_name='paid_in') created_at = models.DateTimeField(auto_now_add=True) + class Meta: + verbose_name = "Payroll Record" + verbose_name_plural = "Payroll Records" + def __str__(self): return f"Payment to {self.worker.name} on {self.date}" @@ -87,6 +111,10 @@ class Loan(models.Model): reason = models.TextField(blank=True) is_active = models.BooleanField(default=True) + class Meta: + verbose_name = "Loan" + verbose_name_plural = "Loans" + def save(self, *args, **kwargs): if not self.pk: # On creation self.balance = self.amount @@ -112,6 +140,10 @@ class PayrollAdjustment(models.Model): description = models.CharField(max_length=255) type = models.CharField(max_length=20, choices=ADJUSTMENT_TYPES, default='DEDUCTION') + class Meta: + verbose_name = "Payroll Adjustment" + verbose_name_plural = "Payroll Adjustments" + def __str__(self): return f"{self.get_type_display()} - {self.amount} for {self.worker.name}" @@ -142,6 +174,10 @@ class ExpenseReceipt(models.Model): created_at = models.DateTimeField(auto_now_add=True) + class Meta: + verbose_name = "Expense Receipt" + verbose_name_plural = "Expense Receipts" + def __str__(self): return f"Receipt from {self.vendor} - {self.date}" @@ -150,5 +186,9 @@ class ExpenseLineItem(models.Model): product = models.CharField(max_length=255, verbose_name="Product/Item") amount = models.DecimalField(max_digits=10, decimal_places=2) + class Meta: + verbose_name = "Expense Line Item" + verbose_name_plural = "Expense Line Items" + def __str__(self): return f"{self.product} - {self.amount}" \ No newline at end of file