You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

710 lines
25 KiB

# -*- coding: utf-8 -*-
from StringIO import StringIO
from django import forms
from django.shortcuts import render, redirect
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_protect
from django.contrib.admin.views.decorators import staff_member_required
from django.contrib.auth.decorators import permission_required
from django.utils.encoding import force_text
from django.utils import timezone
from django.db import connection, transaction
from django.conf import settings
import xlrd
import xlsxwriter
from urlify import urlify
from .models import OblOtdelen, NasPunkt, NasPunktOtdelen, OtdelType, Otdel
from .forms import AdminBaseImportForm
DEBUG_IMPORT = getattr(settings, 'DEBUG_PENSFONDS_IMPORT', False)
ALLOW_EMPTY_NASPUNKTS = False # разрешить пустые населенные пункты при импорте отделений
def _getval_str(row, cx, multiline=False):
"""Взять значение из колонки внутри переданной строки.
Если значение прочитано из экселя как float,
то сначала переводит его в int - чтобы отбросить дробную часть.
Если задан multiline, то преобразует символы # в \n.
Всегда возвращает строку, начальные и хвостовые пробелы отрезаются.
"""
val = row[cx].value
if isinstance(val, float):
val = int(val)
if multiline:
return force_text(u'%s' % val).replace('#','\n').replace('\r','').strip()
else:
return force_text((u'%s' % val).strip())
def _getval_int(row, cx):
"""Взять целое значение из колонки внутри переданной строки.
Возвращает число или выбрасывает ошибку конвертации.
"""
val = row[cx].value
return int(val)
def _prep_str(s):
"""Заменяет в переданной строке \r\n на \n.
Преобразует \n в символ #.
Начальные и хвостовые пробелы отрезаются.
"""
return s.replace('\r\n','\n').replace('\n','#').strip()
@csrf_protect
@staff_member_required
@permission_required('can_import_export_oblotdelen', raise_exception=True)
def import_obl_otdelen(request):
"""Импорт центральных отделений."""
import_errors = []
xls_file = None
wrong_file = None
start_nrow = 1 # с какой строки начинать импорт (отсчёт с нуля)
form_class = AdminBaseImportForm
if request.method == 'GET':
form = form_class()
else:
form = form_class(request.POST, request.FILES)
if request.method == 'POST' and form.is_valid():
xls_file = request.FILES['xls_file']
xls_to_import = xls_file.read()
book = xlrd.open_workbook(file_contents=xls_to_import)
sh = book.sheet_by_index(0)
rx = 0
for rx in xrange(start_nrow, sh.nrows):
row = sh.row(rx)
try:
oblast = _getval_str(row, 0)
if not oblast:
raise Exception(u'Не указана `область` в строке номер %d!' % (rx+1))
otdelen, created = OblOtdelen.objects.get_or_create(
oblast__iexact=oblast)
otdelen.oblast = oblast
otdelen.oblast_short = _getval_str(row, 1)
otdelen.po_regionu = _getval_str(row, 2)
otdelen.po_regionu_short = _getval_str(row, 3)
otdelen.regiona = _getval_str(row, 4)
otdelen.regiona_short = _getval_str(row, 5)
otdelen.regionalnoe = _getval_str(row, 6)
otdelen.otdely = _getval_str(row, 7)
otdelen.addr = _getval_str(row, 8)
#
otdelen.tel_hotline = _getval_str(row, 9, multiline=True) # multiline
otdelen.priemnaya = _getval_str(row, 10)
otdelen.fax = _getval_str(row, 11)
otdelen.smi = _getval_str(row, 12)
otdelen.head = _getval_str(row, 13)
otdelen.min_pensia = row[14].value or None
#
otdelen.doplata = row[15].value or None
## make slug
otdelen.slug = urlify(otdelen.oblast)
# now make sure slug is unique
_suffix = 2
while OblOtdelen.objects.filter(slug=otdelen.slug).exclude(pk=otdelen.pk).exists():
if _suffix > 10:
raise Exception(u'Too many iterations! Row %d.' % (rx+1))
otdelen.slug = "%s-%d" % (otdelen.slug, _suffix)
_suffix += 1
##
otdelen.save()
except:
if DEBUG_IMPORT:
raise
else:
import_errors.append(rx + 1)
if rx + 1 > len(import_errors):
wrong_file = False
else:
wrong_file = True
return render(request, 'admin/pensfonds/import_obl_otdelen.html',
{
'form': form,
'import_errors': import_errors,
'xls': xls_file,
'wrong_file': wrong_file,
},
)
@csrf_protect
@staff_member_required
@permission_required('can_import_export_naspunkts', raise_exception=True)
def import_naspunkts(request):
"""Импорт населённых пунктов."""
import_errors = []
xls_file = None
wrong_file = None
start_nrow = 1 # с какой строки начинать импорт (отсчёт с нуля)
form_class = AdminBaseImportForm
if request.method == 'GET':
form = form_class()
else:
form = form_class(request.POST, request.FILES)
if request.method == 'POST' and form.is_valid():
xls_file = request.FILES['xls_file']
xls_to_import = xls_file.read()
book = xlrd.open_workbook(file_contents=xls_to_import)
sh = book.sheet_by_index(0)
# закешировать центральные отделения в словарь. ключ название области
obl_otdelens_cache = {_obl.oblast: _obl for _obl in OblOtdelen.objects.all()}
# закешировать населенные пункты в словарь. ключ tuple(naspunkt.obl_otdelen.pk, naspunkt.name)
naspunkts_cache = {(_naspunkt.obl_otdelen.pk, _naspunkt.name): _naspunkt
for _naspunkt in NasPunkt.objects.all().select_related()}
# какие нас. пункты уже добавили: уникальность по tuple(obl_otdelen.pk, naspunkt.name)
added_otdelens = set()
# список нас.пунктов, которые нужно добававить в базу (для bulk_create)
create_list = []
# список нас.пунктов, которые нужно изменить в базе (для bulk_update)
update_list = []
update_sql = '''UPDATE ''' + NasPunkt._meta.db_table + '''
SET
obl_otdelen_id = %s,
name = %s,
gde = %s,
chego = %s,
chemu = %s,
kakoy = %s,
kakoe = %s,
kakie = %s,
people_count = %s,
slug = %s,
updated_at = %s
WHERE id = %s;
'''
rx = 0
for rx in xrange(start_nrow, sh.nrows):
row = sh.row(rx)
try:
oblast = _getval_str(row, 0)
if not oblast:
raise Exception(u'Не указана `область` в строке номер %d!' % (rx+1))
try:
obl_otdelen = obl_otdelens_cache[oblast]
except KeyError:
raise Exception(u'Не найдено `центральное отделение` в строке номер %d!' % (rx+1))
#
naspunkt_name = _getval_str(row, 1)
if not naspunkt_name:
raise Exception(u'Не указан `населенный пункт` в строке номер %d!' % (rx+1))
#
_curr_otdelen_key = (obl_otdelen.pk, naspunkt_name)
# пропускать повторы
if _curr_otdelen_key in added_otdelens:
raise Exception(u'Повтор населенного пункта в строке номер %d!' % (rx+1))
added_otdelens.add(_curr_otdelen_key)
# изменить существующий объект или создать новый
if _curr_otdelen_key in naspunkts_cache:
otdelen = naspunkts_cache[_curr_otdelen_key]
else:
otdelen = NasPunkt()
otdelen.obl_otdelen = obl_otdelen
otdelen.name = naspunkt_name
otdelen.gde = _getval_str(row, 2)
otdelen.chego = _getval_str(row, 3)
otdelen.chemu = _getval_str(row, 4)
otdelen.kakoy = _getval_str(row, 5)
otdelen.kakoe = _getval_str(row, 6)
otdelen.kakie = _getval_str(row, 7)
otdelen.people_count = _getval_int(row, 8)
otdelen.slug = urlify(otdelen.name)
#
if _curr_otdelen_key in naspunkts_cache:
update_list.append(otdelen)
else:
create_list.append(otdelen)
except:
if DEBUG_IMPORT:
raise
else:
import_errors.append(rx + 1)
if create_list:
NasPunkt.objects.bulk_create(create_list)
if update_list:
_params = []
for otdelen in update_list:
_params.append((
otdelen.obl_otdelen.pk,
otdelen.name,
otdelen.gde,
otdelen.chego,
otdelen.chemu,
otdelen.kakoy,
otdelen.kakoe,
otdelen.kakie,
otdelen.people_count,
otdelen.slug,
timezone.now(), # updated_at
otdelen.pk, # record id for where clause
))
cursor = connection.cursor()
cursor.executemany(update_sql, _params)
transaction.commit_unless_managed()
if rx + 1 > len(import_errors):
wrong_file = False
else:
wrong_file = True
return render(request, 'admin/pensfonds/import_naspunkts.html',
{
'form': form,
'import_errors': import_errors,
'xls': xls_file,
'wrong_file': wrong_file,
},
)
@csrf_protect
@staff_member_required
@permission_required('can_import_export_naspunktotdelen', raise_exception=True)
def import_naspunkt_otdelen(request):
"""Импорт отделений в нас. пунктах."""
import_errors = []
xls_file = None
wrong_file = None
start_nrow = 1 # с какой строки начинать импорт (отсчёт с нуля)
form_class = AdminBaseImportForm
if request.method == 'GET':
form = form_class()
else:
form = form_class(request.POST, request.FILES)
if request.method == 'POST' and form.is_valid():
xls_file = request.FILES['xls_file']
xls_to_import = xls_file.read()
book = xlrd.open_workbook(file_contents=xls_to_import)
types_sh = book.sheet_by_index(1)
sh = book.sheet_by_index(0)
# закешировать центральные отделения в словарь. ключ название области
obl_otdelens_cache = {_obl.oblast: _obl for _obl in OblOtdelen.objects.all()}
# закешировать населенные пункты в словарь. ключ tuple(naspunkt.obl_otdelen.pk, naspunkt.name)
naspunkts_cache = {(_naspunkt.obl_otdelen.pk, _naspunkt.name): _naspunkt
for _naspunkt in NasPunkt.objects.all().select_related()}
# какие отделения в нас. пунктах уже добавили: уникальность по tuple(naspunkt.pk, obj)
added_otdelens = set()
# список объектов отделений, которые нужно сохранить в базу (для bulk_create)
objects_list = []
# в файле есть данные для импорта?
if sh.nrows > start_nrow:
NasPunktOtdelen.objects.all().delete() # очистить таблицу нас. пунктов
rx = 0
for rx in xrange(start_nrow, sh.nrows):
row = sh.row(rx)
try:
order = _getval_str(row, 0)
if not order:
continue # информацию по отделам заполняем в другом цикле
oblast = _getval_str(row, 1)
if not oblast:
raise Exception(u'Не указана `область` в строке номер %d!' % (rx+1))
try:
obl_otdelen = obl_otdelens_cache[oblast]
except KeyError:
raise Exception(u'Не найдено `центральное отделение` в строке номер %d!' % (rx+1))
#
naspunkt_name = _getval_str(row, 2)
if not naspunkt_name and not ALLOW_EMPTY_NASPUNKTS:
raise Exception(u'Не указан `населенный пункт` в строке номер %d!' % (rx+1))
try:
naspunkt = naspunkts_cache[(obl_otdelen.pk, naspunkt_name)]
except KeyError:
if not ALLOW_EMPTY_NASPUNKTS:
raise Exception(u'Не найден `населенный пункт` в строке номер %d!' % (rx+1))
else:
naspunkt = None
#
obj = _getval_str(row, 3)
if not obj:
raise Exception(u'Не указан `объект` в строке номер %d!' % (rx+1))
#
if naspunkt:
_curr_otdelen_key = (naspunkt.pk, obj)
if _curr_otdelen_key in added_otdelens:
raise Exception(u'Повтор отделения в строке номер %d!' % (rx+1))
added_otdelens.add((naspunkt.pk, obj))
#
otdelen = NasPunktOtdelen()
#
otdelen.obl_otdelen = obl_otdelen
otdelen.naspunkt = naspunkt
otdelen.obj = obj
#
otdelen.addr = _getval_str(row, 4)
#
otdelen.tel_hotline = _getval_str(row, 5, multiline=True) # multiline
otdelen.tel_priemn = _getval_str(row, 6, multiline=True) # multiline
otdelen.tel_info = _getval_str(row, 7, multiline=True) # multiline
#
otdelen.ordering = order
objects_list.append(otdelen)
except:
if DEBUG_IMPORT:
raise
else:
import_errors.append(rx + 1)
if objects_list:
NasPunktOtdelen.objects.bulk_create(objects_list)
order = None
OtdelType.objects.all().delete()
Otdel.objects.all().delete()
otdeltype_list = []
for rx in xrange(0, types_sh.nrows):
row = types_sh.row(rx)
otdel_type = OtdelType(name=_getval_str(row, 1))
otdeltype_list.append(otdel_type)
OtdelType.objects.bulk_create(otdeltype_list)
otdel_types = {i.name: i.id for i in OtdelType.objects.all()}
otdel_list = []
last_item = None
for rx in xrange(start_nrow, sh.nrows):
row = sh.row(rx)
try:
order = _getval_str(row, 0)
if order:
last_item = NasPunktOtdelen.objects.get(ordering=order).id
otdel = Otdel()
otdel.nas_punkt_otdelen_id = last_item
otdel.otdel_type_id = otdel_types[_getval_str(row, 8)]
otdel.phones = _getval_str(row, 9, multiline=True)
otdel.schedule = {
u'Пн': [_getval_str(row, 10),
_getval_str(row, 11)],
u'Вт': [_getval_str(row, 12),
_getval_str(row, 13)],
u'Ср': [_getval_str(row, 14),
_getval_str(row, 15)],
u'Чт': [_getval_str(row, 16),
_getval_str(row, 17)],
u'Пт': [_getval_str(row, 18),
_getval_str(row, 19)],
}
otdel.text_block = _getval_str(row, 20, multiline=True)
otdel_list.append(otdel)
except Exception as e:
pass
Otdel.objects.bulk_create(otdel_list)
if rx + 1 > len(import_errors):
wrong_file = False
else:
wrong_file = True
return render(request, 'admin/pensfonds/import_naspunkt_otdelen.html',
{
'form': form,
'import_errors': import_errors,
'xls': xls_file,
'wrong_file': wrong_file,
},
)
@staff_member_required
@permission_required('can_import_export_oblotdelen', raise_exception=True)
def export_obl_otdelen(request):
"""Экспорт центральных отделений."""
buf = StringIO()
book = xlsxwriter.Workbook(buf, {'constant_memory': True})
sh = book.add_worksheet()
sh.set_column('A:T', 30)
format_caption = book.add_format()
format_caption.set_bg_color('yellow')
format_caption.set_border(1)
sh.set_row(0, None, format_caption)
sh.write(0, 0, u'Регион')
sh.write(0, 1, u'Регион - коротко')
sh.write(0, 2, u'По региону')
sh.write(0, 3, u'По региону коротко')
sh.write(0, 4, u'Региона')
sh.write(0, 5, u'Региона коротко')
sh.write(0, 6, u'Региональное')
sh.write(0, 7, u'Отделы и управления')
sh.write(0, 8, u'Адрес')
sh.write(0, 9, u'Горячая линия')
sh.write(0, 10, u'Приемная')
sh.write(0, 11, u'Факс')
sh.write(0, 12, u'Работа со СМИ')
sh.write(0, 13, u'Руководитель отделения')
sh.write(0, 14, u'Мин. пенсия')
sh.write(0, 15, u'Размер соцдоплаты')
for rx, otd in enumerate(OblOtdelen.objects.all(), start=1):
sh.write(rx, 0, otd.oblast)
#
sh.write(rx, 1, otd.oblast_short)
sh.write(rx, 2, otd.po_regionu)
sh.write(rx, 3, otd.po_regionu_short)
sh.write(rx, 4, otd.regiona)
sh.write(rx, 5, otd.regiona_short)
sh.write(rx, 6, otd.regionalnoe)
#
sh.write(rx, 7, otd.otdely)
#
sh.write(rx, 8, otd.addr) # multiline
sh.write(rx, 9, _prep_str(otd.tel_hotline)) # multiline
sh.write(rx, 10, _prep_str(otd.priemnaya)) # multiline
sh.write(rx, 11, _prep_str(otd.fax)) # multiline
sh.write(rx, 12, _prep_str(otd.smi)) # multiline
#
sh.write(rx, 13, otd.head)
#
sh.write(rx, 14, otd.min_pensia)
sh.write(rx, 15, otd.doplata)
book.close()
content = buf.getvalue()
buf.close()
#
filename = u'Центральные_отделения'
response = HttpResponse(content, mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
response['Content-Disposition'] = 'attachment; filename=%s.xlsx' % filename.encode('windows-1251')
return response
@staff_member_required
@permission_required('can_import_export_naspunkts', raise_exception=True)
def export_naspunkts(request):
"""Экспорт населённых пунктов."""
buf = StringIO()
book = xlsxwriter.Workbook(buf, {'constant_memory': True})
sh = book.add_worksheet()
sh.set_column('A:J', 30)
format_caption = book.add_format()
format_caption.set_bg_color('yellow')
format_caption.set_border(1)
sh.set_row(0, None, format_caption)
sh.write(0, 0, u'Регион')
sh.write(0, 1, u'населенный пункт')
sh.write(0, 2, u'где')
sh.write(0, 3, u'чего')
sh.write(0, 4, u'чему')
sh.write(0, 5, u'какой')
sh.write(0, 6, u'какое')
sh.write(0, 7, u'какие')
sh.write(0, 8, u'пенсионеры')
for rx, otd in enumerate(NasPunkt.objects.all().select_related(), start=1):
sh.write(rx, 0, otd.obl_otdelen.oblast)
#
sh.write(rx, 1, otd.name)
sh.write(rx, 2, otd.gde)
sh.write(rx, 3, otd.chego)
sh.write(rx, 4, otd.chemu)
sh.write(rx, 5, otd.kakoy)
sh.write(rx, 6, otd.kakoe)
sh.write(rx, 7, otd.kakie)
#
sh.write(rx, 8, otd.people_count)
book.close()
content = buf.getvalue()
buf.close()
#
filename = u'Населенные_пункты'
response = HttpResponse(content, mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
response['Content-Disposition'] = 'attachment; filename=%s.xlsx' % filename.encode('windows-1251')
return response
@staff_member_required
@permission_required('can_import_export_naspunktotdelen', raise_exception=True)
def export_naspunkt_otdelen(request):
"""Экспорт всех отделений в нас. пунктах."""
buf = StringIO()
book = xlsxwriter.Workbook(buf, {'constant_memory': True})
sh = book.add_worksheet()
sh.set_column('A:O', 30)
format_caption = book.add_format()
format_caption.set_bg_color('yellow')
format_caption.set_border(1)
sh.set_row(0, None, format_caption)
sh.write(0, 0, u'№ объекта')
sh.write(0, 1, u'Область')
sh.write(0, 2, u'Населенный пункт')
sh.write(0, 3, u'Объект')
sh.write(0, 4, u'Адрес')
sh.write(0, 5, u'телефон горячей линии')
sh.write(0, 6, u'телефон приемной')
sh.write(0, 7, u'доп. телефоны')
sh.write(0, 8, u'Отдел')
sh.write(0, 9, u'телефоны')
sh.write(0, 10, u'Пн.')
sh.write(0, 11, u'Пн. обед')
sh.write(0, 12, u'Вт.')
sh.write(0, 13, u'Вт. обед')
sh.write(0, 14, u'Ср.')
sh.write(0, 15, u'Ср. обед')
sh.write(0, 16, u'Чт.')
sh.write(0, 17, u'Чт. обед')
sh.write(0, 18, u'Пт.')
sh.write(0, 19, u'Пт. обед')
sh.write(0, 20, u'текст в блок')
rx = 1
for otd in NasPunktOtdelen.objects.all().order_by('ordering').select_related():
sh.write(rx, 0, otd.ordering)
sh.write(rx, 1, otd.obl_otdelen.oblast)
#
if otd.naspunkt:
sh.write(rx, 2, otd.naspunkt.name)
#
sh.write(rx, 3, otd.obj)
#
sh.write(rx, 4, otd.addr)
sh.write(rx, 5, _prep_str(otd.tel_hotline)) # multiline
sh.write(rx, 6, _prep_str(otd.tel_priemn)) # multiline
sh.write(rx, 7, _prep_str(otd.tel_info)) # multiline
otdels = otd.otdels.all().select_related('otdel_type').order_by()
for otdel in otdels:
sh.write(rx, 8, otdel.otdel_type.name)
sh.write(rx, 9, _prep_str(otdel.phones))
sh.write(rx, 10, otdel.schedule[u'Пн'][0])
sh.write(rx, 11, otdel.schedule[u'Пн'][1])
sh.write(rx, 12, otdel.schedule[u'Вт'][0])
sh.write(rx, 13, otdel.schedule[u'Вт'][1])
sh.write(rx, 14, otdel.schedule[u'Ср'][0])
sh.write(rx, 15, otdel.schedule[u'Ср'][1])
sh.write(rx, 16, otdel.schedule[u'Чт'][0])
sh.write(rx, 17, otdel.schedule[u'Чт'][1])
sh.write(rx, 18, otdel.schedule[u'Пт'][0])
sh.write(rx, 19, otdel.schedule[u'Пт'][1])
sh.write(rx, 20, _prep_str(otdel.text_block))
rx += 1
if not otdels:
rx += 1
# справочник отделов
types_sh = book.add_worksheet()
types_sh.set_column('B:B', 60)
for rx, otdel_type in enumerate(OtdelType.objects.all()):
types_sh.write(rx, 1, otdel_type.name)
book.close()
content = buf.getvalue()
buf.close()
#
filename = u'Отделения_в_населенных_пунктах'
response = HttpResponse(content, mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
response['Content-Disposition'] = 'attachment; filename=%s.xlsx' % filename.encode('windows-1251')
return response