tum vor 1 Jahr
Ursprung
Commit
af6cd6e897

+ 4 - 3
app/core/utils.py

35
 
35
 
36
     config_edit_fields = "__all__"  # "all" or a list of field names to display
36
     config_edit_fields = "__all__"  # "all" or a list of field names to display
37
     ordering = None
37
     ordering = None
38
-
39
-
38
+    form_class = None
40
 
39
 
41
     def get_fields(self):
40
     def get_fields(self):
42
         """
41
         """
139
             model = self.model
138
             model = self.model
140
             template_name = self.form_template_name
139
             template_name = self.form_template_name
141
             fields = self.config_edit_fields
140
             fields = self.config_edit_fields
141
+            form_class = self.form_class
142
 
142
 
143
             def form_valid(inner_self, form):
143
             def form_valid(inner_self, form):
144
                 response = super().form_valid(form)
144
                 response = super().form_valid(form)
165
             model = self.model
165
             model = self.model
166
             template_name = self.form_template_name
166
             template_name = self.form_template_name
167
             fields = self.config_edit_fields
167
             fields = self.config_edit_fields
168
-
168
+            form_class = self.form_class
169
+            
169
             def form_valid(inner_self, form):
170
             def form_valid(inner_self, form):
170
                 response = super().form_valid(form)
171
                 response = super().form_valid(form)
171
                 messages.success(inner_self.request, f"{self.model._meta.verbose_name} updated successfully!")
172
                 messages.success(inner_self.request, f"{self.model._meta.verbose_name} updated successfully!")

+ 37 - 1
app/legacy/filters.py

1
 import django_filters
1
 import django_filters
2
-from .models import Data, DataMs, TbFgPressinfoLotlist, LotSummary
2
+from .models import Data, DataMs, TbFgPressinfoLotlist, LotSummary, DataRl, DataWb, \
3
+        LotSummaryRl, LotSummaryWb
3
 
4
 
4
 class DataFilter(django_filters.FilterSet):
5
 class DataFilter(django_filters.FilterSet):
5
     lot_no = django_filters.CharFilter(field_name='lot_no', lookup_expr='icontains')
6
     lot_no = django_filters.CharFilter(field_name='lot_no', lookup_expr='icontains')
34
     class Meta:
35
     class Meta:
35
         model = LotSummary
36
         model = LotSummary
36
         fields = ['lot_no', 'code']  # Add fields you want to filter
37
         fields = ['lot_no', 'code']  # Add fields you want to filter
38
+
39
+
40
+class DataRlFilter(django_filters.FilterSet):
41
+    lot_no = django_filters.CharFilter(field_name='lot_no', lookup_expr='icontains')
42
+    code = django_filters.CharFilter(field_name='code', lookup_expr='icontains')
43
+
44
+    class Meta:
45
+        model = DataRl
46
+        fields = ['lot_no', 'code']  # Add fields you want to filter
47
+
48
+class DataWbFilter(django_filters.FilterSet):
49
+    lot_no = django_filters.CharFilter(field_name='lot_no', lookup_expr='icontains')
50
+    code = django_filters.CharFilter(field_name='code', lookup_expr='icontains')
51
+
52
+    class Meta:
53
+        model = DataWb
54
+        fields = ['lot_no', 'code']  # Add fields you want to filter
55
+
56
+class LotSummaryRlFilter(django_filters.FilterSet):
57
+    lot_no = django_filters.CharFilter(field_name='lot_no', lookup_expr='icontains')
58
+    code = django_filters.CharFilter(field_name='code', lookup_expr='icontains')
59
+
60
+    class Meta:
61
+        model = LotSummaryRl
62
+        fields = ['lot_no', 'code']  # Add fields you want to filter
63
+
64
+
65
+class LotSummaryWbFilter(django_filters.FilterSet):
66
+    lot_no = django_filters.CharFilter(field_name='lot_no', lookup_expr='icontains')
67
+    code = django_filters.CharFilter(field_name='code', lookup_expr='icontains')
68
+
69
+    class Meta:
70
+        model = LotSummaryWb
71
+        fields = ['lot_no', 'code']  # Add fields you want to filter
72
+

+ 1 - 1
app/legacy/templates/legacy/datacrud_list.html

47
                     {% elif field.name == 'file' and obj.file %}
47
                     {% elif field.name == 'file' and obj.file %}
48
                     <a href="{{ obj.file.url }}" target="_blank">View</a>
48
                     <a href="{{ obj.file.url }}" target="_blank">View</a>
49
                     {% else %}
49
                     {% else %}
50
-                        {{ obj|attr:field.name }}
50
+                        {{ obj|attr:field.name | safe_floatformat:2 }}
51
                     {% endif %}
