class Meta:
        model = models.ReviewRequest
        fields = [
            'title', 'due_date', 'target_registration_state',
            'registration_date', 'concepts', 'cascade_registration'
        ]
        widgets = {
            'title':
            forms.Textarea(attrs={"rows": "1"}),
            'target_registration_state':
            forms.Select,
            'due_date':
            BootstrapDateTimePicker(options={"format": "YYYY-MM-DD"}),
            'registration_date':
            BootstrapDateTimePicker(options={"format": "YYYY-MM-DD"}),
            'concepts':
            ConceptAutocompleteSelectMultiple(),
            'cascade_registration':
            forms.RadioSelect(),
        }

        help_texts = {
            'target_registration_state':
            "The state for endorsement for metadata in this review",
            'due_date':
            "Date this review needs to be actioned by",
            'registration_date':
            "Date the metadata will be endorsed at",
            'title':
            "A short title for this review",
            'concepts':
            "List of metadata for review",
            'cascade_registration':
            "Include related items when registering metadata. When enabled, see the full list of metadata under the \"impact\" tab.",
        }
예제 #2
0
class EditStatusForm(ModelForm):
    class Meta:
        model = MDR.Status
        fields = ['registrationDate', 'until_date', 'state', 'changeDetails']

    registrationDate = forms.DateField(
        required=False,
        label=_("Registration date"),
        widget=BootstrapDateTimePicker(options={"format": "YYYY-MM-DD"}),
    )
    until_date = forms.DateField(
        required=False,
        label=_("Expiration date"),
        widget=BootstrapDateTimePicker(options={"format": "YYYY-MM-DD"}),
    )
    state = forms.ChoiceField(
        choices=MDR.STATES,
        widget=forms.RadioSelect,
    )
    changeDetails = forms.CharField(
        max_length=512,
        required=True,
        label=_("Why is the status being changed for these items?"),
        widget=forms.Textarea
    )
예제 #3
0
    def __init__(self, *args, **kwargs):
        # TODO: Have tis throw a 'no user' error
        first_load = kwargs.pop('first_load', None)
        super().__init__(*args, **kwargs)

        for f in self.fields:
            if f == "workgroup":
                self.fields[f].widget = widgets.WorkgroupAutocompleteSelect()
                self.fields[f].widget.choices = self.fields[f].choices
                if not self.user.is_superuser:
                    self.fields[
                        'workgroup'].queryset = self.user.profile.editable_workgroups
            elif hasattr(self.fields[f], 'queryset') and type(
                    self.fields[f].queryset) == ConceptQuerySet:
                if hasattr(self.fields[f].queryset, 'visible'):
                    if f in [
                            m2m.name
                            for m2m in self._meta.model._meta.many_to_many
                    ]:
                        field_widget = widgets.ConceptAutocompleteSelectMultiple
                    else:
                        field_widget = widgets.ConceptAutocompleteSelect
                    self.fields[f].queryset = self.fields[f].queryset.all(
                    ).visible(self.user)
                    self.fields[f].widget = field_widget(
                        model=self.fields[f].queryset.model)
                    self.fields[f].widget.choices = self.fields[f].choices
            elif type(self.fields[f]) == forms.fields.DateField:
                self.fields[f].widget = BootstrapDateTimePicker(
                    options={"format": "YYYY-MM-DD"})
            elif type(self.fields[f]) == forms.fields.DateTimeField:
                self.fields[f].widget = BootstrapDateTimePicker(
                    options={"format": "YYYY-MM-DD"})
예제 #4
0
 class Meta:
     model = VersionPublicationRecord
     exclude = ['content_type', 'object_id']
     widgets = {
         'public_user_publication_date':
         BootstrapDateTimePicker(options={"format": "YYYY-MM-DD HH:MM"}),
         'authenticated_user_publication_date':
         BootstrapDateTimePicker(options={"format": "YYYY-MM-DD HH:MM"}),
     }
예제 #5
0
def get_aristotle_widgets(model, ordering_field=None):

    _widgets = {}

    for f in model._meta.fields:
        foreign_model = model._meta.get_field(f.name).related_model
        widget = None

        if foreign_model and issubclass(foreign_model, _concept):
            widget = widgets.ConceptAutocompleteSelect(
                model=foreign_model, attrs={"style": "max-width:250px"})
        elif foreign_model:
            widget = forms.Select(attrs={"class": "form-control"})

        if isinstance(model._meta.get_field(f.name), DateField):
            widget = BootstrapDateTimePicker(
                options=datePickerOptions, attrs={"style": "min-width:150px"})

        if ordering_field is not None and f.name == ordering_field:
            widget = forms.HiddenInput()

        if widget is not None:
            _widgets[f.name] = widget

    for f in model._meta.many_to_many:
        foreign_model = model._meta.get_field(f.name).related_model
        if foreign_model and issubclass(foreign_model, _concept):
            _widgets.update({
                f.name:
                widgets.ConceptAutocompleteSelectMultiple(model=foreign_model)
            })

    return _widgets
