aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--servo/forms/notes.py23
-rw-r--r--servo/models/order.py21
-rwxr-xr-xservo/templates/notes/form.html9
-rw-r--r--servo/views/note.py44
-rw-r--r--servo/views/order.py53
-rw-r--r--settings.py69
6 files changed, 119 insertions, 100 deletions
diff --git a/servo/forms/notes.py b/servo/forms/notes.py
index d6ed693..a5b0bb4 100644
--- a/servo/forms/notes.py
+++ b/servo/forms/notes.py
@@ -2,9 +2,7 @@
import json
from django import forms
-from gsxws import escalations
from django.core.urlresolvers import reverse
-from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
from servo.models import Note, Escalation, Template
@@ -12,17 +10,23 @@ from servo.forms import BaseModelForm, AutocompleteTextarea, TextInput
class NoteForm(BaseModelForm):
+ attach_confirmation = forms.BooleanField(
+ label=_('Attach confirmation'),
+ help_text=_('Attach order confirmation as a PDF'),
+ required=False
+ )
+
class Meta:
model = Note
exclude = []
widgets = {
- 'recipient' : TextInput,
- 'subject' : TextInput,
- 'order' : forms.HiddenInput,
- 'parent' : forms.HiddenInput,
- 'customer' : forms.HiddenInput,
- 'type' : forms.HiddenInput,
- 'labels' : forms.CheckboxSelectMultiple,
+ 'recipient': TextInput,
+ 'subject': TextInput,
+ 'order': forms.HiddenInput,
+ 'parent': forms.HiddenInput,
+ 'customer': forms.HiddenInput,
+ 'type': forms.HiddenInput,
+ 'labels': forms.CheckboxSelectMultiple,
}
def __init__(self, *args, **kwargs):
@@ -71,4 +75,3 @@ class EscalationForm(BaseModelForm):
class Meta:
model = Escalation
fields = ('issue_type', 'status', 'gsx_account', 'contexts',)
-
diff --git a/servo/models/order.py b/servo/models/order.py
index 8121024..9640029 100644
--- a/servo/models/order.py
+++ b/servo/models/order.py
@@ -277,6 +277,25 @@ class Order(models.Model):
return user.location.user_set.filter(is_active=True)
+ def get_print_dict(self, kind='confirmation'):
+ """
+ Return context dict for printing this order
+ """
+ r = {}
+ r['order'] = self
+ r['conf'] = Configuration.conf()
+ r['title'] = _(u"Service Order #%s") % self.code
+ r['notes'] = self.note_set.filter(is_reported=True)
+
+ if kind == 'receipt':
+ try:
+ # Include the latest invoice data for receipts
+ r['invoice'] = self.invoice_set.latest()
+ except Exception as e:
+ pass
+
+ return r
+
def get_title(self):
"""
Returns a human-readable title for this order, based on various criteria
@@ -524,7 +543,7 @@ class Order(models.Model):
def unset_status(self, user):
if self.is_closed:
- return # fail silently
+ return # fail silently
self.status = None
self.status_started_at = None
diff --git a/servo/templates/notes/form.html b/servo/templates/notes/form.html
index ae0f019..d471385 100755
--- a/servo/templates/notes/form.html
+++ b/servo/templates/notes/form.html
@@ -50,7 +50,14 @@
{% include "form_field_snippet.html" with field=form.subject %}
{% include "form_field_snippet.html" with field=form.sender %}
{% include "form_field_snippet.html" with field=form.body %}
- {% include "form_field_snippet.html" with field=form.is_reported %}
+ {% if note.order %}
+ <table>
+ <tr>
+ <td>{% include "form_field_snippet.html" with field=form.is_reported %}</td>
+ <td>{% include "form_field_snippet.html" with field=form.attach_confirmation %}</td>
+ </tr>
+ </table>
+ {% endif %}
{{ form.type }}
</div>
<div class="tab-pane" id="tab2">
diff --git a/servo/views/note.py b/servo/views/note.py
index c3b0b99..9672a84 100644
--- a/servo/views/note.py
+++ b/servo/views/note.py
@@ -10,34 +10,34 @@ from django.http import HttpResponse
from django.utils.translation import ugettext as _
from django.forms.models import modelformset_factory
from django.views.decorators.csrf import csrf_exempt
-from django.views.decorators.cache import cache_page
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.decorators import permission_required
+from django.core.files.base import ContentFile
+
from reportlab.lib.units import mm
from reportlab.graphics.shapes import Drawing
from reportlab.graphics.barcode import createBarcodeDrawing
from servo.lib.utils import paginate
from servo.models import (Order, Template, Tag, Customer, Note,
- Attachment, Escalation, Article,)
+ Attachment, Escalation, Article,)
from servo.forms import NoteForm, NoteSearchForm, EscalationForm
class BarcodeDrawing(Drawing):
+ """Pretty generic Reportlab drawing to render barcodes."""
def __init__(self, text_value, *args, **kwargs):
barcode = createBarcodeDrawing("Code128",
value=text_value.encode("utf-8"),
- barHeight=10*mm,
- width=80*mm)
+ barHeight=10 * mm,
+ width=80 * mm)
Drawing.__init__(self, barcode.width, barcode.height, *args, **kwargs)
self.add(barcode, name="barcode")
def show_barcode(request, text):
- """
- Returns text as a barcode
- """
+ """Return text as a barcode."""
if request.GET.get('f') == 'svg':
import barcode
output = StringIO.StringIO()
@@ -52,9 +52,7 @@ def show_barcode(request, text):
def prep_list_view(request, kind):
- """
- Prepares the view for listing notes/messages
- """
+ """Prepare the view for listing notes/messages."""
data = {'title': _("Messages")}
all_notes = Note.objects.all().order_by("-created_at")
@@ -82,10 +80,7 @@ def prep_list_view(request, kind):
@permission_required('servo.change_note')
def copy(request, pk):
- """
- Copies a note with its attachments and labels
- """
- from servo.lib.shorturl import from_time
+ """Copy a note with its attachments and labels."""
note = get_object_or_404(Note, pk=pk)
new_note = Note(created_by=request.user)
@@ -96,7 +91,7 @@ def copy(request, pk):
new_note.labels = note.labels.all()
- for a in note.attachments.all(): # also copy the attachments
+ for a in note.attachments.all(): # also copy the attachments
a.pk = None
a.content_object = new_note
a.save()
@@ -106,10 +101,11 @@ def copy(request, pk):
@permission_required('servo.change_note')
-def edit(request, pk=None, order_id=None, parent=None, recipient=None,
- customer=None):
+def edit(request, pk=None, order_id=None,
+ parent=None, recipient=None, customer=None):
"""
- Edits a note
+ Edit a note.
+
@FIXME: Should split this up into smaller pieces
"""
to = []
@@ -220,6 +216,18 @@ def edit(request, pk=None, order_id=None, parent=None, recipient=None,
return redirect(note)
note.attachments.add(*files)
+
+ if form.cleaned_data.get('attach_confirmation'):
+ from servo.views.order import put_on_paper
+ response = put_on_paper(request, note.order_id, fmt='pdf')
+ filename = response.filename
+ content = response.render().content
+ content = ContentFile(content, filename)
+ attachment = Attachment(content=content, content_object=note)
+ attachment.save()
+ attachment.content.save(filename, content)
+ note.attachments.add(attachment)
+
note.save()
try:
diff --git a/servo/views/order.py b/servo/views/order.py
index 835aaf4..6853ee4 100644
--- a/servo/views/order.py
+++ b/servo/views/order.py
@@ -17,12 +17,13 @@ from django.db import DatabaseError
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
-from django.views.decorators.cache import cache_page
from django.shortcuts import render, redirect, get_object_or_404
from django.views.decorators.csrf import csrf_exempt
from django.contrib.auth.decorators import permission_required
+from wkhtmltopdf.views import PDFTemplateView
+
from servo.lib.utils import paginate
from servo.models.order import *
@@ -31,7 +32,6 @@ from servo.forms.repairs import StatusForm
from servo.models import Note, User, Device, Customer
from servo.models.common import (Tag,
- Configuration,
FlaggedItem,
GsxAccount,)
from servo.models.repair import (Checklist,
@@ -191,9 +191,7 @@ def prepare_detail_view(request, pk):
@permission_required("servo.change_order")
def close(request, pk):
- """
- Closes this Service Order
- """
+ """Close this Service Order."""
order = get_object_or_404(Order, pk=pk)
if request.method == 'POST':
@@ -218,6 +216,7 @@ def close(request, pk):
@permission_required("servo.delete_order")
def reopen_order(request, pk):
+ """Open a closed order."""
order = get_object_or_404(Order, pk=pk)
msg = order.reopen(request.user)
messages.success(request, msg)
@@ -225,7 +224,10 @@ def reopen_order(request, pk):
@permission_required("servo.add_order")
-def create(request, sn=None, device_id=None, product_id=None, note_id=None, customer_id=None):
+def create(request, sn=None, device_id=None,
+ product_id=None,
+ note_id=None,
+ customer_id=None):
"""Create a new Service Order."""
order = Order(created_by=request.user)
@@ -554,32 +556,29 @@ def update_order(request, pk, what, what_id):
return redirect(order)
-def put_on_paper(request, pk, kind="confirmation"):
+def put_on_paper(request, pk, kind="confirmation", fmt='html'):
"""
'Print' was taken?
"""
- conf = Configuration.conf()
order = get_object_or_404(Order, pk=pk)
-
- title = _(u"Service Order #%s") % order.code
- notes = order.note_set.filter(is_reported=True)
-
+ data = order.get_print_dict(kind)
template = order.get_print_template(kind)
-
- if kind == "receipt":
- try:
- invoice = order.invoice_set.latest()
- except Exception as e:
- pass
- return render(request, template, locals())
+ if fmt == 'pdf':
+ fn = data.get('title') + '.pdf'
+ view = PDFTemplateView(request=request, template_name=template,
+ filename=fn)
+ return view.render_to_response(data)
+
+ return render(request, template, data)
@permission_required("servo.change_order")
def add_device(request, pk, device_id=None, sn=None):
"""
- Adds a device to a service order
- using device_id with existing devices or
+ Add a device to a service order.
+
+ Use device_id with existing devices or
sn for new devices (which should have gone through GSX search)
"""
order = get_object_or_404(Order, pk=pk)
@@ -670,9 +669,7 @@ def reserve_products(request, pk):
@permission_required("servo.change_order")
def edit_product(request, pk, item_id):
- """
- Edits a product added to an order
- """
+ """Edit a product added to an order."""
order = Order.objects.get(pk=pk)
item = get_object_or_404(ServiceOrderItem, pk=item_id)
@@ -857,9 +854,7 @@ def choose_customer(request, pk):
if len(query) > 2:
customers = Customer.objects.filter(
- Q(fullname__icontains=query)
- | Q(email__icontains=query)
- | Q(phone__contains=query)
+ Q(fullname__icontains=query) | Q(email__icontains=query) | Q(phone__contains=query)
)
if kind == 'companies':
@@ -936,8 +931,8 @@ def download_results(request):
writer.writerow(header)
for o in request.session['order_queryset']:
- row = [o.code, o.customer, o.created_at,
- o.user, o.checkin_location, o.location]
+ row = [o.code, o.customer, o.created_at,
+ o.user, o.checkin_location, o.location]
coded = [unicode(s).encode('utf-8') for s in row]
writer.writerow(coded)
diff --git a/settings.py b/settings.py
index 74f01ae..a7b0889 100644
--- a/settings.py
+++ b/settings.py
@@ -14,7 +14,7 @@ MESSAGE_TAGS = {
}
BASE_DIR = os.path.dirname(__file__)
-APP_DIR = os.path.join(BASE_DIR, 'servo')
+APP_DIR = os.path.join(BASE_DIR, 'servo')
ADMINS = (
('ServoApp Support', 'support@servoapp.com'),
@@ -32,55 +32,30 @@ LANGUAGES = (
)
SITE_ID = 1
-
-# If you set this to False, Django will make some optimizations so as not
-# to load the internationalization machinery.
+USE_TZ = True
USE_I18N = True
-
-# If you set this to False, Django will not format dates, numbers and
-# calendars according to the current locale.
USE_L10N = True
USE_THOUSAND_SEPARATOR = True
-# If you set this to False, Django will not use timezone-aware datetimes.
-USE_TZ = True
-
# Absolute filesystem path to the directory that will hold user-uploaded files.
-# Example: "/home/media/media.lawrence.com/media/"
MEDIA_ROOT = os.path.join(BASE_DIR, 'uploads')
-TEMP_ROOT = os.path.join(MEDIA_ROOT, 'temp')
-
-# URL that handles the media served from MEDIA_ROOT. Make sure to use a
-# trailing slash.
-# Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
MEDIA_URL = '/files/'
-# Absolute path to the directory static files should be collected to.
-# Don't put anything in this directory yourself; store your static files
-# in apps' "static/" subdirectories and in STATICFILES_DIRS.
-# Example: "/home/media/media.lawrence.com/static/"
-STATIC_ROOT = os.path.dirname(os.path.join(BASE_DIR, 'static'))
-
-# Additional locations of static files
-STATICFILES_DIRS = (
- 'static',
-)
-
-# URL prefix for static files.
-# Example: "http://media.lawrence.com/static/"
+STATIC_ROOT = os.path.join(APP_DIR, 'static')
STATIC_URL = '/static/'
-
-MESSAGE_STORAGE = 'django.contrib.messages.storage.fallback.FallbackStorage'
-
-# List of finder classes that know how to find static files in
-# various locations.
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
#'django.contrib.staticfiles.finders.DefaultStorageFinder',
)
+# Absolute path to the directory that will hold temporary files.
+TEMP_ROOT = os.path.join(MEDIA_ROOT, 'temp')
+
+MESSAGE_STORAGE = 'django.contrib.messages.storage.fallback.FallbackStorage'
+
MIDDLEWARE_CLASSES = (
+ 'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware',
@@ -109,6 +84,7 @@ TEMPLATES = [
),
'OPTIONS': {
'context_processors': (
+ #'django.template.context_processors.debug',
'django.contrib.auth.context_processors.auth',
'django.template.context_processors.static',
'django.template.context_processors.request',
@@ -128,7 +104,7 @@ INSTALLED_APPS = (
'django.contrib.sites',
'django.contrib.sessions',
#'debug_toolbar',
- 'rest_framework',
+ 'rest_framework', 'wkhtmltopdf',
'rest_framework.authtoken',
'mptt', 'bootstrap3',
'servo',
@@ -172,8 +148,8 @@ FILE_UPLOAD_HANDLERS = ("django_excel.ExcelMemoryFileUploadHandler",
SESSION_ENGINE = "django.contrib.sessions.backends.cached_db"
EXEMPT_URLS = []
-LOGIN_URL = '/login/'
-LOGOUT_URL = '/logout/'
+LOGIN_URL = '/login/'
+LOGOUT_URL = '/logout/'
# URLs that should work without logging in
LOGIN_EXEMPT_URLS = [
@@ -215,13 +191,13 @@ REST_FRAMEWORK = {
ENABLE_RULES = False
TIMEZONE = 'Europe/Helsinki'
-BACKUP_DIR = os.path.join(BASE_DIR, 'backups')
+BACKUP_DIR = os.path.join(BASE_DIR, 'backups')
GSX_CERT = os.path.join(BASE_DIR, 'uploads/settings/gsx_cert.pem')
-GSX_KEY = os.path.join(BASE_DIR, 'uploads/settings/gsx_key.pem')
+GSX_KEY = os.path.join(BASE_DIR, 'uploads/settings/gsx_key.pem')
os.environ['GSX_CERT'] = GSX_CERT
-os.environ['GSX_KEY'] = GSX_KEY
+os.environ['GSX_KEY'] = GSX_KEY
CELERYBEAT_SCHEDULE = {
'check_mail': {
@@ -230,11 +206,22 @@ CELERYBEAT_SCHEDULE = {
},
}
+WKHTMLTOPDF_ENV = {
+ 'ignore_404': 'True'
+}
+
+WKHTMLTOPDF_CMD = '/usr/local/bin/wkhtmltopdf'
+WKHTMLTOPDF_CMD_OPTIONS = {
+ 'quiet': True,
+ 'zoom': '2.5',
+ 'page-size': 'a4'
+}
+
from local_settings import *
CACHES['comptia'] = {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': '127.0.0.1:11211',
- 'TIMEOUT': 60*60*24,
+ 'TIMEOUT': 60 * 60 * 24,
'KEY_PREFIX': 'comptia_'
}