@@ -487,41 +487,130 @@ class ProductGroupAdmin(CustomModelAdmin):
487487
488488
489489class ProductAdmin (CustomModelAdmin ):
490- __last_register__ = None
491- list_display = ('id' , 'article' , 'name' , 'get_barcodes' , 'get_qrcodes' , 'get_cost' , 'get_price' , 'count' , 'get_tax' , 'get_model' , 'get_group' , 'extinfo' )
490+ __objs__ = {}
491+ list_display = ('id' , 'article' , 'name' , 'get_barcodes' , 'get_qrcodes' , 'get_cost' , 'get_price' , 'count' , 'get_sum' , ' get_tax' , 'get_model' , 'get_group' , 'extinfo' )
492492 list_display_links = ('id' , 'article' , 'name' )
493493 search_fields = ('name' , 'article' , 'extinfo' , 'barcodes__id' , 'qrcodes__id' , 'group__name' )
494494 list_select_related = ('tax' , 'model' , 'group' )
495495 list_filter = (ProductGroupFilter , ProductManufacturerFilter , ProductModelFilter , TaxFilter )
496496 autocomplete_fields = ('tax' , 'model' , 'group' , 'barcodes' , 'qrcodes' )
497- actions = ('from_xls' , 'to_xls' , 'barcode_generator ' )
497+ actions = ('from_xls' , 'to_xls' , 'price_to_xls ' )
498498
499499 #class Media:
500500 #js = ['admin/js/autocomplete.js', 'admin/js/vendor/select2/select2.full.js']
501501
502- def get_cost (self , obj ):
503- if not self .__last_register__ :
502+ def worksheet_cell_write (self , worksheet , row , col , value , type_value = None , fmt = None ):
503+ func_write = worksheet .write
504+ if type_value == 'as_number' :
505+ func_write = worksheet .write_number
506+ elif type_value == 'as_datetime' :
507+ func_write = worksheet .write_datetime
508+ try :
509+ if fmt :
510+ func_write (row , col , value , fmt )
511+ else :
512+ func_write (row , col , value )
513+ except Exception as e :
504514 try :
505- self .__last_register__ = get_model ('core.Register' ).objects .filter (rec__product_id = obj .id ).order_by ('-rec__doc__registered_at' ).first ()
515+ if fmt :
516+ func_write (row , col , repr (value ), fmt )
517+ else :
518+ func_write (row , col , repr (value ))
519+ except Exception as e :
520+ self .loge (e , row , col )
521+ return col + 1
522+
523+ def queryset_to_xls (self , request , queryset , fields = {}, exclude_fields = ['id' ]):
524+ import xlsxwriter
525+ output = None
526+ if queryset .count ():
527+ field_names = list (fields .keys ())
528+ if not field_names :
529+ for field in queryset .model ._meta .get_fields ():
530+ if field .name and field .name not in exclude_fields :
531+ field_names .append (field .name )
532+ output = BytesIO ()
533+ workbook = xlsxwriter .Workbook (output , {'in_memory' : True })
534+ worksheet = workbook .add_worksheet ()
535+ cell_format_bold = workbook .add_format ({'align' :'center' , 'valign' :'vcenter' , 'bold' :True })
536+ cell_format_left = workbook .add_format ({'align' :'left' , 'valign' :'vcenter' })
537+ col = 0
538+ for field_name in field_names :
539+ field_title = field_name
540+ if field_name in fields :
541+ width = fields [field_name ].get ('width' , None )
542+ if width is not None :
543+ worksheet .set_column (col , col , width )
544+ field_title = fields [field_name ].get ('title' , field_title )
545+ col = self .worksheet_cell_write (worksheet , 0 , col , _ (field_title ), fmt = cell_format_bold )
546+ row = 1
547+ for item in queryset :
548+ worksheet .set_row (row , None , cell_format_left )
549+ col = 0
550+ for field_name in field_names :
551+ if not hasattr (item , field_name ):
552+ col += 1
553+ continue
554+ else :
555+ if not field_name :
556+ col += 1
557+ continue
558+ try :
559+ value = getattr (item , field_name )
560+ except AttributeError as e :
561+ col += 1
562+ self .loge (e )
563+ except Exception as e :
564+ col += 1
565+ self .loge (e )
566+ else :
567+ if not value :
568+ col += 1
569+ else :
570+ format_value = None
571+ tvalue = None
572+ if isinstance (value , datetime ):
573+ value = f'{ value .strftime ("%Y.%m.%d %H:%M:%S" )} '
574+ elif isinstance (value , (int , float )):
575+ tvalue = 'as_number'
576+ elif not isinstance (value , str ):
577+ value = f'{ value } '
578+ col = self .worksheet_cell_write (worksheet , row , col , value , tvalue , format_value )
579+ row += 1
580+ workbook .close ()
581+ output .seek (0 )
582+ return output
583+
584+ def get_last_reg (self , obj ):
585+ if obj .id in self .__objs__ and 'lreg' in self .__objs__ [obj .id ]:
586+ return self .__objs__ [obj .id ]['lreg' ]
587+ else :
588+ if obj .id not in self .__objs__ :
589+ self .__objs__ [obj .id ] = {}
590+ try :
591+ self .__objs__ [obj .id ]['lreg' ] = get_model ('core.Register' ).objects .filter (rec__product_id = obj .id ).order_by ('-rec__doc__registered_at' ).first ()
506592 except Exception as e :
507593 self .loge (e )
594+ else :
595+ return self .__objs__ [obj .id ]['lreg' ]
596+
597+ def get_price_value (self , obj ):
598+ last_reg = self .get_last_reg (obj )
599+ if last_reg :
600+ return last_reg .rec .price
601+ return obj .price
602+
603+ def get_cost (self , obj ):
604+ last_reg = self .get_last_reg (obj )
508605 value = obj .cost
509- if self . __last_register__ :
510- value = self . __last_register__ .rec .cost
606+ if last_reg :
607+ value = last_reg .rec .cost
511608 return format_html ('<font color="green" face="Verdana, Geneva, sans-serif">{} {}</font>' , value , obj .currency .name if obj .currency else '' )
512609 get_cost .short_description = _ ('cost' )
513610 get_cost .admin_order_field = 'cost'
514611
515612 def get_price (self , obj ):
516- if not self .__last_register__ :
517- try :
518- self .__last_register__ = get_model ('core.Register' ).objects .filter (rec__product_id = obj .id ).order_by ('-rec__doc__registered_at' ).first ()
519- except Exception as e :
520- self .loge (e )
521- value = obj .price
522- if self .__last_register__ :
523- value = self .__last_register__ .rec .price
524- return format_html ('<font color="green" face="Verdana, Geneva, sans-serif">{} {}</font>' , value .quantize (Decimal ('0.00' )), obj .currency .name if obj .currency else '' )
613+ return format_html ('<font color="green" face="Verdana, Geneva, sans-serif">{} {}</font>' , self .get_price_value (obj ).quantize (Decimal ('0.00' )), obj .currency .name if obj .currency else '' )
525614 get_price .short_description = _ ('price' )
526615 get_price .admin_order_field = 'price'
527616
@@ -549,14 +638,37 @@ def get_qrcodes(self, obj):
549638 return format_html ('<details><summary>{}</summary>{}</details>' , idxs [0 ][1 ], content )
550639 get_qrcodes .short_description = _ ('Qr Codes' )
551640
641+ def get_count_from_reg (self , obj ):
642+ if obj .id in self .__objs__ and 'count' in self .__objs__ [obj .id ]:
643+ return self .__objs__ [obj .id ]['count' ]
644+ else :
645+ if obj .id not in self .__objs__ :
646+ self .__objs__ [obj .id ] = {}
647+ SumIncome = Sum ('rec__count' , filter = Q (rec__doc__type__income = True ), default = 0 )
648+ SumExpense = Sum ('rec__count' , filter = Q (rec__doc__type__income = False ), default = 0 )
649+ try :
650+ self .__objs__ [obj .id ]['count' ] = get_model ('core.Register' ).objects .filter (rec__product_id = obj .id ).aggregate (count = SumIncome - SumExpense )['count' ]
651+ except Exception as e :
652+ self .loge (e )
653+ else :
654+ return self .__objs__ [obj .id ]['count' ]
655+ return 0
656+
552657 def count (self , obj ):
553- SumIncome = Sum ('rec__count' , filter = Q (rec__doc__type__income = True ), default = 0 )
554- SumExpense = Sum ('rec__count' , filter = Q (rec__doc__type__income = False ), default = 0 )
555- result = get_model ('core.Register' ).objects .filter (rec__product_id = obj .id ).aggregate (count = SumIncome - SumExpense )['count' ]
658+ result = self .get_count_from_reg (obj )
556659 color = 'green' if result > 0 else 'red'
557660 return format_html ('<p><a href="{}/core/register/?rec__product={}" target="_blank" style="color:{}">{}</a></p>' , settings .ADMIN_PATH_PREFIX , obj .id , color , result )
558661 count .short_description = _ ('Count' )
559662
663+ def get_sum (self , obj ):
664+ count = self .get_count_from_reg (obj )
665+ price = self .get_price_value (obj )
666+ if count > 0 and price > 0 :
667+ #return f'{count * price:.2f}'
668+ return format_html ('<font color="green" face="Verdana, Geneva, sans-serif">{} {}</font>' , (price * count ).quantize (Decimal ('0.00' )), obj .currency .name if obj .currency else '' )
669+ return '0'
670+ get_sum .short_description = _ ('sum' )
671+
560672 def get_tax (self , obj ):
561673 o = obj .tax
562674 if not o :
@@ -585,7 +697,6 @@ def get_group(self, obj):
585697 get_group .admin_order_field = 'group'
586698
587699 def from_xls (self , request , queryset , ** kwargs ):
588- import xlsxwriter
589700 from transliterate import slugify
590701 from openpyxl import load_workbook
591702 form = None
@@ -701,6 +812,26 @@ def from_xls(self, request, queryset, **kwargs):
701812 context ['available_apps' ] = (m .app_label ,)
702813 context ['app_label' ] = m .app_label
703814 return render (request , 'admin_select_file_form.html' , context )
704- from_xls .short_description = f'⚔{ _ ("load from XLS file" )} 🔙'
815+ from_xls .short_description = f'⚔{ _ ("load from XLS file" )} '
816+
817+ def to_xls (self , request , queryset ):
818+ output = self .queryset_to_xls (request , queryset .annotate (unit_name = F ('unit__name' ), barcode_first = F ('barcodes__id' ), qrcode_first = F ('qrcodes__id' )), {'group' :{'width' :20 }, 'name' :{'width' :20 }, 'article' :{'width' :20 }, 'barcode_first' :{'width' :20 , 'title' :'barcode' }, 'unit_name' :{'title' :'unit' }, 'cost' :{}, 'price' :{}, 'qrcode_first' :{'width' :20 , 'title' :'qrcode' }})
819+ if output :
820+ fn = '{}.xlsx' .format (django_timezone .now ().strftime ('%Y%m%d%H%M%S' ))
821+ self .message_user (request , f'🆗 { _ ("Finished" )} ✏️({ fn } )' , messages .SUCCESS )
822+ return FileResponse (output , as_attachment = True , filename = fn )
823+ self .message_user (request , _ ('please select items' ), messages .ERROR )
824+ to_xls .short_description = f'⚔{ _ ("export to XLS file" )} ↘️'
825+
826+ def price_to_xls (self , request , queryset ):
827+ last_register = get_model ('core.Register' ).objects .filter (rec__product_id = OuterRef ('pk' )).order_by ('-rec__doc__registered_at' )[:1 ]
828+ queryset = queryset .annotate (last_price = Subquery (last_register .values ('rec__price' )))
829+ output = self .queryset_to_xls (request , queryset , {'article' :{'width' :30 }, 'name' :{'width' :50 }, 'last_price' :{'width' :20 , 'title' :'price' }})
830+ if output :
831+ fn = '{}.xlsx' .format (django_timezone .now ().strftime ('%Y%m%d%H%M%S' ))
832+ self .message_user (request , f'🆗 { _ ("Finished" )} ✏️({ fn } )' , messages .SUCCESS )
833+ return FileResponse (output , as_attachment = True , filename = fn )
834+ self .message_user (request , _ ('please select items' ), messages .ERROR )
835+ price_to_xls .short_description = f'⚔{ _ ("unload price to XLS file" )} ↘️'
705836
706837admin .site .register (Product , ProductAdmin )
0 commit comments