PageRenderTime 8ms CodeModel.GetById 3ms app.highlight 67ms RepoModel.GetById 1ms app.codeStats 0ms

/odk_viewer/views.py

https://github.com/zoulema/formhub
Python | 736 lines | 708 code | 25 blank | 3 comment | 17 complexity | ef127173d6ddd5b5d8668bafdb4b1d87 MD5 | raw file
  1import json
  2import os
  3from datetime import datetime
  4from tempfile import NamedTemporaryFile
  5from time import strftime, strptime
  6
  7from django.views.decorators.http import require_POST
  8from django.contrib.auth.models import User
  9from django.core.urlresolvers import reverse
 10from django.http import HttpResponseForbidden,\
 11    HttpResponseRedirect, HttpResponseNotFound, HttpResponseBadRequest,\
 12    HttpResponse
 13from django.shortcuts import render_to_response, get_object_or_404, redirect
 14from django.template import RequestContext
 15from django.utils.translation import ugettext as _
 16from django.core.files.storage import FileSystemStorage
 17from django.core.files.storage import get_storage_class
 18
 19from main.models import UserProfile, MetaData, TokenStorageModel
 20from odk_logger.models import XForm, Attachment
 21from odk_logger.views import download_jsonform
 22from odk_viewer.models import DataDictionary, ParsedInstance
 23from odk_viewer.pandas_mongo_bridge import NoRecordsFoundError
 24from utils.image_tools import image_url
 25from xls_writer import XlsWriter
 26from utils.logger_tools import response_with_mimetype_and_name,\
 27    disposition_ext_and_date
 28from utils.viewer_tools import image_urls
 29from odk_viewer.tasks import create_async_export
 30from utils.user_auth import has_permission, get_xform_and_perms,\
 31    helper_auth_helper
 32from utils.google import google_export_xls, redirect_uri
 33# TODO: using from main.views import api breaks the application, why?
 34from odk_viewer.models import Export
 35from utils.export_tools import generate_export, should_create_new_export
 36from utils.export_tools import kml_export_data
 37from utils.export_tools import newset_export_for
 38from utils.viewer_tools import export_def_from_filename
 39from utils.viewer_tools import create_attachments_zipfile
 40from utils.log import audit_log, Actions
 41from common_tags import SUBMISSION_TIME
 42
 43
 44def encode(time_str):
 45    time = strptime(time_str, "%Y_%m_%d_%H_%M_%S")
 46    return strftime("%Y-%m-%d %H:%M:%S", time)
 47
 48
 49def dd_for_params(id_string, owner, request):
 50    start = end = None
 51    dd = DataDictionary.objects.get(id_string=id_string,
 52                                    user=owner)
 53    if request.GET.get('start'):
 54        try:
 55            start = encode(request.GET['start'])
 56        except ValueError:
 57            # bad format
 58            return [False,
 59                    HttpResponseBadRequest(
 60                        _(u'Start time format must be YY_MM_DD_hh_mm_ss'))
 61                    ]
 62        dd.surveys_for_export = \
 63            lambda d: d.surveys.filter(date_created__gte=start)
 64    if request.GET.get('end'):
 65        try:
 66            end = encode(request.GET['end'])
 67        except ValueError:
 68            # bad format
 69            return [False,
 70                    HttpResponseBadRequest(
 71                        _(u'End time format must be YY_MM_DD_hh_mm_ss'))
 72                    ]
 73        dd.surveys_for_export = \
 74            lambda d: d.surveys.filter(date_created__lte=end)
 75    if start and end:
 76        dd.surveys_for_export = \
 77            lambda d: d.surveys.filter(date_created__lte=end,
 78                                       date_created__gte=start)
 79    return [True, dd]
 80
 81
 82def parse_label_for_display(pi, xpath):
 83    label = pi.data_dictionary.get_label(xpath)
 84    if not type(label) == dict:
 85        label = {'Unknown': label}
 86    return label.items()
 87
 88
 89def average(values):
 90    if len(values):
 91        return sum(values, 0.0) / len(values)
 92    return None
 93
 94
 95def map_view(request, username, id_string, template='map.html'):
 96    owner = get_object_or_404(User, username=username)
 97    xform = get_object_or_404(XForm, id_string=id_string, user=owner)
 98    if not has_permission(xform, owner, request):
 99        return HttpResponseForbidden(_(u'Not shared.'))