def get_aristotle_widgets(model, ordering_field=None):

    _widgets = {}

    for f in model._meta.fields:
        foreign_model = model._meta.get_field(f.name).related_model
        if foreign_model and issubclass(foreign_model, _concept):
            _widgets.update({
                f.name: widgets.ConceptAutocompleteSelect(
                    model=foreign_model
                )
            })

        if isinstance(model._meta.get_field(f.name), DateField):
            _widgets.update({
                f.name: BootstrapDateTimePicker(options=datePickerOptions)
            })

        if ordering_field is not None and f.name == ordering_field:
            _widgets.update({
                ordering_field: forms.HiddenInput()
            })

    for f in model._meta.many_to_many:
        foreign_model = model._meta.get_field(f.name).related_model
        if foreign_model and issubclass(foreign_model, _concept):
            _widgets.update({
                f.name: widgets.ConceptAutocompleteSelectMultiple(
                    model=foreign_model
                )
            })

    return _widgets
class ChangeStatusGenericForm(RegistrationAuthorityMixin, UserAwareForm):
    state = forms.ChoiceField(
        choices=BLANK_CHOICE_DASH + MDR.STATES,
        widget=forms.Select(attrs={"class": "form-control"}))
    registrationDate = forms.DateField(
        required=False,
        label=_("Registration date"),
        help_text="Date the registration state will be active from.",
        widget=BootstrapDateTimePicker(options={"format": "YYYY-MM-DD"}),
        initial=timezone.now())
    cascadeRegistration = forms.ChoiceField(initial=0,
                                            choices=CASCADE_OPTIONS,
                                            label=_("Cascade registration"),
                                            help_text=CASCADE_HELP_TEXT,
                                            widget=forms.RadioSelect())
    changeDetails = forms.CharField(
        max_length=512,
        required=False,
        label=_("Administrative Note"),
        help_text=
        "The administrative note is a publishable statement describing the reasons for registration.",
        widget=forms.Textarea)
    registrationAuthorities = forms.ChoiceField(
        label="Registration Authorities",
        choices=MDR.RegistrationAuthority.objects.none(),
        widget=forms.Select())

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_registration_authority_field(
            field_name="registrationAuthorities",
            qs=self.user.profile.registrarAuthorities.filter(active=0))
예제 #8
0
class ChangeStatusGenericForm(RegistrationAuthorityMixin, UserAwareForm):
    state = forms.ChoiceField(choices=MDR.STATES, widget=forms.RadioSelect)
    registrationDate = forms.DateField(
        required=False,
        label=_("Registration date"),
        widget=BootstrapDateTimePicker(options={"format": "YYYY-MM-DD"}),
        initial=timezone.now()
    )
    cascadeRegistration = forms.ChoiceField(
        initial=0,
        choices=[(0, _('No')), (1, _('Yes'))],
        label=_("Do you want to request a status change for associated items")
    )
    changeDetails = forms.CharField(
        max_length=512,
        required=True,
        label=_("Why is the status being changed for these items?"),
        widget=forms.Textarea
    )
    registrationAuthorities = forms.ChoiceField(
        label="Registration Authorities",
        choices=MDR.RegistrationAuthority.objects.none(),
        widget=forms.RadioSelect
    )

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_registration_authority_field(
            field_name="registrationAuthorities", qs=self.user.profile.registrarAuthorities.filter(active=0)
        )
 class Meta:
     model = PublicationRecord
     exclude = ('user', 'concept')
     widgets = {
         'publication_date':
         BootstrapDateTimePicker(options={"format": "YYYY-MM-DD"}),
         'visibility':
         RadioSelect()
     }
예제 #10
0
 class Meta:
     model = MDR.SupersedeRelationship
     fields = [
         'older_item', 'registration_authority', 'message', 'date_effective'
     ]
     widgets = {
         'date_effective':
         BootstrapDateTimePicker(options={"format": "YYYY-MM-DD"}),
     }
class RequestReviewForm(ChangeStatusGenericForm):

    due_date = forms.DateField(
        required=False,
        label=_("Due date"),
        widget=BootstrapDateTimePicker(options={"format": "YYYY-MM-DD"}),
        initial=timezone.now())

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_registration_authority_field(
            field_name='registrationAuthorities')

    def clean_registrationAuthorities(self):
        value = self.cleaned_data['registrationAuthorities']
        return MDR.RegistrationAuthority.objects.get(id=int(value))
class RequestReviewEndorseForm(RequestReviewAcceptForm):
    registration_state = forms.ChoiceField(
        widget=forms.RadioSelect(),
        choices=MDR.STATES,
        label=_("Registration State"),
        help_text="The state for endorsement for metadata in this review",
    )
    registration_date = forms.DateField(
        widget=BootstrapDateTimePicker(options={"format": "YYYY-MM-DD"}),
        label=_("Registration Date"),
    )
    cascade_registration = forms.ChoiceField(
        initial=0,
        choices=[(0, _('No')), (1, _('Yes'))],
        label=_("Do you want to request a status change for associated items"))

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['close_review'].initial = 0
def one_to_many_formset_factory(model_to_add,
                                model_to_add_field,
                                ordering_field,
                                extra_excludes=[]):
    # creates a one to many formset
    # model_to_add is weak entity class, model_to_add_field is the foriegn key field name
    _widgets = {}
    exclude_fields = [model_to_add_field, ordering_field]
    exclude_fields += extra_excludes

    for f in model_to_add._meta.fields:
        foreign_model = 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)
            })

        if isinstance(model_to_add._meta.get_field(f.name), DateField):
            _widgets.update(
                {f.name: BootstrapDateTimePicker(options=datePickerOptions)})

    for f in model_to_add._meta.many_to_many:
        foreign_model = model_to_add._meta.get_field(f.name).related_model
        if foreign_model and issubclass(foreign_model, _concept):
            _widgets.update({
                f.name:
                widgets.ConceptAutocompleteSelectMultiple(model=foreign_model)
            })

    return modelformset_factory(
        model_to_add,
        formset=HiddenOrderModelFormSet,
        can_order=True,  # we assign this back to the ordering field
        can_delete=True,
        exclude=exclude_fields,
        # fields='__all__',
        extra=1,
        widgets=_widgets)