51
                     {% endif %}
52
                 </td>
52
                 </td>
53
                 {% endfor %}
53
                 {% endfor %}

+ 10 - 0
app/legacy/templatetags/legacy_filters.py

1
 from django import template
1
 from django import template
2
 from django.template import Context
2
 from django.template import Context
3
+from django.template.defaultfilters import floatformat
3
 
4
 
4
 
5
 
5
 register = template.Library()
6
 register = template.Library()
27
     Context({
28
     Context({
28
         'breadcrumbs': breadcrumbs
29
         'breadcrumbs': breadcrumbs
29
     }))
30
     }))
31
+
32
+@register.filter
33
+def safe_floatformat(value, decimal_places):
34
+    try:
35
+        # Attempt to convert the value to a float and format it
36
+        return floatformat(float(value), decimal_places)
37
+    except (ValueError, TypeError):
38
+        # If the value is not a number, return it as is
39
+        return value

+ 26 - 2
app/legacy/urls.py

1
 from django.urls import path
1
 from django.urls import path
2
 from .views import DataListView, DataDetailView, DataCreateView, DataUpdateView, DataDeleteView,\
2
 from .views import DataListView, DataDetailView, DataCreateView, DataUpdateView, DataDeleteView,\
3
-DataMsCRUDView, TbFgPressInfoLotListCRUDView, LotSummaryCRUDView, VMasterViewCRUDView, MgMasterViewCRUDView, BelMasterViewCRUDView, EMasterViewCRUDView
3
+DataMsCRUDView, TbFgPressInfoLotListCRUDView, LotSummaryCRUDView, VMasterViewCRUDView, MgMasterViewCRUDView,\
4
+BelMasterViewCRUDView, EMasterViewCRUDView, DataRLCRUDView, DataWbCRUDView, LotSummaryRlCRUDView, LotSummaryWbCRUDView
4
 
5
 
5
 app_name = 'legacy'  # Namespace for this app
6
 app_name = 'legacy'  # Namespace for this app
6
 
7
 
11
 mg_crud = MgMasterViewCRUDView()
12
 mg_crud = MgMasterViewCRUDView()
12
 bel_crud = BelMasterViewCRUDView()
13
 bel_crud = BelMasterViewCRUDView()
13
 em_crud = EMasterViewCRUDView()
14
 em_crud = EMasterViewCRUDView()
14
-
15
+datarl_crud = DataRLCRUDView()
16
+datawb_crud = DataWbCRUDView()
17
+lsrl_crud = LotSummaryRlCRUDView()
18
+lswb_crud = LotSummaryWbCRUDView()
15
 
19
 
