diff options
Diffstat (limited to 'servo/views/device.py')
-rw-r--r-- | servo/views/device.py | 605 |
1 files changed, 605 insertions, 0 deletions
diff --git a/servo/views/device.py b/servo/views/device.py new file mode 100644 index 0000000..f35bd99 --- /dev/null +++ b/servo/views/device.py @@ -0,0 +1,605 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2013, First Party Software +# All rights reserved. + +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: + +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. + +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. + +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +import gsxws + +from django.db.models import Q +from django.contrib import messages + +from django.core.cache import cache +from django.shortcuts import render, redirect, get_object_or_404 + +from django.utils.translation import ugettext as _ +from django.template.defaultfilters import slugify +from django.views.decorators.cache import cache_page +from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger + +from servo.models import Device, Product, GsxAccount, ServiceOrderItem +from servo.forms.devices import DeviceForm, DeviceUploadForm, DeviceSearchForm + +class RepairDiagnosticResults: + pass + +class DiagnosticResults(object): + def __init__(self, diags): + if not diags.diagnosticTestData: + raise gsxws.GsxError('Missing diagnostic data') + + self.diags = dict(result={}, profile={}, report={}) + + for r in diags.diagnosticTestData.testResult.result: + self.diags['result'][r.name] = r.value + + for r in diags.diagnosticProfileData.profile.unit.key: + self.diags['profile'][r.name] = r.value + + for r in diags.diagnosticProfileData.report.reportData.key: + self.diags['report'][r.name] = r.value + + def __iter__(self): + return iter(self.diags) + + +def model_from_slug(product_line, model=None): + """ + Returns product description for model slug or models dict for + the specified product line + """ + if not cache.get("slugmap"): + slugmap = {} # Map model slug to corresponding product description + product_lines = gsxws.products.models() + + for k, v in product_lines.items(): + d = {} + for p in v['models']: + slug = slugify(p) + d[slug] = p + + slugmap[k] = d + + cache.set("slugmap", slugmap) + + models = cache.get("slugmap").get(product_line) + + if model is not None: + return models.get(model) + + return models + + +def prep_list_view(request, product_line=None, model=None): + title = _('Devices') + all_devices = Device.objects.all() + product_lines = gsxws.products.models() + + if product_line is None: + product_line = product_lines.keys()[0] + + models = model_from_slug(product_line) + + if model is None: + model = models.keys()[0] + title = product_lines[product_line]['name'] + else: + title = models.get(model) + + if product_line == "OTHER": + all_devices = all_devices.filter(product_line=product_line) + else: + all_devices = all_devices.filter(slug=model) + + page = request.GET.get('page') + paginator = Paginator(all_devices, 50) + + try: + devices = paginator.page(page) + except PageNotAnInteger: + devices = paginator.page(1) + except EmptyPage: + devices = paginator.page(paginator.num_pages) + + return locals() + + +def prep_detail_view(request, pk, product_line=None, model=None): + if pk is None: + device = Device() + else: + device = Device.objects.get(pk=pk) + + data = prep_list_view(request, product_line, model) + + data['device'] = device + data['title'] = device.description + + return data + + +def index(request, product_line=None, model=None): + if request.session.get('return_to'): + del(request.session['return_to']) + + data = prep_list_view(request, product_line, model) + + if data['all_devices'].count() > 0: + return redirect(data['all_devices'].latest()) + + return render(request, "devices/index.html", data) + + +def delete_device(request, product_line, model, pk): + dev = Device.objects.get(pk=pk) + + if request.method == 'POST': + from django.db.models import ProtectedError + try: + dev.delete() + messages.success(request, _("Device deleted")) + except ProtectedError: + messages.error(request, _("Cannot delete device with GSX repairs")) + return redirect(dev) + + return redirect(index) + + data = {'action': request.path} + data['device'] = dev + + return render(request, "devices/remove.html", data) + + +def edit_device(request, pk=None, product_line=None, model=None): + """ + Edits an existing device or adds a new one + """ + device = Device() + device.sn = request.GET.get('sn', '') + + if product_line is not None: + device.product_line = product_line + + if model is not None: + device.product_line = product_line + device.description = model_from_slug(product_line, model) + + if pk is not None: + device = Device.objects.get(pk=pk) + + form = DeviceForm(instance=device) + + if request.method == "POST": + + form = DeviceForm(request.POST, request.FILES, instance=device) + + if form.is_valid(): + device = form.save() + messages.success(request, _(u"%s saved") % device.description) + device.add_tags(request.POST.getlist('tag')) + + return redirect(view_device, + pk=device.pk, + product_line=device.product_line, + model=device.slug) + + data = prep_detail_view(request, pk, product_line, model) + data['form'] = form + + return render(request, 'devices/form.html', data) + + +def view_device(request, pk, product_line=None, model=None): + data = prep_detail_view(request, pk, product_line, model) + return render(request, "devices/view.html", data) + + +def diagnostics(request, pk): + """ + Fetches MRI diagnostics or initiates iOS diags from GSX + """ + device = get_object_or_404(Device, pk=pk) + + if request.GET.get('a') == 'init': + if request.method == 'POST': + from gsxws import diagnostics + order = request.POST.get('order') + order = device.order_set.get(pk=order) + email = request.POST.get('email') + diag = diagnostics.Diagnostics(serialNumber=device.sn) + diag.emailAddress = email + diag.shipTo = order.location.gsx_shipto + + try: + GsxAccount.default(request.user) + res = diag.initiate() + msg = _('Diagnostics initiated - diags://%s') % res + order.notify("init_diags", msg, request.user) + messages.success(request, msg) + except gsxws.GsxError, e: + messages.error(request, e) + + return redirect(order) + + order = request.GET.get('order') + order = device.order_set.get(pk=order) + customer = order.customer + url = request.path + return render(request, "devices/diagnostic_init.html", locals()) + + if request.GET.get('a') == 'get': + try: + diagnostics = device.get_diagnostics(request.user) + if device.is_ios(): + diagnostics = DiagnosticResults(diagnostics) + return render(request, "devices/diagnostic_ios.html", locals()) + return render(request, "devices/diagnostic_results.html", locals()) + except gsxws.GsxError, e: + return render(request, "devices/diagnostic_error.html", {'error': e}) + + return render(request, "devices/diagnostics.html", locals()) + + +def get_gsx_search_results(request, what, param, query): + """ + The second phase of a GSX search. + There should be an active GSX session open at this stage. + """ + data = {} + results = [] + query = query.upper() + device = Device(sn=query) + error_template = "search/results/gsx_error.html" + + # @TODO: this isn't a GSX search. Move it somewhere else. + if what == "orders": + try: + if param == 'serialNumber': + device = Device.objects.get(sn__exact=query) + if param == 'alternateDeviceId': + device = Device.objects.get(imei__exact=query) + except (Device.DoesNotExist, ValueError,): + return render(request, "search/results/gsx_notfound.html") + + orders = device.order_set.all() + return render(request, "orders/list.html", locals()) + + if what == "warranty": + # Update wty info if been here before + try: + device = Device.objects.get(sn__exact=query) + device.update_gsx_details() + except Exception: + try: + device = Device.from_gsx(query) + except Exception, e: + return render(request, error_template, {'message': e}) + + results.append(device) + + # maybe it's a device we've already replaced... + try: + soi = ServiceOrderItem.objects.get(sn__iexact=query) + results[0].repeat_service = soi.order + except ServiceOrderItem.DoesNotExist: + pass + + if what == "parts": + # looking for parts + if param == "partNumber": + # ... with a part number + part = gsxws.Part(partNumber=query) + + try: + partinfo = part.lookup() + except gsxws.GsxError, e: + return render(request, error_template, {'message': e}) + + product = Product.from_gsx(partinfo) + cache.set(query, product) + results.append(product) + + if param == "serialNumber": + # ... with a serial number + try: + results = device.get_parts() + data['device'] = device + except Exception, e: + return render(request, error_template, {'message': e}) + + if param == "productName": + product = gsxws.Product(productName=query) + parts = product.parts() + for p in parts: + results.append(Product.from_gsx(p)) + + if what == "repairs": + # Looking for GSX repairs + if param == "serialNumber": + # ... with a serial number + try: + device = gsxws.Product(query) + #results = device.repairs() + # @TODO: move the encoding hack to py-gsxws + for i, p in enumerate(device.repairs()): + d = {'purchaseOrderNumber': p.purchaseOrderNumber} + d['repairConfirmationNumber'] = p.repairConfirmationNumber + d['createdOn'] = p.createdOn + d['customerName'] = p.customerName.encode('utf-8') + d['repairStatus'] = p.repairStatus + results.append(d) + except gsxws.GsxError, e: + return render(request, "search/results/gsx_notfound.html") + + elif param == "dispatchId": + # ... with a repair confirmation number + repair = gsxws.Repair(number=query) + try: + results = repair.lookup() + except gsxws.GsxError, message: + return render(request, error_template, locals()) + + return render(request, "devices/search_gsx_%s.html" % what, locals()) + + +def search_gsx(request, what, param, query): + """ + The first phase of a GSX search + """ + title = _(u'Search results for "%s"') % query + + try: + act = request.session.get("gsx_account") + act = None + if act is None: + GsxAccount.default(user=request.user) + else: + act.connect(request.user) + except gsxws.GsxError, message: + return render(request, "devices/search_gsx_error.html", locals()) + + if request.is_ajax(): + if what == "parts": + try: + dev = Device.from_gsx(query) + products = dev.get_parts() + return render(request, "devices/parts.html", locals()) + except gsxws.GsxError, message: + return render(request, "search/results/gsx_error.html", locals()) + + return get_gsx_search_results(request, what, param, query) + + return render(request, "devices/search_gsx.html", locals()) + + +def search(request): + """ + Searching for devices from the main navbar + """ + query = request.GET.get("q", '').strip() + request.session['search_query'] = query + + query = query.upper() + valid_arg = gsxws.validate(query) + + if valid_arg in ('serialNumber', 'alternateDeviceId',): + return redirect(search_gsx, "warranty", valid_arg, query) + + devices = Device.objects.filter( + Q(sn__icontains=query) | Q(description__icontains=query) + ) + + title = _(u'Devices matching "%s"') % query + + return render(request, "devices/search.html", locals()) + + +def find(request): + """ + Searching for device from devices/find + """ + title = _("Device search") + form = DeviceSearchForm() + results = Device.objects.none() + + if request.method == 'POST': + form = DeviceSearchForm(request.POST) + if form.is_valid(): + fdata = form.cleaned_data + results = Device.objects.all() + + if fdata.get("product_line"): + results = results.filter(product_line__in=fdata['product_line']) + if fdata.get("warranty_status"): + results = results.filter(warranty_status__in=fdata['warranty_status']) + if fdata.get("description"): + results = results.filter(description__icontains=fdata['description']) + if fdata.get("sn"): + results = results.filter(sn__icontains=fdata['sn']) + if fdata.get("date_start"): + results = results.filter(created_at__range=[fdata['date_start'], + fdata['date_end']]) + + paginator = Paginator(results, 100) + page = request.GET.get("page") + + try: + devices = paginator.page(page) + except PageNotAnInteger: + devices = paginator.page(1) + except EmptyPage: + devices = paginator.page(paginator.num_pages) + + return render(request, "devices/find.html", locals()) + + +#@cache_page(60*5) +def parts(request, pk, order_id, queue_id): + """ + Lists available parts for this device/order + taking into account the order's queues GSX Sold-To + and the Location's corresponding GSX account + """ + from decimal import InvalidOperation + + device = Device.objects.get(pk=pk) + order = device.order_set.get(pk=order_id) + + try: + # remember the right GSX account + act = GsxAccount.default(request.user, order.queue) + request.session['gsx_account'] = act.pk + products = device.get_parts() + except gsxws.GsxError as message: + return render(request, "search/results/gsx_error.html", locals()) + except AttributeError: + message = _('Invalid serial number for parts lookup') + return render(request, "search/results/gsx_error.html", locals()) + except InvalidOperation: + message = _('Error calculating prices. Please check your system settings.') + return render(request, "search/results/gsx_error.html", locals()) + + return render(request, "devices/parts.html", locals()) + + +def model_parts(request, product_line=None, model=None): + """ + Shows parts for this device model + """ + data = prep_list_view(request, product_line, model) + + if cache.get("slugmap") and model: + models = cache.get("slugmap")[product_line] + data['what'] = "parts" + data['param'] = "productName" + data['query'] = models[model] + data['products'] = Product.objects.filter(tags__tag=data['query']) + + return render(request, "devices/index.html", data) + + +def choose(request, order_id): + """ + Choosing a device from within an SRO + Does GSX lookup in case device is not found locally + """ + context = {'order': order_id} + + if request.method == "POST": + + query = request.POST.get('q').upper() + results = Device.objects.filter(Q(sn__iexact=query) | Q(imei=query)) + + if len(results) < 1: + try: + current_order = request.session.get("current_order_id") + current_order = Order.objects.get(pk=current_order) + if current_order and current_order.queue: + GsxAccount.default(request.user, current_order.queue) + else: + GsxAccount.default(request.user) + results = [Device.from_gsx(query)] + except Exception as e: + context['error'] = e + return render(request, "devices/choose-error.html", context) + + context['results'] = results + return render(request, "devices/choose-list.html", context) + + return render(request, "devices/choose.html", context) + + +def upload_devices(request): + """ + User uploads device DB as tab-delimited CSV file + SN USERNAME PASSWORD NOTES + """ + gsx_account = None + form = DeviceUploadForm() + + if request.method == "POST": + form = DeviceUploadForm(request.POST, request.FILES) + + if form.is_valid(): + i = 0 + df = form.cleaned_data['datafile'].read() + + if form.cleaned_data.get('do_warranty_check'): + gsx_account = GsxAccount.default(request.user) + + for l in df.split("\r"): + l = l.decode("latin-1").encode("utf-8") + row = l.strip().split("\t") + + if gsx_account: + try: + device = Device.from_gsx(row[0]) + except Exception, e: + messages.error(request, e) + break + else: + device = Device.objects.get_or_create(sn=row[0])[0] + + try: + device.username = row[1] + device.password = row[2] + device.notes = row[3] + except IndexError: + pass + + device.save() + i += 1 + + if form.cleaned_data.get("customer"): + customer = form.cleaned_data['customer'] + customer.devices.add(device) + + messages.success(request, _("%d devices imported") % i) + + return redirect(index) + + data = {'form': form, 'action': request.path} + return render(request, "devices/upload_devices.html", data) + + +def update_gsx_details(request, pk): + """ + Updates devices GSX warranty details + """ + device = get_object_or_404(Device, pk=pk) + try: + GsxAccount.default(request.user) + device.update_gsx_details() + messages.success(request, _("Warranty status updated successfully")) + except Exception, e: + messages.error(request, e) + + if request.session.get('return_to'): + return redirect(request.session['return_to']) + + return redirect(device) + + +def get_info(request, pk): + device = get_object_or_404(Device, pk=pk) + return render(request, "devices/get_info.html", locals()) |