diff options
-rw-r--r-- | servo/forms/notes.py | 23 | ||||
-rw-r--r-- | servo/models/order.py | 21 | ||||
-rwxr-xr-x | servo/templates/notes/form.html | 9 | ||||
-rw-r--r-- | servo/views/note.py | 44 | ||||
-rw-r--r-- | servo/views/order.py | 53 | ||||
-rw-r--r-- | settings.py | 69 |
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_' } |