16
 urlpatterns = [
20
 urlpatterns = [
17
     path('data/', DataListView.as_view(), name='data-list'),            # data/
21
     path('data/', DataListView.as_view(), name='data-list'),            # data/
24
     path('datams/create/', datams_crud.get_create_view().as_view(), name='datams-create'),
28
     path('datams/create/', datams_crud.get_create_view().as_view(), name='datams-create'),
25
     path('datams/<int:pk>/update/', datams_crud.get_update_view().as_view(), name='datams-update'),
29
     path('datams/<int:pk>/update/', datams_crud.get_update_view().as_view(), name='datams-update'),
26
     path('datams/<int:pk>/delete/', datams_crud.get_delete_view().as_view(), name='datams-delete'),
30
     path('datams/<int:pk>/delete/', datams_crud.get_delete_view().as_view(), name='datams-delete'),
31
+
32
+    path('datarl/', datarl_crud.get_list_view().as_view(), name='datarl-list'),
33
+    path('datarl/create/', datarl_crud.get_create_view().as_view(), name='datarl-create'),
34
+    path('datarl/<int:pk>/update/', datarl_crud.get_update_view().as_view(), name='datarl-update'),
35
+    path('datarl/<int:pk>/delete/', datarl_crud.get_delete_view().as_view(), name='datarl-delete'),
36
+
37
+    path('datawb/', datawb_crud.get_list_view().as_view(), name='datawb-list'),
38
+    path('datawb/create/', datawb_crud.get_create_view().as_view(), name='datawb-create'),
39
+    path('datawb/<int:pk>/update/', datawb_crud.get_update_view().as_view(), name='datawb-update'),
40
+    path('datawb/<int:pk>/delete/', datawb_crud.get_delete_view().as_view(), name='datawb-delete'),
27
     
41
     
28
     path('fg/', fg_crud.get_list_view().as_view(), name='fg-list'),
42
     path('fg/', fg_crud.get_list_view().as_view(), name='fg-list'),
29
     path('fg/create/', fg_crud.get_create_view().as_view(), name='fg-create'),
43
     path('fg/create/', fg_crud.get_create_view().as_view(), name='fg-create'),
34
     path('ls/create/', ls_crud.get_create_view().as_view(), name='ls-create'),
48
     path('ls/create/', ls_crud.get_create_view().as_view(), name='ls-create'),
35
     path('ls/<int:pk>/update/', ls_crud.get_update_view().as_view(), name='ls-update'),
49
     path('ls/<int:pk>/update/', ls_crud.get_update_view().as_view(), name='ls-update'),
36
     path('ls/<int:pk>/delete/', ls_crud.get_delete_view().as_view(), name='ls-delete'),
50
     path('ls/<int:pk>/delete/', ls_crud.get_delete_view().as_view(), name='ls-delete'),
51
+
37
     path('vm/', vm_crud.get_list_view().as_view(), name='vm-list'),
52
     path('vm/', vm_crud.get_list_view().as_view(), name='vm-list'),
38
     path('vm/create/', vm_crud.get_create_view().as_view(), name='vm-create'),
53
     path('vm/create/', vm_crud.get_create_view().as_view(), name='vm-create'),
39
     path('vm/<str:pk>/update/', vm_crud.get_update_view().as_view(), name='vm-update'),
54
     path('vm/<str:pk>/update/', vm_crud.get_update_view().as_view(), name='vm-update'),
55
     path('em/<str:pk>/delete/', em_crud.get_delete_view().as_view(), name='em-delete'),
70
     path('em/<str:pk>/delete/', em_crud.get_delete_view().as_view(), name='em-delete'),
56
 
71
 
57
 
72
 
73
+    path('lsrl/', lsrl_crud.get_list_view().as_view(), name='lsrl-list'),
74
+    path('lsrl/create/', lsrl_crud.get_create_view().as_view(), name='lsrl-create'),
75
+    path('lsrl/<int:pk>/update/', lsrl_crud.get_update_view().as_view(), name='lsrl-update'),
76
+    path('lsrl/<int:pk>/delete/', lsrl_crud.get_delete_view().as_view(), name='lsrl-delete'),
77
+
78
+    path('lswb/', lswb_crud.get_list_view().as_view(), name='lswb-list'),
79
+    path('lswb/create/', lswb_crud.get_create_view().as_view(), name='lswb-create'),
80
+    path('lswb/<int:pk>/update/', lswb_crud.get_update_view().as_view(), name='lswb-update'),
81
+    path('lswb/<int:pk>/delete/', lswb_crud.get_delete_view().as_view(), name='lswb-delete'),
58
 ]
82
 ]

+ 75 - 3
app/legacy/views.py

11
     DeleteView,
11
     DeleteView,
12
 )
12
 )
13
 from django.core.paginator import Paginator
13
 from django.core.paginator import Paginator
14
-from .models import Data, DataMs, TbFgPressinfoLotlist, LotSummary
15
-from .filters import DataFilter, DataMsFilter, TbFgPressFilter, LotSummaryFilter
14
+from .models import Data, DataMs, TbFgPressinfoLotlist, LotSummary, DataRl, DataWb, LotSummaryRl, LotSummaryWb
15
+from .filters import DataFilter, DataMsFilter, TbFgPressFilter, LotSummaryFilter, \
16
+        DataRlFilter, DataWbFilter, LotSummaryRlFilter, LotSummaryWbFilter
16
 from django.urls import reverse
17
 from django.urls import reverse
17
 from django.contrib import messages
18
 from django.contrib import messages
18
 from pprint import pprint
19
 from pprint import pprint
158
     detail_template_name = 'legacy/datacrud_detail.html'
159
     detail_template_name = 'legacy/datacrud_detail.html'
159
     form_template_name = 'legacy/datacrud_form.html'
160
     form_template_name = 'legacy/datacrud_form.html'
160
     confirm_delete_template_name = 'legacy/datacrud_confirm_delete.html'
161
     confirm_delete_template_name = 'legacy/datacrud_confirm_delete.html'
161
-    filterset_class = DataMsFilter
162
+    filterset_class = LotSummaryFilter
162
 
163
 
163
     page_title = "Lot Summary"
164
     page_title = "Lot Summary"
164
 
165
 
301
     # Default ordering
302
     # Default ordering
302
     # ordering = ["-id", "PRO2"]
303
     # ordering = ["-id", "PRO2"]
303
     
304
     
