Source code for aristotle_mdr.contrib.generic.views

from django import forms
from django.apps import apps
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse
from django.db import transaction
from django.forms.models import modelformset_factory
from django.http import HttpResponse, Http404, HttpResponseRedirect
from django.shortcuts import redirect, get_object_or_404
from django.utils.translation import ugettext_lazy as _
from django.views.generic import FormView, ListView, TemplateView

from aristotle_mdr.contrib.autocomplete import widgets
from aristotle_mdr.utils import get_concepts_for_apps
from aristotle_mdr.models import _concept
from aristotle_mdr.perms import user_can_edit, user_can_view
from aristotle_mdr.utils import construct_change_message

import reversion


def generic_foreign_key_factory_view(request, **kwargs):
    item = get_object_or_404(_concept, pk=kwargs['iid']).item
    field = None

    for f in item._meta.fields:
        if f.name.lower() == kwargs['fk_field'].lower():
            field = f.name

    if not field:
        raise Http404

    return GenericAlterForeignKey.as_view(
        model_base=item.__class__,
        model_base_field=field,
        form_title=_('Add Object Class')
    )(request, **kwargs)


class GenericWithItemURLFormView(FormView):
    user_checks = []
    permission_checks = [user_can_view]
    model_base = _concept

    def dispatch(self, request, *args, **kwargs):
        self.item = get_object_or_404(self.model_base, pk=self.kwargs['iid'])

        if not (
            self.item and
            all([perm(request.user, self.item) for perm in self.permission_checks]) and
            all([perm(request.user) for perm in self.user_checks])
        ):
            if request.user.is_anonymous():
                return redirect(reverse('friendly_login') + '?next=%s' % request.path)
            else:
                raise PermissionDenied
        return super(GenericWithItemURLFormView, self).dispatch(request, *args, **kwargs)

    def get_context_data(self, **kwargs):
        context = super(GenericWithItemURLFormView, self).get_context_data(**kwargs)
        context['item'] = self.item
        context['submit_url'] = self.request.get_full_path()
        return context

    def get_success_url(self):
        return self.item.get_absolute_url()


class GenericAlterManyToSomethingFormView(GenericWithItemURLFormView):
    permission_checks = [user_can_edit]
    model_base = None
    model_to_add = None
    model_base_field = None
    form_title = None
    form_submit_text = _('Save')

    def get_context_data(self, **kwargs):
        context = super(GenericAlterManyToSomethingFormView, self).get_context_data(**kwargs)
        context['model_to_add'] = self.model_to_add
        context['model_base'] = self.model_base
        context['item'] = self.item
        context['form_title'] = self.form_title or _('Add child item')
        context['form_submit_text'] = self.form_submit_text
        return context


