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.
836 lines
34 KiB
836 lines
34 KiB
# -*- 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 ghostscript
|
|
|
|
from django.shortcuts import render, get_object_or_404, redirect
|
|
from django.http import HttpResponseServerError, 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 django.conf import settings
|
|
|
|
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 docs import filters
|
|
|
|
from project.customer.decorators import license_required
|
|
from project.customer.utils import raise_if_no_profile
|
|
|
|
|
|
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_FORM_CLASS = EmailForm # форма создания и отправки email
|
|
|
|
# поля, по которым можно сортировать список документов
|
|
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_date', '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.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(),
|
|
}
|
|
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():
|
|
new_obj = 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'Удалён документ:<br /> %s №%d от %s' % (self.PADEJI['imenit'], del_num, del_date),
|
|
'success': success,
|
|
'del_id': del_id,
|
|
}
|
|
return HttpResponse(json.dumps(result), mimetype='application/json')
|
|
|
|
#dictionary = {
|
|
# 'padeji': self.PADEJI,
|
|
# 'padeji_mnoj': self.PADEJI_MNOJ,
|
|
# 'obj': obj,
|
|
#}
|
|
#return render(self.request, self.TEMPLATE_DELETE, dictionary)
|
|
|
|
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):
|
|
"""Вывести превью документа."""
|
|
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)
|
|
os.chmod(tmp_dir, 755)
|
|
tmp_dirname = os.path.split(tmp_dir)[1]
|
|
|
|
f = open(filename, 'w')
|
|
f.write(pdf)
|
|
f.close()
|
|
args = ["",
|
|
"-dNOPAUSE", "-dBATCH", "-dSAFER",
|
|
"-sDEVICE=png16m",
|
|
"-r150",
|
|
"-sOutputFile=%s" % os.path.join(tmp_dir, "page-%03d.png"),
|
|
"-f", filename
|
|
]
|
|
GS = ghostscript.Ghostscript(*args)
|
|
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]
|
|
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:
|
|
initial['to'] = client.contact_email # подставить в форму 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), mimetype='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, mimetype='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, **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
|
|
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():
|
|
new_items = formset.save(commit=False)
|
|
for item in new_items:
|
|
item.parent = new_obj
|
|
item.save()
|
|
return redirect(self.REDIRECT_AFTER_ADD)
|
|
else:
|
|
initial = kwargs.get('initial') or self.init_form()
|
|
initial_items = kwargs.get('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,
|
|
}
|
|
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,
|
|
}
|
|
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
|
|
|