305
+class DataRLCRUDView(ConfigurableCRUDView):
306
+    model = DataRl
307
+    list_template_name = 'legacy/datacrud_list.html'
308
+    detail_template_name = 'legacy/datacrud_detail.html'
309
+    form_template_name = 'legacy/datacrud_form.html'
310
+    confirm_delete_template_name = 'legacy/datacrud_confirm_delete.html'
311
+    filterset_class = DataRlFilter
312
+
313
+    page_title = "Data RL"
314
+
315
+    # URL name mappings
316
+    list_url_name = 'legacy:datarl-list'
317
+    create_url_name = 'legacy:datarl-create'
318
+    update_url_name = 'legacy:datarl-update'
319
+    delete_url_name = 'legacy:datarl-delete'
320
+    # excludes = ["splitdata"]
321
+    config_field_orders = ["id", "lot_no", "code"]  # Display these fields first
322
+
323
+class DataWbCRUDView(ConfigurableCRUDView):
324
+    model = DataWb
325
+    list_template_name = 'legacy/datacrud_list.html'
326
+    detail_template_name = 'legacy/datacrud_detail.html'
327
+    form_template_name = 'legacy/datacrud_form.html'
328
+    confirm_delete_template_name = 'legacy/datacrud_confirm_delete.html'
329
+    filterset_class = DataWbFilter
330
+
331
+    page_title = "Data WB"
332
+
333
+    # URL name mappings
334
+    list_url_name = 'legacy:datawb-list'
335
+    create_url_name = 'legacy:datawb-create'
336
+    update_url_name = 'legacy:datawb-update'
337
+    delete_url_name = 'legacy:datawb-delete'
338
+    # excludes = ["splitdata"]
339
+    config_field_orders = ["id", "lot_no", "code"]  # Display these fields first
340
+
341
+class LotSummaryRlCRUDView(ConfigurableCRUDView):
342
+    model = LotSummaryRl
343
+    list_template_name = 'legacy/datacrud_list.html'
344
+    detail_template_name = 'legacy/datacrud_detail.html'
345
+    form_template_name = 'legacy/datacrud_form.html'
346
+    confirm_delete_template_name = 'legacy/datacrud_confirm_delete.html'
347
+    filterset_class = LotSummaryRlFilter
348
+
349
+    page_title = "Lot Summary RL"
350
+
351
+    # URL name mappings
352
+    list_url_name = 'legacy:lsrl-list'
353
+    create_url_name = 'legacy:lsrl-create'
354
+    update_url_name = 'legacy:lsrl-update'
355
+    delete_url_name = 'legacy:lsrl-delete'
356
+    # excludes = ["splitdata"]
357
+    config_field_orders = ["id", "lot_no", "code"]  # Display these fields first
358
+
359
+class LotSummaryWbCRUDView(ConfigurableCRUDView):
360
+    model = LotSummaryWb
361
+    list_template_name = 'legacy/datacrud_list.html'
362
+    detail_template_name = 'legacy/datacrud_detail.html'
363
+    form_template_name = 'legacy/datacrud_form.html'
364
+    confirm_delete_template_name = 'legacy/datacrud_confirm_delete.html'
365
+    filterset_class = LotSummaryWbFilter
366
+
367
+    page_title = "Lot Summary WB"
368
+
369
+    # URL name mappings
370
+    list_url_name = 'legacy:lswb-list'
371
+    create_url_name = 'legacy:lswb-create'
372
+    update_url_name = 'legacy:lswb-update'
373
+    delete_url_name = 'legacy:lswb-delete'
374
+    # excludes = ["splitdata"]
375
+    config_field_orders = ["id", "lot_no", "code"]  # Display these fields first

BIN
app/report/coi_templates.xlsx


+ 20 - 0
app/report/templates/report/coi.html

32
         </label>
32
         </label>
33
         {% endfor %}
33
         {% endfor %}
34
         </div>
34
         </div>
35
+        <div class="grid grid-cols-2 gap-4">
36
+          <div class="my-4">
37
+            <label for="qa1" class="block mb-2 text-sm font-medium text-gray-900">Select QA.1</label>
38
+            <select id="qa1" name="qa1" class="block w-full px-3 py-2 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500">
39
+              <option value="" disabled selected>Choose a user</option>
40
+              {% for user in users %}
41
+              <option value="{{ user.id }}">{{ user.profile }}</option>
42
+              {% endfor %}
43
+            </select>
44
+          </div>
45
+          <div class="my-4">
46
+            <label for="qa2" class="block mb-2 text-sm font-medium text-gray-900">Select QA.2</label>
47
+            <select id="qa2" name="qa2" class="block w-full px-3 py-2 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500">
48
+              <option value="" disabled selected>Choose a user</option>
49
+              {% for user in users %}
50
+              <option value="{{ user.id }}">{{ user.profile }}</option>
51
+              {% endfor %}
52
+            </select>
53
+          </div>
54
+        </div>
35
         <div class="flex justify-end my-3 space-x-4">
55
         <div class="flex justify-end my-3 space-x-4">
36
           <div>
56
           <div>
