|
133 | 133 | from NEMO.widgets.dynamic_form import DynamicForm, PostUsageGroupQuestion, admin_render_dynamic_form_preview |
134 | 134 |
|
135 | 135 |
|
| 136 | +def has_fk_filter(fk_field_name: str, title: str): |
| 137 | + filter_title = title |
| 138 | + param = f"has_{fk_field_name}" |
| 139 | + |
| 140 | + class HasForeignKeyFilter(admin.SimpleListFilter): |
| 141 | + title = filter_title |
| 142 | + parameter_name = param |
| 143 | + |
| 144 | + def lookups(self, request, model_admin): |
| 145 | + return ( |
| 146 | + ("yes", f"Has {title}"), |
| 147 | + ("no", f"No {title}"), |
| 148 | + ) |
| 149 | + |
| 150 | + def queryset(self, request, queryset): |
| 151 | + value = self.value() |
| 152 | + if value == "yes": |
| 153 | + return queryset.filter(**{f"{fk_field_name}__isnull": False}) |
| 154 | + if value == "no": |
| 155 | + return queryset.filter(**{f"{fk_field_name}__isnull": True}) |
| 156 | + return queryset |
| 157 | + |
| 158 | + HasForeignKeyFilter.__name__ = f"Has{title.replace(' ', '')}Filter" |
| 159 | + return HasForeignKeyFilter |
| 160 | + |
| 161 | + |
136 | 162 | # Formset to require at least one inline form |
137 | 163 | class AtLeastOneRequiredInlineFormSet(BaseInlineFormSet): |
138 | 164 | def clean(self): |
@@ -174,7 +200,11 @@ class Media: |
174 | 200 | ) |
175 | 201 |
|
176 | 202 | _tool_calendar_color = forms.CharField( |
177 | | - required=False, max_length=9, initial="#33ad33", widget=forms.TextInput(attrs={"type": "color"}) |
| 203 | + label="Tool calendar color", |
| 204 | + required=False, |
| 205 | + max_length=9, |
| 206 | + initial="#33ad33", |
| 207 | + widget=forms.TextInput(attrs={"type": "color"}), |
178 | 208 | ) |
179 | 209 |
|
180 | 210 | def __init__(self, *args, **kwargs): |
@@ -229,6 +259,7 @@ class ToolAdmin(admin.ModelAdmin): |
229 | 259 | "_category", |
230 | 260 | "_location", |
231 | 261 | ("_requires_area_access", admin.RelatedOnlyFieldListFilter), |
| 262 | + has_fk_filter("staff_charge", "Staff Charge"), |
232 | 263 | ) |
233 | 264 | autocomplete_fields = [ |
234 | 265 | "_primary_owner", |
@@ -628,27 +659,46 @@ def formfield_for_foreignkey(self, db_field, request, **kwargs): |
628 | 659 |
|
629 | 660 | @register(StaffCharge) |
630 | 661 | class StaffChargeAdmin(ObjPermissionAdminMixin, ModelAdminRedirectMixin, admin.ModelAdmin): |
631 | | - list_display = ("id", "staff_member", "customer", "start", "end", "waived") |
| 662 | + list_display = ("id", "staff_member", "customer", "start", "end", "waived", "has_area_record", "has_usage_event") |
632 | 663 | list_filter = ( |
633 | 664 | "start", |
634 | 665 | "waived", |
635 | 666 | ("customer", admin.RelatedOnlyFieldListFilter), |
636 | 667 | ("staff_member", admin.RelatedOnlyFieldListFilter), |
| 668 | + has_fk_filter("areaaccessrecord", "Area Record"), |
| 669 | + has_fk_filter("usageevent", "Usage Event"), |
637 | 670 | ) |
638 | 671 | date_hierarchy = "start" |
639 | 672 | autocomplete_fields = ["staff_member", "customer", "project", "validated_by", "waived_by"] |
640 | 673 | actions = [waive_selected_charges] |
641 | 674 |
|
| 675 | + @admin.display(boolean=True, description="Usage Event") |
| 676 | + def has_usage_event(self, obj) -> bool: |
| 677 | + return obj.usageevent_set.exists() |
| 678 | + |
| 679 | + @admin.display(boolean=True, description="Area Record") |
| 680 | + def has_area_record(self, obj) -> bool: |
| 681 | + return obj.areaaccessrecord_set.exists() |
| 682 | + |
642 | 683 |
|
643 | 684 | @register(AreaAccessRecord) |
644 | 685 | class AreaAccessRecordAdmin(ObjPermissionAdminMixin, ModelAdminRedirectMixin, admin.ModelAdmin): |
645 | | - list_display = ("id", "customer", "area", "project", "start", "end", "waived") |
646 | | - list_filter = (("area", TreeRelatedFieldListFilter), "start", "waived") |
| 686 | + list_display = ("id", "customer", "area", "project", "start", "end", "waived", "has_staff_charge") |
| 687 | + list_filter = ( |
| 688 | + ("area", TreeRelatedFieldListFilter), |
| 689 | + "start", |
| 690 | + "waived", |
| 691 | + has_fk_filter("staff_charge", "Staff Charge"), |
| 692 | + ) |
647 | 693 | date_hierarchy = "start" |
648 | 694 | autocomplete_fields = ["customer", "project", "validated_by", "waived_by"] |
649 | 695 | readonly_fields = ["has_ended"] |
650 | 696 | actions = [waive_selected_charges] |
651 | 697 |
|
| 698 | + @admin.display(boolean=True, description="Staff Charge") |
| 699 | + def has_staff_charge(self, obj) -> bool: |
| 700 | + return obj.staff_charge_id is not None |
| 701 | + |
652 | 702 |
|
653 | 703 | @register(Configuration) |
654 | 704 | class ConfigurationAdmin(admin.ModelAdmin): |
@@ -933,14 +983,30 @@ def questions_preview(self, obj): |
933 | 983 |
|
934 | 984 | @register(UsageEvent) |
935 | 985 | class UsageEventAdmin(ObjPermissionAdminMixin, ModelAdminRedirectMixin, admin.ModelAdmin): |
936 | | - list_display = ("id", "tool", "user", "operator", "project", "start", "end", "duration", "remote_work", "waived") |
| 986 | + list_display = ( |
| 987 | + "id", |
| 988 | + "tool", |
| 989 | + "user", |
| 990 | + "operator", |
| 991 | + "project", |
| 992 | + "start", |
| 993 | + "end", |
| 994 | + "duration", |
| 995 | + "remote_work", |
| 996 | + "waived", |
| 997 | + "has_staff_charge", |
| 998 | + ) |
937 | 999 | list_filter = ("remote_work", "training", "start", "end", "waived", ("tool", admin.RelatedOnlyFieldListFilter)) |
938 | 1000 | date_hierarchy = "start" |
939 | 1001 | autocomplete_fields = ["tool", "user", "operator", "project", "validated_by", "waived_by"] |
940 | 1002 | readonly_fields = ["has_ended"] |
941 | 1003 | actions = [waive_selected_charges] |
942 | 1004 | search_fields = ["user__username", "user__first_name", "user__last_name", "tool__name"] |
943 | 1005 |
|
| 1006 | + @admin.display(boolean=True, description="Staff Charge") |
| 1007 | + def has_staff_charge(self, obj) -> bool: |
| 1008 | + return obj.staff_charge_id is not None |
| 1009 | + |
944 | 1010 |
|
945 | 1011 | @register(Consumable) |
946 | 1012 | class ConsumableAdmin(admin.ModelAdmin): |
@@ -969,9 +1035,14 @@ class ConsumableCategoryAdmin(admin.ModelAdmin): |
969 | 1035 | @register(ConsumableWithdraw) |
970 | 1036 | class ConsumableWithdrawAdmin(ObjPermissionAdminMixin, ModelAdminRedirectMixin, admin.ModelAdmin): |
971 | 1037 | list_display = ("id", "customer", "merchant", "consumable", "quantity", "project", "date", "waived") |
972 | | - list_filter = ("date", "waived", ("consumable", admin.RelatedOnlyFieldListFilter)) |
| 1038 | + list_filter = ( |
| 1039 | + "date", |
| 1040 | + "waived", |
| 1041 | + ("consumable__category", admin.RelatedOnlyFieldListFilter), |
| 1042 | + ("consumable", admin.RelatedOnlyFieldListFilter), |
| 1043 | + ) |
973 | 1044 | date_hierarchy = "date" |
974 | | - autocomplete_fields = ["customer", "merchant", "consumable", "project", "validated_by", "waived_by"] |
| 1045 | + autocomplete_fields = ["customer", "merchant", "consumable", "project", "validated_by", "waived_by", "usage_event"] |
975 | 1046 | actions = [waive_selected_charges] |
976 | 1047 |
|
977 | 1048 |
|
|
0 commit comments