tum 1 gadu atpakaļ
vecāks
revīzija
af6cd6e897

+ 4 - 3
app/core/utils.py

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

+ 37 - 1
app/legacy/filters.py

@@ -1,5 +1,6 @@
1 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 5
 class DataFilter(django_filters.FilterSet):
5 6
     lot_no = django_filters.CharFilter(field_name='lot_no', lookup_expr='icontains')
@@ -34,3 +35,38 @@ class LotSummaryFilter(django_filters.FilterSet):
34 35
     class Meta:
35 36
         model = LotSummary
36 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,7 +47,7 @@
47 47
                     {% elif field.name == 'file' and obj.file %}
48 48
                     <a href="{{ obj.file.url }}" target="_blank">View</a>
49 49
                     {% else %}
50
-                        {{ obj|attr:field.name }}
50
+                        {{ obj|attr:field.name | safe_floatformat:2 }}
51 51
                     {% endif %}
52 52
                 </td>
53 53
                 {% endfor %}

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

@@ -1,5 +1,6 @@
1 1
 from django import template
2 2
 from django.template import Context
3
+from django.template.defaultfilters import floatformat
3 4
 
4 5
 
5 6
 register = template.Library()
@@ -27,3 +28,12 @@ def render_breadcrumbs(context, breadcrumbs):
27 28
     Context({
28 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,6 +1,7 @@
1 1
 from django.urls import path
2 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 6
 app_name = 'legacy'  # Namespace for this app
6 7
 
@@ -11,7 +12,10 @@ vm_crud = VMasterViewCRUDView()
11 12
 mg_crud = MgMasterViewCRUDView()
12 13
 bel_crud = BelMasterViewCRUDView()
13 14
 em_crud = EMasterViewCRUDView()
14
-
15
+datarl_crud = DataRLCRUDView()
16
+datawb_crud = DataWbCRUDView()
17
+lsrl_crud = LotSummaryRlCRUDView()
18
+lswb_crud = LotSummaryWbCRUDView()
15 19
 
16 20
 urlpatterns = [
17 21
     path('data/', DataListView.as_view(), name='data-list'),            # data/
@@ -24,6 +28,16 @@ urlpatterns = [
24 28
     path('datams/create/', datams_crud.get_create_view().as_view(), name='datams-create'),
25 29
     path('datams/<int:pk>/update/', datams_crud.get_update_view().as_view(), name='datams-update'),
26 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 42
     path('fg/', fg_crud.get_list_view().as_view(), name='fg-list'),
29 43
     path('fg/create/', fg_crud.get_create_view().as_view(), name='fg-create'),
@@ -34,6 +48,7 @@ urlpatterns = [
34 48
     path('ls/create/', ls_crud.get_create_view().as_view(), name='ls-create'),
35 49
     path('ls/<int:pk>/update/', ls_crud.get_update_view().as_view(), name='ls-update'),
36 50
     path('ls/<int:pk>/delete/', ls_crud.get_delete_view().as_view(), name='ls-delete'),
51
+
37 52
     path('vm/', vm_crud.get_list_view().as_view(), name='vm-list'),
38 53
     path('vm/create/', vm_crud.get_create_view().as_view(), name='vm-create'),
39 54
     path('vm/<str:pk>/update/', vm_crud.get_update_view().as_view(), name='vm-update'),
@@ -55,4 +70,13 @@ urlpatterns = [
55 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,8 +11,9 @@ from django.views.generic import (
11 11
     DeleteView,
12 12
 )
13 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 17
 from django.urls import reverse
17 18
 from django.contrib import messages
18 19
 from pprint import pprint
@@ -158,7 +159,7 @@ class LotSummaryCRUDView(ConfigurableCRUDView):
158 159
     detail_template_name = 'legacy/datacrud_detail.html'
159 160
     form_template_name = 'legacy/datacrud_form.html'
160 161
     confirm_delete_template_name = 'legacy/datacrud_confirm_delete.html'
161
-    filterset_class = DataMsFilter
162
+    filterset_class = LotSummaryFilter
162 163
 
163 164
     page_title = "Lot Summary"
164 165
 
@@ -301,3 +302,74 @@ class EMasterViewCRUDView(ConfigurableCRUDView):
301 302
     # Default ordering
302 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,6 +32,26 @@
32 32
         </label>
33 33
         {% endfor %}
34 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 55
         <div class="flex justify-end my-3 space-x-4">
36 56
           <div>
37 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,6 +15,8 @@ from django.views.decorators.csrf import csrf_exempt
15 15
 from django.http import JsonResponse, HttpResponseBadRequest
16 16
 import json
17 17
 from django.contrib.auth.decorators import login_required
18
+from django.contrib.auth.models import User
19
+
18 20
 
19 21
 def index(request):
20 22
     reports = Report.objects.all()
@@ -127,6 +129,8 @@ SHEET_NAMES = {
127 129
 }
128 130
 def coi_view(request):
129 131
     pprint(f"xxxx method = xxx {request.method}")
132
+    users = User.objects.all()
133
+
130 134
     if request.method == "POST":
131 135
         pprint(request.POST)
132 136
         exports = request.POST.getlist("exports")  # Retrieve the list of selected values
@@ -187,7 +191,7 @@ def coi_view(request):
187 191
 
188 192
         messages.success(request, "Request Sent")
189 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 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

@@ -0,0 +1,12 @@
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,6 +2,11 @@
2 2
 from django import forms
3 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 10
 class CustomLoginForm(AuthenticationForm):
6 11
     username = forms.CharField(widget=forms.TextInput(attrs={
7 12
         'placeholder': 'Username'
@@ -11,10 +16,6 @@ class CustomLoginForm(AuthenticationForm):
11 16
     }))
12 17
 
13 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 20
 class CustomUserCreationForm(UserCreationForm):
20 21
     class Meta:
@@ -41,3 +42,53 @@ class UserProfileForm(forms.ModelForm):
41 42
     class Meta:
42 43
         model = UserProfile
43 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,7 +7,9 @@ from django.contrib.auth.models import User
7 7
 class UserProfile(models.Model):
8 8
     POSITION_CHOICES = [
9 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 14
     user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
13 15
     bio = models.TextField(blank=True, null=True)
@@ -17,4 +19,4 @@ class UserProfile(models.Model):
17 19
     position = models.CharField(max_length=20, choices=POSITION_CHOICES, blank=True, null=True)  # New position field
18 20
 
19 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

@@ -0,0 +1,71 @@
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,10 +3,16 @@ from . import views
3 3
 
4 4
 app_name = "sysadmin"  # Use this namespace for reverse URL lookups
5 5
 
6
+users_crud = views.UserCRUDView()
7
+
6 8
 urlpatterns = [
7 9
     path('login/', views.login_view, name='login'),
8 10
     path('register/', views.register_view, name='register'),
9 11
     path('logout/', views.logout_view, name='logout'),
10 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,10 +1,13 @@
1 1
 # views.py
2 2
 from django.contrib.auth import authenticate, login, logout
3 3
 from django.shortcuts import render, redirect
4
-from .forms import CustomLoginForm, CustomUserCreationForm, UserProfileForm
4
+from .forms import CustomLoginForm, CustomUserCreationForm, UserProfileForm, UserCustomForm
5 5
 from .models import UserProfile
6
+from .filters import UserFilter
6 7
 from django.contrib.auth.decorators import login_required
7 8
 from django.contrib import messages
9
+from core.utils import ConfigurableCRUDView
10
+from django.contrib.auth.models import User
8 11
 
9 12
 def login_view(request):
10 13
     if request.method == "POST":
@@ -61,3 +64,25 @@ def profile_view(request):
61 64
         form = UserProfileForm(instance=profile)
62 65
 
63 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,6 +82,15 @@
82 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 83
                           </li>
84 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 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 95
                           </li>
87 96
                           <li>
@@ -91,6 +100,12 @@
91 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 101
                           </li>
93 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 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 110
                           </li>
96 111
                           <li>
@@ -102,12 +117,26 @@
102 117
                           <li>
103 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 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 120
                     </ul>
109 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 140
             </ul>
112 141
         </div>
113 142
     </aside>