Skip to content

Commit 5f38034

Browse files
Merge pull request #7 from usermicrodevices/develop
add admin product export
2 parents e4824b3 + eb47de1 commit 5f38034

3 files changed

Lines changed: 221 additions & 29 deletions

File tree

core/admin.py

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import logging, sys
12
from decimal import Decimal
23
from io import BytesIO, StringIO
34
from datetime import datetime, timedelta
@@ -15,7 +16,7 @@
1516
from django import forms
1617
from django.http import StreamingHttpResponse, FileResponse, HttpResponseRedirect
1718
from django.contrib.auth.forms import ReadOnlyPasswordHashField
18-
from django.db.models import F, Q, Min, Max, Value, Count, IntegerField, TextField, CharField, OuterRef, Subquery
19+
from django.db.models import F, Q, Min, Max, Sum, Value, Count, IntegerField, TextField, CharField, OuterRef, Subquery
1920
from django.db.models.query import QuerySet
2021
from django.db import connections
2122
from django.contrib.admin.models import LogEntry
@@ -65,7 +66,7 @@ def loge(self, err, *args):
6566

6667

6768
class DocAdmin(CustomModelAdmin):
68-
list_display = ('id', 'created_at', 'registered_at', 'owner', 'contractor', 'type', 'tax', 'author', 'get_records', 'extinfo')
69+
list_display = ('id', 'created_at', 'registered_at', 'get_records', 'get_sum_cost', 'get_sum_price', 'owner', 'contractor', 'type', 'tax', 'author', 'extinfo')
6970
list_display_links = ('id', 'created_at', 'registered_at')
7071
search_fields = ('id', 'created_at', 'registered_at', 'owner__name', 'contractor__name', 'type__name', 'tax__name', 'sale_point__name', 'author__username', 'extinfo')
7172

@@ -82,6 +83,7 @@ def get_records(self, obj):
8283
try:
8384
idxs = Record.objects.filter(doc=obj).annotate(admin_path_prefix=Value(settings.ADMIN_PATH_PREFIX, CharField())).values_list('admin_path_prefix', 'product_id', 'product__name')
8485
except Exception as e:
86+
self.loge(e)
8587
return ''
8688
else:
8789
if not idxs:
@@ -90,14 +92,36 @@ def get_records(self, obj):
9092
return format_html('<details><summary>{}</summary>{}</details>', idxs[0][2], content)
9193
get_records.short_description = _('Products')
9294

95+
def get_sum_cost(self, obj):
96+
full_sum = 0
97+
try:
98+
full_sum = Record.objects.filter(doc=obj).aggregate(full_sum=Sum(F('count')*F('cost')))['full_sum'].quantize(Decimal('0.00'))
99+
except Exception as e:
100+
self.loge(e)
101+
return format_html('<font color="green" face="Verdana, Geneva, sans-serif">{}</font>', full_sum)
102+
get_sum_cost.short_description = _('sum cost')
103+
104+
def get_sum_price(self, obj):
105+
full_sum = 0
106+
try:
107+
full_sum = Record.objects.filter(doc=obj).aggregate(full_sum=Sum(F('count')*F('price')))['full_sum'].quantize(Decimal('0.00'))
108+
except Exception as e:
109+
self.loge(e)
110+
return format_html('<font color="green" face="Verdana, Geneva, sans-serif">{}</font>', full_sum)
111+
get_sum_price.short_description = _('sum price')
112+
93113
admin.site.register(Doc, DocAdmin)
94114

95115

96116
class RecordAdmin(CustomModelAdmin):
97-
list_display = ('id', 'product', 'get_count', 'get_cost', 'get_price', 'doc', 'extinfo')
117+
list_display = ('id', 'get_product', 'get_cost', 'get_price', 'get_count', 'get_sum_cost', 'get_sum_price', 'doc', 'extinfo')
98118
list_display_links = ('id',)
99119
search_fields = ('id', 'doc__owner__name', 'doc__contractor__name', 'doc__type__name', 'doc__tax__name', 'doc__sale_point__name', 'doc__author__username', 'extinfo')
100120

121+
def get_product(self, obj):
122+
return format_html('<a href="{}/refs/product/?id={}" target="_blank">{}</a>', settings.ADMIN_PATH_PREFIX, obj.product.id, obj.product.name)
123+
get_product.short_description = _('product')
124+
101125
def get_count(self, obj):
102126
color = 'green' if obj.doc.type.income==True else 'red'
103127
return format_html('<font color="{}" face="Verdana, Geneva, sans-serif">{}</font>', color, obj.count)
@@ -114,17 +138,25 @@ def get_price(self, obj):
114138
get_price.short_description = _('price')
115139
get_price.admin_order_field = 'price'
116140