예제 #14
0
class PermissionSearchForm(TokenSearchForm):
    """
        We need to make a new form as permissions to view objects are a bit finicky.
        This form allows us to perform the base query then restrict it to just those
        of interest.
    """
    # Use short names to reduce URL length
    mq=forms.ChoiceField(
        required=False,
        initial=QUICK_DATES.anytime,
        choices=QUICK_DATES,
        widget=BootstrapDropdownIntelligentDate
    )
    mds = forms.DateField(
        required=False,
        label="Modified after date",
        widget=BootstrapDateTimePicker(options=datePickerOptions)
    )
    mde = forms.DateField(
        required=False,
        label="Modified before date",
        widget=BootstrapDateTimePicker(options=datePickerOptions)
    )
    cq=forms.ChoiceField(
        required=False,
        initial=QUICK_DATES.anytime,
        choices=QUICK_DATES,
        widget=BootstrapDropdownIntelligentDate
    )
    cds = forms.DateField(
        required=False,
        label="Created after date",
        widget=BootstrapDateTimePicker(options=datePickerOptions)
    )
    cde = forms.DateField(
        required=False,
        label="Created before date",
        widget=BootstrapDateTimePicker(options=datePickerOptions)
    )

    ra = forms.MultipleChoiceField(
        required=False, label=_("Registration authority"),
        choices=[], widget=BootstrapDropdownSelectMultiple
    )
    sort = forms.ChoiceField(
        required=False, initial=SORT_OPTIONS.natural,
        choices=SORT_OPTIONS, widget=BootstrapDropdownSelect
    )

    from aristotle_mdr.search_indexes import BASE_RESTRICTION
    res = forms.ChoiceField(
        required=False, initial=None,
        choices=BASE_RESTRICTION.items(),
        label="Item visibility state"
    )

    state = forms.MultipleChoiceField(
        required=False,
        label=_("Registration status"),
        choices=MDR.STATES + [(-99, _('Unregistered'))],  # Allow unregistered as a selection
        widget=BootstrapDropdownSelectMultiple
    )
    public_only = forms.BooleanField(
        required=False,
        label="Only show public items"
    )
    title_only = forms.BooleanField(
        required=False,
        label="Search titles only"
    )
    myWorkgroups_only = forms.BooleanField(
        required=False,
        label="Only show items in my workgroups"
    )
    category = forms.ChoiceField(
        choices=SEARCH_CATEGORIES,
        required=False, label=_('Categories'),
        widget=BootstrapDropdownSelect
    )
    models = forms.MultipleChoiceField(
        choices=[],  # model_choices(),
        required=False, label=_('Item type'),
        widget=BootstrapDropdownSelectMultiple
    )
    rpp = forms.IntegerField(
        required=False,
        label='Results per page'
    )
    # Hidden Workgroup field that is not rendered in the template,
    # label is required for faceting display
    wg = forms.IntegerField(required=False,
                            label="Workgroup")
    # Hidden Stewardship Organisation field that is not rendered in the template,
    # label is required for faceting display
    sa = forms.IntegerField(required=False,
                            label="Stewardship Organisation")

    # Filters that are to be applied
    filters = ["models", "mq", "cq", "cds", "cde", "mds", "mde", "state", "ra", "res", "wg", "sa"]

    def __init__(self, *args, **kwargs):
        if 'searchqueryset' not in kwargs.keys() or kwargs['searchqueryset'] is None:
            kwargs['searchqueryset'] = get_permission_sqs()
        if not issubclass(type(kwargs['searchqueryset']), PermissionSearchQuerySet):
            raise ImproperlyConfigured("Aristotle Search Queryset connection must be a subclass of PermissionSearchQuerySet")
        super().__init__(*args, **kwargs)

        # Populate choice of Registration Authorities ordered by active state and name
        # Inactive last
        self.fields['ra'].choices = [(ra.id, ra.name) for ra in MDR.RegistrationAuthority.objects.filter(active__in=[0, 1]).order_by('active', 'name')]

        # List of models that you can search for

        self.default_models = [
            m[0] for m in model_choices()
            if m[0].split('.', 1)[0] in allowable_search_models()
        ]

        # Set choices for models
        self.fields['models'].choices = [
            m for m in model_choices()
            if m[0].split('.', 1)[0] in allowable_search_models()
        ]

    def get_models(self):
        """Return an alphabetical list of model classes in the index."""
        search_models = []

        if self.is_valid():
            app_labels = self.default_models
            if self.cleaned_data['models']:
                app_labels = self.cleaned_data['models']

            for model in app_labels:
                search_models.append(apps.get_model(*model.split('.')))

        return search_models

    @property
    def applied_filters(self):
        """
        Returns the filters appearing in the URL that are applied
        """
        if not hasattr(self, 'cleaned_data'):
            return []
        return [f for f in self.filters if self.cleaned_data.get(f, False)]

    def search(self, repeat_search=False):
        # First, store the SearchQuerySet received from other processing.
        sqs = super().search()

        # If we got an empty search queryset, no need for further processing
        if isinstance(sqs, EmptyPermissionSearchQuerySet):
            return sqs

        if not self.token_models and self.get_models():
            sqs = sqs.models(*self.get_models())
        self.repeat_search = repeat_search

        # Is there no filter and no query -> no search was performed
        has_filter = self.kwargs or self.token_models or self.applied_filters
        if not has_filter and not self.query_text:
            return self.no_query_found()

        if self.applied_filters and not self.query_text:
            # Set flag when filtering with no query (used in template)
            self.filter_search = True

        # Get filter data from query
        states = self.cleaned_data.get('state', None)
        ras = self.cleaned_data.get('ra', None)
        restriction = self.cleaned_data['res']
        search_category = self.cleaned_data['category']
        workgroup = self.cleaned_data.get('wg', None)
        stewardship_organisation = self.cleaned_data.get('sa', None)

        # Apply the filters
        sqs = sqs.apply_registration_status_filters(states, ras)
        if restriction:
            sqs = sqs.filter(restriction=restriction)

        if search_category and search_category != SEARCH_CATEGORIES.all:
            sqs = sqs.filter(category=search_category)

        sqs = self.apply_date_filtering(sqs)
        sqs = sqs.apply_permission_checks(
            user=self.request.user,
            public_only=self.cleaned_data['public_only'],
            user_workgroups_only=self.cleaned_data['myWorkgroups_only']
        )

        if workgroup is not None:
            # We don't want to filter on a non-existent field
            # Must filter exactly
            sqs = sqs.filter(workgroup__exact=workgroup)

        if stewardship_organisation is not None:
            # Apply the stewardship organisation filter
            sqs = sqs.filter(stewardship_organisation=stewardship_organisation)

        # f for facets
        extra_facets_details = {}
        facets_opts = self.request.GET.getlist('f', [])

        for _facet in facets_opts:
            _facet, value = _facet.split("::", 1)
            # Force exact as otherwise we don't match when there are spaces.
            sqs = sqs.filter(**{"%s__exact" % _facet: value})
            facets_details = extra_facets_details.get(_facet, {'applied': []})
            facets_details['applied'] = list(set(facets_details['applied'] + [value]))
            extra_facets_details[_facet] = facets_details

        self.has_spelling_suggestions = False
        if not self.repeat_search:

            if sqs.count() < 5:
                self.check_spelling(sqs)

            if sqs.count() == 0:
                if sqs.count() == 0 and self.has_spelling_suggestions:
                    self.auto_correct_spell_search = True
                    self.cleaned_data['q'] = self.suggested_query
                elif has_filter and self.cleaned_data['q']:
                    # If there are 0 results with a search term, and filters applied
                    # lets be nice and remove the filters and try again.
                    # There will be a big message on the search page that says what we did.
                    for f in self.filters:
                        self.cleaned_data[f] = None
                    self.auto_broaden_search = True
                # Re run the query with the updated details
                sqs = self.search(repeat_search=True)
            # Only apply sorting on the first pass through
            sqs = self.apply_sorting(sqs)

        # Don't applying sorting on the facet as ElasticSearch2 doesn't like this.
        filters_to_facets = {
            'ra': 'registrationAuthorities',
            'models': 'facet_model_ct',
            'state': 'statuses',
        }

        # Add filters that are also facets to Search Query Set
        for _filter, facet in filters_to_facets.items():
            if _filter not in self.applied_filters:
                sqs = sqs.facet(facet)

        logged_in_facets = {
            'wg': 'workgroup',
            'res': 'restriction'
        }

        # If user is logged in, add permisssioned facets to Search Query Set
        if self.request.user.is_active:
            for _filter, facet in logged_in_facets.items():
                if _filter not in self.applied_filters:
                    # Don't do this: sqs = sqs.facet(facet, sort='count')
                    sqs = sqs.facet(facet)

        # For facets that will always appear on the sidebar, but are not part of the previous lists
        additional_hardcoded_facets = ['stewardship_organisation']
        for facet in additional_hardcoded_facets:
            sqs = sqs.facet(facet)

        # Generate details about facets from ``concepts`` registered (as facetable) with a Haystack search index that conforms
        # to Aristotle permissions. Excludes facets that have been previously been added.
        extra_facets = []
        from aristotle_mdr.search_indexes import registered_indexes
        for model_index in registered_indexes:
            for name, field in model_index.fields.items():
                if field.faceted:
                    if name not in (list(filters_to_facets.values()) + list(logged_in_facets.values()) +
                                    additional_hardcoded_facets):
                        extra_facets.append(name)

                        x = extra_facets_details.get(name, {})
                        x.update({
                            'title': getattr(field, 'title', name),
                            'display': getattr(field, 'display', None),
                            'allow_search': getattr(field, 'allow_search', False),
                        })
                        extra_facets_details[name]= x
                        # Don't do this: sqs = sqs.facet(facet, sort='count')  # Why Sam, why?
                        sqs = sqs.facet(name)

        # Generate facet content
        self.facets = sqs.facet_counts()

        # Populate the extra facet fields
        if 'fields' in self.facets:
            self.extra_facet_fields = [
                (k, {'values': sorted(v, key=lambda x: -x[1])[:10], 'details': extra_facets_details[k]})
                for k, v in self.facets['fields'].items()
                if k in extra_facets
            ]

            self.extra_facet_fields = [
                (k, {
                    'values': [
                        f for f in
                        sorted(v, key=lambda x: -x[1])
                        if f[0] not in extra_facets_details.get(k, {}).get('applied', [])
                        ][:10],
                    'details': extra_facets_details[k]
                })
                for k, v in self.facets['fields'].items()
                if k in extra_facets
            ]

            # Cut down to only the top 10 results for each facet, order by number of results
            for facet, counts in self.facets['fields'].items():
                self.facets['fields'][facet] = sorted(counts, key=lambda x: -x[1])[:10]

            # Perform id to object lookup
            from django.contrib.contenttypes.models import ContentType
            model_types = {
                'stewardship_organisation': MDR.StewardOrganisation,
                'registrationAuthorities': MDR.RegistrationAuthority,
                'workgroup': MDR.Workgroup,
                'facet_model_ct': ContentType,
            }

            for facet in self.facets['fields'].keys():
                if facet in model_types.keys():
                    id_to_item = {}
                    # Facet is for a model that must be looked up from the database
                    item_type = model_types.get(facet)
                    ids = []
                    for id, count in self.facets['fields'][facet]:
                        if id is not None:
                            ids.append(id)

                    id_to_instance = item_type.objects.in_bulk(ids)

                    for id, count in self.facets['fields'][facet]:
                        if id is None:
                            id_to_item[id] = (None, count)
                        # TODO: eradicate -99 from code
                        elif (id == -99):
                            id_to_item[id] = (None, count)

                        else:
                            id_to_item[id] = (id_to_instance[int(id)], count)
                    self.facets['fields'][facet] = id_to_item

        return sqs

    def check_spelling(self, sqs):
        if self.query_text:
            original_query = self.cleaned_data.get('q', "")

            from urllib.parse import quote_plus

            suggestions = []
            has_suggestions = False
            suggested_query = []

            # lets assume the words are ordered in importance
            # So we suggest words in order
            optimal_query = original_query
            for token in self.cleaned_data.get('q', "").split(" "):
                if token:  # remove blanks
                    suggestion = self.searchqueryset.spelling_suggestion(token)
                    if suggestion:
                        test_query = optimal_query.replace(token, suggestion)
                        # Haystack can *over correct* so we'll do a quick search with the
                        # suggested spelling to compare words against
                        try:
                            self.searchqueryset.auto_query(test_query)[0]
                            suggested_query.append(suggestion)
                            has_suggestions = True
                            optimal_query = test_query
                        except:
                            suggestion = None
                    else:
                        suggested_query.append(token)
                    suggestions.append((token, suggestion))

            if optimal_query != original_query:
                if optimal_query == original_query.lower():
                    # If the suggested query is the same query but in lowercase, don't suggest it
                    self.has_spelling_suggestions = False
                else:
                    self.spelling_suggestions = suggestions
                    self.has_spelling_suggestions = has_suggestions
                    self.original_query = self.cleaned_data.get('q')
                    self.suggested_query = quote_plus(' '.join(suggested_query), safe="")

    def apply_date_filtering(self, sqs):
        modify_quick_date = self.cleaned_data['mq']
        create_quick_date = self.cleaned_data['cq']
        create_date_start = self.cleaned_data['cds']
        create_date_end = self.cleaned_data['cde']
        modify_date_start = self.cleaned_data['mds']
        modify_date_end = self.cleaned_data['mde']

        """
        Modified filtering is really hard to do formal testing for as the modified
        dates are altered on save, so its impossible to alter the modified dates
        to check the search is working.
        However, this is the exact same process as creation date (which we can alter),
        so if creation filtering is working, modified filtering should work too.
        """
        if modify_quick_date and modify_quick_date is not QUICK_DATES.anytime:  # pragma: no cover
            delta = time_delta(modify_quick_date)
            if delta is not None:
                sqs = sqs.filter(modifed__gte=delta)
        elif modify_date_start or modify_date_end:  # pragma: no cover
            if modify_date_start:
                sqs = sqs.filter(modifed__gte=modify_date_start)
            if modify_date_end:
                sqs = sqs.filter(modifed__lte=modify_date_end)

        if create_quick_date and create_quick_date is not QUICK_DATES.anytime:
            delta = time_delta(create_quick_date)
            if delta is not None:
                sqs = sqs.filter(created__gte=delta)
        elif create_date_start or create_date_end:
            if create_date_start:
                sqs = sqs.filter(created__gte=create_date_start)
            if create_date_end:
                sqs = sqs.filter(created__lte=create_date_end)

        return sqs

    def apply_sorting(self, sqs):  # pragma: no cover, no security issues, standard Haystack methods, so already tested.

        sort_order = self.cleaned_data['sort']

        # Ordering is evaluated from left to right
        if sort_order == SORT_OPTIONS.modified_ascending:
            sqs = sqs.order_by('modified', 'name_sortable')

        elif sort_order == SORT_OPTIONS.modified_descending:
            sqs = sqs.order_by('-modified', 'name_sortable')

        elif sort_order == SORT_OPTIONS.created_ascending:
            sqs = sqs.order_by('created', 'name_sortable')

        elif sort_order == SORT_OPTIONS.created_descending:
            sqs = sqs.order_by('-created', 'name_sortable')

        elif sort_order == SORT_OPTIONS.alphabetical:
            sqs = sqs.order_by('name_sortable')

        elif sort_order == SORT_OPTIONS.state:
            sqs = sqs.order_by('-highest_state', 'name_sortable')

        return sqs
