# -*- coding: utf-8 -*- import os import glob from datetime import datetime import tempfile from email.header import Header from time import time import simplejson as json import sys import subprocess as s from django.contrib import messages from django.db import DatabaseError from django.shortcuts import render, get_object_or_404, redirect from django.http import HttpResponseBadRequest, HttpResponse from django.utils.decorators import method_decorator from django.views.decorators.http import require_POST from django.views.decorators.csrf import csrf_protect from django.db.models import Sum from django.forms.models import inlineformset_factory, model_to_dict from django.template.loader import render_to_string from django.core.mail import EmailMessage from django.utils.encoding import smart_str from commons.utils import dthandler from commons.paginator import pagination, save_per_page_value from commons.pdf_tools import render_pdf_to_string, pdf_to_response from commons.xls import xls_to_response from customer.models import get_profile, BankAccount from customer.forms import ClientsListForm, ClientForm from docs.as_xls import render_xls_to_string from docs.forms import EmailForm, InvoicesListForm from customer.decorators import license_required from customer.utils import raise_if_no_profile from django.conf import settings import traceback DEBUG = getattr(settings, 'DEBUG', False) SUPPORT_EMAIL = getattr(settings, 'SUPPORT_EMAIL', '') MEDIA_ROOT = getattr(settings, 'MEDIA_ROOT', '') MEDIA_URL = getattr(settings, 'MEDIA_URL', '') ORDER_VAR = 'o' ORDER_TYPE_VAR = 'ot' class Ordering(object): """Параметры сортировки. Для передачи в шаблон.""" order_var = ORDER_VAR order_type_var = ORDER_TYPE_VAR def __init__(self, order_field, order_type): self.order_field = order_field self.order_type = order_type class BaseViews(object): """Базовые views для простых документов (без табличной части).""" # модель документа MODEL = None # форма документа FORM_CLASS = None # форма создания и отправки email EMAIL_FORM_CLASS = EmailForm # поля, по которым можно сортировать список документов ORDER_FIELDS = ('doc_date', 'doc_num', 'client__name', 'doc_sum',) # фильтры FILTERSET_CLASS = None # префикс именованных урлов документов данного типа, для передачи в шаблон URL_PREFIX = '' # именованные урлы операций URL_LIST = '' URL_EDIT = '' # пути к шаблонам TEMPLATE_LIST = 'docs/_base/base_list.html' TEMPLATE_ADD = 'docs/_base/base_add.html' TEMPLATE_EDIT = 'docs/_base/base_edit.html' TEMPLATE_DELETE = 'docs/_base/base_delete.html' TEMPLATE_FORM = 'docs/_base/base_form.html' TEMPLATE_IMG = 'docs/_base/preview.html' TEMPLATE_FORM_JS = 'docs/stub_js.html' TEMPLATE_CREATE_DOCS = '' TEMPLATE_LINKED_DOCS_LIST = '' TEMPLATE_EMAIL = 'docs/email/base_email.html' TEMPLATE_EMAIL_FORM = 'docs/email/base_email_form.html' # шаблон письма EMAIL_MSG_TEMPLATE = 'docs/email/email.txt' # для генерации pdf/xls PDF_TEMPLATE = '' XLS_TEMPLATE = '' FILENAME = u'Документ № %s, %s' # без расширения MAYBE_SIGNED = False # --- константы для вывода наименований в шаблонах PADEJI = { 'imenit': u'документ', # кто? что? 'rodit': u'документа', # кого? чего? 'dateln': u'документу', # кому? чему? 'vinit': u'документ', # кого? что? 'tvorit': u'документом', # кем? чем? 'predlojn': u'документе', # о ком? о чём? } PADEJI_MNOJ = { 'imenit': u'документы', # кто? что? 'rodit': u'документов', # кого? чего? 'dateln': u'документам', # кому? чему? 'vinit': u'документы', # кого? что? 'tvorit': u'документами', # кем? чем? 'predlojn': u'документах', # о ком? о чём? } def __init__(self, request): self.request = request self.user = request.user self.profile = request.profile self.set_redirects() self.MODEL_NAME = self.MODEL.__name__.lower() self.asserts() def asserts(self): """Проверить объект класса на типичные ошибки.""" assert self.request is not None, \ (u"%s.request can't be None!" % self.__class__.__name__) assert self.MODEL is not None, \ (u"%s.MODEL can't be None!" % self.__class__.__name__) assert self.FORM_CLASS is not None, \ (u"%s.FORM_CLASS can't be None!" % self.__class__.__name__) assert self.EMAIL_FORM_CLASS is not None, \ (u"%s.EMAIL_FORM_CLASS can't be None!" % self.__class__.__name__) assert (isinstance(self.ORDER_FIELDS, tuple) or isinstance(self.ORDER_FIELDS, list)), \ (u"%s.ORDER_FIELDS should be of tuple or list type!" % self.__class__.__name__) assert self.FILTERSET_CLASS is not None, \ (u"%s.FILTERSET_CLASS can't be None!" % self.__class__.__name__) def set_redirects(self): """Куда редиректить после операции.""" self.REDIRECT_AFTER_ADD = self.URL_LIST self.REDIRECT_AFTER_EDIT = self.URL_LIST self.REDIRECT_AFTER_COPY = self.URL_EDIT self.REDIRECT_AFTER_DELETE = self.URL_LIST self.REDIRECT_AFTER_EMAIL = self.URL_LIST def get_ordering(self): """Поле и порядок сортировки.""" order_field, order_type = 'doc_num', 'desc' # default params = dict(self.request.GET.items()) if params.get(ORDER_VAR) in self.ORDER_FIELDS: order_field = params.get(ORDER_VAR) if params.get(ORDER_TYPE_VAR) in ('asc', 'desc'): order_type = params.get(ORDER_TYPE_VAR) return order_field, order_type def get_list_qs(self): """QuerySet для просмотра списка документов.""" qs = self.MODEL.objects.filter(user=self.request.user) qs = qs.select_related('client') # задать сортировку order_field, order_type = self.get_ordering() if order_field: qs = qs.order_by('%s%s' % ((order_type == 'desc' and '-' or ''), order_field,)) return qs def get_filters(self, qs): """Возвращает объект с набором фильтров.""" return self.FILTERSET_CLASS(self.request, data=self.request.GET, queryset=qs) def get_obj(self, id, only_form_fields=False): """Объект документа или ошибка 404, если его нет в базе. Поведение когда флаг only_form_fields=True: если в форме редактирования документа задан атрибут Meta.fields, то запрашивает только поля, перечисленные в нём. Иначе (как и по умолчанию) дампит вообще все поля, которые есть в модели. """ if only_form_fields: try: fields_list = self.FORM_CLASS.Meta.fields except AttributeError: fields_list = [] return get_object_or_404(self.MODEL.objects.values(*fields_list), pk=id, user=self.request.user) else: return get_object_or_404(self.MODEL, pk=id, user=self.request.user) def get_filename(self, *args, **kwargs): obj = self.get_obj(kwargs['id']) client = obj.client.name_short_dadata.replace('\n', ' ').replace('\r', ' ').strip() return self.FILENAME % (obj.doc_num, client,) def update_list_dict(self, dictionary): """ Здесь можно изменить словарь параметров перед передачей его в шаблон вывода списка документов. """ dictionary['clients_form'] = ClientsListForm(self.request.user) dictionary['invoices_form'] = InvoicesListForm(self.request.user) @method_decorator(csrf_protect) @method_decorator(save_per_page_value) def list(self, *args, **kwargs): """Список документов.""" obj_list = self.get_list_qs() # фильтрация списка filters = self.get_filters(obj_list) # сколько записей было в списке до его фильтрации obj_list_count_before_filtering = 0 if not filters.qs: obj_list_count_before_filtering = obj_list.count() obj_list = filters.qs # пагинация списка page_num = kwargs.get('page_num') page, pagination_form = pagination(self.request, obj_list, page_num) # параметры сортировки для отрисовки в шаблоне # реальная сортировка QuerySet производится в методе get_list_qs order_field, order_type = self.get_ordering() ordering = Ordering(order_field, order_type) email_form = self.EMAIL_FORM_CLASS() dictionary = { 'padeji': self.PADEJI, 'padeji_mnoj': self.PADEJI_MNOJ, 'url_prefix': self.URL_PREFIX, 'model_name': self.MODEL_NAME, 'page': page, 'pagination_form': pagination_form, 'ordering': ordering, 'filters': filters, 'obj_list_count_before_filtering': obj_list_count_before_filtering, 'email_form': email_form, 'template_create_docs': self.TEMPLATE_CREATE_DOCS, 'template_linked_docs_list': self.TEMPLATE_LINKED_DOCS_LIST, 'maybe_sign': self.MAYBE_SIGNED, } self.update_list_dict(dictionary) return render(self.request, self.TEMPLATE_LIST, dictionary) def init_form(self): """Начальные значения полей формы документа.""" initial = {'doc_date': datetime.now(), } # номер нового документа doc_num = self.MODEL.objects.get_max_doc_num(self.request.user) or 0 initial['doc_num'] = doc_num + 1 return initial @method_decorator(license_required) @method_decorator(csrf_protect) def add(self, *args, **kwargs): """ Добавить документ. Если при GET-запросе в kwargs передать initial, то создаст предзаполненный документ. """ raise_if_no_profile(self.request) if self.request.method == 'POST' and '_cancel' in self.request.POST: return redirect(self.REDIRECT_AFTER_ADD) if self.request.method == 'POST': form = self.FORM_CLASS(self.request.user, data=self.request.POST) if form.is_valid(): new_obj = form.save(commit=False) new_obj.user = self.request.user new_obj.company = self.request.user.profile new_obj.save() return redirect(self.REDIRECT_AFTER_ADD) else: initial = kwargs.get('initial') or self.init_form() form = self.FORM_CLASS(self.request.user, initial=initial) dictionary = { 'padeji': self.PADEJI, 'padeji_mnoj': self.PADEJI_MNOJ, 'url_prefix': self.URL_PREFIX, 'form_template': self.TEMPLATE_FORM, 'form_template_js': self.TEMPLATE_FORM_JS, 'form': form, 'client_form': ClientForm(), } self.update_list_dict(dictionary) return render(self.request, self.TEMPLATE_ADD, dictionary) def copy(self, *args, **kwargs): """Создать полную копию документа.""" obj = self.get_obj(kwargs['id']) kwargs['initial'] = model_to_dict( obj, fields=getattr(self.FORM_CLASS.Meta, 'fields', None), exclude=getattr(self.FORM_CLASS.Meta, 'exclude', None) ) kwargs['initial'].update(self.init_form()) # обязательно убрать ключи kwargs['initial'].pop('pk', None) kwargs['initial'].pop('id', None) kwargs['initial'].pop('created_at', None) kwargs['initial'].pop('updated_at', None) return self.add(self.request, *args, **kwargs) @method_decorator(csrf_protect) def edit(self, *args, **kwargs): """Редактировать документ.""" if self.request.method == 'POST' and '_cancel' in self.request.POST: return redirect(self.REDIRECT_AFTER_EDIT) obj = self.get_obj(kwargs['id']) if self.request.method == 'POST': form = self.FORM_CLASS(self.request.user, data=self.request.POST, instance=obj) if form.is_valid(): form.save() return redirect(self.REDIRECT_AFTER_EDIT) else: form = self.FORM_CLASS(self.request.user, instance=obj) dictionary = { 'padeji': self.PADEJI, 'padeji_mnoj': self.PADEJI_MNOJ, 'url_prefix': self.URL_PREFIX, 'form_template': self.TEMPLATE_FORM, 'form_template_js': self.TEMPLATE_FORM_JS, 'obj': obj, 'form': form, 'client_form': ClientForm(), } return render(self.request, self.TEMPLATE_EDIT, dictionary) @method_decorator(csrf_protect) def delete(self, *args, **kwargs): """Удалить документ.""" success = False result = None if self.request.is_ajax(): obj = self.get_obj(kwargs['id']) del_num = obj.doc_num del_id = obj.id del_date = datetime.strftime(obj.doc_date, '%d.%m.%Y') if self.request.method == 'POST': obj.delete() success = True result = {'title': u'Удаление документа', 'msg': u'Удалён документ:
%s №%d от %s' % (self.PADEJI['imenit'], del_num, del_date), 'success': success, 'del_id': del_id, } return HttpResponse(json.dumps(result), content_type='application/json') def prepare(self, obj, export_to=None): """Изменить/подмешать дополнительные поля к документу.""" pass def get_pdf(self, *args, **kwargs): """Создать документ в PDF и вернуть как строку.""" obj = self.get_obj(kwargs['id']) profile = get_profile(obj.user) main_account = BankAccount.objects.get_main(profile) self.prepare(obj, export_to='pdf') params = { 'obj': obj, 'obj_items': None, 'profile': profile, 'main_account': main_account, 'doc_sign': kwargs.get('doc_sign', False), } c1 = time() pdf = render_pdf_to_string(self.request, self.PDF_TEMPLATE, params) if DEBUG: print('%s generation time (seconds): %s' % (self.PDF_TEMPLATE, time() - c1,)) return pdf def get_xls(self, *args, **kwargs): """Создать документ в Excel и вернуть как строку.""" obj = self.get_obj(kwargs['id']) profile = get_profile(obj.user) main_account = BankAccount.objects.get_main(profile) self.prepare(obj, export_to='xls') params = { 'obj': obj, 'obj_items': None, 'profile': profile, 'main_account': main_account, } c1 = time() xls = render_xls_to_string(self.request, self.XLS_TEMPLATE, params) if DEBUG: print('%s generation time (seconds): %s' % (self.XLS_TEMPLATE, time() - c1,)) return xls def as_img(self, *args, **kwargs): """Вывести превью документа.""" GHOSTSCRIPTCMD = 'gs' try: obj = self.get_obj(kwargs['id']) pdf = self.get_pdf(*args, **kwargs) _, filename = tempfile.mkstemp() tmp_media_dir = os.path.join(MEDIA_ROOT, 'tmp') if not os.path.exists(tmp_media_dir): os.makedirs(tmp_media_dir) tmp_dir = tempfile.mkdtemp(dir=tmp_media_dir) if not settings.DEBUG: os.chmod(tmp_dir, 755) GHOSTSCRIPTCMD = '/usr/bin/gs' tmp_dirname = os.path.split(tmp_dir)[1] f = open(filename, 'wb') f.write(pdf) f.close() args = [GHOSTSCRIPTCMD, "-q", "-dNOPAUSE", "-dBATCH", "-dSAFER", "-sDEVICE=png16m", "-r150", "-sOutputFile=%s" % os.path.join(tmp_dir, "page-%03d.png"), "-f", filename ] try: if settings.DEBUG: print("Running command:\n%s" % ' '.join(args)) p = s.Popen(args=args, stdout=s.PIPE, stderr=s.PIPE) p.wait() except OSError as e: sys.exit("{}\nError executing Ghostscript {}. " "Is it in your PATH?".format(e, GHOSTSCRIPTCMD)) except: print("Error while running Ghostscript subprocess. Traceback:") print("Traceback:\n%s" % traceback.format_exc()) os.remove(filename) filename = '%s.pdf' % self.get_filename(*args, **kwargs) imgs = glob.glob(os.path.join(tmp_dir, '*.png')) imgs = [os.path.split(i)[1] for i in imgs] imgs = ['%stmp/%s/%s' % (MEDIA_URL, tmp_dirname, i) for i in imgs] imgs.sort() email_form = self.EMAIL_FORM_CLASS() dictionary = { 'obj': obj, 'images': imgs, 'padeji': self.PADEJI, 'padeji_mnoj': self.PADEJI_MNOJ, 'url_prefix': self.URL_PREFIX, 'template_create_docs': self.TEMPLATE_CREATE_DOCS, 'template_linked_docs_list': self.TEMPLATE_LINKED_DOCS_LIST, 'email_form': email_form, 'maybe_sign': self.MAYBE_SIGNED, } return render(self.request, self.TEMPLATE_IMG, dictionary) except: raise # if DEBUG: # raise # else: # return HttpResponseServerError('Server error. Try later.') def as_pdf(self, *args, **kwargs): """Вывести документ в формате PDF в HttpResponse.""" try: pdf = self.get_pdf(*args, **kwargs) filename = '%s.pdf' % self.get_filename(*args, **kwargs) return pdf_to_response(pdf, filename) except: raise # if DEBUG: # raise # else: # return HttpResponseServerError('Server error. Try later.') def as_xls(self, *args, **kwargs): """Вывести документ в формате Excel в HttpResponse.""" try: xls = self.get_xls(*args, **kwargs) filename = '%s.xls' % self.get_filename(*args, **kwargs) return xls_to_response(xls, filename) except: raise # if DEBUG: # raise # else: # return HttpResponseServerError('Server error. Try later.') def send_email(self, subject, to, body, files, sign_doc): """Отправка письма.""" dict_context = {'body': body, 'support_email': SUPPORT_EMAIL} email_body = render_to_string(self.EMAIL_MSG_TEMPLATE, dict_context) attachments = [] for f in files: attachments.append((smart_str(Header(f['filename'], 'cp1251')), f['content'], f['mimetype'])) email = EmailMessage(subject=subject, to=(to,), body=email_body, attachments=attachments) return email.send() def _process_email_form_and_send(self, form, *args, **kwargs): """Обработка формы отправки документа и отправка email-а.""" if form.cleaned_data['save_client_email']: client = getattr(self.get_obj(kwargs['id']), 'client', None) if client: client.contact_email = form.cleaned_data['to'] # сохранить email клиента client.save() doc_format = form.cleaned_data['doc_format'] kwargs['doc_sign'] = form.cleaned_data.get('insert_sign', False) if doc_format in ('pdf', 'xls',): files = [] filename = self.get_filename(*args, **kwargs) if doc_format == 'pdf': files = [{ 'filename': '%s.%s' % (filename, doc_format,), 'content': self.get_pdf(*args, **kwargs), 'mimetype': 'application/pdf', }, ] elif doc_format == 'xls': files = [{ 'filename': '%s.%s' % (filename, doc_format,), 'content': self.get_xls(*args, **kwargs), 'mimetype': 'application/ms-excel', }, ] return self.send_email( subject=u'%s' % filename, # тема письма = имя файла без расширения to=form.cleaned_data['to'], body=form.cleaned_data['body'], files=files, sign_doc=form.cleaned_data.get('save_client_email', None) ) return False # что-то пошло не так @method_decorator(csrf_protect) def email(self, *args, **kwargs): """Отправить документ на email аттачем в заданном формате.""" if self.request.method == 'POST' and '_cancel' in self.request.POST: return redirect(self.REDIRECT_AFTER_EMAIL) obj = self.get_obj(kwargs['id']) if self.request.method == 'POST': form = self.EMAIL_FORM_CLASS(data=self.request.POST) if form.is_valid(): self._process_email_form_and_send(form, *args, **kwargs) return redirect(self.REDIRECT_AFTER_EMAIL) else: initial = {} client = getattr(self.get_obj(kwargs['id']), 'client', None) if client: # подставить в форму email клиента initial['to'] = client.contact_email form = self.EMAIL_FORM_CLASS(initial=initial) dictionary = { 'padeji': self.PADEJI, 'padeji_mnoj': self.PADEJI_MNOJ, 'url_prefix': self.URL_PREFIX, 'form_template': self.TEMPLATE_EMAIL_FORM, 'obj': obj, 'form': form, } return render(self.request, self.TEMPLATE_EMAIL, dictionary) @method_decorator(require_POST) @method_decorator(csrf_protect) def email_ajax(self, *args, **kwargs): """Отправить документ на email аттачем в заданном формате - AJAX.""" if not self.request.is_ajax(): return HttpResponseBadRequest() result = False form = self.EMAIL_FORM_CLASS(data=self.request.POST) if form.is_valid(): result = self._process_email_form_and_send(form, *args, **kwargs) non_field_errors = form.non_field_errors() if not form.is_valid(): non_field_errors.append(u'Заполните/исправьте выделенные поля.') data = { 'success': form.is_valid(), 'field_errors': form.errors, # ошибки полей 'form_errors': non_field_errors, # ошибки формы } if form.is_valid() and result: data['message'] = {'title': 'Инфо', 'msg': 'Письмо отправлено.'} return HttpResponse(json.dumps(data), content_type='application/json') def get_ajax(self, *args, **kwargs): """Получить документ - AJAX.""" if not self.request.is_ajax(): return HttpResponseBadRequest() obj = self.get_obj(kwargs['id'], only_form_fields=True) data = json.dumps(obj, default=dthandler()) return HttpResponse(data, content_type='application/json') # ----------------------------------------------------------------------------- class BaseItemsViews(BaseViews): """Базовые views для документов с табличной частью.""" ITEM_MODEL = None # модель табличной части документа ITEM_FORM_CLASS = None # форма табличной части документа ITEM_FORM_PREFIX = None # префикс формы табличной части # по какому полю суммировать табличную часть при показе списка документов LIST_SUM_FIELD = None # None или строка def __init__(self, request): super(BaseItemsViews, self).__init__(request) self.set_item_formset_class() def asserts(self): """Проверить объект класса на типичные ошибки.""" super(BaseItemsViews, self).asserts() assert self.ITEM_MODEL is not None, \ (u"%s.ITEM_MODEL can't be None!" % self.__class__.__name__) assert self.ITEM_FORM_CLASS is not None, \ (u"%s.ITEM_FORM_CLASS can't be None!" % self.__class__.__name__) def set_item_formset_class(self, extra_count=2): """Класс FormSet-а для табличной части документа.""" self.ITEM_FORMSET_CLASS = inlineformset_factory( parent_model=self.MODEL, model=self.ITEM_MODEL, form=self.ITEM_FORM_CLASS, extra=extra_count, can_delete=True, ) def get_list_qs(self): """QuerySet для просмотра списка документов. Плюс суммирование табличной части по заданному полю. """ queryset = super(BaseItemsViews, self).get_list_qs() if self.LIST_SUM_FIELD: queryset = queryset.annotate(doc_sum=Sum(self.LIST_SUM_FIELD)) return queryset def get_obj_items_qs(self, obj): """QuerySet табличной части документа.""" return self.ITEM_MODEL.objects.filter(parent=obj).select_related() def update_parent_on_items_save(self, obj, obj_items): """Обновить родительскую модель.""" pass @method_decorator(license_required) @method_decorator(csrf_protect) def add(self, *args, initial_items=None, initial=None, **kwargs): """Добавить документ. Если при GET-запросе в kwargs передать initial и/или initial_items, то создаст предзаполненный документ. """ raise_if_no_profile(self.request) if self.request.method == 'POST' and '_cancel' in self.request.POST: return redirect(self.REDIRECT_AFTER_ADD) receiver_choice = None sender_choice = None # import ipdb;ipdb.set_trace() if self.request.method == 'POST': form = self.FORM_CLASS(self.request.user, data=self.request.POST) formset = self.ITEM_FORMSET_CLASS(self.request.POST or None, prefix=self.ITEM_FORM_PREFIX) receiver_choice = form.data.get('receiver_group', None) sender_choice = form.data.get('sender_group', None) if form.is_valid() and formset.is_valid(): new_obj = form.save(commit=False) new_obj.user = self.request.user new_obj.company = self.request.user.profile new_obj.save() if formset.is_valid(): try: if not formset.instance.pk: formset.instance = new_obj new_items = formset.save(commit=False) for item in new_items: item.parent = new_obj item.save() messages.success(self.request, "Документ успешно сохранен") except DatabaseError: messages.error(self.request, "Ошибка базы данных. " "Пожалуйста, попробуйте снова") return redirect(self.REDIRECT_AFTER_ADD) else: initial = initial or self.init_form() initial_items = initial_items form = self.FORM_CLASS(self.request.user, initial=initial) formset = self.ITEM_FORMSET_CLASS(prefix=self.ITEM_FORM_PREFIX, initial=initial_items) dictionary = { 'padeji': self.PADEJI, 'padeji_mnoj': self.PADEJI_MNOJ, 'url_prefix': self.URL_PREFIX, 'form_template': self.TEMPLATE_FORM, 'form_template_js': self.TEMPLATE_FORM_JS, 'form': form, 'formset': formset, 'client_form': ClientForm(), 'receiver_choice': receiver_choice, 'sender_choice': sender_choice, 'dadata_api_key': settings.DADATA_API_KEY, } return render(self.request, self.TEMPLATE_ADD, dictionary) def copy(self, *args, **kwargs): """Создать полную копию документа.""" source_id = kwargs['id'] obj = self.get_obj(source_id) obj_items = self.get_obj_items_qs(obj) kwargs['initial'] = model_to_dict( obj, fields=getattr(self.FORM_CLASS.Meta, 'fields', None), exclude=getattr(self.FORM_CLASS.Meta, 'exclude', None) ) kwargs['initial'].update(self.init_form()) # обязательно убрать ключи kwargs['initial'].pop('pk', None) kwargs['initial'].pop('id', None) kwargs['initial'].pop('created_at', None) kwargs['initial'].pop('updated_at', None) if obj_items: kwargs['initial_items'] = [] for item in obj_items: d = model_to_dict( item, fields=getattr(self.ITEM_FORM_CLASS.Meta, 'fields', None), exclude=getattr(self.ITEM_FORM_CLASS.Meta, 'exclude', None) ) # обязательно убрать ключи d.pop('pk', None) d.pop('id', None) d.pop('created_at', None) d.pop('updated_at', None) kwargs['initial_items'].append(d) self.set_item_formset_class(extra_count=len(kwargs['initial_items'])) return self.add(self.request, *args, **kwargs) @method_decorator(csrf_protect) def edit(self, *args, **kwargs): """Редактировать документ.""" if self.request.method == 'POST' and '_cancel' in self.request.POST: return redirect(self.REDIRECT_AFTER_EDIT) obj = self.get_obj(kwargs['id']) receiver_choice = 1 sender_choice = 1 if self.request.method == 'GET': if hasattr(obj, 'user_is_sender'): if obj.user_is_sender: sender_choice = 'he_is' elif obj.sender: sender_choice = 'another' else: sender_choice = 'nobody' if hasattr(obj, 'receiver') and hasattr(obj, 'sender'): if obj.receiver == obj.client: receiver_choice = 'he_is' elif obj.receiver: receiver_choice = 'another' else: receiver_choice = 'nobody' formset = self.ITEM_FORMSET_CLASS(self.request.POST or None, prefix=self.ITEM_FORM_PREFIX, instance=obj) form = self.FORM_CLASS(self.request.user, data=self.request.POST or None, instance=obj) if form.is_valid() and formset.is_valid(): new_obj = form.save() # По сути - для фактуры. receiver = form.data.get('receiver_group', None) if receiver == 'he_is': new_obj.receiver = new_obj.client elif receiver == 'nobody': new_obj.receiver = None # # sender = form.data.get('sender_group', None) if sender == 'he_is': new_obj.user_is_sender = True new_obj.sender = None elif sender == 'nobody': new_obj.user_is_sender = False new_obj.sender = None elif sender == 'another': new_obj.user_is_sender = False new_obj.save() # сохранить табличную часть if formset.is_valid(): items = formset.save(commit=False) for item in items: item.parent = new_obj item.save() self.update_parent_on_items_save(new_obj, items) return redirect(self.REDIRECT_AFTER_EDIT) dictionary = { 'padeji': self.PADEJI, 'padeji_mnoj': self.PADEJI_MNOJ, 'url_prefix': self.URL_PREFIX, 'form_template': self.TEMPLATE_FORM, 'form_template_js': self.TEMPLATE_FORM_JS, 'obj': obj, 'form': form, 'formset': formset, 'client_form': ClientForm(), 'receiver_choice': receiver_choice, 'sender_choice': sender_choice, 'dadata_api_key': settings.DADATA_API_KEY, } return render(self.request, self.TEMPLATE_EDIT, dictionary) def prepare(self, obj, obj_items, export_to=None): """Подмешать дополнительные поля к документу.""" pass def get_pdf(self, *args, **kwargs): """Создать документ в PDF и вернуть как строку.""" obj = self.get_obj(kwargs['id']) obj_items = self.get_obj_items_qs(obj) profile = get_profile(obj.user) main_account = BankAccount.objects.get_main(profile) self.prepare(obj, obj_items, export_to='pdf') params = { 'obj': obj, 'obj_items': obj_items, 'profile': profile, 'main_account': main_account, 'doc_sign': kwargs.get('doc_sign', False), } c1 = time() pdf = render_pdf_to_string(self.request, self.PDF_TEMPLATE, params) if DEBUG: print('%s generation time (seconds): %s' % (self.PDF_TEMPLATE, time() - c1,)) return pdf def get_xls(self, *args, **kwargs): """Создать документ в Excel и вернуть как строку.""" obj = self.get_obj(kwargs['id']) obj_items = self.get_obj_items_qs(obj) profile = get_profile(obj.user) main_account = BankAccount.objects.get_main(profile) self.prepare(obj, obj_items, export_to='xls') params = { 'obj': obj, 'obj_items': obj_items, 'profile': profile, 'main_account': main_account, } c1 = time() xls = render_xls_to_string(self.request, self.XLS_TEMPLATE, params) if DEBUG: print('%s generation time (seconds): %s' % (self.XLS_TEMPLATE, time() - c1,)) return xls