37
             <button  type='button' class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600" 
57
             <button  type='button' class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600" 

+ 5 - 1
app/report/views.py

15
 from django.http import JsonResponse, HttpResponseBadRequest
15
 from django.http import JsonResponse, HttpResponseBadRequest
16
 import json
16
 import json
17
 from django.contrib.auth.decorators import login_required
17
 from django.contrib.auth.decorators import login_required
18
+from django.contrib.auth.models import User
19
+
18
 
20
 
19
 def index(request):
21
 def index(request):
20
     reports = Report.objects.all()
22
     reports = Report.objects.all()
127
 }
129
 }
128
 def coi_view(request):
130
 def coi_view(request):
129
     pprint(f"xxxx method = xxx {request.method}")
131
     pprint(f"xxxx method = xxx {request.method}")
132
+    users = User.objects.all()
133
+
130
     if request.method == "POST":
134
     if request.method == "POST":
131
         pprint(request.POST)
135
         pprint(request.POST)
132
         exports = request.POST.getlist("exports")  # Retrieve the list of selected values
136
         exports = request.POST.getlist("exports")  # Retrieve the list of selected values
187
 
191
 
188
         messages.success(request, "Request Sent")
192
         messages.success(request, "Request Sent")
189
         return redirect(request.path_info)
193
         return redirect(request.path_info)
190
-    return render(request, 'report/coi.html', {'SHEET_NAMES': SHEET_NAMES})
194
+    return render(request, 'report/coi.html', {'SHEET_NAMES': SHEET_NAMES, 'users': users})
191
 
195
 
192
 
196
 
193
 @csrf_exempt  # Disable CSRF for API requests (ensure this is secure in production)
197
 @csrf_exempt  # Disable CSRF for API requests (ensure this is secure in production)

BIN
app/report/~$coi_templates.xlsx


+ 12 - 0
app/sysadmin/filters.py

1
+
2
+import django_filters
3
+from django.contrib.auth.models import User
4
+
5
+class UserFilter(django_filters.FilterSet):
6
+    username = django_filters.CharFilter(field_name='username', lookup_expr='icontains')
7
+    first_name = django_filters.CharFilter(field_name='first_name', lookup_expr='icontains')
8
+    last_name = django_filters.CharFilter(field_name='last_name', lookup_expr='icontains')
9
+
10
+    class Meta:
11
+        model = User
12
+        fields = ['username', 'first_name', 'last_name']  # Add fields you want to filter

+ 55 - 4
app/sysadmin/forms.py

2
 from django import forms
2
 from django import forms
3
 from django.contrib.auth.forms import AuthenticationForm
3
 from django.contrib.auth.forms import AuthenticationForm
4
 
4
 
5
+from django.contrib.auth.forms import UserCreationForm
6
+from django.contrib.auth.models import User
7
+
8
+from .models import UserProfile
9
+
5
 class CustomLoginForm(AuthenticationForm):
10
 class CustomLoginForm(AuthenticationForm):
6
     username = forms.CharField(widget=forms.TextInput(attrs={
11
     username = forms.CharField(widget=forms.TextInput(attrs={
7
         'placeholder': 'Username'
12
         'placeholder': 'Username'
11
     }))
16
     }))
12
 
17
 
13
 # forms.py
18
 # forms.py
14
-from django.contrib.auth.forms import UserCreationForm
15
-from django.contrib.auth.models import User
16
-
17
-from .models import UserProfile
18
 
19
 
19
 class CustomUserCreationForm(UserCreationForm):
20
 class CustomUserCreationForm(UserCreationForm):
20
     class Meta:
21
     class Meta:
41
     class Meta:
42
     class Meta:
42
         model = UserProfile
43
         model = UserProfile
43
         fields = ['profile_picture', 'position', 'signed_picture']  # Include the fields you want to manage
44
         fields = ['profile_picture', 'position', 'signed_picture']  # Include the fields you want to manage
