py">+ self.vehicles.append(new_vehicle) 190
+            self.log.debug("Created new vehicle #%d from match (%d, %d)."
191
+                , new_vehicle.id, centroid[0], centroid[1])
192
+
193
+        # Count any uncounted vehicles that are past the divider
194
+        for vehicle in self.vehicles:
195
+            if not vehicle.counted and (((vehicle.last_position[1] > self.divider) and (vehicle.vehicle_dir == 1)) or
196
+                                          ((vehicle.last_position[1] < self.divider) and (vehicle.vehicle_dir == -1))) and (vehicle.frames_seen > 6):
197
+
198
+                vehicle.counted = True
199
+                # update appropriate counter
200
+                if ((vehicle.last_position[1] > self.divider) and (vehicle.vehicle_dir == 1) and (vehicle.last_position[0] >= (int(frame_w/2)-10))):
201
+                    self.vehicle_RHS += 1
202
+                    self.vehicle_count += 1
203
+                elif ((vehicle.last_position[1] < self.divider) and (vehicle.vehicle_dir == -1) and (vehicle.last_position[0] <= (int(frame_w/2)+10))):
204
+                    self.vehicle_LHS += 1
205
+                    self.vehicle_count += 1
206
+
207
+                self.log.debug("Counted vehicle #%d (total count=%d)."
208
+                    , vehicle.id, self.vehicle_count)
209
+
210
+        # Optionally draw the vehicles on an image
211
+        if output_image is not None:
212
+            for vehicle in self.vehicles:
213
+                vehicle.draw(output_image)
214
+
215
+            # LHS
216
+            cv2.putText(output_image, ("LH Lane: %02d" % self.vehicle_LHS), (12, 56)
217
+                , cv2.FONT_HERSHEY_PLAIN, 1.2, (127,255, 255), 2)
218
+            # RHS
219
+            cv2.putText(output_image, ("RH Lane: %02d" % self.vehicle_RHS), (216, 56)
220
+                , cv2.FONT_HERSHEY_PLAIN, 1.2, (127, 255, 255), 2)
221
+
222
+        # Remove vehicles that have not been seen long enough
223
+        removed = [ v.id for v in self.vehicles
224
+            if v.frames_since_seen >= self.max_unseen_frames ]
225
+        self.vehicles[:] = [ v for v in self.vehicles
226
+            if not v.frames_since_seen >= self.max_unseen_frames ]
227
+        for id in removed:
228
+            self.log.debug("Removed vehicle #%d.", id)
229
+
230
+        self.log.debug("Count updated, tracking %d vehicles.", len(self.vehicles))
231
+
232
+# ============================================================================
233
+
234
+def process_video():
235
+    global frame_no
236
+    global frame_w
237
+    camera = re.match(r".*/(\d+)_.*", inputFile)
238
+    camera = camera.group(1)
239
+
240
+# import video file
241
+    cap = cv2.VideoCapture(inputFile)
242
+
243
+# get list of background files
244
+    f = []
245
+    for (_, _, filenames) in walk(loc+"/backgrounds/"):
246
+        f.extend(filenames)
247
+        break
248
+
249
+# if background exists for camera: import, else avg will be built on fly
250
+    if camera+"_bg.jpg" in f:
251
+        bg = loc+"/backgrounds/"+camera+"_bg.jpg"
252
+        default_bg = cv2.imread(bg)
253
+        default_bg = cv2.cvtColor(default_bg, cv2.COLOR_BGR2HSV)
254
+        (_,avgSat,default_bg) = cv2.split(default_bg)
255
+        avg = default_bg.copy().astype("float")
256
+    else:
257
+        avg = None
258
+
259
+# get frame size
260
+    frame_w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
261
+    frame_h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
262
+
263
+# create a mask (manual for each camera)
264
+    mask = np.zeros((frame_h,frame_w), np.uint8)
265
+    mask[:,:] = 255
266
+    mask[:100, :] = 0
267
+    mask[230:, 160:190] = 0
268
+    mask[170:230,170:190] = 0
269
+    mask[140:170,176:190] = 0
270
+    mask[100:140,176:182] = 0
271
+
272
+# The cutoff for threshold. A lower number means smaller changes between
273
+# the average and current scene are more readily detected.
274
+    THRESHOLD_SENSITIVITY = 40
275
+    t_retval.append(THRESHOLD_SENSITIVITY)
276
+# Blob size limit before we consider it for tracking.
277
+    CONTOUR_WIDTH = 21
278
+    CONTOUR_HEIGHT = 16#21
279
+# The weighting to apply to "this" frame when averaging. A higher number
280
+# here means that the average scene will pick up changes more readily,
281
+# thus making the difference between average and current scenes smaller.
282
+    DEFAULT_AVERAGE_WEIGHT = 0.01
283
+    INITIAL_AVERAGE_WEIGHT = DEFAULT_AVERAGE_WEIGHT / 50
284
+# Blob smoothing function, to join 'gaps' in cars
285
+    SMOOTH = max(2,int(round((CONTOUR_WIDTH**0.5)/2,0)))
286
+# Constants for drawing on the frame.
287
+    LINE_THICKNESS = 1
288
+
289
+    fourcc = cv2.VideoWriter_fourcc(*'mp4v')
290
+    out = loc+'/outputs/'+camera+'_output.mp4'
291
+#print(out)
292
+#exit()
293
+    out = cv2.VideoWriter(out, fourcc, 20, (frame_w, frame_h))
294
+
295
+    outblob = loc+'/outputs/'+camera+'_outblob.mp4'
296
+    diffop = loc+'/outputs/'+camera+'_outdiff.mp4'
297
+    outblob = cv2.VideoWriter(outblob, fourcc, 20, (frame_w, frame_h))
298
+    diffop = cv2.VideoWriter(diffop, fourcc, 20, (frame_w, frame_h))
299
+
300
+# A list of "tracked blobs".
301
+    blobs = []
302
+    car_counter = None  # will be created later
303
+    frame_no = 0
304
+
305
+    total_frames = cap.get(cv2.CAP_PROP_FRAME_COUNT)
306
+    total_cars = 0
307
+
308
+    start_time = time.time()
309
+    ret, frame = cap.read()
310
+
311
+    while ret:
312
+        ret, frame = cap.read()
313
+        frame_no = frame_no + 1
314
+
315
+        if ret and frame_no < total_frames:
316
+
317
+            print("Processing frame ",frame_no)
318
+
319
+            # get returned time
320
+            frame_time = time.time()
321
+
322
+            # convert BGR to HSV
323
+            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
324
+
325
+            # only use the Value channel of the frame
326
+            (_,_,grayFrame) = cv2.split(frame)
327
+            grayFrame = cv2.bilateralFilter(grayFrame, 11, 21, 21)
328
+
329
+            if avg is None:
330
+                # Set up the average if this is the first time through.
331
+                avg = grayFrame.copy().astype("float")
332
+                continue
333
+
334
+            # Build the average scene image by accumulating this frame
335
+            # with the existing average.
336
+            if frame_no < 10:
337
+                def_wt = INITIAL_AVERAGE_WEIGHT
338
+            else:
339
+                def_wt = DEFAULT_AVERAGE_WEIGHT
340
+
341
+            cv2.accumulateWeighted(grayFrame, avg, def_wt)
342
+
343
+            # export averaged background for use in next video feed run
344
+            #if frame_no > int(total_frames * 0.975):
345
+            if frame_no > int(200):
346
+                grayOp = cv2.cvtColor(cv2.convertScaleAbs(avg), cv2.COLOR_GRAY2BGR)
347
+                backOut = loc+"/backgrounds/"+camera+"_bg.jpg"
348
+                cv2.imwrite(backOut, grayOp)
349
+
350
+            # Compute the grayscale difference between the current grayscale frame and
351
+            # the average of the scene.
352
+            differenceFrame = cv2.absdiff(grayFrame, cv2.convertScaleAbs(avg))
353
+            # blur the difference image
354
+            differenceFrame = cv2.GaussianBlur(differenceFrame, (5, 5), 0)
355
+#        cv2.imshow("difference", differenceFrame)
356
+            diffout = cv2.cvtColor(differenceFrame, cv2.COLOR_GRAY2BGR)
357
+            diffop.write(diffout)
358
+
359
+            # get estimated otsu threshold level
360
+            retval, _ = cv2.threshold(differenceFrame, 0, 255,
361
+                                      cv2.THRESH_BINARY+cv2.THRESH_OTSU)
362
+            # add to list of threshold levels
363
+            t_retval.append(retval)
364
+
365
+            # apply threshold based on average threshold value
366
+            if frame_no < 10:
367
+                ret2, thresholdImage = cv2.threshold(differenceFrame,
368
+                                                     int(np.mean(t_retval)*0.9),
369
+                                                     255, cv2.THRESH_BINARY)
370
+            else:
371
+                ret2, thresholdImage = cv2.threshold(differenceFrame,
372
+                                                 int(np.mean(t_retval[-10:-1])*0.9),
373
+                                                 255, cv2.THRESH_BINARY)
374
+
375
+            # We'll need to fill in the gaps to make a complete vehicle as windows
376
+            # and other features can split them!
377
+            kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (SMOOTH, SMOOTH))
378
+            # Fill any small holes
379
+            thresholdImage = cv2.morphologyEx(thresholdImage, cv2.MORPH_CLOSE, kernel)
380
+
381
+            # Remove noise
382
+            thresholdImage = cv2.morphologyEx(thresholdImage, cv2.MORPH_OPEN, kernel)
383
+
384
+            # Dilate to merge adjacent blobs
385
+            thresholdImage = cv2.dilate(thresholdImage, kernel, iterations = 2)
386
+
387
+            # apply mask
388
+            thresholdImage = cv2.bitwise_and(thresholdImage, thresholdImage, mask = mask)
389
+#        cv2.imshow("threshold", thresholdImage)
390
+            threshout = cv2.cvtColor(thresholdImage, cv2.COLOR_GRAY2BGR)
391
+            outblob.write(threshout)
392
+
393
+            # Find contours aka blobs in the threshold image.
394
+            contours, hierarchy = cv2.findContours(thresholdImage,
395
+                                                      cv2.RETR_EXTERNAL,
396
+                                                      cv2.CHAIN_APPROX_SIMPLE)
397
+
398
+            print("Found ",len(contours)," vehicle contours.")
399
+            # process contours if they exist!
400
+            if contours:
401
+                for (i, contour) in enumerate(contours):
402
+                    # Find the bounding rectangle and center for each blob
403
+                    (x, y, w, h) = cv2.boundingRect(contour)
404
+                    contour_valid = (w > CONTOUR_WIDTH) and (h > CONTOUR_HEIGHT)
405
+
406
+                    print("Contour #",i,": pos=(x=",x,", y=",y,") size=(w=",w,
407
+                          ", h=",h,") valid=",contour_valid)
408
+
409
+                    if not contour_valid:
410
+                        continue
411
+
412
+                    center = (int(x + w/2), int(y + h/2))
413
+                    blobs.append(((x, y, w, h), center))
414
+
415
+            for (i, match) in enumerate(blobs):
416
+                contour, centroid = match
417
+                x, y, w, h = contour
418
+
419
+                # store the contour data
420
+                c = dict(
421
+                            frame_no = frame_no,
422
+                            centre_x = x,
423
+                            centre_y = y,
424
+                            width = w,
425
+                            height = h
426
+                            )
427
+                tracked_conts.append(c)
428
+
429
+                cv2.rectangle(frame, (x, y), (x + w - 1, y + h - 1), (0, 0, 255), LINE_THICKNESS)
430
+                cv2.circle(frame, centroid, 2, (0, 0, 255), -1)
431
+
432
+            if car_counter is None:
433
+                print("Creating vehicle counter...")
434
+                car_counter = VehicleCounter(frame.shape[:2], 2*frame.shape[0] / 3)
435
+
436
+            # get latest count
437
+            car_counter.update_count(blobs, frame)
438
+            current_count = car_counter.vehicle_RHS + car_counter.vehicle_LHS
439
+
440
+            # print elapsed time to console
441
+            elapsed_time = time.time()-start_time
442
+            print("-- %s seconds --" % round(elapsed_time,2))
443
+
444
+            # output video
445
+            frame = cv2.cvtColor(frame, cv2.COLOR_HSV2BGR)
446
+
447
+            # draw dividing line
448
+            # flash green when new car counted
449
+            if current_count > total_cars:
450
+                cv2.line(frame, (0, int(2*frame_h/3)),(frame_w, int(2*frame_h/3)),
451
+                     (0,255,0), 2*LINE_THICKNESS)
452
+            else:
453
+                cv2.line(frame, (0, int(2*frame_h/3)),(frame_w, int(2*frame_h/3)),
454
+                 (0,0,255), LINE_THICKNESS)
455
+
456
+             # update with latest count
457
+            total_cars = current_count
458
+
459
+            # draw upper limit
460
+            cv2.line(frame, (0, 100),(frame_w, 100), (0,0,0), LINE_THICKNESS)
461
+
462
+            ret, buffer = cv2.imencode('.jpg', frame)
463
+            frame2 = buffer.tobytes()
464
+            yield (b'--frame\r\n'
465
+                b'Content-Type: image/jpeg\r\n\r\n' + frame2 + b'\r\n')  # concat frame one by one and show result
466
+
467
+            #cv2.imshow("preview", frame)
468
+            #cv2.imwrite("../flask-hls-demo/static/frame.jpg", frame)
469
+            out.write(frame)
470
+
471
+            if cv2.waitKey(27) and 0xFF == ord('q'):
472
+                break
473
+        else:
474
+            break
475
+
476
+    #cv2.line()
477
+    #cv2.destroyAllWindows()
478
+    #cap.release()
479
+    #out.release()
480
+
481
+from flask import Flask, render_template, Response
482
+import cv2
483
+
484
+app = Flask(__name__)
485
+
486
+
487
+def find_camera(id):
488
+    '''
489
+    cameras = ['rtsp://username:password@ip_address:554/user=username_password='password'_channel=channel_number_stream=0.sdp',
490
+    'rtsp://username:password@ip_address:554/user=username_password='password'_channel=channel_number_stream=0.sdp']
491
+    '''
492
+    cameras = ['rtsp://admin:@Unv123456@192.168.10.252:554/unicast/c1/s1/live']
493
+    return cameras[int(id)]
494
+#  for cctv camera use rtsp://username:password@ip_address:554/user=username_password='password'_channel=channel_number_stream=0.sdp' instead of camera
495
+#  for webcam use zero(0)
496
+
497
+
498
+def gen_frames(camera_id):
499
+
500
+    cam = find_camera(camera_id)
501
+    cap=  cv2.VideoCapture(cam)
502
+
503
+    while True:
504
+        # for cap in caps:
505
+        # # Capture frame-by-frame
506
+        success, frame = cap.read()  # read the camera frame
507
+        if not success:
508
+            break
509
+        else:
510
+            ret, buffer = cv2.imencode('.jpg', frame)
511
+            frame = buffer.tobytes()
512
+            yield (b'--frame\r\n'
513
+                b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')  # concat frame one by one and show result
514
+@app.route('/video_feed/<string:id>/', methods=["GET"])
515
+def video_feed(id):
516
+
517
+    """Video streaming route. Put this in the src attribute of an img tag."""
518
+    '''
519
+    return Response(gen_frames(id),
520
+                    mimetype='multipart/x-mixed-replace; boundary=frame')
521
+    '''
522
+    return Response(process_video(),
523
+                    mimetype='multipart/x-mixed-replace; boundary=frame')
524
+
525
+@app.route('/', methods=["GET"])
526
+def index():
527
+    return render_template('index.html')
528
+
529
+
530
+if __name__ == '__main__':
531
+    app.run(debug=True, port=9099)

binární
cap/frame0.jpg


binární
cap/frame1.jpg


binární
cap/frame10.jpg


binární
cap/frame100.jpg


binární
cap/frame101.jpg


binární
cap/frame102.jpg


binární
cap/frame103.jpg


binární
cap/frame104.jpg


binární
cap/frame105.jpg


binární
cap/frame106.jpg


binární
cap/frame107.jpg


binární
cap/frame108.jpg


binární
cap/frame109.jpg


binární
cap/frame11.jpg


binární
cap/frame110.jpg


binární
cap/frame111.jpg


binární
cap/frame112.jpg


binární
cap/frame113.jpg


binární
cap/frame114.jpg


binární
cap/frame115.jpg


binární
cap/frame116.jpg


binární
cap/frame117.jpg


binární
cap/frame118.jpg


binární
cap/frame119.jpg


binární
cap/frame12.jpg


binární
cap/frame120.jpg


binární
cap/frame121.jpg


binární
cap/frame122.jpg


binární
cap/frame123.jpg


binární
cap/frame124.jpg


binární
cap/frame125.jpg


binární
cap/frame126.jpg


binární
cap/frame127.jpg


binární
cap/frame128.jpg


binární
cap/frame129.jpg


binární
cap/frame13.jpg


binární
cap/frame130.jpg


binární
cap/frame131.jpg


binární
cap/frame132.jpg


binární
cap/frame133.jpg


binární
cap/frame134.jpg


binární
cap/frame135.jpg


binární
cap/frame136.jpg


binární
cap/frame137.jpg


binární
cap/frame138.jpg


binární
cap/frame139.jpg


binární
cap/frame14.jpg


binární
cap/frame140.jpg


binární
cap/frame141.jpg


binární
cap/frame142.jpg


binární
cap/frame143.jpg


binární
cap/frame144.jpg


binární
cap/frame145.jpg


binární
cap/frame146.jpg


binární
cap/frame147.jpg


binární
cap/frame148.jpg


binární
cap/frame149.jpg


binární
cap/frame15.jpg


binární
cap/frame150.jpg


binární
cap/frame151.jpg


binární
cap/frame152.jpg


binární
cap/frame153.jpg


binární
cap/frame154.jpg


binární
cap/frame155.jpg


binární
cap/frame156.jpg


binární
cap/frame157.jpg


binární
cap/frame158.jpg


binární
cap/frame159.jpg


binární
cap/frame16.jpg


binární
cap/frame160.jpg


binární
cap/frame161.jpg


binární
cap/frame162.jpg


binární
cap/frame163.jpg


binární
cap/frame164.jpg


binární
cap/frame165.jpg


binární
cap/frame166.jpg


binární
cap/frame167.jpg


binární
cap/frame168.jpg


binární
cap/frame169.jpg


binární
cap/frame17.jpg


binární
cap/frame170.jpg


binární
cap/frame171.jpg


binární
cap/frame172.jpg


binární
cap/frame173.jpg


binární
cap/frame174.jpg


binární
cap/frame175.jpg


binární
cap/frame176.jpg


binární
cap/frame177.jpg


binární
cap/frame178.jpg


binární
cap/frame179.jpg


binární
cap/frame18.jpg


binární
cap/frame180.jpg


binární
cap/frame181.jpg


+ 0 - 0
cap/frame182.jpg


Some files were not shown because too many files changed in this diff

coi template · f526bca111 - Gogs: Simplico Git Service
浏览代码

coi template

tum 1 年之前
父节点
当前提交
f526bca111
共有 8 个文件被更改,包括 160 次插入17 次删除
  1. 1 1
      app/coi/settings.py
  2. 1 0
      app/package.json
  3. 二进制
      app/report/coi_templates.xlsx
  4. 58 3
      app/report/templates/report/coi.html
  5. 2 0
      app/report/urls.py
  6. 89 7
      app/report/views.py
  7. 二进制
      app/report/~$coi_templates.xlsx
  8. 9 6
      app/templates/base.html

+ 1 - 1
app/coi/settings.py

@@ -172,7 +172,7 @@ AUTH_PASSWORD_VALIDATORS = [
172 172
 
173 173
 LANGUAGE_CODE = "en-us"
174 174
 
175
-TIME_ZONE = "UTC"
175
+TIME_ZONE = "Asia/Bangkok"
176 176
 
177 177
 USE_I18N = True
178 178
 

+ 1 - 0
app/package.json

@@ -1,6 +1,7 @@
1 1
 {
2 2
   "dependencies": {
3 3
     "alpinejs": "^3.14.7",
4
+    "axios": "^1.7.9",
4 5
     "font-awesome": "^4.7.0",
5 6
     "heroicons": "^2.2.0",
6 7
     "tailwindcss": "^3.4.17",

二进制
app/report/coi_templates.xlsx


+ 58 - 3
app/report/templates/report/coi.html

@@ -3,7 +3,7 @@
3 3
 {% block title %}Report Dashboard{% endblock %}
4 4
 
5 5
 {% block content %}
6
-<div class="container mx-auto px-4 py-8">
6
+<div class="container mx-auto px-4 py-8" x-data="COIReport">
7 7
 
8 8
   <h1 class="text-2xl font-bold text-gray-800">Export Center</h1>
9 9
   <form method='post'>
@@ -16,7 +16,7 @@
16 16
     </div>
17 17
     <div class="flex items-center gap-2 mb-4">
18 18
       <label for="lot-number" class="text-gray-700 font-medium">Lot No. :</label>
19
-      <input id="lot-number" type="text" class="border border-gray-300 rounded px-4 py-2 focus:outline-blue-500" placeholder="Enter Lot No." name='lot_no' required>
19
+      <input id="lot-number" type="text" class="border border-gray-300 rounded px-4 py-2 focus:outline-blue-500" placeholder="Enter Lot No." name='lot_no' required x-model="lot_no">
20 20
       <button class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600" type="submit" name="search_lot">
21 21
         🔍
22 22
       </button>
@@ -59,9 +59,16 @@
59 59
           <span>Final Judge</span>
60 60
         </label>
61 61
       </div>
62
-      <button class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600" type='submit' name='export'>
62
+      <button  type='button' class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600" 
63
+        @click="exportCOI">
63 64
         Export Excel
64 65
       </button>
66
+      <!-- TODO: add download here -->
67
+      <div class=" text-center" x-show="downloadUrl">
68
+        <a :href="downloadUrl"  class="px-4 py-2 bg-green-600 text-white font-medium rounded-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2" download>
69
+          Download Report
70
+        </a>
71
+      </div>
65 72
     </div>
66 73
   </form>
67 74
   {{ result }}
@@ -130,3 +137,51 @@
130 137
 <div class="border-t border-gray-300 my-4"></div>
131 138
 {% include "report/_cen.html" %}
132 139
 {% endblock %}
140
+{% block footer_script %}
141
+<script type="text/javascript">
142
+  function COIReport() {
143
+    return {
144
+      lot_no: '', // Bind this to the input value
145
+      gen_report_url: '{% url "report:gen_report" %}', 
146
+      downloadUrl: null, // Stores the download link after export success
147
+      init() {
148
+        //alert("COI Report");
149
+      },
150
+      async exportCOI() {
151
+              if (!this.lot_no) {
152
+                  alert("Please enter a Lot No.");
153
+                  return;
154
+              }
155
+
156
+              try {
157
+                  // Make POST request using Axios
158
+                  const response = await axios.post(this.gen_report_url, {
159
+                      lot_no: this.lot_no,
160
+                  });
161
+
162
+                  if (response.status === 200) {
163
+                      const result = response.data;
164
+                      alert(`Report generated successfully: ${result.file_url}`);
165
+                      console.log('File URL:', result.file_url);
166
+                      this.downloadUrl = result.file_url; // Set the download URL
167
+                  }
168
+              } catch (error) {
169
+                  if (error.response) {
170
+                      // Server responded with a status other than 2xx
171
+                      alert(`Error: ${error.response.data.message || 'Failed to generate report'}`);
172
+                      console.error('Error response:', error.response);
173
+                  } else if (error.request) {
174
+                      // Request was made but no response received
175
+                      alert('No response from server.');
176
+                      console.error('Request error:', error.request);
177
+                  } else {
178
+                      // Something else caused the error
179
+                      alert('An unexpected error occurred.');
180
+                      console.error('Unexpected error:', error.message);
181
+                  }
182
+              }
183
+          },
184
+    };
185
+  }
186
+</script>
187
+{% endblock %}

+ 2 - 0
app/report/urls.py

@@ -14,6 +14,8 @@ urlpatterns = [
14 14
     path('report/<int:pk>/update/', report_crud.get_update_view().as_view(), name='report-update'),
15 15
     path('report/<int:pk>/delete/', report_crud.get_delete_view().as_view(), name='report-delete'),
16 16
     path('coi/', views.coi_view, name='coi-view'),
17
+    path('report/generate/', views.gen_report_view, name='gen_report'),
18
+
17 19
     # path('create/', views.create_report, name='create'),  # Create a new report
18 20
     # path('<int:pk>/', views.detail_report, name='detail'),  # View details of a specific report
19 21
     # path('<int:pk>/update/', views.update_report, name='update'),  # Update a specific report

+ 89 - 7
app/report/views.py

@@ -11,7 +11,10 @@ from pprint import pprint
11 11
 from .gen_report import gen_xlsx
12 12
 from django.core.files.base import File
13 13
 from pathlib import Path
14
-
14
+from django.views.decorators.csrf import csrf_exempt
15
+from django.http import JsonResponse, HttpResponseBadRequest
16
+import json
17
+from django.contrib.auth.decorators import login_required
15 18
 
16 19
 def index(request):
17 20
     reports = Report.objects.all()
@@ -62,6 +65,52 @@ class ReportCRUDView(ConfigurableCRUDView):
62 65
     # config_edit_fields = ["lot_no", "code"]
63 66
     ordering = ["-created_at", "-id",]
64 67
 
68
+def create_coi_file(lot_no, user):
69
+
70
+    data = {
71
+        "customer": "Tum Coder",
72
+        "inspect_date": "2025-01-15",
73
+        "lot_no": "12345",
74
+        "staff_name":  "Tum 8888",
75
+        "man_name":  "Tum 999",
76
+        "size": "Large",
77
+        "pcs": "10 pcs",
78
+        "spec": "Spec-A",
79
+        "hardness_out.d1_act": "10",
80
+        "hardness_out.d2_act": "0[24:28]",  # Hide rows 24 to 28 if the prefix is "0"
81
+        "hardness_out.acc": True,  # Hide rows 24 to 28 if the prefix is "0"
82
+        "hardness_out.spe_acc": False,  # Hide rows 24 to 28 if the prefix is "0"
83
+        "hardness_out.qa1": "Hello world",
84
+        "hardness_out.qa2": "Unknow person",
85
+        "hardness_out.v11": 5,
86
+        "hardness_out.v12": 15,
87
+        "hardness_out.v13": 10,
88
+        "hardness_out.v21": 9,
89
+        "hardness_out.v22": 8,
90
+        "hardness_out.v23": 7,
91
+        "dimension_app.d1_act": "33",
92
+        "dimension_app.d2_act": "0[26:32]",  # Hide rows 24 to 28 if the prefix is "0"
93
+        "dimension_app.acc": True,  # Hide rows 24 to 28 if the prefix is "0"
94
+        "dimension_app.spe_acc": True,  # Hide rows 24 to 28 if the prefix is "0"
95
+    }
96
+    output_file = gen_xlsx(
97
+        template_file="/app/report/coi_templates.xlsx",
98
+        selected_sheets=["hardness_out", "dimension_app"],  # Replace with your actual sheet names
99
+        prefix_filename="/app/media/coi",
100
+        data=data
101
+    )
102
+    report = Report.objects.create(
103
+        name=lot_no,
104
+        created_by=user,
105
+        file=None  # Leave this as None or assign a file if required
106
+    )
107
+    output_file_path = Path(output_file)  # Convert to a Path object for convenience
108
+    with open(output_file_path, "rb") as f:
109
+        report.file.save(output_file_path.name, File(f), save=True)
110
+
111
+    pprint(f"outputfile = {output_file}")
112
+    return report
113
+
65 114
 def coi_view(request):
66 115
     pprint(f"xxxx method = xxx {request.method}")
67 116
     if request.method == "POST":
@@ -75,12 +124,12 @@ def coi_view(request):
75 124
                 "staff_name":  "Tum 8888",
76 125
                 "man_name":  "Tum 999",
77 126
                 "size": "Large",
78
-                "pcs": "10 pcs",
127
+                "lot_size": "10 pcs",
79 128
                 "spec": "Spec-A",
80
-                "hardness.d1_act": "10",
81
-                "hardness.d2_act": "0[24:28]",  # Hide rows 24 to 28 if the prefix is "0"
82
-                "hardness.acc": True,  # Hide rows 24 to 28 if the prefix is "0"
83
-                "hardness.spe_acc": False,  # Hide rows 24 to 28 if the prefix is "0"
129
+                "hardness_out.d1_act": "10",
130
+                "hardness_out.d2_act": "0[24:28]",  # Hide rows 24 to 28 if the prefix is "0"
131
+                "hardness_out.acc": True,  # Hide rows 24 to 28 if the prefix is "0"
132
+                "hardness_out.spe_acc": False,  # Hide rows 24 to 28 if the prefix is "0"
84 133
                 "dimension_app.d1_act": "33",
85 134
                 "dimension_app.d2_act": "0[26:32]",  # Hide rows 24 to 28 if the prefix is "0"
86 135
                 "dimension_app.acc": True,  # Hide rows 24 to 28 if the prefix is "0"
@@ -88,7 +137,7 @@ def coi_view(request):
88 137
             }
89 138
             output_file = gen_xlsx(
90 139
                 template_file="/app/report/coi_templates.xlsx",
91
-                selected_sheets=["hardness", "dimension_app"],  # Replace with your actual sheet names
140
+                selected_sheets=["hardness_out", "dimension_app"],  # Replace with your actual sheet names
92 141
                 prefix_filename="/app/media/coi",
93 142
                 data=data
94 143
             )
@@ -124,3 +173,36 @@ def coi_view(request):
124 173
         messages.success(request, "Request Sent")
125 174
         return redirect(request.path_info)
126 175
     return render(request, 'report/coi.html')
176
+
177
+@csrf_exempt  # Disable CSRF for API requests (ensure this is secure in production)
178
+@login_required
179
+def gen_report_view(request):
180
+    if request.method == "POST":
181
+        try:
182
+            # Parse JSON data from the request body
183
+            data = json.loads(request.body)
184
+            lot_no = data.get("lot_no")
185
+
186
+            if not lot_no:
187
+                return HttpResponseBadRequest("Missing 'lot_no' in request data")
188
+
189
+            # Call the `create_coi_file` function with the provided lot_no
190
+            report = create_coi_file(lot_no, request.user)
191
+
192
+            # Return a success response with the report details
193
+            return JsonResponse({
194
+                "message": "Report generated successfully",
195
+                "report_id": report.id,
196
+                "file_url": report.file.url if report.file else None,
197
+            })
198
+
199
+        except json.JSONDecodeError:
200
+            return HttpResponseBadRequest("Invalid JSON data")
201
+        except Exception as e:
202
+            pprint(e)
203
+            return JsonResponse({"error": str(e)}, status=500)
204
+    else:
205
+        return HttpResponseBadRequest("Only POST requests are allowed")
206
+
207
+
208
+

二进制
app/report/~$coi_templates.xlsx


+ 9 - 6
app/templates/base.html

@@ -16,6 +16,7 @@
16 16
     <link href="{% static "font-awesome/css/font-awesome.css" %}" rel="stylesheet" />
17 17
     <script type="text/javascript" defer src="{% static "alpinejs/dist/cdn.min.js" %}"></script>
18 18
     <link href="https://cdn.jsdelivr.net/npm/flowbite@2.5.2/dist/flowbite.min.css" rel="stylesheet" />
19
+    <script type="text/javascript"  src="{% static "axios/dist/axios.js" %}"></script>
19 20
     <script type="text/javascript"  src="{% static "js/main.js" %}"></script>
20 21
 </head>
21 22
 <body class="bg-gray-100 text-gray-800">
@@ -144,14 +145,16 @@
144 145
 
145 146
     {% django_browser_reload_script %}
146 147
     <script>
147
-    // Auto-hide the message after 5 seconds
148
-    setTimeout(() => {
148
+      // Auto-hide the message after 5 seconds
149
+      setTimeout(() => {
149 150
         const alert = document.getElementById('message-alert');
150 151
         if (alert) {
151
-            alert.style.opacity = '0'; // Fade out
152
-            setTimeout(() => alert.remove(), 500); // Remove after fade-out
152
+          alert.style.opacity = '0'; // Fade out
153
+          setTimeout(() => alert.remove(), 500); // Remove after fade-out
153 154
         }
154
-    }, 5000); // 5 seconds
155
-</script>
155
+      }, 5000); // 5 seconds
156
+    </script>
157
+    {% block footer_script %}
158
+    {% endblock %}
156 159
 </body>
157 160
 </html>