100    context = RequestContext(request)
101    context.content_user = owner
102    context.xform = xform
103    context.profile, created = UserProfile.objects.get_or_create(user=owner)
104
105    context.form_view = True
106    context.jsonform_url = reverse(download_jsonform,
107                                   kwargs={"username": username,
108                                           "id_string": id_string})
109    context.enketo_edit_url = reverse('edit_data',
110                                      kwargs={"username": username,
111                                              "id_string": id_string,
112                                              "data_id": 0})
113    context.enketo_add_url = reverse('enter_data',
114                                     kwargs={"username": username,
115                                             "id_string": id_string})
116
117    context.enketo_add_with_url = reverse('add_submission_with',
118                                          kwargs={"username": username,
119                                                  "id_string": id_string})
120    context.mongo_api_url = reverse('mongo_view_api',
121                                    kwargs={"username": username,
122                                            "id_string": id_string})
123    context.delete_data_url = reverse('delete_data',
124                                      kwargs={"username": username,
125                                              "id_string": id_string})
126    context.mapbox_layer = MetaData.mapbox_layer_upload(xform)
127    audit = {
128        "xform": xform.id_string
129    }
130    audit_log(Actions.FORM_MAP_VIEWED, request.user, owner,
131              _("Requested map on '%(id_string)s'.")
132              % {'id_string': xform.id_string}, audit, request)
133    return render_to_response(template, context_instance=context)
134
135
136def map_embed_view(request, username, id_string):
137    return map_view(request, username, id_string, template='map_embed.html')
138
139
140def add_submission_with(request, username, id_string):
141
142    import uuid
143    import requests
144
145    from django.conf import settings
146    from django.template import loader, Context
147    from dpath import util as dpath_util
148    from dict2xml import dict2xml
149
150    def geopoint_xpaths(username, id_string):
151        d = DataDictionary.objects.get(user__username=username, id_string=id_string)
152        return [e.get_abbreviated_xpath()
153                for e in d.get_survey_elements()
154                if e.bind.get(u'type') == u'geopoint']
155
156    value = request.GET.get('coordinates')
157    xpaths = geopoint_xpaths(username, id_string)
158    xml_dict = {}
159    for path in xpaths:
160        dpath_util.new(xml_dict, path, value)
161
162    context = {'username': username,
163               'id_string': id_string,
164               'xml_content': dict2xml(xml_dict)}
165    instance_xml = loader.get_template("instance_add.xml").render(Context(context))
166
167    url = settings.ENKETO_API_INSTANCE_IFRAME_URL
168    return_url = reverse('thank_you_submission', kwargs={"username": username,
169                                                         "id_string": id_string})
170    if settings.DEBUG:
171        openrosa_url = "https://dev.formhub.org/{}".format(username)
172    else:
173        openrosa_url = request.build_absolute_uri("/{}".format(username))
174    payload = {'return_url': return_url,
175               'form_id': id_string,
176               'server_url': openrosa_url,
177               'instance': instance_xml,
178               'instance_id': uuid.uuid4().hex}
179
180    r = requests.post(url, data=payload,
181                      auth=(settings.ENKETO_API_TOKEN, ''), verify=False)
182
183    return HttpResponse(r.text, mimetype='application/json')
184
185
186def thank_you_submission(request, username, id_string):
187    return HttpResponse("Thank You")
188
189
190# TODO: do a good job of displaying hierarchical data
191def survey_responses(request, instance_id):
192    pi = get_object_or_404(ParsedInstance, instance=instance_id)
193    xform, is_owner, can_edit, can_view = \
194        get_xform_and_perms(pi.instance.user.username,
195                            pi.instance.xform.id_string, request)
196    # no access
197    if not (xform.shared_data or can_view or
198            request.session.get('public_link') == xform.uuid):
199        return HttpResponseRedirect('/')
200    data = pi.to_dict()
201
202    # get rid of keys with leading underscores
203    data_for_display = {}
204    for k, v in data.items():
205        if not k.startswith(u"_"):
206            data_for_display[k] = v
207
208    xpaths = data_for_display.keys()
209    xpaths.sort(cmp=pi.data_dictionary.get_xpath_cmp())
210    label_value_pairs = [
211        (parse_label_for_display(pi, xpath),
212         data_for_display[xpath]) for xpath in xpaths
213    ]
214    languages = label_value_pairs[-1][0]
215    audit = {
216        "xform": xform.id_string,
217        "instance_id": instance_id
218    }
219    audit_log(
220        Actions.FORM_DATA_VIEWED, request.user, xform.user,
221        _("Requested survey with id '%(instance_id)s' on '%(id_string)s'.") %
222        {
223            'id_string': xform.id_string,
224            'instance_id': instance_id
225        }, audit, request)
226    return render_to_response('survey.html', {
227        'label_value_pairs': label_value_pairs,
228        'image_urls': image_urls(pi.instance),
229        'languages': languages,
230        'default_language': languages[0][0]
231    })
232
233
234def data_export(request, username, id_string, export_type):
235    owner = get_object_or_404(User, username=username)
236    xform = get_object_or_404(XForm, id_string=id_string, user=owner)
237    helper_auth_helper(request)
238    if not has_permission(xform, owner, request):
239        return HttpResponseForbidden(_(u'Not shared.'))
240    query = request.GET.get("query")
241    extension = export_type
242
243    # check if we should force xlsx
244    force_xlsx = request.GET.get('xls') != 'true'
245    if export_type == Export.XLS_EXPORT and force_xlsx:
246        extension = 'xlsx'
247    elif export_type == Export.CSV_ZIP_EXPORT:
248        extension = 'zip'
249
250    audit = {
251        "xform": xform.id_string,
252        "export_type": export_type
253    }
254    # check if we need to re-generate,
255    # we always re-generate if a filter is specified
256    if should_create_new_export(xform, export_type) or query or\
257                    'start' in request.GET or 'end' in request.GET:
258        format_date_for_mongo = lambda x, datetime: datetime.strptime(
259            x, '%y_%m_%d_%H_%M_%S').strftime('%Y-%m-%dT%H:%M:%S')
260        # check for start and end params
261        if 'start' in request.GET or 'end' in request.GET:
262            if not query:
263                query = '{}'
264            query = json.loads(query)
265            query[SUBMISSION_TIME] = {}
266            try:
267                if request.GET.get('start'):
268                    query[SUBMISSION_TIME]['$gte'] = format_date_for_mongo(
269                        request.GET['start'], datetime)
270                if request.GET.get('end'):
271                    query[SUBMISSION_TIME]['$lte'] = format_date_for_mongo(
272                        request.GET['end'], datetime)
273            except ValueError:
274                return HttpResponseBadRequest(
275                    _("Dates must be in the format YY_MM_DD_hh_mm_ss"))
276            else:
277                query = json.dumps(query)
278        try:
279            export = generate_export(
280                export_type, extension, username, id_string, None, query)
281            audit_log(
282                Actions.EXPORT_CREATED, request.user, owner,
283                _("Created %(export_type)s export on '%(id_string)s'.") %
284                {
285                    'id_string': xform.id_string,
286                    'export_type': export_type.upper()
287                }, audit, request)
288        except NoRecordsFoundError:
289            return HttpResponseNotFound(_("No records found to export"))
290    else:
291        export = newset_export_for(xform, export_type)
292
293    # log download as well
294    audit_log(
295        Actions.EXPORT_DOWNLOADED, request.user, owner,
296        _("Downloaded %(export_type)s export on '%(id_string)s'.") %
297        {
298            'id_string': xform.id_string,
299            'export_type': export_type.upper()
300        }, audit, request)
301
302    if not export.filename:
303        # tends to happen when using newset_export_for.
304        return HttpResponseNotFound("File does not exist!")
305    # get extension from file_path, exporter could modify to
306    # xlsx if it exceeds limits
307    path, ext = os.path.splitext(export.filename)
308    ext = ext[1:]
309    if request.GET.get('raw'):
310        id_string = None
311    response = response_with_mimetype_and_name(
312        Export.EXPORT_MIMES[ext], id_string, extension=ext,
313        file_path=export.filepath)
314    return response
315
316
317@require_POST
318def create_export(request, username, id_string, export_type):
319    owner = get_object_or_404(User, username=username)
320    xform = get_object_or_404(XForm, id_string=id_string, user=owner)
321    if not has_permission(xform, owner, request):
322        return HttpResponseForbidden(_(u'Not shared.'))
323
324    query = request.POST.get("query")
325    force_xlsx = request.POST.get('xls') != 'true'
326
327    # export options
328    group_delimiter = request.POST.get("options[group_delimiter]", '/')
329    if group_delimiter not in ['.', '/']:
330        return HttpResponseBadRequest(
331            _("%s is not a valid delimiter" % group_delimiter))
332
333    # default is True, so when dont_.. is yes
334    # split_select_multiples becomes False
335    split_select_multiples = request.POST.get(
336        "options[dont_split_select_multiples]", "no") == "no"
337
338    options = {
339        'group_delimiter': group_delimiter,
340        'split_select_multiples': split_select_multiples
341    }
342
343    try:
344        create_async_export(xform, export_type, query, force_xlsx, options)
345    except Export.ExportTypeError:
346        return HttpResponseBadRequest(
347            _("%s is not a valid export type" % export_type))
348    else:
349        audit = {
350            "xform": xform.id_string,
351            "export_type": export_type
352        }
353        audit_log(
354            Actions.EXPORT_CREATED, request.user, owner,
355            _("Created %(export_type)s export on '%(id_string)s'.") %
356            {
357                'export_type': export_type.upper(),
358                'id_string': xform.id_string,
359            }, audit, request)
360        return HttpResponseRedirect(reverse(
361            export_list,
362            kwargs={
363                "username": username,
364                "id_string": id_string,
365                "export_type": export_type
366            })
367        )
368
369
370def _get_google_token(request, redirect_to_url):
371    token = None
372    if request.user.is_authenticated():
373        try:
374            ts = TokenStorageModel.objects.get(id=request.user)
375        except TokenStorageModel.DoesNotExist:
376            pass
377        else:
378            token = ts.token
379    elif request.session.get('access_token'):
380        token = request.session.get('access_token')
381    if token is None:
382        request.session["google_redirect_url"] = redirect_to_url
383        return HttpResponseRedirect(redirect_uri)
384    return token
385
386
387def export_list(request, username, id_string, export_type):
388    if export_type == Export.GDOC_EXPORT:
389        redirect_url = reverse(
390            export_list,
391            kwargs={
392                'username': username, 'id_string': id_string,
393                'export_type': export_type})
394        token = _get_google_token(request, redirect_url)
395        if isinstance(token, HttpResponse):
396            return token
397    owner = get_object_or_404(User, username=username)
398    xform = get_object_or_404(XForm, id_string=id_string, user=owner)
399    if not has_permission(xform, owner, request):
400        return HttpResponseForbidden(_(u'Not shared.'))
401
402    if should_create_new_export(xform, export_type):
403        try:
404            create_async_export(
405                xform, export_type, query=None, force_xlsx=True)
406        except Export.ExportTypeError:
407            return HttpResponseBadRequest(
408                _("%s is not a valid export type" % export_type))
409
410    context = RequestContext(request)
411    context.username = owner.username
412    context.xform = xform
413    # TODO: better output e.g. Excel instead of XLS
414    context.export_type = export_type
415    context.export_type_name = Export.EXPORT_TYPE_DICT[export_type]
416    exports = Export.objects.filter(xform=xform, export_type=export_type)\
417        .order_by('-created_on')
418    context.exports = exports
419    return render_to_response('export_list.html', context_instance=context)
420
421
422def export_progress(request, username, id_string, export_type):
423    owner = get_object_or_404(User, username=username)
424    xform = get_object_or_404(XForm, id_string=id_string, user=owner)
425    if not has_permission(xform, owner, request):
426        return HttpResponseForbidden(_(u'Not shared.'))
427
428    # find the export entry in the db
429    export_ids = request.GET.getlist('export_ids')
430    exports = Export.objects.filter(xform=xform, id__in=export_ids)
431    statuses = []
432    for export in exports:
433        status = {
434            'complete': False,
435            'url': None,
436            'filename': None,
437            'export_id': export.id
438        }
439
440        if export.status == Export.SUCCESSFUL:
441            status['url'] = reverse(export_download, kwargs={
442                'username': owner.username,
443                'id_string': xform.id_string,
444                'export_type': export.export_type,
445                'filename': export.filename
446            })
447            status['filename'] = export.filename
448            if export.export_type == Export.GDOC_EXPORT and \
449                    export.export_url is None:
450                redirect_url = reverse(
451                    export_progress,
452                    kwargs={
453                        'username': username, 'id_string': id_string,
454                        'export_type': export_type})
455                token = _get_google_token(request, redirect_url)
456                if isinstance(token, HttpResponse):
457                    return token
458                status['url'] = None
459                try:
460                    url = google_export_xls(
461                        export.full_filepath, xform.title, token, blob=True)
462                except Exception, e:
463                    status['error'] = True
464                    status['message'] = e.message
465                else:
466                    export.export_url = url
467                    export.save()
468                    status['url'] = url
469        # mark as complete if it either failed or succeeded but NOT pending
470        if export.status == Export.SUCCESSFUL \
471                or export.status == Export.FAILED:
472            status['complete'] = True
473        statuses.append(status)
474
475    return HttpResponse(
476        json.dumps(statuses), mimetype='application/json')
477
478
479def export_download(request, username, id_string, export_type, filename):
480    owner = get_object_or_404(User, username=username)
481    xform = get_object_or_404(XForm, id_string=id_string, user=owner)
482    helper_auth_helper(request)
483    if not has_permission(xform, owner, request):
484        return HttpResponseForbidden(_(u'Not shared.'))
485
486    # find the export entry in the db
487    export = get_object_or_404(Export, xform=xform, filename=filename)
488
489    if export_type == Export.GDOC_EXPORT and export.export_url is not None:
490        return HttpResponseRedirect(export.export_url)
491
492    ext, mime_type = export_def_from_filename(export.filename)
493
494    audit = {
495        "xform": xform.id_string,
496        "export_type": export.export_type
497    }
498    audit_log(
499        Actions.EXPORT_DOWNLOADED, request.user, owner,
500        _("Downloaded %(export_type)s export '%(filename)s' "
501          "on '%(id_string)s'.") %
502        {
503            'export_type': export.export_type.upper(),
504            'filename': export.filename,
505            'id_string': xform.id_string,
506        }, audit, request)
507    if request.GET.get('raw'):
508        id_string = None
509
510    default_storage = get_storage_class()()
511    if not isinstance(default_storage, FileSystemStorage):
512        return HttpResponseRedirect(default_storage.url(export.filepath))
513    basename = os.path.splitext(export.filename)[0]
514    response = response_with_mimetype_and_name(
515        mime_type, name=basename, extension=ext,
516        file_path=export.filepath, show_date=False)
517    return response
518
519
520@require_POST
521def delete_export(request, username, id_string, export_type):
522    owner = get_object_or_404(User, username=username)
523    xform = get_object_or_404(XForm, id_string=id_string, user=owner)
524    if not has_permission(xform, owner, request):
525        return HttpResponseForbidden(_(u'Not shared.'))
526
527    export_id = request.POST.get('export_id')
528
529    # find the export entry in the db
530    export = get_object_or_404(Export, id=export_id)
531
532    export.delete()
533    audit = {
534        "xform": xform.id_string,
535        "export_type": export.export_type
536    }
537    audit_log(
538        Actions.EXPORT_DOWNLOADED, request.user, owner,
539        _("Deleted %(export_type)s export '%(filename)s'"
540          " on '%(id_string)s'.") %
541        {
542            'export_type': export.export_type.upper(),
543            'filename': export.filename,
544            'id_string': xform.id_string,
545        }, audit, request)
546    return HttpResponseRedirect(reverse(
547        export_list,
548        kwargs={
549            "username": username,
550            "id_string": id_string,
551            "export_type": export_type
552        }))
553
554
555def zip_export(request, username, id_string):
556    owner = get_object_or_404(User, username=username)
557    xform = get_object_or_404(XForm, id_string=id_string, user=owner)
558    helper_auth_helper(request)
559    if not has_permission(xform, owner, request):
560        return HttpResponseForbidden(_(u'Not shared.'))
561    if request.GET.get('raw'):
562        id_string = None
563    attachments = Attachment.objects.filter(instance__xform=xform)
564    zip_file = create_attachments_zipfile(attachments)
565    audit = {
566        "xform": xform.id_string,
567        "export_type": Export.ZIP_EXPORT
568    }
569    audit_log(
570        Actions.EXPORT_CREATED, request.user, owner,
571        _("Created ZIP export on '%(id_string)s'.") %
572        {
573            'id_string': xform.id_string,
574        }, audit, request)
575    # log download as well
576    audit_log(
577        Actions.EXPORT_DOWNLOADED, request.user, owner,
578        _("Downloaded ZIP export on '%(id_string)s'.") %
579        {
580            'id_string': xform.id_string,
581        }, audit, request)
582    if request.GET.get('raw'):
583        id_string = None
584    response = response_with_mimetype_and_name('zip', id_string,
585                                               file_path=zip_file,
586                                               use_local_filesystem=True)
587    return response
588
589
590def kml_export(request, username, id_string):
591    # read the locations from the database
592    context = RequestContext(request)
593    context.message = "HELLO!!"
594    owner = get_object_or_404(User, username=username)
595    xform = get_object_or_404(XForm, id_string=id_string, user=owner)
596    helper_auth_helper(request)
597    if not has_permission(xform, owner, request):
598        return HttpResponseForbidden(_(u'Not shared.'))
599    context.data = kml_export_data(id_string, user=owner)
600    response = \
601        render_to_response("survey.kml", context_instance=context,
602                           mimetype="application/vnd.google-earth.kml+xml")
603    response['Content-Disposition'] = \
604        disposition_ext_and_date(id_string, 'kml')
605    audit = {
606        "xform": xform.id_string,
607        "export_type": Export.KML_EXPORT
608    }
609    audit_log(
610        Actions.EXPORT_CREATED, request.user, owner,
611        _("Created KML export on '%(id_string)s'.") %
612        {
613            'id_string': xform.id_string,
614        }, audit, request)
615    # log download as well
616    audit_log(
617        Actions.EXPORT_DOWNLOADED, request.user, owner,
618        _("Downloaded KML export on '%(id_string)s'.") %
619        {
620            'id_string': xform.id_string,
621        }, audit, request)
622    return response
623
624
625def google_xls_export(request, username, id_string):
626    token = None
627    if request.user.is_authenticated():
628        try:
629            ts = TokenStorageModel.objects.get(id=request.user)
630        except TokenStorageModel.DoesNotExist:
631            pass
632        else:
633            token = ts.token
634    elif request.session.get('access_token'):
635        token = request.session.get('access_token')
636    if token is None:
637        request.session["google_redirect_url"] = reverse(
638            google_xls_export,
639            kwargs={'username': username, 'id_string': id_string})
640        return HttpResponseRedirect(redirect_uri)
641    owner = get_object_or_404(User, username=username)
642    xform = get_object_or_404(XForm, id_string=id_string, user=owner)
643    if not has_permission(xform, owner, request):
644        return HttpResponseForbidden(_(u'Not shared.'))
645    valid, dd = dd_for_params(id_string, owner, request)
646    if not valid:
647        return dd
648    ddw = XlsWriter()
649    tmp = NamedTemporaryFile(delete=False)
650    ddw.set_file(tmp)
651    ddw.set_data_dictionary(dd)
652    temp_file = ddw.save_workbook_to_file()
653    temp_file.close()
654    url = google_export_xls(tmp.name, xform.title, token, blob=True)
655    os.unlink(tmp.name)
656    audit = {
657        "xform": xform.id_string,
658        "export_type": "google"
659    }
660    audit_log(
661        Actions.EXPORT_CREATED, request.user, owner,
662        _("Created Google Docs export on '%(id_string)s'.") %
663        {
664            'id_string': xform.id_string,
665        }, audit, request)
666    return HttpResponseRedirect(url)
667
668
669def data_view(request, username, id_string):
670    owner = get_object_or_404(User, username=username)
671    xform = get_object_or_404(XForm, id_string=id_string, user=owner)
672    if not has_permission(xform, owner, request):
673        return HttpResponseForbidden(_(u'Not shared.'))
674
675    context = RequestContext(request)
676    context.owner = owner
677    context.xform = xform
678    audit = {
679        "xform": xform.id_string,
680    }
681    audit_log(
682        Actions.FORM_DATA_VIEWED, request.user, owner,
683        _("Requested data view for '%(id_string)s'.") %
684        {
685            'id_string': xform.id_string,
686        }, audit, request)
687    return render_to_response("data_view.html", context_instance=context)
688
689
690def attachment_url(request, size='medium'):
691    media_file = request.GET.get('media_file')
692    # TODO: how to make sure we have the right media file,
693    # this assumes duplicates are the same file
694    result = Attachment.objects.filter(media_file=media_file)[0:1]
695    if result.count() == 0:
696        return HttpResponseNotFound(_(u'Attachment not found'))
697    attachment = result[0]
698    if not attachment.mimetype.startswith('image'):
699        return redirect(attachment.media_file.url)
700    try:
701        media_url = image_url(attachment, size)
702    except:
703        # TODO: log this somewhere
704        # image not found, 404, S3ResponseError timeouts
705        pass
706    else:
707        if media_url:
708            return redirect(media_url)
709    return HttpResponseNotFound(_(u'Error: Attachment not found'))
710
711
712def instance(request, username, id_string):
713    xform, is_owner, can_edit, can_view = get_xform_and_perms(
714        username, id_string, request)
715    # no access
716    if not (xform.shared_data or can_view or
717            request.session.get('public_link') == xform.uuid):
718        return HttpResponseForbidden(_(u'Not shared.'))
719
720    context = RequestContext(request)
721
722    audit = {
723        "xform": xform.id_string,
724    }
725    audit_log(
726        Actions.FORM_DATA_VIEWED, request.user, xform.user,
727        _("Requested instance view for '%(id_string)s'.") %
728        {
729            'id_string': xform.id_string,
730        }, audit, request)
731    return render_to_response('instance.html', {
732        'username': username,
733        'id_string': id_string,
734        'xform': xform,
735        'can_edit': can_edit
736    }, context_instance=context)