45
+
46
+class UserCustomForm(forms.ModelForm):
47
+    # Profile fields
48
+    profile_picture = forms.ImageField(required=False, label="Profile Picture")
49
+    signed_picture = forms.ImageField(required=False, label="Signed Picture")
50
+    position = forms.ChoiceField(
51
+        choices=UserProfile.POSITION_CHOICES, 
52
+        required=False, 
53
+        label="Position"
54
+    )
55
+
56
+    class Meta:
57
+        model = User
58
+        fields = [
59
+            "first_name",
60
+            "last_name",
61
+            "is_staff",
62
+            "is_superuser",
63
+            "is_active",
64
+        ]
65
+
66
+    def __init__(self, *args, **kwargs):
67
+        # Allow passing `instance` to access both User and UserProfile objects
68
+        user_instance = kwargs.pop('instance', None)
69
+        profile_instance = getattr(user_instance, 'profile', None)
70
+        super().__init__(instance=user_instance, *args, **kwargs)
71
+
72
+        # Populate initial data for profile fields if `profile` exists
73
+        if profile_instance:
74
+            self.fields['profile_picture'].initial = profile_instance.profile_picture
75
+            self.fields['signed_picture'].initial = profile_instance.signed_picture
76
+            self.fields['position'].initial = profile_instance.position
77
+
78
+    def save(self, commit=True):
79
+        # Save the User instance first
80
+        user = super().save(commit=commit)
81
+        profile_data = {
82
+            'profile_picture': self.cleaned_data.get('profile_picture'),
83
+            'signed_picture': self.cleaned_data.get('signed_picture'),
84
+            'position': self.cleaned_data.get('position'),
85
+        }
86
+
87
+        # Ensure profile exists for the user
88
+        profile, created = UserProfile.objects.get_or_create(user=user)
89
+        for key, value in profile_data.items():
90
+            setattr(profile, key, value)
91
+        if commit:
92
+            profile.save()
93
+
94
+        return user

+ 4 - 2
app/sysadmin/models.py

7
 class UserProfile(models.Model):
7
 class UserProfile(models.Model):
8
     POSITION_CHOICES = [
8
     POSITION_CHOICES = [
9
         ('QA_STAFF', 'QA Staff'),
9
         ('QA_STAFF', 'QA Staff'),
10
-        ('QA_MANAGER', 'QA Manager'),
10
+        ('QA_MANAGER', 'QA. MG.'),
11
+        ('QA_AST_MANAGER', 'QA. Asst. MG.'),
12
+        ('QA_ENGINEER', 'QA. Engineer'),
11
     ]
13
     ]
12
     user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
14
     user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
13
     bio = models.TextField(blank=True, null=True)
15
     bio = models.TextField(blank=True, null=True)
17
     position = models.CharField(max_length=20, choices=POSITION_CHOICES, blank=True, null=True)  # New position field
19
     position = models.CharField(max_length=20, choices=POSITION_CHOICES, blank=True, null=True)  # New position field
18
 
20
 
19
     def __str__(self):
21
     def __str__(self):
20
-        return self.user.username
22
+        return f"{self.user.username} / {self.user.first_name} {self.user.last_name} #{self.position}"

+ 71 - 0
app/sysadmin/templates/sysadmin/user_form.html

1
+{% extends "base.html" %}
2
+
3
+{% load legacy_filters %}
4
+{% load tailwind_filters %}
5
+{% load crispy_forms_filters %}
6
+
7
+{% block title %}
8
+    {% if view.title %}
9
+        {{ view.title }}
10
+    {% else %}
11
+        {{ view|class_name }}
12
+    {% endif %}
13
+{% endblock %}
14
+
15
+{% block content %}
16
+<div class="container mx-auto px-4 py-6">
17
+    <h1 class="text-2xl font-bold mb-6">
18
+        {% if view.title %}
19
+            {{ view.title }}
20
+        {% elif view|class_name == "CreateViewClass" %}
21
+            Create {{ model_verbose_name }}
22
+        {% else %}
23
+            Update {{ model_verbose_name }}
24
+        {% endif %}
25
+    </h1>
26
+
27
+    <!-- Render the Form -->
28
+    <form method="post" enctype="multipart/form-data">
29
+        {% csrf_token %}
30
+        <div class="flex flex-wrap items-center space-x-4">
31
+            <!-- Show profile_picture -->
32
+            <div class=" mb-4">
33
+                {% if form.instance.profile and form.instance.profile.profile_picture %}
34
+                    <div class="mb-2">
35
+                        <img src="{{ form.instance.profile.profile_picture.url }}" alt="Profile Picture" class="max-w-xs border rounded" width=100>
36
+                    </div>
37
+                {% endif %}
38
+                {{ form.profile_picture|as_crispy_field }}
39
+            </div>
40
+
41
+            <!-- Show signed_picture -->
42
+            <div class=" mb-4">
43
+                {% if form.instance.profile and form.instance.profile.signed_picture %}
44
+                    <div class="mb-2">
45
+                        <img src="{{ form.instance.profile.signed_picture.url }}" alt="Signed Picture" class="max-w-xs border rounded" width=100>
46
+                    </div>
47
+                {% endif %}
48
+                {{ form.signed_picture|as_crispy_field }}
49
+            </div>
50
+
51
+            <!-- Render other form fields -->
52
+            {% for field in form %}
53
+                {% if field.name not in "profile_picture signed_picture" %}
54
+                    <div class=" mb-4">
55
+                        {{ field|as_crispy_field }}
56
+                    </div>
57
+                {% endif %}
58
+            {% endfor %}
59
+        </div>
60
+
61
+        <div class="mt-4">
62
+            <button type="submit" class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600">
63
+                Save
64
+            </button>
65
+            <a href="{% url list_url_name %}" class="bg-gray-300 text-gray-800 px-4 py-2 rounded hover:bg-gray-400">
66
+                Cancel
67
+            </a>
68
+        </div>
69
+    </form>
70
+</div>
71
+{% endblock %}