예제 #15
0
# Type choices presented when creating a custom field
type_choices = Choices(('int', 'Integer'), ('str', 'Text'),
                       ('html', 'Rich Text'), ('date', 'Date'),
                       ('enum', 'Choice'))

# Form field used when creating a custom value of the specified type
type_field_mapping = {
    'int': {
        'field': forms.IntegerField,
    },
    'str': {
        'field': forms.CharField,
        'args': {
            'widget': forms.widgets.Textarea
        }
    },
    'date': {
        'field': forms.DateField,
        'args': {
            'widget': BootstrapDateTimePicker(options={'format': 'YYYY-MM-DD'})
        }
    },
    'html': {
        'field': RichTextFormField
    },
    'enum': {
        'field': forms.ChoiceField
    }
}
예제 #16
0
class PermissionSearchForm(TokenSearchForm):
    """
        We need to make a new form as permissions to view objects are a bit finicky.
        This form allows us to perform the base query then restrict it to just those
        of interest.

        TODO: This might not scale well, so it may need to be looked at in production.
    """
    mq=forms.ChoiceField(
        required=False,
        initial=QUICK_DATES.anytime,
        choices=QUICK_DATES,
        widget=BootstrapDropdownIntelligentDate
    )
    mds = forms.DateField(
        required=False,
        label="Modified after date",
        widget=BootstrapDateTimePicker(options=datePickerOptions)
    )
    mde = forms.DateField(
        required=False,
        label="Modified before date",
        widget=BootstrapDateTimePicker(options=datePickerOptions)
    )
    cq=forms.ChoiceField(
        required=False,
        initial=QUICK_DATES.anytime,
        choices=QUICK_DATES,
        widget=BootstrapDropdownIntelligentDate
    )
    cds = forms.DateField(
        required=False,
        label="Created after date",
        widget=BootstrapDateTimePicker(options=datePickerOptions)
    )
    cde = forms.DateField(
        required=False,
        label="Created before date",
        widget=BootstrapDateTimePicker(options=datePickerOptions)
    )

    # Use short singular names
    # ras = [(ra.id, ra.name) for ra in MDR.RegistrationAuthority.objects.all()]
    ra = forms.MultipleChoiceField(
        required=False, label=_("Registration authority"),
        choices=[], widget=BootstrapDropdownSelectMultiple
    )

    sort = forms.ChoiceField(
        required=False, initial=SORT_OPTIONS.natural,
        choices=SORT_OPTIONS, widget=BootstrapDropdownSelect
    )
    from aristotle_mdr.search_indexes import BASE_RESTRICTION
    res = forms.ChoiceField(
        required=False, initial=None,
        choices=BASE_RESTRICTION.items(),
        label="Item visibility state"
    )

    state = forms.MultipleChoiceField(
        required=False,
        label=_("Registration status"),
        choices=MDR.STATES + [(-99, _('Unregistered'))],  # Allow unregistered as a selection
        widget=BootstrapDropdownSelectMultiple
    )
    public_only = forms.BooleanField(
        required=False,
        label="Only show public items"
    )
    myWorkgroups_only = forms.BooleanField(
        required=False,
        label="Only show items in my workgroups"
    )
    models = forms.MultipleChoiceField(
        choices=[],  # model_choices(),
        required=False, label=_('Item type'),
        widget=BootstrapDropdownSelectMultiple
    )
    rpp = forms.IntegerField(
        required=False,
        label='Results per page'
    )
    # F for facet!
    # searchqueryset = PermissionSearchQuerySet

    def __init__(self, *args, **kwargs):
        if 'searchqueryset' not in kwargs.keys() or kwargs['searchqueryset'] is None:
            kwargs['searchqueryset'] = get_permission_sqs()
        if not issubclass(type(kwargs['searchqueryset']), PermissionSearchQuerySet):
            raise ImproperlyConfigured("Aristotle Search Queryset connection must be a subclass of PermissionSearchQuerySet")
        super().__init__(*args, **kwargs)

        # Show visible workgroups ordered by active state and name
        # Inactive last
        self.fields['ra'].choices = [(ra.id, ra.name) for ra in MDR.RegistrationAuthority.objects.filter(active__in=[0, 1]).order_by('active', 'name')]

        self.fields['models'].choices = [
            m for m in model_choices()
            if m[0].split('.', 1)[0] in fetch_metadata_apps() + ['aristotle_mdr_help']
        ]

    def get_models(self):
        """Return an alphabetical list of model classes in the index."""
        search_models = []

        if self.is_valid() and self.cleaned_data['models']:
            for model in self.cleaned_data['models']:
                search_models.append(apps.get_model(*model.split('.')))

        return search_models

    filters = "models mq cq cds cde mds mde state ra res".split()

    @property
    def applied_filters(self):
        if not hasattr(self, 'cleaned_data'):
            return []
        return [f for f in self.filters if self.cleaned_data.get(f, False)]

    def search(self, repeat_search=False):
        # First, store the SearchQuerySet received from other processing.
        sqs = super().search()
        if not self.token_models and self.get_models():
            sqs = sqs.models(*self.get_models())
        self.repeat_search = repeat_search

        has_filter = self.kwargs or self.token_models or self.applied_filters
        if not has_filter and not self.query_text:
            return self.no_query_found()

        if self.applied_filters and not self.query_text:  # and not self.kwargs:
            # If there is a filter, but no query then we'll force some results.
            sqs = self.searchqueryset.order_by('-modified')
            self.filter_search = True
            self.attempted_filter_search = True

        states = self.cleaned_data.get('state', None)
        ras = self.cleaned_data.get('ra', None)
        restriction = self.cleaned_data['res']
        sqs = sqs.apply_registration_status_filters(states, ras)

        if restriction:
            sqs = sqs.filter(restriction=restriction)

        sqs = self.apply_date_filtering(sqs)
        sqs = sqs.apply_permission_checks(
            user=self.request.user,
            public_only=self.cleaned_data['public_only'],
            user_workgroups_only=self.cleaned_data['myWorkgroups_only']
        )

        extra_facets_details = {}
        facets_opts = self.request.GET.getlist('f', [])

        for _facet in facets_opts:
            _facet, value = _facet.split("::", 1)
            # Force exact as otherwise we don't match when there are spaces.
            sqs = sqs.filter(**{"%s__exact" % _facet: value})
            facets_details = extra_facets_details.get(_facet, {'applied': []})
            facets_details['applied'] = list(set(facets_details['applied'] + [value]))
            extra_facets_details[_facet] = facets_details

        self.has_spelling_suggestions = False
        if not self.repeat_search:

            if sqs.count() < 5:
                self.check_spelling(sqs)

            if sqs.count() == 0:
                if sqs.count() == 0 and self.has_spelling_suggestions:
                    self.auto_correct_spell_search = True
                    self.cleaned_data['q'] = self.suggested_query
                elif has_filter and self.cleaned_data['q']:
                    # If there are 0 results with a search term, and filters applied
                    # lets be nice and remove the filters and try again.
                    # There will be a big message on the search page that says what we did.
                    for f in self.filters:
                        self.cleaned_data[f] = None
                    self.auto_broaden_search = True
                # Re run the query with the updated details
                sqs = self.search(repeat_search=True)
            # Only apply sorting on the first pass through
            sqs = self.apply_sorting(sqs)

        # Don't applying sorting on the facet as ElasticSearch2 doesn't like this.
        filters_to_facets = {
            'ra': 'registrationAuthorities',
            'models': 'facet_model_ct',
            'state': 'statuses',
        }
        for _filter, facet in filters_to_facets.items():
            if _filter not in self.applied_filters:
                # Don't do this: sqs = sqs.facet(facet, sort='count')
                sqs = sqs.facet(facet)

        logged_in_facets = {
            'wg': 'workgroup',
            'res': 'restriction'
        }
        if self.request.user.is_active:
            for _filter, facet in logged_in_facets.items():
                if _filter not in self.applied_filters:
                    # Don't do this: sqs = sqs.facet(facet, sort='count')
                    sqs = sqs.facet(facet)

        extra_facets = []

        from aristotle_mdr.search_indexes import registered_indexes
        for model_index in registered_indexes:
            for name, field in model_index.fields.items():
                if field.faceted:
                    if name not in (list(filters_to_facets.values()) + list(logged_in_facets.values())):
                        extra_facets.append(name)

                        x = extra_facets_details.get(name, {})
                        x.update(**{
                            'title': getattr(field, 'title', name),
                            'display': getattr(field, 'display', None),
                            'allow_search': getattr(field, 'allow_search', False),
                        })
                        extra_facets_details[name]= x
                        # Don't do this: sqs = sqs.facet(facet, sort='count')  # Why Sam, why?
                        sqs = sqs.facet(name)

        self.facets = sqs.facet_counts()

        if 'fields' in self.facets:
            self.extra_facet_fields = [
                (k, {'values': sorted(v, key=lambda x: -x[1])[:10], 'details': extra_facets_details[k]})
                for k, v in self.facets['fields'].items()
                if k in extra_facets
            ]

            self.extra_facet_fields = [
                (k, {
                    'values': [
                        f for f in
                        sorted(v, key=lambda x: -x[1])
                        if f[0] not in extra_facets_details.get(k, {}).get('applied', [])
                        ][:10],
                    'details': extra_facets_details[k]
                })
                for k, v in self.facets['fields'].items()
                if k in extra_facets
            ]

            for facet, counts in self.facets['fields'].items():
                # Return the 5 top results for each facet in order of number of results.
                self.facets['fields'][facet] = sorted(counts, key=lambda x: -x[1])[:10]

        return sqs

    def check_spelling(self, sqs):
        if self.query_text:
            original_query = self.cleaned_data.get('q', "")

            from urllib.parse import quote_plus

            suggestions = []
            has_suggestions = False
            suggested_query = []

            # lets assume the words are ordered in importance
            # So we suggest words in order
            optimal_query = original_query
            for token in self.cleaned_data.get('q', "").split(" "):
                if token:  # remove blanks
                    suggestion = self.searchqueryset.spelling_suggestion(token)
                    if suggestion:
                        test_query = optimal_query.replace(token, suggestion)
                        # Haystack can *over correct* so we'll do a quick search with the
                        # suggested spelling to compare words against
                        try:
                            self.searchqueryset.auto_query(test_query)[0]
                            suggested_query.append(suggestion)
                            has_suggestions = True
                            optimal_query = test_query
                        except:
                            suggestion = None
                    else:
                        suggested_query.append(token)
                    suggestions.append((token, suggestion))
            if optimal_query != original_query:
                self.spelling_suggestions = suggestions
                self.has_spelling_suggestions = has_suggestions
                self.original_query = self.cleaned_data.get('q')
                self.suggested_query = quote_plus(' '.join(suggested_query), safe="")

    def apply_date_filtering(self, sqs):
        modify_quick_date = self.cleaned_data['mq']
        create_quick_date = self.cleaned_data['cq']
        create_date_start = self.cleaned_data['cds']
        create_date_end = self.cleaned_data['cde']
        modify_date_start = self.cleaned_data['mds']
        modify_date_end = self.cleaned_data['mde']

        """
        Modified filtering is really hard to do formal testing for as the modified
        dates are altered on save, so its impossible to alter the modified dates
        to check the search is working.
        However, this is the exact same process as creation date (which we can alter),
        so if creation filtering is working, modified filtering should work too.
        """
        if modify_quick_date and modify_quick_date is not QUICK_DATES.anytime:  # pragma: no cover
            delta = time_delta(modify_quick_date)
            if delta is not None:
                sqs = sqs.filter(modifed__gte=delta)
        elif modify_date_start or modify_date_end:  # pragma: no cover
            if modify_date_start:
                sqs = sqs.filter(modifed__gte=modify_date_start)
            if modify_date_end:
                sqs = sqs.filter(modifed__lte=modify_date_end)

        if create_quick_date and create_quick_date is not QUICK_DATES.anytime:
            delta = time_delta(create_quick_date)
            if delta is not None:
                sqs = sqs.filter(created__gte=delta)
        elif create_date_start or create_date_end:
            if create_date_start:
                sqs = sqs.filter(created__gte=create_date_start)
            if create_date_end:
                sqs = sqs.filter(created__lte=create_date_end)

        return sqs

    def apply_sorting(self, sqs):  # pragma: no cover, no security issues, standard Haystack methods, so already tested.
        sort_order = self.cleaned_data['sort']
        if sort_order == SORT_OPTIONS.modified_ascending:
            sqs = sqs.order_by('-modified', 'name_sortable')
        elif sort_order == SORT_OPTIONS.modified_descending:
            sqs = sqs.order_by('modified', '-name_sortable')
        elif sort_order == SORT_OPTIONS.created_ascending:
            sqs = sqs.order_by('-created', 'name_sortable')
        elif sort_order == SORT_OPTIONS.created_descending:
            sqs = sqs.order_by('created', '-name_sortable')
        elif sort_order == SORT_OPTIONS.alphabetical:
            sqs = sqs.order_by('name_sortable')
        elif sort_order == SORT_OPTIONS.state:
            sqs = sqs.order_by('-highest_state', 'name_sortable')

        return sqs
    def __init__(self, *args, **kwargs):
        from comet.managers import FrameworkDimensionQuerySet
        super().__init__(*args, **kwargs)

        if 'aristotle_mdr_backwards' not in fetch_aristotle_settings().get(
                'CONTENT_EXTENSIONS', []):
            bc_fields = self._meta.model.backwards_compatible_fields
            for fname in bc_fields:
                if fname in self.fields:
                    del self.fields[fname]

        for f in self.fields:
            # Add workgroup
            if f == "workgroup":
                self.fields[f].widget = widgets.WorkgroupAutocompleteSelect()
                self.fields[f].widget.choices = self.fields[f].choices
                if not self.user.is_superuser:
                    self.fields[
                        'workgroup'].queryset = self.user.profile.editable_workgroups

            # Add foreign keys and m2m key widgets
            elif hasattr(self.fields[f], 'queryset') and type(
                    self.fields[f].queryset) == ConceptQuerySet:
                if hasattr(self.fields[f].queryset, 'visible'):
                    if f in [
                            m2m.name
                            for m2m in self._meta.model._meta.many_to_many
                    ]:
                        field_widget = widgets.ConceptAutocompleteSelectMultiple
                    else:
                        field_widget = widgets.ConceptAutocompleteSelect
                    self.fields[f].queryset = self.fields[f].queryset.all(
                    ).visible(self.user)
                    self.fields[f].widget = field_widget(
                        model=self.fields[f].queryset.model)
                    self.fields[f].widget.choices = self.fields[f].choices

            elif hasattr(self.fields[f], 'queryset') and type(
                    self.fields[f].queryset) == FrameworkDimensionQuerySet:
                if f in [
                        m2m.name for m2m in self._meta.model._meta.many_to_many
                ]:
                    field_widget = widgets.FrameworkDimensionAutocompleteSelectMultiple
                    self.fields[f].widget = field_widget(
                        model=self.fields[f].queryset.model)
                    self.fields[f].queryset = self.fields[f].queryset.all()

            # Add date field
            elif type(self.fields[f]) == forms.fields.DateField:
                self.fields[f].widget = BootstrapDateTimePicker(
                    options={"format": "YYYY-MM-DD"})
            elif type(self.fields[f]) == forms.fields.DateTimeField:
                self.fields[f].widget = BootstrapDateTimePicker(
                    options={"format": "YYYY-MM-DD"})

        # Add the name suggestion button
        aristotle_settings = fetch_aristotle_settings()
        self.fields['name'].widget = NameSuggestInput(
            name_suggest_fields=self._meta.model.name_suggest_fields,
            separator=aristotle_settings['SEPARATORS'].get(
                self._meta.model.__name__, '-'))