[docs]class GenericAlterForeignKey(GenericAlterManyToSomethingFormView): """ A view that provides a framework for altering ManyToOne relationships (Include through models from ManyToMany relationships) from one 'base' object to many others. The URL pattern must pass a kwarg with the name `iid` that is the object from the `model_base` to use as the main link for the many to many relation. * `model_base` - mandatory - The model with the instance to be altered * `model_to_add` - mandatory - The model that has instances we will link to the base. * `template_name` - optional - The template used to display the form. - default - "aristotle_mdr/generic/actions/alter_foreign_key.html" * `model_base_field` - mandatory - the name of the field that goes from the `model_base` to the `model_to_add`. * `model_to_add_field` - mandatory - the name of the field on the `model_to_add` model that links to the `model_base` model. * `form_title` - Title for the form For example: If we have a many to many relationship from `DataElement`s to `Dataset`s, to alter the `DataElement`s attached to a `Dataset`, `Dataset` is the `base_model` and `model_to_add` is `DataElement`. """ template_name = "aristotle_mdr/generic/actions/alter_foreign_key.html" model_to_add_field = None form = None def get_context_data(self, **kwargs): context = super(GenericAlterForeignKey, self).get_context_data(**kwargs) context['form_add_another_text'] = self.form_submit_text or _('Add another') form = self.form or self.get_form()(instance=self.item) context['form'] = form return context def get_form(self, form_class=None): foreign_model = self.model_base._meta.get_field(self.model_base_field).related_model qs = foreign_model.objects.visible(self.request.user) model_base_field = self.model_base_field class FKOnlyForm(forms.ModelForm): class Meta(): model = self.model_base fields = (self.model_base_field,) widgets = { self.model_base_field: widgets.ConceptAutocompleteSelect( model=foreign_model ) } def __init__(self, *args, **kwargs): super(FKOnlyForm, self).__init__(*args, **kwargs) self.fields[model_base_field].queryset = qs return FKOnlyForm
[docs] def post(self, request, *args, **kwargs): """ Handles POST requests, instantiating a form instance with the passed POST variables and then checked for validity. """ form = self.get_form() self.form = form(self.request.POST, self.request.FILES, instance=self.item) if self.form.is_valid(): with transaction.atomic(), reversion.revisions.create_revision(): self.form.save() # do this to ensure we are saving reversion records for the value domain, not just the values reversion.revisions.set_user(request.user) reversion.revisions.set_comment( _("Altered relationship of '%s' on %s") % (self.model_base_field, self.item) ) return HttpResponseRedirect(self.get_success_url()) else: return self.form_invalid(form)
[docs]class GenericAlterManyToManyView(GenericAlterManyToSomethingFormView): """ A view that provides a framework for altering ManyToMany relationships from one 'base' object to many others. The URL pattern must pass a kwarg with the name `iid` that is the object from the `model_base` to use as the main link for the many to many relation. * `model_base` - mandatory - The model with the instance to be altered * `model_to_add` - mandatory - The model that has instances we will link to the base. * `template_name` - optional - The template used to display the form. - default - "aristotle_mdr/generic/actions/alter_many_to_many.html" * `model_base_field` - mandatory - the field name that goes from the `model_base` to the `model_to_add`. * `form_title` - Title for the form For example: If we have a many to many relationship from `DataElement`s to `Dataset`s, to alter the `DataElement`s attached to a `Dataset`, `Dataset` is the `base_model` and `model_to_add` is `DataElement`. """ template_name = "aristotle_mdr/generic/actions/alter_many_to_many.html" def get_context_data(self, **kwargs): context = super(GenericAlterManyToManyView, self).get_context_data(**kwargs) return context def get_form_class(self): class M2MForm(forms.Form): items_to_add = forms.ModelMultipleChoiceField( queryset=self.model_to_add.objects.visible(self.request.user), label="Attach", required=False, widget=widgets.ConceptAutocompleteSelectMultiple( model=self.model_to_add ) ) return M2MForm def get_initial(self): return { 'items_to_add': getattr(self.item, self.model_base_field).all() }
[docs] def post(self, request, *args, **kwargs): """ Handles POST requests, instantiating a form instance with the passed POST variables and then checked for validity. """ form = self.get_form() if form.is_valid(): return self.form_valid(form) else: return self.form_invalid(form)
def form_valid(self, form): self.item.__setattr__(self.model_base_field, form.cleaned_data['items_to_add']) self.item.save() return HttpResponseRedirect(self.get_success_url())
[docs]class GenericAlterOneToManyView(GenericAlterManyToSomethingFormView): """ A view that provides a framework for altering ManyToOne relationships (Include through models from ManyToMany relationships) from one 'base' object to many others. The URL pattern must pass a kwarg with the name `iid` that is the object from the `model_base` to use as the main link for the many to many relation. * `model_base` - mandatory - The model with the instance to be altered * `model_to_add` - mandatory - The model that has instances we will link to the base. * `template_name` - optional - The template used to display the form. - default - "aristotle_mdr/generic/actions/alter_many_to_many.html" * `model_base_field` - mandatory - the name of the field that goes from the `model_base` to the `model_to_add`. * `model_to_add_field` - mandatory - the name of the field on the `model_to_add` model that links to the `model_base` model. * `ordering_field` - optional - name of the ordering field, if entered this field is hidden and updated using a drag-and-drop library * `form_add_another_text` - optional - string used for the button to add a new row to the form - defaults to "Add another" * `form_title` - Title for the form For example: If we have a many to many relationship from `DataElement`s to `Dataset`s, to alter the `DataElement`s attached to a `Dataset`, `Dataset` is the `base_model` and `model_to_add` is `DataElement`. """ template_name = "aristotle_mdr/generic/actions/alter_one_to_many.html" model_to_add_field = None ordering_field = None form_add_another_text = None formset = None def get_context_data(self, **kwargs): context = super(GenericAlterOneToManyView, self).get_context_data(**kwargs) context['form_add_another_text'] = self.form_add_another_text or _('Add another') num_items = getattr(self.item, self.model_base_field).count() context['formset'] = self.formset or self.get_formset()( queryset=getattr(self.item, self.model_base_field).all(), initial=[{'ORDER': num_items + 1}] ) return context def get_form(self, form_class=None): return None def get_formset(self): _widgets = {} for f in self.model_to_add._meta.fields: foreign_model = self.model_to_add._meta.get_field(f.name).related_model if foreign_model and issubclass(foreign_model, _concept): _widgets.update({ f.name: widgets.ConceptAutocompleteSelect( model=foreign_model ) }) from aristotle_mdr.contrib.generic.forms import HiddenOrderModelFormSet return modelformset_factory( self.model_to_add, formset=HiddenOrderModelFormSet, can_order=True, # we assign this back to the ordering field can_delete=True, exclude=[self.model_to_add_field, self.ordering_field], # fields='__all__', extra=1, widgets=_widgets )
[docs] def post(self, request, *args, **kwargs): """ Handles POST requests, instantiating a form instance with the passed POST variables and then checked for validity. """ form = self.get_form() GenericFormSet = self.get_formset() self.formset = GenericFormSet(self.request.POST, self.request.FILES) formset = self.formset if formset.is_valid(): with transaction.atomic(), reversion.revisions.create_revision(): self.item.save() # do this to ensure we are saving reversion records for the value domain, not just the values formset.save(commit=False) for form in formset.forms: all_blank = not any(form[f].value() for f in form.fields if f is not self.ordering_field) if all_blank: continue if form['id'].value() not in [deleted_record['id'].value() for deleted_record in formset.deleted_forms]: # Don't immediately save, we need to attach the parent object value = form.save(commit=False) setattr(value, self.model_to_add_field, self.item) if self.ordering_field: setattr(value, self.ordering_field, form.cleaned_data['ORDER']) value.save() for obj in formset.deleted_objects: obj.delete() # formset.save(commit=True) reversion.revisions.set_user(request.user) reversion.revisions.set_comment(construct_change_message(request, None, [formset])) return HttpResponseRedirect(self.get_success_url()) else: return self.form_invalid(form)