def observation_detail(request, observation_id):
    observation = Observation.objects.get(id=observation_id)
    
    # the observation_detail.html template needs jQuery
    extra_context = {
        'media': Media(js=(settings.JQUERY_FILE, 'radiohider.js')),
    }
    
    show_cases_form = False
    if request.method == 'POST':
        cases_form = ObservationCasesForm(observation, data=request.POST)
        if cases_form.is_valid():
            new_cases = set(cases_form.cleaned_data['cases'])
            old_cases = set(observation.cases.all())
            if new_cases != old_cases:
                observation.cases = new_cases
        else: # there was an error with the form
            show_cases_form = True
    else:
        cases_form = ObservationCasesForm(observation)

    extra_context['media'] += cases_form.media
    extra_context.update({
        'show_cases_form': show_cases_form,
        'cases_form': cases_form,
    })
    
    if request.user.has_perms(('incidents.change_observation', 'incidents.delete_observation')):
        merge_form = ObservationMergeSourceForm(destination=observation)
        extra_context['merge_form'] = merge_form
        extra_context['media'] += merge_form.media

    case_data = []
    for c in observation.cases.all():
        case_datum = {'case': c}
        try:
            case_datum['previous'] = observation.get_case_previous(c)
        except Observation.DoesNotExist:
            case_datum['previous'] = None
        try:
            case_datum['next'] = observation.get_case_next(c)
        except Observation.DoesNotExist:
            case_datum['next'] = None
        case_data.append(case_datum)
    extra_context['case_data'] = case_data

    # TODO generify
    for oe_attr in (
        'entanglements_entanglementobservation', 
        'shipstrikes_shipstrikeobservation',
    ):
        if hasattr(observation, oe_attr):
            extra_context.update(getattr(observation, oe_attr)._extra_context)
    return generic_views.object_detail(
        request,
        object_id= observation_id,
        queryset= Observation.objects.all(),
        template_object_name= 'observation',
        extra_context= extra_context,
    )
def case_detail(request, case_id, extra_context={}):
    # TODO this is quite inefficient
    case = Case.objects.get(id=case_id)
    case_class = case.specific_class()
    
    # TODO hack
    if case.case_type == 'Entanglement':
        return redirect(case.specific_instance())
    
    # the case_detail.html template needs jQuery
    if not 'media' in extra_context:
        extra_context['media'] = Media(js=(settings.JQUERY_FILE,))

    if request.user.has_perms(('incidents.change_case', 'incidents.delete_case')):
        merge_form = CaseMergeSourceForm(destination=case)
        extra_context['merge_form'] = merge_form
        extra_context['media'] += merge_form.media
        extra_context['media'] += Media(js=(settings.JQUERY_FILE,))

    return generic_views.object_detail(
        request,
        object_id= case_id,
        queryset= case_class.objects.select_related().all(),
        template_object_name= 'case',
        extra_context= extra_context,
    )
def entanglement_detail(request, case_id, extra_context):
    
    case = Entanglement.objects.get(id=case_id)
    
    # the entanglement_detail.html template needs jQuery
    if not 'media' in extra_context:
        extra_context['media'] = Media()

    if request.user.has_perms(('incidents.change_entanglement', 'incidents.delete_entanglement')):
        merge_form = CaseMergeSourceForm(destination=case)
        extra_context['merge_form'] = merge_form
        extra_context['media'] += merge_form.media
        extra_context['media'] += Media(js=(settings.JQUERY_FILE,))
    
    if request.user.has_perms((
        'entanglements.change_entanglement',
        'entanglements.view_gearowner',
        'entanglements.add_gearowner',
        'entanglements.change_gearowner',
    )):
        extra_context['media'] += Media(
            js= (settings.JQUERY_FILE, settings.JQUERYUI_JS_FILE),
            css= {'all': (settings.JQUERYUI_CSS_FILE,)},
        )
        forms = _instantiate_gear_analysis_forms(request, case)
        form_media = reduce(lambda m, f: m + f.media, forms.values(), Media())
        extra_context['gear_analysis_forms'] = forms
        extra_context['media'] += form_media
    
        if request.method == 'POST':
            result = _process_gear_analysis_forms(extra_context['gear_analysis_forms'])
            if not result is None:
                return redirect(result)
    
    template_media = Media(
        js= (settings.JQUERY_FILE, 'radiohider.js', 'checkboxhider.js', 'selecthider.js'),
    )

    extra_context['media'] += template_media

    return generic_views.object_detail(
        request,
        object_id= case_id,
        queryset= Entanglement.objects.select_related().all(),
        template_object_name= 'case',
        extra_context= extra_context,
    )