# -*- 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