Commit de231f43 authored by Patrick Colmant's avatar Patrick Colmant Committed by Patrick

Django 1.8.5, Django-cms 3.2 rc 3

parent 0ff4e700
This diff is collapsed.
......@@ -2,6 +2,7 @@
from __future__ import unicode_literals
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
from const import *
from models import Customer
from models import LUT_DepartmentForCustomer
......@@ -11,6 +12,7 @@ from models import Product
# Filters in the right sidebar of the change list page of the admin
from django.contrib.admin import SimpleListFilter
from tools import sint
class ProductFilterByProducer(SimpleListFilter):
......@@ -56,8 +58,13 @@ class ProductFilterByDepartmentForThisProducer(SimpleListFilter):
inner_qs = Product.objects.filter(is_active=True, producer_id=producer_id).order_by().distinct(
'department_for_customer__id')
else:
inner_qs = Product.objects.filter(is_active=True).order_by().distinct(
'department_for_customer__id')
permanence_id = request.GET.get('permanence')
if permanence_id:
inner_qs = Product.objects.filter(offeritem__permanence_id=permanence_id).order_by().distinct(
'department_for_customer__id')
else:
inner_qs = Product.objects.filter(is_active=True).order_by().distinct(
'department_for_customer__id')
return [(c.id, c.short_name) for c in
LUT_DepartmentForCustomer.objects.filter(is_active=True, product__in=inner_qs)
......@@ -78,16 +85,23 @@ class PurchaseFilterByCustomerForThisPermanence(SimpleListFilter):
def lookups(self, request, model_admin):
permanence_id = request.GET.get('permanence')
if permanence_id:
return [(c.id, c.short_basket_name) for c in
Customer.objects.filter(purchase__permanence_id=permanence_id).distinct()
]
permanence_id = sint(permanence_id, 0)
if permanence_id >= 0:
return [(c.id, c.short_basket_name) for c in
Customer.objects.filter(purchase__permanence_id=permanence_id).distinct()
]
else:
# This is a year
return [(c.id, c.short_basket_name) for c in
Customer.objects.filter(purchase__permanence_date__year=-permanence_id).distinct()
]
else:
return [(c.id, c.short_basket_name) for c in
Customer.objects.filter(may_order=True)
]
def queryset(self, request, queryset):
# This query set is a collection of permanence
# This query set is a collection of purchase
if self.value():
return queryset.filter(customer_id=self.value())
else:
......@@ -101,16 +115,23 @@ class PurchaseFilterByProducerForThisPermanence(SimpleListFilter):
def lookups(self, request, model_admin):
permanence_id = request.GET.get('permanence')
if permanence_id:
return [(c.id, c.short_profile_name) for c in
Producer.objects.filter(permanence=permanence_id).distinct()
]
permanence_id = sint(permanence_id, 0)
if permanence_id >= 0:
return [(c.id, c.short_profile_name) for c in
Producer.objects.filter(permanence=permanence_id).distinct()
]
else:
# This is a year
return [(c.id, c.short_profile_name) for c in
Producer.objects.filter(permanence__permanence_date__year=-permanence_id).distinct()
]
else:
return [(c.id, c.short_profile_name) for c in
Producer.objects.filter(is_active=True)
]
def queryset(self, request, queryset):
# This query set is a collection of permanence
# This query set is a collection of purchase
if self.value():
return queryset.filter(producer_id=self.value())
else:
......@@ -123,29 +144,55 @@ class PurchaseFilterByPermanence(SimpleListFilter):
def lookups(self, request, model_admin):
# This list is a collection of permanence.id, .name
return [(c.id, c.__str__()) for c in
Permanence.objects.filter(status__in=model_admin.permanence_status_list)
]
if PERMANENCE_DONE in model_admin.permanence_status_list \
or PERMANENCE_ARCHIVED in model_admin.permanence_status_list:
this_year = timezone.now().year
return [
(-(this_year -i), str(this_year - i)) for i in xrange(10)
]
else:
return [(c.id, c.__str__()) for c in
Permanence.objects.filter(status__in=model_admin.permanence_status_list)
]
def queryset(self, request, queryset):
# This query set is a collection of permanence
# This query set is a collection of purchase
if self.value():
return queryset.filter(permanence_id=self.value())
permanence_id = sint(self.value(),0)
if permanence_id >= 0:
return queryset.filter(permanence_id=permanence_id)
else:
return queryset.filter(permanence_date__year=-permanence_id)
else:
return queryset
class OfferItemSendFilter(SimpleListFilter):
class OfferItemFilter(SimpleListFilter):
title = _("products")
parameter_name = 'is_filled_exact'
def lookups(self, request, model_admin):
# This list is a collection of permanence.id, .name
# This list is a collection of offer_item.id, .name
return [(1, _('only invoiced')),]
def queryset(self, request, queryset):
# This query set is a collection of permanence
# This query set is a collection of offer_item
if self.value():
return queryset.exclude(quantity_invoiced=DECIMAL_ZERO)
else:
return queryset
class BankAccountFilterByPermanence(SimpleListFilter):
title = _("permanence")
parameter_name = 'is_filled_exact'
def lookups(self, request, model_admin):
# This list is a collection of bankaccount.id, .name
return [(1, _('not invoiced')),]
def queryset(self, request, queryset):
# This query set is a collection of bankaccount
if self.value():
return queryset.filter(permanence_id__isnull=True)
else:
return queryset
\ No newline at end of file
......@@ -2,75 +2,44 @@
from __future__ import unicode_literals
from django.apps import AppConfig
from django.conf import settings
from django.contrib.auth.models import Group, Permission
from django.contrib.sites.models import Site
from django.utils.translation import ugettext_lazy as _
from callback import pasword_reset_callback
from const import *
from password_reset.signals import user_recovers_password
from django.contrib.contenttypes.models import ContentType
# repanier_settings = {
# 'CONFIG': None,
# 'TEST_MODE': None,
# 'GROUP_NAME': None,
# 'PERMANENCE_NAME': None,
# 'PERMANENCES_NAME': None,
# 'PERMANENCE_ON_NAME': None,
# 'MAX_WEEK_WO_PARTICIPATION': None,
# 'SEND_OPENING_MAIL_TO_CUSTOMER': None,
# 'SEND_ORDER_MAIL_TO_CUSTOMER': None,
# 'SEND_ORDER_MAIL_TO_PRODUCER': None,
# 'SEND_ORDER_MAIL_TO_BOARD': None,
# 'SEND_INVOICE_MAIL_TO_CUSTOMER': None,
# 'SEND_INVOICE_MAIL_TO_PRODUCER': None,
# 'DISPLAY_ANONYMOUS_ORDER_FORM': None,
# 'DISPLAY_PRODUCERS_ON_ORDER_FORM': None,
# 'BANK_ACCOUNT': None,
# 'PRODUCER_ORDER_ROUNDED': None,
# 'PRODUCER_PRE_OPENING': None,
# 'ACCEPT_CHILD_GROUP': None,
# 'DELIVERY_POINT': None,
# 'INVOICE': None,
# 'STOCK': None,
# 'DISPLAY_VAT': None,
# 'VAT_ID': None,
# 'PAGE_BREAK_ON_CUSTOMER_CHECK': None
# }
# class RepanierConfig(AppConfig):
REPANIER_SETTINGS_CONFIG = None
REPANIER_SETTINGS_TEST_MODE = None
REPANIER_SETTINGS_GROUP_NAME = None
REPANIER_SETTINGS_PERMANENCE_NAME = None
REPANIER_SETTINGS_PERMANENCES_NAME = None
REPANIER_SETTINGS_PERMANENCE_ON_NAME = None
REPANIER_SETTINGS_MAX_WEEK_WO_PARTICIPATION = None
REPANIER_SETTINGS_SEND_OPENING_MAIL_TO_CUSTOMER = None
REPANIER_SETTINGS_SEND_ORDER_MAIL_TO_CUSTOMER = None
REPANIER_SETTINGS_SEND_ABSTRACT_ORDER_MAIL_TO_CUSTOMER = None
REPANIER_SETTINGS_SEND_ORDER_MAIL_TO_PRODUCER = None
REPANIER_SETTINGS_SEND_ABSTRACT_ORDER_MAIL_TO_PRODUCER = None
REPANIER_SETTINGS_SEND_ORDER_MAIL_TO_BOARD = None
REPANIER_SETTINGS_SEND_INVOICE_MAIL_TO_CUSTOMER = None
REPANIER_SETTINGS_SEND_INVOICE_MAIL_TO_PRODUCER = None
REPANIER_SETTINGS_INVOICE= None
REPANIER_SETTINGS_DISPLAY_ANONYMOUS_ORDER_FORM = None
REPANIER_SETTINGS_DISPLAY_PRODUCER_ON_ORDER_FORM = None
REPANIER_SETTINGS_BANK_ACCOUNT = None
REPANIER_SETTINGS_DELIVERY_POINT = None
REPANIER_SETTINGS_DISPLAY_VAT = None
REPANIER_SETTINGS_VAT_ID = None
REPANIER_SETTINGS_PAGE_BREAK_ON_CUSTOMER_CHECK = None
class RepanierSettings(AppConfig):
name = 'repanier'
verbose_name = "Repanier"
config = None
test_mode = None
group_name = None
permanence_name = None
permanences_name = None
permanence_on_name = None
max_week_wo_participation = None
send_opening_mail_to_customer = None
send_order_mail_to_customer = None
send_order_mail_to_producer = None
send_order_mail_to_board = None
send_invoice_mail_to_customer = None
send_invoice_mail_to_producer = None
invoice = None
stock = None
display_anonymous_order_form = None
display_producer_on_order_form = None
bank_account = None
producer_order_rounded = None
producer_pre_opening = None
accept_child_group = None
delivery_point = None
display_vat = None
vat_id = None
page_break_on_customer_check = None
def ready(self):
user_recovers_password.connect(pasword_reset_callback)
from models import Configuration
try:
# Create if needed and load RepanierSettings var when performing config.save()
config = Configuration.objects.filter(id=DECIMAL_ONE).first()
if config is None:
group_name = settings.ALLOWED_HOSTS[0]
......@@ -88,22 +57,79 @@ class RepanierSettings(AppConfig):
send_order_mail_to_customer=False,
send_order_mail_to_producer=False,
send_invoice_mail_to_customer=False,
send_abstract_order_mail_to_customer=False,
send_invoice_mail_to_producer=False,
send_abstract_order_mail_to_producer=False,
send_order_mail_to_board=False,
invoice=True,
stock=False,
display_anonymous_order_form=False,
display_producer_on_order_form=False,
bank_account="BE99 9999 9999 9999",
producer_order_rounded=False,
producer_pre_opening=False,
accept_child_group=False,
delivery_point=False,
display_vat=False,
vat_id=EMPTY_STRING,
page_break_on_customer_check=False
)
config.save()
# Create groups with correct rights
order_group = Group.objects.filter(name=ORDER_GROUP).only('id').order_by().first()
if order_group is None:
order_group = Group.objects.create(name=ORDER_GROUP)
invoice_group = Group.objects.filter(name=INVOICE_GROUP).only('id').order_by().first()
if invoice_group is None:
invoice_group = Group.objects.create(name=INVOICE_GROUP)
coordination_group = Group.objects.filter(name=COORDINATION_GROUP).only('id').order_by().first()
if coordination_group is None:
coordination_group = Group.objects.create(name=COORDINATION_GROUP)
content_types = ContentType.objects.exclude(
app_label__in=[
'admin',
'aldryn_bootstrap3',
'auth',
'cms',
'cmsplugin_cascade',
'cmsplugin_filer_file',
'cmsplugin_filer_folder',
'cmsplugin_filer_image',
'cmsplugin_filer_link',
'cmsplugin_filer_video',
'contenttypes',
'djangocms_text_ckeditor',
'easy_thumbnails',
'filer'
'menus',
'reversion',
'sessions',
'sites',
]
).only('id').order_by()
permissions = Permission.objects.filter(
content_type__in=content_types
).only('id').order_by()
order_group.permissions = permissions
invoice_group.permissions = permissions
coordination_group.permissions = permissions
content_types = ContentType.objects.exclude(
app_label__in=[
'admin',
'auth',
'contenttypes',
'filer'
'menus',
'repanier',
'reversion',
'sessions',
'sites',
]
).only('id').order_by()
permissions = Permission.objects.filter(
content_type__in=content_types
).only('id').order_by()
webmaster_group = Group.objects.filter(name=WEBMASTER_GROUP).only('id').order_by().first()
if webmaster_group is None:
webmaster_group = Group.objects.create(name=WEBMASTER_GROUP)
webmaster_group.permissions = permissions
except Exception as error_str:
print("##################################")
print error_str
......
......@@ -5,7 +5,7 @@ from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.models import User
from django.db.models import F, Q
from const import DECIMAL_ZERO, DECIMAL_ONE, DECIMAL_TWO
from const import DECIMAL_ZERO, DECIMAL_ONE, DECIMAL_TWO, EMPTY_STRING, DECIMAL_THREE
from email.email_alert import send_error
from models import Customer, Staff, Configuration
from django.utils.translation import ugettext_lazy as _
......@@ -17,104 +17,93 @@ class RepanierCustomBackend(ModelBackend):
user = None
def __init__(self, *args, **kwargs):
super(RepanierCustomBackend, self).__init__(*args, **kwargs)
super(RepanierCustomBackend, self).__init__()
def authenticate(self, username=None, password=None, confirm=None, **kwargs):
self.user = None
# try: (Q(income__gte=5000) | Q(income__isnull=True))
user_username = User.objects.filter(Q(username=username[:30]) | Q(email=username)).order_by().first()
staff = customer = None
login_attempt_counter = DECIMAL_THREE
if user_username is not None:
username = user_username.username
staff = Staff.objects.filter(
customer = Customer.objects.filter(
user=user_username, is_active=True
).order_by().first()
if staff is not None:
customer = staff.customer_responsible
else:
customer = Customer.objects.filter(
if customer is None:
staff = Staff.objects.filter(
user=user_username, is_active=True
).order_by().first()
user_or_none = super(RepanierCustomBackend, self).authenticate(username, password)
if customer is not None:
# This is a customer or staff member
login_attempt_counter = customer.login_attempt_counter
if login_attempt_counter > DECIMAL_TWO:
# if confirm is None:
# confirm = ""
# else:
# confirm = str(sint(confirm))
phone_digits = ""
i = 0
phone1 = customer.phone1
while i < len(phone1):
if '0' <= phone1[i] <= '9':
phone_digits += phone1[i]
i += 1
if confirm is not None and len(confirm) >= 4 and phone_digits.endswith(confirm):
pass
else:
# Sorry you may no log in because the four last digits of your phone are not given
user_or_none = None
if user_or_none is None:
if login_attempt_counter < 20:
Customer.objects.filter(id=customer.id).update(
login_attempt_counter=F('login_attempt_counter') +
DECIMAL_ONE
)
else:
# That's really not normal
Customer.objects.filter(id=customer.id).update(
may_order=False
)
Staff.objects.filter(customer_responsible_id=customer.id).update(
is_active=False
)
if login_attempt_counter > DECIMAL_ONE:
raise forms.ValidationError(
_("Too many attempt."),
code='attempt',
)
if staff is None:
login_attempt_counter = Configuration.objects.filter(
id=DECIMAL_ONE
).only(
'login_attempt_counter'
).first().login_attempt_counter
else:
login_attempt_counter = staff.login_attempt_counter
else:
login_attempt_counter = customer.login_attempt_counter
user_or_none = super(RepanierCustomBackend, self).authenticate(username, password)
if user_or_none is None:
# Failed to log in
if login_attempt_counter < 20:
# Do not increment indefinitely
if customer is not None:
Customer.objects.filter(id=customer.id).update(
login_attempt_counter=DECIMAL_ZERO
login_attempt_counter=F('login_attempt_counter') +
DECIMAL_ONE
)
elif user_username is not None and user_username.is_superuser:
# This is the superuser. One and only one superuser should be defined.
login_attempt_counter = Configuration.objects.filter(
id=DECIMAL_ONE
).only(
'login_attempt_counter'
).first().login_attempt_counter
if user_or_none is None:
# Failed to log in
if login_attempt_counter < 50:
Configuration.objects.filter(id=DECIMAL_ONE).update(
login_attempt_counter=F('login_attempt_counter') +
DECIMAL_ONE
elif staff is not None:
Staff.objects.filter(id=staff.id).update(
login_attempt_counter=F('login_attempt_counter') +
DECIMAL_ONE
)
else:
Configuration.objects.filter(id=DECIMAL_ONE).update(
login_attempt_counter=F('login_attempt_counter') +
DECIMAL_ONE
)
if login_attempt_counter >= DECIMAL_THREE:
raise forms.ValidationError(
_("Because you tried to log in too many time without success, you must now first reset your password."),
# _("Too many attempt."),
code='attempt',
)
else:
if login_attempt_counter >= DECIMAL_THREE:
raise forms.ValidationError(
_("Because you tried to log in too many time without success, you must now first reset your password."),
# _("Too many attempt."),
code='attempt',
)
else:
# Reset login_attempt_counter
if customer is not None:
if login_attempt_counter > DECIMAL_ZERO:
Customer.objects.filter(id=customer.id).update(
login_attempt_counter=DECIMAL_ZERO
)
elif staff is not None:
if login_attempt_counter > DECIMAL_ZERO:
Staff.objects.filter(id=staff.id).update(
login_attempt_counter=DECIMAL_ZERO
)
if login_attempt_counter > DECIMAL_ONE:
send_error("Login attempt failed : %s" % username)
else:
# Log in successful
if login_attempt_counter > 6:
# Sorry you may no log in because of too many failed log in attempt
user_or_none = None
else:
if login_attempt_counter > DECIMAL_ZERO:
Configuration.objects.filter(id=DECIMAL_ONE).update(
login_attempt_counter=DECIMAL_ZERO
)
if login_attempt_counter > DECIMAL_TWO:
send_error("Login attempt success : %s" % username)
# except:
# user_or_none = None
self.user = user_or_none
# if user_or_none :
# print ('Authenticate user : %s' % getattr(user_or_none, get_user_model().USERNAME_FIELD))
# else:
# print ('Authenticate user : not defined')
return user_or_none
else:
return None
# except:
# user_or_none = None
self.user = user_or_none
# if user_or_none :
# print ('Authenticate user : %s' % getattr(user_or_none, get_user_model().USERNAME_FIELD))
# else:
# print ('Authenticate user : not defined')
return user_or_none
def get_user(self, user_id):
if self.user is not None and self.user.id == user_id:
......
# -*- coding: utf-8
from __future__ import unicode_literals
from const import DECIMAL_ZERO
def pasword_reset_callback(sender, user, request, **kwargs):
if not user.is_superuser:
from models import Staff, Customer
staff = Staff.objects.filter(
user=user, is_active=True
).order_by().first()
if staff is not None:
customer = staff.customer_responsible
else:
customer = Customer.objects.filter(
user=user, is_active=True
).order_by().first()
Customer.objects.filter(id=customer.id).update(
login_attempt_counter=DECIMAL_ZERO
)
......@@ -3,10 +3,10 @@ from __future__ import unicode_literals
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from cms.toolbar_pool import toolbar_pool
from cms.toolbar.items import Break, SubMenu
from cms.cms_toolbar import ADMIN_MENU_IDENTIFIER, ADMINISTRATION_BREAK
from cms.toolbar.items import SubMenu
from cms.cms_toolbars import ADMIN_MENU_IDENTIFIER, ADMINISTRATION_BREAK
from cms.toolbar_base import CMSToolbar
from apps import RepanierSettings
import apps
from const import *
from models import Configuration
......@@ -39,7 +39,7 @@ class RepanierToolbar(CMSToolbar):
office_menu.add_sideframe_item(_('Permanence Role List'), url=url)
url = reverse('admin:repanier_lut_productionmode_changelist')
office_menu.add_sideframe_item(_('Production Mode List'), url=url)
if RepanierSettings.delivery_point:
if apps.REPANIER_SETTINGS_DELIVERY_POINT:
url = reverse('admin:repanier_lut_deliverypoint_changelist')
office_menu.add_sideframe_item(_('Delivery Point List'), url=url)
url = reverse('admin:repanier_lut_departmentforcustomer_changelist')
......@@ -55,13 +55,13 @@ class RepanierToolbar(CMSToolbar):
position += 1
url = reverse('admin:repanier_permanenceinpreparation_changelist')
admin_menu.add_sideframe_item(_("%(name)s in preparation list") % {'name': RepanierSettings.permanences_name},
admin_menu.add_sideframe_item(_("%(name)s in preparation list") % {'name': apps.REPANIER_SETTINGS_PERMANENCES_NAME},
url=url, position=position)
if RepanierSettings.invoice:
if apps.REPANIER_SETTINGS_INVOICE:
position += 1
url = reverse('admin:repanier_permanencedone_changelist')
admin_menu.add_sideframe_item(_("%(name)s done list") % {'name': RepanierSettings.permanences_name},
admin_menu.add_sideframe_item(_("%(name)s done list") % {'name': apps.REPANIER_SETTINGS_PERMANENCES_NAME},
url=url, position=position)
position += 1
......@@ -70,5 +70,5 @@ class RepanierToolbar(CMSToolbar):
else:
position += 1
url = reverse('admin:repanier_permanencedone_changelist')
admin_menu.add_sideframe_item(_("%(name)s archived list") % {'name': RepanierSettings.permanences_name},
admin_menu.add_sideframe_item(_("%(name)s archived list") % {'name': apps.REPANIER_SETTINGS_PERMANENCES_NAME},
url=url, position=position)
......@@ -13,6 +13,7 @@ EMPTY_STRING = ''
DECIMAL_ZERO = Decimal('0')
DECIMAL_ONE = Decimal('1')
DECIMAL_TWO = Decimal('2')
DECIMAL_THREE = Decimal('3')
DECIMAL_1_02 = Decimal('1.02')
DECIMAL_1_06 = Decimal('1.06')
DECIMAL_1_12 = Decimal('1.12')
......@@ -117,6 +118,7 @@ LUT_PRODUCT_ORDER_UNIT_REVERSE = (
LUT_PRODUCER_PRODUCT_ORDER_UNIT = (
(PRODUCT_ORDER_UNIT_PC_PRICE_PC, _("Sold by piece")),
(PRODUCT_ORDER_UNIT_PC_PRICE_KG, _("Sold by weight")),
(PRODUCT_ORDER_UNIT_PC_PRICE_LT, _("Sold by l")),
(PRODUCT_ORDER_UNIT_PC_KG, _("Sold by piece, invoiced following the weight")),
)
......@@ -166,4 +168,7 @@ LUT_PERMANENCE_NAME = (
(PERMANENCE_NAME_CLOSURE, _('Closure')),
(PERMANENCE_NAME_DELIVERY, _('Delivery')),
(PERMANENCE_NAME_ORDER, _('Order')),
)
\ No newline at end of file
)
LIMIT_ORDER_QTY_ITEM = 50
LIMIT_DISPLAYED_PERMANENCE = 25
\ No newline at end of file
# -*- coding: utf-8
from __future__ import unicode_literals
from repanier.apps import RepanierSettings
import repanier.apps
from repanier.const import *
from django.conf import settings
from django.core.mail import send_mail
......@@ -9,7 +9,7 @@ from django.core.mail import send_mail
def send(permanence):
try:
send_mail("Alert - %s - %s" % (permanence, RepanierSettings.group_name), permanence.get_status_display(),
send_mail("Alert - %s - %s" % (permanence, repanier.apps.REPANIER_SETTINGS_GROUP_NAME), permanence.get_status_display(),
"%s@repanier.be" % (settings.ALLOWED_HOSTS[0]), [v for k, v in settings.ADMINS])
except:
pass
......@@ -17,7 +17,7 @@ def send(permanence):
def send_error(error_str):
try:
send_mail("Alert - %s" % RepanierSettings.group_name, error_str,
send_mail("Alert - %s" % repanier.apps.REPANIER_SETTINGS_GROUP_NAME, error_str,
"%s@repanier.be" % (settings.ALLOWED_HOSTS[0]), [v for k, v in settings.ADMINS])
except:
pass
......
......@@ -8,22 +8,22 @@ from django.utils.html import strip_tags
from django.utils.translation import ugettext_lazy as _
from parler.models import TranslationDoesNotExist
from openpyxl.writer.excel import save_virtual_workbook
from repanier.apps import RepanierSettings
import repanier.apps
from repanier.models import Customer
from repanier.models import Permanence
from repanier.models import Producer
from repanier.models import Staff
from repanier.tools import *
from repanier.xslx import xslx_invoice
from repanier.xlsx import xslx_invoice
from django.conf import settings
# from repanier.const import *
def send(permanence_id):
def send_invoice(permanence_id):
permanence = Permanence.objects.get(id=permanence_id)
sender_email, sender_function, signature, cc_email_staff = get_signature(is_reply_to_invoice_email=True)