Commit 91684ae1 authored by Patrick's avatar Patrick

release used in production

parent 8db31f0d
No preview for this file type
......@@ -83,14 +83,14 @@ Install Required libs for Django installation
.. code:: bash
sudo apt-get install gettext unzip
sudo apt-get python-setuptools
# vv For the PostgreSQL connector
sudo apt-get install libpq-dev python-dev
# vv for Pillow
sudo apt-get install libtiff4-dev libjpeg8-dev zlib1g-dev libfreetype6-dev liblcms1-dev libwebp-dev tcl8.5-dev tk8.5-dev
# vv for docx
sudo apt-get install libxml2-dev libxslt1-dev
sudo apt-get install gettext unzip
sudo apt-get install python-setuptools
# vv For the PostgreSQL connector
sudo apt-get install libpq-dev python-dev
# vv for Pillow
sudo apt-get install libtiff4-dev libjpeg8-dev zlib1g-dev libfreetype6-dev liblcms1-dev libwebp-dev tcl8.5-dev tk8.5-dev
# vv for docx
sudo apt-get install libxml2-dev libxslt1-dev
......
......@@ -57,13 +57,13 @@ Check if CMS install is ok
python manage.py cms check --settings=ptidej.ptidej_settings
Compile translation files
.. Compile translation files
.. code:: bash
.. .. code:: bash
cd ~/v1/mysite/repanier
export DJANGO_SETTINGS_MODULE=
django-admin.py compilemessages
.. cd ~/v1/mysite/repanier
.. export DJANGO_SETTINGS_MODULE=
.. django-admin.py compilemessages
If you want, initialize the DB with test content : copy the content of /install/createdb into ~/v1/mysite/
......@@ -95,6 +95,10 @@ Restart Nginx and Uwsgi
# Start Uwsgi
sudo /etc/init.d/uwsgi start
# The same Stop, Clean, Start Uwsgi in one line
sudo /etc/init.d/uwsgi stop && rm -rf /var/tmp/django_cache/* && sudo /etc/init.d/uwsgi start
The surf on your sites
When needed, upgrade the DB with south for a new version of specific INSTALLED_APPS (eg repanier)
......
......@@ -16,6 +16,7 @@ pip install psycopg2
# ELSE installing the latest development version
# pip install https://github.com/divio/django-cms/archive/develop.zip
# OR UPGRADE
pip install -U django
pip install -U https://github.com/divio/django-cms/archive/develop.zip
# ELSE installing a specific version
# pip install https://github.com/divio/django-cms/archive/3.0.0.beta3.zip
......@@ -37,10 +38,12 @@ pip install -U django_compressor
pip install -U django-admin-sortable2
pip install -U openpyxl
# pip install -U django-hvad
pip install -U docx
# pip install -U docx
# pip install django_debug_toolbar
# pip install django-dajaxice ! not working with Django 1.6
# pip install django-custom-user
# pip install django-registration
pip install -U django-crispy-forms
pip install -U django-celery
# pip install -U django-celery
# pip install -U celery
pip install -U django-password-reset
\ No newline at end of file
......@@ -19,7 +19,13 @@ ADMINS = (
os.getenv('DJANGO_SETTINGS_MODULE_ADMIN_EMAIL','')
),
)
SERVER_EMAIL = os.getenv('DJANGO_SETTINGS_MODULE_ADMIN_EMAIL','')
# MANAGERS = (
# (
# os.getenv('DJANGO_SETTINGS_MODULE_ADMIN_NAME',''),
# os.getenv('DJANGO_SETTINGS_MODULE_ADMIN_EMAIL','')
# ),
# )
######################
DATABASES = {
......@@ -208,6 +214,11 @@ LOGOUT_URL = "/leave_repanier/"
# ],
# }
CKEDITOR_SETTINGS = {
'language': '{{ language }}',
'toolbar': 'HTMLField',
}
TEXT_SAVE_IMAGE_FUNCTION='cmsplugin_filer_image.integrations.ckeditor.create_image_plugin'
FILER_ENABLE_LOGGING = False
......
......@@ -8,8 +8,13 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
{% addtoblock "css" %}<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">{% endaddtoblock %}
{% addtoblock "css" %}<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap-theme.min.css">{% endaddtoblock %}
{% addtoblock "css" %}<link rel="stylesheet" href="{{ STATIC_URL}}bootstrap/css/custom.css">{% endaddtoblock %}
{% addtoblock "css" %}
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
<style type="text/css">
a.skip_link{
position:absolute;
......@@ -52,23 +57,31 @@
<li><a href="{% url "logout_form" %}">{% trans "Logout" %}</a></li>
</ul>
</li>
<li class="dropdown">
<a href="?offeritem=purchased"><span id="my_basket">{{ prepared_amount }}</span> &euro; <span class="glyphicon glyphicon-shopping-cart"></span></a>
</li>
{% else %}
<li class="dropdown">
<a href="{% url "login_form" %}">{% trans "Login" %}</a>
</li>
{% endif %}
{% if languages.len > 1 %}
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">{% trans "Language" %} <b class="caret"></b></a>
<ul class="dropdown-menu">
{% language_chooser "native" %}
</ul>
</li>
{% endif %}
</ul>
</div><!--/.nav-collapse -->
</div>
</div>
<!-- Begin page content -->
<a name="content"></a>
<!--[if lt IE 9]>
<h1>{% trans "It will be easier for you to crawl this website with a newer browser. For example" %} <a href="http://www.mozilla.org/fr/">Firefox</a> {% trans "or" %} <a href="http://www.google.be/intl/fr/chrome/browser/">Chrome</a>.</h1>
<![endif]-->
{% block base_content%}{% endblock %}
<!-- Footer -->
<div id="footer" class="container">
......
......@@ -9,8 +9,8 @@
<p>Repanier est un outil de gestion du cycle des permamnences destiné aux groupements d'achats et aux producteurs locaux.</p>
<p>Contraction de repas et panier, repanier est le fruit de l'expérience des membres de plusieurs groupements d'achats</p>
<ul>
<li><a href="http://ptidej.gasath.be/">Gasath, groupe ptidej</a>, une trentaine d'acheteurs de produits locaux, de préférence bio, sur Ath</li>
<li><a href="http://apero.gasath.be/">Gasath, groupe apéro</a>, une trentaine d'acheteurs de produits locaux et bio sur Ath</li>
<li><a href="http://ptidej.repanier.be/">Gasath, groupe ptidej</a>, une trentaine d'acheteurs de produits locaux, de préférence bio, sur Ath</li>
<li><a href="http://apero.repanier.be/">Gasath, groupe apéro</a>, une trentaine d'acheteurs de produits locaux et bio sur Ath</li>
<li><a href="http://www.lepanierlensois.be/">Le Panier Lensois</a>, une trentaine d'acheteurs de produits locaux et bio sur Lens</li>
<li><a href="http://www.lebiodici.be/">Le Bio d'Ici</a>, une quinzaine de maraîchers bio regroupés pour les restaurants, collectivités locales, ...</li>
</ul>
......
{% load menu_tags i18n %}
{% for language in languages %}
<li><a href="{% page_language_url language.0 %}" {% ifequal current_language language.0 %} class="active"{% endifequal %}>{{ language.1 }}</a></li>
{% if not forloop.last %}
<!-- <li role="presentation" class="divider"></li> -->
{% endif %}
{% endfor %}
\ No newline at end of file
......@@ -17,6 +17,7 @@ from django.contrib.auth import get_user_model
from django.contrib.auth.models import User
from django.core import urlresolvers
import datetime
from django.utils import timezone
from django.utils.timezone import utc
from django import forms
......@@ -32,7 +33,7 @@ from django.contrib.sites.models import get_current_site
from django.shortcuts import get_object_or_404
# from adminsortable.admin import SortableAdminMixin
from repanier.adminsortable import SortableAdminMixin
# from repanier.adminsortable import SortableAdminMixin
from repanier.models import LUT_ProductionMode
from repanier.models import LUT_DepartmentForCustomer
......@@ -57,6 +58,7 @@ from repanier.views import render_response
from repanier.admin_export_xlsx import export_permanence_planified_xlsx
from repanier.admin_export_xlsx import export_orders_xlsx
from repanier.admin_export_xlsx import export_product_xlsx
from repanier.admin_export_xlsx import export_invoices_xlsx
from repanier.admin_export_xlsx import export_permanence_done_xlsx
from repanier.admin_import_xlsx import import_product_xlsx
from repanier.admin_import_xlsx import import_permanence_done_xlsx
......@@ -66,10 +68,7 @@ from repanier.admin_send_mail import send_alert_email
from menus.base import Menu, NavigationNode
from menus.menu_pool import menu_pool
from repanier.tasks import open_offers_async
from repanier.tasks import close_orders_async
from repanier.tasks import done_async
from repanier.tasks import email_invoices_async
from repanier import tasks
# Filters in the right sidebar of the change list page of the admin
from django.contrib.admin import SimpleListFilter
......@@ -197,21 +196,24 @@ class PurchaseFilterByPermanence(SimpleListFilter):
class LUT_ProductionModeAdmin(admin.ModelAdmin):
list_display = ('short_name', 'is_active')
list_display_links = ('short_name',)
list_max_show_all = True
list_per_page = 17
list_max_show_all = 17
admin.site.register(LUT_ProductionMode, LUT_ProductionModeAdmin)
class LUT_DepartmentForCustomerAdmin(admin.ModelAdmin):
list_display = ('short_name', 'is_active')
list_display_links = ('short_name',)
list_max_show_all = True
list_per_page = 17
list_max_show_all = 17
admin.site.register(LUT_DepartmentForCustomer, LUT_DepartmentForCustomerAdmin)
class LUT_PermanenceRoleAdmin(admin.ModelAdmin):
list_display = ('short_name', 'is_active')
list_display_links = ('short_name',)
list_max_show_all = True
list_per_page = 17
list_max_show_all = 17
admin.site.register(LUT_PermanenceRole, LUT_PermanenceRoleAdmin)
......@@ -220,22 +222,23 @@ class ProducerAdmin(admin.ModelAdmin):
('short_profile_name', 'long_profile_name'),
('email', 'fax'),
('phone1', 'phone2',),
'order_description',
'invoice_description',
('price_list_multiplier','vat_level'),
('date_balance', 'balance'),
'represent_this_buyinggroup',
# 'order_description',
# 'invoice_description',
('price_list_multiplier', 'vat_level'),
('initial_balance', 'date_balance', 'balance'),
('invoice_by_basket', 'represent_this_buyinggroup'),
'address',
'is_active']
readonly_fields = (
'represent_this_buyinggroup',
# 'represent_this_buyinggroup',
'date_balance',
'balance',
)
search_fields = ('short_profile_name', 'email')
list_display = ('short_profile_name', 'get_products', 'get_balance', 'phone1', 'email', 'represent_this_buyinggroup',
'is_active')
list_max_show_all = True
list_per_page = 17
list_max_show_all = 17
actions = [
'export_xlsx',
'import_xlsx',
......@@ -249,6 +252,7 @@ class ProducerAdmin(admin.ModelAdmin):
return import_product_xlsx(self, admin, request, queryset)
import_xlsx.short_description = _("Import products of selected producer(s) from a XLSX file")
# def get_producer_phone1(self, obj):
# if obj.producer:
# return '%s'%(obj.producer.phone1)
......@@ -382,17 +386,18 @@ class CustomerWithUserDataAdmin(admin.ModelAdmin):
('email','email2'),
('phone1', 'phone2'),
'address', 'vat_id',
('date_balance', 'balance'),
('initial_balance', 'date_balance', 'balance'),
('represent_this_buyinggroup', 'may_order','is_active')
]
readonly_fields = (
'represent_this_buyinggroup',
# 'represent_this_buyinggroup',
'date_balance',
'balance',
)
search_fields = ('short_basket_name', 'user__email', 'email2')
list_display = ('__unicode__', 'get_balance', 'may_order', 'phone1', 'phone2', 'get_email', 'email2')
list_max_show_all = True
list_display = ('__unicode__', 'get_balance', 'may_order', 'phone1', 'phone2', 'get_email', 'email2', 'represent_this_buyinggroup')
list_per_page = 17
list_max_show_all = 17
def get_email(self, obj):
if obj.user:
......@@ -449,7 +454,9 @@ class StaffWithUserDataAdmin(admin.ModelAdmin):
'is_reply_to_order_email', 'is_reply_to_invoice_email',
'customer_responsible','long_name', 'function_description', 'is_active']
list_display = ('__unicode__', 'customer_responsible', 'get_customer_phone1', 'is_active')
list_max_show_all = True
list_select_related = ('customer_responsible',)
list_per_page = 17
list_max_show_all = 17
def get_form(self,request, obj=None, **kwargs):
form = super(StaffWithUserDataAdmin,self).get_form(request, obj, **kwargs)
......@@ -494,36 +501,37 @@ class StaffWithUserDataAdmin(admin.ModelAdmin):
admin.site.register(Staff, StaffWithUserDataAdmin)
class ProductAdmin(SortableAdminMixin, admin.ModelAdmin):
list_display = ('producer',
class ProductAdmin(admin.ModelAdmin):
list_display = (
'is_into_offer',
'long_name',
'producer',
'department_for_customer',
'long_name',
'original_unit_price',
'unit_deposit',
'customer_alert_order_quantity',
'get_order_unit',
'is_active')
list_display_links = ('long_name',)
list_editable = ('original_unit_price',)
readonly_fields = ('is_created_on',
'is_updated_on')
fields = (
('producer', 'long_name'),
('original_unit_price', 'unit_deposit', 'order_average_weight'),
('producer', 'long_name', 'picture'),
('original_unit_price', 'unit_deposit', 'vat_level'),
# ('order_by_kg_pay_by_kg', 'order_by_piece_pay_by_piece', 'order_by_piece_pay_by_kg', 'producer_must_give_order_detail_per_customer', 'automatically_added'),
# 'usage_description',
('order_unit', 'order_average_weight', 'customer_minimum_order_quantity', 'customer_increment_order_quantity', 'customer_alert_order_quantity'),
('production_mode', 'department_for_customer', 'placement'),
'offer_description',
'usage_description',
('order_by_kg_pay_by_kg', 'order_by_piece_pay_by_piece', 'order_by_piece_pay_by_kg', 'producer_must_give_order_detail_per_customer'),
('customer_minimum_order_quantity', 'customer_increment_order_quantity'),
'customer_alert_order_quantity',
('production_mode', 'picture'),
('department_for_customer', 'placement'),
('vat_level', 'automatically_added'),
('is_into_offer', 'is_active', 'is_created_on', 'is_updated_on')
)
list_max_show_all = True
# ordering = ('producer',
# 'department_for_customer',
# 'long_name',)
list_select_related = ('producer', 'department_for_customer')
list_per_page = 100
list_max_show_all = 100
ordering = ('producer',
'department_for_customer',
'long_name',)
search_fields = ('long_name',)
list_filter = ('is_active',
'is_into_offer',
......@@ -531,6 +539,10 @@ class ProductAdmin(SortableAdminMixin, admin.ModelAdmin):
ProductFilterByProducer,)
actions = ['flip_flop_select_for_offer_status', 'duplicate_product' ]
def get_order_unit(self, obj):
return obj.get_order_unit_display()
get_order_unit.short_description = _("order unit")
def flip_flop_select_for_offer_status(self, request, queryset):
for product in queryset.order_by():
product.is_into_offer = not product.is_into_offer
......@@ -540,13 +552,37 @@ class ProductAdmin(SortableAdminMixin, admin.ModelAdmin):
'flip_flop_select_for_offer_status for offer')
def duplicate_product(self, request, queryset):
user_message = _("The product is duplicated.")
user_message_level = messages.INFO
product_count = 0
duplicate_count = 0
for product in queryset:
super(ProductAdmin,self).move_for_duplicate(product)
long_name_prefix = _("COPY_OF_")
max_length = Product._meta.get_field('long_name').max_length
product.long_name = cap(long_name_prefix + product.long_name, max_length)
product.id = None
product.save()
product_count += 1
long_name_postfix = unicode(_(" (COPY)"))
max_length = Product._meta.get_field('long_name').max_length - len(long_name_postfix)
product.long_name = cap(product.long_name, max_length).decode("utf8") + long_name_postfix
product_set = Product.objects.filter(
producer_id = product.producer_id,
long_name = product.long_name).order_by()[:1]
if product_set:
# avoid to break the unique index : producer_id, long_name
pass
else:
product.id = None
product.save()
duplicate_count += 1
if product_count == duplicate_count:
if product_count > 1:
user_message = _("The products are duplicated.")
else:
if product_count == 1:
user_message = _("The product has not been duplicated because a product with the same long name already exists.")
user_message_level = messages.ERROR
else:
user_message = _("At least one product has not been duplicated because a product with the same long name already exists.")
user_message_level = messages.WARNING
self.message_user(request, user_message, user_message_level)
duplicate_product.short_description = _('duplicate product')
......@@ -554,6 +590,7 @@ class ProductAdmin(SortableAdminMixin, admin.ModelAdmin):
form = super(ProductAdmin,self).get_form(request, obj, **kwargs)
# If we are coming from a list screen, use the filter to pre-fill the form
# print form.base_fields
producer = form.base_fields["producer"]
department_for_customer = form.base_fields["department_for_customer"]
production_mode = form.base_fields["production_mode"]
......@@ -630,7 +667,7 @@ class PermanenceBoardInline(admin.TabularInline):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "customer":
kwargs["queryset"] = Customer.objects.all(
).active().not_the_buyinggroup()
).active() # .not_the_buyinggroup()
if db_field.name == "permanence_role":
kwargs["queryset"] = LUT_PermanenceRole.objects.all(
).active()
......@@ -679,12 +716,13 @@ class PermanenceInPreparationAdmin(admin.ModelAdmin):
# ('status', 'automaticaly_closed'),
'automaticaly_closed',
'offer_description',
'order_description',
# 'order_description',
'producers'
)
# readonly_fields = ('status', 'is_created_on', 'is_updated_on')
exclude = ['invoice_description']
list_max_show_all = True
list_per_page = 10
list_max_show_all = 10
filter_horizontal = ('producers',)
# inlines = [PermanenceBoardInline, OfferItemInline]
inlines = [PermanenceBoardInline]
......@@ -698,6 +736,7 @@ class PermanenceInPreparationAdmin(admin.ModelAdmin):
'close_and_send_orders',
'delete_purchases',
'back_to_planified',
'generate_calendar'
]
def get_readonly_fields(self, request, obj=None):
......@@ -718,9 +757,10 @@ class PermanenceInPreparationAdmin(admin.ModelAdmin):
def download_orders(self, request, queryset):
for permanence in queryset[:1]:
if permanence.status==PERMANENCE_OPENED:
if permanence.status >=PERMANENCE_OPENED:
response = HttpResponse(mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
response['Content-Disposition'] = 'attachment; filename=' + unicode(_('Check')) + '.xlsx'
filename = (unicode(_("Check")) + u" - " + permanence.__unicode__() + u'.xlsx').encode('latin-1', errors='ignore')
response['Content-Disposition'] = 'attachment; filename=' + filename
wb = export_orders_xlsx(permanence)
wb.save(response)
return response
......@@ -733,25 +773,27 @@ class PermanenceInPreparationAdmin(admin.ModelAdmin):
current_site = get_current_site(request)
user_message = _("The status of this permanence prohibit you to open and send offers.")
user_message_level = messages.ERROR
now = timezone.now()
for permanence in queryset[:1]:
if permanence.status==PERMANENCE_PLANIFIED:
permanence.status = PERMANENCE_WAIT_FOR_OPEN
permanence.save(update_fields=['status'])
thread.start_new_thread( open_offers_async, (permanence.id, current_site.name) )
permanence.is_updated_on = now
permanence.save(update_fields=['status','is_updated_on'])
thread.start_new_thread( tasks.open_offers, (permanence.id, current_site.name) )
user_message = _("The offers are being generated.")
user_message_level = messages.INFO
elif permanence.status==PERMANENCE_WAIT_FOR_OPEN:
# On demand 15 minutes after the previous attempt, go back to previous status and send alert email
now = datetime.datetime.utcnow().replace(tzinfo=utc)
# use only timediff, -> timezone conversion not needed
timediff = now - permanence.is_updated_on
if timediff.total_seconds() > (15 * 60):
send_alert_email(permanence, current_site.name)
if timediff.total_seconds() > (30 * 60):
thread.start_new_thread( send_alert_email, (permanence, current_site.name) )
permanence.status = PERMANENCE_PLANIFIED
permanence.save(update_fields=['status'])
user_message = _("The action has been canceled by the system and an email send to the site administrator.")
user_message_level = messages.INFO
user_message_level = messages.WARNING
else:
user_message = _("Action canceled by the system.")
user_message = _("Action refused by the system. Please, retry in %d minutes.") % (31 - (int(timediff.total_seconds()) / 60))
user_message_level = messages.WARNING
elif 'cancel' not in request.POST:
opts = self.model._meta
......@@ -775,25 +817,28 @@ class PermanenceInPreparationAdmin(admin.ModelAdmin):
user_message = _("The status of this permanence prohibit you to close it.")
user_message_level = messages.ERROR
current_site = get_current_site(request)
now = timezone.now()
for permanence in queryset[:1]:
if permanence.status==PERMANENCE_OPENED:
permanence.status = PERMANENCE_WAIT_FOR_SEND
permanence.save(update_fields=['status'])
thread.start_new_thread( close_orders_async, (permanence.id, current_site.name) )
permanence.is_updated_on = now
permanence.save(update_fields=['status','is_updated_on'])
thread.start_new_thread( tasks.close_orders, (permanence.id, current_site.name) )
# tasks.close_orders(permanence.id, current_site.name)
user_message = _("The orders are being closed.")
user_message_level = messages.INFO
elif permanence.status==PERMANENCE_WAIT_FOR_SEND:
# On demand 30 minutes after the previous attempt, go back to previous status and send alert email
now = datetime.datetime.utcnow().replace(tzinfo=utc)
# use only timediff, -> timezone conversion not needed
timediff = now - permanence.is_updated_on
if timediff.total_seconds() > (30 * 60):
send_alert_email(permanence, current_site.name)
thread.start_new_thread( send_alert_email, (permanence, current_site.name) )
permanence.status = PERMANENCE_OPENED
permanence.save(update_fields=['status'])
user_message = _("The action has been canceled by the system and an email send to the site administrator.")
user_message_level = messages.INFO
user_message_level = messages.WARNING
else:
user_message = _("Action canceled by the system.")
user_message = _("Action refused by the system. Please, retry in %d minutes.") % (31 - (int(timediff.total_seconds()) / 60))
user_message_level = messages.WARNING
elif 'cancel' not in request.POST:
opts = self.model._meta
......@@ -868,16 +913,27 @@ class PermanenceInPreparationAdmin(admin.ModelAdmin):
delete_purchases.short_description = _('delete purchases')
def get_actions(self, request):
actions = super(PermanenceInPreparationAdmin, self).get_actions(request)
if 'delete_selected' in actions:
del actions['delete_selected']
if not actions:
try:
self.list_display.remove('action_checkbox')
except ValueError:
pass
return actions
def generate_calendar(self, request, queryset):
for permanence in queryset[:1]:
starting_date = permanence.distribution_date
for i in xrange(1,13):
# PermanenceInPreparation used to generate PermanenceBoard when post_save
try:
PermanenceInPreparation.objects.create(distribution_date=starting_date+datetime.timedelta(days=7*i))
except:
pass
generate_calendar.short_description = _("Generate 12 weekly permanences starting from this")
# def get_actions(self, request):
# actions = super(PermanenceInPreparationAdmin, self).get_actions(request)
# if 'delete_selected' in actions:
# del actions['delete_selected']
# if not actions:
# try:
# self.list_display.remove('action_checkbox')
# except ValueError:
# pass
# return actions
def formfield_for_manytomany(self, db_field, request, **kwargs):
if db_field.name == "producers":
......@@ -908,8 +964,9 @@ class PermanenceDoneAdmin(admin.ModelAdmin):
# 'status'
)
readonly_fields = ('status', 'is_created_on', 'is_updated_on', 'automaticaly_closed')
exclude = ['offer_description', 'order_description']
list_max_show_all = True
exclude = ['offer_description',]
list_per_page = 10
list_max_show_all = 10
# inlines = [PermanenceBoardInline, OfferItemInline]
inlines = [PermanenceBoardInline]
date_hierarchy = 'distribution_date'
......@@ -919,6 +976,7 @@ class PermanenceDoneAdmin(admin.ModelAdmin):
'export_xlsx',
'import_xlsx',
'generate_invoices',
'preview_invoices',
'send_invoices',
'cancel_invoices',
]
......@@ -931,55 +989,48 @@ class PermanenceDoneAdmin(admin.ModelAdmin):
return import_permanence_done_xlsx(self, admin, request, queryset)
import_xlsx.short_description = _("Import orders prepared from a XLSX file")
# def get_urls(self):
# urls = super(PermanenceDoneAdmin, self).get_urls()
# my_urls = patterns('',
# (r'\d+/purchase/$', self.admin_site.admin_view(self.purchase)),
# )
# return my_urls + urls
def preview_invoices(self, request, queryset):
current_site = get_current_site(request)
for permanence in queryset[:1]:
if permanence.status==PERMANENCE_DONE:
response = HttpResponse(mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
filename = (unicode(_("Invoice")) + u" - " + permanence.__unicode__() + u'.xlsx').encode('latin-1', errors='ignore')
response['Content-Disposition'] = 'attachment; filename=' + filename
wb = export_invoices_xlsx(permanence=permanence, wb=None, sheet_name=current_site.name)
wb.save(response)
return response
else:
user_message = _("You can only preview invoices when the permanence status is 'done'.")
user_message_level = messages.WARNING
preview_invoices.short_description = _("Preview invoices before sending them by email")
def generate_invoices(self, request, queryset):
user_message = _("Action canceled by the user.")
user_message_level = messages.WARNING
if 'apply' in request.POST:
current_site = get_current_site(request)
permanence_done_pending_set = Permanence.objects.filter(status__in= [PERMANENCE_WAIT_FOR_DONE, PERMANENCE_INVOICES_VALIDATION_FAILED]).order_by()[:1]
user_message = _("The status of this permanence prohibit you to close invoices.")
user_message_level = messages.ERROR
if not permanence_done_pending_set:
# Accept to close only one at the same time because the order of execution is important.
for permanence in queryset[:1]:
if permanence.status==PERMANENCE_SEND:
permanence.status = PERMANENCE_WAIT_FOR_DONE
permanence.save(update_fields=['status'])
thread.start_new_thread( done_async, (permanence.id, permanence.distribution_date, current_site.name) )
user_message = _("The invoices are being generated.")
user_message_level = messages.INFO
else:
permanence_done_pending = permanence_done_pending_set[0]
for permanence in queryset[:1]:
if permanence_done_pending.id == permanence.id:
if permanence_done_pending.status == PERMANENCE_INVOICES_VALIDATION_FAILED:
self.cancel(permanence_done_pending.id)
permanence_done_pending.status = PERMANENCE_WAIT_FOR_DONE
permanence_done_pending.save(update_fields=['status'])
thread.start_new_thread( permanence_done_pending_async, (permanence_done_pending.id, permanence_done_pending.distribution_date, current_site.name) )
user_message = _("The invoices are being generated.")
user_message_level = messages.INFO
else:
# On demand 30 minutes after the previous attempt, go back to previuos status and send alert email
now = datetime.datetime.utcnow().replace(tzinfo=utc)
timediff = now - permanence.is_updated_on
if timediff.total_seconds() > (30 * 60):
send_alert_email(permanence, current_site.name)
permanence.status = PERMANENCE_DONE
permanence.save(update_fields=['status'])
self.cancel(permanence_done_pending.id)
user_message = _("The action has been canceled by the system and an email send to the site administrator.")
user_message_level = messages.INFO
else:
user_message = _("Action canceled by the system.")
user_message_level = messages.WARNING
# user_message = _("The status of another permanence prohibit you to close invoices of this permanence.")
# user_message_level = messages.ERROR
# permanence_done_pending_set = Permanence.objects.filter(status__in= [PERMANENCE_WAIT_FOR_DONE, PERMANENCE_INVOICES_VALIDATION_FAILED]).order_by()[:1]
# if permanence_done_pending_set:
# pass
# else:
# Accept to close only one at the same time because the order of execution is important.
for permanence in queryset[:1]:
if permanence.status==PERMANENCE_SEND:
# permanence.status = PERMANENCE_WAIT_FOR_DONE
# permanence.save(update_fields=['status'])
# thread.start_new_thread( tasks.done, (permanence.id, permanence.distribution_date, current_site.name) )
tasks.done(permanence.id, permanence.distribution_date, current_site.name)
user_message = _("Action performed.")
user_message_level = messages.INFO
else:
if permanence.status == PERMANENCE_INVOICES_VALIDATION_FAILED:
user_message = _("The permanence status says there is an error. You must cancel the invoice then correct, before retrying.")
user_message_level = messages.WARNING
else:
user_message = _("You can only generate invoices when the permanence status is 'send'.")
user_message_level = messages.WARNING
elif 'cancel' not in request.POST:
opts = self.model._meta
app_label = opts.app_label
......@@ -1002,9 +1053,10 @@ class PermanenceDoneAdmin(admin.ModelAdmin):
current_site = get_current_site(request)
for permanence in queryset[:1]:
if permanence.status==PERMANENCE_DONE:
thread.start_new_thread( email_invoices_async, (permanence.id, current_site.name) )
thread.start_new_thread( tasks.email_invoices, (permanence.id, current_site.name) )
# tasks.email_invoices(permanence.id, current_site.name)
user_message = _("Emails containing the invoices will be send to the customers and the producers.")
user_message_level = messages.ERROR
user_message_level = messages.INFO
else:
user_message = _("The status of this permanence prohibit you to send invoices.")
user_message_level = messages.ERROR
......@@ -1027,17 +1079,21 @@ class PermanenceDoneAdmin(admin.ModelAdmin):
user_message = _("Action canceled by the user.")
user_message_level = messages.WARNING
if 'apply' in request.POST:
# TODO : Use the bank account total record
latest_customer_invoice_set=CustomerInvoice.objects.order_by('-id')[:1]
if latest_customer_invoice_set:
user_message = _("The status of this permanence prohibit you to close invoices.")
user_message_level = messages.ERROR
for permanence in queryset[:1]:
if permanence.status == PERMANENCE_DONE:
if permanence.status in [PERMANENCE_WAIT_FOR_DONE, PERMANENCE_INVOICES_VALIDATION_FAILED, PERMANENCE_DONE] :
if latest_customer_invoice_set[0].permanence.id == permanence.id:
# This is well the latest closed permanence. The invoices can be cancelled without damages.
self.cancel(permanence.id)
user_message = _("The selected invoice has been canceled.")
user_message_level = messages.INFO
# else:
# user_message = _("Please retry later, an operation on the bank account is ongoing.")
# user_message_level = messages.WARNING
else:
user_message = _("The selected invoice is not the latest invoice.")
user_message_level = messages.ERROR
......@@ -1062,6 +1118,13 @@ class PermanenceDoneAdmin(admin.ModelAdmin):
def cancel(self, permanence_id):
# Lock BankAccount for update
# lock = BankAccount.objects.filter(
# operation_status=BANK_LATEST_TOTAL).order_by().update(
# operation_status=BANK_CALCULTAING_LATEST_TOTAL)
# if lock != 1 :
# return False