+ 6 - 0
app/sysadmin/urls.py

3
 
3
 
4
 app_name = "sysadmin"  # Use this namespace for reverse URL lookups
4
 app_name = "sysadmin"  # Use this namespace for reverse URL lookups
5
 
5
 
6
+users_crud = views.UserCRUDView()
7
+
6
 urlpatterns = [
8
 urlpatterns = [
7
     path('login/', views.login_view, name='login'),
9
     path('login/', views.login_view, name='login'),
8
     path('register/', views.register_view, name='register'),
10
     path('register/', views.register_view, name='register'),
9
     path('logout/', views.logout_view, name='logout'),
11
     path('logout/', views.logout_view, name='logout'),
10
     path('profile/', views.profile_view, name='profile'),  # Add profile view URL
12
     path('profile/', views.profile_view, name='profile'),  # Add profile view URL
11
 
13
 
14
+    path('users/', users_crud.get_list_view().as_view(), name='users-list'),
15
+    path('users/create/', users_crud.get_create_view().as_view(), name='users-create'),
16
+    path('users/<int:pk>/update/', users_crud.get_update_view().as_view(), name='users-update'),
17
+    path('users/<int:pk>/delete/', users_crud.get_delete_view().as_view(), name='users-delete'),
12
 ]
18
 ]

+ 26 - 1
app/sysadmin/views.py

1
 # views.py
1
 # views.py
2
 from django.contrib.auth import authenticate, login, logout
2
 from django.contrib.auth import authenticate, login, logout
3
 from django.shortcuts import render, redirect
3
 from django.shortcuts import render, redirect
4
-from .forms import CustomLoginForm, CustomUserCreationForm, UserProfileForm
4
+from .forms import CustomLoginForm, CustomUserCreationForm, UserProfileForm, UserCustomForm
5
 from .models import UserProfile
5
 from .models import UserProfile
6
+from .filters import UserFilter
6
 from django.contrib.auth.decorators import login_required
7
 from django.contrib.auth.decorators import login_required
7
 from django.contrib import messages
8
 from django.contrib import messages
9
+from core.utils import ConfigurableCRUDView
10
+from django.contrib.auth.models import User
8
 
11
 
9
 def login_view(request):
12
 def login_view(request):
10
     if request.method == "POST":
13
     if request.method == "POST":
61
         form = UserProfileForm(instance=profile)
64
         form = UserProfileForm(instance=profile)
62
 
65
 
63
     return render(request, 'sysadmin/profile.html', {'form': form})
66
     return render(request, 'sysadmin/profile.html', {'form': form})
67
+
68
+
69
+class UserCRUDView(ConfigurableCRUDView):
70
+    model = User
71
+    list_template_name = 'legacy/datacrud_list.html'
72
+    detail_template_name = 'legacy/datacrud_detail.html'
73
+    form_template_name = 'sysadmin/user_form.html'
74
+    confirm_delete_template_name = 'legacy/datacrud_confirm_delete.html'
75
+    filterset_class = UserFilter
76
+
77
+    page_title = "Users"
78
+
79
+    # URL name mappings
80
+    list_url_name = 'sysadmin:users-list'
81
+    create_url_name = 'sysadmin:users-create'
82
+    update_url_name = 'sysadmin:users-update'
83
+    delete_url_name = 'sysadmin:users-delete'
84
+    # excludes = ["splitdata"]
85
+    config_fields = ("id", "username", "is_active", "is_staff", "is_superuser", "last_login")
86
+    config_field_orders = ["id",]  # Display these fields first
87
+    form_class = UserCustomForm
88
+    config_edit_fields = None

+ 33 - 4
app/templates/base.html

82
                             <a href="{% url "legacy:data-list" %}" class="flex items-center w-full p-2 text-gray-900 transition duration-75 rounded-lg pl-11 group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700">Data</a>
82
                             <a href="{% url "legacy:data-list" %}" class="flex items-center w-full p-2 text-gray-900 transition duration-75 rounded-lg pl-11 group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700">Data</a>
83
                           </li>
83
                           </li>
84
                           <li>
84
                           <li>