141+
def get_sum_cost(self, obj):
142+
return format_html('<font color="green" face="Verdana, Geneva, sans-serif">{} {}</font>', (obj.cost*obj.count).quantize(Decimal('0.00')), obj.currency.name if obj.currency else '')
143+
get_sum_cost.short_description = _('sum price')
144+
145+
def get_sum_price(self, obj):
146+
return format_html('<font color="green" face="Verdana, Geneva, sans-serif">{} {}</font>', (obj.price*obj.count).quantize(Decimal('0.00')), obj.currency.name if obj.currency else '')
147+
get_sum_price.short_description = _('sum price')
148+
117149
admin.site.register(Record, RecordAdmin)
118150

119151

120152
class RegisterAdmin(CustomModelAdmin):
121-
list_display = ('id', 'rec', 'get_product', 'get_count', 'get_cost', 'get_price', 'get_doc')
153+
list_display = ('id', 'rec', 'get_product', 'get_cost', 'get_price', 'get_count', 'get_sum_cost', 'get_sum_price', 'get_doc')
122154
list_display_links = ('id',)
123155
search_fields = ('id', 'rec__doc__owner__name', 'rec__doc__contractor__name', 'rec__doc__type__name')
124156
list_filter = ('rec__product', 'rec__doc', 'rec__doc__type')
125157

126158
def get_product(self, obj):
127-
return format_html('<font color="green" face="Verdana, Geneva, sans-serif">{}</font>', obj.rec.product)
159+
return format_html('<a href="{}/refs/product/?id={}" target="_blank">{}</a>', settings.ADMIN_PATH_PREFIX, obj.rec.product.id, obj.rec.product.name)
128160
get_product.short_description = _('product')
129161

130162
def get_count(self, obj):
@@ -144,4 +176,12 @@ def get_doc(self, obj):
144176
return format_html('<font color="green" face="Verdana, Geneva, sans-serif">{}</font>', obj.rec.doc)
145177
get_doc.short_description = _('document')
146178

179+
def get_sum_cost(self, obj):
180+
return format_html('<font color="green" face="Verdana, Geneva, sans-serif">{} {}</font>', (obj.rec.cost*obj.rec.count).quantize(Decimal('0.00')), obj.rec.currency.name if obj.rec.currency else '')
181+
get_sum_cost.short_description = _('sum cost')
182+
183+
def get_sum_price(self, obj):
184+
return format_html('<font color="green" face="Verdana, Geneva, sans-serif">{} {}</font>', (obj.rec.price*obj.rec.count).quantize(Decimal('0.00')), obj.rec.currency.name if obj.rec.currency else '')
185+
get_sum_price.short_description = _('sum price')
186+
147187
admin.site.register(Register, RegisterAdmin)

locale/ru_RU/LC_MESSAGES/django.po

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,11 +125,14 @@ msgstr "Город"
125125
msgid "Cities"
126126
msgstr "Города"
127127

128+
msgid "unit"
129+
msgstr "единица"
130+
128131
msgid "Unit"
129-
msgstr "Еденица"
132+
msgstr "Единица"
130133

131134
msgid "Units"
132-
msgstr "Еденицы"
135+
msgstr "Единицы"
133136

134137
msgid "Currency"
135138
msgstr "Валюта"
@@ -493,3 +496,21 @@ msgstr "Пользователь"
493496

494497
msgid "Users"
495498
msgstr "Пользователи"
499+
500+
msgid "load from XLS file"
501+
msgstr "загрузить из файла XLS"
502+
503+
msgid "export to XLS file"
504+
msgstr "экспорт в файл XLS"
505+
506+
msgid "unload price to XLS file"
507+
msgstr "выгрузить прайс в файл XLS"
508+
509+
msgid "sum"
510+
msgstr "сумма"
511+
512+
msgid "sum cost"
513+
msgstr "сумма закупки"
514+
515+
msgid "sum price"
516+
msgstr "сумма продажи"

refs/admin.py

Lines changed: 153 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -487,41 +487,130 @@ class ProductGroupAdmin(CustomModelAdmin):
487487

488488

489489
class 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

706837
admin.site.register(Product, ProductAdmin)

0 commit comments

Comments
 (0)