85
+                            <a href="{% url "legacy:datams-list" %}" class="flex items-center w-full p-2 text-gray-900 transition duration-75 rounded-lg pl-11 group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700">Data MS</a>
86
+                          </li>
87
+                          <li>
88
+                            <a href="{% url "legacy:datarl-list" %}" class="flex items-center w-full p-2 text-gray-900 transition duration-75 rounded-lg pl-11 group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700">Data RL</a>
89
+                          </li>
90
+                          <li>
91
+                            <a href="{% url "legacy:datawb-list" %}" class="flex items-center w-full p-2 text-gray-900 transition duration-75 rounded-lg pl-11 group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700">Data WB</a>
92
+                          </li>
93
+                          <li>
85
                             <a href="{% url "legacy:datams-list" %}" class="flex items-center w-full p-2 text-gray-900 transition duration-75 rounded-lg pl-11 group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700">Measurement</a>
94
                             <a href="{% url "legacy:datams-list" %}" class="flex items-center w-full p-2 text-gray-900 transition duration-75 rounded-lg pl-11 group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700">Measurement</a>
86
                           </li>
95
                           </li>
87
                           <li>
96
                           <li>
91
                             <a href="{% url "legacy:ls-list" %}" class="flex items-center w-full p-2 text-gray-900 transition duration-75 rounded-lg pl-11 group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700">Lot Summary</a>
100
                             <a href="{% url "legacy:ls-list" %}" class="flex items-center w-full p-2 text-gray-900 transition duration-75 rounded-lg pl-11 group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700">Lot Summary</a>
92
                           </li>
101
                           </li>
93
                           <li>
102
                           <li>
103
+                            <a href="{% url "legacy:lsrl-list" %}" class="flex items-center w-full p-2 text-gray-900 transition duration-75 rounded-lg pl-11 group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700">Lot Summary RL</a>
104
+                          </li>
105
+                          <li>
106
+                            <a href="{% url "legacy:lswb-list" %}" class="flex items-center w-full p-2 text-gray-900 transition duration-75 rounded-lg pl-11 group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700">Lot Summary WB</a>
107
+                          </li>
108
+                          <li>
94
                             <a href="{% url "legacy:vm-list" %}" class="flex items-center w-full p-2 text-gray-900 transition duration-75 rounded-lg pl-11 group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700">VMaster</a>
109
                             <a href="{% url "legacy:vm-list" %}" class="flex items-center w-full p-2 text-gray-900 transition duration-75 rounded-lg pl-11 group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700">VMaster</a>
95
                           </li>
110
                           </li>
96
                           <li>
111
                           <li>
102
                           <li>
117
                           <li>
103
                             <a href="{% url "legacy:em-list" %}" class="flex items-center w-full p-2 text-gray-900 transition duration-75 rounded-lg pl-11 group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700">EMaster</a>
118
                             <a href="{% url "legacy:em-list" %}" class="flex items-center w-full p-2 text-gray-900 transition duration-75 rounded-lg pl-11 group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700">EMaster</a>
104
                           </li>
119
                           </li>
105
-                          <li>
106
-                             <a href="#" class="flex items-center w-full p-2 text-gray-900 transition duration-75 rounded-lg pl-11 group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700">Invoice</a>
107
-                          </li>
108
                     </ul>
120
                     </ul>
109
                  </li>
121
                  </li>
110
-                <li><a href="/settings/" class="flex items-center p-2 text-gray-900 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 dark:text-white"><span class="ml-3">Settings</span></a></li>
122
+                 <li>
123
+
124
+                   <button type="button" class="flex items-center w-full p-2 text-base text-gray-900 transition duration-75 rounded-lg group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700" aria-controls="setting-sub" data-collapse-toggle="setting-sub">
125
+                     <svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
126
+                       <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 6c0 1.657-3.134 3-7 3S5 7.657 5 6m14 0c0-1.657-3.134-3-7-3S5 4.343 5 6m14 0v6M5 6v6m0 0c0 1.657 3.134 3 7 3s7-1.343 7-3M5 12v6c0 1.657 3.134 3 7 3s7-1.343 7-3v-6"/>
127
+                     </svg>
128
+
129
+                     <span class="flex-1 ms-3 text-left rtl:text-right whitespace-nowrap">Settings</span>
130
+                     <svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
131
+                       <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
132
+                     </svg>
133
+                   </button>
134
+                   <ul id="setting-sub" class="hidden py-2 space-y-2">
135
+                     <li>
136
+                       <a href="{% url "sysadmin:users-list" %}" class="flex items-center w-full p-2 text-gray-900 transition duration-75 rounded-lg pl-11 group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700">Users</a>
137
+                     </li>
138
+                   </ul>
139
+                 </li>
111
             </ul>
140
             </ul>
112
         </div>
141
         </div>
113
     </aside>
142
     </aside>